@arronqzy/vue-view 0.1.4 → 0.1.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/package.json +6 -5
- package/src/panel/VueViewOnlinePreview.vue +55 -19
- package/src/panel/VueViewPanel.vue +123 -66
- package/src/panel/components/BusyOverlay.vue +24 -0
- package/src/panel/components/ConfigHintIcon.vue +10 -10
- package/src/panel/components/ElementsLayer.vue +51 -8
- package/src/panel/components/MaterialPreview.vue +8 -0
- package/src/panel/components/PanelConfigSidebar.vue +32 -20
- package/src/panel/components/WorkspaceConfigSidebar.vue +11 -0
- package/src/panel/components/WorkspaceProjectNav.vue +36 -8
- package/src/panel/components/config/ConfigColorField.vue +33 -9
- package/src/panel/components/config/ConfigFieldGroup.vue +35 -6
- package/src/panel/components/config/PanelConfigAudioSection.vue +37 -19
- package/src/panel/components/config/PanelConfigChartSection.vue +101 -84
- package/src/panel/components/config/PanelConfigGeometrySection.vue +10 -2
- package/src/panel/components/config/PanelConfigImageSection.vue +18 -10
- package/src/panel/components/config/PanelConfigNodeInfo.vue +21 -12
- package/src/panel/components/config/PanelConfigReferenceSection.vue +5 -1
- package/src/panel/components/config/PanelConfigScene3dSection.vue +505 -0
- package/src/panel/components/config/PanelConfigStyleSections.vue +19 -11
- package/src/panel/components/config/PanelConfigTextSection.vue +25 -7
- package/src/panel/components/config/PanelConfigVideoSection.vue +13 -9
- package/src/panel/components/config/shared.ts +16 -21
- package/src/panel/components/elementsLayerNodes.ts +64 -59
- package/src/panel/components/materialSidebarData.ts +2 -0
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.vue +1 -1
- package/src/panel/components/table/PanelConfigTableSection.vue +39 -25
- package/src/panel/components/table/TableNodeContent.vue +36 -15
- package/src/panel/hooks/usePanelElements.ts +13 -1
- package/src/panel/hooks/useWorkspaceProjects.ts +128 -57
- package/src/panel/library/workspace-project-cache.ts +7 -2
- package/src/panel/library/workspace-project-db.ts +29 -11
- package/src/panel/library/workspace-snapshot.ts +21 -4
- package/src/panel/scope/view-scope-store.ts +8 -0
- package/src/panel/types.ts +4 -2
- package/src/panel/utils/async-work.ts +153 -0
- package/src/panel/utils/chartOptionBuilder.ts +7 -1
- package/src/panel/utils/gridPlacement.ts +29 -0
- package/src/panel/utils/outputScale.ts +56 -0
- package/src/panel/utils/panelElementDefaults.ts +37 -2
- package/src/panel/utils/scope-autocomplete.ts +0 -114
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useI18n } from "@arronqzy/i18n/vue";
|
|
3
|
-
import { computed, onMounted, ref, watch } from "vue";
|
|
3
|
+
import { computed, onMounted, provide, ref, watch } from "vue";
|
|
4
4
|
import { Empty, Input } from "ant-design-vue";
|
|
5
5
|
import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../types";
|
|
6
6
|
import { CHART_TYPES } from "../utils/chartOptionBuilder";
|
|
@@ -20,6 +20,7 @@ import PanelConfigTextSection from "./config/PanelConfigTextSection.vue";
|
|
|
20
20
|
import PanelConfigAudioSection from "./config/PanelConfigAudioSection.vue";
|
|
21
21
|
import PanelConfigVideoSection from "./config/PanelConfigVideoSection.vue";
|
|
22
22
|
import PanelConfigGeometrySection from "./config/PanelConfigGeometrySection.vue";
|
|
23
|
+
import PanelConfigScene3dSection from "./config/PanelConfigScene3dSection.vue";
|
|
23
24
|
import PanelConfigGridSection from "./config/PanelConfigGridSection.vue";
|
|
24
25
|
import PanelConfigGridChildSpan from "./config/PanelConfigGridChildSpan.vue";
|
|
25
26
|
import PanelConfigReferenceSection from "./config/PanelConfigReferenceSection.vue";
|
|
@@ -61,8 +62,8 @@ const sidebarScrollRef = ref<HTMLElement | null>(null);
|
|
|
61
62
|
|
|
62
63
|
const expandedSections = ref<Record<string, boolean>>({
|
|
63
64
|
nodeInfo: true,
|
|
64
|
-
styleBackground:
|
|
65
|
-
styleBorder:
|
|
65
|
+
styleBackground: false,
|
|
66
|
+
styleBorder: false,
|
|
66
67
|
chartBasic: true,
|
|
67
68
|
chartAdvanced: false,
|
|
68
69
|
textConfig: true,
|
|
@@ -123,6 +124,7 @@ const scopeWarnings = computed(() => {
|
|
|
123
124
|
const materialType = computed(() => props.selectedElement?.materialType ?? "");
|
|
124
125
|
|
|
125
126
|
const forceOpenSections = computed(() => hasSearch.value);
|
|
127
|
+
provide("configHasSearch", hasSearch);
|
|
126
128
|
|
|
127
129
|
function isSectionExpanded(key: string, defaultValue = true) {
|
|
128
130
|
return expandedSections.value[key] ?? defaultValue;
|
|
@@ -259,23 +261,6 @@ watch(
|
|
|
259
261
|
@update:open="(v) => setSectionExpanded('nodeInfo', v)"
|
|
260
262
|
/>
|
|
261
263
|
|
|
262
|
-
<PanelConfigStyleSections
|
|
263
|
-
v-if="
|
|
264
|
-
shouldShowSection('styleBackground', t('panel.config.sectionStyleBackground'), [t('panel.config.backgroundColor'), 'background']) ||
|
|
265
|
-
shouldShowSection('styleBorder', t('panel.config.sectionStyleBorder'), [t('panel.config.searchKwBorder'), 'border'])
|
|
266
|
-
"
|
|
267
|
-
:element="selectedElement"
|
|
268
|
-
:is-editable="isNodeEditable"
|
|
269
|
-
:show-background="shouldShowSection('styleBackground', t('panel.config.sectionStyleBackground'), [t('panel.config.backgroundColor'), 'background'])"
|
|
270
|
-
:show-border="shouldShowSection('styleBorder', t('panel.config.sectionStyleBorder'), [t('panel.config.searchKwBorder'), 'border'])"
|
|
271
|
-
:background-open="isSectionExpanded('styleBackground')"
|
|
272
|
-
:border-open="isSectionExpanded('styleBorder')"
|
|
273
|
-
:force-open="forceOpenSections"
|
|
274
|
-
:update-element="updateElement"
|
|
275
|
-
@update:background-open="(v) => setSectionExpanded('styleBackground', v)"
|
|
276
|
-
@update:border-open="(v) => setSectionExpanded('styleBorder', v)"
|
|
277
|
-
/>
|
|
278
|
-
|
|
279
264
|
<PanelConfigChartSection
|
|
280
265
|
v-if="isChartElement && shouldShowSection('chartBasic', t('panel.config.sectionChartBasic'), [t('panel.material.charts'), 'tooltip'])"
|
|
281
266
|
:element="selectedElement"
|
|
@@ -349,6 +334,16 @@ watch(
|
|
|
349
334
|
@update:open="(v) => setSectionExpanded('geometryConfig', v)"
|
|
350
335
|
/>
|
|
351
336
|
|
|
337
|
+
<PanelConfigScene3dSection
|
|
338
|
+
v-if="materialType === 'scene3d' && shouldShowSection('scene3dConfig', t('panel.config.sectionScene3d'), [t('panel.material.scene3d'), '3d', 'glb', t('panel.config.groupScene3dCamera')])"
|
|
339
|
+
:element="selectedElement"
|
|
340
|
+
:is-editable="isNodeEditable"
|
|
341
|
+
:open="isSectionExpanded('scene3dConfig')"
|
|
342
|
+
:force-open="forceOpenSections"
|
|
343
|
+
:update-element="updateElement"
|
|
344
|
+
@update:open="(v) => setSectionExpanded('scene3dConfig', v)"
|
|
345
|
+
/>
|
|
346
|
+
|
|
352
347
|
<PanelConfigGridSection
|
|
353
348
|
v-if="materialType === 'grid' && shouldShowSection('gridConfig', t('panel.config.sectionGrid'), [t('panel.config.searchKwGrid'), t('panel.config.rowsShort'), t('panel.config.colsShort'), t('panel.config.searchKwGap')])"
|
|
354
349
|
:element="selectedElement"
|
|
@@ -381,6 +376,23 @@ watch(
|
|
|
381
376
|
@update:open="(v) => setSectionExpanded('reference', v)"
|
|
382
377
|
/>
|
|
383
378
|
|
|
379
|
+
<PanelConfigStyleSections
|
|
380
|
+
v-if="
|
|
381
|
+
shouldShowSection('styleBackground', t('panel.config.sectionStyleBackground'), [t('panel.config.backgroundColor'), 'background']) ||
|
|
382
|
+
shouldShowSection('styleBorder', t('panel.config.sectionStyleBorder'), [t('panel.config.searchKwBorder'), 'border'])
|
|
383
|
+
"
|
|
384
|
+
:element="selectedElement"
|
|
385
|
+
:is-editable="isNodeEditable"
|
|
386
|
+
:show-background="shouldShowSection('styleBackground', t('panel.config.sectionStyleBackground'), [t('panel.config.backgroundColor'), 'background'])"
|
|
387
|
+
:show-border="shouldShowSection('styleBorder', t('panel.config.sectionStyleBorder'), [t('panel.config.searchKwBorder'), 'border'])"
|
|
388
|
+
:background-open="isSectionExpanded('styleBackground')"
|
|
389
|
+
:border-open="isSectionExpanded('styleBorder')"
|
|
390
|
+
:force-open="forceOpenSections"
|
|
391
|
+
:update-element="updateElement"
|
|
392
|
+
@update:background-open="(v) => setSectionExpanded('styleBackground', v)"
|
|
393
|
+
@update:border-open="(v) => setSectionExpanded('styleBorder', v)"
|
|
394
|
+
/>
|
|
395
|
+
|
|
384
396
|
<div
|
|
385
397
|
v-if="!isChartElement && materialType && !['text', 'table', 'audio', 'video', 'image', 'geometry', 'grid', 'reference'].includes(materialType)"
|
|
386
398
|
class="text-xs leading-6 text-gray-500"
|
|
@@ -5,6 +5,7 @@ import { Empty } from "ant-design-vue";
|
|
|
5
5
|
import {
|
|
6
6
|
BlueprintExecutionLogPanel,
|
|
7
7
|
BlueprintNodeConfigSidebar,
|
|
8
|
+
type BlueprintGraphEdge,
|
|
8
9
|
type BlueprintGraphNode,
|
|
9
10
|
type ExecutionLogSettings,
|
|
10
11
|
type ExecutionTraceEntry,
|
|
@@ -56,6 +57,9 @@ export type WorkspaceConfigSidebarProps = Omit<
|
|
|
56
57
|
configFocus: WorkspaceConfigFocus;
|
|
57
58
|
executionLog?: BlueprintExecutionLogViewProps;
|
|
58
59
|
selectedBlueprintNode: BlueprintGraphNode | null;
|
|
60
|
+
blueprintGraphNodes?: BlueprintGraphNode[];
|
|
61
|
+
blueprintGraphEdges?: BlueprintGraphEdge[];
|
|
62
|
+
blueprintTraceEntries?: ExecutionTraceEntry[];
|
|
59
63
|
allowFalseSignalPropagation?: boolean;
|
|
60
64
|
onUpdateAllowFalseSignalPropagation?: (value: boolean) => void;
|
|
61
65
|
onUpdateBlueprintNode: (
|
|
@@ -76,6 +80,7 @@ export type WorkspaceConfigSidebarProps = Omit<
|
|
|
76
80
|
| "jsonConfig"
|
|
77
81
|
| "logicConfig"
|
|
78
82
|
| "clockConfig"
|
|
83
|
+
| "eventConfig"
|
|
79
84
|
>
|
|
80
85
|
>
|
|
81
86
|
) => void;
|
|
@@ -90,6 +95,9 @@ const props = withDefaults(
|
|
|
90
95
|
{
|
|
91
96
|
allowFalseSignalPropagation: false,
|
|
92
97
|
blueprintLibraryOptions: () => [],
|
|
98
|
+
blueprintGraphNodes: () => [],
|
|
99
|
+
blueprintGraphEdges: () => [],
|
|
100
|
+
blueprintTraceEntries: () => [],
|
|
93
101
|
blueprintNodeOptions: () => [],
|
|
94
102
|
selectedElements: () => [],
|
|
95
103
|
}
|
|
@@ -143,6 +151,9 @@ const viewElementOptions = computed(() =>
|
|
|
143
151
|
<BlueprintNodeConfigSidebar
|
|
144
152
|
v-else
|
|
145
153
|
:node="selectedBlueprintNode"
|
|
154
|
+
:graph-nodes="blueprintGraphNodes"
|
|
155
|
+
:graph-edges="blueprintGraphEdges"
|
|
156
|
+
:trace-entries="blueprintTraceEntries.length ? blueprintTraceEntries : executionLog?.entries ?? []"
|
|
146
157
|
:allow-false-signal-propagation="allowFalseSignalPropagation"
|
|
147
158
|
:on-update-allow-false-signal-propagation="onUpdateAllowFalseSignalPropagation"
|
|
148
159
|
:view-element-options="viewElementOptions"
|
|
@@ -19,6 +19,7 @@ const props = defineProps<{
|
|
|
19
19
|
activeProjectId: string | null;
|
|
20
20
|
activeProjectName: string | null;
|
|
21
21
|
dirty: boolean;
|
|
22
|
+
previewingProjectIds?: string[];
|
|
22
23
|
}>();
|
|
23
24
|
|
|
24
25
|
const emit = defineEmits<{
|
|
@@ -76,8 +77,10 @@ const projectMenu = computed(() =>
|
|
|
76
77
|
default: () =>
|
|
77
78
|
props.projects.length === 0
|
|
78
79
|
? [h(Menu.Item, { key: "empty", disabled: true }, () => t("panel.workspace.noSavedWorkspaces"))]
|
|
79
|
-
: props.projects.map((project) =>
|
|
80
|
-
|
|
80
|
+
: props.projects.map((project) => {
|
|
81
|
+
const isCurrent = props.activeProjectId === project.id;
|
|
82
|
+
const isPreviewing = (props.previewingProjectIds ?? []).includes(project.id);
|
|
83
|
+
return h(
|
|
81
84
|
"div",
|
|
82
85
|
{ key: project.id, class: "px-1 py-0.5" },
|
|
83
86
|
h("div", { class: "flex items-center gap-1" }, [
|
|
@@ -86,16 +89,41 @@ const projectMenu = computed(() =>
|
|
|
86
89
|
{
|
|
87
90
|
type: "button",
|
|
88
91
|
class: [
|
|
89
|
-
"min-w-0 flex-1 rounded px-2 py-1.5 text-left text-xs hover:bg-
|
|
90
|
-
|
|
91
|
-
? "bg-
|
|
92
|
+
"min-w-0 flex-1 rounded px-2 py-1.5 text-left text-xs hover:bg-gray-100",
|
|
93
|
+
isCurrent
|
|
94
|
+
? "bg-blue-50 font-medium text-blue-700 ring-1 ring-inset ring-blue-300"
|
|
95
|
+
: "",
|
|
96
|
+
!isCurrent && isPreviewing
|
|
97
|
+
? "bg-sky-50 ring-1 ring-inset ring-sky-300"
|
|
92
98
|
: "",
|
|
93
99
|
].join(" "),
|
|
94
100
|
onClick: () =>
|
|
95
101
|
void runAction(() => emit("openProject", project.id)),
|
|
96
102
|
},
|
|
97
103
|
[
|
|
98
|
-
h("div", { class: "
|
|
104
|
+
h("div", { class: "flex items-center gap-1" }, [
|
|
105
|
+
h("div", { class: "min-w-0 flex-1 truncate" }, project.name),
|
|
106
|
+
isCurrent
|
|
107
|
+
? h(
|
|
108
|
+
"span",
|
|
109
|
+
{
|
|
110
|
+
class:
|
|
111
|
+
"shrink-0 rounded bg-blue-600 px-1 py-px text-[9px] leading-none text-white",
|
|
112
|
+
},
|
|
113
|
+
t("panel.workspace.currentBadge")
|
|
114
|
+
)
|
|
115
|
+
: null,
|
|
116
|
+
isPreviewing
|
|
117
|
+
? h(
|
|
118
|
+
"span",
|
|
119
|
+
{
|
|
120
|
+
class:
|
|
121
|
+
"shrink-0 rounded bg-sky-600 px-1 py-px text-[9px] leading-none text-white",
|
|
122
|
+
},
|
|
123
|
+
t("panel.workspace.previewingBadge")
|
|
124
|
+
)
|
|
125
|
+
: null,
|
|
126
|
+
]),
|
|
99
127
|
h(
|
|
100
128
|
"div",
|
|
101
129
|
{ class: "truncate text-[10px] text-muted-foreground" },
|
|
@@ -132,8 +160,8 @@ const projectMenu = computed(() =>
|
|
|
132
160
|
() => t("common.delete")
|
|
133
161
|
),
|
|
134
162
|
])
|
|
135
|
-
)
|
|
136
|
-
),
|
|
163
|
+
);
|
|
164
|
+
}),
|
|
137
165
|
}
|
|
138
166
|
)
|
|
139
167
|
);
|
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useI18n } from "@arronqzy/i18n/vue";
|
|
3
3
|
import { computed } from "vue";
|
|
4
|
-
import { Input } from "ant-design-vue";
|
|
4
|
+
import { Input, Switch } from "ant-design-vue";
|
|
5
|
+
import { isCssTransparent } from "@arronqzy/view-table";
|
|
5
6
|
|
|
6
|
-
const { t
|
|
7
|
-
const props =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
const { t } = useI18n();
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
label: string;
|
|
11
|
+
value: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
allowTransparent?: boolean;
|
|
14
|
+
fallbackHex?: string;
|
|
15
|
+
}>(),
|
|
16
|
+
{ allowTransparent: false, fallbackHex: "#ffffff" }
|
|
17
|
+
);
|
|
12
18
|
|
|
13
19
|
const emit = defineEmits<{
|
|
14
20
|
"update:value": [value: string];
|
|
15
21
|
}>();
|
|
16
22
|
|
|
23
|
+
const transparent = computed(
|
|
24
|
+
() => props.allowTransparent && isCssTransparent(props.value)
|
|
25
|
+
);
|
|
26
|
+
|
|
17
27
|
const pickerValue = computed(() =>
|
|
18
28
|
/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(props.value || "")
|
|
19
29
|
? props.value
|
|
20
|
-
: "#
|
|
30
|
+
: props.fallbackHex.startsWith("#")
|
|
31
|
+
? props.fallbackHex
|
|
32
|
+
: "#000000"
|
|
21
33
|
);
|
|
22
34
|
|
|
23
35
|
function onTextChange(v: string) {
|
|
@@ -28,12 +40,23 @@ function onColorInput(e: Event) {
|
|
|
28
40
|
const target = e.target as HTMLInputElement;
|
|
29
41
|
emit("update:value", target.value);
|
|
30
42
|
}
|
|
43
|
+
|
|
44
|
+
function onTransparentChange(v: unknown) {
|
|
45
|
+
emit("update:value", v === true ? "transparent" : props.fallbackHex);
|
|
46
|
+
}
|
|
31
47
|
</script>
|
|
32
48
|
|
|
33
49
|
<template>
|
|
34
50
|
<label class="block space-y-1.5">
|
|
35
51
|
<div class="text-[11px] text-gray-500">{{ label }}</div>
|
|
36
|
-
<div class="flex items-center gap-2">
|
|
52
|
+
<div v-if="allowTransparent" class="flex items-center gap-2">
|
|
53
|
+
<Switch size="small" :checked="transparent" :disabled="disabled" @update:checked="onTransparentChange" />
|
|
54
|
+
<span class="text-[11px] text-gray-600">{{ t("panel.config.tableBgTransparent") }}</span>
|
|
55
|
+
</div>
|
|
56
|
+
<div v-if="transparent" class="text-[10px] text-gray-500">
|
|
57
|
+
{{ t("panel.config.tableBgTransparentHint") }}
|
|
58
|
+
</div>
|
|
59
|
+
<div v-else class="flex items-center gap-2">
|
|
37
60
|
<Input
|
|
38
61
|
size="small"
|
|
39
62
|
:value="value"
|
|
@@ -52,3 +75,4 @@ function onColorInput(e: Event) {
|
|
|
52
75
|
</div>
|
|
53
76
|
</label>
|
|
54
77
|
</template>
|
|
78
|
+
|
|
@@ -1,20 +1,49 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed, inject, ref, type ComputedRef } from "vue";
|
|
2
3
|
import ConfigHintIcon from "../ConfigHintIcon.vue";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const props = withDefaults(
|
|
6
|
+
defineProps<{
|
|
7
|
+
title: string;
|
|
8
|
+
hint?: boolean;
|
|
9
|
+
collapsible?: boolean;
|
|
10
|
+
defaultOpen?: boolean;
|
|
11
|
+
}>(),
|
|
12
|
+
{ defaultOpen: true, collapsible: false }
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const hasSearch = inject<ComputedRef<boolean>>(
|
|
16
|
+
"configHasSearch",
|
|
17
|
+
computed(() => false)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const open = ref(props.defaultOpen);
|
|
21
|
+
const isOpen = computed(() => !props.collapsible || hasSearch.value || open.value);
|
|
22
|
+
|
|
23
|
+
function toggle() {
|
|
24
|
+
if (!props.collapsible || hasSearch.value) return;
|
|
25
|
+
open.value = !open.value;
|
|
26
|
+
}
|
|
8
27
|
</script>
|
|
9
28
|
|
|
10
29
|
<template>
|
|
11
30
|
<div class="space-y-2.5 rounded-lg border border-gray-200/80 bg-white/80 p-2.5">
|
|
12
|
-
<div class="flex items-center gap-1">
|
|
31
|
+
<div class="flex items-center gap-1.5">
|
|
32
|
+
<button
|
|
33
|
+
v-if="collapsible"
|
|
34
|
+
type="button"
|
|
35
|
+
class="flex h-5 w-5 items-center justify-center rounded text-[11px] hover:bg-gray-100"
|
|
36
|
+
@click="toggle"
|
|
37
|
+
>
|
|
38
|
+
{{ isOpen ? "▾" : "▸" }}
|
|
39
|
+
</button>
|
|
13
40
|
<div class="text-[11px] font-semibold text-gray-500">{{ title }}</div>
|
|
14
41
|
<ConfigHintIcon v-if="$slots.hint" :label="title">
|
|
15
42
|
<slot name="hint" />
|
|
16
43
|
</ConfigHintIcon>
|
|
17
44
|
</div>
|
|
18
|
-
<
|
|
45
|
+
<div v-show="isOpen" class="space-y-2.5">
|
|
46
|
+
<slot />
|
|
47
|
+
</div>
|
|
19
48
|
</div>
|
|
20
49
|
</template>
|
|
@@ -44,22 +44,30 @@ function patch(patch: Partial<PanelElement>) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
async function handleUploadAudioFile(file: File) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
try {
|
|
48
|
+
const base64 = await readFileAsDataUrl(file, msgs().readAudioFailed, "audio");
|
|
49
|
+
patch({ audioSrc: base64 });
|
|
50
|
+
audioStatus.value = msgs().audioLocalSaved;
|
|
51
|
+
const url = await uploadFileToRemote(file);
|
|
52
|
+
if (url) {
|
|
53
|
+
patch({ audioRemoteUrl: url });
|
|
54
|
+
audioStatus.value = msgs().audioRemoteUploaded;
|
|
55
|
+
} else {
|
|
56
|
+
audioStatus.value = msgs().audioServerUploadFailed;
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
audioStatus.value = error instanceof Error ? error.message : msgs().readAudioFailed;
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
async function handleUploadAudioPoster(file: File) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
try {
|
|
65
|
+
const base64 = await readFileAsDataUrl(file, msgs().readImageFailed);
|
|
66
|
+
patch({ audioPosterImage: base64 });
|
|
67
|
+
audioStatus.value = msgs().audioPosterSet;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
audioStatus.value = error instanceof Error ? error.message : msgs().readImageFailed;
|
|
70
|
+
}
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
function stopRecordingAudio() {
|
|
@@ -85,12 +93,18 @@ async function startRecordingAudio() {
|
|
|
85
93
|
const blob = new Blob(audioChunksRef.value, {
|
|
86
94
|
type: recorder.mimeType || "audio/webm",
|
|
87
95
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
try {
|
|
97
|
+
const dataUrl = await readFileAsDataUrl(
|
|
98
|
+
new File([blob], "recording.webm", { type: blob.type }),
|
|
99
|
+
msgs().readRecordAudioFailed,
|
|
100
|
+
"audio"
|
|
101
|
+
);
|
|
102
|
+
patch({ audioSrc: dataUrl });
|
|
103
|
+
audioStatus.value = msgs().audioRecordSaved;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
audioStatus.value =
|
|
106
|
+
error instanceof Error ? error.message : msgs().readRecordAudioFailed;
|
|
107
|
+
}
|
|
94
108
|
recordStreamRef.value?.getTracks().forEach((track) => track.stop());
|
|
95
109
|
recordStreamRef.value = null;
|
|
96
110
|
recorderRef.value = null;
|
|
@@ -168,7 +182,11 @@ function onPosterFileChange(e: Event) {
|
|
|
168
182
|
:src="element.audioSrc || element.audioRemoteUrl || ''"
|
|
169
183
|
/>
|
|
170
184
|
</ConfigFieldGroup>
|
|
171
|
-
<ConfigFieldGroup
|
|
185
|
+
<ConfigFieldGroup
|
|
186
|
+
:title="t('panel.config.groupDisplayStyle')"
|
|
187
|
+
collapsible
|
|
188
|
+
:default-open="false"
|
|
189
|
+
>
|
|
172
190
|
<label class="flex items-center gap-2">
|
|
173
191
|
<Checkbox
|
|
174
192
|
:checked="element.mediaAutoPauseOnEdit !== false"
|
|
@@ -143,6 +143,100 @@ function onOptionJsonChange(v: string) {
|
|
|
143
143
|
:disabled="!isEditable"
|
|
144
144
|
@update:value="(v) => updateChart({ color: v || '#3b82f6' })"
|
|
145
145
|
/>
|
|
146
|
+
</ConfigFieldGroup>
|
|
147
|
+
<ConfigFieldGroup :title="t('panel.config.groupData')">
|
|
148
|
+
<label class="block space-y-1">
|
|
149
|
+
<div>{{ t("panel.config.labelsCsv") }}</div>
|
|
150
|
+
<Input
|
|
151
|
+
size="small"
|
|
152
|
+
:value="getChartLabelsDisplayText(element.chart)"
|
|
153
|
+
:disabled="!isEditable"
|
|
154
|
+
@update:value="(v: string) => updateChart({ labelsText: v })"
|
|
155
|
+
/>
|
|
156
|
+
</label>
|
|
157
|
+
<label class="block space-y-1">
|
|
158
|
+
<div>{{ t("panel.config.valuesCsv") }}</div>
|
|
159
|
+
<Input
|
|
160
|
+
size="small"
|
|
161
|
+
:value="getChartValuesDisplayText(element.chart)"
|
|
162
|
+
:disabled="!isEditable"
|
|
163
|
+
@update:value="(v: string) => updateChart({ valuesText: v })"
|
|
164
|
+
/>
|
|
165
|
+
</label>
|
|
166
|
+
</ConfigFieldGroup>
|
|
167
|
+
|
|
168
|
+
<ConfigFieldGroup
|
|
169
|
+
v-if="['bar', 'line', 'area', 'pie'].includes(selectedChartType)"
|
|
170
|
+
:title="t('panel.config.groupSeries')"
|
|
171
|
+
>
|
|
172
|
+
<label v-if="selectedChartType === 'bar'" class="block space-y-1">
|
|
173
|
+
<div>{{ t("panel.config.barWidthPx") }}</div>
|
|
174
|
+
<InputNumber
|
|
175
|
+
size="small"
|
|
176
|
+
class="w-full"
|
|
177
|
+
:min="1"
|
|
178
|
+
:placeholder="t('panel.config.barWidthAuto')"
|
|
179
|
+
:value="element.chart?.barWidth ?? undefined"
|
|
180
|
+
:disabled="!isEditable"
|
|
181
|
+
@update:value="(v) => {
|
|
182
|
+
if (v == null || v === '') {
|
|
183
|
+
updateChart({ barWidth: undefined });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const n = Number(v);
|
|
187
|
+
if (!Number.isNaN(n) && n > 0) updateChart({ barWidth: Math.max(1, n) });
|
|
188
|
+
}"
|
|
189
|
+
/>
|
|
190
|
+
</label>
|
|
191
|
+
<label
|
|
192
|
+
v-if="selectedChartType === 'line' || selectedChartType === 'area'"
|
|
193
|
+
class="flex items-center gap-2"
|
|
194
|
+
>
|
|
195
|
+
<Checkbox
|
|
196
|
+
:checked="element.chart?.smooth ?? true"
|
|
197
|
+
:disabled="!isEditable"
|
|
198
|
+
@update:checked="(v) => updateChart({ smooth: v === true })"
|
|
199
|
+
/>
|
|
200
|
+
<span>{{ t("panel.config.smooth") }}</span>
|
|
201
|
+
</label>
|
|
202
|
+
<div v-if="selectedChartType === 'pie'" class="grid grid-cols-2 gap-2">
|
|
203
|
+
<label class="block space-y-1">
|
|
204
|
+
<div>{{ t("panel.config.pieInnerRadiusPct") }}</div>
|
|
205
|
+
<InputNumber
|
|
206
|
+
size="small"
|
|
207
|
+
class="w-full"
|
|
208
|
+
:min="0"
|
|
209
|
+
:max="99"
|
|
210
|
+
:value="element.chart?.pieInnerRadius ?? 30"
|
|
211
|
+
:disabled="!isEditable"
|
|
212
|
+
@update:value="(v) => {
|
|
213
|
+
const n = Number(v);
|
|
214
|
+
if (!Number.isNaN(n)) updateChart({ pieInnerRadius: Math.max(0, Math.min(99, n)) });
|
|
215
|
+
}"
|
|
216
|
+
/>
|
|
217
|
+
</label>
|
|
218
|
+
<label class="block space-y-1">
|
|
219
|
+
<div>{{ t("panel.config.pieOuterRadiusPct") }}</div>
|
|
220
|
+
<InputNumber
|
|
221
|
+
size="small"
|
|
222
|
+
class="w-full"
|
|
223
|
+
:min="1"
|
|
224
|
+
:max="100"
|
|
225
|
+
:value="element.chart?.pieOuterRadius ?? 65"
|
|
226
|
+
:disabled="!isEditable"
|
|
227
|
+
@update:value="(v) => {
|
|
228
|
+
const n = Number(v);
|
|
229
|
+
if (!Number.isNaN(n)) updateChart({ pieOuterRadius: Math.max(1, Math.min(100, n)) });
|
|
230
|
+
}"
|
|
231
|
+
/>
|
|
232
|
+
</label>
|
|
233
|
+
</div>
|
|
234
|
+
</ConfigFieldGroup>
|
|
235
|
+
<ConfigFieldGroup
|
|
236
|
+
:title="t('panel.config.groupChartDisplayMore')"
|
|
237
|
+
collapsible
|
|
238
|
+
:default-open="false"
|
|
239
|
+
>
|
|
146
240
|
<label class="flex items-center gap-2">
|
|
147
241
|
<Checkbox
|
|
148
242
|
:checked="element.chart?.colorMode === 'gradient'"
|
|
@@ -208,7 +302,11 @@ function onOptionJsonChange(v: string) {
|
|
|
208
302
|
</label>
|
|
209
303
|
</ConfigFieldGroup>
|
|
210
304
|
|
|
211
|
-
<ConfigFieldGroup
|
|
305
|
+
<ConfigFieldGroup
|
|
306
|
+
:title="t('panel.config.groupTooltip')"
|
|
307
|
+
collapsible
|
|
308
|
+
:default-open="false"
|
|
309
|
+
>
|
|
212
310
|
<div class="grid grid-cols-2 gap-2">
|
|
213
311
|
<label class="flex items-center gap-2">
|
|
214
312
|
<Checkbox
|
|
@@ -264,30 +362,11 @@ function onOptionJsonChange(v: string) {
|
|
|
264
362
|
</label>
|
|
265
363
|
</ConfigFieldGroup>
|
|
266
364
|
|
|
267
|
-
<ConfigFieldGroup :title="t('panel.config.groupData')">
|
|
268
|
-
<label class="block space-y-1">
|
|
269
|
-
<div>{{ t("panel.config.labelsCsv") }}</div>
|
|
270
|
-
<Input
|
|
271
|
-
size="small"
|
|
272
|
-
:value="getChartLabelsDisplayText(element.chart)"
|
|
273
|
-
:disabled="!isEditable"
|
|
274
|
-
@update:value="(v: string) => updateChart({ labelsText: v })"
|
|
275
|
-
/>
|
|
276
|
-
</label>
|
|
277
|
-
<label class="block space-y-1">
|
|
278
|
-
<div>{{ t("panel.config.valuesCsv") }}</div>
|
|
279
|
-
<Input
|
|
280
|
-
size="small"
|
|
281
|
-
:value="getChartValuesDisplayText(element.chart)"
|
|
282
|
-
:disabled="!isEditable"
|
|
283
|
-
@update:value="(v: string) => updateChart({ valuesText: v })"
|
|
284
|
-
/>
|
|
285
|
-
</label>
|
|
286
|
-
</ConfigFieldGroup>
|
|
287
|
-
|
|
288
365
|
<ConfigFieldGroup
|
|
289
366
|
v-if="['bar', 'line', 'area', 'scatter'].includes(selectedChartType)"
|
|
290
367
|
:title="t('panel.config.groupAxes')"
|
|
368
|
+
collapsible
|
|
369
|
+
:default-open="false"
|
|
291
370
|
>
|
|
292
371
|
<div class="grid grid-cols-2 gap-2">
|
|
293
372
|
<label class="block">
|
|
@@ -407,68 +486,6 @@ function onOptionJsonChange(v: string) {
|
|
|
407
486
|
</div>
|
|
408
487
|
</ConfigFieldGroup>
|
|
409
488
|
|
|
410
|
-
<ConfigFieldGroup
|
|
411
|
-
v-if="['bar', 'line', 'area', 'pie'].includes(selectedChartType)"
|
|
412
|
-
:title="t('panel.config.groupSeries')"
|
|
413
|
-
>
|
|
414
|
-
<label v-if="selectedChartType === 'bar'" class="block space-y-1">
|
|
415
|
-
<div>{{ t("panel.config.barWidthPx") }}</div>
|
|
416
|
-
<InputNumber
|
|
417
|
-
size="small"
|
|
418
|
-
class="w-full"
|
|
419
|
-
:min="1"
|
|
420
|
-
:value="element.chart?.barWidth ?? 24"
|
|
421
|
-
:disabled="!isEditable"
|
|
422
|
-
@update:value="(v) => {
|
|
423
|
-
const n = Number(v);
|
|
424
|
-
if (!Number.isNaN(n)) updateChart({ barWidth: Math.max(1, n) });
|
|
425
|
-
}"
|
|
426
|
-
/>
|
|
427
|
-
</label>
|
|
428
|
-
<label
|
|
429
|
-
v-if="selectedChartType === 'line' || selectedChartType === 'area'"
|
|
430
|
-
class="flex items-center gap-2"
|
|
431
|
-
>
|
|
432
|
-
<Checkbox
|
|
433
|
-
:checked="element.chart?.smooth ?? true"
|
|
434
|
-
:disabled="!isEditable"
|
|
435
|
-
@update:checked="(v) => updateChart({ smooth: v === true })"
|
|
436
|
-
/>
|
|
437
|
-
<span>{{ t("panel.config.smooth") }}</span>
|
|
438
|
-
</label>
|
|
439
|
-
<div v-if="selectedChartType === 'pie'" class="grid grid-cols-2 gap-2">
|
|
440
|
-
<label class="block space-y-1">
|
|
441
|
-
<div>{{ t("panel.config.pieInnerRadiusPct") }}</div>
|
|
442
|
-
<InputNumber
|
|
443
|
-
size="small"
|
|
444
|
-
class="w-full"
|
|
445
|
-
:min="0"
|
|
446
|
-
:max="99"
|
|
447
|
-
:value="element.chart?.pieInnerRadius ?? 30"
|
|
448
|
-
:disabled="!isEditable"
|
|
449
|
-
@update:value="(v) => {
|
|
450
|
-
const n = Number(v);
|
|
451
|
-
if (!Number.isNaN(n)) updateChart({ pieInnerRadius: Math.max(0, Math.min(99, n)) });
|
|
452
|
-
}"
|
|
453
|
-
/>
|
|
454
|
-
</label>
|
|
455
|
-
<label class="block space-y-1">
|
|
456
|
-
<div>{{ t("panel.config.pieOuterRadiusPct") }}</div>
|
|
457
|
-
<InputNumber
|
|
458
|
-
size="small"
|
|
459
|
-
class="w-full"
|
|
460
|
-
:min="1"
|
|
461
|
-
:max="100"
|
|
462
|
-
:value="element.chart?.pieOuterRadius ?? 65"
|
|
463
|
-
:disabled="!isEditable"
|
|
464
|
-
@update:value="(v) => {
|
|
465
|
-
const n = Number(v);
|
|
466
|
-
if (!Number.isNaN(n)) updateChart({ pieOuterRadius: Math.max(1, Math.min(100, n)) });
|
|
467
|
-
}"
|
|
468
|
-
/>
|
|
469
|
-
</label>
|
|
470
|
-
</div>
|
|
471
|
-
</ConfigFieldGroup>
|
|
472
489
|
</ConfigSection>
|
|
473
490
|
|
|
474
491
|
<ConfigSection
|
|
@@ -138,7 +138,11 @@ function clearSketch() {
|
|
|
138
138
|
@update:value="(v) => patch({ geometryColor: v || '#3b82f6' })"
|
|
139
139
|
/>
|
|
140
140
|
</ConfigFieldGroup>
|
|
141
|
-
<ConfigFieldGroup
|
|
141
|
+
<ConfigFieldGroup
|
|
142
|
+
:title="t('panel.config.groupCanvasScript')"
|
|
143
|
+
collapsible
|
|
144
|
+
:default-open="false"
|
|
145
|
+
>
|
|
142
146
|
<template #hint>
|
|
143
147
|
{{ t("panel.config.canvasScriptHint") }}
|
|
144
148
|
</template>
|
|
@@ -152,7 +156,11 @@ function clearSketch() {
|
|
|
152
156
|
@update:value="(v: string) => patch({ geometryScript: v || undefined })"
|
|
153
157
|
/>
|
|
154
158
|
</ConfigFieldGroup>
|
|
155
|
-
<ConfigFieldGroup
|
|
159
|
+
<ConfigFieldGroup
|
|
160
|
+
:title="t('panel.config.groupSketchOverlay')"
|
|
161
|
+
collapsible
|
|
162
|
+
:default-open="false"
|
|
163
|
+
>
|
|
156
164
|
<div class="flex items-center gap-2">
|
|
157
165
|
<label class="block space-y-1">
|
|
158
166
|
<div class="text-[11px]">{{ t("panel.config.penColor") }}</div>
|