@conduction/nextcloud-vue 0.1.0-beta.1 → 0.1.0-beta.11
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/README.md +226 -0
- package/css/index.css +5 -0
- package/dist/nextcloud-vue.cjs.js +79416 -7715
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.css +3583 -504
- package/dist/nextcloud-vue.esm.js +79343 -7692
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/l10n/en.json +164 -0
- package/l10n/nl.json +164 -0
- package/package.json +104 -63
- package/src/components/CnActionsBar/CnActionsBar.vue +254 -0
- package/src/components/CnActionsBar/index.js +1 -0
- package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +570 -0
- package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
- package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
- package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +422 -0
- package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
- package/src/components/CnAdvancedFormDialog/index.js +1 -0
- package/src/components/CnCard/CnCard.vue +415 -0
- package/src/components/CnCard/index.js +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +156 -152
- package/src/components/CnCardGrid/index.js +1 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
- package/src/components/CnCellRenderer/index.js +1 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +346 -0
- package/src/components/CnChartWidget/index.js +1 -0
- package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
- package/src/components/CnConfigurationCard/index.js +1 -1
- package/src/components/CnContextMenu/CnContextMenu.vue +142 -0
- package/src/components/CnContextMenu/index.js +1 -0
- package/src/components/CnCopyDialog/CnCopyDialog.vue +266 -0
- package/src/components/CnCopyDialog/index.js +1 -0
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +229 -0
- package/src/components/CnDashboardGrid/index.js +1 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +397 -0
- package/src/components/CnDashboardPage/index.js +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +362 -354
- package/src/components/CnDataTable/index.js +1 -1
- package/src/components/CnDeleteDialog/CnDeleteDialog.vue +177 -0
- package/src/components/CnDeleteDialog/index.js +1 -0
- package/src/components/CnDetailCard/CnDetailCard.vue +225 -0
- package/src/components/CnDetailCard/index.js +1 -0
- package/src/components/CnDetailGrid/CnDetailGrid.vue +256 -0
- package/src/components/CnDetailGrid/index.js +1 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +432 -0
- package/src/components/CnDetailPage/index.js +1 -0
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +234 -223
- package/src/components/CnFacetSidebar/index.js +1 -1
- package/src/components/CnFilterBar/CnFilterBar.vue +153 -152
- package/src/components/CnFilterBar/index.js +1 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +1047 -0
- package/src/components/CnFormDialog/index.js +1 -0
- package/src/components/CnIcon/CnIcon.vue +89 -0
- package/src/components/CnIcon/index.js +1 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +980 -682
- package/src/components/CnIndexPage/index.js +1 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +536 -0
- package/src/components/CnIndexSidebar/index.js +1 -0
- package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
- package/src/components/CnInfoWidget/index.js +1 -0
- package/src/components/CnItemCard/CnItemCard.vue +134 -0
- package/src/components/CnItemCard/index.js +1 -0
- package/src/components/CnJsonViewer/CnJsonViewer.vue +312 -0
- package/src/components/CnJsonViewer/index.js +1 -0
- package/src/components/CnKpiGrid/CnKpiGrid.vue +93 -89
- package/src/components/CnKpiGrid/index.js +1 -1
- package/src/components/CnMassActionBar/CnMassActionBar.vue +161 -160
- package/src/components/CnMassActionBar/index.js +1 -1
- package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +327 -320
- package/src/components/CnMassCopyDialog/index.js +1 -1
- package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +245 -238
- package/src/components/CnMassDeleteDialog/index.js +1 -1
- package/src/components/CnMassExportDialog/CnMassExportDialog.vue +191 -190
- package/src/components/CnMassExportDialog/index.js +1 -1
- package/src/components/CnMassImportDialog/CnMassImportDialog.vue +494 -491
- package/src/components/CnMassImportDialog/index.js +1 -1
- package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
- package/src/components/CnNoteCard/index.js +1 -0
- package/src/components/CnNotesCard/CnNotesCard.vue +416 -0
- package/src/components/CnNotesCard/index.js +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +294 -292
- package/src/components/CnObjectCard/index.js +1 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +854 -0
- package/src/components/CnObjectDataWidget/index.js +1 -0
- package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +289 -0
- package/src/components/CnObjectMetadataWidget/index.js +1 -0
- package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +369 -0
- package/src/components/CnObjectSidebar/CnFilesTab.vue +287 -0
- package/src/components/CnObjectSidebar/CnNotesTab.vue +250 -0
- package/src/components/CnObjectSidebar/CnObjectSidebar.vue +255 -0
- package/src/components/CnObjectSidebar/CnTagsTab.vue +259 -0
- package/src/components/CnObjectSidebar/CnTasksTab.vue +483 -0
- package/src/components/CnObjectSidebar/index.js +6 -0
- package/src/components/CnPageHeader/CnPageHeader.vue +61 -0
- package/src/components/CnPageHeader/index.js +1 -0
- package/src/components/CnPagination/CnPagination.vue +253 -252
- package/src/components/CnPagination/index.js +1 -1
- package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
- package/src/components/CnProgressBar/index.js +1 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +793 -0
- package/src/components/CnRegisterMapping/index.js +1 -0
- package/src/components/CnRowActions/CnRowActions.vue +95 -73
- package/src/components/CnRowActions/index.js +1 -1
- package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
- package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +788 -0
- package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
- package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
- package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
- package/src/components/CnSchemaFormDialog/index.js +1 -0
- package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
- package/src/components/CnSettingsCard/index.js +1 -1
- package/src/components/CnSettingsSection/CnSettingsSection.vue +267 -266
- package/src/components/CnSettingsSection/index.js +1 -1
- package/src/components/CnStatsBlock/CnStatsBlock.vue +437 -366
- package/src/components/CnStatsBlock/index.js +1 -1
- package/src/components/CnStatsPanel/CnStatsPanel.vue +321 -0
- package/src/components/CnStatsPanel/index.js +1 -0
- package/src/components/CnStatusBadge/CnStatusBadge.vue +90 -77
- package/src/components/CnStatusBadge/index.js +1 -1
- package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +545 -0
- package/src/components/CnTabbedFormDialog/index.js +1 -0
- package/src/components/CnTableWidget/CnTableWidget.vue +333 -0
- package/src/components/CnTableWidget/index.js +1 -0
- package/src/components/CnTasksCard/CnTasksCard.vue +374 -0
- package/src/components/CnTasksCard/index.js +1 -0
- package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
- package/src/components/CnTileWidget/index.js +1 -0
- package/src/components/CnTimelineStages/CnTimelineStages.vue +294 -0
- package/src/components/CnTimelineStages/index.js +1 -0
- package/src/components/CnUserActionMenu/CnUserActionMenu.vue +436 -0
- package/src/components/CnUserActionMenu/index.js +1 -0
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +313 -312
- package/src/components/CnVersionInfoCard/index.js +1 -1
- package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
- package/src/components/CnWidgetRenderer/index.js +1 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +248 -0
- package/src/components/CnWidgetWrapper/index.js +1 -0
- package/src/components/index.js +57 -25
- package/src/composables/index.js +5 -3
- package/src/composables/useContextMenu.js +126 -0
- package/src/composables/useDashboardView.js +286 -0
- package/src/composables/useDetailView.js +290 -132
- package/src/composables/useListView.js +364 -153
- package/src/composables/useSubResource.js +142 -142
- package/src/constants/metadata.js +30 -0
- package/src/css/CnSchemaFormDialog.css +546 -0
- package/src/css/__sample_nextcloud_tokens.css +110 -0
- package/src/css/actions-bar.css +54 -0
- package/src/css/badge.css +83 -51
- package/src/css/card.css +129 -128
- package/src/css/context-menu.css +20 -0
- package/src/css/dashboard.css +70 -0
- package/src/css/detail-page.css +235 -0
- package/src/css/detail.css +68 -68
- package/src/css/index-page.css +44 -0
- package/src/css/index-sidebar.css +193 -0
- package/src/css/index.css +17 -8
- package/src/css/layout.css +90 -90
- package/src/css/page-header.css +35 -0
- package/src/css/pagination.css +72 -72
- package/src/css/table.css +142 -143
- package/src/css/timeline-stages.css +220 -0
- package/src/css/utilities.css +46 -46
- package/src/index.js +95 -50
- package/src/l10n/index.js +12 -0
- package/src/mixins/gridLayout.js +118 -0
- package/src/store/createCrudStore.d.ts +350 -0
- package/src/store/createCrudStore.js +413 -0
- package/src/store/createSubResourcePlugin.js +125 -135
- package/src/store/index.js +4 -3
- package/src/store/pluginMerge.js +55 -0
- package/src/store/plugins/auditTrails.js +357 -17
- package/src/store/plugins/files.js +250 -186
- package/src/store/plugins/index.js +8 -4
- package/src/store/plugins/lifecycle.js +180 -180
- package/src/store/plugins/logs.d.ts +22 -0
- package/src/store/plugins/logs.js +172 -0
- package/src/store/plugins/registerMapping.js +195 -0
- package/src/store/plugins/relations.js +68 -68
- package/src/store/plugins/search.js +385 -0
- package/src/store/plugins/selection.js +104 -0
- package/src/store/useObjectStore.js +793 -625
- package/src/types/auditTrail.d.ts +32 -32
- package/src/types/file.d.ts +23 -23
- package/src/types/index.d.ts +67 -35
- package/src/types/notification.d.ts +36 -36
- package/src/types/object.d.ts +40 -40
- package/src/types/organisation.d.ts +41 -41
- package/src/types/register.d.ts +25 -25
- package/src/types/schema.d.ts +39 -39
- package/src/types/shared.d.ts +79 -79
- package/src/types/source.d.ts +14 -14
- package/src/types/task.d.ts +31 -31
- package/src/utils/errors.js +96 -96
- package/src/utils/getTheme.js +9 -0
- package/src/utils/headers.js +80 -44
- package/src/utils/id.js +13 -0
- package/src/utils/index.js +4 -3
- package/src/utils/schema.js +423 -287
- package/src/utils/widgetVisibility.js +162 -0
- package/src/components/CnDetailViewLayout/CnDetailViewLayout.vue +0 -88
- package/src/components/CnDetailViewLayout/index.js +0 -1
- package/src/components/CnEmptyState/CnEmptyState.vue +0 -78
- package/src/components/CnEmptyState/index.js +0 -1
- package/src/components/CnListViewLayout/CnListViewLayout.vue +0 -80
- package/src/components/CnListViewLayout/index.js +0 -1
- package/src/components/CnViewModeToggle/CnViewModeToggle.vue +0 -77
- package/src/components/CnViewModeToggle/index.js +0 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { ref, computed, onMounted } from 'vue'
|
|
2
|
+
import axios from '@nextcloud/axios'
|
|
3
|
+
import { generateOcsUrl } from '@nextcloud/router'
|
|
4
|
+
import { filterWidgetsByVisibility } from '../utils/widgetVisibility.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Composable for managing dashboard view state.
|
|
8
|
+
*
|
|
9
|
+
* Handles widget definition loading (including NC Dashboard API widgets),
|
|
10
|
+
* layout management, edit mode, and role-based widget visibility filtering.
|
|
11
|
+
*
|
|
12
|
+
* Widgets can specify a `visibility` property to control which users see them:
|
|
13
|
+
* ```js
|
|
14
|
+
* {
|
|
15
|
+
* id: 'kcc-search',
|
|
16
|
+
* type: 'custom',
|
|
17
|
+
* title: 'Quick Search',
|
|
18
|
+
* visibility: {
|
|
19
|
+
* users: ['admin'], // specific user IDs (optional)
|
|
20
|
+
* groups: ['KCC', 'Admins'], // Nextcloud group names (optional)
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
* If `visibility` is not set or both arrays are empty, the widget is visible to everyone.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} [options] Configuration options
|
|
27
|
+
* @param {Array} [options.widgets] Static widget definitions from the app
|
|
28
|
+
* @param {Array} [options.defaultLayout] Default layout if no saved layout exists
|
|
29
|
+
* @param {Function} [options.loadLayout] Async function that returns saved layout array, or null
|
|
30
|
+
* @param {Function} [options.saveLayout] Async function that persists layout: (layout) => Promise
|
|
31
|
+
* @param {boolean} [options.includeNcWidgets] Whether to also load NC Dashboard API widgets
|
|
32
|
+
* @param {number} [options.columns] Grid columns
|
|
33
|
+
* @return {object} Reactive state and methods for CnDashboardPage
|
|
34
|
+
*
|
|
35
|
+
* @example Basic usage with static widgets
|
|
36
|
+
* const { widgets, layout, loading, onLayoutChange } = useDashboardView({
|
|
37
|
+
* widgets: [
|
|
38
|
+
* { id: 'kpis', title: 'KPIs', type: 'custom' },
|
|
39
|
+
* { id: 'chart', title: 'Status Chart', type: 'custom' },
|
|
40
|
+
* ],
|
|
41
|
+
* defaultLayout: [
|
|
42
|
+
* { id: 1, widgetId: 'kpis', gridX: 0, gridY: 0, gridWidth: 12, gridHeight: 2 },
|
|
43
|
+
* { id: 2, widgetId: 'chart', gridX: 0, gridY: 2, gridWidth: 6, gridHeight: 4 },
|
|
44
|
+
* ],
|
|
45
|
+
* })
|
|
46
|
+
*
|
|
47
|
+
* @example With persistence and NC widgets
|
|
48
|
+
* const dashboard = useDashboardView({
|
|
49
|
+
* widgets: myWidgets,
|
|
50
|
+
* defaultLayout: defaultLayout,
|
|
51
|
+
* loadLayout: () => fetch('/api/dashboard-layout').then(r => r.json()),
|
|
52
|
+
* saveLayout: (layout) => fetch('/api/dashboard-layout', { method: 'PUT', body: JSON.stringify(layout) }),
|
|
53
|
+
* includeNcWidgets: true,
|
|
54
|
+
* })
|
|
55
|
+
*
|
|
56
|
+
* @example With role-based visibility
|
|
57
|
+
* const dashboard = useDashboardView({
|
|
58
|
+
* widgets: [
|
|
59
|
+
* { id: 'admin-panel', title: 'Admin Panel', type: 'custom', visibility: { groups: ['admin'] } },
|
|
60
|
+
* { id: 'kcc-search', title: 'KCC Search', type: 'custom', visibility: { groups: ['KCC'] } },
|
|
61
|
+
* { id: 'public-info', title: 'Info', type: 'custom' }, // visible to everyone
|
|
62
|
+
* ],
|
|
63
|
+
* defaultLayout: [...],
|
|
64
|
+
* })
|
|
65
|
+
*/
|
|
66
|
+
export function useDashboardView(options = {}) {
|
|
67
|
+
const opts = {
|
|
68
|
+
widgets: [],
|
|
69
|
+
defaultLayout: [],
|
|
70
|
+
loadLayout: null,
|
|
71
|
+
saveLayout: null,
|
|
72
|
+
includeNcWidgets: false,
|
|
73
|
+
columns: 12,
|
|
74
|
+
...options,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── State ────────────────────────────────────────────────────────────
|
|
78
|
+
const appWidgets = ref(opts.widgets)
|
|
79
|
+
const ncWidgets = ref([])
|
|
80
|
+
const visibleAppWidgets = ref([])
|
|
81
|
+
const visibleNcWidgets = ref([])
|
|
82
|
+
const layout = ref([])
|
|
83
|
+
const loading = ref(false)
|
|
84
|
+
const saving = ref(false)
|
|
85
|
+
const isEditing = ref(false)
|
|
86
|
+
|
|
87
|
+
// ── Computed ─────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/** All available widgets (app + NC Dashboard API), filtered by visibility */
|
|
90
|
+
const widgets = computed(() => {
|
|
91
|
+
return [...visibleAppWidgets.value, ...visibleNcWidgets.value]
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
/** Widget IDs currently on the dashboard */
|
|
95
|
+
const activeWidgetIds = computed(() => {
|
|
96
|
+
return layout.value.map(item => item.widgetId)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
/** Widgets not yet placed on the dashboard */
|
|
100
|
+
const availableWidgets = computed(() => {
|
|
101
|
+
return widgets.value.filter(w => !activeWidgetIds.value.includes(w.id))
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// ── Methods ──────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Apply visibility filtering to the current widget sets and update
|
|
108
|
+
* the layout to remove items whose widgets are no longer visible.
|
|
109
|
+
*/
|
|
110
|
+
async function applyVisibilityFilter() {
|
|
111
|
+
visibleAppWidgets.value = await filterWidgetsByVisibility(appWidgets.value)
|
|
112
|
+
visibleNcWidgets.value = await filterWidgetsByVisibility(ncWidgets.value)
|
|
113
|
+
|
|
114
|
+
// Remove layout items that reference widgets the user cannot see
|
|
115
|
+
const visibleIds = new Set(widgets.value.map(w => w.id))
|
|
116
|
+
const filteredLayout = layout.value.filter(item => visibleIds.has(item.widgetId))
|
|
117
|
+
if (filteredLayout.length !== layout.value.length) {
|
|
118
|
+
layout.value = filteredLayout
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Load NC Dashboard API widgets from the OCS endpoint.
|
|
124
|
+
*/
|
|
125
|
+
async function loadNcWidgets() {
|
|
126
|
+
try {
|
|
127
|
+
const url = generateOcsUrl('/apps/dashboard/api/v1/widgets')
|
|
128
|
+
const response = await axios.get(url)
|
|
129
|
+
const data = response.data?.ocs?.data || {}
|
|
130
|
+
|
|
131
|
+
ncWidgets.value = Object.values(data).map(w => ({
|
|
132
|
+
id: w.id,
|
|
133
|
+
title: w.title,
|
|
134
|
+
iconClass: w.icon_class,
|
|
135
|
+
iconUrl: w.icon_url,
|
|
136
|
+
widgetUrl: w.widget_url,
|
|
137
|
+
itemApiVersions: w.item_api_versions || [],
|
|
138
|
+
itemIconsRound: w.item_icons_round || false,
|
|
139
|
+
reloadInterval: w.reload_interval || 0,
|
|
140
|
+
buttons: w.buttons || [],
|
|
141
|
+
type: 'nc-widget',
|
|
142
|
+
}))
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('[useDashboardView] Failed to load NC widgets:', error)
|
|
145
|
+
ncWidgets.value = []
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Initialize the dashboard: load layout and optionally NC widgets.
|
|
151
|
+
*/
|
|
152
|
+
async function init() {
|
|
153
|
+
loading.value = true
|
|
154
|
+
try {
|
|
155
|
+
const tasks = []
|
|
156
|
+
|
|
157
|
+
if (opts.includeNcWidgets) {
|
|
158
|
+
tasks.push(loadNcWidgets())
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (opts.loadLayout) {
|
|
162
|
+
tasks.push(
|
|
163
|
+
opts.loadLayout().then(saved => {
|
|
164
|
+
if (saved && saved.length > 0) {
|
|
165
|
+
layout.value = saved
|
|
166
|
+
} else {
|
|
167
|
+
layout.value = [...opts.defaultLayout]
|
|
168
|
+
}
|
|
169
|
+
}),
|
|
170
|
+
)
|
|
171
|
+
} else {
|
|
172
|
+
layout.value = [...opts.defaultLayout]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
await Promise.all(tasks)
|
|
176
|
+
|
|
177
|
+
// Apply visibility filtering after all data is loaded
|
|
178
|
+
await applyVisibilityFilter()
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error('[useDashboardView] Init failed:', error)
|
|
181
|
+
layout.value = [...opts.defaultLayout]
|
|
182
|
+
} finally {
|
|
183
|
+
loading.value = false
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Handle layout change from the grid. Persists if saveLayout is provided.
|
|
189
|
+
*
|
|
190
|
+
* @param {Array} newLayout Updated layout array
|
|
191
|
+
*/
|
|
192
|
+
async function onLayoutChange(newLayout) {
|
|
193
|
+
layout.value = newLayout
|
|
194
|
+
|
|
195
|
+
if (opts.saveLayout) {
|
|
196
|
+
saving.value = true
|
|
197
|
+
try {
|
|
198
|
+
await opts.saveLayout(newLayout)
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error('[useDashboardView] Failed to save layout:', error)
|
|
201
|
+
} finally {
|
|
202
|
+
saving.value = false
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Add a widget to the dashboard at the next available position.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} widgetId Widget ID to add
|
|
211
|
+
* @param {object} [position] Override position { gridX, gridY, gridWidth, gridHeight }
|
|
212
|
+
*/
|
|
213
|
+
function addWidget(widgetId, position = {}) {
|
|
214
|
+
const maxId = layout.value.reduce((max, item) => {
|
|
215
|
+
const num = typeof item.id === 'number' ? item.id : 0
|
|
216
|
+
return num > max ? num : max
|
|
217
|
+
}, 0)
|
|
218
|
+
|
|
219
|
+
const maxY = layout.value.reduce((max, item) => {
|
|
220
|
+
const bottom = (item.gridY || 0) + (item.gridHeight || 3)
|
|
221
|
+
return bottom > max ? bottom : max
|
|
222
|
+
}, 0)
|
|
223
|
+
|
|
224
|
+
const newItem = {
|
|
225
|
+
id: maxId + 1,
|
|
226
|
+
widgetId,
|
|
227
|
+
gridX: position.gridX ?? 0,
|
|
228
|
+
gridY: position.gridY ?? maxY,
|
|
229
|
+
gridWidth: position.gridWidth ?? 6,
|
|
230
|
+
gridHeight: position.gridHeight ?? 3,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const newLayout = [...layout.value, newItem]
|
|
234
|
+
onLayoutChange(newLayout)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Remove a widget from the dashboard by layout item ID.
|
|
239
|
+
*
|
|
240
|
+
* @param {string|number} itemId Layout item ID to remove
|
|
241
|
+
*/
|
|
242
|
+
function removeWidget(itemId) {
|
|
243
|
+
const newLayout = layout.value.filter(item => item.id !== itemId)
|
|
244
|
+
onLayoutChange(newLayout)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Update app widget definitions (e.g., when data changes).
|
|
249
|
+
* Re-applies visibility filtering after update.
|
|
250
|
+
*
|
|
251
|
+
* @param {Array} newWidgets Updated widget definitions
|
|
252
|
+
*/
|
|
253
|
+
async function setWidgets(newWidgets) {
|
|
254
|
+
appWidgets.value = newWidgets
|
|
255
|
+
await applyVisibilityFilter()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── Lifecycle ────────────────────────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
onMounted(async () => {
|
|
261
|
+
await init()
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// ── Return ───────────────────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
// State
|
|
268
|
+
widgets,
|
|
269
|
+
layout,
|
|
270
|
+
loading,
|
|
271
|
+
saving,
|
|
272
|
+
isEditing,
|
|
273
|
+
|
|
274
|
+
// Derived
|
|
275
|
+
activeWidgetIds,
|
|
276
|
+
availableWidgets,
|
|
277
|
+
ncWidgets,
|
|
278
|
+
|
|
279
|
+
// Methods
|
|
280
|
+
onLayoutChange,
|
|
281
|
+
addWidget,
|
|
282
|
+
removeWidget,
|
|
283
|
+
setWidgets,
|
|
284
|
+
init,
|
|
285
|
+
}
|
|
286
|
+
}
|