mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-06 17:57:42 +02:00
Edit and remove avatar (#1218)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"Close": "Close",
|
||||
"NotSelected": "Not selected",
|
||||
"Deselect": "Deselect",
|
||||
"AddSocialLinks": "Add social links"
|
||||
"AddSocialLinks": "Add social links",
|
||||
"Change": "Change",
|
||||
"Remove": "Remove"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user