@ptengine/inline-editor 0.1.5 → 0.1.6
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/CHANGELOG.md +43 -0
- package/dist/InlineEditor.d.ts +15 -1
- package/dist/InlineEditor.js +71 -41
- package/dist/canvas-ui/tree/TreePanel.d.ts +2 -1
- package/dist/canvas-ui/tree/TreePanel.js +79 -39
- package/dist/chrome/EditorChrome.d.ts +2 -0
- package/dist/chrome/EditorChrome.js +1 -1
- package/dist/chrome/VersionMenu.d.ts +3 -1
- package/dist/chrome/VersionMenu.js +24 -12
- package/dist/chrome/topbarPrimitives.d.ts +5 -52
- package/dist/chrome/topbarPrimitives.js +13 -31
- package/dist/dialogs/ClickActionDialog.js +4 -5
- package/dist/dialogs/CodeField.d.ts +4 -0
- package/dist/dialogs/CodeField.js +10 -0
- package/dist/dialogs/HtmlEditDialog.js +3 -2
- package/dist/dialogs/ImageEditDialog.js +53 -15
- package/dist/dialogs/ImageSettingDialog.js +3 -3
- package/dist/dialogs/InsertElementDialog.js +6 -6
- package/dist/dialogs/MediaPreview.d.ts +5 -0
- package/dist/dialogs/MediaPreview.js +34 -0
- package/dist/dialogs/ReplaceVideoDialog.js +8 -4
- package/dist/index.d.ts +1 -0
- package/dist/kernel/blocks/BlockOverlay.js +1 -1
- package/dist/kernel/blocks/resolveBlocks.d.ts +16 -0
- package/dist/kernel/blocks/resolveBlocks.js +39 -6
- package/dist/kernel/changelist/ChangeListPanel.js +11 -9
- package/dist/kernel/changelist/useChangeList.d.ts +4 -0
- package/dist/kernel/changelist/useChangeList.js +84 -10
- package/dist/kernel/selection/SelectionOverlay.js +1 -1
- package/dist/kernel/selection/SelectionToolbar.js +26 -8
- package/dist/kernel/selection/buildRootShelf.d.ts +49 -2
- package/dist/kernel/selection/buildRootShelf.js +156 -41
- package/dist/kernel/selection/elementAiContext.d.ts +11 -3
- package/dist/kernel/selection/resolveAnchors.d.ts +17 -0
- package/dist/kernel/selection/resolveAnchors.js +59 -11
- package/dist/locales.d.ts +186 -48
- package/dist/locales.js +10 -9
- package/dist/panels/EditorStatus.d.ts +5 -0
- package/dist/panels/EditorStatus.js +4 -0
- package/dist/panels/ScreenshotCard.d.ts +7 -0
- package/dist/panels/ScreenshotCard.js +57 -0
- package/dist/panels/StylePanel.d.ts +7 -1
- package/dist/panels/StylePanel.js +105 -12
- package/dist/reviewLocales.d.ts +140 -0
- package/dist/reviewLocales.js +134 -0
- package/dist/tree/LayersPanel.js +4 -2
- package/package.json +1 -1
|
@@ -1,20 +1,103 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { X } from 'lucide-react';
|
|
5
|
-
import { Button,
|
|
5
|
+
import { Button, InputGroup, InputGroupInput, InputGroupAddon, InputGroupText, Label, NativeSelect, Separator } from '@ptengine/design-components';
|
|
6
6
|
import { composeStyleValue, visibleSections } from './styleFields.js';
|
|
7
|
-
export function StylePanel({ tag, capabilities, declaredStyles = {}, onApply, onClose }) {
|
|
7
|
+
export function StylePanel({ anchor, tag, capabilities, declaredStyles = {}, onApply, onReset, onClose }) {
|
|
8
8
|
const { t } = useTranslation();
|
|
9
9
|
const sections = visibleSections(capabilities);
|
|
10
|
+
const panelRef = useRef(null);
|
|
11
|
+
const [offset, setOffset] = useState(() => anchor ?? { x: 0, y: 0 });
|
|
12
|
+
const renderedOffset = useRef(offset);
|
|
13
|
+
renderedOffset.current = offset;
|
|
14
|
+
const anchored = anchor !== undefined;
|
|
15
|
+
const drag = useRef(null);
|
|
16
|
+
useLayoutEffect(() => {
|
|
17
|
+
const clamp = () => {
|
|
18
|
+
if (drag.current)
|
|
19
|
+
return;
|
|
20
|
+
const rect = panelRef.current?.getBoundingClientRect();
|
|
21
|
+
if (!rect)
|
|
22
|
+
return;
|
|
23
|
+
const left = Math.max(8, Math.min(rect.left, window.innerWidth - rect.width - 8));
|
|
24
|
+
const top = Math.max(8, Math.min(rect.top, window.innerHeight - rect.height - 8));
|
|
25
|
+
if (anchored && panelRef.current) {
|
|
26
|
+
panelRef.current.style.left = `${left}px`;
|
|
27
|
+
panelRef.current.style.top = `${top}px`;
|
|
28
|
+
}
|
|
29
|
+
const next = anchored
|
|
30
|
+
? { x: left, y: top }
|
|
31
|
+
: {
|
|
32
|
+
x: renderedOffset.current.x + left - rect.left,
|
|
33
|
+
y: renderedOffset.current.y + top - rect.top
|
|
34
|
+
};
|
|
35
|
+
setOffset(prev => (prev.x === next.x && prev.y === next.y ? prev : next));
|
|
36
|
+
};
|
|
37
|
+
clamp();
|
|
38
|
+
window.addEventListener('resize', clamp);
|
|
39
|
+
const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(clamp);
|
|
40
|
+
if (panelRef.current)
|
|
41
|
+
observer?.observe(panelRef.current);
|
|
42
|
+
const frame = typeof requestAnimationFrame === 'undefined' ? null : requestAnimationFrame(clamp);
|
|
43
|
+
return () => {
|
|
44
|
+
window.removeEventListener('resize', clamp);
|
|
45
|
+
observer?.disconnect();
|
|
46
|
+
if (frame !== null)
|
|
47
|
+
cancelAnimationFrame(frame);
|
|
48
|
+
};
|
|
49
|
+
}, [anchored, sections.length]);
|
|
10
50
|
if (sections.length === 0)
|
|
11
51
|
return null;
|
|
12
|
-
return (_jsxs("div", {
|
|
52
|
+
return (_jsxs("div", { ref: panelRef, style: anchor
|
|
53
|
+
? { position: 'fixed', left: offset.x, top: offset.y }
|
|
54
|
+
: { transform: `translate(${offset.x}px, ${offset.y}px)` }, "data-testid": "inline-style-panel", className: "flex max-h-[calc(100vh-100px)] w-80 max-w-[calc(100vw-16px)] flex-col overflow-hidden rounded-lg border border-[#DFE1E6]\n bg-white shadow-lg", children: [_jsxs("header", { "data-testid": "inline-style-panel-drag", onPointerDown: event => {
|
|
55
|
+
if (event.button !== 0 || event.target.closest('button'))
|
|
56
|
+
return;
|
|
57
|
+
const rect = panelRef.current.getBoundingClientRect();
|
|
58
|
+
drag.current = {
|
|
59
|
+
pointer: event.pointerId,
|
|
60
|
+
x: event.clientX,
|
|
61
|
+
y: event.clientY,
|
|
62
|
+
left: rect.left,
|
|
63
|
+
top: rect.top,
|
|
64
|
+
width: rect.width,
|
|
65
|
+
height: rect.height,
|
|
66
|
+
offset
|
|
67
|
+
};
|
|
68
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
}, onPointerMove: event => {
|
|
71
|
+
const start = drag.current;
|
|
72
|
+
if (!start || start.pointer !== event.pointerId)
|
|
73
|
+
return;
|
|
74
|
+
const left = Math.max(8, Math.min(start.left + event.clientX - start.x, window.innerWidth - start.width - 8));
|
|
75
|
+
const top = Math.max(8, Math.min(start.top + event.clientY - start.y, window.innerHeight - start.height - 8));
|
|
76
|
+
const next = {
|
|
77
|
+
x: start.offset.x + left - start.left,
|
|
78
|
+
y: start.offset.y + top - start.top
|
|
79
|
+
};
|
|
80
|
+
if (anchored && panelRef.current) {
|
|
81
|
+
panelRef.current.style.left = `${next.x}px`;
|
|
82
|
+
panelRef.current.style.top = `${next.y}px`;
|
|
83
|
+
}
|
|
84
|
+
setOffset(next);
|
|
85
|
+
}, onPointerUp: () => {
|
|
86
|
+
drag.current = null;
|
|
87
|
+
}, onPointerCancel: () => {
|
|
88
|
+
drag.current = null;
|
|
89
|
+
}, onLostPointerCapture: () => {
|
|
90
|
+
drag.current = null;
|
|
91
|
+
}, className: "flex cursor-grab touch-none select-none h-11 shrink-0 items-center justify-between border-b\n border-[#EBECF0] bg-[#F4F5F7] px-3.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("code", { className: "text-xs text-[#6B778C]", children: ["<", tag, ">"] }), _jsx("span", { className: "text-[13px] font-semibold text-[#091E42]", children: t(`taginfo.${tag.toUpperCase()}`, tag) })] }), _jsx(Button, { variant: "ghost", size: "icon", "aria-label": t('inlineCanvas.chrome.close', '关闭'), "data-testid": "inline-style-panel-close", onClick: onClose, children: _jsx(X, { className: "size-4" }) })] }), _jsx("div", { className: "min-h-0 flex-1 flex-col overflow-y-auto", children: sections.map((section, index) => (_jsxs("section", { "data-testid": `style-section-${section.id}`, className: "flex flex-col gap-3 px-3.5 py-3.5", children: [index > 0 && _jsx(Separator, { className: "-mt-3.5 mb-0.5" }), _jsx("p", { className: "text-xs font-semibold text-[#091E42]", children: t(section.labelKey) }), _jsx("div", { className: "grid grid-cols-2 gap-2.5", children: section.fields.map(field => (_jsx(StyleFieldInput, { field: field, initial: declaredStyles[field.property] ?? '', onApply: onApply, onReset: onReset }, field.property))) })] }, section.id))) }), _jsx("footer", { className: "shrink-0 bg-[#F7F8F9] px-3.5 py-3 text-xs leading-5 text-[#6B778C]", children: t('inlineCanvas.style.footnote', '每次改动都会记入改动清单,可单条撤销') })] }));
|
|
13
92
|
}
|
|
14
|
-
function StyleFieldInput({ field, initial, onApply }) {
|
|
93
|
+
function StyleFieldInput({ field, initial, onApply, onReset }) {
|
|
15
94
|
const { t } = useTranslation();
|
|
16
95
|
const [value, setValue] = useState(initial);
|
|
17
96
|
const [committed, setCommitted] = useState(initial);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
setValue(initial);
|
|
99
|
+
setCommitted(initial);
|
|
100
|
+
}, [initial]);
|
|
18
101
|
const commit = () => {
|
|
19
102
|
const composed = composeStyleValue(field, value);
|
|
20
103
|
if (!composed || composed === committed)
|
|
@@ -24,17 +107,27 @@ function StyleFieldInput({ field, initial, onApply }) {
|
|
|
24
107
|
};
|
|
25
108
|
const label = t(field.labelKey);
|
|
26
109
|
const id = `style-${field.property}`;
|
|
27
|
-
return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: id, className: "text-xs font-normal text-
|
|
110
|
+
return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-1", children: [_jsx(Label, { htmlFor: id, className: "text-xs font-normal text-muted-foreground", children: label }), onReset && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `${label} · ${t('inlineCanvas.review.reset', '清除覆盖')}`, onClick: () => {
|
|
111
|
+
setValue(initial);
|
|
112
|
+
setCommitted(initial);
|
|
113
|
+
onReset(field.property);
|
|
114
|
+
}, children: t('inlineCanvas.review.reset', '清除覆盖') }))] }), field.kind === 'select' ? (_jsxs(NativeSelect, { id: id, "data-testid": id, "aria-label": label, value: value, onChange: e => {
|
|
28
115
|
const next = e.target.value;
|
|
29
116
|
setValue(next);
|
|
30
117
|
if (next && next !== committed) {
|
|
31
118
|
setCommitted(next);
|
|
32
119
|
void onApply(field.property, next);
|
|
33
120
|
}
|
|
34
|
-
}, children: [_jsx("option", { value: "", children: "--" }), field.options?.map(o => (_jsx("option", { value: o, children:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
121
|
+
}, children: [_jsx("option", { value: "", children: "--" }), field.options?.map(o => (_jsx("option", { value: o, children: field.property === 'text-align'
|
|
122
|
+
? t(`inlineCanvas.review.align_${o}`, o)
|
|
123
|
+
: o }, o)))] })) : (_jsxs("div", { className: "flex items-center gap-1", children: [field.kind === 'color' && (_jsx("input", { type: "color", "aria-label": `${label} · ${t('inlineCanvas.review.colorSwatch', '选择颜色')}`, value: /^#[0-9a-f]{6}$/i.test(value) ? value : '#000000', className: "h-8 w-8 shrink-0 cursor-pointer rounded border border-border", onChange: e => {
|
|
124
|
+
setValue(e.target.value);
|
|
125
|
+
setCommitted(e.target.value);
|
|
126
|
+
void onApply(field.property, e.target.value);
|
|
127
|
+
} })), _jsxs(InputGroup, { children: [_jsx(InputGroupInput, { id: id, "data-testid": id, "aria-label": label, value: value, type: field.kind === 'color' ? 'text' : 'text', placeholder: field.unit ? 'auto / 100% / 1em' : undefined, onChange: e => setValue(e.target.value), onBlur: commit, onKeyDown: e => {
|
|
128
|
+
if (e.key === 'Enter') {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
commit();
|
|
131
|
+
}
|
|
132
|
+
} }), field.unit && (_jsx(InputGroupAddon, { align: "inline-end", children: _jsx(InputGroupText, { children: field.unit }) }))] })] }))] }));
|
|
40
133
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export declare const reviewLocales: {
|
|
2
|
+
readonly 'zh-CN': {
|
|
3
|
+
readonly generating: "正在生成截图…";
|
|
4
|
+
readonly noLayers: "没有匹配的图层";
|
|
5
|
+
readonly clearSearch: "清除搜索";
|
|
6
|
+
readonly searchLayers: "搜索图层";
|
|
7
|
+
readonly linkCopied: "链接已复制";
|
|
8
|
+
readonly copyLinkManual: "复制失败,请从下方手动复制链接";
|
|
9
|
+
readonly uploadError: "上传失败,可重试;本地图片仍可下载";
|
|
10
|
+
readonly screenshot: "元素截图";
|
|
11
|
+
readonly enlarge: "放大预览";
|
|
12
|
+
readonly shotTruncated: "元素超出可视区,只截到可见部分";
|
|
13
|
+
readonly shotScale: "截图分辨率约为原图的 {{scale}}%";
|
|
14
|
+
readonly downloadImage: "下载图片";
|
|
15
|
+
readonly imageCopied: "图片已复制";
|
|
16
|
+
readonly copyError: "复制失败,请下载图片";
|
|
17
|
+
readonly copyImage: "复制图片";
|
|
18
|
+
readonly uploading: "正在上传…";
|
|
19
|
+
readonly copyLink: "复制链接";
|
|
20
|
+
readonly shotLink: "截图链接";
|
|
21
|
+
readonly reset: "清除覆盖";
|
|
22
|
+
readonly colorSwatch: "选择颜色";
|
|
23
|
+
readonly saveDraft: "保存草稿";
|
|
24
|
+
readonly versions: "版本管理";
|
|
25
|
+
readonly removeEmptyVersion: "移除空版本";
|
|
26
|
+
readonly versionLimit: "最多 4 个版本";
|
|
27
|
+
readonly imageLimit: "请选择不超过 10 MB 的图片";
|
|
28
|
+
readonly currentMedia: "当前媒体";
|
|
29
|
+
readonly uploadFile: "上传文件";
|
|
30
|
+
readonly useLink: "使用链接";
|
|
31
|
+
readonly replaceImage: "替换当前图片";
|
|
32
|
+
readonly replaceBackground: "替换背景图片";
|
|
33
|
+
readonly insertImage: "插入新图片";
|
|
34
|
+
readonly dropImage: "拖入图片或选择文件,支持图片格式,最大 10 MB。上传只准备素材,应用后才修改画布。";
|
|
35
|
+
readonly pendingMedia: "待应用媒体";
|
|
36
|
+
readonly altHelp: "替代文本用于描述图片内容。装饰图片可留空。";
|
|
37
|
+
readonly videoLinkHint: "支持视频文件链接和 YouTube 嵌入链接。加载就绪后应用到画布。";
|
|
38
|
+
readonly mediaPreview: "媒体预览";
|
|
39
|
+
readonly loading: "正在加载预览…";
|
|
40
|
+
readonly loadError: "预览加载失败";
|
|
41
|
+
readonly retry: "重试";
|
|
42
|
+
readonly ready: "媒体已就绪,应用后修改画布";
|
|
43
|
+
readonly align_left: "左对齐";
|
|
44
|
+
readonly align_center: "居中";
|
|
45
|
+
readonly align_right: "右对齐";
|
|
46
|
+
readonly align_justify: "两端对齐";
|
|
47
|
+
};
|
|
48
|
+
readonly 'en-US': {
|
|
49
|
+
readonly generating: "Generating screenshot…";
|
|
50
|
+
readonly noLayers: "No matching layers";
|
|
51
|
+
readonly clearSearch: "Clear search";
|
|
52
|
+
readonly searchLayers: "Search layers";
|
|
53
|
+
readonly linkCopied: "Link copied";
|
|
54
|
+
readonly copyLinkManual: "Copy failed. Copy the link below manually.";
|
|
55
|
+
readonly uploadError: "Upload failed. Retry or download the local image.";
|
|
56
|
+
readonly screenshot: "Element screenshot";
|
|
57
|
+
readonly enlarge: "Enlarge preview";
|
|
58
|
+
readonly shotTruncated: "Only the visible part of the element was captured.";
|
|
59
|
+
readonly shotScale: "Screenshot resolution is about {{scale}}% of the original.";
|
|
60
|
+
readonly downloadImage: "Download image";
|
|
61
|
+
readonly imageCopied: "Image copied";
|
|
62
|
+
readonly copyError: "Copy failed. Download the image instead.";
|
|
63
|
+
readonly copyImage: "Copy image";
|
|
64
|
+
readonly uploading: "Uploading…";
|
|
65
|
+
readonly copyLink: "Copy link";
|
|
66
|
+
readonly shotLink: "Screenshot link";
|
|
67
|
+
readonly reset: "Clear override";
|
|
68
|
+
readonly colorSwatch: "Choose color";
|
|
69
|
+
readonly saveDraft: "Save draft";
|
|
70
|
+
readonly versions: "Manage versions";
|
|
71
|
+
readonly removeEmptyVersion: "Remove empty version";
|
|
72
|
+
readonly versionLimit: "Maximum 4 versions";
|
|
73
|
+
readonly imageLimit: "Choose an image up to 10 MB.";
|
|
74
|
+
readonly currentMedia: "Current media";
|
|
75
|
+
readonly uploadFile: "Upload file";
|
|
76
|
+
readonly useLink: "Use link";
|
|
77
|
+
readonly replaceImage: "Replace current image";
|
|
78
|
+
readonly replaceBackground: "Replace background image";
|
|
79
|
+
readonly insertImage: "Insert new image";
|
|
80
|
+
readonly dropImage: "Drop an image or choose a file, up to 10 MB. Upload prepares the asset; Apply changes the canvas.";
|
|
81
|
+
readonly pendingMedia: "Media to apply";
|
|
82
|
+
readonly altHelp: "Describe the image for assistive technology. Leave blank for decorative images.";
|
|
83
|
+
readonly videoLinkHint: "Use a video file URL or YouTube embed link. Apply after the preview is ready.";
|
|
84
|
+
readonly mediaPreview: "Media preview";
|
|
85
|
+
readonly loading: "Loading preview…";
|
|
86
|
+
readonly loadError: "Preview failed to load";
|
|
87
|
+
readonly retry: "Retry";
|
|
88
|
+
readonly ready: "Ready. Apply to update the canvas.";
|
|
89
|
+
readonly align_left: "Align left";
|
|
90
|
+
readonly align_center: "Center";
|
|
91
|
+
readonly align_right: "Align right";
|
|
92
|
+
readonly align_justify: "Justify";
|
|
93
|
+
};
|
|
94
|
+
readonly 'ja-JP': {
|
|
95
|
+
readonly generating: "スクリーンショットを生成中…";
|
|
96
|
+
readonly noLayers: "一致するレイヤーがありません";
|
|
97
|
+
readonly clearSearch: "検索をクリア";
|
|
98
|
+
readonly searchLayers: "レイヤーを検索";
|
|
99
|
+
readonly linkCopied: "リンクをコピーしました";
|
|
100
|
+
readonly copyLinkManual: "コピーできませんでした。下のリンクを手動でコピーしてください。";
|
|
101
|
+
readonly uploadError: "アップロードできませんでした。再試行または画像をダウンロードしてください。";
|
|
102
|
+
readonly screenshot: "要素のスクリーンショット";
|
|
103
|
+
readonly enlarge: "プレビューを拡大";
|
|
104
|
+
readonly shotTruncated: "表示されている部分のみ撮影しました。";
|
|
105
|
+
readonly shotScale: "解像度は元の約 {{scale}}% です。";
|
|
106
|
+
readonly downloadImage: "画像をダウンロード";
|
|
107
|
+
readonly imageCopied: "画像をコピーしました";
|
|
108
|
+
readonly copyError: "コピーできませんでした。画像をダウンロードしてください。";
|
|
109
|
+
readonly copyImage: "画像をコピー";
|
|
110
|
+
readonly uploading: "アップロード中…";
|
|
111
|
+
readonly copyLink: "リンクをコピー";
|
|
112
|
+
readonly shotLink: "スクリーンショットのリンク";
|
|
113
|
+
readonly reset: "上書きを解除";
|
|
114
|
+
readonly colorSwatch: "色を選択";
|
|
115
|
+
readonly saveDraft: "下書きを保存";
|
|
116
|
+
readonly versions: "バージョン管理";
|
|
117
|
+
readonly removeEmptyVersion: "空のバージョンを削除";
|
|
118
|
+
readonly versionLimit: "最大4バージョン";
|
|
119
|
+
readonly imageLimit: "10 MB 以下の画像を選択してください。";
|
|
120
|
+
readonly currentMedia: "現在のメディア";
|
|
121
|
+
readonly uploadFile: "ファイルをアップロード";
|
|
122
|
+
readonly useLink: "リンクを使用";
|
|
123
|
+
readonly replaceImage: "現在の画像を置き換え";
|
|
124
|
+
readonly replaceBackground: "背景画像を置き換え";
|
|
125
|
+
readonly insertImage: "新しい画像を挿入";
|
|
126
|
+
readonly dropImage: "画像をドロップするか選択してください(最大10 MB)。アップロードで素材を準備し、適用でキャンバスを変更します。";
|
|
127
|
+
readonly pendingMedia: "適用するメディア";
|
|
128
|
+
readonly altHelp: "画像の内容を説明してください。装飾用の画像は空欄にできます。";
|
|
129
|
+
readonly videoLinkHint: "動画ファイルのURLまたはYouTube埋め込みリンクを入力し、読み込み後に適用してください。";
|
|
130
|
+
readonly mediaPreview: "メディアプレビュー";
|
|
131
|
+
readonly loading: "プレビューを読み込み中…";
|
|
132
|
+
readonly loadError: "プレビューを読み込めませんでした";
|
|
133
|
+
readonly retry: "再試行";
|
|
134
|
+
readonly ready: "準備完了。適用するとキャンバスを変更します。";
|
|
135
|
+
readonly align_left: "左揃え";
|
|
136
|
+
readonly align_center: "中央揃え";
|
|
137
|
+
readonly align_right: "右揃え";
|
|
138
|
+
readonly align_justify: "両端揃え";
|
|
139
|
+
};
|
|
140
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export const reviewLocales = {
|
|
2
|
+
'zh-CN': {
|
|
3
|
+
generating: '正在生成截图…',
|
|
4
|
+
noLayers: '没有匹配的图层', clearSearch: '清除搜索', searchLayers: '搜索图层',
|
|
5
|
+
linkCopied: '链接已复制',
|
|
6
|
+
copyLinkManual: '复制失败,请从下方手动复制链接',
|
|
7
|
+
uploadError: '上传失败,可重试;本地图片仍可下载',
|
|
8
|
+
screenshot: '元素截图',
|
|
9
|
+
enlarge: '放大预览',
|
|
10
|
+
shotTruncated: '元素超出可视区,只截到可见部分',
|
|
11
|
+
shotScale: '截图分辨率约为原图的 {{scale}}%',
|
|
12
|
+
downloadImage: '下载图片',
|
|
13
|
+
imageCopied: '图片已复制',
|
|
14
|
+
copyError: '复制失败,请下载图片',
|
|
15
|
+
copyImage: '复制图片',
|
|
16
|
+
uploading: '正在上传…',
|
|
17
|
+
copyLink: '复制链接',
|
|
18
|
+
shotLink: '截图链接',
|
|
19
|
+
reset: '清除覆盖',
|
|
20
|
+
colorSwatch: '选择颜色',
|
|
21
|
+
saveDraft: '保存草稿',
|
|
22
|
+
versions: '版本管理',
|
|
23
|
+
removeEmptyVersion: '移除空版本',
|
|
24
|
+
versionLimit: '最多 4 个版本',
|
|
25
|
+
imageLimit: '请选择不超过 10 MB 的图片',
|
|
26
|
+
currentMedia: '当前媒体',
|
|
27
|
+
uploadFile: '上传文件',
|
|
28
|
+
useLink: '使用链接',
|
|
29
|
+
replaceImage: '替换当前图片',
|
|
30
|
+
replaceBackground: '替换背景图片',
|
|
31
|
+
insertImage: '插入新图片',
|
|
32
|
+
dropImage: '拖入图片或选择文件,支持图片格式,最大 10 MB。上传只准备素材,应用后才修改画布。',
|
|
33
|
+
pendingMedia: '待应用媒体',
|
|
34
|
+
altHelp: '替代文本用于描述图片内容。装饰图片可留空。',
|
|
35
|
+
videoLinkHint: '支持视频文件链接和 YouTube 嵌入链接。加载就绪后应用到画布。',
|
|
36
|
+
mediaPreview: '媒体预览',
|
|
37
|
+
loading: '正在加载预览…',
|
|
38
|
+
loadError: '预览加载失败',
|
|
39
|
+
retry: '重试',
|
|
40
|
+
ready: '媒体已就绪,应用后修改画布',
|
|
41
|
+
align_left: '左对齐',
|
|
42
|
+
align_center: '居中',
|
|
43
|
+
align_right: '右对齐',
|
|
44
|
+
align_justify: '两端对齐'
|
|
45
|
+
},
|
|
46
|
+
'en-US': {
|
|
47
|
+
generating: 'Generating screenshot…',
|
|
48
|
+
noLayers: 'No matching layers', clearSearch: 'Clear search', searchLayers: 'Search layers',
|
|
49
|
+
linkCopied: 'Link copied',
|
|
50
|
+
copyLinkManual: 'Copy failed. Copy the link below manually.',
|
|
51
|
+
uploadError: 'Upload failed. Retry or download the local image.',
|
|
52
|
+
screenshot: 'Element screenshot',
|
|
53
|
+
enlarge: 'Enlarge preview',
|
|
54
|
+
shotTruncated: 'Only the visible part of the element was captured.',
|
|
55
|
+
shotScale: 'Screenshot resolution is about {{scale}}% of the original.',
|
|
56
|
+
downloadImage: 'Download image',
|
|
57
|
+
imageCopied: 'Image copied',
|
|
58
|
+
copyError: 'Copy failed. Download the image instead.',
|
|
59
|
+
copyImage: 'Copy image',
|
|
60
|
+
uploading: 'Uploading…',
|
|
61
|
+
copyLink: 'Copy link',
|
|
62
|
+
shotLink: 'Screenshot link',
|
|
63
|
+
reset: 'Clear override',
|
|
64
|
+
colorSwatch: 'Choose color',
|
|
65
|
+
saveDraft: 'Save draft',
|
|
66
|
+
versions: 'Manage versions',
|
|
67
|
+
removeEmptyVersion: 'Remove empty version',
|
|
68
|
+
versionLimit: 'Maximum 4 versions',
|
|
69
|
+
imageLimit: 'Choose an image up to 10 MB.',
|
|
70
|
+
currentMedia: 'Current media',
|
|
71
|
+
uploadFile: 'Upload file',
|
|
72
|
+
useLink: 'Use link',
|
|
73
|
+
replaceImage: 'Replace current image',
|
|
74
|
+
replaceBackground: 'Replace background image',
|
|
75
|
+
insertImage: 'Insert new image',
|
|
76
|
+
dropImage: 'Drop an image or choose a file, up to 10 MB. Upload prepares the asset; Apply changes the canvas.',
|
|
77
|
+
pendingMedia: 'Media to apply',
|
|
78
|
+
altHelp: 'Describe the image for assistive technology. Leave blank for decorative images.',
|
|
79
|
+
videoLinkHint: 'Use a video file URL or YouTube embed link. Apply after the preview is ready.',
|
|
80
|
+
mediaPreview: 'Media preview',
|
|
81
|
+
loading: 'Loading preview…',
|
|
82
|
+
loadError: 'Preview failed to load',
|
|
83
|
+
retry: 'Retry',
|
|
84
|
+
ready: 'Ready. Apply to update the canvas.',
|
|
85
|
+
align_left: 'Align left',
|
|
86
|
+
align_center: 'Center',
|
|
87
|
+
align_right: 'Align right',
|
|
88
|
+
align_justify: 'Justify'
|
|
89
|
+
},
|
|
90
|
+
'ja-JP': {
|
|
91
|
+
generating: 'スクリーンショットを生成中…',
|
|
92
|
+
noLayers: '一致するレイヤーがありません', clearSearch: '検索をクリア', searchLayers: 'レイヤーを検索',
|
|
93
|
+
linkCopied: 'リンクをコピーしました',
|
|
94
|
+
copyLinkManual: 'コピーできませんでした。下のリンクを手動でコピーしてください。',
|
|
95
|
+
uploadError: 'アップロードできませんでした。再試行または画像をダウンロードしてください。',
|
|
96
|
+
screenshot: '要素のスクリーンショット',
|
|
97
|
+
enlarge: 'プレビューを拡大',
|
|
98
|
+
shotTruncated: '表示されている部分のみ撮影しました。',
|
|
99
|
+
shotScale: '解像度は元の約 {{scale}}% です。',
|
|
100
|
+
downloadImage: '画像をダウンロード',
|
|
101
|
+
imageCopied: '画像をコピーしました',
|
|
102
|
+
copyError: 'コピーできませんでした。画像をダウンロードしてください。',
|
|
103
|
+
copyImage: '画像をコピー',
|
|
104
|
+
uploading: 'アップロード中…',
|
|
105
|
+
copyLink: 'リンクをコピー',
|
|
106
|
+
shotLink: 'スクリーンショットのリンク',
|
|
107
|
+
reset: '上書きを解除',
|
|
108
|
+
colorSwatch: '色を選択',
|
|
109
|
+
saveDraft: '下書きを保存',
|
|
110
|
+
versions: 'バージョン管理',
|
|
111
|
+
removeEmptyVersion: '空のバージョンを削除',
|
|
112
|
+
versionLimit: '最大4バージョン',
|
|
113
|
+
imageLimit: '10 MB 以下の画像を選択してください。',
|
|
114
|
+
currentMedia: '現在のメディア',
|
|
115
|
+
uploadFile: 'ファイルをアップロード',
|
|
116
|
+
useLink: 'リンクを使用',
|
|
117
|
+
replaceImage: '現在の画像を置き換え',
|
|
118
|
+
replaceBackground: '背景画像を置き換え',
|
|
119
|
+
insertImage: '新しい画像を挿入',
|
|
120
|
+
dropImage: '画像をドロップするか選択してください(最大10 MB)。アップロードで素材を準備し、適用でキャンバスを変更します。',
|
|
121
|
+
pendingMedia: '適用するメディア',
|
|
122
|
+
altHelp: '画像の内容を説明してください。装飾用の画像は空欄にできます。',
|
|
123
|
+
videoLinkHint: '動画ファイルのURLまたはYouTube埋め込みリンクを入力し、読み込み後に適用してください。',
|
|
124
|
+
mediaPreview: 'メディアプレビュー',
|
|
125
|
+
loading: 'プレビューを読み込み中…',
|
|
126
|
+
loadError: 'プレビューを読み込めませんでした',
|
|
127
|
+
retry: '再試行',
|
|
128
|
+
ready: '準備完了。適用するとキャンバスを変更します。',
|
|
129
|
+
align_left: '左揃え',
|
|
130
|
+
align_center: '中央揃え',
|
|
131
|
+
align_right: '右揃え',
|
|
132
|
+
align_justify: '両端揃え'
|
|
133
|
+
}
|
|
134
|
+
};
|
package/dist/tree/LayersPanel.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
2
3
|
import { useState } from 'react';
|
|
3
|
-
import {
|
|
4
|
+
import { SearchInput } from '@ptengine/design-components';
|
|
4
5
|
import { TreePanel } from '../canvas-ui/index.js';
|
|
5
6
|
import { findIdBySelector } from './domTreeAdapter.js';
|
|
6
7
|
export function LayersPanel({ tree, selectedSelector, hoverSelector, onSelect, onHover, onMove, onExpand }) {
|
|
8
|
+
const { t } = useTranslation();
|
|
7
9
|
const [query, setQuery] = useState('');
|
|
8
10
|
if (!tree) {
|
|
9
11
|
return (_jsx("p", { "data-testid": "layers-empty", className: "px-3 py-6 text-center text-xs text-[#8993A4]", children: "\u8FD8\u6CA1\u6709\u62FF\u5230\u9875\u9762\u7ED3\u6784" }));
|
|
10
12
|
}
|
|
11
13
|
const selectorOf = (id) => tree.nodes[id]?.selector ?? id;
|
|
12
14
|
const idOf = (selector) => selector ? findIdBySelector(tree, selector) : null;
|
|
13
|
-
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", "data-testid": "layers-panel", children: [_jsx("div", { className: "p-2", children: _jsx(
|
|
15
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", "data-testid": "layers-panel", children: [_jsx("div", { className: "p-2", children: _jsx(SearchInput, { clearLabel: t('inlineCanvas.review.clearSearch', '清除搜索'), "data-testid": "layers-search", placeholder: t('inlineCanvas.review.searchLayers', '搜索图层'), value: query, onValueChange: setQuery, onClear: () => setQuery('') }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: _jsx(TreePanel, { emptyLabel: t('inlineCanvas.review.noLayers', '没有匹配的图层'), nodes: tree.nodes, rootId: tree.rootId, selectedId: idOf(selectedSelector), hoverId: idOf(hoverSelector ?? null), query: query, onSelect: id => onSelect(selectorOf(id)), onHover: id => onHover(id ? selectorOf(id) : ''), onMove: onMove &&
|
|
14
16
|
((dragId, targetId, pos) => onMove(selectorOf(dragId), selectorOf(targetId), pos)), onExpand: onExpand }) })] }));
|
|
15
17
|
}
|