Skip to content

Commit

Permalink
Fix avatar display on read-only contacts and use global Avatar component
Browse files Browse the repository at this point in the history
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Oct 19, 2020
1 parent 5c2eb89 commit 3bfeb45
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 65 deletions.
33 changes: 33 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@nextcloud/router": "^1.2.0",
"@nextcloud/vue": "2.7.0",
"axios": "^0.20.0",
"b64-to-blob": "^1.2.19",
"cdav-library": "git+https://github.com/nextcloud/cdav-library.git",
"core-js": "^3.6.5",
"debounce": "^1.2.0",
Expand Down
42 changes: 26 additions & 16 deletions src/components/ContactDetails/ContactDetailsAvatar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,20 @@
:style="{ 'backgroundImage': `url(${contact.photoUrl})` }"
class="contact-header-avatar__photo"
@click="toggleModal" />
<Avatar v-else
:disable-tooltip="true"
:display-name="contact.displayName"
:is-no-user="true"
:size="75"
class="contact-header-avatar__photo" />

<!-- attention, this menu exists twice in this file -->
<Actions
default-icon="icon-picture-force-white"
v-if="!isReadOnly || contact.photo"
:force-menu="true"
:open.sync="opened"
class="contact-header-avatar__menu">
class="contact-header-avatar__menu"
default-icon="icon-picture-force-white">
<template v-if="!isReadOnly">
<ActionButton
icon="icon-upload"
Expand Down Expand Up @@ -90,19 +98,17 @@
@close="toggleModal">
<!-- attention, this menu exists twice in this file -->
<template #actions>
<ActionButton
v-if="!isReadOnly"
icon="icon-upload"
@click="selectFileInput">
{{ t('contacts', 'Upload a new picture') }}
</ActionButton>
<ActionButton
v-if="!isReadOnly"
icon="icon-folder"
@click="selectFilePicker">
{{ t('contacts', 'Choose from files') }}
</ActionButton>
<template v-if="!isReadOnly">
<ActionButton
icon="icon-upload"
@click="selectFileInput">
{{ t('contacts', 'Upload a new picture') }}
</ActionButton>
<ActionButton
icon="icon-folder"
@click="selectFilePicker">
{{ t('contacts', 'Choose from files') }}
</ActionButton>
<ActionButton
v-for="network in supportedSocial"
:key="network"
Expand All @@ -111,6 +117,7 @@
{{ t('contacts', 'Get from ' + network) }}
</ActionButton>
</template>

<!-- FIXME: the link seems to have a bigger font size than the button caption -->
<ActionLink
v-if="contact.photo"
Expand All @@ -126,6 +133,7 @@
{{ t('contacts', 'Delete picture') }}
</ActionButton>
</template>

<img ref="img"
:src="contact.photoUrl"
class="contact-header-modal__photo">
Expand All @@ -134,6 +142,7 @@
</template>

<script>
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import Modal from '@nextcloud/vue/dist/Components/Modal'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
Expand All @@ -153,10 +162,11 @@ export default {
name: 'ContactDetailsAvatar',

components: {
Modal,
Actions,
ActionButton,
ActionLink,
Actions,
Avatar,
Modal,
},

props: {
Expand Down
56 changes: 17 additions & 39 deletions src/components/ContactsList/ContactsListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
class="app-content-list-item-checkbox checkbox" @keypress.enter.space.prevent.stop="toggleSelect">
<label :for="contact.key" @click.prevent.stop="toggleSelect" @keypress.enter.space.prevent.stop="toggleSelect" />
-->
<div :style="avatarStyle" class="app-content-list-item-icon">
<!-- try to fetch the avatar only if the contact exists on the server -->
<div v-if="hasPhoto" :style="{ 'backgroundImage': avatarUrl }" class="app-content-list-item-icon__avatar" />
<template v-else>
{{ contact.displayName | firstLetter }}
</template>
</div>
<Avatar
:disable-menu="true"
:disable-tooltip="true"
:display-name="contact.displayName"
:is-no-user="true"
:size="40"
:url="avatarUrl"
class="app-content-list-item-icon" />

<!-- contact data -->
<div class="app-content-list-item-line-one">
Expand All @@ -30,13 +31,15 @@
</template>

<script>
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
export default {
name: 'ContactsListItem',
filters: {
firstLetter(value) {
return value.charAt(0)
},
components: {
Avatar,
},
props: {
index: {
type: Number,
Expand All @@ -47,6 +50,7 @@ export default {
required: true,
},
},
computed: {
selectedGroup() {
return this.$route.params.selectedGroup
Expand All @@ -60,37 +64,11 @@ export default {
return window.btoa(this.contact.key).slice(0, -2)
},
hasPhoto() {
return this.contact.dav && (this.contact.dav.hasphoto || this.contact.photo)
},
avatarStyle() {
if (this.hasPhoto) {
return {
backgroundColor: '#fff',
// The contact photo gets cropped in a circular shape, which might look odd with transparent photos.
// This box shadow ensures that there's always a very faint edge hinting at the circle.
boxShadow: '0 0 5px rgba(0, 0, 0, 0.05) inset',
}
}
try {
const color = this.contact.uid.toRgb()
return {
backgroundColor: `rgb(${color.r}, ${color.g}, ${color.b})`,
}
} catch (e) {
return {
backgroundColor: 'grey',
}
}
},
avatarUrl() {
if (this.contact.photo) {
return `url(${this.contact.photoUrl})`
return `${this.contact.photoUrl}`
}
return `url(${this.contact.url}?photo)`
return `${this.contact.url}?photo`
},
},
methods: {
Expand Down
24 changes: 14 additions & 10 deletions src/models/contact.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import { v4 as uuid } from 'uuid'
import ICAL from 'ical.js'
import b64toBlob from 'b64-to-blob'

import store from '../store'
import updateDesignSet from '../services/updateDesignSet'
Expand Down Expand Up @@ -225,29 +226,32 @@ export default class Contact {

/**
* Return the photo usable url
* We cannot fetch external url because of csp policies
*
* @readonly
* @memberof Contact
*/
get photoUrl() {
const photo = this.vCard.getFirstProperty('photo')
const encoding = photo.getFirstParameter('encoding')
const type = photo.getFirstParameter('type')
let photoType = photo.getFirstParameter('type')
let photoB64 = this.photo

const isBinary = photo.type === 'binary' || encoding === 'b'

if (photo && !this.photo.startsWith('data') && isBinary) {
// split on coma in case of any leftover base64 data and retrieve last part
// usually we come to this part when the base64 image type is unknown
return `data:image/${type};base64,${this.photo.split(',').pop()}`
if (photo && photoB64.startsWith('data') && !isBinary) {
// get the last part = base64
photoB64 = photoB64.split(',').pop()
// 'data:image/png' => 'png'
photoType = photoB64.split(';')[0].split('/')
}
// could be just an url of the already encoded `data:image...`

try {
// eslint-disable-next-line no-new
new URL(this.photo)
return this.photo
// Create blob from url
const blob = b64toBlob(photoB64, `image/${photoType}`)
return URL.createObjectURL(blob)
} catch {
console.error('Invalid photo for the following contact. Ignoring...', this.contact)
console.error('Invalid photo for the following contact. Ignoring...', this.contact, { photoB64, photoType })
return false
}
}
Expand Down

0 comments on commit 3bfeb45

Please sign in to comment.