@dou-ui/dashboard 0.0.0-stage → 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/chart/chart-settings.js +6 -0
- package/dist/chart/chart-settings.js.map +1 -0
- package/dist/chart/chart-settings.vue_vue_type_script_setup_true_lang.js +122 -0
- package/dist/chart/chart-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/chart/chart.js +6 -0
- package/dist/chart/chart.js.map +1 -0
- package/dist/chart/chart.vue_vue_type_script_setup_true_lang.js +99 -0
- package/dist/chart/chart.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/chart/index.js +33 -0
- package/dist/chart/index.js.map +1 -0
- package/dist/collaboration/index.js +281 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/order.js +88 -0
- package/dist/collaboration/order.js.map +1 -0
- package/dist/core/document.js +128 -0
- package/dist/core/document.js.map +1 -0
- package/dist/core/history.js +66 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/transaction.js +218 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/dashboard/dashboard.js +6 -0
- package/dist/dashboard/dashboard.js.map +1 -0
- package/dist/dashboard/dashboard.vue_vue_type_script_setup_true_lang.js +1072 -0
- package/dist/dashboard/dashboard.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/dashboard/export.js +77 -0
- package/dist/dashboard/export.js.map +1 -0
- package/dist/dashboard/filter-bar.js +6 -0
- package/dist/dashboard/filter-bar.js.map +1 -0
- package/dist/dashboard/filter-bar.vue_vue_type_script_setup_true_lang.js +150 -0
- package/dist/dashboard/filter-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/dashboard/frame-context.js +5 -0
- package/dist/dashboard/frame-context.js.map +1 -0
- package/dist/dashboard/observers.js +48 -0
- package/dist/dashboard/observers.js.map +1 -0
- package/dist/dashboard/palette.js +6 -0
- package/dist/dashboard/palette.js.map +1 -0
- package/dist/dashboard/palette.vue_vue_type_script_setup_true_lang.js +54 -0
- package/dist/dashboard/palette.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/dashboard/widget-body.js +6 -0
- package/dist/dashboard/widget-body.js.map +1 -0
- package/dist/dashboard/widget-body.vue_vue_type_script_setup_true_lang.js +96 -0
- package/dist/dashboard/widget-body.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/dashboard/widget-frame.js +6 -0
- package/dist/dashboard/widget-frame.js.map +1 -0
- package/dist/dashboard/widget-frame.vue_vue_type_script_setup_true_lang.js +300 -0
- package/dist/dashboard/widget-frame.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/dashboard/widget-settings.js +6 -0
- package/dist/dashboard/widget-settings.js.map +1 -0
- package/dist/dashboard/widget-settings.vue_vue_type_script_setup_true_lang.js +36 -0
- package/dist/dashboard/widget-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/data/context.js +5 -0
- package/dist/data/context.js.map +1 -0
- package/dist/data/filters.js +31 -0
- package/dist/data/filters.js.map +1 -0
- package/dist/data/store.js +214 -0
- package/dist/data/store.js.map +1 -0
- package/dist/grid/grid-handle.js +6 -0
- package/dist/grid/grid-handle.js.map +1 -0
- package/dist/grid/grid-handle.vue_vue_type_script_setup_true_lang.js +21 -0
- package/dist/grid/grid-handle.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/grid/grid-item.js +80 -0
- package/dist/grid/grid-item.js.map +1 -0
- package/dist/grid/grid-layout.js +6 -0
- package/dist/grid/grid-layout.js.map +1 -0
- package/dist/grid/grid-layout.vue_vue_type_script_setup_true_lang.js +767 -0
- package/dist/grid/grid-layout.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/grid/tiers.js +31 -0
- package/dist/grid/tiers.js.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/engine.js +284 -0
- package/dist/layout/engine.js.map +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/locale.js +245 -0
- package/dist/locale.js.map +1 -0
- package/dist/pivot/index.js +47 -0
- package/dist/pivot/index.js.map +1 -0
- package/dist/pivot/pivot-settings.js +6 -0
- package/dist/pivot/pivot-settings.js.map +1 -0
- package/dist/pivot/pivot-settings.vue_vue_type_script_setup_true_lang.js +64 -0
- package/dist/pivot/pivot-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/pivot/pivot.js +6 -0
- package/dist/pivot/pivot.js.map +1 -0
- package/dist/pivot/pivot.vue_vue_type_script_setup_true_lang.js +76 -0
- package/dist/pivot/pivot.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +12 -0
- package/dist/types/chart/chart-settings.vue.d.ts +11 -0
- package/dist/types/chart/chart.vue.d.ts +3 -0
- package/dist/types/chart/index.d.ts +9 -0
- package/dist/types/chart/types.d.ts +23 -0
- package/dist/types/collaboration/index.d.ts +53 -0
- package/dist/types/collaboration/order.d.ts +20 -0
- package/dist/types/core/collaboration.d.ts +51 -0
- package/dist/types/core/document.d.ts +35 -0
- package/dist/types/core/history.d.ts +15 -0
- package/dist/types/core/transaction.d.ts +86 -0
- package/dist/types/core/types.d.ts +27 -0
- package/dist/types/dashboard/dashboard.vue.d.ts +33 -0
- package/dist/types/dashboard/export.d.ts +11 -0
- package/dist/types/dashboard/filter-bar.vue.d.ts +19 -0
- package/dist/types/dashboard/frame-context.d.ts +54 -0
- package/dist/types/dashboard/observers.d.ts +15 -0
- package/dist/types/dashboard/palette.vue.d.ts +20 -0
- package/dist/types/dashboard/types.d.ts +190 -0
- package/dist/types/dashboard/widget-body.vue.d.ts +19 -0
- package/dist/types/dashboard/widget-frame.vue.d.ts +6 -0
- package/dist/types/dashboard/widget-settings.vue.d.ts +16 -0
- package/dist/types/data/context.d.ts +18 -0
- package/dist/types/data/filters.d.ts +9 -0
- package/dist/types/data/store.d.ts +48 -0
- package/dist/types/data/types.d.ts +67 -0
- package/dist/types/grid/grid-handle.vue.d.ts +18 -0
- package/dist/types/grid/grid-item.d.ts +78 -0
- package/dist/types/grid/grid-layout.vue.d.ts +46 -0
- package/dist/types/grid/tiers.d.ts +12 -0
- package/dist/types/grid/types.d.ts +113 -0
- package/dist/types/index.d.ts +295 -0
- package/dist/types/layout/engine.d.ts +50 -0
- package/dist/types/layout/index.d.ts +2 -0
- package/dist/types/layout/types.d.ts +39 -0
- package/dist/types/locale.d.ts +117 -0
- package/dist/types/pivot/index.d.ts +14 -0
- package/dist/types/pivot/pivot-settings.vue.d.ts +16 -0
- package/dist/types/pivot/pivot.vue.d.ts +7 -0
- package/dist/types/pivot/types.d.ts +11 -0
- package/dist/types/widget/context.d.ts +91 -0
- package/dist/types/widget/define.d.ts +50 -0
- package/dist/types/widgets/field-select.vue.d.ts +18 -0
- package/dist/types/widgets/index.d.ts +15 -0
- package/dist/types/widgets/settings-field.vue.d.ts +17 -0
- package/dist/types/widgets/shared.d.ts +20 -0
- package/dist/types/widgets/source-field.vue.d.ts +15 -0
- package/dist/types/widgets/stat-settings.vue.d.ts +11 -0
- package/dist/types/widgets/stat.vue.d.ts +3 -0
- package/dist/types/widgets/table-settings.vue.d.ts +11 -0
- package/dist/types/widgets/table.vue.d.ts +3 -0
- package/dist/types/widgets/text-settings.vue.d.ts +11 -0
- package/dist/types/widgets/text.vue.d.ts +3 -0
- package/dist/types/widgets/types.d.ts +38 -0
- package/dist/widget/context.js +88 -0
- package/dist/widget/context.js.map +1 -0
- package/dist/widget/define.js +32 -0
- package/dist/widget/define.js.map +1 -0
- package/dist/widgets/field-select.js +6 -0
- package/dist/widgets/field-select.js.map +1 -0
- package/dist/widgets/field-select.vue_vue_type_script_setup_true_lang.js +54 -0
- package/dist/widgets/field-select.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/widgets/index.js +74 -0
- package/dist/widgets/index.js.map +1 -0
- package/dist/widgets/settings-field.js +6 -0
- package/dist/widgets/settings-field.js.map +1 -0
- package/dist/widgets/settings-field.vue_vue_type_script_setup_true_lang.js +28 -0
- package/dist/widgets/settings-field.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/widgets/shared.js +68 -0
- package/dist/widgets/shared.js.map +1 -0
- package/dist/widgets/source-field.js +6 -0
- package/dist/widgets/source-field.js.map +1 -0
- package/dist/widgets/source-field.vue_vue_type_script_setup_true_lang.js +47 -0
- package/dist/widgets/source-field.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/widgets/stat-settings.js +6 -0
- package/dist/widgets/stat-settings.js.map +1 -0
- package/dist/widgets/stat-settings.vue_vue_type_script_setup_true_lang.js +107 -0
- package/dist/widgets/stat-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/widgets/stat.js +6 -0
- package/dist/widgets/stat.js.map +1 -0
- package/dist/widgets/stat.vue_vue_type_script_setup_true_lang.js +72 -0
- package/dist/widgets/stat.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/widgets/table-settings.js +6 -0
- package/dist/widgets/table-settings.js.map +1 -0
- package/dist/widgets/table-settings.vue_vue_type_script_setup_true_lang.js +90 -0
- package/dist/widgets/table-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/widgets/table.js +6 -0
- package/dist/widgets/table.js.map +1 -0
- package/dist/widgets/table.vue_vue_type_script_setup_true_lang.js +91 -0
- package/dist/widgets/table.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/widgets/text-settings.js +6 -0
- package/dist/widgets/text-settings.js.map +1 -0
- package/dist/widgets/text-settings.vue_vue_type_script_setup_true_lang.js +63 -0
- package/dist/widgets/text-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/widgets/text.js +6 -0
- package/dist/widgets/text.js.map +1 -0
- package/dist/widgets/text.vue_vue_type_script_setup_true_lang.js +16 -0
- package/dist/widgets/text.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/package.json +98 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/collaboration/index.ts"],"sourcesContent":["import { collaborationColors } from 'dou-ui'\nimport { shallowRef } from 'vue'\nimport * as Y from 'yjs'\nimport type {\n DashboardCollaboration,\n DashboardCollaborationHost,\n DashboardPeer,\n DashboardPresence,\n} from '../core/collaboration'\nimport type { DashboardTransaction } from '../core/transaction'\nimport type { DashboardDocument, DashboardWidget } from '../core/types'\nimport { orderKeys } from './order'\n\n/** The part of an `Awareness` (`y-protocols/awareness`) the dashboard uses. */\nexport interface AwarenessLike {\n clientID: number\n getLocalState(): Record<string, unknown> | null\n setLocalStateField(field: string, value: unknown): void\n getStates(): Map<number, Record<string, unknown>>\n on(event: 'change', listener: (...args: unknown[]) => void): void\n off(event: 'change', listener: (...args: unknown[]) => void): void\n}\n\n/** How the people editing together see each other. */\nexport interface CollaborationUser {\n name: string\n /** The color of this user's outlines, any CSS color. Picked from the client id when left out. */\n color?: string\n}\n\nexport interface CollaborationOptions {\n /** Where the widgets live, e.g. `ydoc.getMap('widgets')`. */\n widgets: Y.Map<unknown>\n /** Shares which widget each person is at, and what they do there (`y-protocols/awareness`). */\n awareness?: AwarenessLike\n /** This user, shown to the others. Needs `awareness`. */\n user?: CollaborationUser\n}\n\nexport { collaborationColors }\nexport type { DashboardPeer, DashboardPresence }\n\ntype Shared = Y.Map<unknown>\ntype Item = Record<string, unknown>\n\n/** A widget's place among the others, kept beside its keys. */\nconst ORDER = '$order'\n/** The places by breakpoint, a map of their own, so places on two breakpoints don't overwrite each other. */\nconst PLACE = 'place'\n\nconst same = (a: unknown, b: unknown) =>\n a === b ||\n (typeof a === 'object' && typeof b === 'object' && JSON.stringify(a) === JSON.stringify(b))\n\n/** Where in `value` something JSON can't carry sits, e.g. a component or a function. */\nfunction unshareable(value: unknown, path: string): string | null {\n if (value === null || ['string', 'number', 'boolean', 'undefined'].includes(typeof value))\n return null\n if (typeof value !== 'object') return path\n const proto = Object.getPrototypeOf(value)\n if (!Array.isArray(value) && proto !== Object.prototype && proto !== null) return path\n for (const [key, item] of Object.entries(value)) {\n const found = unshareable(item, Array.isArray(value) ? `${path}[${key}]` : `${path}.${key}`)\n if (found) return found\n }\n return null\n}\n\nconst warned = new Set<string>()\n/** Others would get a broken copy of such a value while this dashboard shows it fine. */\nfunction warnUnshareable(id: string, key: string, value: unknown) {\n // the app's bundler replaces this, as for Vue's own warnings\n if (process.env.NODE_ENV === 'production') return\n const path = unshareable(value, key)\n if (!path || warned.has(`${id}:${path}`)) return\n warned.add(`${id}:${path}`)\n console.warn(\n `[@dou-ui/dashboard] \"${id}\" has ${path}, which can't be shared: only JSON values reach the others.`,\n )\n}\n\n/** Writes the places that changed into a widget's place map. */\nfunction writePlace(fields: Shared, place: DashboardWidget['place']) {\n const shared = fields.get(PLACE)\n if (!(shared instanceof Y.Map)) {\n const map = new Y.Map<unknown>()\n for (const [name, value] of Object.entries(place)) if (value) map.set(name, { ...value })\n fields.set(PLACE, map)\n return\n }\n for (const [name, value] of Object.entries(place))\n if (value && !same(shared.get(name), value)) shared.set(name, { ...value })\n for (const name of Array.from(shared.keys())) if (!place[name]) shared.delete(name)\n}\n\n/** Writes a widget's keys besides its order, only those that changed; `keys` limits which. */\nfunction writeWidget(map: Shared, widget: DashboardWidget, keys?: Set<string>) {\n const existing = map.get(widget.id)\n const item = widget as unknown as Item\n if (!(existing instanceof Y.Map)) {\n // a new map can't be read before it is in the document: fill it, then put it in\n const fields = new Y.Map<unknown>()\n for (const [key, value] of Object.entries(item)) {\n if (key === 'id' || value === undefined) continue\n if (key === PLACE) {\n const places = new Y.Map<unknown>()\n for (const [name, place] of Object.entries(value as DashboardWidget['place']))\n if (place) places.set(name, { ...place })\n fields.set(PLACE, places)\n continue\n }\n warnUnshareable(widget.id, key, value)\n fields.set(key, value)\n }\n map.set(widget.id, fields)\n return\n }\n for (const [key, value] of Object.entries(item)) {\n if (key === 'id' || value === undefined || (keys && !keys.has(key))) continue\n if (key === PLACE) writePlace(existing, value as DashboardWidget['place'])\n else if (!same(existing.get(key), value)) {\n warnUnshareable(widget.id, key, value)\n existing.set(key, value)\n }\n }\n for (const key of Array.from(existing.keys()))\n if (key[0] !== '$' && item[key] === undefined && (!keys || keys.has(key))) existing.delete(key)\n}\n\n/** Writes each widget's order key, giving new keys only where it must. */\nfunction writeOrder(map: Shared, widgets: readonly DashboardWidget[]) {\n const shared = widgets.map((widget) => {\n const fields = map.get(widget.id)\n return fields instanceof Y.Map ? fields : null\n })\n const keys = orderKeys(shared.map((fields) => (fields?.get(ORDER) as string | undefined) ?? null))\n shared.forEach((fields, index) => {\n if (fields && fields.get(ORDER) !== keys[index]) fields.set(ORDER, keys[index])\n })\n}\n\n/**\n * Writes a dashboard into an empty map, e.g. when a dashboard so far saved\n * as JSON starts being edited together. Do it once, where the shared\n * document is created; otherwise the first person to open the dashboard\n * brings theirs.\n */\nexport function fillMap(map: Y.Map<unknown>, document: DashboardDocument) {\n const fill = () => {\n for (const widget of document.widgets) writeWidget(map, widget)\n writeOrder(map, document.widgets)\n }\n if (map.doc) map.doc.transact(fill)\n else fill()\n}\n\n/** The dashboard the map holds: its widgets by their order keys (then ids, the same for everyone). */\nexport function readMap(map: Y.Map<unknown>): DashboardDocument {\n const list: { widget: DashboardWidget; order: string }[] = []\n map.forEach((value, id) => {\n if (!(value instanceof Y.Map)) return\n const { [ORDER]: order, ...rest } = value.toJSON() as Item\n if (typeof rest.type !== 'string') return\n const place = rest[PLACE]\n list.push({\n widget: {\n ...rest,\n id,\n place: place && typeof place === 'object' ? place : {},\n } as DashboardWidget,\n order: typeof order === 'string' ? order : '',\n })\n })\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts the list built here\n list.sort((a, b) =>\n a.order < b.order ? -1 : a.order > b.order ? 1 : a.widget.id < b.widget.id ? -1 : 1,\n )\n return { version: 1, widgets: list.map((entry) => entry.widget) }\n}\n\n/** The widgets and keys a transaction changed, written to the map. */\nfunction writeChanges(map: Shared, tr: DashboardTransaction) {\n const after = new Map(tr.doc.widgets.map((widget) => [widget.id, widget]))\n const touched = new Map<string, Set<string> | true>()\n let added = false\n let removed = false\n for (const step of tr.steps) {\n if (step.type === 'add') {\n touched.set(step.widget.id, true)\n added = true\n } else if (step.type === 'remove') {\n removed = true\n touched.delete(step.id)\n } else {\n const current = touched.get(step.id)\n if (current === true) continue\n const keys = current ?? new Set<string>()\n for (const key of Object.keys(step.patch)) keys.add(key)\n touched.set(step.id, keys)\n }\n }\n if (removed)\n for (const widget of tr.before.widgets) if (!after.has(widget.id)) map.delete(widget.id)\n for (const [id, keys] of touched) {\n const widget = after.get(id)\n if (!widget) continue\n // a widget removed and added again in one change is written anew\n if (keys === true && map.get(id) instanceof Y.Map && removed) map.delete(id)\n writeWidget(map, widget, keys === true ? undefined : keys)\n }\n if (added) writeOrder(map, tr.doc.widgets)\n}\n\nconst NO_PEERS: readonly DashboardPeer[] = []\n\n/**\n * Edit a dashboard together through a Yjs document: pass the result to\n * `DouDashboard` as `collaboration`. Each widget lives in `widgets` as its\n * own `Y.Map`, each key on its own and each breakpoint's place on its own,\n * so two people changing different widgets, or different settings of one,\n * both keep their change. When two people move widgets at once, everyone\n * lays the places out the same way, so the dashboards agree. While a widget\n * is dragged, changes from others wait until it is let go. The dashboard's\n * `v-model` only mirrors the shared document: a document set through\n * `v-model` from outside is not written back. Undo only takes back this\n * user's own changes. With `awareness`, each person sees which widget the\n * others are at, outlined in their colors, and whether they are moving,\n * resizing or changing its settings.\n */\nexport function collaboration(options: CollaborationOptions): DashboardCollaboration {\n const { widgets: map, awareness, user } = options\n const ydoc = map.doc\n if (!ydoc) throw new Error('[@dou-ui/dashboard] widgets must be a map of a Y.Doc')\n if (awareness && user) {\n const color = user.color ?? collaborationColors[ydoc.clientID % collaborationColors.length]\n awareness.setLocalStateField('user', { name: user.name, color })\n }\n\n /** This dashboard's own changes, which undo can take back. */\n const tracked = { dashboard: 'tracked' }\n /** Changes undo leaves alone, e.g. filling the map. */\n const untracked = { dashboard: 'untracked' }\n const undoManager = new Y.UndoManager([map], {\n trackedOrigins: new Set([tracked]),\n captureTimeout: 500,\n })\n /** Bumped when the undo stacks change, so `canUndo` and `canRedo` update. */\n const stacks = shallowRef(0)\n const bump = () => stacks.value++\n undoManager.on('stack-item-added', bump)\n undoManager.on('stack-item-popped', bump)\n undoManager.on('stack-cleared', bump)\n\n let host: DashboardCollaborationHost | undefined\n /** What undo or redo is running, to name the change. */\n let travelling: 'undo' | 'redo' | null = null\n let lastGroup: string | undefined\n\n /** Brings the dashboard in line with the map. */\n function pull() {\n host?.apply(readMap(map), travelling ?? 'remote')\n }\n\n function run(action: 'undo' | 'redo') {\n const stack = action === 'undo' ? undoManager.undoStack : undoManager.redoStack\n if (!stack.length) return false\n travelling = action\n try {\n undoManager[action]()\n } finally {\n travelling = null\n }\n return true\n }\n\n /** The others at each widget. */\n const peersByWidget = shallowRef(new Map<string, DashboardPeer[]>())\n function readPeers() {\n if (!awareness) return\n const next = new Map<string, DashboardPeer[]>()\n awareness.getStates().forEach((state, client) => {\n if (client === awareness.clientID) return\n const who = state.user as { name?: string; color?: string } | undefined\n const presence = state.dashboard as DashboardPresence | null | undefined\n if (!who || !presence?.widget) return\n const peer: DashboardPeer = {\n client,\n name: who.name ?? '',\n color: who.color ?? 'currentColor',\n action: presence.action,\n }\n const list = next.get(presence.widget)\n if (list) list.push(peer)\n else next.set(presence.widget, [peer])\n })\n peersByWidget.value = next\n }\n\n return {\n setup(dashboard) {\n host = dashboard\n // the first one to open the dashboard brings its widgets; everyone else reads the map\n if (!map.size) ydoc.transact(() => fillMap(map, dashboard.document()), untracked)\n pull()\n\n // changes from others, and from undo: applied once per Yjs transaction\n let dirty = false\n const observe = (_events: unknown, transaction: Y.Transaction) => {\n if (transaction.origin !== tracked && transaction.origin !== untracked) dirty = true\n }\n const flush = () => {\n if (!dirty) return\n dirty = false\n pull()\n }\n map.observeDeep(observe)\n ydoc.on('afterTransaction', flush)\n if (awareness) {\n readPeers()\n awareness.on('change', readPeers)\n }\n return () => {\n map.unobserveDeep(observe)\n ydoc.off('afterTransaction', flush)\n if (awareness) awareness.off('change', readPeers)\n host = undefined\n }\n },\n transaction(tr) {\n const tracking = tr.meta.history !== false\n // like the dashboard's own history: only changes of one group undo together\n if (tracking && (!tr.meta.group || tr.meta.group !== lastGroup)) undoManager.stopCapturing()\n if (tracking) lastGroup = tr.meta.group\n ydoc.transact(() => writeChanges(map, tr), tracking ? tracked : untracked)\n },\n sync: pull,\n undo: () => run('undo'),\n redo: () => run('redo'),\n canUndo: () => (stacks.value, undoManager.undoStack.length > 0),\n canRedo: () => (stacks.value, undoManager.redoStack.length > 0),\n presence: awareness\n ? (presence) => awareness.setLocalStateField('dashboard', presence)\n : undefined,\n peers: awareness ? (id) => peersByWidget.value.get(id) ?? NO_PEERS : undefined,\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/collaboration/order.ts
|
|
2
|
+
/**
|
|
3
|
+
* Order keys: strings that sort like the widgets they belong to. A key can
|
|
4
|
+
* always be made between two others, so inserting a widget only
|
|
5
|
+
* writes that widget's key, never its neighbours'.
|
|
6
|
+
*/
|
|
7
|
+
var DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
8
|
+
/** A key between `a` and `b` (`b === null` means no upper bound). Keys never end in `0`. */
|
|
9
|
+
function midpoint(a, b) {
|
|
10
|
+
if (b !== null) {
|
|
11
|
+
let n = 0;
|
|
12
|
+
while ((a[n] ?? "0") === b[n]) n++;
|
|
13
|
+
if (n > 0) return b.slice(0, n) + midpoint(a.slice(n), b.slice(n));
|
|
14
|
+
}
|
|
15
|
+
const low = a ? DIGITS.indexOf(a[0]) : 0;
|
|
16
|
+
const high = b !== null ? DIGITS.indexOf(b[0]) : 62;
|
|
17
|
+
if (high - low > 1) return DIGITS[Math.round((low + high) / 2)];
|
|
18
|
+
if (b !== null && b.length > 1) return b.slice(0, 1);
|
|
19
|
+
return DIGITS[low] + midpoint(a.slice(1), null);
|
|
20
|
+
}
|
|
21
|
+
/** A key after `a` and before `b`; either may be missing. Needs `a < b`. */
|
|
22
|
+
function keyBetween(a, b) {
|
|
23
|
+
if (a !== null && b !== null && a >= b) throw new Error(`[@dou-ui/dashboard] bad order keys ${a} ${b}`);
|
|
24
|
+
return midpoint(a ?? "", b);
|
|
25
|
+
}
|
|
26
|
+
/** `n` keys in order between `a` and `b`, kept short by splitting the range in halves. */
|
|
27
|
+
function keysBetween(a, b, n) {
|
|
28
|
+
if (n <= 0) return [];
|
|
29
|
+
const middle = keyBetween(a, b);
|
|
30
|
+
const left = Math.floor((n - 1) / 2);
|
|
31
|
+
return [
|
|
32
|
+
...keysBetween(a, middle, left),
|
|
33
|
+
middle,
|
|
34
|
+
...keysBetween(middle, b, n - 1 - left)
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Which of `keys` (in their wanted order) can stay: the longest strictly
|
|
39
|
+
* increasing run. Missing keys (`null`) never stay. Returns the indexes.
|
|
40
|
+
*/
|
|
41
|
+
function keptIndexes(keys) {
|
|
42
|
+
const tails = [];
|
|
43
|
+
const previous = [];
|
|
44
|
+
keys.forEach((key, index) => {
|
|
45
|
+
if (key === null) return;
|
|
46
|
+
let low = 0;
|
|
47
|
+
let high = tails.length;
|
|
48
|
+
while (low < high) {
|
|
49
|
+
const mid = low + high >> 1;
|
|
50
|
+
if (keys[tails[mid]] < key) low = mid + 1;
|
|
51
|
+
else high = mid;
|
|
52
|
+
}
|
|
53
|
+
previous[index] = low > 0 ? tails[low - 1] : -1;
|
|
54
|
+
tails[low] = index;
|
|
55
|
+
});
|
|
56
|
+
const kept = /* @__PURE__ */ new Set();
|
|
57
|
+
let index = tails.length ? tails[tails.length - 1] : -1;
|
|
58
|
+
while (index >= 0) {
|
|
59
|
+
kept.add(index);
|
|
60
|
+
index = previous[index];
|
|
61
|
+
}
|
|
62
|
+
return kept;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Order keys for items in their wanted order, given each one's current key
|
|
66
|
+
* (`null` when it has none). The longest run already in order keeps its
|
|
67
|
+
* keys; the others get fresh keys between their kept neighbours.
|
|
68
|
+
*/
|
|
69
|
+
function orderKeys(keys) {
|
|
70
|
+
const kept = keptIndexes(keys);
|
|
71
|
+
const result = keys.slice();
|
|
72
|
+
let i = 0;
|
|
73
|
+
while (i < result.length) {
|
|
74
|
+
if (kept.has(i)) {
|
|
75
|
+
i++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
let j = i;
|
|
79
|
+
while (j < result.length && !kept.has(j)) j++;
|
|
80
|
+
const fresh = keysBetween(i > 0 ? result[i - 1] : null, j < result.length ? result[j] : null, j - i);
|
|
81
|
+
for (let k = i; k < j; k++) result[k] = fresh[k - i];
|
|
82
|
+
i = j;
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
export { keptIndexes, keyBetween, keysBetween, orderKeys };
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.js","names":[],"sources":["../../src/collaboration/order.ts"],"sourcesContent":["/**\n * Order keys: strings that sort like the widgets they belong to. A key can\n * always be made between two others, so inserting a widget only\n * writes that widget's key, never its neighbours'.\n */\n\n// ascending in code-unit order, so plain `<` compares keys\nconst DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\n\n/** A key between `a` and `b` (`b === null` means no upper bound). Keys never end in `0`. */\nfunction midpoint(a: string, b: string | null): string {\n if (b !== null) {\n let n = 0\n while ((a[n] ?? '0') === b[n]) n++\n if (n > 0) return b.slice(0, n) + midpoint(a.slice(n), b.slice(n))\n }\n const low = a ? DIGITS.indexOf(a[0]!) : 0\n const high = b !== null ? DIGITS.indexOf(b[0]!) : DIGITS.length\n if (high - low > 1) return DIGITS[Math.round((low + high) / 2)]!\n if (b !== null && b.length > 1) return b.slice(0, 1)\n return DIGITS[low]! + midpoint(a.slice(1), null)\n}\n\n/** A key after `a` and before `b`; either may be missing. Needs `a < b`. */\nexport function keyBetween(a: string | null, b: string | null): string {\n if (a !== null && b !== null && a >= b)\n throw new Error(`[@dou-ui/dashboard] bad order keys ${a} ${b}`)\n return midpoint(a ?? '', b)\n}\n\n/** `n` keys in order between `a` and `b`, kept short by splitting the range in halves. */\nexport function keysBetween(a: string | null, b: string | null, n: number): string[] {\n if (n <= 0) return []\n const middle = keyBetween(a, b)\n const left = Math.floor((n - 1) / 2)\n return [...keysBetween(a, middle, left), middle, ...keysBetween(middle, b, n - 1 - left)]\n}\n\n/**\n * Which of `keys` (in their wanted order) can stay: the longest strictly\n * increasing run. Missing keys (`null`) never stay. Returns the indexes.\n */\nexport function keptIndexes(keys: (string | null)[]): Set<number> {\n // patience sorting with back links\n const tails: number[] = []\n const previous: number[] = []\n keys.forEach((key, index) => {\n if (key === null) return\n let low = 0\n let high = tails.length\n while (low < high) {\n const mid = (low + high) >> 1\n if (keys[tails[mid]!]! < key) low = mid + 1\n else high = mid\n }\n previous[index] = low > 0 ? tails[low - 1]! : -1\n tails[low] = index\n })\n const kept = new Set<number>()\n let index = tails.length ? tails[tails.length - 1]! : -1\n while (index >= 0) {\n kept.add(index)\n index = previous[index]!\n }\n return kept\n}\n\n/**\n * Order keys for items in their wanted order, given each one's current key\n * (`null` when it has none). The longest run already in order keeps its\n * keys; the others get fresh keys between their kept neighbours.\n */\nexport function orderKeys(keys: (string | null)[]): string[] {\n const kept = keptIndexes(keys)\n const result = keys.slice()\n let i = 0\n while (i < result.length) {\n if (kept.has(i)) {\n i++\n continue\n }\n let j = i\n while (j < result.length && !kept.has(j)) j++\n const fresh = keysBetween(\n i > 0 ? result[i - 1]! : null,\n j < result.length ? result[j]! : null,\n j - i,\n )\n for (let k = i; k < j; k++) result[k] = fresh[k - i]!\n i = j\n }\n return result as string[]\n}\n"],"mappings":";;;;;;AAOA,IAAM,SAAS;;AAGf,SAAS,SAAS,GAAW,GAA0B;CACrD,IAAI,MAAM,MAAM;EACd,IAAI,IAAI;EACR,QAAQ,EAAE,MAAM,SAAS,EAAE,IAAI;EAC/B,IAAI,IAAI,GAAG,OAAO,EAAE,MAAM,GAAG,CAAC,IAAI,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;CACnE;CACA,MAAM,MAAM,IAAI,OAAO,QAAQ,EAAE,EAAG,IAAI;CACxC,MAAM,OAAO,MAAM,OAAO,OAAO,QAAQ,EAAE,EAAG,IAAI;CAClD,IAAI,OAAO,MAAM,GAAG,OAAO,OAAO,KAAK,OAAO,MAAM,QAAQ,CAAC;CAC7D,IAAI,MAAM,QAAQ,EAAE,SAAS,GAAG,OAAO,EAAE,MAAM,GAAG,CAAC;CACnD,OAAO,OAAO,OAAQ,SAAS,EAAE,MAAM,CAAC,GAAG,IAAI;AACjD;;AAGA,SAAgB,WAAW,GAAkB,GAA0B;CACrE,IAAI,MAAM,QAAQ,MAAM,QAAQ,KAAK,GACnC,MAAM,IAAI,MAAM,sCAAsC,EAAE,GAAG,GAAG;CAChE,OAAO,SAAS,KAAK,IAAI,CAAC;AAC5B;;AAGA,SAAgB,YAAY,GAAkB,GAAkB,GAAqB;CACnF,IAAI,KAAK,GAAG,OAAO,CAAC;CACpB,MAAM,SAAS,WAAW,GAAG,CAAC;CAC9B,MAAM,OAAO,KAAK,OAAO,IAAI,KAAK,CAAC;CACnC,OAAO;EAAC,GAAG,YAAY,GAAG,QAAQ,IAAI;EAAG;EAAQ,GAAG,YAAY,QAAQ,GAAG,IAAI,IAAI,IAAI;CAAC;AAC1F;;;;;AAMA,SAAgB,YAAY,MAAsC;CAEhE,MAAM,QAAkB,CAAC;CACzB,MAAM,WAAqB,CAAC;CAC5B,KAAK,SAAS,KAAK,UAAU;EAC3B,IAAI,QAAQ,MAAM;EAClB,IAAI,MAAM;EACV,IAAI,OAAO,MAAM;EACjB,OAAO,MAAM,MAAM;GACjB,MAAM,MAAO,MAAM,QAAS;GAC5B,IAAI,KAAK,MAAM,QAAU,KAAK,MAAM,MAAM;QACrC,OAAO;EACd;EACA,SAAS,SAAS,MAAM,IAAI,MAAM,MAAM,KAAM;EAC9C,MAAM,OAAO;CACf,CAAC;CACD,MAAM,uBAAO,IAAI,IAAY;CAC7B,IAAI,QAAQ,MAAM,SAAS,MAAM,MAAM,SAAS,KAAM;CACtD,OAAO,SAAS,GAAG;EACjB,KAAK,IAAI,KAAK;EACd,QAAQ,SAAS;CACnB;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,UAAU,MAAmC;CAC3D,MAAM,OAAO,YAAY,IAAI;CAC7B,MAAM,SAAS,KAAK,MAAM;CAC1B,IAAI,IAAI;CACR,OAAO,IAAI,OAAO,QAAQ;EACxB,IAAI,KAAK,IAAI,CAAC,GAAG;GACf;GACA;EACF;EACA,IAAI,IAAI;EACR,OAAO,IAAI,OAAO,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG;EAC1C,MAAM,QAAQ,YACZ,IAAI,IAAI,OAAO,IAAI,KAAM,MACzB,IAAI,OAAO,SAAS,OAAO,KAAM,MACjC,IAAI,CACN;EACA,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,OAAO,KAAK,MAAM,IAAI;EAClD,IAAI;CACN;CACA,OAAO;AACT"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// src/core/document.ts
|
|
2
|
+
/** A dashboard with no widgets, the `v-model` when none is bound. */
|
|
3
|
+
var emptyDocument = () => ({
|
|
4
|
+
version: 1,
|
|
5
|
+
widgets: []
|
|
6
|
+
});
|
|
7
|
+
var isCount = (value, min) => typeof value === "number" && Number.isInteger(value) && value >= min;
|
|
8
|
+
/** Whether `value` is a place on a grid: whole numbers, the size at least 1. */
|
|
9
|
+
function isDashboardPlace(value) {
|
|
10
|
+
if (!value || typeof value !== "object") return false;
|
|
11
|
+
const place = value;
|
|
12
|
+
return isCount(place.x, 0) && isCount(place.y, 0) && isCount(place.w, 1) && isCount(place.h, 1);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* What is wrong with the shape itself: the document is not an object, or a
|
|
16
|
+
* list in it is not an array. Nothing else can be checked until it is fixed.
|
|
17
|
+
*/
|
|
18
|
+
function shapeProblems(value, lists) {
|
|
19
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [{
|
|
20
|
+
code: "shape",
|
|
21
|
+
path: "",
|
|
22
|
+
text: "",
|
|
23
|
+
message: "The document is not an object"
|
|
24
|
+
}];
|
|
25
|
+
return lists.filter((key) => !Array.isArray(value[key])).map((key) => ({
|
|
26
|
+
code: "shape",
|
|
27
|
+
path: key,
|
|
28
|
+
text: "",
|
|
29
|
+
message: `"${key}" is not a list`
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The problems of a dashboard document, before it is saved or shown: an
|
|
34
|
+
* unknown version, widgets without an id or sharing one, types nobody
|
|
35
|
+
* registered, and places that are not whole cells.
|
|
36
|
+
*/
|
|
37
|
+
function checkDocument(document, options = {}) {
|
|
38
|
+
const shape = shapeProblems(document, ["widgets"]);
|
|
39
|
+
if (shape.length) return shape;
|
|
40
|
+
const problems = [];
|
|
41
|
+
if (document.version !== 1) problems.push({
|
|
42
|
+
code: "version",
|
|
43
|
+
path: "version",
|
|
44
|
+
text: String(document.version),
|
|
45
|
+
message: `Version ${String(document.version)} is not known`
|
|
46
|
+
});
|
|
47
|
+
const types = options.widgets && new Set(options.widgets.map((type) => type.type));
|
|
48
|
+
const ids = /* @__PURE__ */ new Set();
|
|
49
|
+
document.widgets.forEach((widget, i) => {
|
|
50
|
+
const path = `widgets[${i}]`;
|
|
51
|
+
if (!widget.id) problems.push({
|
|
52
|
+
code: "missing-id",
|
|
53
|
+
path,
|
|
54
|
+
text: "",
|
|
55
|
+
message: `${path} has no id`
|
|
56
|
+
});
|
|
57
|
+
else if (ids.has(widget.id)) problems.push({
|
|
58
|
+
code: "duplicate-id",
|
|
59
|
+
widgetId: widget.id,
|
|
60
|
+
path,
|
|
61
|
+
text: widget.id,
|
|
62
|
+
message: `The id "${widget.id}" is used more than once`
|
|
63
|
+
});
|
|
64
|
+
ids.add(widget.id);
|
|
65
|
+
if (types && !types.has(widget.type)) problems.push({
|
|
66
|
+
code: "unknown-type",
|
|
67
|
+
widgetId: widget.id,
|
|
68
|
+
path,
|
|
69
|
+
text: widget.type,
|
|
70
|
+
message: `No widget type "${widget.type}" is registered`
|
|
71
|
+
});
|
|
72
|
+
for (const [name, place] of Object.entries(widget.place ?? {})) if (place !== void 0 && !isDashboardPlace(place)) problems.push({
|
|
73
|
+
code: "bad-place",
|
|
74
|
+
widgetId: widget.id,
|
|
75
|
+
path: `${path}.place.${name}`,
|
|
76
|
+
text: name,
|
|
77
|
+
message: `The place of "${widget.id}" on "${name}" is not whole cells`
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
return problems;
|
|
81
|
+
}
|
|
82
|
+
/** The saved data as an object of version 1, or an error saying why it can't be. */
|
|
83
|
+
function sourceOf(input) {
|
|
84
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error(`[@dou-ui/dashboard] a dashboard document must be an object, not ${Array.isArray(input) ? "an array" : JSON.stringify(input) ?? String(input)}`);
|
|
85
|
+
const source = input;
|
|
86
|
+
const version = source.version ?? 1;
|
|
87
|
+
if (version !== 1) throw new Error(`[@dou-ui/dashboard] unknown document version ${JSON.stringify(version) ?? String(version)} (expected 1); data saved by a newer @dou-ui/dashboard needs that version to open`);
|
|
88
|
+
return source;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Brings a saved document up to the current version, and fills what an
|
|
92
|
+
* older or hand-written one may lack (`version`, `place`). Widgets that are
|
|
93
|
+
* not objects and places that are not whole cells are dropped; anything
|
|
94
|
+
* else is kept as it is. Throws for data that is not an object
|
|
95
|
+
* (an array, `null`, a string) and for a version it does not know.
|
|
96
|
+
*/
|
|
97
|
+
function migrateDocument(input) {
|
|
98
|
+
const source = sourceOf(input);
|
|
99
|
+
const list = Array.isArray(source.widgets) ? source.widgets : [];
|
|
100
|
+
const widgets = [];
|
|
101
|
+
for (const value of list) {
|
|
102
|
+
if (!value || typeof value !== "object") continue;
|
|
103
|
+
const widget = value;
|
|
104
|
+
const place = {};
|
|
105
|
+
if (widget.place && typeof widget.place === "object") {
|
|
106
|
+
for (const [name, entry] of Object.entries(widget.place)) if (isDashboardPlace(entry)) place[name] = {
|
|
107
|
+
x: entry.x,
|
|
108
|
+
y: entry.y,
|
|
109
|
+
w: entry.w,
|
|
110
|
+
h: entry.h
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
widgets.push({
|
|
114
|
+
...widget,
|
|
115
|
+
id: String(widget.id ?? ""),
|
|
116
|
+
type: String(widget.type ?? ""),
|
|
117
|
+
place
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
...source,
|
|
122
|
+
version: 1,
|
|
123
|
+
widgets
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export { checkDocument, emptyDocument, isDashboardPlace, migrateDocument };
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","names":[],"sources":["../../src/core/document.ts"],"sourcesContent":["import type { DashboardWidgetType } from '../widget/define'\nimport type { DashboardDocument, DashboardPlace, DashboardWidget } from './types'\n\n/** A dashboard with no widgets, the `v-model` when none is bound. */\nexport const emptyDocument = (): DashboardDocument => ({ version: 1, widgets: [] })\n\nexport interface DocumentProblem {\n code: 'version' | 'shape' | 'duplicate-id' | 'missing-id' | 'unknown-type' | 'bad-place'\n /** The widget at fault; none for the document's own problems. */\n widgetId?: string\n /** Where the problem is, e.g. `widgets[2]` or `widgets[2].place.lg`; `version` for the version. */\n path: string\n /** The id, type or breakpoint at fault, for the editor's message. */\n text: string\n /** An English description, for logs. */\n message: string\n}\n\nexport interface CheckDocumentOptions {\n /** The registered widget types; without them, types are not checked. */\n widgets?: readonly DashboardWidgetType<any>[]\n}\n\nconst isCount = (value: unknown, min: number) =>\n typeof value === 'number' && Number.isInteger(value) && value >= min\n\n/** Whether `value` is a place on a grid: whole numbers, the size at least 1. */\nexport function isDashboardPlace(value: unknown): value is DashboardPlace {\n if (!value || typeof value !== 'object') return false\n const place = value as Record<string, unknown>\n return isCount(place.x, 0) && isCount(place.y, 0) && isCount(place.w, 1) && isCount(place.h, 1)\n}\n\n/**\n * What is wrong with the shape itself: the document is not an object, or a\n * list in it is not an array. Nothing else can be checked until it is fixed.\n */\nfunction shapeProblems(value: unknown, lists: string[]): DocumentProblem[] {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return [{ code: 'shape', path: '', text: '', message: 'The document is not an object' }]\n return lists\n .filter((key) => !Array.isArray((value as Record<string, unknown>)[key]))\n .map((key) => ({ code: 'shape', path: key, text: '', message: `\"${key}\" is not a list` }))\n}\n\n/**\n * The problems of a dashboard document, before it is saved or shown: an\n * unknown version, widgets without an id or sharing one, types nobody\n * registered, and places that are not whole cells.\n */\nexport function checkDocument(\n document: DashboardDocument,\n options: CheckDocumentOptions = {},\n): DocumentProblem[] {\n const shape = shapeProblems(document, ['widgets'])\n if (shape.length) return shape\n const problems: DocumentProblem[] = []\n if (document.version !== 1)\n problems.push({\n code: 'version',\n path: 'version',\n text: String(document.version),\n message: `Version ${String(document.version)} is not known`,\n })\n const types = options.widgets && new Set(options.widgets.map((type) => type.type))\n const ids = new Set<string>()\n document.widgets.forEach((widget, i) => {\n const path = `widgets[${i}]`\n if (!widget.id) {\n problems.push({ code: 'missing-id', path, text: '', message: `${path} has no id` })\n } else if (ids.has(widget.id)) {\n problems.push({\n code: 'duplicate-id',\n widgetId: widget.id,\n path,\n text: widget.id,\n message: `The id \"${widget.id}\" is used more than once`,\n })\n }\n ids.add(widget.id)\n if (types && !types.has(widget.type))\n problems.push({\n code: 'unknown-type',\n widgetId: widget.id,\n path,\n text: widget.type,\n message: `No widget type \"${widget.type}\" is registered`,\n })\n for (const [name, place] of Object.entries(widget.place ?? {}))\n if (place !== undefined && !isDashboardPlace(place))\n problems.push({\n code: 'bad-place',\n widgetId: widget.id,\n path: `${path}.place.${name}`,\n text: name,\n message: `The place of \"${widget.id}\" on \"${name}\" is not whole cells`,\n })\n })\n return problems\n}\n\n/** The saved data as an object of version 1, or an error saying why it can't be. */\nfunction sourceOf(input: unknown): Record<string, unknown> {\n if (!input || typeof input !== 'object' || Array.isArray(input))\n throw new Error(\n `[@dou-ui/dashboard] a dashboard document must be an object, not ${Array.isArray(input) ? 'an array' : (JSON.stringify(input) ?? String(input))}`,\n )\n const source = input as Record<string, unknown>\n const version = source.version ?? 1\n if (version !== 1)\n throw new Error(\n `[@dou-ui/dashboard] unknown document version ${JSON.stringify(version) ?? String(version)} (expected 1); data saved by a newer @dou-ui/dashboard needs that version to open`,\n )\n return source\n}\n\n/**\n * Brings a saved document up to the current version, and fills what an\n * older or hand-written one may lack (`version`, `place`). Widgets that are\n * not objects and places that are not whole cells are dropped; anything\n * else is kept as it is. Throws for data that is not an object\n * (an array, `null`, a string) and for a version it does not know.\n */\nexport function migrateDocument(input: unknown): DashboardDocument {\n const source = sourceOf(input)\n const list = Array.isArray(source.widgets) ? source.widgets : []\n const widgets: DashboardWidget[] = []\n for (const value of list) {\n if (!value || typeof value !== 'object') continue\n const widget = value as Record<string, unknown>\n const place: DashboardWidget['place'] = {}\n if (widget.place && typeof widget.place === 'object')\n for (const [name, entry] of Object.entries(widget.place))\n if (isDashboardPlace(entry))\n place[name] = { x: entry.x, y: entry.y, w: entry.w, h: entry.h }\n widgets.push({\n ...widget,\n id: String(widget.id ?? ''),\n type: String(widget.type ?? ''),\n place,\n } as DashboardWidget)\n }\n return { ...source, version: 1, widgets } as DashboardDocument\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { applySteps } from "./transaction.js";
|
|
2
|
+
// src/core/history.ts
|
|
3
|
+
/** Transactions of one group merge when they come within this many ms. */
|
|
4
|
+
var GROUP_WINDOW = 1e3;
|
|
5
|
+
/** Undo and redo of a dashboard: the steps of each transaction and the steps that undo them. */
|
|
6
|
+
var DashboardHistory = class {
|
|
7
|
+
depth;
|
|
8
|
+
done = [];
|
|
9
|
+
undone = [];
|
|
10
|
+
constructor(depth = 200) {
|
|
11
|
+
this.depth = depth;
|
|
12
|
+
}
|
|
13
|
+
get canUndo() {
|
|
14
|
+
return this.done.length > 0;
|
|
15
|
+
}
|
|
16
|
+
get canRedo() {
|
|
17
|
+
return this.undone.length > 0;
|
|
18
|
+
}
|
|
19
|
+
record(tr, now = Date.now()) {
|
|
20
|
+
if (!tr.changed || tr.meta.history === false) return;
|
|
21
|
+
this.undone = [];
|
|
22
|
+
const last = this.done[this.done.length - 1];
|
|
23
|
+
const group = tr.meta.group;
|
|
24
|
+
if (last && group && last.group === group && now - last.time < GROUP_WINDOW) {
|
|
25
|
+
last.steps = [...last.steps, ...tr.steps];
|
|
26
|
+
last.inverse = [...tr.inverse, ...last.inverse];
|
|
27
|
+
last.time = now;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.done.push({
|
|
31
|
+
steps: tr.steps.slice(),
|
|
32
|
+
inverse: tr.inverse.slice(),
|
|
33
|
+
group,
|
|
34
|
+
time: now
|
|
35
|
+
});
|
|
36
|
+
if (this.done.length > this.depth) this.done.shift();
|
|
37
|
+
}
|
|
38
|
+
undo(document) {
|
|
39
|
+
const entry = this.done.pop();
|
|
40
|
+
if (!entry) return null;
|
|
41
|
+
this.undone.push(entry);
|
|
42
|
+
return applySteps(document, entry.inverse, {
|
|
43
|
+
history: false,
|
|
44
|
+
origin: "undo"
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
redo(document) {
|
|
48
|
+
const entry = this.undone.pop();
|
|
49
|
+
if (!entry) return null;
|
|
50
|
+
this.done.push({
|
|
51
|
+
...entry,
|
|
52
|
+
group: void 0
|
|
53
|
+
});
|
|
54
|
+
return applySteps(document, entry.steps, {
|
|
55
|
+
history: false,
|
|
56
|
+
origin: "redo"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
clear() {
|
|
60
|
+
this.done = [];
|
|
61
|
+
this.undone = [];
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
export { DashboardHistory };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","names":[],"sources":["../../src/core/history.ts"],"sourcesContent":["import { applySteps } from './transaction'\nimport type { DashboardStep, DashboardTransaction } from './transaction'\nimport type { DashboardDocument } from './types'\n\ninterface Entry {\n steps: DashboardStep[]\n inverse: DashboardStep[]\n group?: string\n time: number\n}\n\n/** Transactions of one group merge when they come within this many ms. */\nconst GROUP_WINDOW = 1000\n\n/** Undo and redo of a dashboard: the steps of each transaction and the steps that undo them. */\nexport class DashboardHistory {\n private done: Entry[] = []\n private undone: Entry[] = []\n\n constructor(private depth = 200) {}\n\n get canUndo() {\n return this.done.length > 0\n }\n\n get canRedo() {\n return this.undone.length > 0\n }\n\n record(tr: DashboardTransaction, now = Date.now()) {\n if (!tr.changed || tr.meta.history === false) return\n this.undone = []\n const last = this.done[this.done.length - 1]\n const group = tr.meta.group\n if (last && group && last.group === group && now - last.time < GROUP_WINDOW) {\n last.steps = [...last.steps, ...tr.steps]\n last.inverse = [...tr.inverse, ...last.inverse]\n last.time = now\n return\n }\n this.done.push({ steps: tr.steps.slice(), inverse: tr.inverse.slice(), group, time: now })\n if (this.done.length > this.depth) this.done.shift()\n }\n\n undo(document: DashboardDocument) {\n const entry = this.done.pop()\n if (!entry) return null\n this.undone.push(entry)\n return applySteps(document, entry.inverse, { history: false, origin: 'undo' })\n }\n\n redo(document: DashboardDocument) {\n const entry = this.undone.pop()\n if (!entry) return null\n this.done.push({ ...entry, group: undefined })\n return applySteps(document, entry.steps, { history: false, origin: 'redo' })\n }\n\n clear() {\n this.done = []\n this.undone = []\n }\n}\n"],"mappings":";;;AAYA,IAAM,eAAe;;AAGrB,IAAa,mBAAb,MAA8B;CAIR;CAHpB,OAAwB,CAAC;CACzB,SAA0B,CAAC;CAE3B,YAAY,QAAgB,KAAK;EAAb,KAAA,QAAA;CAAc;CAElC,IAAI,UAAU;EACZ,OAAO,KAAK,KAAK,SAAS;CAC5B;CAEA,IAAI,UAAU;EACZ,OAAO,KAAK,OAAO,SAAS;CAC9B;CAEA,OAAO,IAA0B,MAAM,KAAK,IAAI,GAAG;EACjD,IAAI,CAAC,GAAG,WAAW,GAAG,KAAK,YAAY,OAAO;EAC9C,KAAK,SAAS,CAAC;EACf,MAAM,OAAO,KAAK,KAAK,KAAK,KAAK,SAAS;EAC1C,MAAM,QAAQ,GAAG,KAAK;EACtB,IAAI,QAAQ,SAAS,KAAK,UAAU,SAAS,MAAM,KAAK,OAAO,cAAc;GAC3E,KAAK,QAAQ,CAAC,GAAG,KAAK,OAAO,GAAG,GAAG,KAAK;GACxC,KAAK,UAAU,CAAC,GAAG,GAAG,SAAS,GAAG,KAAK,OAAO;GAC9C,KAAK,OAAO;GACZ;EACF;EACA,KAAK,KAAK,KAAK;GAAE,OAAO,GAAG,MAAM,MAAM;GAAG,SAAS,GAAG,QAAQ,MAAM;GAAG;GAAO,MAAM;EAAI,CAAC;EACzF,IAAI,KAAK,KAAK,SAAS,KAAK,OAAO,KAAK,KAAK,MAAM;CACrD;CAEA,KAAK,UAA6B;EAChC,MAAM,QAAQ,KAAK,KAAK,IAAI;EAC5B,IAAI,CAAC,OAAO,OAAO;EACnB,KAAK,OAAO,KAAK,KAAK;EACtB,OAAO,WAAW,UAAU,MAAM,SAAS;GAAE,SAAS;GAAO,QAAQ;EAAO,CAAC;CAC/E;CAEA,KAAK,UAA6B;EAChC,MAAM,QAAQ,KAAK,OAAO,IAAI;EAC9B,IAAI,CAAC,OAAO,OAAO;EACnB,KAAK,KAAK,KAAK;GAAE,GAAG;GAAO,OAAO,KAAA;EAAU,CAAC;EAC7C,OAAO,WAAW,UAAU,MAAM,OAAO;GAAE,SAAS;GAAO,QAAQ;EAAO,CAAC;CAC7E;CAEA,QAAQ;EACN,KAAK,OAAO,CAAC;EACb,KAAK,SAAS,CAAC;CACjB;AACF"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// src/core/transaction.ts
|
|
2
|
+
var PROTECTED = /* @__PURE__ */ new Set(["id", "type"]);
|
|
3
|
+
/** Deep equality of JSON values; key order doesn't matter. */
|
|
4
|
+
function sameValue(a, b) {
|
|
5
|
+
if (a === b) return true;
|
|
6
|
+
if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
|
|
7
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
8
|
+
const keys = Object.keys(a);
|
|
9
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
10
|
+
return keys.every((key) => sameValue(a[key], b[key]));
|
|
11
|
+
}
|
|
12
|
+
/** Applies one step: the new document and the step that undoes it, or `null` when it changes nothing. */
|
|
13
|
+
function applyStep(document, step) {
|
|
14
|
+
const widgets = document.widgets;
|
|
15
|
+
const indexOf = (id) => widgets.findIndex((widget) => widget.id === id);
|
|
16
|
+
if (step.type === "add") {
|
|
17
|
+
if (indexOf(step.widget.id) >= 0) return null;
|
|
18
|
+
const index = Math.max(0, Math.min(widgets.length, step.index));
|
|
19
|
+
const next = [
|
|
20
|
+
...widgets.slice(0, index),
|
|
21
|
+
step.widget,
|
|
22
|
+
...widgets.slice(index)
|
|
23
|
+
];
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
...document,
|
|
27
|
+
widgets: next
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
...step,
|
|
31
|
+
index
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: "remove",
|
|
35
|
+
id: step.widget.id
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
const index = indexOf(step.id);
|
|
40
|
+
if (index < 0) return null;
|
|
41
|
+
const widget = widgets[index];
|
|
42
|
+
if (step.type === "remove") {
|
|
43
|
+
const next = [...widgets.slice(0, index), ...widgets.slice(index + 1)];
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
...document,
|
|
47
|
+
widgets: next
|
|
48
|
+
},
|
|
49
|
+
step,
|
|
50
|
+
{
|
|
51
|
+
type: "add",
|
|
52
|
+
widget,
|
|
53
|
+
index
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
const old = widget;
|
|
58
|
+
const changed = { ...old };
|
|
59
|
+
const patch = {};
|
|
60
|
+
const undo = {};
|
|
61
|
+
for (const [key, value] of Object.entries(step.patch)) {
|
|
62
|
+
if (PROTECTED.has(key)) throw new Error(`[@dou-ui/dashboard] a widget's "${key}" cannot be changed`);
|
|
63
|
+
if (sameValue(old[key], value)) continue;
|
|
64
|
+
patch[key] = value;
|
|
65
|
+
undo[key] = old[key];
|
|
66
|
+
if (value === void 0) delete changed[key];
|
|
67
|
+
else changed[key] = value;
|
|
68
|
+
}
|
|
69
|
+
if (!Object.keys(patch).length) return null;
|
|
70
|
+
const next = widgets.slice();
|
|
71
|
+
next[index] = changed;
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
...document,
|
|
75
|
+
widgets: next
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "update",
|
|
79
|
+
id: step.id,
|
|
80
|
+
patch
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: "update",
|
|
84
|
+
id: step.id,
|
|
85
|
+
patch: undo
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Changes to a dashboard, made step by step and then handed to the
|
|
91
|
+
* dashboard's `dispatch` as one change (one undo step). A step that changes
|
|
92
|
+
* nothing (a widget that isn't there, a patch to the same values, an id
|
|
93
|
+
* already in use) is left out.
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* const tr = dashboard.value.transaction()
|
|
97
|
+
* tr.update('orders', { title: '本周订单' }).remove('memo')
|
|
98
|
+
* dashboard.value.dispatch(tr)
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
var DashboardTransaction = class {
|
|
102
|
+
before;
|
|
103
|
+
/** The document after the steps so far. */
|
|
104
|
+
doc;
|
|
105
|
+
steps = [];
|
|
106
|
+
/** The steps that undo the ones so far, last one first. */
|
|
107
|
+
inverse = [];
|
|
108
|
+
meta = {};
|
|
109
|
+
constructor(before) {
|
|
110
|
+
this.before = before;
|
|
111
|
+
this.doc = before;
|
|
112
|
+
}
|
|
113
|
+
/** Whether any step was made. */
|
|
114
|
+
get changed() {
|
|
115
|
+
return this.steps.length > 0;
|
|
116
|
+
}
|
|
117
|
+
/** Apply a step as it is. */
|
|
118
|
+
step(step) {
|
|
119
|
+
const result = applyStep(this.doc, step);
|
|
120
|
+
if (!result) return this;
|
|
121
|
+
this.doc = result[0];
|
|
122
|
+
this.steps.push(result[1]);
|
|
123
|
+
this.inverse.unshift(result[2]);
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
/** Put a widget into the document at `index` (the end by default). */
|
|
127
|
+
add(widget, index = Infinity) {
|
|
128
|
+
return this.step({
|
|
129
|
+
type: "add",
|
|
130
|
+
widget,
|
|
131
|
+
index
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/** Take a widget out. */
|
|
135
|
+
remove(id) {
|
|
136
|
+
return this.step({
|
|
137
|
+
type: "remove",
|
|
138
|
+
id
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/** Set keys of a widget, e.g. `{ title }`, `{ props }` or `{ place }`; `undefined` removes a key. */
|
|
142
|
+
update(id, patch) {
|
|
143
|
+
return this.step({
|
|
144
|
+
type: "update",
|
|
145
|
+
id,
|
|
146
|
+
patch
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
setMeta(key, value) {
|
|
150
|
+
this.meta[key] = value;
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Applies steps to a document in one go, giving the transaction: its `doc`
|
|
156
|
+
* (unchanged widgets are kept as they are) and the `inverse` steps that
|
|
157
|
+
* undo it. For the server, tests, or saved steps.
|
|
158
|
+
*/
|
|
159
|
+
function applySteps(document, steps, meta = {}) {
|
|
160
|
+
const tr = new DashboardTransaction(document);
|
|
161
|
+
Object.assign(tr.meta, meta);
|
|
162
|
+
for (const step of steps) tr.step(step);
|
|
163
|
+
return tr;
|
|
164
|
+
}
|
|
165
|
+
/** A deep copy of a JSON value (also of a reactive one). */
|
|
166
|
+
var cloneJson = (value) => JSON.parse(JSON.stringify(value));
|
|
167
|
+
/** The indexes of the longest strictly increasing run of `values`. */
|
|
168
|
+
function longestRun(values) {
|
|
169
|
+
const tails = [];
|
|
170
|
+
const previous = [];
|
|
171
|
+
values.forEach((value, index) => {
|
|
172
|
+
let low = 0;
|
|
173
|
+
let high = tails.length;
|
|
174
|
+
while (low < high) {
|
|
175
|
+
const mid = low + high >> 1;
|
|
176
|
+
if (values[tails[mid]] < value) low = mid + 1;
|
|
177
|
+
else high = mid;
|
|
178
|
+
}
|
|
179
|
+
previous[index] = low > 0 ? tails[low - 1] : -1;
|
|
180
|
+
tails[low] = index;
|
|
181
|
+
});
|
|
182
|
+
const kept = /* @__PURE__ */ new Set();
|
|
183
|
+
let index = tails.length ? tails[tails.length - 1] : -1;
|
|
184
|
+
while (index >= 0) {
|
|
185
|
+
kept.add(index);
|
|
186
|
+
index = previous[index];
|
|
187
|
+
}
|
|
188
|
+
return kept;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Adds the steps that turn the transaction's document into `next`: widgets
|
|
192
|
+
* that are gone are removed, new ones added, changed keys updated, and the
|
|
193
|
+
* fewest widgets taken out and put back to match the order. Unchanged
|
|
194
|
+
* widgets keep their objects.
|
|
195
|
+
*/
|
|
196
|
+
function replaceWidgets(tr, next) {
|
|
197
|
+
const wanted = new Map(next.widgets.map((widget, index) => [widget.id, index]));
|
|
198
|
+
for (const widget of tr.doc.widgets) {
|
|
199
|
+
const index = wanted.get(widget.id);
|
|
200
|
+
if (index === void 0 || next.widgets[index].type !== widget.type) tr.remove(widget.id);
|
|
201
|
+
}
|
|
202
|
+
const current = tr.doc.widgets;
|
|
203
|
+
const kept = longestRun(current.map((widget) => wanted.get(widget.id)));
|
|
204
|
+
for (const [index, widget] of current.entries()) if (!kept.has(index)) tr.remove(widget.id);
|
|
205
|
+
next.widgets.forEach((widget, index) => {
|
|
206
|
+
const there = tr.doc.widgets[index];
|
|
207
|
+
if (there?.id !== widget.id) return void tr.add(widget, index);
|
|
208
|
+
const patch = {};
|
|
209
|
+
const old = there;
|
|
210
|
+
const item = widget;
|
|
211
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(old), ...Object.keys(item)])) if (!PROTECTED.has(key)) patch[key] = item[key];
|
|
212
|
+
tr.update(widget.id, patch);
|
|
213
|
+
});
|
|
214
|
+
return tr;
|
|
215
|
+
}
|
|
216
|
+
export { DashboardTransaction, applySteps, cloneJson, replaceWidgets, sameValue };
|
|
217
|
+
|
|
218
|
+
//# sourceMappingURL=transaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction.js","names":[],"sources":["../../src/core/transaction.ts"],"sourcesContent":["import type { DashboardDocument, DashboardWidget } from './types'\n\n/**\n * Keys of a widget to set; `undefined` removes the key. `id` and `type`\n * cannot be set: remove the widget and add another instead.\n */\nexport type DashboardWidgetPatch = { [key: string]: unknown } & {\n id?: never\n type?: never\n}\n\n/** One change to a dashboard. `index` is where in `widgets` an added widget goes. */\nexport type DashboardStep =\n | { type: 'add'; widget: DashboardWidget; index: number }\n | { type: 'remove'; id: string }\n | { type: 'update'; id: string; patch: DashboardWidgetPatch }\n\nexport interface DashboardTransactionMeta {\n /** `false` keeps the change out of undo, e.g. a change from another person. */\n history?: boolean\n /** Changes of the same group within a second become one undo step, e.g. typing in a setting. */\n group?: string\n /** Where the change came from: `layout`, `menu`, `keyboard`, `settings`, `api`… */\n origin?: string\n [key: string]: unknown\n}\n\nconst PROTECTED = new Set(['id', 'type'])\n\n/** Deep equality of JSON values; key order doesn't matter. */\nexport function sameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false\n if (Array.isArray(a) !== Array.isArray(b)) return false\n const keys = Object.keys(a)\n if (keys.length !== Object.keys(b).length) return false\n return keys.every((key) =>\n sameValue((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n )\n}\n\n/** Applies one step: the new document and the step that undoes it, or `null` when it changes nothing. */\nfunction applyStep(\n document: DashboardDocument,\n step: DashboardStep,\n): [DashboardDocument, DashboardStep, DashboardStep] | null {\n const widgets = document.widgets\n const indexOf = (id: string) => widgets.findIndex((widget) => widget.id === id)\n if (step.type === 'add') {\n if (indexOf(step.widget.id) >= 0) return null\n const index = Math.max(0, Math.min(widgets.length, step.index))\n const next = [...widgets.slice(0, index), step.widget, ...widgets.slice(index)]\n return [\n { ...document, widgets: next },\n { ...step, index },\n { type: 'remove', id: step.widget.id },\n ]\n }\n const index = indexOf(step.id)\n if (index < 0) return null\n const widget = widgets[index]!\n if (step.type === 'remove') {\n const next = [...widgets.slice(0, index), ...widgets.slice(index + 1)]\n return [{ ...document, widgets: next }, step, { type: 'add', widget, index }]\n }\n const old = widget as unknown as Record<string, unknown>\n const changed: Record<string, unknown> = { ...old }\n const patch: Record<string, unknown> = {}\n const undo: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(step.patch)) {\n if (PROTECTED.has(key))\n throw new Error(`[@dou-ui/dashboard] a widget's \"${key}\" cannot be changed`)\n if (sameValue(old[key], value)) continue\n patch[key] = value\n undo[key] = old[key]\n if (value === undefined) delete changed[key]\n else changed[key] = value\n }\n if (!Object.keys(patch).length) return null\n const next = widgets.slice()\n next[index] = changed as unknown as DashboardWidget\n return [\n { ...document, widgets: next },\n { type: 'update', id: step.id, patch: patch as DashboardWidgetPatch },\n { type: 'update', id: step.id, patch: undo as DashboardWidgetPatch },\n ]\n}\n\n/**\n * Changes to a dashboard, made step by step and then handed to the\n * dashboard's `dispatch` as one change (one undo step). A step that changes\n * nothing (a widget that isn't there, a patch to the same values, an id\n * already in use) is left out.\n *\n * ```ts\n * const tr = dashboard.value.transaction()\n * tr.update('orders', { title: '本周订单' }).remove('memo')\n * dashboard.value.dispatch(tr)\n * ```\n */\nexport class DashboardTransaction {\n /** The document after the steps so far. */\n doc: DashboardDocument\n readonly steps: DashboardStep[] = []\n /** The steps that undo the ones so far, last one first. */\n readonly inverse: DashboardStep[] = []\n readonly meta: DashboardTransactionMeta = {}\n\n constructor(\n /** The document the transaction starts from. */\n readonly before: DashboardDocument,\n ) {\n this.doc = before\n }\n\n /** Whether any step was made. */\n get changed() {\n return this.steps.length > 0\n }\n\n /** Apply a step as it is. */\n step(step: DashboardStep) {\n const result = applyStep(this.doc, step)\n if (!result) return this\n this.doc = result[0]\n this.steps.push(result[1])\n this.inverse.unshift(result[2])\n return this\n }\n\n /** Put a widget into the document at `index` (the end by default). */\n add(widget: DashboardWidget, index = Infinity) {\n return this.step({ type: 'add', widget, index })\n }\n\n /** Take a widget out. */\n remove(id: string) {\n return this.step({ type: 'remove', id })\n }\n\n /** Set keys of a widget, e.g. `{ title }`, `{ props }` or `{ place }`; `undefined` removes a key. */\n update(id: string, patch: DashboardWidgetPatch) {\n return this.step({ type: 'update', id, patch })\n }\n\n setMeta(key: string, value: unknown) {\n this.meta[key] = value\n return this\n }\n}\n\n/**\n * Applies steps to a document in one go, giving the transaction: its `doc`\n * (unchanged widgets are kept as they are) and the `inverse` steps that\n * undo it. For the server, tests, or saved steps.\n */\nexport function applySteps(\n document: DashboardDocument,\n steps: readonly DashboardStep[],\n meta: DashboardTransactionMeta = {},\n): DashboardTransaction {\n const tr = new DashboardTransaction(document)\n Object.assign(tr.meta, meta)\n for (const step of steps) tr.step(step)\n return tr\n}\n\n/** A deep copy of a JSON value (also of a reactive one). */\nexport const cloneJson = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T\n\n/** The indexes of the longest strictly increasing run of `values`. */\nfunction longestRun(values: readonly number[]): Set<number> {\n const tails: number[] = []\n const previous: number[] = []\n values.forEach((value, index) => {\n let low = 0\n let high = tails.length\n while (low < high) {\n const mid = (low + high) >> 1\n if (values[tails[mid]!]! < value) low = mid + 1\n else high = mid\n }\n previous[index] = low > 0 ? tails[low - 1]! : -1\n tails[low] = index\n })\n const kept = new Set<number>()\n let index = tails.length ? tails[tails.length - 1]! : -1\n while (index >= 0) {\n kept.add(index)\n index = previous[index]!\n }\n return kept\n}\n\n/**\n * Adds the steps that turn the transaction's document into `next`: widgets\n * that are gone are removed, new ones added, changed keys updated, and the\n * fewest widgets taken out and put back to match the order. Unchanged\n * widgets keep their objects.\n */\nexport function replaceWidgets(tr: DashboardTransaction, next: DashboardDocument) {\n const wanted = new Map(next.widgets.map((widget, index) => [widget.id, index]))\n for (const widget of tr.doc.widgets) {\n const index = wanted.get(widget.id)\n // a widget whose type changed is a new widget\n if (index === undefined || next.widgets[index]!.type !== widget.type) tr.remove(widget.id)\n }\n const current = tr.doc.widgets\n const kept = longestRun(current.map((widget) => wanted.get(widget.id)!))\n for (const [index, widget] of current.entries()) if (!kept.has(index)) tr.remove(widget.id)\n next.widgets.forEach((widget, index) => {\n const there = tr.doc.widgets[index]\n if (there?.id !== widget.id) return void tr.add(widget, index)\n const patch: Record<string, unknown> = {}\n const old = there as unknown as Record<string, unknown>\n const item = widget as unknown as Record<string, unknown>\n for (const key of new Set([...Object.keys(old), ...Object.keys(item)]))\n if (!PROTECTED.has(key)) patch[key] = item[key]\n tr.update(widget.id, patch as DashboardWidgetPatch)\n })\n return tr\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import dashboard_vue_vue_type_script_setup_true_lang_default from "./dashboard.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
// src/dashboard/dashboard.vue
|
|
3
|
+
var dashboard_default = dashboard_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
export { dashboard_default as default };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=dashboard.js.map
|