mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
UBERF-4946 Drive files preview (#5638)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
866e82e89b
commit
762483d7d5
@ -14,14 +14,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import activity from '@hcengineering/activity'
|
import activity from '@hcengineering/activity'
|
||||||
import type {
|
import type { Attachment, AttachmentMetadata, Photo, SavedAttachments } from '@hcengineering/attachment'
|
||||||
Attachment,
|
|
||||||
AttachmentMetadata,
|
|
||||||
Photo,
|
|
||||||
SavedAttachments,
|
|
||||||
AttachmentPreviewExtension
|
|
||||||
} from '@hcengineering/attachment'
|
|
||||||
import presentation, { TComponentPointExtension } from '@hcengineering/model-presentation'
|
|
||||||
import { type Domain, IndexKind, type Ref } from '@hcengineering/core'
|
import { type Domain, IndexKind, type Ref } from '@hcengineering/core'
|
||||||
import {
|
import {
|
||||||
type Builder,
|
type Builder,
|
||||||
@ -38,7 +31,6 @@ import {
|
|||||||
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'
|
||||||
import view, { createAction } from '@hcengineering/model-view'
|
import view, { createAction } from '@hcengineering/model-view'
|
||||||
import { type Resource } from '@hcengineering/platform'
|
|
||||||
|
|
||||||
import attachment from './plugin'
|
import attachment from './plugin'
|
||||||
|
|
||||||
@ -86,17 +78,8 @@ export class TSavedAttachments extends TPreference implements SavedAttachments {
|
|||||||
declare attachedTo: Ref<Attachment>
|
declare attachedTo: Ref<Attachment>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(attachment.class.AttachmentPreviewExtension, presentation.class.ComponentPointExtension)
|
|
||||||
export class TAttachmentPreviewExtension extends TComponentPointExtension implements AttachmentPreviewExtension {
|
|
||||||
@Prop(TypeString(), attachment.string.ContentType)
|
|
||||||
contentType!: string | string[]
|
|
||||||
|
|
||||||
alignment?: string
|
|
||||||
availabilityChecker?: Resource<() => Promise<boolean>>
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(TAttachment, TPhoto, TSavedAttachments, TAttachmentPreviewExtension)
|
builder.createModel(TAttachment, TPhoto, TSavedAttachments)
|
||||||
|
|
||||||
builder.mixin(attachment.class.Attachment, core.class.Class, view.mixin.ObjectPresenter, {
|
builder.mixin(attachment.class.Attachment, core.class.Class, view.mixin.ObjectPresenter, {
|
||||||
presenter: attachment.component.AttachmentPresenter
|
presenter: attachment.component.AttachmentPresenter
|
||||||
@ -181,42 +164,6 @@ export function createModel (builder: Builder): void {
|
|||||||
attachment.category.Attachments
|
attachment.category.Attachments
|
||||||
)
|
)
|
||||||
|
|
||||||
builder.createDoc(
|
|
||||||
attachment.class.AttachmentPreviewExtension,
|
|
||||||
core.space.Model,
|
|
||||||
{
|
|
||||||
contentType: 'image/*',
|
|
||||||
alignment: 'centered',
|
|
||||||
component: attachment.component.PDFViewer,
|
|
||||||
extension: attachment.extension.AttachmentPreview
|
|
||||||
},
|
|
||||||
attachment.previewExtension.Image
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.createDoc(
|
|
||||||
attachment.class.AttachmentPreviewExtension,
|
|
||||||
core.space.Model,
|
|
||||||
{
|
|
||||||
contentType: ['video/*', 'audio/*'],
|
|
||||||
alignment: 'centered',
|
|
||||||
component: attachment.component.MediaViewer,
|
|
||||||
extension: attachment.extension.AttachmentPreview
|
|
||||||
},
|
|
||||||
attachment.previewExtension.Media
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.createDoc(
|
|
||||||
attachment.class.AttachmentPreviewExtension,
|
|
||||||
core.space.Model,
|
|
||||||
{
|
|
||||||
contentType: ['application/pdf', 'application/json', 'text/*'],
|
|
||||||
alignment: 'float',
|
|
||||||
component: attachment.component.PDFViewer,
|
|
||||||
extension: attachment.extension.AttachmentPreview
|
|
||||||
},
|
|
||||||
attachment.previewExtension.PDF
|
|
||||||
)
|
|
||||||
|
|
||||||
createAction(builder, {
|
createAction(builder, {
|
||||||
action: view.actionImpl.ShowEditor,
|
action: view.actionImpl.ShowEditor,
|
||||||
actionProps: {
|
actionProps: {
|
||||||
|
@ -14,19 +14,31 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import core, {
|
import core, {
|
||||||
|
type Blob,
|
||||||
type Domain,
|
type Domain,
|
||||||
type Role,
|
type Role,
|
||||||
type RolesAssignment,
|
type RolesAssignment,
|
||||||
type Type,
|
type Type,
|
||||||
Account,
|
Account,
|
||||||
AccountRole,
|
AccountRole,
|
||||||
Ref,
|
|
||||||
IndexKind,
|
IndexKind,
|
||||||
DOMAIN_MODEL,
|
Ref,
|
||||||
type Blob
|
DOMAIN_MODEL
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { type Drive, type File, type Folder, type Resource, driveId } from '@hcengineering/drive'
|
import { type Drive, type File, type Folder, type Resource, driveId } from '@hcengineering/drive'
|
||||||
import { type Builder, Model, UX, Mixin, Prop, TypeString, Index, TypeRef, ReadOnly } from '@hcengineering/model'
|
import {
|
||||||
|
type Builder,
|
||||||
|
Hidden,
|
||||||
|
Index,
|
||||||
|
Mixin,
|
||||||
|
Model,
|
||||||
|
Prop,
|
||||||
|
ReadOnly,
|
||||||
|
TypeRecord,
|
||||||
|
TypeRef,
|
||||||
|
TypeString,
|
||||||
|
UX
|
||||||
|
} from '@hcengineering/model'
|
||||||
import { TDoc, TType, TTypedSpace } from '@hcengineering/model-core'
|
import { TDoc, TType, TTypedSpace } from '@hcengineering/model-core'
|
||||||
import tracker from '@hcengineering/model-tracker'
|
import tracker from '@hcengineering/model-tracker'
|
||||||
import view, { type Viewlet, classPresenter, createAction } from '@hcengineering/model-view'
|
import view, { type Viewlet, classPresenter, createAction } from '@hcengineering/model-view'
|
||||||
@ -103,6 +115,11 @@ export class TFile extends TResource implements File {
|
|||||||
@ReadOnly()
|
@ReadOnly()
|
||||||
declare file: Ref<Blob>
|
declare file: Ref<Blob>
|
||||||
|
|
||||||
|
@Prop(TypeRecord(), drive.string.Metadata)
|
||||||
|
@ReadOnly()
|
||||||
|
@Hidden()
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
|
||||||
@Prop(TypeRef(drive.class.Folder), drive.string.Parent)
|
@Prop(TypeRef(drive.class.Folder), drive.string.Parent)
|
||||||
@Index(IndexKind.Indexed)
|
@Index(IndexKind.Indexed)
|
||||||
@ReadOnly()
|
@ReadOnly()
|
||||||
|
@ -72,6 +72,7 @@ export default mergeIds(driveId, drive, {
|
|||||||
Description: '' as IntlString,
|
Description: '' as IntlString,
|
||||||
Size: '' as IntlString,
|
Size: '' as IntlString,
|
||||||
Type: '' as IntlString,
|
Type: '' as IntlString,
|
||||||
|
Metadata: '' as IntlString,
|
||||||
LastModified: '' as IntlString,
|
LastModified: '' as IntlString,
|
||||||
Parent: '' as IntlString,
|
Parent: '' as IntlString,
|
||||||
Path: '' as IntlString,
|
Path: '' as IntlString,
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { type Class, DOMAIN_MODEL, type Doc, type Ref } from '@hcengineering/core'
|
import { type Blob, type Class, type Doc, type Ref, DOMAIN_MODEL } from '@hcengineering/core'
|
||||||
import { type Builder, Model, Prop, TypeRef } from '@hcengineering/model'
|
import { type Builder, Model, Prop, TypeRef, TypeString } 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.
|
||||||
@ -23,12 +23,14 @@ import {
|
|||||||
type PresentationMiddlewareFactory
|
type PresentationMiddlewareFactory
|
||||||
} from '@hcengineering/presentation/src/pipeline'
|
} from '@hcengineering/presentation/src/pipeline'
|
||||||
import {
|
import {
|
||||||
|
type BlobMetadata,
|
||||||
type ComponentPointExtension,
|
type ComponentPointExtension,
|
||||||
type CreateExtensionKind,
|
type CreateExtensionKind,
|
||||||
type DocAttributeRule,
|
type DocAttributeRule,
|
||||||
type DocRules,
|
type DocRules,
|
||||||
type DocCreateExtension,
|
type DocCreateExtension,
|
||||||
type DocCreateFunction,
|
type DocCreateFunction,
|
||||||
|
type FilePreviewExtension,
|
||||||
type ObjectSearchContext,
|
type ObjectSearchContext,
|
||||||
type ObjectSearchCategory,
|
type ObjectSearchCategory,
|
||||||
type ObjectSearchFactory
|
type ObjectSearchFactory
|
||||||
@ -82,12 +84,23 @@ export class TDocRules extends TDoc implements DocRules {
|
|||||||
fieldRules!: DocAttributeRule[]
|
fieldRules!: DocAttributeRule[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Model(presentation.class.FilePreviewExtension, presentation.class.ComponentPointExtension)
|
||||||
|
export class TFilePreviewExtension extends TComponentPointExtension implements FilePreviewExtension {
|
||||||
|
@Prop(TypeString(), presentation.string.ContentType)
|
||||||
|
contentType!: string | string[]
|
||||||
|
|
||||||
|
alignment?: string
|
||||||
|
metadataProvider?: Resource<(file: File, blob: Ref<Blob>) => Promise<BlobMetadata | undefined>>
|
||||||
|
availabilityChecker?: Resource<() => Promise<boolean>>
|
||||||
|
}
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(
|
builder.createModel(
|
||||||
TObjectSearchCategory,
|
TObjectSearchCategory,
|
||||||
TPresentationMiddlewareFactory,
|
TPresentationMiddlewareFactory,
|
||||||
TComponentPointExtension,
|
TComponentPointExtension,
|
||||||
TDocCreateExtension,
|
TDocCreateExtension,
|
||||||
TDocRules
|
TDocRules,
|
||||||
|
TFilePreviewExtension
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -598,6 +598,56 @@ export function createModel (builder: Builder): void {
|
|||||||
view.pipeline.AnalyticsMiddleware
|
view.pipeline.AnalyticsMiddleware
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
presentation.class.FilePreviewExtension,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
contentType: ['audio/*'],
|
||||||
|
alignment: 'centered',
|
||||||
|
component: view.component.AudioViewer,
|
||||||
|
extension: presentation.extension.FilePreviewExtension
|
||||||
|
},
|
||||||
|
view.extension.Audio
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
presentation.class.FilePreviewExtension,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
contentType: 'image/*',
|
||||||
|
alignment: 'centered',
|
||||||
|
component: view.component.ImageViewer,
|
||||||
|
metadataProvider: view.function.BlobImageMetadata,
|
||||||
|
extension: presentation.extension.FilePreviewExtension
|
||||||
|
},
|
||||||
|
view.extension.Image
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
presentation.class.FilePreviewExtension,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
contentType: ['video/*'],
|
||||||
|
alignment: 'centered',
|
||||||
|
component: view.component.VideoViewer,
|
||||||
|
metadataProvider: view.function.BlobVideoMetadata,
|
||||||
|
extension: presentation.extension.FilePreviewExtension
|
||||||
|
},
|
||||||
|
view.extension.Video
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
presentation.class.FilePreviewExtension,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
contentType: ['application/pdf', 'application/json', 'text/*'],
|
||||||
|
alignment: 'float',
|
||||||
|
component: view.component.PDFViewer,
|
||||||
|
extension: presentation.extension.FilePreviewExtension
|
||||||
|
},
|
||||||
|
view.extension.PDF
|
||||||
|
)
|
||||||
|
|
||||||
createAction(
|
createAction(
|
||||||
builder,
|
builder,
|
||||||
{
|
{
|
||||||
|
@ -13,10 +13,11 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { type Doc, type Ref } from '@hcengineering/core'
|
import { type Blob, type Doc, type Ref } from '@hcengineering/core'
|
||||||
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
|
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
|
||||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||||
import { type FilterFunction, type ViewAction, type ViewCategoryAction, viewId } from '@hcengineering/view'
|
import { type FilterFunction, type ViewAction, type ViewCategoryAction, viewId } from '@hcengineering/view'
|
||||||
|
import { type BlobMetadata, type FilePreviewExtension } from '@hcengineering/presentation'
|
||||||
import { type PresentationMiddlewareFactory } from '@hcengineering/presentation/src/pipeline'
|
import { type PresentationMiddlewareFactory } from '@hcengineering/presentation/src/pipeline'
|
||||||
import view from '@hcengineering/view-resources/src/plugin'
|
import view from '@hcengineering/view-resources/src/plugin'
|
||||||
|
|
||||||
@ -82,7 +83,11 @@ export default mergeIds(viewId, view, {
|
|||||||
StatusPresenter: '' as AnyComponent,
|
StatusPresenter: '' as AnyComponent,
|
||||||
StatusRefPresenter: '' as AnyComponent,
|
StatusRefPresenter: '' as AnyComponent,
|
||||||
DateFilterPresenter: '' as AnyComponent,
|
DateFilterPresenter: '' as AnyComponent,
|
||||||
StringFilterPresenter: '' as AnyComponent
|
StringFilterPresenter: '' as AnyComponent,
|
||||||
|
AudioViewer: '' as AnyComponent,
|
||||||
|
ImageViewer: '' as AnyComponent,
|
||||||
|
VideoViewer: '' as AnyComponent,
|
||||||
|
PDFViewer: '' as AnyComponent
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
Table: '' as IntlString,
|
Table: '' as IntlString,
|
||||||
@ -132,10 +137,18 @@ export default mergeIds(viewId, view, {
|
|||||||
CanArchiveSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
CanArchiveSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||||
CanDeleteSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
CanDeleteSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||||
CanJoinSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
CanJoinSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||||
CanLeaveSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>
|
CanLeaveSpace: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||||
|
BlobImageMetadata: '' as Resource<(file: File, blob: Ref<Blob>) => Promise<BlobMetadata | undefined>>,
|
||||||
|
BlobVideoMetadata: '' as Resource<(file: File, blob: Ref<Blob>) => Promise<BlobMetadata | undefined>>
|
||||||
},
|
},
|
||||||
pipeline: {
|
pipeline: {
|
||||||
PresentationMiddleware: '' as Ref<PresentationMiddlewareFactory>,
|
PresentationMiddleware: '' as Ref<PresentationMiddlewareFactory>,
|
||||||
AnalyticsMiddleware: '' as Ref<PresentationMiddlewareFactory>
|
AnalyticsMiddleware: '' as Ref<PresentationMiddlewareFactory>
|
||||||
|
},
|
||||||
|
extension: {
|
||||||
|
Audio: '' as Ref<FilePreviewExtension>,
|
||||||
|
Image: '' as Ref<FilePreviewExtension>,
|
||||||
|
Video: '' as Ref<FilePreviewExtension>,
|
||||||
|
PDF: '' as Ref<FilePreviewExtension>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -31,6 +31,11 @@
|
|||||||
"Created": "Created",
|
"Created": "Created",
|
||||||
"NoResults": "No results to show",
|
"NoResults": "No results to show",
|
||||||
"Next": "Next",
|
"Next": "Next",
|
||||||
"FailedToPreview": "Failed to preview"
|
"FailedToPreview": "Failed to preview",
|
||||||
|
"ContentType": "Content type",
|
||||||
|
"ContentTypeNotSupported": "Content type not supported"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"FileTooLarge": "File too large"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,11 @@
|
|||||||
"Created": "Creado",
|
"Created": "Creado",
|
||||||
"NoResults": "No hay resultados para mostrar",
|
"NoResults": "No hay resultados para mostrar",
|
||||||
"Next": "Siguiente",
|
"Next": "Siguiente",
|
||||||
"FailedToPreview": "Error al previsualizar"
|
"FailedToPreview": "Error al previsualizar",
|
||||||
|
"ContentType": "Tipo de contenido",
|
||||||
|
"ContentTypeNotSupported": "Tipo de contenido no admitido"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"FileTooLarge": "Archivo demasiado grande"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,6 +31,11 @@
|
|||||||
"Created": "Criado",
|
"Created": "Criado",
|
||||||
"NoResults": "Sem resultados para mostrar",
|
"NoResults": "Sem resultados para mostrar",
|
||||||
"Next": "Seguinte",
|
"Next": "Seguinte",
|
||||||
"FailedToPreview": "Falha ao pré-visualizar"
|
"FailedToPreview": "Falha ao pré-visualizar",
|
||||||
|
"ContentType": "Tipo de conteúdo",
|
||||||
|
"ContentTypeNotSupported": "Tipo de conteúdo não suportado"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"FileTooLarge": "Ficheiro demasiado grande"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,6 +31,11 @@
|
|||||||
"Created": "Созданные",
|
"Created": "Созданные",
|
||||||
"NoResults": "Нет результатов",
|
"NoResults": "Нет результатов",
|
||||||
"Next": "Далее",
|
"Next": "Далее",
|
||||||
"FailedToPreview": "Ошибка предпросмотра"
|
"FailedToPreview": "Ошибка предпросмотра",
|
||||||
|
"ContentType": "Тип контента",
|
||||||
|
"ContentTypeNotSupported": "Тип контента не поддерживается"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"FileTooLarge": "Файл слишком большой"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
151
packages/presentation/src/components/FilePreviewPopup.svelte
Normal file
151
packages/presentation/src/components/FilePreviewPopup.svelte
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// 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 { createEventDispatcher, onMount } from 'svelte'
|
||||||
|
import { type Blob, type Ref } from '@hcengineering/core'
|
||||||
|
import { Label, Dialog, Button, Component } from '@hcengineering/ui'
|
||||||
|
|
||||||
|
import presentation from '../plugin'
|
||||||
|
|
||||||
|
import { getPreviewType, previewTypes } from '../file'
|
||||||
|
import { BlobMetadata, FilePreviewExtension } from '../types'
|
||||||
|
import { getFileUrl } from '../utils'
|
||||||
|
|
||||||
|
import ActionContext from './ActionContext.svelte'
|
||||||
|
import Download from './icons/Download.svelte'
|
||||||
|
|
||||||
|
export let file: Ref<Blob> | undefined
|
||||||
|
export let name: string
|
||||||
|
export let contentType: string
|
||||||
|
export let metadata: BlobMetadata | undefined
|
||||||
|
export let props: Record<string, any> = {}
|
||||||
|
|
||||||
|
export let fullSize = false
|
||||||
|
export let showIcon = true
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (fullSize) {
|
||||||
|
dispatch('fullsize')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function iconLabel (name: string): string {
|
||||||
|
const parts = `${name}`.split('.')
|
||||||
|
const ext = parts[parts.length - 1]
|
||||||
|
return ext.substring(0, 4).toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
let previewType: FilePreviewExtension | undefined = undefined
|
||||||
|
$: if (file !== undefined) {
|
||||||
|
void getPreviewType(contentType, $previewTypes).then((res) => {
|
||||||
|
previewType = res
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
previewType = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
let download: HTMLAnchorElement
|
||||||
|
$: src = file === undefined ? '' : getFileUrl(file, 'full', 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(name)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<span class="wrapped-title">{name}</span>
|
||||||
|
</div>
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="utils">
|
||||||
|
{#if src !== ''}
|
||||||
|
<a class="no-line" href={src} download={name} bind:this={download}>
|
||||||
|
<Button
|
||||||
|
icon={Download}
|
||||||
|
kind={'ghost'}
|
||||||
|
on:click={() => {
|
||||||
|
download.click()
|
||||||
|
}}
|
||||||
|
showTooltip={{ label: presentation.string.Download }}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
{#if src === ''}
|
||||||
|
<div class="centered">
|
||||||
|
<Label label={presentation.string.FailedToPreview} />
|
||||||
|
</div>
|
||||||
|
{:else if previewType !== undefined}
|
||||||
|
<div class="content flex-col flex-grow">
|
||||||
|
<Component is={previewType.component} props={{ value: file, name, contentType, metadata, ...props }} />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="centered flex-col flex-gap-3">
|
||||||
|
<Label label={presentation.string.ContentTypeNotSupported} />
|
||||||
|
<Button
|
||||||
|
label={presentation.string.Download}
|
||||||
|
kind={'primary'}
|
||||||
|
on:click={() => {
|
||||||
|
download.click()
|
||||||
|
}}
|
||||||
|
showTooltip={{ label: presentation.string.Download }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/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;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: auto;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.centered {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 100;
|
||||||
|
height: 100;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
// Copyright © 2020 Anticrm Platform Contributors.
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
// 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
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
179
packages/presentation/src/file.ts
Normal file
179
packages/presentation/src/file.ts
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { type Blob, type Ref, concatLink } from '@hcengineering/core'
|
||||||
|
import { PlatformError, Severity, Status, getMetadata, getResource } from '@hcengineering/platform'
|
||||||
|
import { type PopupAlignment } from '@hcengineering/ui'
|
||||||
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
|
import { type BlobMetadata, type FilePreviewExtension } from './types'
|
||||||
|
import { createQuery } from './utils'
|
||||||
|
import plugin from './plugin'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function uploadFile (file: File): Promise<Ref<Blob>> {
|
||||||
|
const uploadUrl = getMetadata(plugin.metadata.UploadURL)
|
||||||
|
|
||||||
|
if (uploadUrl === undefined) {
|
||||||
|
throw Error('UploadURL is not defined')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new FormData()
|
||||||
|
data.append('file', file)
|
||||||
|
|
||||||
|
const resp = await fetch(uploadUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + (getMetadata(plugin.metadata.Token) as string)
|
||||||
|
},
|
||||||
|
body: data
|
||||||
|
})
|
||||||
|
|
||||||
|
if (resp.status !== 200) {
|
||||||
|
if (resp.status === 413) {
|
||||||
|
throw new PlatformError(new Status(Severity.ERROR, plugin.status.FileTooLarge, {}))
|
||||||
|
} else {
|
||||||
|
throw Error(`Failed to upload file: ${resp.statusText}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await resp.text()) as Ref<Blob>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function deleteFile (id: string): Promise<void> {
|
||||||
|
const uploadUrl = getMetadata(plugin.metadata.UploadURL) ?? ''
|
||||||
|
|
||||||
|
const url = concatLink(uploadUrl, `?file=${id}`)
|
||||||
|
const resp = await fetch(url, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + (getMetadata(plugin.metadata.Token) as string)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (resp.status !== 200) {
|
||||||
|
throw new Error('Failed to delete file')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function getFileMetadata (file: File, uuid: Ref<Blob>): Promise<BlobMetadata | undefined> {
|
||||||
|
const previewType = await getPreviewType(file.type, $previewTypes)
|
||||||
|
if (previewType?.metadataProvider === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadataProvider = await getResource(previewType.metadataProvider)
|
||||||
|
if (metadataProvider === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return await metadataProvider(file, uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const previewTypes = writable<FilePreviewExtension[]>([])
|
||||||
|
const previewTypesQuery = createQuery(true)
|
||||||
|
previewTypesQuery.query(plugin.class.FilePreviewExtension, {}, (result) => {
|
||||||
|
previewTypes.set(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
let $previewTypes: FilePreviewExtension[] = []
|
||||||
|
previewTypes.subscribe((it) => {
|
||||||
|
$previewTypes = it
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function canPreviewFile (contentType: string, _previewTypes: FilePreviewExtension[]): Promise<boolean> {
|
||||||
|
for (const previewType of _previewTypes) {
|
||||||
|
if (await isApplicableType(previewType, contentType)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function getPreviewType (
|
||||||
|
contentType: string,
|
||||||
|
_previewTypes: FilePreviewExtension[]
|
||||||
|
): Promise<FilePreviewExtension | undefined> {
|
||||||
|
const applicableTypes: FilePreviewExtension[] = []
|
||||||
|
for (const previewType of _previewTypes) {
|
||||||
|
if (await isApplicableType(previewType, contentType)) {
|
||||||
|
applicableTypes.push(previewType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicableTypes.sort(comparePreviewTypes)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function getPreviewAlignment (contentType: string): PopupAlignment {
|
||||||
|
if (contentType.startsWith('image/')) {
|
||||||
|
return 'centered'
|
||||||
|
} else if (contentType.startsWith('video/')) {
|
||||||
|
return 'centered'
|
||||||
|
} else {
|
||||||
|
return 'float'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreviewTypeRegExp (type: string): RegExp {
|
||||||
|
return new RegExp(`^${type.replaceAll('/', '\\/').replaceAll('*', '.*')}$`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isApplicableType (
|
||||||
|
{ contentType, availabilityChecker }: FilePreviewExtension,
|
||||||
|
_contentType: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
const checkAvailability = availabilityChecker !== undefined ? await getResource(availabilityChecker) : undefined
|
||||||
|
const isAvailable: boolean = checkAvailability === undefined || (await checkAvailability())
|
||||||
|
|
||||||
|
return (
|
||||||
|
isAvailable &&
|
||||||
|
(Array.isArray(contentType) ? contentType : [contentType]).some((type) =>
|
||||||
|
getPreviewTypeRegExp(type).test(_contentType)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function comparePreviewTypes (a: FilePreviewExtension, b: FilePreviewExtension): number {
|
||||||
|
if (a.order === undefined && b.order === undefined) {
|
||||||
|
return 0
|
||||||
|
} else if (a.order === undefined) {
|
||||||
|
return -1
|
||||||
|
} else if (b.order === undefined) {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return a.order - b.order
|
||||||
|
}
|
||||||
|
}
|
@ -39,6 +39,7 @@ export { default as IndexedDocumentPreview } from './components/IndexedDocumentP
|
|||||||
export { default as IndexedDocumentCompare } from './components/IndexedDocumentCompare.svelte'
|
export { default as IndexedDocumentCompare } from './components/IndexedDocumentCompare.svelte'
|
||||||
export { default as NavLink } from './components/NavLink.svelte'
|
export { default as NavLink } from './components/NavLink.svelte'
|
||||||
export { default as IconDownload } from './components/icons/Download.svelte'
|
export { default as IconDownload } from './components/icons/Download.svelte'
|
||||||
|
export { default as FilePreviewPopup } from './components/FilePreviewPopup.svelte'
|
||||||
export { default as IconForward } from './components/icons/Forward.svelte'
|
export { default as IconForward } from './components/icons/Forward.svelte'
|
||||||
export { default as Breadcrumbs } from './components/breadcrumbs/Breadcrumbs.svelte'
|
export { default as Breadcrumbs } from './components/breadcrumbs/Breadcrumbs.svelte'
|
||||||
export { default as BreadcrumbsElement } from './components/breadcrumbs/BreadcrumbsElement.svelte'
|
export { default as BreadcrumbsElement } from './components/breadcrumbs/BreadcrumbsElement.svelte'
|
||||||
@ -48,6 +49,7 @@ export { default as SearchResult } from './components/SearchResult.svelte'
|
|||||||
export { default } from './plugin'
|
export { default } from './plugin'
|
||||||
export * from './types'
|
export * from './types'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
|
export * from './file'
|
||||||
export * from './drafts'
|
export * from './drafts'
|
||||||
export { presentationId }
|
export { presentationId }
|
||||||
export * from './collaborator'
|
export * from './collaborator'
|
||||||
|
@ -15,13 +15,15 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { type Class, type Ref } from '@hcengineering/core'
|
import { type Class, type Ref } from '@hcengineering/core'
|
||||||
import type { Asset, IntlString, Metadata, Plugin } from '@hcengineering/platform'
|
import type { Asset, IntlString, Metadata, Plugin, StatusCode } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
|
import { type ComponentExtensionId } from '@hcengineering/ui'
|
||||||
import { type PresentationMiddlewareFactory } from './pipeline'
|
import { type PresentationMiddlewareFactory } from './pipeline'
|
||||||
import {
|
import {
|
||||||
type ComponentPointExtension,
|
type ComponentPointExtension,
|
||||||
type DocRules,
|
type DocRules,
|
||||||
type DocCreateExtension,
|
type DocCreateExtension,
|
||||||
|
type FilePreviewExtension,
|
||||||
type ObjectSearchCategory
|
type ObjectSearchCategory
|
||||||
} from './types'
|
} from './types'
|
||||||
|
|
||||||
@ -36,7 +38,8 @@ export default plugin(presentationId, {
|
|||||||
PresentationMiddlewareFactory: '' as Ref<Class<PresentationMiddlewareFactory>>,
|
PresentationMiddlewareFactory: '' as Ref<Class<PresentationMiddlewareFactory>>,
|
||||||
ComponentPointExtension: '' as Ref<Class<ComponentPointExtension>>,
|
ComponentPointExtension: '' as Ref<Class<ComponentPointExtension>>,
|
||||||
DocCreateExtension: '' as Ref<Class<DocCreateExtension>>,
|
DocCreateExtension: '' as Ref<Class<DocCreateExtension>>,
|
||||||
DocRules: '' as Ref<Class<DocRules>>
|
DocRules: '' as Ref<Class<DocRules>>,
|
||||||
|
FilePreviewExtension: '' as Ref<Class<FilePreviewExtension>>
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
Create: '' as IntlString,
|
Create: '' as IntlString,
|
||||||
@ -72,7 +75,12 @@ export default plugin(presentationId, {
|
|||||||
Created: '' as IntlString,
|
Created: '' as IntlString,
|
||||||
NoResults: '' as IntlString,
|
NoResults: '' as IntlString,
|
||||||
Next: '' as IntlString,
|
Next: '' as IntlString,
|
||||||
FailedToPreview: '' as IntlString
|
FailedToPreview: '' as IntlString,
|
||||||
|
ContentType: '' as IntlString,
|
||||||
|
ContentTypeNotSupported: '' as IntlString
|
||||||
|
},
|
||||||
|
extension: {
|
||||||
|
FilePreviewExtension: '' as ComponentExtensionId
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
RequiredVersion: '' as Metadata<string>,
|
RequiredVersion: '' as Metadata<string>,
|
||||||
@ -82,5 +90,8 @@ export default plugin(presentationId, {
|
|||||||
CollaboratorApiUrl: '' as Metadata<string>,
|
CollaboratorApiUrl: '' as Metadata<string>,
|
||||||
Token: '' as Metadata<string>,
|
Token: '' as Metadata<string>,
|
||||||
FrontUrl: '' as Asset
|
FrontUrl: '' as Asset
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
FileTooLarge: '' as StatusCode
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
type Blob,
|
||||||
type Class,
|
type Class,
|
||||||
type Client,
|
type Client,
|
||||||
type Doc,
|
type Doc,
|
||||||
@ -166,3 +167,19 @@ export interface DocRules extends Doc {
|
|||||||
mixin?: Ref<Mixin<Space>>
|
mixin?: Ref<Mixin<Space>>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type BlobMetadata = Record<string, any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface FilePreviewExtension extends ComponentPointExtension {
|
||||||
|
contentType: string | string[]
|
||||||
|
alignment?: string
|
||||||
|
metadataProvider?: Resource<(file: File, blob: Ref<Blob>) => Promise<BlobMetadata | undefined>>
|
||||||
|
// Extension is only available if this checker returns true
|
||||||
|
availabilityChecker?: Resource<() => Promise<boolean>>
|
||||||
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
import { PDFViewer, getFileUrl } from '@hcengineering/presentation'
|
import { FilePreviewPopup, getFileUrl } from '@hcengineering/presentation'
|
||||||
import { IconExpand, IconMoreH, IconSize, SelectPopup, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
import { IconExpand, IconMoreH, IconSize, SelectPopup, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
||||||
import { Editor } from '@tiptap/core'
|
import { Editor } from '@tiptap/core'
|
||||||
import IconAlignCenter from './icons/AlignCenter.svelte'
|
import IconAlignCenter from './icons/AlignCenter.svelte'
|
||||||
@ -42,7 +42,7 @@
|
|||||||
const fileId = attributes['file-id'] ?? attributes.src
|
const fileId = attributes['file-id'] ?? attributes.src
|
||||||
const fileName = attributes.alt ?? ''
|
const fileName = attributes.alt ?? ''
|
||||||
showPopup(
|
showPopup(
|
||||||
PDFViewer,
|
FilePreviewPopup,
|
||||||
{ file: fileId, name: fileName, contentType: 'image/*', fullSize: true, showIcon: false },
|
{ file: fileId, name: fileName, contentType: 'image/*', fullSize: true, showIcon: false },
|
||||||
'centered',
|
'centered',
|
||||||
() => {
|
() => {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// 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 { getFileUrl, PDFViewer } from '@hcengineering/presentation'
|
import { FilePreviewPopup, getFileUrl } from '@hcengineering/presentation'
|
||||||
import { FileNode, type FileOptions as FileNodeOptions } from '@hcengineering/text'
|
import { FileNode, type FileOptions as FileNodeOptions } from '@hcengineering/text'
|
||||||
import { showPopup } from '@hcengineering/ui'
|
import { showPopup } from '@hcengineering/ui'
|
||||||
import { nodeInputRule } from '@tiptap/core'
|
import { nodeInputRule } from '@tiptap/core'
|
||||||
@ -123,15 +123,14 @@ export const FileExtension = FileNode.extend<FileOptions>({
|
|||||||
if (fileId === '') return
|
if (fileId === '') return
|
||||||
const fileName = node.attrs['data-file-name'] ?? ''
|
const fileName = node.attrs['data-file-name'] ?? ''
|
||||||
const fileType: string = node.attrs['data-file-type'] ?? ''
|
const fileType: string = node.attrs['data-file-type'] ?? ''
|
||||||
if (!(fileType.startsWith('image/') || fileType === 'text/plain' || fileType === 'application/pdf')) return
|
|
||||||
|
|
||||||
showPopup(
|
showPopup(
|
||||||
PDFViewer,
|
FilePreviewPopup,
|
||||||
{
|
{
|
||||||
file: fileId,
|
file: fileId,
|
||||||
name: fileName,
|
name: fileName,
|
||||||
contentType: fileType,
|
contentType: fileType,
|
||||||
fullSize: true,
|
fullSize: false,
|
||||||
showIcon: false
|
showIcon: false
|
||||||
},
|
},
|
||||||
'centered'
|
'centered'
|
||||||
|
@ -12,10 +12,9 @@
|
|||||||
// 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 { PDFViewer } from '@hcengineering/presentation'
|
import { FilePreviewPopup } from '@hcengineering/presentation'
|
||||||
import { showPopup } from '@hcengineering/ui'
|
|
||||||
import { ImageNode, type ImageOptions as ImageNodeOptions } from '@hcengineering/text'
|
import { ImageNode, type ImageOptions as ImageNodeOptions } from '@hcengineering/text'
|
||||||
import { type IconSize, getIconSize2x } from '@hcengineering/ui'
|
import { type IconSize, getIconSize2x, 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'
|
||||||
|
|
||||||
@ -201,7 +200,7 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
|
|||||||
const fileName = node.attrs.alt ?? ''
|
const fileName = node.attrs.alt ?? ''
|
||||||
|
|
||||||
showPopup(
|
showPopup(
|
||||||
PDFViewer,
|
FilePreviewPopup,
|
||||||
{
|
{
|
||||||
file: fileId,
|
file: fileId,
|
||||||
name: fileName,
|
name: fileName,
|
||||||
|
@ -53,8 +53,5 @@
|
|||||||
"FilterAttachments": "Attachments",
|
"FilterAttachments": "Attachments",
|
||||||
"RemovedAttachment": "Removed attachment",
|
"RemovedAttachment": "Removed attachment",
|
||||||
"ContentType": "Content type"
|
"ContentType": "Content type"
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"FileTooLarge": "File too large"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,8 +53,5 @@
|
|||||||
"FilterAttachments": "Adjuntos",
|
"FilterAttachments": "Adjuntos",
|
||||||
"RemovedAttachment": "Adjunto eliminado",
|
"RemovedAttachment": "Adjunto eliminado",
|
||||||
"ContentType": "Tipo de contenido"
|
"ContentType": "Tipo de contenido"
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"FileTooLarge": "Archivo demasiado grande"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -53,8 +53,5 @@
|
|||||||
"FilterAttachments": "Anexos",
|
"FilterAttachments": "Anexos",
|
||||||
"RemovedAttachment": "Anexo removido",
|
"RemovedAttachment": "Anexo removido",
|
||||||
"ContentType": "Tipo de conteúdo"
|
"ContentType": "Tipo de conteúdo"
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"FileTooLarge": "Ficheiro demasiado grande"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -53,8 +53,5 @@
|
|||||||
"FilterAttachments": "Вложения",
|
"FilterAttachments": "Вложения",
|
||||||
"RemovedAttachment": "Удалил(а) вложение",
|
"RemovedAttachment": "Удалил(а) вложение",
|
||||||
"ContentType": "Тип контента"
|
"ContentType": "Тип контента"
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"FileTooLarge": "Файл слишком большой"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,22 +13,18 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AttachmentPreviewExtension, type Attachment } from '@hcengineering/attachment'
|
import { type Attachment } from '@hcengineering/attachment'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import { getFileUrl } from '@hcengineering/presentation'
|
|
||||||
import {
|
import {
|
||||||
Action as UIAction,
|
FilePreviewPopup,
|
||||||
ActionIcon,
|
getFileUrl,
|
||||||
IconMoreH,
|
previewTypes,
|
||||||
IconOpen,
|
canPreviewFile,
|
||||||
Menu,
|
getPreviewAlignment
|
||||||
closeTooltip,
|
} from '@hcengineering/presentation'
|
||||||
showPopup,
|
import { Action as UIAction, ActionIcon, IconMoreH, IconOpen, Menu, closeTooltip, showPopup } from '@hcengineering/ui'
|
||||||
PopupAlignment
|
|
||||||
} from '@hcengineering/ui'
|
|
||||||
import view, { Action } from '@hcengineering/view'
|
import view, { Action } from '@hcengineering/view'
|
||||||
|
|
||||||
import { previewTypes, getPreviewType, isOpenable } from '../utils'
|
|
||||||
import attachmentPlugin from '../plugin'
|
import attachmentPlugin from '../plugin'
|
||||||
import FileDownload from './icons/FileDownload.svelte'
|
import FileDownload from './icons/FileDownload.svelte'
|
||||||
|
|
||||||
@ -39,24 +35,18 @@
|
|||||||
let download: HTMLAnchorElement
|
let download: HTMLAnchorElement
|
||||||
|
|
||||||
$: contentType = attachment?.type ?? ''
|
$: contentType = attachment?.type ?? ''
|
||||||
let openable = false
|
|
||||||
$: {
|
|
||||||
void isOpenable(contentType, $previewTypes).then((res) => {
|
|
||||||
openable = res
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let previewType: AttachmentPreviewExtension | undefined = undefined
|
let canPreview: boolean = false
|
||||||
$: if (openable) {
|
$: if (attachment !== undefined) {
|
||||||
void getPreviewType(contentType, $previewTypes).then((res) => {
|
void canPreviewFile(contentType, $previewTypes).then((res) => {
|
||||||
previewType = res
|
canPreview = res
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
previewType = undefined
|
canPreview = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPreview (e: MouseEvent): void {
|
function showPreview (e: MouseEvent): void {
|
||||||
if (!openable || previewType === undefined) {
|
if (!canPreview) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,9 +59,14 @@
|
|||||||
closeTooltip()
|
closeTooltip()
|
||||||
|
|
||||||
showPopup(
|
showPopup(
|
||||||
previewType.component,
|
FilePreviewPopup,
|
||||||
{ ...(previewType.props ?? {}), file: attachment.file, name: attachment.name, contentType, value: attachment },
|
{
|
||||||
(previewType.alignment ?? 'center') as PopupAlignment
|
file: attachment.file,
|
||||||
|
name: attachment.name,
|
||||||
|
contentType: attachment.type ?? '',
|
||||||
|
metadata: attachment.metadata
|
||||||
|
},
|
||||||
|
getPreviewAlignment(attachment.type ?? '')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +90,7 @@
|
|||||||
|
|
||||||
const showMenu = (ev: Event) => {
|
const showMenu = (ev: Event) => {
|
||||||
const actions: UIAction[] = []
|
const actions: UIAction[] = []
|
||||||
if (openable) {
|
if (canPreview) {
|
||||||
actions.push(openAction)
|
actions.push(openAction)
|
||||||
}
|
}
|
||||||
actions.push({
|
actions.push({
|
||||||
@ -133,7 +128,7 @@
|
|||||||
bind:this={download}
|
bind:this={download}
|
||||||
on:click|stopPropagation
|
on:click|stopPropagation
|
||||||
>
|
>
|
||||||
{#if openable}
|
{#if canPreview}
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
icon={IconOpen}
|
icon={IconOpen}
|
||||||
size={'medium'}
|
size={'medium'}
|
||||||
|
@ -15,8 +15,7 @@
|
|||||||
<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 { showPopup, closeTooltip } from '@hcengineering/ui'
|
||||||
import { PDFViewer, getFileUrl } from '@hcengineering/presentation'
|
import { FilePreviewPopup, getFileUrl } from '@hcengineering/presentation'
|
||||||
import MediaViewer from './MediaViewer.svelte'
|
|
||||||
import { getType } from '../utils'
|
import { getType } from '../utils'
|
||||||
import filesize from 'filesize'
|
import filesize from 'filesize'
|
||||||
|
|
||||||
@ -32,22 +31,24 @@
|
|||||||
return ext.substring(0, 4).toUpperCase()
|
return ext.substring(0, 4).toUpperCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlayable (contentType: string) {
|
function isImage (contentType: string): boolean {
|
||||||
const type = getType(contentType)
|
|
||||||
return type === 'video' || type === 'audio'
|
|
||||||
}
|
|
||||||
function isImage (contentType: string) {
|
|
||||||
return getType(contentType) === 'image'
|
return getType(contentType) === 'image'
|
||||||
}
|
}
|
||||||
function isEmbedded (contentType: string) {
|
|
||||||
|
function isEmbedded (contentType: string): boolean {
|
||||||
return getType(contentType) !== 'other'
|
return getType(contentType) !== 'other'
|
||||||
}
|
}
|
||||||
|
|
||||||
function openAttachment () {
|
function openAttachment (): void {
|
||||||
closeTooltip()
|
closeTooltip()
|
||||||
showPopup(
|
showPopup(
|
||||||
isPlayable(value.type) ? MediaViewer : PDFViewer,
|
FilePreviewPopup,
|
||||||
{ file: value.file, name: value.name, contentType: value.type, value },
|
{
|
||||||
|
file: value.file,
|
||||||
|
name: value.name,
|
||||||
|
contentType: value.type,
|
||||||
|
metadata: value.metadata
|
||||||
|
},
|
||||||
isImage(value.type) ? 'centered' : 'float'
|
isImage(value.type) ? 'centered' : 'float'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Doc } from '@hcengineering/core'
|
import type { Doc } from '@hcengineering/core'
|
||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import { createQuery, getClient } 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 { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
|
|
||||||
import { AttachmentPresenter } from '..'
|
import { AttachmentPresenter } from '..'
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import { getAttachmentMetadata, uploadFile } from '../utils'
|
|
||||||
|
|
||||||
// export let attachments: number
|
// export let attachments: number
|
||||||
export let object: Doc
|
export let object: Doc
|
||||||
@ -55,7 +54,7 @@
|
|||||||
async function createAttachment (file: File) {
|
async function createAttachment (file: File) {
|
||||||
try {
|
try {
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
const metadata = await getAttachmentMetadata(file, uuid)
|
const metadata = await getFileMetadata(file, uuid)
|
||||||
|
|
||||||
await client.addCollection(attachment.class.Attachment, object.space, object._id, object._class, 'attachments', {
|
await client.addCollection(attachment.class.Attachment, object.space, object._id, object._class, 'attachments', {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
|
@ -14,14 +14,20 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import type { Attachment, AttachmentPreviewExtension } from '@hcengineering/attachment'
|
|
||||||
import { showPopup, closeTooltip, Label, getIconSize2x, Loading, PopupAlignment } from '@hcengineering/ui'
|
|
||||||
import presentation, { getFileUrl } from '@hcengineering/presentation'
|
|
||||||
import filesize from 'filesize'
|
import filesize from 'filesize'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
import core from '@hcengineering/core'
|
import core from '@hcengineering/core'
|
||||||
|
import { showPopup, closeTooltip, Label, getIconSize2x, Loading } from '@hcengineering/ui'
|
||||||
|
import presentation, {
|
||||||
|
FilePreviewPopup,
|
||||||
|
canPreviewFile,
|
||||||
|
getFileUrl,
|
||||||
|
getPreviewAlignment,
|
||||||
|
previewTypes
|
||||||
|
} from '@hcengineering/presentation'
|
||||||
import { permissionsStore } from '@hcengineering/view-resources'
|
import { permissionsStore } from '@hcengineering/view-resources'
|
||||||
import { getType, isOpenable, previewTypes, getPreviewType } from '../utils'
|
import { getType } from '../utils'
|
||||||
|
|
||||||
import AttachmentName from './AttachmentName.svelte'
|
import AttachmentName from './AttachmentName.svelte'
|
||||||
|
|
||||||
@ -54,28 +60,18 @@
|
|||||||
return getType(contentType) === 'image'
|
return getType(contentType) === 'image'
|
||||||
}
|
}
|
||||||
|
|
||||||
let openable = false
|
let canPreview: boolean = false
|
||||||
$: if (value !== undefined) {
|
$: if (value !== undefined) {
|
||||||
void isOpenable(value.type, $previewTypes).then((res) => {
|
void canPreviewFile(value.type, $previewTypes).then((res) => {
|
||||||
openable = res
|
canPreview = res
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
openable = false
|
canPreview = false
|
||||||
}
|
|
||||||
|
|
||||||
let previewType: AttachmentPreviewExtension | undefined = undefined
|
|
||||||
$: if (openable && value !== undefined) {
|
|
||||||
void getPreviewType(value.type, $previewTypes).then((res) => {
|
|
||||||
previewType = res
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
previewType = undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickHandler (e: MouseEvent): void {
|
function clickHandler (e: MouseEvent): void {
|
||||||
if (value === undefined) return
|
if (value === undefined || !canPreview) return
|
||||||
|
|
||||||
if (!openable || previewType === undefined) return
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
if (e.metaKey || e.ctrlKey) {
|
if (e.metaKey || e.ctrlKey) {
|
||||||
@ -84,9 +80,14 @@
|
|||||||
}
|
}
|
||||||
closeTooltip()
|
closeTooltip()
|
||||||
showPopup(
|
showPopup(
|
||||||
previewType.component,
|
FilePreviewPopup,
|
||||||
{ ...(previewType.props ?? {}), file: value.file, name: value.name, contentType: value.type, value },
|
{
|
||||||
(previewType.alignment ?? 'center') as PopupAlignment
|
file: value.file,
|
||||||
|
name: value.name,
|
||||||
|
contentType: value.type,
|
||||||
|
metadata: value.metadata
|
||||||
|
},
|
||||||
|
getPreviewAlignment(value.type)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Attachment } from '@hcengineering/attachment'
|
import type { Attachment } from '@hcengineering/attachment'
|
||||||
import { PDFViewer } from '@hcengineering/presentation'
|
import { FilePreviewPopup } from '@hcengineering/presentation'
|
||||||
import { showPopup, closeTooltip } from '@hcengineering/ui'
|
import { showPopup, closeTooltip } from '@hcengineering/ui'
|
||||||
import { ListSelectionProvider } from '@hcengineering/view-resources'
|
import { ListSelectionProvider } from '@hcengineering/view-resources'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
@ -49,7 +49,7 @@
|
|||||||
closeTooltip()
|
closeTooltip()
|
||||||
if (listProvider !== undefined) listProvider.updateFocus(value)
|
if (listProvider !== undefined) listProvider.updateFocus(value)
|
||||||
const popupInfo = showPopup(
|
const popupInfo = showPopup(
|
||||||
PDFViewer,
|
FilePreviewPopup,
|
||||||
{ file: value.file, name: value.name, contentType: value.type },
|
{ file: value.file, name: value.name, contentType: value.type },
|
||||||
value.type.startsWith('image/') ? 'centered' : 'float'
|
value.type.startsWith('image/') ? 'centered' : 'float'
|
||||||
)
|
)
|
||||||
|
@ -17,10 +17,17 @@
|
|||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import { Account, Class, Doc, generateId, IdMap, Markup, Ref, Space, toIdMap } from '@hcengineering/core'
|
import { Account, Class, Doc, generateId, IdMap, Markup, Ref, Space, toIdMap } from '@hcengineering/core'
|
||||||
import { IntlString, setPlatformStatus, unknownError, Asset } from '@hcengineering/platform'
|
import { IntlString, setPlatformStatus, unknownError, Asset } from '@hcengineering/platform'
|
||||||
import { createQuery, DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
import {
|
||||||
|
createQuery,
|
||||||
|
DraftController,
|
||||||
|
deleteFile,
|
||||||
|
draftsStore,
|
||||||
|
getClient,
|
||||||
|
getFileMetadata,
|
||||||
|
uploadFile
|
||||||
|
} from '@hcengineering/presentation'
|
||||||
import textEditor, { AttachIcon, EmptyMarkup, type RefAction, ReferenceInput } from '@hcengineering/text-editor'
|
import textEditor, { AttachIcon, EmptyMarkup, type RefAction, ReferenceInput } from '@hcengineering/text-editor'
|
||||||
import { Loading, type AnySvelteComponent } from '@hcengineering/ui'
|
import { Loading, type AnySvelteComponent } from '@hcengineering/ui'
|
||||||
import { deleteFile, getAttachmentMetadata, uploadFile } from '../utils'
|
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||||
|
|
||||||
@ -104,7 +111,7 @@
|
|||||||
async function createAttachment (file: File) {
|
async function createAttachment (file: File) {
|
||||||
try {
|
try {
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
const metadata = await getAttachmentMetadata(file, uuid)
|
const metadata = await getFileMetadata(file, uuid)
|
||||||
|
|
||||||
const _id: Ref<Attachment> = generateId()
|
const _id: Ref<Attachment> = generateId()
|
||||||
attachments.set(_id, {
|
attachments.set(_id, {
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import contact from '@hcengineering/contact'
|
import contact from '@hcengineering/contact'
|
||||||
import { Account, Doc, Ref, generateId } from '@hcengineering/core'
|
import { Account, Doc, Ref, generateId } from '@hcengineering/core'
|
||||||
import { IntlString, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { IntlString, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import { KeyedAttribute, createQuery, getClient } 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'
|
||||||
import textEditor, {
|
import textEditor, {
|
||||||
AttachIcon,
|
AttachIcon,
|
||||||
@ -32,7 +32,6 @@
|
|||||||
import { defaultRefActions, getModelRefActions } from '@hcengineering/text-editor/src/components/editor/actions'
|
import { defaultRefActions, getModelRefActions } from '@hcengineering/text-editor/src/components/editor/actions'
|
||||||
|
|
||||||
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
||||||
import { uploadFile } from '../utils'
|
|
||||||
|
|
||||||
export let object: Doc
|
export let object: Doc
|
||||||
export let key: KeyedAttribute
|
export let key: KeyedAttribute
|
||||||
|
@ -17,12 +17,19 @@
|
|||||||
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 } from '@hcengineering/core'
|
||||||
import { IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import { createQuery, DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
import {
|
||||||
|
createQuery,
|
||||||
|
DraftController,
|
||||||
|
deleteFile,
|
||||||
|
draftsStore,
|
||||||
|
getClient,
|
||||||
|
getFileMetadata,
|
||||||
|
uploadFile
|
||||||
|
} from '@hcengineering/presentation'
|
||||||
import textEditor, { AttachIcon, EmptyMarkup, type RefAction, StyledTextBox } from '@hcengineering/text-editor'
|
import textEditor, { AttachIcon, EmptyMarkup, type RefAction, StyledTextBox } from '@hcengineering/text-editor'
|
||||||
import { ButtonSize } from '@hcengineering/ui'
|
import { ButtonSize } from '@hcengineering/ui'
|
||||||
|
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import { deleteFile, getAttachmentMetadata, uploadFile } from '../utils'
|
|
||||||
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
||||||
|
|
||||||
export let objectId: Ref<Doc> | undefined = undefined
|
export let objectId: Ref<Doc> | undefined = undefined
|
||||||
@ -134,7 +141,7 @@
|
|||||||
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)
|
||||||
const metadata = await getAttachmentMetadata(file, uuid)
|
const metadata = await getFileMetadata(file, uuid)
|
||||||
const _id: Ref<Attachment> = generateId()
|
const _id: Ref<Attachment> = generateId()
|
||||||
|
|
||||||
attachments.set(_id, {
|
attachments.set(_id, {
|
||||||
|
@ -1,111 +0,0 @@
|
|||||||
<!--
|
|
||||||
// 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 { Button, Dialog } from '@hcengineering/ui'
|
|
||||||
import type { Attachment } from '@hcengineering/attachment'
|
|
||||||
import AudioPlayer from './AudioPlayer.svelte'
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte'
|
|
||||||
import presentation, { ActionContext, PDFViewer, IconDownload, getFileUrl } from '@hcengineering/presentation'
|
|
||||||
import { getType } from '../utils'
|
|
||||||
|
|
||||||
export let value: 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 = getFileUrl(value.file, 'full', 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>
|
|
@ -17,10 +17,9 @@
|
|||||||
import { Photo } from '@hcengineering/attachment'
|
import { Photo } from '@hcengineering/attachment'
|
||||||
import { Class, Doc, Ref, Space } from '@hcengineering/core'
|
import { Class, Doc, Ref, Space } from '@hcengineering/core'
|
||||||
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient, getFileUrl, PDFViewer } from '@hcengineering/presentation'
|
import { FilePreviewPopup, createQuery, getClient, getFileUrl, uploadFile } from '@hcengineering/presentation'
|
||||||
import { Button, IconAdd, Label, showPopup, Spinner } from '@hcengineering/ui'
|
import { Button, IconAdd, Label, showPopup, Spinner } from '@hcengineering/ui'
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import { uploadFile } from '../utils'
|
|
||||||
import UploadDuo from './icons/UploadDuo.svelte'
|
import UploadDuo from './icons/UploadDuo.svelte'
|
||||||
|
|
||||||
export let objectId: Ref<Doc>
|
export let objectId: Ref<Doc>
|
||||||
@ -88,7 +87,7 @@
|
|||||||
el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
|
el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
|
||||||
if (item !== undefined) {
|
if (item !== undefined) {
|
||||||
showPopup(
|
showPopup(
|
||||||
PDFViewer,
|
FilePreviewPopup,
|
||||||
{ file: item.file, name: item.name, contentType: item.type },
|
{ file: item.file, name: item.name, contentType: item.type },
|
||||||
item.type.startsWith('image/') ? 'centered' : 'float'
|
item.type.startsWith('image/') ? 'centered' : 'float'
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,7 @@ import attachment, { type Attachment } from '@hcengineering/attachment'
|
|||||||
import { type ObjQueryType, SortingOrder, type SortingQuery, type Markup } from '@hcengineering/core'
|
import { type ObjQueryType, SortingOrder, type SortingQuery, type Markup } from '@hcengineering/core'
|
||||||
import { type IntlString, type Resources } from '@hcengineering/platform'
|
import { type IntlString, type Resources } from '@hcengineering/platform'
|
||||||
import preference from '@hcengineering/preference'
|
import preference from '@hcengineering/preference'
|
||||||
import { getClient, PDFViewer } from '@hcengineering/presentation'
|
import { PDFViewer, deleteFile, getClient, uploadFile } from '@hcengineering/presentation'
|
||||||
import activity, { type ActivityMessage, type DocUpdateMessage } from '@hcengineering/activity'
|
import activity, { type ActivityMessage, type DocUpdateMessage } from '@hcengineering/activity'
|
||||||
|
|
||||||
import TxAttachmentCreate from './components/activity/TxAttachmentCreate.svelte'
|
import TxAttachmentCreate from './components/activity/TxAttachmentCreate.svelte'
|
||||||
@ -42,8 +42,6 @@ import IconAttachment from './components/icons/Attachment.svelte'
|
|||||||
import AttachmentPreview from './components/AttachmentPreview.svelte'
|
import AttachmentPreview from './components/AttachmentPreview.svelte'
|
||||||
import AttachmentsUpdatedMessage from './components/activity/AttachmentsUpdatedMessage.svelte'
|
import AttachmentsUpdatedMessage from './components/activity/AttachmentsUpdatedMessage.svelte'
|
||||||
import AttachmentsTooltip from './components/AttachmentsTooltip.svelte'
|
import AttachmentsTooltip from './components/AttachmentsTooltip.svelte'
|
||||||
import MediaViewer from './components/MediaViewer.svelte'
|
|
||||||
import { deleteFile, uploadFile } from './utils'
|
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
|
|
||||||
@ -260,8 +258,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
Attachments,
|
Attachments,
|
||||||
FileBrowser,
|
FileBrowser,
|
||||||
Photos,
|
Photos,
|
||||||
PDFViewer,
|
PDFViewer
|
||||||
MediaViewer
|
|
||||||
},
|
},
|
||||||
activity: {
|
activity: {
|
||||||
TxAttachmentCreate,
|
TxAttachmentCreate,
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import attachment, { attachmentId } from '@hcengineering/attachment'
|
import attachment, { attachmentId } from '@hcengineering/attachment'
|
||||||
import type { IntlString, StatusCode } from '@hcengineering/platform'
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
import { mergeIds } from '@hcengineering/platform'
|
import { mergeIds } from '@hcengineering/platform'
|
||||||
import { type ViewAction } from '@hcengineering/view'
|
import { type ViewAction } from '@hcengineering/view'
|
||||||
|
|
||||||
@ -42,9 +42,6 @@ export default mergeIds(attachmentId, attachment, {
|
|||||||
RemoveAttachmentFromSaved: '' as IntlString,
|
RemoveAttachmentFromSaved: '' as IntlString,
|
||||||
Pinned: '' as IntlString
|
Pinned: '' as IntlString
|
||||||
},
|
},
|
||||||
status: {
|
|
||||||
FileTooLarge: '' as StatusCode
|
|
||||||
},
|
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
AddAttachmentToSaved: '' as ViewAction,
|
AddAttachmentToSaved: '' as ViewAction,
|
||||||
DeleteAttachmentFromSaved: '' as ViewAction,
|
DeleteAttachmentFromSaved: '' as ViewAction,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
// Copyright © 2021 Hardcore Engineering Inc.
|
// Copyright © 2021, 2024 Hardcore Engineering Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
// 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
|
// you may not use this file except in compliance with the License. You may
|
||||||
@ -14,79 +14,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { writable } from 'svelte/store'
|
import { type Attachment } from '@hcengineering/attachment'
|
||||||
import attachments, {
|
import { type Class, type Data, type Doc, type Ref, type Space, type TxOperations as Client } from '@hcengineering/core'
|
||||||
type AttachmentPreviewExtension,
|
import { getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
||||||
type Attachment,
|
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
type AttachmentMetadata
|
|
||||||
} from '@hcengineering/attachment'
|
|
||||||
import {
|
|
||||||
type Class,
|
|
||||||
concatLink,
|
|
||||||
type Data,
|
|
||||||
type Doc,
|
|
||||||
type Ref,
|
|
||||||
type Space,
|
|
||||||
type TxOperations as Client
|
|
||||||
} from '@hcengineering/core'
|
|
||||||
import presentation, { createQuery, getFileUrl, getImageSize } from '@hcengineering/presentation'
|
|
||||||
import {
|
|
||||||
PlatformError,
|
|
||||||
Severity,
|
|
||||||
Status,
|
|
||||||
getMetadata,
|
|
||||||
getResource,
|
|
||||||
setPlatformStatus,
|
|
||||||
unknownError
|
|
||||||
} from '@hcengineering/platform'
|
|
||||||
|
|
||||||
import attachment from './plugin'
|
import attachment from './plugin'
|
||||||
|
|
||||||
export async function uploadFile (file: File): Promise<string> {
|
|
||||||
const uploadUrl = getMetadata(presentation.metadata.UploadURL)
|
|
||||||
|
|
||||||
if (uploadUrl === undefined) {
|
|
||||||
throw Error('UploadURL is not defined')
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new FormData()
|
|
||||||
data.append('file', file)
|
|
||||||
|
|
||||||
const resp = await fetch(uploadUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer ' + (getMetadata(presentation.metadata.Token) as string)
|
|
||||||
},
|
|
||||||
body: data
|
|
||||||
})
|
|
||||||
|
|
||||||
if (resp.status !== 200) {
|
|
||||||
if (resp.status === 413) {
|
|
||||||
throw new PlatformError(new Status(Severity.ERROR, attachment.status.FileTooLarge, {}))
|
|
||||||
} else {
|
|
||||||
throw Error(`Failed to upload file: ${resp.statusText}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await resp.text()
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteFile (id: string): Promise<void> {
|
|
||||||
const uploadUrl = getMetadata(presentation.metadata.UploadURL) ?? ''
|
|
||||||
|
|
||||||
const url = concatLink(uploadUrl, `?file=${id}`)
|
|
||||||
const resp = await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer ' + (getMetadata(presentation.metadata.Token) as string)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (resp.status !== 200) {
|
|
||||||
throw new Error('Failed to delete file')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createAttachments (
|
export async function createAttachments (
|
||||||
client: Client,
|
client: Client,
|
||||||
list: FileList,
|
list: FileList,
|
||||||
@ -100,7 +34,7 @@ export async function createAttachments (
|
|||||||
const file = list.item(index)
|
const file = list.item(index)
|
||||||
if (file !== null) {
|
if (file !== null) {
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
const metadata = await getAttachmentMetadata(file, uuid)
|
const metadata = await getFileMetadata(file, uuid)
|
||||||
|
|
||||||
await client.addCollection(attachmentClass, space, objectId, objectClass, 'attachments', {
|
await client.addCollection(attachmentClass, space, objectId, objectClass, 'attachments', {
|
||||||
...extraData,
|
...extraData,
|
||||||
@ -140,114 +74,3 @@ export function getType (type: string): 'image' | 'text' | 'json' | 'video' | 'a
|
|||||||
|
|
||||||
return 'other'
|
return 'other'
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAttachmentMetadata (file: File, uuid: string): Promise<AttachmentMetadata | undefined> {
|
|
||||||
const type = getType(file.type)
|
|
||||||
|
|
||||||
if (type === 'video') {
|
|
||||||
const size = await getVideoSize(uuid)
|
|
||||||
|
|
||||||
if (size === undefined) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
originalHeight: size.height,
|
|
||||||
originalWidth: size.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'image') {
|
|
||||||
const size = await getImageSize(file, getFileUrl(uuid, 'full'))
|
|
||||||
|
|
||||||
return {
|
|
||||||
originalHeight: size.height,
|
|
||||||
originalWidth: size.width,
|
|
||||||
pixelRatio: size.pixelRatio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getVideoSize (uuid: string): Promise<{ width: number, height: number } | undefined> {
|
|
||||||
const promise = new Promise<{ width: number, height: number }>((resolve, reject) => {
|
|
||||||
const element = document.createElement('video')
|
|
||||||
|
|
||||||
element.onloadedmetadata = () => {
|
|
||||||
const height = element.videoHeight
|
|
||||||
const width = element.videoWidth
|
|
||||||
|
|
||||||
resolve({ height, width })
|
|
||||||
}
|
|
||||||
|
|
||||||
element.onerror = reject
|
|
||||||
element.src = getFileUrl(uuid, 'full')
|
|
||||||
})
|
|
||||||
|
|
||||||
return await promise
|
|
||||||
}
|
|
||||||
|
|
||||||
export const previewTypes = writable<AttachmentPreviewExtension[]>([])
|
|
||||||
const previewTypesQuery = createQuery(true)
|
|
||||||
previewTypesQuery.query(attachments.class.AttachmentPreviewExtension, {}, (result) => {
|
|
||||||
previewTypes.set(result)
|
|
||||||
})
|
|
||||||
|
|
||||||
function getPreviewTypeRegExp (type: string): RegExp {
|
|
||||||
return new RegExp(`^${type.replaceAll('/', '\\/').replaceAll('*', '.*')}$`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export async function isOpenable (contentType: string, _previewTypes: AttachmentPreviewExtension[]): Promise<boolean> {
|
|
||||||
for (const previewType of _previewTypes) {
|
|
||||||
if (await isApplicableType(previewType, contentType)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
async function isApplicableType (
|
|
||||||
{ contentType, availabilityChecker }: AttachmentPreviewExtension,
|
|
||||||
_contentType: string
|
|
||||||
): Promise<boolean> {
|
|
||||||
const checkAvailability = availabilityChecker !== undefined ? await getResource(availabilityChecker) : undefined
|
|
||||||
const isAvailable: boolean = checkAvailability === undefined || (await checkAvailability())
|
|
||||||
|
|
||||||
return (
|
|
||||||
isAvailable &&
|
|
||||||
(Array.isArray(contentType) ? contentType : [contentType]).some((type) =>
|
|
||||||
getPreviewTypeRegExp(type).test(_contentType)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function comparePreviewTypes (a: AttachmentPreviewExtension, b: AttachmentPreviewExtension): number {
|
|
||||||
if (a.order === undefined && b.order === undefined) {
|
|
||||||
return 0
|
|
||||||
} else if (a.order === undefined) {
|
|
||||||
return -1
|
|
||||||
} else if (b.order === undefined) {
|
|
||||||
return 1
|
|
||||||
} else {
|
|
||||||
return a.order - b.order
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPreviewType (
|
|
||||||
contentType: string,
|
|
||||||
_previewTypes: AttachmentPreviewExtension[]
|
|
||||||
): Promise<AttachmentPreviewExtension | undefined> {
|
|
||||||
const applicableTypes: AttachmentPreviewExtension[] = []
|
|
||||||
for (const previewType of _previewTypes) {
|
|
||||||
if (await isApplicableType(previewType, contentType)) {
|
|
||||||
applicableTypes.push(previewType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return applicableTypes.sort(comparePreviewTypes)[0]
|
|
||||||
}
|
|
||||||
|
@ -18,8 +18,8 @@ import type { AttachedDoc, 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'
|
||||||
import { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
|
import { type BlobMetadata } from '@hcengineering/presentation'
|
||||||
import { type ComponentPointExtension } from '@hcengineering/presentation'
|
import { AnyComponent } from '@hcengineering/ui'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -41,24 +41,7 @@ export interface Attachment extends AttachedDoc {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type AttachmentMetadata = ImageMetadata | VideoMetadata
|
export type AttachmentMetadata = BlobMetadata
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface ImageMetadata {
|
|
||||||
originalWidth: number
|
|
||||||
originalHeight: number
|
|
||||||
pixelRatio: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface VideoMetadata {
|
|
||||||
originalWidth: number
|
|
||||||
originalHeight: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -72,16 +55,6 @@ export interface SavedAttachments extends Preference {
|
|||||||
attachedTo: Ref<Attachment>
|
attachedTo: Ref<Attachment>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface AttachmentPreviewExtension extends ComponentPointExtension {
|
|
||||||
contentType: string | string[]
|
|
||||||
alignment?: string
|
|
||||||
// Extension is only available if this checker returns true
|
|
||||||
availabilityChecker?: Resource<() => Promise<boolean>>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -93,8 +66,7 @@ export default plugin(attachmentId, {
|
|||||||
Photos: '' as AnyComponent,
|
Photos: '' as AnyComponent,
|
||||||
AttachmentsPresenter: '' as AnyComponent,
|
AttachmentsPresenter: '' as AnyComponent,
|
||||||
FileBrowser: '' as AnyComponent,
|
FileBrowser: '' as AnyComponent,
|
||||||
PDFViewer: '' as AnyComponent,
|
PDFViewer: '' as AnyComponent
|
||||||
MediaViewer: '' as AnyComponent
|
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
Attachment: '' as Asset,
|
Attachment: '' as Asset,
|
||||||
@ -103,8 +75,7 @@ export default plugin(attachmentId, {
|
|||||||
class: {
|
class: {
|
||||||
Attachment: '' as Ref<Class<Attachment>>,
|
Attachment: '' as Ref<Class<Attachment>>,
|
||||||
Photo: '' as Ref<Class<Photo>>,
|
Photo: '' as Ref<Class<Photo>>,
|
||||||
SavedAttachments: '' as Ref<Class<SavedAttachments>>,
|
SavedAttachments: '' as Ref<Class<SavedAttachments>>
|
||||||
AttachmentPreviewExtension: '' as Ref<Class<AttachmentPreviewExtension>>
|
|
||||||
},
|
},
|
||||||
helper: {
|
helper: {
|
||||||
UploadFile: '' as Resource<(file: File) => Promise<string>>,
|
UploadFile: '' as Resource<(file: File) => Promise<string>>,
|
||||||
@ -130,13 +101,5 @@ export default plugin(attachmentId, {
|
|||||||
DeleteFile: '' as IntlString,
|
DeleteFile: '' as IntlString,
|
||||||
Attachments: '' as IntlString,
|
Attachments: '' as IntlString,
|
||||||
FileBrowser: '' as IntlString
|
FileBrowser: '' as IntlString
|
||||||
},
|
|
||||||
previewExtension: {
|
|
||||||
Image: '' as Ref<AttachmentPreviewExtension>,
|
|
||||||
Media: '' as Ref<AttachmentPreviewExtension>,
|
|
||||||
PDF: '' as Ref<AttachmentPreviewExtension>
|
|
||||||
},
|
|
||||||
extension: {
|
|
||||||
AttachmentPreview: '' as ComponentExtensionId
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
import attachment from '@hcengineering/attachment'
|
import attachment from '@hcengineering/attachment'
|
||||||
import { AvatarType } from '@hcengineering/contact'
|
import { AvatarType } from '@hcengineering/contact'
|
||||||
import { Asset, getResource } from '@hcengineering/platform'
|
import { Asset, getResource } from '@hcengineering/platform'
|
||||||
|
import { uploadFile } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
AnySvelteComponent,
|
AnySvelteComponent,
|
||||||
IconSize,
|
IconSize,
|
||||||
|
@ -41,7 +41,6 @@
|
|||||||
"@hcengineering/platform": "^0.6.9",
|
"@hcengineering/platform": "^0.6.9",
|
||||||
"@hcengineering/analytics": "^0.6.0",
|
"@hcengineering/analytics": "^0.6.0",
|
||||||
"@hcengineering/core": "^0.6.28",
|
"@hcengineering/core": "^0.6.28",
|
||||||
"@hcengineering/attachment": "^0.6.9",
|
|
||||||
"@hcengineering/drive": "^0.6.0",
|
"@hcengineering/drive": "^0.6.0",
|
||||||
"@hcengineering/contact-resources": "^0.6.0",
|
"@hcengineering/contact-resources": "^0.6.0",
|
||||||
"@hcengineering/panel": "^0.6.15",
|
"@hcengineering/panel": "^0.6.15",
|
||||||
|
@ -15,28 +15,53 @@
|
|||||||
//
|
//
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { WithLookup } from '@hcengineering/core'
|
||||||
import { File } from '@hcengineering/drive'
|
import { File } from '@hcengineering/drive'
|
||||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
import { Icon, tooltip } from '@hcengineering/ui'
|
import { FilePreviewPopup } from '@hcengineering/presentation'
|
||||||
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
|
import { Icon, showPopup, tooltip } from '@hcengineering/ui'
|
||||||
import { ObjectPresenterType } from '@hcengineering/view'
|
import { ObjectPresenterType } from '@hcengineering/view'
|
||||||
|
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
import drive from '../plugin'
|
import drive from '../plugin'
|
||||||
|
|
||||||
export let value: File
|
export let value: WithLookup<File>
|
||||||
export let inline: boolean = false
|
export let inline: boolean = false
|
||||||
export let disabled: boolean = false
|
export let disabled: boolean = false
|
||||||
export let accent: boolean = false
|
export let accent: boolean = false
|
||||||
export let noUnderline: boolean = false
|
export let noUnderline: boolean = false
|
||||||
export let shouldShowAvatar = true
|
export let shouldShowAvatar = true
|
||||||
export let type: ObjectPresenterType = 'link'
|
export let type: ObjectPresenterType = 'link'
|
||||||
|
|
||||||
|
function handleClick (): void {
|
||||||
|
if (disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.$lookup?.file === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = value.$lookup?.file
|
||||||
|
|
||||||
|
showPopup(
|
||||||
|
FilePreviewPopup,
|
||||||
|
{
|
||||||
|
file: blob._id,
|
||||||
|
contentType: blob.contentType,
|
||||||
|
name: value.name,
|
||||||
|
metadata: value.metadata
|
||||||
|
},
|
||||||
|
'float'
|
||||||
|
)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
{#if inline}
|
{#if inline}
|
||||||
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
|
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
|
||||||
{:else if type === 'link'}
|
{:else if type === 'link'}
|
||||||
<DocNavLink {disabled} object={value} {accent} {noUnderline}>
|
<DocNavLink object={value} onClick={handleClick} {disabled} {accent} {noUnderline}>
|
||||||
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
|
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
|
||||||
{#if shouldShowAvatar}
|
{#if shouldShowAvatar}
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
|
@ -13,11 +13,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import attachment from '@hcengineering/attachment'
|
import { type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||||
import { type Blob, type Class, type Doc, type Ref } from '@hcengineering/core'
|
|
||||||
import drive, { type Drive, type Folder } from '@hcengineering/drive'
|
import drive, { type Drive, type Folder } from '@hcengineering/drive'
|
||||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient, getFileMetadata, uploadFile } from '@hcengineering/presentation'
|
||||||
import { showPopup } from '@hcengineering/ui'
|
import { showPopup } from '@hcengineering/ui'
|
||||||
import { openDoc } from '@hcengineering/view-resources'
|
import { openDoc } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
@ -68,14 +67,13 @@ export async function createFile (file: File, space: Ref<Drive>, parent: Folder
|
|||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uploadFile = await getResource(attachment.helper.UploadFile)
|
|
||||||
const uuid = await uploadFile(file)
|
const uuid = await uploadFile(file)
|
||||||
// TODO obtain metadata
|
const metadata = await getFileMetadata(file, uuid)
|
||||||
// const metadata = await getAttachmentMetadata(file, uuid)
|
|
||||||
|
|
||||||
await client.createDoc(drive.class.File, space, {
|
await client.createDoc(drive.class.File, space, {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
file: uuid as Ref<Blob>,
|
file: uuid,
|
||||||
|
metadata,
|
||||||
parent: parent?._id ?? drive.ids.Root,
|
parent: parent?._id ?? drive.ids.Root,
|
||||||
path: parent !== undefined ? [parent._id, ...parent.path] : []
|
path: parent !== undefined ? [parent._id, ...parent.path] : []
|
||||||
})
|
})
|
||||||
|
@ -41,6 +41,7 @@ export interface Folder extends Resource {
|
|||||||
/** @public */
|
/** @public */
|
||||||
export interface File extends Resource {
|
export interface File extends Resource {
|
||||||
file: Ref<Blob>
|
file: Ref<Blob>
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
|
||||||
parent: Ref<Folder>
|
parent: Ref<Folder>
|
||||||
path: Ref<Folder>[]
|
path: Ref<Folder>[]
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
<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 { deleteFile } from '@hcengineering/attachment-resources/src/utils'
|
|
||||||
import contact, { Channel, ChannelProvider, combineName, findContacts, Person } from '@hcengineering/contact'
|
import contact, { 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 {
|
||||||
@ -33,6 +32,7 @@
|
|||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { getMetadata, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { getMetadata, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import presentation, {
|
import presentation, {
|
||||||
|
FilePreviewPopup,
|
||||||
Card,
|
Card,
|
||||||
createQuery,
|
createQuery,
|
||||||
DraftController,
|
DraftController,
|
||||||
@ -41,7 +41,7 @@
|
|||||||
KeyedAttribute,
|
KeyedAttribute,
|
||||||
MessageBox,
|
MessageBox,
|
||||||
MultipleDraftController,
|
MultipleDraftController,
|
||||||
PDFViewer
|
deleteFile
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
import type { Candidate, CandidateDraft } from '@hcengineering/recruit'
|
import type { Candidate, CandidateDraft } from '@hcengineering/recruit'
|
||||||
import { recognizeDocument } from '@hcengineering/rekoni'
|
import { recognizeDocument } from '@hcengineering/rekoni'
|
||||||
@ -740,8 +740,12 @@
|
|||||||
icon={FileIcon}
|
icon={FileIcon}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showPopup(
|
showPopup(
|
||||||
PDFViewer,
|
FilePreviewPopup,
|
||||||
{ file: object.resumeUuid, name: object.resumeName },
|
{
|
||||||
|
file: object.resumeUuid,
|
||||||
|
contentType: object.resumeType ?? 'application/pdf',
|
||||||
|
name: object.resumeName
|
||||||
|
},
|
||||||
object.resumeType?.startsWith('image/') ? 'centered' : 'float'
|
object.resumeType?.startsWith('image/') ? 'centered' : 'float'
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
@ -14,9 +14,8 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
import { deleteFile } from '@hcengineering/attachment-resources/src/utils'
|
|
||||||
import core, { AttachedData, Doc, Ref, SortingOrder } from '@hcengineering/core'
|
import core, { AttachedData, Doc, Ref, SortingOrder } from '@hcengineering/core'
|
||||||
import { DraftController, draftsStore, getClient } from '@hcengineering/presentation'
|
import { DraftController, draftsStore, getClient, deleteFile } from '@hcengineering/presentation'
|
||||||
import tags from '@hcengineering/tags'
|
import tags from '@hcengineering/tags'
|
||||||
import { makeRank } from '@hcengineering/task'
|
import { makeRank } from '@hcengineering/task'
|
||||||
import { Component, Issue, IssueDraft, IssueParentInfo, Milestone, Project } from '@hcengineering/tracker'
|
import { Component, Issue, IssueDraft, IssueParentInfo, Milestone, Project } from '@hcengineering/tracker'
|
||||||
|
58
plugins/view-resources/src/blob.ts
Normal file
58
plugins/view-resources/src/blob.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { type Blob, type Ref } from '@hcengineering/core'
|
||||||
|
import { type BlobMetadata, getFileUrl, getImageSize } from '@hcengineering/presentation'
|
||||||
|
|
||||||
|
export async function blobImageMetadata (file: File, blob: Ref<Blob>): Promise<BlobMetadata | undefined> {
|
||||||
|
const size = await getImageSize(file, getFileUrl(blob, 'full'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
originalHeight: size.height,
|
||||||
|
originalWidth: size.width,
|
||||||
|
pixelRatio: size.pixelRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function blobVideoMetadata (file: File, blob: Ref<Blob>): Promise<BlobMetadata | undefined> {
|
||||||
|
const size = await getVideoSize(blob)
|
||||||
|
|
||||||
|
if (size === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
originalHeight: size.height,
|
||||||
|
originalWidth: size.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getVideoSize (uuid: string): Promise<{ width: number, height: number } | undefined> {
|
||||||
|
const promise = new Promise<{ width: number, height: number }>((resolve, reject) => {
|
||||||
|
const element = document.createElement('video')
|
||||||
|
|
||||||
|
element.onloadedmetadata = () => {
|
||||||
|
const height = element.videoHeight
|
||||||
|
const width = element.videoWidth
|
||||||
|
|
||||||
|
resolve({ height, width })
|
||||||
|
}
|
||||||
|
|
||||||
|
element.onerror = reject
|
||||||
|
element.src = getFileUrl(uuid, 'full')
|
||||||
|
})
|
||||||
|
|
||||||
|
return await promise
|
||||||
|
}
|
43
plugins/view-resources/src/components/icons/Pause.svelte
Normal file
43
plugins/view-resources/src/components/icons/Pause.svelte
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// 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">
|
||||||
|
export let size: 'small' | 'medium' | 'large'
|
||||||
|
const fill: string = 'currentColor'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg class="svg-{size}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<g {fill}>
|
||||||
|
<path
|
||||||
|
d="M39.806,72.858h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.119c0-2.176,1.764-3.94,3.94-3.94h8.915 c2.176,0,3.94,1.764,3.94,3.94v37.799C43.746,71.094,41.982,72.858,39.806,72.858z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M68.109,72.821h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.082c0-2.176,1.764-3.94,3.94-3.94h8.915 c2.176,0,3.94,1.764,3.94,3.94v37.799C72.049,71.057,70.285,72.821,68.109,72.821z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M40.489,27.248c0.769,0.719,1.257,1.735,1.257,2.871v37.799c0,2.176-1.764,3.94-3.94,3.94h-8.915 c-0.234,0-0.46-0.03-0.683-0.069c0.704,0.658,1.643,1.069,2.683,1.069h8.915c2.176,0,3.94-1.764,3.94-3.94V31.119 C43.746,29.177,42.338,27.573,40.489,27.248z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M68.792,27.211c0.769,0.719,1.257,1.735,1.257,2.871v37.799c0,2.176-1.764,3.94-3.94,3.94h-8.915 c-0.234,0-0.46-0.03-0.683-0.069c0.704,0.658,1.643,1.069,2.683,1.069h8.915c2.176,0,3.94-1.764,3.94-3.94V31.082 C72.049,29.14,70.641,27.535,68.792,27.211z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M39.806,72.858h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.119 c0-2.176,1.764-3.94,3.94-3.94h8.915c2.176,0,3.94,1.764,3.94,3.94v37.799C43.746,71.094,41.982,72.858,39.806,72.858z"
|
||||||
|
style="fill:none;stroke:#000000;stroke-miterlimit:10;"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M68.109,72.821h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.082 c0-2.176,1.764-3.94,3.94-3.94h8.915c2.176,0,3.94,1.764,3.94,3.94v37.799C72.049,71.057,70.285,72.821,68.109,72.821z"
|
||||||
|
style="fill:none;stroke:#000000;stroke-miterlimit:10;"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
@ -0,0 +1,67 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// 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 { type Blob, type Ref } from '@hcengineering/core'
|
||||||
|
import { CircleButton, Progress } from '@hcengineering/ui'
|
||||||
|
|
||||||
|
import Play from '../icons/Play.svelte'
|
||||||
|
import Pause from '../icons/Pause.svelte'
|
||||||
|
import { getFileUrl } from '@hcengineering/presentation'
|
||||||
|
|
||||||
|
export let value: Ref<Blob>
|
||||||
|
export let name: string
|
||||||
|
export let contentType: string
|
||||||
|
export let fullSize = false
|
||||||
|
|
||||||
|
let time = 0
|
||||||
|
let duration = Number.POSITIVE_INFINITY
|
||||||
|
let paused = true
|
||||||
|
|
||||||
|
function handleClick (): void {
|
||||||
|
paused = !paused
|
||||||
|
}
|
||||||
|
|
||||||
|
$: icon = !paused ? Pause : Play
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container flex-between" class:fullSize>
|
||||||
|
<CircleButton size="x-large" on:click={handleClick} {icon} />
|
||||||
|
<div class="w-full ml-4">
|
||||||
|
<Progress
|
||||||
|
value={time}
|
||||||
|
max={Number.isFinite(duration) ? duration : 100}
|
||||||
|
editable
|
||||||
|
on:change={(e) => (time = e.detail)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<audio bind:duration bind:currentTime={time} bind:paused>
|
||||||
|
<source src={getFileUrl(value, 'full', name)} type={contentType} />
|
||||||
|
</audio>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.container {
|
||||||
|
padding: 0.5rem;
|
||||||
|
width: 20rem;
|
||||||
|
background-color: var(--accent-bg-color);
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
|
||||||
|
&.fullSize {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,41 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// 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 { type Blob, type Ref } from '@hcengineering/core'
|
||||||
|
import { type BlobMetadata, getFileUrl } from '@hcengineering/presentation'
|
||||||
|
import AudioPlayer from './AudioPlayer.svelte'
|
||||||
|
|
||||||
|
export let value: Ref<Blob>
|
||||||
|
export let name: string
|
||||||
|
export let contentType: string
|
||||||
|
export let metadata: BlobMetadata | undefined
|
||||||
|
|
||||||
|
$: src = value === undefined ? '' : getFileUrl(value, 'full', name)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if src}
|
||||||
|
<AudioPlayer {value} {name} {contentType} fullSize={true} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.img-fit {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,40 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// 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 { type Blob, type Ref } from '@hcengineering/core'
|
||||||
|
import { type BlobMetadata, getFileUrl } from '@hcengineering/presentation'
|
||||||
|
|
||||||
|
export let value: Ref<Blob>
|
||||||
|
export let name: string
|
||||||
|
export let contentType: string
|
||||||
|
export let metadata: BlobMetadata | undefined
|
||||||
|
|
||||||
|
$: src = value === undefined ? '' : getFileUrl(value, 'full', name)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if src}
|
||||||
|
<img class="w-full h-full img-fit" {src} alt="" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.img-fit {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,35 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// 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 { type Blob, type Ref } from '@hcengineering/core'
|
||||||
|
import { type BlobMetadata, getFileUrl } from '@hcengineering/presentation'
|
||||||
|
|
||||||
|
export let value: Ref<Blob>
|
||||||
|
export let name: string
|
||||||
|
export let contentType: string
|
||||||
|
export let metadata: BlobMetadata | undefined
|
||||||
|
|
||||||
|
$: src = value === undefined ? '' : getFileUrl(value, 'full', name)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if src}
|
||||||
|
<iframe src={src + '#view=FitH&navpanes=0'} class="w-full h-full" title={name} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
iframe {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,32 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// 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 { type Blob, type Ref } from '@hcengineering/core'
|
||||||
|
import { type BlobMetadata, getFileUrl } from '@hcengineering/presentation'
|
||||||
|
|
||||||
|
export let value: Ref<Blob>
|
||||||
|
export let name: string
|
||||||
|
export let contentType: string
|
||||||
|
export let metadata: BlobMetadata | undefined
|
||||||
|
|
||||||
|
$: src = value === undefined ? '' : getFileUrl(value, 'full', name)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if src}
|
||||||
|
<video controls preload={'auto'}>
|
||||||
|
<source {src} />
|
||||||
|
<track kind="captions" label={name} />
|
||||||
|
</video>
|
||||||
|
{/if}
|
@ -93,6 +93,12 @@ import TreeItem from './components/navigator/TreeItem.svelte'
|
|||||||
import TreeNode from './components/navigator/TreeNode.svelte'
|
import TreeNode from './components/navigator/TreeNode.svelte'
|
||||||
import StatusPresenter from './components/status/StatusPresenter.svelte'
|
import StatusPresenter from './components/status/StatusPresenter.svelte'
|
||||||
import StatusRefPresenter from './components/status/StatusRefPresenter.svelte'
|
import StatusRefPresenter from './components/status/StatusRefPresenter.svelte'
|
||||||
|
import AudioViewer from './components/viewer/AudioViewer.svelte'
|
||||||
|
import ImageViewer from './components/viewer/ImageViewer.svelte'
|
||||||
|
import VideoViewer from './components/viewer/VideoViewer.svelte'
|
||||||
|
import PDFViewer from './components/viewer/PDFViewer.svelte'
|
||||||
|
|
||||||
|
import { blobImageMetadata, blobVideoMetadata } from './blob'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
afterResult,
|
afterResult,
|
||||||
@ -281,7 +287,11 @@ export default async (): Promise<Resources> => ({
|
|||||||
StringFilterPresenter,
|
StringFilterPresenter,
|
||||||
AttachedDocPanel,
|
AttachedDocPanel,
|
||||||
ObjectMention,
|
ObjectMention,
|
||||||
SearchSelector
|
SearchSelector,
|
||||||
|
AudioViewer,
|
||||||
|
ImageViewer,
|
||||||
|
VideoViewer,
|
||||||
|
PDFViewer
|
||||||
},
|
},
|
||||||
popup: {
|
popup: {
|
||||||
PositionElementAlignment
|
PositionElementAlignment
|
||||||
@ -317,6 +327,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
CanArchiveSpace: canArchiveSpace,
|
CanArchiveSpace: canArchiveSpace,
|
||||||
CanDeleteSpace: canDeleteSpace,
|
CanDeleteSpace: canDeleteSpace,
|
||||||
CanJoinSpace: canJoinSpace,
|
CanJoinSpace: canJoinSpace,
|
||||||
CanLeaveSpace: canLeaveSpace
|
CanLeaveSpace: canLeaveSpace,
|
||||||
|
BlobImageMetadata: blobImageMetadata,
|
||||||
|
BlobVideoMetadata: blobVideoMetadata
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user