@arronqzy/vue-view 0.1.21 → 0.1.22
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/package.json +8 -8
- package/src/panel/VueViewOnlinePreview.vue +6 -1
- package/src/panel/VueViewPanel.vue +19 -3
- package/src/panel/components/ConfigHintIcon.vue +15 -2
- package/src/panel/components/config/PanelConfigAudioSection.vue +7 -4
- package/src/panel/components/config/PanelConfigChartSection.vue +14 -8
- package/src/panel/components/config/PanelConfigImageSection.vue +2 -2
- package/src/panel/components/config/PanelConfigMultiSelect.vue +7 -2
- package/src/panel/components/config/PanelConfigScene3dSection.vue +8 -4
- package/src/panel/components/config/PanelConfigStyleSections.vue +8 -4
- package/src/panel/components/config/PanelConfigTextSection.vue +8 -4
- package/src/panel/components/config/PanelConfigVideoSection.vue +8 -4
- package/src/panel/components/table/ConditionEditor.vue +7 -2
- package/src/panel/components/table/PanelConfigTableSection.vue +10 -4
- package/src/panel/hooks/usePanelElements.ts +18 -2
- package/src/panel/utils/panelElementDefaults.ts +47 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/vue-view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Vue 3 visual view editor for Abuilder (Ant Design Vue)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"selecto": "^1.26.3",
|
|
24
24
|
"uuid": "^13.0.0",
|
|
25
25
|
"vue": "^3.5.13",
|
|
26
|
-
"@arronqzy/blueprint-dsl": "1.0.
|
|
27
|
-
"@arronqzy/
|
|
26
|
+
"@arronqzy/blueprint-dsl": "1.0.17",
|
|
27
|
+
"@arronqzy/i18n": "0.1.17",
|
|
28
28
|
"@arronqzy/view-table": "0.1.8",
|
|
29
|
-
"@arronqzy/
|
|
29
|
+
"@arronqzy/rx-store": "1.0.8",
|
|
30
|
+
"@arronqzy/vue-blueprint": "0.1.17",
|
|
30
31
|
"@arronqzy/vue-rx-store": "0.1.6",
|
|
31
|
-
"@arronqzy/
|
|
32
|
-
"@arronqzy/rx-store": "1.0.8"
|
|
32
|
+
"@arronqzy/view-scene3d": "0.1.5"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"vue": "^3.4.0"
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"eslint": "^8.57.0",
|
|
40
40
|
"typescript": "^5.5.4",
|
|
41
41
|
"vue-tsc": "^2.2.0",
|
|
42
|
-
"@arronqzy/
|
|
43
|
-
"@arronqzy/
|
|
42
|
+
"@arronqzy/eslint-config": "0.0.0",
|
|
43
|
+
"@arronqzy/typescript-config": "1.0.0"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useI18n } from "@arronqzy/i18n/vue";
|
|
3
3
|
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
|
4
4
|
import {
|
|
5
|
+
useBlueprintDslLocaleSync,
|
|
5
6
|
BlueprintGraph,
|
|
6
7
|
documentToRunnableGraph,
|
|
7
8
|
getBlueprintLibraryRecord,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
useViewScopeStoreVersion,
|
|
23
24
|
} from "./scope/view-scope-store";
|
|
24
25
|
import { resolvePanelElementScope } from "./utils/scope-template";
|
|
26
|
+
import { localizePanelElement } from "./utils/panelElementDefaults";
|
|
25
27
|
import {
|
|
26
28
|
getWorkspaceProject,
|
|
27
29
|
type WorkspaceProjectRecord,
|
|
@@ -40,6 +42,7 @@ import {
|
|
|
40
42
|
import { applyPreviewSceneFill, readOutputScale } from "./utils/outputScale";
|
|
41
43
|
|
|
42
44
|
const { t, locale } = useI18n();
|
|
45
|
+
useBlueprintDslLocaleSync();
|
|
43
46
|
|
|
44
47
|
const PREVIEW_BOOT_PHASES: PageLifecyclePhase[] = ["mounted"];
|
|
45
48
|
|
|
@@ -194,7 +197,9 @@ const scopedLayerElements = computed(() => {
|
|
|
194
197
|
const sceneBounds = computed(() => computePanelSceneBounds(scopedLayerElements.value));
|
|
195
198
|
|
|
196
199
|
const displayElements = computed(() =>
|
|
197
|
-
scopedLayerElements.value.map((
|
|
200
|
+
scopedLayerElements.value.map((source) => {
|
|
201
|
+
void locale.value;
|
|
202
|
+
const el = localizePanelElement(source, t);
|
|
198
203
|
const x = el.x - sceneBounds.value.minX;
|
|
199
204
|
const y = el.y - sceneBounds.value.minY;
|
|
200
205
|
if (outputScale.value) return { ...el, x, y };
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from "vue";
|
|
3
3
|
import type { State } from "@arronqzy/rx-store";
|
|
4
4
|
import {
|
|
5
|
+
useBlueprintDslLocaleSync,
|
|
5
6
|
ApiCollectionManagerDialog,
|
|
6
7
|
BlueprintGraph,
|
|
7
8
|
abortClockNode,
|
|
@@ -72,12 +73,14 @@ import {
|
|
|
72
73
|
import { resolvePanelElementScope } from "./utils/scope-template";
|
|
73
74
|
import { useRafThrottledScroll } from "./hooks/useRafThrottledScroll";
|
|
74
75
|
import { useI18n } from "@arronqzy/i18n/vue";
|
|
76
|
+
import { relocalizeMessage } from "@arronqzy/i18n";
|
|
75
77
|
import { getPanelMessages } from "./constants/messages";
|
|
76
78
|
import BusyOverlay from "./components/BusyOverlay.vue";
|
|
77
79
|
import { assertFileSize, parseJsonText, runBusyTask } from "./utils/async-work";
|
|
78
80
|
import "../tailwind.css";
|
|
79
81
|
|
|
80
82
|
const { t, locale, setLocale } = useI18n();
|
|
83
|
+
useBlueprintDslLocaleSync();
|
|
81
84
|
const panelMessages = () => getPanelMessages(t);
|
|
82
85
|
|
|
83
86
|
const props = withDefaults(
|
|
@@ -152,6 +155,18 @@ const workspaceBlueprintRef = shallowRef<{
|
|
|
152
155
|
} | null>(null);
|
|
153
156
|
const configFocus = ref<WorkspaceConfigFocus>("view");
|
|
154
157
|
const productName = ref(t("panel.defaults.unnamedProduct"));
|
|
158
|
+
const displayProductName = computed(() => {
|
|
159
|
+
void locale.value;
|
|
160
|
+
return relocalizeMessage(productName.value, "panel.defaults.unnamedProduct", t);
|
|
161
|
+
});
|
|
162
|
+
const displayBlueprintName = computed(() => {
|
|
163
|
+
void locale.value;
|
|
164
|
+
return relocalizeMessage(
|
|
165
|
+
blueprintMeta.value.name,
|
|
166
|
+
["panel.defaults.unnamedBlueprint", "blueprint.dialog.unnamedBlueprint"],
|
|
167
|
+
t
|
|
168
|
+
);
|
|
169
|
+
});
|
|
155
170
|
const titleIconDataUrl = ref("");
|
|
156
171
|
const editingLayerId = ref<string | null>(null);
|
|
157
172
|
const editingLayerName = ref("");
|
|
@@ -346,7 +361,7 @@ function handleViewScopeUpdate(viewElementIds: string[], scope: unknown) {
|
|
|
346
361
|
const blueprintDebugSession = useBlueprintDebugSession({
|
|
347
362
|
graph: blueprintGraph,
|
|
348
363
|
blueprintId: activeBlueprintLibraryId,
|
|
349
|
-
blueprintName: computed(() =>
|
|
364
|
+
blueprintName: computed(() => displayBlueprintName.value || t("panel.defaults.unnamedBlueprint")),
|
|
350
365
|
resolveLibraryBlueprint,
|
|
351
366
|
libraryNameById: blueprintLibraryNameById,
|
|
352
367
|
onExecutionBlocked: handleBlueprintExecutionBlocked,
|
|
@@ -892,8 +907,9 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
892
907
|
<div class="flex h-full min-h-0 flex-col">
|
|
893
908
|
<div class="flex flex-wrap items-center gap-2 border-b bg-white px-3 py-2">
|
|
894
909
|
<Input
|
|
895
|
-
|
|
910
|
+
:value="displayProductName"
|
|
896
911
|
size="small"
|
|
912
|
+
@update:value="(value: string) => (productName = value)"
|
|
897
913
|
class="w-[200px]"
|
|
898
914
|
:placeholder="t('panel.menubar.productName')"
|
|
899
915
|
/>
|
|
@@ -919,7 +935,7 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
919
935
|
:blueprint-props="blueprintCanvasProps"
|
|
920
936
|
:blueprint-library-items="blueprintLibraryItems"
|
|
921
937
|
:active-blueprint-library-id="activeBlueprintLibraryId"
|
|
922
|
-
:current-blueprint-label="
|
|
938
|
+
:current-blueprint-label="displayBlueprintName"
|
|
923
939
|
:can-sync-blueprint="blueprintLibraryDirty"
|
|
924
940
|
:blueprint-debug="blueprintDebugToolbar"
|
|
925
941
|
:on-select-blueprint-library-item="(id) => void handleSelectBlueprintLibraryItem(id)"
|
|
@@ -26,11 +26,24 @@ const resolvedLabel = computed(() => props.label || t("common.hint"));
|
|
|
26
26
|
type="button"
|
|
27
27
|
:class="
|
|
28
28
|
buttonClass ??
|
|
29
|
-
'inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full
|
|
29
|
+
'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'
|
|
30
30
|
"
|
|
31
31
|
:aria-label="t('common.hintAria', { label: resolvedLabel })"
|
|
32
32
|
>
|
|
33
|
-
|
|
33
|
+
<svg
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
fill="none"
|
|
36
|
+
stroke="currentColor"
|
|
37
|
+
stroke-width="2"
|
|
38
|
+
stroke-linecap="round"
|
|
39
|
+
stroke-linejoin="round"
|
|
40
|
+
class="h-3.5 w-3.5"
|
|
41
|
+
aria-hidden="true"
|
|
42
|
+
>
|
|
43
|
+
<circle cx="12" cy="12" r="10" />
|
|
44
|
+
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
|
|
45
|
+
<path d="M12 17h.01" />
|
|
46
|
+
</svg>
|
|
34
47
|
</button>
|
|
35
48
|
</Tooltip>
|
|
36
49
|
</template>
|
|
@@ -141,7 +141,12 @@ function onPosterFileChange(e: Event) {
|
|
|
141
141
|
>
|
|
142
142
|
<ConfigFieldGroup :title="t('panel.config.groupAudioSource')">
|
|
143
143
|
<label class="block space-y-1" data-config-field="audioRemoteUrl">
|
|
144
|
-
<div
|
|
144
|
+
<div class="flex items-center gap-1">
|
|
145
|
+
{{ t("panel.config.audioUrl") }}
|
|
146
|
+
<ConfigHintIcon :label="t('panel.config.audioUrl')">
|
|
147
|
+
{{ t("panel.config.urlScopeHint") }}
|
|
148
|
+
</ConfigHintIcon>
|
|
149
|
+
</div>
|
|
145
150
|
<Input
|
|
146
151
|
size="small"
|
|
147
152
|
class="font-mono text-[11px]"
|
|
@@ -152,9 +157,7 @@ function onPosterFileChange(e: Event) {
|
|
|
152
157
|
audioRemoteUrl: v || undefined,
|
|
153
158
|
audioSrc: v || element.audioSrc,
|
|
154
159
|
})"
|
|
155
|
-
/>
|
|
156
|
-
<p class="text-[10px] text-gray-500">{{ t("panel.config.urlScopeHint") }}</p>
|
|
157
|
-
</label>
|
|
160
|
+
/> </label>
|
|
158
161
|
<div class="flex items-center gap-2">
|
|
159
162
|
<label
|
|
160
163
|
class="inline-flex cursor-pointer items-center rounded border border-gray-200 px-2 py-1 text-[11px] hover:bg-gray-50"
|
|
@@ -146,27 +146,33 @@ function onOptionJsonChange(v: string) {
|
|
|
146
146
|
</ConfigFieldGroup>
|
|
147
147
|
<ConfigFieldGroup :title="t('panel.config.groupData')">
|
|
148
148
|
<label class="block space-y-1" data-config-field="chart.labelsText">
|
|
149
|
-
<div
|
|
149
|
+
<div class="flex items-center gap-1">
|
|
150
|
+
{{ t("panel.config.labelsCsv") }}
|
|
151
|
+
<ConfigHintIcon :label="t('panel.config.labelsCsv')">
|
|
152
|
+
{{ t("panel.config.scopeTemplateHint") }}
|
|
153
|
+
</ConfigHintIcon>
|
|
154
|
+
</div>
|
|
150
155
|
<Input
|
|
151
156
|
size="small"
|
|
152
157
|
:value="getChartLabelsDisplayText(element.chart)"
|
|
153
158
|
:disabled="!isEditable"
|
|
154
159
|
:placeholder="t('panel.config.labelsCsvPlaceholder')"
|
|
155
160
|
@update:value="(v: string) => updateChart({ labelsText: v })"
|
|
156
|
-
/>
|
|
157
|
-
<p class="text-[10px] text-gray-500">{{ t("panel.config.scopeTemplateHint") }}</p>
|
|
158
|
-
</label>
|
|
161
|
+
/> </label>
|
|
159
162
|
<label class="block space-y-1" data-config-field="chart.valuesText">
|
|
160
|
-
<div
|
|
163
|
+
<div class="flex items-center gap-1">
|
|
164
|
+
{{ t("panel.config.valuesCsv") }}
|
|
165
|
+
<ConfigHintIcon :label="t('panel.config.valuesCsv')">
|
|
166
|
+
{{ t("panel.config.scopeTemplateHint") }}
|
|
167
|
+
</ConfigHintIcon>
|
|
168
|
+
</div>
|
|
161
169
|
<Input
|
|
162
170
|
size="small"
|
|
163
171
|
:value="getChartValuesDisplayText(element.chart)"
|
|
164
172
|
:disabled="!isEditable"
|
|
165
173
|
:placeholder="t('panel.config.valuesCsvPlaceholder')"
|
|
166
174
|
@update:value="(v: string) => updateChart({ valuesText: v })"
|
|
167
|
-
/>
|
|
168
|
-
<p class="text-[10px] text-gray-500">{{ t("panel.config.scopeTemplateHint") }}</p>
|
|
169
|
-
</label>
|
|
175
|
+
/> </label>
|
|
170
176
|
</ConfigFieldGroup>
|
|
171
177
|
|
|
172
178
|
<ConfigFieldGroup
|
|
@@ -74,7 +74,8 @@ function onFileChange(e: Event) {
|
|
|
74
74
|
<div class="flex items-center gap-1">
|
|
75
75
|
<span>{{ t("panel.config.imageUrlCss") }}</span>
|
|
76
76
|
<ConfigHintIcon :label="t('panel.config.imageUrlHintLabel')">
|
|
77
|
-
{{ t("panel.config.imageUrlHint") }}
|
|
77
|
+
<p>{{ t("panel.config.imageUrlHint") }}</p>
|
|
78
|
+
<p class="mt-1">{{ t("panel.config.urlScopeHint") }}</p>
|
|
78
79
|
</ConfigHintIcon>
|
|
79
80
|
</div>
|
|
80
81
|
<Input
|
|
@@ -85,7 +86,6 @@ function onFileChange(e: Event) {
|
|
|
85
86
|
:placeholder="t('panel.config.urlScopePlaceholder')"
|
|
86
87
|
@update:value="(v: string) => patchStyle(patchBackgroundImageFromInput(String(v ?? '')))"
|
|
87
88
|
/>
|
|
88
|
-
<p class="text-[10px] text-gray-500">{{ t("panel.config.urlScopeHint") }}</p>
|
|
89
89
|
</label>
|
|
90
90
|
<div class="flex items-center gap-2">
|
|
91
91
|
<label
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type ViewportOverflowMode,
|
|
11
11
|
} from "../../utils/viewportPlacement";
|
|
12
12
|
import { patchBackgroundImageFromInput } from "../../utils/background-image-style";
|
|
13
|
+
import ConfigHintIcon from "../ConfigHintIcon.vue";
|
|
13
14
|
|
|
14
15
|
const { t, locale } = useI18n();
|
|
15
16
|
const props = defineProps<{
|
|
@@ -434,7 +435,12 @@ function setNodeCardExpanded(id: string, open: boolean) {
|
|
|
434
435
|
|
|
435
436
|
<template v-if="el.materialType === 'image'">
|
|
436
437
|
<label class="block space-y-1" data-config-field="style.backgroundImageRemoteUrl">
|
|
437
|
-
<div
|
|
438
|
+
<div class="flex items-center gap-1">
|
|
439
|
+
{{ t("panel.config.backgroundImageUrl") }}
|
|
440
|
+
<ConfigHintIcon :label="t('panel.config.backgroundImageUrl')">
|
|
441
|
+
{{ t("panel.config.urlScopeHint") }}
|
|
442
|
+
</ConfigHintIcon>
|
|
443
|
+
</div>
|
|
438
444
|
<Input
|
|
439
445
|
size="small"
|
|
440
446
|
class="font-mono text-[11px]"
|
|
@@ -447,7 +453,6 @@ function setNodeCardExpanded(id: string, open: boolean) {
|
|
|
447
453
|
},
|
|
448
454
|
})"
|
|
449
455
|
/>
|
|
450
|
-
<p class="text-[10px] text-gray-500">{{ t("panel.config.urlScopeHint") }}</p>
|
|
451
456
|
</label>
|
|
452
457
|
<label class="block space-y-1">
|
|
453
458
|
<div>{{ t("panel.config.objectFit") }}</div>
|
|
@@ -344,6 +344,7 @@ function onTagHtmlInput(modelId: string, objectName: string, event: Event) {
|
|
|
344
344
|
@update:open="emit('update:open', $event)"
|
|
345
345
|
>
|
|
346
346
|
<ConfigFieldGroup :title="t('panel.config.groupScene3dPurpose')">
|
|
347
|
+
<template #hint>{{ t("panel.config.scene3dPurposeHint") }}</template>
|
|
347
348
|
<Select
|
|
348
349
|
size="small"
|
|
349
350
|
class="w-full"
|
|
@@ -356,7 +357,6 @@ function onTagHtmlInput(modelId: string, objectName: string, event: Event) {
|
|
|
356
357
|
]"
|
|
357
358
|
@update:value="(v) => patch(purposePresetPatch(String(v) as Scene3dPurpose))"
|
|
358
359
|
/>
|
|
359
|
-
<div class="text-[10px] text-gray-500">{{ t("panel.config.scene3dPurposeHint") }}</div>
|
|
360
360
|
</ConfigFieldGroup>
|
|
361
361
|
|
|
362
362
|
<ConfigFieldGroup :title="t('panel.config.groupScene3dModels')">
|
|
@@ -374,7 +374,12 @@ function onTagHtmlInput(modelId: string, objectName: string, event: Event) {
|
|
|
374
374
|
/>
|
|
375
375
|
</div>
|
|
376
376
|
<label class="block space-y-1" data-config-field="scene3d.modelUrl">
|
|
377
|
-
<div class="text-[10px] text-gray-500">
|
|
377
|
+
<div class="flex items-center gap-1 text-[10px] text-gray-500">
|
|
378
|
+
{{ t("panel.config.scene3dModelUrl") }}
|
|
379
|
+
<ConfigHintIcon :label="t('panel.config.scene3dModelUrl')">
|
|
380
|
+
{{ t("panel.config.urlScopeHint") }}
|
|
381
|
+
</ConfigHintIcon>
|
|
382
|
+
</div>
|
|
378
383
|
<div class="flex gap-1.5">
|
|
379
384
|
<Input
|
|
380
385
|
size="small"
|
|
@@ -392,7 +397,6 @@ function onTagHtmlInput(modelId: string, objectName: string, event: Event) {
|
|
|
392
397
|
{{ t("common.add") }}
|
|
393
398
|
</Button>
|
|
394
399
|
</div>
|
|
395
|
-
<p class="text-[10px] text-gray-500">{{ t("panel.config.urlScopeHint") }}</p>
|
|
396
400
|
</label>
|
|
397
401
|
<div v-if="config.models.length === 0" class="text-[11px] text-gray-500">
|
|
398
402
|
{{ t("panel.config.scene3dNoModels") }}
|
|
@@ -477,7 +481,7 @@ function onTagHtmlInput(modelId: string, objectName: string, event: Event) {
|
|
|
477
481
|
</ConfigFieldGroup>
|
|
478
482
|
|
|
479
483
|
<ConfigFieldGroup :title="t('panel.config.groupScene3dCamera')">
|
|
480
|
-
<
|
|
484
|
+
<template #hint>{{ t("panel.config.scene3dCameraEditDesc") }}</template>
|
|
481
485
|
<div class="grid grid-cols-3 gap-1">
|
|
482
486
|
<Input
|
|
483
487
|
v-for="(axis, index) in ['x', 'y', 'z']"
|
|
@@ -9,6 +9,7 @@ import { getPanelMessages } from "../../constants/messages";
|
|
|
9
9
|
import ConfigColorField from "./ConfigColorField.vue";
|
|
10
10
|
import ConfigFieldGroup from "./ConfigFieldGroup.vue";
|
|
11
11
|
import ConfigSection from "./ConfigSection.vue";
|
|
12
|
+
import ConfigHintIcon from "../ConfigHintIcon.vue";
|
|
12
13
|
|
|
13
14
|
const { t, locale } = useI18n();
|
|
14
15
|
const msgs = () => getPanelMessages(t);
|
|
@@ -89,7 +90,12 @@ function onFileChange(e: Event) {
|
|
|
89
90
|
:default-open="false"
|
|
90
91
|
>
|
|
91
92
|
<label class="block space-y-1" data-config-field="style.backgroundImage">
|
|
92
|
-
<div
|
|
93
|
+
<div class="flex items-center gap-1">
|
|
94
|
+
{{ t("panel.config.backgroundImage") }}
|
|
95
|
+
<ConfigHintIcon :label="t('panel.config.backgroundImage')">
|
|
96
|
+
{{ t("panel.config.urlScopeHint") }}
|
|
97
|
+
</ConfigHintIcon>
|
|
98
|
+
</div>
|
|
93
99
|
<Input
|
|
94
100
|
size="small"
|
|
95
101
|
class="font-mono text-[11px]"
|
|
@@ -97,9 +103,7 @@ function onFileChange(e: Event) {
|
|
|
97
103
|
:disabled="!isEditable"
|
|
98
104
|
:placeholder="t('panel.config.urlScopePlaceholder')"
|
|
99
105
|
@update:value="(v: string) => patchStyle(patchBackgroundImageFromInput(String(v ?? '')))"
|
|
100
|
-
/>
|
|
101
|
-
<p class="text-[10px] text-gray-500">{{ t("panel.config.urlScopeHint") }}</p>
|
|
102
|
-
</label>
|
|
106
|
+
/> </label>
|
|
103
107
|
<div class="flex items-center gap-2">
|
|
104
108
|
<label
|
|
105
109
|
class="inline-flex cursor-pointer items-center rounded border border-gray-200 px-2 py-1 text-[11px] hover:bg-gray-50"
|
|
@@ -7,6 +7,7 @@ import { cssTextLineHeight, cssTextAlignStyle } from "../../utils/panelElementDe
|
|
|
7
7
|
import ConfigColorField from "./ConfigColorField.vue";
|
|
8
8
|
import ConfigFieldGroup from "./ConfigFieldGroup.vue";
|
|
9
9
|
import ConfigSection from "./ConfigSection.vue";
|
|
10
|
+
import ConfigHintIcon from "../ConfigHintIcon.vue";
|
|
10
11
|
|
|
11
12
|
const { t, locale } = useI18n();
|
|
12
13
|
const props = defineProps<{
|
|
@@ -106,7 +107,12 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
|
|
|
106
107
|
}"
|
|
107
108
|
/>
|
|
108
109
|
<label class="block space-y-1">
|
|
109
|
-
<div
|
|
110
|
+
<div class="flex items-center gap-1">
|
|
111
|
+
{{ t("panel.config.textHtmlSource") }}
|
|
112
|
+
<ConfigHintIcon :label="t('panel.config.textHtmlSource')">
|
|
113
|
+
{{ t("panel.config.scopeTemplateHint") }}
|
|
114
|
+
</ConfigHintIcon>
|
|
115
|
+
</div>
|
|
110
116
|
<textarea
|
|
111
117
|
data-config-field="textHtml"
|
|
112
118
|
class="w-full rounded border border-gray-200 bg-white px-2 py-1.5 font-mono text-[11px] leading-relaxed outline-none"
|
|
@@ -116,9 +122,7 @@ watch(() => [props.element.id, props.element.textHtml], syncEditorHtml);
|
|
|
116
122
|
:disabled="!isEditable"
|
|
117
123
|
:placeholder="t('panel.config.textHtmlSourcePlaceholder')"
|
|
118
124
|
@input="(e) => patch({ textHtml: (e.target as HTMLTextAreaElement).value || '<p><br/></p>' })"
|
|
119
|
-
/>
|
|
120
|
-
<p class="text-[10px] text-gray-500">{{ t("panel.config.scopeTemplateHint") }}</p>
|
|
121
|
-
</label>
|
|
125
|
+
/> </label>
|
|
122
126
|
</ConfigFieldGroup>
|
|
123
127
|
<ConfigFieldGroup :title="t('panel.config.groupTextStyle')">
|
|
124
128
|
<label class="block space-y-1">
|
|
@@ -7,6 +7,7 @@ import { readFileAsDataUrl, uploadFileToRemote } from "./shared";
|
|
|
7
7
|
import { getPanelMessages } from "../../constants/messages";
|
|
8
8
|
import ConfigFieldGroup from "./ConfigFieldGroup.vue";
|
|
9
9
|
import ConfigSection from "./ConfigSection.vue";
|
|
10
|
+
import ConfigHintIcon from "../ConfigHintIcon.vue";
|
|
10
11
|
|
|
11
12
|
const { t, locale } = useI18n();
|
|
12
13
|
const msgs = () => getPanelMessages(t);
|
|
@@ -67,7 +68,12 @@ function onVideoFileChange(e: Event) {
|
|
|
67
68
|
>
|
|
68
69
|
<ConfigFieldGroup :title="t('panel.config.groupVideoSource')">
|
|
69
70
|
<label class="block space-y-1" data-config-field="videoRemoteUrl">
|
|
70
|
-
<div
|
|
71
|
+
<div class="flex items-center gap-1">
|
|
72
|
+
{{ t("panel.config.videoUrl") }}
|
|
73
|
+
<ConfigHintIcon :label="t('panel.config.videoUrl')">
|
|
74
|
+
{{ t("panel.config.urlScopeHint") }}
|
|
75
|
+
</ConfigHintIcon>
|
|
76
|
+
</div>
|
|
71
77
|
<Input
|
|
72
78
|
size="small"
|
|
73
79
|
class="font-mono text-[11px]"
|
|
@@ -78,9 +84,7 @@ function onVideoFileChange(e: Event) {
|
|
|
78
84
|
videoRemoteUrl: v || undefined,
|
|
79
85
|
videoSrc: v || element.videoSrc,
|
|
80
86
|
})"
|
|
81
|
-
/>
|
|
82
|
-
<p class="text-[10px] text-gray-500">{{ t("panel.config.urlScopeHint") }}</p>
|
|
83
|
-
</label>
|
|
87
|
+
/> </label>
|
|
84
88
|
<div class="flex items-center gap-2">
|
|
85
89
|
<label
|
|
86
90
|
class="inline-flex cursor-pointer items-center rounded border border-gray-200 px-2 py-1 text-[11px] hover:bg-gray-50"
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { useI18n } from "@arronqzy/i18n/vue";
|
|
14
14
|
import { Button, Input, Select } from "ant-design-vue";
|
|
15
15
|
import { computed } from "vue";
|
|
16
|
+
import ConfigHintIcon from "../ConfigHintIcon.vue";
|
|
16
17
|
|
|
17
18
|
const props = withDefaults(
|
|
18
19
|
defineProps<{
|
|
@@ -92,7 +93,12 @@ function patchLeaf(index: number, patch: Partial<ConditionLeaf>) {
|
|
|
92
93
|
<div class="space-y-2">
|
|
93
94
|
<div class="flex items-center justify-between gap-2">
|
|
94
95
|
<label class="min-w-0 flex-1 space-y-1">
|
|
95
|
-
<div class="text-[11px] text-gray-500">
|
|
96
|
+
<div class="flex items-center gap-1 text-[11px] text-gray-500">
|
|
97
|
+
{{ t("panel.config.tableConditionLogic") }}
|
|
98
|
+
<ConfigHintIcon :label="t('panel.config.tableConditionLogic')">
|
|
99
|
+
{{ t("panel.config.tableConditionLogicHint") }}
|
|
100
|
+
</ConfigHintIcon>
|
|
101
|
+
</div>
|
|
96
102
|
<Select
|
|
97
103
|
size="small"
|
|
98
104
|
class="w-full"
|
|
@@ -114,7 +120,6 @@ function patchLeaf(index: number, patch: Partial<ConditionLeaf>) {
|
|
|
114
120
|
{{ t("panel.config.tableConditionAddItem") }}
|
|
115
121
|
</Button>
|
|
116
122
|
</div>
|
|
117
|
-
<div class="text-[10px] text-gray-500">{{ t("panel.config.tableConditionLogicHint") }}</div>
|
|
118
123
|
<div
|
|
119
124
|
v-for="(item, index) in items"
|
|
120
125
|
:key="index"
|
|
@@ -504,10 +504,16 @@ function patchProgressRuleWhen(ruleIndex: number, when: Condition) {
|
|
|
504
504
|
</label>
|
|
505
505
|
<details class="rounded-md border border-gray-200 bg-gray-50/60 px-2 py-1.5">
|
|
506
506
|
<summary class="cursor-pointer text-[11px] font-medium text-gray-600">
|
|
507
|
-
|
|
507
|
+
<span class="inline-flex items-center gap-1 align-middle">
|
|
508
|
+
{{ t("panel.config.tableAdvancedData") }}
|
|
509
|
+
<span class="inline-flex" @click.prevent>
|
|
510
|
+
<ConfigHintIcon :label="t('panel.config.tableAdvancedData')">
|
|
511
|
+
{{ t("panel.config.tableAdvancedDataHint") }}
|
|
512
|
+
</ConfigHintIcon>
|
|
513
|
+
</span>
|
|
514
|
+
</span>
|
|
508
515
|
</summary>
|
|
509
516
|
<div class="mt-2 space-y-2 border-t border-gray-100 pt-2">
|
|
510
|
-
<p class="text-[10px] text-gray-500">{{ t("panel.config.tableAdvancedDataHint") }}</p>
|
|
511
517
|
<div class="grid grid-cols-2 gap-2">
|
|
512
518
|
<label class="block space-y-1.5">
|
|
513
519
|
<div class="flex items-center gap-1">
|
|
@@ -1338,7 +1344,7 @@ function patchProgressRuleWhen(ruleIndex: number, when: Condition) {
|
|
|
1338
1344
|
<Input
|
|
1339
1345
|
size="small"
|
|
1340
1346
|
:value="String(selectedColumn.column.widgetProps?.progressStatic ?? '')"
|
|
1341
|
-
placeholder="
|
|
1347
|
+
:placeholder="t('panel.config.tableProgressStaticPlaceholder')"
|
|
1342
1348
|
:disabled="!isEditable"
|
|
1343
1349
|
@update:value="
|
|
1344
1350
|
(v: unknown) =>
|
|
@@ -1756,7 +1762,7 @@ function patchProgressRuleWhen(ruleIndex: number, when: Condition) {
|
|
|
1756
1762
|
size="small"
|
|
1757
1763
|
:value="selectedColumn.column.displayTemplate ?? ''"
|
|
1758
1764
|
:disabled="!isEditable"
|
|
1759
|
-
placeholder="
|
|
1765
|
+
:placeholder="t('panel.config.tableDisplayTemplatePlaceholder')"
|
|
1760
1766
|
@update:value="
|
|
1761
1767
|
(v: unknown) => {
|
|
1762
1768
|
const s = asSelectString(v).trim();
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
getDefaultSizeByMaterial,
|
|
23
23
|
getDefaultTableConfig,
|
|
24
24
|
getDefaultTextContent,
|
|
25
|
+
localizeLayers,
|
|
26
|
+
localizePanelElement,
|
|
25
27
|
normalizePrimaryLayer,
|
|
26
28
|
randomId,
|
|
27
29
|
} from "../utils/panelElementDefaults";
|
|
@@ -122,7 +124,8 @@ export function usePanelElements() {
|
|
|
122
124
|
const layers = computed(() => {
|
|
123
125
|
const rawLayers =
|
|
124
126
|
(stateRef.value.variables?.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
|
|
125
|
-
|
|
127
|
+
void locale.value;
|
|
128
|
+
return localizeLayers(normalizePrimaryLayer(rawLayers), t);
|
|
126
129
|
});
|
|
127
130
|
|
|
128
131
|
const activeLayerId = computed(
|
|
@@ -133,13 +136,26 @@ export function usePanelElements() {
|
|
|
133
136
|
const allElementsCache = shallowRef<PanelElement[]>([]);
|
|
134
137
|
const elementsCache = shallowRef<PanelElement[]>([]);
|
|
135
138
|
|
|
139
|
+
let localizedCache = { locale: locale.value, map: new WeakMap<PanelElement, PanelElement>() };
|
|
140
|
+
|
|
136
141
|
const allElements = computed(() => {
|
|
137
142
|
const next = materializePanelElements(
|
|
138
143
|
stateRef.value.root.children,
|
|
139
144
|
allElementsCache.value
|
|
140
145
|
);
|
|
141
146
|
allElementsCache.value = next;
|
|
142
|
-
|
|
147
|
+
if (localizedCache.locale !== locale.value) {
|
|
148
|
+
localizedCache = { locale: locale.value, map: new WeakMap() };
|
|
149
|
+
}
|
|
150
|
+
const { map } = localizedCache;
|
|
151
|
+
return next.map((el) => {
|
|
152
|
+
let localized = map.get(el);
|
|
153
|
+
if (!localized) {
|
|
154
|
+
localized = localizePanelElement(el, t);
|
|
155
|
+
map.set(el, localized);
|
|
156
|
+
}
|
|
157
|
+
return localized;
|
|
158
|
+
});
|
|
143
159
|
});
|
|
144
160
|
|
|
145
161
|
const elements = computed(() => {
|
|
@@ -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 { createDefaultTableConfig, type PanelTableConfig } from "@arronqzy/view-table";
|
|
4
4
|
import { createDefaultScene3dConfig } from "@arronqzy/view-scene3d";
|
|
5
5
|
import type { PanelChartConfig, PanelLayer, PanelElement } from "../types";
|
|
@@ -35,6 +35,52 @@ export function getDefaultNodeName(
|
|
|
35
35
|
return key ? t(key) : materialType;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
const DEFAULT_LAYER_NAME_KEYS = [
|
|
39
|
+
"panel.defaults.layer1",
|
|
40
|
+
"panel.defaults.layerN",
|
|
41
|
+
"panel.defaults.mappingLayerN",
|
|
42
|
+
"panel.defaults.layerRandom",
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/** Persisted default layer names ("图层1" / "Layer 2") follow the current locale. */
|
|
46
|
+
export function localizeLayerName(name: string, t: TranslateFn): string {
|
|
47
|
+
return relocalizeMessage(name, DEFAULT_LAYER_NAME_KEYS, t);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function localizeLayers(layers: PanelLayer[], t: TranslateFn): PanelLayer[] {
|
|
51
|
+
return layers.map((layer) => {
|
|
52
|
+
const name = localizeLayerName(layer.name, t);
|
|
53
|
+
return name === layer.name ? layer : { ...layer, name };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const DEFAULT_TEXT_HTML_RE = /^<p>(.*)<\/p>$/s;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Default node name, chart title and text content are stored in the creation locale;
|
|
61
|
+
* re-render them in the current one. Returns the same object when nothing changes.
|
|
62
|
+
*/
|
|
63
|
+
export function localizePanelElement(element: PanelElement, t: TranslateFn): PanelElement {
|
|
64
|
+
const key = DEFAULT_NODE_NAME_KEYS[element.materialType ?? ""];
|
|
65
|
+
if (!key) return element;
|
|
66
|
+
const name = element.name ? relocalizeMessage(element.name, key, t) : element.name;
|
|
67
|
+
const title = element.chart?.title ? relocalizeMessage(element.chart.title, key, t) : undefined;
|
|
68
|
+
let textHtml = element.textHtml;
|
|
69
|
+
const textInner = textHtml ? DEFAULT_TEXT_HTML_RE.exec(textHtml)?.[1] : undefined;
|
|
70
|
+
if (textInner) {
|
|
71
|
+
const localized = relocalizeMessage(textInner, "panel.defaults.doubleClickTextHtml", t);
|
|
72
|
+
if (localized !== textInner) textHtml = `<p>${localized}</p>`;
|
|
73
|
+
}
|
|
74
|
+
const titleChanged = title !== undefined && title !== element.chart?.title;
|
|
75
|
+
if (name === element.name && !titleChanged && textHtml === element.textHtml) return element;
|
|
76
|
+
return {
|
|
77
|
+
...element,
|
|
78
|
+
name,
|
|
79
|
+
textHtml,
|
|
80
|
+
...(titleChanged && element.chart ? { chart: { ...element.chart, title } } : null),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
38
84
|
export function getDefaultLayer(t: TranslateFn = tForLocale("zh-CN")): PanelLayer {
|
|
39
85
|
return {
|
|
40
86
|
id: DEFAULT_LAYER_ID,
|