Edit and remove avatar (#1218)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov
2022-03-28 22:25:39 +07:00
committed by GitHub
parent 80185a2e4e
commit f003dcd5a0
15 changed files with 131 additions and 35 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ export class TContact extends TDoc implements Contact {
@Index(IndexKind.FullText)
name!: string
avatar?: string
avatar?: string | null
@Prop(Collection(contact.class.Channel), contact.string.ContactInfo)
channels?: number
+3 -1
View File
@@ -8,6 +8,8 @@
"Close": "Close",
"NotSelected": "Not selected",
"Deselect": "Deselect",
"AddSocialLinks": "Add social links"
"AddSocialLinks": "Add social links",
"Change": "Change",
"Remove": "Remove"
}
}
+3 -1
View File
@@ -8,6 +8,8 @@
"Close": "Закрыть",
"NotSelected": "Не выбрано",
"Deselect": "Снять выделение",
"AddSocialLinks": "Добавить контактную информацию"
"AddSocialLinks": "Добавить контактную информацию",
"Change": "Изменить",
"Remove": "Удалить"
}
}
@@ -18,7 +18,7 @@
import { getBlobURL, getFileUrl } from '../utils'
export let avatar: string | undefined = undefined
export let avatar: string | null | undefined = undefined
export let direct: Blob | undefined = undefined
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
@@ -27,7 +27,7 @@
getBlobURL(direct).then((blobURL) => {
url = blobURL
})
} else if (avatar !== undefined) {
} else if (avatar !== undefined && avatar !== null) {
url = getFileUrl(avatar)
} else {
url = undefined
@@ -19,27 +19,57 @@
import imageCropper from '@anticrm/image-cropper'
import plugin from '../plugin'
export let file: File
export let file: Blob
let inputRef: HTMLInputElement
const targetMimes = ['image/png', 'image/jpg', 'image/jpeg']
const dispatch = createEventDispatcher()
const CropperP = getResource(imageCropper.component.Cropper)
let cropper: any
function onSelect (e: any) {
const newFile = e.target?.files[0] as File | undefined
if (newFile === undefined || !targetMimes.includes(newFile.type)) {
return
}
file = newFile
e.target.value = null
}
async function onCrop () {
const res = await cropper.crop()
dispatch('close', res)
}
async function remove () {
dispatch('close', null)
}
function selectAnother () {
inputRef.click()
}
</script>
<input style="display: none;" type="file" bind:this={inputRef} on:change={onSelect} accept={targetMimes.join(',')}/>
<div class="overlay" on:click={() => { dispatch('close') }} />
<div class="root">
{#await CropperP then Cropper}
<div class="cropper">
<Cropper bind:this={cropper} image={file} />
</div>
<div class="footer ml-6 mr-6 mt-4 mb-4">
<Button label={plugin.string.Save} primary on:click={onCrop} />
<div class="footer">
<div>
<Button label={plugin.string.Save} primary on:click={onCrop} />
</div>
<div class="ml-4 mr-4">
<Button label={plugin.string.Change} on:click={selectAnother} />
</div>
<div>
<Button label={plugin.string.Remove} on:click={remove} />
</div>
</div>
{/await}
</div>
@@ -85,5 +115,6 @@
.footer {
display: flex;
flex-direction: row-reverse;
padding: 1rem 1.5rem;
}
</style>
@@ -15,13 +15,13 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { showPopup } from '@anticrm/ui'
import Avatar from './Avatar.svelte'
import EditAvatarPopup from './EditAvatarPopup.svelte'
import { getFileUrl } from '../utils'
export let avatar: string | undefined = undefined
export let avatar: string | null | undefined = undefined
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'
export let direct: Blob | undefined = undefined
@@ -29,8 +29,28 @@
let inputRef: HTMLInputElement
const targetMimes = ['image/png', 'image/jpg', 'image/jpeg']
function onClick () {
inputRef.click()
async function onClick () {
let file: Blob
if (direct !== undefined) {
file = direct
} else if (avatar != null) {
const url = getFileUrl(avatar)
file = await (await fetch(url)).blob()
} else {
return inputRef.click()
}
showPopup(EditAvatarPopup, { file }, undefined, (blob) => {
if (blob === undefined) {
return
}
if (blob === null) {
direct = undefined
dispatch('remove')
} else {
direct = blob
dispatch('done', { file: new File([blob], 'avatar') })
}
})
}
@@ -44,9 +64,13 @@
if (blob === undefined) {
return
}
direct = blob
dispatch('done', { file: new File([blob], file.name) })
if (blob === null) {
direct = undefined
dispatch('remove')
} else {
direct = blob
dispatch('done', { file: new File([blob], file.name) })
}
})
e.target.value = null
}
+3 -1
View File
@@ -37,6 +37,8 @@ export default plugin(presentationId, {
Close: '' as IntlString,
NotSelected: '' as IntlString,
Deselect: '' as IntlString,
AddSocialLinks: '' as IntlString
AddSocialLinks: '' as IntlString,
Change: '' as IntlString,
Remove: '' as IntlString
}
})
@@ -50,14 +50,19 @@
avatar = file
}
async function createPerson () {
const uploadFile = await getResource(attachment.helper.UploadFile)
const avatarProp = avatar !== undefined ? { avatar: await uploadFile(avatar) } : {}
function removeAvatar (): void {
avatar = undefined
}
async function createPerson () {
const person: Data<Person> = {
name: combineName(firstName, lastName),
city: object.city,
...avatarProp
city: object.city
}
if (avatar !== undefined) {
const uploadFile = await getResource(attachment.helper.UploadFile)
person.avatar = await uploadFile(avatar)
}
await client.createDoc(contact.class.Person, contact.space.Contacts, person, id)
@@ -101,7 +106,7 @@
{/if}
<div class="flex-row-center">
<div class="mr-4">
<EditableAvatar avatar={object.avatar} size={'large'} on:done={onAvatarDone} />
<EditableAvatar avatar={object.avatar} size={'large'} on:done={onAvatarDone} on:remove={removeAvatar} />
</div>
<div class="flex-col">
<div class="fs-title">
@@ -79,13 +79,23 @@
avatar: uuid
})
}
async function removeAvatar (): Promise<void> {
const deleteFile = await getResource(attachment.helper.DeleteFile)
if (object.avatar != null) {
await client.updateDoc(object._class, object.space, object._id, {
avatar: null
})
await deleteFile(object.avatar)
}
}
</script>
{#if object !== undefined}
<div class="flex-row-streach flex-grow">
<div class="mr-8">
{#if editable}
<EditableAvatar avatar={object.avatar} size={'x-large'} on:done={onAvatarDone} />
<EditableAvatar avatar={object.avatar} size={'x-large'} on:done={onAvatarDone} on:remove={removeAvatar} />
{:else}
<Avatar avatar={object.avatar} size={'x-large'} />
{/if}
@@ -34,7 +34,7 @@
return {
_id: org._id,
label: org.name,
image: org.avatar
image: org.avatar === null ? undefined : org.avatar
}
})
if (value !== undefined) {
@@ -39,7 +39,7 @@
return {
_id: org._id,
label: org.name,
image: org.avatar
image: org.avatar === null ? undefined : org.avatar
}
})
if (value !== undefined) {
+1 -1
View File
@@ -51,7 +51,7 @@ export interface Channel extends AttachedDoc {
*/
export interface Contact extends Doc {
name: string
avatar?: string
avatar?: string | null
attachments?: number
comments?: number
channels?: number
@@ -16,13 +16,13 @@
import Cropper from 'cropperjs'
import smartcrop from 'smartcrop'
export let image: File
export let image: Blob
export let cropSize = 1200
let imgRef: HTMLImageElement
let cropper: Cropper | undefined
async function init () {
async function init (image: Blob) {
const bitmap = await createImageBitmap(image)
const canvas = document.createElement('canvas')
canvas.height = bitmap.height
@@ -42,6 +42,10 @@ async function init () {
const initialArea = (await smartcrop.crop(canvas, { width: 100, height: 100 })).topCrop
if (cropper !== undefined) {
cropper.destroy()
cropper = undefined
}
const cropperInst = new Cropper(imgRef, {
aspectRatio: 1,
viewMode: 1,
@@ -82,7 +86,7 @@ export async function crop () {
<div class="w-full h-full flex">
<img class="image" bind:this={imgRef} alt="img"/>
{#await init()}
{#await init(image)}
Waiting...
{/await}
</div>
@@ -113,12 +113,13 @@
})
async function createCandidate () {
const uploadFile = await getResource(attachment.helper.UploadFile)
const avatarProp = avatar !== undefined ? { avatar: await uploadFile(avatar) } : {}
const candidate: Data<Person> = {
name: combineName(firstName, lastName),
city: object.city,
...avatarProp
city: object.city
}
if (avatar !== undefined) {
const uploadFile = await getResource(attachment.helper.UploadFile)
candidate.avatar = await uploadFile(avatar)
}
const candidateData: MixinData<Person, Candidate> = {
title: object.title,
@@ -381,6 +382,10 @@
$: findPerson(client, { ...object, name: combineName(firstName, lastName) }, channels).then((p) => {
matches = p
})
function removeAvatar (): void {
avatar = undefined
}
</script>
<Card
@@ -405,7 +410,7 @@
{/if}
<div class="flex-row-center">
<div class="mr-4">
<EditableAvatar bind:direct={avatar} avatar={object.avatar} size={'large'} on:done={onAvatarDone} />
<EditableAvatar bind:direct={avatar} avatar={object.avatar} size={'large'} on:remove={removeAvatar} on:done={onAvatarDone} />
</div>
<div class="flex-col">
<div class="fs-title">
@@ -63,6 +63,17 @@
avatar: uuid
})
}
async function removeAvatar (): Promise<void> {
if (employee === undefined) return
const deleteFile = await getResource(attachment.helper.DeleteFile)
if (employee.avatar != null) {
await client.updateDoc(employee._class, employee.space, employee._id, {
avatar: null
})
await deleteFile(employee.avatar)
}
}
</script>
<div class="antiComponent">
@@ -73,7 +84,7 @@
{#if employee}
<div class="ac-body columns p-10">
<div class="mr-8">
<EditableAvatar avatar={employee.avatar} size={'x-large'} on:done={onAvatarDone} />
<EditableAvatar avatar={employee.avatar} size={'x-large'} on:done={onAvatarDone} on:remove={removeAvatar} />
</div>
<div class="flex-grow flex-col">
<div class="flex-col">