mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 11:42:30 +03:00
UBERF-7011: Switch to Ref<Blob> (#5661)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
27a98e0641
commit
8c6a5f4e9d
@ -11,6 +11,9 @@ dependencies:
|
|||||||
'@aws-sdk/client-s3':
|
'@aws-sdk/client-s3':
|
||||||
specifier: ^3.575.0
|
specifier: ^3.575.0
|
||||||
version: 3.577.0
|
version: 3.577.0
|
||||||
|
'@aws-sdk/s3-request-presigner':
|
||||||
|
specifier: ^3.582.0
|
||||||
|
version: 3.582.0
|
||||||
'@elastic/elasticsearch':
|
'@elastic/elasticsearch':
|
||||||
specifier: ^7.14.0
|
specifier: ^7.14.0
|
||||||
version: 7.17.13
|
version: 7.17.13
|
||||||
@ -1857,6 +1860,21 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@aws-sdk/middleware-sdk-s3@3.582.0:
|
||||||
|
resolution: {integrity: sha512-PJqQpLoLaZPRI4L/XZUeHkd9UVK8VAr9R38wv0osGeMTvzD9iwzzk0I2TtBqFda/5xEB1YgVYZwyqvmStXmttg==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/types': 3.577.0
|
||||||
|
'@aws-sdk/util-arn-parser': 3.568.0
|
||||||
|
'@smithy/node-config-provider': 3.0.0
|
||||||
|
'@smithy/protocol-http': 4.0.0
|
||||||
|
'@smithy/signature-v4': 3.0.0
|
||||||
|
'@smithy/smithy-client': 3.0.1
|
||||||
|
'@smithy/types': 3.0.0
|
||||||
|
'@smithy/util-config-provider': 3.0.0
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@aws-sdk/middleware-signing@3.577.0:
|
/@aws-sdk/middleware-signing@3.577.0:
|
||||||
resolution: {integrity: sha512-QS/dh3+NqZbXtY0j/DZ867ogP413pG5cFGqBy9OeOhDMsolcwLrQbi0S0c621dc1QNq+er9ffaMhZ/aPkyXXIg==}
|
resolution: {integrity: sha512-QS/dh3+NqZbXtY0j/DZ867ogP413pG5cFGqBy9OeOhDMsolcwLrQbi0S0c621dc1QNq+er9ffaMhZ/aPkyXXIg==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
@ -1902,6 +1920,20 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@aws-sdk/s3-request-presigner@3.582.0:
|
||||||
|
resolution: {integrity: sha512-h2tn0IjJ3Tsnh0Ep8FUqYwAJIjursK68gegrWEUpf7oeJlJer5gaNlD5CXCeRHwyhNiA1uzHaX4BjjyeKHl0Kw==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/signature-v4-multi-region': 3.582.0
|
||||||
|
'@aws-sdk/types': 3.577.0
|
||||||
|
'@aws-sdk/util-format-url': 3.577.0
|
||||||
|
'@smithy/middleware-endpoint': 3.0.0
|
||||||
|
'@smithy/protocol-http': 4.0.0
|
||||||
|
'@smithy/smithy-client': 3.0.1
|
||||||
|
'@smithy/types': 3.0.0
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@aws-sdk/signature-v4-multi-region@3.577.0:
|
/@aws-sdk/signature-v4-multi-region@3.577.0:
|
||||||
resolution: {integrity: sha512-mMykGRFBYmlDcMhdbhNM0z1JFUaYYZ8r9WV7Dd0T2PWELv2brSAjDAOBHdJLHObDMYRnM6H0/Y974qTl3icEcQ==}
|
resolution: {integrity: sha512-mMykGRFBYmlDcMhdbhNM0z1JFUaYYZ8r9WV7Dd0T2PWELv2brSAjDAOBHdJLHObDMYRnM6H0/Y974qTl3icEcQ==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
@ -1914,6 +1946,18 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@aws-sdk/signature-v4-multi-region@3.582.0:
|
||||||
|
resolution: {integrity: sha512-aFCOjjNqEX2l+V8QjOWy5F7CtHIC/RlYdBuv3No6yxn+pMvVUUe6zdMk2yHWcudVpHWsyvcZzAUBliAPeFLPsQ==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/middleware-sdk-s3': 3.582.0
|
||||||
|
'@aws-sdk/types': 3.577.0
|
||||||
|
'@smithy/protocol-http': 4.0.0
|
||||||
|
'@smithy/signature-v4': 3.0.0
|
||||||
|
'@smithy/types': 3.0.0
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.577.0):
|
/@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.577.0):
|
||||||
resolution: {integrity: sha512-0CkIZpcC3DNQJQ1hDjm2bdSy/Xjs7Ny5YvSsacasGOkNfk+FdkiQy6N67bZX3Zbc9KIx+Nz4bu3iDeNSNplnnQ==}
|
resolution: {integrity: sha512-0CkIZpcC3DNQJQ1hDjm2bdSy/Xjs7Ny5YvSsacasGOkNfk+FdkiQy6N67bZX3Zbc9KIx+Nz4bu3iDeNSNplnnQ==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
@ -1953,6 +1997,16 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@aws-sdk/util-format-url@3.577.0:
|
||||||
|
resolution: {integrity: sha512-SyEGC2J+y/krFRuPgiF02FmMYhqbiIkOjDE6k4nYLJQRyS6XEAGxZoG+OHeOVEM+bsDgbxokXZiM3XKGu6qFIg==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/types': 3.577.0
|
||||||
|
'@smithy/querystring-builder': 3.0.0
|
||||||
|
'@smithy/types': 3.0.0
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@aws-sdk/util-locate-window@3.568.0:
|
/@aws-sdk/util-locate-window@3.568.0:
|
||||||
resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==}
|
resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
@ -19662,7 +19716,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/drive-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
file:projects/drive-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||||
resolution: {integrity: sha512-E2zO+OiX83Ig7B8ZJHiKJZydZf0RqPxXrHBnGl99l3biAcxNId4UJryMUM8KC7PQQw16ue/bERRU1dOBIs9Hng==, tarball: file:projects/drive-resources.tgz}
|
resolution: {integrity: sha512-BxbFyIUUuLwK5yzY3OTgPRmKE1fZxRUW9ZJ3iC/7nxvu5b32end9aJqm2fwZwNQZJxxeyw1AMbf9t7EDiadfMg==, tarball: file:projects/drive-resources.tgz}
|
||||||
id: file:projects/drive-resources.tgz
|
id: file:projects/drive-resources.tgz
|
||||||
name: '@rush-temp/drive-resources'
|
name: '@rush-temp/drive-resources'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
@ -20629,7 +20683,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/minio.tgz(esbuild@0.20.1)(ts-node@10.9.2):
|
file:projects/minio.tgz(esbuild@0.20.1)(ts-node@10.9.2):
|
||||||
resolution: {integrity: sha512-9iaGGSGj2mgvQwhe6oLTf5RzaROFLBsi+kU/nMGXxw3m7pvmELcgVW2Ijk4R+P/AHBZF5TDp0jWdFif8WWvwXg==, tarball: file:projects/minio.tgz}
|
resolution: {integrity: sha512-wjtXX+XX515IIjugAtzTrJN8TyMp3iPEgBlQBXvxMuOpIBEmLQLnuOOD4LN7S9sdKYLXOP0IgmDVwyDp0bZvyw==, tarball: file:projects/minio.tgz}
|
||||||
id: file:projects/minio.tgz
|
id: file:projects/minio.tgz
|
||||||
name: '@rush-temp/minio'
|
name: '@rush-temp/minio'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
@ -22817,12 +22871,13 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/s3.tgz(esbuild@0.20.1)(ts-node@10.9.2):
|
file:projects/s3.tgz(esbuild@0.20.1)(ts-node@10.9.2):
|
||||||
resolution: {integrity: sha512-2gkJlE5D6k8qqIbcjFSxW2ytwlziBej2ZJz7KRaEiAU5uDaMCiL378VaVvS1DmvPvPxmHJ7swZ5IkL/WRqxwqg==, tarball: file:projects/s3.tgz}
|
resolution: {integrity: sha512-545xE/hFO0K5PpSsSnM7hTqQilfo1Psno9mMs6XSYEC8SZQFX/mV1j0/W9l3++yX7lZQwxHKHBv/morA8v/RAA==, tarball: file:projects/s3.tgz}
|
||||||
id: file:projects/s3.tgz
|
id: file:projects/s3.tgz
|
||||||
name: '@rush-temp/s3'
|
name: '@rush-temp/s3'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/client-s3': 3.577.0
|
'@aws-sdk/client-s3': 3.577.0
|
||||||
|
'@aws-sdk/s3-request-presigner': 3.582.0
|
||||||
'@types/jest': 29.5.12
|
'@types/jest': 29.5.12
|
||||||
'@types/node': 20.11.19
|
'@types/node': 20.11.19
|
||||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
||||||
|
@ -17,42 +17,42 @@ import attachment from '@hcengineering/attachment'
|
|||||||
import chunter, { type ChatMessage } from '@hcengineering/chunter'
|
import chunter, { type ChatMessage } from '@hcengineering/chunter'
|
||||||
import contact from '@hcengineering/contact'
|
import contact from '@hcengineering/contact'
|
||||||
import core, {
|
import core, {
|
||||||
|
ClassifierKind,
|
||||||
|
DOMAIN_STATUS,
|
||||||
DOMAIN_TX,
|
DOMAIN_TX,
|
||||||
type MeasureContext,
|
|
||||||
SortingOrder,
|
SortingOrder,
|
||||||
TxOperations,
|
TxOperations,
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
generateId,
|
generateId,
|
||||||
getObjectValue,
|
getObjectValue,
|
||||||
|
toIdMap,
|
||||||
type BackupClient,
|
type BackupClient,
|
||||||
|
type Class,
|
||||||
type Client as CoreClient,
|
type Client as CoreClient,
|
||||||
type Doc,
|
type Doc,
|
||||||
type Domain,
|
type Domain,
|
||||||
|
type MeasureContext,
|
||||||
type Ref,
|
type Ref,
|
||||||
type TxCreateDoc,
|
|
||||||
type WorkspaceId,
|
|
||||||
type StatusCategory,
|
|
||||||
type TxMixin,
|
|
||||||
type TxCUD,
|
|
||||||
type TxUpdateDoc,
|
|
||||||
DOMAIN_STATUS,
|
|
||||||
type Status,
|
type Status,
|
||||||
toIdMap,
|
type StatusCategory,
|
||||||
type Class,
|
type TxCUD,
|
||||||
ClassifierKind
|
type TxCreateDoc,
|
||||||
|
type TxMixin,
|
||||||
|
type TxUpdateDoc,
|
||||||
|
type WorkspaceId
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
|
import { DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
|
||||||
|
import { DOMAIN_SPACE } from '@hcengineering/model-core'
|
||||||
|
import recruitModel, { defaultApplicantStatuses } from '@hcengineering/model-recruit'
|
||||||
import { getWorkspaceDB } from '@hcengineering/mongo'
|
import { getWorkspaceDB } from '@hcengineering/mongo'
|
||||||
import recruit, { type Applicant, type Vacancy } from '@hcengineering/recruit'
|
import recruit, { type Applicant, type Vacancy } from '@hcengineering/recruit'
|
||||||
import recruitModel, { defaultApplicantStatuses } from '@hcengineering/model-recruit'
|
|
||||||
import { type StorageAdapter } from '@hcengineering/server-core'
|
import { type StorageAdapter } from '@hcengineering/server-core'
|
||||||
import { connect } from '@hcengineering/server-tool'
|
import { connect } from '@hcengineering/server-tool'
|
||||||
import tags, { type TagCategory, type TagElement, type TagReference } from '@hcengineering/tags'
|
import tags, { type TagCategory, type TagElement, type TagReference } from '@hcengineering/tags'
|
||||||
import task, { type Task, type ProjectType, type TaskType } from '@hcengineering/task'
|
import task, { type ProjectType, type Task, type TaskType } from '@hcengineering/task'
|
||||||
import tracker from '@hcengineering/tracker'
|
import tracker from '@hcengineering/tracker'
|
||||||
import { deepEqual } from 'fast-equals'
|
import { deepEqual } from 'fast-equals'
|
||||||
import { MongoClient } from 'mongodb'
|
import { MongoClient } from 'mongodb'
|
||||||
import { DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
|
|
||||||
import { DOMAIN_SPACE } from '@hcengineering/model-core'
|
|
||||||
|
|
||||||
export async function cleanWorkspace (
|
export async function cleanWorkspace (
|
||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
@ -77,7 +77,7 @@ export async function cleanWorkspace (
|
|||||||
const contacts = await ops.findAll(contact.class.Contact, {})
|
const contacts = await ops.findAll(contact.class.Contact, {})
|
||||||
|
|
||||||
const files = new Set(
|
const files = new Set(
|
||||||
attachments.map((it) => it.file).concat(contacts.map((it) => it.avatar).filter((it) => it) as string[])
|
attachments.map((it) => it.file as string).concat(contacts.map((it) => it.avatar).filter((it) => it) as string[])
|
||||||
)
|
)
|
||||||
|
|
||||||
const minioList = await storageAdapter.listStream(ctx, workspaceId)
|
const minioList = await storageAdapter.listStream(ctx, workspaceId)
|
||||||
@ -177,7 +177,7 @@ export async function fixMinioBW (
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (obj.modifiedOn < from) continue
|
if (obj.modifiedOn < from) continue
|
||||||
if ((obj._id as string).includes('%size%')) {
|
if ((obj._id as string).includes('%preview%')) {
|
||||||
await storageService.remove(ctx, workspaceId, [obj._id])
|
await storageService.remove(ctx, workspaceId, [obj._id])
|
||||||
removed++
|
removed++
|
||||||
if (removed % 100 === 0) {
|
if (removed % 100 === 0) {
|
||||||
|
@ -15,18 +15,18 @@
|
|||||||
|
|
||||||
import activity from '@hcengineering/activity'
|
import activity from '@hcengineering/activity'
|
||||||
import type { Attachment, AttachmentMetadata, Photo, SavedAttachments } from '@hcengineering/attachment'
|
import type { Attachment, AttachmentMetadata, Photo, SavedAttachments } from '@hcengineering/attachment'
|
||||||
import { type Domain, IndexKind, type Ref } from '@hcengineering/core'
|
import { IndexKind, type Blob, type Domain, type Ref } from '@hcengineering/core'
|
||||||
import {
|
import {
|
||||||
type Builder,
|
|
||||||
Index,
|
Index,
|
||||||
Model,
|
Model,
|
||||||
Prop,
|
Prop,
|
||||||
TypeAttachment,
|
TypeBlob,
|
||||||
TypeBoolean,
|
TypeBoolean,
|
||||||
TypeRef,
|
TypeRef,
|
||||||
TypeString,
|
TypeString,
|
||||||
TypeTimestamp,
|
TypeTimestamp,
|
||||||
UX
|
UX,
|
||||||
|
type Builder
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import core, { TAttachedDoc } from '@hcengineering/model-core'
|
import core, { TAttachedDoc } from '@hcengineering/model-core'
|
||||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||||
@ -46,8 +46,8 @@ export class TAttachment extends TAttachedDoc implements Attachment {
|
|||||||
@Index(IndexKind.FullText)
|
@Index(IndexKind.FullText)
|
||||||
name!: string
|
name!: string
|
||||||
|
|
||||||
@Prop(TypeAttachment(), attachment.string.File)
|
@Prop(TypeBlob(), attachment.string.File)
|
||||||
file!: string
|
file!: Ref<Blob>
|
||||||
|
|
||||||
@Prop(TypeString(), attachment.string.Size)
|
@Prop(TypeString(), attachment.string.Size)
|
||||||
size!: number
|
size!: number
|
||||||
|
@ -36,6 +36,7 @@ import {
|
|||||||
DOMAIN_MODEL,
|
DOMAIN_MODEL,
|
||||||
DateRangeMode,
|
DateRangeMode,
|
||||||
IndexKind,
|
IndexKind,
|
||||||
|
type Blob,
|
||||||
type Class,
|
type Class,
|
||||||
type Domain,
|
type Domain,
|
||||||
type Markup,
|
type Markup,
|
||||||
@ -50,10 +51,11 @@ import {
|
|||||||
Model,
|
Model,
|
||||||
Prop,
|
Prop,
|
||||||
ReadOnly,
|
ReadOnly,
|
||||||
TypeAttachment,
|
TypeBlob,
|
||||||
TypeBoolean,
|
TypeBoolean,
|
||||||
TypeCollaborativeMarkup,
|
TypeCollaborativeMarkup,
|
||||||
TypeDate,
|
TypeDate,
|
||||||
|
TypeRecord,
|
||||||
TypeRef,
|
TypeRef,
|
||||||
TypeString,
|
TypeString,
|
||||||
TypeTimestamp,
|
TypeTimestamp,
|
||||||
@ -104,10 +106,23 @@ export class TContact extends TDoc implements Contact {
|
|||||||
@Index(IndexKind.FullText)
|
@Index(IndexKind.FullText)
|
||||||
name!: string
|
name!: string
|
||||||
|
|
||||||
@Prop(TypeAttachment(), contact.string.Avatar)
|
@Prop(TypeString(), contact.string.Avatar)
|
||||||
@Index(IndexKind.FullText)
|
@Index(IndexKind.FullText)
|
||||||
@Hidden()
|
@Hidden()
|
||||||
avatar?: string | null
|
avatarType!: AvatarType
|
||||||
|
|
||||||
|
@Prop(TypeBlob(), contact.string.Avatar)
|
||||||
|
@Index(IndexKind.FullText)
|
||||||
|
@Hidden()
|
||||||
|
avatar!: Ref<Blob> | null | undefined
|
||||||
|
|
||||||
|
@Prop(TypeRecord(), contact.string.Avatar)
|
||||||
|
@Index(IndexKind.FullText)
|
||||||
|
@Hidden()
|
||||||
|
avatarProps?: {
|
||||||
|
color?: string
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
|
||||||
@Prop(Collection(contact.class.Channel), contact.string.ContactInfo)
|
@Prop(Collection(contact.class.Channel), contact.string.ContactInfo)
|
||||||
channels?: number
|
channels?: number
|
||||||
@ -709,6 +724,16 @@ export function createModel (builder: Builder): void {
|
|||||||
contact.avatarProvider.Gravatar
|
contact.avatarProvider.Gravatar
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
contact.class.AvatarProvider,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
type: AvatarType.EXTERNAL,
|
||||||
|
getUrl: contact.function.GetExternalUrl
|
||||||
|
},
|
||||||
|
contact.avatarProvider.Color
|
||||||
|
)
|
||||||
|
|
||||||
builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectPresenter, {
|
builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectPresenter, {
|
||||||
presenter: contact.component.PersonPresenter
|
presenter: contact.component.PersonPresenter
|
||||||
})
|
})
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { DOMAIN_TX, type Space, TxOperations, type Class, type Doc, type Domain, type Ref } from '@hcengineering/core'
|
import { DOMAIN_TX, TxOperations, type Class, type Doc, type Domain, type Ref, type Space } from '@hcengineering/core'
|
||||||
import {
|
import {
|
||||||
createDefaultSpace,
|
createDefaultSpace,
|
||||||
tryMigrate,
|
tryMigrate,
|
||||||
tryUpgrade,
|
tryUpgrade,
|
||||||
type MigrateOperation,
|
type MigrateOperation,
|
||||||
|
type MigrateUpdate,
|
||||||
type MigrationClient,
|
type MigrationClient,
|
||||||
|
type MigrationDocumentQuery,
|
||||||
type MigrationUpgradeClient,
|
type MigrationUpgradeClient,
|
||||||
type ModelLogger
|
type ModelLogger
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
@ -14,6 +16,7 @@ import activity, { DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
|
|||||||
import core from '@hcengineering/model-core'
|
import core from '@hcengineering/model-core'
|
||||||
import { DOMAIN_VIEW } from '@hcengineering/model-view'
|
import { DOMAIN_VIEW } from '@hcengineering/model-view'
|
||||||
|
|
||||||
|
import { AvatarType, type Contact } from '@hcengineering/contact'
|
||||||
import contact, { DOMAIN_CONTACT, contactId } from './index'
|
import contact, { DOMAIN_CONTACT, contactId } from './index'
|
||||||
|
|
||||||
async function createEmployeeEmail (client: TxOperations): Promise<void> {
|
async function createEmployeeEmail (client: TxOperations): Promise<void> {
|
||||||
@ -48,6 +51,55 @@ async function createEmployeeEmail (client: TxOperations): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const colorPrefix = 'color://'
|
||||||
|
const gravatarPrefix = 'gravatar://'
|
||||||
|
|
||||||
|
async function migrateAvatars (client: MigrationClient): Promise<void> {
|
||||||
|
const classes = client.hierarchy.getDescendants(contact.class.Contact)
|
||||||
|
const i = await client.traverse<Contact>(DOMAIN_CONTACT, {
|
||||||
|
_class: { $in: classes },
|
||||||
|
avatar: { $regex: 'color|gravatar://.*' }
|
||||||
|
})
|
||||||
|
while (true) {
|
||||||
|
const docs = await i.next(50)
|
||||||
|
if (docs === null || docs?.length === 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const updates: { filter: MigrationDocumentQuery<Contact>, update: MigrateUpdate<Contact> }[] = []
|
||||||
|
for (const d of docs) {
|
||||||
|
if (d.avatar?.startsWith(colorPrefix) ?? false) {
|
||||||
|
d.avatarProps = { color: d.avatar?.slice(colorPrefix.length) ?? '' }
|
||||||
|
updates.push({
|
||||||
|
filter: { _id: d._id },
|
||||||
|
update: {
|
||||||
|
avatarType: AvatarType.COLOR,
|
||||||
|
avatar: null,
|
||||||
|
avatarProps: { color: d.avatar?.slice(colorPrefix.length) ?? '' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (d.avatar?.startsWith(gravatarPrefix) ?? false) {
|
||||||
|
updates.push({
|
||||||
|
filter: { _id: d._id },
|
||||||
|
update: {
|
||||||
|
avatarType: AvatarType.GRAVATAR,
|
||||||
|
avatar: null,
|
||||||
|
avatarProps: { url: d.avatar?.slice(gravatarPrefix.length) ?? '' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updates.length > 0) {
|
||||||
|
await client.bulk(DOMAIN_CONTACT, updates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.update(
|
||||||
|
DOMAIN_CONTACT,
|
||||||
|
{ _class: { $in: classes }, avatarKind: { $exists: false } },
|
||||||
|
{ avatarKind: AvatarType.IMAGE }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const contactOperation: MigrateOperation = {
|
export const contactOperation: MigrateOperation = {
|
||||||
async migrate (client: MigrationClient, logger: ModelLogger): Promise<void> {
|
async migrate (client: MigrationClient, logger: ModelLogger): Promise<void> {
|
||||||
await tryMigrate(client, contactId, [
|
await tryMigrate(client, contactId, [
|
||||||
@ -183,6 +235,12 @@ export const contactOperation: MigrateOperation = {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
state: 'avatars',
|
||||||
|
func: async (client) => {
|
||||||
|
await migrateAvatars(client)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
@ -213,8 +213,8 @@ export class TTypeString extends TType {}
|
|||||||
export class TTypeRecord extends TType {}
|
export class TTypeRecord extends TType {}
|
||||||
|
|
||||||
@UX(core.string.String)
|
@UX(core.string.String)
|
||||||
@Model(core.class.TypeAttachment, core.class.Type)
|
@Model(core.class.TypeBlob, core.class.Type)
|
||||||
export class TTypeAttachment extends TType {}
|
export class TTypeBlob extends TType {}
|
||||||
|
|
||||||
@UX(core.string.Hyperlink)
|
@UX(core.string.Hyperlink)
|
||||||
@Model(core.class.TypeHyperlink, core.class.Type)
|
@Model(core.class.TypeHyperlink, core.class.Type)
|
||||||
|
@ -60,7 +60,7 @@ import {
|
|||||||
TRefTo,
|
TRefTo,
|
||||||
TType,
|
TType,
|
||||||
TTypeAny,
|
TTypeAny,
|
||||||
TTypeAttachment,
|
TTypeBlob,
|
||||||
TTypeBoolean,
|
TTypeBoolean,
|
||||||
TTypeCollaborativeDoc,
|
TTypeCollaborativeDoc,
|
||||||
TTypeCollaborativeDocVersion,
|
TTypeCollaborativeDocVersion,
|
||||||
@ -153,7 +153,7 @@ export function createModel (builder: Builder): void {
|
|||||||
TTypeString,
|
TTypeString,
|
||||||
TTypeRank,
|
TTypeRank,
|
||||||
TTypeRecord,
|
TTypeRecord,
|
||||||
TTypeAttachment,
|
TTypeBlob,
|
||||||
TTypeHyperlink,
|
TTypeHyperlink,
|
||||||
TCollection,
|
TCollection,
|
||||||
TVersion,
|
TVersion,
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { type Blob, type Class, type Doc, type Ref, DOMAIN_MODEL } from '@hcengineering/core'
|
import { DOMAIN_MODEL, type Blob, type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||||
import { type Builder, Model, Prop, TypeRef, TypeString } from '@hcengineering/model'
|
import { Model, Prop, TypeRef, TypeString, type Builder } from '@hcengineering/model'
|
||||||
import core, { TDoc } from '@hcengineering/model-core'
|
import core, { TDoc } from '@hcengineering/model-core'
|
||||||
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||||
// Import types to prevent .svelte components to being exposed to type typescript.
|
// Import types to prevent .svelte components to being exposed to type typescript.
|
||||||
@ -27,12 +27,12 @@ import {
|
|||||||
type ComponentPointExtension,
|
type ComponentPointExtension,
|
||||||
type CreateExtensionKind,
|
type CreateExtensionKind,
|
||||||
type DocAttributeRule,
|
type DocAttributeRule,
|
||||||
type DocRules,
|
|
||||||
type DocCreateExtension,
|
type DocCreateExtension,
|
||||||
type DocCreateFunction,
|
type DocCreateFunction,
|
||||||
|
type DocRules,
|
||||||
type FilePreviewExtension,
|
type FilePreviewExtension,
|
||||||
type ObjectSearchContext,
|
|
||||||
type ObjectSearchCategory,
|
type ObjectSearchCategory,
|
||||||
|
type ObjectSearchContext,
|
||||||
type ObjectSearchFactory
|
type ObjectSearchFactory
|
||||||
} from '@hcengineering/presentation/src/types'
|
} from '@hcengineering/presentation/src/types'
|
||||||
import { type AnyComponent, type ComponentExtensionId } from '@hcengineering/ui/src/types'
|
import { type AnyComponent, type ComponentExtensionId } from '@hcengineering/ui/src/types'
|
||||||
|
@ -45,8 +45,8 @@ export function createModel (builder: Builder): void {
|
|||||||
builder.mixin(contact.class.Contact, core.class.Class, serverCore.mixin.SearchPresenter, {
|
builder.mixin(contact.class.Contact, core.class.Class, serverCore.mixin.SearchPresenter, {
|
||||||
searchConfig: {
|
searchConfig: {
|
||||||
iconConfig: {
|
iconConfig: {
|
||||||
component: contact.component.Avatar,
|
component: contact.component.AvatarRef,
|
||||||
props: ['avatar', 'name']
|
props: ['_id']
|
||||||
},
|
},
|
||||||
title: { props: ['name'] }
|
title: { props: ['name'] }
|
||||||
},
|
},
|
||||||
|
@ -57,8 +57,8 @@ export function createModel (builder: Builder): void {
|
|||||||
builder.mixin(recruit.class.Applicant, core.class.Class, serverCore.mixin.SearchPresenter, {
|
builder.mixin(recruit.class.Applicant, core.class.Class, serverCore.mixin.SearchPresenter, {
|
||||||
searchConfig: {
|
searchConfig: {
|
||||||
iconConfig: {
|
iconConfig: {
|
||||||
component: contact.component.Avatar,
|
component: contact.component.AvatarRef,
|
||||||
props: [{ avatar: ['attachedTo', 'avatar'] }, { name: ['attachedTo', 'name'] }]
|
props: [{ _id: ['attachedTo'] }]
|
||||||
},
|
},
|
||||||
shortTitle: 'identifier',
|
shortTitle: 'identifier',
|
||||||
title: {
|
title: {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
import activity from '@hcengineering/activity'
|
import activity from '@hcengineering/activity'
|
||||||
import contact from '@hcengineering/contact'
|
import contact from '@hcengineering/contact'
|
||||||
import { AccountRole, DOMAIN_MODEL, type Account, type Domain, type Ref } from '@hcengineering/core'
|
import { AccountRole, DOMAIN_MODEL, type Account, type Blob, type Domain, type Ref } from '@hcengineering/core'
|
||||||
import { Mixin, Model, type Builder, UX } from '@hcengineering/model'
|
import { Mixin, Model, type Builder, UX } from '@hcengineering/model'
|
||||||
import core, { TClass, TConfiguration, TDoc } from '@hcengineering/model-core'
|
import core, { TClass, TConfiguration, TDoc } from '@hcengineering/model-core'
|
||||||
import view, { createAction } from '@hcengineering/model-view'
|
import view, { createAction } from '@hcengineering/model-view'
|
||||||
@ -105,7 +105,7 @@ export class TInviteSettings extends TConfiguration implements InviteSettings {
|
|||||||
|
|
||||||
@Model(setting.class.WorkspaceSetting, core.class.Doc, DOMAIN_SETTING)
|
@Model(setting.class.WorkspaceSetting, core.class.Doc, DOMAIN_SETTING)
|
||||||
export class TWorkspaceSetting extends TDoc implements WorkspaceSetting {
|
export class TWorkspaceSetting extends TDoc implements WorkspaceSetting {
|
||||||
icon?: string
|
icon?: Ref<Blob>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mixin(setting.mixin.SpaceTypeEditor, core.class.Class)
|
@Mixin(setting.mixin.SpaceTypeEditor, core.class.Class)
|
||||||
|
@ -460,7 +460,7 @@ export function createModel (builder: Builder): void {
|
|||||||
view.component.StringEditor,
|
view.component.StringEditor,
|
||||||
view.component.StringEditorPopup
|
view.component.StringEditorPopup
|
||||||
)
|
)
|
||||||
classPresenter(builder, core.class.TypeAttachment, view.component.StringPresenter)
|
classPresenter(builder, core.class.TypeBlob, view.component.StringPresenter)
|
||||||
classPresenter(
|
classPresenter(
|
||||||
builder,
|
builder,
|
||||||
core.class.TypeHyperlink,
|
core.class.TypeHyperlink,
|
||||||
|
@ -559,6 +559,22 @@ export interface Blob extends Doc {
|
|||||||
size: number
|
size: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For every blob will automatically add a lookup.
|
||||||
|
*
|
||||||
|
* It extends Blob to allow for $lookup operations work as expected.
|
||||||
|
*/
|
||||||
|
export interface BlobLookup extends Blob {
|
||||||
|
// An URL document could be downloaded from, with ${id} to put blobId into
|
||||||
|
downloadUrl: string
|
||||||
|
// A URL document could be updated at
|
||||||
|
uploadUrl?: string
|
||||||
|
// A URL document could be previewed at
|
||||||
|
previewUrl?: string
|
||||||
|
// A formats preview is available at
|
||||||
|
previewFormats?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*
|
*
|
||||||
|
@ -111,7 +111,7 @@ export default plugin(coreId, {
|
|||||||
Account: '' as Ref<Class<Account>>,
|
Account: '' as Ref<Class<Account>>,
|
||||||
Type: '' as Ref<Class<Type<any>>>,
|
Type: '' as Ref<Class<Type<any>>>,
|
||||||
TypeString: '' as Ref<Class<Type<string>>>,
|
TypeString: '' as Ref<Class<Type<string>>>,
|
||||||
TypeAttachment: '' as Ref<Class<Type<string>>>,
|
TypeBlob: '' as Ref<Class<Type<Ref<Blob>>>>,
|
||||||
TypeIntlString: '' as Ref<Class<Type<IntlString>>>,
|
TypeIntlString: '' as Ref<Class<Type<IntlString>>>,
|
||||||
TypeHyperlink: '' as Ref<Class<Type<Hyperlink>>>,
|
TypeHyperlink: '' as Ref<Class<Type<Hyperlink>>>,
|
||||||
TypeNumber: '' as Ref<Class<Type<number>>>,
|
TypeNumber: '' as Ref<Class<Type<number>>>,
|
||||||
|
@ -13,20 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { LoadModelResponse } from '.'
|
import type { Doc, Domain, Ref } from './classes'
|
||||||
import type { Class, Doc, Domain, Ref, Timestamp } from './classes'
|
|
||||||
import { Hierarchy } from './hierarchy'
|
|
||||||
import { MeasureContext, type FullParamsType, type ParamsType } from './measurements'
|
import { MeasureContext, type FullParamsType, type ParamsType } from './measurements'
|
||||||
import { ModelDb } from './memdb'
|
|
||||||
import type {
|
|
||||||
DocumentQuery,
|
|
||||||
FindOptions,
|
|
||||||
FindResult,
|
|
||||||
SearchOptions,
|
|
||||||
SearchQuery,
|
|
||||||
SearchResult,
|
|
||||||
TxResult
|
|
||||||
} from './storage'
|
|
||||||
import type { Tx } from './tx'
|
import type { Tx } from './tx'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,24 +66,3 @@ export interface LowLevelStorage {
|
|||||||
// Remove a list of documents.
|
// Remove a list of documents.
|
||||||
clean: (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]) => Promise<void>
|
clean: (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]) => Promise<void>
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface ServerStorage extends LowLevelStorage {
|
|
||||||
hierarchy: Hierarchy
|
|
||||||
modelDb: ModelDb
|
|
||||||
findAll: <T extends Doc>(
|
|
||||||
ctx: MeasureContext,
|
|
||||||
_class: Ref<Class<T>>,
|
|
||||||
query: DocumentQuery<T>,
|
|
||||||
options?: FindOptions<T> & {
|
|
||||||
domain?: Domain // Allow to find for Doc's in specified domain only.
|
|
||||||
prefix?: string
|
|
||||||
}
|
|
||||||
) => Promise<FindResult<T>>
|
|
||||||
searchFulltext: (ctx: MeasureContext, query: SearchQuery, options: SearchOptions) => Promise<SearchResult>
|
|
||||||
tx: (ctx: SessionOperationContext, tx: Tx) => Promise<TxResult>
|
|
||||||
apply: (ctx: SessionOperationContext, tx: Tx[], broadcast: boolean) => Promise<TxResult>
|
|
||||||
close: () => Promise<void>
|
|
||||||
loadModel: (last: Timestamp, hash?: string) => Promise<Tx[] | LoadModelResponse>
|
|
||||||
}
|
|
||||||
|
@ -214,7 +214,7 @@ export function extractDocKey (key: string): {
|
|||||||
export function isFullTextAttribute (attr: AnyAttribute): boolean {
|
export function isFullTextAttribute (attr: AnyAttribute): boolean {
|
||||||
return (
|
return (
|
||||||
attr.index === IndexKind.FullText ||
|
attr.index === IndexKind.FullText ||
|
||||||
attr.type._class === core.class.TypeAttachment ||
|
attr.type._class === core.class.TypeBlob ||
|
||||||
attr.type._class === core.class.EnumOf ||
|
attr.type._class === core.class.EnumOf ||
|
||||||
attr.type._class === core.class.TypeCollaborativeDoc
|
attr.type._class === core.class.TypeCollaborativeDoc
|
||||||
)
|
)
|
||||||
|
@ -387,8 +387,8 @@ export function TypeString (): Type<string> {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function TypeAttachment (): Type<string> {
|
export function TypeBlob (): Type<string> {
|
||||||
return { _class: core.class.TypeAttachment, label: core.string.String }
|
return { _class: core.class.TypeBlob, label: core.string.String }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,20 +13,20 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onMount } from 'svelte'
|
|
||||||
import { type Blob, type Ref } from '@hcengineering/core'
|
import { type Blob, type Ref } from '@hcengineering/core'
|
||||||
import { Label, Dialog, Button, Component } from '@hcengineering/ui'
|
import { Button, Component, Dialog, Label } from '@hcengineering/ui'
|
||||||
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
|
|
||||||
import presentation from '../plugin'
|
import presentation from '../plugin'
|
||||||
|
|
||||||
import { getPreviewType, previewTypes } from '../file'
|
import { getPreviewType, previewTypes } from '../file'
|
||||||
import { BlobMetadata, FilePreviewExtension } from '../types'
|
import { BlobMetadata, FilePreviewExtension } from '../types'
|
||||||
import { getFileUrl } from '../utils'
|
import { getBlobHref, getFileUrl } from '../utils'
|
||||||
|
|
||||||
import ActionContext from './ActionContext.svelte'
|
import ActionContext from './ActionContext.svelte'
|
||||||
import Download from './icons/Download.svelte'
|
import Download from './icons/Download.svelte'
|
||||||
|
|
||||||
export let file: Ref<Blob> | undefined
|
export let file: Blob | Ref<Blob> | undefined
|
||||||
export let name: string
|
export let name: string
|
||||||
export let contentType: string
|
export let contentType: string
|
||||||
export let metadata: BlobMetadata | undefined
|
export let metadata: BlobMetadata | undefined
|
||||||
@ -57,9 +57,8 @@
|
|||||||
} else {
|
} else {
|
||||||
previewType = undefined
|
previewType = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
let download: HTMLAnchorElement
|
let download: HTMLAnchorElement
|
||||||
$: src = file === undefined ? '' : getFileUrl(file, 'full', name)
|
$: src = file === undefined ? '' : typeof file === 'string' ? getFileUrl(file, name) : getBlobHref(file, file._id)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionContext context={{ mode: 'browser' }} />
|
<ActionContext context={{ mode: 'browser' }} />
|
||||||
|
@ -14,14 +14,15 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// import { Doc } from '@hcengineering/core'
|
// import { Doc } from '@hcengineering/core'
|
||||||
|
import type { Blob, Ref } from '@hcengineering/core'
|
||||||
import { Button, Dialog, Label, Spinner } from '@hcengineering/ui'
|
import { Button, Dialog, Label, Spinner } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher, onMount } from 'svelte'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import presentation from '..'
|
import presentation from '..'
|
||||||
import { getFileUrl } from '../utils'
|
import { getBlobHref, getFileUrl } from '../utils'
|
||||||
import Download from './icons/Download.svelte'
|
|
||||||
import ActionContext from './ActionContext.svelte'
|
import ActionContext from './ActionContext.svelte'
|
||||||
|
import Download from './icons/Download.svelte'
|
||||||
|
|
||||||
export let file: string | undefined
|
export let file: Blob | Ref<Blob> | undefined
|
||||||
export let name: string
|
export let name: string
|
||||||
export let contentType: string | undefined
|
export let contentType: string | undefined
|
||||||
// export let popupOptions: PopupOptions
|
// export let popupOptions: PopupOptions
|
||||||
@ -44,7 +45,8 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
let download: HTMLAnchorElement
|
let download: HTMLAnchorElement
|
||||||
$: src = file === undefined ? '' : getFileUrl(file, 'full', name)
|
$: src = file === undefined ? '' : typeof file === 'string' ? getFileUrl(file, name) : getBlobHref(file, file._id)
|
||||||
|
|
||||||
$: isImage = contentType !== undefined && contentType.startsWith('image/')
|
$: isImage = contentType !== undefined && contentType.startsWith('image/')
|
||||||
|
|
||||||
let frame: HTMLIFrameElement | undefined = undefined
|
let frame: HTMLIFrameElement | undefined = undefined
|
||||||
|
@ -13,14 +13,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { type Blob, type Ref, concatLink } from '@hcengineering/core'
|
import { concatLink, type Blob, type Ref } from '@hcengineering/core'
|
||||||
import { PlatformError, Severity, Status, getMetadata, getResource } from '@hcengineering/platform'
|
import { PlatformError, Severity, Status, getMetadata, getResource } from '@hcengineering/platform'
|
||||||
import { type PopupAlignment } from '@hcengineering/ui'
|
import { type PopupAlignment } from '@hcengineering/ui'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
import { type BlobMetadata, type FilePreviewExtension } from './types'
|
|
||||||
import { createQuery } from './utils'
|
|
||||||
import plugin from './plugin'
|
import plugin from './plugin'
|
||||||
|
import type { BlobMetadata, FilePreviewExtension } from './types'
|
||||||
|
import { createQuery } from './utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
|
@ -22,6 +22,7 @@ import core, {
|
|||||||
type AnyAttribute,
|
type AnyAttribute,
|
||||||
type ArrOf,
|
type ArrOf,
|
||||||
type AttachedDoc,
|
type AttachedDoc,
|
||||||
|
type BlobLookup,
|
||||||
type Class,
|
type Class,
|
||||||
type Client,
|
type Client,
|
||||||
type Collection,
|
type Collection,
|
||||||
@ -34,23 +35,25 @@ import core, {
|
|||||||
type MeasureDoneOperation,
|
type MeasureDoneOperation,
|
||||||
type Mixin,
|
type Mixin,
|
||||||
type Obj,
|
type Obj,
|
||||||
|
type Blob as PlatformBlob,
|
||||||
type Ref,
|
type Ref,
|
||||||
type RefTo,
|
type RefTo,
|
||||||
type SearchOptions,
|
type SearchOptions,
|
||||||
type SearchQuery,
|
type SearchQuery,
|
||||||
type SearchResult,
|
type SearchResult,
|
||||||
|
type Space,
|
||||||
type Tx,
|
type Tx,
|
||||||
type TxResult,
|
type TxResult,
|
||||||
type TypeAny,
|
type TypeAny,
|
||||||
type WithLookup,
|
type WithLookup
|
||||||
type Space
|
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { getMetadata, getResource } from '@hcengineering/platform'
|
import { getMetadata, getResource } from '@hcengineering/platform'
|
||||||
import { LiveQuery as LQ } from '@hcengineering/query'
|
import { LiveQuery as LQ } from '@hcengineering/query'
|
||||||
import { type AnyComponent, type AnySvelteComponent, type IconSize } from '@hcengineering/ui'
|
import { workspaceId, type AnyComponent, type AnySvelteComponent } from '@hcengineering/ui'
|
||||||
import view, { type AttributeCategory, type AttributeEditor } from '@hcengineering/view'
|
import view, { type AttributeCategory, type AttributeEditor } from '@hcengineering/view'
|
||||||
import { deepEqual } from 'fast-equals'
|
import { deepEqual } from 'fast-equals'
|
||||||
import { onDestroy } from 'svelte'
|
import { onDestroy } from 'svelte'
|
||||||
|
import { get } from 'svelte/store'
|
||||||
import { type KeyedAttribute } from '..'
|
import { type KeyedAttribute } from '..'
|
||||||
import { OptimizeQueryMiddleware, PresentationPipelineImpl, type PresentationPipeline } from './pipeline'
|
import { OptimizeQueryMiddleware, PresentationPipelineImpl, type PresentationPipeline } from './pipeline'
|
||||||
import plugin from './plugin'
|
import plugin from './plugin'
|
||||||
@ -357,19 +360,117 @@ export function createQuery (dontDestroy?: boolean): LiveQuery {
|
|||||||
return new LiveQuery(dontDestroy)
|
return new LiveQuery(dontDestroy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSrcSet (_blob: PlatformBlob, width?: number): string {
|
||||||
|
let result = ''
|
||||||
|
const blob = _blob as BlobLookup
|
||||||
|
|
||||||
|
if (blob.previewUrl === undefined) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
for (const f of blob.previewFormats ?? []) {
|
||||||
|
if (result.length > 0) {
|
||||||
|
result += ', '
|
||||||
|
}
|
||||||
|
const fu = blob.previewUrl.replaceAll(':format', f)
|
||||||
|
if (width !== undefined) {
|
||||||
|
result +=
|
||||||
|
fu.replaceAll(':size', `${width}`) +
|
||||||
|
', ' +
|
||||||
|
fu.replaceAll(':size', `${width * 2}`) +
|
||||||
|
', ' +
|
||||||
|
fu.replaceAll(':size', `${width * 3}`)
|
||||||
|
} else {
|
||||||
|
result += fu.replaceAll(':size', `${-1}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function getFileUrl (file: string, size: IconSize = 'full', filename?: string): string {
|
export function getFileUrlSrcSet (
|
||||||
|
file: Ref<PlatformBlob>,
|
||||||
|
width?: number,
|
||||||
|
formats: string[] = supportedFormats
|
||||||
|
): string {
|
||||||
if (file.includes('://')) {
|
if (file.includes('://')) {
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
const uploadUrl = getMetadata(plugin.metadata.UploadURL)
|
const uploadUrl = getMetadata(plugin.metadata.UploadURL)
|
||||||
|
|
||||||
if (filename !== undefined) {
|
let result = ''
|
||||||
return `${uploadUrl as string}/${filename}?file=${file}&size=${size as string}`
|
|
||||||
|
for (const f of formats) {
|
||||||
|
if (result.length > 0) {
|
||||||
|
result += ', '
|
||||||
}
|
}
|
||||||
return `${uploadUrl as string}?file=${file}&size=${size as string}`
|
if (width !== undefined) {
|
||||||
|
const fu = `${uploadUrl as string}/${get(workspaceId)}?file=${file}.${f}&size=:size`
|
||||||
|
result +=
|
||||||
|
fu.replaceAll(':size', `${width}`) +
|
||||||
|
', ' +
|
||||||
|
fu.replaceAll(':size', `${width * 2}`) +
|
||||||
|
', ' +
|
||||||
|
fu.replaceAll(':size', `${width * 3}`)
|
||||||
|
} else {
|
||||||
|
result += `${uploadUrl as string}/${get(workspaceId)}?file=${file}.${f}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBlobHref (_blob: PlatformBlob | undefined, file: Ref<PlatformBlob>, filename?: string): string {
|
||||||
|
const blob = _blob as BlobLookup
|
||||||
|
return blob?.downloadUrl ?? getFileUrl(file, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBlobSrcSet (_blob: PlatformBlob | undefined, file: Ref<PlatformBlob>, width?: number): string {
|
||||||
|
return _blob !== undefined ? getSrcSet(_blob, width) : getFileUrlSrcSet(file, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function getFileUrl (file: Ref<PlatformBlob>, filename?: string): string {
|
||||||
|
if (file.includes('://')) {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
const uploadUrl = getMetadata(plugin.metadata.UploadURL)
|
||||||
|
if (filename !== undefined) {
|
||||||
|
return `${uploadUrl as string}/${get(workspaceId)}/${encodeURIComponent(filename)}?file=${file}`
|
||||||
|
}
|
||||||
|
return `${uploadUrl as string}/${get(workspaceId)}?file=${file}`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupportedFormat = 'jpeg' | 'avif' | 'heif' | 'webp' | 'png'
|
||||||
|
const supportedFormats: SupportedFormat[] = ['avif', 'webp', 'heif', 'jpeg', 'png']
|
||||||
|
|
||||||
|
export function sizeToWidth (size: string): number | undefined {
|
||||||
|
let width: number | undefined
|
||||||
|
switch (size) {
|
||||||
|
case 'inline':
|
||||||
|
case 'tiny':
|
||||||
|
case 'card':
|
||||||
|
case 'x-small':
|
||||||
|
case 'smaller':
|
||||||
|
case 'small':
|
||||||
|
width = 32
|
||||||
|
break
|
||||||
|
case 'medium':
|
||||||
|
width = 64
|
||||||
|
break
|
||||||
|
case 'large':
|
||||||
|
width = 256
|
||||||
|
break
|
||||||
|
case 'x-large':
|
||||||
|
width = 512
|
||||||
|
break
|
||||||
|
case '2x-large':
|
||||||
|
width = 1024
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return width
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -561,3 +662,14 @@ export function isAdminUser (): boolean {
|
|||||||
export function isSpace (space: Doc): space is Space {
|
export function isSpace (space: Doc): space is Space {
|
||||||
return getClient().getHierarchy().isDerived(space._class, core.class.Space)
|
return getClient().getHierarchy().isDerived(space._class, core.class.Space)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setPresentationCookie (token: string, workspaceId: string): void {
|
||||||
|
function setToken (path: string): void {
|
||||||
|
document.cookie =
|
||||||
|
encodeURIComponent(plugin.metadata.Token.replaceAll(':', '-')) +
|
||||||
|
'=' +
|
||||||
|
encodeURIComponent(token) +
|
||||||
|
`; path=${path}`
|
||||||
|
}
|
||||||
|
setToken('/files/' + workspaceId)
|
||||||
|
}
|
||||||
|
@ -13,10 +13,19 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { type Blob, type MeasureContext, type WorkspaceId } from '@hcengineering/core'
|
import {
|
||||||
|
type Blob,
|
||||||
|
type DocumentUpdate,
|
||||||
|
type MeasureContext,
|
||||||
|
type Ref,
|
||||||
|
type WorkspaceId,
|
||||||
|
type WorkspaceIdWithUrl
|
||||||
|
} from '@hcengineering/core'
|
||||||
|
import type { BlobLookup } from '@hcengineering/core/src/classes'
|
||||||
import { type Readable } from 'stream'
|
import { type Readable } from 'stream'
|
||||||
|
|
||||||
export type ListBlobResult = Omit<Blob, 'contentType' | 'version'>
|
export type ListBlobResult = Omit<Blob, 'contentType' | 'version'>
|
||||||
|
|
||||||
export interface UploadedObjectInfo {
|
export interface UploadedObjectInfo {
|
||||||
etag: string
|
etag: string
|
||||||
versionId: string | null
|
versionId: string | null
|
||||||
@ -27,6 +36,11 @@ export interface BlobStorageIterator {
|
|||||||
close: () => Promise<void>
|
close: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BlobLookupResult {
|
||||||
|
lookups: BlobLookup[]
|
||||||
|
updates?: Map<Ref<Blob>, DocumentUpdate<BlobLookup>>
|
||||||
|
}
|
||||||
|
|
||||||
export interface StorageAdapter {
|
export interface StorageAdapter {
|
||||||
initialize: (ctx: MeasureContext, workspaceId: WorkspaceId) => Promise<void>
|
initialize: (ctx: MeasureContext, workspaceId: WorkspaceId) => Promise<void>
|
||||||
|
|
||||||
@ -56,6 +70,9 @@ export interface StorageAdapter {
|
|||||||
offset: number,
|
offset: number,
|
||||||
length?: number
|
length?: number
|
||||||
) => Promise<Readable>
|
) => Promise<Readable>
|
||||||
|
|
||||||
|
// Lookup will extend Blob with lookup information.
|
||||||
|
lookup: (ctx: MeasureContext, workspaceId: WorkspaceIdWithUrl, docs: Blob[]) => Promise<BlobLookupResult>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageAdapterEx extends StorageAdapter {
|
export interface StorageAdapterEx extends StorageAdapter {
|
||||||
@ -133,6 +150,10 @@ export class DummyStorageAdapter implements StorageAdapter, StorageAdapterEx {
|
|||||||
): Promise<UploadedObjectInfo> {
|
): Promise<UploadedObjectInfo> {
|
||||||
throw new Error('not implemented')
|
throw new Error('not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async lookup (ctx: MeasureContext, workspaceId: WorkspaceIdWithUrl, docs: Blob[]): Promise<BlobLookupResult> {
|
||||||
|
return { lookups: [] }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDummyStorageAdapter (): StorageAdapter {
|
export function createDummyStorageAdapter (): StorageAdapter {
|
||||||
|
@ -15,23 +15,23 @@
|
|||||||
//
|
//
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type Class, type CollaborativeDoc, type Doc, type Ref } from '@hcengineering/core'
|
|
||||||
import { type DocumentId, type PlatformDocumentId } from '@hcengineering/collaborator-client'
|
import { type DocumentId, type PlatformDocumentId } from '@hcengineering/collaborator-client'
|
||||||
|
import { type Class, type CollaborativeDoc, type Doc, type Ref } from '@hcengineering/core'
|
||||||
import { IntlString, getMetadata, translate } from '@hcengineering/platform'
|
import { IntlString, getMetadata, translate } from '@hcengineering/platform'
|
||||||
import { markupToJSON } from '@hcengineering/text'
|
|
||||||
import presentation, { getFileUrl, getImageSize } from '@hcengineering/presentation'
|
import presentation, { getFileUrl, getImageSize } from '@hcengineering/presentation'
|
||||||
import view from '@hcengineering/view'
|
import { markupToJSON } from '@hcengineering/text'
|
||||||
import {
|
import {
|
||||||
AnySvelteComponent,
|
AnySvelteComponent,
|
||||||
Button,
|
Button,
|
||||||
IconSize,
|
IconSize,
|
||||||
Loading,
|
Loading,
|
||||||
PopupAlignment,
|
PopupAlignment,
|
||||||
|
ThrottledCaller,
|
||||||
getEventPositionElement,
|
getEventPositionElement,
|
||||||
getPopupPositionElement,
|
getPopupPositionElement,
|
||||||
ThrottledCaller,
|
|
||||||
themeStore
|
themeStore
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
|
import view from '@hcengineering/view'
|
||||||
import { AnyExtension, Editor, FocusPosition, mergeAttributes } from '@tiptap/core'
|
import { AnyExtension, Editor, FocusPosition, mergeAttributes } from '@tiptap/core'
|
||||||
import Collaboration, { isChangeOrigin } from '@tiptap/extension-collaboration'
|
import Collaboration, { isChangeOrigin } from '@tiptap/extension-collaboration'
|
||||||
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
|
||||||
@ -39,8 +39,8 @@
|
|||||||
import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte'
|
import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte'
|
||||||
import { Doc as YDoc } from 'yjs'
|
import { Doc as YDoc } from 'yjs'
|
||||||
|
|
||||||
import { deleteAttachment } from '../command/deleteAttachment'
|
|
||||||
import { Completion } from '../Completion'
|
import { Completion } from '../Completion'
|
||||||
|
import { deleteAttachment } from '../command/deleteAttachment'
|
||||||
import { textEditorCommandHandler } from '../commands'
|
import { textEditorCommandHandler } from '../commands'
|
||||||
import { EditorKit } from '../kits/editor-kit'
|
import { EditorKit } from '../kits/editor-kit'
|
||||||
import textEditorPlugin from '../plugin'
|
import textEditorPlugin from '../plugin'
|
||||||
@ -64,13 +64,13 @@
|
|||||||
import { noSelectionRender, renderCursor } from './editor/collaboration'
|
import { noSelectionRender, renderCursor } from './editor/collaboration'
|
||||||
import { defaultEditorAttributes } from './editor/editorProps'
|
import { defaultEditorAttributes } from './editor/editorProps'
|
||||||
import { EmojiExtension } from './extension/emoji'
|
import { EmojiExtension } from './extension/emoji'
|
||||||
import { ImageUploadExtension } from './extension/imageUploadExt'
|
|
||||||
import { type FileAttachFunction } from './extension/types'
|
|
||||||
import { FileUploadExtension } from './extension/fileUploadExt'
|
import { FileUploadExtension } from './extension/fileUploadExt'
|
||||||
import { LeftMenuExtension } from './extension/leftMenu'
|
import { ImageUploadExtension } from './extension/imageUploadExt'
|
||||||
import { InlineCommandsExtension } from './extension/inlineCommands'
|
import { InlineCommandsExtension } from './extension/inlineCommands'
|
||||||
import { InlinePopupExtension } from './extension/inlinePopup'
|
import { InlinePopupExtension } from './extension/inlinePopup'
|
||||||
import { InlineStyleToolbarExtension } from './extension/inlineStyleToolbar'
|
import { InlineStyleToolbarExtension } from './extension/inlineStyleToolbar'
|
||||||
|
import { LeftMenuExtension } from './extension/leftMenu'
|
||||||
|
import { type FileAttachFunction } from './extension/types'
|
||||||
import { completionConfig, inlineCommandsConfig } from './extensions'
|
import { completionConfig, inlineCommandsConfig } from './extensions'
|
||||||
|
|
||||||
export let collaborativeDoc: CollaborativeDoc
|
export let collaborativeDoc: CollaborativeDoc
|
||||||
@ -288,7 +288,7 @@
|
|||||||
optionalExtensions.push(
|
optionalExtensions.push(
|
||||||
ImageUploadExtension.configure({
|
ImageUploadExtension.configure({
|
||||||
attachFile,
|
attachFile,
|
||||||
uploadUrl: getMetadata(presentation.metadata.UploadURL)
|
getFileUrl
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -339,10 +339,7 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const size = await getImageSize(
|
const size = await getImageSize(file, getFileUrl(attached.file))
|
||||||
file,
|
|
||||||
getFileUrl(attached.file, 'full', getMetadata(presentation.metadata.UploadURL))
|
|
||||||
)
|
|
||||||
|
|
||||||
editor.commands.insertContent(
|
editor.commands.insertContent(
|
||||||
{
|
{
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
function openOriginalImage (): void {
|
function openOriginalImage (): void {
|
||||||
const attributes = textEditor.getAttributes('image')
|
const attributes = textEditor.getAttributes('image')
|
||||||
const fileId = attributes['file-id'] ?? attributes.src
|
const fileId = attributes['file-id'] ?? attributes.src
|
||||||
const url = getFileUrl(fileId, 'full')
|
const url = getFileUrl(fileId)
|
||||||
window.open(url, '_blank')
|
window.open(url, '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Markup } from '@hcengineering/core'
|
import { Markup } from '@hcengineering/core'
|
||||||
import { IntlString, getMetadata } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import presentation, { MessageViewer, getFileUrl, getImageSize } from '@hcengineering/presentation'
|
import presentation, { MessageViewer, getFileUrl, getImageSize } from '@hcengineering/presentation'
|
||||||
import { EmptyMarkup } from '@hcengineering/text'
|
import { EmptyMarkup } from '@hcengineering/text'
|
||||||
import {
|
import {
|
||||||
@ -17,21 +17,21 @@
|
|||||||
registerFocus,
|
registerFocus,
|
||||||
resizeObserver
|
resizeObserver
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import type { AnyExtension } from '@tiptap/core'
|
import type { AnyExtension } from '@tiptap/core'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
import { Completion } from '../Completion'
|
import { Completion } from '../Completion'
|
||||||
import textEditorPlugin from '../plugin'
|
import textEditorPlugin from '../plugin'
|
||||||
import StyledTextEditor from './StyledTextEditor.svelte'
|
import StyledTextEditor from './StyledTextEditor.svelte'
|
||||||
|
|
||||||
import { completionConfig, inlineCommandsConfig } from './extensions'
|
import { RefAction } from '../types'
|
||||||
|
import { addTableHandler } from '../utils'
|
||||||
import { EmojiExtension } from './extension/emoji'
|
import { EmojiExtension } from './extension/emoji'
|
||||||
import { FocusExtension } from './extension/focus'
|
import { FocusExtension } from './extension/focus'
|
||||||
import { ImageUploadExtension } from './extension/imageUploadExt'
|
import { ImageUploadExtension } from './extension/imageUploadExt'
|
||||||
import { InlineCommandsExtension } from './extension/inlineCommands'
|
import { InlineCommandsExtension } from './extension/inlineCommands'
|
||||||
import { type FileAttachFunction } from './extension/types'
|
import { type FileAttachFunction } from './extension/types'
|
||||||
import { RefAction } from '../types'
|
import { completionConfig, inlineCommandsConfig } from './extensions'
|
||||||
import { addTableHandler } from '../utils'
|
|
||||||
|
|
||||||
export let label: IntlString | undefined = undefined
|
export let label: IntlString | undefined = undefined
|
||||||
export let content: Markup
|
export let content: Markup
|
||||||
@ -177,7 +177,7 @@
|
|||||||
function configureExtensions (): AnyExtension[] {
|
function configureExtensions (): AnyExtension[] {
|
||||||
const imageUploadPlugin = ImageUploadExtension.configure({
|
const imageUploadPlugin = ImageUploadExtension.configure({
|
||||||
attachFile,
|
attachFile,
|
||||||
uploadUrl: getMetadata(presentation.metadata.UploadURL)
|
getFileUrl
|
||||||
})
|
})
|
||||||
|
|
||||||
const completionPlugin = Completion.configure({
|
const completionPlugin = Completion.configure({
|
||||||
@ -251,10 +251,7 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const size = await getImageSize(
|
const size = await getImageSize(file, getFileUrl(attached.file))
|
||||||
file,
|
|
||||||
getFileUrl(attached.file, 'full', getMetadata(presentation.metadata.UploadURL))
|
|
||||||
)
|
|
||||||
|
|
||||||
textEditor.editorHandler.insertContent(
|
textEditor.editorHandler.insertContent(
|
||||||
{
|
{
|
||||||
|
@ -79,7 +79,7 @@ export const FileExtension = FileNode.extend<FileOptions>({
|
|||||||
const fileType = HTMLAttributes['data-file-type']
|
const fileType = HTMLAttributes['data-file-type']
|
||||||
let href: string = ''
|
let href: string = ''
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
href = getFileUrl(id, 'full', fileName)
|
href = getFileUrl(id, fileName)
|
||||||
}
|
}
|
||||||
const linkAttributes = {
|
const linkAttributes = {
|
||||||
class: 'file-name',
|
class: 'file-name',
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
//
|
//
|
||||||
import { FilePreviewPopup } from '@hcengineering/presentation'
|
import { FilePreviewPopup } from '@hcengineering/presentation'
|
||||||
import { ImageNode, type ImageOptions as ImageNodeOptions } from '@hcengineering/text'
|
import { ImageNode, type ImageOptions as ImageNodeOptions } from '@hcengineering/text'
|
||||||
import { type IconSize, getIconSize2x, showPopup } from '@hcengineering/ui'
|
import { showPopup } from '@hcengineering/ui'
|
||||||
import { mergeAttributes, nodeInputRule } from '@tiptap/core'
|
import { mergeAttributes, nodeInputRule } from '@tiptap/core'
|
||||||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||||
|
|
||||||
@ -26,9 +26,7 @@ export type ImageAlignment = 'center' | 'left' | 'right'
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface ImageOptions extends ImageNodeOptions {
|
export interface ImageOptions extends ImageNodeOptions {}
|
||||||
uploadUrl: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageAlignmentOptions {
|
export interface ImageAlignmentOptions {
|
||||||
align?: ImageAlignment
|
align?: ImageAlignment
|
||||||
@ -63,11 +61,6 @@ declare module '@tiptap/core' {
|
|||||||
*/
|
*/
|
||||||
export const inputRegex = /(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/
|
export const inputRegex = /(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/
|
||||||
|
|
||||||
// This is a simplified version of getFileUrl from presentation plugin, which we cannot use
|
|
||||||
export function getFileUrl (fileId: string, size: IconSize = 'full', uploadUrl: string): string {
|
|
||||||
return `${uploadUrl}?file=${fileId}&size=${size as string}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -76,7 +69,8 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
|
|||||||
return {
|
return {
|
||||||
inline: true,
|
inline: true,
|
||||||
HTMLAttributes: {},
|
HTMLAttributes: {},
|
||||||
uploadUrl: ''
|
getFileUrl: () => '',
|
||||||
|
getFileUrlSrcSet: () => ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -105,33 +99,32 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
|
|||||||
this.options.HTMLAttributes,
|
this.options.HTMLAttributes,
|
||||||
HTMLAttributes
|
HTMLAttributes
|
||||||
)
|
)
|
||||||
|
const getFileUrl = this.options.getFileUrl
|
||||||
const uploadUrl = this.options.uploadUrl ?? ''
|
const getFileUrlSrcSet = this.options.getFileUrlSrcSet
|
||||||
|
|
||||||
const id = imgAttributes['file-id']
|
const id = imgAttributes['file-id']
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
imgAttributes.src = getFileUrl(id, 'full', uploadUrl)
|
imgAttributes.src = getFileUrl(id)
|
||||||
let width: IconSize | undefined
|
let width: number | undefined
|
||||||
|
// TODO: Use max width of component may be?
|
||||||
switch (imgAttributes.width) {
|
switch (imgAttributes.width) {
|
||||||
case '32px':
|
case '32px':
|
||||||
width = 'small'
|
width = 32
|
||||||
break
|
break
|
||||||
case '64px':
|
case '64px':
|
||||||
width = 'medium'
|
width = 64
|
||||||
break
|
break
|
||||||
case '128px':
|
case '128px':
|
||||||
|
width = 128
|
||||||
|
break
|
||||||
case '256px':
|
case '256px':
|
||||||
width = 'large'
|
width = 256
|
||||||
break
|
break
|
||||||
case '512px':
|
case '512px':
|
||||||
width = 'x-large'
|
width = 512
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (width !== undefined) {
|
imgAttributes.srcset = getFileUrlSrcSet(id, width)
|
||||||
imgAttributes.src = getFileUrl(id, width, uploadUrl)
|
|
||||||
imgAttributes.srcset =
|
|
||||||
getFileUrl(id, width, uploadUrl) + ' 1x,' + getFileUrl(id, getIconSize2x(width), uploadUrl) + ' 2x'
|
|
||||||
}
|
|
||||||
imgAttributes.class = 'text-editor-image'
|
imgAttributes.class = 'text-editor-image'
|
||||||
imgAttributes.contentEditable = false
|
imgAttributes.contentEditable = false
|
||||||
}
|
}
|
||||||
|
@ -12,21 +12,21 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import { getImageSize } from '@hcengineering/presentation'
|
import { getImageSize } from '@hcengineering/presentation'
|
||||||
import { Extension } from '@tiptap/core'
|
import { Extension } from '@tiptap/core'
|
||||||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||||
import { type EditorView } from '@tiptap/pm/view'
|
import { type EditorView } from '@tiptap/pm/view'
|
||||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
|
||||||
|
|
||||||
import { getFileUrl } from './imageExt'
|
|
||||||
import { type FileAttachFunction } from './types'
|
import { type FileAttachFunction } from './types'
|
||||||
|
import type { Blob, Ref } from '@hcengineering/core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface ImageUploadOptions {
|
export interface ImageUploadOptions {
|
||||||
attachFile?: FileAttachFunction
|
attachFile?: FileAttachFunction
|
||||||
uploadUrl: string
|
getFileUrl: (fileId: Ref<Blob>) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
function getType (type: string): 'image' | 'other' {
|
function getType (type: string): 'image' | 'other' {
|
||||||
@ -43,13 +43,13 @@ function getType (type: string): 'image' | 'other' {
|
|||||||
export const ImageUploadExtension = Extension.create<ImageUploadOptions>({
|
export const ImageUploadExtension = Extension.create<ImageUploadOptions>({
|
||||||
addOptions () {
|
addOptions () {
|
||||||
return {
|
return {
|
||||||
uploadUrl: ''
|
getFileUrl: () => ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addProseMirrorPlugins () {
|
addProseMirrorPlugins () {
|
||||||
const attachFile = this.options.attachFile
|
const attachFile = this.options.attachFile
|
||||||
const uploadUrl = this.options.uploadUrl
|
const getFileUrl = this.options.getFileUrl
|
||||||
|
|
||||||
function handleDrop (
|
function handleDrop (
|
||||||
view: EditorView,
|
view: EditorView,
|
||||||
@ -61,9 +61,6 @@ export const ImageUploadExtension = Extension.create<ImageUploadOptions>({
|
|||||||
for (const uri of uris) {
|
for (const uri of uris) {
|
||||||
if (uri !== '') {
|
if (uri !== '') {
|
||||||
const url = new URL(uri)
|
const url = new URL(uri)
|
||||||
if (uploadUrl === undefined || !url.href.includes(uploadUrl)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const _file = (url.searchParams.get('file') ?? '').split('/').join('')
|
const _file = (url.searchParams.get('file') ?? '').split('/').join('')
|
||||||
|
|
||||||
@ -77,7 +74,7 @@ export const ImageUploadExtension = Extension.create<ImageUploadOptions>({
|
|||||||
if (type === 'image') {
|
if (type === 'image') {
|
||||||
const node = view.state.schema.nodes.image.create({
|
const node = view.state.schema.nodes.image.create({
|
||||||
'file-id': _file,
|
'file-id': _file,
|
||||||
src: getFileUrl(_file, 'full', uploadUrl)
|
src: getFileUrl(_file as Ref<Blob>)
|
||||||
})
|
})
|
||||||
const transaction = view.state.tr.insert(pos?.pos ?? 0, node)
|
const transaction = view.state.tr.insert(pos?.pos ?? 0, node)
|
||||||
view.dispatch(transaction)
|
view.dispatch(transaction)
|
||||||
@ -95,7 +92,7 @@ export const ImageUploadExtension = Extension.create<ImageUploadOptions>({
|
|||||||
const file = files.item(i)
|
const file = files.item(i)
|
||||||
if (file != null && file.type.startsWith('image/')) {
|
if (file != null && file.type.startsWith('image/')) {
|
||||||
result = true
|
result = true
|
||||||
void handleImageUpload(file, view, pos, attachFile, uploadUrl)
|
void handleImageUpload(file, view, pos, attachFile, getFileUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +136,7 @@ async function handleImageUpload (
|
|||||||
view: EditorView,
|
view: EditorView,
|
||||||
pos: { pos: number, inside: number } | null,
|
pos: { pos: number, inside: number } | null,
|
||||||
attachFile: FileAttachFunction,
|
attachFile: FileAttachFunction,
|
||||||
uploadUrl: string
|
getFileUrl: (fileId: Ref<Blob>) => string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const attached = await attachFile(file)
|
const attached = await attachFile(file)
|
||||||
|
|
||||||
@ -152,7 +149,7 @@ async function handleImageUpload (
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = getFileUrl(attached.file, 'full', uploadUrl)
|
const url = getFileUrl(attached.file)
|
||||||
const size = await getImageSize(file, url)
|
const size = await getImageSize(file, url)
|
||||||
const node = view.state.schema.nodes.image.create({
|
const node = view.state.schema.nodes.image.create({
|
||||||
'file-id': attached.file,
|
'file-id': attached.file,
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import type { Blob, Ref } from '@hcengineering/core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type FileAttachFunction = (file: File) => Promise<{ file: string, type: string } | undefined>
|
export type FileAttachFunction = (file: File) => Promise<{ file: Ref<Blob>, type: string } | undefined>
|
||||||
|
@ -22,14 +22,13 @@ import TaskList from '@tiptap/extension-task-list'
|
|||||||
|
|
||||||
import { DefaultKit, type DefaultKitOptions } from './default-kit'
|
import { DefaultKit, type DefaultKitOptions } from './default-kit'
|
||||||
|
|
||||||
import { getMetadata } from '@hcengineering/platform'
|
import { getFileUrl, getFileUrlSrcSet } from '@hcengineering/presentation'
|
||||||
import presentation from '@hcengineering/presentation'
|
|
||||||
import { CodeBlockExtension, codeBlockOptions } from '@hcengineering/text'
|
import { CodeBlockExtension, codeBlockOptions } from '@hcengineering/text'
|
||||||
import { CodemarkExtension } from '../components/extension/codemark'
|
import { CodemarkExtension } from '../components/extension/codemark'
|
||||||
|
import { FileExtension, type FileOptions } from '../components/extension/fileExt'
|
||||||
|
import { ImageExtension, type ImageOptions } from '../components/extension/imageExt'
|
||||||
import { NodeUuidExtension } from '../components/extension/nodeUuid'
|
import { NodeUuidExtension } from '../components/extension/nodeUuid'
|
||||||
import { Table, TableCell, TableRow } from '../components/extension/table'
|
import { Table, TableCell, TableRow } from '../components/extension/table'
|
||||||
import { type ImageOptions, ImageExtension } from '../components/extension/imageExt'
|
|
||||||
import { type FileOptions, FileExtension } from '../components/extension/fileExt'
|
|
||||||
|
|
||||||
const headingLevels: Level[] = [1, 2, 3]
|
const headingLevels: Level[] = [1, 2, 3]
|
||||||
|
|
||||||
@ -105,7 +104,8 @@ export const EditorKit = Extension.create<EditorKitOptions>({
|
|||||||
? [
|
? [
|
||||||
ImageExtension.configure({
|
ImageExtension.configure({
|
||||||
inline: true,
|
inline: true,
|
||||||
uploadUrl: getMetadata(presentation.metadata.UploadURL) ?? '',
|
getFileUrl,
|
||||||
|
getFileUrlSrcSet,
|
||||||
...this.options.image
|
...this.options.image
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
@ -18,6 +18,8 @@ import { collaborativeDocParse, concatLink } from '@hcengineering/core'
|
|||||||
import { ObservableV2 as Observable } from 'lib0/observable'
|
import { ObservableV2 as Observable } from 'lib0/observable'
|
||||||
import { type Doc as YDoc, applyUpdate } from 'yjs'
|
import { type Doc as YDoc, applyUpdate } from 'yjs'
|
||||||
import { type DocumentId, parseDocumentId } from '@hcengineering/collaborator-client'
|
import { type DocumentId, parseDocumentId } from '@hcengineering/collaborator-client'
|
||||||
|
import { workspaceId } from '@hcengineering/ui'
|
||||||
|
import { get } from 'svelte/store'
|
||||||
|
|
||||||
interface EVENTS {
|
interface EVENTS {
|
||||||
synced: (...args: any[]) => void
|
synced: (...args: any[]) => void
|
||||||
@ -29,7 +31,7 @@ async function fetchContent (doc: YDoc, name: string): Promise<void> {
|
|||||||
const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin
|
const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(concatLink(frontUrl, `/files?file=${name}`))
|
const res = await fetch(concatLink(frontUrl, `/files/${get(workspaceId)}?file=${name}`))
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const blob = await res.blob()
|
const blob = await res.blob()
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
//
|
//
|
||||||
import { Node, mergeAttributes } from '@tiptap/core'
|
import { Node, mergeAttributes } from '@tiptap/core'
|
||||||
import { getDataAttribute } from './utils'
|
import { getDataAttribute } from './utils'
|
||||||
|
import type { Ref, Blob } from '@hcengineering/core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -21,12 +22,8 @@ import { getDataAttribute } from './utils'
|
|||||||
export interface ImageOptions {
|
export interface ImageOptions {
|
||||||
inline: boolean
|
inline: boolean
|
||||||
HTMLAttributes: Record<string, any>
|
HTMLAttributes: Record<string, any>
|
||||||
uploadUrl?: string
|
getFileUrl: (fileId: Ref<Blob>, filename?: string) => string
|
||||||
}
|
getFileUrlSrcSet: (fileId: Ref<Blob>, size?: number) => string
|
||||||
|
|
||||||
// This is a simplified version of getFileUrl from presentation plugin, which we cannot use
|
|
||||||
function getFileUrl (uploadUrl: string, fileId: string, size: string = 'full'): string {
|
|
||||||
return `${uploadUrl}?file=${fileId}&size=${size}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +36,8 @@ export const ImageNode = Node.create<ImageOptions>({
|
|||||||
return {
|
return {
|
||||||
inline: true,
|
inline: true,
|
||||||
HTMLAttributes: {},
|
HTMLAttributes: {},
|
||||||
uploadUrl: ''
|
getFileUrl: () => '',
|
||||||
|
getFileUrlSrcSet: () => ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -107,8 +105,8 @@ export const ImageNode = Node.create<ImageOptions>({
|
|||||||
|
|
||||||
const fileId = imgAttributes['file-id']
|
const fileId = imgAttributes['file-id']
|
||||||
if (fileId != null) {
|
if (fileId != null) {
|
||||||
const uploadUrl = this.options.uploadUrl ?? ''
|
imgAttributes.src = this.options.getFileUrl(fileId)
|
||||||
imgAttributes.src = getFileUrl(uploadUrl, fileId)
|
imgAttributes.srcset = this.options.getFileUrlSrcSet(fileId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['div', divAttributes, ['img', imgAttributes]]
|
return ['div', divAttributes, ['img', imgAttributes]]
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
personByIdStore,
|
personByIdStore,
|
||||||
SystemAvatar
|
SystemAvatar
|
||||||
} from '@hcengineering/contact-resources'
|
} from '@hcengineering/contact-resources'
|
||||||
import core, { Account, Doc, Ref, Timestamp } from '@hcengineering/core'
|
import core, { Account, Doc, Ref, Timestamp, type WithLookup } from '@hcengineering/core'
|
||||||
import { Icon, Label, resizeObserver, TimeSince, tooltip } from '@hcengineering/ui'
|
import { Icon, Label, resizeObserver, TimeSince, tooltip } from '@hcengineering/ui'
|
||||||
import { Asset, getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
import { Asset, getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||||
import activity, { ActivityMessage, ActivityMessagePreviewType } from '@hcengineering/activity'
|
import activity, { ActivityMessage, ActivityMessagePreviewType } from '@hcengineering/activity'
|
||||||
@ -47,7 +47,7 @@
|
|||||||
const limit = 300
|
const limit = 300
|
||||||
|
|
||||||
let isActionsOpened = false
|
let isActionsOpened = false
|
||||||
let person: Person | undefined = undefined
|
let person: WithLookup<Person> | undefined = undefined
|
||||||
|
|
||||||
let width: number
|
let width: number
|
||||||
|
|
||||||
@ -59,7 +59,7 @@
|
|||||||
_id: Ref<Account> | undefined,
|
_id: Ref<Account> | undefined,
|
||||||
accountById: Map<Ref<PersonAccount>, PersonAccount>,
|
accountById: Map<Ref<PersonAccount>, PersonAccount>,
|
||||||
personById: Map<Ref<Person>, Person>
|
personById: Map<Ref<Person>, Person>
|
||||||
): Person | undefined {
|
): WithLookup<Person> | undefined {
|
||||||
if (_id === undefined) {
|
if (_id === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@ -116,7 +116,7 @@
|
|||||||
{#if headerObject}
|
{#if headerObject}
|
||||||
<Icon icon={headerIcon ?? classIcon(client, headerObject._class) ?? activity.icon.Activity} size="small" />
|
<Icon icon={headerIcon ?? classIcon(client, headerObject._class) ?? activity.icon.Activity} size="small" />
|
||||||
{:else if person}
|
{:else if person}
|
||||||
<Avatar size="card" avatar={person.avatar} name={person.name} />
|
<Avatar size="card" {person} name={person.name} />
|
||||||
{:else}
|
{:else}
|
||||||
<SystemAvatar size="card" />
|
<SystemAvatar size="card" />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
<div class="flex-row-center">
|
<div class="flex-row-center">
|
||||||
<div class="avatars">
|
<div class="avatars">
|
||||||
{#each displayPersons as person}
|
{#each displayPersons as person}
|
||||||
<Avatar size="x-small" avatar={person.avatar} name={person.name} />
|
<Avatar size="x-small" {person} name={person.name} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
{#if value.icon}
|
{#if value.icon}
|
||||||
<SystemAvatar size="medium" icon={value.icon} iconProps={value.iconProps} />
|
<SystemAvatar size="medium" icon={value.icon} iconProps={value.iconProps} />
|
||||||
{:else if person}
|
{:else if person}
|
||||||
<Avatar size="medium" avatar={person.avatar} name={person.name} />
|
<Avatar size="medium" {person} name={person.name} />
|
||||||
{:else}
|
{:else}
|
||||||
<SystemAvatar size="medium" />
|
<SystemAvatar size="medium" />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -144,7 +144,7 @@
|
|||||||
{#if $$slots.icon}
|
{#if $$slots.icon}
|
||||||
<slot name="icon" />
|
<slot name="icon" />
|
||||||
{:else if person}
|
{:else if person}
|
||||||
<Avatar size="medium" avatar={person.avatar} name={person.name} />
|
<Avatar size="medium" {person} name={person.name} />
|
||||||
{:else}
|
{:else}
|
||||||
<SystemAvatar size="medium" />
|
<SystemAvatar size="medium" />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -14,21 +14,31 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type Attachment } from '@hcengineering/attachment'
|
import { type Attachment } from '@hcengineering/attachment'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource, getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
import {
|
import {
|
||||||
FilePreviewPopup,
|
FilePreviewPopup,
|
||||||
getFileUrl,
|
|
||||||
previewTypes,
|
previewTypes,
|
||||||
canPreviewFile,
|
canPreviewFile,
|
||||||
getPreviewAlignment
|
getPreviewAlignment,
|
||||||
|
getBlobHref
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
import { Action as UIAction, ActionIcon, IconMoreH, IconOpen, Menu, closeTooltip, showPopup } from '@hcengineering/ui'
|
import {
|
||||||
|
Action as UIAction,
|
||||||
|
ActionIcon,
|
||||||
|
IconMoreH,
|
||||||
|
IconOpen,
|
||||||
|
Menu,
|
||||||
|
closeTooltip,
|
||||||
|
showPopup,
|
||||||
|
tooltip
|
||||||
|
} from '@hcengineering/ui'
|
||||||
import view, { Action } from '@hcengineering/view'
|
import view, { Action } from '@hcengineering/view'
|
||||||
|
|
||||||
import attachmentPlugin from '../plugin'
|
import attachmentPlugin from '../plugin'
|
||||||
import FileDownload from './icons/FileDownload.svelte'
|
import FileDownload from './icons/FileDownload.svelte'
|
||||||
|
import type { WithLookup } from '@hcengineering/core'
|
||||||
|
|
||||||
export let attachment: Attachment
|
export let attachment: WithLookup<Attachment>
|
||||||
export let isSaved = false
|
export let isSaved = false
|
||||||
export let removable = false
|
export let removable = false
|
||||||
|
|
||||||
@ -61,7 +71,7 @@
|
|||||||
showPopup(
|
showPopup(
|
||||||
FilePreviewPopup,
|
FilePreviewPopup,
|
||||||
{
|
{
|
||||||
file: attachment.file,
|
file: attachment.$lookup?.file ?? attachment.file,
|
||||||
name: attachment.name,
|
name: attachment.name,
|
||||||
contentType: attachment.type ?? '',
|
contentType: attachment.type ?? '',
|
||||||
metadata: attachment.metadata
|
metadata: attachment.metadata
|
||||||
@ -123,9 +133,10 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<a
|
<a
|
||||||
class="mr-1 flex-row-center gap-2 p-1"
|
class="mr-1 flex-row-center gap-2 p-1"
|
||||||
href={getFileUrl(attachment.file, 'full', attachment.name)}
|
href={getBlobHref(attachment.$lookup?.file, attachment.file, attachment.name)}
|
||||||
download={attachment.name}
|
download={attachment.name}
|
||||||
bind:this={download}
|
bind:this={download}
|
||||||
|
use:tooltip={{ label: getEmbeddedLabel(attachment.name) }}
|
||||||
on:click|stopPropagation
|
on:click|stopPropagation
|
||||||
>
|
>
|
||||||
{#if canPreview}
|
{#if canPreview}
|
||||||
|
@ -14,12 +14,12 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import type { Doc, Ref } from '@hcengineering/core'
|
import core, { type Doc, type Ref, type WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
|
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import AttachmentList from './AttachmentList.svelte'
|
|
||||||
import { AttachmentImageSize } from '../types'
|
import { AttachmentImageSize } from '../types'
|
||||||
|
import AttachmentList from './AttachmentList.svelte'
|
||||||
|
|
||||||
export let value: Doc & { attachments?: number }
|
export let value: Doc & { attachments?: number }
|
||||||
export let attachments: Attachment[] | undefined = undefined
|
export let attachments: Attachment[] | undefined = undefined
|
||||||
@ -30,7 +30,7 @@
|
|||||||
const savedAttachmentsQuery = createQuery()
|
const savedAttachmentsQuery = createQuery()
|
||||||
|
|
||||||
let savedAttachmentsIds: Ref<Attachment>[] = []
|
let savedAttachmentsIds: Ref<Attachment>[] = []
|
||||||
let resAttachments: Attachment[] = []
|
let resAttachments: WithLookup<Attachment>[] = []
|
||||||
|
|
||||||
$: updateQuery(value, attachments)
|
$: updateQuery(value, attachments)
|
||||||
|
|
||||||
@ -48,6 +48,11 @@
|
|||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
resAttachments = res
|
resAttachments = res
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lookup: {
|
||||||
|
file: core.class.Blob
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,12 +14,13 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Attachment } from '@hcengineering/attachment'
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
import { showPopup, closeTooltip } from '@hcengineering/ui'
|
import type { WithLookup } from '@hcengineering/core'
|
||||||
import { FilePreviewPopup, getFileUrl } from '@hcengineering/presentation'
|
import { FilePreviewPopup, getBlobHref } from '@hcengineering/presentation'
|
||||||
import { getType } from '../utils'
|
import { closeTooltip, showPopup } from '@hcengineering/ui'
|
||||||
import filesize from 'filesize'
|
import filesize from 'filesize'
|
||||||
|
import { getType } from '../utils'
|
||||||
|
|
||||||
export let value: Attachment
|
export let value: WithLookup<Attachment>
|
||||||
|
|
||||||
const maxLength: number = 18
|
const maxLength: number = 18
|
||||||
const trimFilename = (fname: string): string =>
|
const trimFilename = (fname: string): string =>
|
||||||
@ -44,7 +45,7 @@
|
|||||||
showPopup(
|
showPopup(
|
||||||
FilePreviewPopup,
|
FilePreviewPopup,
|
||||||
{
|
{
|
||||||
file: value.file,
|
file: value.$lookup?.file ?? value.file,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
contentType: value.type,
|
contentType: value.type,
|
||||||
metadata: value.metadata
|
metadata: value.metadata
|
||||||
@ -60,7 +61,7 @@
|
|||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="cellImagePreview" on:click={openAttachment}>
|
<div class="cellImagePreview" on:click={openAttachment}>
|
||||||
<img class={'img-fit'} src={getFileUrl(value.file, 'full', value.name)} alt={value.name} />
|
<img class={'img-fit'} src={getBlobHref(value.$lookup?.file, value.file, value.name)} alt={value.name} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="cellMiscPreview">
|
<div class="cellMiscPreview">
|
||||||
@ -71,7 +72,7 @@
|
|||||||
{extensionIconLabel(value.name)}
|
{extensionIconLabel(value.name)}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<a class="no-line" href={getFileUrl(value.file, 'full', value.name)} download={value.name}>
|
<a class="no-line" href={getBlobHref(value.$lookup?.file, value.file, value.name)} download={value.name}>
|
||||||
<div class="flex-center extensionIcon">{extensionIconLabel(value.name)}</div>
|
<div class="flex-center extensionIcon">{extensionIconLabel(value.name)}</div>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
@ -86,7 +87,7 @@
|
|||||||
{extensionIconLabel(value.name)}
|
{extensionIconLabel(value.name)}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<a class="no-line" href={getFileUrl(value.file, 'full', value.name)} download={value.name}>
|
<a class="no-line" href={getBlobHref(value.$lookup?.file, value.file, value.name)} download={value.name}>
|
||||||
<div class="flex-center extensionIcon">{extensionIconLabel(value.name)}</div>
|
<div class="flex-center extensionIcon">{extensionIconLabel(value.name)}</div>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
@ -99,7 +100,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="eCellInfoFilename">
|
<div class="eCellInfoFilename">
|
||||||
<a href={getFileUrl(value.file, 'full', value.name)} download={value.name}>{trimFilename(value.name)}</a>
|
<a href={getBlobHref(value.$lookup?.file, value.file, value.name)} download={value.name}
|
||||||
|
>{trimFilename(value.name)}</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="eCellInfoFilesize">{filesize(value.size)}</div>
|
<div class="eCellInfoFilesize">{filesize(value.size)}</div>
|
||||||
|
@ -13,13 +13,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getIconSize2x, IconSize } from '@hcengineering/ui'
|
|
||||||
import { getFileUrl } from '@hcengineering/presentation'
|
|
||||||
import type { Attachment } from '@hcengineering/attachment'
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
|
import { getBlobHref, getBlobSrcSet, getFileUrlSrcSet, sizeToWidth } from '@hcengineering/presentation'
|
||||||
|
import { IconSize } from '@hcengineering/ui'
|
||||||
|
|
||||||
|
import type { WithLookup } from '@hcengineering/core'
|
||||||
import { AttachmentImageSize } from '../types'
|
import { AttachmentImageSize } from '../types'
|
||||||
|
|
||||||
export let value: Attachment
|
export let value: WithLookup<Attachment>
|
||||||
export let size: AttachmentImageSize = 'auto'
|
export let size: AttachmentImageSize = 'auto'
|
||||||
|
|
||||||
interface Dimensions {
|
interface Dimensions {
|
||||||
@ -104,15 +105,13 @@
|
|||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<img
|
<img
|
||||||
src={getFileUrl(value.file, urlSize)}
|
src={getBlobHref(value.$lookup?.file, value.file, value.name)}
|
||||||
style:object-fit={getObjectFit(dimensions)}
|
style:object-fit={getObjectFit(dimensions)}
|
||||||
width={dimensions.width}
|
width={dimensions.width}
|
||||||
height={dimensions.height}
|
height={dimensions.height}
|
||||||
srcset={`${getFileUrl(value.file, urlSize, value.name)} 1x, ${getFileUrl(
|
srcset={value.$lookup?.file !== undefined
|
||||||
value.file,
|
? getBlobSrcSet(value.$lookup?.file, value.file, sizeToWidth(urlSize))
|
||||||
getIconSize2x(urlSize),
|
: getFileUrlSrcSet(value.file, sizeToWidth(urlSize))}
|
||||||
value.name
|
|
||||||
)} 2x`}
|
|
||||||
alt={value.name}
|
alt={value.name}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -14,13 +14,13 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import { Ref } from '@hcengineering/core'
|
import { Ref, type WithLookup } from '@hcengineering/core'
|
||||||
import { Scroller } from '@hcengineering/ui'
|
import { Scroller } from '@hcengineering/ui'
|
||||||
|
|
||||||
import AttachmentPreview from './AttachmentPreview.svelte'
|
import AttachmentPreview from './AttachmentPreview.svelte'
|
||||||
import { AttachmentImageSize } from '../types'
|
import { AttachmentImageSize } from '../types'
|
||||||
|
|
||||||
export let attachments: Attachment[] = []
|
export let attachments: WithLookup<Attachment>[] = []
|
||||||
export let savedAttachmentsIds: Ref<Attachment>[] = []
|
export let savedAttachmentsIds: Ref<Attachment>[] = []
|
||||||
export let imageSize: AttachmentImageSize | undefined = undefined
|
export let imageSize: AttachmentImageSize | undefined = undefined
|
||||||
export let videoPreload = true
|
export let videoPreload = true
|
||||||
|
@ -14,12 +14,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Doc } from '@hcengineering/core'
|
|
||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import { createQuery, getClient, getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
import { createQuery, getClient, getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
||||||
import { ActionIcon, IconAdd, Label, Loading } from '@hcengineering/ui'
|
import { ActionIcon, IconAdd, Label, Loading } from '@hcengineering/ui'
|
||||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
|
||||||
|
|
||||||
|
import type { Doc, WithLookup } from '@hcengineering/core'
|
||||||
|
import core from '@hcengineering/core'
|
||||||
|
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import { AttachmentPresenter } from '..'
|
import { AttachmentPresenter } from '..'
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
|
|
||||||
@ -30,7 +31,7 @@
|
|||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
let docs: Attachment[] = []
|
let docs: WithLookup<Attachment>[] = []
|
||||||
|
|
||||||
let progress = false
|
let progress = false
|
||||||
|
|
||||||
@ -42,6 +43,11 @@
|
|||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
docs = res
|
docs = res
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lookup: {
|
||||||
|
file: core.class.Blob
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,24 +14,26 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import filesize from 'filesize'
|
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import type { Attachment } from '@hcengineering/attachment'
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
import core from '@hcengineering/core'
|
import core, { type WithLookup } from '@hcengineering/core'
|
||||||
import { showPopup, closeTooltip, Label, getIconSize2x, Loading } from '@hcengineering/ui'
|
|
||||||
import presentation, {
|
import presentation, {
|
||||||
FilePreviewPopup,
|
FilePreviewPopup,
|
||||||
canPreviewFile,
|
canPreviewFile,
|
||||||
getFileUrl,
|
getBlobHref,
|
||||||
|
getBlobSrcSet,
|
||||||
getPreviewAlignment,
|
getPreviewAlignment,
|
||||||
previewTypes
|
previewTypes,
|
||||||
|
sizeToWidth
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
|
import { Label, closeTooltip, showPopup } from '@hcengineering/ui'
|
||||||
import { permissionsStore } from '@hcengineering/view-resources'
|
import { permissionsStore } from '@hcengineering/view-resources'
|
||||||
|
import filesize from 'filesize'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { getType } from '../utils'
|
import { getType } from '../utils'
|
||||||
|
|
||||||
import AttachmentName from './AttachmentName.svelte'
|
import AttachmentName from './AttachmentName.svelte'
|
||||||
|
|
||||||
export let value: Attachment | undefined
|
export let value: WithLookup<Attachment> | undefined
|
||||||
export let removable: boolean = false
|
export let removable: boolean = false
|
||||||
export let showPreview = false
|
export let showPreview = false
|
||||||
export let preview = false
|
export let preview = false
|
||||||
@ -82,7 +84,7 @@
|
|||||||
showPopup(
|
showPopup(
|
||||||
FilePreviewPopup,
|
FilePreviewPopup,
|
||||||
{
|
{
|
||||||
file: value.file,
|
file: value.$lookup?.file ?? value.file,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
contentType: value.type,
|
contentType: value.type,
|
||||||
metadata: value.metadata
|
metadata: value.metadata
|
||||||
@ -100,24 +102,6 @@
|
|||||||
|
|
||||||
let download: HTMLAnchorElement
|
let download: HTMLAnchorElement
|
||||||
|
|
||||||
$: imgStyle = getImageStyle(value)
|
|
||||||
|
|
||||||
function getImageStyle (value?: Attachment): string {
|
|
||||||
if (value === undefined) return ''
|
|
||||||
|
|
||||||
return isImage(value.type)
|
|
||||||
? `background-image: url(${getFileUrl(value.file, 'large')});
|
|
||||||
background-image: -webkit-image-set(
|
|
||||||
${getFileUrl(value.file, 'large')} 1x,
|
|
||||||
${getFileUrl(value.file, getIconSize2x('large'))} 2x
|
|
||||||
);
|
|
||||||
background-image: image-set(
|
|
||||||
${getFileUrl(value.file, 'large')} 1x,
|
|
||||||
${getFileUrl(value.file, getIconSize2x('large'))} 2x
|
|
||||||
);`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragStart (event: DragEvent): void {
|
function dragStart (event: DragEvent): void {
|
||||||
if (value === undefined) return
|
if (value === undefined) return
|
||||||
event.dataTransfer?.setData('application/contentType', value.type)
|
event.dataTransfer?.setData('application/contentType', value.type)
|
||||||
@ -132,25 +116,21 @@
|
|||||||
<a
|
<a
|
||||||
class="no-line"
|
class="no-line"
|
||||||
style:flex-shrink={0}
|
style:flex-shrink={0}
|
||||||
href={getFileUrl(value.file, 'full', value.name)}
|
href={getBlobHref(value.$lookup?.file, value.file, value.name)}
|
||||||
download={value.name}
|
download={value.name}
|
||||||
on:click={clickHandler}
|
on:click={clickHandler}
|
||||||
on:mousedown={middleClickHandler}
|
on:mousedown={middleClickHandler}
|
||||||
on:dragstart={dragStart}
|
on:dragstart={dragStart}
|
||||||
>
|
>
|
||||||
{#if showPreview}
|
{#if showPreview && isImage(value.type)}
|
||||||
<div
|
<img
|
||||||
|
src={getBlobHref(value.$lookup?.file, value.file)}
|
||||||
|
srcset={getBlobSrcSet(value.$lookup?.file, value.file, sizeToWidth('large'))}
|
||||||
class="flex-center icon"
|
class="flex-center icon"
|
||||||
class:svg={value.type === 'image/svg+xml'}
|
class:svg={value.type === 'image/svg+xml'}
|
||||||
class:image={isImage(value.type)}
|
class:image={isImage(value.type)}
|
||||||
style={imgStyle}
|
alt={value.name}
|
||||||
>
|
/>
|
||||||
{#if progress}
|
|
||||||
<div class="flex p-3">
|
|
||||||
<Loading />
|
|
||||||
</div>
|
|
||||||
{:else if !isImage(value.type)}{iconLabel(value.name)}{/if}
|
|
||||||
</div>
|
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex-center icon">
|
<div class="flex-center icon">
|
||||||
{iconLabel(value.name)}
|
{iconLabel(value.name)}
|
||||||
@ -160,7 +140,7 @@
|
|||||||
<div class="flex-col info-container">
|
<div class="flex-col info-container">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<a
|
<a
|
||||||
href={getFileUrl(value.file, 'full', value.name)}
|
href={getBlobHref(value.$lookup?.file, value.file, value.name)}
|
||||||
download={value.name}
|
download={value.name}
|
||||||
on:click={clickHandler}
|
on:click={clickHandler}
|
||||||
on:mousedown={middleClickHandler}
|
on:mousedown={middleClickHandler}
|
||||||
@ -174,7 +154,7 @@
|
|||||||
<span>•</span>
|
<span>•</span>
|
||||||
<a
|
<a
|
||||||
class="no-line colorInherit"
|
class="no-line colorInherit"
|
||||||
href={getFileUrl(value.file, 'full', value.name)}
|
href={getBlobHref(value.$lookup?.file, value.file, value.name)}
|
||||||
download={value.name}
|
download={value.name}
|
||||||
bind:this={download}
|
bind:this={download}
|
||||||
>
|
>
|
||||||
|
@ -27,8 +27,9 @@
|
|||||||
import { AttachmentImageSize } from '../types'
|
import { AttachmentImageSize } from '../types'
|
||||||
import AttachmentImagePreview from './AttachmentImagePreview.svelte'
|
import AttachmentImagePreview from './AttachmentImagePreview.svelte'
|
||||||
import AttachmentVideoPreview from './AttachmentVideoPreview.svelte'
|
import AttachmentVideoPreview from './AttachmentVideoPreview.svelte'
|
||||||
|
import type { WithLookup } from '@hcengineering/core'
|
||||||
|
|
||||||
export let value: Attachment
|
export let value: WithLookup<Attachment>
|
||||||
export let isSaved: boolean = false
|
export let isSaved: boolean = false
|
||||||
export let listProvider: ListSelectionProvider | undefined = undefined
|
export let listProvider: ListSelectionProvider | undefined = undefined
|
||||||
export let imageSize: AttachmentImageSize = 'auto'
|
export let imageSize: AttachmentImageSize = 'auto'
|
||||||
@ -50,7 +51,7 @@
|
|||||||
if (listProvider !== undefined) listProvider.updateFocus(value)
|
if (listProvider !== undefined) listProvider.updateFocus(value)
|
||||||
const popupInfo = showPopup(
|
const popupInfo = showPopup(
|
||||||
FilePreviewPopup,
|
FilePreviewPopup,
|
||||||
{ file: value.file, name: value.name, contentType: value.type },
|
{ file: value.$lookup?.file ?? value.file, name: value.name, contentType: value.type },
|
||||||
value.type.startsWith('image/') ? 'centered' : 'float'
|
value.type.startsWith('image/') ? 'centered' : 'float'
|
||||||
)
|
)
|
||||||
dispatch('open', popupInfo.id)
|
dispatch('open', popupInfo.id)
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
import contact from '@hcengineering/contact'
|
import contact from '@hcengineering/contact'
|
||||||
import { Account, Doc, Ref, generateId } from '@hcengineering/core'
|
import { Account, Doc, Ref, generateId, type Blob } from '@hcengineering/core'
|
||||||
import { IntlString, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { IntlString, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import { KeyedAttribute, createQuery, getClient, uploadFile } from '@hcengineering/presentation'
|
import { KeyedAttribute, createQuery, getClient, uploadFile } from '@hcengineering/presentation'
|
||||||
import { getCollaborationUser, getObjectLinkFragment } from '@hcengineering/view-resources'
|
import { getCollaborationUser, getObjectLinkFragment } from '@hcengineering/view-resources'
|
||||||
@ -132,7 +132,7 @@
|
|||||||
progress = false
|
progress = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createAttachment (file: File): Promise<{ file: string, type: string } | undefined> {
|
async function createAttachment (file: File): Promise<{ file: Ref<Blob>, type: string } | undefined> {
|
||||||
try {
|
try {
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
const _id: Ref<Attachment> = generateId()
|
const _id: Ref<Attachment> = generateId()
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import { Account, Class, Doc, generateId, Markup, Ref, Space, toIdMap } from '@hcengineering/core'
|
import { Account, Class, Doc, generateId, Markup, Ref, Space, toIdMap, type Blob } from '@hcengineering/core'
|
||||||
import { IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import {
|
import {
|
||||||
createQuery,
|
createQuery,
|
||||||
@ -137,7 +137,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createAttachment (file: File): Promise<{ file: string, type: string } | undefined> {
|
async function createAttachment (file: File): Promise<{ file: Ref<Blob>, type: string } | undefined> {
|
||||||
if (space === undefined || objectId === undefined || _class === undefined) return
|
if (space === undefined || objectId === undefined || _class === undefined) return
|
||||||
try {
|
try {
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
|
@ -13,12 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getFileUrl } from '@hcengineering/presentation'
|
import { getBlobHref } from '@hcengineering/presentation'
|
||||||
import type { Attachment } from '@hcengineering/attachment'
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
|
|
||||||
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||||
|
import type { WithLookup } from '@hcengineering/core'
|
||||||
|
|
||||||
export let value: Attachment
|
export let value: WithLookup<Attachment>
|
||||||
export let preload = true
|
export let preload = true
|
||||||
|
|
||||||
const maxSizeRem = 20
|
const maxSizeRem = 20
|
||||||
@ -57,7 +58,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<video controls width={dimensions.width} height={dimensions.height} preload={preload ? 'auto' : 'none'}>
|
<video controls width={dimensions.width} height={dimensions.height} preload={preload ? 'auto' : 'none'}>
|
||||||
<source src={getFileUrl(value.file, 'full', value.name)} />
|
<source src={getBlobHref(value.$lookup?.file, value.file, value.name)} />
|
||||||
<track kind="captions" label={value.name} />
|
<track kind="captions" label={value.name} />
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<AttachmentPresenter {value} />
|
<AttachmentPresenter {value} />
|
||||||
|
@ -14,13 +14,13 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
import { Doc, getCurrentAccount } from '@hcengineering/core'
|
import { Doc, getCurrentAccount, type WithLookup } from '@hcengineering/core'
|
||||||
import { getFileUrl, getClient } from '@hcengineering/presentation'
|
import { getBlobHref, getClient } from '@hcengineering/presentation'
|
||||||
import { Icon, IconMoreV, showPopup, Menu } from '@hcengineering/ui'
|
import { Icon, IconMoreV, Menu, showPopup } from '@hcengineering/ui'
|
||||||
import FileDownload from './icons/FileDownload.svelte'
|
|
||||||
import { AttachmentGalleryPresenter } from '..'
|
import { AttachmentGalleryPresenter } from '..'
|
||||||
|
import FileDownload from './icons/FileDownload.svelte'
|
||||||
|
|
||||||
export let attachments: Attachment[]
|
export let attachments: WithLookup<Attachment>[]
|
||||||
let selectedFileNumber: number | undefined
|
let selectedFileNumber: number | undefined
|
||||||
const myAccId = getCurrentAccount()._id
|
const myAccId = getCurrentAccount()._id
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
@ -57,7 +57,10 @@
|
|||||||
<AttachmentGalleryPresenter value={attachment}>
|
<AttachmentGalleryPresenter value={attachment}>
|
||||||
<svelte:fragment slot="rowMenu">
|
<svelte:fragment slot="rowMenu">
|
||||||
<div class="eAttachmentCellActions" class:fixed={i === selectedFileNumber}>
|
<div class="eAttachmentCellActions" class:fixed={i === selectedFileNumber}>
|
||||||
<a href={getFileUrl(attachment.file, 'full', attachment.name)} download={attachment.name}>
|
<a
|
||||||
|
href={getBlobHref(attachment.$lookup?.file, attachment.file, attachment.name)}
|
||||||
|
download={attachment.name}
|
||||||
|
>
|
||||||
<Icon icon={FileDownload} size={'small'} />
|
<Icon icon={FileDownload} size={'small'} />
|
||||||
</a>
|
</a>
|
||||||
<div class="eAttachmentCellMenu" on:click={(event) => showFileMenu(event, attachment, i)}>
|
<div class="eAttachmentCellMenu" on:click={(event) => showFileMenu(event, attachment, i)}>
|
||||||
|
@ -14,13 +14,13 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
import { Doc, getCurrentAccount } from '@hcengineering/core'
|
import { Doc, getCurrentAccount, type WithLookup } from '@hcengineering/core'
|
||||||
import { getFileUrl, getClient } from '@hcengineering/presentation'
|
import { getFileUrl, getClient, getBlobHref } from '@hcengineering/presentation'
|
||||||
import { Icon, IconMoreV, showPopup, Menu } from '@hcengineering/ui'
|
import { Icon, IconMoreV, showPopup, Menu } from '@hcengineering/ui'
|
||||||
import FileDownload from './icons/FileDownload.svelte'
|
import FileDownload from './icons/FileDownload.svelte'
|
||||||
import { AttachmentPresenter } from '..'
|
import { AttachmentPresenter } from '..'
|
||||||
|
|
||||||
export let attachments: Attachment[]
|
export let attachments: WithLookup<Attachment>[]
|
||||||
let selectedFileNumber: number | undefined
|
let selectedFileNumber: number | undefined
|
||||||
const myAccId = getCurrentAccount()._id
|
const myAccId = getCurrentAccount()._id
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
@ -56,7 +56,7 @@
|
|||||||
<AttachmentPresenter value={attachment} />
|
<AttachmentPresenter value={attachment} />
|
||||||
</div>
|
</div>
|
||||||
<div class="eAttachmentRowActions" class:fixed={i === selectedFileNumber}>
|
<div class="eAttachmentRowActions" class:fixed={i === selectedFileNumber}>
|
||||||
<a href={getFileUrl(attachment.file, 'full', attachment.name)} download={attachment.name}>
|
<a href={getBlobHref(attachment.$lookup?.file, attachment.file, attachment.name)} download={attachment.name}>
|
||||||
<Icon icon={FileDownload} size={'small'} />
|
<Icon icon={FileDownload} size={'small'} />
|
||||||
</a>
|
</a>
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
@ -14,19 +14,20 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Attachment } from '@hcengineering/attachment'
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
import { getFileUrl } from '@hcengineering/presentation'
|
import { getBlobHref, getFileUrl } from '@hcengineering/presentation'
|
||||||
import { CircleButton, Progress } from '@hcengineering/ui'
|
import { CircleButton, Progress } from '@hcengineering/ui'
|
||||||
import Play from './icons/Play.svelte'
|
import Play from './icons/Play.svelte'
|
||||||
import Pause from './icons/Pause.svelte'
|
import Pause from './icons/Pause.svelte'
|
||||||
|
import type { WithLookup } from '@hcengineering/core'
|
||||||
|
|
||||||
export let value: Attachment
|
export let value: WithLookup<Attachment>
|
||||||
export let fullSize = false
|
export let fullSize = false
|
||||||
|
|
||||||
let time = 0
|
let time = 0
|
||||||
let duration = Number.POSITIVE_INFINITY
|
let duration = Number.POSITIVE_INFINITY
|
||||||
let paused = true
|
let paused = true
|
||||||
|
|
||||||
function buttonClick () {
|
function buttonClick (): void {
|
||||||
paused = !paused
|
paused = !paused
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<audio bind:duration bind:currentTime={time} bind:paused>
|
<audio bind:duration bind:currentTime={time} bind:paused>
|
||||||
<source src={getFileUrl(value.file, 'full', value.name)} type={value.type} />
|
<source src={getBlobHref(value.$lookup?.file, value.file, value.name)} type={value.type} />
|
||||||
</audio>
|
</audio>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
112
plugins/attachment-resources/src/components/MediaViewer.svelte
Normal file
112
plugins/attachment-resources/src/components/MediaViewer.svelte
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2020 Anticrm Platform Contributors.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
// import { Doc } from '@hcengineering/core'
|
||||||
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
|
import type { WithLookup } from '@hcengineering/core'
|
||||||
|
import presentation, { ActionContext, IconDownload, getBlobHref } from '@hcengineering/presentation'
|
||||||
|
import { Button, Dialog } from '@hcengineering/ui'
|
||||||
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
|
import { getType } from '../utils'
|
||||||
|
import AudioPlayer from './AudioPlayer.svelte'
|
||||||
|
|
||||||
|
export let value: WithLookup<Attachment>
|
||||||
|
export let showIcon = true
|
||||||
|
export let fullSize = false
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
function iconLabel (name: string): string {
|
||||||
|
const parts = name.split('.')
|
||||||
|
const ext = parts[parts.length - 1]
|
||||||
|
return ext.substring(0, 4).toUpperCase()
|
||||||
|
}
|
||||||
|
onMount(() => {
|
||||||
|
if (fullSize) {
|
||||||
|
dispatch('fullsize')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let download: HTMLAnchorElement
|
||||||
|
$: type = getType(value.type)
|
||||||
|
$: src = getBlobHref(value.$lookup?.file, value.file, value.name)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ActionContext context={{ mode: 'browser' }} />
|
||||||
|
<Dialog
|
||||||
|
isFullSize
|
||||||
|
on:fullsize
|
||||||
|
on:close={() => {
|
||||||
|
dispatch('close')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="title">
|
||||||
|
<div class="antiTitle icon-wrapper">
|
||||||
|
{#if showIcon}
|
||||||
|
<div class="wrapped-icon">
|
||||||
|
<div class="flex-center icon">
|
||||||
|
{iconLabel(value.name)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<span class="wrapped-title">{value.name}</span>
|
||||||
|
</div>
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="utils">
|
||||||
|
<a class="no-line" href={src} download={value.name} bind:this={download}>
|
||||||
|
<Button
|
||||||
|
icon={IconDownload}
|
||||||
|
kind={'ghost'}
|
||||||
|
on:click={() => {
|
||||||
|
download.click()
|
||||||
|
}}
|
||||||
|
showTooltip={{ label: presentation.string.Download }}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
{#if type === 'video'}
|
||||||
|
<video controls preload={'auto'}>
|
||||||
|
<source {src} />
|
||||||
|
<track kind="captions" label={value.name} />
|
||||||
|
</video>
|
||||||
|
{:else if type === 'audio'}
|
||||||
|
<AudioPlayer {value} fullSize={true} />
|
||||||
|
{:else}
|
||||||
|
<iframe class="pdfviewer-content" src={src + '#view=FitH&navpanes=0'} title="" />
|
||||||
|
{/if}
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.icon {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.625rem;
|
||||||
|
color: var(--primary-button-color);
|
||||||
|
background-color: var(--primary-button-default);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -15,10 +15,10 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Photo } from '@hcengineering/attachment'
|
import { Photo } from '@hcengineering/attachment'
|
||||||
import { Class, Doc, Ref, Space } from '@hcengineering/core'
|
import { Class, Doc, Ref, Space, type WithLookup } from '@hcengineering/core'
|
||||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import { FilePreviewPopup, createQuery, getClient, getFileUrl, uploadFile } from '@hcengineering/presentation'
|
import { FilePreviewPopup, createQuery, getBlobHref, getClient, uploadFile } from '@hcengineering/presentation'
|
||||||
import { Button, IconAdd, Label, showPopup, Spinner } from '@hcengineering/ui'
|
import { Button, IconAdd, Label, Spinner, showPopup } from '@hcengineering/ui'
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import UploadDuo from './icons/UploadDuo.svelte'
|
import UploadDuo from './icons/UploadDuo.svelte'
|
||||||
|
|
||||||
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
let inputFile: HTMLInputElement
|
let inputFile: HTMLInputElement
|
||||||
let loading = 0
|
let loading = 0
|
||||||
let images: Photo[] = []
|
let images: WithLookup<Photo>[] = []
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const query = createQuery()
|
const query = createQuery()
|
||||||
@ -42,12 +42,12 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
async function create (file: File) {
|
async function create (file: File): Promise<void> {
|
||||||
if (!file.type.startsWith('image/')) return
|
if (!file.type.startsWith('image/')) return
|
||||||
loading++
|
loading++
|
||||||
try {
|
try {
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
client.addCollection(attachment.class.Photo, space, objectId, _class, 'attachments', {
|
await client.addCollection(attachment.class.Photo, space, objectId, _class, 'attachments', {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
file: uuid,
|
file: uuid,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
@ -55,40 +55,44 @@
|
|||||||
lastModified: file.lastModified
|
lastModified: file.lastModified
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setPlatformStatus(unknownError(err))
|
await setPlatformStatus(unknownError(err))
|
||||||
} finally {
|
} finally {
|
||||||
loading--
|
loading--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileSelected () {
|
function fileSelected (): void {
|
||||||
const list = inputFile.files
|
const list = inputFile.files
|
||||||
if (list === null || list.length === 0) return
|
if (list === null || list.length === 0) return
|
||||||
for (let index = 0; index < list.length; index++) {
|
for (let index = 0; index < list.length; index++) {
|
||||||
const file = list.item(index)
|
const file = list.item(index)
|
||||||
if (file !== null) create(file)
|
if (file !== null) {
|
||||||
|
void create(file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inputFile.value = ''
|
inputFile.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileDrop (e: DragEvent) {
|
function fileDrop (e: DragEvent): void {
|
||||||
const list = e.dataTransfer?.files
|
const list = e.dataTransfer?.files
|
||||||
if (list === undefined || list.length === 0) return
|
if (list === undefined || list.length === 0) return
|
||||||
for (let index = 0; index < list.length; index++) {
|
for (let index = 0; index < list.length; index++) {
|
||||||
const file = list.item(index)
|
const file = list.item(index)
|
||||||
if (file !== null) create(file)
|
if (file !== null) {
|
||||||
|
void create(file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dragover = false
|
let dragover = false
|
||||||
|
|
||||||
function click (ev: Event, item?: Photo): void {
|
function click (ev: Event, item?: WithLookup<Photo>): void {
|
||||||
const el: HTMLElement = ev.currentTarget as HTMLElement
|
const el: HTMLElement = ev.currentTarget as HTMLElement
|
||||||
el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
|
el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
|
||||||
if (item !== undefined) {
|
if (item !== undefined) {
|
||||||
showPopup(
|
showPopup(
|
||||||
FilePreviewPopup,
|
FilePreviewPopup,
|
||||||
{ file: item.file, name: item.name, contentType: item.type },
|
{ file: item.$lookup?.file ?? item.file, name: item.name, contentType: item.type },
|
||||||
item.type.startsWith('image/') ? 'centered' : 'float'
|
item.type.startsWith('image/') ? 'centered' : 'float'
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -145,7 +149,7 @@
|
|||||||
click(ev, image)
|
click(ev, image)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img src={getFileUrl(image.file, 'full', image.name)} alt={image.name} />
|
<img src={getBlobHref(image.$lookup?.file, image.file, image.name)} alt={image.name} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { type Attachment } from '@hcengineering/attachment'
|
import { type Attachment } from '@hcengineering/attachment'
|
||||||
import { type Class, type Data, type Doc, type Ref, type Space, type TxOperations as Client } from '@hcengineering/core'
|
import { type Class, type TxOperations as Client, type Data, type Doc, type Ref, type Space } from '@hcengineering/core'
|
||||||
import { getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
|
||||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
|
import { getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
||||||
|
|
||||||
import attachment from './plugin'
|
import attachment from './plugin'
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { AttachedDoc, Class, Ref } from '@hcengineering/core'
|
import type { AttachedDoc, Blob, Class, Ref } from '@hcengineering/core'
|
||||||
import type { Asset, Plugin } from '@hcengineering/platform'
|
import type { Asset, Plugin } from '@hcengineering/platform'
|
||||||
import { IntlString, plugin, Resource } from '@hcengineering/platform'
|
import { IntlString, plugin, Resource } from '@hcengineering/platform'
|
||||||
import type { Preference } from '@hcengineering/preference'
|
import type { Preference } from '@hcengineering/preference'
|
||||||
@ -26,7 +26,7 @@ import { AnyComponent } from '@hcengineering/ui'
|
|||||||
*/
|
*/
|
||||||
export interface Attachment extends AttachedDoc {
|
export interface Attachment extends AttachedDoc {
|
||||||
name: string
|
name: string
|
||||||
file: string
|
file: Ref<Blob>
|
||||||
size: number
|
size: number
|
||||||
type: string
|
type: string
|
||||||
lastModified: number
|
lastModified: number
|
||||||
@ -78,7 +78,7 @@ export default plugin(attachmentId, {
|
|||||||
SavedAttachments: '' as Ref<Class<SavedAttachments>>
|
SavedAttachments: '' as Ref<Class<SavedAttachments>>
|
||||||
},
|
},
|
||||||
helper: {
|
helper: {
|
||||||
UploadFile: '' as Resource<(file: File) => Promise<string>>,
|
UploadFile: '' as Resource<(file: File) => Promise<Ref<Blob>>>,
|
||||||
DeleteFile: '' as Resource<(id: string) => Promise<void>>
|
DeleteFile: '' as Resource<(id: string) => Promise<void>>
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
import contact, { Channel, combineName, Contact, Employee, PersonAccount } from '@hcengineering/contact'
|
import contact, { AvatarType, Channel, combineName, Contact, Employee, PersonAccount } from '@hcengineering/contact'
|
||||||
import core, {
|
import core, {
|
||||||
Account,
|
Account,
|
||||||
AccountRole,
|
AccountRole,
|
||||||
@ -21,7 +21,8 @@ import core, {
|
|||||||
Timestamp,
|
Timestamp,
|
||||||
TxOperations,
|
TxOperations,
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
WithLookup
|
WithLookup,
|
||||||
|
type Blob as PlatformBlob
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import gmail, { Message } from '@hcengineering/gmail'
|
import gmail, { Message } from '@hcengineering/gmail'
|
||||||
import recruit from '@hcengineering/recruit'
|
import recruit from '@hcengineering/recruit'
|
||||||
@ -242,7 +243,7 @@ export async function syncDocument (
|
|||||||
body: data
|
body: data
|
||||||
})
|
})
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
const uuid = await resp.text()
|
const uuid = (await resp.text()) as Ref<PlatformBlob>
|
||||||
|
|
||||||
ed.file = uuid
|
ed.file = uuid
|
||||||
ed._id = attachmentId as Ref<Attachment & BitrixSyncDoc>
|
ed._id = attachmentId as Ref<Attachment & BitrixSyncDoc>
|
||||||
@ -812,7 +813,7 @@ async function downloadComments (
|
|||||||
attachedToClass: c._class,
|
attachedToClass: c._class,
|
||||||
bitrixId: `attach-${v.id}`,
|
bitrixId: `attach-${v.id}`,
|
||||||
collection: 'attachments',
|
collection: 'attachments',
|
||||||
file: '',
|
file: '' as Ref<PlatformBlob>,
|
||||||
lastModified: Date.now(),
|
lastModified: Date.now(),
|
||||||
modifiedBy: userList.get(it.AUTHOR_ID) ?? core.account.System,
|
modifiedBy: userList.get(it.AUTHOR_ID) ?? core.account.System,
|
||||||
modifiedOn: new Date(it.CREATED ?? new Date().toString()).getTime(),
|
modifiedOn: new Date(it.CREATED ?? new Date().toString()).getTime(),
|
||||||
@ -941,7 +942,8 @@ async function synchronizeUsers (
|
|||||||
if (accountId === undefined) {
|
if (accountId === undefined) {
|
||||||
const employeeId = await ops.client.createDoc(contact.class.Person, contact.space.Contacts, {
|
const employeeId = await ops.client.createDoc(contact.class.Person, contact.space.Contacts, {
|
||||||
name: combineName(u.NAME, u.LAST_NAME),
|
name: combineName(u.NAME, u.LAST_NAME),
|
||||||
avatar: u.PERSONAL_PHOTO,
|
avatarType: AvatarType.EXTERNAL,
|
||||||
|
avatarProps: { url: u.PERSONAL_PHOTO },
|
||||||
city: u.PERSONAL_CITY
|
city: u.PERSONAL_CITY
|
||||||
})
|
})
|
||||||
await ops.client.createMixin(employeeId, contact.class.Person, contact.space.Contacts, contact.mixin.Employee, {
|
await ops.client.createMixin(employeeId, contact.class.Person, contact.space.Contacts, contact.mixin.Employee, {
|
||||||
|
@ -14,7 +14,8 @@ import core, {
|
|||||||
Space,
|
Space,
|
||||||
TxOperations,
|
TxOperations,
|
||||||
WithLookup,
|
WithLookup,
|
||||||
generateId
|
generateId,
|
||||||
|
type Blob as PlatformBlob
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { Message } from '@hcengineering/gmail'
|
import { Message } from '@hcengineering/gmail'
|
||||||
import recruit, { Applicant, Candidate, Vacancy } from '@hcengineering/recruit'
|
import recruit, { Applicant, Candidate, Vacancy } from '@hcengineering/recruit'
|
||||||
@ -561,7 +562,7 @@ export async function convert (
|
|||||||
const attachDoc: Attachment & BitrixSyncDoc = {
|
const attachDoc: Attachment & BitrixSyncDoc = {
|
||||||
_id: generateId(),
|
_id: generateId(),
|
||||||
bitrixId: `${blobRef.id}`,
|
bitrixId: `${blobRef.id}`,
|
||||||
file: '', // Empty since not uploaded yet.
|
file: '' as Ref<PlatformBlob>, // Empty since not uploaded yet.
|
||||||
name: blobRef.id,
|
name: blobRef.id,
|
||||||
size: -1,
|
size: -1,
|
||||||
type: 'application/octet-stream',
|
type: 'application/octet-stream',
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
on:click={() => onClick(p)}
|
on:click={() => onClick(p)}
|
||||||
>
|
>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Avatar size={'x-small'} avatar={p.avatar} name={p.name} />
|
<Avatar size={'x-small'} person={p} name={p.name} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
{#if persons.length === 1}
|
{#if persons.length === 1}
|
||||||
<Avatar
|
<Avatar
|
||||||
avatar={persons[0].avatar}
|
person={persons[0]}
|
||||||
size={avatarSize}
|
size={avatarSize}
|
||||||
name={persons[0].name}
|
name={persons[0].name}
|
||||||
{showStatus}
|
{showStatus}
|
||||||
@ -66,7 +66,7 @@
|
|||||||
{#if persons.length > 1 && size === 'medium'}
|
{#if persons.length > 1 && size === 'medium'}
|
||||||
<div class="group">
|
<div class="group">
|
||||||
{#each persons.slice(0, visiblePersons - 1) as person}
|
{#each persons.slice(0, visiblePersons - 1) as person}
|
||||||
<Avatar avatar={person.avatar} size="tiny" name={person.name} />
|
<Avatar {person} size="tiny" name={person.name} />
|
||||||
{/each}
|
{/each}
|
||||||
{#if persons.length > visiblePersons}
|
{#if persons.length > visiblePersons}
|
||||||
<div class="rect">
|
<div class="rect">
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
import { AttachmentPresenter, FileDownload } from '@hcengineering/attachment-resources'
|
import { AttachmentPresenter, FileDownload } from '@hcengineering/attachment-resources'
|
||||||
import { ChunterSpace } from '@hcengineering/chunter'
|
import { ChunterSpace } from '@hcengineering/chunter'
|
||||||
import { Doc, SortingOrder, getCurrentAccount } from '@hcengineering/core'
|
import { Doc, SortingOrder, getCurrentAccount, type WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery, getClient, getFileUrl } from '@hcengineering/presentation'
|
import { createQuery, getBlobHref, getClient } from '@hcengineering/presentation'
|
||||||
import { Icon, IconMoreV, Label, Menu, getCurrentResolvedLocation, navigate, showPopup } from '@hcengineering/ui'
|
import { Icon, IconMoreV, Label, Menu, getCurrentResolvedLocation, navigate, showPopup } from '@hcengineering/ui'
|
||||||
|
|
||||||
export let channel: ChunterSpace | undefined
|
export let channel: ChunterSpace | undefined
|
||||||
@ -26,7 +26,7 @@
|
|||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
const query = createQuery()
|
const query = createQuery()
|
||||||
let visibleAttachments: Attachment[] | undefined
|
let visibleAttachments: WithLookup<Attachment>[] | undefined
|
||||||
let totalAttachments = 0
|
let totalAttachments = 0
|
||||||
const ATTACHEMNTS_LIMIT = 5
|
const ATTACHEMNTS_LIMIT = 5
|
||||||
let selectedRowNumber: number | undefined
|
let selectedRowNumber: number | undefined
|
||||||
@ -83,7 +83,10 @@
|
|||||||
<AttachmentPresenter value={attachment} />
|
<AttachmentPresenter value={attachment} />
|
||||||
</div>
|
</div>
|
||||||
<div class="eAttachmentRowActions" class:fixed={i === selectedRowNumber}>
|
<div class="eAttachmentRowActions" class:fixed={i === selectedRowNumber}>
|
||||||
<a href={getFileUrl(attachment.file, 'full', attachment.name)} download={attachment.name}>
|
<a
|
||||||
|
href={getBlobHref(attachment.$lookup?.file, attachment.file, attachment.name)}
|
||||||
|
download={attachment.name}
|
||||||
|
>
|
||||||
<Icon icon={FileDownload} size={'small'} />
|
<Icon icon={FileDownload} size={'small'} />
|
||||||
</a>
|
</a>
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
import contact, { AvatarProvider } from '@hcengineering/contact'
|
import contact, { AvatarProvider, getAvatarColorForId, type AvatarInfo } from '@hcengineering/contact'
|
||||||
import { Client, Ref } from '@hcengineering/core'
|
import { Ref, type Data, type WithLookup } from '@hcengineering/core'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient, sizeToWidth } from '@hcengineering/presentation'
|
||||||
|
|
||||||
const providers = new Map<string, AvatarProvider | null>()
|
const providers = new Map<string, AvatarProvider | null>()
|
||||||
|
|
||||||
async function getProvider (client: Client, providerId: Ref<AvatarProvider>): Promise<AvatarProvider | undefined> {
|
async function getProvider (providerId: Ref<AvatarProvider>): Promise<AvatarProvider | undefined> {
|
||||||
const p = providers.get(providerId)
|
const p = providers.get(providerId)
|
||||||
if (p !== undefined) {
|
if (p !== undefined) {
|
||||||
return p ?? undefined
|
return p ?? undefined
|
||||||
@ -31,7 +31,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AvatarType, getAvatarProviderId, getFirstName, getLastName } from '@hcengineering/contact'
|
import { getAvatarProviderId, getFirstName, getLastName } from '@hcengineering/contact'
|
||||||
|
import { Account } from '@hcengineering/core'
|
||||||
import { Asset, getMetadata, getResource } from '@hcengineering/platform'
|
import { Asset, getMetadata, getResource } from '@hcengineering/platform'
|
||||||
import { getBlobURL, reduceCalls } from '@hcengineering/presentation'
|
import { getBlobURL, reduceCalls } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
@ -44,11 +45,10 @@
|
|||||||
themeStore
|
themeStore
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { Account } from '@hcengineering/core'
|
|
||||||
import AvatarInstance from './AvatarInstance.svelte'
|
|
||||||
import { loadUsersStatus, statusByUserStore } from '../utils'
|
import { loadUsersStatus, statusByUserStore } from '../utils'
|
||||||
|
import AvatarInstance from './AvatarInstance.svelte'
|
||||||
|
|
||||||
export let avatar: string | null | undefined = undefined
|
export let person: Data<WithLookup<AvatarInfo>> | undefined = undefined
|
||||||
export let name: string | null | undefined = undefined
|
export let name: string | null | undefined = undefined
|
||||||
export let direct: Blob | undefined = undefined
|
export let direct: Blob | undefined = undefined
|
||||||
export let size: IconSize
|
export let size: IconSize
|
||||||
@ -63,7 +63,9 @@
|
|||||||
avatarInst.pulse()
|
avatarInst.pulse()
|
||||||
}
|
}
|
||||||
|
|
||||||
let url: string[] | undefined
|
let url: string | undefined
|
||||||
|
let srcSet: string | undefined
|
||||||
|
|
||||||
let avatarProvider: AvatarProvider | undefined
|
let avatarProvider: AvatarProvider | undefined
|
||||||
let color: ColorDefinition | undefined = undefined
|
let color: ColorDefinition | undefined = undefined
|
||||||
let element: HTMLElement
|
let element: HTMLElement
|
||||||
@ -86,26 +88,28 @@
|
|||||||
|
|
||||||
const update = reduceCalls(async function (
|
const update = reduceCalls(async function (
|
||||||
size: IconSize,
|
size: IconSize,
|
||||||
avatar?: string | null,
|
avatar?: Data<WithLookup<AvatarInfo>>,
|
||||||
direct?: Blob,
|
direct?: Blob,
|
||||||
name?: string | null
|
name?: string | null
|
||||||
) {
|
) {
|
||||||
|
const width = sizeToWidth(size)
|
||||||
if (direct !== undefined) {
|
if (direct !== undefined) {
|
||||||
const blobURL = await getBlobURL(direct)
|
const blobURL = await getBlobURL(direct)
|
||||||
url = [blobURL]
|
url = blobURL
|
||||||
avatarProvider = undefined
|
avatarProvider = undefined
|
||||||
} else if (avatar) {
|
} else if (avatar != null) {
|
||||||
const avatarProviderId = getAvatarProviderId(avatar)
|
const avatarProviderId = getAvatarProviderId(avatar.avatarType)
|
||||||
avatarProvider = avatarProviderId && (await getProvider(getClient(), avatarProviderId))
|
avatarProvider = avatarProviderId !== undefined ? await getProvider(avatarProviderId) : undefined
|
||||||
|
|
||||||
if (!avatarProvider || avatarProvider.type === AvatarType.COLOR) {
|
if (avatarProvider === undefined) {
|
||||||
url = undefined
|
url = undefined
|
||||||
color = getPlatformAvatarColorByName(avatar.split('://')[1], $themeStore.dark)
|
color = getPlatformAvatarColorByName(
|
||||||
} else if (avatarProvider?.type === AvatarType.IMAGE) {
|
avatar.avatarProps?.color ?? getAvatarColorForId(displayName),
|
||||||
url = (await getResource(avatarProvider.getUrl))(avatar, size)
|
$themeStore.dark
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
const uri = avatar.split('://')[1]
|
;({ url, srcSet, color } = (await getResource(avatarProvider.getUrl))(avatar, displayName, width))
|
||||||
url = (await getResource(avatarProvider.getUrl))(uri, size)
|
console.log(url, srcSet, color)
|
||||||
}
|
}
|
||||||
} else if (name != null) {
|
} else if (name != null) {
|
||||||
color = getPlatformAvatarColorForTextDef(name, $themeStore.dark)
|
color = getPlatformAvatarColorForTextDef(name, $themeStore.dark)
|
||||||
@ -116,15 +120,13 @@
|
|||||||
avatarProvider = undefined
|
avatarProvider = undefined
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
$: void update(size, avatar, direct, name)
|
$: void update(size, person, direct, name)
|
||||||
|
|
||||||
$: srcset = url?.slice(1)?.join(', ')
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
loadUsersStatus()
|
loadUsersStatus()
|
||||||
})
|
})
|
||||||
|
|
||||||
$: userStatus = account ? $statusByUserStore.get(account) : undefined
|
$: userStatus = account !== undefined ? $statusByUserStore.get(account) : undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showStatus && account}
|
{#if showStatus && account}
|
||||||
@ -132,7 +134,7 @@
|
|||||||
<AvatarInstance
|
<AvatarInstance
|
||||||
bind:this={avatarInst}
|
bind:this={avatarInst}
|
||||||
{url}
|
{url}
|
||||||
{srcset}
|
srcset={srcSet}
|
||||||
{displayName}
|
{displayName}
|
||||||
{size}
|
{size}
|
||||||
{icon}
|
{icon}
|
||||||
@ -155,7 +157,7 @@
|
|||||||
<AvatarInstance
|
<AvatarInstance
|
||||||
bind:this={avatarInst}
|
bind:this={avatarInst}
|
||||||
{url}
|
{url}
|
||||||
{srcset}
|
srcset={srcSet}
|
||||||
{displayName}
|
{displayName}
|
||||||
{size}
|
{size}
|
||||||
{icon}
|
{icon}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import { AnySvelteComponent, ColorDefinition, Icon, IconSize, resizeObserver } from '@hcengineering/ui'
|
import { AnySvelteComponent, ColorDefinition, Icon, IconSize, resizeObserver } from '@hcengineering/ui'
|
||||||
import AvatarIcon from './icons/Avatar.svelte'
|
import AvatarIcon from './icons/Avatar.svelte'
|
||||||
|
|
||||||
export let url: string[] | undefined
|
export let url: string | undefined
|
||||||
export let srcset: string | undefined
|
export let srcset: string | undefined
|
||||||
export let displayName: string
|
export let displayName: string
|
||||||
export let size: IconSize
|
export let size: IconSize
|
||||||
@ -86,7 +86,7 @@
|
|||||||
style:background-color={color && !url ? color.icon : 'var(--theme-button-default)'}
|
style:background-color={color && !url ? color.icon : 'var(--theme-button-default)'}
|
||||||
>
|
>
|
||||||
{#if url}
|
{#if url}
|
||||||
<img class="hulyAvatarSize-{size} ava-image" src={url[0]} {srcset} alt={''} />
|
<img class="hulyAvatarSize-{size} ava-image" src={url} {srcset} alt={''} />
|
||||||
{:else if displayName && displayName !== ''}
|
{:else if displayName && displayName !== ''}
|
||||||
<div
|
<div
|
||||||
class="ava-text"
|
class="ava-text"
|
||||||
|
51
plugins/contact-resources/src/components/AvatarRef.svelte
Normal file
51
plugins/contact-resources/src/components/AvatarRef.svelte
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Anticrm Platform Contributors.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import contact, { type Contact, type Employee } from '@hcengineering/contact'
|
||||||
|
import core, { Account, type Ref, type WithLookup } from '@hcengineering/core'
|
||||||
|
import { Asset } from '@hcengineering/platform'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { AnySvelteComponent, IconSize } from '@hcengineering/ui'
|
||||||
|
import { employeeByIdStore, personByIdStore } from '../utils'
|
||||||
|
import Avatar from './Avatar.svelte'
|
||||||
|
|
||||||
|
export let _id: Ref<Contact>
|
||||||
|
|
||||||
|
export let name: string | null | undefined = undefined
|
||||||
|
export let size: IconSize
|
||||||
|
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||||
|
export let variant: 'circle' | 'roundedRect' | 'none' = 'roundedRect'
|
||||||
|
export let borderColor: number | undefined = undefined
|
||||||
|
export let standby: boolean = false
|
||||||
|
export let showStatus: boolean = true
|
||||||
|
export let account: Ref<Account> | undefined = undefined
|
||||||
|
|
||||||
|
$: empValue = $employeeByIdStore.get(_id as Ref<Employee>) ?? $personByIdStore.get(_id)
|
||||||
|
|
||||||
|
let _contact: WithLookup<Contact> | undefined
|
||||||
|
|
||||||
|
$: if (empValue === undefined) {
|
||||||
|
void getClient()
|
||||||
|
.findOne(contact.class.Contact, { _id }, { lookup: { avatar: core.class.Blob } })
|
||||||
|
.then((c) => {
|
||||||
|
_contact = c
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
_contact = $employeeByIdStore.get(_id as Ref<Employee>) ?? $personByIdStore.get(_id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Avatar person={_contact} {name} {size} {icon} {variant} {borderColor} {standby} {showStatus} {account} />
|
@ -39,5 +39,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if person}
|
{#if person}
|
||||||
<Avatar bind:this={avatar} {size} avatar={person.avatar} name={person.name} borderColor={user.color} standby />
|
<Avatar bind:this={avatar} {size} {person} name={person.name} borderColor={user.color} standby />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each persons as person, i}
|
{#each persons as person, i}
|
||||||
<div class="combine-avatar {size}" data-over={getDataOver(persons.length === i + 1, items)}>
|
<div class="combine-avatar {size}" data-over={getDataOver(persons.length === i + 1, items)}>
|
||||||
<Avatar avatar={person.avatar} {size} name={person.name} showStatus={false} />
|
<Avatar {person} {size} name={person.name} showStatus={false} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Channel, combineName, Employee, PersonAccount } from '@hcengineering/contact'
|
import { AvatarType, Channel, combineName, Employee, PersonAccount } from '@hcengineering/contact'
|
||||||
import core, { AccountRole, AttachedData, Data, generateId, Ref } from '@hcengineering/core'
|
import core, { AccountRole, AttachedData, Data, generateId, Ref } from '@hcengineering/core'
|
||||||
import login from '@hcengineering/login'
|
import login from '@hcengineering/login'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
@ -44,7 +44,8 @@
|
|||||||
const person: Data<Employee> = {
|
const person: Data<Employee> = {
|
||||||
name: '',
|
name: '',
|
||||||
city: '',
|
city: '',
|
||||||
active: true
|
active: true,
|
||||||
|
avatarType: AvatarType.COLOR
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
@ -56,7 +57,10 @@
|
|||||||
changeEmail()
|
changeEmail()
|
||||||
const name = combineName(firstName, lastName)
|
const name = combineName(firstName, lastName)
|
||||||
person.name = name
|
person.name = name
|
||||||
person.avatar = await avatarEditor.createAvatar()
|
const info = await avatarEditor.createAvatar()
|
||||||
|
person.avatar = info.avatar
|
||||||
|
person.avatarType = info.avatarType
|
||||||
|
person.avatarProps = info.avatarProps
|
||||||
|
|
||||||
await client.createDoc(contact.class.Person, contact.space.Contacts, person, id)
|
await client.createDoc(contact.class.Person, contact.space.Contacts, person, id)
|
||||||
await client.createMixin(id, contact.class.Person, contact.space.Contacts, contact.mixin.Employee, {
|
await client.createMixin(id, contact.class.Person, contact.space.Contacts, contact.mixin.Employee, {
|
||||||
@ -179,7 +183,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<EditableAvatar
|
<EditableAvatar
|
||||||
avatar={person.avatar}
|
{person}
|
||||||
name={combineName(firstName, lastName)}
|
name={combineName(firstName, lastName)}
|
||||||
{email}
|
{email}
|
||||||
size={'large'}
|
size={'large'}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Channel, combineName, Person, PersonAccount } from '@hcengineering/contact'
|
import { AvatarType, Channel, combineName, Person, PersonAccount } from '@hcengineering/contact'
|
||||||
import core, { AccountRole, AttachedData, Data, generateId, Ref } from '@hcengineering/core'
|
import core, { AccountRole, AttachedData, Data, generateId, Ref } from '@hcengineering/core'
|
||||||
import login from '@hcengineering/login'
|
import login from '@hcengineering/login'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
@ -48,7 +48,8 @@
|
|||||||
const name = combineName(firstName, lastName)
|
const name = combineName(firstName, lastName)
|
||||||
const person: Data<Person> = {
|
const person: Data<Person> = {
|
||||||
name,
|
name,
|
||||||
city: ''
|
city: '',
|
||||||
|
avatarType: AvatarType.COLOR
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.createDoc(contact.class.Person, contact.space.Contacts, person, id)
|
await client.createDoc(contact.class.Person, contact.space.Contacts, person, id)
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Channel, combineName, findPerson, Person } from '@hcengineering/contact'
|
import { AvatarType, Channel, combineName, findPerson, Person } from '@hcengineering/contact'
|
||||||
import { AttachedData, Data, generateId } from '@hcengineering/core'
|
import { AttachedData, Data, generateId } from '@hcengineering/core'
|
||||||
import { Card, getClient } from '@hcengineering/presentation'
|
import { Card, getClient } from '@hcengineering/presentation'
|
||||||
import { createFocusManager, EditBox, FocusHandler, IconInfo, Label } from '@hcengineering/ui'
|
import { createFocusManager, EditBox, FocusHandler, IconInfo, Label } from '@hcengineering/ui'
|
||||||
@ -42,10 +42,14 @@
|
|||||||
async function createPerson () {
|
async function createPerson () {
|
||||||
const person: Data<Person> = {
|
const person: Data<Person> = {
|
||||||
name: combineName(firstName, lastName),
|
name: combineName(firstName, lastName),
|
||||||
city: object.city
|
city: object.city,
|
||||||
|
avatarType: AvatarType.COLOR
|
||||||
}
|
}
|
||||||
|
|
||||||
person.avatar = await avatarEditor.createAvatar()
|
const info = await avatarEditor.createAvatar()
|
||||||
|
person.avatar = info.avatar
|
||||||
|
person.avatarType = info.avatarType
|
||||||
|
person.avatarProps = info.avatarProps
|
||||||
|
|
||||||
const personId = await client.createDoc(contact.class.Person, contact.space.Contacts, person, id)
|
const personId = await client.createDoc(contact.class.Person, contact.space.Contacts, person, id)
|
||||||
|
|
||||||
@ -115,12 +119,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<EditableAvatar
|
<EditableAvatar person={object} name={combineName(firstName, lastName)} size={'large'} bind:this={avatarEditor} />
|
||||||
avatar={object.avatar}
|
|
||||||
name={combineName(firstName, lastName)}
|
|
||||||
size={'large'}
|
|
||||||
bind:this={avatarEditor}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<svelte:fragment slot="pool">
|
<svelte:fragment slot="pool">
|
||||||
|
@ -92,9 +92,7 @@
|
|||||||
await avatarEditor.removeAvatar(object.avatar)
|
await avatarEditor.removeAvatar(object.avatar)
|
||||||
}
|
}
|
||||||
const avatar = await avatarEditor.createAvatar()
|
const avatar = await avatarEditor.createAvatar()
|
||||||
await client.update(object, {
|
await client.diffUpdate(object, avatar)
|
||||||
avatar
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const manager = createFocusManager()
|
const manager = createFocusManager()
|
||||||
@ -108,7 +106,7 @@
|
|||||||
{#key object}
|
{#key object}
|
||||||
{#if editable}
|
{#if editable}
|
||||||
<EditableAvatar
|
<EditableAvatar
|
||||||
avatar={object.avatar}
|
person={object}
|
||||||
{email}
|
{email}
|
||||||
size={'x-large'}
|
size={'x-large'}
|
||||||
name={object.name}
|
name={object.name}
|
||||||
@ -116,7 +114,7 @@
|
|||||||
on:done={onAvatarDone}
|
on:done={onAvatarDone}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<Avatar avatar={object.avatar} size={'x-large'} name={object.name} />
|
<Avatar person={object} size={'x-large'} name={object.name} />
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,9 +70,7 @@
|
|||||||
await avatarEditor.removeAvatar(object.avatar)
|
await avatarEditor.removeAvatar(object.avatar)
|
||||||
}
|
}
|
||||||
const avatar = await avatarEditor.createAvatar()
|
const avatar = await avatarEditor.createAvatar()
|
||||||
await client.update(object, {
|
await client.diffUpdate(object, avatar)
|
||||||
avatar
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const manager = createFocusManager()
|
const manager = createFocusManager()
|
||||||
@ -86,7 +84,7 @@
|
|||||||
{#key object}
|
{#key object}
|
||||||
<EditableAvatar
|
<EditableAvatar
|
||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
avatar={object.avatar}
|
person={object}
|
||||||
size={'x-large'}
|
size={'x-large'}
|
||||||
name={object.name}
|
name={object.name}
|
||||||
bind:this={avatarEditor}
|
bind:this={avatarEditor}
|
||||||
|
@ -14,22 +14,16 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import attachment from '@hcengineering/attachment'
|
import attachment from '@hcengineering/attachment'
|
||||||
import { AvatarType } from '@hcengineering/contact'
|
import { AvatarType, type AvatarInfo } from '@hcengineering/contact'
|
||||||
import { Asset, getResource } from '@hcengineering/platform'
|
import { Asset, getResource } from '@hcengineering/platform'
|
||||||
import { uploadFile } from '@hcengineering/presentation'
|
import { AnySvelteComponent, IconSize, showPopup } from '@hcengineering/ui'
|
||||||
import {
|
|
||||||
AnySvelteComponent,
|
|
||||||
IconSize,
|
|
||||||
getPlatformAvatarColorForTextDef,
|
|
||||||
showPopup,
|
|
||||||
themeStore
|
|
||||||
} from '@hcengineering/ui'
|
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
|
import type { Data, Blob as PlatformBlob, Ref, WithLookup } from '@hcengineering/core'
|
||||||
import AvatarComponent from './Avatar.svelte'
|
import AvatarComponent from './Avatar.svelte'
|
||||||
import SelectAvatarPopup from './SelectAvatarPopup.svelte'
|
import SelectAvatarPopup from './SelectAvatarPopup.svelte'
|
||||||
|
|
||||||
export let avatar: string | null | undefined
|
export let person: Data<WithLookup<AvatarInfo>> | undefined
|
||||||
export let name: string | null | undefined = undefined
|
export let name: string | null | undefined = undefined
|
||||||
export let email: string | undefined = undefined
|
export let email: string | undefined = undefined
|
||||||
export let size: IconSize
|
export let size: IconSize
|
||||||
@ -39,30 +33,24 @@
|
|||||||
export let imageOnly: boolean = false
|
export let imageOnly: boolean = false
|
||||||
export let lessCrop: boolean = false
|
export let lessCrop: boolean = false
|
||||||
|
|
||||||
$: [schema, uri] = avatar?.split('://') || []
|
$: selectedAvatarType = person?.avatarType ?? AvatarType.COLOR
|
||||||
|
$: selectedAvatar = person?.avatar
|
||||||
|
$: selectedAvatarProps = person?.avatarProps
|
||||||
|
|
||||||
let selectedAvatarType: AvatarType | undefined
|
export async function createAvatar (): Promise<Data<AvatarInfo>> {
|
||||||
let selectedAvatar: string | null | undefined
|
const result: Data<AvatarInfo> = {
|
||||||
$: selectedAvatarType = avatar?.includes('://')
|
avatarType: selectedAvatarType,
|
||||||
? (schema as AvatarType)
|
avatarProps: selectedAvatarProps,
|
||||||
: avatar === undefined
|
avatar: selectedAvatar
|
||||||
? AvatarType.COLOR
|
|
||||||
: AvatarType.IMAGE
|
|
||||||
$: selectedAvatar = selectedAvatarType === AvatarType.IMAGE ? avatar : uri
|
|
||||||
$: if (selectedAvatar === undefined && selectedAvatarType === AvatarType.COLOR) {
|
|
||||||
selectedAvatar = getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAvatar (): Promise<string | undefined> {
|
|
||||||
if (selectedAvatarType === AvatarType.IMAGE && direct !== undefined) {
|
if (selectedAvatarType === AvatarType.IMAGE && direct !== undefined) {
|
||||||
const uploadFile = await getResource(attachment.helper.UploadFile)
|
const uploadFile = await getResource(attachment.helper.UploadFile)
|
||||||
const file = new File([direct], 'avatar', { type: direct.type })
|
const file = new File([direct], 'avatar', { type: direct.type })
|
||||||
|
|
||||||
return await uploadFile(file)
|
result.avatar = await uploadFile(file)
|
||||||
}
|
|
||||||
if (selectedAvatarType != null && selectedAvatar) {
|
|
||||||
return `${selectedAvatarType}://${selectedAvatar}`
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeAvatar (avatar: string) {
|
export async function removeAvatar (avatar: string) {
|
||||||
@ -72,11 +60,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePopupSubmit (submittedAvatarType?: AvatarType, submittedAvatar?: string, submittedDirect?: Blob) {
|
function handlePopupSubmit (
|
||||||
|
submittedAvatarType: AvatarType,
|
||||||
|
submittedAvatar: Ref<PlatformBlob> | undefined | null,
|
||||||
|
submittedProps: Record<string, any> | undefined,
|
||||||
|
submittedDirect?: Blob
|
||||||
|
) {
|
||||||
selectedAvatarType = submittedAvatarType
|
selectedAvatarType = submittedAvatarType
|
||||||
selectedAvatar = submittedAvatar
|
selectedAvatar = submittedAvatar
|
||||||
|
selectedAvatarProps = submittedProps
|
||||||
direct = submittedDirect
|
direct = submittedDirect
|
||||||
avatar = selectedAvatarType === AvatarType.IMAGE ? selectedAvatar : `${selectedAvatarType}://${selectedAvatar}`
|
|
||||||
dispatch('done')
|
dispatch('done')
|
||||||
}
|
}
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
@ -84,12 +77,10 @@
|
|||||||
async function showSelectionPopup (e: MouseEvent) {
|
async function showSelectionPopup (e: MouseEvent) {
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
showPopup(SelectAvatarPopup, {
|
showPopup(SelectAvatarPopup, {
|
||||||
avatar:
|
avatar: selectedAvatar,
|
||||||
selectedAvatarType === AvatarType.IMAGE
|
selectedAvatarType,
|
||||||
? selectedAvatar
|
selectedAvatarProps,
|
||||||
: selectedAvatarType === AvatarType.COLOR && avatar == null
|
selectedAvatar,
|
||||||
? undefined
|
|
||||||
: `${selectedAvatarType}://${selectedAvatar}`,
|
|
||||||
email,
|
email,
|
||||||
name,
|
name,
|
||||||
file: direct,
|
file: direct,
|
||||||
@ -109,11 +100,11 @@
|
|||||||
{direct}
|
{direct}
|
||||||
{size}
|
{size}
|
||||||
{icon}
|
{icon}
|
||||||
avatar={selectedAvatarType === AvatarType.IMAGE
|
person={{
|
||||||
? selectedAvatar
|
avatarType: selectedAvatarType,
|
||||||
: selectedAvatarType === AvatarType.COLOR && avatar == null
|
avatarProps: selectedAvatarProps,
|
||||||
? undefined
|
avatar: selectedAvatar
|
||||||
: `${selectedAvatarType}://${selectedAvatar}`}
|
}}
|
||||||
{name}
|
{name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
>
|
>
|
||||||
{#if employee}
|
{#if employee}
|
||||||
<div class="flex-col-center pb-2">
|
<div class="flex-col-center pb-2">
|
||||||
<Avatar size={'x-large'} avatar={employee.avatar} name={employee.name} />
|
<Avatar size={'x-large'} person={employee} name={employee.name} />
|
||||||
</div>
|
</div>
|
||||||
<div class="pb-2">{getName(client.getHierarchy(), employee)}</div>
|
<div class="pb-2">{getName(client.getHierarchy(), employee)}</div>
|
||||||
<DocNavLink object={employee}>
|
<DocNavLink object={employee}>
|
||||||
|
@ -107,7 +107,7 @@
|
|||||||
if (_update.avatar !== undefined || sourcePerson.avatar === targetPerson.avatar) {
|
if (_update.avatar !== undefined || sourcePerson.avatar === targetPerson.avatar) {
|
||||||
// We replace avatar, we need to update source with target
|
// We replace avatar, we need to update source with target
|
||||||
await client.update(sourcePerson, {
|
await client.update(sourcePerson, {
|
||||||
avatar: sourcePerson.avatar === targetPerson.avatar ? '' : targetPerson.avatar
|
avatar: sourcePerson.avatar === targetPerson.avatar ? null : targetPerson.avatar
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await client.update(targetPerson, _update)
|
await client.update(targetPerson, _update)
|
||||||
@ -360,7 +360,7 @@
|
|||||||
selected={update.avatar !== undefined}
|
selected={update.avatar !== undefined}
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="item" let:item>
|
<svelte:fragment slot="item" let:item>
|
||||||
<Avatar avatar={item.avatar} size={'x-large'} icon={contact.icon.Person} name={item.name} />
|
<Avatar person={item} size={'x-large'} icon={contact.icon.Person} name={item.name} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</MergeComparer>
|
</MergeComparer>
|
||||||
<MergeComparer
|
<MergeComparer
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
<div class="antiContactCard">
|
<div class="antiContactCard">
|
||||||
<div class="label uppercase"><Label label={contact.string.Organization} /></div>
|
<div class="label uppercase"><Label label={contact.string.Organization} /></div>
|
||||||
<div class="flex-center logo">
|
<div class="flex-center logo">
|
||||||
<Avatar avatar={organization.avatar} size={'large'} icon={contact.icon.Company} />
|
<Avatar person={organization} size={'large'} icon={contact.icon.Company} />
|
||||||
</div>
|
</div>
|
||||||
{#if organization}
|
{#if organization}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
<div class="antiContactCard">
|
<div class="antiContactCard">
|
||||||
<div class="label uppercase"><Label label={contact.string.Person} /></div>
|
<div class="label uppercase"><Label label={contact.string.Person} /></div>
|
||||||
<div class="flex-center logo">
|
<div class="flex-center logo">
|
||||||
<Avatar avatar={object.avatar} size={'large'} icon={contact.icon.Company} name={object.name} />
|
<Avatar person={object} size={'large'} icon={contact.icon.Company} name={object.name} />
|
||||||
</div>
|
</div>
|
||||||
{#if object}
|
{#if object}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
class:mr-2={shouldShowName && !enlargedText}
|
class:mr-2={shouldShowName && !enlargedText}
|
||||||
class:mr-3={shouldShowName && enlargedText}
|
class:mr-3={shouldShowName && enlargedText}
|
||||||
>
|
>
|
||||||
<Avatar size={avatarSize} avatar={value.avatar} name={value.name} {showStatus} account={account?._id} />
|
<Avatar size={avatarSize} person={value} name={value.name} {showStatus} account={account?._id} />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if shouldShowName}
|
{#if shouldShowName}
|
||||||
|
@ -22,5 +22,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
<Avatar avatar={value.avatar} {size} name={value.name} />
|
<Avatar person={value} {size} name={value.name} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -15,62 +15,56 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
import { AvatarType, buildGravatarId, checkHasGravatar } from '@hcengineering/contact'
|
import { AvatarType, buildGravatarId, checkHasGravatar, type AvatarInfo } from '@hcengineering/contact'
|
||||||
|
import type { Ref } from '@hcengineering/core'
|
||||||
|
import { Blob as PlatformBlob } from '@hcengineering/core'
|
||||||
import { Asset } from '@hcengineering/platform'
|
import { Asset } from '@hcengineering/platform'
|
||||||
|
import presentation, { Card, getFileUrl } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
AnySvelteComponent,
|
AnySvelteComponent,
|
||||||
|
ColorDefinition,
|
||||||
Label,
|
Label,
|
||||||
showPopup,
|
|
||||||
TabList,
|
TabList,
|
||||||
eventToHTMLElement,
|
eventToHTMLElement,
|
||||||
getPlatformAvatarColorForTextDef,
|
|
||||||
getPlatformAvatarColorByName,
|
getPlatformAvatarColorByName,
|
||||||
|
getPlatformAvatarColorForTextDef,
|
||||||
getPlatformAvatarColors,
|
getPlatformAvatarColors,
|
||||||
ColorDefinition,
|
showPopup,
|
||||||
themeStore
|
themeStore
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { ColorsPopup } from '@hcengineering/view-resources'
|
import { ColorsPopup } from '@hcengineering/view-resources'
|
||||||
import presentation, { Card, getFileUrl } from '@hcengineering/presentation'
|
|
||||||
import contact from '../plugin'
|
import contact from '../plugin'
|
||||||
import { getAvatarTypeDropdownItems } from '../utils'
|
import { getAvatarTypeDropdownItems } from '../utils'
|
||||||
import AvatarComponent from './Avatar.svelte'
|
import AvatarComponent from './Avatar.svelte'
|
||||||
import EditAvatarPopup from './EditAvatarPopup.svelte'
|
import EditAvatarPopup from './EditAvatarPopup.svelte'
|
||||||
|
|
||||||
export let avatar: string | null | undefined = undefined
|
export let selectedAvatarType: AvatarType
|
||||||
|
export let selectedAvatar: AvatarInfo['avatar']
|
||||||
|
export let selectedAvatarProps: AvatarInfo['avatarProps']
|
||||||
|
|
||||||
|
const initialSelectedAvatarType = selectedAvatarType
|
||||||
|
const initialSelectedAvatar = selectedAvatar
|
||||||
|
const initialSelectedAvatarProps = { ...selectedAvatarProps }
|
||||||
|
|
||||||
export let name: string | null | undefined = undefined
|
export let name: string | null | undefined = undefined
|
||||||
export let email: string | undefined
|
export let email: string | undefined
|
||||||
export let file: Blob | undefined
|
export let file: Blob | undefined
|
||||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||||
export let imageOnly: boolean = false
|
export let imageOnly: boolean = false
|
||||||
export let lessCrop: boolean = false
|
export let lessCrop: boolean = false
|
||||||
export let onSubmit: (avatarType?: AvatarType, avatar?: string, file?: Blob) => void
|
export let onSubmit: (
|
||||||
|
submittedAvatarType: AvatarType,
|
||||||
|
submittedAvatar: Ref<PlatformBlob> | undefined | null,
|
||||||
|
submittedProps: Record<string, any> | undefined,
|
||||||
|
submittedDirect?: Blob
|
||||||
|
) => void
|
||||||
|
|
||||||
const [schema, uri] = avatar?.split('://') || []
|
|
||||||
const colors = getPlatformAvatarColors($themeStore.dark)
|
const colors = getPlatformAvatarColors($themeStore.dark)
|
||||||
let color: ColorDefinition | undefined =
|
let color: ColorDefinition | undefined =
|
||||||
(schema as AvatarType) === AvatarType.COLOR ? getPlatformAvatarColorByName(uri, $themeStore.dark) : undefined
|
(selectedAvatarType as AvatarType) === AvatarType.COLOR
|
||||||
|
? getPlatformAvatarColorByName(selectedAvatarProps?.color ?? '', $themeStore.dark)
|
||||||
|
: undefined
|
||||||
|
|
||||||
const initialSelectedType = (() => {
|
|
||||||
if (file) {
|
|
||||||
return AvatarType.IMAGE
|
|
||||||
}
|
|
||||||
if (!avatar) {
|
|
||||||
return AvatarType.COLOR
|
|
||||||
}
|
|
||||||
|
|
||||||
return avatar.includes('://') ? (schema as AvatarType) : AvatarType.IMAGE
|
|
||||||
})()
|
|
||||||
|
|
||||||
const initialSelectedAvatar = (() => {
|
|
||||||
if (!avatar) {
|
|
||||||
return getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name
|
|
||||||
}
|
|
||||||
|
|
||||||
return avatar.includes('://') ? uri : avatar
|
|
||||||
})()
|
|
||||||
|
|
||||||
let selectedAvatarType: AvatarType = initialSelectedType
|
|
||||||
let selectedAvatar: string = initialSelectedAvatar
|
|
||||||
let selectedFile: Blob | undefined = file
|
let selectedFile: Blob | undefined = file
|
||||||
|
|
||||||
let hasGravatar = false
|
let hasGravatar = false
|
||||||
@ -82,38 +76,43 @@
|
|||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
function submit () {
|
function submit () {
|
||||||
onSubmit(selectedAvatarType, selectedAvatar, selectedAvatarType === AvatarType.IMAGE ? selectedFile : undefined)
|
onSubmit(
|
||||||
|
selectedAvatarType,
|
||||||
|
selectedAvatar,
|
||||||
|
selectedAvatarProps,
|
||||||
|
selectedAvatarType === AvatarType.IMAGE ? selectedFile : undefined
|
||||||
|
)
|
||||||
}
|
}
|
||||||
let inputRef: HTMLInputElement
|
let inputRef: HTMLInputElement
|
||||||
const targetMimes = ['image/png', 'image/jpg', 'image/jpeg']
|
const targetMimes = ['image/png', 'image/jpg', 'image/jpeg']
|
||||||
|
|
||||||
function handleDropdownSelection (e: any) {
|
function handleDropdownSelection (e: any) {
|
||||||
if (selectedAvatarType === AvatarType.GRAVATAR && email) {
|
if (selectedAvatarType === AvatarType.GRAVATAR && email) {
|
||||||
selectedAvatar = buildGravatarId(email)
|
selectedAvatarProps = { url: buildGravatarId(email) }
|
||||||
} else if (selectedAvatarType === AvatarType.IMAGE) {
|
} else if (selectedAvatarType === AvatarType.IMAGE) {
|
||||||
if (selectedFile) {
|
if (selectedFile) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (file) {
|
if (file) {
|
||||||
selectedFile = file
|
selectedFile = file
|
||||||
} else if (avatar && !avatar.includes('://')) {
|
|
||||||
selectedAvatar = avatar
|
|
||||||
} else {
|
} else {
|
||||||
selectedAvatar = ''
|
selectedAvatar = undefined
|
||||||
inputRef.click()
|
inputRef.click()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedAvatar = color ? color.name : getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name
|
selectedAvatarProps = {
|
||||||
|
color: color ? color.name : getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleImageAvatarClick () {
|
async function handleImageAvatarClick (): Promise<void> {
|
||||||
let editableFile: Blob
|
let editableFile: Blob
|
||||||
|
|
||||||
if (selectedFile !== undefined) {
|
if (selectedFile !== undefined) {
|
||||||
editableFile = selectedFile
|
editableFile = selectedFile
|
||||||
} else if (selectedAvatar && !(imageOnly && selectedAvatar === initialSelectedAvatar)) {
|
} else if (selectedAvatar && !(imageOnly && selectedAvatar === initialSelectedAvatar)) {
|
||||||
const url = getFileUrl(selectedAvatar, 'full')
|
const url = getFileUrl(selectedAvatar)
|
||||||
editableFile = await (await fetch(url)).blob()
|
editableFile = await (await fetch(url)).blob()
|
||||||
} else {
|
} else {
|
||||||
inputRef.click()
|
inputRef.click()
|
||||||
@ -122,18 +121,21 @@
|
|||||||
if (editableFile.size > 0) showCropper(editableFile)
|
if (editableFile.size > 0) showCropper(editableFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
function showCropper (editableFile: Blob) {
|
function showCropper (editableFile: Blob): void {
|
||||||
showPopup(EditAvatarPopup, { file: editableFile, lessCrop }, undefined, (blob) => {
|
showPopup(EditAvatarPopup, { file: editableFile, lessCrop }, undefined, (blob) => {
|
||||||
if (blob === undefined) {
|
if (blob === undefined) {
|
||||||
if (!selectedFile && (!avatar || avatar.includes('://'))) {
|
if (!selectedFile && !initialSelectedAvatar) {
|
||||||
selectedAvatarType = AvatarType.COLOR
|
selectedAvatarType = AvatarType.COLOR
|
||||||
selectedAvatar = getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name
|
selectedAvatarProps = { color: getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name }
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (blob === null) {
|
if (blob === null) {
|
||||||
selectedAvatarType = AvatarType.COLOR
|
selectedAvatarType = AvatarType.COLOR
|
||||||
selectedAvatar = imageOnly ? '' : getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name
|
selectedAvatar = undefined
|
||||||
|
selectedAvatarProps = {
|
||||||
|
color: imageOnly ? '' : getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name
|
||||||
|
}
|
||||||
selectedFile = undefined
|
selectedFile = undefined
|
||||||
} else {
|
} else {
|
||||||
selectedFile = blob
|
selectedFile = blob
|
||||||
@ -142,7 +144,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSelectFile (e: any) {
|
function onSelectFile (e: any): void {
|
||||||
const targetFile = e.target?.files[0] as File | undefined
|
const targetFile = e.target?.files[0] as File | undefined
|
||||||
|
|
||||||
if (targetFile === undefined || !targetMimes.includes(targetFile.type)) {
|
if (targetFile === undefined || !targetMimes.includes(targetFile.type)) {
|
||||||
@ -153,13 +155,14 @@
|
|||||||
document.body.onfocus = null
|
document.body.onfocus = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFileSelectionCancel () {
|
function handleFileSelectionCancel (): void {
|
||||||
document.body.onfocus = null
|
document.body.onfocus = null
|
||||||
|
|
||||||
if (!inputRef.value.length) {
|
if (!inputRef.value.length) {
|
||||||
if (!selectedFile) {
|
if (!selectedFile) {
|
||||||
selectedAvatarType = AvatarType.COLOR
|
selectedAvatarType = AvatarType.COLOR
|
||||||
selectedAvatar = getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name
|
selectedAvatar = undefined
|
||||||
|
selectedAvatarProps = { color: getPlatformAvatarColorForTextDef(name ?? '', $themeStore.dark).name }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,14 +173,14 @@
|
|||||||
{
|
{
|
||||||
colors,
|
colors,
|
||||||
columns: 6,
|
columns: 6,
|
||||||
selected: getPlatformAvatarColorByName(selectedAvatar, $themeStore.dark),
|
selected: getPlatformAvatarColorByName(selectedAvatarProps?.color ?? '', $themeStore.dark),
|
||||||
key: 'icon'
|
key: 'icon'
|
||||||
},
|
},
|
||||||
eventToHTMLElement(event),
|
eventToHTMLElement(event),
|
||||||
(col) => {
|
(col) => {
|
||||||
if (col != null) {
|
if (col != null) {
|
||||||
color = colors[col]
|
color = colors[col]
|
||||||
selectedAvatar = color.name
|
selectedAvatarProps = { color: color.name }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -189,10 +192,10 @@
|
|||||||
okLabel={presentation.string.Save}
|
okLabel={presentation.string.Save}
|
||||||
width={'x-small'}
|
width={'x-small'}
|
||||||
accentHeader
|
accentHeader
|
||||||
canSave={selectedAvatarType !== initialSelectedType ||
|
canSave={selectedAvatarType !== initialSelectedAvatarType ||
|
||||||
selectedAvatar !== initialSelectedAvatar ||
|
selectedAvatar !== initialSelectedAvatar ||
|
||||||
selectedFile !== file ||
|
JSON.stringify(initialSelectedAvatarProps) !== JSON.stringify(selectedAvatarProps) ||
|
||||||
!avatar}
|
selectedFile !== file}
|
||||||
okAction={submit}
|
okAction={submit}
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
dispatch('close')
|
dispatch('close')
|
||||||
@ -214,11 +217,11 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AvatarComponent
|
<AvatarComponent
|
||||||
avatar={selectedAvatarType === AvatarType.IMAGE
|
person={{
|
||||||
? selectedAvatar === ''
|
avatarType: selectedAvatarType,
|
||||||
? `${AvatarType.COLOR}://${color?.color}`
|
avatar: selectedAvatar,
|
||||||
: selectedAvatar
|
avatarProps: selectedAvatarProps
|
||||||
: `${selectedAvatarType}://${selectedAvatar}`}
|
}}
|
||||||
direct={selectedAvatarType === AvatarType.IMAGE ? selectedFile : undefined}
|
direct={selectedAvatarType === AvatarType.IMAGE ? selectedFile : undefined}
|
||||||
size={'2x-large'}
|
size={'2x-large'}
|
||||||
{icon}
|
{icon}
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="flex-row-center" on:click>
|
<div class="flex-row-center" on:click>
|
||||||
<Avatar
|
<Avatar
|
||||||
avatar={person.avatar}
|
{person}
|
||||||
size={avatarSize}
|
size={avatarSize}
|
||||||
name={person.name}
|
name={person.name}
|
||||||
on:accent-color
|
on:accent-color
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="flex-row-center" on:click>
|
<div class="flex-row-center" on:click>
|
||||||
<Avatar avatar={value.avatar} {size} {icon} name={value.name} on:accent-color {showStatus} account={account?._id} />
|
<Avatar person={value} {size} {icon} name={value.name} on:accent-color {showStatus} account={account?._id} />
|
||||||
<div class="flex-col min-w-0 {size === 'tiny' || size === 'inline' ? 'ml-1' : 'ml-2'}" class:max-w-20={short}>
|
<div class="flex-col min-w-0 {size === 'tiny' || size === 'inline' ? 'ml-1' : 'ml-2'}" class:max-w-20={short}>
|
||||||
{#if subtitle}<div class="content-dark-color text-sm">{subtitle}</div>{/if}
|
{#if subtitle}<div class="content-dark-color text-sm">{subtitle}</div>{/if}
|
||||||
<div class="label text-left">{getName(client.getHierarchy(), value)}</div>
|
<div class="label text-left">{getName(client.getHierarchy(), value)}</div>
|
||||||
|
@ -14,33 +14,47 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { type Channel, type Contact, getGravatarUrl, getName, type Person } from '@hcengineering/contact'
|
import {
|
||||||
|
getGravatarUrl,
|
||||||
|
getName,
|
||||||
|
type AvatarInfo,
|
||||||
|
type Channel,
|
||||||
|
type Contact,
|
||||||
|
type Person
|
||||||
|
} from '@hcengineering/contact'
|
||||||
import {
|
import {
|
||||||
type Class,
|
type Class,
|
||||||
type Client,
|
type Client,
|
||||||
|
type Data,
|
||||||
type DocumentQuery,
|
type DocumentQuery,
|
||||||
type Ref,
|
type Ref,
|
||||||
type RelatedDocument,
|
type RelatedDocument,
|
||||||
type WithLookup
|
type WithLookup
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import login from '@hcengineering/login'
|
import login from '@hcengineering/login'
|
||||||
import { type IntlString, type Resources, getResource } from '@hcengineering/platform'
|
import { getResource, type IntlString, type Resources } from '@hcengineering/platform'
|
||||||
import { MessageBox, type ObjectSearchResult, getClient, getFileUrl } from '@hcengineering/presentation'
|
import { MessageBox, getBlobHref, getBlobSrcSet, getClient, type ObjectSearchResult } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
|
getPlatformAvatarColorByName,
|
||||||
|
getPlatformAvatarColorForTextDef,
|
||||||
|
getPlatformColorDef,
|
||||||
|
hexColorToNumber,
|
||||||
|
parseURL,
|
||||||
|
showPopup,
|
||||||
|
themeStore,
|
||||||
type AnyComponent,
|
type AnyComponent,
|
||||||
type AnySvelteComponent,
|
type AnySvelteComponent,
|
||||||
type IconSize,
|
type ColorDefinition,
|
||||||
type TooltipAlignment,
|
type TooltipAlignment
|
||||||
getIconSize2x,
|
|
||||||
parseURL,
|
|
||||||
showPopup
|
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import AccountArrayEditor from './components/AccountArrayEditor.svelte'
|
import AccountArrayEditor from './components/AccountArrayEditor.svelte'
|
||||||
import AccountBox from './components/AccountBox.svelte'
|
import AccountBox from './components/AccountBox.svelte'
|
||||||
import AssigneeBox from './components/AssigneeBox.svelte'
|
import AssigneeBox from './components/AssigneeBox.svelte'
|
||||||
import AssigneePopup from './components/AssigneePopup.svelte'
|
import AssigneePopup from './components/AssigneePopup.svelte'
|
||||||
import Avatar from './components/Avatar.svelte'
|
import Avatar from './components/Avatar.svelte'
|
||||||
|
import AvatarRef from './components/AvatarRef.svelte'
|
||||||
import ChannelFilter from './components/ChannelFilter.svelte'
|
import ChannelFilter from './components/ChannelFilter.svelte'
|
||||||
|
import ChannelIcon from './components/ChannelIcon.svelte'
|
||||||
import ChannelPanel from './components/ChannelPanel.svelte'
|
import ChannelPanel from './components/ChannelPanel.svelte'
|
||||||
import ChannelPresenter from './components/ChannelPresenter.svelte'
|
import ChannelPresenter from './components/ChannelPresenter.svelte'
|
||||||
import Channels from './components/Channels.svelte'
|
import Channels from './components/Channels.svelte'
|
||||||
@ -48,6 +62,7 @@ import ChannelsDropdown from './components/ChannelsDropdown.svelte'
|
|||||||
import ChannelsEditor from './components/ChannelsEditor.svelte'
|
import ChannelsEditor from './components/ChannelsEditor.svelte'
|
||||||
import ChannelsPresenter from './components/ChannelsPresenter.svelte'
|
import ChannelsPresenter from './components/ChannelsPresenter.svelte'
|
||||||
import ChannelsView from './components/ChannelsView.svelte'
|
import ChannelsView from './components/ChannelsView.svelte'
|
||||||
|
import CollaborationUserAvatar from './components/CollaborationUserAvatar.svelte'
|
||||||
import CombineAvatars from './components/CombineAvatars.svelte'
|
import CombineAvatars from './components/CombineAvatars.svelte'
|
||||||
import ContactArrayEditor from './components/ContactArrayEditor.svelte'
|
import ContactArrayEditor from './components/ContactArrayEditor.svelte'
|
||||||
import ContactPresenter from './components/ContactPresenter.svelte'
|
import ContactPresenter from './components/ContactPresenter.svelte'
|
||||||
@ -55,18 +70,16 @@ import ContactRefPresenter from './components/ContactRefPresenter.svelte'
|
|||||||
import Contacts from './components/Contacts.svelte'
|
import Contacts from './components/Contacts.svelte'
|
||||||
import ContactsTabs from './components/ContactsTabs.svelte'
|
import ContactsTabs from './components/ContactsTabs.svelte'
|
||||||
import CreateEmployee from './components/CreateEmployee.svelte'
|
import CreateEmployee from './components/CreateEmployee.svelte'
|
||||||
|
import CreateGuest from './components/CreateGuest.svelte'
|
||||||
import CreateOrganization from './components/CreateOrganization.svelte'
|
import CreateOrganization from './components/CreateOrganization.svelte'
|
||||||
import CreatePerson from './components/CreatePerson.svelte'
|
import CreatePerson from './components/CreatePerson.svelte'
|
||||||
import CollaborationUserAvatar from './components/CollaborationUserAvatar.svelte'
|
|
||||||
import DeleteConfirmationPopup from './components/DeleteConfirmationPopup.svelte'
|
import DeleteConfirmationPopup from './components/DeleteConfirmationPopup.svelte'
|
||||||
import EditEmployee from './components/EditEmployee.svelte'
|
import EditEmployee from './components/EditEmployee.svelte'
|
||||||
import EditMember from './components/EditMember.svelte'
|
import EditMember from './components/EditMember.svelte'
|
||||||
import EditOrganization from './components/EditOrganization.svelte'
|
import EditOrganization from './components/EditOrganization.svelte'
|
||||||
|
import EditOrganizationPanel from './components/EditOrganizationPanel.svelte'
|
||||||
import EditPerson from './components/EditPerson.svelte'
|
import EditPerson from './components/EditPerson.svelte'
|
||||||
import EditableAvatar from './components/EditableAvatar.svelte'
|
import EditableAvatar from './components/EditableAvatar.svelte'
|
||||||
import PersonAccountFilterValuePresenter from './components/PersonAccountFilterValuePresenter.svelte'
|
|
||||||
import PersonAccountPresenter from './components/PersonAccountPresenter.svelte'
|
|
||||||
import PersonAccountRefPresenter from './components/PersonAccountRefPresenter.svelte'
|
|
||||||
import EmployeeArrayEditor from './components/EmployeeArrayEditor.svelte'
|
import EmployeeArrayEditor from './components/EmployeeArrayEditor.svelte'
|
||||||
import EmployeeBox from './components/EmployeeBox.svelte'
|
import EmployeeBox from './components/EmployeeBox.svelte'
|
||||||
import EmployeeBrowser from './components/EmployeeBrowser.svelte'
|
import EmployeeBrowser from './components/EmployeeBrowser.svelte'
|
||||||
@ -82,33 +95,34 @@ import MembersPresenter from './components/MembersPresenter.svelte'
|
|||||||
import MergePersons from './components/MergePersons.svelte'
|
import MergePersons from './components/MergePersons.svelte'
|
||||||
import OrganizationEditor from './components/OrganizationEditor.svelte'
|
import OrganizationEditor from './components/OrganizationEditor.svelte'
|
||||||
import OrganizationPresenter from './components/OrganizationPresenter.svelte'
|
import OrganizationPresenter from './components/OrganizationPresenter.svelte'
|
||||||
|
import PersonAccountFilterValuePresenter from './components/PersonAccountFilterValuePresenter.svelte'
|
||||||
|
import PersonAccountPresenter from './components/PersonAccountPresenter.svelte'
|
||||||
|
import PersonAccountRefPresenter from './components/PersonAccountRefPresenter.svelte'
|
||||||
import PersonEditor from './components/PersonEditor.svelte'
|
import PersonEditor from './components/PersonEditor.svelte'
|
||||||
|
import PersonIcon from './components/PersonIcon.svelte'
|
||||||
import PersonPresenter from './components/PersonPresenter.svelte'
|
import PersonPresenter from './components/PersonPresenter.svelte'
|
||||||
import PersonRefPresenter from './components/PersonRefPresenter.svelte'
|
import PersonRefPresenter from './components/PersonRefPresenter.svelte'
|
||||||
import SelectAvatars from './components/SelectAvatars.svelte'
|
import SelectAvatars from './components/SelectAvatars.svelte'
|
||||||
|
import SelectUsersPopup from './components/SelectUsersPopup.svelte'
|
||||||
import SocialEditor from './components/SocialEditor.svelte'
|
import SocialEditor from './components/SocialEditor.svelte'
|
||||||
import SpaceMembers from './components/SpaceMembers.svelte'
|
import SpaceMembers from './components/SpaceMembers.svelte'
|
||||||
|
import SpaceMembersEditor from './components/SpaceMembersEditor.svelte'
|
||||||
|
import SystemAvatar from './components/SystemAvatar.svelte'
|
||||||
import UserBox from './components/UserBox.svelte'
|
import UserBox from './components/UserBox.svelte'
|
||||||
import UserBoxItems from './components/UserBoxItems.svelte'
|
import UserBoxItems from './components/UserBoxItems.svelte'
|
||||||
import UserBoxList from './components/UserBoxList.svelte'
|
import UserBoxList from './components/UserBoxList.svelte'
|
||||||
|
import UserDetails from './components/UserDetails.svelte'
|
||||||
import UserInfo from './components/UserInfo.svelte'
|
import UserInfo from './components/UserInfo.svelte'
|
||||||
|
import UsersList from './components/UsersList.svelte'
|
||||||
import UsersPopup from './components/UsersPopup.svelte'
|
import UsersPopup from './components/UsersPopup.svelte'
|
||||||
import ActivityChannelPresenter from './components/activity/ActivityChannelPresenter.svelte'
|
import ActivityChannelPresenter from './components/activity/ActivityChannelPresenter.svelte'
|
||||||
|
import NameChangedActivityMessage from './components/activity/NameChangedActivityMessage.svelte'
|
||||||
|
import TxNameChange from './components/activity/TxNameChange.svelte'
|
||||||
|
import IconAddMember from './components/icons/AddMember.svelte'
|
||||||
import ExpandRightDouble from './components/icons/ExpandRightDouble.svelte'
|
import ExpandRightDouble from './components/icons/ExpandRightDouble.svelte'
|
||||||
import IconMembers from './components/icons/Members.svelte'
|
import IconMembers from './components/icons/Members.svelte'
|
||||||
import TxNameChange from './components/activity/TxNameChange.svelte'
|
|
||||||
import NameChangedActivityMessage from './components/activity/NameChangedActivityMessage.svelte'
|
|
||||||
import SystemAvatar from './components/SystemAvatar.svelte'
|
|
||||||
import PersonIcon from './components/PersonIcon.svelte'
|
|
||||||
import UsersList from './components/UsersList.svelte'
|
|
||||||
import SelectUsersPopup from './components/SelectUsersPopup.svelte'
|
|
||||||
import IconAddMember from './components/icons/AddMember.svelte'
|
|
||||||
import UserDetails from './components/UserDetails.svelte'
|
|
||||||
import EditOrganizationPanel from './components/EditOrganizationPanel.svelte'
|
|
||||||
import ChannelIcon from './components/ChannelIcon.svelte'
|
|
||||||
import CreateGuest from './components/CreateGuest.svelte'
|
|
||||||
import SpaceMembersEditor from './components/SpaceMembersEditor.svelte'
|
|
||||||
|
|
||||||
|
import { get } from 'svelte/store'
|
||||||
import contact from './plugin'
|
import contact from './plugin'
|
||||||
import {
|
import {
|
||||||
channelIdentifierProvider,
|
channelIdentifierProvider,
|
||||||
@ -133,53 +147,54 @@ import {
|
|||||||
export * from './utils'
|
export * from './utils'
|
||||||
export { employeeByIdStore, employeesStore } from './utils'
|
export { employeeByIdStore, employeesStore } from './utils'
|
||||||
export {
|
export {
|
||||||
Channels,
|
|
||||||
ChannelsEditor,
|
|
||||||
ContactRefPresenter,
|
|
||||||
ContactPresenter,
|
|
||||||
ChannelsView,
|
|
||||||
ChannelsDropdown,
|
|
||||||
EmployeePresenter,
|
|
||||||
PersonPresenter,
|
|
||||||
OrganizationPresenter,
|
|
||||||
EmployeeBrowser,
|
|
||||||
MemberPresenter,
|
|
||||||
EmployeeArrayEditor,
|
|
||||||
EmployeeEditor,
|
|
||||||
PersonAccountRefPresenter,
|
|
||||||
PersonAccountPresenter,
|
|
||||||
MembersPresenter,
|
|
||||||
EditPerson,
|
|
||||||
EmployeeRefPresenter,
|
|
||||||
AccountArrayEditor,
|
AccountArrayEditor,
|
||||||
AccountBox,
|
AccountBox,
|
||||||
CreateOrganization,
|
|
||||||
ExpandRightDouble,
|
|
||||||
EditableAvatar,
|
|
||||||
UserBox,
|
|
||||||
AssigneeBox,
|
AssigneeBox,
|
||||||
AssigneePopup,
|
AssigneePopup,
|
||||||
Avatar,
|
Avatar,
|
||||||
UsersPopup,
|
AvatarRef,
|
||||||
EmployeeBox,
|
Channels,
|
||||||
UserBoxList,
|
ChannelsDropdown,
|
||||||
Members,
|
ChannelsEditor,
|
||||||
SpaceMembers,
|
ChannelsView,
|
||||||
CombineAvatars,
|
CombineAvatars,
|
||||||
UserInfo,
|
ContactPresenter,
|
||||||
IconMembers,
|
ContactRefPresenter,
|
||||||
SelectAvatars,
|
CreateGuest,
|
||||||
UserBoxItems,
|
CreateOrganization,
|
||||||
MembersBox,
|
|
||||||
PersonRefPresenter,
|
|
||||||
SystemAvatar,
|
|
||||||
PersonIcon,
|
|
||||||
UsersList,
|
|
||||||
SelectUsersPopup,
|
|
||||||
IconAddMember,
|
|
||||||
UserDetails,
|
|
||||||
DeleteConfirmationPopup,
|
DeleteConfirmationPopup,
|
||||||
CreateGuest
|
EditPerson,
|
||||||
|
EditableAvatar,
|
||||||
|
EmployeeArrayEditor,
|
||||||
|
EmployeeBox,
|
||||||
|
EmployeeBrowser,
|
||||||
|
EmployeeEditor,
|
||||||
|
EmployeePresenter,
|
||||||
|
EmployeeRefPresenter,
|
||||||
|
ExpandRightDouble,
|
||||||
|
IconAddMember,
|
||||||
|
IconMembers,
|
||||||
|
MemberPresenter,
|
||||||
|
Members,
|
||||||
|
MembersBox,
|
||||||
|
MembersPresenter,
|
||||||
|
OrganizationPresenter,
|
||||||
|
PersonAccountPresenter,
|
||||||
|
PersonAccountRefPresenter,
|
||||||
|
PersonIcon,
|
||||||
|
PersonPresenter,
|
||||||
|
PersonRefPresenter,
|
||||||
|
SelectAvatars,
|
||||||
|
SelectUsersPopup,
|
||||||
|
SpaceMembers,
|
||||||
|
SystemAvatar,
|
||||||
|
UserBox,
|
||||||
|
UserBoxItems,
|
||||||
|
UserBoxList,
|
||||||
|
UserDetails,
|
||||||
|
UserInfo,
|
||||||
|
UsersList,
|
||||||
|
UsersPopup
|
||||||
}
|
}
|
||||||
|
|
||||||
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({
|
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({
|
||||||
@ -287,6 +302,18 @@ export interface PersonLabelTooltip {
|
|||||||
props?: any
|
props?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPersonColor (person: Data<WithLookup<AvatarInfo>>, name: string): ColorDefinition {
|
||||||
|
const dark = get(themeStore).dark
|
||||||
|
|
||||||
|
if (person.avatarProps?.color !== undefined) {
|
||||||
|
if (person.avatarProps?.color?.startsWith('#')) {
|
||||||
|
return getPlatformColorDef(hexColorToNumber(person.avatarProps?.color), dark)
|
||||||
|
}
|
||||||
|
return getPlatformAvatarColorByName(person.avatarProps?.color, dark)
|
||||||
|
}
|
||||||
|
return getPlatformAvatarColorForTextDef(name, dark)
|
||||||
|
}
|
||||||
|
|
||||||
export default async (): Promise<Resources> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
KickEmployee: kickEmployee,
|
KickEmployee: kickEmployee,
|
||||||
@ -330,6 +357,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
ChannelFilter,
|
ChannelFilter,
|
||||||
MergePersons,
|
MergePersons,
|
||||||
Avatar,
|
Avatar,
|
||||||
|
AvatarRef,
|
||||||
UserBoxList,
|
UserBoxList,
|
||||||
ChannelPresenter,
|
ChannelPresenter,
|
||||||
ChannelPanel,
|
ChannelPanel,
|
||||||
@ -362,19 +390,31 @@ export default async (): Promise<Resources> => ({
|
|||||||
) => await queryContact(contact.class.Organization, client, query, filter)
|
) => await queryContact(contact.class.Organization, client, query, filter)
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
GetFileUrl: (file: string, size: IconSize, fileName?: string) => {
|
GetFileUrl: (person: Data<WithLookup<AvatarInfo>>, name: string, width: number) => {
|
||||||
return [
|
if (person.avatar == null) {
|
||||||
getFileUrl(file, size, fileName),
|
return {
|
||||||
getFileUrl(file, size, fileName) + ' 1x',
|
color: getPersonColor(person, name)
|
||||||
getFileUrl(file, getIconSize2x(size), fileName) + ' 2x'
|
}
|
||||||
]
|
}
|
||||||
|
return {
|
||||||
|
url: getBlobHref(person.$lookup?.avatar, person.avatar),
|
||||||
|
srcset: getBlobSrcSet(person.$lookup?.avatar, person.avatar, width),
|
||||||
|
color: getPersonColor(person, name)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
GetGravatarUrl: (file: string, size: IconSize, fileName?: string) => [
|
GetGravatarUrl: (person: Data<WithLookup<AvatarInfo>>, name: string, width: number) => ({
|
||||||
getGravatarUrl(file, size),
|
url: person.avatarProps?.url !== undefined ? getGravatarUrl(person.avatarProps?.url, width) : undefined,
|
||||||
getGravatarUrl(file, size) + ' 1x',
|
srcset:
|
||||||
getGravatarUrl(file, getIconSize2x(size)) + ' 2x'
|
person.avatarProps?.url !== undefined
|
||||||
],
|
? `${getGravatarUrl(person.avatarProps?.url, width)} 1x, ${getGravatarUrl(person.avatarProps?.url, width * 2)} 2x`
|
||||||
GetColorUrl: (uri: string) => [uri],
|
: undefined,
|
||||||
|
color: getPersonColor(person, name)
|
||||||
|
}),
|
||||||
|
GetColorUrl: (person: Data<WithLookup<AvatarInfo>>, name: string) => ({ color: getPersonColor(person, name) }),
|
||||||
|
GetExternalUrl: (person: Data<WithLookup<AvatarInfo>>, name: string) => ({
|
||||||
|
color: getPersonColor(person, name),
|
||||||
|
url: person.avatarProps?.url
|
||||||
|
}),
|
||||||
EmployeeSort: employeeSort,
|
EmployeeSort: employeeSort,
|
||||||
FilterChannelInResult: filterChannelInResult,
|
FilterChannelInResult: filterChannelInResult,
|
||||||
FilterChannelNinResult: filterChannelNinResult,
|
FilterChannelNinResult: filterChannelNinResult,
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
import core, {
|
import core, {
|
||||||
getCurrentAccount,
|
getCurrentAccount,
|
||||||
toIdMap,
|
toIdMap,
|
||||||
|
type Account,
|
||||||
type Class,
|
type Class,
|
||||||
type Client,
|
type Client,
|
||||||
type Doc,
|
type Doc,
|
||||||
@ -39,8 +40,8 @@ import core, {
|
|||||||
type Ref,
|
type Ref,
|
||||||
type Timestamp,
|
type Timestamp,
|
||||||
type TxOperations,
|
type TxOperations,
|
||||||
type Account,
|
type UserStatus,
|
||||||
type UserStatus
|
type WithLookup
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
||||||
import { getEmbeddedLabel, getResource, translate } from '@hcengineering/platform'
|
import { getEmbeddedLabel, getResource, translate } from '@hcengineering/platform'
|
||||||
@ -287,14 +288,14 @@ async function generateLocation (loc: Location, id: Ref<Contact>): Promise<Resol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const employeeByIdStore = writable<IdMap<Employee>>(new Map())
|
export const employeeByIdStore = writable<IdMap<WithLookup<Employee>>>(new Map())
|
||||||
export const employeesStore = writable<Employee[]>([])
|
export const employeesStore = writable<Array<WithLookup<Employee>>>([])
|
||||||
|
|
||||||
export const personAccountByIdStore = writable<IdMap<PersonAccount>>(new Map())
|
export const personAccountByIdStore = writable<IdMap<PersonAccount>>(new Map())
|
||||||
|
|
||||||
export const channelProviders = writable<ChannelProvider[]>([])
|
export const channelProviders = writable<ChannelProvider[]>([])
|
||||||
|
|
||||||
export const personAccountPersonByIdStore = writable<IdMap<Person>>(new Map())
|
export const personAccountPersonByIdStore = writable<IdMap<WithLookup<Person>>>(new Map())
|
||||||
|
|
||||||
export const statusByUserStore = writable<Map<Ref<Account>, UserStatus>>(new Map())
|
export const statusByUserStore = writable<Map<Ref<Account>, UserStatus>>(new Map())
|
||||||
|
|
||||||
@ -311,10 +312,19 @@ function fillStores (): void {
|
|||||||
const accountPersonQuery = createQuery(true)
|
const accountPersonQuery = createQuery(true)
|
||||||
|
|
||||||
const query = createQuery(true)
|
const query = createQuery(true)
|
||||||
query.query(contact.mixin.Employee, {}, (res) => {
|
query.query(
|
||||||
|
contact.mixin.Employee,
|
||||||
|
{},
|
||||||
|
(res) => {
|
||||||
employeesStore.set(res)
|
employeesStore.set(res)
|
||||||
employeeByIdStore.set(toIdMap(res))
|
employeeByIdStore.set(toIdMap(res))
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
lookup: {
|
||||||
|
avatar: core.class.Blob
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const accountQ = createQuery(true)
|
const accountQ = createQuery(true)
|
||||||
accountQ.query(contact.class.PersonAccount, {}, (res) => {
|
accountQ.query(contact.class.PersonAccount, {}, (res) => {
|
||||||
@ -322,11 +332,16 @@ function fillStores (): void {
|
|||||||
|
|
||||||
const persons = res.map((it) => it.person)
|
const persons = res.map((it) => it.person)
|
||||||
|
|
||||||
accountPersonQuery.query(
|
accountPersonQuery.query<Person>(
|
||||||
contact.class.Person,
|
contact.class.Person,
|
||||||
{ _id: { $in: persons }, [contact.mixin.Employee]: { $exists: false } },
|
{ _id: { $in: persons }, [contact.mixin.Employee]: { $exists: false } },
|
||||||
(res) => {
|
(res) => {
|
||||||
personAccountPersonByIdStore.set(toIdMap(res))
|
personAccountPersonByIdStore.set(toIdMap(res))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lookup: {
|
||||||
|
avatar: core.class.Blob
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -14,11 +14,23 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Account, AttachedDoc, Class, Doc, Ref, Space, Timestamp, UXObject } from '@hcengineering/core'
|
import {
|
||||||
|
Account,
|
||||||
|
AttachedDoc,
|
||||||
|
Class,
|
||||||
|
Doc,
|
||||||
|
Ref,
|
||||||
|
Space,
|
||||||
|
Timestamp,
|
||||||
|
UXObject,
|
||||||
|
type Blob,
|
||||||
|
type Data,
|
||||||
|
type WithLookup
|
||||||
|
} from '@hcengineering/core'
|
||||||
import type { Asset, Metadata, Plugin, Resource } from '@hcengineering/platform'
|
import type { Asset, Metadata, Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { IntlString, plugin } from '@hcengineering/platform'
|
import { IntlString, plugin } from '@hcengineering/platform'
|
||||||
import { TemplateField, TemplateFieldCategory } from '@hcengineering/templates'
|
import { TemplateField, TemplateFieldCategory } from '@hcengineering/templates'
|
||||||
import type { AnyComponent, IconSize, ResolvedLocation } from '@hcengineering/ui'
|
import type { AnyComponent, ColorDefinition, ResolvedLocation } from '@hcengineering/ui'
|
||||||
import { Action, FilterMode, Viewlet } from '@hcengineering/view'
|
import { Action, FilterMode, Viewlet } from '@hcengineering/view'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,13 +77,19 @@ export interface ChannelItem extends AttachedDoc {
|
|||||||
export enum AvatarType {
|
export enum AvatarType {
|
||||||
COLOR = 'color',
|
COLOR = 'color',
|
||||||
IMAGE = 'image',
|
IMAGE = 'image',
|
||||||
GRAVATAR = 'gravatar'
|
GRAVATAR = 'gravatar',
|
||||||
|
|
||||||
|
EXTERNAL = 'external'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type GetAvatarUrl = (uri: string, size: IconSize) => string[]
|
export type GetAvatarUrl = (
|
||||||
|
uri: Data<WithLookup<AvatarInfo>>,
|
||||||
|
name: string,
|
||||||
|
width?: number
|
||||||
|
) => { url?: string, srcSet?: string, color: ColorDefinition }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -81,12 +99,19 @@ export interface AvatarProvider extends Doc {
|
|||||||
getUrl: Resource<GetAvatarUrl>
|
getUrl: Resource<GetAvatarUrl>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AvatarInfo extends Doc {
|
||||||
|
avatarType: AvatarType
|
||||||
|
avatar?: Ref<Blob> | null
|
||||||
|
avatarProps?: {
|
||||||
|
color?: string
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface Contact extends Doc {
|
export interface Contact extends Doc, AvatarInfo {
|
||||||
name: string
|
name: string
|
||||||
avatar?: string | null
|
|
||||||
attachments?: number
|
attachments?: number
|
||||||
comments?: number
|
comments?: number
|
||||||
channels?: number
|
channels?: number
|
||||||
@ -180,6 +205,7 @@ export const contactPlugin = plugin(contactId, {
|
|||||||
ChannelsPresenter: '' as AnyComponent,
|
ChannelsPresenter: '' as AnyComponent,
|
||||||
MembersPresenter: '' as AnyComponent,
|
MembersPresenter: '' as AnyComponent,
|
||||||
Avatar: '' as AnyComponent,
|
Avatar: '' as AnyComponent,
|
||||||
|
AvatarRef: '' as AnyComponent,
|
||||||
UserBoxList: '' as AnyComponent,
|
UserBoxList: '' as AnyComponent,
|
||||||
ChannelPresenter: '' as AnyComponent,
|
ChannelPresenter: '' as AnyComponent,
|
||||||
SpaceMembers: '' as AnyComponent,
|
SpaceMembers: '' as AnyComponent,
|
||||||
@ -212,7 +238,8 @@ export const contactPlugin = plugin(contactId, {
|
|||||||
function: {
|
function: {
|
||||||
GetColorUrl: '' as Resource<GetAvatarUrl>,
|
GetColorUrl: '' as Resource<GetAvatarUrl>,
|
||||||
GetFileUrl: '' as Resource<GetAvatarUrl>,
|
GetFileUrl: '' as Resource<GetAvatarUrl>,
|
||||||
GetGravatarUrl: '' as Resource<GetAvatarUrl>
|
GetGravatarUrl: '' as Resource<GetAvatarUrl>,
|
||||||
|
GetExternalUrl: '' as Resource<GetAvatarUrl>
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
ContactApplication: '' as Asset,
|
ContactApplication: '' as Asset,
|
||||||
|
@ -13,12 +13,12 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { AttachedData, Class, Client, Doc, FindResult, Ref, Hierarchy } from '@hcengineering/core'
|
import { AttachedData, Class, Client, Doc, FindResult, Hierarchy, Ref } from '@hcengineering/core'
|
||||||
import { IconSize, ColorDefinition } from '@hcengineering/ui'
|
|
||||||
import { MD5 } from 'crypto-js'
|
|
||||||
import { AvatarProvider, AvatarType, Channel, Contact, contactPlugin, Person } from '.'
|
|
||||||
import { AVATAR_COLORS, GravatarPlaceholderType } from './types'
|
|
||||||
import { getMetadata } from '@hcengineering/platform'
|
import { getMetadata } from '@hcengineering/platform'
|
||||||
|
import { ColorDefinition } from '@hcengineering/ui'
|
||||||
|
import { MD5 } from 'crypto-js'
|
||||||
|
import { AvatarProvider, AvatarType, Channel, Contact, Person, contactPlugin } from '.'
|
||||||
|
import { AVATAR_COLORS, GravatarPlaceholderType } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -58,16 +58,8 @@ export function buildGravatarId (email: string): string {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function getAvatarProviderId (avatar?: string | null): Ref<AvatarProvider> | undefined {
|
export function getAvatarProviderId (kind: AvatarType): Ref<AvatarProvider> | undefined {
|
||||||
if (avatar === null || avatar === undefined || avatar === '') {
|
switch (kind) {
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!avatar.includes('://')) {
|
|
||||||
return contactPlugin.avatarProvider.Image
|
|
||||||
}
|
|
||||||
const [schema] = avatar.split('://')
|
|
||||||
|
|
||||||
switch (schema) {
|
|
||||||
case AvatarType.GRAVATAR:
|
case AvatarType.GRAVATAR:
|
||||||
return contactPlugin.avatarProvider.Gravatar
|
return contactPlugin.avatarProvider.Gravatar
|
||||||
case AvatarType.COLOR:
|
case AvatarType.COLOR:
|
||||||
@ -81,31 +73,9 @@ export function getAvatarProviderId (avatar?: string | null): Ref<AvatarProvider
|
|||||||
*/
|
*/
|
||||||
export function getGravatarUrl (
|
export function getGravatarUrl (
|
||||||
gravatarId: string,
|
gravatarId: string,
|
||||||
size: IconSize = 'full',
|
width: number = 64,
|
||||||
placeholder: GravatarPlaceholderType = 'identicon'
|
placeholder: GravatarPlaceholderType = 'identicon'
|
||||||
): string {
|
): string {
|
||||||
let width = 64
|
|
||||||
switch (size) {
|
|
||||||
case 'inline':
|
|
||||||
case 'tiny':
|
|
||||||
case 'x-small':
|
|
||||||
case 'small':
|
|
||||||
case 'medium':
|
|
||||||
width = 128
|
|
||||||
break
|
|
||||||
case 'large':
|
|
||||||
width = 256
|
|
||||||
break
|
|
||||||
case 'x-large':
|
|
||||||
width = 512
|
|
||||||
break
|
|
||||||
case '2x-large':
|
|
||||||
width = 1024
|
|
||||||
break
|
|
||||||
case 'full':
|
|
||||||
width = 2048
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return `https://gravatar.com/avatar/${gravatarId}?s=${width}&d=${placeholder}`
|
return `https://gravatar.com/avatar/${gravatarId}?s=${width}&d=${placeholder}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +84,7 @@ export function getGravatarUrl (
|
|||||||
*/
|
*/
|
||||||
export async function checkHasGravatar (gravatarId: string, fetch?: typeof window.fetch): Promise<boolean> {
|
export async function checkHasGravatar (gravatarId: string, fetch?: typeof window.fetch): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
return (await (fetch ?? window.fetch)(getGravatarUrl(gravatarId, 'full', '404'))).ok
|
return (await (fetch ?? window.fetch)(getGravatarUrl(gravatarId, 2048, '404'))).ok
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
import core, { Doc, Ref, WithLookup, generateId } from '@hcengineering/core'
|
import core, { Doc, Ref, WithLookup, generateId, type Blob } from '@hcengineering/core'
|
||||||
import { Document } from '@hcengineering/document'
|
import { Document } from '@hcengineering/document'
|
||||||
import notification from '@hcengineering/notification'
|
import notification from '@hcengineering/notification'
|
||||||
import { Panel } from '@hcengineering/panel'
|
import { Panel } from '@hcengineering/panel'
|
||||||
@ -102,7 +102,7 @@
|
|||||||
isStarred = res.length !== 0
|
isStarred = res.length !== 0
|
||||||
})
|
})
|
||||||
|
|
||||||
async function createEmbedding (file: File): Promise<{ file: string, type: string } | undefined> {
|
async function createEmbedding (file: File): Promise<{ file: Ref<Blob>, type: string } | undefined> {
|
||||||
if (doc === undefined) {
|
if (doc === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
showPopup(
|
showPopup(
|
||||||
FilePreviewPopup,
|
FilePreviewPopup,
|
||||||
{
|
{
|
||||||
file: blob._id,
|
file: value.$lookup?.file ?? value.file,
|
||||||
contentType: blob.contentType,
|
contentType: blob.contentType,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
metadata: value.metadata
|
metadata: value.metadata
|
||||||
|
@ -13,17 +13,17 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { type Doc, type Ref } from '@hcengineering/core'
|
import { type Doc, type Ref, type WithLookup } from '@hcengineering/core'
|
||||||
import drive, { type Drive, type File, type Folder } from '@hcengineering/drive'
|
import drive, { type Drive, type File, type Folder } from '@hcengineering/drive'
|
||||||
import { type Resources } from '@hcengineering/platform'
|
import { type Resources } from '@hcengineering/platform'
|
||||||
import { getFileUrl } from '@hcengineering/presentation'
|
import { getBlobHref } from '@hcengineering/presentation'
|
||||||
import { type Location, showPopup } from '@hcengineering/ui'
|
import { showPopup, type Location } from '@hcengineering/ui'
|
||||||
|
|
||||||
import CreateDrive from './components/CreateDrive.svelte'
|
import CreateDrive from './components/CreateDrive.svelte'
|
||||||
import DrivePanel from './components/DrivePanel.svelte'
|
import DrivePanel from './components/DrivePanel.svelte'
|
||||||
|
import DrivePresenter from './components/DrivePresenter.svelte'
|
||||||
import DriveSpaceHeader from './components/DriveSpaceHeader.svelte'
|
import DriveSpaceHeader from './components/DriveSpaceHeader.svelte'
|
||||||
import DriveSpacePresenter from './components/DriveSpacePresenter.svelte'
|
import DriveSpacePresenter from './components/DriveSpacePresenter.svelte'
|
||||||
import DrivePresenter from './components/DrivePresenter.svelte'
|
|
||||||
import EditFolder from './components/EditFolder.svelte'
|
import EditFolder from './components/EditFolder.svelte'
|
||||||
import FilePresenter from './components/FilePresenter.svelte'
|
import FilePresenter from './components/FilePresenter.svelte'
|
||||||
import FileSizePresenter from './components/FileSizePresenter.svelte'
|
import FileSizePresenter from './components/FileSizePresenter.svelte'
|
||||||
@ -47,10 +47,10 @@ async function EditDrive (drive: Drive): Promise<void> {
|
|||||||
showPopup(CreateDrive, { drive })
|
showPopup(CreateDrive, { drive })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function DownloadFile (doc: File | File[]): Promise<void> {
|
async function DownloadFile (doc: WithLookup<File> | Array<WithLookup<File>>): Promise<void> {
|
||||||
const files = Array.isArray(doc) ? doc : [doc]
|
const files = Array.isArray(doc) ? doc : [doc]
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const href = getFileUrl(file.file, 'full', file.name)
|
const href = getBlobHref(file.$lookup?.file, file.file, file.name)
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
link.style.display = 'none'
|
link.style.display = 'none'
|
||||||
link.target = '_blank'
|
link.target = '_blank'
|
||||||
|
@ -10,9 +10,15 @@ import core, {
|
|||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import login, { loginId } from '@hcengineering/login'
|
import login, { loginId } from '@hcengineering/login'
|
||||||
import { getMetadata, getResource, setMetadata } from '@hcengineering/platform'
|
import { getMetadata, getResource, setMetadata } from '@hcengineering/platform'
|
||||||
import presentation, { closeClient, refreshClient, setClient } from '@hcengineering/presentation'
|
import presentation, { closeClient, refreshClient, setClient, setPresentationCookie } from '@hcengineering/presentation'
|
||||||
import { fetchMetadataLocalStorage, getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
|
import {
|
||||||
import { writable } from 'svelte/store'
|
fetchMetadataLocalStorage,
|
||||||
|
getCurrentLocation,
|
||||||
|
navigate,
|
||||||
|
setMetadataLocalStorage,
|
||||||
|
workspaceId
|
||||||
|
} from '@hcengineering/ui'
|
||||||
|
import { writable, get } from 'svelte/store'
|
||||||
|
|
||||||
export const versionError = writable<string | undefined>(undefined)
|
export const versionError = writable<string | undefined>(undefined)
|
||||||
|
|
||||||
@ -31,8 +37,8 @@ export async function connect (title: string): Promise<Client | undefined> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
setMetadata(presentation.metadata.Token, token)
|
setMetadata(presentation.metadata.Token, token)
|
||||||
document.cookie =
|
|
||||||
encodeURIComponent(presentation.metadata.Token.replaceAll(':', '-')) + '=' + encodeURIComponent(token) + '; path=/'
|
setPresentationCookie(token, get(workspaceId))
|
||||||
|
|
||||||
const getEndpoint = await getResource(login.function.GetEndpoint)
|
const getEndpoint = await getResource(login.function.GetEndpoint)
|
||||||
const endpoint = await getEndpoint()
|
const endpoint = await getEndpoint()
|
||||||
@ -183,8 +189,7 @@ function clearMetadata (ws: string): void {
|
|||||||
}
|
}
|
||||||
setMetadata(presentation.metadata.Token, null)
|
setMetadata(presentation.metadata.Token, null)
|
||||||
setMetadataLocalStorage(login.metadata.LastToken, null)
|
setMetadataLocalStorage(login.metadata.LastToken, null)
|
||||||
document.cookie =
|
setPresentationCookie('', get(workspaceId))
|
||||||
encodeURIComponent(presentation.metadata.Token.replaceAll(':', '-')) + '=' + encodeURIComponent('') + '; path=/'
|
|
||||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
|
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
|
||||||
setMetadataLocalStorage(login.metadata.LoginEmail, null)
|
setMetadataLocalStorage(login.metadata.LoginEmail, null)
|
||||||
void closeClient()
|
void closeClient()
|
||||||
|
@ -123,7 +123,7 @@
|
|||||||
<div class="mr-2">
|
<div class="mr-2">
|
||||||
<Button icon={IconAdd} kind={'list'} on:click={createChild} />
|
<Button icon={IconAdd} kind={'list'} on:click={createChild} />
|
||||||
</div>
|
</div>
|
||||||
<Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} name={value.name} />
|
<Avatar size={'medium'} person={value} icon={hr.icon.Department} name={value.name} />
|
||||||
<div class="flex-row ml-2 mr-4">
|
<div class="flex-row ml-2 mr-4">
|
||||||
<div class="fs-title">
|
<div class="fs-title">
|
||||||
{value.name}
|
{value.name}
|
||||||
|
@ -37,9 +37,7 @@
|
|||||||
await avatarEditor.removeAvatar(object.avatar)
|
await avatarEditor.removeAvatar(object.avatar)
|
||||||
}
|
}
|
||||||
const avatar = await avatarEditor.createAvatar()
|
const avatar = await avatarEditor.createAvatar()
|
||||||
await client.updateDoc(object._class, object.space, object._id, {
|
await client.diffUpdate(object, avatar)
|
||||||
avatar
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function nameChange (): Promise<void> {
|
async function nameChange (): Promise<void> {
|
||||||
@ -73,7 +71,7 @@
|
|||||||
<div class="mr-8">
|
<div class="mr-8">
|
||||||
{#key object}
|
{#key object}
|
||||||
<EditableAvatar
|
<EditableAvatar
|
||||||
avatar={object.avatar}
|
person={object}
|
||||||
size={'x-large'}
|
size={'x-large'}
|
||||||
icon={hr.icon.Department}
|
icon={hr.icon.Department}
|
||||||
bind:this={avatarEditor}
|
bind:this={avatarEditor}
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
<DocNavLink object={value}>
|
<DocNavLink object={value}>
|
||||||
<div class="flex-row-center">
|
<div class="flex-row-center">
|
||||||
<div class="member-icon mr-2">
|
<div class="member-icon mr-2">
|
||||||
<Avatar size={'medium'} avatar={value.avatar} name={value.name} />
|
<Avatar size={'medium'} person={value} name={value.name} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="member-title fs-title">
|
<div class="member-title fs-title">
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Channel, combineName, Contact, findContacts } from '@hcengineering/contact'
|
import { AvatarType, Channel, combineName, Contact, findContacts } from '@hcengineering/contact'
|
||||||
import { ChannelsDropdown, EditableAvatar, PersonPresenter } from '@hcengineering/contact-resources'
|
import { ChannelsDropdown, EditableAvatar, PersonPresenter } from '@hcengineering/contact-resources'
|
||||||
import contact from '@hcengineering/contact-resources/src/plugin'
|
import contact from '@hcengineering/contact-resources/src/plugin'
|
||||||
import { AttachedData, Class, Data, Doc, generateId, MixinData, Ref, WithLookup } from '@hcengineering/core'
|
import { AttachedData, Class, Data, Doc, generateId, MixinData, Ref, WithLookup } from '@hcengineering/core'
|
||||||
@ -60,10 +60,14 @@
|
|||||||
async function createCustomer () {
|
async function createCustomer () {
|
||||||
const candidate: Data<Contact> = {
|
const candidate: Data<Contact> = {
|
||||||
name: formatName(targetClass._id, firstName, lastName, object.name),
|
name: formatName(targetClass._id, firstName, lastName, object.name),
|
||||||
city: object.city
|
city: object.city,
|
||||||
|
avatarType: AvatarType.COLOR
|
||||||
}
|
}
|
||||||
if (avatar !== undefined) {
|
if (avatar !== undefined) {
|
||||||
candidate.avatar = await avatarEditor.createAvatar()
|
const info = await avatarEditor.createAvatar()
|
||||||
|
candidate.avatar = info.avatar
|
||||||
|
candidate.avatarType = info.avatarType
|
||||||
|
candidate.avatarProps = info.avatarProps
|
||||||
}
|
}
|
||||||
const candidateData: MixinData<Contact, Customer> = {
|
const candidateData: MixinData<Contact, Customer> = {
|
||||||
description: object.description
|
description: object.description
|
||||||
@ -188,7 +192,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-4 flex">
|
<div class="ml-4 flex">
|
||||||
<EditableAvatar
|
<EditableAvatar
|
||||||
avatar={object.avatar}
|
person={object}
|
||||||
size={'large'}
|
size={'large'}
|
||||||
name={object.name}
|
name={object.name}
|
||||||
bind:this={avatarEditor}
|
bind:this={avatarEditor}
|
||||||
|
@ -181,7 +181,7 @@
|
|||||||
<div class="flex-between header bottom-divider">
|
<div class="flex-between header bottom-divider">
|
||||||
<div class="flex-row-center">
|
<div class="flex-row-center">
|
||||||
{#if employee}
|
{#if employee}
|
||||||
<Avatar size={'smaller'} avatar={employee.avatar} name={employee.name} />
|
<Avatar size={'smaller'} person={employee} name={employee.name} />
|
||||||
<span class="font-medium mx-2">{getName(client.getHierarchy(), employee)}</span>
|
<span class="font-medium mx-2">{getName(client.getHierarchy(), employee)}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if newTxes > 0}
|
{#if newTxes > 0}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<svelte:fragment slot="content">
|
<svelte:fragment slot="content">
|
||||||
<div class="flex-row-center flex-wrap gap-2">
|
<div class="flex-row-center flex-wrap gap-2">
|
||||||
{#if sender}
|
{#if sender}
|
||||||
<Avatar avatar={sender.avatar} name={sender.name} size={'small'} />
|
<Avatar person={sender} name={sender.name} size={'small'} />
|
||||||
{/if}
|
{/if}
|
||||||
<span class="overflow-label">
|
<span class="overflow-label">
|
||||||
{value.body}
|
{value.body}
|
||||||
|
@ -89,7 +89,7 @@
|
|||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="inbox-activity__content shrink flex-grow clear-mins" class:read={newTxes === 0}>
|
<div class="inbox-activity__content shrink flex-grow clear-mins" class:read={newTxes === 0}>
|
||||||
<div class="flex-row-center gap-2">
|
<div class="flex-row-center gap-2">
|
||||||
<Avatar avatar={employee?.avatar} size={'small'} name={employee?.name} />
|
<Avatar person={employee} size={'small'} name={employee?.name} />
|
||||||
{#if employee}
|
{#if employee}
|
||||||
<span class="font-medium">{getName(client.getHierarchy(), employee)}</span>
|
<span class="font-medium">{getName(client.getHierarchy(), employee)}</span>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
<div class="antiContactCard">
|
<div class="antiContactCard">
|
||||||
<div class="label uppercase"><Label label={recruit.string.Talent} /></div>
|
<div class="label uppercase"><Label label={recruit.string.Talent} /></div>
|
||||||
<Avatar avatar={candidate?.avatar} size={'large'} name={candidate?.name} />
|
<Avatar person={candidate} size={'large'} name={candidate?.name} />
|
||||||
{#if candidate}
|
{#if candidate}
|
||||||
<DocNavLink object={candidate} {disabled}>
|
<DocNavLink object={candidate} {disabled}>
|
||||||
<div class="name lines-limit-2">
|
<div class="name lines-limit-2">
|
||||||
|
@ -15,7 +15,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
import attachment from '@hcengineering/attachment'
|
import attachment from '@hcengineering/attachment'
|
||||||
import contact, { Channel, ChannelProvider, combineName, findContacts, Person } from '@hcengineering/contact'
|
import contact, {
|
||||||
|
AvatarType,
|
||||||
|
Channel,
|
||||||
|
ChannelProvider,
|
||||||
|
combineName,
|
||||||
|
findContacts,
|
||||||
|
Person
|
||||||
|
} from '@hcengineering/contact'
|
||||||
import { ChannelsDropdown, EditableAvatar, PersonPresenter } from '@hcengineering/contact-resources'
|
import { ChannelsDropdown, EditableAvatar, PersonPresenter } from '@hcengineering/contact-resources'
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
@ -182,11 +189,13 @@
|
|||||||
const candidate: Data<Person> = {
|
const candidate: Data<Person> = {
|
||||||
name: combineName(object.firstName ?? '', object.lastName ?? ''),
|
name: combineName(object.firstName ?? '', object.lastName ?? ''),
|
||||||
city: object.city,
|
city: object.city,
|
||||||
channels: 0
|
channels: 0,
|
||||||
}
|
avatarType: AvatarType.COLOR
|
||||||
if (avatar !== undefined) {
|
|
||||||
candidate.avatar = await avatarEditor.createAvatar()
|
|
||||||
}
|
}
|
||||||
|
const info = await avatarEditor.createAvatar()
|
||||||
|
candidate.avatar = info.avatar
|
||||||
|
candidate.avatarType = info.avatarType
|
||||||
|
candidate.avatarProps = info.avatarProps
|
||||||
const candidateData: MixinData<Person, Candidate> = {
|
const candidateData: MixinData<Person, Candidate> = {
|
||||||
title: object.title,
|
title: object.title,
|
||||||
onsite: object.onsite,
|
onsite: object.onsite,
|
||||||
@ -619,7 +628,9 @@
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
bind:this={avatarEditor}
|
bind:this={avatarEditor}
|
||||||
bind:direct={object.avatar}
|
bind:direct={object.avatar}
|
||||||
avatar={undefined}
|
person={{
|
||||||
|
avatarType: AvatarType.COLOR
|
||||||
|
}}
|
||||||
size={'large'}
|
size={'large'}
|
||||||
name={combineName(object?.firstName?.trim() ?? '', object?.lastName?.trim() ?? '')}
|
name={combineName(object?.firstName?.trim() ?? '', object?.lastName?.trim() ?? '')}
|
||||||
/>
|
/>
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex-between mb-1">
|
<div class="flex-between mb-1">
|
||||||
<div class="flex-row-center">
|
<div class="flex-row-center">
|
||||||
<Avatar avatar={object.$lookup?.attachedTo?.avatar} size={'medium'} name={object.$lookup?.attachedTo?.name} />
|
<Avatar person={object.$lookup?.attachedTo} size={'medium'} name={object.$lookup?.attachedTo?.name} />
|
||||||
<div class="flex-grow flex-col min-w-0 ml-2">
|
<div class="flex-grow flex-col min-w-0 ml-2">
|
||||||
<div class="fs-title over-underline lines-limit-2">
|
<div class="fs-title over-underline lines-limit-2">
|
||||||
{object.$lookup?.attachedTo ? getName(client.getHierarchy(), object.$lookup.attachedTo) : ''}
|
{object.$lookup?.attachedTo ? getName(client.getHierarchy(), object.$lookup.attachedTo) : ''}
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
on:click={() => onClick(p)}
|
on:click={() => onClick(p)}
|
||||||
>
|
>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Avatar size={'x-small'} avatar={p.avatar} name={p.name} />
|
<Avatar size={'x-small'} person={p} name={p.name} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -53,9 +53,7 @@
|
|||||||
await avatarEditor.removeAvatar(employee.avatar)
|
await avatarEditor.removeAvatar(employee.avatar)
|
||||||
}
|
}
|
||||||
const avatar = await avatarEditor.createAvatar()
|
const avatar = await avatarEditor.createAvatar()
|
||||||
await client.update(employee, {
|
await client.diffUpdate(employee, avatar)
|
||||||
avatar
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const manager = createFocusManager()
|
const manager = createFocusManager()
|
||||||
@ -97,7 +95,7 @@
|
|||||||
<div class="flex flex-grow w-full">
|
<div class="flex flex-grow w-full">
|
||||||
<div class="mr-8">
|
<div class="mr-8">
|
||||||
<EditableAvatar
|
<EditableAvatar
|
||||||
avatar={employee.avatar}
|
person={employee}
|
||||||
email={account.email}
|
email={account.email}
|
||||||
size={'x-large'}
|
size={'x-large'}
|
||||||
name={employee.name}
|
name={employee.name}
|
||||||
|
@ -13,23 +13,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
import { AvatarType } from '@hcengineering/contact'
|
||||||
import contact, { Employee, PersonAccount, combineName, getFirstName, getLastName } from '@hcengineering/contact'
|
import { EditableAvatar } from '@hcengineering/contact-resources'
|
||||||
import { ChannelsEditor, EditableAvatar, employeeByIdStore } from '@hcengineering/contact-resources'
|
|
||||||
import { AttributeEditor, getClient, MessageBox } from '@hcengineering/presentation'
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
createFocusManager,
|
|
||||||
EditBox,
|
|
||||||
FocusHandler,
|
|
||||||
showPopup,
|
|
||||||
Header,
|
|
||||||
Breadcrumb,
|
|
||||||
Label
|
|
||||||
} from '@hcengineering/ui'
|
|
||||||
import setting from '../plugin'
|
|
||||||
import { WorkspaceSetting } from '@hcengineering/setting'
|
|
||||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { WorkspaceSetting } from '@hcengineering/setting'
|
||||||
|
import { FocusHandler, Label, createFocusManager } from '@hcengineering/ui'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import setting from '../plugin'
|
||||||
|
|
||||||
export let visibleNav: boolean = true
|
export let visibleNav: boolean = true
|
||||||
|
|
||||||
@ -49,18 +40,19 @@
|
|||||||
await client.createDoc(
|
await client.createDoc(
|
||||||
setting.class.WorkspaceSetting,
|
setting.class.WorkspaceSetting,
|
||||||
setting.space.Setting,
|
setting.space.Setting,
|
||||||
{ icon: avatar },
|
{ icon: avatar.avatar },
|
||||||
setting.ids.WorkspaceSetting
|
setting.ids.WorkspaceSetting
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workspaceSettings.icon != null) {
|
const avatar = await avatarEditor.createAvatar()
|
||||||
|
if (workspaceSettings.icon != null && workspaceSettings.icon !== avatar.avatar) {
|
||||||
|
// Different avatar
|
||||||
await avatarEditor.removeAvatar(workspaceSettings.icon)
|
await avatarEditor.removeAvatar(workspaceSettings.icon)
|
||||||
}
|
}
|
||||||
const avatar = await avatarEditor.createAvatar()
|
|
||||||
await client.update(workspaceSettings, {
|
await client.update(workspaceSettings, {
|
||||||
icon: avatar
|
icon: avatar.avatar
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +63,10 @@
|
|||||||
|
|
||||||
<div class="hulyComponent p-10 flex ac-body row">
|
<div class="hulyComponent p-10 flex ac-body row">
|
||||||
<EditableAvatar
|
<EditableAvatar
|
||||||
avatar={workspaceSettings?.icon}
|
person={{
|
||||||
|
avatarType: AvatarType.IMAGE,
|
||||||
|
avatar: workspaceSettings?.icon
|
||||||
|
}}
|
||||||
size={'x-large'}
|
size={'x-large'}
|
||||||
bind:this={avatarEditor}
|
bind:this={avatarEditor}
|
||||||
on:done={onAvatarDone}
|
on:done={onAvatarDone}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user