@revenexx/studio-shared 0.3.2 → 0.3.3
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 +4 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.d.vue.ts +15 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +39 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue.d.ts +15 -0
- package/dist/runtime/components/Dashboard/DashboardControls.d.vue.ts +57 -0
- package/dist/runtime/components/Dashboard/DashboardControls.vue +209 -0
- package/dist/runtime/components/Dashboard/DashboardControls.vue.d.ts +57 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.d.vue.ts +16 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.vue +87 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.vue.d.ts +16 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +61 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.d.vue.ts +14 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +100 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue.d.ts +14 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.d.vue.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +155 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue.d.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.d.vue.ts +59 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.vue +79 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.vue.d.ts +59 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.d.vue.ts +42 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +153 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue.d.ts +42 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +80 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.d.vue.ts +6 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue +30 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue.d.ts +6 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.d.vue.ts +8 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue +13 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue.d.ts +8 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.d.vue.ts +23 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue +140 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue.d.ts +23 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.d.vue.ts +22 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue +124 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue.d.ts +22 -0
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +36 -3
- package/dist/runtime/components/DataTable/DataTable.vue +252 -27
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +36 -3
- package/dist/runtime/components/DataTable/FacetChips.d.vue.ts +16 -0
- package/dist/runtime/components/DataTable/FacetChips.vue +72 -0
- package/dist/runtime/components/DataTable/FacetChips.vue.d.ts +16 -0
- package/dist/runtime/components/DataTable/FacetPanel.d.vue.ts +20 -0
- package/dist/runtime/components/DataTable/FacetPanel.vue +359 -0
- package/dist/runtime/components/DataTable/FacetPanel.vue.d.ts +20 -0
- package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +5 -1
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue +83 -63
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +5 -1
- package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +5 -1
- package/dist/runtime/components/DataTable/SortMenu.vue +40 -12
- package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +5 -1
- package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +2 -2
- package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +2 -2
- package/dist/runtime/components/Media/MediaUploader.d.vue.ts +2 -2
- package/dist/runtime/components/Media/MediaUploader.vue.d.ts +2 -2
- package/dist/runtime/components/Shared/SortHeader.vue +6 -2
- package/dist/runtime/components/Shared/TabBar.d.vue.ts +23 -7
- package/dist/runtime/components/Shared/TabBar.vue +3 -2
- package/dist/runtime/components/Shared/TabBar.vue.d.ts +23 -7
- package/dist/runtime/composables/useDashboards.d.ts +27 -0
- package/dist/runtime/composables/useDashboards.js +143 -0
- package/dist/runtime/composables/useSavedViews.d.ts +44 -0
- package/dist/runtime/composables/useSavedViews.js +44 -0
- package/dist/runtime/composables/useStickyListView.d.ts +60 -0
- package/dist/runtime/composables/useStickyListView.js +32 -0
- package/dist/runtime/lib/canonicalJson.d.ts +43 -0
- package/dist/runtime/lib/canonicalJson.js +38 -0
- package/dist/runtime/lib/dashboardIcons.d.ts +4 -0
- package/dist/runtime/lib/dashboardIcons.js +50 -0
- package/dist/runtime/lib/facets.d.ts +200 -0
- package/dist/runtime/lib/facets.js +171 -0
- package/dist/runtime/lib/gridTracks.d.ts +55 -0
- package/dist/runtime/lib/gridTracks.js +29 -0
- package/dist/runtime/lib/widgetRegistry.d.ts +124 -0
- package/dist/runtime/lib/widgetRegistry.js +66 -0
- package/dist/runtime/types/dashboard.d.ts +95 -0
- package/dist/runtime/types/dashboard.js +2 -0
- package/dist/types.d.mts +8 -0
- package/package.json +3 -2
package/dist/module.d.mts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import * as nuxt_schema from 'nuxt/schema';
|
|
2
2
|
export * from '../dist/runtime/components/DataTable/types.js';
|
|
3
3
|
export * from '../dist/runtime/types/storage.js';
|
|
4
|
+
export * from '../dist/runtime/types/dashboard.js';
|
|
4
5
|
export { SidebarNavItem, SidebarNavSection } from '../dist/runtime/types/nav.js';
|
|
5
6
|
export { PlatformAuth } from '../dist/runtime/composables/usePlatformAuth.js';
|
|
6
7
|
export { PlatformTenant } from '../dist/runtime/composables/usePlatformTenant.js';
|
|
7
8
|
export { PlatformTalkback } from '../dist/runtime/composables/usePlatformTalkback.js';
|
|
8
9
|
export { TalkbackChannel, TalkbackChannelOptions, TalkbackMaybeRef } from '../dist/runtime/composables/useTalkbackChannels.js';
|
|
9
10
|
export { MediaLibraryClient } from '../dist/runtime/composables/useMediaLibrary.js';
|
|
11
|
+
export { SavedViewsStore, StoredSavedView } from '../dist/runtime/composables/useSavedViews.js';
|
|
12
|
+
export { WidgetCategory, WidgetCategoryGroup, WidgetConfigField, WidgetDefinition, WidgetPreviewShape, WidgetRegistry } from '../dist/runtime/lib/widgetRegistry.js';
|
|
13
|
+
export { StickyListView } from '../dist/runtime/composables/useStickyListView.js';
|
|
10
14
|
export { FloatingPanelDock, FloatingPanelMode, FloatingPanelOptions } from '../dist/runtime/composables/useFloatingPanel.js';
|
|
11
15
|
export { IsoOption } from '../dist/runtime/lib/isoLists.js';
|
|
12
16
|
export { Translate, TranslateNamed, Translator } from '../dist/runtime/lib/i18nBridge.js';
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -36,6 +36,12 @@ const module$1 = defineNuxtModule({
|
|
|
36
36
|
// Collapsible list-rail state (persisted per surface) behind the
|
|
37
37
|
// DataTable toolbar's rail handle.
|
|
38
38
|
{ name: "useListRail", from: join(runtime, "composables/useListRail"), priority: -1 },
|
|
39
|
+
// Where the DataTable toolbar's saved views are kept. The default is
|
|
40
|
+
// localStorage, which is what a host without a cockpit backend has; the
|
|
41
|
+
// cockpit overrides it with an account-backed store (PE-608).
|
|
42
|
+
{ name: "useSavedViews", from: join(runtime, "composables/useSavedViews"), priority: -1 },
|
|
43
|
+
// The narrowing a list was left in, kept across a page change (PE-608).
|
|
44
|
+
{ name: "useStickyListView", from: join(runtime, "composables/useStickyListView"), priority: -1 },
|
|
39
45
|
// A panel that docks to either side of a canvas, floats anywhere on it,
|
|
40
46
|
// or collapses to a pill — and is dragged between those by its header.
|
|
41
47
|
// Lifted out of the integration studio's workflow editor so a second
|
|
@@ -53,6 +59,9 @@ const module$1 = defineNuxtModule({
|
|
|
53
59
|
"useTalkbackStream",
|
|
54
60
|
"useTalkbackPresence"
|
|
55
61
|
].map((name) => ({ name, from: join(runtime, "composables/useTalkbackChannels") })));
|
|
62
|
+
addImports([
|
|
63
|
+
{ name: "useDashboards", from: join(runtime, "composables/useDashboards") }
|
|
64
|
+
]);
|
|
56
65
|
addImports([
|
|
57
66
|
{ name: "useT", from: join(runtime, "composables/useT") },
|
|
58
67
|
{ name: "useUiLocale", from: join(runtime, "composables/useT") }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
open: boolean;
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
confirmLabel: string;
|
|
6
|
+
destructive?: boolean;
|
|
7
|
+
};
|
|
8
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:open": (value: boolean) => any;
|
|
10
|
+
confirm: () => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
13
|
+
onConfirm?: (() => any) | undefined;
|
|
14
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
export default _default;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { AlertDialog, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Button } from "@revenexx/studio";
|
|
3
|
+
defineProps({
|
|
4
|
+
open: { type: Boolean, required: true },
|
|
5
|
+
title: { type: String, required: true },
|
|
6
|
+
description: { type: String, required: false },
|
|
7
|
+
confirmLabel: { type: String, required: true },
|
|
8
|
+
destructive: { type: Boolean, required: false }
|
|
9
|
+
});
|
|
10
|
+
const emit = defineEmits(["update:open", "confirm"]);
|
|
11
|
+
const t = useT();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<AlertDialog :open="open" @update:open="(o) => {
|
|
16
|
+
if (!o) emit('update:open', false);
|
|
17
|
+
}">
|
|
18
|
+
<AlertDialogContent>
|
|
19
|
+
<AlertDialogHeader>
|
|
20
|
+
<AlertDialogTitle>{{ title }}</AlertDialogTitle>
|
|
21
|
+
<AlertDialogDescription v-if="description">{{ description }}</AlertDialogDescription>
|
|
22
|
+
</AlertDialogHeader>
|
|
23
|
+
<AlertDialogFooter>
|
|
24
|
+
<Button variant="outline" size="sm" class="h-7 text-sm" @click="emit('update:open', false)">
|
|
25
|
+
{{ t("dashboard.dialog.action.cancel") }}
|
|
26
|
+
</Button>
|
|
27
|
+
<Button
|
|
28
|
+
size="sm"
|
|
29
|
+
class="h-7 text-sm"
|
|
30
|
+
:class="destructive ? 'bg-rose-600 text-white hover:bg-rose-700' : ''"
|
|
31
|
+
@click="emit('confirm');
|
|
32
|
+
emit('update:open', false)"
|
|
33
|
+
>
|
|
34
|
+
{{ confirmLabel }}
|
|
35
|
+
</Button>
|
|
36
|
+
</AlertDialogFooter>
|
|
37
|
+
</AlertDialogContent>
|
|
38
|
+
</AlertDialog>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
open: boolean;
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
confirmLabel: string;
|
|
6
|
+
destructive?: boolean;
|
|
7
|
+
};
|
|
8
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:open": (value: boolean) => any;
|
|
10
|
+
confirm: () => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
13
|
+
onConfirm?: (() => any) | undefined;
|
|
14
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
export default _default;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Dashboard } from '../../types/dashboard.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
dashboards: Dashboard[];
|
|
4
|
+
current: Dashboard | null;
|
|
5
|
+
editing: boolean;
|
|
6
|
+
dirty: boolean;
|
|
7
|
+
saving: boolean;
|
|
8
|
+
/** House-header sizing: `h-7` studio buttons instead of the greeting bar's pills. */
|
|
9
|
+
compact?: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare var __VLS_149: {
|
|
12
|
+
pillClass: any;
|
|
13
|
+
paneClass: any;
|
|
14
|
+
compact: any;
|
|
15
|
+
};
|
|
16
|
+
type __VLS_Slots = {} & {
|
|
17
|
+
scope?: (props: typeof __VLS_149) => any;
|
|
18
|
+
};
|
|
19
|
+
declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {
|
|
20
|
+
PILL: string;
|
|
21
|
+
PILL_PANE: string;
|
|
22
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
23
|
+
edit: () => any;
|
|
24
|
+
select: (id: string) => any;
|
|
25
|
+
save: () => any;
|
|
26
|
+
discard: () => any;
|
|
27
|
+
addWidget: () => any;
|
|
28
|
+
rename: () => any;
|
|
29
|
+
share: () => any;
|
|
30
|
+
browse: () => any;
|
|
31
|
+
create: () => any;
|
|
32
|
+
duplicate: () => any;
|
|
33
|
+
remove: () => any;
|
|
34
|
+
setDefault: () => any;
|
|
35
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
36
|
+
onEdit?: (() => any) | undefined;
|
|
37
|
+
onSelect?: ((id: string) => any) | undefined;
|
|
38
|
+
onSave?: (() => any) | undefined;
|
|
39
|
+
onDiscard?: (() => any) | undefined;
|
|
40
|
+
onAddWidget?: (() => any) | undefined;
|
|
41
|
+
onRename?: (() => any) | undefined;
|
|
42
|
+
onShare?: (() => any) | undefined;
|
|
43
|
+
onBrowse?: (() => any) | undefined;
|
|
44
|
+
onCreate?: (() => any) | undefined;
|
|
45
|
+
onDuplicate?: (() => any) | undefined;
|
|
46
|
+
onRemove?: (() => any) | undefined;
|
|
47
|
+
onSetDefault?: (() => any) | undefined;
|
|
48
|
+
}>, {
|
|
49
|
+
compact: boolean;
|
|
50
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
51
|
+
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
52
|
+
export default _default;
|
|
53
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
54
|
+
new (): {
|
|
55
|
+
$slots: S;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuGroup,
|
|
7
|
+
DropdownMenuItem,
|
|
8
|
+
DropdownMenuLabel,
|
|
9
|
+
DropdownMenuSeparator,
|
|
10
|
+
DropdownMenuTrigger
|
|
11
|
+
} from "@revenexx/studio";
|
|
12
|
+
import { AddSquare, AltArrowDown as ChevronsUpDown, Copy, Diskette, Global, Magnifier, Pen, Restart, Star, TrashBinMinimalistic } from "#solar-icons";
|
|
13
|
+
import { computed } from "vue";
|
|
14
|
+
const props = defineProps({
|
|
15
|
+
dashboards: { type: Array, required: true },
|
|
16
|
+
current: { type: [Object, null], required: true },
|
|
17
|
+
editing: { type: Boolean, required: true },
|
|
18
|
+
dirty: { type: Boolean, required: true },
|
|
19
|
+
saving: { type: Boolean, required: true },
|
|
20
|
+
compact: { type: Boolean, required: false, default: false }
|
|
21
|
+
});
|
|
22
|
+
const emit = defineEmits(["select", "edit", "save", "discard", "addWidget", "rename", "share", "browse", "create", "duplicate", "remove", "setDefault"]);
|
|
23
|
+
const t = useT();
|
|
24
|
+
const MENU_LIMIT = 5;
|
|
25
|
+
const PILL = "group cursor-pointer rounded-lg border border-gray-400/10 outline-1 outline-white/30 transition-colors hover:border-primary/50 disabled:cursor-not-allowed disabled:opacity-55 disabled:hover:border-gray-400/10";
|
|
26
|
+
const PILL_PANE = "flex items-center gap-1.5 rounded-lg bg-white px-2.5 py-1.5 text-sm font-medium text-[#0B0522] transition-colors group-hover:bg-muted/60 group-disabled:group-hover:bg-white";
|
|
27
|
+
const PILL_PANE_PRIMARY = "flex items-center gap-1.5 rounded-lg bg-primary px-2.5 py-1.5 text-sm font-medium text-white transition-colors group-hover:bg-primary/90";
|
|
28
|
+
defineExpose({ PILL, PILL_PANE });
|
|
29
|
+
const mine = computed(() => props.dashboards.filter((d) => d.isOwner));
|
|
30
|
+
const shared = computed(() => props.dashboards.filter((d) => !d.isOwner));
|
|
31
|
+
function shortlist(list) {
|
|
32
|
+
const currentFirst = [...list].sort((a, b) => {
|
|
33
|
+
if (a.id === props.current?.id) return -1;
|
|
34
|
+
if (b.id === props.current?.id) return 1;
|
|
35
|
+
return 0;
|
|
36
|
+
});
|
|
37
|
+
return currentFirst.slice(0, MENU_LIMIT);
|
|
38
|
+
}
|
|
39
|
+
const mineShown = computed(() => shortlist(mine.value));
|
|
40
|
+
const sharedShown = computed(() => shortlist(shared.value));
|
|
41
|
+
const hiddenCount = computed(
|
|
42
|
+
() => mine.value.length - mineShown.value.length + (shared.value.length - sharedShown.value.length)
|
|
43
|
+
);
|
|
44
|
+
const actions = computed(() => {
|
|
45
|
+
if (props.editing) {
|
|
46
|
+
return [
|
|
47
|
+
{ key: "rename", icon: Pen, label: t("dashboard.header.action.rename"), run: () => emit("rename") },
|
|
48
|
+
{ key: "add", icon: AddSquare, label: t("dashboard.header.action.add_widget"), run: () => emit("addWidget") },
|
|
49
|
+
{ key: "discard", icon: Restart, label: t("dashboard.header.action.discard"), disabled: props.saving, run: () => emit("discard") },
|
|
50
|
+
{
|
|
51
|
+
key: "save",
|
|
52
|
+
icon: Diskette,
|
|
53
|
+
label: props.saving ? t("dashboard.header.action.saving") : t("dashboard.header.action.save"),
|
|
54
|
+
disabled: props.saving || !props.dirty,
|
|
55
|
+
primary: true,
|
|
56
|
+
run: () => emit("save")
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
if (props.current?.canEdit) {
|
|
61
|
+
return [{ key: "edit", icon: Pen, label: t("dashboard.header.action.edit"), run: () => emit("edit") }];
|
|
62
|
+
}
|
|
63
|
+
if (props.current) {
|
|
64
|
+
return [{ key: "duplicate", icon: Copy, label: t("dashboard.header.action.duplicate_to_edit"), run: () => emit("duplicate") }];
|
|
65
|
+
}
|
|
66
|
+
return [];
|
|
67
|
+
});
|
|
68
|
+
const triggerLabel = computed(() => props.current && !props.current.isOwner ? t("dashboard.header.trigger.shared") : t("dashboard.header.trigger.mine"));
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<div class="relative flex shrink-0 flex-wrap items-center gap-2">
|
|
73
|
+
<!-- Dashboard picker. Same DropdownMenu as the profile menu in the topbar,
|
|
74
|
+
so hover, spacing and colours are shared rather than reproduced. -->
|
|
75
|
+
<!-- Hidden in edit mode: switching dashboards mid-edit was guarded by a
|
|
76
|
+
discard prompt, and not offering the switch is a better answer than
|
|
77
|
+
asking about it. -->
|
|
78
|
+
<DropdownMenu v-if="!editing">
|
|
79
|
+
<DropdownMenuTrigger as-child>
|
|
80
|
+
<Button v-if="compact" variant="outline" size="sm" class="h-7 gap-1.5 text-sm">
|
|
81
|
+
<span class="text-muted-foreground">{{ triggerLabel }}:</span>
|
|
82
|
+
<span class="max-w-[12rem] truncate font-medium">{{ current?.name ?? t("dashboard.page.title_fallback") }}</span>
|
|
83
|
+
<ChevronsUpDown weight="Bold" :size="12" class="shrink-0 text-muted-foreground" />
|
|
84
|
+
</Button>
|
|
85
|
+
<button
|
|
86
|
+
v-else
|
|
87
|
+
type="button"
|
|
88
|
+
class="group cursor-pointer rounded-lg border border-gray-400/10 outline-1 outline-white/30 transition-colors hover:border-primary/50"
|
|
89
|
+
>
|
|
90
|
+
<div class="flex items-center gap-2 rounded-lg bg-white px-2.5 py-1.5 transition-colors group-hover:bg-muted/60">
|
|
91
|
+
<span class="shrink-0 text-sm text-muted-foreground">{{ triggerLabel }}:</span>
|
|
92
|
+
<span class="max-w-[14rem] truncate text-sm font-medium text-[#0B0522]">
|
|
93
|
+
{{ current?.name ?? t("dashboard.page.title_fallback") }}
|
|
94
|
+
</span>
|
|
95
|
+
<ChevronsUpDown weight="Bold" :size="12" class="shrink-0 text-muted-foreground" />
|
|
96
|
+
</div>
|
|
97
|
+
</button>
|
|
98
|
+
</DropdownMenuTrigger>
|
|
99
|
+
|
|
100
|
+
<DropdownMenuContent align="end" class="w-64">
|
|
101
|
+
<DropdownMenuLabel class="text-xs font-normal text-muted-foreground">
|
|
102
|
+
{{ t("dashboard.header.group.mine") }}
|
|
103
|
+
</DropdownMenuLabel>
|
|
104
|
+
<DropdownMenuGroup>
|
|
105
|
+
<DropdownMenuItem
|
|
106
|
+
v-for="dashboard in mineShown"
|
|
107
|
+
:key="dashboard.id"
|
|
108
|
+
:class="dashboard.id === current?.id ? 'font-medium' : ''"
|
|
109
|
+
@select="emit('select', dashboard.id)"
|
|
110
|
+
>
|
|
111
|
+
<span class="flex-1 truncate">{{ dashboard.name }}</span>
|
|
112
|
+
<span
|
|
113
|
+
v-if="dashboard.isDefault"
|
|
114
|
+
class="flex shrink-0 items-center gap-1 rounded bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-amber-700"
|
|
115
|
+
>
|
|
116
|
+
{{ t("dashboard.header.menu.default_badge") }}
|
|
117
|
+
</span>
|
|
118
|
+
</DropdownMenuItem>
|
|
119
|
+
</DropdownMenuGroup>
|
|
120
|
+
|
|
121
|
+
<template v-if="shared.length">
|
|
122
|
+
<DropdownMenuSeparator />
|
|
123
|
+
<DropdownMenuLabel class="text-xs font-normal text-muted-foreground">
|
|
124
|
+
{{ t("dashboard.header.group.shared") }}
|
|
125
|
+
</DropdownMenuLabel>
|
|
126
|
+
<DropdownMenuGroup>
|
|
127
|
+
<DropdownMenuItem
|
|
128
|
+
v-for="dashboard in sharedShown"
|
|
129
|
+
:key="dashboard.id"
|
|
130
|
+
:class="dashboard.id === current?.id ? 'font-medium' : ''"
|
|
131
|
+
@select="emit('select', dashboard.id)"
|
|
132
|
+
>
|
|
133
|
+
<span class="flex-1 truncate">{{ dashboard.name }}</span>
|
|
134
|
+
<span
|
|
135
|
+
v-if="dashboard.isDefault"
|
|
136
|
+
class="flex shrink-0 items-center gap-1 rounded bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-amber-700"
|
|
137
|
+
>
|
|
138
|
+
{{ t("dashboard.header.menu.default_badge") }}
|
|
139
|
+
</span>
|
|
140
|
+
</DropdownMenuItem>
|
|
141
|
+
</DropdownMenuGroup>
|
|
142
|
+
</template>
|
|
143
|
+
|
|
144
|
+
<DropdownMenuSeparator />
|
|
145
|
+
<DropdownMenuGroup>
|
|
146
|
+
<DropdownMenuItem v-if="hiddenCount > 0" @select="emit('browse')">
|
|
147
|
+
<Magnifier :size="14" />
|
|
148
|
+
{{ t("dashboard.header.menu.browse") }}
|
|
149
|
+
</DropdownMenuItem>
|
|
150
|
+
<DropdownMenuItem @select="emit('create')">
|
|
151
|
+
<AddSquare :size="14" />
|
|
152
|
+
{{ t("dashboard.header.menu.new") }}
|
|
153
|
+
</DropdownMenuItem>
|
|
154
|
+
</DropdownMenuGroup>
|
|
155
|
+
|
|
156
|
+
<DropdownMenuSeparator />
|
|
157
|
+
<DropdownMenuGroup>
|
|
158
|
+
<DropdownMenuItem @select="emit('duplicate')">
|
|
159
|
+
<Copy :size="14" />
|
|
160
|
+
{{ t("dashboard.header.menu.duplicate") }}
|
|
161
|
+
</DropdownMenuItem>
|
|
162
|
+
<DropdownMenuItem v-if="current?.isOwner" @select="emit('share')">
|
|
163
|
+
<Global :size="14" />
|
|
164
|
+
{{ t("dashboard.header.menu.share") }}
|
|
165
|
+
</DropdownMenuItem>
|
|
166
|
+
<DropdownMenuItem v-if="current && !current.isDefault" @select="emit('setDefault')">
|
|
167
|
+
<Star :size="14" />
|
|
168
|
+
{{ t("dashboard.header.menu.set_default") }}
|
|
169
|
+
</DropdownMenuItem>
|
|
170
|
+
<DropdownMenuItem
|
|
171
|
+
v-if="current?.isOwner"
|
|
172
|
+
class="text-destructive focus:text-destructive"
|
|
173
|
+
@select="emit('remove')"
|
|
174
|
+
>
|
|
175
|
+
<TrashBinMinimalistic :size="14" />
|
|
176
|
+
{{ t("dashboard.header.menu.delete") }}
|
|
177
|
+
</DropdownMenuItem>
|
|
178
|
+
</DropdownMenuGroup>
|
|
179
|
+
</DropdownMenuContent>
|
|
180
|
+
</DropdownMenu>
|
|
181
|
+
|
|
182
|
+
<!-- What this surface scopes its widgets by: a period on the home screen,
|
|
183
|
+
a market in a studio. The row owns the position and the pill styling;
|
|
184
|
+
the surface owns the control. -->
|
|
185
|
+
<slot name="scope" :pill-class="PILL" :pane-class="PILL_PANE" :compact="compact" />
|
|
186
|
+
|
|
187
|
+
<span v-if="editing && dirty" class="text-xs text-muted-foreground">{{ t("dashboard.header.unsaved") }}</span>
|
|
188
|
+
|
|
189
|
+
<template v-for="action in actions" :key="action.key">
|
|
190
|
+
<Button
|
|
191
|
+
v-if="compact"
|
|
192
|
+
:variant="action.primary ? 'default' : 'outline'"
|
|
193
|
+
size="sm"
|
|
194
|
+
class="h-7 text-sm"
|
|
195
|
+
:disabled="action.disabled"
|
|
196
|
+
@click="action.run()"
|
|
197
|
+
>
|
|
198
|
+
<component :is="action.icon" :size="13" :class="action.primary ? '' : 'text-muted-foreground'" />
|
|
199
|
+
{{ action.label }}
|
|
200
|
+
</Button>
|
|
201
|
+
<button v-else type="button" :class="PILL" :disabled="action.disabled" @click="action.run()">
|
|
202
|
+
<span :class="action.primary ? PILL_PANE_PRIMARY : PILL_PANE">
|
|
203
|
+
<component :is="action.icon" :size="13" :class="action.primary ? '' : 'text-muted-foreground'" />
|
|
204
|
+
{{ action.label }}
|
|
205
|
+
</span>
|
|
206
|
+
</button>
|
|
207
|
+
</template>
|
|
208
|
+
</div>
|
|
209
|
+
</template>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Dashboard } from '../../types/dashboard.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
dashboards: Dashboard[];
|
|
4
|
+
current: Dashboard | null;
|
|
5
|
+
editing: boolean;
|
|
6
|
+
dirty: boolean;
|
|
7
|
+
saving: boolean;
|
|
8
|
+
/** House-header sizing: `h-7` studio buttons instead of the greeting bar's pills. */
|
|
9
|
+
compact?: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare var __VLS_149: {
|
|
12
|
+
pillClass: any;
|
|
13
|
+
paneClass: any;
|
|
14
|
+
compact: any;
|
|
15
|
+
};
|
|
16
|
+
type __VLS_Slots = {} & {
|
|
17
|
+
scope?: (props: typeof __VLS_149) => any;
|
|
18
|
+
};
|
|
19
|
+
declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {
|
|
20
|
+
PILL: string;
|
|
21
|
+
PILL_PANE: string;
|
|
22
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
23
|
+
edit: () => any;
|
|
24
|
+
select: (id: string) => any;
|
|
25
|
+
save: () => any;
|
|
26
|
+
discard: () => any;
|
|
27
|
+
addWidget: () => any;
|
|
28
|
+
rename: () => any;
|
|
29
|
+
share: () => any;
|
|
30
|
+
browse: () => any;
|
|
31
|
+
create: () => any;
|
|
32
|
+
duplicate: () => any;
|
|
33
|
+
remove: () => any;
|
|
34
|
+
setDefault: () => any;
|
|
35
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
36
|
+
onEdit?: (() => any) | undefined;
|
|
37
|
+
onSelect?: ((id: string) => any) | undefined;
|
|
38
|
+
onSave?: (() => any) | undefined;
|
|
39
|
+
onDiscard?: (() => any) | undefined;
|
|
40
|
+
onAddWidget?: (() => any) | undefined;
|
|
41
|
+
onRename?: (() => any) | undefined;
|
|
42
|
+
onShare?: (() => any) | undefined;
|
|
43
|
+
onBrowse?: (() => any) | undefined;
|
|
44
|
+
onCreate?: (() => any) | undefined;
|
|
45
|
+
onDuplicate?: (() => any) | undefined;
|
|
46
|
+
onRemove?: (() => any) | undefined;
|
|
47
|
+
onSetDefault?: (() => any) | undefined;
|
|
48
|
+
}>, {
|
|
49
|
+
compact: boolean;
|
|
50
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
51
|
+
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
52
|
+
export default _default;
|
|
53
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
54
|
+
new (): {
|
|
55
|
+
$slots: S;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DashboardLayoutItem, DashboardWidget } from '../../types/dashboard.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
layout: DashboardLayoutItem[];
|
|
4
|
+
widgets: DashboardWidget[];
|
|
5
|
+
editing?: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
remove: (widgetId: string) => any;
|
|
9
|
+
configure: (widgetId: string) => any;
|
|
10
|
+
"update:layout": (layout: DashboardLayoutItem[]) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
onRemove?: ((widgetId: string) => any) | undefined;
|
|
13
|
+
onConfigure?: ((widgetId: string) => any) | undefined;
|
|
14
|
+
"onUpdate:layout"?: ((layout: DashboardLayoutItem[]) => any) | undefined;
|
|
15
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { GridItem, GridLayout } from "grid-layout-plus";
|
|
3
|
+
import { computed, ref, watch } from "vue";
|
|
4
|
+
import WidgetFrame from "./DashboardWidgetFrame.vue";
|
|
5
|
+
import WidgetHost from "./DashboardWidgetHost.vue";
|
|
6
|
+
import { useWidgetRegistry } from "../../lib/widgetRegistry";
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
layout: { type: Array, required: true },
|
|
9
|
+
widgets: { type: Array, required: true },
|
|
10
|
+
editing: { type: Boolean, required: false }
|
|
11
|
+
});
|
|
12
|
+
const emit = defineEmits(["update:layout", "configure", "remove"]);
|
|
13
|
+
const registry = useWidgetRegistry();
|
|
14
|
+
const internalLayout = ref(props.layout.map((item) => ({ ...item })));
|
|
15
|
+
function normalize(items) {
|
|
16
|
+
return JSON.stringify(items.map(({ i, x, y, w, h }) => ({ i, x, y, w, h })));
|
|
17
|
+
}
|
|
18
|
+
function idsOf(items) {
|
|
19
|
+
return items.map((item) => item.i).sort().join("|");
|
|
20
|
+
}
|
|
21
|
+
watch(() => props.layout, (next) => {
|
|
22
|
+
const membershipChanged = idsOf(next) !== idsOf(internalLayout.value);
|
|
23
|
+
if (props.editing && !membershipChanged) return;
|
|
24
|
+
if (membershipChanged || normalize(next) !== normalize(internalLayout.value)) {
|
|
25
|
+
internalLayout.value = next.map((item) => ({ ...item }));
|
|
26
|
+
}
|
|
27
|
+
}, { deep: true });
|
|
28
|
+
watch(() => props.editing, () => {
|
|
29
|
+
internalLayout.value = props.layout.map((item) => ({ ...item }));
|
|
30
|
+
});
|
|
31
|
+
function onLayoutUpdated(next) {
|
|
32
|
+
if (!props.editing) return;
|
|
33
|
+
emit("update:layout", next.map(({ i, x, y, w, h }) => ({ i, x, y, w, h })));
|
|
34
|
+
}
|
|
35
|
+
function widgetFor(id) {
|
|
36
|
+
return props.widgets.find((w) => w.id === id);
|
|
37
|
+
}
|
|
38
|
+
const placedLayout = computed(() => internalLayout.value.filter((item) => !!widgetFor(item.i)));
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<!-- `-mx-3 -mt-3`: grid-layout-plus insets every item by `margin` on all four
|
|
43
|
+
sides, so without this the widgets sit one gutter inside the page padding
|
|
44
|
+
and one inside the header. Cancelling it on three sides keeps the gaps
|
|
45
|
+
between widgets and drops the duplicated outer one. -->
|
|
46
|
+
<GridLayout
|
|
47
|
+
v-model:layout="internalLayout"
|
|
48
|
+
class="relative z-0 -mx-3 -mt-3"
|
|
49
|
+
:col-num="12"
|
|
50
|
+
:row-height="20"
|
|
51
|
+
:margin="[12, 12]"
|
|
52
|
+
:is-draggable="!!editing"
|
|
53
|
+
:is-resizable="!!editing"
|
|
54
|
+
:responsive="false"
|
|
55
|
+
:vertical-compact="true"
|
|
56
|
+
:use-css-transforms="true"
|
|
57
|
+
drag-allow-from=".widget-drag-handle"
|
|
58
|
+
@layout-updated="onLayoutUpdated"
|
|
59
|
+
>
|
|
60
|
+
<GridItem
|
|
61
|
+
v-for="item in placedLayout"
|
|
62
|
+
:key="item.i"
|
|
63
|
+
:i="item.i"
|
|
64
|
+
:x="item.x"
|
|
65
|
+
:y="item.y"
|
|
66
|
+
:w="item.w"
|
|
67
|
+
:h="item.h"
|
|
68
|
+
:min-w="registry.get(widgetFor(item.i).type)?.minSize?.w ?? 2"
|
|
69
|
+
:min-h="registry.get(widgetFor(item.i).type)?.minSize?.h ?? 3"
|
|
70
|
+
>
|
|
71
|
+
<WidgetFrame
|
|
72
|
+
:definition="registry.get(widgetFor(item.i).type)"
|
|
73
|
+
:title="widgetFor(item.i).title"
|
|
74
|
+
:editing="editing"
|
|
75
|
+
:configurable="!!registry.get(widgetFor(item.i).type)?.configFields?.length"
|
|
76
|
+
@configure="emit('configure', item.i)"
|
|
77
|
+
@remove="emit('remove', item.i)"
|
|
78
|
+
>
|
|
79
|
+
<WidgetHost :widget="widgetFor(item.i)" />
|
|
80
|
+
</WidgetFrame>
|
|
81
|
+
</GridItem>
|
|
82
|
+
</GridLayout>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<style scoped>
|
|
86
|
+
:deep(.vgl-item--placeholder){background:rgba(124,58,237,.16);border:2px dashed rgba(124,58,237,.55);border-radius:.5rem;box-shadow:inset 0 0 0 9999px rgba(124,58,237,.04);opacity:1}:deep(.vgl-item--dragging){cursor:grabbing;z-index:40}:deep(.vgl-item--dragging)>*{border-radius:.5rem;box-shadow:0 12px 32px -8px rgba(11,5,34,.25);transform:scale(1.01);transition:transform .12s ease,box-shadow .12s ease}:deep(.vgl-item--resizing)>*{border-radius:.5rem;box-shadow:0 8px 24px -8px rgba(11,5,34,.2)}:deep(.vgl-item__resizer){opacity:.55;z-index:30}:deep(.vgl-item:hover .vgl-item__resizer){opacity:1}
|
|
87
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DashboardLayoutItem, DashboardWidget } from '../../types/dashboard.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
layout: DashboardLayoutItem[];
|
|
4
|
+
widgets: DashboardWidget[];
|
|
5
|
+
editing?: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
remove: (widgetId: string) => any;
|
|
9
|
+
configure: (widgetId: string) => any;
|
|
10
|
+
"update:layout": (layout: DashboardLayoutItem[]) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
onRemove?: ((widgetId: string) => any) | undefined;
|
|
13
|
+
onConfigure?: ((widgetId: string) => any) | undefined;
|
|
14
|
+
"onUpdate:layout"?: ((layout: DashboardLayoutItem[]) => any) | undefined;
|
|
15
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
open: boolean;
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
label: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
confirmLabel: string;
|
|
8
|
+
/** Pre-filled value — a rename starts from the current name. */
|
|
9
|
+
initial?: string;
|
|
10
|
+
};
|
|
11
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
|
+
"update:open": (value: boolean) => any;
|
|
13
|
+
submit: (name: string) => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
15
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
16
|
+
onSubmit?: ((name: string) => any) | undefined;
|
|
17
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label } from "@revenexx/studio";
|
|
3
|
+
const props = defineProps({
|
|
4
|
+
open: { type: Boolean, required: true },
|
|
5
|
+
title: { type: String, required: true },
|
|
6
|
+
description: { type: String, required: false },
|
|
7
|
+
label: { type: String, required: true },
|
|
8
|
+
placeholder: { type: String, required: false },
|
|
9
|
+
confirmLabel: { type: String, required: true },
|
|
10
|
+
initial: { type: String, required: false }
|
|
11
|
+
});
|
|
12
|
+
const emit = defineEmits(["update:open", "submit"]);
|
|
13
|
+
const t = useT();
|
|
14
|
+
const value = ref("");
|
|
15
|
+
const field = ref(null);
|
|
16
|
+
watch(() => props.open, async (open) => {
|
|
17
|
+
if (!open) return;
|
|
18
|
+
value.value = props.initial ?? "";
|
|
19
|
+
await nextTick();
|
|
20
|
+
const el = field.value?.$el ?? field.value;
|
|
21
|
+
el?.querySelector?.("input")?.focus?.() ?? el?.focus?.();
|
|
22
|
+
});
|
|
23
|
+
const canSubmit = computed(() => value.value.trim().length > 0);
|
|
24
|
+
function submit() {
|
|
25
|
+
if (!canSubmit.value) return;
|
|
26
|
+
emit("submit", value.value.trim());
|
|
27
|
+
emit("update:open", false);
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<Dialog :open="open" @update:open="emit('update:open', $event)">
|
|
33
|
+
<DialogContent class="max-w-md">
|
|
34
|
+
<DialogHeader>
|
|
35
|
+
<DialogTitle>{{ title }}</DialogTitle>
|
|
36
|
+
<DialogDescription v-if="description">{{ description }}</DialogDescription>
|
|
37
|
+
</DialogHeader>
|
|
38
|
+
|
|
39
|
+
<div class="space-y-1.5">
|
|
40
|
+
<Label for="dashboard-name">{{ label }}</Label>
|
|
41
|
+
<Input
|
|
42
|
+
id="dashboard-name"
|
|
43
|
+
ref="field"
|
|
44
|
+
v-model="value"
|
|
45
|
+
class="bg-white"
|
|
46
|
+
:placeholder="placeholder"
|
|
47
|
+
@keydown.enter.prevent="submit"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<DialogFooter>
|
|
52
|
+
<Button variant="outline" size="sm" class="h-7 text-sm" @click="emit('update:open', false)">
|
|
53
|
+
{{ t("dashboard.dialog.action.cancel") }}
|
|
54
|
+
</Button>
|
|
55
|
+
<Button size="sm" class="h-7 text-sm" :disabled="!canSubmit" @click="submit">
|
|
56
|
+
{{ confirmLabel }}
|
|
57
|
+
</Button>
|
|
58
|
+
</DialogFooter>
|
|
59
|
+
</DialogContent>
|
|
60
|
+
</Dialog>
|
|
61
|
+
</template>
|