@arronqzy/react-view 1.0.33 → 1.0.36
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/styles.css +223 -0
- package/package.json +7 -7
- package/readme.md +70 -82
- package/src/core/instructions.tsx +1 -1
- package/src/core/template.tsx +1 -1
- package/src/index.ts +2 -0
- package/src/panel/I18nRoot.tsx +2 -1
- package/src/panel/ReactViewOnlinePreview.tsx +4 -1
- package/src/panel/ReactViewPanel.tsx +64 -7
- package/src/panel/components/ConfigHintIcon.tsx +15 -2
- package/src/panel/components/PanelMenubar.tsx +6 -0
- package/src/panel/components/config/PanelConfigAudioSection.tsx +6 -4
- package/src/panel/components/config/PanelConfigChartSection.tsx +12 -8
- package/src/panel/components/config/PanelConfigScene3dSection.tsx +16 -8
- package/src/panel/components/config/PanelConfigStyleSections.tsx +7 -4
- package/src/panel/components/config/PanelConfigTextSection.tsx +7 -4
- package/src/panel/components/config/PanelConfigVideoSection.tsx +7 -4
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.tsx +4 -8
- package/src/panel/components/scope-config/ScopeTemplateUsageHint.tsx +1 -0
- package/src/panel/components/table/ConditionEditor.tsx +7 -2
- package/src/panel/components/table/PanelConfigTableSection.tsx +11 -5
- package/src/panel/hooks/usePanelElements.ts +20 -3
- package/src/panel/library/event-subscription.ts +20 -0
- package/src/panel/utils/panelElementDefaults.ts +47 -1
- package/src/panel/utils/scope-template.ts +20 -8
|
@@ -305,7 +305,12 @@ export function PanelConfigScene3dSection({
|
|
|
305
305
|
return (
|
|
306
306
|
<div className="space-y-3">
|
|
307
307
|
<div className="space-y-1.5">
|
|
308
|
-
<div className="text-[11px] font-medium">
|
|
308
|
+
<div className="flex items-center gap-1 text-[11px] font-medium">
|
|
309
|
+
{t("panel.config.groupScene3dPurpose")}
|
|
310
|
+
<ConfigHintIcon label={t("panel.config.groupScene3dPurpose")}>
|
|
311
|
+
{t("panel.config.scene3dPurposeHint")}
|
|
312
|
+
</ConfigHintIcon>
|
|
313
|
+
</div>
|
|
309
314
|
<Select
|
|
310
315
|
value={config.presentation.purpose}
|
|
311
316
|
onValueChange={(value) => {
|
|
@@ -322,9 +327,6 @@ export function PanelConfigScene3dSection({
|
|
|
322
327
|
<SelectItem value="sandbox">{t("panel.config.scene3dPurposeSandbox")}</SelectItem>
|
|
323
328
|
</SelectContent>
|
|
324
329
|
</Select>
|
|
325
|
-
<div className="text-[10px] text-muted-foreground">
|
|
326
|
-
{t("panel.config.scene3dPurposeHint")}
|
|
327
|
-
</div>
|
|
328
330
|
<label className="block space-y-0.5 text-[10px]">
|
|
329
331
|
<span>{t("panel.config.scene3dEnvironment")}</span>
|
|
330
332
|
<Select
|
|
@@ -403,8 +405,11 @@ export function PanelConfigScene3dSection({
|
|
|
403
405
|
/>
|
|
404
406
|
</div>
|
|
405
407
|
<label className="block space-y-1" data-config-field="scene3d.modelUrl">
|
|
406
|
-
<div className="text-[10px] text-muted-foreground">
|
|
408
|
+
<div className="flex items-center gap-1 text-[10px] text-muted-foreground">
|
|
407
409
|
{t("panel.config.scene3dModelUrl")}
|
|
410
|
+
<ConfigHintIcon label={t("panel.config.scene3dModelUrl")}>
|
|
411
|
+
{t("panel.config.urlScopeHint")}
|
|
412
|
+
</ConfigHintIcon>
|
|
408
413
|
</div>
|
|
409
414
|
<div className="flex gap-1.5">
|
|
410
415
|
<Input
|
|
@@ -424,7 +429,6 @@ export function PanelConfigScene3dSection({
|
|
|
424
429
|
{t("common.add")}
|
|
425
430
|
</Button>
|
|
426
431
|
</div>
|
|
427
|
-
<p className="text-[10px] text-muted-foreground">{t("panel.config.urlScopeHint")}</p>
|
|
428
432
|
</label>
|
|
429
433
|
|
|
430
434
|
{config.models.length === 0 ? (
|
|
@@ -688,7 +692,12 @@ export function PanelConfigScene3dSection({
|
|
|
688
692
|
)}
|
|
689
693
|
|
|
690
694
|
<div className="space-y-2">
|
|
691
|
-
<div className="text-[11px] font-medium">
|
|
695
|
+
<div className="flex items-center gap-1 text-[11px] font-medium">
|
|
696
|
+
{t("panel.config.groupScene3dCamera")}
|
|
697
|
+
<ConfigHintIcon label={t("panel.config.groupScene3dCamera")}>
|
|
698
|
+
{t("panel.config.scene3dCameraEditDesc")}
|
|
699
|
+
</ConfigHintIcon>
|
|
700
|
+
</div>
|
|
692
701
|
<div className="grid grid-cols-3 gap-1.5">
|
|
693
702
|
{(["x", "y", "z"] as const).map((axis, index) => (
|
|
694
703
|
<label key={`pos-${axis}`} className="space-y-0.5 text-[10px]">
|
|
@@ -734,7 +743,6 @@ export function PanelConfigScene3dSection({
|
|
|
734
743
|
}
|
|
735
744
|
/>
|
|
736
745
|
</label>
|
|
737
|
-
<div className="text-[10px] text-muted-foreground">{t("panel.config.scene3dCameraEditDesc")}</div>
|
|
738
746
|
</div>
|
|
739
747
|
|
|
740
748
|
<div className="space-y-2">
|
|
@@ -12,6 +12,7 @@ import type { PanelElement, PanelElementStyle } from "../../types";
|
|
|
12
12
|
import { readFileAsDataUrl, runBusyTask } from "../../utils/async-work";
|
|
13
13
|
import { patchBackgroundImageFromInput } from "../../utils/background-image-style";
|
|
14
14
|
import { getPanelMessages } from "../../constants/messages";
|
|
15
|
+
import { ConfigHintIcon } from "../ConfigHintIcon";
|
|
15
16
|
import { type ConfigSectionHelpers, type UpdateElement } from "./helpers";
|
|
16
17
|
|
|
17
18
|
export function PanelConfigStyleSections({
|
|
@@ -90,7 +91,12 @@ export function PanelConfigStyleSections({
|
|
|
90
91
|
t("panel.config.groupBgLayout"),
|
|
91
92
|
<>
|
|
92
93
|
<label className="block space-y-1" data-config-field="style.backgroundImage">
|
|
93
|
-
<div
|
|
94
|
+
<div className="flex items-center gap-1">
|
|
95
|
+
{t("panel.config.backgroundImage")}
|
|
96
|
+
<ConfigHintIcon label={t("panel.config.backgroundImage")}>
|
|
97
|
+
{t("panel.config.urlScopeHint")}
|
|
98
|
+
</ConfigHintIcon>
|
|
99
|
+
</div>
|
|
94
100
|
<Input
|
|
95
101
|
value={
|
|
96
102
|
selectedElement.style?.backgroundImageRemoteUrl ??
|
|
@@ -103,9 +109,6 @@ export function PanelConfigStyleSections({
|
|
|
103
109
|
placeholder={t("panel.config.urlScopePlaceholder")}
|
|
104
110
|
className="h-7 font-mono text-[11px]"
|
|
105
111
|
/>
|
|
106
|
-
<p className="text-[10px] text-muted-foreground">
|
|
107
|
-
{t("panel.config.urlScopeHint")}
|
|
108
|
-
</p>
|
|
109
112
|
</label>
|
|
110
113
|
<div className="flex items-center gap-2">
|
|
111
114
|
<label className="inline-flex cursor-pointer items-center rounded border border-border px-2 py-1 text-[11px] hover:bg-accent">
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "@arronqzy/ui";
|
|
12
12
|
import type { PanelElement } from "../../types";
|
|
13
13
|
import { cssTextLineHeight, cssTextAlignStyle } from "../../utils/panelElementDefaults";
|
|
14
|
+
import { ConfigHintIcon } from "../ConfigHintIcon";
|
|
14
15
|
import { type ConfigSectionHelpers, type UpdateElement } from "./helpers";
|
|
15
16
|
|
|
16
17
|
export function PanelConfigTextSection({
|
|
@@ -111,7 +112,12 @@ export function PanelConfigTextSection({
|
|
|
111
112
|
}}
|
|
112
113
|
/>
|
|
113
114
|
<label className="block space-y-1">
|
|
114
|
-
<div
|
|
115
|
+
<div className="flex items-center gap-1">
|
|
116
|
+
{t("panel.config.textHtmlSource")}
|
|
117
|
+
<ConfigHintIcon label={t("panel.config.textHtmlSource")}>
|
|
118
|
+
{t("panel.config.scopeTemplateHint")}
|
|
119
|
+
</ConfigHintIcon>
|
|
120
|
+
</div>
|
|
115
121
|
<textarea
|
|
116
122
|
data-config-field="textHtml"
|
|
117
123
|
value={selectedElement.textHtml ?? ""}
|
|
@@ -125,9 +131,6 @@ export function PanelConfigTextSection({
|
|
|
125
131
|
placeholder={t("panel.config.textHtmlSourcePlaceholder")}
|
|
126
132
|
className="w-full rounded border border-border bg-background px-2 py-1.5 font-mono text-[11px] leading-relaxed outline-none focus-visible:ring-1 focus-visible:ring-primary"
|
|
127
133
|
/>
|
|
128
|
-
<p className="text-[10px] text-muted-foreground">
|
|
129
|
-
{t("panel.config.scopeTemplateHint")}
|
|
130
|
-
</p>
|
|
131
134
|
</label>
|
|
132
135
|
</>
|
|
133
136
|
)}
|
|
@@ -4,6 +4,7 @@ import { Checkbox, Input } from "@arronqzy/ui";
|
|
|
4
4
|
import type { PanelElement } from "../../types";
|
|
5
5
|
import { readFileAsDataUrl, runBusyTask } from "../../utils/async-work";
|
|
6
6
|
import { getPanelMessages } from "../../constants/messages";
|
|
7
|
+
import { ConfigHintIcon } from "../ConfigHintIcon";
|
|
7
8
|
import { type ConfigSectionHelpers, type UpdateElement } from "./helpers";
|
|
8
9
|
|
|
9
10
|
export function PanelConfigVideoSection({
|
|
@@ -70,7 +71,12 @@ export function PanelConfigVideoSection({
|
|
|
70
71
|
t("panel.config.groupVideoSource"),
|
|
71
72
|
<>
|
|
72
73
|
<label className="block space-y-1" data-config-field="videoRemoteUrl">
|
|
73
|
-
<div
|
|
74
|
+
<div className="flex items-center gap-1">
|
|
75
|
+
{t("panel.config.videoUrl")}
|
|
76
|
+
<ConfigHintIcon label={t("panel.config.videoUrl")}>
|
|
77
|
+
{t("panel.config.urlScopeHint")}
|
|
78
|
+
</ConfigHintIcon>
|
|
79
|
+
</div>
|
|
74
80
|
<Input
|
|
75
81
|
value={selectedElement.videoRemoteUrl ?? ""}
|
|
76
82
|
onChange={(e) =>
|
|
@@ -82,9 +88,6 @@ export function PanelConfigVideoSection({
|
|
|
82
88
|
placeholder={t("panel.config.urlScopePlaceholder")}
|
|
83
89
|
className="h-7 font-mono text-[11px]"
|
|
84
90
|
/>
|
|
85
|
-
<p className="text-[10px] text-muted-foreground">
|
|
86
|
-
{t("panel.config.urlScopeHint")}
|
|
87
|
-
</p>
|
|
88
91
|
</label>
|
|
89
92
|
<div className="flex items-center gap-2">
|
|
90
93
|
<label className="inline-flex cursor-pointer items-center rounded border border-border px-2 py-1 text-[11px] hover:bg-accent">
|
|
@@ -73,17 +73,13 @@ export function ScopeTemplateAutocompleteHost({
|
|
|
73
73
|
|
|
74
74
|
const refreshForInput = useCallback(
|
|
75
75
|
(input: HTMLInputElement | HTMLTextAreaElement) => {
|
|
76
|
-
if (scope === undefined) {
|
|
77
|
-
closeDropdown();
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
76
|
const cursor = input.selectionStart ?? input.value.length;
|
|
81
|
-
const parsed = parseScopeAutocomplete(input.value, cursor, scope);
|
|
77
|
+
const parsed = parseScopeAutocomplete(input.value, cursor, scope ?? {});
|
|
82
78
|
if (!parsed) {
|
|
83
79
|
closeDropdown();
|
|
84
80
|
return;
|
|
85
81
|
}
|
|
86
|
-
const suggestions = getScopeAutocompleteSuggestions(scope, parsed);
|
|
82
|
+
const suggestions = getScopeAutocompleteSuggestions(scope ?? {}, parsed);
|
|
87
83
|
if (suggestions.length === 0) {
|
|
88
84
|
closeDropdown();
|
|
89
85
|
return;
|
|
@@ -124,7 +120,7 @@ export function ScopeTemplateAutocompleteHost({
|
|
|
124
120
|
|
|
125
121
|
useEffect(() => {
|
|
126
122
|
const container = containerRef?.current;
|
|
127
|
-
if (!container
|
|
123
|
+
if (!container) return;
|
|
128
124
|
|
|
129
125
|
const onFocusIn = (event: FocusEvent) => {
|
|
130
126
|
if (!isAutocompleteTarget(event.target)) return;
|
|
@@ -213,7 +209,7 @@ export function ScopeTemplateAutocompleteHost({
|
|
|
213
209
|
scope,
|
|
214
210
|
]);
|
|
215
211
|
|
|
216
|
-
if (!dropdown
|
|
212
|
+
if (!dropdown) return null;
|
|
217
213
|
|
|
218
214
|
return createPortal(
|
|
219
215
|
<div
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
SelectValue,
|
|
21
21
|
} from "@arronqzy/ui";
|
|
22
22
|
import React from "react";
|
|
23
|
+
import { ConfigHintIcon } from "../ConfigHintIcon";
|
|
23
24
|
|
|
24
25
|
const CONDITION_OPS: ConditionOp[] = [
|
|
25
26
|
"eq",
|
|
@@ -90,7 +91,12 @@ export function ConditionEditor({
|
|
|
90
91
|
<div className="space-y-2">
|
|
91
92
|
<div className="flex items-center justify-between gap-2">
|
|
92
93
|
<label className={`${fieldClass} min-w-0 flex-1`}>
|
|
93
|
-
<div className="text-[11px] text-muted-foreground">
|
|
94
|
+
<div className="flex items-center gap-1 text-[11px] text-muted-foreground">
|
|
95
|
+
{t("panel.config.tableConditionLogic")}
|
|
96
|
+
<ConfigHintIcon label={t("panel.config.tableConditionLogic")}>
|
|
97
|
+
{t("panel.config.tableConditionLogicHint")}
|
|
98
|
+
</ConfigHintIcon>
|
|
99
|
+
</div>
|
|
94
100
|
<Select
|
|
95
101
|
value={logic}
|
|
96
102
|
onValueChange={(next) => onChange(setConditionLogic(value, next as ConditionLogic))}
|
|
@@ -115,7 +121,6 @@ export function ConditionEditor({
|
|
|
115
121
|
{t("panel.config.tableConditionAddItem")}
|
|
116
122
|
</Button>
|
|
117
123
|
</div>
|
|
118
|
-
<div className="text-[10px] text-muted-foreground">{t("panel.config.tableConditionLogicHint")}</div>
|
|
119
124
|
{items.map((item, index) => {
|
|
120
125
|
const leaf: ConditionLeaf = isConditionLeaf(item)
|
|
121
126
|
? item
|
|
@@ -427,10 +427,16 @@ export function PanelConfigTableSection({
|
|
|
427
427
|
</div>
|
|
428
428
|
<details className="rounded-md border border-border/50 bg-muted/10 px-2 py-1.5">
|
|
429
429
|
<summary className="cursor-pointer text-[11px] font-medium text-muted-foreground">
|
|
430
|
-
|
|
430
|
+
<span className="inline-flex items-center gap-1 align-middle">
|
|
431
|
+
{t("panel.config.tableAdvancedData")}
|
|
432
|
+
<span className="inline-flex" onClick={(e) => e.preventDefault()}>
|
|
433
|
+
<ConfigHintIcon label={t("panel.config.tableAdvancedData")}>
|
|
434
|
+
{t("panel.config.tableAdvancedDataHint")}
|
|
435
|
+
</ConfigHintIcon>
|
|
436
|
+
</span>
|
|
437
|
+
</span>
|
|
431
438
|
</summary>
|
|
432
439
|
<div className="mt-2 space-y-2 border-t border-border/40 pt-2">
|
|
433
|
-
<p className="text-[10px] text-muted-foreground">{t("panel.config.tableAdvancedDataHint")}</p>
|
|
434
440
|
<div className="grid grid-cols-2 gap-2">
|
|
435
441
|
<label className={fieldClass}>
|
|
436
442
|
{fieldLabel("panel.config.tableTransformMode", "panel.config.tableTransformModeHint")}
|
|
@@ -1000,7 +1006,7 @@ export function PanelConfigTableSection({
|
|
|
1000
1006
|
imageUrlPrefix: e.target.value || undefined,
|
|
1001
1007
|
})
|
|
1002
1008
|
}
|
|
1003
|
-
placeholder="
|
|
1009
|
+
placeholder={t("panel.config.tableImageUrlPrefixPlaceholder")}
|
|
1004
1010
|
className={inputClass}
|
|
1005
1011
|
/>
|
|
1006
1012
|
</label>
|
|
@@ -1292,7 +1298,7 @@ export function PanelConfigTableSection({
|
|
|
1292
1298
|
progressStatic: e.target.value || undefined,
|
|
1293
1299
|
})
|
|
1294
1300
|
}
|
|
1295
|
-
placeholder="
|
|
1301
|
+
placeholder={t("panel.config.tableProgressStaticPlaceholder")}
|
|
1296
1302
|
className={inputClass}
|
|
1297
1303
|
/>
|
|
1298
1304
|
</label>
|
|
@@ -1724,7 +1730,7 @@ export function PanelConfigTableSection({
|
|
|
1724
1730
|
displayTemplate: e.target.value.trim() ? e.target.value : undefined,
|
|
1725
1731
|
})
|
|
1726
1732
|
}
|
|
1727
|
-
placeholder="
|
|
1733
|
+
placeholder={t("panel.config.tableDisplayTemplatePlaceholder")}
|
|
1728
1734
|
className={inputClass}
|
|
1729
1735
|
/>
|
|
1730
1736
|
</label>
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
getDefaultScene3dConfigForMaterial,
|
|
23
23
|
getDefaultSizeByMaterial,
|
|
24
24
|
getDefaultTextContent,
|
|
25
|
+
localizeLayers,
|
|
26
|
+
localizePanelElement,
|
|
25
27
|
normalizePrimaryLayer,
|
|
26
28
|
randomId,
|
|
27
29
|
} from "../utils/panelElementDefaults";
|
|
@@ -67,7 +69,7 @@ export function usePanelElements() {
|
|
|
67
69
|
() => store.getState()
|
|
68
70
|
) as State;
|
|
69
71
|
const rawLayers = (state.variables?.layers as PanelLayer[] | undefined) ?? [defaultLayer];
|
|
70
|
-
const layers = useMemo(() => normalizePrimaryLayer(rawLayers), [rawLayers]);
|
|
72
|
+
const layers = useMemo(() => localizeLayers(normalizePrimaryLayer(rawLayers), t), [rawLayers, t]);
|
|
71
73
|
const activeLayerId =
|
|
72
74
|
(state.variables?.activeLayerId as string | undefined) ?? DEFAULT_LAYER_ID;
|
|
73
75
|
const canUndo = store.getHistoryCursorIndex() > 0;
|
|
@@ -137,14 +139,29 @@ export function usePanelElements() {
|
|
|
137
139
|
});
|
|
138
140
|
}, []);
|
|
139
141
|
|
|
142
|
+
const localizedCacheRef = useRef<{ t: typeof t; map: WeakMap<PanelElement, PanelElement> }>({
|
|
143
|
+
t,
|
|
144
|
+
map: new WeakMap(),
|
|
145
|
+
});
|
|
140
146
|
const allElements = useMemo(() => {
|
|
141
147
|
const next = materializePanelElements(
|
|
142
148
|
state.root.children,
|
|
143
149
|
allElementsCacheRef.current
|
|
144
150
|
);
|
|
145
151
|
allElementsCacheRef.current = next;
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
if (localizedCacheRef.current.t !== t) {
|
|
153
|
+
localizedCacheRef.current = { t, map: new WeakMap() };
|
|
154
|
+
}
|
|
155
|
+
const { map } = localizedCacheRef.current;
|
|
156
|
+
return next.map((el) => {
|
|
157
|
+
let localized = map.get(el);
|
|
158
|
+
if (!localized) {
|
|
159
|
+
localized = localizePanelElement(el, t);
|
|
160
|
+
map.set(el, localized);
|
|
161
|
+
}
|
|
162
|
+
return localized;
|
|
163
|
+
});
|
|
164
|
+
}, [state.root.children, t]);
|
|
148
165
|
|
|
149
166
|
const elements = useMemo(() => {
|
|
150
167
|
const next = allElements.filter((el) => el.layerId === activeLayerId);
|
|
@@ -5,6 +5,10 @@ export const AbuilderEvents = {
|
|
|
5
5
|
workspaceAdd: "workspace:add",
|
|
6
6
|
/** 同步工作区 */
|
|
7
7
|
workspaceSync: "workspace:sync",
|
|
8
|
+
/** 开始上传接口集合 */
|
|
9
|
+
apiCollectionUploadStart: "api-collection:upload-start",
|
|
10
|
+
/** 上传接口集合成功 */
|
|
11
|
+
apiCollectionUploadSuccess: "api-collection:upload-success",
|
|
8
12
|
} as const;
|
|
9
13
|
|
|
10
14
|
export type AbuilderEventName = (typeof AbuilderEvents)[keyof typeof AbuilderEvents];
|
|
@@ -15,9 +19,25 @@ export type WorkspaceData = WorkspaceProjectRecord;
|
|
|
15
19
|
export type WorkspaceAddEventPayload = WorkspaceData;
|
|
16
20
|
export type WorkspaceSyncEventPayload = WorkspaceData;
|
|
17
21
|
|
|
22
|
+
export type ApiCollectionUploadStartEventPayload = {
|
|
23
|
+
fileNames: string[];
|
|
24
|
+
count: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ApiCollectionUploadSuccessEventPayload = {
|
|
28
|
+
collections: Array<{
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
apiCount: number;
|
|
32
|
+
persistLocal: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
|
|
18
36
|
export type AbuilderEventPayloadMap = {
|
|
19
37
|
[AbuilderEvents.workspaceAdd]: WorkspaceAddEventPayload;
|
|
20
38
|
[AbuilderEvents.workspaceSync]: WorkspaceSyncEventPayload;
|
|
39
|
+
[AbuilderEvents.apiCollectionUploadStart]: ApiCollectionUploadStartEventPayload;
|
|
40
|
+
[AbuilderEvents.apiCollectionUploadSuccess]: ApiCollectionUploadSuccessEventPayload;
|
|
21
41
|
};
|
|
22
42
|
|
|
23
43
|
type EventCallback = (payload: unknown) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TranslateFn } from "@arronqzy/i18n";
|
|
2
|
-
import { tForLocale } from "@arronqzy/i18n";
|
|
2
|
+
import { relocalizeMessage, tForLocale } from "@arronqzy/i18n";
|
|
3
3
|
import { createDefaultScene3dConfig } from "@arronqzy/view-scene3d";
|
|
4
4
|
import type { PanelChartConfig, PanelLayer, PanelElement } from "../types";
|
|
5
5
|
import { createDefaultTableConfig, type PanelTableConfig } from "@arronqzy/view-table";
|
|
@@ -34,6 +34,52 @@ export function getDefaultNodeName(materialType: string, t: TranslateFn = tFallb
|
|
|
34
34
|
return key ? t(key) : materialType;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
const DEFAULT_LAYER_NAME_KEYS = [
|
|
38
|
+
"panel.defaults.layer1",
|
|
39
|
+
"panel.defaults.layerN",
|
|
40
|
+
"panel.defaults.mappingLayerN",
|
|
41
|
+
"panel.defaults.layerRandom",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/** Persisted default layer names ("图层1" / "Layer 2") follow the current locale. */
|
|
45
|
+
export function localizeLayerName(name: string, t: TranslateFn): string {
|
|
46
|
+
return relocalizeMessage(name, DEFAULT_LAYER_NAME_KEYS, t);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function localizeLayers(layers: PanelLayer[], t: TranslateFn): PanelLayer[] {
|
|
50
|
+
return layers.map((layer) => {
|
|
51
|
+
const name = localizeLayerName(layer.name, t);
|
|
52
|
+
return name === layer.name ? layer : { ...layer, name };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const DEFAULT_TEXT_HTML_RE = /^<p>(.*)<\/p>$/s;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Default node name, chart title and text content are stored in the creation locale;
|
|
60
|
+
* re-render them in the current one. Returns the same object when nothing changes.
|
|
61
|
+
*/
|
|
62
|
+
export function localizePanelElement(element: PanelElement, t: TranslateFn): PanelElement {
|
|
63
|
+
const key = DEFAULT_NODE_NAME_KEYS[element.materialType ?? ""];
|
|
64
|
+
if (!key) return element;
|
|
65
|
+
const name = element.name ? relocalizeMessage(element.name, key, t) : element.name;
|
|
66
|
+
const title = element.chart?.title ? relocalizeMessage(element.chart.title, key, t) : undefined;
|
|
67
|
+
let textHtml = element.textHtml;
|
|
68
|
+
const textInner = textHtml ? DEFAULT_TEXT_HTML_RE.exec(textHtml)?.[1] : undefined;
|
|
69
|
+
if (textInner) {
|
|
70
|
+
const localized = relocalizeMessage(textInner, "panel.defaults.doubleClickTextHtml", t);
|
|
71
|
+
if (localized !== textInner) textHtml = `<p>${localized}</p>`;
|
|
72
|
+
}
|
|
73
|
+
const titleChanged = title !== undefined && title !== element.chart?.title;
|
|
74
|
+
if (name === element.name && !titleChanged && textHtml === element.textHtml) return element;
|
|
75
|
+
return {
|
|
76
|
+
...element,
|
|
77
|
+
name,
|
|
78
|
+
textHtml,
|
|
79
|
+
...(titleChanged && element.chart ? { chart: { ...element.chart, title } } : null),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
37
83
|
export function getDefaultLayer(t: TranslateFn = tFallback): PanelLayer {
|
|
38
84
|
return {
|
|
39
85
|
id: DEFAULT_LAYER_ID,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { PanelElement } from "../types";
|
|
2
2
|
import type { PanelTableConfig } from "@arronqzy/view-table";
|
|
3
3
|
import { isUsableTableRawData } from "@arronqzy/view-table";
|
|
4
|
+
import {
|
|
5
|
+
createSystemRoot,
|
|
6
|
+
isDayjsValue,
|
|
7
|
+
} from "@arronqzy/blueprint-dsl";
|
|
4
8
|
import { materializeChartLabelsFromScope, materializeChartValuesFromScope } from "./scope-template-chart";
|
|
5
9
|
import {
|
|
6
10
|
SCOPE_SPREAD_TEMPLATE_RE,
|
|
@@ -11,12 +15,12 @@ import {
|
|
|
11
15
|
resolveSpreadScopePath,
|
|
12
16
|
} from "./scope-template-spread";
|
|
13
17
|
|
|
14
|
-
const
|
|
18
|
+
const TEMPLATE_TOKEN_RE = /\{((?:scope|system)[^}]*)\}/g;
|
|
15
19
|
|
|
16
20
|
export function hasScopeTemplate(value: string): boolean {
|
|
17
|
-
|
|
21
|
+
TEMPLATE_TOKEN_RE.lastIndex = 0;
|
|
18
22
|
SCOPE_SPREAD_TEMPLATE_RE.lastIndex = 0;
|
|
19
|
-
return
|
|
23
|
+
return TEMPLATE_TOKEN_RE.test(value) || SCOPE_SPREAD_TEMPLATE_RE.test(value);
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export function evaluateScopeExpression(
|
|
@@ -26,8 +30,13 @@ export function evaluateScopeExpression(
|
|
|
26
30
|
const trimmed = expression.trim();
|
|
27
31
|
if (!trimmed) return undefined;
|
|
28
32
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
33
|
+
const system = createSystemRoot();
|
|
34
|
+
const fn = new Function(
|
|
35
|
+
"scope",
|
|
36
|
+
"system",
|
|
37
|
+
`"use strict"; return (${trimmed});`
|
|
38
|
+
);
|
|
39
|
+
return fn(scope, system);
|
|
31
40
|
} catch {
|
|
32
41
|
return undefined;
|
|
33
42
|
}
|
|
@@ -39,6 +48,9 @@ function stringifyScopeValue(value: unknown): string {
|
|
|
39
48
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
40
49
|
return String(value);
|
|
41
50
|
}
|
|
51
|
+
if (isDayjsValue(value)) {
|
|
52
|
+
return value.toISOString();
|
|
53
|
+
}
|
|
42
54
|
try {
|
|
43
55
|
return JSON.stringify(value);
|
|
44
56
|
} catch {
|
|
@@ -60,8 +72,8 @@ export function evaluateScopeTemplate(
|
|
|
60
72
|
}
|
|
61
73
|
);
|
|
62
74
|
|
|
63
|
-
// Only `{scope...}`
|
|
64
|
-
return withSpread.replace(
|
|
75
|
+
// Only `{scope...}` / `{system...}` — keep ECharts / CSS braces like `{b}` intact.
|
|
76
|
+
return withSpread.replace(TEMPLATE_TOKEN_RE, (_: string, rawExpr: string) =>
|
|
65
77
|
stringifyScopeValue(evaluateScopeExpression(rawExpr, scope))
|
|
66
78
|
);
|
|
67
79
|
}
|
|
@@ -91,7 +103,7 @@ function materializeTemplateValue(template: string, scope: unknown): unknown {
|
|
|
91
103
|
);
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
const singleMatch = /^\{(scope[^}]*)\}$/.exec(trimmed);
|
|
106
|
+
const singleMatch = /^\{((?:scope|system)[^}]*)\}$/.exec(trimmed);
|
|
95
107
|
if (singleMatch) {
|
|
96
108
|
return evaluateScopeExpression(singleMatch[1]!, scope);
|
|
97
109
|
}
|