@shwfed/nuxt 0.1.75 → 0.1.77
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 +13 -134
- package/dist/module.json +2 -2
- package/dist/module.mjs +17 -14
- package/dist/runtime/components/app.d.vue.ts +33 -3
- package/dist/runtime/components/app.vue +91 -307
- package/dist/runtime/components/app.vue.d.ts +33 -3
- package/dist/runtime/composables/useFavorite.d.ts +1 -1
- package/dist/runtime/composables/useNavigationTabs.d.ts +19 -5
- package/dist/runtime/composables/useNavigationTabs.js +71 -46
- package/dist/runtime/middleware/token.d.ts +2 -0
- package/dist/runtime/middleware/token.js +9 -0
- package/dist/runtime/plugins/api/index.d.ts +0 -2
- package/dist/runtime/plugins/api/index.js +6 -56
- package/dist/runtime/plugins/cel/env.js +1 -1
- package/dist/runtime/plugins/cel/index.js +3 -2
- package/dist/runtime/plugins/toast/index.d.ts +2 -2
- package/dist/types.d.mts +7 -1
- package/package.json +1 -1
- package/dist/runtime/utils/command-palette.d.ts +0 -71
- package/dist/runtime/utils/command-palette.js +0 -72
- package/dist/runtime/utils/command.d.ts +0 -31
- package/dist/runtime/utils/command.js +0 -40
- package/dist/runtime/utils/navigation-title.d.ts +0 -10
- package/dist/runtime/utils/navigation-title.js +0 -28
- package/dist/runtime/utils/route.d.ts +0 -2
- package/dist/runtime/utils/route.js +0 -14
- package/dist/runtime/utils/sidebar-fallback.d.ts +0 -16
- package/dist/runtime/utils/sidebar-fallback.js +0 -32
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { useHead, useNuxtApp,
|
|
3
|
-
import {
|
|
2
|
+
import { useHead, useNuxtApp, useRuntimeConfig } from "#app";
|
|
3
|
+
import { reactive } from "vue";
|
|
4
4
|
import { CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "./ui/command";
|
|
5
5
|
import { TooltipProvider } from "./ui/tooltip";
|
|
6
6
|
import { Toaster } from "vue-sonner";
|
|
@@ -12,25 +12,28 @@ import { setGlobalDslContext } from "../plugins/cel/context";
|
|
|
12
12
|
import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider } from "./ui/sidebar";
|
|
13
13
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible";
|
|
14
14
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "./ui/dropdown-menu";
|
|
15
|
-
import { z } from "zod";
|
|
16
15
|
import Logo from "./logo.vue";
|
|
17
16
|
import { useFavorite } from "../composables/useFavorite";
|
|
18
17
|
import { useNavigationTabs } from "../composables/useNavigationTabs";
|
|
19
|
-
import { commandActionSchema, executeCommandAction } from "../utils/command";
|
|
20
|
-
import { createPaletteItemValue, normalizeCommandsForPalette, splitNavigationForPalette } from "../utils/command-palette";
|
|
21
|
-
import { resolveNavigationTitle } from "../utils/navigation-title";
|
|
22
|
-
import { isRouteActive } from "../utils/route";
|
|
23
|
-
import { applyRootNavigationFallback } from "../utils/sidebar-fallback";
|
|
24
|
-
const { $dsl, $logout, $md, $api, $toast } = useNuxtApp();
|
|
25
|
-
const { t } = useI18n();
|
|
26
|
-
const route = useRoute();
|
|
27
|
-
const { tabs, active, tabList, activateTab, closeTab } = useNavigationTabs();
|
|
28
18
|
const {
|
|
29
19
|
public: {
|
|
30
|
-
shwfed
|
|
20
|
+
shwfed
|
|
31
21
|
}
|
|
32
22
|
} = useRuntimeConfig();
|
|
23
|
+
const { $dsl } = useNuxtApp();
|
|
24
|
+
const { t } = useI18n();
|
|
25
|
+
const props = defineProps({
|
|
26
|
+
dsl: { type: null, required: false },
|
|
27
|
+
sidebar: { type: Array, required: false },
|
|
28
|
+
commands: { type: Array, required: false }
|
|
29
|
+
});
|
|
30
|
+
const { active, tabs, nameOf, close } = useNavigationTabs(() => props.sidebar ?? []);
|
|
31
|
+
const ui = reactive({
|
|
32
|
+
isCommandPaletteOpen: false,
|
|
33
|
+
isProfileDropdownOpen: false
|
|
34
|
+
});
|
|
33
35
|
useHead({
|
|
36
|
+
title: () => active.value && nameOf(active.value),
|
|
34
37
|
bodyAttrs: {
|
|
35
38
|
style: {
|
|
36
39
|
"--primary": "#2DA8BC",
|
|
@@ -38,221 +41,23 @@ useHead({
|
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
});
|
|
41
|
-
const props = defineProps({
|
|
42
|
-
dsl: { type: null, required: false }
|
|
43
|
-
});
|
|
44
|
-
const ui = reactive({
|
|
45
|
-
isCommandPaletteOpen: false,
|
|
46
|
-
isProfileDropdownOpen: false
|
|
47
|
-
});
|
|
48
44
|
const { start: startProfileCloseTimer, stop: stopProfileCloseTimer } = useTimeoutFn(() => {
|
|
49
45
|
ui.isProfileDropdownOpen = false;
|
|
50
46
|
}, 150, { immediate: false });
|
|
51
47
|
const { meta_k } = useMagicKeys();
|
|
52
48
|
whenever(() => meta_k?.value, () => ui.isCommandPaletteOpen = !ui.isCommandPaletteOpen);
|
|
53
49
|
setGlobalDslContext(await props.dsl?.pipe(Effect.scoped).pipe(Effect.runPromise) ?? {});
|
|
54
|
-
const navigationItem = z.object({
|
|
55
|
-
id: z.union([z.string(), z.number()]),
|
|
56
|
-
title: z.string(),
|
|
57
|
-
icon: z.string().optional(),
|
|
58
|
-
route: z.string(),
|
|
59
|
-
keywords: z.array(z.string()).optional()
|
|
60
|
-
});
|
|
61
|
-
const navigationGroup = z.object({
|
|
62
|
-
id: z.union([z.string(), z.number()]),
|
|
63
|
-
title: z.string(),
|
|
64
|
-
icon: z.string().optional(),
|
|
65
|
-
children: z.array(navigationItem)
|
|
66
|
-
});
|
|
67
|
-
const navigationEntry = z.union([navigationGroup, navigationItem]);
|
|
68
|
-
const navigationItems = computed(() => {
|
|
69
|
-
try {
|
|
70
|
-
const result = $dsl.evaluate`${config.sidebar.menus}`();
|
|
71
|
-
return z.array(navigationEntry).parse(result);
|
|
72
|
-
} catch {
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
50
|
const {
|
|
77
51
|
isFavorited: isNavigationFavorited,
|
|
78
52
|
canBeFavorited: canBeNavigationFavorited,
|
|
79
53
|
toggle: toggleNavigationFavorite,
|
|
80
54
|
withFavorites: navigations
|
|
81
|
-
} = useFavorite("navigation",
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
55
|
+
} = useFavorite("navigation", () => props.sidebar ?? []);
|
|
56
|
+
const logout = () => {
|
|
57
|
+
if (shwfed.api.logout)
|
|
58
|
+
window.location.href = shwfed.api.logout;
|
|
59
|
+
return Effect.void;
|
|
85
60
|
};
|
|
86
|
-
const activeTabLabel = computed(() => {
|
|
87
|
-
if (active.value === void 0)
|
|
88
|
-
return void 0;
|
|
89
|
-
return getTabLabel(active.value);
|
|
90
|
-
});
|
|
91
|
-
useHead(() => ({
|
|
92
|
-
title: activeTabLabel.value
|
|
93
|
-
}));
|
|
94
|
-
const profileName = computed(() => {
|
|
95
|
-
if (config.profile.name === void 0)
|
|
96
|
-
return t("profile");
|
|
97
|
-
try {
|
|
98
|
-
const content = $md.inline`${config.profile.name}`();
|
|
99
|
-
const plainText = content.replace(/<[^>]*>/g, "").trim();
|
|
100
|
-
if (plainText.length === 0)
|
|
101
|
-
return t("profile");
|
|
102
|
-
return plainText;
|
|
103
|
-
} catch {
|
|
104
|
-
return t("profile");
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
const commandItem = z.object({
|
|
108
|
-
id: z.union([z.string(), z.number()]),
|
|
109
|
-
title: z.string(),
|
|
110
|
-
icon: z.string().optional(),
|
|
111
|
-
hidden: z.string().optional(),
|
|
112
|
-
disabled: z.string().optional(),
|
|
113
|
-
effect: z.string().optional(),
|
|
114
|
-
keywords: z.array(z.string()).optional()
|
|
115
|
-
});
|
|
116
|
-
const commandGroup = z.object({
|
|
117
|
-
id: z.union([z.string(), z.number()]),
|
|
118
|
-
title: z.string(),
|
|
119
|
-
icon: z.string().optional(),
|
|
120
|
-
children: z.array(commandItem)
|
|
121
|
-
});
|
|
122
|
-
const evaluateDslBoolean = (expression) => {
|
|
123
|
-
if (expression === void 0)
|
|
124
|
-
return false;
|
|
125
|
-
try {
|
|
126
|
-
return $dsl.evaluate`${expression}`() === true;
|
|
127
|
-
} catch {
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
const normalizeEffectExpression = (expression) => {
|
|
132
|
-
return expression.replace(/([{,]\s*)([a-z_][\w-]*)(\s*:)/gi, "$1'$2'$3");
|
|
133
|
-
};
|
|
134
|
-
const profileCommands = computed(() => {
|
|
135
|
-
if (config.commands === void 0)
|
|
136
|
-
return [];
|
|
137
|
-
try {
|
|
138
|
-
const result = $dsl.evaluate`${config.commands}`();
|
|
139
|
-
const entries = z.array(z.union([commandGroup, commandItem])).parse(result);
|
|
140
|
-
const normalized = [];
|
|
141
|
-
for (const entry of entries) {
|
|
142
|
-
if ("children" in entry) {
|
|
143
|
-
const children = entry.children.filter((child) => !evaluateDslBoolean(child.hidden)).map((child) => ({
|
|
144
|
-
id: child.id,
|
|
145
|
-
title: child.title,
|
|
146
|
-
icon: child.icon,
|
|
147
|
-
disabled: evaluateDslBoolean(child.disabled),
|
|
148
|
-
effect: child.effect,
|
|
149
|
-
keywords: child.keywords
|
|
150
|
-
}));
|
|
151
|
-
if (children.length > 0) {
|
|
152
|
-
normalized.push({
|
|
153
|
-
id: entry.id,
|
|
154
|
-
title: entry.title,
|
|
155
|
-
icon: entry.icon,
|
|
156
|
-
children
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
if (evaluateDslBoolean(entry.hidden))
|
|
162
|
-
continue;
|
|
163
|
-
normalized.push({
|
|
164
|
-
id: entry.id,
|
|
165
|
-
title: entry.title,
|
|
166
|
-
icon: entry.icon,
|
|
167
|
-
disabled: evaluateDslBoolean(entry.disabled),
|
|
168
|
-
effect: entry.effect,
|
|
169
|
-
keywords: entry.keywords
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
return normalized;
|
|
173
|
-
} catch {
|
|
174
|
-
return [];
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
const isBodyInit = (value) => {
|
|
178
|
-
return typeof value === "string" || value instanceof Blob || value instanceof ArrayBuffer || ArrayBuffer.isView(value) || value instanceof FormData || value instanceof URLSearchParams || value instanceof ReadableStream;
|
|
179
|
-
};
|
|
180
|
-
const toApiRequestBody = (value) => {
|
|
181
|
-
if (value === void 0 || value === null)
|
|
182
|
-
return value;
|
|
183
|
-
if (isBodyInit(value))
|
|
184
|
-
return value;
|
|
185
|
-
if (typeof value === "object")
|
|
186
|
-
return value;
|
|
187
|
-
return void 0;
|
|
188
|
-
};
|
|
189
|
-
const executeCommandEffect = async (effect) => {
|
|
190
|
-
if (effect === void 0)
|
|
191
|
-
return false;
|
|
192
|
-
let action;
|
|
193
|
-
try {
|
|
194
|
-
let result;
|
|
195
|
-
try {
|
|
196
|
-
result = $dsl.evaluate`${effect}`();
|
|
197
|
-
} catch {
|
|
198
|
-
result = $dsl.evaluate`${normalizeEffectExpression(effect)}`();
|
|
199
|
-
}
|
|
200
|
-
action = commandActionSchema.parse(result);
|
|
201
|
-
await executeCommandAction(action, {
|
|
202
|
-
logout: $logout,
|
|
203
|
-
request: async (requestAction) => $api(requestAction.url, {
|
|
204
|
-
method: requestAction.method,
|
|
205
|
-
body: toApiRequestBody(requestAction.body),
|
|
206
|
-
query: requestAction.query,
|
|
207
|
-
headers: requestAction.headers
|
|
208
|
-
})
|
|
209
|
-
});
|
|
210
|
-
return true;
|
|
211
|
-
} catch (error) {
|
|
212
|
-
if (action?.type === "request") {
|
|
213
|
-
const message = error instanceof Error && error.message.length > 0 ? error.message : "Request failed";
|
|
214
|
-
$toast.error(message);
|
|
215
|
-
}
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
const executeProfileCommand = async (effect) => {
|
|
220
|
-
if (effect === void 0 || await executeCommandEffect(effect))
|
|
221
|
-
ui.isProfileDropdownOpen = false;
|
|
222
|
-
};
|
|
223
|
-
const normalizedNavigationForPalette = computed(() => {
|
|
224
|
-
try {
|
|
225
|
-
return z.array(navigationEntry).parse(navigations.value);
|
|
226
|
-
} catch {
|
|
227
|
-
return navigationItems.value;
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
const paletteNavigation = computed(() => splitNavigationForPalette(normalizedNavigationForPalette.value));
|
|
231
|
-
const favoriteRoutesForPalette = computed(() => paletteNavigation.value.favoriteRoutes);
|
|
232
|
-
const navigationForPalette = computed(() => paletteNavigation.value.navigationEntries);
|
|
233
|
-
const commandForPalette = computed(() => normalizeCommandsForPalette(profileCommands.value));
|
|
234
|
-
const executePaletteItem = async (item) => {
|
|
235
|
-
if (item.type === "route") {
|
|
236
|
-
activateTab(item.route);
|
|
237
|
-
ui.isCommandPaletteOpen = false;
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (item.effect !== void 0 && !await executeCommandEffect(item.effect))
|
|
241
|
-
return;
|
|
242
|
-
ui.isCommandPaletteOpen = false;
|
|
243
|
-
};
|
|
244
|
-
watch(
|
|
245
|
-
() => route.path,
|
|
246
|
-
(path) => {
|
|
247
|
-
applyRootNavigationFallback({
|
|
248
|
-
path,
|
|
249
|
-
items: navigationItems.value,
|
|
250
|
-
tabs: tabs.value,
|
|
251
|
-
activateTab
|
|
252
|
-
});
|
|
253
|
-
},
|
|
254
|
-
{ immediate: true }
|
|
255
|
-
);
|
|
256
61
|
</script>
|
|
257
62
|
|
|
258
63
|
<template>
|
|
@@ -276,102 +81,68 @@ watch(
|
|
|
276
81
|
</section>
|
|
277
82
|
</CommandEmpty>
|
|
278
83
|
|
|
279
|
-
<CommandGroup
|
|
280
|
-
v-if="favoriteRoutesForPalette.length > 0"
|
|
281
|
-
:heading="t('favorites')"
|
|
282
|
-
>
|
|
283
|
-
<CommandItem
|
|
284
|
-
v-for="menu in favoriteRoutesForPalette"
|
|
285
|
-
:key="`fav:${menu.id}:${menu.route}`"
|
|
286
|
-
:value="createPaletteItemValue('fav', `${menu.id}:${menu.route}`)"
|
|
287
|
-
@select="executePaletteItem({ type: 'route', route: menu.route })"
|
|
288
|
-
>
|
|
289
|
-
<span>{{ menu.title }}</span>
|
|
290
|
-
<span
|
|
291
|
-
v-if="menu.keywords.length > 0"
|
|
292
|
-
class="sr-only"
|
|
293
|
-
>
|
|
294
|
-
{{ menu.keywords.join(" ") }}
|
|
295
|
-
</span>
|
|
296
|
-
</CommandItem>
|
|
297
|
-
</CommandGroup>
|
|
298
|
-
|
|
299
84
|
<template
|
|
300
|
-
v-for="
|
|
301
|
-
:key="
|
|
85
|
+
v-for="command in props.commands"
|
|
86
|
+
:key="command.id"
|
|
302
87
|
>
|
|
303
88
|
<CommandGroup
|
|
304
|
-
v-if="'children' in
|
|
305
|
-
:heading="
|
|
89
|
+
v-if="'children' in command"
|
|
90
|
+
:heading="command.title"
|
|
306
91
|
>
|
|
307
92
|
<CommandItem
|
|
308
|
-
v-for="
|
|
309
|
-
:key="
|
|
310
|
-
:value="
|
|
311
|
-
|
|
93
|
+
v-for="child in command.children.filter((child2) => !child2.hidden)"
|
|
94
|
+
:key="child.id"
|
|
95
|
+
:value="child.id"
|
|
96
|
+
:disabled="child.disabled"
|
|
97
|
+
@select="child.effect?.pipe(Effect.tap(() => {
|
|
98
|
+
ui.isCommandPaletteOpen = false;
|
|
99
|
+
})).pipe(Effect.runPromise)"
|
|
312
100
|
>
|
|
313
|
-
<span>{{
|
|
314
|
-
<span
|
|
315
|
-
|
|
316
|
-
class="sr-only"
|
|
317
|
-
>
|
|
318
|
-
{{ menu.keywords.join(" ") }}
|
|
101
|
+
<span>{{ child.title }}</span>
|
|
102
|
+
<span class="sr-only">
|
|
103
|
+
{{ child.keywords?.join(" ") }}
|
|
319
104
|
</span>
|
|
320
105
|
</CommandItem>
|
|
321
106
|
</CommandGroup>
|
|
322
107
|
<CommandGroup v-else>
|
|
323
108
|
<CommandItem
|
|
324
|
-
:value="
|
|
325
|
-
|
|
109
|
+
:value="command.id"
|
|
110
|
+
:disabled="command.disabled"
|
|
111
|
+
@select="command.effect?.pipe(Effect.tap(() => {
|
|
112
|
+
ui.isCommandPaletteOpen = false;
|
|
113
|
+
})).pipe(Effect.runPromise)"
|
|
326
114
|
>
|
|
327
|
-
<span>{{
|
|
328
|
-
<span
|
|
329
|
-
|
|
330
|
-
class="sr-only"
|
|
331
|
-
>
|
|
332
|
-
{{ navigation.keywords.join(" ") }}
|
|
115
|
+
<span>{{ command.title }}</span>
|
|
116
|
+
<span class="sr-only">
|
|
117
|
+
{{ command.keywords?.join(" ") }}
|
|
333
118
|
</span>
|
|
334
119
|
</CommandItem>
|
|
335
120
|
</CommandGroup>
|
|
336
121
|
</template>
|
|
337
122
|
|
|
338
123
|
<template
|
|
339
|
-
v-for="
|
|
340
|
-
:key="
|
|
124
|
+
v-for="navigation in props.sidebar"
|
|
125
|
+
:key="navigation.id"
|
|
341
126
|
>
|
|
342
127
|
<CommandGroup
|
|
343
|
-
v-if="'children' in
|
|
344
|
-
:heading="
|
|
128
|
+
v-if="'children' in navigation"
|
|
129
|
+
:heading="navigation.title"
|
|
345
130
|
>
|
|
346
131
|
<CommandItem
|
|
347
|
-
v-for="
|
|
348
|
-
:key="
|
|
349
|
-
:value="
|
|
350
|
-
|
|
351
|
-
@select="executePaletteItem({ type: 'command', effect: child.effect })"
|
|
132
|
+
v-for="menu in navigation.children"
|
|
133
|
+
:key="menu.id"
|
|
134
|
+
:value="menu.id"
|
|
135
|
+
@select="$router.replace(menu.route)"
|
|
352
136
|
>
|
|
353
|
-
<span>{{
|
|
354
|
-
<span
|
|
355
|
-
v-if="child.keywords.length > 0"
|
|
356
|
-
class="sr-only"
|
|
357
|
-
>
|
|
358
|
-
{{ child.keywords.join(" ") }}
|
|
359
|
-
</span>
|
|
137
|
+
<span>{{ menu.title }}</span>
|
|
360
138
|
</CommandItem>
|
|
361
139
|
</CommandGroup>
|
|
362
140
|
<CommandGroup v-else>
|
|
363
141
|
<CommandItem
|
|
364
|
-
:value="
|
|
365
|
-
|
|
366
|
-
@select="executePaletteItem({ type: 'command', effect: command.effect })"
|
|
142
|
+
:value="navigation.id"
|
|
143
|
+
@select="$router.replace(navigation.route)"
|
|
367
144
|
>
|
|
368
|
-
<span>{{
|
|
369
|
-
<span
|
|
370
|
-
v-if="command.keywords.length > 0"
|
|
371
|
-
class="sr-only"
|
|
372
|
-
>
|
|
373
|
-
{{ command.keywords.join(" ") }}
|
|
374
|
-
</span>
|
|
145
|
+
<span>{{ navigation.title }}</span>
|
|
375
146
|
</CommandItem>
|
|
376
147
|
</CommandGroup>
|
|
377
148
|
</template>
|
|
@@ -400,7 +171,7 @@ watch(
|
|
|
400
171
|
@click.prevent="stopProfileCloseTimer();
|
|
401
172
|
ui.isProfileDropdownOpen = true"
|
|
402
173
|
>
|
|
403
|
-
|
|
174
|
+
<slot name="profile" />
|
|
404
175
|
</button>
|
|
405
176
|
</DropdownMenuTrigger>
|
|
406
177
|
<DropdownMenuContent
|
|
@@ -411,33 +182,37 @@ watch(
|
|
|
411
182
|
startProfileCloseTimer()"
|
|
412
183
|
>
|
|
413
184
|
<template
|
|
414
|
-
v-for="command in
|
|
185
|
+
v-for="command in props.commands"
|
|
415
186
|
:key="command.id"
|
|
416
187
|
>
|
|
417
|
-
<template v-if="'children' in command">
|
|
188
|
+
<template v-if="'children' in command && command.children.some((child2) => !child2.hidden)">
|
|
418
189
|
<DropdownMenuLabel>
|
|
419
190
|
{{ command.title }}
|
|
420
191
|
</DropdownMenuLabel>
|
|
421
192
|
<DropdownMenuGroup>
|
|
422
|
-
<
|
|
193
|
+
<template
|
|
423
194
|
v-for="child in command.children"
|
|
424
195
|
:key="child.id"
|
|
425
|
-
:disabled="child.disabled"
|
|
426
|
-
@select="executeProfileCommand(child.effect)"
|
|
427
196
|
>
|
|
428
|
-
<
|
|
429
|
-
v-if="child.
|
|
430
|
-
:
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
197
|
+
<DropdownMenuItem
|
|
198
|
+
v-if="!child.hidden"
|
|
199
|
+
:disabled="child.disabled"
|
|
200
|
+
@select="child.effect?.pipe(Effect.runPromise)"
|
|
201
|
+
>
|
|
202
|
+
<Icon
|
|
203
|
+
v-if="child.icon"
|
|
204
|
+
:icon="child.icon"
|
|
205
|
+
/>
|
|
206
|
+
{{ child.title }}
|
|
207
|
+
</DropdownMenuItem>
|
|
208
|
+
</template>
|
|
434
209
|
</DropdownMenuGroup>
|
|
435
210
|
<DropdownMenuSeparator />
|
|
436
211
|
</template>
|
|
437
212
|
<DropdownMenuItem
|
|
438
|
-
v-else
|
|
213
|
+
v-else-if="!('children' in command) && !command.hidden"
|
|
439
214
|
:disabled="command.disabled"
|
|
440
|
-
@select="
|
|
215
|
+
@select="command.effect?.pipe(Effect.runPromise)"
|
|
441
216
|
>
|
|
442
217
|
<Icon
|
|
443
218
|
v-if="command.icon"
|
|
@@ -446,7 +221,15 @@ watch(
|
|
|
446
221
|
{{ command.title }}
|
|
447
222
|
</DropdownMenuItem>
|
|
448
223
|
</template>
|
|
449
|
-
<
|
|
224
|
+
<DropdownMenuItem
|
|
225
|
+
@select="logout"
|
|
226
|
+
>
|
|
227
|
+
<Icon
|
|
228
|
+
icon="fluent:sign-out-20-regular"
|
|
229
|
+
/>
|
|
230
|
+
{{ t("logout") }}
|
|
231
|
+
</DropdownMenuItem>
|
|
232
|
+
<DropdownMenuSeparator v-if="props.commands?.some((command2) => 'children' in command2 ? command2.children.some((child2) => !child2.hidden) : !command2.hidden)" />
|
|
450
233
|
<DropdownMenuItem disabled>
|
|
451
234
|
<Icon icon="fluent:history-20-regular" />
|
|
452
235
|
{{ t("build") }}
|
|
@@ -503,11 +286,11 @@ watch(
|
|
|
503
286
|
<SidebarMenuButton
|
|
504
287
|
v-if="'route' in menu"
|
|
505
288
|
as-child
|
|
506
|
-
:is-active="
|
|
289
|
+
:is-active="active === menu.route"
|
|
507
290
|
>
|
|
508
291
|
<button
|
|
509
292
|
type="button"
|
|
510
|
-
@click="
|
|
293
|
+
@click="active = menu.route"
|
|
511
294
|
>
|
|
512
295
|
<Icon
|
|
513
296
|
v-if="menu.icon"
|
|
@@ -539,11 +322,11 @@ watch(
|
|
|
539
322
|
<SidebarMenuItem>
|
|
540
323
|
<SidebarMenuButton
|
|
541
324
|
as-child
|
|
542
|
-
:is-active="
|
|
325
|
+
:is-active="active === group.route"
|
|
543
326
|
>
|
|
544
327
|
<button
|
|
545
328
|
type="button"
|
|
546
|
-
@click="
|
|
329
|
+
@click="active = group.route"
|
|
547
330
|
>
|
|
548
331
|
<Icon
|
|
549
332
|
v-if="group.icon"
|
|
@@ -564,7 +347,7 @@ watch(
|
|
|
564
347
|
<main class="flex-1 flex flex-col bg-zinc-100 overflow-hidden">
|
|
565
348
|
<nav class="bg-white p-1 border-b border-zinc-100 h-10 flex items-center justify-start gap-1 overflow-x-auto min-w-0">
|
|
566
349
|
<div
|
|
567
|
-
v-for="tab in
|
|
350
|
+
v-for="tab in tabs"
|
|
568
351
|
:key="tab"
|
|
569
352
|
:class="[
|
|
570
353
|
'h-8 max-w-60 border rounded px-2 text-sm flex items-center gap-2 shrink-0 min-w-0 transition-colors',
|
|
@@ -574,14 +357,15 @@ watch(
|
|
|
574
357
|
<button
|
|
575
358
|
type="button"
|
|
576
359
|
class="truncate cursor-pointer"
|
|
577
|
-
@click="
|
|
360
|
+
@click="active = tab"
|
|
578
361
|
>
|
|
579
|
-
{{
|
|
362
|
+
{{ nameOf(tab) }}
|
|
580
363
|
</button>
|
|
581
364
|
<button
|
|
365
|
+
v-if="tabs.size !== 1"
|
|
582
366
|
type="button"
|
|
583
367
|
class="cursor-pointer text-zinc-500 hover:text-zinc-900"
|
|
584
|
-
@click.stop="
|
|
368
|
+
@click.stop="close(tab)"
|
|
585
369
|
>
|
|
586
370
|
<Icon icon="fluent:dismiss-20-regular" />
|
|
587
371
|
</button>
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import type { Scope } from 'effect';
|
|
2
2
|
import { Effect } from 'effect';
|
|
3
|
+
import { type Sidebar as SidebarType } from '../composables/useNavigationTabs.js';
|
|
4
|
+
type ProfileCommandInputItem = Readonly<{
|
|
5
|
+
id: string | number;
|
|
6
|
+
title: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
hidden?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
effect?: Effect.Effect<void>;
|
|
11
|
+
keywords?: Array<string>;
|
|
12
|
+
}>;
|
|
13
|
+
type ProfileCommandInputGroup = Readonly<{
|
|
14
|
+
id: string | number;
|
|
15
|
+
title: string;
|
|
16
|
+
icon?: string;
|
|
17
|
+
children: Array<ProfileCommandInputItem>;
|
|
18
|
+
}>;
|
|
3
19
|
type __VLS_Props = {
|
|
4
20
|
/**
|
|
5
21
|
* 应用的全局 DSL(CEL)执行上下文。传入一个 Effect,在应用启动时运行一次;
|
|
@@ -38,12 +54,26 @@ type __VLS_Props = {
|
|
|
38
54
|
* // CEL 中可访问 user.name、permissions.canEdit 等;失败时在 effect 内兜底,不抛错
|
|
39
55
|
*/
|
|
40
56
|
dsl?: Effect.Effect<Record<string, unknown>, never, Scope.Scope>;
|
|
57
|
+
/**
|
|
58
|
+
* Sidebar config.
|
|
59
|
+
*
|
|
60
|
+
* `menus` is structured navigation data.
|
|
61
|
+
*/
|
|
62
|
+
sidebar?: SidebarType;
|
|
63
|
+
/**
|
|
64
|
+
* Profile command config.
|
|
65
|
+
*
|
|
66
|
+
* Structured command entries for profile menu and command palette.
|
|
67
|
+
*/
|
|
68
|
+
commands?: Array<ProfileCommandInputItem | ProfileCommandInputGroup>;
|
|
41
69
|
};
|
|
42
|
-
declare var
|
|
70
|
+
declare var __VLS_108: {}, __VLS_124: {}, __VLS_347: {};
|
|
43
71
|
type __VLS_Slots = {} & {
|
|
44
|
-
menu?: (props: typeof
|
|
72
|
+
menu?: (props: typeof __VLS_108) => any;
|
|
73
|
+
} & {
|
|
74
|
+
profile?: (props: typeof __VLS_124) => any;
|
|
45
75
|
} & {
|
|
46
|
-
default?: (props: typeof
|
|
76
|
+
default?: (props: typeof __VLS_347) => any;
|
|
47
77
|
};
|
|
48
78
|
declare const __VLS_base: 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>;
|
|
49
79
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -7,7 +7,7 @@ type Group<K extends string | number> = Readonly<{
|
|
|
7
7
|
children: Array<Item<K>>;
|
|
8
8
|
}>;
|
|
9
9
|
type Favoritable<K extends string | number> = Item<K> | Group<K>;
|
|
10
|
-
export declare function useFavorite<K extends string | number, T extends Favoritable<K>>(key: string, items: MaybeRefOrGetter<
|
|
10
|
+
export declare function useFavorite<K extends string | number, T extends Favoritable<K>>(key: string, items: MaybeRefOrGetter<ReadonlyArray<T>>): {
|
|
11
11
|
favorite: <I extends Item<K>>(item: I, of: K) => void;
|
|
12
12
|
unfavorite: <I extends Item<K>>(item: I, of?: K) => void;
|
|
13
13
|
toggle: <I extends Item<K>>(item: I, of: K) => void;
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
import { type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
type SidebarMenuItem = Readonly<{
|
|
3
|
+
id: string | number;
|
|
4
|
+
title: string;
|
|
5
|
+
icon?: string;
|
|
6
|
+
route: string;
|
|
7
|
+
}>;
|
|
8
|
+
type SidebarMenuGroup = Readonly<{
|
|
9
|
+
id: string | number;
|
|
10
|
+
title: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
children: Array<SidebarMenuItem>;
|
|
13
|
+
}>;
|
|
14
|
+
export type Sidebar = ReadonlyArray<SidebarMenuItem | SidebarMenuGroup>;
|
|
15
|
+
export declare function useNavigationTabs(navigations: MaybeRefOrGetter<Sidebar>): {
|
|
2
16
|
tabs: import("@vueuse/shared").RemovableRef<Set<string>>;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
closeTab: (tab: string) => void;
|
|
17
|
+
nameOf: (path: string) => string | undefined;
|
|
18
|
+
close: (raw: string) => void;
|
|
19
|
+
active: import("vue").WritableComputedRef<string | undefined, string>;
|
|
7
20
|
};
|
|
21
|
+
export {};
|