diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a30129e06..4f9f563ee 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,7 +4,7 @@ import { createBoardContent, validateElements } from '@idraw/util'; import { Cursor } from './lib/cursor'; // export { MiddlewareSelector } from './middleware/selector'; -export { MiddlewareSelector, middlewareEventSelect } from './middleware/selector'; +export { MiddlewareSelector, middlewareEventSelect, middlewareEventSelectClear } from './middleware/selector'; export { MiddlewareScroller } from './middleware/scroller'; export { MiddlewareScaler, middlewareEventScale } from './middleware/scaler'; export { MiddlewareRuler, middlewareEventRuler } from './middleware/ruler'; diff --git a/packages/core/src/middleware/selector/index.ts b/packages/core/src/middleware/selector/index.ts index 69487dbde..c6e25000f 100644 --- a/packages/core/src/middleware/selector/index.ts +++ b/packages/core/src/middleware/selector/index.ts @@ -56,6 +56,8 @@ import { middlewareEventTextEdit } from '../text-editor'; export const middlewareEventSelect: string = '@middleware/select'; +export const middlewareEventSelectClear: string = '@middleware/select-clear'; + export const MiddlewareSelector: BoardMiddleware = (opts) => { const { viewer, sharer, boardContent, calculator, eventHub } = opts; const { helperContext } = boardContent; @@ -175,13 +177,20 @@ export const MiddlewareSelector: BoardMiddleware { + clear(); + viewer.drawFrame(); + }; + return { use() { eventHub.on(middlewareEventSelect, selectCallback); + eventHub.on(middlewareEventSelectClear, selectClearCallback); }, disuse() { eventHub.off(middlewareEventSelect, selectCallback); + eventHub.off(middlewareEventSelectClear, selectClearCallback); }, hover: (e: PointWatcherEvent) => { diff --git a/packages/idraw/src/idraw.ts b/packages/idraw/src/idraw.ts index fe2ee1660..45091727f 100644 --- a/packages/idraw/src/idraw.ts +++ b/packages/idraw/src/idraw.ts @@ -28,7 +28,8 @@ import { deleteElementInList, moveElementPosition, getElementPositionFromList, - calcElementListSize + calcElementListSize, + filterCompactData } from '@idraw/util'; import { defaultSettings } from './config'; import { exportImageFileBlobURL } from './file'; @@ -111,8 +112,14 @@ export class iDraw { core.trigger('change', { data, type: 'set-data' }); } - getData(): Data | null { - return this.#core.getData(); + getData(opts?: { compact?: boolean }): Data | null { + const data = this.#core.getData(); + if (data && opts?.compact === true) { + return filterCompactData(data, { + loadItemMap: this.#core.getLoadItemMap() + }); + } + return data; } getViewInfo(): { diff --git a/packages/idraw/src/index.ts b/packages/idraw/src/index.ts index 8651fffa2..963e3fd80 100644 --- a/packages/idraw/src/index.ts +++ b/packages/idraw/src/index.ts @@ -3,6 +3,7 @@ export { Core, MiddlewareSelector, middlewareEventSelect, + middlewareEventSelectClear, MiddlewareScroller, MiddlewareScaler, middlewareEventScale, diff --git a/packages/types/src/lib/core.ts b/packages/types/src/lib/core.ts index 30fc9a5fd..532d92bfc 100644 --- a/packages/types/src/lib/core.ts +++ b/packages/types/src/lib/core.ts @@ -30,6 +30,7 @@ export interface CoreEventSelect { positions?: Array>; } export interface CoreEventChange { + type: string; data: Data; } export interface CoreEventScale { diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index 5d4d74b19..adc5db111 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -2,7 +2,7 @@ export { delay, compose, throttle } from './lib/time'; export { downloadImageFromCanvas, parseFileToBase64, pickFile, parseFileToText, downloadFileFromText } from './lib/file'; export { toColorHexStr, toColorHexNum, isColorStr, colorNameToHex, colorToCSS, colorToLinearGradientCSS, mergeHexColorAlpha } from './lib/color'; export { createUUID, isAssetId, createAssetId } from './lib/uuid'; -export { deepClone, sortDataAsserts, deepCloneElement } from './lib/data'; +export { deepClone, sortDataAsserts, deepCloneElement, filterCompactData } from './lib/data'; export { istype } from './lib/istype'; export { loadImage, loadSVG, loadHTML } from './lib/load'; export { is } from './lib/is'; diff --git a/packages/util/src/lib/data.ts b/packages/util/src/lib/data.ts index 429c7e07b..46ac9afcf 100644 --- a/packages/util/src/lib/data.ts +++ b/packages/util/src/lib/data.ts @@ -1,5 +1,5 @@ -import type { Data, ElementAssets, Elements, ElementType, Element } from '@idraw/types'; -import { createAssetId, createUUID } from './uuid'; +import type { Data, ElementAssets, Elements, ElementType, Element, LoadItemMap } from '@idraw/types'; +import { createAssetId, createUUID, isAssetId } from './uuid'; export function deepClone(target: T): T { function _clone(t: T) { @@ -82,7 +82,79 @@ export function sortDataAsserts(data: Data, opts?: { clone?: boolean }): Data { const assetUUID = createAssetId(html); if (!assets[assetUUID]) { assets[assetUUID] = { + type: 'html', + value: html + }; + } + (elem as Element<'html'>).detail.html = assetUUID; + } else if (elem.type === 'group' && Array.isArray((elem as Element<'group'>).detail.children)) { + const groupAssets = (elem as Element<'group'>).detail.assets || {}; + Object.keys(groupAssets).forEach((assetId) => { + if (!assets[assetId]) { + assets[assetId] = groupAssets[assetId]; + } + }); + delete (elem as Element<'group'>).detail.assets; + _scanElements((elem as Element<'group'>).detail.children); + } + }); + }; + + _scanElements(sortedData.elements); + sortedData.assets = assets; + return sortedData; +} + +export function filterCompactData(data: Data, opts?: { loadItemMap?: LoadItemMap }) { + const assets: ElementAssets = data.assets || {}; + const sortedData = deepClone(data); + const loadItemMap = opts?.loadItemMap || {}; + + const _scanElements = (elems: Elements) => { + elems.forEach((elem: Element) => { + if (elem.type === 'image' && (elem as Element<'image'>).detail.src) { + const src = (elem as Element<'image'>).detail.src; + if (isAssetId(src) && !assets[src] && loadItemMap[src] && typeof loadItemMap[src]?.source === 'string') { + assets[src] = { + type: 'image', + value: loadItemMap[src].source as string + }; + } else { + const assetUUID = createAssetId(src); + if (!assets[assetUUID]) { + assets[assetUUID] = { + type: 'image', + value: src + }; + } + (elem as Element<'image'>).detail.src = assetUUID; + } + } else if (elem.type === 'svg') { + const svg = (elem as Element<'svg'>).detail.svg; + const assetUUID = createAssetId(svg); + if (isAssetId(svg) && !assets[svg] && loadItemMap[svg] && typeof loadItemMap[svg]?.source === 'string') { + assets[svg] = { type: 'svg', + value: loadItemMap[svg].source as string + }; + } else if (!assets[assetUUID]) { + assets[assetUUID] = { + type: 'svg', + value: svg + }; + } + (elem as Element<'svg'>).detail.svg = assetUUID; + } else if (elem.type === 'html') { + const html = (elem as Element<'html'>).detail.html; + const assetUUID = createAssetId(html); + if (isAssetId(html) && !assets[html] && loadItemMap[html] && typeof loadItemMap[html]?.source === 'string') { + assets[html] = { + type: 'html', + value: loadItemMap[html].source as string + }; + } else if (!assets[assetUUID]) { + assets[assetUUID] = { + type: 'html', value: html }; } diff --git a/packages/util/src/lib/file.ts b/packages/util/src/lib/file.ts index 22c7e2150..d0c00ce26 100644 --- a/packages/util/src/lib/file.ts +++ b/packages/util/src/lib/file.ts @@ -10,10 +10,13 @@ export function downloadImageFromCanvas(canvas: HTMLCanvasElement, opts: { fileN downloadLink = null; } -export function pickFile(opts: { success: (data: { file: File }) => void; error?: (err: ErrorEvent) => void }) { - const { success, error } = opts; +export function pickFile(opts: { accept?: string; success: (data: { file: File }) => void; error?: (err: Error | any) => void }) { + const { accept, success, error } = opts; let input: HTMLInputElement | null = document.createElement('input') as HTMLInputElement; input.type = 'file'; + if (accept) { + input.accept = accept; + } input.addEventListener('change', function () { const file: File = (input as HTMLInputElement).files?.[0] as File; success({