@revenexx/studio-shared 0.3.3 → 0.4.1
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/module.d.mts +7 -3
- package/dist/module.json +2 -2
- package/dist/module.mjs +10 -4
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +1 -1
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardControls.d.vue.ts +8 -7
- package/dist/runtime/components/Dashboard/DashboardControls.vue.d.ts +8 -7
- package/dist/runtime/components/Dashboard/DashboardGrid.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardGrid.vue +45 -1
- package/dist/runtime/components/Dashboard/DashboardGrid.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardNameDialog.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +1 -1
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +1 -1
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardShareDialog.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +1 -1
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardTopBar.d.vue.ts +9 -4
- package/dist/runtime/components/Dashboard/DashboardTopBar.vue.d.ts +9 -4
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.d.vue.ts +8 -6
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +1 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue.d.ts +8 -6
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +5 -4
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +14 -3
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +5 -4
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue.d.ts +2 -1
- package/dist/runtime/components/DataTable/ColumnsMenu.d.vue.ts +2 -1
- package/dist/runtime/components/DataTable/ColumnsMenu.vue.d.ts +2 -1
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +120 -37
- package/dist/runtime/components/DataTable/DataTable.vue +493 -53
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +120 -37
- package/dist/runtime/components/DataTable/FacetChips.d.vue.ts +2 -1
- package/dist/runtime/components/DataTable/FacetChips.vue.d.ts +2 -1
- package/dist/runtime/components/DataTable/FacetPanel.d.vue.ts +6 -5
- package/dist/runtime/components/DataTable/FacetPanel.vue +57 -8
- package/dist/runtime/components/DataTable/FacetPanel.vue.d.ts +6 -5
- package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +6 -5
- package/dist/runtime/components/DataTable/FilterMenu.vue +5 -5
- package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +6 -5
- package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +2 -1
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue +61 -12
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +2 -1
- package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +4 -2
- package/dist/runtime/components/DataTable/SortMenu.vue +4 -1
- package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +4 -2
- package/dist/runtime/components/DataTable/ToolbarLabelTrigger.d.vue.ts +23 -0
- package/dist/runtime/components/DataTable/ToolbarLabelTrigger.vue +29 -0
- package/dist/runtime/components/DataTable/ToolbarLabelTrigger.vue.d.ts +23 -0
- package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +4 -2
- package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +4 -2
- package/dist/runtime/components/DataTable/types.d.ts +17 -0
- package/dist/runtime/components/Form/FieldCell.d.vue.ts +32 -0
- package/dist/runtime/components/Form/FieldCell.vue +46 -0
- package/dist/runtime/components/Form/FieldCell.vue.d.ts +32 -0
- package/dist/runtime/components/Form/FieldGrid.d.vue.ts +13 -0
- package/dist/runtime/components/Form/FieldGrid.vue +11 -0
- package/dist/runtime/components/Form/FieldGrid.vue.d.ts +13 -0
- package/dist/runtime/components/Form/FormContextCard.d.vue.ts +33 -0
- package/dist/runtime/components/Form/FormContextCard.vue +56 -0
- package/dist/runtime/components/Form/FormContextCard.vue.d.ts +33 -0
- package/dist/runtime/components/Form/FormLayout.d.vue.ts +47 -0
- package/dist/runtime/components/Form/FormLayout.vue +169 -0
- package/dist/runtime/components/Form/FormLayout.vue.d.ts +47 -0
- package/dist/runtime/components/Form/FormSection.d.vue.ts +25 -0
- package/dist/runtime/components/Form/FormSection.vue +48 -0
- package/dist/runtime/components/Form/FormSection.vue.d.ts +25 -0
- package/dist/runtime/components/Form/SaveBar.d.vue.ts +44 -0
- package/dist/runtime/components/Form/SaveBar.vue +71 -0
- package/dist/runtime/components/Form/SaveBar.vue.d.ts +44 -0
- package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +6 -5
- package/dist/runtime/components/Media/MediaFolderTree.vue +1 -2
- package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +6 -5
- package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +13 -1
- package/dist/runtime/components/Media/MediaPickerDialog.vue +206 -54
- package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +13 -1
- package/dist/runtime/components/Media/MediaUploader.d.vue.ts +2 -1
- package/dist/runtime/components/Media/MediaUploader.vue +1 -1
- package/dist/runtime/components/Media/MediaUploader.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/AppIcon.d.vue.ts +21 -0
- package/dist/runtime/components/Shared/AppIcon.vue +47 -0
- package/dist/runtime/components/Shared/AppIcon.vue.d.ts +21 -0
- package/dist/runtime/components/Shared/BuildLogsFlyout.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/BuildLogsFlyout.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/CardSizeToggle.d.vue.ts +11 -0
- package/dist/runtime/components/Shared/CardSizeToggle.vue +19 -0
- package/dist/runtime/components/Shared/CardSizeToggle.vue.d.ts +11 -0
- package/dist/runtime/components/Shared/DetailSheet.d.vue.ts +60 -0
- package/dist/runtime/components/Shared/DetailSheet.vue +211 -0
- package/dist/runtime/components/Shared/DetailSheet.vue.d.ts +60 -0
- package/dist/runtime/components/Shared/EmptyNote.d.vue.ts +19 -0
- package/dist/runtime/components/Shared/EmptyNote.vue +25 -0
- package/dist/runtime/components/Shared/EmptyNote.vue.d.ts +19 -0
- package/dist/runtime/components/Shared/FloatingPanel.d.vue.ts +7 -6
- package/dist/runtime/components/Shared/FloatingPanel.vue.d.ts +7 -6
- package/dist/runtime/components/Shared/GitDeploymentSettings.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/GitDeploymentSettings.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/KpiTile.d.vue.ts +8 -1
- package/dist/runtime/components/Shared/KpiTile.vue +10 -9
- package/dist/runtime/components/Shared/KpiTile.vue.d.ts +8 -1
- package/dist/runtime/components/Shared/ListSurface.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/ListSurface.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/PageHeader.d.vue.ts +7 -6
- package/dist/runtime/components/Shared/PageHeader.vue +15 -4
- package/dist/runtime/components/Shared/PageHeader.vue.d.ts +7 -6
- package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +8 -7
- package/dist/runtime/components/Shared/RecordLayout.vue +1 -2
- package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +8 -7
- package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/RecordRailCard.vue +3 -3
- package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/SelectionBar.d.vue.ts +22 -0
- package/dist/runtime/components/Shared/SelectionBar.vue +40 -0
- package/dist/runtime/components/Shared/SelectionBar.vue.d.ts +22 -0
- package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +39 -4
- package/dist/runtime/components/Shared/SidebarNav.vue +71 -25
- package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +39 -4
- package/dist/runtime/components/Shared/SidebarNavAreas.d.vue.ts +7 -0
- package/dist/runtime/components/Shared/SidebarNavAreas.vue +291 -0
- package/dist/runtime/components/Shared/SidebarNavAreas.vue.d.ts +7 -0
- package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +3 -10
- package/dist/runtime/components/Shared/SidebarNavLink.vue +31 -5
- package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +3 -10
- package/dist/runtime/components/Shared/SortHeader.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/SortHeader.vue +6 -2
- package/dist/runtime/components/Shared/SortHeader.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/StateCard.d.vue.ts +6 -5
- package/dist/runtime/components/Shared/StateCard.vue +1 -1
- package/dist/runtime/components/Shared/StateCard.vue.d.ts +6 -5
- package/dist/runtime/components/Shared/StudioCard.d.vue.ts +5 -4
- package/dist/runtime/components/Shared/StudioCard.vue.d.ts +5 -4
- package/dist/runtime/components/Shared/TabBar.d.vue.ts +4 -21
- package/dist/runtime/components/Shared/TabBar.vue +30 -13
- package/dist/runtime/components/Shared/TabBar.vue.d.ts +4 -21
- package/dist/runtime/components/Shared/ViewTab.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/ViewTab.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/Wizard.d.vue.ts +20 -7
- package/dist/runtime/components/Shared/Wizard.vue +9 -5
- package/dist/runtime/components/Shared/Wizard.vue.d.ts +20 -7
- package/dist/runtime/composables/useDetailSheet.d.ts +20 -0
- package/dist/runtime/composables/useDetailSheet.js +50 -0
- package/dist/runtime/composables/useMediaLibrary.d.ts +12 -0
- package/dist/runtime/composables/usePlatformTenant.d.ts +6 -0
- package/dist/runtime/composables/useSavedViews.d.ts +11 -1
- package/dist/runtime/composables/useSavedViews.js +4 -4
- package/dist/runtime/composables/useWidgetFigures.d.ts +12 -0
- package/dist/runtime/composables/useWidgetFigures.js +24 -0
- package/dist/runtime/composables/useWidgetHeader.d.ts +15 -0
- package/dist/runtime/composables/useWidgetHeader.js +15 -0
- package/dist/runtime/lib/activeTenant.d.ts +58 -0
- package/dist/runtime/lib/activeTenant.js +45 -0
- package/dist/runtime/lib/appIcon.d.ts +48 -0
- package/dist/runtime/lib/appIcon.js +27 -0
- package/dist/runtime/lib/auditEnvelope.d.ts +43 -0
- package/dist/runtime/lib/auditEnvelope.js +55 -0
- package/dist/runtime/lib/cardSize.d.ts +13 -0
- package/dist/runtime/lib/cardSize.js +20 -0
- package/dist/runtime/lib/dashboardIcons.js +2 -0
- package/dist/runtime/lib/dataTableFilters.d.ts +22 -0
- package/dist/runtime/lib/dataTableFilters.js +5 -0
- package/dist/runtime/lib/detailSheet.d.ts +43 -0
- package/dist/runtime/lib/detailSheet.js +34 -0
- package/dist/runtime/lib/facets.d.ts +13 -0
- package/dist/runtime/lib/formLayout.d.ts +164 -0
- package/dist/runtime/lib/formLayout.js +117 -0
- package/dist/runtime/lib/formLayoutContext.d.ts +26 -0
- package/dist/runtime/lib/formLayoutContext.js +1 -0
- package/dist/runtime/lib/gridTracks.d.ts +14 -1
- package/dist/runtime/lib/gridTracks.js +13 -2
- package/dist/runtime/lib/isoLists.d.ts +37 -2
- package/dist/runtime/lib/isoLists.js +54 -1
- package/dist/runtime/lib/notice.d.ts +14 -0
- package/dist/runtime/lib/notice.js +19 -0
- package/dist/runtime/lib/pageSize.d.ts +15 -0
- package/dist/runtime/lib/pageSize.js +26 -0
- package/dist/runtime/lib/rowMove.d.ts +38 -0
- package/dist/runtime/lib/rowMove.js +20 -0
- package/dist/runtime/lib/sidebarAreas.d.ts +46 -0
- package/dist/runtime/lib/sidebarAreas.js +25 -0
- package/dist/runtime/lib/sidebarNav.d.ts +21 -2
- package/dist/runtime/lib/sidebarNav.js +23 -4
- package/dist/runtime/lib/solarIconMap.d.ts +16 -0
- package/dist/runtime/lib/solarIconMap.js +326 -0
- package/dist/runtime/lib/solarIcons.d.ts +37 -0
- package/dist/runtime/lib/solarIcons.js +25 -0
- package/dist/runtime/lib/swipeToClose.d.ts +25 -0
- package/dist/runtime/lib/swipeToClose.js +118 -0
- package/dist/runtime/lib/tableExport.d.ts +31 -0
- package/dist/runtime/lib/tableExport.js +143 -0
- package/dist/runtime/lib/talkback.d.ts +5 -0
- package/dist/runtime/lib/talkback.js +8 -0
- package/dist/runtime/lib/talkbackTokenBatch.d.ts +35 -0
- package/dist/runtime/lib/talkbackTokenBatch.js +101 -0
- package/dist/runtime/types/nav.d.ts +9 -0
- package/dist/types.d.mts +8 -0
- package/package.json +4 -3
|
@@ -1,34 +1,51 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
const props = defineProps({
|
|
3
4
|
tabs: { type: Array, required: true },
|
|
4
5
|
modelValue: { type: String, required: true },
|
|
5
|
-
sticky: { type: Boolean, required: false, default: true }
|
|
6
|
+
sticky: { type: Boolean, required: false, default: true },
|
|
7
|
+
label: { type: String, required: false }
|
|
6
8
|
});
|
|
7
|
-
defineEmits(["update:modelValue"]);
|
|
9
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
10
|
+
const buttons = ref([]);
|
|
11
|
+
function onKeydown(event, index) {
|
|
12
|
+
const last = props.tabs.length - 1;
|
|
13
|
+
const next = event.key === "ArrowRight" ? index === last ? 0 : index + 1 : event.key === "ArrowLeft" ? index === 0 ? last : index - 1 : event.key === "Home" ? 0 : event.key === "End" ? last : -1;
|
|
14
|
+
if (next < 0) return;
|
|
15
|
+
event.preventDefault();
|
|
16
|
+
const tab = props.tabs[next];
|
|
17
|
+
if (!tab) return;
|
|
18
|
+
emit("update:modelValue", tab.key);
|
|
19
|
+
buttons.value[next]?.focus();
|
|
20
|
+
}
|
|
8
21
|
</script>
|
|
9
22
|
|
|
10
23
|
<template>
|
|
11
24
|
<!-- Sticks flush against the bottom of the sticky PageHeader (57px = its
|
|
12
25
|
pt-4 pb-3 + text-xl line + 1px border) so header and tabs stay glued as
|
|
13
|
-
one unit while the panel scrolls.
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
one unit while the panel scrolls. Opaque in the shell's ground, like the
|
|
27
|
+
header — content scrolling through read as overlapping text
|
|
28
|
+
(customers-09). -->
|
|
29
|
+
<div :class="sticky ? 'sticky top-[57px] z-10 bg-[var(--color-shell,#f3f1fa)]' : 'relative'">
|
|
16
30
|
<!-- Pull the row out by the tab padding so the first/last label sits flush
|
|
17
31
|
with the surrounding content (headline) edges. -->
|
|
18
|
-
<div class="relative z-10 -mx-2.5 flex gap-1 overflow-x-auto">
|
|
32
|
+
<div class="relative z-10 -mx-2.5 flex gap-1 overflow-x-auto" role="tablist" :aria-label="label">
|
|
19
33
|
<button
|
|
20
|
-
v-for="t in tabs"
|
|
34
|
+
v-for="(t, index) in tabs"
|
|
21
35
|
:key="t.key"
|
|
36
|
+
ref="buttons"
|
|
22
37
|
type="button"
|
|
38
|
+
role="tab"
|
|
39
|
+
:aria-selected="modelValue === t.key"
|
|
40
|
+
:tabindex="modelValue === t.key || !tabs.some((x) => x.key === modelValue) && index === 0 ? 0 : -1"
|
|
23
41
|
class="group relative shrink-0 cursor-pointer whitespace-nowrap px-2.5 py-2 text-sm font-medium transition-colors"
|
|
24
42
|
:class="modelValue === t.key ? 'text-[#0B0522] hover:text-primary' : 'text-[#0B0522]/80 hover:text-primary'"
|
|
25
|
-
@click="
|
|
43
|
+
@click="emit('update:modelValue', t.key)"
|
|
44
|
+
@keydown="onKeydown($event, index)"
|
|
26
45
|
>
|
|
46
|
+
<!-- No counters in tabs (Michael 13.10.): only some tabs could carry
|
|
47
|
+
one, and a half-counted tab bar reads as inconsistent. -->
|
|
27
48
|
{{ t.label }}
|
|
28
|
-
<span
|
|
29
|
-
v-if="t.badge"
|
|
30
|
-
class="ml-1 rounded-full bg-muted px-1.5 text-mono font-medium tabular-nums text-muted-foreground"
|
|
31
|
-
>{{ t.badge }}</span>
|
|
32
49
|
<!-- 2px indicator, exactly under the label (inset-x-3 = px-3). -->
|
|
33
50
|
<span
|
|
34
51
|
class="pointer-events-none absolute inset-x-2.5 bottom-0 h-0.5 rounded-full transition-colors"
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Underline tab bar — the standard cockpit tabs. A full-width hairline sits
|
|
3
|
-
* behind the row; the active/hover indicator is a 2px bar spanning exactly the
|
|
4
|
-
* label width (inset-x-2.5 = the button's px-2.5), sitting on the baseline over
|
|
5
|
-
* the hairline. Active = primary; hover = soft primary. Tabs scroll
|
|
6
|
-
* horizontally on overflow. To bleed the line to the panel edges, pass e.g.
|
|
7
|
-
* `class="-mx-4 px-4"` from a padded page.
|
|
8
|
-
*
|
|
9
|
-
* ## `sticky`
|
|
10
|
-
*
|
|
11
|
-
* On by default, because the original job was a detail page: the row glues
|
|
12
|
-
* itself to the bottom of the sticky `PageHeader` (hence the magic `57px`) and
|
|
13
|
-
* frosts its background so content scrolls through.
|
|
14
|
-
*
|
|
15
|
-
* Off for a row INSIDE a panel — the queue tabs between a DataTable's toolbar
|
|
16
|
-
* and its header, say. There the offset is wrong (there is no page header above
|
|
17
|
-
* it) and the frosted layer is worse than nothing, because the panel already
|
|
18
|
-
* has a background of its own and the blur only muddies the rows scrolling past
|
|
19
|
-
* underneath. Same tabs, same indicator, same hairline; just not pinned.
|
|
20
|
-
*/
|
|
21
1
|
export interface TabItem {
|
|
22
2
|
key: string;
|
|
23
3
|
label: string;
|
|
@@ -28,12 +8,15 @@ type __VLS_Props = {
|
|
|
28
8
|
modelValue: string;
|
|
29
9
|
/** Pin to the page header. Off for a bar inside a panel. */
|
|
30
10
|
sticky?: boolean;
|
|
11
|
+
/** Accessible name of the tab list. */
|
|
12
|
+
label?: string;
|
|
31
13
|
};
|
|
32
|
-
declare const
|
|
14
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
33
15
|
"update:modelValue": (key: string) => any;
|
|
34
16
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
35
17
|
"onUpdate:modelValue"?: ((key: string) => any) | undefined;
|
|
36
18
|
}>, {
|
|
37
19
|
sticky: boolean;
|
|
38
20
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
declare const _default: typeof __VLS_export;
|
|
39
22
|
export default _default;
|
|
@@ -3,5 +3,6 @@ type __VLS_Props = {
|
|
|
3
3
|
icon?: unknown;
|
|
4
4
|
label: string;
|
|
5
5
|
};
|
|
6
|
-
declare const
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
7
8
|
export default _default;
|
|
@@ -3,5 +3,6 @@ type __VLS_Props = {
|
|
|
3
3
|
icon?: unknown;
|
|
4
4
|
label: string;
|
|
5
5
|
};
|
|
6
|
-
declare const
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
7
8
|
export default _default;
|
|
@@ -9,17 +9,28 @@ type __VLS_Props = {
|
|
|
9
9
|
/** Gate the Next/Finish button for the current step. */
|
|
10
10
|
canAdvance?: boolean;
|
|
11
11
|
busy?: boolean;
|
|
12
|
+
/** Label of the last step's button — the surface's own verb ("Anlegen"). */
|
|
13
|
+
finishLabel?: string;
|
|
14
|
+
/** Extra class on the foot, e.g. `rx-dialog-foot` inside a modal (PE-1055). */
|
|
15
|
+
footerClass?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Fill a fixed-height parent: the steps and the foot stay put, only the
|
|
18
|
+
* step's content scrolls (a stepped create dialog, Michael 18.10.).
|
|
19
|
+
*/
|
|
20
|
+
fill?: boolean;
|
|
12
21
|
};
|
|
22
|
+
declare function back(): void;
|
|
23
|
+
declare function next(): void;
|
|
13
24
|
declare var __VLS_1: {
|
|
14
25
|
step: any;
|
|
15
26
|
index: any;
|
|
16
27
|
}, __VLS_3: {
|
|
17
|
-
back:
|
|
18
|
-
next:
|
|
28
|
+
back: typeof back;
|
|
29
|
+
next: typeof next;
|
|
19
30
|
isFirst: any;
|
|
20
31
|
isLast: any;
|
|
21
|
-
canAdvance:
|
|
22
|
-
busy:
|
|
32
|
+
canAdvance: boolean;
|
|
33
|
+
busy: boolean;
|
|
23
34
|
index: any;
|
|
24
35
|
};
|
|
25
36
|
type __VLS_Slots = {} & {
|
|
@@ -27,18 +38,20 @@ type __VLS_Slots = {} & {
|
|
|
27
38
|
} & {
|
|
28
39
|
footer?: (props: typeof __VLS_3) => any;
|
|
29
40
|
};
|
|
30
|
-
declare const
|
|
41
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
42
|
"update:modelValue": (args_0: number) => any;
|
|
32
43
|
finish: () => any;
|
|
33
44
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
34
45
|
"onUpdate:modelValue"?: ((args_0: number) => any) | undefined;
|
|
35
46
|
onFinish?: (() => any) | undefined;
|
|
36
47
|
}>, {
|
|
48
|
+
fill: boolean;
|
|
49
|
+
busy: boolean;
|
|
37
50
|
modelValue: number;
|
|
38
51
|
canAdvance: boolean;
|
|
39
|
-
busy: boolean;
|
|
40
52
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
41
|
-
declare const
|
|
53
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
54
|
+
declare const _default: typeof __VLS_export;
|
|
42
55
|
export default _default;
|
|
43
56
|
type __VLS_WithSlots<T, S> = T & {
|
|
44
57
|
new (): {
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { DIALOG_BODY_CLASS } from "../../lib/formLayout";
|
|
2
3
|
import { Button } from "@revenexx/studio";
|
|
3
4
|
const props = defineProps({
|
|
4
5
|
steps: { type: Array, required: true },
|
|
5
6
|
modelValue: { type: Number, required: false, default: 0 },
|
|
6
7
|
canAdvance: { type: Boolean, required: false, default: true },
|
|
7
|
-
busy: { type: Boolean, required: false, default: false }
|
|
8
|
+
busy: { type: Boolean, required: false, default: false },
|
|
9
|
+
finishLabel: { type: String, required: false },
|
|
10
|
+
footerClass: { type: String, required: false },
|
|
11
|
+
fill: { type: Boolean, required: false, default: false }
|
|
8
12
|
});
|
|
9
13
|
const emit = defineEmits(["update:modelValue", "finish"]);
|
|
10
14
|
const t = useT();
|
|
@@ -29,7 +33,7 @@ function goTo(i) {
|
|
|
29
33
|
</script>
|
|
30
34
|
|
|
31
35
|
<template>
|
|
32
|
-
<div class="space-y-5">
|
|
36
|
+
<div :class="fill ? 'flex min-h-0 flex-1 flex-col gap-5' : 'space-y-5'">
|
|
33
37
|
<!-- Go-Live-style stepper: a fill bar + label per step, completed steps
|
|
34
38
|
are clickable to jump back. -->
|
|
35
39
|
<div class="flex gap-3">
|
|
@@ -46,11 +50,11 @@ function goTo(i) {
|
|
|
46
50
|
</div>
|
|
47
51
|
</div>
|
|
48
52
|
|
|
49
|
-
<div>
|
|
53
|
+
<div :class="fill ? DIALOG_BODY_CLASS : ''" data-testid="wizard-body">
|
|
50
54
|
<slot :step="activeStep" :index="index" />
|
|
51
55
|
</div>
|
|
52
56
|
|
|
53
|
-
<div class="flex items-center justify-between border-t pt-3">
|
|
57
|
+
<div class="flex shrink-0 items-center justify-between border-t pt-3" :class="footerClass">
|
|
54
58
|
<!-- Custom footer (e.g. multiple finish actions on a review step). Falls
|
|
55
59
|
back to the standard Back / Next·Finish pair. -->
|
|
56
60
|
<slot name="footer" :back="back" :next="next" :is-first="isFirst" :is-last="isLast" :can-advance="canAdvance" :busy="busy" :index="index">
|
|
@@ -58,7 +62,7 @@ function goTo(i) {
|
|
|
58
62
|
{{ t("shared.wizard.action.back") }}
|
|
59
63
|
</Button>
|
|
60
64
|
<Button :disabled="!canAdvance || busy" class="gap-1" @click="next">
|
|
61
|
-
{{ isLast ? t("shared.wizard.action.finish") : t("shared.wizard.action.next") }}
|
|
65
|
+
{{ isLast ? finishLabel || t("shared.wizard.action.finish") : t("shared.wizard.action.next") }}
|
|
62
66
|
</Button>
|
|
63
67
|
</slot>
|
|
64
68
|
</div>
|
|
@@ -9,17 +9,28 @@ type __VLS_Props = {
|
|
|
9
9
|
/** Gate the Next/Finish button for the current step. */
|
|
10
10
|
canAdvance?: boolean;
|
|
11
11
|
busy?: boolean;
|
|
12
|
+
/** Label of the last step's button — the surface's own verb ("Anlegen"). */
|
|
13
|
+
finishLabel?: string;
|
|
14
|
+
/** Extra class on the foot, e.g. `rx-dialog-foot` inside a modal (PE-1055). */
|
|
15
|
+
footerClass?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Fill a fixed-height parent: the steps and the foot stay put, only the
|
|
18
|
+
* step's content scrolls (a stepped create dialog, Michael 18.10.).
|
|
19
|
+
*/
|
|
20
|
+
fill?: boolean;
|
|
12
21
|
};
|
|
22
|
+
declare function back(): void;
|
|
23
|
+
declare function next(): void;
|
|
13
24
|
declare var __VLS_1: {
|
|
14
25
|
step: any;
|
|
15
26
|
index: any;
|
|
16
27
|
}, __VLS_3: {
|
|
17
|
-
back:
|
|
18
|
-
next:
|
|
28
|
+
back: typeof back;
|
|
29
|
+
next: typeof next;
|
|
19
30
|
isFirst: any;
|
|
20
31
|
isLast: any;
|
|
21
|
-
canAdvance:
|
|
22
|
-
busy:
|
|
32
|
+
canAdvance: boolean;
|
|
33
|
+
busy: boolean;
|
|
23
34
|
index: any;
|
|
24
35
|
};
|
|
25
36
|
type __VLS_Slots = {} & {
|
|
@@ -27,18 +38,20 @@ type __VLS_Slots = {} & {
|
|
|
27
38
|
} & {
|
|
28
39
|
footer?: (props: typeof __VLS_3) => any;
|
|
29
40
|
};
|
|
30
|
-
declare const
|
|
41
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
42
|
"update:modelValue": (args_0: number) => any;
|
|
32
43
|
finish: () => any;
|
|
33
44
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
34
45
|
"onUpdate:modelValue"?: ((args_0: number) => any) | undefined;
|
|
35
46
|
onFinish?: (() => any) | undefined;
|
|
36
47
|
}>, {
|
|
48
|
+
fill: boolean;
|
|
49
|
+
busy: boolean;
|
|
37
50
|
modelValue: number;
|
|
38
51
|
canAdvance: boolean;
|
|
39
|
-
busy: boolean;
|
|
40
52
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
41
|
-
declare const
|
|
53
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
54
|
+
declare const _default: typeof __VLS_export;
|
|
42
55
|
export default _default;
|
|
43
56
|
type __VLS_WithSlots<T, S> = T & {
|
|
44
57
|
new (): {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ComputedRef, MaybeRefOrGetter, WritableComputedRef } from 'vue';
|
|
2
|
+
export interface DetailSheetBinding {
|
|
3
|
+
/** The open record's id, or `null` when the panel is shut. */
|
|
4
|
+
openId: ComputedRef<string | null>;
|
|
5
|
+
/** For `v-model:open` — writing `false` closes. */
|
|
6
|
+
isOpen: WritableComputedRef<boolean>;
|
|
7
|
+
open: (id: string) => void;
|
|
8
|
+
close: () => void;
|
|
9
|
+
/** Move to the neighbouring record in `ids`; a no-op at either end. */
|
|
10
|
+
step: (dir: -1 | 1) => void;
|
|
11
|
+
/** Whether `step(-1)` / `step(1)` would land anywhere — for the panel's arrow buttons. */
|
|
12
|
+
canStep: ComputedRef<{
|
|
13
|
+
prev: boolean;
|
|
14
|
+
next: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare function useDetailSheet(key: string, options?: {
|
|
18
|
+
/** The records in the order the list shows them, for `step`. */
|
|
19
|
+
ids?: MaybeRefOrGetter<readonly string[]>;
|
|
20
|
+
}): DetailSheetBinding;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { computed, toValue, watch } from "vue";
|
|
2
|
+
import { idFromQuery, neighbourId, queryWith } from "../lib/detailSheet.js";
|
|
3
|
+
const MARKER = "detailSheet";
|
|
4
|
+
export function useDetailSheet(key, options = {}) {
|
|
5
|
+
const route = useRoute();
|
|
6
|
+
const router = useRouter();
|
|
7
|
+
const openId = computed(() => idFromQuery(route.query[key]));
|
|
8
|
+
function pushedHere() {
|
|
9
|
+
if (typeof window === "undefined") return false;
|
|
10
|
+
return window.history.state?.[MARKER] === key;
|
|
11
|
+
}
|
|
12
|
+
let closing = false;
|
|
13
|
+
watch(openId, (id) => {
|
|
14
|
+
if (id === null) closing = false;
|
|
15
|
+
});
|
|
16
|
+
function open(id) {
|
|
17
|
+
if (openId.value === id) return;
|
|
18
|
+
closing = false;
|
|
19
|
+
const query = queryWith(route.query, key, id);
|
|
20
|
+
if (openId.value === null) {
|
|
21
|
+
void router.push({ query, state: { [MARKER]: key } });
|
|
22
|
+
} else {
|
|
23
|
+
void router.replace({ query, state: pushedHere() ? { [MARKER]: key } : {} });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function close() {
|
|
27
|
+
if (openId.value === null || closing) return;
|
|
28
|
+
closing = true;
|
|
29
|
+
if (pushedHere()) router.back();
|
|
30
|
+
else void router.replace({ query: queryWith(route.query, key, null) });
|
|
31
|
+
}
|
|
32
|
+
function step(dir) {
|
|
33
|
+
const next = neighbourId(toValue(options.ids) ?? [], openId.value, dir);
|
|
34
|
+
if (next !== null) open(next);
|
|
35
|
+
}
|
|
36
|
+
const canStep = computed(() => {
|
|
37
|
+
const ids = toValue(options.ids) ?? [];
|
|
38
|
+
return {
|
|
39
|
+
prev: neighbourId(ids, openId.value, -1) !== null,
|
|
40
|
+
next: neighbourId(ids, openId.value, 1) !== null
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
const isOpen = computed({
|
|
44
|
+
get: () => openId.value !== null,
|
|
45
|
+
set: (v) => {
|
|
46
|
+
if (!v) close();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return { openId, isOpen, open, close, step, canStep };
|
|
50
|
+
}
|
|
@@ -37,5 +37,17 @@ export interface MediaLibraryClient {
|
|
|
37
37
|
thumbnailUrl: (asset: StorageAsset, size?: number) => string | null;
|
|
38
38
|
/** True while the asset is still being scanned/processed. */
|
|
39
39
|
isProcessing: (asset: StorageAsset) => boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Optional server-side narrowing (the cockpit's client has it): free-text
|
|
42
|
+
* search over name, title, alt text and tags; one kind; the sort key
|
|
43
|
+
* (`-created_at`, `original_name`, …); a reload of page 1; a reset. A host
|
|
44
|
+
* without them gets a picker without search, chips and sort.
|
|
45
|
+
*/
|
|
46
|
+
search?: Ref<string>;
|
|
47
|
+
kind?: Ref<string | null>;
|
|
48
|
+
sortKey?: Ref<string>;
|
|
49
|
+
total?: Ref<number | null>;
|
|
50
|
+
loadAssets?: () => Promise<void>;
|
|
51
|
+
resetFilters?: () => void;
|
|
40
52
|
}
|
|
41
53
|
export declare function useMediaLibrary(): MediaLibraryClient;
|
|
@@ -9,6 +9,12 @@ import type { Ref } from 'vue';
|
|
|
9
9
|
*/
|
|
10
10
|
export interface PlatformTenant {
|
|
11
11
|
slug: Ref<string | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves once the active tenant is known, or once it is clear there is
|
|
14
|
+
* none yet (bounded — the host decides how long). Never throws. Callers that
|
|
15
|
+
* send tenant-scoped requests use `requirePlatformTenant` from
|
|
16
|
+
* `@revenexx/studio-shared/lib/activeTenant` instead of reading `slug`.
|
|
17
|
+
*/
|
|
12
18
|
load: () => Promise<void>;
|
|
13
19
|
}
|
|
14
20
|
export declare function usePlatformTenant(): PlatformTenant;
|
|
@@ -28,12 +28,22 @@ export interface StoredSavedView {
|
|
|
28
28
|
id: string;
|
|
29
29
|
name: string;
|
|
30
30
|
snapshot: unknown;
|
|
31
|
+
/** PE-1052: shared with everyone in the tenant. Private when absent. */
|
|
32
|
+
shared?: boolean;
|
|
33
|
+
/** Who shared it, for a view that is not the reader's own. */
|
|
34
|
+
ownerName?: string | null;
|
|
35
|
+
/** The reader's own view (may change and delete it). Absent = own. */
|
|
36
|
+
mine?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface SaveViewOptions {
|
|
39
|
+
/** „Für alle im Mandanten freigeben“ (PE-1052). Default private. */
|
|
40
|
+
shared?: boolean;
|
|
31
41
|
}
|
|
32
42
|
export interface SavedViewsStore {
|
|
33
43
|
/** Read this surface's views. Never throws — a failure is an empty list. */
|
|
34
44
|
list: (surface: string) => Promise<StoredSavedView[]>;
|
|
35
45
|
/** Save under a name, replacing what that name held. */
|
|
36
|
-
save: (surface: string, name: string, snapshot: unknown) => Promise<StoredSavedView>;
|
|
46
|
+
save: (surface: string, name: string, snapshot: unknown, options?: SaveViewOptions) => Promise<StoredSavedView>;
|
|
37
47
|
remove: (surface: string, id: string) => Promise<void>;
|
|
38
48
|
/**
|
|
39
49
|
* Does this store survive a new browser? The menu says so in one line, so
|
|
@@ -8,7 +8,7 @@ function readAll(surface) {
|
|
|
8
8
|
if (!raw) return [];
|
|
9
9
|
const parsed = JSON.parse(raw);
|
|
10
10
|
if (!Array.isArray(parsed)) return [];
|
|
11
|
-
return parsed.filter((v) => !!v && typeof v === "object" && typeof v.name === "string").map((v) => ({ id: localId(surface, v.name), name: v.name, snapshot: v.snapshot }));
|
|
11
|
+
return parsed.filter((v) => !!v && typeof v === "object" && typeof v.name === "string").map((v) => ({ id: localId(surface, v.name), name: v.name, snapshot: v.snapshot, shared: v.shared === true }));
|
|
12
12
|
} catch {
|
|
13
13
|
return [];
|
|
14
14
|
}
|
|
@@ -17,7 +17,7 @@ function writeAll(surface, views) {
|
|
|
17
17
|
try {
|
|
18
18
|
if (typeof window === "undefined") return;
|
|
19
19
|
window.localStorage.setItem(surface, JSON.stringify(
|
|
20
|
-
views.map((v) => ({ name: v.name, snapshot: v.snapshot }))
|
|
20
|
+
views.map((v) => ({ name: v.name, snapshot: v.snapshot, ...v.shared ? { shared: true } : {} }))
|
|
21
21
|
));
|
|
22
22
|
} catch {
|
|
23
23
|
}
|
|
@@ -28,9 +28,9 @@ export function useSavedViews() {
|
|
|
28
28
|
async list(surface) {
|
|
29
29
|
return readAll(surface);
|
|
30
30
|
},
|
|
31
|
-
async save(surface, name, snapshot) {
|
|
31
|
+
async save(surface, name, snapshot, options) {
|
|
32
32
|
const views = readAll(surface);
|
|
33
|
-
const entry = { id: localId(surface, name), name, snapshot };
|
|
33
|
+
const entry = { id: localId(surface, name), name, snapshot, shared: options?.shared === true };
|
|
34
34
|
const at = views.findIndex((v) => v.name === name);
|
|
35
35
|
if (at >= 0) views.splice(at, 1, entry);
|
|
36
36
|
else views.push(entry);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MaybeRefOrGetter, Ref } from 'vue';
|
|
2
|
+
/** One figure as the widget shows it, already formatted. */
|
|
3
|
+
export interface WidgetFigure {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
/** Called by the frame: the figures its widget may provide. */
|
|
8
|
+
export declare function provideWidgetFigures(): Ref<WidgetFigure[] | null>;
|
|
9
|
+
/** Called by a widget: keep its frame's figures in step with `figures`. */
|
|
10
|
+
export declare function useWidgetFigures(figures: MaybeRefOrGetter<WidgetFigure[] | null>): void;
|
|
11
|
+
/** The figures as card text: one `label: value` line each. */
|
|
12
|
+
export declare function figuresText(figures: WidgetFigure[] | null): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getCurrentInstance, inject, onBeforeUnmount, provide, ref, toValue, watch } from "vue";
|
|
2
|
+
const WIDGET_FIGURES = Symbol("widgetFigures");
|
|
3
|
+
export function provideWidgetFigures() {
|
|
4
|
+
const figures = ref(null);
|
|
5
|
+
provide(WIDGET_FIGURES, figures);
|
|
6
|
+
return figures;
|
|
7
|
+
}
|
|
8
|
+
export function useWidgetFigures(figures) {
|
|
9
|
+
const target = inject(WIDGET_FIGURES, null);
|
|
10
|
+
if (!target) return;
|
|
11
|
+
watch(
|
|
12
|
+
() => toValue(figures)?.filter((f) => f.label && f.value) ?? null,
|
|
13
|
+
(next) => {
|
|
14
|
+
target.value = next?.length ? next : null;
|
|
15
|
+
},
|
|
16
|
+
{ immediate: true, deep: true }
|
|
17
|
+
);
|
|
18
|
+
if (getCurrentInstance()) onBeforeUnmount(() => {
|
|
19
|
+
target.value = null;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function figuresText(figures) {
|
|
23
|
+
return figures?.map((f) => `${f.label}: ${f.value}`).join("\n") ?? "";
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
export interface WidgetHeaderStatus {
|
|
3
|
+
/** Show the loading indicator. */
|
|
4
|
+
busy: boolean;
|
|
5
|
+
/** One short line; truncated to fit beside the title. */
|
|
6
|
+
text: string;
|
|
7
|
+
/** How far along, 0 to 100. Omit when unknown: the frame shows a spinner. */
|
|
8
|
+
progress?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Called by the frame: the status its widget may set, provided to the widget below it. */
|
|
11
|
+
export declare function provideWidgetHeader(): Ref<WidgetHeaderStatus | null>;
|
|
12
|
+
/** Called by a widget: set or clear the status its frame shows. */
|
|
13
|
+
export declare function useWidgetHeader(): {
|
|
14
|
+
setStatus: (next: WidgetHeaderStatus | null) => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getCurrentInstance, inject, onBeforeUnmount, provide, ref } from "vue";
|
|
2
|
+
const WIDGET_HEADER_STATUS = Symbol("widgetHeaderStatus");
|
|
3
|
+
export function provideWidgetHeader() {
|
|
4
|
+
const status = ref(null);
|
|
5
|
+
provide(WIDGET_HEADER_STATUS, status);
|
|
6
|
+
return status;
|
|
7
|
+
}
|
|
8
|
+
export function useWidgetHeader() {
|
|
9
|
+
const status = inject(WIDGET_HEADER_STATUS, null);
|
|
10
|
+
function setStatus(next) {
|
|
11
|
+
if (status) status.value = next;
|
|
12
|
+
}
|
|
13
|
+
if (getCurrentInstance()) onBeforeUnmount(() => setStatus(null));
|
|
14
|
+
return { setStatus };
|
|
15
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The active tenant, as every tenant-scoped client sends it (PE-828).
|
|
3
|
+
*
|
|
4
|
+
* The contract the Cockpit shares with Console, Baseline and every gateway:
|
|
5
|
+
* each tenant-scoped request carries the active tenant slug in a header, and
|
|
6
|
+
* the service checks it against the token. The header only SELECTS a tenant
|
|
7
|
+
* the token already holds; it never grants one.
|
|
8
|
+
*
|
|
9
|
+
* Before this helper each client had its own copy of "send the header if we
|
|
10
|
+
* happen to know the slug", and most of them sent the request without it while
|
|
11
|
+
* the top-bar selection was still resolving. In an org with two tenants that
|
|
12
|
+
* is an ambiguous request, which the services answer with 400/403/422.
|
|
13
|
+
*
|
|
14
|
+
* So the rule is now one function: wait (bounded) for the active tenant, and
|
|
15
|
+
* refuse to send a tenant-scoped request when there is none. A missing tenant
|
|
16
|
+
* is a `NoActiveTenantError`, which the UI shows as "pick a tenant".
|
|
17
|
+
*
|
|
18
|
+
* Pure (Vue only), so the cockpit, studio-commerce and their tests share it.
|
|
19
|
+
*/
|
|
20
|
+
import { type Ref } from 'vue';
|
|
21
|
+
/** The header the platform, Console, Baseline and most gateways read. */
|
|
22
|
+
export declare const TENANT_HEADER = "X-Revenexx-Tenant";
|
|
23
|
+
/** The header the AI gateway, Aria and integrations read instead. */
|
|
24
|
+
export declare const TENANT_ID_HEADER = "X-Tenant-Id";
|
|
25
|
+
/** How long a request waits for the active tenant before it gives up. */
|
|
26
|
+
export declare const TENANT_WAIT_MS = 8000;
|
|
27
|
+
export declare class NoActiveTenantError extends Error {
|
|
28
|
+
readonly status = 400;
|
|
29
|
+
readonly code = "no_active_tenant";
|
|
30
|
+
constructor(message?: string);
|
|
31
|
+
}
|
|
32
|
+
/** True for the error `requireTenant` throws, across bundle copies. */
|
|
33
|
+
export declare function isNoActiveTenant(err: unknown): err is NoActiveTenantError;
|
|
34
|
+
/**
|
|
35
|
+
* Wait for a shared ref to hold a value, bounded (RAD-182).
|
|
36
|
+
*
|
|
37
|
+
* Loads what can be loaded, then waits until the value arrives or `timeoutMs`
|
|
38
|
+
* passes. Resolves null on timeout; never throws.
|
|
39
|
+
*/
|
|
40
|
+
export declare function waitForValue<T>(source: Ref<T | null | undefined>, load: () => Promise<unknown>, timeoutMs?: number): Promise<T | null>;
|
|
41
|
+
/** Anything that can name the active tenant: `useConsoleTenants`, `usePlatformTenant`. */
|
|
42
|
+
export interface TenantSource {
|
|
43
|
+
slug: Ref<string | null>;
|
|
44
|
+
load: () => Promise<unknown>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The active tenant slug, or a `NoActiveTenantError`.
|
|
48
|
+
*
|
|
49
|
+
* Never returns null: a caller that gets past this line has a tenant to send.
|
|
50
|
+
*/
|
|
51
|
+
export declare function requireTenant(source: TenantSource, timeoutMs?: number): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* The active tenant from a `usePlatformTenant()` seam, or a `NoActiveTenantError`.
|
|
54
|
+
*
|
|
55
|
+
* The seam's `load` already waits for the selection (the host bounds it), so
|
|
56
|
+
* nothing is waited for twice here.
|
|
57
|
+
*/
|
|
58
|
+
export declare function requirePlatformTenant(tenant: TenantSource): Promise<string>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { watch } from "vue";
|
|
2
|
+
export const TENANT_HEADER = "X-Revenexx-Tenant";
|
|
3
|
+
export const TENANT_ID_HEADER = "X-Tenant-Id";
|
|
4
|
+
export const TENANT_WAIT_MS = 8e3;
|
|
5
|
+
export class NoActiveTenantError extends Error {
|
|
6
|
+
status = 400;
|
|
7
|
+
code = "no_active_tenant";
|
|
8
|
+
constructor(message = "No active tenant. Pick one in the top bar.") {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "NoActiveTenantError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function isNoActiveTenant(err) {
|
|
14
|
+
return err?.code === "no_active_tenant";
|
|
15
|
+
}
|
|
16
|
+
export async function waitForValue(source, load, timeoutMs = TENANT_WAIT_MS) {
|
|
17
|
+
if (source.value) return source.value;
|
|
18
|
+
try {
|
|
19
|
+
await load();
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
if (source.value) return source.value;
|
|
23
|
+
if (timeoutMs <= 0) return null;
|
|
24
|
+
return await new Promise((resolve) => {
|
|
25
|
+
let stop = null;
|
|
26
|
+
const timer = setTimeout(() => {
|
|
27
|
+
stop?.();
|
|
28
|
+
resolve(null);
|
|
29
|
+
}, timeoutMs);
|
|
30
|
+
stop = watch(source, (value) => {
|
|
31
|
+
if (!value) return;
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
stop?.();
|
|
34
|
+
resolve(value);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export async function requireTenant(source, timeoutMs = TENANT_WAIT_MS) {
|
|
39
|
+
const slug = await waitForValue(source.slug, source.load, timeoutMs);
|
|
40
|
+
if (!slug) throw new NoActiveTenantError();
|
|
41
|
+
return slug;
|
|
42
|
+
}
|
|
43
|
+
export function requirePlatformTenant(tenant) {
|
|
44
|
+
return requireTenant(tenant, 0);
|
|
45
|
+
}
|