diff --git a/models/attachment/src/index.ts b/models/attachment/src/index.ts
index 163ce99ac9..87ed96ec97 100644
--- a/models/attachment/src/index.ts
+++ b/models/attachment/src/index.ts
@@ -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
diff --git a/packages/presentation/lang/cs.json b/packages/presentation/lang/cs.json
index bfdee2bce8..9c8abdbdd9 100644
--- a/packages/presentation/lang/cs.json
+++ b/packages/presentation/lang/cs.json
@@ -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ý"
diff --git a/packages/presentation/lang/en.json b/packages/presentation/lang/en.json
index 3bf9c961cd..0d31fff8f0 100644
--- a/packages/presentation/lang/en.json
+++ b/packages/presentation/lang/en.json
@@ -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"
diff --git a/packages/presentation/lang/es.json b/packages/presentation/lang/es.json
index 3f22269188..14d938443e 100644
--- a/packages/presentation/lang/es.json
+++ b/packages/presentation/lang/es.json
@@ -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"
diff --git a/packages/presentation/lang/fr.json b/packages/presentation/lang/fr.json
index 0d450533f9..c8ddbc4c9c 100644
--- a/packages/presentation/lang/fr.json
+++ b/packages/presentation/lang/fr.json
@@ -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"
diff --git a/packages/presentation/lang/it.json b/packages/presentation/lang/it.json
index 9c761f9d0b..213bff86bb 100644
--- a/packages/presentation/lang/it.json
+++ b/packages/presentation/lang/it.json
@@ -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"
diff --git a/packages/presentation/lang/pt.json b/packages/presentation/lang/pt.json
index be2daa978f..8b8fe39dd2 100644
--- a/packages/presentation/lang/pt.json
+++ b/packages/presentation/lang/pt.json
@@ -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"
diff --git a/packages/presentation/lang/ru.json b/packages/presentation/lang/ru.json
index d34ca683a2..5d58cfa38d 100644
--- a/packages/presentation/lang/ru.json
+++ b/packages/presentation/lang/ru.json
@@ -34,7 +34,8 @@
"FailedToPreview": "Ошибка предпросмотра",
"ContentType": "Тип контента",
"ContentTypeNotSupported": "Предварительный просмотр недоступен для этого типа контента",
- "StartDrawing": "Сделать набросок"
+ "StartDrawing": "Сделать набросок",
+ "DrawingHistory": "История набросков"
},
"status": {
"FileTooLarge": "Файл слишком большой"
diff --git a/packages/presentation/lang/zh.json b/packages/presentation/lang/zh.json
index 37e2d021f2..e1dd4686ce 100644
--- a/packages/presentation/lang/zh.json
+++ b/packages/presentation/lang/zh.json
@@ -34,7 +34,8 @@
"FailedToPreview": "预览失败",
"ContentType": "内容类型",
"ContentTypeNotSupported": "此內容類型無法預覽",
- "StartDrawing": "随意涂鸦"
+ "StartDrawing": "随意涂鸦",
+ "DrawingHistory": "涂鸦的历史"
},
"status": {
"FileTooLarge": "文件太大"
diff --git a/packages/presentation/src/components/DocPopup.svelte b/packages/presentation/src/components/DocPopup.svelte
index e5799c6bef..44e47e1d8a 100644
--- a/packages/presentation/src/components/DocPopup.svelte
+++ b/packages/presentation/src/components/DocPopup.svelte
@@ -14,8 +14,9 @@
-->
@@ -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 @@
+ {:else if type === 'presenter'}
+ {#if presenter !== undefined}
+
+ {/if}
{:else}
{/if}
diff --git a/packages/presentation/src/components/DrawingBoard.svelte b/packages/presentation/src/components/DrawingBoard.svelte
index 73692de942..046e5585b0 100644
--- a/packages/presentation/src/components/DrawingBoard.svelte
+++ b/packages/presentation/src/components/DrawingBoard.svelte
@@ -14,25 +14,30 @@
-->
-{#if active}
+{#if active && drawingData !== undefined}
{
+ modified = true
+ if (drawingData !== undefined) {
+ drawingData.content = content
+ }
+ }
}}
>
{#if !readonly}
@@ -68,6 +116,7 @@
kind="icon"
on:click={() => {
drawingData = {}
+ modified = true
}}
/>
diff --git a/packages/presentation/src/components/FilePreviewPopup.svelte b/packages/presentation/src/components/FilePreviewPopup.svelte
index 5dfb00a309..29015db6c7 100644
--- a/packages/presentation/src/components/FilePreviewPopup.svelte
+++ b/packages/presentation/src/components/FilePreviewPopup.svelte
@@ -13,9 +13,9 @@
// limitations under the License.
-->
@@ -91,13 +138,24 @@
{#if props.drawingAvailable === true}
+ {#if props.drawings !== undefined && props.drawings.length > 0}
+
+ {/if}
+
{/if}
[] = []
export let ignoreObjects: Ref[] = []
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[] | 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
diff --git a/packages/presentation/src/drawing.ts b/packages/presentation/src/drawing.ts
index 08ec556c4a..0332c3e3c5 100644
--- a/packages/presentation/src/drawing.ts
+++ b/packages/presentation/src/drawing.ts
@@ -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
-
+ 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()
}
}
}
diff --git a/packages/presentation/src/plugin.ts b/packages/presentation/src/plugin.ts
index a76fa5ebc9..2033fc4067 100644
--- a/packages/presentation/src/plugin.ts
+++ b/packages/presentation/src/plugin.ts
@@ -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,
diff --git a/packages/theme/styles/popups.scss b/packages/theme/styles/popups.scss
index aab96f82ca..9455d0e570 100644
--- a/packages/theme/styles/popups.scss
+++ b/packages/theme/styles/popups.scss
@@ -133,6 +133,10 @@
box-shadow: none;
}
+ &.auto {
+ max-width: unset;
+ }
+
&.full-width {
flex-grow: 1;
background: none;
diff --git a/packages/ui/src/components/calendar/DatePresenter.svelte b/packages/ui/src/components/calendar/DatePresenter.svelte
index c041825c87..3fd33f8529 100644
--- a/packages/ui/src/components/calendar/DatePresenter.svelte
+++ b/packages/ui/src/components/calendar/DatePresenter.svelte
@@ -120,7 +120,7 @@
{#if showIcon}
diff --git a/packages/ui/src/components/icons/History.svelte b/packages/ui/src/components/icons/History.svelte
new file mode 100644
index 0000000000..d841a28812
--- /dev/null
+++ b/packages/ui/src/components/icons/History.svelte
@@ -0,0 +1,10 @@
+
+
+
diff --git a/packages/ui/src/components/icons/Scribble.svelte b/packages/ui/src/components/icons/Scribble.svelte
new file mode 100644
index 0000000000..ce5440c9cb
--- /dev/null
+++ b/packages/ui/src/components/icons/Scribble.svelte
@@ -0,0 +1,10 @@
+
+
+
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 4e6151b557..ec8c8b97ab 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -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'
diff --git a/plugins/attachment-resources/src/components/DrawingPresenter.svelte b/plugins/attachment-resources/src/components/DrawingPresenter.svelte
new file mode 100644
index 0000000000..311c77cb4e
--- /dev/null
+++ b/plugins/attachment-resources/src/components/DrawingPresenter.svelte
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/plugins/attachment-resources/src/index.ts b/plugins/attachment-resources/src/index.ts
index 0e839f0694..972ceaaf18 100644
--- a/plugins/attachment-resources/src/index.ts
+++ b/plugins/attachment-resources/src/index.ts
@@ -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 => ({
AttachmentPresenter,
AttachmentGalleryPresenter,
Attachments,
+ DrawingPresenter,
FileBrowser,
Photos,
PDFViewer,
diff --git a/plugins/attachment-resources/src/utils.ts b/plugins/attachment-resources/src/utils.ts
index 3363ea2c49..51feeba355 100644
--- a/plugins/attachment-resources/src/utils.ts
+++ b/plugins/attachment-resources/src/utils.ts
@@ -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): Popu
if (value?.type?.startsWith('image/')) {
props.drawingAvailable = true
- props.loadDrawings = async (): Promise => {
+ props.loadDrawings = async (): Promise => {
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 => {
+ props.createDrawing = async (data: DrawingData): Promise => {
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, {
- content: data.content
- })
+ console.error('Unable to find just created drawing')
+ return data
}
}
}
diff --git a/plugins/attachment/src/index.ts b/plugins/attachment/src/index.ts
index 84a5f4e3b2..73172572ec 100644
--- a/plugins/attachment/src/index.ts
+++ b/plugins/attachment/src/index.ts
@@ -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
},
diff --git a/plugins/view-resources/src/components/viewer/ImageViewer.svelte b/plugins/view-resources/src/components/viewer/ImageViewer.svelte
index fd5ef2ca80..c9476d2e8c 100644
--- a/plugins/view-resources/src/components/viewer/ImageViewer.svelte
+++ b/plugins/view-resources/src/components/viewer/ImageViewer.svelte
@@ -24,8 +24,8 @@
export let drawingAvailable: boolean
export let drawingEditable: boolean
- export let drawingData: any
- export let saveDrawing: (data: any) => Promise
+ export let drawings: any
+ export let createDrawing: (data: any) => Promise
$: originalWidth = metadata?.originalWidth
$: originalHeight = metadata?.originalHeight
@@ -49,8 +49,8 @@
{
+ const result: Tx[] = []
const toDelete: string[] = []
for (const tx of txes) {
const rmTx = tx as TxRemoveDoc
@@ -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