@arronqzy/react-view 1.0.22 → 1.0.23
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 +11 -11
- package/src/panel/ReactViewPanel.tsx +109 -965
- package/src/panel/components/PanelConfigSidebar.tsx +297 -3712
- package/src/panel/components/PanelHistoryPopover.tsx +163 -0
- package/src/panel/components/PanelLayerDock.tsx +412 -0
- package/src/panel/components/PanelMenubar.tsx +149 -0
- package/src/panel/components/PanelWorkspaceDialogs.tsx +271 -0
- package/src/panel/components/config/PanelConfigAudioSection.tsx +334 -0
- package/src/panel/components/config/PanelConfigChartSection.tsx +799 -0
- package/src/panel/components/config/PanelConfigGeometrySection.tsx +233 -0
- package/src/panel/components/config/PanelConfigGridChildSpan.tsx +70 -0
- package/src/panel/components/config/PanelConfigGridSection.tsx +118 -0
- package/src/panel/components/config/PanelConfigMultiSelect.tsx +1291 -0
- package/src/panel/components/config/PanelConfigNodeInfo.tsx +169 -0
- package/src/panel/components/config/PanelConfigReferenceSection.tsx +99 -0
- package/src/panel/components/config/PanelConfigScene3dSection.tsx +6 -2
- package/src/panel/components/config/PanelConfigStyleSections.tsx +249 -0
- package/src/panel/components/config/PanelConfigTextSection.tsx +268 -0
- package/src/panel/components/config/PanelConfigVideoSection.tsx +133 -0
- package/src/panel/components/config/PanelConfigViewportSection.tsx +139 -0
- package/src/panel/components/config/helpers.ts +85 -0
- package/src/panel/icons.tsx +160 -0
- package/src/panel/utils/panelSelection.ts +57 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
2
|
+
import { Input } from "@arronqzy/ui";
|
|
3
|
+
import type { PanelElement } from "../../types";
|
|
4
|
+
import { type ConfigSectionHelpers, type UpdateElement } from "./helpers";
|
|
5
|
+
|
|
6
|
+
export function PanelConfigNodeInfo({
|
|
7
|
+
element,
|
|
8
|
+
helpers,
|
|
9
|
+
updateElement,
|
|
10
|
+
nodeZOrderLabel,
|
|
11
|
+
onAdjustNodeZOrder,
|
|
12
|
+
}: {
|
|
13
|
+
element: PanelElement;
|
|
14
|
+
helpers: ConfigSectionHelpers;
|
|
15
|
+
updateElement: UpdateElement;
|
|
16
|
+
nodeZOrderLabel?: string;
|
|
17
|
+
onAdjustNodeZOrder?: (
|
|
18
|
+
nodeId: string,
|
|
19
|
+
action: "bringForward" | "sendBackward" | "bringToFront" | "sendToBack"
|
|
20
|
+
) => void;
|
|
21
|
+
}) {
|
|
22
|
+
const { t } = useI18n();
|
|
23
|
+
const { renderSection, renderFieldGroup } = helpers;
|
|
24
|
+
const selectedElement = element;
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
{renderSection(
|
|
28
|
+
"nodeInfo",
|
|
29
|
+
t("panel.config.sectionNodeInfo"),
|
|
30
|
+
<>
|
|
31
|
+
<label className="block space-y-1" data-config-field="name">
|
|
32
|
+
<div>{t("panel.config.nodeName")}</div>
|
|
33
|
+
<Input
|
|
34
|
+
value={selectedElement.name ?? ""}
|
|
35
|
+
onChange={(e) =>
|
|
36
|
+
updateElement(selectedElement.id, {
|
|
37
|
+
name: e.target.value || undefined,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
placeholder={t("panel.config.nodeNamePlaceholder")}
|
|
41
|
+
className="h-7"
|
|
42
|
+
/>
|
|
43
|
+
</label>
|
|
44
|
+
<div className="grid grid-cols-2 gap-2">
|
|
45
|
+
<label className="block space-y-1" data-config-field="x">
|
|
46
|
+
<div>X</div>
|
|
47
|
+
<Input
|
|
48
|
+
type="number"
|
|
49
|
+
value={selectedElement.x}
|
|
50
|
+
onChange={(e) =>
|
|
51
|
+
updateElement(selectedElement.id, {
|
|
52
|
+
x: Number(e.target.value) || 0,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
className="h-7"
|
|
56
|
+
/>
|
|
57
|
+
</label>
|
|
58
|
+
<label className="block space-y-1" data-config-field="y">
|
|
59
|
+
<div>Y</div>
|
|
60
|
+
<Input
|
|
61
|
+
type="number"
|
|
62
|
+
value={selectedElement.y}
|
|
63
|
+
onChange={(e) =>
|
|
64
|
+
updateElement(selectedElement.id, {
|
|
65
|
+
y: Number(e.target.value) || 0,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
className="h-7"
|
|
69
|
+
/>
|
|
70
|
+
</label>
|
|
71
|
+
</div>
|
|
72
|
+
<div className="grid grid-cols-2 gap-2">
|
|
73
|
+
<label className="block space-y-1" data-config-field="width">
|
|
74
|
+
<div>{t("panel.config.width")}</div>
|
|
75
|
+
<Input
|
|
76
|
+
type="number"
|
|
77
|
+
min={1}
|
|
78
|
+
value={selectedElement?.width ?? 1}
|
|
79
|
+
onChange={(e) => {
|
|
80
|
+
if (!selectedElement) return;
|
|
81
|
+
updateElement(selectedElement.id, {
|
|
82
|
+
width: Math.max(1, Number(e.target.value) || 1),
|
|
83
|
+
});
|
|
84
|
+
}}
|
|
85
|
+
className="h-7"
|
|
86
|
+
/>
|
|
87
|
+
</label>
|
|
88
|
+
<label className="block space-y-1" data-config-field="height">
|
|
89
|
+
<div>{t("panel.config.height")}</div>
|
|
90
|
+
<Input
|
|
91
|
+
type="number"
|
|
92
|
+
min={1}
|
|
93
|
+
value={selectedElement.height}
|
|
94
|
+
onChange={(e) =>
|
|
95
|
+
updateElement(selectedElement.id, {
|
|
96
|
+
height: Math.max(1, Number(e.target.value) || 1),
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
className="h-7"
|
|
100
|
+
/>
|
|
101
|
+
</label>
|
|
102
|
+
</div>
|
|
103
|
+
{renderFieldGroup(
|
|
104
|
+
t("panel.config.groupNodeMore"),
|
|
105
|
+
<>
|
|
106
|
+
<div className="grid grid-cols-2 gap-2">
|
|
107
|
+
<label className="block space-y-1" data-config-field="rotate">
|
|
108
|
+
<div>{t("panel.config.rotate")}</div>
|
|
109
|
+
<Input
|
|
110
|
+
type="number"
|
|
111
|
+
value={selectedElement.rotate ?? 0}
|
|
112
|
+
onChange={(e) =>
|
|
113
|
+
updateElement(selectedElement.id, {
|
|
114
|
+
rotate: Number(e.target.value) || 0,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
className="h-7"
|
|
118
|
+
/>
|
|
119
|
+
</label>
|
|
120
|
+
</div>
|
|
121
|
+
<div className="space-y-1.5">
|
|
122
|
+
<div className="text-[11px] text-muted-foreground">{t("panel.config.nodeZOrder")}</div>
|
|
123
|
+
<div className="text-[11px] text-muted-foreground/90">
|
|
124
|
+
{t("panel.config.currentZIndex", { value: nodeZOrderLabel ?? "-" })}
|
|
125
|
+
</div>
|
|
126
|
+
<div className="grid grid-cols-2 gap-2">
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
className="rounded border border-border bg-background px-2 py-1 text-[11px] hover:bg-accent"
|
|
130
|
+
onClick={() => onAdjustNodeZOrder?.(selectedElement.id, "bringForward")}
|
|
131
|
+
>
|
|
132
|
+
{t("panel.menubar.bringForward")}
|
|
133
|
+
</button>
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
className="rounded border border-border bg-background px-2 py-1 text-[11px] hover:bg-accent"
|
|
137
|
+
onClick={() => onAdjustNodeZOrder?.(selectedElement.id, "sendBackward")}
|
|
138
|
+
>
|
|
139
|
+
{t("panel.menubar.sendBackward")}
|
|
140
|
+
</button>
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
className="rounded border border-border bg-background px-2 py-1 text-[11px] hover:bg-accent"
|
|
144
|
+
onClick={() => onAdjustNodeZOrder?.(selectedElement.id, "bringToFront")}
|
|
145
|
+
>
|
|
146
|
+
{t("panel.menubar.bringToFront")}
|
|
147
|
+
</button>
|
|
148
|
+
<button
|
|
149
|
+
type="button"
|
|
150
|
+
className="rounded border border-border bg-background px-2 py-1 text-[11px] hover:bg-accent"
|
|
151
|
+
onClick={() => onAdjustNodeZOrder?.(selectedElement.id, "sendToBack")}
|
|
152
|
+
>
|
|
153
|
+
{t("panel.menubar.sendToBack")}
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
<div className="truncate text-muted-foreground">ID: {selectedElement.id}</div>
|
|
158
|
+
<div className="text-muted-foreground">{t("panel.config.type")}: {selectedElement.materialType ?? selectedElement.id}</div>
|
|
159
|
+
</>,
|
|
160
|
+
undefined,
|
|
161
|
+
{ groupKey: "nodeMore", defaultOpen: false }
|
|
162
|
+
)}
|
|
163
|
+
</>,
|
|
164
|
+
true,
|
|
165
|
+
[t("panel.config.name"), "id", t("panel.config.type"), t("panel.config.rotate"), t("panel.config.nodeZOrder"), t("panel.layers.lockShort"), "locked", "name"]
|
|
166
|
+
)}
|
|
167
|
+
</>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
2
|
+
import {
|
|
3
|
+
Select,
|
|
4
|
+
SelectContent,
|
|
5
|
+
SelectItem,
|
|
6
|
+
SelectTrigger,
|
|
7
|
+
SelectValue,
|
|
8
|
+
} from "@arronqzy/ui";
|
|
9
|
+
import type { PanelElement, PanelLayer, ReferenceCopyMode } from "../../types";
|
|
10
|
+
import { type ConfigSectionHelpers, type UpdateElement } from "./helpers";
|
|
11
|
+
|
|
12
|
+
export function PanelConfigReferenceSection({
|
|
13
|
+
element,
|
|
14
|
+
helpers,
|
|
15
|
+
updateElement,
|
|
16
|
+
layers,
|
|
17
|
+
setReferenceCopyMode,
|
|
18
|
+
}: {
|
|
19
|
+
element: PanelElement;
|
|
20
|
+
helpers: ConfigSectionHelpers;
|
|
21
|
+
updateElement: UpdateElement;
|
|
22
|
+
layers: PanelLayer[];
|
|
23
|
+
setReferenceCopyMode?: (id: string, mode: ReferenceCopyMode) => void;
|
|
24
|
+
}) {
|
|
25
|
+
const { t } = useI18n();
|
|
26
|
+
const { renderSection, renderFieldGroup } = helpers;
|
|
27
|
+
const selectedElement = element;
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
{
|
|
31
|
+
renderSection(
|
|
32
|
+
"reference",
|
|
33
|
+
t("panel.config.sectionReference"),
|
|
34
|
+
<>
|
|
35
|
+
{renderFieldGroup(
|
|
36
|
+
t("panel.config.groupRefSource"),
|
|
37
|
+
<label className="block space-y-1">
|
|
38
|
+
<div>{t("panel.config.refLayer")}</div>
|
|
39
|
+
<Select
|
|
40
|
+
value={selectedElement.refLayerId ?? "__none__"}
|
|
41
|
+
onValueChange={(value) =>
|
|
42
|
+
updateElement(selectedElement.id, {
|
|
43
|
+
refLayerId: value === "__none__" ? undefined : value,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
>
|
|
47
|
+
<SelectTrigger className="h-7">
|
|
48
|
+
<SelectValue placeholder={t("panel.config.pleaseSelectLayer")} />
|
|
49
|
+
</SelectTrigger>
|
|
50
|
+
<SelectContent>
|
|
51
|
+
<SelectItem value="__none__">{t("panel.config.noneNoRef")}</SelectItem>
|
|
52
|
+
{layers
|
|
53
|
+
.filter((l) => l.id !== selectedElement.layerId)
|
|
54
|
+
.map((l) => (
|
|
55
|
+
<SelectItem key={l.id} value={l.id}>
|
|
56
|
+
{l.name}
|
|
57
|
+
</SelectItem>
|
|
58
|
+
))}
|
|
59
|
+
</SelectContent>
|
|
60
|
+
</Select>
|
|
61
|
+
</label>
|
|
62
|
+
)}
|
|
63
|
+
{renderFieldGroup(
|
|
64
|
+
t("panel.config.groupCopyStrategy"),
|
|
65
|
+
<>
|
|
66
|
+
<label className="block space-y-1">
|
|
67
|
+
<div>{t("panel.config.copyMode")}</div>
|
|
68
|
+
<Select
|
|
69
|
+
value={selectedElement.refCopyMode ?? "shallow"}
|
|
70
|
+
onValueChange={(value) =>
|
|
71
|
+
setReferenceCopyMode?.(
|
|
72
|
+
selectedElement.id,
|
|
73
|
+
value as ReferenceCopyMode
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
>
|
|
77
|
+
<SelectTrigger className="h-7">
|
|
78
|
+
<SelectValue placeholder={t("panel.config.selectCopyMode")} />
|
|
79
|
+
</SelectTrigger>
|
|
80
|
+
<SelectContent>
|
|
81
|
+
<SelectItem value="shallow">{t("panel.config.shallowFollow")}</SelectItem>
|
|
82
|
+
<SelectItem value="deep">{t("panel.config.deepFreeze")}</SelectItem>
|
|
83
|
+
</SelectContent>
|
|
84
|
+
</Select>
|
|
85
|
+
</label>
|
|
86
|
+
</>,
|
|
87
|
+
<>
|
|
88
|
+
{t("panel.config.copyStrategyHint")}
|
|
89
|
+
</>,
|
|
90
|
+
{ groupKey: "referenceCopyStrategy", defaultOpen: false }
|
|
91
|
+
)}
|
|
92
|
+
</>,
|
|
93
|
+
true,
|
|
94
|
+
[t("panel.config.searchKwRef"), "ref", t("panel.config.layer"), t("panel.material.shallowCopy"), t("panel.material.deepCopy"), "snapshot"]
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useMemo,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
} from "react";
|
|
2
7
|
import { useI18n } from "@arronqzy/i18n/react";
|
|
3
8
|
import {
|
|
4
9
|
Button,
|
|
@@ -31,7 +36,6 @@ import type { PanelElement } from "../../types";
|
|
|
31
36
|
import { readFileAsDataUrl } from "../../utils/async-work";
|
|
32
37
|
import { randomId } from "../../utils/panelElementDefaults";
|
|
33
38
|
import { ConfigHintIcon } from "../ConfigHintIcon";
|
|
34
|
-
|
|
35
39
|
type UpdateElement = (
|
|
36
40
|
id: string,
|
|
37
41
|
patch: Partial<PanelElement>,
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
3
|
+
import {
|
|
4
|
+
Input,
|
|
5
|
+
Select,
|
|
6
|
+
SelectContent,
|
|
7
|
+
SelectItem,
|
|
8
|
+
SelectTrigger,
|
|
9
|
+
SelectValue,
|
|
10
|
+
} from "@arronqzy/ui";
|
|
11
|
+
import type { PanelElement, PanelElementStyle } from "../../types";
|
|
12
|
+
import { readFileAsDataUrl, runBusyTask } from "../../utils/async-work";
|
|
13
|
+
import { getPanelMessages } from "../../constants/messages";
|
|
14
|
+
import { type ConfigSectionHelpers, type UpdateElement } from "./helpers";
|
|
15
|
+
|
|
16
|
+
export function PanelConfigStyleSections({
|
|
17
|
+
element,
|
|
18
|
+
helpers,
|
|
19
|
+
updateElement,
|
|
20
|
+
}: {
|
|
21
|
+
element: PanelElement;
|
|
22
|
+
helpers: ConfigSectionHelpers;
|
|
23
|
+
updateElement: UpdateElement;
|
|
24
|
+
}) {
|
|
25
|
+
const { t } = useI18n();
|
|
26
|
+
const messages = React.useMemo(() => getPanelMessages(t), [t]);
|
|
27
|
+
const { renderSection, renderFieldGroup, renderColorField } = helpers;
|
|
28
|
+
const selectedElement = element;
|
|
29
|
+
const [uploadStatus, setUploadStatus] = useState("");
|
|
30
|
+
|
|
31
|
+
const updateSelectedStyle = useCallback(
|
|
32
|
+
(patch: Partial<PanelElementStyle>) => {
|
|
33
|
+
updateElement(selectedElement.id, {
|
|
34
|
+
style: { ...(selectedElement.style ?? {}), ...patch },
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
[selectedElement, updateElement]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const handleUploadBackgroundImage = useCallback(
|
|
41
|
+
async (file: File) => {
|
|
42
|
+
try {
|
|
43
|
+
await runBusyTask(t("common.uploadingFile"), async () => {
|
|
44
|
+
const base64 = await readFileAsDataUrl(file, messages.readImageFailed, "image");
|
|
45
|
+
updateSelectedStyle({
|
|
46
|
+
backgroundImage: `url("${base64}")`,
|
|
47
|
+
});
|
|
48
|
+
setUploadStatus(t("panel.config.uploadWrittenBase64"));
|
|
49
|
+
try {
|
|
50
|
+
const form = new FormData();
|
|
51
|
+
form.append("file", file);
|
|
52
|
+
const resp = await fetch("/api/upload", { method: "POST", body: form });
|
|
53
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
54
|
+
const data = (await resp.json()) as { url?: string };
|
|
55
|
+
if (data.url) {
|
|
56
|
+
updateSelectedStyle({ backgroundImageRemoteUrl: data.url });
|
|
57
|
+
setUploadStatus(t("panel.config.uploadServerAndBase64"));
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
setUploadStatus(t("panel.config.uploadServerFailedKeepBase64"));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
setUploadStatus(
|
|
65
|
+
error instanceof Error ? error.message : messages.readImageFailed
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
[messages.readImageFailed, t, updateSelectedStyle]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
{renderSection(
|
|
75
|
+
"styleBackground",
|
|
76
|
+
t("panel.config.sectionStyleBackground"),
|
|
77
|
+
<>
|
|
78
|
+
{renderFieldGroup(
|
|
79
|
+
t("panel.config.groupBgFill"),
|
|
80
|
+
<>
|
|
81
|
+
{renderColorField(
|
|
82
|
+
t("panel.config.backgroundColor"),
|
|
83
|
+
selectedElement.style?.backgroundColor ?? "",
|
|
84
|
+
(next) => updateSelectedStyle({ backgroundColor: next || undefined })
|
|
85
|
+
)}
|
|
86
|
+
</>
|
|
87
|
+
)}
|
|
88
|
+
{renderFieldGroup(
|
|
89
|
+
t("panel.config.groupBgLayout"),
|
|
90
|
+
<>
|
|
91
|
+
<label className="block space-y-1">
|
|
92
|
+
<div>{t("panel.config.backgroundImage")}</div>
|
|
93
|
+
<Input
|
|
94
|
+
value={selectedElement.style?.backgroundImage ?? ""}
|
|
95
|
+
onChange={(e) => updateSelectedStyle({ backgroundImage: e.target.value || undefined })}
|
|
96
|
+
placeholder='url("https://...") / linear-gradient(...)'
|
|
97
|
+
className="h-7"
|
|
98
|
+
/>
|
|
99
|
+
</label>
|
|
100
|
+
<div className="flex items-center gap-2">
|
|
101
|
+
<label className="inline-flex cursor-pointer items-center rounded border border-border px-2 py-1 text-[11px] hover:bg-accent">
|
|
102
|
+
{t("panel.config.uploadImage")}
|
|
103
|
+
<Input
|
|
104
|
+
type="file"
|
|
105
|
+
accept="image/*"
|
|
106
|
+
className="hidden"
|
|
107
|
+
onChange={async (e) => {
|
|
108
|
+
const file = e.target.files?.[0];
|
|
109
|
+
e.currentTarget.value = "";
|
|
110
|
+
if (!file) return;
|
|
111
|
+
await handleUploadBackgroundImage(file);
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
</label>
|
|
115
|
+
{uploadStatus ? (
|
|
116
|
+
<span className="text-[11px] text-muted-foreground">{uploadStatus}</span>
|
|
117
|
+
) : null}
|
|
118
|
+
</div>
|
|
119
|
+
<div className="grid grid-cols-2 gap-2">
|
|
120
|
+
<label className="block space-y-1">
|
|
121
|
+
<div>{t("panel.config.backgroundSize")}</div>
|
|
122
|
+
<Select
|
|
123
|
+
value={selectedElement.style?.backgroundSize ?? "__none__"}
|
|
124
|
+
onValueChange={(value) =>
|
|
125
|
+
updateSelectedStyle({
|
|
126
|
+
backgroundSize: value === "__none__" ? undefined : value,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
>
|
|
130
|
+
<SelectTrigger className="h-7">
|
|
131
|
+
<SelectValue placeholder={t("panel.config.selectBackgroundSize")} />
|
|
132
|
+
</SelectTrigger>
|
|
133
|
+
<SelectContent>
|
|
134
|
+
<SelectItem value="__none__">{t("common.default")}</SelectItem>
|
|
135
|
+
<SelectItem value="cover">cover</SelectItem>
|
|
136
|
+
<SelectItem value="contain">contain</SelectItem>
|
|
137
|
+
<SelectItem value="100% 100%">100% 100%</SelectItem>
|
|
138
|
+
<SelectItem value="auto">auto</SelectItem>
|
|
139
|
+
</SelectContent>
|
|
140
|
+
</Select>
|
|
141
|
+
</label>
|
|
142
|
+
<label className="block space-y-1">
|
|
143
|
+
<div>{t("panel.config.backgroundPosition")}</div>
|
|
144
|
+
<Select
|
|
145
|
+
value={selectedElement.style?.backgroundPosition ?? "__none__"}
|
|
146
|
+
onValueChange={(value) =>
|
|
147
|
+
updateSelectedStyle({
|
|
148
|
+
backgroundPosition: value === "__none__" ? undefined : value,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
>
|
|
152
|
+
<SelectTrigger className="h-7">
|
|
153
|
+
<SelectValue placeholder={t("panel.config.selectBackgroundPosition")} />
|
|
154
|
+
</SelectTrigger>
|
|
155
|
+
<SelectContent>
|
|
156
|
+
<SelectItem value="__none__">{t("common.default")}</SelectItem>
|
|
157
|
+
<SelectItem value="center">center</SelectItem>
|
|
158
|
+
<SelectItem value="top">top</SelectItem>
|
|
159
|
+
<SelectItem value="bottom">bottom</SelectItem>
|
|
160
|
+
<SelectItem value="left">left</SelectItem>
|
|
161
|
+
<SelectItem value="right">right</SelectItem>
|
|
162
|
+
<SelectItem value="top left">top left</SelectItem>
|
|
163
|
+
<SelectItem value="top right">top right</SelectItem>
|
|
164
|
+
<SelectItem value="bottom left">bottom left</SelectItem>
|
|
165
|
+
<SelectItem value="bottom right">bottom right</SelectItem>
|
|
166
|
+
</SelectContent>
|
|
167
|
+
</Select>
|
|
168
|
+
</label>
|
|
169
|
+
</div>
|
|
170
|
+
</>,
|
|
171
|
+
undefined,
|
|
172
|
+
{ groupKey: "styleBgLayout", defaultOpen: false }
|
|
173
|
+
)}
|
|
174
|
+
</>,
|
|
175
|
+
false,
|
|
176
|
+
[t("panel.config.backgroundColor"), t("panel.config.backgroundImage"), "background", "backgroundSize", "backgroundPosition", t("panel.config.searchKwLayout")]
|
|
177
|
+
)}
|
|
178
|
+
{renderSection(
|
|
179
|
+
"styleBorder",
|
|
180
|
+
t("panel.config.sectionStyleBorder"),
|
|
181
|
+
<>
|
|
182
|
+
{renderFieldGroup(
|
|
183
|
+
t("panel.config.groupBorderGeometry"),
|
|
184
|
+
<div className="grid grid-cols-2 gap-2">
|
|
185
|
+
<label className="block space-y-1">
|
|
186
|
+
<div>{t("panel.config.borderWidth")}</div>
|
|
187
|
+
<Input
|
|
188
|
+
type="number"
|
|
189
|
+
min={0}
|
|
190
|
+
value={selectedElement.style?.borderWidth ?? 0}
|
|
191
|
+
onChange={(e) =>
|
|
192
|
+
updateSelectedStyle({ borderWidth: Math.max(0, Number(e.target.value) || 0) })
|
|
193
|
+
}
|
|
194
|
+
className="h-7"
|
|
195
|
+
/>
|
|
196
|
+
</label>
|
|
197
|
+
<label className="block space-y-1">
|
|
198
|
+
<div>{t("panel.config.borderRadius")}</div>
|
|
199
|
+
<Input
|
|
200
|
+
type="number"
|
|
201
|
+
min={0}
|
|
202
|
+
value={selectedElement.style?.borderRadius ?? 0}
|
|
203
|
+
onChange={(e) =>
|
|
204
|
+
updateSelectedStyle({ borderRadius: Math.max(0, Number(e.target.value) || 0) })
|
|
205
|
+
}
|
|
206
|
+
className="h-7"
|
|
207
|
+
/>
|
|
208
|
+
</label>
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
{renderFieldGroup(
|
|
212
|
+
t("panel.config.groupBorderVisual"),
|
|
213
|
+
<div className="grid grid-cols-2 gap-2">
|
|
214
|
+
<label className="block space-y-1">
|
|
215
|
+
<div>{t("panel.config.borderStyle")}</div>
|
|
216
|
+
<Select
|
|
217
|
+
value={selectedElement.style?.borderStyle ?? "solid"}
|
|
218
|
+
onValueChange={(value) =>
|
|
219
|
+
updateSelectedStyle({
|
|
220
|
+
borderStyle: value as NonNullable<PanelElementStyle["borderStyle"]>,
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
>
|
|
224
|
+
<SelectTrigger className="h-7">
|
|
225
|
+
<SelectValue placeholder={t("panel.config.selectBorderStyle")} />
|
|
226
|
+
</SelectTrigger>
|
|
227
|
+
<SelectContent>
|
|
228
|
+
<SelectItem value="none">none</SelectItem>
|
|
229
|
+
<SelectItem value="solid">solid</SelectItem>
|
|
230
|
+
<SelectItem value="dashed">dashed</SelectItem>
|
|
231
|
+
<SelectItem value="dotted">dotted</SelectItem>
|
|
232
|
+
<SelectItem value="double">double</SelectItem>
|
|
233
|
+
</SelectContent>
|
|
234
|
+
</Select>
|
|
235
|
+
</label>
|
|
236
|
+
{renderColorField(
|
|
237
|
+
t("panel.config.borderColor"),
|
|
238
|
+
selectedElement.style?.borderColor ?? "",
|
|
239
|
+
(next) => updateSelectedStyle({ borderColor: next || undefined })
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
</>,
|
|
244
|
+
false,
|
|
245
|
+
[t("panel.config.searchKwBorder"), "border", t("panel.config.searchKwWidth"), t("panel.config.searchKwRadius"), t("panel.config.color"), t("panel.config.searchKwStyle")]
|
|
246
|
+
)}
|
|
247
|
+
</>
|
|
248
|
+
);
|
|
249
|
+
}
|