@conduction/nextcloud-vue 0.1.0-beta.1 → 0.1.0-beta.10
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 +67614 -0
- package/dist/nextcloud-vue.cjs.js +76311 -5905
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.cjs.map +1 -0
- package/dist/nextcloud-vue.css +3279 -203
- package/dist/nextcloud-vue.esm.js +76240 -5882
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/package.json +89 -63
- package/src/components/CnActionsBar/CnActionsBar.vue +254 -0
- package/src/components/CnActionsBar/index.js +1 -0
- package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +569 -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 +23 -20
- package/src/components/CnCardGrid/index.js +1 -1
- package/src/components/CnCellRenderer/index.js +1 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +318 -0
- package/src/components/CnChartWidget/index.js +1 -0
- 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 +257 -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 +396 -0
- package/src/components/CnDashboardPage/index.js +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +24 -16
- 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 +254 -0
- package/src/components/CnDetailGrid/index.js +1 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +431 -0
- package/src/components/CnDetailPage/index.js +1 -0
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +12 -2
- package/src/components/CnFacetSidebar/index.js +1 -1
- package/src/components/CnFilterBar/index.js +1 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +934 -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 +589 -291
- package/src/components/CnIndexPage/index.js +1 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +535 -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 +283 -0
- package/src/components/CnJsonViewer/index.js +1 -0
- package/src/components/CnKpiGrid/CnKpiGrid.vue +5 -1
- package/src/components/CnKpiGrid/index.js +1 -1
- package/src/components/CnMassActionBar/CnMassActionBar.vue +6 -5
- package/src/components/CnMassActionBar/index.js +1 -1
- package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +16 -9
- package/src/components/CnMassCopyDialog/index.js +1 -1
- package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +16 -9
- package/src/components/CnMassDeleteDialog/index.js +1 -1
- package/src/components/CnMassExportDialog/CnMassExportDialog.vue +8 -7
- package/src/components/CnMassExportDialog/index.js +1 -1
- package/src/components/CnMassImportDialog/CnMassImportDialog.vue +20 -17
- 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 +415 -0
- package/src/components/CnNotesCard/index.js +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +3 -1
- package/src/components/CnObjectCard/index.js +1 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +853 -0
- package/src/components/CnObjectDataWidget/index.js +1 -0
- package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +288 -0
- package/src/components/CnObjectMetadataWidget/index.js +1 -0
- package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +368 -0
- package/src/components/CnObjectSidebar/CnFilesTab.vue +286 -0
- package/src/components/CnObjectSidebar/CnNotesTab.vue +249 -0
- package/src/components/CnObjectSidebar/CnObjectSidebar.vue +254 -0
- package/src/components/CnObjectSidebar/CnTagsTab.vue +258 -0
- package/src/components/CnObjectSidebar/CnTasksTab.vue +482 -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 +7 -6
- 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 +792 -0
- package/src/components/CnRegisterMapping/index.js +1 -0
- package/src/components/CnRowActions/CnRowActions.vue +25 -3
- package/src/components/CnRowActions/index.js +1 -1
- package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
- package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -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/index.js +1 -1
- package/src/components/CnSettingsSection/index.js +1 -1
- package/src/components/CnStatsBlock/CnStatsBlock.vue +89 -19
- package/src/components/CnStatsBlock/index.js +1 -1
- package/src/components/CnStatsPanel/CnStatsPanel.vue +320 -0
- package/src/components/CnStatsPanel/index.js +1 -0
- package/src/components/CnStatusBadge/CnStatusBadge.vue +15 -2
- package/src/components/CnStatusBadge/index.js +1 -1
- package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +544 -0
- package/src/components/CnTabbedFormDialog/index.js +1 -0
- package/src/components/CnTableWidget/CnTableWidget.vue +332 -0
- package/src/components/CnTableWidget/index.js +1 -0
- package/src/components/CnTasksCard/CnTasksCard.vue +373 -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 +292 -0
- package/src/components/CnTimelineStages/index.js +1 -0
- package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -0
- package/src/components/CnUserActionMenu/index.js +1 -0
- 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 +246 -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 +91 -50
- package/src/mixins/gridLayout.js +118 -0
- package/src/store/createCrudStore.js +360 -0
- package/src/store/createSubResourcePlugin.js +125 -135
- package/src/store/index.js +4 -3
- package/src/store/plugins/auditTrails.js +357 -17
- package/src/store/plugins/files.js +250 -186
- package/src/store/plugins/index.js +7 -4
- package/src/store/plugins/lifecycle.js +180 -180
- 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 +823 -625
- package/src/types/auditTrail.d.ts +32 -32
- package/src/types/file.d.ts +23 -23
- package/src/types/index.d.ts +35 -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 +422 -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,396 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CnDashboardPage — Top-level dashboard page with GridStack widget grid.
|
|
3
|
+
|
|
4
|
+
The dashboard equivalent of CnIndexPage. Assembles a complete dashboard
|
|
5
|
+
from a widget definition array and a layout array. Supports:
|
|
6
|
+
- Custom widgets via scoped slots (#widget-{widgetId})
|
|
7
|
+
- Nextcloud Dashboard API widgets (auto-rendered)
|
|
8
|
+
- Tile widgets (quick-access links)
|
|
9
|
+
- Drag-and-drop editing mode
|
|
10
|
+
- Header with title, actions, and edit toggle
|
|
11
|
+
-->
|
|
12
|
+
<template>
|
|
13
|
+
<div class="cn-dashboard-page">
|
|
14
|
+
<!-- Header -->
|
|
15
|
+
<div class="cn-dashboard-page__header">
|
|
16
|
+
<div class="cn-dashboard-page__header-left">
|
|
17
|
+
<h2 v-if="title" class="cn-dashboard-page__title">
|
|
18
|
+
{{ title }}
|
|
19
|
+
</h2>
|
|
20
|
+
<p v-if="description" class="cn-dashboard-page__description">
|
|
21
|
+
{{ description }}
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="cn-dashboard-page__header-actions">
|
|
25
|
+
<slot name="actions" />
|
|
26
|
+
<NcButton
|
|
27
|
+
v-if="allowEdit"
|
|
28
|
+
:type="isEditing ? 'primary' : 'secondary'"
|
|
29
|
+
@click="toggleEdit">
|
|
30
|
+
<template #icon>
|
|
31
|
+
<Pencil v-if="!isEditing" :size="20" />
|
|
32
|
+
<Check v-else :size="20" />
|
|
33
|
+
</template>
|
|
34
|
+
{{ isEditing ? doneLabel : editLabel }}
|
|
35
|
+
</NcButton>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<!-- Loading state -->
|
|
40
|
+
<NcLoadingIcon v-if="loading" />
|
|
41
|
+
|
|
42
|
+
<!-- Empty state -->
|
|
43
|
+
<div v-else-if="!hasWidgets" class="cn-dashboard-page__empty">
|
|
44
|
+
<slot name="empty">
|
|
45
|
+
<NcEmptyContent :description="emptyLabel">
|
|
46
|
+
<template #icon>
|
|
47
|
+
<ViewDashboardOutline :size="48" />
|
|
48
|
+
</template>
|
|
49
|
+
</NcEmptyContent>
|
|
50
|
+
</slot>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<!-- Dashboard grid -->
|
|
54
|
+
<CnDashboardGrid
|
|
55
|
+
v-else
|
|
56
|
+
:layout="layout"
|
|
57
|
+
:editable="isEditing"
|
|
58
|
+
:columns="columns"
|
|
59
|
+
:cell-height="cellHeight"
|
|
60
|
+
:margin="gridMargin"
|
|
61
|
+
@layout-change="onLayoutChange">
|
|
62
|
+
<template #widget="{ item }">
|
|
63
|
+
<!-- Tile widget -->
|
|
64
|
+
<CnTileWidget
|
|
65
|
+
v-if="isTile(item)"
|
|
66
|
+
:tile="getTileConfig(item)" />
|
|
67
|
+
|
|
68
|
+
<!-- Custom slot widget — apps provide their own rendering -->
|
|
69
|
+
<template v-else-if="hasWidgetSlot(item.widgetId)">
|
|
70
|
+
<CnWidgetWrapper
|
|
71
|
+
:title="getWidgetTitle(item)"
|
|
72
|
+
:icon-url="getWidgetIconUrl(item)"
|
|
73
|
+
:icon-class="getWidgetIconClass(item)"
|
|
74
|
+
:show-title="item.showTitle !== false"
|
|
75
|
+
:borderless="item.showTitle === false"
|
|
76
|
+
:flush="item.flush === true"
|
|
77
|
+
:buttons="getWidgetButtons(item)"
|
|
78
|
+
:style-config="item.styleConfig || {}">
|
|
79
|
+
<!-- Per-widget header actions (e.g. #widget-my-work-actions) -->
|
|
80
|
+
<template v-if="$slots['widget-' + item.widgetId + '-actions']" #actions>
|
|
81
|
+
<slot :name="'widget-' + item.widgetId + '-actions'" :item="item" :widget="getWidgetDef(item.widgetId)" />
|
|
82
|
+
</template>
|
|
83
|
+
<slot :name="'widget-' + item.widgetId" :item="item" :widget="getWidgetDef(item.widgetId)" />
|
|
84
|
+
</CnWidgetWrapper>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<!-- NC Dashboard API widget -->
|
|
88
|
+
<template v-else-if="isNcWidget(item)">
|
|
89
|
+
<CnWidgetWrapper
|
|
90
|
+
:title="getWidgetTitle(item)"
|
|
91
|
+
:icon-url="getWidgetIconUrl(item)"
|
|
92
|
+
:icon-class="getWidgetIconClass(item)"
|
|
93
|
+
:show-title="item.showTitle !== false"
|
|
94
|
+
:buttons="getWidgetButtons(item)"
|
|
95
|
+
:style-config="item.styleConfig || {}">
|
|
96
|
+
<CnWidgetRenderer
|
|
97
|
+
:widget="getWidgetDef(item.widgetId)"
|
|
98
|
+
:unavailable-text="unavailableLabel" />
|
|
99
|
+
</CnWidgetWrapper>
|
|
100
|
+
</template>
|
|
101
|
+
|
|
102
|
+
<!-- Unknown widget fallback -->
|
|
103
|
+
<CnWidgetWrapper
|
|
104
|
+
v-else
|
|
105
|
+
:title="getWidgetTitle(item)"
|
|
106
|
+
:show-title="item.showTitle !== false">
|
|
107
|
+
<div class="cn-dashboard-page__unknown">
|
|
108
|
+
{{ unavailableLabel }}
|
|
109
|
+
</div>
|
|
110
|
+
</CnWidgetWrapper>
|
|
111
|
+
</template>
|
|
112
|
+
</CnDashboardGrid>
|
|
113
|
+
</div>
|
|
114
|
+
</template>
|
|
115
|
+
|
|
116
|
+
<script>
|
|
117
|
+
import { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
|
|
118
|
+
import Pencil from 'vue-material-design-icons/Pencil.vue'
|
|
119
|
+
import Check from 'vue-material-design-icons/Check.vue'
|
|
120
|
+
import ViewDashboardOutline from 'vue-material-design-icons/ViewDashboardOutline.vue'
|
|
121
|
+
import CnDashboardGrid from '../CnDashboardGrid/CnDashboardGrid.vue'
|
|
122
|
+
import CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'
|
|
123
|
+
import CnWidgetRenderer from '../CnWidgetRenderer/CnWidgetRenderer.vue'
|
|
124
|
+
import CnTileWidget from '../CnTileWidget/CnTileWidget.vue'
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* CnDashboardPage — Top-level dashboard page component.
|
|
128
|
+
*
|
|
129
|
+
* The dashboard equivalent of CnIndexPage. Renders a configurable grid
|
|
130
|
+
* of widgets from a `widgets` definition array and a `layout` array.
|
|
131
|
+
*
|
|
132
|
+
* Widget types:
|
|
133
|
+
* 1. **Custom** — App provides rendering via `#widget-{widgetId}` slot
|
|
134
|
+
* 2. **NC Dashboard API** — Widgets with `itemApiVersions` are auto-rendered
|
|
135
|
+
* 3. **Tile** — Items with `type: 'tile'` render as quick-access tiles
|
|
136
|
+
*
|
|
137
|
+
* @example Basic usage with custom widgets
|
|
138
|
+
* <CnDashboardPage
|
|
139
|
+
* title="Dashboard"
|
|
140
|
+
* :widgets="widgetDefs"
|
|
141
|
+
* :layout="savedLayout"
|
|
142
|
+
* @layout-change="saveLayout">
|
|
143
|
+
* <template #widget-cases-by-status="{ item }">
|
|
144
|
+
* <StatusChart :data="statusData" />
|
|
145
|
+
* </template>
|
|
146
|
+
* <template #widget-my-work="{ item }">
|
|
147
|
+
* <MyWorkList :items="workItems" />
|
|
148
|
+
* </template>
|
|
149
|
+
* </CnDashboardPage>
|
|
150
|
+
*
|
|
151
|
+
* @example With NC Dashboard API widgets
|
|
152
|
+
* <CnDashboardPage
|
|
153
|
+
* title="Dashboard"
|
|
154
|
+
* :widgets="[...appWidgets, ...ncWidgets]"
|
|
155
|
+
* :layout="layout"
|
|
156
|
+
* @layout-change="saveLayout" />
|
|
157
|
+
*/
|
|
158
|
+
export default {
|
|
159
|
+
name: 'CnDashboardPage',
|
|
160
|
+
|
|
161
|
+
components: {
|
|
162
|
+
NcButton,
|
|
163
|
+
NcEmptyContent,
|
|
164
|
+
NcLoadingIcon,
|
|
165
|
+
Pencil,
|
|
166
|
+
Check,
|
|
167
|
+
ViewDashboardOutline,
|
|
168
|
+
CnDashboardGrid,
|
|
169
|
+
CnWidgetWrapper,
|
|
170
|
+
CnWidgetRenderer,
|
|
171
|
+
CnTileWidget,
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
props: {
|
|
175
|
+
/** Page title */
|
|
176
|
+
title: {
|
|
177
|
+
type: String,
|
|
178
|
+
default: '',
|
|
179
|
+
},
|
|
180
|
+
/** Page description (shown below title) */
|
|
181
|
+
description: {
|
|
182
|
+
type: String,
|
|
183
|
+
default: '',
|
|
184
|
+
},
|
|
185
|
+
/**
|
|
186
|
+
* Widget definitions array. Each widget defines metadata for rendering.
|
|
187
|
+
*
|
|
188
|
+
* Custom widgets: `{ id: 'my-widget', title: 'My Widget', type: 'custom' }`
|
|
189
|
+
* NC API widgets: `{ id: 'calendar', title: 'Calendar', itemApiVersions: [1,2], ... }`
|
|
190
|
+
* Tile widgets: `{ id: 'tile-files', type: 'tile', title: 'Files', icon: 'M12...', iconType: 'svg', backgroundColor: '#0082c9', textColor: '#fff', linkType: 'app', linkValue: 'files' }`
|
|
191
|
+
*
|
|
192
|
+
* @type {Array<{ id: string, title: string, type?: string, iconUrl?: string, iconClass?: string, buttons?: Array, itemApiVersions?: number[], reloadInterval?: number, [key: string]: any }>}
|
|
193
|
+
*/
|
|
194
|
+
widgets: {
|
|
195
|
+
type: Array,
|
|
196
|
+
default: () => [],
|
|
197
|
+
},
|
|
198
|
+
/**
|
|
199
|
+
* Layout array defining widget positions in the grid.
|
|
200
|
+
*
|
|
201
|
+
* Each item: `{ id: 'unique-id', widgetId: 'my-widget', gridX: 0, gridY: 0, gridWidth: 4, gridHeight: 3 }`
|
|
202
|
+
*
|
|
203
|
+
* Additional properties (showTitle, styleConfig, tile config) are passed through.
|
|
204
|
+
*
|
|
205
|
+
* @type {Array<{ id: string|number, widgetId: string, gridX: number, gridY: number, gridWidth: number, gridHeight: number, showTitle?: boolean, styleConfig?: object, [key: string]: any }>}
|
|
206
|
+
*/
|
|
207
|
+
layout: {
|
|
208
|
+
type: Array,
|
|
209
|
+
default: () => [],
|
|
210
|
+
},
|
|
211
|
+
/** Whether the dashboard is loading */
|
|
212
|
+
loading: {
|
|
213
|
+
type: Boolean,
|
|
214
|
+
default: false,
|
|
215
|
+
},
|
|
216
|
+
/** Whether to show the edit toggle button */
|
|
217
|
+
allowEdit: {
|
|
218
|
+
type: Boolean,
|
|
219
|
+
default: false,
|
|
220
|
+
},
|
|
221
|
+
/** Number of grid columns */
|
|
222
|
+
columns: {
|
|
223
|
+
type: Number,
|
|
224
|
+
default: 12,
|
|
225
|
+
},
|
|
226
|
+
/** Grid cell height in pixels */
|
|
227
|
+
cellHeight: {
|
|
228
|
+
type: Number,
|
|
229
|
+
default: 80,
|
|
230
|
+
},
|
|
231
|
+
/** Grid margin in pixels */
|
|
232
|
+
gridMargin: {
|
|
233
|
+
type: Number,
|
|
234
|
+
default: 12,
|
|
235
|
+
},
|
|
236
|
+
/** Label for the edit button */
|
|
237
|
+
editLabel: {
|
|
238
|
+
type: String,
|
|
239
|
+
default: 'Edit',
|
|
240
|
+
},
|
|
241
|
+
/** Label for the done button (when editing) */
|
|
242
|
+
doneLabel: {
|
|
243
|
+
type: String,
|
|
244
|
+
default: 'Done',
|
|
245
|
+
},
|
|
246
|
+
/** Label for the empty state */
|
|
247
|
+
emptyLabel: {
|
|
248
|
+
type: String,
|
|
249
|
+
default: 'No widgets configured',
|
|
250
|
+
},
|
|
251
|
+
/** Label for unavailable widgets */
|
|
252
|
+
unavailableLabel: {
|
|
253
|
+
type: String,
|
|
254
|
+
default: 'Widget not available',
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
emits: ['layout-change', 'edit-toggle'],
|
|
259
|
+
|
|
260
|
+
data() {
|
|
261
|
+
return {
|
|
262
|
+
isEditing: false,
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
computed: {
|
|
267
|
+
hasWidgets() {
|
|
268
|
+
return this.layout.length > 0
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
widgetMap() {
|
|
272
|
+
const map = {}
|
|
273
|
+
for (const w of this.widgets) {
|
|
274
|
+
map[w.id] = w
|
|
275
|
+
}
|
|
276
|
+
return map
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
methods: {
|
|
281
|
+
toggleEdit() {
|
|
282
|
+
this.isEditing = !this.isEditing
|
|
283
|
+
this.$emit('edit-toggle', this.isEditing)
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
onLayoutChange(updated) {
|
|
287
|
+
this.$emit('layout-change', updated)
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
getWidgetDef(widgetId) {
|
|
291
|
+
return this.widgetMap[widgetId] || null
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
getWidgetTitle(item) {
|
|
295
|
+
const def = this.getWidgetDef(item.widgetId)
|
|
296
|
+
return item.customTitle || def?.title || item.widgetId
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
getWidgetIconUrl(item) {
|
|
300
|
+
const def = this.getWidgetDef(item.widgetId)
|
|
301
|
+
return def?.iconUrl || null
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
getWidgetIconClass(item) {
|
|
305
|
+
const def = this.getWidgetDef(item.widgetId)
|
|
306
|
+
return def?.iconClass || null
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
getWidgetButtons(item) {
|
|
310
|
+
const def = this.getWidgetDef(item.widgetId)
|
|
311
|
+
return def?.buttons || []
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
isTile(item) {
|
|
315
|
+
const def = this.getWidgetDef(item.widgetId)
|
|
316
|
+
return def?.type === 'tile'
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
getTileConfig(item) {
|
|
320
|
+
const def = this.getWidgetDef(item.widgetId)
|
|
321
|
+
if (!def) return null
|
|
322
|
+
return {
|
|
323
|
+
title: def.title,
|
|
324
|
+
icon: def.icon,
|
|
325
|
+
iconType: def.iconType,
|
|
326
|
+
backgroundColor: def.backgroundColor,
|
|
327
|
+
textColor: def.textColor,
|
|
328
|
+
linkType: def.linkType,
|
|
329
|
+
linkValue: def.linkValue,
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
isNcWidget(item) {
|
|
334
|
+
const def = this.getWidgetDef(item.widgetId)
|
|
335
|
+
return def?.itemApiVersions && def.itemApiVersions.length > 0
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
hasWidgetSlot(widgetId) {
|
|
339
|
+
return !!this.$scopedSlots['widget-' + widgetId]
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
</script>
|
|
344
|
+
|
|
345
|
+
<style scoped>
|
|
346
|
+
.cn-dashboard-page {
|
|
347
|
+
padding: 20px;
|
|
348
|
+
max-width: 1400px;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.cn-dashboard-page__header {
|
|
352
|
+
display: flex;
|
|
353
|
+
justify-content: space-between;
|
|
354
|
+
align-items: flex-start;
|
|
355
|
+
margin-bottom: 20px;
|
|
356
|
+
flex-wrap: wrap;
|
|
357
|
+
gap: 12px;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.cn-dashboard-page__header-left {
|
|
361
|
+
min-width: 0;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.cn-dashboard-page__title {
|
|
365
|
+
margin: 0;
|
|
366
|
+
font-size: 20px;
|
|
367
|
+
font-weight: 700;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.cn-dashboard-page__description {
|
|
371
|
+
margin: 4px 0 0;
|
|
372
|
+
font-size: 14px;
|
|
373
|
+
color: var(--color-text-maxcontrast);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.cn-dashboard-page__header-actions {
|
|
377
|
+
display: flex;
|
|
378
|
+
gap: 8px;
|
|
379
|
+
flex-wrap: wrap;
|
|
380
|
+
flex-shrink: 0;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.cn-dashboard-page__empty {
|
|
384
|
+
padding: 60px 20px;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.cn-dashboard-page__unknown {
|
|
388
|
+
display: flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
justify-content: center;
|
|
391
|
+
height: 100%;
|
|
392
|
+
color: var(--color-text-maxcontrast);
|
|
393
|
+
font-size: 14px;
|
|
394
|
+
padding: 16px;
|
|
395
|
+
}
|
|
396
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnDashboardPage } from './CnDashboardPage.vue'
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
<!-- Actions column -->
|
|
40
40
|
<th v-if="$scopedSlots['row-actions']" class="cn-table-col--actions">
|
|
41
|
-
|
|
41
|
+
<slot name="actions-header" />
|
|
42
42
|
</th>
|
|
43
43
|
</tr>
|
|
44
44
|
</thead>
|
|
@@ -63,7 +63,8 @@
|
|
|
63
63
|
isSelected(row) ? 'cn-table-row--selected' : '',
|
|
64
64
|
rowClass ? rowClass(row) : '',
|
|
65
65
|
]"
|
|
66
|
-
@click="$emit('row-click', row)"
|
|
66
|
+
@click="$emit('row-click', row)"
|
|
67
|
+
@contextmenu.prevent="$emit('row-context-menu', { row, event: $event })">
|
|
67
68
|
<!-- Checkbox -->
|
|
68
69
|
<td v-if="selectable" class="cn-table-col--checkbox" @click.stop>
|
|
69
70
|
<NcCheckboxRadioSwitch
|
|
@@ -101,6 +102,7 @@
|
|
|
101
102
|
</template>
|
|
102
103
|
|
|
103
104
|
<script>
|
|
105
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
104
106
|
import { NcLoadingIcon, NcCheckboxRadioSwitch } from '@nextcloud/vue'
|
|
105
107
|
import { CnCellRenderer } from '../CnCellRenderer/index.js'
|
|
106
108
|
import { columnsFromSchema } from '../../utils/schema.js'
|
|
@@ -118,11 +120,6 @@ import { columnsFromSchema } from '../../utils/schema.js'
|
|
|
118
120
|
* (dates, booleans, UUIDs, enums, etc.). Scoped slots still override individual
|
|
119
121
|
* columns when needed.
|
|
120
122
|
*
|
|
121
|
-
* NL Design tokens used:
|
|
122
|
-
* - --nldesign-component-table-header-background-color
|
|
123
|
-
* - --nldesign-component-table-row-hover-background-color
|
|
124
|
-
* - --nldesign-component-table-border-color
|
|
125
|
-
*
|
|
126
123
|
* @example Manual columns (backwards compatible)
|
|
127
124
|
* <CnDataTable
|
|
128
125
|
* :columns="[
|
|
@@ -203,11 +200,11 @@ export default {
|
|
|
203
200
|
type: String,
|
|
204
201
|
default: null,
|
|
205
202
|
},
|
|
206
|
-
/** Current sort order: 'asc'
|
|
203
|
+
/** Current sort order: 'asc', 'desc', or null (no sort) */
|
|
207
204
|
sortOrder: {
|
|
208
205
|
type: String,
|
|
209
206
|
default: 'asc',
|
|
210
|
-
validator: (v) => ['asc', 'desc'].includes(v),
|
|
207
|
+
validator: (v) => v === null || ['asc', 'desc'].includes(v),
|
|
211
208
|
},
|
|
212
209
|
/** Whether rows can be selected with checkboxes */
|
|
213
210
|
selectable: {
|
|
@@ -227,7 +224,7 @@ export default {
|
|
|
227
224
|
/** Text shown when there are no rows */
|
|
228
225
|
emptyText: {
|
|
229
226
|
type: String,
|
|
230
|
-
default: 'No items found',
|
|
227
|
+
default: () => t('nextcloud-vue', 'No items found'),
|
|
231
228
|
},
|
|
232
229
|
/** Function returning CSS class(es) for a row: (row) => string|object */
|
|
233
230
|
rowClass: {
|
|
@@ -242,7 +239,7 @@ export default {
|
|
|
242
239
|
/** Text shown while loading */
|
|
243
240
|
loadingText: {
|
|
244
241
|
type: String,
|
|
245
|
-
default: 'Loading...',
|
|
242
|
+
default: () => t('nextcloud-vue', 'Loading...'),
|
|
246
243
|
},
|
|
247
244
|
},
|
|
248
245
|
|
|
@@ -320,15 +317,22 @@ export default {
|
|
|
320
317
|
* @param {string} key Column key
|
|
321
318
|
*/
|
|
322
319
|
onSort(key) {
|
|
320
|
+
let newKey = key
|
|
323
321
|
let order = 'asc'
|
|
324
322
|
if (this.sortKey === key) {
|
|
325
|
-
|
|
323
|
+
if (this.sortOrder === 'asc') {
|
|
324
|
+
order = 'desc'
|
|
325
|
+
} else {
|
|
326
|
+
// desc → disabled: clear sort entirely
|
|
327
|
+
newKey = null
|
|
328
|
+
order = null
|
|
329
|
+
}
|
|
326
330
|
}
|
|
327
331
|
/**
|
|
328
332
|
* @event sort Emitted when a sortable column header is clicked.
|
|
329
|
-
* @type {{ key: string, order: 'asc'|'desc' }}
|
|
333
|
+
* @type {{ key: string|null, order: 'asc'|'desc'|null }}
|
|
330
334
|
*/
|
|
331
|
-
this.$emit('sort', { key, order })
|
|
335
|
+
this.$emit('sort', { key: newKey, order })
|
|
332
336
|
},
|
|
333
337
|
|
|
334
338
|
toggleSelect(row) {
|
|
@@ -342,9 +346,13 @@ export default {
|
|
|
342
346
|
|
|
343
347
|
toggleSelectAll() {
|
|
344
348
|
if (this.allSelected) {
|
|
345
|
-
|
|
349
|
+
// Remove only current page IDs, preserving cross-page selections
|
|
350
|
+
const currentPageIds = new Set(this.rows.map((row) => row[this.rowKey]))
|
|
351
|
+
this.$emit('select', this.selectedIds.filter((id) => !currentPageIds.has(id)))
|
|
346
352
|
} else {
|
|
347
|
-
|
|
353
|
+
// Add current page IDs to existing selections
|
|
354
|
+
const merged = new Set([...this.selectedIds, ...this.rows.map((row) => row[this.rowKey])])
|
|
355
|
+
this.$emit('select', [...merged])
|
|
348
356
|
}
|
|
349
357
|
/** @event select-all Emitted when select-all checkbox is toggled. */
|
|
350
358
|
this.$emit('select-all', !this.allSelected)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as CnDataTable } from './CnDataTable.vue'
|
|
1
|
+
export { default as CnDataTable } from './CnDataTable.vue'
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<NcDialog
|
|
3
|
+
:name="dialogTitle"
|
|
4
|
+
size="small"
|
|
5
|
+
:can-close="!loading"
|
|
6
|
+
@closing="$emit('close')">
|
|
7
|
+
<!-- Result phase -->
|
|
8
|
+
<div v-if="result !== null" class="cn-delete__result">
|
|
9
|
+
<NcNoteCard v-if="result.success" type="success">
|
|
10
|
+
{{ successText }}
|
|
11
|
+
</NcNoteCard>
|
|
12
|
+
<NcNoteCard v-if="result.error" type="error">
|
|
13
|
+
{{ result.error }}
|
|
14
|
+
</NcNoteCard>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<!-- Confirm phase -->
|
|
18
|
+
<div v-else class="cn-delete__confirm">
|
|
19
|
+
<NcNoteCard type="warning">
|
|
20
|
+
{{ resolvedWarningText }}
|
|
21
|
+
</NcNoteCard>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<template #actions>
|
|
25
|
+
<NcButton @click="$emit('close')">
|
|
26
|
+
{{ result !== null ? closeLabel : cancelLabel }}
|
|
27
|
+
</NcButton>
|
|
28
|
+
<NcButton
|
|
29
|
+
v-if="result === null"
|
|
30
|
+
type="error"
|
|
31
|
+
:disabled="loading"
|
|
32
|
+
@click="executeDelete">
|
|
33
|
+
<template #icon>
|
|
34
|
+
<NcLoadingIcon v-if="loading" :size="20" />
|
|
35
|
+
<TrashCanOutline v-else :size="20" />
|
|
36
|
+
</template>
|
|
37
|
+
{{ confirmLabel }}
|
|
38
|
+
</NcButton>
|
|
39
|
+
</template>
|
|
40
|
+
</NcDialog>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script>
|
|
44
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
45
|
+
import { NcDialog, NcButton, NcNoteCard, NcLoadingIcon } from '@nextcloud/vue'
|
|
46
|
+
import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* CnDeleteDialog — Single-item delete confirmation dialog.
|
|
50
|
+
*
|
|
51
|
+
* Two-phase UI: confirm then result. The dialog does NOT perform the delete
|
|
52
|
+
* itself — it emits a `confirm` event with the item ID. The parent performs
|
|
53
|
+
* the actual API call and calls `setResult()` via a ref.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* <CnDeleteDialog
|
|
57
|
+
* v-if="showDeleteDialog"
|
|
58
|
+
* ref="deleteDialog"
|
|
59
|
+
* :item="itemToDelete"
|
|
60
|
+
* @confirm="onDeleteConfirm"
|
|
61
|
+
* @close="showDeleteDialog = false" />
|
|
62
|
+
*
|
|
63
|
+
* // In methods:
|
|
64
|
+
* async onDeleteConfirm(id) {
|
|
65
|
+
* try {
|
|
66
|
+
* await store.deleteItem(id)
|
|
67
|
+
* this.$refs.deleteDialog.setResult({ success: true })
|
|
68
|
+
* } catch (e) {
|
|
69
|
+
* this.$refs.deleteDialog.setResult({ error: e.message })
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
*/
|
|
73
|
+
export default {
|
|
74
|
+
name: 'CnDeleteDialog',
|
|
75
|
+
|
|
76
|
+
components: {
|
|
77
|
+
NcDialog,
|
|
78
|
+
NcButton,
|
|
79
|
+
NcNoteCard,
|
|
80
|
+
NcLoadingIcon,
|
|
81
|
+
TrashCanOutline,
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
props: {
|
|
85
|
+
/** The item to delete. Must have an `id` property. */
|
|
86
|
+
item: {
|
|
87
|
+
type: Object,
|
|
88
|
+
required: true,
|
|
89
|
+
},
|
|
90
|
+
/** Property name used for display (e.g., 'title', 'name') */
|
|
91
|
+
nameField: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: 'title',
|
|
94
|
+
},
|
|
95
|
+
/** Optional function to format the item name. Receives the item, returns a string. Overrides nameField when provided. */
|
|
96
|
+
nameFormatter: {
|
|
97
|
+
type: Function,
|
|
98
|
+
default: null,
|
|
99
|
+
},
|
|
100
|
+
/** Dialog title */
|
|
101
|
+
dialogTitle: {
|
|
102
|
+
type: String,
|
|
103
|
+
default: () => t('nextcloud-vue', 'Delete item'),
|
|
104
|
+
},
|
|
105
|
+
/** Warning text. Use `{name}` as placeholder for the item name. */
|
|
106
|
+
warningText: {
|
|
107
|
+
type: String,
|
|
108
|
+
default: () => t('nextcloud-vue', 'Are you sure you want to permanently delete "{name}"? This action cannot be undone.'),
|
|
109
|
+
},
|
|
110
|
+
/** Success message */
|
|
111
|
+
successText: {
|
|
112
|
+
type: String,
|
|
113
|
+
default: () => t('nextcloud-vue', 'Item successfully deleted.'),
|
|
114
|
+
},
|
|
115
|
+
cancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },
|
|
116
|
+
closeLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },
|
|
117
|
+
confirmLabel: { type: String, default: () => t('nextcloud-vue', 'Delete') },
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
data() {
|
|
121
|
+
return {
|
|
122
|
+
loading: false,
|
|
123
|
+
result: null,
|
|
124
|
+
closeTimeout: null,
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
computed: {
|
|
129
|
+
itemName() {
|
|
130
|
+
if (this.nameFormatter) return this.nameFormatter(this.item)
|
|
131
|
+
return this.item[this.nameField] || this.item.name || this.item.title || this.item.id
|
|
132
|
+
},
|
|
133
|
+
resolvedWarningText() {
|
|
134
|
+
return this.warningText.replace('{name}', this.itemName)
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
beforeDestroy() {
|
|
139
|
+
if (this.closeTimeout) clearTimeout(this.closeTimeout)
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @event confirm Emitted when the user confirms deletion. Payload: the item ID.
|
|
144
|
+
* @event close Emitted when the dialog should be closed (cancel, close button, or auto-close after success).
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
methods: {
|
|
148
|
+
executeDelete() {
|
|
149
|
+
this.loading = true
|
|
150
|
+
this.$emit('confirm', this.item.id)
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set the result of the delete operation. Call this from the parent
|
|
155
|
+
* after the API call completes.
|
|
156
|
+
*
|
|
157
|
+
* @param {{ success?: boolean, error?: string }} resultData - Result data to pass to the dialog
|
|
158
|
+
* @public
|
|
159
|
+
*/
|
|
160
|
+
setResult(resultData) {
|
|
161
|
+
this.loading = false
|
|
162
|
+
this.result = resultData
|
|
163
|
+
if (resultData.success) {
|
|
164
|
+
this.closeTimeout = setTimeout(() => {
|
|
165
|
+
this.$emit('close')
|
|
166
|
+
}, 2000)
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
</script>
|
|
172
|
+
|
|
173
|
+
<style scoped>
|
|
174
|
+
.cn-delete__confirm {
|
|
175
|
+
padding: 4px 0;
|
|
176
|
+
}
|
|
177
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CnDeleteDialog } from './CnDeleteDialog.vue'
|