@shwfed/config 3.0.4 → 3.0.7
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.json +1 -1
- package/dist/runtime/components/actions/buttons/2026-04-21/com.shwfed.actions.button.navigation/runtime.vue +4 -10
- package/dist/runtime/components/actions/buttons/2026-05-21/com.shwfed.actions.button.http.download/schema.d.ts +1 -1
- package/dist/runtime/components/actions/buttons/2026-05-21/com.shwfed.actions.button.http.download/schema.js +2 -2
- package/dist/runtime/components/actions/buttons/2026-07-13/com.shwfed.actions.button.state.write/runtime.vue +1 -1
- package/dist/runtime/components/actions/buttons/2026-07-13/com.shwfed.actions.button.state.write/schema.d.ts +4 -2
- package/dist/runtime/components/actions/buttons/2026-07-13/com.shwfed.actions.button.state.write/schema.js +1 -1
- package/dist/runtime/components/actions/config.vue +109 -3
- package/dist/runtime/components/actions/utils/steps.d.ts +1 -1
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.d.vue.ts +74 -74
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.vue.d.ts +74 -74
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.d.vue.ts +74 -74
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.vue.d.ts +74 -74
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/schema.d.ts +37 -37
- package/dist/runtime/components/config/history-picker.vue +6 -4
- package/dist/runtime/components/config/index.vue +25 -0
- package/dist/runtime/components/config/prune-dialog.d.vue.ts +14 -0
- package/dist/runtime/components/config/prune-dialog.vue +83 -0
- package/dist/runtime/components/config/prune-dialog.vue.d.ts +14 -0
- package/dist/runtime/components/config/schema.js +2 -2
- package/dist/runtime/components/config/use-editor.d.ts +9 -0
- package/dist/runtime/components/config/use-editor.js +39 -11
- package/dist/runtime/components/config/utils/linkage.d.ts +8 -0
- package/dist/runtime/components/config/utils/linkage.js +7 -2
- package/dist/runtime/components/config/utils/prune.d.ts +68 -0
- package/dist/runtime/components/config/utils/prune.js +92 -0
- package/dist/runtime/components/form/fields/2026-04-22/com.shwfed.form.field.text/config.d.vue.ts +22 -22
- package/dist/runtime/components/form/fields/2026-04-22/com.shwfed.form.field.text/config.vue.d.ts +22 -22
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.d.vue.ts +22 -22
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue.d.ts +22 -22
- package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/config.d.vue.ts +31 -0
- package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/config.vue +191 -0
- package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/config.vue.d.ts +31 -0
- package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/expand.d.ts +49 -0
- package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/expand.js +87 -0
- package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/runtime.d.vue.ts +8 -0
- package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/runtime.vue +72 -0
- package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/runtime.vue.d.ts +8 -0
- package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/schema.d.ts +59 -0
- package/dist/runtime/components/form/fields/2026-07-13/com.shwfed.form.field.dynamic/schema.js +139 -0
- package/dist/runtime/components/form/unit-config.vue +2 -2
- package/dist/runtime/components/form/utils/form-vars.d.ts +5 -15
- package/dist/runtime/components/form/utils/form-vars.js +12 -6
- package/dist/runtime/components/operations/2026-06-11/com.shwfed.operation.http.request.json/schema.js +2 -2
- package/dist/runtime/components/operations/2026-06-15/com.shwfed.operation.http.download/schema.js +2 -2
- package/dist/runtime/components/operations/2026-06-15/com.shwfed.operation.navigation/runtime.js +5 -11
- package/dist/runtime/components/operations/2026-06-15/com.shwfed.operation.navigation/schema.js +1 -1
- package/dist/runtime/components/operations/utils/resolve.d.ts +1 -1
- package/dist/runtime/components/operations/utils/step-vars.d.ts +4 -2
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.select/runtime.vue +1 -21
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.select/schema.d.ts +2 -0
- package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.select/schema.js +16 -1
- package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +2 -2
- package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +2 -2
- package/dist/runtime/components/table/config.vue +2 -2
- package/dist/runtime/components/table/index.vue +15 -0
- package/dist/runtime/components/table/schema.d.ts +74 -74
- package/dist/runtime/components/table/utils/resolve.d.ts +26 -0
- package/dist/runtime/components/table/utils/resolve.js +2 -1
- package/dist/runtime/components/ui/expression-editor/picker-entries.js +1 -1
- package/dist/runtime/components/ui/expression-editor/step-chip-extension.js +1 -1
- package/dist/runtime/components/ui/expression-editor/step-refs.js +1 -1
- package/dist/runtime/share/clipboard.d.ts +15 -9
- package/dist/runtime/share/clipboard.js +22 -10
- package/dist/runtime/share/event-bus.js +2 -2
- package/dist/runtime/share/navigation.d.ts +1 -0
- package/dist/runtime/share/navigation.js +4 -0
- package/dist/runtime/utils/cel-context.d.ts +2 -2
- package/dist/runtime/vendor/cel-js/CLAUDE.md +7 -2
- package/dist/runtime/vendor/cel-js/PROMPT.md +27 -0
- package/dist/runtime/vendor/cel-js/lib/evaluator.js +2 -0
- package/dist/runtime/vendor/cel-js/lib/globals.d.ts +2 -0
- package/dist/runtime/vendor/cel-js/lib/globals.js +10 -0
- package/dist/runtime/vendor/cel-js/lib/map-builtins.d.ts +2 -0
- package/dist/runtime/vendor/cel-js/lib/map-builtins.js +80 -0
- package/dist/runtime/vendor/cel-js/lib/operators.js +2 -12
- package/package.json +1 -1
|
@@ -47,40 +47,6 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
|
|
|
47
47
|
readonly message: string;
|
|
48
48
|
}[]] | undefined;
|
|
49
49
|
}[];
|
|
50
|
-
readonly query?: {
|
|
51
|
-
readonly style?: string | undefined;
|
|
52
|
-
readonly id?: string | undefined;
|
|
53
|
-
readonly initial?: {
|
|
54
|
-
readonly data: string;
|
|
55
|
-
readonly request?: string | undefined;
|
|
56
|
-
} | undefined;
|
|
57
|
-
readonly displayName?: string | undefined;
|
|
58
|
-
readonly kind: "shwfed.component.form";
|
|
59
|
-
readonly fields: readonly any[];
|
|
60
|
-
readonly layouts: readonly {
|
|
61
|
-
readonly name: string;
|
|
62
|
-
readonly layout: {
|
|
63
|
-
readonly style?: string | undefined;
|
|
64
|
-
readonly columns: number;
|
|
65
|
-
readonly gap?: number | undefined;
|
|
66
|
-
readonly rows?: number | undefined;
|
|
67
|
-
readonly placements: {
|
|
68
|
-
readonly [x: string]: {
|
|
69
|
-
readonly area: readonly [readonly [number, number], readonly [number, number]];
|
|
70
|
-
readonly v?: "stretch" | "center" | "end" | "start" | undefined;
|
|
71
|
-
readonly h?: "stretch" | "center" | "end" | "start" | undefined;
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
};
|
|
75
|
-
readonly media?: string | undefined;
|
|
76
|
-
}[];
|
|
77
|
-
readonly readonly?: string | undefined;
|
|
78
|
-
} | undefined;
|
|
79
|
-
readonly dataSource?: {
|
|
80
|
-
readonly data: string;
|
|
81
|
-
readonly total?: string | undefined;
|
|
82
|
-
readonly request?: string | undefined;
|
|
83
|
-
} | undefined;
|
|
84
50
|
readonly actions?: {
|
|
85
51
|
readonly size: "default" | "sm" | "xs";
|
|
86
52
|
readonly style?: string | undefined;
|
|
@@ -148,6 +114,40 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
|
|
|
148
114
|
readonly variant?: "default" | "link" | "destructive" | "primary" | "ghost" | undefined;
|
|
149
115
|
}[];
|
|
150
116
|
} | undefined;
|
|
117
|
+
readonly query?: {
|
|
118
|
+
readonly style?: string | undefined;
|
|
119
|
+
readonly id?: string | undefined;
|
|
120
|
+
readonly initial?: {
|
|
121
|
+
readonly data: string;
|
|
122
|
+
readonly request?: string | undefined;
|
|
123
|
+
} | undefined;
|
|
124
|
+
readonly displayName?: string | undefined;
|
|
125
|
+
readonly kind: "shwfed.component.form";
|
|
126
|
+
readonly fields: readonly any[];
|
|
127
|
+
readonly layouts: readonly {
|
|
128
|
+
readonly name: string;
|
|
129
|
+
readonly layout: {
|
|
130
|
+
readonly style?: string | undefined;
|
|
131
|
+
readonly columns: number;
|
|
132
|
+
readonly gap?: number | undefined;
|
|
133
|
+
readonly rows?: number | undefined;
|
|
134
|
+
readonly placements: {
|
|
135
|
+
readonly [x: string]: {
|
|
136
|
+
readonly area: readonly [readonly [number, number], readonly [number, number]];
|
|
137
|
+
readonly v?: "stretch" | "center" | "end" | "start" | undefined;
|
|
138
|
+
readonly h?: "stretch" | "center" | "end" | "start" | undefined;
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
readonly media?: string | undefined;
|
|
143
|
+
}[];
|
|
144
|
+
readonly readonly?: string | undefined;
|
|
145
|
+
} | undefined;
|
|
146
|
+
readonly dataSource?: {
|
|
147
|
+
readonly data: string;
|
|
148
|
+
readonly total?: string | undefined;
|
|
149
|
+
readonly request?: string | undefined;
|
|
150
|
+
} | undefined;
|
|
151
151
|
readonly bottomActions?: {
|
|
152
152
|
readonly size: "default" | "sm" | "xs";
|
|
153
153
|
readonly style?: string | undefined;
|
|
@@ -241,6 +241,9 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
|
|
|
241
241
|
readonly expanded?: boolean | {
|
|
242
242
|
readonly [x: string]: boolean;
|
|
243
243
|
} | undefined;
|
|
244
|
+
readonly rowSelection?: {
|
|
245
|
+
readonly [x: string]: boolean;
|
|
246
|
+
} | undefined;
|
|
244
247
|
readonly columnVisibility?: {
|
|
245
248
|
readonly [x: string]: boolean;
|
|
246
249
|
} | undefined;
|
|
@@ -261,9 +264,6 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
|
|
|
261
264
|
readonly columnSizing?: {
|
|
262
265
|
readonly [x: string]: number;
|
|
263
266
|
} | undefined;
|
|
264
|
-
readonly rowSelection?: {
|
|
265
|
-
readonly [x: string]: boolean;
|
|
266
|
-
} | undefined;
|
|
267
267
|
} | undefined;
|
|
268
268
|
}, Schema.Struct<{
|
|
269
269
|
kind: Schema.tag<"shwfed.component.table">;
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from "../ui/command";
|
|
15
15
|
import {
|
|
16
16
|
AlertDialog,
|
|
17
|
-
AlertDialogAction,
|
|
18
17
|
AlertDialogCancel,
|
|
19
18
|
AlertDialogContent,
|
|
20
19
|
AlertDialogDescription,
|
|
@@ -140,14 +139,17 @@ function onCancel() {
|
|
|
140
139
|
{{ t("history-confirm-body") }}
|
|
141
140
|
</p>
|
|
142
141
|
<AlertDialogFooter>
|
|
143
|
-
<AlertDialogCancel
|
|
142
|
+
<AlertDialogCancel>
|
|
144
143
|
<Icon icon="fluent:dismiss-20-regular" />
|
|
145
144
|
{{ t("history-cancel") }}
|
|
146
145
|
</AlertDialogCancel>
|
|
147
|
-
<
|
|
146
|
+
<Button
|
|
147
|
+
variant="primary"
|
|
148
|
+
@click="onConfirm"
|
|
149
|
+
>
|
|
148
150
|
<Icon icon="fluent:checkmark-20-regular" />
|
|
149
151
|
{{ t("history-confirm") }}
|
|
150
|
-
</
|
|
152
|
+
</Button>
|
|
151
153
|
</AlertDialogFooter>
|
|
152
154
|
</AlertDialogContent>
|
|
153
155
|
</AlertDialog>
|
|
@@ -13,6 +13,7 @@ import ShwfedModal from "../modal.vue";
|
|
|
13
13
|
import EditorBody from "./config.vue";
|
|
14
14
|
import EditorBreadcrumb from "./breadcrumb.vue";
|
|
15
15
|
import EditorFooter from "./footer.vue";
|
|
16
|
+
import PruneDialog from "./prune-dialog.vue";
|
|
16
17
|
import { provideEventTarget } from "../../share/event-bus";
|
|
17
18
|
import { operationHandlers } from "../operations/utils/resolve";
|
|
18
19
|
import * as alertOperation from "../operations/2026-06-09/com.shwfed.operation.alert/schema";
|
|
@@ -95,12 +96,31 @@ provideEventTarget(PAGE_TARGET_ID, {
|
|
|
95
96
|
)
|
|
96
97
|
});
|
|
97
98
|
const editorOpen = ref(false);
|
|
99
|
+
const pendingPrune = ref(null);
|
|
100
|
+
let settlePrune = null;
|
|
101
|
+
function confirmPrune(items) {
|
|
102
|
+
resolvePrune(false);
|
|
103
|
+
pendingPrune.value = items;
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
settlePrune = resolve;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function resolvePrune(proceed) {
|
|
109
|
+
pendingPrune.value = null;
|
|
110
|
+
const settle = settlePrune;
|
|
111
|
+
settlePrune = null;
|
|
112
|
+
settle?.(proceed);
|
|
113
|
+
}
|
|
114
|
+
onBeforeUnmount(() => {
|
|
115
|
+
if (settlePrune) resolvePrune(false);
|
|
116
|
+
});
|
|
98
117
|
const editorState = useConfigEditor(config, {
|
|
99
118
|
configure: mergedConfigure,
|
|
100
119
|
// Presence is decided once at setup (a host toggling the prop's existence
|
|
101
120
|
// at runtime is not supported); the wrapper reads the current prop per
|
|
102
121
|
// call so a rebound handler identity stays honored.
|
|
103
122
|
save: props.save ? (cfg) => props.save(cfg) : void 0,
|
|
123
|
+
confirmPrune,
|
|
104
124
|
onClose: () => {
|
|
105
125
|
editorOpen.value = false;
|
|
106
126
|
}
|
|
@@ -243,6 +263,11 @@ const isMac = computed(
|
|
|
243
263
|
/>
|
|
244
264
|
</div>
|
|
245
265
|
</div>
|
|
266
|
+
<PruneDialog
|
|
267
|
+
:items="pendingPrune"
|
|
268
|
+
@confirm="resolvePrune(true)"
|
|
269
|
+
@cancel="resolvePrune(false)"
|
|
270
|
+
/>
|
|
246
271
|
<template #footer>
|
|
247
272
|
<EditorFooter
|
|
248
273
|
:state="editorState"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type PruneSummary } from './utils/prune.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Non-null while the gate is open; one entry per item about to be dropped. */
|
|
4
|
+
items: ReadonlyArray<PruneSummary> | null;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
7
|
+
cancel: () => any;
|
|
8
|
+
confirm: () => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
+
onCancel?: (() => any) | undefined;
|
|
11
|
+
onConfirm?: (() => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { Icon } from "@iconify/vue";
|
|
4
|
+
import {
|
|
5
|
+
AlertDialog,
|
|
6
|
+
AlertDialogCancel,
|
|
7
|
+
AlertDialogContent,
|
|
8
|
+
AlertDialogDescription,
|
|
9
|
+
AlertDialogFooter,
|
|
10
|
+
AlertDialogHeader,
|
|
11
|
+
AlertDialogTitle
|
|
12
|
+
} from "../ui/alert-dialog";
|
|
13
|
+
import { Button } from "../ui/button";
|
|
14
|
+
import { pruneKindNoun } from "./utils/prune";
|
|
15
|
+
defineOptions({ name: "ShwfedConfigPruneDialog" });
|
|
16
|
+
const props = defineProps({
|
|
17
|
+
items: { type: [Array, null], required: true }
|
|
18
|
+
});
|
|
19
|
+
const emit = defineEmits(["confirm", "cancel"]);
|
|
20
|
+
const open = computed(() => props.items !== null);
|
|
21
|
+
function onOpenChange(next) {
|
|
22
|
+
if (!next) emit("cancel");
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<AlertDialog
|
|
28
|
+
:open="open"
|
|
29
|
+
@update:open="onOpenChange"
|
|
30
|
+
>
|
|
31
|
+
<AlertDialogContent class="sm:px-12 sm:py-8 flex flex-col gap-8">
|
|
32
|
+
<AlertDialogHeader class="sr-only">
|
|
33
|
+
<AlertDialogTitle>清理未使用的内容</AlertDialogTitle>
|
|
34
|
+
<AlertDialogDescription>Confirmation dialog</AlertDialogDescription>
|
|
35
|
+
</AlertDialogHeader>
|
|
36
|
+
<Icon
|
|
37
|
+
icon="fluent:broom-20-filled"
|
|
38
|
+
class="text-6xl mx-auto text-yellow-500"
|
|
39
|
+
/>
|
|
40
|
+
<div class="flex flex-col gap-3">
|
|
41
|
+
<p class="text-sm text-zinc-900">
|
|
42
|
+
以下内容未放入任何布局,不会在页面上显示。保存时将一并删除:
|
|
43
|
+
</p>
|
|
44
|
+
<ul class="max-h-60 overflow-y-auto flex flex-col gap-1 rounded border border-zinc-200 bg-zinc-50 p-3">
|
|
45
|
+
<li
|
|
46
|
+
v-for="(item, i) in props.items ?? []"
|
|
47
|
+
:key="i"
|
|
48
|
+
class="flex items-baseline gap-2 text-sm text-zinc-700"
|
|
49
|
+
>
|
|
50
|
+
<span class="shrink-0 rounded bg-zinc-200 px-1.5 py-0.5 text-xs text-zinc-600">
|
|
51
|
+
{{ pruneKindNoun(item.kind) }}
|
|
52
|
+
</span>
|
|
53
|
+
<span class="break-all">{{ item.label }}</span>
|
|
54
|
+
</li>
|
|
55
|
+
</ul>
|
|
56
|
+
<p class="text-xs text-zinc-500">
|
|
57
|
+
若仍需保留,请点击取消,并将其放入布局。
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
<AlertDialogFooter>
|
|
61
|
+
<!-- Closes itself, which `onOpenChange` reads as the 取消. -->
|
|
62
|
+
<AlertDialogCancel>
|
|
63
|
+
<Icon icon="fluent:dismiss-20-regular" />
|
|
64
|
+
取消
|
|
65
|
+
</AlertDialogCancel>
|
|
66
|
+
<!-- A plain Button, NOT an AlertDialogAction: reka's action IS a
|
|
67
|
+
DialogClose, and its built-in close handler — bound in reka's own
|
|
68
|
+
template — runs BEFORE a `@click` we hang on it, which arrives as a
|
|
69
|
+
fallthrough attr and merges after. The close would reach
|
|
70
|
+
`onOpenChange` first and cancel the very save this button
|
|
71
|
+
authorizes. The dialog shuts anyway: the host drops `items` the
|
|
72
|
+
moment it takes the verdict. -->
|
|
73
|
+
<Button
|
|
74
|
+
variant="primary"
|
|
75
|
+
@click="emit('confirm')"
|
|
76
|
+
>
|
|
77
|
+
<Icon icon="fluent:checkmark-20-regular" />
|
|
78
|
+
删除并保存
|
|
79
|
+
</Button>
|
|
80
|
+
</AlertDialogFooter>
|
|
81
|
+
</AlertDialogContent>
|
|
82
|
+
</AlertDialog>
|
|
83
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type PruneSummary } from './utils/prune.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Non-null while the gate is open; one entry per item about to be dropped. */
|
|
4
|
+
items: ReadonlyArray<PruneSummary> | null;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
7
|
+
cancel: () => any;
|
|
8
|
+
confirm: () => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
+
onCancel?: (() => any) | undefined;
|
|
11
|
+
onConfirm?: (() => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
@@ -33,7 +33,7 @@ export const metadata = {
|
|
|
33
33
|
icon: httpRequestOperation.metadata.icon
|
|
34
34
|
},
|
|
35
35
|
// Navigation as an op (replaces the standalone navigation *button*): its URL
|
|
36
|
-
// expression can reference `steps["<id>"]`, so a chain can navigate using an
|
|
36
|
+
// expression can reference `steps[?"<id>"]`, so a chain can navigate using an
|
|
37
37
|
// earlier request's response. The page hosts it for the same reason as the
|
|
38
38
|
// request op — navigation acts on nothing instance-specific.
|
|
39
39
|
{
|
|
@@ -43,7 +43,7 @@ export const metadata = {
|
|
|
43
43
|
icon: navigationOperation.metadata.icon
|
|
44
44
|
},
|
|
45
45
|
// Download as an op (replaces the standalone download *button*): its request
|
|
46
|
-
// expression can reference `steps["<id>"]`, so the old two-step「先取凭据再下载」
|
|
46
|
+
// expression can reference `steps[?"<id>"]`, so the old two-step「先取凭据再下载」
|
|
47
47
|
// becomes a 发送请求 row followed by this download row. The page hosts it for
|
|
48
48
|
// the same reason as the request op — the download acts on nothing
|
|
49
49
|
// instance-specific.
|
|
@@ -4,6 +4,7 @@ import { type LayoutSetValue, type PlacementValue, type SlotValue } from '../../
|
|
|
4
4
|
import type { BreadcrumbCrumb, BreadcrumbParentSuffix } from './breadcrumb-extension.js';
|
|
5
5
|
import { type BlockValue, type PageConfigValue } from './schema.js';
|
|
6
6
|
import { type BlockEntry } from './utils/resolve.js';
|
|
7
|
+
import { type PruneSummary } from './utils/prune.js';
|
|
7
8
|
export type PickerEntry = {
|
|
8
9
|
type: string;
|
|
9
10
|
compatibilityDate: string;
|
|
@@ -133,6 +134,14 @@ export interface UseConfigEditorOptions {
|
|
|
133
134
|
* the editor guarantees the session is never lost to a failed save.
|
|
134
135
|
*/
|
|
135
136
|
save?: (config: PageConfigValue) => void | Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Consent gate for the save-time prune of unplaced items (utils/prune.ts).
|
|
139
|
+
* Called only when 确认 would actually drop something, with a breadcrumb per
|
|
140
|
+
* removal; resolving `false` aborts the save with the draft untouched. Hosts
|
|
141
|
+
* wire this to a confirmation dialog — `ShwfedConfig` does. Left unwired, the
|
|
142
|
+
* prune is silent.
|
|
143
|
+
*/
|
|
144
|
+
confirmPrune?: (items: ReadonlyArray<PruneSummary>) => boolean | Promise<boolean>;
|
|
136
145
|
/**
|
|
137
146
|
* Called after a successful 确认 commits the draft to the outer model.
|
|
138
147
|
* Hosts wire this to close the modal; legacy mode emits `close`.
|
|
@@ -7,6 +7,12 @@ import { PageConfig } from "./schema.js";
|
|
|
7
7
|
import { BLOCKS, findBlock } from "./utils/resolve.js";
|
|
8
8
|
import { formatValidationError } from "./utils/validation-error.js";
|
|
9
9
|
import { findDanglingLinkages, formatDanglingLinkages } from "./utils/linkage.js";
|
|
10
|
+
import {
|
|
11
|
+
findPruneConflicts,
|
|
12
|
+
formatPruneConflicts,
|
|
13
|
+
pruneUnplaced,
|
|
14
|
+
summarizePrune
|
|
15
|
+
} from "./utils/prune.js";
|
|
10
16
|
import { createExpressionValidityRegistry, provideExpressionValidity } from "../ui/expression-editor/index.js";
|
|
11
17
|
function deepClone(value) {
|
|
12
18
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -199,7 +205,7 @@ export function useSlotClipboard(target) {
|
|
|
199
205
|
if (p) placements[id] = p;
|
|
200
206
|
}
|
|
201
207
|
}
|
|
202
|
-
if (!await writeClip("block", items, placements)) {
|
|
208
|
+
if (!await writeClip("block", { items, placements })) {
|
|
203
209
|
toast.error("\u590D\u5236\u5931\u8D25");
|
|
204
210
|
return;
|
|
205
211
|
}
|
|
@@ -208,7 +214,7 @@ export function useSlotClipboard(target) {
|
|
|
208
214
|
async function paste(anchor) {
|
|
209
215
|
const payload = await readClip();
|
|
210
216
|
if (!payload || payload.surface !== "block") return;
|
|
211
|
-
const { items, placements } = reidFragment(payload
|
|
217
|
+
const { items, placements } = reidFragment(payload);
|
|
212
218
|
const pasted = items;
|
|
213
219
|
if (pasted.length === 0) return;
|
|
214
220
|
if (placements && Object.keys(placements).length > 0) {
|
|
@@ -396,20 +402,42 @@ export function useConfigEditor(model, options = {}) {
|
|
|
396
402
|
validationError.value = "\u5B58\u5728\u65E0\u6548\u7684\u8DE8\u5C42\u4F5C\u7528\u57DF\u5F15\u7528\uFF0C\u8BF7\u4FEE\u6B63\u6807\u7EA2\u7684\u8868\u8FBE\u5F0F\u540E\u518D\u4FDD\u5B58";
|
|
397
403
|
return false;
|
|
398
404
|
}
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
405
|
+
const { config: pruned, removed } = pruneUnplaced(decoded);
|
|
406
|
+
const draftBlockLabelAt = (i) => {
|
|
407
|
+
const b = topSlotEditor.blocks.value[i];
|
|
408
|
+
return b ? topSlotEditor.getBlockLabel(b) : void 0;
|
|
409
|
+
};
|
|
410
|
+
const prunedBlockLabelAt = (i) => {
|
|
411
|
+
const b = pruned.slot.blocks[i];
|
|
412
|
+
return b ? topSlotEditor.getBlockLabel(b) : void 0;
|
|
413
|
+
};
|
|
414
|
+
const conflicts = findPruneConflicts(pruned, removed);
|
|
415
|
+
if (conflicts.length > 0) {
|
|
416
|
+
validationError.value = formatPruneConflicts(conflicts, {
|
|
417
|
+
prunedRoot: pruned,
|
|
418
|
+
originalRoot: decoded,
|
|
419
|
+
prunedBlockLabelAt,
|
|
420
|
+
originalBlockLabelAt: draftBlockLabelAt
|
|
404
421
|
});
|
|
405
422
|
return false;
|
|
406
423
|
}
|
|
424
|
+
const dangling = findDanglingLinkages(pruned);
|
|
425
|
+
if (dangling.length > 0) {
|
|
426
|
+
validationError.value = formatDanglingLinkages(dangling, pruned, prunedBlockLabelAt);
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
407
429
|
validationError.value = null;
|
|
430
|
+
if (removed.length > 0 && options.confirmPrune) {
|
|
431
|
+
const proceed = await options.confirmPrune(
|
|
432
|
+
summarizePrune(removed, decoded, draftBlockLabelAt)
|
|
433
|
+
);
|
|
434
|
+
if (!proceed) return false;
|
|
435
|
+
}
|
|
408
436
|
if (options.save) {
|
|
409
437
|
saving.value = true;
|
|
410
438
|
saveError.value = null;
|
|
411
439
|
try {
|
|
412
|
-
await options.save(deepClone(
|
|
440
|
+
await options.save(deepClone(pruned));
|
|
413
441
|
} catch (err) {
|
|
414
442
|
saveError.value = `\u4FDD\u5B58\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`;
|
|
415
443
|
return false;
|
|
@@ -417,9 +445,9 @@ export function useConfigEditor(model, options = {}) {
|
|
|
417
445
|
saving.value = false;
|
|
418
446
|
}
|
|
419
447
|
}
|
|
420
|
-
model.value =
|
|
421
|
-
baseline.value = deepClone(
|
|
422
|
-
draft.value = deepClone(
|
|
448
|
+
model.value = pruned;
|
|
449
|
+
baseline.value = deepClone(pruned);
|
|
450
|
+
draft.value = deepClone(pruned);
|
|
423
451
|
stack.value = [{ kind: "layout" }];
|
|
424
452
|
topSlotEditor.selectedBlockIds.value = [];
|
|
425
453
|
topSlotEditor.activeLayoutIndex.value = 0;
|
|
@@ -11,6 +11,13 @@ export type DanglingLinkage = Readonly<{
|
|
|
11
11
|
*/
|
|
12
12
|
path: ReadonlyArray<PropertyKey>;
|
|
13
13
|
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Every uuid `id` defined under `node`. Exported so the save-time prune
|
|
16
|
+
* (utils/prune.ts) asks what a to-be-removed subtree *defines* with the very
|
|
17
|
+
* same rule this gate uses to decide what is *defined* — the two must agree, or
|
|
18
|
+
* a prune would strand a linkage this walker then fails to flag.
|
|
19
|
+
*/
|
|
20
|
+
export declare function definedIds(node: unknown): Set<string>;
|
|
14
21
|
/**
|
|
15
22
|
* Find every linkage in `config` whose target uuid is not defined anywhere in
|
|
16
23
|
* the tree. `PAGE_TARGET_ID` (the singleton page target sentinel) is always
|
|
@@ -19,6 +26,7 @@ export type DanglingLinkage = Readonly<{
|
|
|
19
26
|
* the caller can render a field-level breadcrumb (see `describePath`).
|
|
20
27
|
*/
|
|
21
28
|
export declare function findDanglingLinkages(config: unknown): DanglingLinkage[];
|
|
29
|
+
export declare const LINKAGE_KIND_LABEL: Record<LinkageKind, string>;
|
|
22
30
|
/**
|
|
23
31
|
* One-line save-footer message for a non-empty dangling list. Renders the
|
|
24
32
|
* breadcrumb to the first offending reference (`「表单 › 确定 › value」`) via the
|
|
@@ -17,6 +17,11 @@ function collectDefinedIds(node, into) {
|
|
|
17
17
|
collectDefinedIds(v, into);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
export function definedIds(node) {
|
|
21
|
+
const out = /* @__PURE__ */ new Set();
|
|
22
|
+
collectDefinedIds(node, out);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
20
25
|
function collectRefs(node, path, into) {
|
|
21
26
|
if (Array.isArray(node)) {
|
|
22
27
|
node.forEach((v, i) => collectRefs(v, [...path, i], into));
|
|
@@ -41,7 +46,7 @@ export function findDanglingLinkages(config) {
|
|
|
41
46
|
collectRefs(config, [], refs);
|
|
42
47
|
return refs.filter((r) => r.id !== PAGE_TARGET_ID && !defined.has(r.id));
|
|
43
48
|
}
|
|
44
|
-
const
|
|
49
|
+
export const LINKAGE_KIND_LABEL = {
|
|
45
50
|
operation: "\u89E6\u53D1\u64CD\u4F5C",
|
|
46
51
|
step: "\u6B65\u9AA4\u5F15\u7528",
|
|
47
52
|
scope: "\u8DE8\u5C42\u4F5C\u7528\u57DF\u5F15\u7528"
|
|
@@ -49,7 +54,7 @@ const KIND_LABEL = {
|
|
|
49
54
|
export function formatDanglingLinkages(dangling, rootData, blockLabelAt) {
|
|
50
55
|
const first = dangling[0];
|
|
51
56
|
if (!first) return "";
|
|
52
|
-
const kind =
|
|
57
|
+
const kind = LINKAGE_KIND_LABEL[first.kind];
|
|
53
58
|
const segs = describePath(first.path, rootData, blockLabelAt);
|
|
54
59
|
const where = segs.length > 0 ? `\u300C${segs.join(" \u203A ")}\u300D\u7684${kind}` : kind;
|
|
55
60
|
const suffix = dangling.length > 1 ? `\uFF08\u8FD8\u6709 ${dangling.length - 1} \u4E2A\uFF09` : "";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type DanglingLinkage } from './linkage.js';
|
|
2
|
+
/** Which pool an item came out of — drives the noun in user-facing copy. */
|
|
3
|
+
export type PruneKind = 'block' | 'field';
|
|
4
|
+
export declare function pruneKindNoun(kind: PruneKind): string;
|
|
5
|
+
export type UnplacedItem = Readonly<{
|
|
6
|
+
kind: PruneKind;
|
|
7
|
+
id: string;
|
|
8
|
+
/**
|
|
9
|
+
* Property path from the ORIGINAL config root to the removed item
|
|
10
|
+
* (`['slot','blocks',2]`). Fed to `describePath` against that same root.
|
|
11
|
+
*/
|
|
12
|
+
path: ReadonlyArray<PropertyKey>;
|
|
13
|
+
/**
|
|
14
|
+
* Every uuid defined anywhere in this item's subtree — the item's own id plus
|
|
15
|
+
* its trigger rows, nested blocks, nested fields, … Dropping the item
|
|
16
|
+
* un-defines all of them at once, so this is the set a surviving reference
|
|
17
|
+
* must not point into.
|
|
18
|
+
*/
|
|
19
|
+
ids: ReadonlySet<string>;
|
|
20
|
+
}>;
|
|
21
|
+
/** A pruned item's label, pre-rendered for the confirmation dialog. */
|
|
22
|
+
export type PruneSummary = Readonly<{
|
|
23
|
+
kind: PruneKind;
|
|
24
|
+
/** Breadcrumb to the item in the pre-prune config (`标签页 › 表格`). */
|
|
25
|
+
label: string;
|
|
26
|
+
}>;
|
|
27
|
+
export type PruneConflict = Readonly<{
|
|
28
|
+
/** The surviving reference left pointing at nothing. */
|
|
29
|
+
ref: DanglingLinkage;
|
|
30
|
+
/** The item whose removal stranded it. */
|
|
31
|
+
owner: UnplacedItem;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Drop every item that no layout in its own pool places, at every depth.
|
|
35
|
+
* Returns the rewritten config plus the removals, outermost-first.
|
|
36
|
+
*/
|
|
37
|
+
export declare function pruneUnplaced<T>(config: T): {
|
|
38
|
+
config: T;
|
|
39
|
+
removed: UnplacedItem[];
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* References held by SURVIVORS that point into a removed subtree. Non-empty
|
|
43
|
+
* means the prune would strand a linkage, and the save must be refused rather
|
|
44
|
+
* than quietly persisting a config whose trigger targets nothing.
|
|
45
|
+
*
|
|
46
|
+
* Runs the existing dangling-linkage walker over the PRUNED tree and keeps only
|
|
47
|
+
* the danglers the prune itself caused — a linkage that was already dangling
|
|
48
|
+
* before the prune is not our problem and stays with `findDanglingLinkages`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function findPruneConflicts(pruned: unknown, removed: ReadonlyArray<UnplacedItem>): PruneConflict[];
|
|
51
|
+
/**
|
|
52
|
+
* Pre-render each removal as a breadcrumb for the confirmation dialog.
|
|
53
|
+
* `root` must be the pre-prune config the paths index into.
|
|
54
|
+
*/
|
|
55
|
+
export declare function summarizePrune(removed: ReadonlyArray<UnplacedItem>, root: unknown, blockLabelAt: (index: number) => string | undefined): PruneSummary[];
|
|
56
|
+
/**
|
|
57
|
+
* Save-footer message for a non-empty conflict list. Names both ends — the item
|
|
58
|
+
* about to be dropped and the surviving reference that still needs it — because
|
|
59
|
+
* the raw dangling message ("引用了已失效的资源") would point at the reference
|
|
60
|
+
* and leave the designer hunting for a block that looks perfectly fine sitting
|
|
61
|
+
* in the unplaced tray.
|
|
62
|
+
*/
|
|
63
|
+
export declare function formatPruneConflicts(conflicts: ReadonlyArray<PruneConflict>, ctx: {
|
|
64
|
+
prunedRoot: unknown;
|
|
65
|
+
originalRoot: unknown;
|
|
66
|
+
prunedBlockLabelAt: (index: number) => string | undefined;
|
|
67
|
+
originalBlockLabelAt: (index: number) => string | undefined;
|
|
68
|
+
}): string;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { definedIds, findDanglingLinkages, LINKAGE_KIND_LABEL } from "./linkage.js";
|
|
2
|
+
import { describePath } from "./validation-error.js";
|
|
3
|
+
const KIND_NOUN = {
|
|
4
|
+
block: "\u5757",
|
|
5
|
+
field: "\u5B57\u6BB5"
|
|
6
|
+
};
|
|
7
|
+
export function pruneKindNoun(kind) {
|
|
8
|
+
return KIND_NOUN[kind];
|
|
9
|
+
}
|
|
10
|
+
function isObject(v) {
|
|
11
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
12
|
+
}
|
|
13
|
+
const POOLS = [
|
|
14
|
+
{ key: "blocks", kind: "block" },
|
|
15
|
+
{ key: "fields", kind: "field" }
|
|
16
|
+
];
|
|
17
|
+
function poolOf(node) {
|
|
18
|
+
if (!Array.isArray(node.layouts)) return void 0;
|
|
19
|
+
return POOLS.find((p) => Array.isArray(node[p.key]));
|
|
20
|
+
}
|
|
21
|
+
function placedIds(layouts) {
|
|
22
|
+
const out = /* @__PURE__ */ new Set();
|
|
23
|
+
for (const set of layouts) {
|
|
24
|
+
if (!isObject(set) || !isObject(set.layout)) continue;
|
|
25
|
+
const placements = set.layout.placements;
|
|
26
|
+
if (isObject(placements)) {
|
|
27
|
+
for (const id of Object.keys(placements)) out.add(id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
function walk(node, path, removed) {
|
|
33
|
+
if (Array.isArray(node)) return node.map((v, i) => walk(v, [...path, i], removed));
|
|
34
|
+
if (!isObject(node)) return node;
|
|
35
|
+
const pool = poolOf(node);
|
|
36
|
+
const out = {};
|
|
37
|
+
for (const [key, value] of Object.entries(node)) {
|
|
38
|
+
if (pool && key === pool.key) {
|
|
39
|
+
const placed = placedIds(node.layouts);
|
|
40
|
+
const kept = [];
|
|
41
|
+
for (const [i, item] of value.entries()) {
|
|
42
|
+
const id = isObject(item) && typeof item.id === "string" ? item.id : void 0;
|
|
43
|
+
if (id !== void 0 && !placed.has(id)) {
|
|
44
|
+
removed.push({ kind: pool.kind, id, path: [...path, key, i], ids: definedIds(item) });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
kept.push(walk(item, [...path, key, i], removed));
|
|
48
|
+
}
|
|
49
|
+
out[key] = kept;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
out[key] = walk(value, [...path, key], removed);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
export function pruneUnplaced(config) {
|
|
57
|
+
const removed = [];
|
|
58
|
+
const next = walk(config, [], removed);
|
|
59
|
+
return { config: next, removed };
|
|
60
|
+
}
|
|
61
|
+
export function findPruneConflicts(pruned, removed) {
|
|
62
|
+
if (removed.length === 0) return [];
|
|
63
|
+
const owner = /* @__PURE__ */ new Map();
|
|
64
|
+
for (const item of removed) {
|
|
65
|
+
for (const id of item.ids) {
|
|
66
|
+
if (!owner.has(id)) owner.set(id, item);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const ref of findDanglingLinkages(pruned)) {
|
|
71
|
+
const o = owner.get(ref.id);
|
|
72
|
+
if (o) out.push({ ref, owner: o });
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
export function summarizePrune(removed, root, blockLabelAt) {
|
|
77
|
+
return removed.map((item) => ({
|
|
78
|
+
kind: item.kind,
|
|
79
|
+
label: describePath(item.path, root, blockLabelAt).join(" \u203A ") || pruneKindNoun(item.kind)
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
export function formatPruneConflicts(conflicts, ctx) {
|
|
83
|
+
const first = conflicts[0];
|
|
84
|
+
if (!first) return "";
|
|
85
|
+
const ownerSegs = describePath(first.owner.path, ctx.originalRoot, ctx.originalBlockLabelAt);
|
|
86
|
+
const owner = ownerSegs.length > 0 ? ownerSegs.join(" \u203A ") : pruneKindNoun(first.owner.kind);
|
|
87
|
+
const refSegs = describePath(first.ref.path, ctx.prunedRoot, ctx.prunedBlockLabelAt);
|
|
88
|
+
const kind = LINKAGE_KIND_LABEL[first.ref.kind];
|
|
89
|
+
const where = refSegs.length > 0 ? `\u300C${refSegs.join(" \u203A ")}\u300D\u7684${kind}` : kind;
|
|
90
|
+
const suffix = conflicts.length > 1 ? `\uFF08\u8FD8\u6709 ${conflicts.length - 1} \u4E2A\uFF09` : "";
|
|
91
|
+
return `${pruneKindNoun(first.owner.kind)}\u300C${owner}\u300D\u672A\u653E\u5165\u4EFB\u4F55\u5E03\u5C40\uFF0C\u4FDD\u5B58\u65F6\u4F1A\u88AB\u6E05\u7406\uFF0C\u4F46${where}\u4ECD\u5728\u5F15\u7528\u5B83\u3002\u8BF7\u5148\u5C06\u5B83\u653E\u5165\u5E03\u5C40\uFF0C\u6216\u79FB\u9664\u8BE5\u5F15\u7528${suffix}`;
|
|
92
|
+
}
|