Enhanced drawing overlay for screenshots (#7231)
Some checks are pending
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions

Signed-off-by: Nikolay Chunosov <Chunosov.N@gmail.com>
This commit is contained in:
Chunosov 2024-11-26 13:45:49 +07:00 committed by GitHub
parent aea704fd0f
commit 27547e580c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 284 additions and 96 deletions

View File

@ -241,6 +241,10 @@ export function createModel (builder: Builder): void {
{ _class: 1 }
]
})
builder.mixin(attachment.class.Drawing, core.class.Class, view.mixin.ObjectPresenter, {
presenter: attachment.component.DrawingPresenter
})
}
export default attachment

View File

@ -34,7 +34,8 @@
"FailedToPreview": "Náhled se nezdařil",
"ContentType": "Typ obsahu",
"ContentTypeNotSupported": "Náhled není dostupný pro tento typ obsahu",
"StartDrawing": "Načmárejte"
"StartDrawing": "Načmárejte",
"DrawingHistory": "Čmárání historie"
},
"status": {
"FileTooLarge": "Soubor je příliš velký"

View File

@ -34,7 +34,8 @@
"FailedToPreview": "Failed to preview",
"ContentType": "Content type",
"ContentTypeNotSupported": "Preview is not available for this content type",
"StartDrawing": "Scribble over"
"StartDrawing": "Scribble over",
"DrawingHistory": "Scribble history"
},
"status": {
"FileTooLarge": "File too large"

View File

@ -34,7 +34,8 @@
"FailedToPreview": "Error al previsualizar",
"ContentType": "Tipo de contenido",
"ContentTypeNotSupported": "La vista previa no está disponible para este tipo de contenido",
"StartDrawing": "Garabatear encima"
"StartDrawing": "Garabatear encima",
"DrawingHistory": "Historia de garabatos"
},
"status": {
"FileTooLarge": "Archivo demasiado grande"

View File

@ -34,7 +34,8 @@
"FailedToPreview": "Échec de l'aperçu",
"ContentType": "Type de contenu",
"ContentTypeNotSupported": "L'aperçu n'est pas disponible pour ce type de contenu",
"StartDrawing": "Gribouiller dessus"
"StartDrawing": "Gribouiller dessus",
"DrawingHistory": "Histoire de gribouillage"
},
"status": {
"FileTooLarge": "Fichier trop volumineux"

View File

@ -34,7 +34,8 @@
"FailedToPreview": "Impossibile mostrare l'anteprima",
"ContentType": "Tipo di contenuto",
"ContentTypeNotSupported": "Anteprima non disponibile per questo tipo di contenuto",
"StartDrawing": "Scarabocchiare sopra"
"StartDrawing": "Scarabocchiare sopra",
"DrawingHistory": "Scarabocchiare la storia"
},
"status": {
"FileTooLarge": "File troppo grande"

View File

@ -34,7 +34,8 @@
"FailedToPreview": "Falha ao pré-visualizar",
"ContentType": "Tipo de conteúdo",
"ContentTypeNotSupported": "A visualização não está disponível para este tipo de conteúdo",
"StartDrawing": "Scarabocchiare sopra"
"StartDrawing": "Scarabocchiare sopra",
"DrawingHistory": "História de rabiscos"
},
"status": {
"FileTooLarge": "Ficheiro demasiado grande"

View File

@ -34,7 +34,8 @@
"FailedToPreview": "Ошибка предпросмотра",
"ContentType": "Тип контента",
"ContentTypeNotSupported": "Предварительный просмотр недоступен для этого типа контента",
"StartDrawing": "Сделать набросок"
"StartDrawing": "Сделать набросок",
"DrawingHistory": "История набросков"
},
"status": {
"FileTooLarge": "Файл слишком большой"

View File

@ -34,7 +34,8 @@
"FailedToPreview": "预览失败",
"ContentType": "内容类型",
"ContentTypeNotSupported": "此內容類型無法預覽",
"StartDrawing": "随意涂鸦"
"StartDrawing": "随意涂鸦",
"DrawingHistory": "涂鸦的历史"
},
"status": {
"FileTooLarge": "文件太大"

View File

@ -14,8 +14,9 @@
-->
<script lang="ts">
import { getObjectValue, type Class, type Doc, type Ref } from '@hcengineering/core'
import type { IntlString } from '@hcengineering/platform'
import { getResource, type IntlString } from '@hcengineering/platform'
import {
AnySvelteComponent,
Button,
EditWithIcon,
FocusHandler,
@ -31,6 +32,7 @@
showPopup,
tooltip
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import presentation from '..'
import { ObjectCreate } from '../types'
@ -47,7 +49,7 @@
export let placeholder: IntlString = presentation.string.Search
export let selectedObjects: Ref<Doc>[] = []
export let shadows: boolean = true
export let width: 'medium' | 'large' | 'full' = 'medium'
export let width: 'medium' | 'large' | 'full' | 'auto' = 'medium'
export let size: 'small' | 'medium' | 'large' = 'large'
export let noSearchField: boolean = false
@ -59,7 +61,7 @@
export let created: Doc[] = []
export let embedded: boolean = false
export let loading: boolean = false
export let type: 'text' | 'object' = 'text'
export let type: 'text' | 'object' | 'presenter' = 'text'
let search: string = ''
@ -71,6 +73,11 @@
created.length > 0 ||
objects.map((it) => getObjectValue(groupBy, it)).filter((it, index, arr) => arr.indexOf(it) === index).length > 1
let presenter: AnySvelteComponent | undefined = undefined
$: if (type === 'presenter') {
findObjectPresenter(_class)
}
const checkSelected = (item?: Doc): void => {
if (item === undefined) {
return
@ -154,6 +161,19 @@
}
return getObjectValue(groupBy, toAny(doc))
}
function findObjectPresenter (_class: Ref<Class<Doc>>): void {
const presenterMixin = client.getHierarchy().classHierarchyMixin(_class, view.mixin.ObjectPresenter)
if (presenterMixin?.presenter !== undefined) {
getResource(presenterMixin.presenter)
.then((result) => {
presenter = result
})
.catch((err) => {
console.error('Failed to find presenter for class ' + _class, err)
})
}
}
</script>
<FocusHandler {manager} />
@ -164,6 +184,7 @@
class:full-width={width === 'full'}
class:plainContainer={!shadows}
class:width-40={width === 'large'}
class:auto={width === 'auto'}
class:embedded
on:keydown={onKeydown}
use:resizeObserver={() => {
@ -229,6 +250,10 @@
<span class="label" class:disabled={readonly || isDeselectDisabled || loading}>
<slot name="item" item={obj} />
</span>
{:else if type === 'presenter'}
{#if presenter !== undefined}
<svelte:component this={presenter} value={obj} />
{/if}
{:else}
<slot name="item" item={obj} />
{/if}

View File

@ -14,25 +14,30 @@
-->
<script lang="ts">
import { Button, IconDelete, IconEdit, resizeObserver } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import { drawing, type DrawingData, type DrawingTool } from '../drawing'
import IconEraser from './icons/Eraser.svelte'
export let active = false
export let readonly = false
export let readonly = true
export let imageWidth: number | undefined
export let imageHeight: number | undefined
export let drawingData: DrawingData
export let saveDrawing: (data: any) => Promise<void>
export let drawings: DrawingData[]
export let createDrawing: (data: any) => Promise<void>
let drawingTool: DrawingTool = 'pen'
let penColor = 'blue'
const penColors = ['red', 'green', 'blue', 'white', 'black']
let drawingData: DrawingData | undefined
let board: HTMLDivElement
let toolbar: HTMLDivElement
let toolbarInside = false
let oldReadonly: boolean
let oldDrawings: DrawingData[]
let modified = false
$: updateToolbarPosition(readonly, board, toolbar)
$: updateEditableState(drawings, readonly)
function updateToolbarPosition (readonly: boolean, board: HTMLDivElement, toolbar: HTMLDivElement): void {
if (!readonly && board?.offsetTop !== undefined && toolbar?.clientHeight !== undefined) {
@ -41,9 +46,47 @@
toolbarInside = board.offsetTop <= toolbar.clientHeight * 3
}
}
function updateEditableState (drawings: DrawingData[], readonly: boolean): void {
if (readonly !== oldReadonly || drawings !== oldDrawings) {
if (drawings !== undefined) {
if (readonly) {
if (modified && drawingData !== undefined) {
createDrawing(drawingData).catch((error) => {
console.error('Failed to save drawing', error)
})
}
drawingData = drawings[0]
modified = false
} else {
if (drawingData === undefined) {
drawingData = {}
} else {
// Edit current content as a new drawing
drawingData = {
content: drawingData.content
}
}
modified = false
}
} else {
drawingData = undefined
}
oldDrawings = drawings
oldReadonly = readonly
}
}
onDestroy(() => {
if (modified && drawingData !== undefined) {
createDrawing(drawingData).catch((error) => {
console.error('Failed to save drawing', error)
})
}
})
</script>
{#if active}
{#if active && drawingData !== undefined}
<div
{...$$restProps}
style:position="relative"
@ -56,9 +99,14 @@
imageWidth,
imageHeight,
drawingData,
saveDrawing,
drawingTool,
penColor
penColor,
changed: (content) => {
modified = true
if (drawingData !== undefined) {
drawingData.content = content
}
}
}}
>
{#if !readonly}
@ -68,6 +116,7 @@
kind="icon"
on:click={() => {
drawingData = {}
modified = true
}}
/>
<div class="divider buttons-divider" />

View File

@ -13,9 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import { type Blob, type Ref } from '@hcengineering/core'
import { SortingOrder, type Blob, type Ref } from '@hcengineering/core'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Button, Dialog, IconEdit, tooltip } from '@hcengineering/ui'
import { Button, Dialog, IconHistory, IconScribble, showPopup, tooltip } from '@hcengineering/ui'
import { createEventDispatcher, onMount } from 'svelte'
import { BlobMetadata } from '../types'
@ -23,6 +23,7 @@
import ActionContext from './ActionContext.svelte'
import FilePreview from './FilePreview.svelte'
import DownloadFileButton from './DownloadFileButton.svelte'
import ObjectPopup from './ObjectPopup.svelte'
import { ComponentExtensions } from '../index'
import presentation from '../plugin'
import FileTypeIcon from './FileTypeIcon.svelte'
@ -31,12 +32,19 @@
export let name: string
export let contentType: string
export let metadata: BlobMetadata | undefined
export let props: Record<string, any> = {}
export let props: Record<string, any> & {
drawings?: any[]
drawingAvailable?: boolean
drawingEditable?: boolean
loadDrawings?: () => Promise<any>
createDrawing?: (data: any) => Promise<any>
} = {}
export let fullSize = false
export let showIcon = true
let drawingLoading = false
let createDrawing: (data: any) => Promise<any>
const dispatch = createEventDispatcher()
@ -45,28 +53,67 @@
dispatch('fullsize')
}
if (props.drawingAvailable === true) {
loadDrawings(props.loadDrawings)
if (props.loadDrawings !== undefined) {
drawingLoading = true
props
.loadDrawings()
.then((result) => {
drawingLoading = false
props.drawings = result
})
.catch((error) => {
drawingLoading = false
console.error('Failed to load drawings for file', file, error)
})
}
if (props.createDrawing !== undefined) {
createDrawing = props.createDrawing
props.createDrawing = async (data: any): Promise<any> => {
const newDrawing = await createDrawing(data)
if (props.drawings !== undefined) {
props.drawings = [newDrawing, ...props.drawings]
} else {
props.drawings = [newDrawing]
}
return newDrawing
}
}
}
})
function toggleDrawingEdit (): void {
const editable = props.drawingEditable === true
props = { ...props, drawingEditable: !editable }
props.drawingEditable = !(props.drawingEditable === true)
}
function loadDrawings (load: () => Promise<any>): void {
if (load !== undefined) {
drawingLoading = true
load()
.then((result) => {
drawingLoading = false
props.drawingData = result
})
.catch((error) => {
drawingLoading = false
console.error('Failed to load drawings for file', file, error)
})
function selectCurrentDrawing (ev: MouseEvent): void {
if (props.drawings === undefined || props.drawings.length === 0) {
// no current means no history
return
}
showPopup(
ObjectPopup,
{
_class: props.drawings[0]._class,
selected: props.drawings[0]._id,
docQuery: {
parent: props.drawings[0].parent
},
options: {
sort: {
createdOn: SortingOrder.Descending
}
},
searchMode: 'disabled',
type: 'presenter',
width: 'auto'
},
ev.target as HTMLElement,
async (result) => {
if (result !== undefined) {
props.drawings = [result]
}
}
)
}
</script>
@ -91,13 +138,24 @@
<svelte:fragment slot="utils">
{#if props.drawingAvailable === true}
{#if props.drawings !== undefined && props.drawings.length > 0}
<Button
icon={IconHistory}
kind="icon"
disabled={drawingLoading || props.drawingEditable === true}
showTooltip={{ label: presentation.string.DrawingHistory }}
on:click={selectCurrentDrawing}
/>
{/if}
<Button
icon={IconEdit}
icon={IconScribble}
kind="icon"
disabled={drawingLoading}
selected={props.drawingEditable === true}
showTooltip={{ label: presentation.string.StartDrawing }}
on:click={toggleDrawingEdit}
/>
<div class="buttons-divider" />
{/if}
<DownloadFileButton {name} {file} />
<ComponentExtensions

View File

@ -43,7 +43,7 @@
export let selectedObjects: Ref<Doc>[] = []
export let ignoreObjects: Ref<Doc>[] = []
export let shadows: boolean = true
export let width: 'medium' | 'large' | 'full' = 'medium'
export let width: 'medium' | 'large' | 'full' | 'auto' = 'medium'
export let size: 'small' | 'medium' | 'large' = 'large'
export let searchMode: 'field' | 'fulltext' | 'disabled' = 'field'
@ -55,7 +55,7 @@
export let disallowDeselect: Ref<Doc>[] | undefined = undefined
export let embedded: boolean = false
export let loading: boolean = false
export let type: 'text' | 'object' = 'text'
export let type: 'text' | 'object' | 'presenter' = 'text'
export let filter: (it: Doc) => boolean = () => {
return true

View File

@ -14,19 +14,17 @@
//
export interface DrawingData {
id?: string
content?: string
}
export interface DrawingProps {
readonly?: boolean
readonly: boolean
imageWidth?: number
imageHeight?: number
drawingData?: DrawingData
saveDrawing?: (data: any) => Promise<void>
drawingData: DrawingData
drawingTool?: DrawingTool
penColor?: string
changed?: (content: string) => void
}
interface DrawCmd {
@ -198,7 +196,6 @@ export function drawing (node: HTMLElement, props: DrawingProps): any {
draw.penColor = props.penColor ?? 'blue'
updateCanvasCursor()
let modified = false
let commands: DrawCmd[] = []
let drawingData = props.drawingData
parseData()
@ -300,7 +297,7 @@ export function drawing (node: HTMLElement, props: DrawingProps): any {
points: draw.points
}
commands.push(cmd)
modified = true
props.changed?.(JSON.stringify(commands))
}
}
@ -340,7 +337,7 @@ export function drawing (node: HTMLElement, props: DrawingProps): any {
function parseData (): void {
clearCanvas()
if (drawingData?.content !== undefined) {
if (drawingData.content !== undefined && drawingData.content !== null) {
try {
commands = JSON.parse(drawingData.content)
replayCommands()
@ -356,42 +353,24 @@ export function drawing (node: HTMLElement, props: DrawingProps): any {
return {
update (props: DrawingProps) {
if (drawingData !== props.drawingData) {
// Currently it expectes only the empty data on update
// which means we pressed the "Clear canvas" button
// We don't support yet creation of multiple drawings for the same image
// so preserve the id to continue editing the previous drawing
const oldId = drawingData?.id
drawingData = props.drawingData
if (drawingData !== undefined) {
drawingData.id = oldId
}
modified = true
parseData()
}
let updateCursor = false
if (draw.tool !== props.drawingTool) {
draw.tool = props.drawingTool ?? 'pen'
updateCanvasCursor()
updateCursor = true
}
if (draw.penColor !== props.penColor) {
draw.penColor = props.penColor ?? 'blue'
updateCanvasCursor()
updateCursor = true
}
if (props.readonly !== readonly) {
readonly = props.readonly ?? false
updateCanvasCursor()
updateCursor = true
}
},
destroy () {
if (props.saveDrawing === undefined) {
console.log('Save drawing method is not provided')
} else {
if (modified && (commands.length > 0 || drawingData?.id !== undefined)) {
const data: DrawingData = drawingData ?? {}
data.content = JSON.stringify(commands)
props.saveDrawing(data).catch((error) => {
console.error('Failed to save drawing', error)
})
}
if (updateCursor) {
updateCanvasCursor()
}
}
}

View File

@ -122,7 +122,8 @@ export default plugin(presentationId, {
FailedToPreview: '' as IntlString,
ContentType: '' as IntlString,
ContentTypeNotSupported: '' as IntlString,
StartDrawing: '' as IntlString
StartDrawing: '' as IntlString,
DrawingHistory: '' as IntlString
},
extension: {
FilePreviewExtension: '' as ComponentExtensionId,

View File

@ -133,6 +133,10 @@
box-shadow: none;
}
&.auto {
max-width: unset;
}
&.full-width {
flex-grow: 1;
background: none;

View File

@ -120,7 +120,7 @@
{#if showIcon}
<div class="btn-icon {iconModifier}" class:buttonIconNoLabel={!shouldShowLabel}>
<Icon
icon={icon ?? (iconModifier === 'overdue' && !shouldIgnoreOverdue) ? DPCalendarOver : DPCalendar}
icon={icon ?? (iconModifier === 'overdue' && !shouldIgnoreOverdue ? DPCalendarOver : DPCalendar)}
size={'full'}
/>
</div>

View File

@ -0,0 +1,10 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.5 8H12v5l4.28 2.54.72-1.21-3.5-2.08V8M13 3a9 9 0 00-9 9H1l3.96 4.03L9 12H6a7 7 0 017-7 7 7 0 017 7 7 7 0 01-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.896 8.896 0 0013 21a9 9 0 009-9 9 9 0 00-9-9"
/>
</svg>

View File

@ -0,0 +1,10 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.06 2.334c-1.077.463-2.426 1.515-3.113 2.89l-.894-.448c.813-1.625 2.364-2.823 3.612-3.36.316-.136.626-.236.912-.287.278-.05.571-.061.833.017a.923.923 0 01.645.619c.091.3.028.623-.09.92-.284.708-.897 1.514-1.538 2.302-.196.241-.396.482-.596.723-.478.574-.953 1.147-1.365 1.71-.593.811-.972 1.501-1.04 2.034-.032.247.007.434.103.588.1.16.293.338.668.498 1.104.474 2.543.426 3.98.028.05-.527.17-1.101.342-1.705.536-1.876 1.757-3.141 2.93-3.581.583-.219 1.223-.254 1.743.053.542.32.808.924.808 1.665 0 .48-.196.947-.483 1.367-.29.424-.692.834-1.164 1.213-.86.69-1.99 1.308-3.195 1.73.02.326.086.581.186.77.123.234.31.394.603.476.313.088.77.092 1.418-.062.643-.154 1.44-.456 2.411-.941l.448.894c-1.013.507-1.885.842-2.627 1.02-.738.175-1.382.202-1.92.052a1.917 1.917 0 01-1.218-.972 2.711 2.711 0 01-.279-.946c-1.483.37-3.064.421-4.377-.142-.5-.214-.885-.505-1.123-.888-.242-.389-.3-.819-.246-1.244.103-.81.63-1.683 1.225-2.497.43-.59.939-1.201 1.425-1.786.195-.235.386-.465.567-.688.656-.805 1.168-1.499 1.385-2.042a.832.832 0 00.06-.216 1.008 1.008 0 00-.343.015 3.27 3.27 0 00-.693.221zm3.172 7.88c.952-.375 1.825-.876 2.495-1.414.419-.336.745-.676.964-.996C11.91 7.48 12 7.209 12 7c0-.509-.171-.718-.317-.804-.168-.099-.465-.134-.882.022-.827.31-1.856 1.295-2.32 2.92a9.81 9.81 0 00-.249 1.077z"
/>
</svg>

View File

@ -240,6 +240,8 @@ export { default as IconFolderCollapsed } from './components/icons/FolderCollaps
export { default as IconFolderExpanded } from './components/icons/FolderExpanded.svelte'
export { default as IconCheckmark } from './components/icons/Checkmark.svelte'
export { default as IconToDetails } from './components/icons/ToDetails.svelte'
export { default as IconHistory } from './components/icons/History.svelte'
export { default as IconScribble } from './components/icons/Scribble.svelte'
export { default as PanelInstance } from './components/PanelInstance.svelte'
export { default as Panel } from './components/Panel.svelte'

View File

@ -0,0 +1,13 @@
<script lang="ts">
import { type Drawing } from '@hcengineering/attachment'
import core, { DateRangeMode } from '@hcengineering/core'
import { DatePresenter, IconScribble } from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources'
export let value: Drawing
</script>
<div class="flex-presenter flex-gap-3">
<DatePresenter value={value.createdOn} mode={DateRangeMode.DATETIME} kind="list" icon={IconScribble} />
<ObjectPresenter objectId={value.createdBy} _class={core.class.Account} shouldShowName={false} />
</div>

View File

@ -42,6 +42,7 @@ import FileDownload from './components/icons/FileDownload.svelte'
import IconUploadDuo from './components/icons/UploadDuo.svelte'
import PreviewWidget from './components/PreviewWidget.svelte'
import PreviewPopupActions from './components/PreviewPopupActions.svelte'
import DrawingPresenter from './components/DrawingPresenter.svelte'
export * from './types'
@ -256,6 +257,7 @@ export default async (): Promise<Resources> => ({
AttachmentPresenter,
AttachmentGalleryPresenter,
Attachments,
DrawingPresenter,
FileBrowser,
Photos,
PDFViewer,

View File

@ -16,6 +16,7 @@
import { type BlobMetadata, type Attachment, type Drawing } from '@hcengineering/attachment'
import core, {
SortingOrder,
type Blob,
type Class,
type TxOperations as Client,
@ -151,28 +152,42 @@ export function showAttachmentPreviewPopup (value: WithLookup<Attachment>): Popu
if (value?.type?.startsWith('image/')) {
props.drawingAvailable = true
props.loadDrawings = async (): Promise<DrawingData | undefined> => {
props.loadDrawings = async (): Promise<Drawing[] | undefined> => {
const client = getClient()
const drawing = await client.findOne(attachment.class.Drawing, { parent: value.file })
if (drawing !== undefined) {
return {
id: drawing._id,
content: drawing.content
const drawings = await client.findAll(
attachment.class.Drawing,
{
parent: value.file,
space: value.space
},
{
sort: {
createdOn: SortingOrder.Descending
},
limit: 1
}
)
const result = []
if (drawings !== undefined) {
for (const drawing of drawings) {
result.push(drawing)
}
}
return result
}
props.saveDrawing = async (data: DrawingData): Promise<void> => {
props.createDrawing = async (data: DrawingData): Promise<DrawingData> => {
const client = getClient()
if (data.id === undefined) {
await client.createDoc(attachment.class.Drawing, value.space, {
parent: value.file,
parentClass: core.class.Blob,
content: data.content
})
const newId = await client.createDoc(attachment.class.Drawing, value.space, {
parent: value.file,
parentClass: core.class.Blob,
content: data.content
})
const newDrawing = await client.findOne(attachment.class.Drawing, { _id: newId })
if (newDrawing !== undefined) {
return newDrawing
} else {
await client.updateDoc(attachment.class.Drawing, value.space, data.id as Ref<Drawing>, {
content: data.content
})
console.error('Unable to find just created drawing')
return data
}
}
}

View File

@ -81,6 +81,7 @@ export default plugin(attachmentId, {
Attachments: '' as AnyComponent,
Photos: '' as AnyComponent,
AttachmentsPresenter: '' as AnyComponent,
DrawingPresenter: '' as AnyComponent,
FileBrowser: '' as AnyComponent,
PDFViewer: '' as AnyComponent
},

View File

@ -24,8 +24,8 @@
export let drawingAvailable: boolean
export let drawingEditable: boolean
export let drawingData: any
export let saveDrawing: (data: any) => Promise<void>
export let drawings: any
export let createDrawing: (data: any) => Promise<any>
$: originalWidth = metadata?.originalWidth
$: originalHeight = metadata?.originalHeight
@ -49,8 +49,8 @@
<DrawingBoard
{imageWidth}
{imageHeight}
{drawingData}
{saveDrawing}
{drawings}
{createDrawing}
active={drawingAvailable && !loading}
readonly={drawingAvailable && !drawingEditable}
class="object-contain mx-auto"

View File

@ -14,7 +14,7 @@
// limitations under the License.
//
import type { Attachment } from '@hcengineering/attachment'
import attachment, { type Attachment } from '@hcengineering/attachment'
import type { Tx, TxRemoveDoc } from '@hcengineering/core'
import type { TriggerControl } from '@hcengineering/server-core'
@ -23,8 +23,9 @@ import type { TriggerControl } from '@hcengineering/server-core'
*/
export async function OnAttachmentDelete (
txes: Tx[],
{ removedMap, ctx, storageAdapter, workspace }: TriggerControl
{ removedMap, ctx, storageAdapter, workspace, findAll, txFactory }: TriggerControl
): Promise<Tx[]> {
const result: Tx[] = []
const toDelete: string[] = []
for (const tx of txes) {
const rmTx = tx as TxRemoveDoc<Attachment>
@ -36,12 +37,18 @@ export async function OnAttachmentDelete (
continue
}
toDelete.push(attach.file)
const drawings = await findAll(ctx, attachment.class.Drawing, { parent: attach.file })
for (const drawing of drawings) {
const removeTx = txFactory.createTxRemoveDoc(drawing._class, drawing.space, drawing._id)
result.push(removeTx)
}
}
if (toDelete.length > 0) {
await storageAdapter.remove(ctx, workspace, toDelete)
}
return []
return result
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type