@arronqzy/react-view 1.0.35 → 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 +8 -0
- package/package.json +8 -8
- package/src/core/instructions.tsx +1 -1
- package/src/core/template.tsx +1 -1
- package/src/panel/I18nRoot.tsx +2 -1
- package/src/panel/ReactViewOnlinePreview.tsx +4 -1
- package/src/panel/ReactViewPanel.tsx +12 -6
- package/src/panel/components/ConfigHintIcon.tsx +15 -2
- 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/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/utils/panelElementDefaults.ts +47 -1
package/dist/styles.css
CHANGED
|
@@ -2511,6 +2511,10 @@ video {
|
|
|
2511
2511
|
text-align: center;
|
|
2512
2512
|
}
|
|
2513
2513
|
|
|
2514
|
+
.align-middle {
|
|
2515
|
+
vertical-align: middle;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2514
2518
|
.font-mono {
|
|
2515
2519
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
2516
2520
|
}
|
|
@@ -3239,6 +3243,10 @@ video {
|
|
|
3239
3243
|
--tw-ring-color: hsl(var(--primary));
|
|
3240
3244
|
}
|
|
3241
3245
|
|
|
3246
|
+
.focus-visible\:ring-ring:focus-visible {
|
|
3247
|
+
--tw-ring-color: hsl(var(--ring));
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3242
3250
|
.focus-visible\:ring-offset-0:focus-visible {
|
|
3243
3251
|
--tw-ring-offset-width: 0px;
|
|
3244
3252
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/react-view",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.36",
|
|
4
4
|
"files": [
|
|
5
5
|
"src",
|
|
6
6
|
"dist",
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"@codemirror/lang-json": "^6.0.1",
|
|
31
31
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
32
32
|
"@codemirror/view": "^6.36.2",
|
|
33
|
-
"@arronqzy/
|
|
34
|
-
"@arronqzy/
|
|
35
|
-
"@arronqzy/
|
|
36
|
-
"@arronqzy/blueprint
|
|
37
|
-
"@arronqzy/view-scene3d": "0.1.5",
|
|
33
|
+
"@arronqzy/blueprint-dsl": "1.0.17",
|
|
34
|
+
"@arronqzy/ui": "1.0.10",
|
|
35
|
+
"@arronqzy/i18n": "0.1.17",
|
|
36
|
+
"@arronqzy/react-blueprint": "1.0.22",
|
|
38
37
|
"@arronqzy/rx-store": "1.0.8",
|
|
38
|
+
"@arronqzy/view-scene3d": "0.1.5",
|
|
39
39
|
"@arronqzy/view-table": "0.1.8",
|
|
40
40
|
"@arronqzy/webllm-assistant": "0.1.16"
|
|
41
41
|
},
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"@types/use-sync-external-store": "^1.5.0",
|
|
46
46
|
"eslint": "^8.57.0",
|
|
47
47
|
"typescript": "^5.5.4",
|
|
48
|
+
"@arronqzy/typescript-config": "1.0.0",
|
|
48
49
|
"@arronqzy/eslint-config": "0.0.0",
|
|
49
|
-
"@arronqzy/tailwind": "1.0.2"
|
|
50
|
-
"@arronqzy/typescript-config": "1.0.0"
|
|
50
|
+
"@arronqzy/tailwind": "1.0.2"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
package/src/core/template.tsx
CHANGED
package/src/panel/I18nRoot.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { I18nProvider } from "@arronqzy/i18n/react";
|
|
2
2
|
import { LOCALE_STORAGE_KEY } from "@arronqzy/i18n";
|
|
3
3
|
import { appStorageKey } from "@arronqzy/blueprint-dsl";
|
|
4
|
+
import { BlueprintDslLocaleSync } from "@arronqzy/react-blueprint";
|
|
4
5
|
import type { Locale } from "@arronqzy/i18n";
|
|
5
6
|
import type { ReactNode } from "react";
|
|
6
7
|
|
|
@@ -24,7 +25,7 @@ export function I18nRoot({
|
|
|
24
25
|
onLocaleChange={onLocaleChange}
|
|
25
26
|
storageKey={appStorageKey(LOCALE_STORAGE_KEY, nameSpace)}
|
|
26
27
|
>
|
|
27
|
-
{children}
|
|
28
|
+
<BlueprintDslLocaleSync>{children}</BlueprintDslLocaleSync>
|
|
28
29
|
</I18nProvider>
|
|
29
30
|
);
|
|
30
31
|
}
|
|
@@ -17,6 +17,7 @@ import type { State } from "@arronqzy/rx-store";
|
|
|
17
17
|
import { ElementsLayer } from "./components/ElementsLayer";
|
|
18
18
|
import type { TableCellActionHandler } from "./components/table/TableNodeContent";
|
|
19
19
|
import type { PanelElement } from "./types";
|
|
20
|
+
import { localizePanelElement } from "./utils/panelElementDefaults";
|
|
20
21
|
import {
|
|
21
22
|
clearViewElementScopes,
|
|
22
23
|
getViewElementScope,
|
|
@@ -236,7 +237,8 @@ function ReactViewOnlinePreviewInner({
|
|
|
236
237
|
|
|
237
238
|
const displayElements = useMemo(
|
|
238
239
|
() =>
|
|
239
|
-
scopedLayerElements.map((
|
|
240
|
+
scopedLayerElements.map((source) => {
|
|
241
|
+
const el = localizePanelElement(source, t);
|
|
240
242
|
const x = el.x - sceneBounds.minX;
|
|
241
243
|
const y = el.y - sceneBounds.minY;
|
|
242
244
|
if (outputScale) return { ...el, x, y };
|
|
@@ -255,6 +257,7 @@ function ReactViewOnlinePreviewInner({
|
|
|
255
257
|
sceneBounds.minX,
|
|
256
258
|
sceneBounds.minY,
|
|
257
259
|
scopedLayerElements,
|
|
260
|
+
t,
|
|
258
261
|
]
|
|
259
262
|
);
|
|
260
263
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { useI18n } from "@arronqzy/i18n/react";
|
|
3
|
-
import type
|
|
3
|
+
import { relocalizeMessage, type Locale } from "@arronqzy/i18n";
|
|
4
4
|
import type { State } from "@arronqzy/rx-store";
|
|
5
5
|
import type { PanelElement } from "./types";
|
|
6
6
|
|
|
@@ -283,6 +283,12 @@ function ReactViewPanelInner({
|
|
|
283
283
|
name: t("panel.defaults.unnamedBlueprint"),
|
|
284
284
|
remark: "",
|
|
285
285
|
});
|
|
286
|
+
const displayProductName = relocalizeMessage(productName, "panel.defaults.unnamedProduct", t);
|
|
287
|
+
const displayBlueprintName = relocalizeMessage(
|
|
288
|
+
blueprintMeta.name,
|
|
289
|
+
["panel.defaults.unnamedBlueprint", "blueprint.dialog.unnamedBlueprint"],
|
|
290
|
+
t
|
|
291
|
+
);
|
|
286
292
|
const [blueprintLibraryItems, setBlueprintLibraryItems] = useState<
|
|
287
293
|
BlueprintLibraryListItem[]
|
|
288
294
|
>([]);
|
|
@@ -682,7 +688,7 @@ function ReactViewPanelInner({
|
|
|
682
688
|
const blueprintDebugSession = useBlueprintDebugSession({
|
|
683
689
|
graph: blueprintGraph,
|
|
684
690
|
blueprintId: activeBlueprintLibraryId,
|
|
685
|
-
blueprintName:
|
|
691
|
+
blueprintName: displayBlueprintName || t("panel.defaults.unnamedBlueprint"),
|
|
686
692
|
resolveLibraryBlueprint,
|
|
687
693
|
libraryNameById: blueprintLibraryNameById,
|
|
688
694
|
onExecutionBlocked: handleBlueprintExecutionBlocked,
|
|
@@ -1648,7 +1654,7 @@ function ReactViewPanelInner({
|
|
|
1648
1654
|
const previewFaviconLink = faviconHrefWithVersion
|
|
1649
1655
|
? `<link rel="icon" type="image/png" href="${faviconHrefWithVersion}" /><link rel="shortcut icon" type="image/png" href="${faviconHrefWithVersion}" />`
|
|
1650
1656
|
: "";
|
|
1651
|
-
const previewTitle =
|
|
1657
|
+
const previewTitle = displayProductName.trim() || t("panel.defaults.unnamedProduct");
|
|
1652
1658
|
const escapedPreviewTitle = previewTitle
|
|
1653
1659
|
.replace(/&/g, "&")
|
|
1654
1660
|
.replace(/</g, "<")
|
|
@@ -1787,7 +1793,7 @@ function ReactViewPanelInner({
|
|
|
1787
1793
|
win.document.head.appendChild(link);
|
|
1788
1794
|
});
|
|
1789
1795
|
}
|
|
1790
|
-
}, [allElements, layers,
|
|
1796
|
+
}, [allElements, layers, displayProductName, t, titleIconDataUrl]);
|
|
1791
1797
|
|
|
1792
1798
|
const handlePreviewLayer = useCallback(() => {
|
|
1793
1799
|
void (async () => {
|
|
@@ -2048,7 +2054,7 @@ function ReactViewPanelInner({
|
|
|
2048
2054
|
style={{ zIndex: PANEL_Z_INDEX.toolbar }}
|
|
2049
2055
|
>
|
|
2050
2056
|
<Input
|
|
2051
|
-
value={
|
|
2057
|
+
value={displayProductName}
|
|
2052
2058
|
onChange={(e) => setProductName(e.target.value)}
|
|
2053
2059
|
placeholder={t("panel.menubar.productNamePlaceholder")}
|
|
2054
2060
|
className="h-7 w-[220px] text-xs focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none"
|
|
@@ -2332,7 +2338,7 @@ function ReactViewPanelInner({
|
|
|
2332
2338
|
blueprintProps={blueprintCanvasProps}
|
|
2333
2339
|
blueprintLibraryItems={blueprintLibraryItems}
|
|
2334
2340
|
activeBlueprintLibraryId={activeBlueprintLibraryId}
|
|
2335
|
-
currentBlueprintLabel={
|
|
2341
|
+
currentBlueprintLabel={displayBlueprintName}
|
|
2336
2342
|
onSelectBlueprintLibraryItem={(id) => {
|
|
2337
2343
|
void handleSelectBlueprintLibraryItem(id);
|
|
2338
2344
|
}}
|
|
@@ -31,11 +31,24 @@ export function ConfigHintIcon({
|
|
|
31
31
|
type="button"
|
|
32
32
|
className={
|
|
33
33
|
className ??
|
|
34
|
-
"inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full
|
|
34
|
+
"inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
35
35
|
}
|
|
36
36
|
aria-label={t("common.hintAria", { label: resolvedLabel })}
|
|
37
37
|
>
|
|
38
|
-
|
|
38
|
+
<svg
|
|
39
|
+
viewBox="0 0 24 24"
|
|
40
|
+
fill="none"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
strokeWidth={2}
|
|
43
|
+
strokeLinecap="round"
|
|
44
|
+
strokeLinejoin="round"
|
|
45
|
+
className="h-3.5 w-3.5"
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
>
|
|
48
|
+
<circle cx="12" cy="12" r="10" />
|
|
49
|
+
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
|
|
50
|
+
<path d="M12 17h.01" />
|
|
51
|
+
</svg>
|
|
39
52
|
</button>
|
|
40
53
|
</TooltipTrigger>
|
|
41
54
|
<TooltipContent
|
|
@@ -152,7 +152,12 @@ export function PanelConfigAudioSection({
|
|
|
152
152
|
t("panel.config.groupAudioSource"),
|
|
153
153
|
<>
|
|
154
154
|
<label className="block space-y-1" data-config-field="audioRemoteUrl">
|
|
155
|
-
<div
|
|
155
|
+
<div className="flex items-center gap-1">
|
|
156
|
+
{t("panel.config.audioUrl")}
|
|
157
|
+
<ConfigHintIcon label={t("panel.config.audioUrl")}>
|
|
158
|
+
{t("panel.config.urlScopeHint")}
|
|
159
|
+
</ConfigHintIcon>
|
|
160
|
+
</div>
|
|
156
161
|
<Input
|
|
157
162
|
value={selectedElement.audioRemoteUrl ?? ""}
|
|
158
163
|
onChange={(e) =>
|
|
@@ -164,9 +169,6 @@ export function PanelConfigAudioSection({
|
|
|
164
169
|
placeholder={t("panel.config.urlScopePlaceholder")}
|
|
165
170
|
className="h-7 font-mono text-[11px]"
|
|
166
171
|
/>
|
|
167
|
-
<p className="text-[10px] text-muted-foreground">
|
|
168
|
-
{t("panel.config.urlScopeHint")}
|
|
169
|
-
</p>
|
|
170
172
|
</label>
|
|
171
173
|
<div className="flex items-center gap-2">
|
|
172
174
|
<label className="inline-flex cursor-pointer items-center rounded border border-border px-2 py-1 text-[11px] hover:bg-accent">
|
|
@@ -118,7 +118,12 @@ export function PanelConfigChartSection({
|
|
|
118
118
|
t("panel.config.groupData"),
|
|
119
119
|
<>
|
|
120
120
|
<label className="block space-y-1" data-config-field="chart.labelsText">
|
|
121
|
-
<div
|
|
121
|
+
<div className="flex items-center gap-1">
|
|
122
|
+
{t("panel.config.labelsCsv")}
|
|
123
|
+
<ConfigHintIcon label={t("panel.config.labelsCsv")}>
|
|
124
|
+
{t("panel.config.scopeTemplateHint")}
|
|
125
|
+
</ConfigHintIcon>
|
|
126
|
+
</div>
|
|
122
127
|
<Input
|
|
123
128
|
value={getChartLabelsDisplayText(selectedElement.chart)}
|
|
124
129
|
onChange={(e) =>
|
|
@@ -129,13 +134,15 @@ export function PanelConfigChartSection({
|
|
|
129
134
|
placeholder={t("panel.config.labelsCsvPlaceholder")}
|
|
130
135
|
className="h-7"
|
|
131
136
|
/>
|
|
132
|
-
<p className="text-[10px] text-muted-foreground">
|
|
133
|
-
{t("panel.config.scopeTemplateHint")}
|
|
134
|
-
</p>
|
|
135
137
|
</label>
|
|
136
138
|
|
|
137
139
|
<label className="block space-y-1" data-config-field="chart.valuesText">
|
|
138
|
-
<div
|
|
140
|
+
<div className="flex items-center gap-1">
|
|
141
|
+
{t("panel.config.valuesCsv")}
|
|
142
|
+
<ConfigHintIcon label={t("panel.config.valuesCsv")}>
|
|
143
|
+
{t("panel.config.scopeTemplateHint")}
|
|
144
|
+
</ConfigHintIcon>
|
|
145
|
+
</div>
|
|
139
146
|
<Input
|
|
140
147
|
value={getChartValuesDisplayText(selectedElement.chart)}
|
|
141
148
|
onChange={(e) =>
|
|
@@ -146,9 +153,6 @@ export function PanelConfigChartSection({
|
|
|
146
153
|
placeholder={t("panel.config.valuesCsvPlaceholder")}
|
|
147
154
|
className="h-7"
|
|
148
155
|
/>
|
|
149
|
-
<p className="text-[10px] text-muted-foreground">
|
|
150
|
-
{t("panel.config.scopeTemplateHint")}
|
|
151
|
-
</p>
|
|
152
156
|
</label>
|
|
153
157
|
</>
|
|
154
158
|
)}
|
|
@@ -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">
|
|
@@ -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);
|
|
@@ -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,
|