@conduction/nextcloud-vue 2.56.0-beta.13 → 2.56.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +19 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +10 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +2 -2
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +37 -26
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js +3 -3
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +4 -4
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +89 -49
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +337 -315
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/defaultActions.js +6 -7
- package/dist/esm/components/CnIndexPage/defaultActions.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +33 -34
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js +3 -3
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +5 -11
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +3 -2
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useContextMenu.js +4 -2
- package/dist/esm/composables/useContextMenu.js.map +1 -1
- package/dist/esm/composables/useDependencyCheck.js +44 -1
- package/dist/esm/composables/useDependencyCheck.js.map +1 -1
- package/dist/esm/composables/useListView.js +43 -1
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/composables/useSetupStatus.js +26 -3
- package/dist/esm/composables/useSetupStatus.js.map +1 -1
- package/dist/esm/l10n/en.json.js +4 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +4 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +20 -2
- package/dist/esm/store/plugins/liveUpdates.js +19 -9
- package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
- package/dist/esm/store/useObjectStore.js +20 -9
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/resolveRowActions.js +130 -0
- package/dist/esm/utils/resolveRowActions.js.map +1 -0
- package/dist/esm/utils/rowActionItem.js +99 -0
- package/dist/esm/utils/rowActionItem.js.map +1 -0
- package/dist/esm/utils/timelineEvents.js +44 -1
- package/dist/esm/utils/timelineEvents.js.map +1 -1
- package/dist/esm/utils/validateManifest.js +59 -3
- package/dist/esm/utils/validateManifest.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1325 -781
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +20 -2
- package/l10n/en.json +4 -1
- package/l10n/nl.json +4 -1
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +2 -2
- package/src/components/CnActionsBar/CnActionsBar.md +2 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +25 -1
- package/src/components/CnAppNav/CnAppNav.vue +2 -2
- package/src/components/CnContextMenu/CnContextMenu.vue +40 -29
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +4 -4
- package/src/components/CnIndexPage/CnIndexPage.md +23 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +108 -52
- package/src/components/CnIndexPage/defaultActions.js +6 -7
- package/src/components/CnRowActions/CnRowActions.vue +36 -37
- package/src/components/CnSetupWizard/CnSetupWizard.vue +5 -11
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +3 -2
- package/src/composables/useContextMenu.js +4 -2
- package/src/composables/useDependencyCheck.js +43 -0
- package/src/composables/useListView.js +43 -1
- package/src/composables/useSetupStatus.js +26 -3
- package/src/css/actions-bar.css +20 -2
- package/src/schemas/app-manifest-v2.schema.json +48 -10
- package/src/schemas/app-manifest.schema.json +1 -1
- package/src/store/plugins/liveUpdates.js +19 -9
- package/src/store/useObjectStore.js +19 -8
- package/src/utils/resolveRowActions.js +127 -0
- package/src/utils/rowActionItem.js +95 -0
- package/src/utils/timelineEvents.js +43 -0
- package/src/utils/validateManifest.js +60 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useContextMenu.js","sources":["../../../src/composables/useContextMenu.js"],"sourcesContent":["import { onBeforeUnmount, ref } from 'vue'\n\nexport const CTX_MENU_CSS_VAR_X = '--cn-ctx-menu-x'\nexport const CTX_MENU_CSS_VAR_Y = '--cn-ctx-menu-y'\nexport const CTX_MENU_DATA_ATTR = 'data-cn-ctx-menu'\n\n/**\n * Marker attribute `CnContextMenu` stamps on its own `.v-popper__popper`\n * element, and the only thing the cursor-positioning CSS keys on. See\n * `src/css/context-menu.css` and `CnContextMenu#tagPopper`.\n *\n * An attribute, not a class: floating-vue binds a dynamic `class` on that\n * element (`--shown` / `--hidden` / `--show-from` / …) and Vue's `patchClass`\n * assigns `el.className` wholesale, so a manually added class is wiped the\n * first time the popper opens. Vue only patches props that appear in the\n * vnode, so an attribute it never renders survives untouched.\n */\nexport const CTX_MENU_POPPER_ATTR = 'data-cn-ctx-menu-popper'\n\n// Internal aliases — keep the original short names for the existing code below.\nconst CSS_VAR_X = CTX_MENU_CSS_VAR_X\nconst CSS_VAR_Y = CTX_MENU_CSS_VAR_Y\nconst DATA_ATTR = CTX_MENU_DATA_ATTR\n\n/**\n * Strip the position CSS vars + data attribute from `<html>`.\n *\n * Full tear-down, used on unmount. Note that positioning no longer depends on\n * the data attribute (the CSS keys on `CTX_MENU_POPPER_CLASS` instead), so\n * calling this while a menu is mid-hide-animation is harmless.\n */\nexport function clearContextMenuPositionDom() {\n\tdocument.documentElement.style.removeProperty(CSS_VAR_X)\n\tdocument.documentElement.style.removeProperty(CSS_VAR_Y)\n\tdocument.documentElement.removeAttribute(DATA_ATTR)\n}\n\n/**\n * Composable for managing a right-click context menu positioned at the cursor.\n *\n * Handles cursor-based positioning through CSS custom properties and a data\n * attribute on `document.documentElement`. The `<NcActions>` template stays in\n * the consuming component — this composable only manages open/close state,\n * the target item reference, and action helpers.\n *\n * Pair with the shared CSS in `src/css/context-menu.css` (auto-imported via\n * `src/css/index.css`), which overrides the transform of the one popper\n * carrying `CTX_MENU_POPPER_ATTR`.\n *\n * @example In a Vue Options API component with setup()\n * ```js\n * import { useContextMenu } from '@conduction/nextcloud-vue'\n *\n * export default {\n * setup() {\n * const ctx = useContextMenu()\n * return {\n * contextMenuOpen: ctx.isOpen,\n * contextMenuRow: ctx.targetItem,\n * openContextMenu: ctx.open,\n * closeContextMenu: ctx.close,\n * isContextActionDisabled: ctx.isActionDisabled,\n * triggerContextAction: ctx.triggerAction,\n * }\n * },\n * }\n * ```\n *\n * @return {{\n * isOpen: import('vue').Ref<boolean>,\n * targetItem: import('vue').Ref<object>,\n * open: (params: { item: object, event: MouseEvent }) => void,\n * close: () => void,\n * isActionDisabled: (action: { disabled?: boolean | ((item: object) => boolean) }) => boolean,\n * triggerAction: (action: { label: string, handler?: (item: object) => void }) => { action: string, row: object },\n * }}\n */\nexport function useContextMenu() {\n\tconst isOpen = ref(false)\n\tconst targetItem = ref(null)\n\n\t/**\n\t * Open the context menu at the cursor position.\n\t *\n\t * Sets CSS custom properties for x/y coordinates on `document.documentElement`\n\t * so the shared CSS can override Popper positioning, plus a data attribute\n\t * marking \"a context menu is currently open\" for consumer CSS and tests.\n\t *\n\t * The vars are written *before* `isOpen` flips, so the popper is always\n\t * positioned with fresh coordinates on the very first frame it shows.\n\t *\n\t * @param {object} params - Context menu trigger parameters.\n\t * @param {object} params.item The item associated with the right-click (row, folder, etc.)\n\t * @param {MouseEvent} params.event The native contextmenu event\n\t */\n\tfunction open({ item, event }) {\n\t\tdocument.documentElement.style.setProperty(CSS_VAR_X, event.clientX + 'px')\n\t\tdocument.documentElement.style.setProperty(CSS_VAR_Y, event.clientY + 'px')\n\t\tdocument.documentElement.setAttribute(DATA_ATTR, '')\n\t\ttargetItem.value = item\n\t\tisOpen.value = true\n\t}\n\n\t/**\n\t * Close the context menu.\n\t *\n\t * Only flips reactive state. `CnContextMenu` drops the data attribute as\n\t * soon as it closes; the CSS vars are deliberately left in place so the\n\t * hide animation keeps its transform, and are overwritten by the next\n\t * `open()` (or cleared wholesale on unmount).\n\t *\n\t * Use as the `@close` handler on `<NcActions>` / `<CnContextMenu>`.\n\t */\n\tfunction close() {\n\t\tisOpen.value = false\n\t\ttargetItem.value = null\n\t}\n\n\t/**\n\t * Resolve whether an action is disabled for the current target item.\n\t * Supports both a static boolean and a function `(item) => boolean`.\n\t *\n\t * @param {object} action Action definition with optional `disabled` field\n\t * @return {boolean}\n\t */\n\tfunction isActionDisabled(action) {\n\t\tif (typeof action.disabled === 'function') {\n\t\t\treturn action.disabled(targetItem.value)\n\t\t}\n\t\treturn !!action.disabled\n\t}\n\n\t/**\n\t * Execute a context menu action.\n\t *\n\t * Calls `action.handler(targetItem)` if a handler exists, then returns a\n\t * payload object the caller can pass to `$emit('action', payload)`.\n\t *\n\t * @param {object} action Action definition with `label` and optional `handler`\n\t * @return {{ action: string, row: object }}\n\t */\n\tfunction triggerAction(action) {\n\t\tif (action.handler && typeof action.handler === 'function') {\n\t\t\taction.handler(targetItem.value)\n\t\t}\n\t\treturn
|
|
1
|
+
{"version":3,"file":"useContextMenu.js","sources":["../../../src/composables/useContextMenu.js"],"sourcesContent":["import { onBeforeUnmount, ref } from 'vue'\nimport { rowActionPayload } from '../utils/rowActionItem.js'\n\nexport const CTX_MENU_CSS_VAR_X = '--cn-ctx-menu-x'\nexport const CTX_MENU_CSS_VAR_Y = '--cn-ctx-menu-y'\nexport const CTX_MENU_DATA_ATTR = 'data-cn-ctx-menu'\n\n/**\n * Marker attribute `CnContextMenu` stamps on its own `.v-popper__popper`\n * element, and the only thing the cursor-positioning CSS keys on. See\n * `src/css/context-menu.css` and `CnContextMenu#tagPopper`.\n *\n * An attribute, not a class: floating-vue binds a dynamic `class` on that\n * element (`--shown` / `--hidden` / `--show-from` / …) and Vue's `patchClass`\n * assigns `el.className` wholesale, so a manually added class is wiped the\n * first time the popper opens. Vue only patches props that appear in the\n * vnode, so an attribute it never renders survives untouched.\n */\nexport const CTX_MENU_POPPER_ATTR = 'data-cn-ctx-menu-popper'\n\n// Internal aliases — keep the original short names for the existing code below.\nconst CSS_VAR_X = CTX_MENU_CSS_VAR_X\nconst CSS_VAR_Y = CTX_MENU_CSS_VAR_Y\nconst DATA_ATTR = CTX_MENU_DATA_ATTR\n\n/**\n * Strip the position CSS vars + data attribute from `<html>`.\n *\n * Full tear-down, used on unmount. Note that positioning no longer depends on\n * the data attribute (the CSS keys on `CTX_MENU_POPPER_CLASS` instead), so\n * calling this while a menu is mid-hide-animation is harmless.\n */\nexport function clearContextMenuPositionDom() {\n\tdocument.documentElement.style.removeProperty(CSS_VAR_X)\n\tdocument.documentElement.style.removeProperty(CSS_VAR_Y)\n\tdocument.documentElement.removeAttribute(DATA_ATTR)\n}\n\n/**\n * Composable for managing a right-click context menu positioned at the cursor.\n *\n * Handles cursor-based positioning through CSS custom properties and a data\n * attribute on `document.documentElement`. The `<NcActions>` template stays in\n * the consuming component — this composable only manages open/close state,\n * the target item reference, and action helpers.\n *\n * Pair with the shared CSS in `src/css/context-menu.css` (auto-imported via\n * `src/css/index.css`), which overrides the transform of the one popper\n * carrying `CTX_MENU_POPPER_ATTR`.\n *\n * @example In a Vue Options API component with setup()\n * ```js\n * import { useContextMenu } from '@conduction/nextcloud-vue'\n *\n * export default {\n * setup() {\n * const ctx = useContextMenu()\n * return {\n * contextMenuOpen: ctx.isOpen,\n * contextMenuRow: ctx.targetItem,\n * openContextMenu: ctx.open,\n * closeContextMenu: ctx.close,\n * isContextActionDisabled: ctx.isActionDisabled,\n * triggerContextAction: ctx.triggerAction,\n * }\n * },\n * }\n * ```\n *\n * @return {{\n * isOpen: import('vue').Ref<boolean>,\n * targetItem: import('vue').Ref<object>,\n * open: (params: { item: object, event: MouseEvent }) => void,\n * close: () => void,\n * isActionDisabled: (action: { disabled?: boolean | ((item: object) => boolean) }) => boolean,\n * triggerAction: (action: { label: string, handler?: (item: object) => void }) => { action: string, row: object },\n * }}\n */\nexport function useContextMenu() {\n\tconst isOpen = ref(false)\n\tconst targetItem = ref(null)\n\n\t/**\n\t * Open the context menu at the cursor position.\n\t *\n\t * Sets CSS custom properties for x/y coordinates on `document.documentElement`\n\t * so the shared CSS can override Popper positioning, plus a data attribute\n\t * marking \"a context menu is currently open\" for consumer CSS and tests.\n\t *\n\t * The vars are written *before* `isOpen` flips, so the popper is always\n\t * positioned with fresh coordinates on the very first frame it shows.\n\t *\n\t * @param {object} params - Context menu trigger parameters.\n\t * @param {object} params.item The item associated with the right-click (row, folder, etc.)\n\t * @param {MouseEvent} params.event The native contextmenu event\n\t */\n\tfunction open({ item, event }) {\n\t\tdocument.documentElement.style.setProperty(CSS_VAR_X, event.clientX + 'px')\n\t\tdocument.documentElement.style.setProperty(CSS_VAR_Y, event.clientY + 'px')\n\t\tdocument.documentElement.setAttribute(DATA_ATTR, '')\n\t\ttargetItem.value = item\n\t\tisOpen.value = true\n\t}\n\n\t/**\n\t * Close the context menu.\n\t *\n\t * Only flips reactive state. `CnContextMenu` drops the data attribute as\n\t * soon as it closes; the CSS vars are deliberately left in place so the\n\t * hide animation keeps its transform, and are overwritten by the next\n\t * `open()` (or cleared wholesale on unmount).\n\t *\n\t * Use as the `@close` handler on `<NcActions>` / `<CnContextMenu>`.\n\t */\n\tfunction close() {\n\t\tisOpen.value = false\n\t\ttargetItem.value = null\n\t}\n\n\t/**\n\t * Resolve whether an action is disabled for the current target item.\n\t * Supports both a static boolean and a function `(item) => boolean`.\n\t *\n\t * @param {object} action Action definition with optional `disabled` field\n\t * @return {boolean}\n\t */\n\tfunction isActionDisabled(action) {\n\t\tif (typeof action.disabled === 'function') {\n\t\t\treturn action.disabled(targetItem.value)\n\t\t}\n\t\treturn !!action.disabled\n\t}\n\n\t/**\n\t * Execute a context menu action.\n\t *\n\t * Calls `action.handler(targetItem)` if a handler exists, then returns a\n\t * payload object the caller can pass to `$emit('action', payload)`.\n\t * The payload is the one CnRowActions and CnContextMenu emit: the label as `action`, plus `id` and `builtin: true` when the action carries them.\n\t *\n\t * @param {object} action Action definition with `label` and optional `handler`\n\t * @return {{ action: string, row: object, id?: string, builtin?: boolean }}\n\t */\n\tfunction triggerAction(action) {\n\t\tif (action.handler && typeof action.handler === 'function') {\n\t\t\taction.handler(targetItem.value)\n\t\t}\n\t\treturn rowActionPayload(action, targetItem.value)\n\t}\n\n\t// Clean up DOM if the consumer unmounts while the menu is open — nothing\n\t// else would ever remove the leftover vars/attribute.\n\tonBeforeUnmount(() => {\n\t\tisOpen.value = false\n\t\ttargetItem.value = null\n\t\tclearContextMenuPositionDom()\n\t})\n\n\treturn {\n\t\tisOpen,\n\t\ttargetItem,\n\t\topen,\n\t\tclose,\n\t\tisActionDisabled,\n\t\ttriggerAction,\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadState } from '@nextcloud/initial-state';
|
|
1
2
|
import { useAppStatus } from './useAppStatus.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -61,5 +62,47 @@ function isDependencyResolved(row) {
|
|
|
61
62
|
return !!row && row.installed === true && row.enabled === true
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Read the `dependency_statuses` initial state an app may inject from PHP.
|
|
67
|
+
*
|
|
68
|
+
* Keyed by app id: `{ installed, enabled }`. Returns `{}` when the app injects
|
|
69
|
+
* none or the state cannot be read, so callers fall back to `useAppStatus`.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} appId The app whose initial state to read.
|
|
72
|
+
* @return {object} Server-reported statuses keyed by app id.
|
|
73
|
+
*
|
|
74
|
+
* @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable
|
|
75
|
+
*/
|
|
76
|
+
function readServerAppStatuses(appId) {
|
|
77
|
+
try {
|
|
78
|
+
const statuses = loadState(appId, 'dependency_statuses', {});
|
|
79
|
+
return (statuses && typeof statuses === 'object') ? statuses : {}
|
|
80
|
+
} catch {
|
|
81
|
+
return {}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The apps in a setup step's `requires` list that are not installed and
|
|
87
|
+
* enabled.
|
|
88
|
+
*
|
|
89
|
+
* This is the one resolution every setup surface uses: `CnSetupWizard` skips a
|
|
90
|
+
* step when it is non-empty, and `useSetupStatus` then counts that step as not
|
|
91
|
+
* applicable rather than unmet. Keeping both on one function is what stops the
|
|
92
|
+
* wizard and the status from disagreeing about the same step.
|
|
93
|
+
*
|
|
94
|
+
* @param {Array<string>} requires The step's `requires` app ids.
|
|
95
|
+
* @param {object} [serverStatuses] Server-reported statuses keyed by app id.
|
|
96
|
+
* @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} The missing apps.
|
|
97
|
+
*
|
|
98
|
+
* @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable
|
|
99
|
+
*/
|
|
100
|
+
function missingRequiredApps(requires, serverStatuses = {}) {
|
|
101
|
+
if (!Array.isArray(requires) || requires.length === 0) {
|
|
102
|
+
return []
|
|
103
|
+
}
|
|
104
|
+
return checkDependencies(requires, serverStatuses).filter((row) => !isDependencyResolved(row))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export { checkDependencies, isDependencyResolved, missingRequiredApps, readServerAppStatuses };
|
|
65
108
|
//# sourceMappingURL=useDependencyCheck.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDependencyCheck.js","sources":["../../../src/composables/useDependencyCheck.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { useAppStatus } from './useAppStatus.js'\n\n/**\n * Resolve `manifest.dependencies` entries to their live status.\n *\n * Accepts the manifest's own shape: an app-id string (a REQUIRED dependency)\n * or `{ id, name?, required? }` where `required` defaults to true. Each entry\n * becomes `{ id, name, required, installed, enabled }`.\n *\n * `serverStatuses` is the optional `dependency_statuses` initial state an app\n * injects from PHP, keyed by app id: `{ installed, enabled }`. It wins over\n * the browser heuristic, because only the server can tell \"installed but\n * disabled\" from \"not installed\". Without it, `useAppStatus` answers, which\n * is the same resolution `CnAppRoot` uses for its dependency-missing phase.\n *\n * @param {Array<string|{id: string, name?: string, required?: boolean}>} entries The declared dependencies.\n * @param {object} [serverStatuses] Server-reported statuses keyed by app id.\n * @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} One row per valid entry.\n *\n * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n *\n * @example\n * const missing = checkDependencies(manifest.dependencies)\n * .filter((d) => d.required && !(d.installed && d.enabled))\n */\nexport function checkDependencies(entries, serverStatuses = {}) {\n\tconst list = Array.isArray(entries) ? entries : []\n\tconst server = (serverStatuses && typeof serverStatuses === 'object') ? serverStatuses : {}\n\treturn list\n\t\t.map((entry) => {\n\t\t\tconst isObject = entry !== null && typeof entry === 'object'\n\t\t\tconst id = isObject ? entry.id : entry\n\t\t\tif (typeof id !== 'string' || id === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst required = isObject ? entry.required !== false : true\n\t\t\tconst name = (isObject && entry.name) || id\n\t\t\tconst reported = server[id]\n\t\t\tif (reported !== undefined && reported !== null) {\n\t\t\t\treturn { id, name, required, installed: !!reported.installed, enabled: !!reported.enabled }\n\t\t\t}\n\t\t\tconst status = useAppStatus(id)\n\t\t\treturn { id, name, required, installed: !!status.installed.value, enabled: !!status.enabled.value }\n\t\t})\n\t\t.filter((row) => row !== null)\n}\n\n/**\n * Whether a resolved dependency row is usable: installed AND enabled.\n *\n * @param {{installed: boolean, enabled: boolean}} row A row from `checkDependencies`.\n * @return {boolean} True when the app can be used.\n */\nexport function isDependencyResolved(row) {\n\treturn !!row && row.installed === true && row.enabled === true\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useDependencyCheck.js","sources":["../../../src/composables/useDependencyCheck.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { loadState } from '@nextcloud/initial-state'\nimport { useAppStatus } from './useAppStatus.js'\n\n/**\n * Resolve `manifest.dependencies` entries to their live status.\n *\n * Accepts the manifest's own shape: an app-id string (a REQUIRED dependency)\n * or `{ id, name?, required? }` where `required` defaults to true. Each entry\n * becomes `{ id, name, required, installed, enabled }`.\n *\n * `serverStatuses` is the optional `dependency_statuses` initial state an app\n * injects from PHP, keyed by app id: `{ installed, enabled }`. It wins over\n * the browser heuristic, because only the server can tell \"installed but\n * disabled\" from \"not installed\". Without it, `useAppStatus` answers, which\n * is the same resolution `CnAppRoot` uses for its dependency-missing phase.\n *\n * @param {Array<string|{id: string, name?: string, required?: boolean}>} entries The declared dependencies.\n * @param {object} [serverStatuses] Server-reported statuses keyed by app id.\n * @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} One row per valid entry.\n *\n * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n *\n * @example\n * const missing = checkDependencies(manifest.dependencies)\n * .filter((d) => d.required && !(d.installed && d.enabled))\n */\nexport function checkDependencies(entries, serverStatuses = {}) {\n\tconst list = Array.isArray(entries) ? entries : []\n\tconst server = (serverStatuses && typeof serverStatuses === 'object') ? serverStatuses : {}\n\treturn list\n\t\t.map((entry) => {\n\t\t\tconst isObject = entry !== null && typeof entry === 'object'\n\t\t\tconst id = isObject ? entry.id : entry\n\t\t\tif (typeof id !== 'string' || id === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst required = isObject ? entry.required !== false : true\n\t\t\tconst name = (isObject && entry.name) || id\n\t\t\tconst reported = server[id]\n\t\t\tif (reported !== undefined && reported !== null) {\n\t\t\t\treturn { id, name, required, installed: !!reported.installed, enabled: !!reported.enabled }\n\t\t\t}\n\t\t\tconst status = useAppStatus(id)\n\t\t\treturn { id, name, required, installed: !!status.installed.value, enabled: !!status.enabled.value }\n\t\t})\n\t\t.filter((row) => row !== null)\n}\n\n/**\n * Whether a resolved dependency row is usable: installed AND enabled.\n *\n * @param {{installed: boolean, enabled: boolean}} row A row from `checkDependencies`.\n * @return {boolean} True when the app can be used.\n */\nexport function isDependencyResolved(row) {\n\treturn !!row && row.installed === true && row.enabled === true\n}\n\n/**\n * Read the `dependency_statuses` initial state an app may inject from PHP.\n *\n * Keyed by app id: `{ installed, enabled }`. Returns `{}` when the app injects\n * none or the state cannot be read, so callers fall back to `useAppStatus`.\n *\n * @param {string} appId The app whose initial state to read.\n * @return {object} Server-reported statuses keyed by app id.\n *\n * @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable\n */\nexport function readServerAppStatuses(appId) {\n\ttry {\n\t\tconst statuses = loadState(appId, 'dependency_statuses', {})\n\t\treturn (statuses && typeof statuses === 'object') ? statuses : {}\n\t} catch {\n\t\treturn {}\n\t}\n}\n\n/**\n * The apps in a setup step's `requires` list that are not installed and\n * enabled.\n *\n * This is the one resolution every setup surface uses: `CnSetupWizard` skips a\n * step when it is non-empty, and `useSetupStatus` then counts that step as not\n * applicable rather than unmet. Keeping both on one function is what stops the\n * wizard and the status from disagreeing about the same step.\n *\n * @param {Array<string>} requires The step's `requires` app ids.\n * @param {object} [serverStatuses] Server-reported statuses keyed by app id.\n * @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} The missing apps.\n *\n * @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable\n */\nexport function missingRequiredApps(requires, serverStatuses = {}) {\n\tif (!Array.isArray(requires) || requires.length === 0) {\n\t\treturn []\n\t}\n\treturn checkDependencies(requires, serverStatuses).filter((row) => !isDependencyResolved(row))\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -94,6 +94,13 @@ function useListView(objectTypeOrOptions, options) {
|
|
|
94
94
|
// field (a folder pane grouping by it, say) reads them here rather than
|
|
95
95
|
// deriving them from `objects`, which is only the current page.
|
|
96
96
|
const facets = computed(() => objectStore.facets[objectType] || {});
|
|
97
|
+
// Outcome of the latest `refresh()`: the store's error for it, or null once
|
|
98
|
+
// it succeeded or a later fetch stored new rows. The store keeps the previous
|
|
99
|
+
// rows on a failed fetch, so this is how a consumer tells stale rows from
|
|
100
|
+
// current ones.
|
|
101
|
+
const error = ref(null);
|
|
102
|
+
let refreshSeq = 0;
|
|
103
|
+
let pendingRefreshes = 0;
|
|
97
104
|
|
|
98
105
|
let searchTimeout = null;
|
|
99
106
|
|
|
@@ -178,14 +185,48 @@ function useListView(objectTypeOrOptions, options) {
|
|
|
178
185
|
|
|
179
186
|
/**
|
|
180
187
|
* Fetch the collection using current state params and update sidebar facet data.
|
|
188
|
+
* Only the most recently started refresh sets `error`, so an older response
|
|
189
|
+
* settling late cannot override a newer one's outcome.
|
|
181
190
|
*
|
|
182
191
|
* @param {number} [page] Page to fetch
|
|
183
192
|
* @return {Promise<void>}
|
|
184
193
|
*/
|
|
185
194
|
async function refresh(page = 1) {
|
|
186
|
-
|
|
195
|
+
const seq = ++refreshSeq;
|
|
196
|
+
// This call's own result. `errors[objectType]` is shared by every call
|
|
197
|
+
// for the type and is not cleared on success, so it cannot say which
|
|
198
|
+
// request failed; it is only the fallback for a store that ignores
|
|
199
|
+
// `options.outcome`.
|
|
200
|
+
const outcome = {};
|
|
201
|
+
pendingRefreshes++;
|
|
202
|
+
try {
|
|
203
|
+
await objectStore.fetchCollection(objectType, buildParams(page), { outcome });
|
|
204
|
+
} catch (e) {
|
|
205
|
+
if (seq === refreshSeq) {
|
|
206
|
+
error.value = e;
|
|
207
|
+
}
|
|
208
|
+
throw e
|
|
209
|
+
} finally {
|
|
210
|
+
pendingRefreshes--;
|
|
211
|
+
}
|
|
212
|
+
if (seq === refreshSeq) {
|
|
213
|
+
error.value = ('error' in outcome) ? outcome.error : (objectStore.errors?.[objectType] || null);
|
|
214
|
+
}
|
|
187
215
|
}
|
|
188
216
|
|
|
217
|
+
// A fetch outside `refresh()` (e.g. the live-updates refetch) that stores
|
|
218
|
+
// new rows recovers the list from an earlier failure. Rows written while
|
|
219
|
+
// a refresh is pending are left to that refresh, which decides `error`.
|
|
220
|
+
watch(
|
|
221
|
+
() => objectStore.collections[objectType],
|
|
222
|
+
() => {
|
|
223
|
+
if (pendingRefreshes === 0) {
|
|
224
|
+
error.value = null;
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
{ flush: 'sync' },
|
|
228
|
+
);
|
|
229
|
+
|
|
189
230
|
// ── Event handlers ───────────────────────────────────────────────────
|
|
190
231
|
|
|
191
232
|
/**
|
|
@@ -323,6 +364,7 @@ function useListView(objectTypeOrOptions, options) {
|
|
|
323
364
|
loading,
|
|
324
365
|
pagination,
|
|
325
366
|
facets,
|
|
367
|
+
error,
|
|
326
368
|
// Local state
|
|
327
369
|
searchTerm,
|
|
328
370
|
sortKey,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useListView.js","sources":["../../../src/composables/useListView.js"],"sourcesContent":["import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useObjectStore } from '../store/index.js'\n\n/**\n * Composable for managing list view state with full objectStore integration.\n *\n * When called with an `objectType` string, connects to the objectStore and handles\n * schema loading, collection fetching, sidebar wiring, and all event handlers\n * automatically. Everything a `CnIndexPage`-based list view needs is returned\n * directly — no additional computed properties or methods required in the component.\n *\n * Backward-compatible: existing `useListView(options)` and `useListView()` calls\n * continue to work without modification.\n *\n * @param {string|object} [objectTypeOrOptions] Object type slug (new API) or legacy options object\n * @param {object} [options] Options (new API only)\n * @param {object|null} [options.objectStore] Custom object store instance (from createObjectStore). When provided, uses this store instead of the default useObjectStore(). Required when the app uses createObjectStore with a custom store ID.\n * @param {object|null} [options.sidebarState] Sidebar state object from `inject('sidebarState')`. When provided, the composable wires and unwires the sidebar automatically on mount/unmount.\n * @param {number} [options.defaultPageSize] Default `_limit` sent to the API\n * @param {number} [options.debounceMs] Search debounce in milliseconds\n * @param {object} [options.defaultSort] Default sort applied on mount e.g. `{ key: 'createdAt', order: 'desc' }`\n * @param {Array<{key: string, order: 'asc'|'desc'}>} [options.defaultSortKeys] Default MULTI-column sort applied on mount (ordered priority list). Takes precedence over `defaultSort` when both are given; used e.g. to restore a sort persisted in the route query.\n * @param {Array<string>|null} [options.defaultVisibleColumns] Initial visible-column key set. Seeds the sidebar's Columns tab with the curated default (e.g. a manifest `columns` list) so toggles add/remove from it. Omit (or `null`) for schema-driven tables where every column starts visible.\n * @param {object} [options.defaultActiveFilters] Initial facet-filter map, e.g. to restore filters persisted in the route query on mount.\n * @param {string} [options.defaultSearchTerm] Initial search term, e.g. to restore `_search` from the route query on mount.\n * @param {object|(() => object)} [options.fixedFilters] A filter map (or getter returning one) merged into every fetch AFTER the user's facet filters, so the fixed entries always win. Used e.g. by `CnIndexPage` to apply a route-param-scoped `pages[].config.filter`. Default `{}` — omitting it is behaviourally identical to before.\n * @param {Array<string>|(() => Array<string>)} [options.extend] OpenRegister `_extend[]` values (or a getter returning them) forwarded on every fetch, e.g. `['calculations']` so a schema's `materialise: false` calculations are evaluated and can be shown as columns. Default `[]` — omitting it sends no `_extend` at all, exactly as before.\n * @return {object} Reactive state and event handlers\n *\n * @example\n * // New API — minimal\n * const { schema, objects, loading, pagination,\n * onSearch, onSort, onFilterChange, onPageChange, refresh } = useListView('client')\n *\n * @example\n * // New API — with sidebar wiring\n * const list = useListView('client', {\n * sidebarState: inject('sidebarState', null),\n * })\n *\n * @example\n * // Legacy API — still works\n * const { searchTerm, filters, onSearchInput, toggleSort } = useListView({\n * objectType: 'client',\n * fetchFn: (type, params) => objectStore.fetchCollection(type, params),\n * })\n */\nexport function useListView(objectTypeOrOptions, options) {\n\t// Backward compat: if first arg is an object or absent, delegate to legacy implementation\n\tif (!objectTypeOrOptions || typeof objectTypeOrOptions === 'object') {\n\t\treturn useLegacyListView(objectTypeOrOptions || {})\n\t}\n\n\t// ── New API ──────────────────────────────────────────────────────────\n\tconst objectType = objectTypeOrOptions\n\tconst opts = options || {}\n\tconst sidebarState = opts.sidebarState || null\n\n\tconst objectStore = opts.objectStore || useObjectStore()\n\n\t// ── State refs ───────────────────────────────────────────────────────\n\tconst schema = ref(null)\n\tconst searchTerm = ref(opts.defaultSearchTerm || '')\n\t// `sortKeys` is the source of truth (ordered multi-column priority list);\n\t// `sortKey`/`sortOrder` are kept as a primary-entry mirror so every\n\t// existing reader of the pre-multi-sort refs keeps working unchanged.\n\tconst initialSortKeys = Array.isArray(opts.defaultSortKeys) && opts.defaultSortKeys.length > 0\n\t\t? opts.defaultSortKeys\n\t\t: (opts.defaultSort?.key ? [{ key: opts.defaultSort.key, order: opts.defaultSort.order || 'asc' }] : [])\n\tconst sortKeys = ref(initialSortKeys)\n\tconst sortKey = ref(initialSortKeys[0]?.key || null)\n\tconst sortOrder = ref(initialSortKeys[0]?.order || 'asc')\n\tconst activeFilters = ref((opts.defaultActiveFilters && typeof opts.defaultActiveFilters === 'object') ? opts.defaultActiveFilters : {})\n\t// Seed the visible-column set to the configured columns so the sidebar's\n\t// Columns tab reflects the curated default and toggles add/remove from it.\n\t// `null` (no seed) means \"all columns visible\" (schema-driven tables).\n\tconst visibleColumns = ref(opts.defaultVisibleColumns ?? null)\n\tconst pageSize = ref(opts.defaultPageSize || 20)\n\n\t// ── Computed refs from the store ─────────────────────────────────────\n\tconst objects = computed(() => objectStore.collections[objectType] || [])\n\t// True from creation until the mount sequence below has issued its first\n\t// fetch. The store's own flag only goes up once `fetchCollection` runs, and\n\t// `onMounted` awaits `fetchSchema()` first — so for the length of that round\n\t// trip the list was \"not loading\" with zero rows, and every consumer\n\t// rendered its empty state (\"No log entries to show\") before any data had\n\t// been asked for. Folded into `loading` rather than exposed separately so\n\t// existing consumers get the fix without touching their templates.\n\tconst bootstrapping = ref(true)\n\tconst loading = computed(() => bootstrapping.value || objectStore.loading[objectType] || false)\n\tconst pagination = computed(() => objectStore.pagination[objectType] || { total: 0, page: 1, pages: 1, limit: 20 })\n\t// Facets are computed by the platform over the whole query rather than the\n\t// loaded page, so anything that needs the complete set of values for a\n\t// field (a folder pane grouping by it, say) reads them here rather than\n\t// deriving them from `objects`, which is only the current page.\n\tconst facets = computed(() => objectStore.facets[objectType] || {})\n\n\tlet searchTimeout = null\n\n\t// ── Param construction ───────────────────────────────────────────────\n\n\t/**\n\t * Resolve `opts.fixedFilters` to a plain map. Accepts a plain object OR a\n\t * function/getter returning one (so callers can derive it from reactive\n\t * sources — e.g. route params — and have it re-read on every fetch).\n\t *\n\t * @return {object} The fixed-filter map (may be empty).\n\t */\n\tfunction resolveFixedFilters() {\n\t\tconst f = typeof opts.fixedFilters === 'function' ? opts.fixedFilters() : opts.fixedFilters\n\t\treturn (f && typeof f === 'object') ? f : {}\n\t}\n\n\t/**\n\t * Resolve `opts.extend` to a list of non-empty strings.\n\t *\n\t * Accepts an array or a getter returning one, so a caller can re-scope it\n\t * between fetches the way `fixedFilters` allows.\n\t *\n\t * @return {Array<string>} The `_extend[]` values, possibly empty.\n\t */\n\tfunction resolveExtend() {\n\t\tconst e = typeof opts.extend === 'function' ? opts.extend() : opts.extend\n\t\tif (!Array.isArray(e)) {\n\t\t\treturn []\n\t\t}\n\t\treturn e.filter((v) => typeof v === 'string' && v !== '')\n\t}\n\n\t/**\n\t * Build API fetch params from current reactive state.\n\t *\n\t * @param {number} page Page number to request\n\t * @return {object} Params object ready to pass to fetchCollection\n\t */\n\tfunction buildParams(page) {\n\t\tconst params = { _limit: pageSize.value, _page: page }\n\n\t\tif (searchTerm.value) {\n\t\t\tparams._search = searchTerm.value\n\t\t}\n\n\t\tif (sortKeys.value.length > 0) {\n\t\t\t// Object key insertion order = sort priority order, matching\n\t\t\t// OpenRegister's `_order` format exactly (`ObjectsController::\n\t\t\t// normalizeOrderParameter` / `MagicMapper`'s ordering loop). A\n\t\t\t// single active key produces byte-identical output to before.\n\t\t\tparams._order = Object.fromEntries(sortKeys.value.map((k) => [k.key, k.order]))\n\t\t}\n\n\t\tfor (const [key, values] of Object.entries(activeFilters.value)) {\n\t\t\tif (values && values.length > 0) {\n\t\t\t\t// Single-value arrays are unwrapped to scalar params\n\t\t\t\tparams[key] = values.length === 1 ? values[0] : values\n\t\t\t}\n\t\t}\n\n\t\t// Fixed filters (e.g. a route-param-scoped `pages[].config.filter`) are\n\t\t// merged LAST so they always win over a colliding facet `activeFilter`.\n\t\t// Repeated `_extend[]`, serialized by the shared query builder. Set\n\t\t// before the fixed filters so an explicit `_extend` in a filter map\n\t\t// still wins, matching every other key's precedence here.\n\t\tconst extend = resolveExtend()\n\t\tif (extend.length > 0) {\n\t\t\tparams._extend = extend\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(resolveFixedFilters())) {\n\t\t\tif (value !== undefined && value !== null && value !== '') {\n\t\t\t\tparams[key] = value\n\t\t\t}\n\t\t}\n\n\t\treturn params\n\t}\n\n\t// ── Fetch ────────────────────────────────────────────────────────────\n\n\t/**\n\t * Fetch the collection using current state params and update sidebar facet data.\n\t *\n\t * @param {number} [page] Page to fetch\n\t * @return {Promise<void>}\n\t */\n\tasync function refresh(page = 1) {\n\t\tawait objectStore.fetchCollection(objectType, buildParams(page))\n\t}\n\n\t// ── Event handlers ───────────────────────────────────────────────────\n\n\t/**\n\t * Handle search input. Debounced by `options.debounceMs` (default 300 ms).\n\t *\n\t * @param {string} value New search string\n\t */\n\tfunction onSearch(value) {\n\t\tsearchTerm.value = value\n\t\tclearTimeout(searchTimeout)\n\t\tsearchTimeout = setTimeout(() => refresh(1), opts.debounceMs || 300)\n\t}\n\n\t/**\n\t * Handle sort change. Updates sort state and triggers refresh.\n\t *\n\t * Accepts CnDataTable's extended `sort` event payload: `keys` (the full\n\t * ordered multi-column list) is used when present; a legacy caller\n\t * passing only `{ key, order }` (no `keys`) still works exactly as\n\t * before — it's normalised to a single-entry `sortKeys` list.\n\t *\n\t * @param {{key: string|null, order: string|null, keys?: Array<{key: string, order: string}>}} sort New sort definition.\n\t */\n\tfunction onSort({ key, order, keys }) {\n\t\tconst nextKeys = Array.isArray(keys) ? keys : (key ? [{ key, order: order || 'asc' }] : [])\n\t\tsortKeys.value = nextKeys\n\t\tsortKey.value = nextKeys[0]?.key ?? null\n\t\tsortOrder.value = nextKeys[0]?.order ?? 'asc'\n\t\trefresh(1)\n\t}\n\n\t/**\n\t * Handle filter change for a single key. Empty arrays remove the key.\n\t *\n\t * @param {string} key Filter key (maps to API param name)\n\t * @param {Array} values Selected filter values\n\t */\n\tfunction onFilterChange(key, values) {\n\t\tif (!values || values.length === 0) {\n\t\t\tconst updated = { ...activeFilters.value }\n\t\t\tdelete updated[key]\n\t\t\tactiveFilters.value = updated\n\t\t} else {\n\t\t\tactiveFilters.value = { ...activeFilters.value, [key]: values }\n\t\t}\n\t\trefresh(1)\n\t}\n\n\t/**\n\t * Handle page navigation.\n\t *\n\t * @param {number} page Page number to navigate to\n\t */\n\tfunction onPageChange(page) {\n\t\trefresh(page)\n\t}\n\n\t/**\n\t * Handle page-size change. Resets to page 1.\n\t *\n\t * @param {number} size New page size\n\t */\n\tfunction onPageSizeChange(size) {\n\t\tpageSize.value = size\n\t\trefresh(1)\n\t}\n\n\t// ── Sidebar wiring ───────────────────────────────────────────────────\n\n\tfunction setupSidebar() {\n\t\tif (!sidebarState) {\n\t\t\treturn\n\t\t}\n\t\tsidebarState.active = true\n\t\tsidebarState.schema = schema.value\n\t\tsidebarState.searchValue = searchTerm.value\n\t\tsidebarState.activeFilters = {}\n\t\tsidebarState.onSearch = onSearch\n\t\tsidebarState.onColumnsChange = (cols) => {\n\t\t\tvisibleColumns.value = cols\n\t\t}\n\t\tsidebarState.onFilterChange = ({ key, values }) => onFilterChange(key, values)\n\t}\n\n\tfunction teardownSidebar() {\n\t\tif (!sidebarState) {\n\t\t\treturn\n\t\t}\n\t\tsidebarState.active = false\n\t\tsidebarState.schema = null\n\t\tsidebarState.activeFilters = {}\n\t\tsidebarState.facetData = {}\n\t\tsidebarState.onSearch = null\n\t\tsidebarState.onColumnsChange = null\n\t\tsidebarState.onFilterChange = null\n\t}\n\n\t// Push facet data to sidebar after each store update\n\tif (sidebarState) {\n\t\twatch(\n\t\t\t() => objectStore.facets[objectType],\n\t\t\t(facets) => {\n\t\t\t\tsidebarState.facetData = facets || {}\n\t\t\t},\n\t\t)\n\t}\n\n\t// ── Lifecycle ────────────────────────────────────────────────────────\n\n\tonMounted(async () => {\n\t\ttry {\n\t\t\tschema.value = await objectStore.fetchSchema(objectType)\n\t\t\tif (sidebarState) {\n\t\t\t\tsetupSidebar()\n\t\t\t}\n\t\t\tawait refresh(1)\n\t\t} finally {\n\t\t\t// Hand the loading state back to the store either way: a schema\n\t\t\t// fetch that rejects must not leave the list spinning forever.\n\t\t\tbootstrapping.value = false\n\t\t}\n\t})\n\n\tonBeforeUnmount(() => {\n\t\tclearTimeout(searchTimeout)\n\t\tteardownSidebar()\n\t})\n\n\t// ── Return value ─────────────────────────────────────────────────────\n\n\treturn {\n\t\t// Store-derived\n\t\tschema,\n\t\tobjects,\n\t\tloading,\n\t\tpagination,\n\t\tfacets,\n\t\t// Local state\n\t\tsearchTerm,\n\t\tsortKey,\n\t\tsortOrder,\n\t\tsortKeys,\n\t\tactiveFilters,\n\t\tvisibleColumns,\n\t\tpageSize,\n\t\t// Event handlers\n\t\tonSearch,\n\t\tonSort,\n\t\tonFilterChange,\n\t\tonPageChange,\n\t\tonPageSizeChange,\n\t\t// Explicit fetch\n\t\trefresh,\n\t}\n}\n\n// ── Legacy implementation ─────────────────────────────────────────────────────\n\n/**\n * Legacy `useListView(options)` implementation.\n * Preserved verbatim for backward compatibility.\n *\n * @param {object} options Legacy options object\n * @param {string} [options.objectType] The registered object type slug\n * @param {(type: string, params: object) => Promise<Array<object>>} [options.fetchFn] Function to call: (type, params) => Promise<Array>\n * @param {number} [options.debounceMs] Search debounce in milliseconds\n * @param {number} [options.pageSize] Default page size\n * @param {object} [options.defaultSort] Default sort: { key: string, order: 'asc'|'desc' }\n * @return {object} Reactive state and methods\n */\nfunction useLegacyListView(options) {\n\tconst searchTerm = ref('')\n\tconst filters = ref({})\n\tconst sortKey = ref(options.defaultSort?.key || null)\n\tconst sortOrder = ref(options.defaultSort?.order || 'asc')\n\tconst currentPage = ref(1)\n\tconst pageSize = ref(options.pageSize || 20)\n\n\tlet searchTimeout = null\n\n\tfunction buildFetchParams() {\n\t\tconst params = {\n\t\t\t_limit: pageSize.value,\n\t\t\t_page: currentPage.value,\n\t\t}\n\n\t\tif (searchTerm.value) {\n\t\t\tparams._search = searchTerm.value\n\t\t}\n\n\t\tif (sortKey.value) {\n\t\t\tparams._order = { [sortKey.value]: sortOrder.value }\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(filters.value)) {\n\t\t\tif (value !== null && value !== '' && value !== false) {\n\t\t\t\tparams[key] = value\n\t\t\t}\n\t\t}\n\n\t\treturn params\n\t}\n\n\tasync function fetchData(page) {\n\t\tif (page !== undefined) {\n\t\t\tcurrentPage.value = page\n\t\t}\n\t\tconst params = buildFetchParams()\n\t\tif (options.fetchFn) {\n\t\t\treturn options.fetchFn(options.objectType, params)\n\t\t}\n\t}\n\n\tfunction onSearchInput(value) {\n\t\tsearchTerm.value = value\n\t\tclearTimeout(searchTimeout)\n\t\tsearchTimeout = setTimeout(() => fetchData(1), options.debounceMs || 300)\n\t}\n\n\tfunction toggleSort(key) {\n\t\tif (sortKey.value === key) {\n\t\t\tif (sortOrder.value === 'asc') {\n\t\t\t\tsortOrder.value = 'desc'\n\t\t\t} else {\n\t\t\t\tsortKey.value = null\n\t\t\t\tsortOrder.value = 'asc'\n\t\t\t}\n\t\t} else {\n\t\t\tsortKey.value = key\n\t\t\tsortOrder.value = 'asc'\n\t\t}\n\t\tfetchData(1)\n\t}\n\n\tfunction setFilter(key, value) {\n\t\tfilters.value = { ...filters.value, [key]: value }\n\t\tfetchData(1)\n\t}\n\n\tfunction clearAllFilters() {\n\t\tsearchTerm.value = ''\n\t\tfilters.value = {}\n\t\tsortKey.value = options.defaultSort?.key || null\n\t\tsortOrder.value = options.defaultSort?.order || 'asc'\n\t\tfetchData(1)\n\t}\n\n\tfunction goToPage(page) {\n\t\tcurrentPage.value = page\n\t\tfetchData()\n\t}\n\n\tonBeforeUnmount(() => clearTimeout(searchTimeout))\n\n\treturn {\n\t\tsearchTerm,\n\t\tfilters,\n\t\tsortKey,\n\t\tsortOrder,\n\t\tcurrentPage,\n\t\tpageSize,\n\t\tonSearchInput,\n\t\ttoggleSort,\n\t\tsetFilter,\n\t\tclearAllFilters,\n\t\tgoToPage,\n\t\tfetch: fetchData,\n\t\tbuildFetchParams,\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useListView.js","sources":["../../../src/composables/useListView.js"],"sourcesContent":["import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { useObjectStore } from '../store/index.js'\n\n/**\n * Composable for managing list view state with full objectStore integration.\n *\n * When called with an `objectType` string, connects to the objectStore and handles\n * schema loading, collection fetching, sidebar wiring, and all event handlers\n * automatically. Everything a `CnIndexPage`-based list view needs is returned\n * directly — no additional computed properties or methods required in the component.\n *\n * Backward-compatible: existing `useListView(options)` and `useListView()` calls\n * continue to work without modification.\n *\n * @param {string|object} [objectTypeOrOptions] Object type slug (new API) or legacy options object\n * @param {object} [options] Options (new API only)\n * @param {object|null} [options.objectStore] Custom object store instance (from createObjectStore). When provided, uses this store instead of the default useObjectStore(). Required when the app uses createObjectStore with a custom store ID.\n * @param {object|null} [options.sidebarState] Sidebar state object from `inject('sidebarState')`. When provided, the composable wires and unwires the sidebar automatically on mount/unmount.\n * @param {number} [options.defaultPageSize] Default `_limit` sent to the API\n * @param {number} [options.debounceMs] Search debounce in milliseconds\n * @param {object} [options.defaultSort] Default sort applied on mount e.g. `{ key: 'createdAt', order: 'desc' }`\n * @param {Array<{key: string, order: 'asc'|'desc'}>} [options.defaultSortKeys] Default MULTI-column sort applied on mount (ordered priority list). Takes precedence over `defaultSort` when both are given; used e.g. to restore a sort persisted in the route query.\n * @param {Array<string>|null} [options.defaultVisibleColumns] Initial visible-column key set. Seeds the sidebar's Columns tab with the curated default (e.g. a manifest `columns` list) so toggles add/remove from it. Omit (or `null`) for schema-driven tables where every column starts visible.\n * @param {object} [options.defaultActiveFilters] Initial facet-filter map, e.g. to restore filters persisted in the route query on mount.\n * @param {string} [options.defaultSearchTerm] Initial search term, e.g. to restore `_search` from the route query on mount.\n * @param {object|(() => object)} [options.fixedFilters] A filter map (or getter returning one) merged into every fetch AFTER the user's facet filters, so the fixed entries always win. Used e.g. by `CnIndexPage` to apply a route-param-scoped `pages[].config.filter`. Default `{}` — omitting it is behaviourally identical to before.\n * @param {Array<string>|(() => Array<string>)} [options.extend] OpenRegister `_extend[]` values (or a getter returning them) forwarded on every fetch, e.g. `['calculations']` so a schema's `materialise: false` calculations are evaluated and can be shown as columns. Default `[]` — omitting it sends no `_extend` at all, exactly as before.\n * @return {object} Reactive state and event handlers\n *\n * @example\n * // New API — minimal\n * const { schema, objects, loading, pagination,\n * onSearch, onSort, onFilterChange, onPageChange, refresh } = useListView('client')\n *\n * @example\n * // New API — with sidebar wiring\n * const list = useListView('client', {\n * sidebarState: inject('sidebarState', null),\n * })\n *\n * @example\n * // Legacy API — still works\n * const { searchTerm, filters, onSearchInput, toggleSort } = useListView({\n * objectType: 'client',\n * fetchFn: (type, params) => objectStore.fetchCollection(type, params),\n * })\n */\nexport function useListView(objectTypeOrOptions, options) {\n\t// Backward compat: if first arg is an object or absent, delegate to legacy implementation\n\tif (!objectTypeOrOptions || typeof objectTypeOrOptions === 'object') {\n\t\treturn useLegacyListView(objectTypeOrOptions || {})\n\t}\n\n\t// ── New API ──────────────────────────────────────────────────────────\n\tconst objectType = objectTypeOrOptions\n\tconst opts = options || {}\n\tconst sidebarState = opts.sidebarState || null\n\n\tconst objectStore = opts.objectStore || useObjectStore()\n\n\t// ── State refs ───────────────────────────────────────────────────────\n\tconst schema = ref(null)\n\tconst searchTerm = ref(opts.defaultSearchTerm || '')\n\t// `sortKeys` is the source of truth (ordered multi-column priority list);\n\t// `sortKey`/`sortOrder` are kept as a primary-entry mirror so every\n\t// existing reader of the pre-multi-sort refs keeps working unchanged.\n\tconst initialSortKeys = Array.isArray(opts.defaultSortKeys) && opts.defaultSortKeys.length > 0\n\t\t? opts.defaultSortKeys\n\t\t: (opts.defaultSort?.key ? [{ key: opts.defaultSort.key, order: opts.defaultSort.order || 'asc' }] : [])\n\tconst sortKeys = ref(initialSortKeys)\n\tconst sortKey = ref(initialSortKeys[0]?.key || null)\n\tconst sortOrder = ref(initialSortKeys[0]?.order || 'asc')\n\tconst activeFilters = ref((opts.defaultActiveFilters && typeof opts.defaultActiveFilters === 'object') ? opts.defaultActiveFilters : {})\n\t// Seed the visible-column set to the configured columns so the sidebar's\n\t// Columns tab reflects the curated default and toggles add/remove from it.\n\t// `null` (no seed) means \"all columns visible\" (schema-driven tables).\n\tconst visibleColumns = ref(opts.defaultVisibleColumns ?? null)\n\tconst pageSize = ref(opts.defaultPageSize || 20)\n\n\t// ── Computed refs from the store ─────────────────────────────────────\n\tconst objects = computed(() => objectStore.collections[objectType] || [])\n\t// True from creation until the mount sequence below has issued its first\n\t// fetch. The store's own flag only goes up once `fetchCollection` runs, and\n\t// `onMounted` awaits `fetchSchema()` first — so for the length of that round\n\t// trip the list was \"not loading\" with zero rows, and every consumer\n\t// rendered its empty state (\"No log entries to show\") before any data had\n\t// been asked for. Folded into `loading` rather than exposed separately so\n\t// existing consumers get the fix without touching their templates.\n\tconst bootstrapping = ref(true)\n\tconst loading = computed(() => bootstrapping.value || objectStore.loading[objectType] || false)\n\tconst pagination = computed(() => objectStore.pagination[objectType] || { total: 0, page: 1, pages: 1, limit: 20 })\n\t// Facets are computed by the platform over the whole query rather than the\n\t// loaded page, so anything that needs the complete set of values for a\n\t// field (a folder pane grouping by it, say) reads them here rather than\n\t// deriving them from `objects`, which is only the current page.\n\tconst facets = computed(() => objectStore.facets[objectType] || {})\n\t// Outcome of the latest `refresh()`: the store's error for it, or null once\n\t// it succeeded or a later fetch stored new rows. The store keeps the previous\n\t// rows on a failed fetch, so this is how a consumer tells stale rows from\n\t// current ones.\n\tconst error = ref(null)\n\tlet refreshSeq = 0\n\tlet pendingRefreshes = 0\n\n\tlet searchTimeout = null\n\n\t// ── Param construction ───────────────────────────────────────────────\n\n\t/**\n\t * Resolve `opts.fixedFilters` to a plain map. Accepts a plain object OR a\n\t * function/getter returning one (so callers can derive it from reactive\n\t * sources — e.g. route params — and have it re-read on every fetch).\n\t *\n\t * @return {object} The fixed-filter map (may be empty).\n\t */\n\tfunction resolveFixedFilters() {\n\t\tconst f = typeof opts.fixedFilters === 'function' ? opts.fixedFilters() : opts.fixedFilters\n\t\treturn (f && typeof f === 'object') ? f : {}\n\t}\n\n\t/**\n\t * Resolve `opts.extend` to a list of non-empty strings.\n\t *\n\t * Accepts an array or a getter returning one, so a caller can re-scope it\n\t * between fetches the way `fixedFilters` allows.\n\t *\n\t * @return {Array<string>} The `_extend[]` values, possibly empty.\n\t */\n\tfunction resolveExtend() {\n\t\tconst e = typeof opts.extend === 'function' ? opts.extend() : opts.extend\n\t\tif (!Array.isArray(e)) {\n\t\t\treturn []\n\t\t}\n\t\treturn e.filter((v) => typeof v === 'string' && v !== '')\n\t}\n\n\t/**\n\t * Build API fetch params from current reactive state.\n\t *\n\t * @param {number} page Page number to request\n\t * @return {object} Params object ready to pass to fetchCollection\n\t */\n\tfunction buildParams(page) {\n\t\tconst params = { _limit: pageSize.value, _page: page }\n\n\t\tif (searchTerm.value) {\n\t\t\tparams._search = searchTerm.value\n\t\t}\n\n\t\tif (sortKeys.value.length > 0) {\n\t\t\t// Object key insertion order = sort priority order, matching\n\t\t\t// OpenRegister's `_order` format exactly (`ObjectsController::\n\t\t\t// normalizeOrderParameter` / `MagicMapper`'s ordering loop). A\n\t\t\t// single active key produces byte-identical output to before.\n\t\t\tparams._order = Object.fromEntries(sortKeys.value.map((k) => [k.key, k.order]))\n\t\t}\n\n\t\tfor (const [key, values] of Object.entries(activeFilters.value)) {\n\t\t\tif (values && values.length > 0) {\n\t\t\t\t// Single-value arrays are unwrapped to scalar params\n\t\t\t\tparams[key] = values.length === 1 ? values[0] : values\n\t\t\t}\n\t\t}\n\n\t\t// Fixed filters (e.g. a route-param-scoped `pages[].config.filter`) are\n\t\t// merged LAST so they always win over a colliding facet `activeFilter`.\n\t\t// Repeated `_extend[]`, serialized by the shared query builder. Set\n\t\t// before the fixed filters so an explicit `_extend` in a filter map\n\t\t// still wins, matching every other key's precedence here.\n\t\tconst extend = resolveExtend()\n\t\tif (extend.length > 0) {\n\t\t\tparams._extend = extend\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(resolveFixedFilters())) {\n\t\t\tif (value !== undefined && value !== null && value !== '') {\n\t\t\t\tparams[key] = value\n\t\t\t}\n\t\t}\n\n\t\treturn params\n\t}\n\n\t// ── Fetch ────────────────────────────────────────────────────────────\n\n\t/**\n\t * Fetch the collection using current state params and update sidebar facet data.\n\t * Only the most recently started refresh sets `error`, so an older response\n\t * settling late cannot override a newer one's outcome.\n\t *\n\t * @param {number} [page] Page to fetch\n\t * @return {Promise<void>}\n\t */\n\tasync function refresh(page = 1) {\n\t\tconst seq = ++refreshSeq\n\t\t// This call's own result. `errors[objectType]` is shared by every call\n\t\t// for the type and is not cleared on success, so it cannot say which\n\t\t// request failed; it is only the fallback for a store that ignores\n\t\t// `options.outcome`.\n\t\tconst outcome = {}\n\t\tpendingRefreshes++\n\t\ttry {\n\t\t\tawait objectStore.fetchCollection(objectType, buildParams(page), { outcome })\n\t\t} catch (e) {\n\t\t\tif (seq === refreshSeq) {\n\t\t\t\terror.value = e\n\t\t\t}\n\t\t\tthrow e\n\t\t} finally {\n\t\t\tpendingRefreshes--\n\t\t}\n\t\tif (seq === refreshSeq) {\n\t\t\terror.value = ('error' in outcome) ? outcome.error : (objectStore.errors?.[objectType] || null)\n\t\t}\n\t}\n\n\t// A fetch outside `refresh()` (e.g. the live-updates refetch) that stores\n\t// new rows recovers the list from an earlier failure. Rows written while\n\t// a refresh is pending are left to that refresh, which decides `error`.\n\twatch(\n\t\t() => objectStore.collections[objectType],\n\t\t() => {\n\t\t\tif (pendingRefreshes === 0) {\n\t\t\t\terror.value = null\n\t\t\t}\n\t\t},\n\t\t{ flush: 'sync' },\n\t)\n\n\t// ── Event handlers ───────────────────────────────────────────────────\n\n\t/**\n\t * Handle search input. Debounced by `options.debounceMs` (default 300 ms).\n\t *\n\t * @param {string} value New search string\n\t */\n\tfunction onSearch(value) {\n\t\tsearchTerm.value = value\n\t\tclearTimeout(searchTimeout)\n\t\tsearchTimeout = setTimeout(() => refresh(1), opts.debounceMs || 300)\n\t}\n\n\t/**\n\t * Handle sort change. Updates sort state and triggers refresh.\n\t *\n\t * Accepts CnDataTable's extended `sort` event payload: `keys` (the full\n\t * ordered multi-column list) is used when present; a legacy caller\n\t * passing only `{ key, order }` (no `keys`) still works exactly as\n\t * before — it's normalised to a single-entry `sortKeys` list.\n\t *\n\t * @param {{key: string|null, order: string|null, keys?: Array<{key: string, order: string}>}} sort New sort definition.\n\t */\n\tfunction onSort({ key, order, keys }) {\n\t\tconst nextKeys = Array.isArray(keys) ? keys : (key ? [{ key, order: order || 'asc' }] : [])\n\t\tsortKeys.value = nextKeys\n\t\tsortKey.value = nextKeys[0]?.key ?? null\n\t\tsortOrder.value = nextKeys[0]?.order ?? 'asc'\n\t\trefresh(1)\n\t}\n\n\t/**\n\t * Handle filter change for a single key. Empty arrays remove the key.\n\t *\n\t * @param {string} key Filter key (maps to API param name)\n\t * @param {Array} values Selected filter values\n\t */\n\tfunction onFilterChange(key, values) {\n\t\tif (!values || values.length === 0) {\n\t\t\tconst updated = { ...activeFilters.value }\n\t\t\tdelete updated[key]\n\t\t\tactiveFilters.value = updated\n\t\t} else {\n\t\t\tactiveFilters.value = { ...activeFilters.value, [key]: values }\n\t\t}\n\t\trefresh(1)\n\t}\n\n\t/**\n\t * Handle page navigation.\n\t *\n\t * @param {number} page Page number to navigate to\n\t */\n\tfunction onPageChange(page) {\n\t\trefresh(page)\n\t}\n\n\t/**\n\t * Handle page-size change. Resets to page 1.\n\t *\n\t * @param {number} size New page size\n\t */\n\tfunction onPageSizeChange(size) {\n\t\tpageSize.value = size\n\t\trefresh(1)\n\t}\n\n\t// ── Sidebar wiring ───────────────────────────────────────────────────\n\n\tfunction setupSidebar() {\n\t\tif (!sidebarState) {\n\t\t\treturn\n\t\t}\n\t\tsidebarState.active = true\n\t\tsidebarState.schema = schema.value\n\t\tsidebarState.searchValue = searchTerm.value\n\t\tsidebarState.activeFilters = {}\n\t\tsidebarState.onSearch = onSearch\n\t\tsidebarState.onColumnsChange = (cols) => {\n\t\t\tvisibleColumns.value = cols\n\t\t}\n\t\tsidebarState.onFilterChange = ({ key, values }) => onFilterChange(key, values)\n\t}\n\n\tfunction teardownSidebar() {\n\t\tif (!sidebarState) {\n\t\t\treturn\n\t\t}\n\t\tsidebarState.active = false\n\t\tsidebarState.schema = null\n\t\tsidebarState.activeFilters = {}\n\t\tsidebarState.facetData = {}\n\t\tsidebarState.onSearch = null\n\t\tsidebarState.onColumnsChange = null\n\t\tsidebarState.onFilterChange = null\n\t}\n\n\t// Push facet data to sidebar after each store update\n\tif (sidebarState) {\n\t\twatch(\n\t\t\t() => objectStore.facets[objectType],\n\t\t\t(facets) => {\n\t\t\t\tsidebarState.facetData = facets || {}\n\t\t\t},\n\t\t)\n\t}\n\n\t// ── Lifecycle ────────────────────────────────────────────────────────\n\n\tonMounted(async () => {\n\t\ttry {\n\t\t\tschema.value = await objectStore.fetchSchema(objectType)\n\t\t\tif (sidebarState) {\n\t\t\t\tsetupSidebar()\n\t\t\t}\n\t\t\tawait refresh(1)\n\t\t} finally {\n\t\t\t// Hand the loading state back to the store either way: a schema\n\t\t\t// fetch that rejects must not leave the list spinning forever.\n\t\t\tbootstrapping.value = false\n\t\t}\n\t})\n\n\tonBeforeUnmount(() => {\n\t\tclearTimeout(searchTimeout)\n\t\tteardownSidebar()\n\t})\n\n\t// ── Return value ─────────────────────────────────────────────────────\n\n\treturn {\n\t\t// Store-derived\n\t\tschema,\n\t\tobjects,\n\t\tloading,\n\t\tpagination,\n\t\tfacets,\n\t\terror,\n\t\t// Local state\n\t\tsearchTerm,\n\t\tsortKey,\n\t\tsortOrder,\n\t\tsortKeys,\n\t\tactiveFilters,\n\t\tvisibleColumns,\n\t\tpageSize,\n\t\t// Event handlers\n\t\tonSearch,\n\t\tonSort,\n\t\tonFilterChange,\n\t\tonPageChange,\n\t\tonPageSizeChange,\n\t\t// Explicit fetch\n\t\trefresh,\n\t}\n}\n\n// ── Legacy implementation ─────────────────────────────────────────────────────\n\n/**\n * Legacy `useListView(options)` implementation.\n * Preserved verbatim for backward compatibility.\n *\n * @param {object} options Legacy options object\n * @param {string} [options.objectType] The registered object type slug\n * @param {(type: string, params: object) => Promise<Array<object>>} [options.fetchFn] Function to call: (type, params) => Promise<Array>\n * @param {number} [options.debounceMs] Search debounce in milliseconds\n * @param {number} [options.pageSize] Default page size\n * @param {object} [options.defaultSort] Default sort: { key: string, order: 'asc'|'desc' }\n * @return {object} Reactive state and methods\n */\nfunction useLegacyListView(options) {\n\tconst searchTerm = ref('')\n\tconst filters = ref({})\n\tconst sortKey = ref(options.defaultSort?.key || null)\n\tconst sortOrder = ref(options.defaultSort?.order || 'asc')\n\tconst currentPage = ref(1)\n\tconst pageSize = ref(options.pageSize || 20)\n\n\tlet searchTimeout = null\n\n\tfunction buildFetchParams() {\n\t\tconst params = {\n\t\t\t_limit: pageSize.value,\n\t\t\t_page: currentPage.value,\n\t\t}\n\n\t\tif (searchTerm.value) {\n\t\t\tparams._search = searchTerm.value\n\t\t}\n\n\t\tif (sortKey.value) {\n\t\t\tparams._order = { [sortKey.value]: sortOrder.value }\n\t\t}\n\n\t\tfor (const [key, value] of Object.entries(filters.value)) {\n\t\t\tif (value !== null && value !== '' && value !== false) {\n\t\t\t\tparams[key] = value\n\t\t\t}\n\t\t}\n\n\t\treturn params\n\t}\n\n\tasync function fetchData(page) {\n\t\tif (page !== undefined) {\n\t\t\tcurrentPage.value = page\n\t\t}\n\t\tconst params = buildFetchParams()\n\t\tif (options.fetchFn) {\n\t\t\treturn options.fetchFn(options.objectType, params)\n\t\t}\n\t}\n\n\tfunction onSearchInput(value) {\n\t\tsearchTerm.value = value\n\t\tclearTimeout(searchTimeout)\n\t\tsearchTimeout = setTimeout(() => fetchData(1), options.debounceMs || 300)\n\t}\n\n\tfunction toggleSort(key) {\n\t\tif (sortKey.value === key) {\n\t\t\tif (sortOrder.value === 'asc') {\n\t\t\t\tsortOrder.value = 'desc'\n\t\t\t} else {\n\t\t\t\tsortKey.value = null\n\t\t\t\tsortOrder.value = 'asc'\n\t\t\t}\n\t\t} else {\n\t\t\tsortKey.value = key\n\t\t\tsortOrder.value = 'asc'\n\t\t}\n\t\tfetchData(1)\n\t}\n\n\tfunction setFilter(key, value) {\n\t\tfilters.value = { ...filters.value, [key]: value }\n\t\tfetchData(1)\n\t}\n\n\tfunction clearAllFilters() {\n\t\tsearchTerm.value = ''\n\t\tfilters.value = {}\n\t\tsortKey.value = options.defaultSort?.key || null\n\t\tsortOrder.value = options.defaultSort?.order || 'asc'\n\t\tfetchData(1)\n\t}\n\n\tfunction goToPage(page) {\n\t\tcurrentPage.value = page\n\t\tfetchData()\n\t}\n\n\tonBeforeUnmount(() => clearTimeout(searchTimeout))\n\n\treturn {\n\t\tsearchTerm,\n\t\tfilters,\n\t\tsortKey,\n\t\tsortOrder,\n\t\tcurrentPage,\n\t\tpageSize,\n\t\tonSearchInput,\n\t\ttoggleSort,\n\t\tsetFilter,\n\t\tclearAllFilters,\n\t\tgoToPage,\n\t\tfetch: fetchData,\n\t\tbuildFetchParams,\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ref, computed } from 'vue';
|
|
2
|
+
import { readServerAppStatuses, missingRequiredApps } from './useDependencyCheck.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
@@ -28,6 +29,14 @@ const cache = new Map();
|
|
|
28
29
|
* every non-admin user of an app with a `setup` block met a wizard they had no
|
|
29
30
|
* permission to complete instead of the app itself.
|
|
30
31
|
*
|
|
32
|
+
* A step that declares `requires: [appId, ...]` is **not applicable** while any
|
|
33
|
+
* of those apps is not installed and enabled: it is neither met nor unmet, so
|
|
34
|
+
* it never lands in `requiredUnmet` or `optionalUnmet`. `CnSetupWizard` skips
|
|
35
|
+
* such a step, and counting it as unmet kept the wizard reopening (optional
|
|
36
|
+
* step) or gating the app (required step) for a step it would never show. App
|
|
37
|
+
* status is resolved exactly as the wizard does it: the `dependency_statuses`
|
|
38
|
+
* initial state first, then `useAppStatus`.
|
|
39
|
+
*
|
|
31
40
|
* @param {string} appId Nextcloud app id (e.g. `"procest"`).
|
|
32
41
|
* @param {object} manifest The app manifest (reads `manifest.setup`).
|
|
33
42
|
* @return {{
|
|
@@ -36,6 +45,7 @@ const cache = new Map();
|
|
|
36
45
|
* requiredUnmet: import('vue').ComputedRef<Array<object>>,
|
|
37
46
|
* optionalUnmet: import('vue').ComputedRef<Array<object>>,
|
|
38
47
|
* optionalUnmetReported: import('vue').ComputedRef<Array<object>>,
|
|
48
|
+
* notApplicable: import('vue').ComputedRef<Array<object>>,
|
|
39
49
|
* completed: import('vue').ComputedRef<boolean>,
|
|
40
50
|
* enabled: boolean,
|
|
41
51
|
* loading: import('vue').Ref<boolean>,
|
|
@@ -46,6 +56,7 @@ const cache = new Map();
|
|
|
46
56
|
*
|
|
47
57
|
* @example
|
|
48
58
|
* const { requiredUnmet, completed, loading } = useSetupStatus('procest', manifest)
|
|
59
|
+
* @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable
|
|
49
60
|
*/
|
|
50
61
|
function useSetupStatus(appId, manifest) {
|
|
51
62
|
const setup = (manifest && manifest.setup) || {};
|
|
@@ -69,6 +80,9 @@ function useSetupStatus(appId, manifest) {
|
|
|
69
80
|
cache.set(appId, entry);
|
|
70
81
|
}
|
|
71
82
|
const { status, loading, error, forbidden } = entry;
|
|
83
|
+
// Same source the wizard's dependency gate reads. Initial state is fixed
|
|
84
|
+
// for the page lifetime, so one read per call is enough.
|
|
85
|
+
const serverAppStatuses = readServerAppStatuses(appId);
|
|
72
86
|
|
|
73
87
|
const steps = computed(() => stepDefs.map((s) => {
|
|
74
88
|
const bag = status.value.steps || {};
|
|
@@ -82,7 +96,12 @@ function useSetupStatus(appId, manifest) {
|
|
|
82
96
|
// that act on an unmet step — CnAppRoot auto-opening the non-gating
|
|
83
97
|
// wizard — must act only on steps the server actually reported, or an
|
|
84
98
|
// unknowable step parks the wizard over the app for ever.
|
|
85
|
-
|
|
99
|
+
//
|
|
100
|
+
// `applicable` is false when the step `requires` an app that is not
|
|
101
|
+
// installed and enabled. The wizard skips such a step, so it can never
|
|
102
|
+
// be done here; it is not applicable rather than unmet.
|
|
103
|
+
const missingApps = missingRequiredApps(s.requires, serverAppStatuses).map((row) => row.id);
|
|
104
|
+
return { ...s, done: st.done === true, reported, detail: st.detail, applicable: missingApps.length === 0, missingApps }
|
|
86
105
|
}));
|
|
87
106
|
// Presentational step types carry no work, so the server never reports a
|
|
88
107
|
// `done` flag for them. Counting them as "unmet" made `optionalUnmet`
|
|
@@ -90,7 +109,9 @@ function useSetupStatus(appId, manifest) {
|
|
|
90
109
|
// which kept CnAppRoot's non-gating setup wizard auto-opening over the
|
|
91
110
|
// app on every fresh browser profile no matter how complete setup was.
|
|
92
111
|
// Only actionable steps can be unmet.
|
|
93
|
-
|
|
112
|
+
// A step whose required apps are absent is skipped by the wizard, so it
|
|
113
|
+
// is neither met nor unmet (see `applicable` above).
|
|
114
|
+
const isActionable = (s) => s.type !== 'info' && s.type !== 'summary' && s.applicable !== false;
|
|
94
115
|
// `forbidden` empties BOTH lists, and that is what actually suppresses the
|
|
95
116
|
// wizard: CnAppRoot gates on `requiredUnmet.length > 0` (blocking) and on
|
|
96
117
|
// `requiredUnmet.length === 0 && optionalUnmet.length > 0` (auto-open) —
|
|
@@ -111,6 +132,8 @@ function useSetupStatus(appId, manifest) {
|
|
|
111
132
|
// suppressing it is what left dossiq's `seed` step (and with it the app's
|
|
112
133
|
// only demo-data affordance) permanently unreachable.
|
|
113
134
|
const optionalUnmetReported = computed(() => optionalUnmet.value.filter((s) => s.reported === true));
|
|
135
|
+
// Steps the wizard skips because an app in their `requires` is absent.
|
|
136
|
+
const notApplicable = computed(() => steps.value.filter((s) => s.applicable === false));
|
|
114
137
|
const completed = computed(() => {
|
|
115
138
|
if (!enabled) {
|
|
116
139
|
return true
|
|
@@ -171,7 +194,7 @@ function useSetupStatus(appId, manifest) {
|
|
|
171
194
|
refresh();
|
|
172
195
|
}
|
|
173
196
|
|
|
174
|
-
return { steps, status, requiredUnmet, optionalUnmet, optionalUnmetReported, completed, enabled, loading, error, forbidden, refresh }
|
|
197
|
+
return { steps, status, requiredUnmet, optionalUnmet, optionalUnmetReported, notApplicable, completed, enabled, loading, error, forbidden, refresh }
|
|
175
198
|
}
|
|
176
199
|
|
|
177
200
|
export { useSetupStatus };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSetupStatus.js","sources":["../../../src/composables/useSetupStatus.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { computed, ref } from 'vue'\n\n/**\n * Per-`appId` cache of setup-status results. The Vue refs are stored so all\n * consumers of the same app share one fetch + reactive state for the page\n * lifetime (mirrors useAppStatus).\n */\nconst cache = new Map()\n\n/**\n * Composable that reports an app's first-time-setup status (ADR-042).\n *\n * Fetches `GET /apps/{appId}/api/setup/status` → `{ version, completed, steps:\n * { <id>: { done, detail } } }`, then cross-references the manifest's\n * `setup.steps[].required` flags so `CnAppRoot` can gate on required-unmet and\n * the wizard can drive itself. On error it falls back to \"nothing done\" so a\n * failed lookup never crashes the shell — the wizard simply shows the steps.\n *\n * One error is treated as an answer rather than a failure: a **401/403** means\n * the caller may not read setup state (setup endpoints are admin-only), so\n * `completed` reports `true` and the wizard stays out of the way. Without that,\n * every non-admin user of an app with a `setup` block met a wizard they had no\n * permission to complete instead of the app itself.\n *\n * @param {string} appId Nextcloud app id (e.g. `\"procest\"`).\n * @param {object} manifest The app manifest (reads `manifest.setup`).\n * @return {{\n * steps: import('vue').ComputedRef<Array<object>>,\n * status: import('vue').Ref<object>,\n * requiredUnmet: import('vue').ComputedRef<Array<object>>,\n * optionalUnmet: import('vue').ComputedRef<Array<object>>,\n * optionalUnmetReported: import('vue').ComputedRef<Array<object>>,\n * completed: import('vue').ComputedRef<boolean>,\n * enabled: boolean,\n * loading: import('vue').Ref<boolean>,\n * error: import('vue').Ref<Error|null>,\n * forbidden: import('vue').Ref<boolean>,\n * refresh: () => Promise<void>\n * }}\n *\n * @example\n * const { requiredUnmet, completed, loading } = useSetupStatus('procest', manifest)\n */\nexport function useSetupStatus(appId, manifest) {\n\tconst setup = (manifest && manifest.setup) || {}\n\tconst enabled = setup.enabled !== false && Array.isArray(setup.steps) && setup.steps.length > 0\n\tconst stepDefs = Array.isArray(setup.steps) ? setup.steps : []\n\tconst requiredById = Object.create(null)\n\tfor (const s of stepDefs) {\n\t\trequiredById[s.id] = s.required === true\n\t}\n\n\tlet entry = cache.get(appId)\n\tif (!entry) {\n\t\tentry = {\n\t\t\tstatus: ref({ version: null, completed: false, steps: {} }),\n\t\t\tloading: ref(enabled),\n\t\t\terror: ref(null),\n\t\t\t// Set when the server answers 401/403 — see `completed` below.\n\t\t\tforbidden: ref(false),\n\t\t\tfetched: false,\n\t\t}\n\t\tcache.set(appId, entry)\n\t}\n\tconst { status, loading, error, forbidden } = entry\n\n\tconst steps = computed(() => stepDefs.map((s) => {\n\t\tconst bag = status.value.steps || {}\n\t\tconst reported = Object.hasOwn(bag, s.id)\n\t\tconst st = bag[s.id] || {}\n\t\t// `reported` separates \"the server says this step is NOT done\" from \"the\n\t\t// server never mentioned this step at all\". Both used to flatten into\n\t\t// `done: false`, and that conflation is load-bearing: a manifest may\n\t\t// declare an optional step its SetupController has no status key for\n\t\t// (pipelinq's `demo-data`), which is UNKNOWN, not outstanding. Callers\n\t\t// that act on an unmet step — CnAppRoot auto-opening the non-gating\n\t\t// wizard — must act only on steps the server actually reported, or an\n\t\t// unknowable step parks the wizard over the app for ever.\n\t\treturn { ...s, done: st.done === true, reported, detail: st.detail }\n\t}))\n\t// Presentational step types carry no work, so the server never reports a\n\t// `done` flag for them. Counting them as \"unmet\" made `optionalUnmet`\n\t// permanently non-empty for any manifest with a welcome/summary step,\n\t// which kept CnAppRoot's non-gating setup wizard auto-opening over the\n\t// app on every fresh browser profile no matter how complete setup was.\n\t// Only actionable steps can be unmet.\n\tconst isActionable = (s) => s.type !== 'info' && s.type !== 'summary'\n\t// `forbidden` empties BOTH lists, and that is what actually suppresses the\n\t// wizard: CnAppRoot gates on `requiredUnmet.length > 0` (blocking) and on\n\t// `requiredUnmet.length === 0 && optionalUnmet.length > 0` (auto-open) —\n\t// it never reads `completed`. A caller who may not READ setup state has no\n\t// unmet setup work *of their own*, so reporting steps as unmet to them\n\t// described work they could neither see nor do.\n\tconst requiredUnmet = computed(() => (forbidden.value === true\n\t\t? []\n\t\t: steps.value.filter((s) => isActionable(s) && requiredById[s.id] && !s.done)))\n\tconst optionalUnmet = computed(() => (forbidden.value === true\n\t\t? []\n\t\t: steps.value.filter((s) => isActionable(s) && !requiredById[s.id] && !s.done)))\n\t// The subset of `optionalUnmet` the server positively reported as not done.\n\t// This — not `optionalUnmet` — is what may auto-open the non-gating wizard:\n\t// a step with no status key is unknown, and prompting on unknown is what\n\t// parked the wizard over pipelinq on every fresh browser profile. A step\n\t// the server DID report as `{done: false}` is genuinely outstanding, and\n\t// suppressing it is what left dossiq's `seed` step (and with it the app's\n\t// only demo-data affordance) permanently unreachable.\n\tconst optionalUnmetReported = computed(() => optionalUnmet.value.filter((s) => s.reported === true))\n\tconst completed = computed(() => {\n\t\tif (!enabled) {\n\t\t\treturn true\n\t\t}\n\t\t// A 401/403 means the caller may not READ setup state — first-time setup\n\t\t// is admin-only. That is not \"setup is unfinished\"; it is \"not this\n\t\t// user's concern\". Reporting incomplete here put every non-admin in\n\t\t// front of a setup wizard they cannot complete, INSTEAD of the app:\n\t\t// `/api/setup/status` answers 200 {completed:true} to an admin and 403\n\t\t// to everyone else, and the generic error path below then left status at\n\t\t// its \"nothing done\" default. Measured on openbuild, where it made the\n\t\t// app unusable for every non-admin as soon as they could see it at all.\n\t\tif (forbidden.value === true) {\n\t\t\treturn true\n\t\t}\n\t\t// Authoritative server flag, but never report complete while a required\n\t\t// step is still unmet (defends against a stale completion flag).\n\t\treturn status.value.completed === true && requiredUnmet.value.length === 0\n\t})\n\n\t/**\n\t * (Re)fetch the setup status from the server.\n\t *\n\t * @return {Promise<void>}\n\t */\n\tasync function refresh() {\n\t\tif (!enabled) {\n\t\t\tloading.value = false\n\t\t\tentry.fetched = true\n\t\t\treturn\n\t\t}\n\t\tloading.value = true\n\t\terror.value = null\n\t\ttry {\n\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\timport('@nextcloud/router'),\n\t\t\t])\n\t\t\tconst { data } = await axios.get(generateUrl(`/apps/${appId}/api/setup/status`))\n\t\t\tstatus.value = (data && typeof data === 'object') ? data : { steps: {} }\n\t\t\tforbidden.value = false\n\t\t} catch (err) {\n\t\t\terror.value = err\n\t\t\t// 401/403 is a DIFFERENT outcome from a failed lookup: the server\n\t\t\t// answered, and its answer was \"you may not see this\". Setup is\n\t\t\t// admin-only, so for everyone else it is settled, not unknown.\n\t\t\t// Anything else (network error, 500) stays unknown and falls back to\n\t\t\t// \"nothing done\" so the wizard is still reachable for an admin.\n\t\t\tconst httpStatus = err && err.response && err.response.status\n\t\t\tforbidden.value = (httpStatus === 401 || httpStatus === 403)\n\t\t} finally {\n\t\t\tloading.value = false\n\t\t\tentry.fetched = true\n\t\t}\n\t}\n\n\tif (!entry.fetched) {\n\t\trefresh()\n\t}\n\n\treturn { steps, status, requiredUnmet, optionalUnmet, optionalUnmetReported, completed, enabled, loading, error, forbidden, refresh }\n}\n\n/**\n * Test-only helper to reset the per-`appId` cache.\n *\n * @internal\n */\nexport function __resetSetupStatusCacheForTests() {\n\tcache.clear()\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useSetupStatus.js","sources":["../../../src/composables/useSetupStatus.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { computed, ref } from 'vue'\nimport { missingRequiredApps, readServerAppStatuses } from './useDependencyCheck.js'\n\n/**\n * Per-`appId` cache of setup-status results. The Vue refs are stored so all\n * consumers of the same app share one fetch + reactive state for the page\n * lifetime (mirrors useAppStatus).\n */\nconst cache = new Map()\n\n/**\n * Composable that reports an app's first-time-setup status (ADR-042).\n *\n * Fetches `GET /apps/{appId}/api/setup/status` → `{ version, completed, steps:\n * { <id>: { done, detail } } }`, then cross-references the manifest's\n * `setup.steps[].required` flags so `CnAppRoot` can gate on required-unmet and\n * the wizard can drive itself. On error it falls back to \"nothing done\" so a\n * failed lookup never crashes the shell — the wizard simply shows the steps.\n *\n * One error is treated as an answer rather than a failure: a **401/403** means\n * the caller may not read setup state (setup endpoints are admin-only), so\n * `completed` reports `true` and the wizard stays out of the way. Without that,\n * every non-admin user of an app with a `setup` block met a wizard they had no\n * permission to complete instead of the app itself.\n *\n * A step that declares `requires: [appId, ...]` is **not applicable** while any\n * of those apps is not installed and enabled: it is neither met nor unmet, so\n * it never lands in `requiredUnmet` or `optionalUnmet`. `CnSetupWizard` skips\n * such a step, and counting it as unmet kept the wizard reopening (optional\n * step) or gating the app (required step) for a step it would never show. App\n * status is resolved exactly as the wizard does it: the `dependency_statuses`\n * initial state first, then `useAppStatus`.\n *\n * @param {string} appId Nextcloud app id (e.g. `\"procest\"`).\n * @param {object} manifest The app manifest (reads `manifest.setup`).\n * @return {{\n * steps: import('vue').ComputedRef<Array<object>>,\n * status: import('vue').Ref<object>,\n * requiredUnmet: import('vue').ComputedRef<Array<object>>,\n * optionalUnmet: import('vue').ComputedRef<Array<object>>,\n * optionalUnmetReported: import('vue').ComputedRef<Array<object>>,\n * notApplicable: import('vue').ComputedRef<Array<object>>,\n * completed: import('vue').ComputedRef<boolean>,\n * enabled: boolean,\n * loading: import('vue').Ref<boolean>,\n * error: import('vue').Ref<Error|null>,\n * forbidden: import('vue').Ref<boolean>,\n * refresh: () => Promise<void>\n * }}\n *\n * @example\n * const { requiredUnmet, completed, loading } = useSetupStatus('procest', manifest)\n * @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable\n */\nexport function useSetupStatus(appId, manifest) {\n\tconst setup = (manifest && manifest.setup) || {}\n\tconst enabled = setup.enabled !== false && Array.isArray(setup.steps) && setup.steps.length > 0\n\tconst stepDefs = Array.isArray(setup.steps) ? setup.steps : []\n\tconst requiredById = Object.create(null)\n\tfor (const s of stepDefs) {\n\t\trequiredById[s.id] = s.required === true\n\t}\n\n\tlet entry = cache.get(appId)\n\tif (!entry) {\n\t\tentry = {\n\t\t\tstatus: ref({ version: null, completed: false, steps: {} }),\n\t\t\tloading: ref(enabled),\n\t\t\terror: ref(null),\n\t\t\t// Set when the server answers 401/403 — see `completed` below.\n\t\t\tforbidden: ref(false),\n\t\t\tfetched: false,\n\t\t}\n\t\tcache.set(appId, entry)\n\t}\n\tconst { status, loading, error, forbidden } = entry\n\t// Same source the wizard's dependency gate reads. Initial state is fixed\n\t// for the page lifetime, so one read per call is enough.\n\tconst serverAppStatuses = readServerAppStatuses(appId)\n\n\tconst steps = computed(() => stepDefs.map((s) => {\n\t\tconst bag = status.value.steps || {}\n\t\tconst reported = Object.hasOwn(bag, s.id)\n\t\tconst st = bag[s.id] || {}\n\t\t// `reported` separates \"the server says this step is NOT done\" from \"the\n\t\t// server never mentioned this step at all\". Both used to flatten into\n\t\t// `done: false`, and that conflation is load-bearing: a manifest may\n\t\t// declare an optional step its SetupController has no status key for\n\t\t// (pipelinq's `demo-data`), which is UNKNOWN, not outstanding. Callers\n\t\t// that act on an unmet step — CnAppRoot auto-opening the non-gating\n\t\t// wizard — must act only on steps the server actually reported, or an\n\t\t// unknowable step parks the wizard over the app for ever.\n\t\t//\n\t\t// `applicable` is false when the step `requires` an app that is not\n\t\t// installed and enabled. The wizard skips such a step, so it can never\n\t\t// be done here; it is not applicable rather than unmet.\n\t\tconst missingApps = missingRequiredApps(s.requires, serverAppStatuses).map((row) => row.id)\n\t\treturn { ...s, done: st.done === true, reported, detail: st.detail, applicable: missingApps.length === 0, missingApps }\n\t}))\n\t// Presentational step types carry no work, so the server never reports a\n\t// `done` flag for them. Counting them as \"unmet\" made `optionalUnmet`\n\t// permanently non-empty for any manifest with a welcome/summary step,\n\t// which kept CnAppRoot's non-gating setup wizard auto-opening over the\n\t// app on every fresh browser profile no matter how complete setup was.\n\t// Only actionable steps can be unmet.\n\t// A step whose required apps are absent is skipped by the wizard, so it\n\t// is neither met nor unmet (see `applicable` above).\n\tconst isActionable = (s) => s.type !== 'info' && s.type !== 'summary' && s.applicable !== false\n\t// `forbidden` empties BOTH lists, and that is what actually suppresses the\n\t// wizard: CnAppRoot gates on `requiredUnmet.length > 0` (blocking) and on\n\t// `requiredUnmet.length === 0 && optionalUnmet.length > 0` (auto-open) —\n\t// it never reads `completed`. A caller who may not READ setup state has no\n\t// unmet setup work *of their own*, so reporting steps as unmet to them\n\t// described work they could neither see nor do.\n\tconst requiredUnmet = computed(() => (forbidden.value === true\n\t\t? []\n\t\t: steps.value.filter((s) => isActionable(s) && requiredById[s.id] && !s.done)))\n\tconst optionalUnmet = computed(() => (forbidden.value === true\n\t\t? []\n\t\t: steps.value.filter((s) => isActionable(s) && !requiredById[s.id] && !s.done)))\n\t// The subset of `optionalUnmet` the server positively reported as not done.\n\t// This — not `optionalUnmet` — is what may auto-open the non-gating wizard:\n\t// a step with no status key is unknown, and prompting on unknown is what\n\t// parked the wizard over pipelinq on every fresh browser profile. A step\n\t// the server DID report as `{done: false}` is genuinely outstanding, and\n\t// suppressing it is what left dossiq's `seed` step (and with it the app's\n\t// only demo-data affordance) permanently unreachable.\n\tconst optionalUnmetReported = computed(() => optionalUnmet.value.filter((s) => s.reported === true))\n\t// Steps the wizard skips because an app in their `requires` is absent.\n\tconst notApplicable = computed(() => steps.value.filter((s) => s.applicable === false))\n\tconst completed = computed(() => {\n\t\tif (!enabled) {\n\t\t\treturn true\n\t\t}\n\t\t// A 401/403 means the caller may not READ setup state — first-time setup\n\t\t// is admin-only. That is not \"setup is unfinished\"; it is \"not this\n\t\t// user's concern\". Reporting incomplete here put every non-admin in\n\t\t// front of a setup wizard they cannot complete, INSTEAD of the app:\n\t\t// `/api/setup/status` answers 200 {completed:true} to an admin and 403\n\t\t// to everyone else, and the generic error path below then left status at\n\t\t// its \"nothing done\" default. Measured on openbuild, where it made the\n\t\t// app unusable for every non-admin as soon as they could see it at all.\n\t\tif (forbidden.value === true) {\n\t\t\treturn true\n\t\t}\n\t\t// Authoritative server flag, but never report complete while a required\n\t\t// step is still unmet (defends against a stale completion flag).\n\t\treturn status.value.completed === true && requiredUnmet.value.length === 0\n\t})\n\n\t/**\n\t * (Re)fetch the setup status from the server.\n\t *\n\t * @return {Promise<void>}\n\t */\n\tasync function refresh() {\n\t\tif (!enabled) {\n\t\t\tloading.value = false\n\t\t\tentry.fetched = true\n\t\t\treturn\n\t\t}\n\t\tloading.value = true\n\t\terror.value = null\n\t\ttry {\n\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\timport('@nextcloud/router'),\n\t\t\t])\n\t\t\tconst { data } = await axios.get(generateUrl(`/apps/${appId}/api/setup/status`))\n\t\t\tstatus.value = (data && typeof data === 'object') ? data : { steps: {} }\n\t\t\tforbidden.value = false\n\t\t} catch (err) {\n\t\t\terror.value = err\n\t\t\t// 401/403 is a DIFFERENT outcome from a failed lookup: the server\n\t\t\t// answered, and its answer was \"you may not see this\". Setup is\n\t\t\t// admin-only, so for everyone else it is settled, not unknown.\n\t\t\t// Anything else (network error, 500) stays unknown and falls back to\n\t\t\t// \"nothing done\" so the wizard is still reachable for an admin.\n\t\t\tconst httpStatus = err && err.response && err.response.status\n\t\t\tforbidden.value = (httpStatus === 401 || httpStatus === 403)\n\t\t} finally {\n\t\t\tloading.value = false\n\t\t\tentry.fetched = true\n\t\t}\n\t}\n\n\tif (!entry.fetched) {\n\t\trefresh()\n\t}\n\n\treturn { steps, status, requiredUnmet, optionalUnmet, optionalUnmetReported, notApplicable, completed, enabled, loading, error, forbidden, refresh }\n}\n\n/**\n * Test-only helper to reset the per-`appId` cache.\n *\n * @internal\n */\nexport function __resetSetupStatusCacheForTests() {\n\tcache.clear()\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/l10n/en.json.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var translations = {
|
|
2
|
+
Advanced: "Advanced",
|
|
2
3
|
"The item could not be created.": "The item could not be created.",
|
|
3
4
|
"Start over": "Start over",
|
|
4
5
|
"Continue where you left off": "Continue where you left off",
|
|
@@ -1084,7 +1085,9 @@ var translations = {
|
|
|
1084
1085
|
"Show the date of today above the heading": "Show the date of today above the heading",
|
|
1085
1086
|
"Plain header without a coloured background": "Plain header without a coloured background",
|
|
1086
1087
|
"Loading…": "Loading…",
|
|
1087
|
-
"A register and schema are required": "A register and schema are required"
|
|
1088
|
+
"A register and schema are required": "A register and schema are required",
|
|
1089
|
+
"Change the search or try again.": "Change the search or try again.",
|
|
1090
|
+
"Try again later.": "Try again later."
|
|
1088
1091
|
};
|
|
1089
1092
|
var plurals = {
|
|
1090
1093
|
"%n year": [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/esm/l10n/nl.json.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var translations = {
|
|
2
|
+
Advanced: "Geavanceerd",
|
|
2
3
|
"The item could not be created.": "Het item kon niet worden aangemaakt.",
|
|
3
4
|
"Start over": "Opnieuw beginnen",
|
|
4
5
|
"Continue where you left off": "Ga verder waar je was",
|
|
@@ -1136,7 +1137,9 @@ var translations = {
|
|
|
1136
1137
|
"Show the date of today above the heading": "Toon de datum van vandaag boven de kop",
|
|
1137
1138
|
"Plain header without a coloured background": "Eenvoudige kop zonder gekleurde achtergrond",
|
|
1138
1139
|
"Loading…": "Laden…",
|
|
1139
|
-
"A register and schema are required": "Een register en schema zijn verplicht"
|
|
1140
|
+
"A register and schema are required": "Een register en schema zijn verplicht",
|
|
1141
|
+
"Change the search or try again.": "Pas de zoekopdracht aan of probeer het opnieuw.",
|
|
1142
|
+
"Try again later.": "Probeer het later opnieuw."
|
|
1140
1143
|
};
|
|
1141
1144
|
var plurals = {
|
|
1142
1145
|
"%n year": [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1940,6 +1940,23 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
1940
1940
|
color: var(--color-text-maxcontrast);
|
|
1941
1941
|
}
|
|
1942
1942
|
|
|
1943
|
+
/* Beside the inline search (`showCountWithSearch`): the search field shrinks
|
|
1944
|
+
first and the counter stays on one line. */
|
|
1945
|
+
.cn-actions-bar__count--beside-search {
|
|
1946
|
+
flex-shrink: 0;
|
|
1947
|
+
white-space: nowrap;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
/* Taken out of the flex row so it adds no gap, but kept in the accessibility
|
|
1951
|
+
tree so the live region exists before a total appears. */
|
|
1952
|
+
.cn-actions-bar__count--empty {
|
|
1953
|
+
position: absolute;
|
|
1954
|
+
width: 1px;
|
|
1955
|
+
height: 1px;
|
|
1956
|
+
overflow: hidden;
|
|
1957
|
+
clip-path: inset(50%);
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1943
1960
|
/* Inline search field */
|
|
1944
1961
|
.cn-actions-bar__search {
|
|
1945
1962
|
position: relative;
|
|
@@ -2199,8 +2216,9 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
2199
2216
|
display: contents;
|
|
2200
2217
|
}
|
|
2201
2218
|
|
|
2202
|
-
/* #after-search filter buttons
|
|
2203
|
-
search field (DOM order — search first via its
|
|
2219
|
+
/* #after-search filter buttons, the beside-search counter and the view
|
|
2220
|
+
toggle ride beside the search field (DOM order — search first via its
|
|
2221
|
+
own order). */
|
|
2204
2222
|
.cn-actions-bar__info > * {
|
|
2205
2223
|
order: 1;
|
|
2206
2224
|
}
|