mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-12 13:42:38 +00:00
Files for cards (#8217)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
7fafd3d772
commit
af2f78a141
@ -24,13 +24,15 @@ import {
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import core, {
|
||||
AccountRole,
|
||||
type Blobs,
|
||||
DOMAIN_MODEL,
|
||||
IndexKind,
|
||||
SortingOrder,
|
||||
type CollectionSize,
|
||||
type MarkupBlobRef,
|
||||
type Rank,
|
||||
type Ref
|
||||
type Ref,
|
||||
ClassifierKind,
|
||||
type MarkupBlobRef
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
Collection,
|
||||
@ -75,6 +77,8 @@ export class TCard extends TDoc implements Card {
|
||||
@Prop(TypeCollaborativeDoc(), card.string.Content)
|
||||
content!: MarkupBlobRef
|
||||
|
||||
blobs!: Blobs
|
||||
|
||||
@Prop(TypeRef(card.class.Card), card.string.Parent)
|
||||
parent?: Ref<Card> | null
|
||||
|
||||
@ -103,6 +107,62 @@ export * from './migration'
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TMasterTag, TTag, TCard, MasterTagEditorSection)
|
||||
|
||||
builder.createDoc(
|
||||
card.class.MasterTag,
|
||||
core.space.Model,
|
||||
{
|
||||
label: attachment.string.File,
|
||||
extends: card.class.Card,
|
||||
icon: card.icon.File,
|
||||
kind: ClassifierKind.CLASS
|
||||
},
|
||||
card.types.File
|
||||
)
|
||||
|
||||
builder.mixin(card.types.File, card.class.MasterTag, setting.mixin.Editable, {
|
||||
value: true
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: card.types.File,
|
||||
descriptor: view.viewlet.Table,
|
||||
configOptions: {
|
||||
hiddenKeys: ['content', 'title']
|
||||
},
|
||||
config: [
|
||||
'',
|
||||
'_class',
|
||||
{ key: '', presenter: view.component.RolePresenter, label: card.string.Tags, props: { fullSize: true } },
|
||||
'modifiedOn'
|
||||
]
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: card.types.File,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: {
|
||||
groupBy: ['_class', 'createdBy', 'modifiedBy'],
|
||||
orderBy: [
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['rank', SortingOrder.Ascending]
|
||||
],
|
||||
other: []
|
||||
},
|
||||
configOptions: {
|
||||
hiddenKeys: ['content', 'title']
|
||||
},
|
||||
config: [
|
||||
{ key: '', props: { showParent: true } },
|
||||
'_class',
|
||||
{ key: '', presenter: view.component.RolePresenter, label: card.string.Tags, props: { fullSize: true } },
|
||||
{ key: '', displayProps: { grow: true } },
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
displayProps: { fixed: 'right', dividerBefore: true }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
core.space.Model,
|
||||
|
@ -13,7 +13,15 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { DOMAIN_MODEL, type Tx, type Blob, type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import {
|
||||
DOMAIN_MODEL,
|
||||
type Tx,
|
||||
type Blob,
|
||||
type Class,
|
||||
type Doc,
|
||||
type Ref,
|
||||
type BlobMetadata
|
||||
} from '@hcengineering/core'
|
||||
import { Mixin, Model, Prop, TypeRef, TypeString, type Builder } from '@hcengineering/model'
|
||||
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
||||
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
@ -23,7 +31,6 @@ import {
|
||||
type PresentationMiddlewareFactory
|
||||
} from '@hcengineering/presentation/src/pipeline'
|
||||
import {
|
||||
type BlobMetadata,
|
||||
type ComponentPointExtension,
|
||||
type CreateExtensionKind,
|
||||
type DocAttributeRule,
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Blob, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type Blob, type BlobMetadata, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import {
|
||||
@ -23,7 +23,7 @@ import {
|
||||
viewId,
|
||||
type ViewOptionsAction
|
||||
} from '@hcengineering/view'
|
||||
import { type BlobMetadata, type FileOrBlob, type FilePreviewExtension } from '@hcengineering/presentation/src/types'
|
||||
import { type FileOrBlob, type FilePreviewExtension } from '@hcengineering/presentation/src/types'
|
||||
import { type PresentationMiddlewareFactory } from '@hcengineering/presentation/src/pipeline'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
|
||||
|
@ -601,6 +601,11 @@ export interface Sequence extends Doc {
|
||||
sequence: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type BlobMetadata = Record<string, any>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
@ -622,6 +627,18 @@ export interface Blob extends Doc {
|
||||
size: number
|
||||
}
|
||||
|
||||
export interface BlobType {
|
||||
file: Ref<Blob>
|
||||
|
||||
type: string
|
||||
|
||||
name: string
|
||||
|
||||
metadata?: BlobMetadata
|
||||
}
|
||||
|
||||
export type Blobs = Record<string, BlobType>
|
||||
|
||||
/**
|
||||
* For every blob will automatically add a lookup.
|
||||
*
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { BlobMetadata, type Blob, type Ref } from '@hcengineering/core'
|
||||
import {
|
||||
Button,
|
||||
Component,
|
||||
@ -28,7 +28,7 @@
|
||||
import { getFileUrl } from '../file'
|
||||
import { getPreviewType, previewTypes } from '../filetypes'
|
||||
import { imageSizeToRatio } from '../image'
|
||||
import { BlobMetadata, FilePreviewExtension } from '../types'
|
||||
import { FilePreviewExtension } from '../types'
|
||||
|
||||
export let file: Ref<Blob>
|
||||
export let name: string
|
||||
|
@ -14,13 +14,11 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { SortingOrder, type Blob, type Ref } from '@hcengineering/core'
|
||||
import { BlobMetadata, SortingOrder, type Blob, type Ref } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Button, Dialog, IconHistory, IconScribble, showPopup, tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
|
||||
import { BlobMetadata } from '../types'
|
||||
|
||||
import ActionContext from './ActionContext.svelte'
|
||||
import FilePreview from './FilePreview.svelte'
|
||||
import DownloadFileButton from './DownloadFileButton.svelte'
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { getBlobRef } from '../preview'
|
||||
|
||||
export let blob: Ref<Blob>
|
||||
export let alt: string = ''
|
||||
export let alt: string | undefined = undefined
|
||||
export let fit: string = 'contain'
|
||||
export let width: number
|
||||
export let height: number
|
||||
|
@ -14,13 +14,13 @@
|
||||
//
|
||||
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { type BlobMetadata, type Blob, type Ref } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { type PopupAlignment } from '@hcengineering/ui'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
import plugin from './plugin'
|
||||
import type { BlobMetadata, FileOrBlob, FilePreviewExtension } from './types'
|
||||
import type { FileOrBlob, FilePreviewExtension } from './types'
|
||||
import { createQuery } from './utils'
|
||||
|
||||
/**
|
||||
|
@ -10,7 +10,8 @@ import {
|
||||
type Ref,
|
||||
type RelatedDocument,
|
||||
type Space,
|
||||
type TxOperations
|
||||
type TxOperations,
|
||||
type BlobMetadata
|
||||
} from '@hcengineering/core'
|
||||
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import { type AnyComponent, type AnySvelteComponent, type ComponentExtensionId } from '@hcengineering/ui/src/types'
|
||||
@ -184,11 +185,6 @@ export interface DocRules extends Doc {
|
||||
*/
|
||||
export type FileOrBlob = File | Blob
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type BlobMetadata = Record<string, any>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -21,7 +21,7 @@
|
||||
export let src: string
|
||||
export let hlsSrc: string
|
||||
export let hlsThumbnail = ''
|
||||
export let name = ''
|
||||
export let name: string | undefined = undefined
|
||||
export let preload = true
|
||||
|
||||
let video: HTMLVideoElement | null = null
|
||||
@ -147,7 +147,7 @@
|
||||
</script>
|
||||
|
||||
<video bind:this={video} width="100%" height="100%" class="plyr" preload={preload ? 'auto' : 'none'} controls>
|
||||
<track kind="captions" label={name} />
|
||||
<track kind="captions" label={name ?? ''} />
|
||||
</video>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
export let src: string | undefined
|
||||
export let srcset: string | undefined = undefined
|
||||
export let alt: string = ''
|
||||
export let alt: string | undefined = undefined
|
||||
export let width: number | string
|
||||
export let height: number | string
|
||||
export let fit: string = 'contain'
|
||||
@ -55,7 +55,7 @@
|
||||
<img
|
||||
{src}
|
||||
{srcset}
|
||||
{alt}
|
||||
alt={alt ?? ''}
|
||||
{width}
|
||||
{height}
|
||||
style:object-fit={fit}
|
||||
|
@ -14,14 +14,14 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let src: string
|
||||
export let name: string = ''
|
||||
export let name: string | undefined = undefined
|
||||
export let preload = true
|
||||
|
||||
let video: HTMLVideoElement
|
||||
</script>
|
||||
|
||||
<video bind:this={video} {src} width="100%" height="100%" preload={preload ? 'auto' : 'none'} controls>
|
||||
<track kind="captions" label={name} />
|
||||
<track kind="captions" label={name ?? ''} />
|
||||
</video>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Attachment } from '@hcengineering/attachment'
|
||||
import type { WithLookup } from '@hcengineering/core'
|
||||
import type { BlobType, WithLookup } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import presentation, { canPreviewFile, getFileUrl, previewTypes } from '@hcengineering/presentation'
|
||||
import { IconMoreH, Menu, Action as UIAction, showPopup, tooltip } from '@hcengineering/ui'
|
||||
@ -24,9 +24,9 @@
|
||||
import AttachmentAction from './AttachmentAction.svelte'
|
||||
import FileDownload from './icons/FileDownload.svelte'
|
||||
import attachmentPlugin from '../plugin'
|
||||
import { openAttachmentInSidebar, showAttachmentPreviewPopup } from '../utils'
|
||||
import { isAttachment, openAttachmentInSidebar, showAttachmentPreviewPopup } from '../utils'
|
||||
|
||||
export let attachment: WithLookup<Attachment>
|
||||
export let attachment: WithLookup<Attachment> | BlobType
|
||||
export let isSaved = false
|
||||
export let removable = false
|
||||
|
||||
@ -87,22 +87,28 @@
|
||||
await openAttachmentInSidebar(attachment)
|
||||
}
|
||||
})
|
||||
actions.push({
|
||||
label: saveAttachmentAction.label,
|
||||
icon: saveAttachmentAction.icon,
|
||||
action: async (props: any, evt: Event) => {
|
||||
const impl = await getResource(saveAttachmentAction.action)
|
||||
await impl(attachment, evt)
|
||||
}
|
||||
})
|
||||
if (removable) {
|
||||
if (isAttachment(attachment)) {
|
||||
actions.push({
|
||||
label: attachmentPlugin.string.DeleteFile,
|
||||
label: saveAttachmentAction.label,
|
||||
icon: saveAttachmentAction.icon,
|
||||
action: async (props: any, evt: Event) => {
|
||||
const impl = await getResource(attachmentPlugin.actionImpl.DeleteAttachment)
|
||||
await impl(attachment, evt)
|
||||
if (isAttachment(attachment)) {
|
||||
const impl = await getResource(saveAttachmentAction.action)
|
||||
await impl(attachment, evt)
|
||||
}
|
||||
}
|
||||
})
|
||||
if (removable) {
|
||||
actions.push({
|
||||
label: attachmentPlugin.string.DeleteFile,
|
||||
action: async (props: any, evt: Event) => {
|
||||
if (isAttachment(attachment)) {
|
||||
const impl = await getResource(attachmentPlugin.actionImpl.DeleteAttachment)
|
||||
await impl(attachment, evt)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
showPopup(
|
||||
Menu,
|
||||
|
@ -14,14 +14,14 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import type { WithLookup } from '@hcengineering/core'
|
||||
import type { BlobType, WithLookup } from '@hcengineering/core'
|
||||
import { Image } from '@hcengineering/presentation'
|
||||
import { Loading } from '@hcengineering/ui'
|
||||
|
||||
import BrokenImage from './icons/BrokenImage.svelte'
|
||||
import { AttachmentImageSize } from '../types'
|
||||
|
||||
export let value: WithLookup<Attachment>
|
||||
export let value: WithLookup<Attachment> | BlobType
|
||||
export let size: AttachmentImageSize = 'auto'
|
||||
|
||||
interface Dimensions {
|
||||
@ -41,7 +41,7 @@
|
||||
|
||||
$: dimensions = getDimensions(value, size)
|
||||
|
||||
function getDimensions (value: Attachment, size: AttachmentImageSize): Dimensions {
|
||||
function getDimensions (value: Attachment | BlobType, size: AttachmentImageSize): Dimensions {
|
||||
if (size === 'auto' || size == null) {
|
||||
return {
|
||||
width: 300,
|
||||
|
@ -14,8 +14,9 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import { BlobType } from '@hcengineering/core'
|
||||
|
||||
export let value: Attachment | undefined
|
||||
export let value: Attachment | BlobType | undefined
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
|
@ -16,7 +16,7 @@
|
||||
<script lang="ts">
|
||||
import contact, { PermissionsStore } from '@hcengineering/contact'
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import core, { type WithLookup } from '@hcengineering/core'
|
||||
import core, { BlobType, type WithLookup } from '@hcengineering/core'
|
||||
import presentation, {
|
||||
canPreviewFile,
|
||||
getBlobRef,
|
||||
@ -31,16 +31,16 @@
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { Readable } from 'svelte/store'
|
||||
import { getType, openAttachmentInSidebar, showAttachmentPreviewPopup } from '../utils'
|
||||
import { getType, isAttachment, openAttachmentInSidebar, showAttachmentPreviewPopup } from '../utils'
|
||||
import AttachmentName from './AttachmentName.svelte'
|
||||
|
||||
export let value: WithLookup<Attachment> | undefined
|
||||
export let value: WithLookup<Attachment> | BlobType | undefined
|
||||
export let removable: boolean = false
|
||||
export let showPreview = false
|
||||
export let preview = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let permissionsStore: Readable<PermissionsStore>
|
||||
let permissionsStore: Readable<PermissionsStore> | undefined = undefined
|
||||
|
||||
onMount(async () => {
|
||||
permissionsStore = await getResource(contact.store.Permissions)
|
||||
@ -51,13 +51,22 @@
|
||||
const trimFilename = (fname: string): string =>
|
||||
fname.length > maxLength ? fname.substr(0, (maxLength - 1) / 2) + '...' + fname.substr(-(maxLength - 1) / 2) : fname
|
||||
|
||||
$: canRemove =
|
||||
removable &&
|
||||
value !== undefined &&
|
||||
value.readonly !== true &&
|
||||
permissionsStore != null &&
|
||||
($permissionsStore.whitelist.has(value.space) ||
|
||||
!$permissionsStore.ps[value.space]?.has(core.permission.ForbidDeleteObject))
|
||||
$: canRemove = isRemovable(removable, value, $permissionsStore)
|
||||
|
||||
function isRemovable (
|
||||
removable: boolean,
|
||||
value: Attachment | BlobType | undefined,
|
||||
permissionsStore: PermissionsStore | undefined
|
||||
): boolean {
|
||||
if (value === undefined || !removable) return false
|
||||
if (!isAttachment(value)) return true
|
||||
if (permissionsStore === undefined) return false
|
||||
return (
|
||||
value.readonly !== true &&
|
||||
(permissionsStore.whitelist.has(value.space) ||
|
||||
!permissionsStore.ps[value.space]?.has(core.permission.ForbidDeleteObject))
|
||||
)
|
||||
}
|
||||
|
||||
function iconLabel (name: string): string {
|
||||
const parts = `${name}`.split('.')
|
||||
@ -201,7 +210,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="info-content flex-row-center">
|
||||
{filesize(value.size, { spacer: '' })}
|
||||
{#if isAttachment(value)}{filesize(value.size, { spacer: '' })}{/if}
|
||||
<span class="actions inline-flex clear-mins ml-1 gap-1">
|
||||
<span>•</span>
|
||||
<a class="no-line colorInherit" href={valueRef.src} download={value.name} bind:this={download}>
|
||||
|
@ -17,16 +17,16 @@
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import { ListSelectionProvider } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { WithLookup } from '@hcengineering/core'
|
||||
import { BlobType, WithLookup } from '@hcengineering/core'
|
||||
import { AttachmentImageSize } from '../types'
|
||||
import { getType, showAttachmentPreviewPopup } from '../utils'
|
||||
import { getType, isAttachment, showAttachmentPreviewPopup } from '../utils'
|
||||
import AttachmentActions from './AttachmentActions.svelte'
|
||||
import AttachmentImagePreview from './AttachmentImagePreview.svelte'
|
||||
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||
import AttachmentVideoPreview from './AttachmentVideoPreview.svelte'
|
||||
import AudioPlayer from './AudioPlayer.svelte'
|
||||
|
||||
export let value: WithLookup<Attachment>
|
||||
export let value: WithLookup<Attachment> | BlobType
|
||||
export let isSaved: boolean = false
|
||||
export let listProvider: ListSelectionProvider | undefined = undefined
|
||||
export let imageSize: AttachmentImageSize = 'auto'
|
||||
@ -44,7 +44,7 @@
|
||||
<div
|
||||
class="content flex-center buttonContainer cursor-pointer"
|
||||
on:click={() => {
|
||||
if (listProvider !== undefined) listProvider.updateFocus(value)
|
||||
if (listProvider !== undefined && isAttachment(value)) listProvider.updateFocus(value)
|
||||
const popupInfo = showAttachmentPreviewPopup(value)
|
||||
dispatch('open', popupInfo.id)
|
||||
}}
|
||||
|
@ -13,9 +13,9 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment, BlobMetadata, AttachmentsEvents } from '@hcengineering/attachment'
|
||||
import attachment, { Attachment, AttachmentsEvents } from '@hcengineering/attachment'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Doc, PersonId, Ref, generateId, type Blob } from '@hcengineering/core'
|
||||
import { BlobMetadata, Doc, PersonId, Ref, generateId, type Blob } from '@hcengineering/core'
|
||||
import { IntlString, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import {
|
||||
FileOrBlob,
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Attachment, BlobMetadata } from '@hcengineering/attachment'
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import {
|
||||
Class,
|
||||
Doc,
|
||||
@ -24,7 +24,8 @@
|
||||
toIdMap,
|
||||
type Blob,
|
||||
TxOperations,
|
||||
PersonId
|
||||
PersonId,
|
||||
BlobMetadata
|
||||
} from '@hcengineering/core'
|
||||
import { IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import {
|
||||
|
@ -14,11 +14,11 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import type { WithLookup } from '@hcengineering/core'
|
||||
import type { BlobType, WithLookup } from '@hcengineering/core'
|
||||
import { getFileUrl, getVideoMeta } from '@hcengineering/presentation'
|
||||
import { HlsVideo, Video } from '@hcengineering/ui'
|
||||
|
||||
export let value: WithLookup<Attachment>
|
||||
export let value: WithLookup<Attachment> | BlobType
|
||||
export let preload = true
|
||||
|
||||
const maxSizeRem = 20
|
||||
@ -27,7 +27,7 @@
|
||||
|
||||
$: dimensions = getDimensions(value)
|
||||
|
||||
function getDimensions (value: Attachment): { width: number, height: number } {
|
||||
function getDimensions (value: Attachment | BlobType): { width: number, height: number } {
|
||||
const fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize)
|
||||
|
||||
if (!value.metadata) {
|
||||
|
@ -14,13 +14,13 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Attachment } from '@hcengineering/attachment'
|
||||
import type { WithLookup } from '@hcengineering/core'
|
||||
import type { BlobType, WithLookup } from '@hcengineering/core'
|
||||
import { getFileUrl } from '@hcengineering/presentation'
|
||||
import { CircleButton, Progress } from '@hcengineering/ui'
|
||||
import Pause from './icons/Pause.svelte'
|
||||
import Play from './icons/Play.svelte'
|
||||
|
||||
export let value: WithLookup<Attachment>
|
||||
export let value: WithLookup<Attachment> | BlobType
|
||||
export let fullSize = false
|
||||
|
||||
let time = 0
|
||||
|
@ -13,8 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Blob, Ref } from '@hcengineering/core'
|
||||
import { BlobMetadata } from '@hcengineering/presentation'
|
||||
import type { BlobMetadata, Blob, Ref } from '@hcengineering/core'
|
||||
import { Button, closePopup, closeTooltip, IconToDetails } from '@hcengineering/ui'
|
||||
import workbench from '@hcengineering/workbench'
|
||||
|
||||
|
@ -22,7 +22,6 @@ import { type ViewAction } from '@hcengineering/view'
|
||||
export default mergeIds(attachmentId, attachment, {
|
||||
string: {
|
||||
NoAttachments: '' as IntlString,
|
||||
UploadDropFilesHere: '' as IntlString,
|
||||
Photos: '' as IntlString,
|
||||
FileBrowserFileCounter: '' as IntlString,
|
||||
FileBrowserListView: '' as IntlString,
|
||||
|
@ -14,8 +14,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type BlobMetadata, type Attachment, type Drawing } from '@hcengineering/attachment'
|
||||
import { type Attachment, type Drawing } from '@hcengineering/attachment'
|
||||
import core, {
|
||||
type BlobMetadata,
|
||||
SortingOrder,
|
||||
type Blob,
|
||||
type Class,
|
||||
@ -24,7 +25,8 @@ import core, {
|
||||
type Doc,
|
||||
type Ref,
|
||||
type Space,
|
||||
type WithLookup
|
||||
type WithLookup,
|
||||
type BlobType
|
||||
} from '@hcengineering/core'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import {
|
||||
@ -116,7 +118,7 @@ export function getType (
|
||||
return 'other'
|
||||
}
|
||||
|
||||
export async function openAttachmentInSidebar (value: Attachment): Promise<void> {
|
||||
export async function openAttachmentInSidebar (value: Attachment | BlobType): Promise<void> {
|
||||
closeTooltip()
|
||||
await openFilePreviewInSidebar(value.file, value.name, value.type, value.metadata)
|
||||
}
|
||||
@ -151,10 +153,14 @@ export async function openFilePreviewInSidebar (
|
||||
await createFn(widget, tab, true)
|
||||
}
|
||||
|
||||
export function showAttachmentPreviewPopup (value: WithLookup<Attachment>): PopupResult {
|
||||
export function isAttachment (value: Attachment | BlobType): value is WithLookup<Attachment> {
|
||||
return (value as Attachment)._id !== undefined
|
||||
}
|
||||
|
||||
export function showAttachmentPreviewPopup (value: WithLookup<Attachment> | BlobType): PopupResult {
|
||||
const props: Record<string, any> = {}
|
||||
|
||||
if (value?.type?.startsWith('image/')) {
|
||||
if (value?.type?.startsWith('image/') && isAttachment(value)) {
|
||||
props.drawingAvailable = true
|
||||
props.loadDrawings = async (): Promise<Drawing[] | undefined> => {
|
||||
const client = getClient()
|
||||
|
@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { AttachedDoc, Blob, Class, Doc, Ref } from '@hcengineering/core'
|
||||
import type { AttachedDoc, Blob, BlobMetadata, Class, Doc, Ref } from '@hcengineering/core'
|
||||
import type { Asset, Plugin } from '@hcengineering/platform'
|
||||
import { IntlString, plugin, Resource } from '@hcengineering/platform'
|
||||
import type { Preference } from '@hcengineering/preference'
|
||||
@ -23,11 +23,6 @@ import { Widget } from '@hcengineering/workbench'
|
||||
|
||||
export * from './analytics'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type BlobMetadata = Record<string, any>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -109,6 +104,7 @@ export default plugin(attachmentId, {
|
||||
PreviewWidget: '' as Ref<Widget>
|
||||
},
|
||||
string: {
|
||||
UploadDropFilesHere: '' as IntlString,
|
||||
Files: '' as IntlString,
|
||||
NoFiles: '' as IntlString,
|
||||
NoParticipants: '' as IntlString,
|
||||
|
@ -37,4 +37,7 @@
|
||||
<path d="M3 8C3 8.55229 2.55228 9 2 9C1.44772 9 1 8.55229 1 8C1 7.44772 1.44772 7 2 7C2.55228 7 3 7.44772 3 8Z" />
|
||||
<path d="M2 13.5C2.55228 13.5 3 13.0523 3 12.5C3 11.9477 2.55228 11.5 2 11.5C1.44772 11.5 1 11.9477 1 12.5C1 13.0523 1.44772 13.5 2 13.5Z" />
|
||||
</symbol>
|
||||
<symbol id="file" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 5.3 KiB |
@ -20,5 +20,6 @@ loadMetadata(card.icon, {
|
||||
MasterTag: `${icons}#masterTag`,
|
||||
Tags: `${icons}#tags`,
|
||||
Tag: `${icons}#tag`,
|
||||
Card: `${icons}#card`
|
||||
Card: `${icons}#card`,
|
||||
File: `${icons}#file`
|
||||
})
|
||||
|
@ -47,6 +47,7 @@
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/setting-resources": "^0.6.0",
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/uploader": "^0.6.0",
|
||||
"@hcengineering/text": "^0.6.5",
|
||||
"@hcengineering/panel": "^0.6.23",
|
||||
"@hcengineering/rank": "^0.6.4",
|
||||
|
@ -1,3 +1,17 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { Card, CardEvents } from '@hcengineering/card'
|
||||
import core, { Data, Doc, fillDefaults, MarkupBlobRef, SortingOrder, WithLookup } from '@hcengineering/core'
|
||||
@ -102,6 +116,7 @@
|
||||
title,
|
||||
rank: makeRank(lastOne?.rank, undefined),
|
||||
content: '' as MarkupBlobRef,
|
||||
blobs: {},
|
||||
parentInfo: [
|
||||
...(object.parentInfo ?? []),
|
||||
{
|
||||
|
46
plugins/card-resources/src/components/Content.svelte
Normal file
46
plugins/card-resources/src/components/Content.svelte
Normal file
@ -0,0 +1,46 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 card, { Card } from '@hcengineering/card'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import Description from './Description.svelte'
|
||||
import FilePreview from '@hcengineering/presentation/src/components/FilePreview.svelte'
|
||||
import FilePlaceholder from './FilePlaceholder.svelte'
|
||||
|
||||
export let doc: Card
|
||||
export let readonly: boolean = false
|
||||
export let content: HTMLElement
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
$: hasDescription = !hierarchy.isDerived(doc._class, card.types.File)
|
||||
</script>
|
||||
|
||||
{#if hasDescription}
|
||||
<Description {doc} {readonly} bind:content />
|
||||
{:else if Object.keys(doc.blobs ?? {}).length === 0 && !readonly}
|
||||
<FilePlaceholder {doc} />
|
||||
{/if}
|
||||
|
||||
{#each Object.values(doc.blobs ?? {}) as blob}
|
||||
<FilePreview
|
||||
file={blob.file}
|
||||
contentType={blob.type}
|
||||
name={blob.name}
|
||||
metadata={blob.metadata}
|
||||
fit={blob.type !== 'application/pdf'}
|
||||
/>
|
||||
{/each}
|
@ -14,12 +14,10 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { MasterTag, Tag } from '@hcengineering/card'
|
||||
import core, { Class, ClassifierKind, Data, Doc, Ref, generateId } from '@hcengineering/core'
|
||||
import core, { Class, ClassifierKind, Data, Ref } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Card, getClient } from '@hcengineering/presentation'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { EditBox, Icon, Label } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import card from '../plugin'
|
||||
|
||||
@ -40,8 +38,7 @@
|
||||
icon: isMasterTag ? card.icon.MasterTag : card.icon.Tag
|
||||
}
|
||||
|
||||
const id = generateId<Class<MasterTag>>()
|
||||
await client.createDoc(_class, core.space.Model, data, id)
|
||||
await client.createDoc(_class, core.space.Model, data)
|
||||
|
||||
dispatch('close')
|
||||
}
|
||||
|
132
plugins/card-resources/src/components/Description.svelte
Normal file
132
plugins/card-resources/src/components/Description.svelte
Normal file
@ -0,0 +1,132 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 attachment from '@hcengineering/attachment'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { Blob, Ref } from '@hcengineering/core'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Heading } from '@hcengineering/text-editor'
|
||||
import { TableOfContents } from '@hcengineering/text-editor-resources'
|
||||
import { navigate } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { getObjectLinkFragment } from '@hcengineering/view-resources'
|
||||
import ContentEditor from './ContentEditor.svelte'
|
||||
|
||||
export let doc: Card
|
||||
export let readonly: boolean = false
|
||||
export let content: HTMLElement
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let editor: ContentEditor
|
||||
|
||||
async function createEmbedding (file: File): Promise<{ file: Ref<Blob>, type: string } | undefined> {
|
||||
if (doc === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const uploadFile = await getResource(attachment.helper.UploadFile)
|
||||
const uuid = await uploadFile(file)
|
||||
// const attachmentId: Ref<Attachment> = generateId()
|
||||
|
||||
// await client.addCollection(
|
||||
// attachment.class.Embedding,
|
||||
// doc.space,
|
||||
// doc._id,
|
||||
// doc._class,
|
||||
// 'embeddings',
|
||||
// {
|
||||
// file: uuid,
|
||||
// name: file.name,
|
||||
// type: file.type,
|
||||
// size: file.size,
|
||||
// lastModified: file.lastModified
|
||||
// },
|
||||
// attachmentId
|
||||
// )
|
||||
|
||||
return { file: uuid, type: file.type }
|
||||
} catch (err: any) {
|
||||
await setPlatformStatus(unknownError(err))
|
||||
}
|
||||
}
|
||||
|
||||
let headings: Heading[] = []
|
||||
</script>
|
||||
|
||||
<div class="content select-text mt-4">
|
||||
<div class="toc-container">
|
||||
<div class="toc">
|
||||
<TableOfContents
|
||||
items={headings}
|
||||
on:select={(evt) => {
|
||||
const heading = evt.detail
|
||||
const element = window.document.getElementById(heading.id)
|
||||
element?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#key doc._id}
|
||||
<ContentEditor
|
||||
focusIndex={30}
|
||||
object={doc}
|
||||
{readonly}
|
||||
boundary={content}
|
||||
overflow={'none'}
|
||||
editorAttributes={{ style: 'padding: 0 2em 2em; margin: 0 -2em; min-height: 30vh' }}
|
||||
attachFile={async (file) => {
|
||||
return await createEmbedding(file)
|
||||
}}
|
||||
on:headings={(evt) => {
|
||||
headings = evt.detail
|
||||
}}
|
||||
on:open-document={async (event) => {
|
||||
const doc = await client.findOne(event.detail._class, { _id: event.detail._id })
|
||||
if (doc != null) {
|
||||
const location = await getObjectLinkFragment(client.getHierarchy(), doc, {}, view.component.EditDoc)
|
||||
navigate(location)
|
||||
}
|
||||
}}
|
||||
bind:this={editor}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.toc-container {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.toc {
|
||||
width: 1rem;
|
||||
pointer-events: all;
|
||||
margin-left: -3rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
color: var(--content-color);
|
||||
line-height: 150%;
|
||||
}
|
||||
</style>
|
@ -16,31 +16,22 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { Card, CardEvents } from '@hcengineering/card'
|
||||
import { Doc, Mixin, Ref, WithLookup, generateId, type Blob } from '@hcengineering/core'
|
||||
import { Doc, Mixin, Ref, WithLookup } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Heading } from '@hcengineering/text-editor'
|
||||
import { TableOfContents } from '@hcengineering/text-editor-resources'
|
||||
import { Button, EditBox, FocusHandler, IconMoreH, createFocusManager, navigate } from '@hcengineering/ui'
|
||||
import { Button, EditBox, FocusHandler, IconMoreH, createFocusManager } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import {
|
||||
ParentsNavigator,
|
||||
RelationsEditor,
|
||||
getDocMixins,
|
||||
getObjectLinkFragment,
|
||||
showMenu
|
||||
} from '@hcengineering/view-resources'
|
||||
import { ParentsNavigator, RelationsEditor, getDocMixins, showMenu } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
import card from '../plugin'
|
||||
import CardAttributeEditor from './CardAttributeEditor.svelte'
|
||||
import CardPresenter from './CardPresenter.svelte'
|
||||
import ContentEditor from './ContentEditor.svelte'
|
||||
import TagsEditor from './TagsEditor.svelte'
|
||||
import Childs from './Childs.svelte'
|
||||
import Content from './Content.svelte'
|
||||
import TagsEditor from './TagsEditor.svelte'
|
||||
|
||||
export let _id: Ref<Card>
|
||||
export let readonly: boolean = false
|
||||
@ -62,16 +53,11 @@
|
||||
let title = ''
|
||||
let innerWidth: number
|
||||
|
||||
let headings: Heading[] = []
|
||||
|
||||
let loadedDocumentContent = false
|
||||
|
||||
const notificationClient = getResource(notification.function.GetInboxNotificationsClient).then((res) => res())
|
||||
|
||||
$: read(_id)
|
||||
function read (_id: Ref<Doc>): void {
|
||||
if (lastId !== _id) {
|
||||
loadedDocumentContent = false
|
||||
const prev = lastId
|
||||
lastId = _id
|
||||
void notificationClient.then((client) => client.readDoc(prev))
|
||||
@ -82,38 +68,6 @@
|
||||
void notificationClient.then((client) => client.readDoc(_id))
|
||||
})
|
||||
|
||||
async function createEmbedding (file: File): Promise<{ file: Ref<Blob>, type: string } | undefined> {
|
||||
if (doc === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const uploadFile = await getResource(attachment.helper.UploadFile)
|
||||
const uuid = await uploadFile(file)
|
||||
const attachmentId: Ref<Attachment> = generateId()
|
||||
|
||||
await client.addCollection(
|
||||
attachment.class.Embedding,
|
||||
doc.space,
|
||||
doc._id,
|
||||
doc._class,
|
||||
'embeddings',
|
||||
{
|
||||
file: uuid,
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
lastModified: file.lastModified
|
||||
},
|
||||
attachmentId
|
||||
)
|
||||
|
||||
return { file: uuid, type: file.type }
|
||||
} catch (err: any) {
|
||||
await setPlatformStatus(unknownError(err))
|
||||
}
|
||||
}
|
||||
|
||||
$: _id !== undefined &&
|
||||
query.query(card.class.Card, { _id }, async (result) => {
|
||||
;[doc] = result
|
||||
@ -145,17 +99,10 @@
|
||||
localStorage.setItem('document.useMaxWidth', useMaxWidth.toString())
|
||||
}
|
||||
|
||||
let sideContentSpace = 0
|
||||
|
||||
function updateSizeContentSpace (width: number): void {
|
||||
sideContentSpace = width
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
Analytics.handleEvent(CardEvents.CardOpened, { id: _id })
|
||||
})
|
||||
|
||||
let editor: ContentEditor
|
||||
let content: HTMLElement
|
||||
|
||||
const manager = createFocusManager()
|
||||
@ -169,14 +116,12 @@
|
||||
|
||||
{#if doc !== undefined}
|
||||
<Panel
|
||||
withoutActivity={!loadedDocumentContent}
|
||||
object={doc}
|
||||
allowClose={!embedded}
|
||||
isAside={false}
|
||||
isHeader={false}
|
||||
isSub={false}
|
||||
bind:useMaxWidth
|
||||
{sideContentSpace}
|
||||
printHeader={false}
|
||||
{embedded}
|
||||
adaptive={'default'}
|
||||
@ -193,66 +138,14 @@
|
||||
|
||||
<div class="container">
|
||||
<div class="title flex-row-center">
|
||||
<EditBox
|
||||
focusIndex={1}
|
||||
bind:value={title}
|
||||
placeholder={card.string.Card}
|
||||
on:blur={(evt) => saveTitle(evt)}
|
||||
on:keydown={(evt) => {
|
||||
if (evt.key === 'Enter' || evt.key === 'ArrowDown') {
|
||||
editor.focus('start')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<EditBox focusIndex={1} bind:value={title} placeholder={card.string.Card} on:blur={(evt) => saveTitle(evt)} />
|
||||
</div>
|
||||
|
||||
<TagsEditor {doc} />
|
||||
|
||||
<CardAttributeEditor value={doc} {mixins} {readonly} ignoreKeys={['title', 'content', 'parent']} />
|
||||
|
||||
<div class="content select-text mt-4">
|
||||
<div class="toc-container">
|
||||
<div class="toc">
|
||||
<TableOfContents
|
||||
items={headings}
|
||||
on:select={(evt) => {
|
||||
const heading = evt.detail
|
||||
const element = window.document.getElementById(heading.id)
|
||||
element?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#key doc._id}
|
||||
<ContentEditor
|
||||
focusIndex={30}
|
||||
object={doc}
|
||||
{readonly}
|
||||
boundary={content}
|
||||
overflow={'none'}
|
||||
editorAttributes={{ style: 'padding: 0 2em 2em; margin: 0 -2em; min-height: 30vh' }}
|
||||
requestSideSpace={updateSizeContentSpace}
|
||||
attachFile={async (file) => {
|
||||
return await createEmbedding(file)
|
||||
}}
|
||||
on:headings={(evt) => {
|
||||
headings = evt.detail
|
||||
}}
|
||||
on:open-document={async (event) => {
|
||||
const doc = await client.findOne(event.detail._class, { _id: event.detail._id })
|
||||
if (doc != null) {
|
||||
const location = await getObjectLinkFragment(client.getHierarchy(), doc, {}, view.component.EditDoc)
|
||||
navigate(location)
|
||||
}
|
||||
}}
|
||||
on:loaded={() => {
|
||||
loadedDocumentContent = true
|
||||
}}
|
||||
bind:this={editor}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
<Content {doc} {readonly} bind:content />
|
||||
</div>
|
||||
|
||||
<Childs object={doc} {readonly} />
|
||||
@ -281,27 +174,6 @@
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.toc-container {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.toc {
|
||||
width: 1rem;
|
||||
pointer-events: all;
|
||||
margin-left: -3rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
color: var(--content-color);
|
||||
line-height: 150%;
|
||||
}
|
||||
.title {
|
||||
font-size: 2.25rem;
|
||||
margin-top: 1.75rem;
|
||||
|
114
plugins/card-resources/src/components/FilePlaceholder.svelte
Normal file
114
plugins/card-resources/src/components/FilePlaceholder.svelte
Normal file
@ -0,0 +1,114 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 attachment from '@hcengineering/attachment'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { getClient, getFileMetadata } from '@hcengineering/presentation'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { FileUploadCallbackParams, uploadFiles } from '@hcengineering/uploader'
|
||||
import UploadDuo from './icons/UploadDuo.svelte'
|
||||
|
||||
export let doc: Card
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let inputFile: HTMLInputElement
|
||||
let dragover = false
|
||||
|
||||
async function onFileUploaded ({ uuid, name, file, type }: FileUploadCallbackParams): Promise<void> {
|
||||
const metadata = await getFileMetadata(file, uuid)
|
||||
const blobs = doc.blobs ?? {}
|
||||
blobs[uuid] = {
|
||||
name,
|
||||
type,
|
||||
metadata,
|
||||
file: uuid
|
||||
}
|
||||
await client.update(doc, {
|
||||
blobs
|
||||
})
|
||||
}
|
||||
|
||||
async function fileSelected (): Promise<void> {
|
||||
const list = inputFile.files
|
||||
if (list === null || list.length === 0) return
|
||||
|
||||
const options = {
|
||||
onFileUploaded,
|
||||
showProgress: {
|
||||
target: { objectId: doc._id, objectClass: doc._class }
|
||||
}
|
||||
}
|
||||
await uploadFiles(list, options)
|
||||
|
||||
inputFile.value = ''
|
||||
}
|
||||
|
||||
async function fileDrop (e: DragEvent): Promise<void> {
|
||||
dragover = false
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const list = e.dataTransfer?.files
|
||||
if (list === undefined || list.length === 0) return
|
||||
|
||||
const options = {
|
||||
onFileUploaded,
|
||||
showProgress: {
|
||||
target: { objectId: doc._id, objectClass: doc._class }
|
||||
}
|
||||
}
|
||||
await uploadFiles(list, options)
|
||||
}
|
||||
</script>
|
||||
|
||||
<input
|
||||
bind:this={inputFile}
|
||||
disabled={inputFile == null}
|
||||
multiple
|
||||
type="file"
|
||||
name="file"
|
||||
id="file"
|
||||
style="display: none"
|
||||
on:change={fileSelected}
|
||||
/>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
on:dragover={(e) => {
|
||||
dragover = true
|
||||
e.preventDefault()
|
||||
}}
|
||||
on:dragleave={() => {
|
||||
dragover = false
|
||||
}}
|
||||
on:drop={fileDrop}
|
||||
>
|
||||
<div class="antiSection-empty attachments flex-col mt-3" class:solid={dragover}>
|
||||
<div class="flex-center caption-color">
|
||||
<UploadDuo size={'large'} />
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="over-underline text-sm caption-color"
|
||||
style:pointer-events={dragover ? 'none' : 'all'}
|
||||
on:click={() => {
|
||||
inputFile.click()
|
||||
}}
|
||||
>
|
||||
<Label label={attachment.string.UploadDropFilesHere} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -36,7 +36,7 @@
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const label = hierarchy.getClass(value._class).label
|
||||
$: label = hierarchy.getClass(value._class).label
|
||||
|
||||
let isCollapsed = false
|
||||
|
||||
|
@ -57,7 +57,8 @@
|
||||
title,
|
||||
rank: makeRank(lastOne?.rank, undefined),
|
||||
content: '' as MarkupBlobRef,
|
||||
parentInfo: []
|
||||
parentInfo: [],
|
||||
blobs: {}
|
||||
}
|
||||
|
||||
const filledData = fillDefaults(hierarchy, data, _class)
|
||||
|
31
plugins/card-resources/src/components/icons/UploadDuo.svelte
Normal file
31
plugins/card-resources/src/components/icons/UploadDuo.svelte
Normal file
@ -0,0 +1,31 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 24 24">
|
||||
<path
|
||||
fill="var(--duotone-color)"
|
||||
d="M18,6.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0-0.1-0.1 c0-0.1-0.1-0.2-0.2-0.4c-0.9-1.9-2.8-3.3-5.1-3.3c-2.3,0-4.2,1.4-5.1,3.3C6.8,5.9,6.7,6,6.7,6.1c0,0.1-0.1,0.1-0.1,0.1 C6.6,6.3,6.6,6.3,6.5,6.3c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.1,0-0.2,0-0.3,0C4,6.4,2.4,8,2.4,10S4,13.6,6,13.6h6h6 c2,0,3.6-1.6,3.6-3.6S20,6.4,18,6.4z"
|
||||
/>
|
||||
<g {fill}>
|
||||
<path
|
||||
d="M18,6.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0 c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1-0.1c0-0.1-0.1-0.2-0.2-0.4c-0.9-1.9-2.8-3.3-5.1-3.3c-2.3,0-4.2,1.4-5.1,3.3 C6.8,5.9,6.7,6,6.7,6.1c0,0.1-0.1,0.1-0.1,0.1c0,0,0,0,0,0C6.6,6.3,6.6,6.3,6.5,6.3c0,0,0,0-0.1,0c0,0,0,0-0.1,0 c-0.1,0-0.2,0-0.3,0C4,6.4,2.4,8,2.4,10S4,13.6,6,13.6h0.6l1.2-1.2H6c-1.3,0-2.4-1.1-2.4-2.4c0-1.3,1.1-2.4,2.4-2.4h0.1 c0.2,0,0.4,0,0.6,0c0.2,0,0.4-0.1,0.6-0.2c0.2-0.1,0.3-0.3,0.4-0.4c0.1-0.1,0.1-0.2,0.2-0.3C7.8,6.5,7.9,6.4,8,6.2l0,0 c0.7-1.5,2.2-2.6,4-2.6s3.3,1.1,4,2.6l0,0c0.1,0.2,0.1,0.3,0.2,0.4c0,0.1,0.1,0.2,0.2,0.3c0.1,0.2,0.2,0.3,0.4,0.4 c0.2,0.1,0.4,0.2,0.6,0.2c0.2,0,0.4,0,0.6,0H18c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4h-1.8l1.2,1.2H18c2,0,3.6-1.6,3.6-3.6 S20,6.4,18,6.4z"
|
||||
/>
|
||||
<path d="M12,11.2l-4.4,4.4l0.8,0.8l3-3V21c0,0.3,0.3,0.6,0.6,0.6s0.6-0.3,0.6-0.6v-7.6l3,3l0.8-0.8L12,11.2z" />
|
||||
</g>
|
||||
</svg>
|
@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Class, Mixin, Doc, Ref, MarkupBlobRef, Rank, Domain } from '@hcengineering/core'
|
||||
import { Blobs, Class, Doc, Domain, MarkupBlobRef, Mixin, Rank, Ref } from '@hcengineering/core'
|
||||
import { Asset, IntlString, plugin, Plugin } from '@hcengineering/platform'
|
||||
import type { AnyComponent } from '@hcengineering/ui'
|
||||
|
||||
@ -22,10 +22,10 @@ export interface MasterTag extends Class<Card> {}
|
||||
export interface Tag extends MasterTag, Mixin<Card> {}
|
||||
|
||||
export interface Card extends Doc {
|
||||
attachments?: number
|
||||
_class: Ref<MasterTag>
|
||||
title: string
|
||||
content: MarkupBlobRef
|
||||
blobs: Blobs
|
||||
children?: number
|
||||
parentInfo: ParentInfo[]
|
||||
parent?: Ref<Card> | null
|
||||
@ -62,12 +62,16 @@ const cardPlugin = plugin(cardId, {
|
||||
Tag: '' as Ref<Class<Tag>>,
|
||||
MasterTagEditorSection: '' as Ref<Class<MasterTagEditorSection>>
|
||||
},
|
||||
types: {
|
||||
File: '' as Ref<MasterTag>
|
||||
},
|
||||
icon: {
|
||||
MasterTags: '' as Asset,
|
||||
MasterTag: '' as Asset,
|
||||
Tag: '' as Asset,
|
||||
Tags: '' as Asset,
|
||||
Card: '' as Asset
|
||||
Card: '' as Asset,
|
||||
File: '' as Asset
|
||||
},
|
||||
string: {
|
||||
MasterTag: '' as IntlString,
|
||||
|
@ -4,9 +4,9 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { type Blob, type BlobMetadata, type Ref } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation, { BlobMetadata, getFileUrl } from '@hcengineering/presentation'
|
||||
import presentation, { getFileUrl } from '@hcengineering/presentation'
|
||||
import { EmbeddedPDF, Spinner, themeStore } from '@hcengineering/ui'
|
||||
import { convertToHTML } from '@hcengineering/print'
|
||||
|
||||
|
@ -54,6 +54,7 @@ async function uploadRecording (recordingName: string, onUploaded: FileUploadCal
|
||||
uuid: getBlobUrl(recordingName) as Ref<Blob>,
|
||||
name: 'Recording-' + now(),
|
||||
file: { ...new Blob(), type: 'video/x-mpegURL' },
|
||||
type: 'video/x-mpegURL',
|
||||
path: undefined,
|
||||
metadata: undefined,
|
||||
navigateOnUpload: true
|
||||
|
@ -94,7 +94,7 @@ export async function uploadFiles (files: File[] | FileList, options: FileUpload
|
||||
const { relativePath } = data
|
||||
const uuid = data.name
|
||||
void limiter.add(async () => {
|
||||
await uploadFile(data, { name: files[i].name, uuid, relativePath }, upload, options)
|
||||
await uploadFile(data, { name: files[i].name, uuid, type: data.type, relativePath }, upload, options)
|
||||
})
|
||||
}
|
||||
|
||||
@ -156,6 +156,7 @@ export async function uploadFile (
|
||||
try {
|
||||
void callbackLimiter.exec(async () => {
|
||||
void onFileUploaded({
|
||||
type: metadata.type,
|
||||
uuid,
|
||||
name: metadata.name,
|
||||
file,
|
||||
|
@ -70,6 +70,7 @@ export interface FileUploadPopupOptions {
|
||||
export interface FileUploadCallbackParams {
|
||||
uuid: Ref<PlatformBlob>
|
||||
name: string
|
||||
type: string
|
||||
file: FileWithPath | Blob
|
||||
path: string | undefined
|
||||
metadata: Record<string, any> | undefined
|
||||
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { type BlobMetadata, getImageSize } from '@hcengineering/presentation'
|
||||
import { type Blob, type BlobMetadata, type Ref } from '@hcengineering/core'
|
||||
import { getImageSize } from '@hcengineering/presentation'
|
||||
|
||||
export async function blobImageMetadata (file: File, blob: Ref<Blob>): Promise<BlobMetadata | undefined> {
|
||||
if (file.size === 0) {
|
||||
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { DrawingBoard, getBlobRef, imageSizeToRatio, type BlobMetadata } from '@hcengineering/presentation'
|
||||
import { type Blob, type Ref, type BlobMetadata } from '@hcengineering/core'
|
||||
import { DrawingBoard, getBlobRef, imageSizeToRatio } from '@hcengineering/presentation'
|
||||
import { Loading } from '@hcengineering/ui'
|
||||
|
||||
export let value: Ref<Blob>
|
||||
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { getFileUrl, getVideoMeta, type BlobMetadata } from '@hcengineering/presentation'
|
||||
import { type Blob, type BlobMetadata, type Ref } from '@hcengineering/core'
|
||||
import { getFileUrl, getVideoMeta } from '@hcengineering/presentation'
|
||||
import { HlsVideo, Video } from '@hcengineering/ui'
|
||||
|
||||
export let value: Ref<Blob>
|
||||
|
@ -187,6 +187,20 @@ async function OnCardRemove (ctx: TxRemoveDoc<Card>[], control: TriggerControl):
|
||||
for (const card of cards) {
|
||||
res.push(control.txFactory.createTxRemoveDoc(card._class, card.space, card._id))
|
||||
}
|
||||
|
||||
const toDelete: string[] = []
|
||||
|
||||
for (const key in removedCard.blobs ?? {}) {
|
||||
const val = removedCard.blobs[key]
|
||||
if (val === undefined) continue
|
||||
const toDelete: string[] = []
|
||||
toDelete.push(val.file)
|
||||
}
|
||||
|
||||
if (toDelete.length > 0) {
|
||||
await control.storageAdapter.remove(control.ctx, control.workspace, toDelete)
|
||||
}
|
||||
|
||||
if (removedCard.parent != null) {
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(card.class.Card, core.space.Workspace, removedCard.parent, {
|
||||
|
Loading…
Reference in New Issue
Block a user