@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,162 @@
|
|
|
1
|
+
import axios from '@nextcloud/axios'
|
|
2
|
+
import { generateOcsUrl } from '@nextcloud/router'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cached user groups — fetched once per session and reused.
|
|
6
|
+
* @type {{ userId: string|null, groups: string[]|null, promise: Promise|null }}
|
|
7
|
+
*/
|
|
8
|
+
const _cache = {
|
|
9
|
+
userId: null,
|
|
10
|
+
groups: null,
|
|
11
|
+
promise: null,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the current Nextcloud user ID from OC.currentUser.
|
|
16
|
+
*
|
|
17
|
+
* @return {string|null} The current user ID, or null if not logged in
|
|
18
|
+
*/
|
|
19
|
+
export function getCurrentUserId() {
|
|
20
|
+
return window.OC?.currentUser?.uid
|
|
21
|
+
|| window.OC?.currentUser
|
|
22
|
+
|| null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fetch the current user's Nextcloud groups. Results are cached so the
|
|
27
|
+
* OCS API is only called once per page load.
|
|
28
|
+
*
|
|
29
|
+
* @return {Promise<string[]>} Array of group IDs the current user belongs to
|
|
30
|
+
*/
|
|
31
|
+
export async function getCurrentUserGroups() {
|
|
32
|
+
const userId = getCurrentUserId()
|
|
33
|
+
if (!userId) {
|
|
34
|
+
return []
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return cached result if we already fetched for this user
|
|
38
|
+
if (_cache.userId === userId && _cache.groups !== null) {
|
|
39
|
+
return _cache.groups
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If a fetch is already in progress for this user, await it
|
|
43
|
+
if (_cache.userId === userId && _cache.promise) {
|
|
44
|
+
return _cache.promise
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_cache.userId = userId
|
|
48
|
+
_cache.promise = _fetchGroups(userId)
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
_cache.groups = await _cache.promise
|
|
52
|
+
} catch {
|
|
53
|
+
_cache.groups = []
|
|
54
|
+
} finally {
|
|
55
|
+
_cache.promise = null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return _cache.groups
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Internal: fetch groups from OCS API.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} userId The user ID to look up
|
|
65
|
+
* @return {Promise<string[]>} Array of group IDs
|
|
66
|
+
*/
|
|
67
|
+
async function _fetchGroups(userId) {
|
|
68
|
+
try {
|
|
69
|
+
const url = generateOcsUrl('/cloud/users/{userId}/groups', { userId })
|
|
70
|
+
const response = await axios.get(url)
|
|
71
|
+
const groups = response.data?.ocs?.data?.groups || []
|
|
72
|
+
return groups
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('[widgetVisibility] Failed to fetch user groups:', error)
|
|
75
|
+
return []
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check whether a single widget is visible to the current user based on
|
|
81
|
+
* its `visibility` configuration.
|
|
82
|
+
*
|
|
83
|
+
* Visibility rules:
|
|
84
|
+
* - No `visibility` property -> visible to everyone
|
|
85
|
+
* - `visibility.users` contains the current user ID -> visible
|
|
86
|
+
* - `visibility.groups` overlaps with the user's groups -> visible
|
|
87
|
+
* - Both `users` and `groups` are specified -> either match grants access (OR logic)
|
|
88
|
+
* - Both are empty arrays -> visible to everyone
|
|
89
|
+
*
|
|
90
|
+
* @param {object} widget Widget definition object
|
|
91
|
+
* @param {object} [widget.visibility] Optional visibility configuration
|
|
92
|
+
* @param {string[]} [widget.visibility.users] User IDs who can see this widget
|
|
93
|
+
* @param {string[]} [widget.visibility.groups] Group names who can see this widget
|
|
94
|
+
* @param {string|null} userId Current user ID
|
|
95
|
+
* @param {string[]} userGroups Current user's group memberships
|
|
96
|
+
* @return {boolean} Whether the widget should be visible
|
|
97
|
+
*/
|
|
98
|
+
export function isWidgetVisible(widget, userId, userGroups) {
|
|
99
|
+
const visibility = widget?.visibility
|
|
100
|
+
if (!visibility) {
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const allowedUsers = visibility.users || []
|
|
105
|
+
const allowedGroups = visibility.groups || []
|
|
106
|
+
|
|
107
|
+
// If both are empty, visible to everyone
|
|
108
|
+
if (allowedUsers.length === 0 && allowedGroups.length === 0) {
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Check user match
|
|
113
|
+
if (allowedUsers.length > 0 && userId && allowedUsers.includes(userId)) {
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check group match
|
|
118
|
+
if (allowedGroups.length > 0 && userGroups.length > 0) {
|
|
119
|
+
const hasGroupMatch = allowedGroups.some(group => userGroups.includes(group))
|
|
120
|
+
if (hasGroupMatch) {
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Filter an array of widget definitions by visibility for the current user.
|
|
130
|
+
*
|
|
131
|
+
* This is an async function because it may need to fetch the user's groups
|
|
132
|
+
* from the OCS API (cached after first call).
|
|
133
|
+
*
|
|
134
|
+
* @param {Array} widgets Array of widget definition objects
|
|
135
|
+
* @return {Promise<Array>} Filtered array of visible widgets
|
|
136
|
+
*/
|
|
137
|
+
export async function filterWidgetsByVisibility(widgets) {
|
|
138
|
+
if (!widgets || widgets.length === 0) {
|
|
139
|
+
return []
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Quick path: if no widgets have visibility config, return all
|
|
143
|
+
const hasVisibilityConfig = widgets.some(w => w.visibility)
|
|
144
|
+
if (!hasVisibilityConfig) {
|
|
145
|
+
return widgets
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const userId = getCurrentUserId()
|
|
149
|
+
const userGroups = await getCurrentUserGroups()
|
|
150
|
+
|
|
151
|
+
return widgets.filter(widget => isWidgetVisible(widget, userId, userGroups))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Reset the internal groups cache. Useful for testing or when user
|
|
156
|
+
* context changes.
|
|
157
|
+
*/
|
|
158
|
+
export function resetVisibilityCache() {
|
|
159
|
+
_cache.userId = null
|
|
160
|
+
_cache.groups = null
|
|
161
|
+
_cache.promise = null
|
|
162
|
+
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="cn-detail-layout">
|
|
3
|
-
<!-- Header -->
|
|
4
|
-
<div class="cn-detail-layout__header">
|
|
5
|
-
<NcButton @click="$emit('back')">
|
|
6
|
-
<template #icon>
|
|
7
|
-
<ArrowLeft :size="20" />
|
|
8
|
-
</template>
|
|
9
|
-
{{ backLabel }}
|
|
10
|
-
</NcButton>
|
|
11
|
-
|
|
12
|
-
<h2 class="cn-detail-layout__title">
|
|
13
|
-
<slot name="title">{{ title }}</slot>
|
|
14
|
-
</h2>
|
|
15
|
-
|
|
16
|
-
<div class="cn-detail-layout__actions">
|
|
17
|
-
<slot name="actions" />
|
|
18
|
-
</div>
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
<!-- Loading state -->
|
|
22
|
-
<div v-if="loading" class="cn-loading-container">
|
|
23
|
-
<NcLoadingIcon :size="32" />
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
<!-- Main content -->
|
|
27
|
-
<div v-else class="cn-detail-layout__content">
|
|
28
|
-
<slot />
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<!-- Delete confirmation dialog -->
|
|
32
|
-
<slot name="dialogs" />
|
|
33
|
-
</div>
|
|
34
|
-
</template>
|
|
35
|
-
|
|
36
|
-
<script>
|
|
37
|
-
import { NcButton, NcLoadingIcon } from '@nextcloud/vue'
|
|
38
|
-
import ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* CnDetailViewLayout — Detail page layout with back button, title, actions, and content.
|
|
42
|
-
*
|
|
43
|
-
* Provides the standard structure for detail/edit views: back navigation,
|
|
44
|
-
* page title, action buttons, and a content area. Supports loading state
|
|
45
|
-
* and a dialogs slot for modals.
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* <CnDetailViewLayout
|
|
49
|
-
* title="Client: Acme Corp"
|
|
50
|
-
* :loading="isLoading"
|
|
51
|
-
* @back="goBack">
|
|
52
|
-
* <template #actions>
|
|
53
|
-
* <NcButton @click="edit">Edit</NcButton>
|
|
54
|
-
* <NcButton type="error" @click="confirmDelete">Delete</NcButton>
|
|
55
|
-
* </template>
|
|
56
|
-
* <div class="cn-detail-grid">
|
|
57
|
-
* <div class="cn-detail-item">...</div>
|
|
58
|
-
* </div>
|
|
59
|
-
* </CnDetailViewLayout>
|
|
60
|
-
*/
|
|
61
|
-
export default {
|
|
62
|
-
name: 'CnDetailViewLayout',
|
|
63
|
-
|
|
64
|
-
components: {
|
|
65
|
-
NcButton,
|
|
66
|
-
NcLoadingIcon,
|
|
67
|
-
ArrowLeft,
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
props: {
|
|
71
|
-
/** Page title */
|
|
72
|
-
title: {
|
|
73
|
-
type: String,
|
|
74
|
-
default: '',
|
|
75
|
-
},
|
|
76
|
-
/** Whether data is loading */
|
|
77
|
-
loading: {
|
|
78
|
-
type: Boolean,
|
|
79
|
-
default: false,
|
|
80
|
-
},
|
|
81
|
-
/** Back button label */
|
|
82
|
-
backLabel: {
|
|
83
|
-
type: String,
|
|
84
|
-
default: 'Back',
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
}
|
|
88
|
-
</script>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as CnDetailViewLayout } from './CnDetailViewLayout.vue'
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<NcEmptyContent :name="title" :description="description">
|
|
3
|
-
<template #icon>
|
|
4
|
-
<slot name="icon">
|
|
5
|
-
<component :is="icon" v-if="icon" :size="64" />
|
|
6
|
-
</slot>
|
|
7
|
-
</template>
|
|
8
|
-
<template v-if="actionLabel" #action>
|
|
9
|
-
<slot name="action">
|
|
10
|
-
<NcButton :type="actionType" @click="$emit('action')">
|
|
11
|
-
{{ actionLabel }}
|
|
12
|
-
</NcButton>
|
|
13
|
-
</slot>
|
|
14
|
-
</template>
|
|
15
|
-
</NcEmptyContent>
|
|
16
|
-
</template>
|
|
17
|
-
|
|
18
|
-
<script>
|
|
19
|
-
import { NcEmptyContent, NcButton } from '@nextcloud/vue'
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* CnEmptyState — Consistent empty state display wrapping NcEmptyContent.
|
|
23
|
-
*
|
|
24
|
-
* Provides a unified empty state pattern with icon, title, description,
|
|
25
|
-
* and optional action button. Used across all list views.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* <CnEmptyState
|
|
29
|
-
* title="No clients yet"
|
|
30
|
-
* description="Create your first client to get started"
|
|
31
|
-
* action-label="New Client"
|
|
32
|
-
* @action="createClient" />
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* <!-- With custom icon -->
|
|
36
|
-
* <CnEmptyState title="No results">
|
|
37
|
-
* <template #icon>
|
|
38
|
-
* <Magnify :size="64" />
|
|
39
|
-
* </template>
|
|
40
|
-
* </CnEmptyState>
|
|
41
|
-
*/
|
|
42
|
-
export default {
|
|
43
|
-
name: 'CnEmptyState',
|
|
44
|
-
|
|
45
|
-
components: {
|
|
46
|
-
NcEmptyContent,
|
|
47
|
-
NcButton,
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
props: {
|
|
51
|
-
/** Main title text */
|
|
52
|
-
title: {
|
|
53
|
-
type: String,
|
|
54
|
-
required: true,
|
|
55
|
-
},
|
|
56
|
-
/** Description text below the title */
|
|
57
|
-
description: {
|
|
58
|
-
type: String,
|
|
59
|
-
default: '',
|
|
60
|
-
},
|
|
61
|
-
/** Vue component for the icon (e.g., imported material design icon) */
|
|
62
|
-
icon: {
|
|
63
|
-
type: [Object, null],
|
|
64
|
-
default: null,
|
|
65
|
-
},
|
|
66
|
-
/** Action button label. If empty, no button is shown. */
|
|
67
|
-
actionLabel: {
|
|
68
|
-
type: String,
|
|
69
|
-
default: '',
|
|
70
|
-
},
|
|
71
|
-
/** NcButton type for the action button */
|
|
72
|
-
actionType: {
|
|
73
|
-
type: String,
|
|
74
|
-
default: 'primary',
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
}
|
|
78
|
-
</script>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as CnEmptyState } from './CnEmptyState.vue'
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="cn-list-layout">
|
|
3
|
-
<!-- Header -->
|
|
4
|
-
<div class="cn-list-layout__header">
|
|
5
|
-
<div class="cn-list-layout__title">
|
|
6
|
-
<h2>{{ title }}</h2>
|
|
7
|
-
<span v-if="totalItems > 0" class="cn-list-layout__count">({{ totalItems }})</span>
|
|
8
|
-
</div>
|
|
9
|
-
<div class="cn-list-layout__actions">
|
|
10
|
-
<slot name="actions" />
|
|
11
|
-
</div>
|
|
12
|
-
</div>
|
|
13
|
-
|
|
14
|
-
<!-- Filters slot -->
|
|
15
|
-
<slot name="filters" />
|
|
16
|
-
|
|
17
|
-
<!-- Loading state -->
|
|
18
|
-
<div v-if="loading" class="cn-loading-container">
|
|
19
|
-
<NcLoadingIcon :size="32" />
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
<!-- Main content (table area) -->
|
|
23
|
-
<template v-else>
|
|
24
|
-
<slot />
|
|
25
|
-
</template>
|
|
26
|
-
|
|
27
|
-
<!-- Pagination slot -->
|
|
28
|
-
<slot name="pagination" />
|
|
29
|
-
</div>
|
|
30
|
-
</template>
|
|
31
|
-
|
|
32
|
-
<script>
|
|
33
|
-
import { NcLoadingIcon } from '@nextcloud/vue'
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* CnListViewLayout — Full list page layout wrapping header, filters, table, and pagination.
|
|
37
|
-
*
|
|
38
|
-
* Provides the standard page structure used by every list view: a header with
|
|
39
|
-
* title + action buttons, a filter/search area, the main content (table), and pagination.
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* <CnListViewLayout title="Clients" :total-items="clients.length" :loading="isLoading">
|
|
43
|
-
* <template #actions>
|
|
44
|
-
* <NcButton type="primary" @click="createClient">New client</NcButton>
|
|
45
|
-
* </template>
|
|
46
|
-
* <template #filters>
|
|
47
|
-
* <CnFilterBar ... />
|
|
48
|
-
* </template>
|
|
49
|
-
* <CnDataTable :columns="columns" :rows="clients" />
|
|
50
|
-
* <template #pagination>
|
|
51
|
-
* <CnPagination ... />
|
|
52
|
-
* </template>
|
|
53
|
-
* </CnListViewLayout>
|
|
54
|
-
*/
|
|
55
|
-
export default {
|
|
56
|
-
name: 'CnListViewLayout',
|
|
57
|
-
|
|
58
|
-
components: {
|
|
59
|
-
NcLoadingIcon,
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
props: {
|
|
63
|
-
/** Page title */
|
|
64
|
-
title: {
|
|
65
|
-
type: String,
|
|
66
|
-
required: true,
|
|
67
|
-
},
|
|
68
|
-
/** Total items count (shown next to title) */
|
|
69
|
-
totalItems: {
|
|
70
|
-
type: Number,
|
|
71
|
-
default: 0,
|
|
72
|
-
},
|
|
73
|
-
/** Whether data is loading */
|
|
74
|
-
loading: {
|
|
75
|
-
type: Boolean,
|
|
76
|
-
default: false,
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
}
|
|
80
|
-
</script>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as CnListViewLayout } from './CnListViewLayout.vue'
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="cn-view-mode-toggle" role="group" :aria-label="ariaLabel">
|
|
3
|
-
<NcButton
|
|
4
|
-
:type="value === 'cards' ? 'primary' : 'secondary'"
|
|
5
|
-
:aria-pressed="String(value === 'cards')"
|
|
6
|
-
@click="$emit('input', 'cards')">
|
|
7
|
-
<template #icon>
|
|
8
|
-
<ViewGrid :size="20" />
|
|
9
|
-
</template>
|
|
10
|
-
{{ cardsLabel }}
|
|
11
|
-
</NcButton>
|
|
12
|
-
<NcButton
|
|
13
|
-
:type="value === 'table' ? 'primary' : 'secondary'"
|
|
14
|
-
:aria-pressed="String(value === 'table')"
|
|
15
|
-
@click="$emit('input', 'table')">
|
|
16
|
-
<template #icon>
|
|
17
|
-
<ViewList :size="20" />
|
|
18
|
-
</template>
|
|
19
|
-
{{ tableLabel }}
|
|
20
|
-
</NcButton>
|
|
21
|
-
</div>
|
|
22
|
-
</template>
|
|
23
|
-
|
|
24
|
-
<script>
|
|
25
|
-
import { NcButton } from '@nextcloud/vue'
|
|
26
|
-
import ViewGrid from 'vue-material-design-icons/ViewGrid.vue'
|
|
27
|
-
import ViewList from 'vue-material-design-icons/ViewList.vue'
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* CnViewModeToggle — Cards/Table view mode toggle.
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* <CnViewModeToggle v-model="viewMode" />
|
|
34
|
-
*/
|
|
35
|
-
export default {
|
|
36
|
-
name: 'CnViewModeToggle',
|
|
37
|
-
|
|
38
|
-
components: {
|
|
39
|
-
NcButton,
|
|
40
|
-
ViewGrid,
|
|
41
|
-
ViewList,
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
props: {
|
|
45
|
-
/** Current view mode: 'cards' or 'table' */
|
|
46
|
-
value: {
|
|
47
|
-
type: String,
|
|
48
|
-
default: 'table',
|
|
49
|
-
validator: (v) => ['cards', 'table'].includes(v),
|
|
50
|
-
},
|
|
51
|
-
/** Label for cards button */
|
|
52
|
-
cardsLabel: {
|
|
53
|
-
type: String,
|
|
54
|
-
default: 'Cards',
|
|
55
|
-
},
|
|
56
|
-
/** Label for table button */
|
|
57
|
-
tableLabel: {
|
|
58
|
-
type: String,
|
|
59
|
-
default: 'Table',
|
|
60
|
-
},
|
|
61
|
-
/** Aria label for the toggle group */
|
|
62
|
-
ariaLabel: {
|
|
63
|
-
type: String,
|
|
64
|
-
default: 'View mode',
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
</script>
|
|
69
|
-
|
|
70
|
-
<style scoped>
|
|
71
|
-
.cn-view-mode-toggle {
|
|
72
|
-
display: inline-flex;
|
|
73
|
-
gap: 0;
|
|
74
|
-
border-radius: var(--border-radius-pill, 20px);
|
|
75
|
-
overflow: hidden;
|
|
76
|
-
}
|
|
77
|
-
</style>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as CnViewModeToggle } from './CnViewModeToggle.vue'
|