@conduction/nextcloud-vue 2.68.0 → 2.69.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.
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +21 -35
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +17 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +40 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +58 -16
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +2 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +70 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +23 -3
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/mixins/pageViews.js +278 -11
- package/dist/esm/mixins/pageViews.js.map +1 -1
- package/dist/esm/store/plugins/dashboardLayouts.js +35 -1
- package/dist/esm/store/plugins/dashboardLayouts.js.map +1 -1
- package/dist/esm/utils/formatMetric.js +137 -16
- package/dist/esm/utils/formatMetric.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +678 -83
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/l10n/en.json +3 -0
- package/l10n/nl.json +3 -0
- package/package.json +1 -1
- package/src/components/CnDashboardPage/CnDashboardPage.md +2 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +34 -35
- package/src/components/CnDetailPage/CnDetailPage.md +6 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +67 -5
- package/src/components/CnStatWidget/CnStatWidget.vue +2 -2
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +82 -3
- package/src/mixins/pageViews.js +277 -10
- package/src/schemas/app-manifest-v2.schema.json +28 -2
- package/src/store/plugins/dashboardLayouts.js +34 -0
- package/src/utils/formatMetric.js +143 -15
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { translate } from '@nextcloud/l10n';
|
|
2
2
|
import { readUserPreference, writeUserPreference, USER_PREFERENCE_STORAGE_PREFIX } from '../composables/useUserPreferences.js';
|
|
3
|
+
import { resolveUserLayoutApi, mergeUserLayout, dashboardLayoutKey, LAYOUT_GEOMETRY_FIELDS } from '../store/plugins/dashboardLayouts.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
@@ -21,6 +22,13 @@ import { readUserPreference, writeUserPreference, USER_PREFERENCE_STORAGE_PREFIX
|
|
|
21
22
|
* `layout` with its own grid, so a view looks exactly like the page's own
|
|
22
23
|
* grid.
|
|
23
24
|
*
|
|
25
|
+
* With `userLayout` on, a user arranges each view's grid for themselves, as
|
|
26
|
+
* they arrange the dashboard's own grid. The arrangement is stored per view,
|
|
27
|
+
* per page and per user, through the same user-preference record the
|
|
28
|
+
* dashboard uses (the server, mirrored in the browser). The manifest stays
|
|
29
|
+
* the base: it decides which widgets a view has, the user decides where they
|
|
30
|
+
* sit, and a reset returns the view to the manifest.
|
|
31
|
+
*
|
|
24
32
|
* @mixin pageViews
|
|
25
33
|
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid
|
|
26
34
|
*/
|
|
@@ -31,18 +39,32 @@ const PAGE_VIEW_QUERY_KEY = 'view';
|
|
|
31
39
|
/** Prefix of the user-preference key; the page id follows it. */
|
|
32
40
|
const PAGE_VIEW_PREFERENCE_PREFIX = 'cn_page_view:';
|
|
33
41
|
|
|
42
|
+
/**
|
|
43
|
+
* The page id one view's user layout is stored under: the page's own id,
|
|
44
|
+
* then `.view.`, then the view id. Spelled once, because a read and a write
|
|
45
|
+
* that disagree about the key read as an arrangement that never persists.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} pageKey The page's id.
|
|
48
|
+
* @param {string} viewId The view id.
|
|
49
|
+
* @return {string} The page id handed to the layout store.
|
|
50
|
+
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves
|
|
51
|
+
*/
|
|
52
|
+
function pageViewLayoutId(pageKey, viewId) {
|
|
53
|
+
return `${String(pageKey || 'page')}.view.${String(viewId)}`
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
let regionSeq = 0;
|
|
35
57
|
|
|
36
58
|
/**
|
|
37
|
-
* Read the browser mirror of a user preference synchronously, so
|
|
38
|
-
*
|
|
59
|
+
* Read the browser mirror of a user preference synchronously, so a stored
|
|
60
|
+
* value is on screen from the first frame. Errors read as nothing stored.
|
|
39
61
|
*
|
|
40
62
|
* @param {string} appId The app id.
|
|
41
63
|
* @param {string} key The preference key.
|
|
42
|
-
* @return {string|null} The stored
|
|
43
|
-
* @spec openspec/changes/view-
|
|
64
|
+
* @return {object|string|number|boolean|Array|null} The stored value, or null.
|
|
65
|
+
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves
|
|
44
66
|
*/
|
|
45
|
-
function
|
|
67
|
+
function readPreferenceMirror(appId, key) {
|
|
46
68
|
try {
|
|
47
69
|
if (typeof localStorage === 'undefined') {
|
|
48
70
|
return null
|
|
@@ -51,19 +73,45 @@ function readStoredViewMirror(appId, key) {
|
|
|
51
73
|
if (raw === null || raw === undefined) {
|
|
52
74
|
return null
|
|
53
75
|
}
|
|
54
|
-
|
|
55
|
-
return typeof value === 'string' && value !== '' ? value : null
|
|
76
|
+
return JSON.parse(raw)
|
|
56
77
|
} catch {
|
|
57
78
|
return null
|
|
58
79
|
}
|
|
59
80
|
}
|
|
60
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Read the browser mirror of the stored view synchronously, so the stored
|
|
84
|
+
* view is on screen from the first frame.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} appId The app id.
|
|
87
|
+
* @param {string} key The preference key.
|
|
88
|
+
* @return {string|null} The stored id, or null.
|
|
89
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-chosen-view-is-linkable-and-remembered
|
|
90
|
+
*/
|
|
91
|
+
function readStoredViewMirror(appId, key) {
|
|
92
|
+
const value = readPreferenceMirror(appId, key);
|
|
93
|
+
return typeof value === 'string' && value !== '' ? value : null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Whether a stored layout record holds an arrangement.
|
|
98
|
+
*
|
|
99
|
+
* @param {object|Array<object>|null} record The record, or its items.
|
|
100
|
+
* @return {boolean}
|
|
101
|
+
*/
|
|
102
|
+
function holdsArrangement(record) {
|
|
103
|
+
const items = Array.isArray(record) ? record : (record && Array.isArray(record.items) ? record.items : []);
|
|
104
|
+
return items.length > 0
|
|
105
|
+
}
|
|
106
|
+
|
|
61
107
|
const pageViews = {
|
|
62
108
|
inject: {
|
|
63
109
|
/** The host app id, provided by CnAppRoot; keys the stored view. */
|
|
64
110
|
cnPageViewsAppId: { from: 'cnAppId', default: '' },
|
|
65
111
|
},
|
|
66
112
|
|
|
113
|
+
emits: ['view-layout-change', 'view-layout-reset'],
|
|
114
|
+
|
|
67
115
|
props: {
|
|
68
116
|
/**
|
|
69
117
|
* Views of this page, each its own widget grid behind one segmented
|
|
@@ -111,6 +159,17 @@ const pageViews = {
|
|
|
111
159
|
viewChosen: false,
|
|
112
160
|
/** Id of the view region, the target of the options' `aria-controls`. */
|
|
113
161
|
viewRegionId: `cn-page-view-region-${regionSeq}`,
|
|
162
|
+
/**
|
|
163
|
+
* This user's arrangement of each view, by view id, when the page
|
|
164
|
+
* keeps a user layout. A separate copy, never the view's `layout`:
|
|
165
|
+
* that array is the manifest, and writing a user's drag into it
|
|
166
|
+
* would rearrange the view for everybody.
|
|
167
|
+
*
|
|
168
|
+
* @type {Record<string, Array<object>>}
|
|
169
|
+
*/
|
|
170
|
+
viewUserLayouts: {},
|
|
171
|
+
/** The views whose arrangement changed since the last save, by id. */
|
|
172
|
+
viewUserLayoutsDirty: {},
|
|
114
173
|
}
|
|
115
174
|
},
|
|
116
175
|
|
|
@@ -208,6 +267,32 @@ const pageViews = {
|
|
|
208
267
|
return this.normalizedViews.find((view) => view.id === this.activeViewId) || null
|
|
209
268
|
},
|
|
210
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Whether a user arranges the views for themselves (`userLayout`).
|
|
272
|
+
*
|
|
273
|
+
* @return {boolean}
|
|
274
|
+
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves
|
|
275
|
+
*/
|
|
276
|
+
viewUserLayoutOn() {
|
|
277
|
+
return this.userLayout === true && this.hasViews
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The chosen view's layout: this user's arrangement when they have
|
|
282
|
+
* one, the manifest's otherwise.
|
|
283
|
+
*
|
|
284
|
+
* @return {Array<object>}
|
|
285
|
+
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves
|
|
286
|
+
*/
|
|
287
|
+
activeViewLayout() {
|
|
288
|
+
const view = this.activeView;
|
|
289
|
+
if (!view) {
|
|
290
|
+
return []
|
|
291
|
+
}
|
|
292
|
+
const arranged = this.viewUserLayoutOn ? this.viewUserLayouts[view.id] : null;
|
|
293
|
+
return Array.isArray(arranged) ? arranged : view.layout
|
|
294
|
+
},
|
|
295
|
+
|
|
211
296
|
/**
|
|
212
297
|
* The switch's options, for CnSegmentedControl.
|
|
213
298
|
*
|
|
@@ -289,8 +374,21 @@ const pageViews = {
|
|
|
289
374
|
},
|
|
290
375
|
},
|
|
291
376
|
|
|
377
|
+
watch: {
|
|
378
|
+
/**
|
|
379
|
+
* Read the user's arrangement of a view the first time it is shown.
|
|
380
|
+
*
|
|
381
|
+
* @param {string|null} id The chosen view id.
|
|
382
|
+
* @return {void}
|
|
383
|
+
*/
|
|
384
|
+
activeViewId(id) {
|
|
385
|
+
this.loadViewUserLayout(id);
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
|
|
292
389
|
created() {
|
|
293
390
|
this.loadStoredView();
|
|
391
|
+
this.loadViewUserLayout(this.activeViewId);
|
|
294
392
|
},
|
|
295
393
|
|
|
296
394
|
methods: {
|
|
@@ -362,18 +460,187 @@ const pageViews = {
|
|
|
362
460
|
},
|
|
363
461
|
|
|
364
462
|
/**
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
463
|
+
* The page id a view's arrangement is stored under.
|
|
464
|
+
*
|
|
465
|
+
* @param {string} viewId The view id.
|
|
466
|
+
* @return {string}
|
|
467
|
+
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves
|
|
468
|
+
*/
|
|
469
|
+
viewLayoutPageId(viewId) {
|
|
470
|
+
const pageKey = this.viewPreferenceKey.slice(PAGE_VIEW_PREFERENCE_PREFIX.length);
|
|
471
|
+
return pageViewLayoutId(pageKey, viewId)
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* The layout store calls: the page's own (`userLayoutApi`, so an
|
|
476
|
+
* injected `userLayoutStore` covers the views too), else the user
|
|
477
|
+
* preferences.
|
|
478
|
+
*
|
|
479
|
+
* @return {{load: (appId: string, pageId: string) => Promise<object|null>, save: (appId: string, pageId: string, layout: Array<object>) => Promise<boolean>, reset: (appId: string, pageId: string) => Promise<boolean>}}
|
|
480
|
+
*/
|
|
481
|
+
viewLayoutApi() {
|
|
482
|
+
if (typeof this.userLayoutApi === 'function') {
|
|
483
|
+
return this.userLayoutApi()
|
|
484
|
+
}
|
|
485
|
+
return resolveUserLayoutApi(null)
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Read this user's arrangement of one view, once: the browser mirror
|
|
490
|
+
* at once, then the server, which wins unless the user already moved
|
|
491
|
+
* something in that view. A page without `userLayout` reads nothing.
|
|
492
|
+
*
|
|
493
|
+
* @param {string|null} viewId The view id.
|
|
494
|
+
* @return {Promise<void>}
|
|
495
|
+
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves
|
|
496
|
+
*/
|
|
497
|
+
async loadViewUserLayout(viewId) {
|
|
498
|
+
if (!this.viewUserLayoutOn || viewId === null || viewId === undefined) {
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
const view = this.normalizedViews.find((candidate) => candidate.id === String(viewId));
|
|
502
|
+
if (!view || Array.isArray(this.viewUserLayouts[view.id])) {
|
|
503
|
+
return
|
|
504
|
+
}
|
|
505
|
+
const appId = this.viewPreferenceAppId;
|
|
506
|
+
const pageId = this.viewLayoutPageId(view.id);
|
|
507
|
+
const mirror = readPreferenceMirror(appId, dashboardLayoutKey(pageId));
|
|
508
|
+
this.viewUserLayouts = { ...this.viewUserLayouts, [view.id]: mergeUserLayout(view.layout, holdsArrangement(mirror) ? mirror : null) };
|
|
509
|
+
if (!appId) {
|
|
510
|
+
return
|
|
511
|
+
}
|
|
512
|
+
let record;
|
|
513
|
+
try {
|
|
514
|
+
record = await this.viewLayoutApi().load(appId, pageId);
|
|
515
|
+
} catch {
|
|
516
|
+
// The mirror, or the manifest, stands: a store that cannot
|
|
517
|
+
// answer leaves the view as the user last saw it.
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
if (this.viewUserLayoutsDirty[view.id] || !holdsArrangement(record)) {
|
|
521
|
+
return
|
|
522
|
+
}
|
|
523
|
+
this.viewUserLayouts = { ...this.viewUserLayouts, [view.id]: mergeUserLayout(view.layout, record) };
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Store the arrangement of every view the user changed since the last
|
|
528
|
+
* save. The page calls it when the user leaves edit mode, so a drag
|
|
529
|
+
* is not a write per pixel gesture.
|
|
530
|
+
*
|
|
531
|
+
* @return {Promise<void>}
|
|
532
|
+
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves
|
|
533
|
+
*/
|
|
534
|
+
async saveViewUserLayouts() {
|
|
535
|
+
if (!this.viewUserLayoutOn) {
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
const ids = Object.keys(this.viewUserLayoutsDirty).filter((id) => this.viewUserLayoutsDirty[id]);
|
|
539
|
+
this.viewUserLayoutsDirty = {};
|
|
540
|
+
const appId = this.viewPreferenceAppId;
|
|
541
|
+
for (const id of ids) {
|
|
542
|
+
const pageId = this.viewLayoutPageId(id);
|
|
543
|
+
const items = this.viewUserLayouts[id] || [];
|
|
544
|
+
try {
|
|
545
|
+
if (appId) {
|
|
546
|
+
await this.viewLayoutApi().save(appId, pageId, items);
|
|
547
|
+
} else {
|
|
548
|
+
// No app to address: keep it in this browser only, as
|
|
549
|
+
// the chosen view is kept.
|
|
550
|
+
await writeUserPreference('', dashboardLayoutKey(pageId), { items: items.map((item) => this.viewGeometry(item, true)) });
|
|
551
|
+
}
|
|
552
|
+
} catch {
|
|
553
|
+
// The arrangement stands for this session. A toast on a
|
|
554
|
+
// failed layout write is noise on an instance without the
|
|
555
|
+
// preference route.
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Drop this user's arrangement of one view and return it to the
|
|
562
|
+
* manifest layout. Other views keep theirs.
|
|
563
|
+
*
|
|
564
|
+
* @param {string} [viewId] The view id; the chosen view when left out.
|
|
565
|
+
* @return {Promise<void>}
|
|
566
|
+
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-resetting-a-view-restores-its-manifest-layout
|
|
567
|
+
*/
|
|
568
|
+
async resetViewUserLayout(viewId) {
|
|
569
|
+
const id = viewId === undefined || viewId === null ? this.activeViewId : String(viewId);
|
|
570
|
+
const view = this.normalizedViews.find((candidate) => candidate.id === id);
|
|
571
|
+
if (!this.viewUserLayoutOn || !view) {
|
|
572
|
+
return
|
|
573
|
+
}
|
|
574
|
+
const appId = this.viewPreferenceAppId;
|
|
575
|
+
const pageId = this.viewLayoutPageId(view.id);
|
|
576
|
+
try {
|
|
577
|
+
if (appId) {
|
|
578
|
+
await this.viewLayoutApi().reset(appId, pageId);
|
|
579
|
+
} else {
|
|
580
|
+
await writeUserPreference('', dashboardLayoutKey(pageId), { items: [] });
|
|
581
|
+
}
|
|
582
|
+
} catch {
|
|
583
|
+
// The view returns to the manifest either way, which is what
|
|
584
|
+
// the user asked for.
|
|
585
|
+
}
|
|
586
|
+
const dirty = { ...this.viewUserLayoutsDirty };
|
|
587
|
+
delete dirty[view.id];
|
|
588
|
+
this.viewUserLayoutsDirty = dirty;
|
|
589
|
+
this.viewUserLayouts = { ...this.viewUserLayouts, [view.id]: mergeUserLayout(view.layout, null) };
|
|
590
|
+
/**
|
|
591
|
+
* Emitted when the user drops their arrangement of a view and it
|
|
592
|
+
* returns to the manifest layout.
|
|
593
|
+
*
|
|
594
|
+
* @event view-layout-reset
|
|
595
|
+
* @type {{view: string}}
|
|
596
|
+
*/
|
|
597
|
+
this.$emit('view-layout-reset', { view: view.id });
|
|
598
|
+
},
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* The geometry fields of one layout item, and nothing else.
|
|
602
|
+
*
|
|
603
|
+
* @param {object} item A layout item, or a grid update.
|
|
604
|
+
* @param {boolean} [withWidgetId] Whether to keep the widget id too.
|
|
605
|
+
* @return {object}
|
|
606
|
+
*/
|
|
607
|
+
viewGeometry(item, withWidgetId = false) {
|
|
608
|
+
const geometry = withWidgetId ? { widgetId: String(item?.widgetId ?? '') } : {};
|
|
609
|
+
for (const field of LAYOUT_GEOMETRY_FIELDS) {
|
|
610
|
+
if (item?.[field] !== undefined) {
|
|
611
|
+
geometry[field] = item[field];
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return geometry
|
|
615
|
+
},
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* A drag or resize in the chosen view's grid. With `userLayout` it
|
|
619
|
+
* moves this user's copy of the view and marks it for saving; the
|
|
620
|
+
* view's manifest `layout` is not touched. Without it the geometry is
|
|
621
|
+
* written back onto the layout items in place, as the page does for
|
|
622
|
+
* its own layout, so the in-app manifest editor sees it.
|
|
368
623
|
*
|
|
369
624
|
* @param {Array<object>} updated The layout from the grid.
|
|
370
625
|
* @return {void}
|
|
626
|
+
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves
|
|
371
627
|
*/
|
|
372
628
|
onViewLayoutChange(updated) {
|
|
373
629
|
const view = this.activeView;
|
|
374
630
|
if (!view || !Array.isArray(updated)) {
|
|
375
631
|
return
|
|
376
632
|
}
|
|
633
|
+
if (this.viewUserLayoutOn) {
|
|
634
|
+
const current = Array.isArray(this.viewUserLayouts[view.id]) ? this.viewUserLayouts[view.id] : mergeUserLayout(view.layout, null);
|
|
635
|
+
const arranged = current.map((item) => {
|
|
636
|
+
const u = updated.find((candidate) => String(candidate.id) === String(item.id));
|
|
637
|
+
return u ? { ...item, ...this.viewGeometry(u) } : item
|
|
638
|
+
});
|
|
639
|
+
this.viewUserLayouts = { ...this.viewUserLayouts, [view.id]: arranged };
|
|
640
|
+
this.viewUserLayoutsDirty = { ...this.viewUserLayoutsDirty, [view.id]: true };
|
|
641
|
+
this.$emit('view-layout-change', { view: view.id, layout: arranged });
|
|
642
|
+
return
|
|
643
|
+
}
|
|
377
644
|
for (const u of updated) {
|
|
378
645
|
const item = view.layout.find((l) => String(l.id) === String(u.id));
|
|
379
646
|
if (!item) {
|
|
@@ -396,5 +663,5 @@ const pageViews = {
|
|
|
396
663
|
},
|
|
397
664
|
};
|
|
398
665
|
|
|
399
|
-
export { PAGE_VIEW_PREFERENCE_PREFIX, PAGE_VIEW_QUERY_KEY, pageViews };
|
|
666
|
+
export { PAGE_VIEW_PREFERENCE_PREFIX, PAGE_VIEW_QUERY_KEY, pageViewLayoutId, pageViews };
|
|
400
667
|
//# sourceMappingURL=pageViews.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageViews.js","sources":["../../../src/mixins/pageViews.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Page views: a few views of one page, each its own widget grid, behind one\n * segmented control (\"My work | My team\").\n *\n * A page that mixes this in accepts `views` (each `{ id, label, icon?,\n * widgets, layout, emptyText? }`), `defaultView` and `viewsLabel`. It keeps\n * the chosen view, writes it to the address (`?view=<id>`) and to the\n * user's preferences (the server, mirrored in the browser), and provides `cnPageViews` so a greeting header widget on\n * the page can draw the switch instead of the page.\n *\n * Which view opens: the address, then the stored view, then `defaultView`,\n * then the first view. An id that names no view is ignored at every step.\n *\n * Used by CnDashboardPage and CnDetailPage. Each renders the chosen view's\n * `layout` with its own grid, so a view looks exactly like the page's own\n * grid.\n *\n * @mixin pageViews\n * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { readUserPreference, USER_PREFERENCE_STORAGE_PREFIX, writeUserPreference } from '../composables/useUserPreferences.js'\n\n/** The query parameter that carries the chosen view. */\nexport const PAGE_VIEW_QUERY_KEY = 'view'\n\n/** Prefix of the user-preference key; the page id follows it. */\nexport const PAGE_VIEW_PREFERENCE_PREFIX = 'cn_page_view:'\n\nlet regionSeq = 0\n\n/**\n * Read the browser mirror of a user preference synchronously, so the stored\n * view is on screen from the first frame. Errors read as nothing stored.\n *\n * @param {string} appId The app id.\n * @param {string} key The preference key.\n * @return {string|null} The stored id, or null.\n * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-chosen-view-is-linkable-and-remembered\n */\nfunction readStoredViewMirror(appId, key) {\n\ttry {\n\t\tif (typeof localStorage === 'undefined') {\n\t\t\treturn null\n\t\t}\n\t\tconst raw = localStorage.getItem(`${USER_PREFERENCE_STORAGE_PREFIX}${appId}:${key}`)\n\t\tif (raw === null || raw === undefined) {\n\t\t\treturn null\n\t\t}\n\t\tconst value = JSON.parse(raw)\n\t\treturn typeof value === 'string' && value !== '' ? value : null\n\t} catch {\n\t\treturn null\n\t}\n}\n\nexport const pageViews = {\n\tinject: {\n\t\t/** The host app id, provided by CnAppRoot; keys the stored view. */\n\t\tcnPageViewsAppId: { from: 'cnAppId', default: '' },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Views of this page, each its own widget grid behind one segmented\n\t\t * control. Each is `{ id, label, icon?, widgets, layout, emptyText? }`:\n\t\t * `widgets` and `layout` have the same shape as the page's own. The\n\t\t * chosen view renders in a region below the page's own grid. Empty\n\t\t * (the default) draws no switch and no region.\n\t\t *\n\t\t * @type {Array<{id: string, label: string, icon?: string, widgets?: Array<object>, layout?: Array<object>, emptyText?: string}>}\n\t\t */\n\t\tviews: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The id of the view chosen when neither the address nor the browser\n\t\t * store names one. Empty chooses the first view.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdefaultView: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Accessible name of the view switch (through the host translate\n\t\t * function). Empty reads \"View\".\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tviewsLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\tregionSeq += 1\n\t\treturn {\n\t\t\t/** The view the user chose in this session, or the stored one. */\n\t\t\tselectedViewId: null,\n\t\t\t/** Whether the user chose a view in this session (a late server read then leaves it alone). */\n\t\t\tviewChosen: false,\n\t\t\t/** Id of the view region, the target of the options' `aria-controls`. */\n\t\t\tviewRegionId: `cn-page-view-region-${regionSeq}`,\n\t\t}\n\t},\n\n\t/**\n\t * Provide the views to descendants, so a greeting header widget in the\n\t * page's grid can draw the switch. Getters keep it reactive.\n\t *\n\t * @return {object} The provided `cnPageViews`.\n\t */\n\tprovide() {\n\t\tconst vm = this\n\t\treturn {\n\t\t\tcnPageViews: {\n\t\t\t\tget views() { return vm.normalizedViews },\n\t\t\t\tget activeId() { return vm.activeViewId },\n\t\t\t\tget regionId() { return vm.viewRegionId },\n\t\t\t\tselect: (id) => vm.selectView(id),\n\t\t\t},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The declared views in one shape, labels translated. Entries without\n\t\t * an id or a label are dropped, as are repeated ids.\n\t\t *\n\t\t * @return {Array<{id: string, label: string, icon: string, widgets: Array<object>, layout: Array<object>, emptyText: string}>}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid\n\t\t */\n\t\tnormalizedViews() {\n\t\t\tconst translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t\tconst seen = new Set()\n\t\t\treturn (Array.isArray(this.views) ? this.views : [])\n\t\t\t\t.filter((view) => {\n\t\t\t\t\tif (!view || typeof view !== 'object' || view.id === undefined || view.id === null || view.id === '') {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof view.label !== 'string' || view.label === '' || seen.has(String(view.id))) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tseen.add(String(view.id))\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t\t.map((view) => ({\n\t\t\t\t\tid: String(view.id),\n\t\t\t\t\tlabel: translate(view.label),\n\t\t\t\t\ticon: typeof view.icon === 'string' ? view.icon : '',\n\t\t\t\t\twidgets: Array.isArray(view.widgets) ? view.widgets : [],\n\t\t\t\t\tlayout: Array.isArray(view.layout) ? view.layout : [],\n\t\t\t\t\temptyText: typeof view.emptyText === 'string' && view.emptyText !== '' ? translate(view.emptyText) : '',\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Whether this page has views.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\thasViews() {\n\t\t\treturn this.normalizedViews.length > 0\n\t\t},\n\n\t\t/**\n\t\t * The chosen view's id: the address, then this session's or the\n\t\t * stored choice, then `defaultView`, then the first view.\n\t\t *\n\t\t * @return {string|null} The id, or null without views.\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-chosen-view-is-linkable-and-remembered\n\t\t */\n\t\tactiveViewId() {\n\t\t\tconst views = this.normalizedViews\n\t\t\tif (views.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst known = (id) => id !== null && id !== undefined && views.some((view) => view.id === String(id))\n\t\t\tconst fromAddress = this.$route && this.$route.query ? this.$route.query[PAGE_VIEW_QUERY_KEY] : undefined\n\t\t\tif (known(fromAddress)) {\n\t\t\t\treturn String(fromAddress)\n\t\t\t}\n\t\t\tif (known(this.selectedViewId)) {\n\t\t\t\treturn String(this.selectedViewId)\n\t\t\t}\n\t\t\tif (known(this.defaultView)) {\n\t\t\t\treturn this.defaultView\n\t\t\t}\n\t\t\treturn views[0].id\n\t\t},\n\n\t\t/**\n\t\t * The chosen view, or null without views.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tactiveView() {\n\t\t\treturn this.normalizedViews.find((view) => view.id === this.activeViewId) || null\n\t\t},\n\n\t\t/**\n\t\t * The switch's options, for CnSegmentedControl.\n\t\t *\n\t\t * @return {Array<{value: string, label: string}>}\n\t\t */\n\t\tviewSwitchOptions() {\n\t\t\treturn this.normalizedViews.map((view) => ({ value: view.id, label: view.label }))\n\t\t},\n\n\t\t/**\n\t\t * The switch's accessible name: `viewsLabel` translated, else \"View\".\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-view-switch-is-accessible\n\t\t */\n\t\tviewSwitchLabel() {\n\t\t\tconst translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t\treturn this.viewsLabel ? translate(this.viewsLabel) : t('nextcloud-vue', 'Views')\n\t\t},\n\n\t\t/**\n\t\t * Whether a greeting header in the page's own widgets draws the\n\t\t * switch (an option with a `view`), so the page draws none.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-greeting-header-can-switch-the-pages-views\n\t\t */\n\t\tviewSwitchInHeaderWidget() {\n\t\t\tconst widgets = Array.isArray(this.widgets) ? this.widgets : []\n\t\t\treturn widgets.some((def) => {\n\t\t\t\tconst options = def && def.content && def.content.views && def.content.views.options\n\t\t\t\treturn Array.isArray(options) && options.some((option) => option && typeof option.view === 'string' && option.view !== '')\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether the page draws the switch itself.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tshowsPageViewSwitch() {\n\t\t\treturn this.hasViews && !this.viewSwitchInHeaderWidget\n\t\t},\n\n\t\t/**\n\t\t * The user-preference key for this page's chosen view.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tviewPreferenceKey() {\n\t\t\t// The page id, else the route name: a detail page's fallback id is\n\t\t\t// its record's title, which would remember a view per record.\n\t\t\tconst id = this.pageId || (this.$route && this.$route.name) || this.resolvedPageId || 'page'\n\t\t\treturn `${PAGE_VIEW_PREFERENCE_PREFIX}${id}`\n\t\t},\n\n\t\t/**\n\t\t * The app id the stored view is kept under: the page's `appId` prop\n\t\t * when it has one, else the app root's.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tviewPreferenceAppId() {\n\t\t\tconst own = typeof this.appId === 'string' ? this.appId : ''\n\t\t\treturn own || (typeof this.cnPageViewsAppId === 'string' ? this.cnPageViewsAppId : '')\n\t\t},\n\n\t\t/**\n\t\t * The message of the chosen view's empty state.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so\n\t\t */\n\t\tviewEmptyText() {\n\t\t\tif (this.activeView && this.activeView.emptyText) {\n\t\t\t\treturn this.activeView.emptyText\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'This view has no widgets yet.')\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.loadStoredView()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the stored view: the browser mirror at once, then the user\n\t\t * preference on the server, which wins unless the user already chose.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-chosen-view-is-linkable-and-remembered\n\t\t */\n\t\tasync loadStoredView() {\n\t\t\tif (!this.hasViews) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst appId = this.viewPreferenceAppId\n\t\t\tconst key = this.viewPreferenceKey\n\t\t\tthis.selectedViewId = readStoredViewMirror(appId, key)\n\t\t\tif (!appId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst stored = await readUserPreference(appId, key, null)\n\t\t\tif (!this.viewChosen && typeof stored === 'string' && stored !== '') {\n\t\t\t\tthis.selectedViewId = stored\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Choose a view: keep it, store it, and write it to the address\n\t\t * (replacing the history entry, keeping the other query keys).\n\t\t *\n\t\t * @param {string} id The view id.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-chosen-view-is-linkable-and-remembered\n\t\t */\n\t\tselectView(id) {\n\t\t\tconst view = this.normalizedViews.find((candidate) => candidate.id === String(id))\n\t\t\tif (!view) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.selectedViewId = view.id\n\t\t\tthis.viewChosen = true\n\t\t\twriteUserPreference(this.viewPreferenceAppId, this.viewPreferenceKey, view.id)\n\t\t\tif (!this.$router || !this.$route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst query = this.$route.query || {}\n\t\t\tif (query[PAGE_VIEW_QUERY_KEY] === view.id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst result = this.$router.replace({ query: { ...query, [PAGE_VIEW_QUERY_KEY]: view.id } })\n\t\t\tif (result && typeof result.catch === 'function') {\n\t\t\t\tresult.catch(() => {})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Look a widget definition up in the views, for a layout item of the\n\t\t * chosen view.\n\t\t *\n\t\t * @param {string} widgetId The widget id.\n\t\t * @return {object|null} The definition, or null.\n\t\t */\n\t\tfindViewWidget(widgetId) {\n\t\t\tconst view = this.activeView\n\t\t\tif (!view) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn view.widgets.find((def) => def && def.id === widgetId) || null\n\t\t},\n\n\t\t/**\n\t\t * Write a drag or resize in the chosen view's grid back onto its\n\t\t * layout items in place, as the page does for its own layout, so the\n\t\t * in-app manifest editor sees it.\n\t\t *\n\t\t * @param {Array<object>} updated The layout from the grid.\n\t\t * @return {void}\n\t\t */\n\t\tonViewLayoutChange(updated) {\n\t\t\tconst view = this.activeView\n\t\t\tif (!view || !Array.isArray(updated)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor (const u of updated) {\n\t\t\t\tconst item = view.layout.find((l) => String(l.id) === String(u.id))\n\t\t\t\tif (!item) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor (const key of ['gridX', 'gridY', 'gridWidth', 'gridHeight']) {\n\t\t\t\t\tif (u[key] !== undefined) {\n\t\t\t\t\t\titem[key] = u[key]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t/**\n\t\t\t * Emitted when a widget in a view's grid is dragged or resized.\n\t\t\t *\n\t\t\t * @event view-layout-change\n\t\t\t * @type {{view: string, layout: Array<object>}}\n\t\t\t */\n\t\t\tthis.$emit('view-layout-change', { view: view.id, layout: view.layout })\n\t\t},\n\t},\n}\n"],"names":["translate","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pageViews.js","sources":["../../../src/mixins/pageViews.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Page views: a few views of one page, each its own widget grid, behind one\n * segmented control (\"My work | My team\").\n *\n * A page that mixes this in accepts `views` (each `{ id, label, icon?,\n * widgets, layout, emptyText? }`), `defaultView` and `viewsLabel`. It keeps\n * the chosen view, writes it to the address (`?view=<id>`) and to the\n * user's preferences (the server, mirrored in the browser), and provides `cnPageViews` so a greeting header widget on\n * the page can draw the switch instead of the page.\n *\n * Which view opens: the address, then the stored view, then `defaultView`,\n * then the first view. An id that names no view is ignored at every step.\n *\n * Used by CnDashboardPage and CnDetailPage. Each renders the chosen view's\n * `layout` with its own grid, so a view looks exactly like the page's own\n * grid.\n *\n * With `userLayout` on, a user arranges each view's grid for themselves, as\n * they arrange the dashboard's own grid. The arrangement is stored per view,\n * per page and per user, through the same user-preference record the\n * dashboard uses (the server, mirrored in the browser). The manifest stays\n * the base: it decides which widgets a view has, the user decides where they\n * sit, and a reset returns the view to the manifest.\n *\n * @mixin pageViews\n * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { readUserPreference, USER_PREFERENCE_STORAGE_PREFIX, writeUserPreference } from '../composables/useUserPreferences.js'\nimport { dashboardLayoutKey, LAYOUT_GEOMETRY_FIELDS, mergeUserLayout, resolveUserLayoutApi } from '../store/plugins/dashboardLayouts.js'\n\n/** The query parameter that carries the chosen view. */\nexport const PAGE_VIEW_QUERY_KEY = 'view'\n\n/** Prefix of the user-preference key; the page id follows it. */\nexport const PAGE_VIEW_PREFERENCE_PREFIX = 'cn_page_view:'\n\n/**\n * The page id one view's user layout is stored under: the page's own id,\n * then `.view.`, then the view id. Spelled once, because a read and a write\n * that disagree about the key read as an arrangement that never persists.\n *\n * @param {string} pageKey The page's id.\n * @param {string} viewId The view id.\n * @return {string} The page id handed to the layout store.\n * @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves\n */\nexport function pageViewLayoutId(pageKey, viewId) {\n\treturn `${String(pageKey || 'page')}.view.${String(viewId)}`\n}\n\nlet regionSeq = 0\n\n/**\n * Read the browser mirror of a user preference synchronously, so a stored\n * value is on screen from the first frame. Errors read as nothing stored.\n *\n * @param {string} appId The app id.\n * @param {string} key The preference key.\n * @return {object|string|number|boolean|Array|null} The stored value, or null.\n * @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves\n */\nfunction readPreferenceMirror(appId, key) {\n\ttry {\n\t\tif (typeof localStorage === 'undefined') {\n\t\t\treturn null\n\t\t}\n\t\tconst raw = localStorage.getItem(`${USER_PREFERENCE_STORAGE_PREFIX}${appId}:${key}`)\n\t\tif (raw === null || raw === undefined) {\n\t\t\treturn null\n\t\t}\n\t\treturn JSON.parse(raw)\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Read the browser mirror of the stored view synchronously, so the stored\n * view is on screen from the first frame.\n *\n * @param {string} appId The app id.\n * @param {string} key The preference key.\n * @return {string|null} The stored id, or null.\n * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-chosen-view-is-linkable-and-remembered\n */\nfunction readStoredViewMirror(appId, key) {\n\tconst value = readPreferenceMirror(appId, key)\n\treturn typeof value === 'string' && value !== '' ? value : null\n}\n\n/**\n * Whether a stored layout record holds an arrangement.\n *\n * @param {object|Array<object>|null} record The record, or its items.\n * @return {boolean}\n */\nfunction holdsArrangement(record) {\n\tconst items = Array.isArray(record) ? record : (record && Array.isArray(record.items) ? record.items : [])\n\treturn items.length > 0\n}\n\nexport const pageViews = {\n\tinject: {\n\t\t/** The host app id, provided by CnAppRoot; keys the stored view. */\n\t\tcnPageViewsAppId: { from: 'cnAppId', default: '' },\n\t},\n\n\temits: ['view-layout-change', 'view-layout-reset'],\n\n\tprops: {\n\t\t/**\n\t\t * Views of this page, each its own widget grid behind one segmented\n\t\t * control. Each is `{ id, label, icon?, widgets, layout, emptyText? }`:\n\t\t * `widgets` and `layout` have the same shape as the page's own. The\n\t\t * chosen view renders in a region below the page's own grid. Empty\n\t\t * (the default) draws no switch and no region.\n\t\t *\n\t\t * @type {Array<{id: string, label: string, icon?: string, widgets?: Array<object>, layout?: Array<object>, emptyText?: string}>}\n\t\t */\n\t\tviews: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The id of the view chosen when neither the address nor the browser\n\t\t * store names one. Empty chooses the first view.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdefaultView: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Accessible name of the view switch (through the host translate\n\t\t * function). Empty reads \"View\".\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tviewsLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\tregionSeq += 1\n\t\treturn {\n\t\t\t/** The view the user chose in this session, or the stored one. */\n\t\t\tselectedViewId: null,\n\t\t\t/** Whether the user chose a view in this session (a late server read then leaves it alone). */\n\t\t\tviewChosen: false,\n\t\t\t/** Id of the view region, the target of the options' `aria-controls`. */\n\t\t\tviewRegionId: `cn-page-view-region-${regionSeq}`,\n\t\t\t/**\n\t\t\t * This user's arrangement of each view, by view id, when the page\n\t\t\t * keeps a user layout. A separate copy, never the view's `layout`:\n\t\t\t * that array is the manifest, and writing a user's drag into it\n\t\t\t * would rearrange the view for everybody.\n\t\t\t *\n\t\t\t * @type {Record<string, Array<object>>}\n\t\t\t */\n\t\t\tviewUserLayouts: {},\n\t\t\t/** The views whose arrangement changed since the last save, by id. */\n\t\t\tviewUserLayoutsDirty: {},\n\t\t}\n\t},\n\n\t/**\n\t * Provide the views to descendants, so a greeting header widget in the\n\t * page's grid can draw the switch. Getters keep it reactive.\n\t *\n\t * @return {object} The provided `cnPageViews`.\n\t */\n\tprovide() {\n\t\tconst vm = this\n\t\treturn {\n\t\t\tcnPageViews: {\n\t\t\t\tget views() { return vm.normalizedViews },\n\t\t\t\tget activeId() { return vm.activeViewId },\n\t\t\t\tget regionId() { return vm.viewRegionId },\n\t\t\t\tselect: (id) => vm.selectView(id),\n\t\t\t},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The declared views in one shape, labels translated. Entries without\n\t\t * an id or a label are dropped, as are repeated ids.\n\t\t *\n\t\t * @return {Array<{id: string, label: string, icon: string, widgets: Array<object>, layout: Array<object>, emptyText: string}>}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid\n\t\t */\n\t\tnormalizedViews() {\n\t\t\tconst translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t\tconst seen = new Set()\n\t\t\treturn (Array.isArray(this.views) ? this.views : [])\n\t\t\t\t.filter((view) => {\n\t\t\t\t\tif (!view || typeof view !== 'object' || view.id === undefined || view.id === null || view.id === '') {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof view.label !== 'string' || view.label === '' || seen.has(String(view.id))) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tseen.add(String(view.id))\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t\t.map((view) => ({\n\t\t\t\t\tid: String(view.id),\n\t\t\t\t\tlabel: translate(view.label),\n\t\t\t\t\ticon: typeof view.icon === 'string' ? view.icon : '',\n\t\t\t\t\twidgets: Array.isArray(view.widgets) ? view.widgets : [],\n\t\t\t\t\tlayout: Array.isArray(view.layout) ? view.layout : [],\n\t\t\t\t\temptyText: typeof view.emptyText === 'string' && view.emptyText !== '' ? translate(view.emptyText) : '',\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Whether this page has views.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\thasViews() {\n\t\t\treturn this.normalizedViews.length > 0\n\t\t},\n\n\t\t/**\n\t\t * The chosen view's id: the address, then this session's or the\n\t\t * stored choice, then `defaultView`, then the first view.\n\t\t *\n\t\t * @return {string|null} The id, or null without views.\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-chosen-view-is-linkable-and-remembered\n\t\t */\n\t\tactiveViewId() {\n\t\t\tconst views = this.normalizedViews\n\t\t\tif (views.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst known = (id) => id !== null && id !== undefined && views.some((view) => view.id === String(id))\n\t\t\tconst fromAddress = this.$route && this.$route.query ? this.$route.query[PAGE_VIEW_QUERY_KEY] : undefined\n\t\t\tif (known(fromAddress)) {\n\t\t\t\treturn String(fromAddress)\n\t\t\t}\n\t\t\tif (known(this.selectedViewId)) {\n\t\t\t\treturn String(this.selectedViewId)\n\t\t\t}\n\t\t\tif (known(this.defaultView)) {\n\t\t\t\treturn this.defaultView\n\t\t\t}\n\t\t\treturn views[0].id\n\t\t},\n\n\t\t/**\n\t\t * The chosen view, or null without views.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tactiveView() {\n\t\t\treturn this.normalizedViews.find((view) => view.id === this.activeViewId) || null\n\t\t},\n\n\t\t/**\n\t\t * Whether a user arranges the views for themselves (`userLayout`).\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves\n\t\t */\n\t\tviewUserLayoutOn() {\n\t\t\treturn this.userLayout === true && this.hasViews\n\t\t},\n\n\t\t/**\n\t\t * The chosen view's layout: this user's arrangement when they have\n\t\t * one, the manifest's otherwise.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t * @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves\n\t\t */\n\t\tactiveViewLayout() {\n\t\t\tconst view = this.activeView\n\t\t\tif (!view) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst arranged = this.viewUserLayoutOn ? this.viewUserLayouts[view.id] : null\n\t\t\treturn Array.isArray(arranged) ? arranged : view.layout\n\t\t},\n\n\t\t/**\n\t\t * The switch's options, for CnSegmentedControl.\n\t\t *\n\t\t * @return {Array<{value: string, label: string}>}\n\t\t */\n\t\tviewSwitchOptions() {\n\t\t\treturn this.normalizedViews.map((view) => ({ value: view.id, label: view.label }))\n\t\t},\n\n\t\t/**\n\t\t * The switch's accessible name: `viewsLabel` translated, else \"View\".\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-view-switch-is-accessible\n\t\t */\n\t\tviewSwitchLabel() {\n\t\t\tconst translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t\treturn this.viewsLabel ? translate(this.viewsLabel) : t('nextcloud-vue', 'Views')\n\t\t},\n\n\t\t/**\n\t\t * Whether a greeting header in the page's own widgets draws the\n\t\t * switch (an option with a `view`), so the page draws none.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-greeting-header-can-switch-the-pages-views\n\t\t */\n\t\tviewSwitchInHeaderWidget() {\n\t\t\tconst widgets = Array.isArray(this.widgets) ? this.widgets : []\n\t\t\treturn widgets.some((def) => {\n\t\t\t\tconst options = def && def.content && def.content.views && def.content.views.options\n\t\t\t\treturn Array.isArray(options) && options.some((option) => option && typeof option.view === 'string' && option.view !== '')\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether the page draws the switch itself.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tshowsPageViewSwitch() {\n\t\t\treturn this.hasViews && !this.viewSwitchInHeaderWidget\n\t\t},\n\n\t\t/**\n\t\t * The user-preference key for this page's chosen view.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tviewPreferenceKey() {\n\t\t\t// The page id, else the route name: a detail page's fallback id is\n\t\t\t// its record's title, which would remember a view per record.\n\t\t\tconst id = this.pageId || (this.$route && this.$route.name) || this.resolvedPageId || 'page'\n\t\t\treturn `${PAGE_VIEW_PREFERENCE_PREFIX}${id}`\n\t\t},\n\n\t\t/**\n\t\t * The app id the stored view is kept under: the page's `appId` prop\n\t\t * when it has one, else the app root's.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tviewPreferenceAppId() {\n\t\t\tconst own = typeof this.appId === 'string' ? this.appId : ''\n\t\t\treturn own || (typeof this.cnPageViewsAppId === 'string' ? this.cnPageViewsAppId : '')\n\t\t},\n\n\t\t/**\n\t\t * The message of the chosen view's empty state.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so\n\t\t */\n\t\tviewEmptyText() {\n\t\t\tif (this.activeView && this.activeView.emptyText) {\n\t\t\t\treturn this.activeView.emptyText\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'This view has no widgets yet.')\n\t\t},\n\t},\n\n\twatch: {\n\t\t/**\n\t\t * Read the user's arrangement of a view the first time it is shown.\n\t\t *\n\t\t * @param {string|null} id The chosen view id.\n\t\t * @return {void}\n\t\t */\n\t\tactiveViewId(id) {\n\t\t\tthis.loadViewUserLayout(id)\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.loadStoredView()\n\t\tthis.loadViewUserLayout(this.activeViewId)\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the stored view: the browser mirror at once, then the user\n\t\t * preference on the server, which wins unless the user already chose.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-chosen-view-is-linkable-and-remembered\n\t\t */\n\t\tasync loadStoredView() {\n\t\t\tif (!this.hasViews) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst appId = this.viewPreferenceAppId\n\t\t\tconst key = this.viewPreferenceKey\n\t\t\tthis.selectedViewId = readStoredViewMirror(appId, key)\n\t\t\tif (!appId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst stored = await readUserPreference(appId, key, null)\n\t\t\tif (!this.viewChosen && typeof stored === 'string' && stored !== '') {\n\t\t\t\tthis.selectedViewId = stored\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Choose a view: keep it, store it, and write it to the address\n\t\t * (replacing the history entry, keeping the other query keys).\n\t\t *\n\t\t * @param {string} id The view id.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-the-chosen-view-is-linkable-and-remembered\n\t\t */\n\t\tselectView(id) {\n\t\t\tconst view = this.normalizedViews.find((candidate) => candidate.id === String(id))\n\t\t\tif (!view) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.selectedViewId = view.id\n\t\t\tthis.viewChosen = true\n\t\t\twriteUserPreference(this.viewPreferenceAppId, this.viewPreferenceKey, view.id)\n\t\t\tif (!this.$router || !this.$route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst query = this.$route.query || {}\n\t\t\tif (query[PAGE_VIEW_QUERY_KEY] === view.id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst result = this.$router.replace({ query: { ...query, [PAGE_VIEW_QUERY_KEY]: view.id } })\n\t\t\tif (result && typeof result.catch === 'function') {\n\t\t\t\tresult.catch(() => {})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Look a widget definition up in the views, for a layout item of the\n\t\t * chosen view.\n\t\t *\n\t\t * @param {string} widgetId The widget id.\n\t\t * @return {object|null} The definition, or null.\n\t\t */\n\t\tfindViewWidget(widgetId) {\n\t\t\tconst view = this.activeView\n\t\t\tif (!view) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn view.widgets.find((def) => def && def.id === widgetId) || null\n\t\t},\n\n\t\t/**\n\t\t * The page id a view's arrangement is stored under.\n\t\t *\n\t\t * @param {string} viewId The view id.\n\t\t * @return {string}\n\t\t * @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves\n\t\t */\n\t\tviewLayoutPageId(viewId) {\n\t\t\tconst pageKey = this.viewPreferenceKey.slice(PAGE_VIEW_PREFERENCE_PREFIX.length)\n\t\t\treturn pageViewLayoutId(pageKey, viewId)\n\t\t},\n\n\t\t/**\n\t\t * The layout store calls: the page's own (`userLayoutApi`, so an\n\t\t * injected `userLayoutStore` covers the views too), else the user\n\t\t * preferences.\n\t\t *\n\t\t * @return {{load: (appId: string, pageId: string) => Promise<object|null>, save: (appId: string, pageId: string, layout: Array<object>) => Promise<boolean>, reset: (appId: string, pageId: string) => Promise<boolean>}}\n\t\t */\n\t\tviewLayoutApi() {\n\t\t\tif (typeof this.userLayoutApi === 'function') {\n\t\t\t\treturn this.userLayoutApi()\n\t\t\t}\n\t\t\treturn resolveUserLayoutApi(null)\n\t\t},\n\n\t\t/**\n\t\t * Read this user's arrangement of one view, once: the browser mirror\n\t\t * at once, then the server, which wins unless the user already moved\n\t\t * something in that view. A page without `userLayout` reads nothing.\n\t\t *\n\t\t * @param {string|null} viewId The view id.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves\n\t\t */\n\t\tasync loadViewUserLayout(viewId) {\n\t\t\tif (!this.viewUserLayoutOn || viewId === null || viewId === undefined) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst view = this.normalizedViews.find((candidate) => candidate.id === String(viewId))\n\t\t\tif (!view || Array.isArray(this.viewUserLayouts[view.id])) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst appId = this.viewPreferenceAppId\n\t\t\tconst pageId = this.viewLayoutPageId(view.id)\n\t\t\tconst mirror = readPreferenceMirror(appId, dashboardLayoutKey(pageId))\n\t\t\tthis.viewUserLayouts = { ...this.viewUserLayouts, [view.id]: mergeUserLayout(view.layout, holdsArrangement(mirror) ? mirror : null) }\n\t\t\tif (!appId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet record\n\t\t\ttry {\n\t\t\t\trecord = await this.viewLayoutApi().load(appId, pageId)\n\t\t\t} catch {\n\t\t\t\t// The mirror, or the manifest, stands: a store that cannot\n\t\t\t\t// answer leaves the view as the user last saw it.\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.viewUserLayoutsDirty[view.id] || !holdsArrangement(record)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.viewUserLayouts = { ...this.viewUserLayouts, [view.id]: mergeUserLayout(view.layout, record) }\n\t\t},\n\n\t\t/**\n\t\t * Store the arrangement of every view the user changed since the last\n\t\t * save. The page calls it when the user leaves edit mode, so a drag\n\t\t * is not a write per pixel gesture.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves\n\t\t */\n\t\tasync saveViewUserLayouts() {\n\t\t\tif (!this.viewUserLayoutOn) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst ids = Object.keys(this.viewUserLayoutsDirty).filter((id) => this.viewUserLayoutsDirty[id])\n\t\t\tthis.viewUserLayoutsDirty = {}\n\t\t\tconst appId = this.viewPreferenceAppId\n\t\t\tfor (const id of ids) {\n\t\t\t\tconst pageId = this.viewLayoutPageId(id)\n\t\t\t\tconst items = this.viewUserLayouts[id] || []\n\t\t\t\ttry {\n\t\t\t\t\tif (appId) {\n\t\t\t\t\t\tawait this.viewLayoutApi().save(appId, pageId, items)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// No app to address: keep it in this browser only, as\n\t\t\t\t\t\t// the chosen view is kept.\n\t\t\t\t\t\tawait writeUserPreference('', dashboardLayoutKey(pageId), { items: items.map((item) => this.viewGeometry(item, true)) })\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// The arrangement stands for this session. A toast on a\n\t\t\t\t\t// failed layout write is noise on an instance without the\n\t\t\t\t\t// preference route.\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Drop this user's arrangement of one view and return it to the\n\t\t * manifest layout. Other views keep theirs.\n\t\t *\n\t\t * @param {string} [viewId] The view id; the chosen view when left out.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-resetting-a-view-restores-its-manifest-layout\n\t\t */\n\t\tasync resetViewUserLayout(viewId) {\n\t\t\tconst id = viewId === undefined || viewId === null ? this.activeViewId : String(viewId)\n\t\t\tconst view = this.normalizedViews.find((candidate) => candidate.id === id)\n\t\t\tif (!this.viewUserLayoutOn || !view) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst appId = this.viewPreferenceAppId\n\t\t\tconst pageId = this.viewLayoutPageId(view.id)\n\t\t\ttry {\n\t\t\t\tif (appId) {\n\t\t\t\t\tawait this.viewLayoutApi().reset(appId, pageId)\n\t\t\t\t} else {\n\t\t\t\t\tawait writeUserPreference('', dashboardLayoutKey(pageId), { items: [] })\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// The view returns to the manifest either way, which is what\n\t\t\t\t// the user asked for.\n\t\t\t}\n\t\t\tconst dirty = { ...this.viewUserLayoutsDirty }\n\t\t\tdelete dirty[view.id]\n\t\t\tthis.viewUserLayoutsDirty = dirty\n\t\t\tthis.viewUserLayouts = { ...this.viewUserLayouts, [view.id]: mergeUserLayout(view.layout, null) }\n\t\t\t/**\n\t\t\t * Emitted when the user drops their arrangement of a view and it\n\t\t\t * returns to the manifest layout.\n\t\t\t *\n\t\t\t * @event view-layout-reset\n\t\t\t * @type {{view: string}}\n\t\t\t */\n\t\t\tthis.$emit('view-layout-reset', { view: view.id })\n\t\t},\n\n\t\t/**\n\t\t * The geometry fields of one layout item, and nothing else.\n\t\t *\n\t\t * @param {object} item A layout item, or a grid update.\n\t\t * @param {boolean} [withWidgetId] Whether to keep the widget id too.\n\t\t * @return {object}\n\t\t */\n\t\tviewGeometry(item, withWidgetId = false) {\n\t\t\tconst geometry = withWidgetId ? { widgetId: String(item?.widgetId ?? '') } : {}\n\t\t\tfor (const field of LAYOUT_GEOMETRY_FIELDS) {\n\t\t\t\tif (item?.[field] !== undefined) {\n\t\t\t\t\tgeometry[field] = item[field]\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn geometry\n\t\t},\n\n\t\t/**\n\t\t * A drag or resize in the chosen view's grid. With `userLayout` it\n\t\t * moves this user's copy of the view and marks it for saving; the\n\t\t * view's manifest `layout` is not touched. Without it the geometry is\n\t\t * written back onto the layout items in place, as the page does for\n\t\t * its own layout, so the in-app manifest editor sees it.\n\t\t *\n\t\t * @param {Array<object>} updated The layout from the grid.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves\n\t\t */\n\t\tonViewLayoutChange(updated) {\n\t\t\tconst view = this.activeView\n\t\t\tif (!view || !Array.isArray(updated)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.viewUserLayoutOn) {\n\t\t\t\tconst current = Array.isArray(this.viewUserLayouts[view.id]) ? this.viewUserLayouts[view.id] : mergeUserLayout(view.layout, null)\n\t\t\t\tconst arranged = current.map((item) => {\n\t\t\t\t\tconst u = updated.find((candidate) => String(candidate.id) === String(item.id))\n\t\t\t\t\treturn u ? { ...item, ...this.viewGeometry(u) } : item\n\t\t\t\t})\n\t\t\t\tthis.viewUserLayouts = { ...this.viewUserLayouts, [view.id]: arranged }\n\t\t\t\tthis.viewUserLayoutsDirty = { ...this.viewUserLayoutsDirty, [view.id]: true }\n\t\t\t\tthis.$emit('view-layout-change', { view: view.id, layout: arranged })\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor (const u of updated) {\n\t\t\t\tconst item = view.layout.find((l) => String(l.id) === String(u.id))\n\t\t\t\tif (!item) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor (const key of ['gridX', 'gridY', 'gridWidth', 'gridHeight']) {\n\t\t\t\t\tif (u[key] !== undefined) {\n\t\t\t\t\t\titem[key] = u[key]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t/**\n\t\t\t * Emitted when a widget in a view's grid is dragged or resized.\n\t\t\t *\n\t\t\t * @event view-layout-change\n\t\t\t * @type {{view: string, layout: Array<object>}}\n\t\t\t */\n\t\t\tthis.$emit('view-layout-change', { view: view.id, layout: view.layout })\n\t\t},\n\t},\n}\n"],"names":["translate","t"],"mappings":"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"}
|
|
@@ -154,6 +154,40 @@ function mergeUserLayout(manifestLayout, record) {
|
|
|
154
154
|
return [...arranged, ...appended]
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* The three calls a user layout needs: from a store that carries the
|
|
159
|
+
* `dashboardLayoutsPlugin` actions, or straight over user preferences.
|
|
160
|
+
*
|
|
161
|
+
* Both paths write through `writeUserPreference`, so a record lands on the
|
|
162
|
+
* server and in the browser mirror whichever one a page uses. CnDashboardPage
|
|
163
|
+
* reads its own grid's record through this, and the page views mixin reads
|
|
164
|
+
* each view's record through it, so the two cannot disagree about where a
|
|
165
|
+
* user's arrangement lives.
|
|
166
|
+
*
|
|
167
|
+
* @param {object|null} [store] A store with `loadDashboardLayout`, `saveDashboardLayout` and `resetDashboardLayout`, or null.
|
|
168
|
+
* @return {{ load: (appId: string, pageId: string) => Promise<object|null>, save: (appId: string, pageId: string, layout: Array<object>) => Promise<boolean>, reset: (appId: string, pageId: string) => Promise<boolean> }} The api.
|
|
169
|
+
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves
|
|
170
|
+
*/
|
|
171
|
+
function resolveUserLayoutApi(store = null) {
|
|
172
|
+
if (store && typeof store.loadDashboardLayout === 'function') {
|
|
173
|
+
return {
|
|
174
|
+
load: (...a) => store.loadDashboardLayout(...a),
|
|
175
|
+
save: (...a) => store.saveDashboardLayout(...a),
|
|
176
|
+
reset: (...a) => store.resetDashboardLayout(...a),
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
load: (appId, pageId) => readUserPreference(appId, dashboardLayoutKey(pageId), null),
|
|
182
|
+
save: (appId, pageId, layout) => writeUserPreference(
|
|
183
|
+
appId,
|
|
184
|
+
dashboardLayoutKey(pageId),
|
|
185
|
+
{ items: (Array.isArray(layout) ? layout : []).map(geometryOf).filter((entry) => entry.widgetId !== '') },
|
|
186
|
+
),
|
|
187
|
+
reset: (appId, pageId) => writeUserPreference(appId, dashboardLayoutKey(pageId), { items: [] }),
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
157
191
|
/**
|
|
158
192
|
* Plugin definition.
|
|
159
193
|
*
|
|
@@ -259,5 +293,5 @@ function dashboardLayoutsPlugin(options = {}) {
|
|
|
259
293
|
}
|
|
260
294
|
}
|
|
261
295
|
|
|
262
|
-
export { LAYOUT_GEOMETRY_FIELDS, dashboardLayoutKey, dashboardLayoutsPlugin, mergeUserLayout };
|
|
296
|
+
export { LAYOUT_GEOMETRY_FIELDS, dashboardLayoutKey, dashboardLayoutsPlugin, mergeUserLayout, resolveUserLayoutApi };
|
|
263
297
|
//# sourceMappingURL=dashboardLayouts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboardLayouts.js","sources":["../../../../src/store/plugins/dashboardLayouts.js"],"sourcesContent":["/**\n * Per-user dashboard layouts for the object store.\n *\n * A manifest dashboard is the same page for everybody. This plugin lets one\n * user keep their own arrangement of it without changing what anybody else\n * sees: the manifest layout stays the base and the reset target, and the\n * user's record is a thin overlay of geometry on top of it.\n *\n * 🔴 THE MANIFEST WINS ON MEMBERSHIP, THE USER WINS ON GEOMETRY. A widget the\n * admin removes disappears for everyone, stored record or not; a widget the\n * admin adds appears for everyone, at the end of the user's grid. What the\n * user owns is where their widgets sit, not which widgets exist. Reading it\n * the other way round would let a stored record resurrect a widget the admin\n * deliberately took off the page, and nothing on screen would say why it was\n * back.\n *\n * 🔴 IT WRITES THROUGH `writeUserPreference`, NOT A SECOND HTTP PATH. That\n * helper already addresses the app's preference route, mirrors to the browser\n * so a layout survives an instance without the endpoint, and refuses an SPA\n * shell answering 200 with HTML. A second client here would be a second answer\n * about where a user's settings live.\n *\n * State added:\n * - `dashboardLayouts` — stored records by page id\n *\n * Actions added:\n * - `loadDashboardLayout(appId, pageId)` — read this user's record\n * - `saveDashboardLayout(appId, pageId, layout)` — store the geometry\n * - `resetDashboardLayout(appId, pageId)` — drop the record\n *\n * @example\n * import { createObjectStore, dashboardLayoutsPlugin } from '@conduction/nextcloud-vue'\n *\n * const useMyStore = createObjectStore('myapp', {\n * plugins: [dashboardLayoutsPlugin()],\n * })\n *\n * const store = useMyStore()\n * await store.loadDashboardLayout('dossiq', 'Dashboard')\n * await store.saveDashboardLayout('dossiq', 'Dashboard', grid)\n *\n * @return {object} Plugin definition\n */\n\nimport {\n\treadUserPreference,\n\twriteUserPreference,\n} from '../../composables/useUserPreferences.js'\n\n/**\n * The preference key one page's layout is stored under.\n *\n * Keyed by page so two dashboards in one app do not overwrite each other. The\n * prefix is spelled once here because a read and a write that disagree about\n * the key read as a layout that never persists.\n *\n * @param {string} pageId The manifest page id.\n * @return {string} The preference key.\n */\nexport function dashboardLayoutKey(pageId) {\n\treturn 'dashboard-layout.' + String(pageId || '')\n}\n\n/**\n * The geometry fields a stored record keeps.\n *\n * Only these five travel. A record that carried the whole layout item would\n * carry the widget's title, its style and its config, and the next manifest\n * change would be silently overridden by a copy the user never edited.\n *\n * @type {Array<string>}\n */\nexport const LAYOUT_GEOMETRY_FIELDS = ['gridX', 'gridY', 'gridWidth', 'gridHeight']\n\n/**\n * One layout item reduced to what a user record stores.\n *\n * @param {object} item The layout item.\n * @return {object} The stored entry.\n */\nfunction geometryOf(item) {\n\tconst entry = { widgetId: String(item?.widgetId ?? '') }\n\tfor (const field of LAYOUT_GEOMETRY_FIELDS) {\n\t\tif (item?.[field] !== undefined) {\n\t\t\tentry[field] = item[field]\n\t\t}\n\t}\n\n\treturn entry\n}\n\n/**\n * Merge a user's stored record over the manifest layout.\n *\n * The rule, in the order it is applied:\n *\n * 1. The manifest layout is the base. Every widget it declares is present and\n * every widget it does not declare is absent, whatever the record says.\n * 2. A widget with a stored entry takes that entry's geometry.\n * 3. A widget with no stored entry keeps its manifest position, so a page that\n * gains a widget shows it rather than hiding it until the user next edits.\n * 4. The user's own order is kept for the widgets they arranged, and widgets\n * the admin added since are appended after them.\n *\n * Pure on purpose: this is the rule the whole change turns on, and a rule that\n * can only be exercised by mounting a grid is a rule nobody tests at the edges.\n *\n * @param {Array<object>} manifestLayout The layout the manifest ships.\n * @param {object|Array<object>|null} record The user's stored record, or its items.\n * @return {Array<object>} The merged layout.\n */\nexport function mergeUserLayout(manifestLayout, record) {\n\tconst base = Array.isArray(manifestLayout) ? manifestLayout : []\n\tconst items = Array.isArray(record) ? record : (record?.items || [])\n\tif (!Array.isArray(items) || items.length === 0) {\n\t\treturn [...base]\n\t}\n\n\tconst stored = new Map()\n\tfor (const entry of items) {\n\t\tconst widgetId = String(entry?.widgetId ?? '')\n\t\tif (widgetId !== '') {\n\t\t\tstored.set(widgetId, entry)\n\t\t}\n\t}\n\n\tconst arranged = []\n\tconst appended = []\n\n\tfor (const item of base) {\n\t\tconst widgetId = String(item?.widgetId ?? '')\n\t\tconst entry = stored.get(widgetId)\n\t\tif (!entry) {\n\t\t\t// A widget the admin added since the user last arranged the page.\n\t\t\t// It goes after what they arranged rather than into the middle of\n\t\t\t// it, where it would push their own grid around.\n\t\t\tappended.push({ ...item })\n\t\t\tcontinue\n\t\t}\n\n\t\tconst merged = { ...item }\n\t\tfor (const field of LAYOUT_GEOMETRY_FIELDS) {\n\t\t\tif (entry[field] !== undefined) {\n\t\t\t\tmerged[field] = entry[field]\n\t\t\t}\n\t\t}\n\t\tarranged.push(merged)\n\t}\n\n\t// The user's own order, for the widgets they arranged.\n\tarranged.sort((a, b) => {\n\t\tconst order = [...stored.keys()]\n\t\treturn order.indexOf(String(a.widgetId)) - order.indexOf(String(b.widgetId))\n\t})\n\n\treturn [...arranged, ...appended]\n}\n\n/**\n * Plugin definition.\n *\n * @param {object} [options] Options.\n * @param {object} [options.http] An axios-shaped client, for tests.\n * @param {object} [options.storage] A localStorage-shaped store, for tests.\n * @return {object} Plugin definition\n */\nexport function dashboardLayoutsPlugin(options = {}) {\n\treturn {\n\t\tname: 'dashboardLayouts',\n\n\t\tstate: () => ({\n\t\t\t/**\n\t\t\t * Stored layout records by page id.\n\t\t\t *\n\t\t\t * @type {Record<string, object|null>}\n\t\t\t */\n\t\t\tdashboardLayouts: {},\n\t\t}),\n\n\t\tactions: {\n\t\t\t/**\n\t\t\t * Read this user's stored layout for one page.\n\t\t\t *\n\t\t\t * Answers null when there is none, which is what a page with no\n\t\t\t * user record must show: the manifest layout, untouched. An empty\n\t\t\t * array would read the same to a caller that only checks length,\n\t\t\t * so null is returned and the difference is stated.\n\t\t\t *\n\t\t\t * @param {string} appId The Nextcloud app id.\n\t\t\t * @param {string} pageId The manifest page id.\n\t\t\t * @return {Promise<object|null>} The record, or null.\n\t\t\t */\n\t\t\tasync loadDashboardLayout(appId, pageId) {\n\t\t\t\tconst record = await readUserPreference(\n\t\t\t\t\tappId,\n\t\t\t\t\tdashboardLayoutKey(pageId),\n\t\t\t\t\tnull,\n\t\t\t\t\toptions,\n\t\t\t\t)\n\n\t\t\t\tconst items = Array.isArray(record) ? record : record?.items\n\t\t\t\tconst stored = Array.isArray(items) && items.length > 0\n\t\t\t\t\t? { items }\n\t\t\t\t\t: null\n\n\t\t\t\tthis.dashboardLayouts = { ...this.dashboardLayouts, [pageId]: stored }\n\n\t\t\t\treturn stored\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Store this user's geometry for one page.\n\t\t\t *\n\t\t\t * Only the geometry is stored, never the whole layout item: a\n\t\t\t * record carrying a widget's title and config would override the\n\t\t\t * next manifest change with a copy the user never edited.\n\t\t\t *\n\t\t\t * @param {string} appId The Nextcloud app id.\n\t\t\t * @param {string} pageId The manifest page id.\n\t\t\t * @param {Array<object>} layout The layout as it now stands.\n\t\t\t * @return {Promise<boolean>} Whether it reached the server.\n\t\t\t */\n\t\t\tasync saveDashboardLayout(appId, pageId, layout) {\n\t\t\t\tconst items = (Array.isArray(layout) ? layout : [])\n\t\t\t\t\t.map(geometryOf)\n\t\t\t\t\t.filter((entry) => entry.widgetId !== '')\n\n\t\t\t\tthis.dashboardLayouts = { ...this.dashboardLayouts, [pageId]: { items } }\n\n\t\t\t\treturn await writeUserPreference(\n\t\t\t\t\tappId,\n\t\t\t\t\tdashboardLayoutKey(pageId),\n\t\t\t\t\t{ items },\n\t\t\t\t\toptions,\n\t\t\t\t)\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Drop this user's record, returning the page to the manifest.\n\t\t\t *\n\t\t\t * An empty record is written rather than a delete, because the\n\t\t\t * preference route answers one verb for a value and a written\n\t\t\t * empty reads back as \"no arrangement\" on every instance,\n\t\t\t * including one whose endpoint has no delete at all.\n\t\t\t *\n\t\t\t * @param {string} appId The Nextcloud app id.\n\t\t\t * @param {string} pageId The manifest page id.\n\t\t\t * @return {Promise<boolean>} Whether it reached the server.\n\t\t\t */\n\t\t\tasync resetDashboardLayout(appId, pageId) {\n\t\t\t\tthis.dashboardLayouts = { ...this.dashboardLayouts, [pageId]: null }\n\n\t\t\t\treturn await writeUserPreference(\n\t\t\t\t\tappId,\n\t\t\t\t\tdashboardLayoutKey(pageId),\n\t\t\t\t\t{ items: [] },\n\t\t\t\t\toptions,\n\t\t\t\t)\n\t\t\t},\n\t\t},\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dashboardLayouts.js","sources":["../../../../src/store/plugins/dashboardLayouts.js"],"sourcesContent":["/**\n * Per-user dashboard layouts for the object store.\n *\n * A manifest dashboard is the same page for everybody. This plugin lets one\n * user keep their own arrangement of it without changing what anybody else\n * sees: the manifest layout stays the base and the reset target, and the\n * user's record is a thin overlay of geometry on top of it.\n *\n * 🔴 THE MANIFEST WINS ON MEMBERSHIP, THE USER WINS ON GEOMETRY. A widget the\n * admin removes disappears for everyone, stored record or not; a widget the\n * admin adds appears for everyone, at the end of the user's grid. What the\n * user owns is where their widgets sit, not which widgets exist. Reading it\n * the other way round would let a stored record resurrect a widget the admin\n * deliberately took off the page, and nothing on screen would say why it was\n * back.\n *\n * 🔴 IT WRITES THROUGH `writeUserPreference`, NOT A SECOND HTTP PATH. That\n * helper already addresses the app's preference route, mirrors to the browser\n * so a layout survives an instance without the endpoint, and refuses an SPA\n * shell answering 200 with HTML. A second client here would be a second answer\n * about where a user's settings live.\n *\n * State added:\n * - `dashboardLayouts` — stored records by page id\n *\n * Actions added:\n * - `loadDashboardLayout(appId, pageId)` — read this user's record\n * - `saveDashboardLayout(appId, pageId, layout)` — store the geometry\n * - `resetDashboardLayout(appId, pageId)` — drop the record\n *\n * @example\n * import { createObjectStore, dashboardLayoutsPlugin } from '@conduction/nextcloud-vue'\n *\n * const useMyStore = createObjectStore('myapp', {\n * plugins: [dashboardLayoutsPlugin()],\n * })\n *\n * const store = useMyStore()\n * await store.loadDashboardLayout('dossiq', 'Dashboard')\n * await store.saveDashboardLayout('dossiq', 'Dashboard', grid)\n *\n * @return {object} Plugin definition\n */\n\nimport {\n\treadUserPreference,\n\twriteUserPreference,\n} from '../../composables/useUserPreferences.js'\n\n/**\n * The preference key one page's layout is stored under.\n *\n * Keyed by page so two dashboards in one app do not overwrite each other. The\n * prefix is spelled once here because a read and a write that disagree about\n * the key read as a layout that never persists.\n *\n * @param {string} pageId The manifest page id.\n * @return {string} The preference key.\n */\nexport function dashboardLayoutKey(pageId) {\n\treturn 'dashboard-layout.' + String(pageId || '')\n}\n\n/**\n * The geometry fields a stored record keeps.\n *\n * Only these five travel. A record that carried the whole layout item would\n * carry the widget's title, its style and its config, and the next manifest\n * change would be silently overridden by a copy the user never edited.\n *\n * @type {Array<string>}\n */\nexport const LAYOUT_GEOMETRY_FIELDS = ['gridX', 'gridY', 'gridWidth', 'gridHeight']\n\n/**\n * One layout item reduced to what a user record stores.\n *\n * @param {object} item The layout item.\n * @return {object} The stored entry.\n */\nfunction geometryOf(item) {\n\tconst entry = { widgetId: String(item?.widgetId ?? '') }\n\tfor (const field of LAYOUT_GEOMETRY_FIELDS) {\n\t\tif (item?.[field] !== undefined) {\n\t\t\tentry[field] = item[field]\n\t\t}\n\t}\n\n\treturn entry\n}\n\n/**\n * Merge a user's stored record over the manifest layout.\n *\n * The rule, in the order it is applied:\n *\n * 1. The manifest layout is the base. Every widget it declares is present and\n * every widget it does not declare is absent, whatever the record says.\n * 2. A widget with a stored entry takes that entry's geometry.\n * 3. A widget with no stored entry keeps its manifest position, so a page that\n * gains a widget shows it rather than hiding it until the user next edits.\n * 4. The user's own order is kept for the widgets they arranged, and widgets\n * the admin added since are appended after them.\n *\n * Pure on purpose: this is the rule the whole change turns on, and a rule that\n * can only be exercised by mounting a grid is a rule nobody tests at the edges.\n *\n * @param {Array<object>} manifestLayout The layout the manifest ships.\n * @param {object|Array<object>|null} record The user's stored record, or its items.\n * @return {Array<object>} The merged layout.\n */\nexport function mergeUserLayout(manifestLayout, record) {\n\tconst base = Array.isArray(manifestLayout) ? manifestLayout : []\n\tconst items = Array.isArray(record) ? record : (record?.items || [])\n\tif (!Array.isArray(items) || items.length === 0) {\n\t\treturn [...base]\n\t}\n\n\tconst stored = new Map()\n\tfor (const entry of items) {\n\t\tconst widgetId = String(entry?.widgetId ?? '')\n\t\tif (widgetId !== '') {\n\t\t\tstored.set(widgetId, entry)\n\t\t}\n\t}\n\n\tconst arranged = []\n\tconst appended = []\n\n\tfor (const item of base) {\n\t\tconst widgetId = String(item?.widgetId ?? '')\n\t\tconst entry = stored.get(widgetId)\n\t\tif (!entry) {\n\t\t\t// A widget the admin added since the user last arranged the page.\n\t\t\t// It goes after what they arranged rather than into the middle of\n\t\t\t// it, where it would push their own grid around.\n\t\t\tappended.push({ ...item })\n\t\t\tcontinue\n\t\t}\n\n\t\tconst merged = { ...item }\n\t\tfor (const field of LAYOUT_GEOMETRY_FIELDS) {\n\t\t\tif (entry[field] !== undefined) {\n\t\t\t\tmerged[field] = entry[field]\n\t\t\t}\n\t\t}\n\t\tarranged.push(merged)\n\t}\n\n\t// The user's own order, for the widgets they arranged.\n\tarranged.sort((a, b) => {\n\t\tconst order = [...stored.keys()]\n\t\treturn order.indexOf(String(a.widgetId)) - order.indexOf(String(b.widgetId))\n\t})\n\n\treturn [...arranged, ...appended]\n}\n\n/**\n * The three calls a user layout needs: from a store that carries the\n * `dashboardLayoutsPlugin` actions, or straight over user preferences.\n *\n * Both paths write through `writeUserPreference`, so a record lands on the\n * server and in the browser mirror whichever one a page uses. CnDashboardPage\n * reads its own grid's record through this, and the page views mixin reads\n * each view's record through it, so the two cannot disagree about where a\n * user's arrangement lives.\n *\n * @param {object|null} [store] A store with `loadDashboardLayout`, `saveDashboardLayout` and `resetDashboardLayout`, or null.\n * @return {{ load: (appId: string, pageId: string) => Promise<object|null>, save: (appId: string, pageId: string, layout: Array<object>) => Promise<boolean>, reset: (appId: string, pageId: string) => Promise<boolean> }} The api.\n * @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves\n */\nexport function resolveUserLayoutApi(store = null) {\n\tif (store && typeof store.loadDashboardLayout === 'function') {\n\t\treturn {\n\t\t\tload: (...a) => store.loadDashboardLayout(...a),\n\t\t\tsave: (...a) => store.saveDashboardLayout(...a),\n\t\t\treset: (...a) => store.resetDashboardLayout(...a),\n\t\t}\n\t}\n\n\treturn {\n\t\tload: (appId, pageId) => readUserPreference(appId, dashboardLayoutKey(pageId), null),\n\t\tsave: (appId, pageId, layout) => writeUserPreference(\n\t\t\tappId,\n\t\t\tdashboardLayoutKey(pageId),\n\t\t\t{ items: (Array.isArray(layout) ? layout : []).map(geometryOf).filter((entry) => entry.widgetId !== '') },\n\t\t),\n\t\treset: (appId, pageId) => writeUserPreference(appId, dashboardLayoutKey(pageId), { items: [] }),\n\t}\n}\n\n/**\n * Plugin definition.\n *\n * @param {object} [options] Options.\n * @param {object} [options.http] An axios-shaped client, for tests.\n * @param {object} [options.storage] A localStorage-shaped store, for tests.\n * @return {object} Plugin definition\n */\nexport function dashboardLayoutsPlugin(options = {}) {\n\treturn {\n\t\tname: 'dashboardLayouts',\n\n\t\tstate: () => ({\n\t\t\t/**\n\t\t\t * Stored layout records by page id.\n\t\t\t *\n\t\t\t * @type {Record<string, object|null>}\n\t\t\t */\n\t\t\tdashboardLayouts: {},\n\t\t}),\n\n\t\tactions: {\n\t\t\t/**\n\t\t\t * Read this user's stored layout for one page.\n\t\t\t *\n\t\t\t * Answers null when there is none, which is what a page with no\n\t\t\t * user record must show: the manifest layout, untouched. An empty\n\t\t\t * array would read the same to a caller that only checks length,\n\t\t\t * so null is returned and the difference is stated.\n\t\t\t *\n\t\t\t * @param {string} appId The Nextcloud app id.\n\t\t\t * @param {string} pageId The manifest page id.\n\t\t\t * @return {Promise<object|null>} The record, or null.\n\t\t\t */\n\t\t\tasync loadDashboardLayout(appId, pageId) {\n\t\t\t\tconst record = await readUserPreference(\n\t\t\t\t\tappId,\n\t\t\t\t\tdashboardLayoutKey(pageId),\n\t\t\t\t\tnull,\n\t\t\t\t\toptions,\n\t\t\t\t)\n\n\t\t\t\tconst items = Array.isArray(record) ? record : record?.items\n\t\t\t\tconst stored = Array.isArray(items) && items.length > 0\n\t\t\t\t\t? { items }\n\t\t\t\t\t: null\n\n\t\t\t\tthis.dashboardLayouts = { ...this.dashboardLayouts, [pageId]: stored }\n\n\t\t\t\treturn stored\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Store this user's geometry for one page.\n\t\t\t *\n\t\t\t * Only the geometry is stored, never the whole layout item: a\n\t\t\t * record carrying a widget's title and config would override the\n\t\t\t * next manifest change with a copy the user never edited.\n\t\t\t *\n\t\t\t * @param {string} appId The Nextcloud app id.\n\t\t\t * @param {string} pageId The manifest page id.\n\t\t\t * @param {Array<object>} layout The layout as it now stands.\n\t\t\t * @return {Promise<boolean>} Whether it reached the server.\n\t\t\t */\n\t\t\tasync saveDashboardLayout(appId, pageId, layout) {\n\t\t\t\tconst items = (Array.isArray(layout) ? layout : [])\n\t\t\t\t\t.map(geometryOf)\n\t\t\t\t\t.filter((entry) => entry.widgetId !== '')\n\n\t\t\t\tthis.dashboardLayouts = { ...this.dashboardLayouts, [pageId]: { items } }\n\n\t\t\t\treturn await writeUserPreference(\n\t\t\t\t\tappId,\n\t\t\t\t\tdashboardLayoutKey(pageId),\n\t\t\t\t\t{ items },\n\t\t\t\t\toptions,\n\t\t\t\t)\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Drop this user's record, returning the page to the manifest.\n\t\t\t *\n\t\t\t * An empty record is written rather than a delete, because the\n\t\t\t * preference route answers one verb for a value and a written\n\t\t\t * empty reads back as \"no arrangement\" on every instance,\n\t\t\t * including one whose endpoint has no delete at all.\n\t\t\t *\n\t\t\t * @param {string} appId The Nextcloud app id.\n\t\t\t * @param {string} pageId The manifest page id.\n\t\t\t * @return {Promise<boolean>} Whether it reached the server.\n\t\t\t */\n\t\t\tasync resetDashboardLayout(appId, pageId) {\n\t\t\t\tthis.dashboardLayouts = { ...this.dashboardLayouts, [pageId]: null }\n\n\t\t\t\treturn await writeUserPreference(\n\t\t\t\t\tappId,\n\t\t\t\t\tdashboardLayoutKey(pageId),\n\t\t\t\t\t{ items: [] },\n\t\t\t\t\toptions,\n\t\t\t\t)\n\t\t\t},\n\t\t},\n\t}\n}\n"],"names":[],"mappings":"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"}
|