summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-10-12 14:23:53 +0200
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-10-20 09:02:22 +0200
commitfefe82209461c5a78b239935d64590a1c83b3e9c (patch)
tree0361c2b2db9fc17a025d5d08d7d6da5e6bdd8a1a /src
parent5c2eb89b0fe280eaf819b9690cf15146a81d8e0a (diff)
Fix avatar display on read-only contacts and use global Avatar component
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/components/ContactDetails/ContactDetailsAvatar.vue42
-rw-r--r--src/components/ContactsList/ContactsListItem.vue59
-rw-r--r--src/models/contact.js24
3 files changed, 60 insertions, 65 deletions
diff --git a/src/components/ContactDetails/ContactDetailsAvatar.vue b/src/components/ContactDetails/ContactDetailsAvatar.vue
index cd5a71b1..aa8af0e8 100644
--- a/src/components/ContactDetails/ContactDetailsAvatar.vue
+++ b/src/components/ContactDetails/ContactDetailsAvatar.vue
@@ -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"
@@ -90,20 +98,18 @@
@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"
:icon="'icon-' + network.toLowerCase()"
@@ -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"
@@ -126,6 +133,7 @@
{{ t('contacts', 'Delete picture') }}
</ActionButton>
</template>
+
<img ref="img"
:src="contact.photoUrl"
class="contact-header-modal__photo">
@@ -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'
@@ -153,10 +162,11 @@ export default {
name: 'ContactDetailsAvatar',
components: {
- Modal,
- Actions,
ActionButton,
ActionLink,
+ Actions,
+ Avatar,
+ Modal,
},
props: {
diff --git a/src/components/ContactsList/ContactsListItem.vue b/src/components/ContactsList/ContactsListItem.vue
index fef0a9ca..14efc25b 100644
--- a/src/components/ContactsList/ContactsListItem.vue
+++ b/src/components/ContactsList/ContactsListItem.vue
@@ -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">
@@ -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,
@@ -47,6 +50,7 @@ export default {
required: true,
},
},
+
computed: {
selectedGroup() {
return this.$route.params.selectedGroup
@@ -60,37 +64,14 @@ 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}`
+ }
+ if (this.contact.url) {
+ return `${this.contact.url}?photo`
}
- return `url(${this.contact.url}?photo)`
+ return undefined
},
},
methods: {
diff --git a/src/models/contact.js b/src/models/contact.js
index 70802626..3b3ace90 100644
--- a/src/models/contact.js
+++ b/src/models/contact.js
@@ -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'
@@ -225,6 +226,7 @@ export default class Contact {
/**
* Return the photo usable url
+ * We cannot fetch external url because of csp policies
*
* @readonly
* @memberof Contact
@@ -232,22 +234,24 @@ export default class 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
}
}