@dou-ui/dashboard 0.0.0-stage → 0.1.0-alpha.0

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.
Files changed (188) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/chart/chart-settings.js +6 -0
  4. package/dist/chart/chart-settings.js.map +1 -0
  5. package/dist/chart/chart-settings.vue_vue_type_script_setup_true_lang.js +122 -0
  6. package/dist/chart/chart-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  7. package/dist/chart/chart.js +6 -0
  8. package/dist/chart/chart.js.map +1 -0
  9. package/dist/chart/chart.vue_vue_type_script_setup_true_lang.js +99 -0
  10. package/dist/chart/chart.vue_vue_type_script_setup_true_lang.js.map +1 -0
  11. package/dist/chart/index.js +33 -0
  12. package/dist/chart/index.js.map +1 -0
  13. package/dist/collaboration/index.js +281 -0
  14. package/dist/collaboration/index.js.map +1 -0
  15. package/dist/collaboration/order.js +88 -0
  16. package/dist/collaboration/order.js.map +1 -0
  17. package/dist/core/document.js +128 -0
  18. package/dist/core/document.js.map +1 -0
  19. package/dist/core/history.js +66 -0
  20. package/dist/core/history.js.map +1 -0
  21. package/dist/core/transaction.js +218 -0
  22. package/dist/core/transaction.js.map +1 -0
  23. package/dist/dashboard/dashboard.js +6 -0
  24. package/dist/dashboard/dashboard.js.map +1 -0
  25. package/dist/dashboard/dashboard.vue_vue_type_script_setup_true_lang.js +1072 -0
  26. package/dist/dashboard/dashboard.vue_vue_type_script_setup_true_lang.js.map +1 -0
  27. package/dist/dashboard/export.js +77 -0
  28. package/dist/dashboard/export.js.map +1 -0
  29. package/dist/dashboard/filter-bar.js +6 -0
  30. package/dist/dashboard/filter-bar.js.map +1 -0
  31. package/dist/dashboard/filter-bar.vue_vue_type_script_setup_true_lang.js +150 -0
  32. package/dist/dashboard/filter-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  33. package/dist/dashboard/frame-context.js +5 -0
  34. package/dist/dashboard/frame-context.js.map +1 -0
  35. package/dist/dashboard/observers.js +48 -0
  36. package/dist/dashboard/observers.js.map +1 -0
  37. package/dist/dashboard/palette.js +6 -0
  38. package/dist/dashboard/palette.js.map +1 -0
  39. package/dist/dashboard/palette.vue_vue_type_script_setup_true_lang.js +54 -0
  40. package/dist/dashboard/palette.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/dashboard/widget-body.js +6 -0
  42. package/dist/dashboard/widget-body.js.map +1 -0
  43. package/dist/dashboard/widget-body.vue_vue_type_script_setup_true_lang.js +96 -0
  44. package/dist/dashboard/widget-body.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/dashboard/widget-frame.js +6 -0
  46. package/dist/dashboard/widget-frame.js.map +1 -0
  47. package/dist/dashboard/widget-frame.vue_vue_type_script_setup_true_lang.js +300 -0
  48. package/dist/dashboard/widget-frame.vue_vue_type_script_setup_true_lang.js.map +1 -0
  49. package/dist/dashboard/widget-settings.js +6 -0
  50. package/dist/dashboard/widget-settings.js.map +1 -0
  51. package/dist/dashboard/widget-settings.vue_vue_type_script_setup_true_lang.js +36 -0
  52. package/dist/dashboard/widget-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  53. package/dist/data/context.js +5 -0
  54. package/dist/data/context.js.map +1 -0
  55. package/dist/data/filters.js +31 -0
  56. package/dist/data/filters.js.map +1 -0
  57. package/dist/data/store.js +214 -0
  58. package/dist/data/store.js.map +1 -0
  59. package/dist/grid/grid-handle.js +6 -0
  60. package/dist/grid/grid-handle.js.map +1 -0
  61. package/dist/grid/grid-handle.vue_vue_type_script_setup_true_lang.js +21 -0
  62. package/dist/grid/grid-handle.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/grid/grid-item.js +80 -0
  64. package/dist/grid/grid-item.js.map +1 -0
  65. package/dist/grid/grid-layout.js +6 -0
  66. package/dist/grid/grid-layout.js.map +1 -0
  67. package/dist/grid/grid-layout.vue_vue_type_script_setup_true_lang.js +767 -0
  68. package/dist/grid/grid-layout.vue_vue_type_script_setup_true_lang.js.map +1 -0
  69. package/dist/grid/tiers.js +31 -0
  70. package/dist/grid/tiers.js.map +1 -0
  71. package/dist/index.js +23 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/layout/engine.js +284 -0
  74. package/dist/layout/engine.js.map +1 -0
  75. package/dist/layout/index.js +1 -0
  76. package/dist/locale.js +245 -0
  77. package/dist/locale.js.map +1 -0
  78. package/dist/pivot/index.js +47 -0
  79. package/dist/pivot/index.js.map +1 -0
  80. package/dist/pivot/pivot-settings.js +6 -0
  81. package/dist/pivot/pivot-settings.js.map +1 -0
  82. package/dist/pivot/pivot-settings.vue_vue_type_script_setup_true_lang.js +64 -0
  83. package/dist/pivot/pivot-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  84. package/dist/pivot/pivot.js +6 -0
  85. package/dist/pivot/pivot.js.map +1 -0
  86. package/dist/pivot/pivot.vue_vue_type_script_setup_true_lang.js +76 -0
  87. package/dist/pivot/pivot.vue_vue_type_script_setup_true_lang.js.map +1 -0
  88. package/dist/style.css +1 -0
  89. package/dist/style.d.ts +1 -0
  90. package/dist/style.js +12 -0
  91. package/dist/types/chart/chart-settings.vue.d.ts +11 -0
  92. package/dist/types/chart/chart.vue.d.ts +3 -0
  93. package/dist/types/chart/index.d.ts +9 -0
  94. package/dist/types/chart/types.d.ts +23 -0
  95. package/dist/types/collaboration/index.d.ts +53 -0
  96. package/dist/types/collaboration/order.d.ts +20 -0
  97. package/dist/types/core/collaboration.d.ts +51 -0
  98. package/dist/types/core/document.d.ts +35 -0
  99. package/dist/types/core/history.d.ts +15 -0
  100. package/dist/types/core/transaction.d.ts +86 -0
  101. package/dist/types/core/types.d.ts +27 -0
  102. package/dist/types/dashboard/dashboard.vue.d.ts +33 -0
  103. package/dist/types/dashboard/export.d.ts +11 -0
  104. package/dist/types/dashboard/filter-bar.vue.d.ts +19 -0
  105. package/dist/types/dashboard/frame-context.d.ts +54 -0
  106. package/dist/types/dashboard/observers.d.ts +15 -0
  107. package/dist/types/dashboard/palette.vue.d.ts +20 -0
  108. package/dist/types/dashboard/types.d.ts +190 -0
  109. package/dist/types/dashboard/widget-body.vue.d.ts +19 -0
  110. package/dist/types/dashboard/widget-frame.vue.d.ts +6 -0
  111. package/dist/types/dashboard/widget-settings.vue.d.ts +16 -0
  112. package/dist/types/data/context.d.ts +18 -0
  113. package/dist/types/data/filters.d.ts +9 -0
  114. package/dist/types/data/store.d.ts +48 -0
  115. package/dist/types/data/types.d.ts +67 -0
  116. package/dist/types/grid/grid-handle.vue.d.ts +18 -0
  117. package/dist/types/grid/grid-item.d.ts +78 -0
  118. package/dist/types/grid/grid-layout.vue.d.ts +46 -0
  119. package/dist/types/grid/tiers.d.ts +12 -0
  120. package/dist/types/grid/types.d.ts +113 -0
  121. package/dist/types/index.d.ts +295 -0
  122. package/dist/types/layout/engine.d.ts +50 -0
  123. package/dist/types/layout/index.d.ts +2 -0
  124. package/dist/types/layout/types.d.ts +39 -0
  125. package/dist/types/locale.d.ts +117 -0
  126. package/dist/types/pivot/index.d.ts +14 -0
  127. package/dist/types/pivot/pivot-settings.vue.d.ts +16 -0
  128. package/dist/types/pivot/pivot.vue.d.ts +7 -0
  129. package/dist/types/pivot/types.d.ts +11 -0
  130. package/dist/types/widget/context.d.ts +91 -0
  131. package/dist/types/widget/define.d.ts +50 -0
  132. package/dist/types/widgets/field-select.vue.d.ts +18 -0
  133. package/dist/types/widgets/index.d.ts +15 -0
  134. package/dist/types/widgets/settings-field.vue.d.ts +17 -0
  135. package/dist/types/widgets/shared.d.ts +20 -0
  136. package/dist/types/widgets/source-field.vue.d.ts +15 -0
  137. package/dist/types/widgets/stat-settings.vue.d.ts +11 -0
  138. package/dist/types/widgets/stat.vue.d.ts +3 -0
  139. package/dist/types/widgets/table-settings.vue.d.ts +11 -0
  140. package/dist/types/widgets/table.vue.d.ts +3 -0
  141. package/dist/types/widgets/text-settings.vue.d.ts +11 -0
  142. package/dist/types/widgets/text.vue.d.ts +3 -0
  143. package/dist/types/widgets/types.d.ts +38 -0
  144. package/dist/widget/context.js +88 -0
  145. package/dist/widget/context.js.map +1 -0
  146. package/dist/widget/define.js +32 -0
  147. package/dist/widget/define.js.map +1 -0
  148. package/dist/widgets/field-select.js +6 -0
  149. package/dist/widgets/field-select.js.map +1 -0
  150. package/dist/widgets/field-select.vue_vue_type_script_setup_true_lang.js +54 -0
  151. package/dist/widgets/field-select.vue_vue_type_script_setup_true_lang.js.map +1 -0
  152. package/dist/widgets/index.js +74 -0
  153. package/dist/widgets/index.js.map +1 -0
  154. package/dist/widgets/settings-field.js +6 -0
  155. package/dist/widgets/settings-field.js.map +1 -0
  156. package/dist/widgets/settings-field.vue_vue_type_script_setup_true_lang.js +28 -0
  157. package/dist/widgets/settings-field.vue_vue_type_script_setup_true_lang.js.map +1 -0
  158. package/dist/widgets/shared.js +68 -0
  159. package/dist/widgets/shared.js.map +1 -0
  160. package/dist/widgets/source-field.js +6 -0
  161. package/dist/widgets/source-field.js.map +1 -0
  162. package/dist/widgets/source-field.vue_vue_type_script_setup_true_lang.js +47 -0
  163. package/dist/widgets/source-field.vue_vue_type_script_setup_true_lang.js.map +1 -0
  164. package/dist/widgets/stat-settings.js +6 -0
  165. package/dist/widgets/stat-settings.js.map +1 -0
  166. package/dist/widgets/stat-settings.vue_vue_type_script_setup_true_lang.js +107 -0
  167. package/dist/widgets/stat-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  168. package/dist/widgets/stat.js +6 -0
  169. package/dist/widgets/stat.js.map +1 -0
  170. package/dist/widgets/stat.vue_vue_type_script_setup_true_lang.js +72 -0
  171. package/dist/widgets/stat.vue_vue_type_script_setup_true_lang.js.map +1 -0
  172. package/dist/widgets/table-settings.js +6 -0
  173. package/dist/widgets/table-settings.js.map +1 -0
  174. package/dist/widgets/table-settings.vue_vue_type_script_setup_true_lang.js +90 -0
  175. package/dist/widgets/table-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  176. package/dist/widgets/table.js +6 -0
  177. package/dist/widgets/table.js.map +1 -0
  178. package/dist/widgets/table.vue_vue_type_script_setup_true_lang.js +91 -0
  179. package/dist/widgets/table.vue_vue_type_script_setup_true_lang.js.map +1 -0
  180. package/dist/widgets/text-settings.js +6 -0
  181. package/dist/widgets/text-settings.js.map +1 -0
  182. package/dist/widgets/text-settings.vue_vue_type_script_setup_true_lang.js +63 -0
  183. package/dist/widgets/text-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  184. package/dist/widgets/text.js +6 -0
  185. package/dist/widgets/text.js.map +1 -0
  186. package/dist/widgets/text.vue_vue_type_script_setup_true_lang.js +16 -0
  187. package/dist/widgets/text.vue_vue_type_script_setup_true_lang.js.map +1 -0
  188. package/package.json +98 -4
@@ -0,0 +1,1072 @@
1
+ import { findPlace, fitItem, moveItem } from "../layout/engine.js";
2
+ import { format, useDashboardMessages } from "../locale.js";
3
+ import { DEFAULT_BREAKPOINTS, DEFAULT_COLUMNS, gridTiers } from "../grid/tiers.js";
4
+ import grid_layout_default from "../grid/grid-layout.js";
5
+ import { emptyDocument } from "../core/document.js";
6
+ import { DashboardTransaction, applySteps, cloneJson, replaceWidgets, sameValue } from "../core/transaction.js";
7
+ import { DashboardHistory } from "../core/history.js";
8
+ import { dataContextKey } from "../data/context.js";
9
+ import { filterText, isEmptyFilter } from "../data/filters.js";
10
+ import { createSourceStore, sameSource } from "../data/store.js";
11
+ import { DEFAULT_WIDGET_SIZE, defaultPropsOf } from "../widget/define.js";
12
+ import { createObservers, observersKey } from "./observers.js";
13
+ import { elementToPNG } from "./export.js";
14
+ import widget_body_default from "./widget-body.js";
15
+ import { frameContextKey } from "./frame-context.js";
16
+ import widget_frame_default from "./widget-frame.js";
17
+ import widget_settings_default from "./widget-settings.js";
18
+ import { DouDialog, DouDrawer, DouEmpty, DouInput, downloadBlob, matchesHotkey, useLocale } from "dou-ui";
19
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeModels, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, provide, ref, shallowReactive, shallowRef, toDisplayString, toRaw, unref, useModel, watch, withCtx } from "vue";
20
+ import { LayoutGridPlusIcon, fileIconOf } from "@dou-ui/icons";
21
+ // src/dashboard/dashboard.vue?vue&type=script&setup=true&lang.ts
22
+ var _hoisted_1 = ["aria-label"];
23
+ var _hoisted_2 = {
24
+ key: 0,
25
+ class: "dou-dashboard__empty"
26
+ };
27
+ var _hoisted_3 = {
28
+ class: "dou-dashboard__live",
29
+ "aria-live": "polite",
30
+ "aria-atomic": "true"
31
+ };
32
+ var _hoisted_4 = {
33
+ key: 0,
34
+ class: "dou-dashboard__settings"
35
+ };
36
+ var _hoisted_5 = { class: "dou-dashboard__field" };
37
+ var _hoisted_6 = { class: "dou-dashboard__field-label" };
38
+ var EAGER = 16;
39
+ var CLIPBOARD_KIND = "dou-dashboard/widget";
40
+ var dashboard_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
41
+ name: "DouDashboard",
42
+ __name: "dashboard",
43
+ props: /*@__PURE__*/ mergeModels({
44
+ widgets: { default: () => [] },
45
+ sources: {},
46
+ editable: { type: Boolean },
47
+ columns: { default: () => DEFAULT_COLUMNS },
48
+ breakpoints: { default: () => DEFAULT_BREAKPOINTS },
49
+ rowHeight: { default: 80 },
50
+ gap: { default: 16 },
51
+ compact: {
52
+ type: [String, Boolean],
53
+ default: "vertical"
54
+ },
55
+ label: {},
56
+ messages: {},
57
+ createId: {},
58
+ collaboration: {},
59
+ exporters: {}
60
+ }, {
61
+ "modelValue": { default: emptyDocument },
62
+ "modelModifiers": {},
63
+ "filters": { default: () => ({}) },
64
+ "filtersModifiers": {}
65
+ }),
66
+ emits: /*@__PURE__*/ mergeModels([
67
+ "change",
68
+ "breakpointChange",
69
+ "exportError"
70
+ ], ["update:modelValue", "update:filters"]),
71
+ setup(__props, { expose: __expose, emit: __emit }) {
72
+ const props = __props;
73
+ const emit = __emit;
74
+ const model = useModel(__props, "modelValue");
75
+ const filters = useModel(__props, "filters");
76
+ const messages = useDashboardMessages(() => props.messages);
77
+ const grid = ref();
78
+ const observers = createObservers();
79
+ provide(observersKey, observers);
80
+ onBeforeUnmount(() => observers.dispose());
81
+ /** This many widgets are drawn in full at once, the rest when they come near the view. */
82
+ const root = ref();
83
+ const typeMap = computed(() => new Map(props.widgets.map((type) => [type.type, type])));
84
+ const typeOf = (widget) => typeMap.value.get(widget.type);
85
+ const widgetMap = computed(() => new Map(model.value.widgets.map((widget) => [widget.id, widget])));
86
+ /** The first widgets of the document, drawn in full without waiting to be seen. */
87
+ const eagerIds = computed(() => new Set(model.value.widgets.slice(0, EAGER).map((w) => w.id)));
88
+ /** Widgets added once the dashboard is up are drawn in full straight away (not reactive on purpose). */
89
+ let mounted = false;
90
+ onMounted(() => mounted = true);
91
+ const isEager = (id) => mounted || eagerIds.value.has(id);
92
+ const titleOf = (widget) => widget.title || typeOf(widget)?.label || widget.type;
93
+ const tiers = computed(() => gridTiers(props.columns, props.breakpoints));
94
+ /** The widest breakpoint: its places are the main ones. */
95
+ const main = computed(() => tiers.value[0]);
96
+ const mainOptions = computed(() => ({
97
+ columns: Math.max(1, Math.round(main.value.columns)),
98
+ compact: props.compact
99
+ }));
100
+ const history = new DashboardHistory();
101
+ /** Bumped whenever the history changes, so `canUndo` and `canRedo` update. */
102
+ const historyVersion = ref(0);
103
+ /** The document last emitted, to tell our own changes from the app's. */
104
+ let emitted;
105
+ watch(model, (doc) => {
106
+ if (toRaw(doc) === emitted) return;
107
+ history.clear();
108
+ historyVersion.value++;
109
+ });
110
+ function commit(tr) {
111
+ emitted = toRaw(tr.doc);
112
+ model.value = tr.doc;
113
+ emit("change", tr);
114
+ }
115
+ /** A transaction on the document as it is now, to `dispatch`. */
116
+ function transaction() {
117
+ return new DashboardTransaction(model.value);
118
+ }
119
+ /** Makes the changes of a transaction, as one undo step (or part of one, by `meta.group`). */
120
+ function dispatch(tr) {
121
+ if (toRaw(tr.before) !== toRaw(model.value)) throw new Error("[@dou-ui/dashboard] the transaction was made on another version of the dashboard");
122
+ if (!tr.changed) return;
123
+ const shared = props.collaboration;
124
+ if (!shared) {
125
+ history.record(tr);
126
+ historyVersion.value++;
127
+ }
128
+ commit(tr);
129
+ shared?.transaction(tr);
130
+ }
131
+ /** Steps made into a transaction and dispatched, for the dashboard's own changes. */
132
+ function run(steps, meta) {
133
+ const tr = applySteps(model.value, steps, meta);
134
+ dispatch(tr);
135
+ return tr;
136
+ }
137
+ function undo() {
138
+ if (props.collaboration) return props.collaboration.undo();
139
+ const tr = history.undo(model.value);
140
+ historyVersion.value++;
141
+ if (!tr) return false;
142
+ if (tr.changed) commit(tr);
143
+ return true;
144
+ }
145
+ function redo() {
146
+ if (props.collaboration) return props.collaboration.redo();
147
+ const tr = history.redo(model.value);
148
+ historyVersion.value++;
149
+ if (!tr) return false;
150
+ if (tr.changed) commit(tr);
151
+ return true;
152
+ }
153
+ const canUndo = computed(() => props.collaboration ? props.collaboration.canUndo() : (historyVersion.value, history.canUndo));
154
+ const canRedo = computed(() => props.collaboration ? props.collaboration.canRedo() : (historyVersion.value, history.canRedo));
155
+ function limitsOf(type) {
156
+ const size = type?.size ?? DEFAULT_WIDGET_SIZE;
157
+ return {
158
+ w: size.w,
159
+ h: size.h,
160
+ minW: size.minW,
161
+ maxW: size.maxW,
162
+ minH: size.minH,
163
+ maxH: size.maxH
164
+ };
165
+ }
166
+ /** The widgets on the widest breakpoint; those without a place there get the first spot they fit. */
167
+ const placeOf = (item) => ({
168
+ x: item.x,
169
+ y: item.y,
170
+ w: item.w,
171
+ h: item.h
172
+ });
173
+ let itemCache = /* @__PURE__ */ new WeakMap();
174
+ watch([typeMap, main], () => itemCache = /* @__PURE__ */ new WeakMap(), { flush: "sync" });
175
+ const items = computed(() => {
176
+ const name = main.value.name;
177
+ const options = mainOptions.value;
178
+ const placed = [];
179
+ const result = /* @__PURE__ */ new Map();
180
+ const missing = [];
181
+ for (const widget of model.value.widgets) {
182
+ const raw = toRaw(widget);
183
+ const place = raw.place[name];
184
+ if (!place) {
185
+ missing.push(raw);
186
+ continue;
187
+ }
188
+ let item = itemCache.get(raw);
189
+ if (!item) {
190
+ const { minW, maxW, minH, maxH } = limitsOf(typeOf(raw));
191
+ item = {
192
+ id: raw.id,
193
+ ...placeOf(place),
194
+ minW,
195
+ maxW,
196
+ minH,
197
+ maxH
198
+ };
199
+ itemCache.set(raw, item);
200
+ }
201
+ placed.push(item);
202
+ result.set(raw.id, item);
203
+ }
204
+ for (const widget of missing) {
205
+ const fitted = fitItem({
206
+ id: widget.id,
207
+ x: 0,
208
+ y: 0,
209
+ ...limitsOf(typeOf(widget))
210
+ }, options.columns);
211
+ const item = {
212
+ ...fitted,
213
+ ...findPlace(placed, fitted, options)
214
+ };
215
+ placed.push(item);
216
+ result.set(widget.id, item);
217
+ }
218
+ return model.value.widgets.map((widget) => result.get(widget.id));
219
+ });
220
+ /** The places on the narrower breakpoints. */
221
+ const layouts = computed(() => {
222
+ const result = {};
223
+ for (const tier of tiers.value.slice(1)) {
224
+ const list = [];
225
+ for (const widget of model.value.widgets) {
226
+ const place = widget.place[tier.name];
227
+ if (place) list.push({
228
+ id: widget.id,
229
+ ...place
230
+ });
231
+ }
232
+ if (list.length) result[tier.name] = list;
233
+ }
234
+ return result;
235
+ });
236
+ let pending = {};
237
+ let flushing = false;
238
+ function gather(part) {
239
+ Object.assign(pending, part);
240
+ if (flushing) return;
241
+ flushing = true;
242
+ queueMicrotask(flush);
243
+ }
244
+ function flush() {
245
+ flushing = false;
246
+ const { items: list, layouts: saved, drop } = pending;
247
+ pending = {};
248
+ const doc = model.value;
249
+ const places = /* @__PURE__ */ new Map();
250
+ const setPlace = (id, name, item) => {
251
+ let place = places.get(id);
252
+ if (!place) places.set(id, place = { ...widgetMap.value.get(id)?.place });
253
+ place[name] = placeOf(item);
254
+ };
255
+ for (const item of list ?? []) setPlace(item.id, main.value.name, item);
256
+ for (const [name, others] of Object.entries(saved ?? {})) for (const item of others ?? []) setPlace(item.id, name, item);
257
+ const steps = [];
258
+ for (const [id, place] of places) if (widgetMap.value.has(id)) steps.push({
259
+ type: "update",
260
+ id,
261
+ patch: { place }
262
+ });
263
+ else if (drop?.id === id) {
264
+ const type = typeMap.value.get(drop.type);
265
+ if (type) steps.push({
266
+ type: "add",
267
+ widget: newWidget(id, type, place),
268
+ index: doc.widgets.length
269
+ });
270
+ }
271
+ const tr = run(steps, { origin: drop ? "drop" : "layout" });
272
+ if (drop && tr.changed) {
273
+ const widget = widgetMap.value.get(drop.id);
274
+ if (widget) say(format(messages.value.added, { item: titleOf(widget) }));
275
+ }
276
+ }
277
+ function onDrop(event) {
278
+ const data = event.data;
279
+ if (data?.type) gather({ drop: {
280
+ id: event.item.id,
281
+ type: data.type
282
+ } });
283
+ }
284
+ const dragging = ref(false);
285
+ function newId(type) {
286
+ const used = widgetMap.value;
287
+ for (;;) {
288
+ const id = props.createId?.(type) ?? `${type}-${Math.random().toString(36).slice(2, 8)}`;
289
+ if (!used.has(id)) return id;
290
+ if (props.createId) throw new Error(`[@dou-ui/dashboard] createId gave "${id}", which is already in use`);
291
+ }
292
+ }
293
+ function newWidget(id, type, place, options = {}) {
294
+ const widget = {
295
+ id,
296
+ type: type.type,
297
+ place
298
+ };
299
+ if (options.title) widget.title = options.title;
300
+ const initial = defaultPropsOf(type);
301
+ if (initial || options.props) widget.props = {
302
+ ...initial,
303
+ ...options.props
304
+ };
305
+ return widget;
306
+ }
307
+ /**
308
+ * Puts a new item on the widest breakpoint, at `at` (pushing the others
309
+ * away) or in the first spot it fits; the steps also move any widget it
310
+ * pushed.
311
+ */
312
+ function placeNew(item, at) {
313
+ const options = mainOptions.value;
314
+ const fitted = fitItem(item, options.columns);
315
+ const spot = at ?? findPlace(items.value, fitted, options);
316
+ const layout = moveItem([...items.value, {
317
+ ...fitted,
318
+ ...spot
319
+ }], item.id, spot, options);
320
+ const steps = [];
321
+ let place = {
322
+ ...fitted,
323
+ ...spot
324
+ };
325
+ for (const next of layout) {
326
+ if (next.id === item.id) {
327
+ place = placeOf(next);
328
+ continue;
329
+ }
330
+ const widget = widgetMap.value.get(next.id);
331
+ const old = widget && items.value.find((other) => other.id === next.id);
332
+ if (widget && old && (old.x !== next.x || old.y !== next.y)) steps.push({
333
+ type: "update",
334
+ id: next.id,
335
+ patch: { place: {
336
+ ...widget.place,
337
+ [main.value.name]: placeOf(next)
338
+ } }
339
+ });
340
+ }
341
+ return {
342
+ place,
343
+ steps
344
+ };
345
+ }
346
+ function addWidget(typeName, options = {}) {
347
+ const type = typeMap.value.get(typeName);
348
+ if (!type) return void 0;
349
+ const id = newId(typeName);
350
+ const { place, steps } = placeNew({
351
+ id,
352
+ x: 0,
353
+ y: 0,
354
+ ...limitsOf(type)
355
+ }, options.at);
356
+ const widget = newWidget(id, type, { [main.value.name]: place }, options);
357
+ run([{
358
+ type: "add",
359
+ widget,
360
+ index: model.value.widgets.length
361
+ }, ...steps], { origin: "api" });
362
+ say(format(messages.value.added, { item: titleOf(widget) }));
363
+ if (options.focus !== false) nextTick(() => focusWidget(id));
364
+ return id;
365
+ }
366
+ /** Adds a copy of a widget (from this dashboard or the clipboard) beside `near`, or in the first free spot. */
367
+ function addCopy(source, near) {
368
+ const type = typeOf(source);
369
+ const id = newId(source.type);
370
+ const columns = mainOptions.value.columns;
371
+ const size = near ?? {
372
+ ...limitsOf(type),
373
+ ...source.place[main.value.name]
374
+ };
375
+ const at = near ? near.x + near.w * 2 <= columns ? {
376
+ x: near.x + near.w,
377
+ y: near.y
378
+ } : {
379
+ x: near.x,
380
+ y: near.y + near.h
381
+ } : void 0;
382
+ const { place, steps } = placeNew({
383
+ ...limitsOf(type),
384
+ id,
385
+ x: 0,
386
+ y: 0,
387
+ w: size.w,
388
+ h: size.h
389
+ }, at);
390
+ const widget = {
391
+ ...cloneJson(source),
392
+ id,
393
+ place: { [main.value.name]: place }
394
+ };
395
+ run([{
396
+ type: "add",
397
+ widget,
398
+ index: near ? model.value.widgets.findIndex((other) => other.id === near.id) + 1 : model.value.widgets.length
399
+ }, ...steps], { origin: "copy" });
400
+ say(format(messages.value.added, { item: titleOf(widget) }));
401
+ nextTick(() => focusWidget(id));
402
+ return id;
403
+ }
404
+ function duplicateWidget(id) {
405
+ const widget = widgetMap.value.get(id);
406
+ const item = items.value.find((other) => other.id === id);
407
+ return widget && item ? addCopy(widget, item) : void 0;
408
+ }
409
+ function removeWidget(id) {
410
+ const list = model.value.widgets;
411
+ const index = list.findIndex((widget) => widget.id === id);
412
+ if (index < 0) return;
413
+ const widget = list[index];
414
+ const hadFocus = root.value?.contains(document.activeElement);
415
+ const neighbour = list[index + 1] ?? list[index - 1];
416
+ if (settingsId.value === id) settingsId.value = void 0;
417
+ if (fullscreenId.value === id) fullscreenId.value = void 0;
418
+ run([{
419
+ type: "remove",
420
+ id
421
+ }], { origin: "api" });
422
+ say(format(messages.value.removed, { item: titleOf(widget) }));
423
+ if (hadFocus) nextTick(() => neighbour ? focusWidget(neighbour.id) : root.value?.focus());
424
+ }
425
+ function focusWidget(id) {
426
+ grid.value?.focusItem(id);
427
+ }
428
+ /** The widget being dragged in, which has no widget yet: its name is its type's. */
429
+ let adding;
430
+ function startAdd(event, typeName) {
431
+ const type = typeMap.value.get(typeName);
432
+ if (!type || !props.editable) return;
433
+ adding = {
434
+ id: newId(typeName),
435
+ label: type.label
436
+ };
437
+ grid.value?.startDrop(event, {
438
+ id: adding.id,
439
+ ...limitsOf(type)
440
+ }, { type: typeName });
441
+ }
442
+ function labelOf(item) {
443
+ const widget = widgetMap.value.get(item.id);
444
+ return widget ? titleOf(widget) : item.id === adding?.id ? adding.label : item.id;
445
+ }
446
+ function setProps(id, patch, options = {}) {
447
+ const widget = widgetMap.value.get(id);
448
+ if (!widget) return;
449
+ run([{
450
+ type: "update",
451
+ id,
452
+ patch: { props: {
453
+ ...widget.props,
454
+ ...patch
455
+ } }
456
+ }], {
457
+ origin: "widget",
458
+ group: options.group
459
+ });
460
+ }
461
+ function rename(id, title) {
462
+ run([{
463
+ type: "update",
464
+ id,
465
+ patch: { title: title.trim() || void 0 }
466
+ }], { origin: "title" });
467
+ }
468
+ const settingsId = ref();
469
+ const settingsWidget = computed(() => settingsId.value ? widgetMap.value.get(settingsId.value) : void 0);
470
+ const fullscreenId = ref();
471
+ const fullscreenWidget = computed(() => fullscreenId.value ? widgetMap.value.get(fullscreenId.value) : void 0);
472
+ /** The widget to give focus back to when a drawer or dialog closes. */
473
+ let returnTo;
474
+ function openSettings(id) {
475
+ if (!props.editable || !widgetMap.value.has(id)) return;
476
+ returnTo = id;
477
+ settingsId.value = id;
478
+ }
479
+ function openFullscreen(id) {
480
+ const widget = widgetMap.value.get(id);
481
+ if (!widget || typeOf(widget)?.fullscreen === false) return;
482
+ returnTo = id;
483
+ fullscreenId.value = id;
484
+ }
485
+ function restoreFocus() {
486
+ const id = returnTo;
487
+ returnTo = void 0;
488
+ if (id && widgetMap.value.has(id)) focusWidget(id);
489
+ }
490
+ watch(() => props.editable, (editable) => {
491
+ if (!editable) settingsId.value = void 0;
492
+ });
493
+ function onAction(id, action) {
494
+ if (action === "settings") openSettings(id);
495
+ else if (action === "fullscreen") openFullscreen(id);
496
+ else if (action === "duplicate") duplicateWidget(id);
497
+ else removeWidget(id);
498
+ }
499
+ /** Changes from others wait while a widget is dragged here; then the shared document is read again. */
500
+ let held = false;
501
+ function applyShared(doc, origin) {
502
+ if (dragging.value) {
503
+ held = true;
504
+ return;
505
+ }
506
+ const tr = replaceWidgets(new DashboardTransaction(model.value), doc);
507
+ if (!tr.changed) return;
508
+ tr.setMeta("history", false).setMeta("origin", origin);
509
+ const ids = new Set(tr.doc.widgets.map((widget) => widget.id));
510
+ if (settingsId.value && !ids.has(settingsId.value)) settingsId.value = void 0;
511
+ if (fullscreenId.value && !ids.has(fullscreenId.value)) fullscreenId.value = void 0;
512
+ commit(tr);
513
+ }
514
+ let setupTimes = [];
515
+ let setupWarned = false;
516
+ function warnRepeatedSetup() {
517
+ if (process.env.NODE_ENV === "production" || setupWarned) return;
518
+ const now = Date.now();
519
+ setupTimes = [...setupTimes.filter((time) => now - time < 1e3), now];
520
+ if (setupTimes.length < 4) return;
521
+ setupWarned = true;
522
+ console.warn("[@dou-ui/dashboard] collaboration keeps being replaced, which starts it over and clears undo each time. Create it once in <script setup> instead of in the template.");
523
+ }
524
+ watch(() => props.collaboration, (shared, _, onCleanup) => {
525
+ if (shared) warnRepeatedSetup();
526
+ history.clear();
527
+ historyVersion.value++;
528
+ if (!shared) return;
529
+ const stop = shared.setup({
530
+ document: () => model.value,
531
+ apply: applyShared
532
+ });
533
+ onCleanup(() => {
534
+ shared.presence?.(null);
535
+ stop();
536
+ });
537
+ }, { immediate: true });
538
+ /** The widget being moved or resized here, and how. */
539
+ const drag = shallowRef();
540
+ function onDragStart(event) {
541
+ dragging.value = true;
542
+ drag.value = event;
543
+ }
544
+ function onDragEnd() {
545
+ dragging.value = false;
546
+ drag.value = void 0;
547
+ if (!held) return;
548
+ held = false;
549
+ queueMicrotask(() => queueMicrotask(() => props.collaboration?.sync()));
550
+ }
551
+ /** The widget whose item, or a control in it, has focus. */
552
+ const focused = ref();
553
+ function onFocusIn(event) {
554
+ const item = event.target.closest?.("[data-grid-id]");
555
+ focused.value = item?.closest(".dou-dashboard") === root.value ? item?.dataset.gridId : void 0;
556
+ }
557
+ function onFocusOut(event) {
558
+ if (!root.value?.contains(event.relatedTarget)) focused.value = void 0;
559
+ }
560
+ /** What this person is doing, shown to the others. */
561
+ const presence = computed(() => {
562
+ const moving = drag.value;
563
+ if (moving && moving.kind !== "drop" && widgetMap.value.has(moving.id)) return {
564
+ widget: moving.id,
565
+ action: moving.kind
566
+ };
567
+ if (settingsId.value) return {
568
+ widget: settingsId.value,
569
+ action: "settings"
570
+ };
571
+ if (focused.value && widgetMap.value.has(focused.value)) return {
572
+ widget: focused.value,
573
+ action: "focus"
574
+ };
575
+ return null;
576
+ });
577
+ watch([presence, () => props.collaboration], ([current, shared], previous) => {
578
+ if (!shared?.presence) return;
579
+ const [before, sharedBefore] = previous ?? [];
580
+ if (sharedBefore === shared && before?.widget === current?.widget && before?.action === current?.action) return;
581
+ shared.presence(current);
582
+ }, { immediate: true });
583
+ const NO_PEERS = [];
584
+ provide(frameContextKey, {
585
+ widget: (id) => widgetMap.value.get(id),
586
+ type: typeOf,
587
+ title: titleOf,
588
+ editing: () => props.editable,
589
+ messages: () => messages.value,
590
+ eager: isEager,
591
+ all: () => showAll.value,
592
+ setProps,
593
+ action: onAction,
594
+ rename,
595
+ linked: (id) => linksByWidget.value.get(id) ?? NO_LINKS,
596
+ clearFilter: (key) => setFilter(void 0, key, void 0),
597
+ peers: (id) => props.collaboration?.peers?.(id) ?? NO_PEERS,
598
+ setWorkbook,
599
+ exporters: (id) => workbooks.has(id) ? exporterList.value : NO_EXPORTERS,
600
+ exportTo: exportFromMenu,
601
+ exporting: (id) => exporting.has(id),
602
+ setDrawn: (id, drawn) => drawn ? drawnIds.add(id) : drawnIds.delete(id),
603
+ exportError: (id) => exportErrors.get(id),
604
+ clearExportError: (id) => exportErrors.delete(id)
605
+ });
606
+ /** How each mounted widget gives its content as a workbook; see `setWorkbook`. */
607
+ const workbooks = shallowReactive(/* @__PURE__ */ new Map());
608
+ function setWorkbook(id, get) {
609
+ workbooks.set(id, get);
610
+ return () => {
611
+ if (workbooks.get(id) === get) workbooks.delete(id);
612
+ };
613
+ }
614
+ const sheetCore = shallowRef();
615
+ let loadingCore;
616
+ function loadSheetCore() {
617
+ loadingCore ??= import("@dou-ui/sheet/core").then((core) => sheetCore.value = core);
618
+ return loadingCore;
619
+ }
620
+ watch(() => props.exporters?.length, (count) => count && void loadSheetCore(), { immediate: true });
621
+ const NO_EXPORTERS = [];
622
+ const { locale } = useLocale();
623
+ /** The exporters of the menu, without those that draw a sheet's grid. */
624
+ const exporterList = computed(() => {
625
+ const core = sheetCore.value;
626
+ if (!core) return NO_EXPORTERS;
627
+ const sheetMessages = core.sheetMessagesFor(locale.value.name);
628
+ return (props.exporters ?? []).filter((exporter) => exporter.needs !== "view").map((exporter) => ({
629
+ name: exporter.name,
630
+ label: typeof exporter.label === "function" ? exporter.label(sheetMessages) : exporter.label,
631
+ icon: exporter.icon ?? fileIconOf(exporter.extension)
632
+ }));
633
+ });
634
+ function exporterOf(exporter) {
635
+ if (typeof exporter !== "string") return exporter;
636
+ const found = props.exporters?.find((item) => item.name === exporter);
637
+ if (!found) throw new RangeError(`[@dou-ui/dashboard] there is no exporter "${exporter}"`);
638
+ return found;
639
+ }
640
+ /** A widget's file and sheet name: its title, or its type's name. */
641
+ const exportNameOf = (widget) => titleOf(widget).trim() || widget.type;
642
+ /** Why something failed, as people read it: a file error's `reason`, or its message. */
643
+ function reasonOf(error) {
644
+ if (error instanceof Error) return "reason" in error && typeof error.reason === "string" ? error.reason : error.message;
645
+ return String(error);
646
+ }
647
+ /**
648
+ * A widget's content as a file: its workbook written by the exporter, a
649
+ * name from `exporters` or an exporter itself. Waits for the data shown,
650
+ * and draws the widget first when it is far from the view.
651
+ */
652
+ async function exportWidget(id, exporter, options = {}) {
653
+ const chosen = exporterOf(exporter);
654
+ const widget = widgetMap.value.get(id);
655
+ if (!widget) throw new RangeError(`[@dou-ui/dashboard] there is no widget "${id}"`);
656
+ const core = await loadSheetCore();
657
+ await settle(5e3, !drawnIds.has(id));
658
+ options.signal?.throwIfAborted();
659
+ const get = workbooks.get(id);
660
+ const book = get?.();
661
+ if (!book) throw new core.SheetFileError(get ? `[@dou-ui/dashboard] the widget "${id}" has no data to export now` : `[@dou-ui/dashboard] the widget "${id}" doesn't give its content as a workbook (setWorkbook)`, "empty", messages.value.nothingToExport);
662
+ const name = exportNameOf(widget);
663
+ const named = book.sheets.some((sheet) => !sheet.name) ? {
664
+ ...book,
665
+ sheets: book.sheets.map((sheet) => sheet.name ? sheet : {
666
+ ...sheet,
667
+ name
668
+ })
669
+ } : book;
670
+ return core.exportWorkbook(named, chosen, {
671
+ options: options.options,
672
+ signal: options.signal,
673
+ locale: locale.value.name
674
+ });
675
+ }
676
+ /** The widgets drawn in full; the others show only their title until they come near. */
677
+ const drawnIds = /* @__PURE__ */ new Set();
678
+ /** Why the last export from each widget's menu failed, shown over its content until closed. */
679
+ const exportErrors = shallowReactive(/* @__PURE__ */ new Map());
680
+ /** The widgets exporting now, whose export button waits. */
681
+ const exporting = shallowReactive(/* @__PURE__ */ new Set());
682
+ /**
683
+ * Exports a widget's content and saves the file in the downloads, as its
684
+ * menu does. A failure shows over the widget and comes as `exportError`,
685
+ * and the promise rejects.
686
+ */
687
+ async function saveWidget(id, exporter, options = {}) {
688
+ const chosen = exporterOf(exporter);
689
+ const widget = widgetMap.value.get(id);
690
+ if (!widget) throw new RangeError(`[@dou-ui/dashboard] there is no widget "${id}"`);
691
+ const name = options.fileName ?? `${exportNameOf(widget)}.${chosen.extension}`;
692
+ exportErrors.delete(id);
693
+ exporting.add(id);
694
+ let blob;
695
+ try {
696
+ blob = await exportWidget(id, chosen, options);
697
+ } catch (error) {
698
+ const event = {
699
+ widget: id,
700
+ exporter: chosen.name,
701
+ error,
702
+ message: format(messages.value.exportFailed, {
703
+ name,
704
+ reason: reasonOf(error)
705
+ }),
706
+ defaultPrevented: false,
707
+ preventDefault() {
708
+ this.defaultPrevented = true;
709
+ }
710
+ };
711
+ emit("exportError", event);
712
+ if (event.defaultPrevented) say(event.message);
713
+ else exportErrors.set(id, event.message);
714
+ throw error;
715
+ } finally {
716
+ exporting.delete(id);
717
+ }
718
+ downloadBlob(blob, name);
719
+ say(format(messages.value.exported, { name }));
720
+ }
721
+ function exportFromMenu(id, name) {
722
+ if (exporting.has(id)) return;
723
+ saveWidget(id, name).catch(() => {});
724
+ }
725
+ const store = createSourceStore(() => props.sources);
726
+ onBeforeUnmount(() => store.dispose());
727
+ watch(() => props.sources, (next, previous) => {
728
+ const names = /* @__PURE__ */ new Set([...Object.keys(previous ?? {}), ...Object.keys(next ?? {})]);
729
+ for (const name of names) if (!sameSource(previous?.[name], next?.[name])) store.reload(name);
730
+ });
731
+ /** The filters set by widgets, by key: they apply to every widget but the one that set them. */
732
+ const links = shallowRef(/* @__PURE__ */ new Map());
733
+ const EMPTY_SET = /* @__PURE__ */ new Set();
734
+ const NO_LINKS = [];
735
+ const linksByWidget = computed(() => {
736
+ const result = /* @__PURE__ */ new Map();
737
+ for (const link of links.value.values()) {
738
+ const list = result.get(link.widget);
739
+ if (list) list.push(link);
740
+ else result.set(link.widget, [link]);
741
+ }
742
+ return result;
743
+ });
744
+ const emittedByWidget = computed(() => {
745
+ const result = /* @__PURE__ */ new Map();
746
+ for (const [id, list] of linksByWidget.value) result.set(id, new Set(list.map((link) => link.key)));
747
+ return result;
748
+ });
749
+ const values = shallowRef(filters.value);
750
+ watch(filters, (next) => values.value = next);
751
+ function setFilter(widget, key, value, options = {}) {
752
+ const next = { ...values.value };
753
+ const nextLinks = new Map(links.value);
754
+ if (isEmptyFilter(value)) delete next[key];
755
+ else next[key] = value;
756
+ if (widget && !isEmptyFilter(value) && widgetMap.value.has(widget)) nextLinks.set(key, {
757
+ key,
758
+ value,
759
+ label: options.label ?? filterText(value),
760
+ widget
761
+ });
762
+ else nextLinks.delete(key);
763
+ links.value = nextLinks;
764
+ values.value = next;
765
+ filters.value = next;
766
+ }
767
+ function clearFilters() {
768
+ links.value = /* @__PURE__ */ new Map();
769
+ values.value = {};
770
+ filters.value = {};
771
+ }
772
+ watch(filters, (current) => {
773
+ let next;
774
+ for (const [key, link] of links.value) {
775
+ if (sameValue(current[key], link.value)) continue;
776
+ next ??= new Map(links.value);
777
+ next.delete(key);
778
+ }
779
+ if (next) links.value = next;
780
+ });
781
+ watch(widgetMap, (map) => {
782
+ for (const link of links.value.values()) if (!map.has(link.widget)) setFilter(void 0, link.key, void 0);
783
+ });
784
+ const sourceNames = computed(() => Object.keys(props.sources ?? {}));
785
+ provide(dataContextKey, {
786
+ store,
787
+ sourceNames: () => sourceNames.value,
788
+ filters: () => values.value,
789
+ emitted: (id) => emittedByWidget.value.get(id) ?? EMPTY_SET,
790
+ linked: (id) => linksByWidget.value.get(id) ?? NO_LINKS,
791
+ setFilter
792
+ });
793
+ function isTyping(target) {
794
+ const el = target;
795
+ if (!el?.tagName) return false;
796
+ return el.isContentEditable || el.tagName === "TEXTAREA" || el.tagName === "SELECT" || el.tagName === "INPUT" && ![
797
+ "checkbox",
798
+ "radio",
799
+ "button",
800
+ "range",
801
+ "color"
802
+ ].includes(el.type);
803
+ }
804
+ /** The widget whose item itself has focus (not a control inside it). */
805
+ function focusedId(target) {
806
+ const el = target;
807
+ return el?.dataset?.gridId && el.closest(".dou-dashboard") === root.value ? el.dataset.gridId : void 0;
808
+ }
809
+ function onKeydown(event) {
810
+ if (!props.editable || event.defaultPrevented || isTyping(event.target)) return;
811
+ if (matchesHotkey(event, "mod+z")) {
812
+ event.preventDefault();
813
+ undo();
814
+ return;
815
+ }
816
+ if (matchesHotkey(event, "mod+shift+z") || matchesHotkey(event, "mod+y")) {
817
+ event.preventDefault();
818
+ redo();
819
+ return;
820
+ }
821
+ const id = focusedId(event.target);
822
+ if (!id || dragging.value) return;
823
+ if (event.key === "Delete" || event.key === "Backspace") {
824
+ event.preventDefault();
825
+ removeWidget(id);
826
+ } else if (matchesHotkey(event, "mod+d")) {
827
+ event.preventDefault();
828
+ duplicateWidget(id);
829
+ }
830
+ }
831
+ function onCopy(event, cut = false) {
832
+ if (isTyping(event.target)) return;
833
+ const id = focusedId(event.target);
834
+ const widget = id && widgetMap.value.get(id);
835
+ if (!widget || !event.clipboardData) return;
836
+ event.clipboardData.setData("text/plain", JSON.stringify({
837
+ kind: CLIPBOARD_KIND,
838
+ widget
839
+ }));
840
+ event.preventDefault();
841
+ if (cut && props.editable) removeWidget(widget.id);
842
+ }
843
+ function onPaste(event) {
844
+ if (!props.editable || isTyping(event.target)) return;
845
+ let data;
846
+ try {
847
+ data = JSON.parse(event.clipboardData?.getData("text/plain") ?? "");
848
+ } catch {
849
+ return;
850
+ }
851
+ const widget = data?.kind === CLIPBOARD_KIND ? data.widget : void 0;
852
+ if (!widget?.type || !typeMap.value.has(widget.type)) return;
853
+ event.preventDefault();
854
+ const id = focusedId(event.target);
855
+ addCopy(widget, id ? items.value.find((item) => item.id === id) : void 0);
856
+ }
857
+ /** Every widget drawn in full, not only those near the view. */
858
+ const showAll = ref(false);
859
+ const nextFrame = () => new Promise((resolve) => requestAnimationFrame(() => resolve()));
860
+ /** Draws every widget (unless `all` is false) and waits for their sources (at most `timeout` ms) and one more frame. */
861
+ async function settle(timeout = 5e3, all = true) {
862
+ if (all) showAll.value = true;
863
+ await nextTick();
864
+ const start = performance.now();
865
+ await nextFrame();
866
+ while (store.busy() && performance.now() - start < timeout) await nextFrame();
867
+ await nextFrame();
868
+ await nextFrame();
869
+ }
870
+ /** The dashboard as it is shown, every widget drawn, as a PNG image. */
871
+ async function exportPNG(options) {
872
+ await settle();
873
+ const el = root.value;
874
+ el.classList.add("is-exporting");
875
+ try {
876
+ return elementToPNG(el, options);
877
+ } finally {
878
+ el.classList.remove("is-exporting");
879
+ }
880
+ }
881
+ /** Draws every widget, waits for their data, and opens the browser's print dialog. */
882
+ async function print() {
883
+ await settle();
884
+ window.print();
885
+ }
886
+ function onBeforePrint() {
887
+ showAll.value = true;
888
+ }
889
+ onMounted(() => window.addEventListener("beforeprint", onBeforePrint));
890
+ onBeforeUnmount(() => window.removeEventListener("beforeprint", onBeforePrint));
891
+ const live = ref("");
892
+ function say(text) {
893
+ live.value = "";
894
+ requestAnimationFrame(() => live.value = text);
895
+ }
896
+ const breakpointName = shallowRef(main.value.name);
897
+ function onBreakpoint(name) {
898
+ breakpointName.value = name;
899
+ emit("breakpointChange", name);
900
+ }
901
+ const minHeight = computed(() => {
902
+ const gap = Array.isArray(props.gap) ? props.gap[1] : props.gap;
903
+ return `${props.rowHeight * 3 + gap * 2}px`;
904
+ });
905
+ __expose({
906
+ transaction,
907
+ dispatch,
908
+ undo,
909
+ redo,
910
+ get canUndo() {
911
+ return canUndo.value;
912
+ },
913
+ get canRedo() {
914
+ return canRedo.value;
915
+ },
916
+ addWidget,
917
+ removeWidget,
918
+ duplicateWidget,
919
+ focusWidget,
920
+ openSettings,
921
+ openFullscreen,
922
+ startAdd,
923
+ get types() {
924
+ return props.widgets;
925
+ },
926
+ get breakpoint() {
927
+ return grid.value?.breakpoint ?? breakpointName.value;
928
+ },
929
+ get editable() {
930
+ return props.editable;
931
+ },
932
+ get document() {
933
+ return model.value;
934
+ },
935
+ get filters() {
936
+ return values.value;
937
+ },
938
+ get linkedFilters() {
939
+ return [...links.value.values()];
940
+ },
941
+ setFilter: (key, value, options) => setFilter(void 0, key, value, options),
942
+ clearFilters,
943
+ refresh: (source) => store.reload(source),
944
+ exportPNG,
945
+ exportWidget,
946
+ saveWidget,
947
+ print
948
+ });
949
+ return (_ctx, _cache) => {
950
+ return openBlock(), createElementBlock("div", {
951
+ ref_key: "root",
952
+ ref: root,
953
+ class: normalizeClass(["dou-dashboard", __props.editable && "is-editable"]),
954
+ role: "region",
955
+ "aria-label": __props.label || unref(messages).dashboard,
956
+ tabindex: "-1",
957
+ onKeydown,
958
+ onCopy: _cache[6] || (_cache[6] = ($event) => onCopy($event)),
959
+ onCut: _cache[7] || (_cache[7] = ($event) => onCopy($event, true)),
960
+ onPaste,
961
+ onFocusin: onFocusIn,
962
+ onFocusout: onFocusOut
963
+ }, [
964
+ createVNode(grid_layout_default, {
965
+ ref_key: "grid",
966
+ ref: grid,
967
+ "model-value": items.value,
968
+ layouts: layouts.value,
969
+ columns: __props.columns,
970
+ breakpoints: __props.breakpoints,
971
+ "row-height": __props.rowHeight,
972
+ gap: __props.gap,
973
+ compact: __props.compact,
974
+ editable: __props.editable,
975
+ handle: true,
976
+ "item-label": labelOf,
977
+ messages: unref(messages),
978
+ style: normalizeStyle(__props.editable && !model.value.widgets.length ? { minHeight: minHeight.value } : void 0),
979
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = (list) => gather({ items: list })),
980
+ "onUpdate:layouts": _cache[1] || (_cache[1] = (next) => gather({ layouts: next })),
981
+ onDrop,
982
+ onDragStart,
983
+ onDragEnd,
984
+ onBreakpointChange: onBreakpoint
985
+ }, {
986
+ item: withCtx(({ item }) => [createVNode(widget_frame_default, { id: item.id }, null, 8, ["id"])]),
987
+ _: 1
988
+ }, 8, [
989
+ "model-value",
990
+ "layouts",
991
+ "columns",
992
+ "breakpoints",
993
+ "row-height",
994
+ "gap",
995
+ "compact",
996
+ "editable",
997
+ "messages",
998
+ "style"
999
+ ]),
1000
+ __props.editable && !model.value.widgets.length ? (openBlock(), createElementBlock("div", _hoisted_2, [createVNode(unref(DouEmpty), {
1001
+ size: "sm",
1002
+ title: unref(messages).emptyTitle,
1003
+ description: unref(messages).emptyHint
1004
+ }, {
1005
+ icon: withCtx(() => [createVNode(unref(LayoutGridPlusIcon))]),
1006
+ _: 1
1007
+ }, 8, ["title", "description"])])) : createCommentVNode("", true),
1008
+ createElementVNode("div", _hoisted_3, toDisplayString(live.value), 1),
1009
+ createVNode(unref(DouDrawer), {
1010
+ open: !!settingsWidget.value,
1011
+ title: settingsWidget.value ? unref(format)(unref(messages).settingsTitle, { title: titleOf(settingsWidget.value) }) : "",
1012
+ size: 360,
1013
+ "onUpdate:open": _cache[4] || (_cache[4] = (open) => !open && (settingsId.value = void 0)),
1014
+ onClosed: restoreFocus
1015
+ }, {
1016
+ default: withCtx(() => [settingsWidget.value ? (openBlock(), createElementBlock("div", _hoisted_4, [createElementVNode("label", _hoisted_5, [createElementVNode("span", _hoisted_6, toDisplayString(unref(messages).titleField), 1), createVNode(unref(DouInput), {
1017
+ "model-value": settingsWidget.value.title ?? "",
1018
+ placeholder: typeOf(settingsWidget.value)?.label ?? settingsWidget.value.type,
1019
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = (value) => run([{
1020
+ type: "update",
1021
+ id: settingsWidget.value.id,
1022
+ patch: { title: value || void 0 }
1023
+ }], {
1024
+ origin: "settings",
1025
+ group: `title:${settingsWidget.value.id}`
1026
+ }))
1027
+ }, null, 8, ["model-value", "placeholder"])]), typeOf(settingsWidget.value)?.settings ? (openBlock(), createBlock(widget_settings_default, {
1028
+ key: 0,
1029
+ widget: settingsWidget.value,
1030
+ type: typeOf(settingsWidget.value),
1031
+ "set-props": setProps,
1032
+ "onUpdate:props": _cache[3] || (_cache[3] = (value) => run([{
1033
+ type: "update",
1034
+ id: settingsWidget.value.id,
1035
+ patch: { props: value }
1036
+ }], {
1037
+ origin: "settings",
1038
+ group: `props:${settingsWidget.value.id}`
1039
+ }))
1040
+ }, null, 8, ["widget", "type"])) : createCommentVNode("", true)])) : createCommentVNode("", true)]),
1041
+ _: 1
1042
+ }, 8, ["open", "title"]),
1043
+ createVNode(unref(DouDialog), {
1044
+ open: !!fullscreenWidget.value,
1045
+ title: fullscreenWidget.value ? titleOf(fullscreenWidget.value) : "",
1046
+ fullscreen: "",
1047
+ "onUpdate:open": _cache[5] || (_cache[5] = (open) => !open && (fullscreenId.value = void 0)),
1048
+ onClosed: restoreFocus
1049
+ }, {
1050
+ default: withCtx(() => [fullscreenWidget.value ? (openBlock(), createBlock(widget_body_default, {
1051
+ key: 0,
1052
+ class: "is-fullscreen",
1053
+ widget: fullscreenWidget.value,
1054
+ type: typeOf(fullscreenWidget.value),
1055
+ editing: false,
1056
+ fullscreen: "",
1057
+ messages: unref(messages),
1058
+ "set-props": setProps
1059
+ }, null, 8, [
1060
+ "widget",
1061
+ "type",
1062
+ "messages"
1063
+ ])) : createCommentVNode("", true)]),
1064
+ _: 1
1065
+ }, 8, ["open", "title"])
1066
+ ], 42, _hoisted_1);
1067
+ };
1068
+ }
1069
+ });
1070
+ export { dashboard_vue_vue_type_script_setup_true_lang_default as default };
1071
+
1072
+ //# sourceMappingURL=dashboard.vue_vue_type_script_setup_true_lang.js.map