@conduction/nextcloud-vue 0.1.0-beta.14 → 0.1.0-beta.16
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/nextcloud-vue.cjs.js +7282 -3443
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.css +719 -100
- package/dist/nextcloud-vue.esm.js +7120 -3300
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/package.json +3 -2
- package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +36 -3
- package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +34 -19
- package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +312 -36
- package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +983 -64
- package/src/components/CnAdvancedFormDialog/index.js +3 -0
- package/src/components/CnAppLoading/CnAppLoading.vue +93 -0
- package/src/components/CnAppLoading/index.js +3 -0
- package/src/components/CnAppNav/CnAppNav.vue +269 -0
- package/src/components/CnAppNav/index.js +3 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +201 -0
- package/src/components/CnAppRoot/index.js +3 -0
- package/src/components/CnColorPicker/CnColorPicker.vue +251 -0
- package/src/components/CnColorPicker/index.js +1 -0
- package/src/components/CnContextMenu/CnContextMenu.vue +41 -4
- package/src/components/CnDashboardPage/CnDashboardPage.vue +8 -0
- package/src/components/CnDependencyMissing/CnDependencyMissing.vue +152 -0
- package/src/components/CnDependencyMissing/index.js +3 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +27 -16
- package/src/components/CnIndexPage/CnIndexPage.vue +36 -6
- package/src/components/CnPageRenderer/CnPageRenderer.vue +278 -0
- package/src/components/CnPageRenderer/index.js +4 -0
- package/src/components/CnPageRenderer/pageTypes.js +37 -0
- package/src/components/CnRowActions/CnRowActions.vue +44 -3
- package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +4 -0
- package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +103 -74
- package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +30 -2
- package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +16 -12
- package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +9 -4
- package/src/components/index.js +7 -1
- package/src/composables/index.js +2 -0
- package/src/composables/useAppManifest.js +115 -0
- package/src/composables/useAppStatus.js +107 -0
- package/src/css/CnSchemaFormDialog.css +22 -0
- package/src/index.js +24 -2
- package/src/schemas/app-manifest.schema.json +153 -0
- package/src/types/index.d.ts +9 -0
- package/src/types/manifest.d.ts +88 -0
- package/src/utils/index.js +1 -1
- package/src/utils/schema.js +157 -2
- package/src/utils/validateManifest.js +113 -0
|
@@ -1 +1,4 @@
|
|
|
1
1
|
export { default as CnAdvancedFormDialog } from './CnAdvancedFormDialog.vue'
|
|
2
|
+
export { default as CnPropertiesTab } from './CnPropertiesTab.vue'
|
|
3
|
+
export { default as CnMetadataTab } from './CnMetadataTab.vue'
|
|
4
|
+
export { default as CnPropertyValueCell } from './CnPropertyValueCell.vue'
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CnAppLoading — full-page loading screen used during the loading phase
|
|
3
|
+
of CnAppRoot (while useAppManifest is fetching).
|
|
4
|
+
|
|
5
|
+
Apps can override CnAppRoot's #loading slot to swap branding /
|
|
6
|
+
messaging. The default rendering shows an optional logo, the
|
|
7
|
+
Nextcloud loading spinner, and a message.
|
|
8
|
+
|
|
9
|
+
See REQ-JMR-010 of the json-manifest-renderer specification.
|
|
10
|
+
-->
|
|
11
|
+
<template>
|
|
12
|
+
<div class="cn-app-loading">
|
|
13
|
+
<div class="cn-app-loading__inner">
|
|
14
|
+
<slot name="logo">
|
|
15
|
+
<img
|
|
16
|
+
v-if="logoUrl"
|
|
17
|
+
:src="logoUrl"
|
|
18
|
+
alt=""
|
|
19
|
+
class="cn-app-loading__logo">
|
|
20
|
+
</slot>
|
|
21
|
+
<NcLoadingIcon :size="48" />
|
|
22
|
+
<p class="cn-app-loading__message">
|
|
23
|
+
{{ message }}
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script>
|
|
30
|
+
import { NcLoadingIcon } from '@nextcloud/vue'
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
name: 'CnAppLoading',
|
|
34
|
+
|
|
35
|
+
components: {
|
|
36
|
+
NcLoadingIcon,
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
props: {
|
|
40
|
+
/**
|
|
41
|
+
* Loading message displayed below the spinner. Plain string —
|
|
42
|
+
* the consuming app pre-translates if needed (this library
|
|
43
|
+
* never imports `t()` from a specific app).
|
|
44
|
+
*
|
|
45
|
+
* @type {string}
|
|
46
|
+
*/
|
|
47
|
+
message: {
|
|
48
|
+
type: String,
|
|
49
|
+
default: 'Loading...',
|
|
50
|
+
},
|
|
51
|
+
/**
|
|
52
|
+
* Optional logo image URL displayed above the spinner. Apps
|
|
53
|
+
* with custom branding can also override the `#logo` slot.
|
|
54
|
+
*
|
|
55
|
+
* @type {string}
|
|
56
|
+
*/
|
|
57
|
+
logoUrl: {
|
|
58
|
+
type: String,
|
|
59
|
+
default: '',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<style>
|
|
66
|
+
.cn-app-loading {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
justify-content: center;
|
|
70
|
+
width: 100%;
|
|
71
|
+
min-height: 100vh;
|
|
72
|
+
background: var(--color-main-background);
|
|
73
|
+
color: var(--color-main-text);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.cn-app-loading__inner {
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: calc(2 * var(--default-grid-baseline));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.cn-app-loading__logo {
|
|
84
|
+
max-width: 96px;
|
|
85
|
+
max-height: 96px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.cn-app-loading__message {
|
|
89
|
+
margin: 0;
|
|
90
|
+
color: var(--color-text-maxcontrast);
|
|
91
|
+
font-size: var(--default-font-size);
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CnAppNav — manifest-driven app navigation.
|
|
3
|
+
|
|
4
|
+
Renders the manifest's `menu[]` array as a Nextcloud app navigation
|
|
5
|
+
(NcAppNavigation + NcAppNavigationItem). One level of nested
|
|
6
|
+
`children[]` is supported. Items are sorted by `order`; items without
|
|
7
|
+
an order render last. Items with a `permission` are filtered against
|
|
8
|
+
the `permissions` prop — when the prop is omitted, all items render.
|
|
9
|
+
|
|
10
|
+
Items split into two groups by `section`:
|
|
11
|
+
- `section: "main"` (default) — top of the navigation, scrollable.
|
|
12
|
+
- `section: "settings"` — pinned to the bottom inside NcAppNavigation's
|
|
13
|
+
`#footer` slot, always visible above the close-toggle, separated
|
|
14
|
+
from the main list by a thin border. Use for documentation links,
|
|
15
|
+
settings entries, or anything that should sit at the bottom.
|
|
16
|
+
|
|
17
|
+
Manifest and translate are injected from CnAppRoot by default but can
|
|
18
|
+
also be passed as props for standalone use without CnAppRoot. Props
|
|
19
|
+
win over inject when both are present.
|
|
20
|
+
|
|
21
|
+
See REQ-JMR-004 of the json-manifest-renderer specification.
|
|
22
|
+
-->
|
|
23
|
+
<template>
|
|
24
|
+
<NcAppNavigation>
|
|
25
|
+
<template #list>
|
|
26
|
+
<NcAppNavigationItem
|
|
27
|
+
v-for="item in mainItems"
|
|
28
|
+
:key="item.id"
|
|
29
|
+
:name="resolveLabel(item)"
|
|
30
|
+
:to="itemTo(item)"
|
|
31
|
+
:exact="isExact(item)"
|
|
32
|
+
:icon="item.icon"
|
|
33
|
+
:active="isActive(item)"
|
|
34
|
+
@click="onItemClick(item, $event)">
|
|
35
|
+
<NcAppNavigationItem
|
|
36
|
+
v-for="child in visibleChildren(item)"
|
|
37
|
+
:key="child.id"
|
|
38
|
+
:name="resolveLabel(child)"
|
|
39
|
+
:to="itemTo(child)"
|
|
40
|
+
:exact="isExact(child)"
|
|
41
|
+
:icon="child.icon"
|
|
42
|
+
:active="isActive(child)"
|
|
43
|
+
@click="onItemClick(child, $event)" />
|
|
44
|
+
</NcAppNavigationItem>
|
|
45
|
+
</template>
|
|
46
|
+
<template v-if="settingsItems.length" #footer>
|
|
47
|
+
<ul class="cn-app-nav__footer-list">
|
|
48
|
+
<NcAppNavigationItem
|
|
49
|
+
v-for="item in settingsItems"
|
|
50
|
+
:key="item.id"
|
|
51
|
+
:name="resolveLabel(item)"
|
|
52
|
+
:to="itemTo(item)"
|
|
53
|
+
:exact="isExact(item)"
|
|
54
|
+
:icon="item.icon"
|
|
55
|
+
:active="isActive(item)"
|
|
56
|
+
@click="onItemClick(item, $event)" />
|
|
57
|
+
</ul>
|
|
58
|
+
</template>
|
|
59
|
+
</NcAppNavigation>
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<script>
|
|
63
|
+
import { NcAppNavigation, NcAppNavigationItem } from '@nextcloud/vue'
|
|
64
|
+
|
|
65
|
+
export default {
|
|
66
|
+
name: 'CnAppNav',
|
|
67
|
+
|
|
68
|
+
components: {
|
|
69
|
+
NcAppNavigation,
|
|
70
|
+
NcAppNavigationItem,
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
inject: {
|
|
74
|
+
cnManifest: { default: null },
|
|
75
|
+
cnTranslate: { default: () => (key) => key },
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
props: {
|
|
79
|
+
/**
|
|
80
|
+
* Manifest object. Falls back to injected `cnManifest`. Provide
|
|
81
|
+
* explicitly when mounting CnAppNav outside of CnAppRoot.
|
|
82
|
+
*
|
|
83
|
+
* @type {object|null}
|
|
84
|
+
*/
|
|
85
|
+
manifest: {
|
|
86
|
+
type: Object,
|
|
87
|
+
default: null,
|
|
88
|
+
},
|
|
89
|
+
/**
|
|
90
|
+
* Translate function. Falls back to injected `cnTranslate`,
|
|
91
|
+
* which itself defaults to an identity function.
|
|
92
|
+
*
|
|
93
|
+
* @type {Function|null}
|
|
94
|
+
*/
|
|
95
|
+
translate: {
|
|
96
|
+
type: Function,
|
|
97
|
+
default: null,
|
|
98
|
+
},
|
|
99
|
+
/**
|
|
100
|
+
* List of permission strings the current user holds. Items
|
|
101
|
+
* declaring a `permission` only render when their permission
|
|
102
|
+
* appears in this list. When the prop is omitted (or empty),
|
|
103
|
+
* all items are visible regardless of their permission field.
|
|
104
|
+
*
|
|
105
|
+
* @type {Array<string>}
|
|
106
|
+
*/
|
|
107
|
+
permissions: {
|
|
108
|
+
type: Array,
|
|
109
|
+
default: () => [],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
computed: {
|
|
114
|
+
effectiveManifest() {
|
|
115
|
+
return this.manifest ?? this.cnManifest
|
|
116
|
+
},
|
|
117
|
+
effectiveTranslate() {
|
|
118
|
+
return this.translate ?? this.cnTranslate
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* All visible items (filtered by permission, sorted by order).
|
|
122
|
+
* Retained for backwards-compat with the previous public API and
|
|
123
|
+
* tests that read this computed; new code should use
|
|
124
|
+
* `mainItems` / `settingsItems` instead.
|
|
125
|
+
*/
|
|
126
|
+
visibleItems() {
|
|
127
|
+
const items = this.effectiveManifest?.menu ?? []
|
|
128
|
+
return items
|
|
129
|
+
.filter((item) => this.passesPermission(item))
|
|
130
|
+
.slice()
|
|
131
|
+
.sort((a, b) => {
|
|
132
|
+
const aHas = typeof a.order === 'number'
|
|
133
|
+
const bHas = typeof b.order === 'number'
|
|
134
|
+
if (aHas && !bHas) return -1
|
|
135
|
+
if (!aHas && bHas) return 1
|
|
136
|
+
if (!aHas && !bHas) return 0
|
|
137
|
+
return a.order - b.order
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
/** Items that render in the top list (default placement). */
|
|
141
|
+
mainItems() {
|
|
142
|
+
return this.visibleItems.filter((item) => (item.section ?? 'main') === 'main')
|
|
143
|
+
},
|
|
144
|
+
/**
|
|
145
|
+
* Items that render inside the `#footer` slot of NcAppNavigation
|
|
146
|
+
* — always visible at the bottom of the navigation, above the
|
|
147
|
+
* close-toggle. Use for help / docs / settings entries that
|
|
148
|
+
* should anchor to the bottom rather than scroll with the main
|
|
149
|
+
* list.
|
|
150
|
+
*/
|
|
151
|
+
settingsItems() {
|
|
152
|
+
return this.visibleItems.filter((item) => item.section === 'settings')
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
methods: {
|
|
157
|
+
passesPermission(item) {
|
|
158
|
+
if (!item.permission) return true
|
|
159
|
+
if (!this.permissions || this.permissions.length === 0) return true
|
|
160
|
+
return this.permissions.includes(item.permission)
|
|
161
|
+
},
|
|
162
|
+
visibleChildren(item) {
|
|
163
|
+
if (!Array.isArray(item.children)) return []
|
|
164
|
+
return item.children.filter((c) => this.passesPermission(c))
|
|
165
|
+
},
|
|
166
|
+
resolveLabel(item) {
|
|
167
|
+
return this.effectiveTranslate(item.label)
|
|
168
|
+
},
|
|
169
|
+
isActive(item) {
|
|
170
|
+
if (item.href || !item.route) return false
|
|
171
|
+
return this.$route?.name === item.route
|
|
172
|
+
},
|
|
173
|
+
/**
|
|
174
|
+
* Look up an item's resolved page (`pages[]` entry whose `id`
|
|
175
|
+
* matches the menu item's `route`) — used to decide whether the
|
|
176
|
+
* NcAppNavigationItem should match its router-link `exact`.
|
|
177
|
+
*
|
|
178
|
+
* @param {object} item Menu item to resolve.
|
|
179
|
+
* @return {object|null} Matching page entry, or null when the item
|
|
180
|
+
* has no `route` or no page matches.
|
|
181
|
+
*/
|
|
182
|
+
pageForItem(item) {
|
|
183
|
+
if (!item.route) return null
|
|
184
|
+
const pages = this.effectiveManifest?.pages ?? []
|
|
185
|
+
return pages.find((p) => p.id === item.route) ?? null
|
|
186
|
+
},
|
|
187
|
+
/**
|
|
188
|
+
* Pass-through for `NcAppNavigationItem`'s router-link `exact`.
|
|
189
|
+
* Root paths (`/`) match every nested route by default, which
|
|
190
|
+
* makes the root item permanently look active. Returning true
|
|
191
|
+
* for `route === '/'` restores the expected behaviour.
|
|
192
|
+
*
|
|
193
|
+
* @param {object} item Menu item being rendered.
|
|
194
|
+
* @return {boolean} Whether to enable exact router-link matching.
|
|
195
|
+
*/
|
|
196
|
+
isExact(item) {
|
|
197
|
+
const page = this.pageForItem(item)
|
|
198
|
+
return page?.route === '/'
|
|
199
|
+
},
|
|
200
|
+
/**
|
|
201
|
+
* Build the `:to` value for an `NcAppNavigationItem`. External
|
|
202
|
+
* (`href`) items return `null` so the underlying anchor falls
|
|
203
|
+
* through to a click handler instead of vue-router; route items
|
|
204
|
+
* return a named route.
|
|
205
|
+
*
|
|
206
|
+
* @param {object} item Menu item being rendered.
|
|
207
|
+
* @return {object|null} A `{ name }` route object, or null for
|
|
208
|
+
* external / route-less items.
|
|
209
|
+
*/
|
|
210
|
+
itemTo(item) {
|
|
211
|
+
if (item.href) return null
|
|
212
|
+
return item.route ? { name: item.route } : null
|
|
213
|
+
},
|
|
214
|
+
/**
|
|
215
|
+
* Click handler. For external (`href`) items, opens the URL in a
|
|
216
|
+
* new tab with safe rel attributes. Route items are handled by
|
|
217
|
+
* `:to` and skip this path.
|
|
218
|
+
*
|
|
219
|
+
* @param {object} item Menu item being clicked.
|
|
220
|
+
* @param {Event} [event] Native click event (used to call
|
|
221
|
+
* preventDefault for external links).
|
|
222
|
+
*/
|
|
223
|
+
onItemClick(item, event) {
|
|
224
|
+
if (!item.href) return
|
|
225
|
+
if (event && typeof event.preventDefault === 'function') {
|
|
226
|
+
event.preventDefault()
|
|
227
|
+
}
|
|
228
|
+
window.open(item.href, '_blank', 'noopener,noreferrer')
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
</script>
|
|
233
|
+
|
|
234
|
+
<style>
|
|
235
|
+
/*
|
|
236
|
+
* The legacy `icon-*` classes in Nextcloud render a background-image
|
|
237
|
+
* with a hardcoded dark fill, so they stay grey when an entry becomes
|
|
238
|
+
* active (text turns white against the primary-element background).
|
|
239
|
+
* Force the icon to white in the active state to match the label.
|
|
240
|
+
* Only applies when the icon is provided via the `icon` prop (CSS
|
|
241
|
+
* class) — items using a `<template #icon>` MDI component already
|
|
242
|
+
* inherit `currentColor`.
|
|
243
|
+
*/
|
|
244
|
+
.app-navigation-entry.active .app-navigation-entry-icon[class*="icon-"] {
|
|
245
|
+
filter: brightness(0) invert(1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/*
|
|
249
|
+
* Footer-list (section: "settings" items rendered in NcAppNavigation's
|
|
250
|
+
* `#footer` slot). Reset list defaults so the entries align with the
|
|
251
|
+
* main list, and add a thin separator above the group so it visually
|
|
252
|
+
* detaches from the scrollable list when the two meet.
|
|
253
|
+
*
|
|
254
|
+
* NcAppNavigation's scoped style targets `.app-navigation__content >
|
|
255
|
+
* ul` with `overflow-y: auto` + flex padding, and Vue 2 propagates the
|
|
256
|
+
* parent's data-v attribute onto slot-root elements — so without these
|
|
257
|
+
* overrides the footer list renders its own scrollbar even though the
|
|
258
|
+
* footer area has plenty of room. The `!important` flags force-beat
|
|
259
|
+
* the parent rule's specificity (which includes the data-v attribute).
|
|
260
|
+
*/
|
|
261
|
+
.cn-app-nav__footer-list {
|
|
262
|
+
list-style: none;
|
|
263
|
+
margin: 0;
|
|
264
|
+
padding: 0 !important;
|
|
265
|
+
border-top: 1px solid var(--color-border);
|
|
266
|
+
overflow: visible !important;
|
|
267
|
+
flex: 0 0 auto;
|
|
268
|
+
}
|
|
269
|
+
</style>
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CnAppRoot — top-level wrapper for manifest-driven Conduction apps.
|
|
3
|
+
|
|
4
|
+
Provides the manifest, custom-component registry, and translate
|
|
5
|
+
function to descendants via Vue's `provide`/`inject`. Orchestrates
|
|
6
|
+
three rendering phases:
|
|
7
|
+
|
|
8
|
+
1. Loading — while useAppManifest.isLoading is true.
|
|
9
|
+
Default: <CnAppLoading />. Override: #loading slot.
|
|
10
|
+
2. Dependency — after loading; when any entry in
|
|
11
|
+
manifest.dependencies is not installed/enabled.
|
|
12
|
+
Default: <CnDependencyMissing />. Override:
|
|
13
|
+
#dependency-missing slot.
|
|
14
|
+
3. Shell — manifest loaded + dependencies satisfied.
|
|
15
|
+
Renders #menu (default <CnAppNav />) + the
|
|
16
|
+
consuming app's <router-view>, plus optional
|
|
17
|
+
#header-actions, #sidebar, and #footer slots.
|
|
18
|
+
|
|
19
|
+
Consuming apps that want manifest-driven pages but their own root
|
|
20
|
+
layout can skip CnAppRoot entirely and use CnPageRenderer / CnAppNav
|
|
21
|
+
with explicit props (the props-vs-inject fallback). CnAppRoot is the
|
|
22
|
+
full-shell convenience.
|
|
23
|
+
|
|
24
|
+
See REQ-JMR-003 and REQ-JMR-013 of the json-manifest-renderer spec.
|
|
25
|
+
-->
|
|
26
|
+
<template>
|
|
27
|
+
<NcContent :app-name="appId">
|
|
28
|
+
<!-- Loading phase -->
|
|
29
|
+
<template v-if="phase === 'loading'">
|
|
30
|
+
<slot name="loading">
|
|
31
|
+
<CnAppLoading />
|
|
32
|
+
</slot>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<!-- Dependency-check phase -->
|
|
36
|
+
<template v-else-if="phase === 'dependency-missing'">
|
|
37
|
+
<slot name="dependency-missing" :dependencies="unresolvedDependencies">
|
|
38
|
+
<CnDependencyMissing
|
|
39
|
+
:dependencies="unresolvedDependencies"
|
|
40
|
+
:app-name="appId" />
|
|
41
|
+
</slot>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<!-- Shell phase -->
|
|
45
|
+
<template v-else>
|
|
46
|
+
<slot name="menu">
|
|
47
|
+
<CnAppNav :permissions="permissions" />
|
|
48
|
+
</slot>
|
|
49
|
+
<NcAppContent>
|
|
50
|
+
<router-view />
|
|
51
|
+
<slot name="header-actions" />
|
|
52
|
+
<slot name="footer" />
|
|
53
|
+
</NcAppContent>
|
|
54
|
+
<slot name="sidebar" />
|
|
55
|
+
</template>
|
|
56
|
+
</NcContent>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script>
|
|
60
|
+
import { NcAppContent, NcContent } from '@nextcloud/vue'
|
|
61
|
+
import CnAppNav from '../CnAppNav/CnAppNav.vue'
|
|
62
|
+
import CnAppLoading from '../CnAppLoading/CnAppLoading.vue'
|
|
63
|
+
import CnDependencyMissing from '../CnDependencyMissing/CnDependencyMissing.vue'
|
|
64
|
+
import { useAppStatus } from '../../composables/useAppStatus.js'
|
|
65
|
+
|
|
66
|
+
export default {
|
|
67
|
+
name: 'CnAppRoot',
|
|
68
|
+
|
|
69
|
+
components: {
|
|
70
|
+
NcAppContent,
|
|
71
|
+
NcContent,
|
|
72
|
+
CnAppNav,
|
|
73
|
+
CnAppLoading,
|
|
74
|
+
CnDependencyMissing,
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
provide() {
|
|
78
|
+
return {
|
|
79
|
+
cnManifest: this.manifest,
|
|
80
|
+
cnCustomComponents: this.customComponents,
|
|
81
|
+
cnTranslate: this.translate,
|
|
82
|
+
cnPageTypes: this.pageTypes,
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
props: {
|
|
87
|
+
/**
|
|
88
|
+
* Reactive manifest object (from useAppManifest). The renderer
|
|
89
|
+
* reads `manifest.dependencies`, `manifest.menu`, and is
|
|
90
|
+
* propagated to descendants via provide/inject.
|
|
91
|
+
*
|
|
92
|
+
* @type {object}
|
|
93
|
+
*/
|
|
94
|
+
manifest: {
|
|
95
|
+
type: Object,
|
|
96
|
+
required: true,
|
|
97
|
+
},
|
|
98
|
+
/**
|
|
99
|
+
* Nextcloud app id. Forwarded to NcContent as `app-name` and
|
|
100
|
+
* to CnDependencyMissing for the heading.
|
|
101
|
+
*
|
|
102
|
+
* @type {string}
|
|
103
|
+
*/
|
|
104
|
+
appId: {
|
|
105
|
+
type: String,
|
|
106
|
+
required: true,
|
|
107
|
+
},
|
|
108
|
+
/**
|
|
109
|
+
* Whether the manifest is still loading from the backend.
|
|
110
|
+
* Typically wired to `useAppManifest().isLoading`. Defaults to
|
|
111
|
+
* false so that apps using only the bundled manifest skip the
|
|
112
|
+
* loading phase.
|
|
113
|
+
*
|
|
114
|
+
* @type {boolean}
|
|
115
|
+
*/
|
|
116
|
+
isLoading: {
|
|
117
|
+
type: Boolean,
|
|
118
|
+
default: false,
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* Custom-component registry consumed by CnPageRenderer for
|
|
122
|
+
* `type: "custom"` pages and slot overrides. Empty by default.
|
|
123
|
+
*
|
|
124
|
+
* @type {object}
|
|
125
|
+
*/
|
|
126
|
+
customComponents: {
|
|
127
|
+
type: Object,
|
|
128
|
+
default: () => ({}),
|
|
129
|
+
},
|
|
130
|
+
/**
|
|
131
|
+
* Translate function provided by the consuming app. The library
|
|
132
|
+
* never imports `t()` from a specific app, so the consumer
|
|
133
|
+
* passes its own translator. Typically a closure over the
|
|
134
|
+
* Nextcloud `t()` mixin pre-bound to the app id, e.g.
|
|
135
|
+
* `(key) => t(appId, key)`.
|
|
136
|
+
*
|
|
137
|
+
* Defaults to an identity function so untranslated keys surface
|
|
138
|
+
* visibly rather than crashing.
|
|
139
|
+
*
|
|
140
|
+
* Note: the prop is named `translate` (not `t`) to avoid
|
|
141
|
+
* shadowing the global `t()` method that Conduction apps
|
|
142
|
+
* install via `Vue.mixin({ methods: { t, n } })`. The provide
|
|
143
|
+
* key is `cnTranslate`.
|
|
144
|
+
*
|
|
145
|
+
* @type {Function}
|
|
146
|
+
*/
|
|
147
|
+
translate: {
|
|
148
|
+
type: Function,
|
|
149
|
+
default: (key) => key,
|
|
150
|
+
},
|
|
151
|
+
/**
|
|
152
|
+
* List of permission strings the current user holds. Forwarded
|
|
153
|
+
* to CnAppNav's permission filter.
|
|
154
|
+
*
|
|
155
|
+
* @type {Array<string>}
|
|
156
|
+
*/
|
|
157
|
+
permissions: {
|
|
158
|
+
type: Array,
|
|
159
|
+
default: () => [],
|
|
160
|
+
},
|
|
161
|
+
/**
|
|
162
|
+
* Page-type registry. Map of `pages[].type` → Vue component.
|
|
163
|
+
* Provided to descendant CnPageRenderer instances via inject.
|
|
164
|
+
* When omitted, the renderer falls back to the library's
|
|
165
|
+
* `defaultPageTypes`. Apps with custom page types pass a merged
|
|
166
|
+
* map: `{ ...defaultPageTypes, report: MyReportPage }`.
|
|
167
|
+
*
|
|
168
|
+
* @type {object|null}
|
|
169
|
+
*/
|
|
170
|
+
pageTypes: {
|
|
171
|
+
type: Object,
|
|
172
|
+
default: null,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
computed: {
|
|
177
|
+
/**
|
|
178
|
+
* Per-dependency status, computed once per `appId` declared in
|
|
179
|
+
* `manifest.dependencies`. Reading the value here triggers the
|
|
180
|
+
* useAppStatus composable for each id; results are cached
|
|
181
|
+
* module-side so subsequent reads are free.
|
|
182
|
+
*/
|
|
183
|
+
dependencyStatuses() {
|
|
184
|
+
const deps = Array.isArray(this.manifest?.dependencies)
|
|
185
|
+
? this.manifest.dependencies
|
|
186
|
+
: []
|
|
187
|
+
return deps.map((id) => ({ id, status: useAppStatus(id) }))
|
|
188
|
+
},
|
|
189
|
+
unresolvedDependencies() {
|
|
190
|
+
return this.dependencyStatuses
|
|
191
|
+
.filter(({ status }) => !status.installed.value || !status.enabled.value)
|
|
192
|
+
.map(({ id }) => ({ id, name: id, enabled: false }))
|
|
193
|
+
},
|
|
194
|
+
phase() {
|
|
195
|
+
if (this.isLoading) return 'loading'
|
|
196
|
+
if (this.unresolvedDependencies.length > 0) return 'dependency-missing'
|
|
197
|
+
return 'shell'
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
</script>
|