@ptengine/inline-editor 0.0.0-stage → 0.1.1
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 +42 -0
- package/README.md +286 -2
- package/dist/InlineEditor.d.ts +349 -0
- package/dist/InlineEditor.js +886 -0
- package/dist/PagePreview.d.ts +83 -0
- package/dist/PagePreview.js +111 -0
- package/dist/assets/AssetsPanel.d.ts +19 -0
- package/dist/assets/AssetsPanel.js +23 -0
- package/dist/assets/componentLibrary.d.ts +23 -0
- package/dist/assets/componentLibrary.js +12 -0
- package/dist/canvas-ui/index.d.ts +17 -0
- package/dist/canvas-ui/index.js +7 -0
- package/dist/canvas-ui/scope.d.ts +13 -0
- package/dist/canvas-ui/scope.js +1 -0
- package/dist/canvas-ui/selection/classNames.d.ts +30 -0
- package/dist/canvas-ui/selection/classNames.js +15 -0
- package/dist/canvas-ui/selection/tokens.css +204 -0
- package/dist/canvas-ui/tree/TreePanel.d.ts +47 -0
- package/dist/canvas-ui/tree/TreePanel.js +132 -0
- package/dist/canvas-ui/tree/classNames.d.ts +21 -0
- package/dist/canvas-ui/tree/classNames.js +8 -0
- package/dist/canvas-ui/tree/order.d.ts +38 -0
- package/dist/canvas-ui/tree/order.js +38 -0
- package/dist/canvas-ui/tree/tree.css +131 -0
- package/dist/chrome/EditorChrome.d.ts +83 -0
- package/dist/chrome/EditorChrome.js +10 -0
- package/dist/chrome/VersionMenu.d.ts +26 -0
- package/dist/chrome/VersionMenu.js +86 -0
- package/dist/chrome/topbarPrimitives.d.ts +69 -0
- package/dist/chrome/topbarPrimitives.js +39 -0
- package/dist/chrome/useDismissOnOutside.d.ts +23 -0
- package/dist/chrome/useDismissOnOutside.js +27 -0
- package/dist/dialogs/ClickActionDialog.d.ts +24 -0
- package/dist/dialogs/ClickActionDialog.js +35 -0
- package/dist/dialogs/HtmlEditDialog.d.ts +23 -0
- package/dist/dialogs/HtmlEditDialog.js +23 -0
- package/dist/dialogs/ImageEditDialog.d.ts +30 -0
- package/dist/dialogs/ImageEditDialog.js +57 -0
- package/dist/dialogs/ImageSettingDialog.d.ts +16 -0
- package/dist/dialogs/ImageSettingDialog.js +17 -0
- package/dist/dialogs/InsertElementDialog.d.ts +23 -0
- package/dist/dialogs/InsertElementDialog.js +28 -0
- package/dist/dialogs/ReplaceVideoDialog.d.ts +18 -0
- package/dist/dialogs/ReplaceVideoDialog.js +16 -0
- package/dist/dialogs/actionTypes.d.ts +53 -0
- package/dist/dialogs/actionTypes.js +74 -0
- package/dist/dialogs/htmlValidation.d.ts +24 -0
- package/dist/dialogs/htmlValidation.js +37 -0
- package/dist/dialogs/imageEdit.d.ts +49 -0
- package/dist/dialogs/imageEdit.js +30 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +53 -0
- package/dist/kernel/CanvasContextBar.d.ts +23 -0
- package/dist/kernel/CanvasContextBar.js +12 -0
- package/dist/kernel/CanvasFrame.d.ts +56 -0
- package/dist/kernel/CanvasFrame.js +53 -0
- package/dist/kernel/ai/aiInlineApply.d.ts +330 -0
- package/dist/kernel/ai/aiInlineApply.js +477 -0
- package/dist/kernel/ai/imageSource.d.ts +24 -0
- package/dist/kernel/ai/imageSource.js +19 -0
- package/dist/kernel/ai/insertHtmlGate.d.ts +71 -0
- package/dist/kernel/ai/insertHtmlGate.js +123 -0
- package/dist/kernel/ai/postApplyCheck.d.ts +56 -0
- package/dist/kernel/ai/postApplyCheck.js +74 -0
- package/dist/kernel/ai/replaceHtmlGate.d.ts +71 -0
- package/dist/kernel/ai/replaceHtmlGate.js +147 -0
- package/dist/kernel/blocks/BlockOverlay.d.ts +16 -0
- package/dist/kernel/blocks/BlockOverlay.js +66 -0
- package/dist/kernel/blocks/annotateAndTile.d.ts +106 -0
- package/dist/kernel/blocks/annotateAndTile.js +122 -0
- package/dist/kernel/blocks/resolveBlocks.d.ts +153 -0
- package/dist/kernel/blocks/resolveBlocks.js +233 -0
- package/dist/kernel/canvasCapabilities.d.ts +31 -0
- package/dist/kernel/canvasCapabilities.js +39 -0
- package/dist/kernel/canvasCommands.d.ts +126 -0
- package/dist/kernel/canvasCommands.js +146 -0
- package/dist/kernel/canvasErrors.d.ts +1 -0
- package/dist/kernel/canvasErrors.js +14 -0
- package/dist/kernel/captureGeometry.d.ts +35 -0
- package/dist/kernel/captureGeometry.js +25 -0
- package/dist/kernel/captureOrchestration.d.ts +48 -0
- package/dist/kernel/captureOrchestration.js +43 -0
- package/dist/kernel/captureSlices.d.ts +36 -0
- package/dist/kernel/captureSlices.js +20 -0
- package/dist/kernel/changelist/ChangeListPanel.d.ts +13 -0
- package/dist/kernel/changelist/ChangeListPanel.js +47 -0
- package/dist/kernel/changelist/ChangeListPopover.d.ts +20 -0
- package/dist/kernel/changelist/ChangeListPopover.js +10 -0
- package/dist/kernel/changelist/builders.d.ts +89 -0
- package/dist/kernel/changelist/builders.js +81 -0
- package/dist/kernel/changelist/changeSummary.d.ts +19 -0
- package/dist/kernel/changelist/changeSummary.js +85 -0
- package/dist/kernel/changelist/clipboard.d.ts +16 -0
- package/dist/kernel/changelist/clipboard.js +4 -0
- package/dist/kernel/changelist/exportForSave.d.ts +30 -0
- package/dist/kernel/changelist/exportForSave.js +11 -0
- package/dist/kernel/changelist/mergeForSave.d.ts +79 -0
- package/dist/kernel/changelist/mergeForSave.js +40 -0
- package/dist/kernel/changelist/moveObserverSafety.d.ts +70 -0
- package/dist/kernel/changelist/moveObserverSafety.js +39 -0
- package/dist/kernel/changelist/overrides.d.ts +22 -0
- package/dist/kernel/changelist/overrides.js +63 -0
- package/dist/kernel/changelist/useChangeList.d.ts +200 -0
- package/dist/kernel/changelist/useChangeList.js +282 -0
- package/dist/kernel/config.d.ts +29 -0
- package/dist/kernel/config.js +10 -0
- package/dist/kernel/dataUrlToBlob.d.ts +8 -0
- package/dist/kernel/dataUrlToBlob.js +17 -0
- package/dist/kernel/linkHref.d.ts +25 -0
- package/dist/kernel/linkHref.js +16 -0
- package/dist/kernel/mobileFit.d.ts +46 -0
- package/dist/kernel/mobileFit.js +44 -0
- package/dist/kernel/moveState.d.ts +35 -0
- package/dist/kernel/moveState.js +14 -0
- package/dist/kernel/selection/SelectionOverlay.d.ts +10 -0
- package/dist/kernel/selection/SelectionOverlay.js +25 -0
- package/dist/kernel/selection/SelectionToolbar.d.ts +30 -0
- package/dist/kernel/selection/SelectionToolbar.js +82 -0
- package/dist/kernel/selection/SelectorPathBar.d.ts +27 -0
- package/dist/kernel/selection/SelectorPathBar.js +55 -0
- package/dist/kernel/selection/buildDescendantShelf.d.ts +40 -0
- package/dist/kernel/selection/buildDescendantShelf.js +50 -0
- package/dist/kernel/selection/buildRootShelf.d.ts +26 -0
- package/dist/kernel/selection/buildRootShelf.js +149 -0
- package/dist/kernel/selection/elementAiContext.d.ts +324 -0
- package/dist/kernel/selection/elementAiContext.js +122 -0
- package/dist/kernel/selection/elementGeometryForAi.d.ts +9 -0
- package/dist/kernel/selection/elementGeometryForAi.js +15 -0
- package/dist/kernel/selection/geometry.d.ts +55 -0
- package/dist/kernel/selection/geometry.js +18 -0
- package/dist/kernel/selection/resolveAnchors.d.ts +116 -0
- package/dist/kernel/selection/resolveAnchors.js +173 -0
- package/dist/kernel/selection/shelfRow.d.ts +33 -0
- package/dist/kernel/selection/shelfRow.js +21 -0
- package/dist/kernel/selection/tagLabels.d.ts +9 -0
- package/dist/kernel/selection/tagLabels.js +46 -0
- package/dist/kernel/selection/toolbarButtons.d.ts +60 -0
- package/dist/kernel/selection/toolbarButtons.js +105 -0
- package/dist/kernel/selection/useAiSelectionContext.d.ts +26 -0
- package/dist/kernel/selection/useAiSelectionContext.js +26 -0
- package/dist/kernel/selection/useCanvasGeometry.d.ts +32 -0
- package/dist/kernel/selection/useCanvasGeometry.js +47 -0
- package/dist/kernel/selection/useSelectionRestore.d.ts +18 -0
- package/dist/kernel/selection/useSelectionRestore.js +25 -0
- package/dist/kernel/shrinkImageForUpload.d.ts +45 -0
- package/dist/kernel/shrinkImageForUpload.js +61 -0
- package/dist/kernel/useCanvasBridge.d.ts +36 -0
- package/dist/kernel/useCanvasBridge.js +41 -0
- package/dist/kernel/useCanvasGate.d.ts +22 -0
- package/dist/kernel/useCanvasGate.js +35 -0
- package/dist/kernel/useCanvasSession.d.ts +44 -0
- package/dist/kernel/useCanvasSession.js +182 -0
- package/dist/kernel/useCanvasStream.d.ts +39 -0
- package/dist/kernel/useCanvasStream.js +45 -0
- package/dist/kernel/useMobileFit.d.ts +40 -0
- package/dist/kernel/useMobileFit.js +139 -0
- package/dist/locales.d.ts +1094 -0
- package/dist/locales.js +545 -0
- package/dist/panels/StylePanel.d.ts +24 -0
- package/dist/panels/StylePanel.js +40 -0
- package/dist/panels/styleFields.d.ts +39 -0
- package/dist/panels/styleFields.js +91 -0
- package/dist/tree/LayersPanel.d.ts +30 -0
- package/dist/tree/LayersPanel.js +15 -0
- package/dist/tree/domTreeAdapter.d.ts +63 -0
- package/dist/tree/domTreeAdapter.js +90 -0
- package/dist/useInlineEditor.d.ts +161 -0
- package/dist/useInlineEditor.js +346 -0
- package/package.json +58 -3
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { CANVAS_UI_SCOPE } from './canvas-ui/index.js';
|
|
5
|
+
import { EditorChrome } from './chrome/EditorChrome.js';
|
|
6
|
+
import { CanvasFrame, TARGET_VIEWPORT_WIDTH, computeZoom } from './kernel/CanvasFrame.js';
|
|
7
|
+
import { CanvasContextBar } from './kernel/CanvasContextBar.js';
|
|
8
|
+
import { SelectionOverlay } from './kernel/selection/SelectionOverlay.js';
|
|
9
|
+
import { BlockOverlay } from './kernel/blocks/BlockOverlay.js';
|
|
10
|
+
import { dataUrlToBlob } from './kernel/dataUrlToBlob.js';
|
|
11
|
+
import { SelectionToolbar } from './kernel/selection/SelectionToolbar.js';
|
|
12
|
+
import { ChangeListPanel } from './kernel/changelist/ChangeListPanel.js';
|
|
13
|
+
import { ChangeListPopover } from './kernel/changelist/ChangeListPopover.js';
|
|
14
|
+
import { frameRectToProductRect, measureFrameContext } from './kernel/selection/geometry.js';
|
|
15
|
+
import { buildElementAiContext, extractPageLinks, linksFragment, ROOT_SHELF_LIMIT, SHELF_TREE_MAX_DEPTH, SHELF_TREE_MAX_NODES } from './kernel/selection/elementAiContext.js';
|
|
16
|
+
import { getAncestorChain, getDomTree, getElementCapabilities, getElementImages, getOuterHtml, resolveCanvasSelector } from './kernel/canvasCommands.js';
|
|
17
|
+
import { useAiSelectionContext } from './kernel/selection/useAiSelectionContext.js';
|
|
18
|
+
import { buildDescendantShelf } from './kernel/selection/buildDescendantShelf.js';
|
|
19
|
+
import { buildRootShelf } from './kernel/selection/buildRootShelf.js';
|
|
20
|
+
import { backgroundImageAncestorFor, blockContainerFor, resolveInlinePlanAnchors, withImageRows } from './kernel/selection/resolveAnchors.js';
|
|
21
|
+
import { elementGeometryForAi } from './kernel/selection/elementGeometryForAi.js';
|
|
22
|
+
import { applyAiChanges } from './kernel/ai/aiInlineApply.js';
|
|
23
|
+
import { APPLY_CHECK_SETTLE_MS } from './kernel/ai/postApplyCheck.js';
|
|
24
|
+
import { deleteElement, editHtml, editStyle, insertElement, replaceImage, replaceVideo, setClickAction, setImageAlt } from './kernel/changelist/useChangeList.js';
|
|
25
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@ptengine/design-components';
|
|
26
|
+
import { pasteModes } from './kernel/selection/toolbarButtons.js';
|
|
27
|
+
import { ClickActionDialog } from './dialogs/ClickActionDialog.js';
|
|
28
|
+
import { ImageEditDialog } from './dialogs/ImageEditDialog.js';
|
|
29
|
+
import { resolveImageBranch } from './dialogs/imageEdit.js';
|
|
30
|
+
import { ImageSettingDialog } from './dialogs/ImageSettingDialog.js';
|
|
31
|
+
import { InsertElementDialog } from './dialogs/InsertElementDialog.js';
|
|
32
|
+
import { ReplaceVideoDialog } from './dialogs/ReplaceVideoDialog.js';
|
|
33
|
+
import { StylePanel } from './panels/StylePanel.js';
|
|
34
|
+
import { HtmlEditDialog } from './dialogs/HtmlEditDialog.js';
|
|
35
|
+
import { setCode } from './kernel/canvasCommands.js';
|
|
36
|
+
import { describeCanvasError } from './kernel/canvasErrors.js';
|
|
37
|
+
import { LayersPanel } from './tree/LayersPanel.js';
|
|
38
|
+
import { AssetsPanel } from './assets/AssetsPanel.js';
|
|
39
|
+
import { componentInsertPayload } from './assets/componentLibrary.js';
|
|
40
|
+
import { moveModeFromDropPos } from './tree/domTreeAdapter.js';
|
|
41
|
+
import { useInlineEditor } from './useInlineEditor.js';
|
|
42
|
+
import { shrinkImageForUpload } from './kernel/shrinkImageForUpload.js';
|
|
43
|
+
const nodeSelectorOf = (n) => n.selector;
|
|
44
|
+
function imageTargetOf(caps) {
|
|
45
|
+
if (!caps)
|
|
46
|
+
return undefined;
|
|
47
|
+
const branch = resolveImageBranch(caps);
|
|
48
|
+
if (branch === 'img-src')
|
|
49
|
+
return 'img';
|
|
50
|
+
return branch === 'background' ? 'background' : undefined;
|
|
51
|
+
}
|
|
52
|
+
const AI_ELEMENT_IMAGE_LIMIT = 4;
|
|
53
|
+
export function InlineEditor({ url, device, onDeviceChange, title, versionLabel = 'V1', chrome = 'full', onExit, onSave, libraryComponents, libraryLoading, onAddVersion, onRenameVersion, onPreview, onToggleAi, aiOpen, uploadImage, initialChanges, onHydrated, onAskAi, onPageContextReady, anchors, onEditorReady, onSessionReady, onAiContextChange, onAiApplyReady, onAiInsertedRemoved, onAiInsertedRestored, trackAiSelection, renderNodeIcon, versions, activeVersionId, versionChanges, onVersionChange, onVersionLeave, onVersionEnter, resolveChangeId, allowedImageOrigins, allowedImageUrls, allowedLinkUrls, viewportWidthOverride, blockOverlay }) {
|
|
54
|
+
const { t } = useTranslation();
|
|
55
|
+
const canvasAreaRef = useRef(null);
|
|
56
|
+
const canvasIframeRef = useRef(null);
|
|
57
|
+
const insertedRemovedRef = useRef(null);
|
|
58
|
+
const insertedRestoredRef = useRef(null);
|
|
59
|
+
const editor = useInlineEditor({
|
|
60
|
+
onInsertedRemoved: selectors => insertedRemovedRef.current?.(selectors),
|
|
61
|
+
onInsertedRestored: selectors => insertedRestoredRef.current?.(selectors),
|
|
62
|
+
url,
|
|
63
|
+
device,
|
|
64
|
+
iframeRef: canvasIframeRef,
|
|
65
|
+
renderNodeIcon
|
|
66
|
+
});
|
|
67
|
+
const { selection, tree, clipboard, move, capturing, textEditing, geometry, changeList, commands, frameProps, domReady } = editor;
|
|
68
|
+
const selectAndReveal = useCallback(async (selector) => {
|
|
69
|
+
await commands.select(selector);
|
|
70
|
+
await commands.reveal(selector);
|
|
71
|
+
}, [commands]);
|
|
72
|
+
const [commandError, setCommandError] = useState(null);
|
|
73
|
+
const [aiApplyNote, setAiApplyNote] = useState(null);
|
|
74
|
+
const [shot, setShot] = useState(null);
|
|
75
|
+
const [shotUrl, setShotUrl] = useState(null);
|
|
76
|
+
const [panelWidth, setPanelWidth] = useState(0);
|
|
77
|
+
const [showChangeList, setShowChangeList] = useState(false);
|
|
78
|
+
const [stylePanelOpen, setStylePanelOpen] = useState(false);
|
|
79
|
+
const [htmlDialog, setHtmlDialog] = useState({ open: false, seed: '' });
|
|
80
|
+
const [imageDialog, setImageDialog] = useState({ open: false, seed: '' });
|
|
81
|
+
const [imageSettingDialog, setImageSettingDialog] = useState({ open: false, seed: '' });
|
|
82
|
+
const [videoDialog, setVideoDialog] = useState({ open: false, seed: '' });
|
|
83
|
+
const [insertDialog, setInsertDialog] = useState(false);
|
|
84
|
+
const [pasteDialog, setPasteDialog] = useState(false);
|
|
85
|
+
const [showLeftPanel, setShowLeftPanel] = useState(true);
|
|
86
|
+
const [leftTab, setLeftTab] = useState('layers');
|
|
87
|
+
const [actionDialog, setActionDialog] = useState({ open: false, seed: null });
|
|
88
|
+
const pageOrigin = useMemo(() => {
|
|
89
|
+
try {
|
|
90
|
+
return new URL(url).origin;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
}, [url]);
|
|
96
|
+
const imageOrigins = useMemo(() => Array.from(new Set([pageOrigin, ...(allowedImageOrigins ?? [])].filter(Boolean))), [pageOrigin, allowedImageOrigins]);
|
|
97
|
+
const changeListRef = useRef(changeList);
|
|
98
|
+
changeListRef.current = changeList;
|
|
99
|
+
const versionChangesRef = useRef(versionChanges);
|
|
100
|
+
versionChangesRef.current = versionChanges;
|
|
101
|
+
const versionCallbacksRef = useRef({ onVersionChange, onVersionLeave, onVersionEnter });
|
|
102
|
+
versionCallbacksRef.current = { onVersionChange, onVersionLeave, onVersionEnter };
|
|
103
|
+
const resolveChangeIdRef = useRef(resolveChangeId);
|
|
104
|
+
resolveChangeIdRef.current = resolveChangeId;
|
|
105
|
+
const hasVersions = !!versions?.length;
|
|
106
|
+
const hasVersionsRef = useRef(hasVersions);
|
|
107
|
+
hasVersionsRef.current = hasVersions;
|
|
108
|
+
const loadedVersionRef = useRef(activeVersionId);
|
|
109
|
+
const [loadedVersion, setLoadedVersion] = useState(activeVersionId);
|
|
110
|
+
const leftVersionIdsRef = useRef(new Map());
|
|
111
|
+
const [switching, setSwitching] = useState(false);
|
|
112
|
+
const switchingRef = useRef(false);
|
|
113
|
+
const queueRef = useRef(Promise.resolve());
|
|
114
|
+
const enqueue = useCallback((task) => {
|
|
115
|
+
const run = queueRef.current.then(task, task);
|
|
116
|
+
queueRef.current = run.then(() => undefined, () => undefined);
|
|
117
|
+
return run;
|
|
118
|
+
}, []);
|
|
119
|
+
const prevIdsFor = useCallback((versionId, next) => {
|
|
120
|
+
const remembered = versionId ? leftVersionIdsRef.current.get(versionId) : undefined;
|
|
121
|
+
if (remembered?.length === next.length)
|
|
122
|
+
return remembered;
|
|
123
|
+
const live = changeListRef.current.peekList().map(r => r.changeId);
|
|
124
|
+
if (live.length === next.length && live.length > 0)
|
|
125
|
+
return live;
|
|
126
|
+
const fromChanges = next.map(c => c.id ?? '');
|
|
127
|
+
return fromChanges.length > 0 && fromChanges.every(Boolean) ? fromChanges : undefined;
|
|
128
|
+
}, []);
|
|
129
|
+
const switchToVersion = useCallback(async (nextId) => {
|
|
130
|
+
const prevId = loadedVersionRef.current;
|
|
131
|
+
if (!hasVersionsRef.current || nextId === prevId)
|
|
132
|
+
return;
|
|
133
|
+
setSwitching(true);
|
|
134
|
+
switchingRef.current = true;
|
|
135
|
+
try {
|
|
136
|
+
const live = changeListRef.current.peekList();
|
|
137
|
+
if (prevId) {
|
|
138
|
+
versionCallbacksRef.current.onVersionLeave?.(prevId, live);
|
|
139
|
+
leftVersionIdsRef.current.set(prevId, live.map(r => r.changeId));
|
|
140
|
+
}
|
|
141
|
+
const next = versionChangesRef.current?.[nextId] ?? [];
|
|
142
|
+
const { idMap } = await changeListRef.current.replaceAll(next, prevIdsFor(nextId, next));
|
|
143
|
+
loadedVersionRef.current = nextId;
|
|
144
|
+
setLoadedVersion(nextId);
|
|
145
|
+
versionCallbacksRef.current.onVersionEnter?.(nextId, idMap);
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
setSwitching(false);
|
|
149
|
+
switchingRef.current = false;
|
|
150
|
+
}
|
|
151
|
+
}, [prevIdsFor]);
|
|
152
|
+
const rejectWhileSwitching = useCallback((run) => {
|
|
153
|
+
if (!switchingRef.current)
|
|
154
|
+
return run();
|
|
155
|
+
return Promise.reject(new Error('VERSION_SWITCHING'));
|
|
156
|
+
}, []);
|
|
157
|
+
const writes = useMemo(() => ({
|
|
158
|
+
addStyle: input => rejectWhileSwitching(() => changeListRef.current.addStyle(input)),
|
|
159
|
+
addContent: input => rejectWhileSwitching(() => changeListRef.current.addContent(input)),
|
|
160
|
+
addAttribute: input => rejectWhileSwitching(() => changeListRef.current.addAttribute(input)),
|
|
161
|
+
addAction: input => rejectWhileSwitching(() => changeListRef.current.addAction(input)),
|
|
162
|
+
addMove: input => rejectWhileSwitching(() => changeListRef.current.addMove(input)),
|
|
163
|
+
revert: id => rejectWhileSwitching(() => changeListRef.current.revert(id)),
|
|
164
|
+
revertAll: () => rejectWhileSwitching(() => changeListRef.current.revertAll())
|
|
165
|
+
}), [rejectWhileSwitching]);
|
|
166
|
+
const uiWrite = useCallback((run) => {
|
|
167
|
+
if (switchingRef.current) {
|
|
168
|
+
setCommandError(t('inlineCanvas.version.switching', '正在切换版本,请稍候'));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
void run().catch(e => setCommandError(describeCanvasError(e)));
|
|
172
|
+
}, [t]);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (!hasVersions || !activeVersionId)
|
|
175
|
+
return;
|
|
176
|
+
if (activeVersionId === loadedVersionRef.current)
|
|
177
|
+
return;
|
|
178
|
+
void enqueue(() => switchToVersion(activeVersionId)).catch(e => {
|
|
179
|
+
setCommandError(describeCanvasError(e));
|
|
180
|
+
const back = loadedVersionRef.current;
|
|
181
|
+
if (back)
|
|
182
|
+
versionCallbacksRef.current.onVersionChange?.(back);
|
|
183
|
+
});
|
|
184
|
+
}, [activeVersionId, hasVersions, enqueue, switchToVersion]);
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
const el = canvasAreaRef.current;
|
|
187
|
+
if (!el)
|
|
188
|
+
return;
|
|
189
|
+
const ro = new ResizeObserver(entries => {
|
|
190
|
+
const w = entries[0]?.contentRect.width;
|
|
191
|
+
if (w)
|
|
192
|
+
setPanelWidth(w);
|
|
193
|
+
});
|
|
194
|
+
ro.observe(el);
|
|
195
|
+
return () => ro.disconnect();
|
|
196
|
+
}, []);
|
|
197
|
+
const sessionIdForAssets = frameProps?.sessionId;
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
injectedAssetsRef.current = [];
|
|
200
|
+
}, [sessionIdForAssets]);
|
|
201
|
+
const hydratedSessionRef = useRef(null);
|
|
202
|
+
const sessionIdForHydrate = frameProps?.sessionId;
|
|
203
|
+
const bootChanges = hasVersions && activeVersionId && versionChanges?.[activeVersionId]
|
|
204
|
+
? versionChanges[activeVersionId]
|
|
205
|
+
: initialChanges;
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (!domReady || !sessionIdForHydrate)
|
|
208
|
+
return;
|
|
209
|
+
if (hydratedSessionRef.current === sessionIdForHydrate)
|
|
210
|
+
return;
|
|
211
|
+
hydratedSessionRef.current = sessionIdForHydrate;
|
|
212
|
+
if (!bootChanges?.length)
|
|
213
|
+
return;
|
|
214
|
+
const versionId = hasVersions ? activeVersionId : undefined;
|
|
215
|
+
void enqueue(async () => {
|
|
216
|
+
const { outcome, idMap } = await changeListRef.current.replaceAll(bootChanges, prevIdsFor(versionId, bootChanges));
|
|
217
|
+
if (versionId) {
|
|
218
|
+
loadedVersionRef.current = versionId;
|
|
219
|
+
setLoadedVersion(versionId);
|
|
220
|
+
versionCallbacksRef.current.onVersionEnter?.(versionId, idMap);
|
|
221
|
+
}
|
|
222
|
+
onHydrated?.(outcome);
|
|
223
|
+
}).catch(e => setCommandError(describeCanvasError(e)));
|
|
224
|
+
}, [domReady, sessionIdForHydrate, bootChanges]);
|
|
225
|
+
const commandsRef = useRef(commands);
|
|
226
|
+
commandsRef.current = commands;
|
|
227
|
+
const sessionReady = domReady && !editor.mobileFit.pending;
|
|
228
|
+
const readySessionRef = useRef(null);
|
|
229
|
+
readySessionRef.current = sessionReady && sessionIdForHydrate ? sessionIdForHydrate : null;
|
|
230
|
+
const fitScaleRef = useRef(1);
|
|
231
|
+
fitScaleRef.current = editor.mobileFit.scale;
|
|
232
|
+
const sessionReadyRef = useRef(null);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (!onSessionReady || !sessionReady || !sessionIdForHydrate)
|
|
235
|
+
return;
|
|
236
|
+
if (sessionReadyRef.current === sessionIdForHydrate)
|
|
237
|
+
return;
|
|
238
|
+
sessionReadyRef.current = sessionIdForHydrate;
|
|
239
|
+
onSessionReady(sessionIdForHydrate);
|
|
240
|
+
}, [onSessionReady, sessionReady, sessionIdForHydrate]);
|
|
241
|
+
const editorHandleSessionRef = useRef(null);
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
if (!onEditorReady || !sessionReady || !sessionIdForHydrate)
|
|
244
|
+
return;
|
|
245
|
+
if (editorHandleSessionRef.current === sessionIdForHydrate)
|
|
246
|
+
return;
|
|
247
|
+
editorHandleSessionRef.current = sessionIdForHydrate;
|
|
248
|
+
onEditorReady({
|
|
249
|
+
clearSelection: () => commandsRef.current.select(''),
|
|
250
|
+
peekList: () => changeListRef.current.peekList(),
|
|
251
|
+
hasUnsavedChanges: () => changeListRef.current.peekList().length > 0,
|
|
252
|
+
capturePage: async () => {
|
|
253
|
+
const outcome = await commandsRef.current.captureSelectedElement('body');
|
|
254
|
+
return dataUrlToBlob(outcome.dataUrl);
|
|
255
|
+
},
|
|
256
|
+
scrollToSelector: selector => commandsRef.current.reveal(selector),
|
|
257
|
+
getSessionId: () => readySessionRef.current,
|
|
258
|
+
getMobileFitScale: () => fitScaleRef.current
|
|
259
|
+
});
|
|
260
|
+
}, [onEditorReady, sessionReady, sessionIdForHydrate]);
|
|
261
|
+
const selectedSelector = selection?.selector;
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
setStylePanelOpen(false);
|
|
264
|
+
}, [selectedSelector]);
|
|
265
|
+
const overridePc = viewportWidthOverride?.pc;
|
|
266
|
+
const overrideMobile = viewportWidthOverride?.mobile;
|
|
267
|
+
const targetViewportWidth = useMemo(() => ({
|
|
268
|
+
pc: overridePc ?? TARGET_VIEWPORT_WIDTH.pc,
|
|
269
|
+
mobile: overrideMobile ?? TARGET_VIEWPORT_WIDTH.mobile
|
|
270
|
+
}), [overridePc, overrideMobile]);
|
|
271
|
+
const zoom = computeZoom(panelWidth, targetViewportWidth[device]);
|
|
272
|
+
const iframeRect = canvasIframeRef.current?.getBoundingClientRect();
|
|
273
|
+
const overlayContext = useMemo(() => measureFrameContext(canvasIframeRef.current, zoom), [iframeRect?.left, iframeRect?.top, zoom]);
|
|
274
|
+
const canvasAreaRect = canvasAreaRef.current?.getBoundingClientRect();
|
|
275
|
+
const area = {
|
|
276
|
+
x: canvasAreaRect?.left ?? 0,
|
|
277
|
+
y: canvasAreaRect?.top ?? 0,
|
|
278
|
+
width: canvasAreaRect?.width ?? 0,
|
|
279
|
+
height: canvasAreaRect?.height ?? 0
|
|
280
|
+
};
|
|
281
|
+
const selectedRect = geometry.latest?.selectedViewportRect
|
|
282
|
+
? frameRectToProductRect(geometry.latest.selectedViewportRect, overlayContext)
|
|
283
|
+
: null;
|
|
284
|
+
const depsOf = useCallback((self) => ({
|
|
285
|
+
identifier: self.selector,
|
|
286
|
+
selector: self.selector,
|
|
287
|
+
ancestors: (selection?.chain ?? []).slice(1).map(n => n.selector),
|
|
288
|
+
addStyle: writes.addStyle,
|
|
289
|
+
addContent: writes.addContent,
|
|
290
|
+
addAttribute: writes.addAttribute,
|
|
291
|
+
addAction: writes.addAction
|
|
292
|
+
}), [writes, selection]);
|
|
293
|
+
const injectedAssetsRef = useRef([]);
|
|
294
|
+
const insertComponent = useCallback(async (component) => {
|
|
295
|
+
const target = selection?.chain[0];
|
|
296
|
+
const sessionId = frameProps?.sessionId;
|
|
297
|
+
if (!target || !sessionId)
|
|
298
|
+
return;
|
|
299
|
+
if (switchingRef.current) {
|
|
300
|
+
setCommandError(t('inlineCanvas.version.switching', '正在切换版本,请稍候'));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
setCommandError(null);
|
|
304
|
+
try {
|
|
305
|
+
const { html, codeList } = componentInsertPayload(component);
|
|
306
|
+
if (codeList.length > 0) {
|
|
307
|
+
injectedAssetsRef.current = [...injectedAssetsRef.current, ...codeList];
|
|
308
|
+
await setCode(sessionId, injectedAssetsRef.current);
|
|
309
|
+
}
|
|
310
|
+
await writes.addContent({
|
|
311
|
+
identifier: target.selector,
|
|
312
|
+
selector: target.selector,
|
|
313
|
+
html,
|
|
314
|
+
mode: 'append'
|
|
315
|
+
});
|
|
316
|
+
if (component.js) {
|
|
317
|
+
setCommandError('组件已插入。它带有 JS,画布里不会执行(客户页没有 SDK 运行时),发布后才生效');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
setCommandError(describeCanvasError(error));
|
|
322
|
+
}
|
|
323
|
+
}, [writes, frameProps?.sessionId, selection, t]);
|
|
324
|
+
const captureAiShot = useCallback(async (selector) => {
|
|
325
|
+
if (!uploadImage)
|
|
326
|
+
return { error: 'host did not provide uploadImage' };
|
|
327
|
+
try {
|
|
328
|
+
const outcome = await commands.captureSelectedElement(selector);
|
|
329
|
+
const raw = await (await fetch(outcome.dataUrl)).blob();
|
|
330
|
+
const blob = await shrinkImageForUpload(raw);
|
|
331
|
+
const url = await uploadImage(blob);
|
|
332
|
+
return url ? { url } : { error: 'upload returned no url' };
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
setCommandError(describeCanvasError(error));
|
|
336
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
337
|
+
}
|
|
338
|
+
}, [uploadImage, commands]);
|
|
339
|
+
const captureAiScreenshot = useCallback(async (selector) => {
|
|
340
|
+
const shot = await captureAiShot(selector);
|
|
341
|
+
return 'url' in shot ? shot.url : null;
|
|
342
|
+
}, [captureAiShot]);
|
|
343
|
+
const descendantShelfRef = useRef([]);
|
|
344
|
+
const geometryRef = useRef(null);
|
|
345
|
+
geometryRef.current = geometry.latest;
|
|
346
|
+
const [elementImages, setElementImages] = useState(null);
|
|
347
|
+
const buildAiContext = useCallback((self, outerHtml) => buildElementAiContext({
|
|
348
|
+
selector: self.selector,
|
|
349
|
+
tag: self.tag,
|
|
350
|
+
imageTarget: imageTargetOf(selection?.capabilities),
|
|
351
|
+
images: elementImages?.selector === self.selector ? elementImages.images : undefined,
|
|
352
|
+
ancestors: (selection?.chain ?? []).map(n => n.selector).slice().reverse(),
|
|
353
|
+
outerHtml,
|
|
354
|
+
descendants: descendantShelfRef.current,
|
|
355
|
+
hasChildren: (selection?.capabilities?.childElementCount ?? 0) > 0,
|
|
356
|
+
geometry: elementGeometryForAi(geometryRef.current, device)
|
|
357
|
+
}), [selection, elementImages]);
|
|
358
|
+
const sessionIdForAiContext = frameProps?.sessionId;
|
|
359
|
+
const readOuterHtmlForAi = useCallback((selector) => getOuterHtml(sessionIdForAiContext, selector), [sessionIdForAiContext]);
|
|
360
|
+
const captureForAi = useCallback((selector) => () => captureAiScreenshot(selector), [captureAiScreenshot]);
|
|
361
|
+
useEffect(() => {
|
|
362
|
+
const picked = selection?.chain[0];
|
|
363
|
+
if (!trackAiSelection || !picked || !sessionIdForAiContext) {
|
|
364
|
+
descendantShelfRef.current = [];
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
let stale = false;
|
|
368
|
+
void getDomTree(sessionIdForAiContext, {
|
|
369
|
+
rootSelector: picked.selector,
|
|
370
|
+
maxDepth: SHELF_TREE_MAX_DEPTH,
|
|
371
|
+
maxChildren: 20,
|
|
372
|
+
maxNodes: SHELF_TREE_MAX_NODES,
|
|
373
|
+
visibleOnly: true,
|
|
374
|
+
includeRect: true
|
|
375
|
+
})
|
|
376
|
+
.then(root => {
|
|
377
|
+
if (!stale)
|
|
378
|
+
descendantShelfRef.current = buildDescendantShelf(root);
|
|
379
|
+
})
|
|
380
|
+
.catch(() => {
|
|
381
|
+
descendantShelfRef.current = [];
|
|
382
|
+
});
|
|
383
|
+
return () => {
|
|
384
|
+
stale = true;
|
|
385
|
+
};
|
|
386
|
+
}, [trackAiSelection, selection, sessionIdForAiContext]);
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
const picked = selection?.chain[0];
|
|
389
|
+
if (!trackAiSelection || !picked || !sessionIdForAiContext)
|
|
390
|
+
return;
|
|
391
|
+
let stale = false;
|
|
392
|
+
void getElementImages(sessionIdForAiContext, picked.selector, AI_ELEMENT_IMAGE_LIMIT)
|
|
393
|
+
.then(({ images }) => {
|
|
394
|
+
if (stale || !images.length)
|
|
395
|
+
return;
|
|
396
|
+
setElementImages({ selector: picked.selector, images });
|
|
397
|
+
})
|
|
398
|
+
.catch(() => {
|
|
399
|
+
});
|
|
400
|
+
return () => {
|
|
401
|
+
stale = true;
|
|
402
|
+
};
|
|
403
|
+
}, [trackAiSelection, selection, sessionIdForAiContext]);
|
|
404
|
+
const pageContextSessionRef = useRef(null);
|
|
405
|
+
const rootShelfRef = useRef([]);
|
|
406
|
+
const aiInsertedRef = useRef([]);
|
|
407
|
+
const onAiInsertedRemovedRef = useRef(onAiInsertedRemoved);
|
|
408
|
+
onAiInsertedRemovedRef.current = onAiInsertedRemoved;
|
|
409
|
+
insertedRemovedRef.current = selectors => {
|
|
410
|
+
const gone = new Set(selectors);
|
|
411
|
+
aiInsertedRef.current = aiInsertedRef.current.filter(s => !gone.has(s));
|
|
412
|
+
onAiInsertedRemovedRef.current?.(selectors);
|
|
413
|
+
};
|
|
414
|
+
const onAiInsertedRestoredRef = useRef(onAiInsertedRestored);
|
|
415
|
+
onAiInsertedRestoredRef.current = onAiInsertedRestored;
|
|
416
|
+
insertedRestoredRef.current = selectors => {
|
|
417
|
+
for (const sel of selectors)
|
|
418
|
+
if (!aiInsertedRef.current.includes(sel))
|
|
419
|
+
aiInsertedRef.current.push(sel);
|
|
420
|
+
onAiInsertedRestoredRef.current?.(selectors);
|
|
421
|
+
};
|
|
422
|
+
const rootShelfAncestorsRef = useRef(new Map());
|
|
423
|
+
const anchorsResultRef = useRef(null);
|
|
424
|
+
const anchorsRef = useRef(anchors);
|
|
425
|
+
useEffect(() => {
|
|
426
|
+
anchorsRef.current = anchors;
|
|
427
|
+
}, [anchors]);
|
|
428
|
+
useEffect(() => {
|
|
429
|
+
rootShelfRef.current = [];
|
|
430
|
+
rootShelfAncestorsRef.current = new Map();
|
|
431
|
+
aiInsertedRef.current = [];
|
|
432
|
+
anchorsResultRef.current = null;
|
|
433
|
+
pageContextSessionRef.current = null;
|
|
434
|
+
leftVersionIdsRef.current = new Map();
|
|
435
|
+
}, [sessionIdForAiContext]);
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
if (!onPageContextReady || !domReady || !sessionIdForAiContext)
|
|
438
|
+
return;
|
|
439
|
+
if (pageContextSessionRef.current === sessionIdForAiContext)
|
|
440
|
+
return;
|
|
441
|
+
pageContextSessionRef.current = sessionIdForAiContext;
|
|
442
|
+
let stale = false;
|
|
443
|
+
void (async () => {
|
|
444
|
+
const root = await getDomTree(sessionIdForAiContext, {
|
|
445
|
+
rootSelector: 'body',
|
|
446
|
+
maxDepth: 16,
|
|
447
|
+
maxChildren: 40,
|
|
448
|
+
maxNodes: 1500,
|
|
449
|
+
visibleOnly: true,
|
|
450
|
+
includeRect: true
|
|
451
|
+
});
|
|
452
|
+
const { html } = await getOuterHtml(sessionIdForAiContext, 'body');
|
|
453
|
+
if (stale)
|
|
454
|
+
return;
|
|
455
|
+
const { shelf, ancestorsOf } = buildRootShelf(root, {
|
|
456
|
+
limit: ROOT_SHELF_LIMIT,
|
|
457
|
+
viewportWidth: geometryRef.current?.viewportWidth
|
|
458
|
+
});
|
|
459
|
+
const planAnchors = anchorsRef.current ?? [];
|
|
460
|
+
const anchorOut = planAnchors.length
|
|
461
|
+
? await resolveInlinePlanAnchors({
|
|
462
|
+
anchors: planAnchors,
|
|
463
|
+
rootTree: root,
|
|
464
|
+
resolveSelector: async (selector) => {
|
|
465
|
+
const r = await resolveCanvasSelector(sessionIdForAiContext, selector);
|
|
466
|
+
return { matched: r.matched, matchCount: r.matchCount };
|
|
467
|
+
},
|
|
468
|
+
getSubtree: (rootSelector) => getDomTree(sessionIdForAiContext, {
|
|
469
|
+
rootSelector,
|
|
470
|
+
maxDepth: SHELF_TREE_MAX_DEPTH,
|
|
471
|
+
maxChildren: 20,
|
|
472
|
+
maxNodes: SHELF_TREE_MAX_NODES,
|
|
473
|
+
visibleOnly: true,
|
|
474
|
+
includeRect: true
|
|
475
|
+
})
|
|
476
|
+
})
|
|
477
|
+
: null;
|
|
478
|
+
if (stale)
|
|
479
|
+
return;
|
|
480
|
+
const anchorRows = anchorOut?.shelf ?? [];
|
|
481
|
+
const anchorSelectors = new Set(anchorRows.map(r => r.selector));
|
|
482
|
+
const mergedShelf = [
|
|
483
|
+
...anchorRows,
|
|
484
|
+
...shelf.filter(row => !anchorSelectors.has(row.selector))
|
|
485
|
+
];
|
|
486
|
+
const mergedAncestors = new Map(ancestorsOf);
|
|
487
|
+
for (const [selector, chain] of anchorOut?.ancestorsOf ?? []) {
|
|
488
|
+
mergedAncestors.set(selector, chain);
|
|
489
|
+
}
|
|
490
|
+
rootShelfRef.current = mergedShelf;
|
|
491
|
+
rootShelfAncestorsRef.current = mergedAncestors;
|
|
492
|
+
anchorsResultRef.current = anchorOut?.anchors ?? null;
|
|
493
|
+
const resolvedAnchors = (anchorOut?.anchors ?? []).filter(a => a.resolved);
|
|
494
|
+
const anchorBlocks = [];
|
|
495
|
+
for (const anchor of resolvedAnchors) {
|
|
496
|
+
const selector = blockContainerFor(anchor, mergedShelf, mergedAncestors);
|
|
497
|
+
if (!anchorBlocks.some(b => b.selector === selector))
|
|
498
|
+
anchorBlocks.push({ anchor, selector });
|
|
499
|
+
}
|
|
500
|
+
const fragments = [];
|
|
501
|
+
const images = [];
|
|
502
|
+
for (const { anchor, selector } of anchorBlocks) {
|
|
503
|
+
try {
|
|
504
|
+
const { html: blockHtml } = await getOuterHtml(sessionIdForAiContext, selector);
|
|
505
|
+
fragments.push({ label: anchor.blockId ?? anchor.label, selector, html: blockHtml });
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
}
|
|
509
|
+
const backgroundUp = anchor.blockSelectors?.length
|
|
510
|
+
? null
|
|
511
|
+
: backgroundImageAncestorFor(anchor.selector, mergedShelf, mergedAncestors);
|
|
512
|
+
const imageRoots = Array.from(new Set([...(anchor.blockSelectors ?? []), selector, ...(backgroundUp ? [backgroundUp] : [])])).slice(0, 3);
|
|
513
|
+
const seenUrls = new Set(images.map(i => i.url));
|
|
514
|
+
for (const root of imageRoots) {
|
|
515
|
+
try {
|
|
516
|
+
const { images: found } = await getElementImages(sessionIdForAiContext, root);
|
|
517
|
+
for (const im of found) {
|
|
518
|
+
if (seenUrls.has(im.url))
|
|
519
|
+
continue;
|
|
520
|
+
seenUrls.add(im.url);
|
|
521
|
+
images.push({
|
|
522
|
+
...(anchor.blockId ? { blockId: anchor.blockId } : {}),
|
|
523
|
+
selector: im.selector,
|
|
524
|
+
kind: im.kind,
|
|
525
|
+
url: im.url
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (stale)
|
|
534
|
+
return;
|
|
535
|
+
const onShelf = new Set(mergedShelf.map(r => r.selector));
|
|
536
|
+
const imageAncestors = new Map();
|
|
537
|
+
for (const selector of new Set(images.map(im => im.selector))) {
|
|
538
|
+
if (onShelf.has(selector))
|
|
539
|
+
continue;
|
|
540
|
+
try {
|
|
541
|
+
const { chain } = await getAncestorChain(sessionIdForAiContext, selector);
|
|
542
|
+
if (chain.length)
|
|
543
|
+
imageAncestors.set(selector, chain.slice(1).map(c => c.selector));
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (stale)
|
|
549
|
+
return;
|
|
550
|
+
const { shelf: pageShelf, added: imageRowCount } = withImageRows(mergedShelf, anchorRows.length, images.filter(im => onShelf.has(im.selector) || imageAncestors.has(im.selector)));
|
|
551
|
+
for (const [selector, chain] of imageAncestors)
|
|
552
|
+
mergedAncestors.set(selector, chain);
|
|
553
|
+
rootShelfRef.current = pageShelf;
|
|
554
|
+
rootShelfAncestorsRef.current = mergedAncestors;
|
|
555
|
+
if (fragments.length) {
|
|
556
|
+
const links = linksFragment(extractPageLinks(html));
|
|
557
|
+
if (links)
|
|
558
|
+
fragments.push(links);
|
|
559
|
+
}
|
|
560
|
+
const captureAnchors = anchorBlocks.length
|
|
561
|
+
? async () => {
|
|
562
|
+
const shots = [];
|
|
563
|
+
for (const { anchor, selector } of anchorBlocks) {
|
|
564
|
+
const shotSelector = anchor.blockSelectors?.[0] ?? selector;
|
|
565
|
+
const shot = await captureAiShot(shotSelector);
|
|
566
|
+
const base = {
|
|
567
|
+
...(anchor.blockId ? { blockId: anchor.blockId } : {}),
|
|
568
|
+
label: anchor.label,
|
|
569
|
+
selector: shotSelector
|
|
570
|
+
};
|
|
571
|
+
shots.push('url' in shot ? { ...base, url: shot.url } : { ...base, error: shot.error });
|
|
572
|
+
}
|
|
573
|
+
return shots;
|
|
574
|
+
}
|
|
575
|
+
: undefined;
|
|
576
|
+
const frame = geometryRef.current;
|
|
577
|
+
onPageContextReady(buildElementAiContext({
|
|
578
|
+
selector: 'body',
|
|
579
|
+
tag: 'body',
|
|
580
|
+
ancestors: [],
|
|
581
|
+
outerHtml: html,
|
|
582
|
+
fragments,
|
|
583
|
+
images,
|
|
584
|
+
descendants: pageShelf,
|
|
585
|
+
descendantLimit: ROOT_SHELF_LIMIT + anchorRows.length + imageRowCount,
|
|
586
|
+
...(anchorsResultRef.current ? { anchors: anchorsResultRef.current } : {}),
|
|
587
|
+
hasChildren: true,
|
|
588
|
+
...(frame
|
|
589
|
+
? {
|
|
590
|
+
geometry: {
|
|
591
|
+
width: Math.round(frame.viewportWidth),
|
|
592
|
+
height: Math.round(frame.viewportHeight),
|
|
593
|
+
topInPage: 0,
|
|
594
|
+
viewportWidth: Math.round(frame.viewportWidth),
|
|
595
|
+
viewportHeight: Math.round(frame.viewportHeight),
|
|
596
|
+
device
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
: {})
|
|
600
|
+
}), () => captureAiScreenshot('body'), captureAnchors);
|
|
601
|
+
})().catch(error => {
|
|
602
|
+
if (!stale)
|
|
603
|
+
setCommandError(describeCanvasError(error));
|
|
604
|
+
});
|
|
605
|
+
return () => {
|
|
606
|
+
stale = true;
|
|
607
|
+
};
|
|
608
|
+
}, [
|
|
609
|
+
onPageContextReady,
|
|
610
|
+
domReady,
|
|
611
|
+
sessionIdForAiContext,
|
|
612
|
+
device,
|
|
613
|
+
captureAiScreenshot,
|
|
614
|
+
captureAiShot
|
|
615
|
+
]);
|
|
616
|
+
useAiSelectionContext({
|
|
617
|
+
enabled: trackAiSelection,
|
|
618
|
+
picked: selection?.chain[0],
|
|
619
|
+
sessionId: sessionIdForAiContext,
|
|
620
|
+
readOuterHtml: readOuterHtmlForAi,
|
|
621
|
+
selectorOf: nodeSelectorOf,
|
|
622
|
+
buildContext: buildAiContext,
|
|
623
|
+
captureFor: captureForAi,
|
|
624
|
+
onChange: onAiContextChange
|
|
625
|
+
});
|
|
626
|
+
useEffect(() => {
|
|
627
|
+
if (!onAiApplyReady)
|
|
628
|
+
return;
|
|
629
|
+
const picked = selection?.chain[0];
|
|
630
|
+
const pageMode = !!onPageContextReady;
|
|
631
|
+
if (!sessionIdForAiContext || (!picked && !pageMode)) {
|
|
632
|
+
onAiApplyReady(null);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const scope = picked
|
|
636
|
+
? {
|
|
637
|
+
selector: picked.selector,
|
|
638
|
+
identifier: picked.selector,
|
|
639
|
+
descendants: () => [
|
|
640
|
+
...descendantShelfRef.current.map(d => d.selector),
|
|
641
|
+
...aiInsertedRef.current
|
|
642
|
+
],
|
|
643
|
+
ancestors: (selection?.chain ?? []).slice(1).map(n => n.selector)
|
|
644
|
+
}
|
|
645
|
+
: {
|
|
646
|
+
selector: 'body',
|
|
647
|
+
identifier: 'body',
|
|
648
|
+
descendants: () => [...rootShelfRef.current.map(d => d.selector), ...aiInsertedRef.current],
|
|
649
|
+
ancestors: [],
|
|
650
|
+
ancestorsOf: (selector) => rootShelfAncestorsRef.current.get(selector)
|
|
651
|
+
};
|
|
652
|
+
onAiApplyReady({
|
|
653
|
+
activeVersionId: loadedVersion,
|
|
654
|
+
apply: async (changes, opts) => enqueue(async () => {
|
|
655
|
+
const target = opts?.versionId;
|
|
656
|
+
const rejectAll = (detail) => ({
|
|
657
|
+
applied: [],
|
|
658
|
+
rejected: changes.map((c, index) => ({
|
|
659
|
+
index,
|
|
660
|
+
selector: typeof c?.selector === 'string' ? c.selector : '',
|
|
661
|
+
reason: 'VERSION_SWITCH_FAILED',
|
|
662
|
+
detail
|
|
663
|
+
})),
|
|
664
|
+
versionId: loadedVersionRef.current ?? ''
|
|
665
|
+
});
|
|
666
|
+
if (target && target !== loadedVersionRef.current) {
|
|
667
|
+
if (!hasVersionsRef.current) {
|
|
668
|
+
return rejectAll('host did not pass `versions`');
|
|
669
|
+
}
|
|
670
|
+
try {
|
|
671
|
+
await switchToVersion(target);
|
|
672
|
+
versionCallbacksRef.current.onVersionChange?.(target);
|
|
673
|
+
}
|
|
674
|
+
catch (e) {
|
|
675
|
+
return rejectAll(describeCanvasError(e));
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const outcome = await applyAiChanges(changes, {
|
|
679
|
+
scope: opts?.grant ? { ...scope, grant: opts.grant } : scope,
|
|
680
|
+
resolve: async (selector) => {
|
|
681
|
+
const r = await resolveCanvasSelector(sessionIdForAiContext, selector);
|
|
682
|
+
return { matched: r.matched, matchCount: r.matchCount, viewportRect: r.viewportRect };
|
|
683
|
+
},
|
|
684
|
+
insertedSelectorsOf: (changeId) => changeListRef.current.peekList().find(r => r.changeId === changeId)
|
|
685
|
+
?.insertedSelectors ?? [],
|
|
686
|
+
settle: () => new Promise(r => setTimeout(r, APPLY_CHECK_SETTLE_MS)),
|
|
687
|
+
viewportWidth: () => geometryRef.current?.viewportWidth,
|
|
688
|
+
getCapabilities: (selector) => getElementCapabilities(sessionIdForAiContext, selector),
|
|
689
|
+
addStyle: changeList.addStyle,
|
|
690
|
+
addContent: changeList.addContent,
|
|
691
|
+
addMove: changeList.addMove,
|
|
692
|
+
addAttribute: changeList.addAttribute,
|
|
693
|
+
addAction: changeList.addAction,
|
|
694
|
+
allowedImageOrigins: imageOrigins,
|
|
695
|
+
allowedImageUrls: allowedImageUrls ?? [],
|
|
696
|
+
allowedLinkUrls: allowedLinkUrls ?? [],
|
|
697
|
+
pageOrigin
|
|
698
|
+
});
|
|
699
|
+
for (const a of outcome.applied) {
|
|
700
|
+
for (const sel of a.insertedSelectors ?? []) {
|
|
701
|
+
if (!aiInsertedRef.current.includes(sel))
|
|
702
|
+
aiInsertedRef.current.push(sel);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const skipped = outcome.rejected
|
|
706
|
+
.map(r => `${r.reason}${r.detail ? `(${r.detail})` : ''}`)
|
|
707
|
+
.join(';');
|
|
708
|
+
setAiApplyNote(outcome.applied.length
|
|
709
|
+
? t('inlineCanvas.ai.applyNote', 'AI 改动:{{ok}} 条已应用{{skip}}', {
|
|
710
|
+
ok: outcome.applied.length,
|
|
711
|
+
skip: skipped
|
|
712
|
+
? `,${outcome.rejected.length} 条被跳过:${skipped}`
|
|
713
|
+
: ''
|
|
714
|
+
})
|
|
715
|
+
: t('inlineCanvas.ai.applyNoneNote', 'AI 改动全部被跳过:{{skip}}', {
|
|
716
|
+
skip: skipped
|
|
717
|
+
}));
|
|
718
|
+
return { ...outcome, versionId: loadedVersionRef.current ?? '' };
|
|
719
|
+
}),
|
|
720
|
+
revert: async (changeIds) => {
|
|
721
|
+
const mapped = changeIds.map(id => resolveChangeIdRef.current?.(id) ?? id);
|
|
722
|
+
const live = new Set(changeListRef.current.peekList().map(r => r.changeId));
|
|
723
|
+
if (mapped.some(id => !live.has(id))) {
|
|
724
|
+
return { ok: false, reason: 'VERSION_NOT_ACTIVE' };
|
|
725
|
+
}
|
|
726
|
+
for (const id of [...mapped].reverse()) {
|
|
727
|
+
await changeListRef.current.revert(id);
|
|
728
|
+
}
|
|
729
|
+
return { ok: true };
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
return () => onAiApplyReady(null);
|
|
733
|
+
}, [
|
|
734
|
+
onAiApplyReady,
|
|
735
|
+
onPageContextReady,
|
|
736
|
+
selection,
|
|
737
|
+
sessionIdForAiContext,
|
|
738
|
+
changeList.addStyle,
|
|
739
|
+
changeList.addContent,
|
|
740
|
+
changeList.addMove,
|
|
741
|
+
changeList.addAttribute,
|
|
742
|
+
changeList.addAction,
|
|
743
|
+
imageOrigins,
|
|
744
|
+
allowedImageUrls,
|
|
745
|
+
allowedLinkUrls,
|
|
746
|
+
pageOrigin,
|
|
747
|
+
enqueue,
|
|
748
|
+
switchToVersion,
|
|
749
|
+
loadedVersion,
|
|
750
|
+
t
|
|
751
|
+
]);
|
|
752
|
+
const onToolbarAction = useCallback((buttonId) => {
|
|
753
|
+
const self = selection?.chain[0];
|
|
754
|
+
const sessionId = frameProps?.sessionId;
|
|
755
|
+
if (!self || !sessionId)
|
|
756
|
+
return;
|
|
757
|
+
if (switchingRef.current) {
|
|
758
|
+
setCommandError(t('inlineCanvas.version.switching', '正在切换版本,请稍候'));
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (buttonId === 'delete') {
|
|
762
|
+
uiWrite(() => deleteElement(depsOf(self)));
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
if (buttonId === 'copy') {
|
|
766
|
+
setCommandError(null);
|
|
767
|
+
commands
|
|
768
|
+
.copyElement(self.selector)
|
|
769
|
+
.catch(error => setCommandError(describeCanvasError(error)));
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (buttonId === 'paste') {
|
|
773
|
+
if (!clipboard) {
|
|
774
|
+
setCommandError(t('inlineCanvas.clipboard.nothingToPaste'));
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
setPasteDialog(true);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (buttonId === 'move') {
|
|
781
|
+
setCommandError(null);
|
|
782
|
+
commands.startMove(self.selector);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (buttonId === 'replace-image') {
|
|
786
|
+
setImageDialog({ open: true, seed: '' });
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (buttonId === 'replace-video') {
|
|
790
|
+
setVideoDialog({ open: true, seed: '' });
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (buttonId === 'insert') {
|
|
794
|
+
setInsertDialog(true);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (buttonId === 'image-setting') {
|
|
798
|
+
setImageSettingDialog({ open: true, seed: '' });
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
if (buttonId === 'click-action') {
|
|
802
|
+
setActionDialog({ open: true, seed: null });
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
if (buttonId === 'edit-style') {
|
|
806
|
+
setStylePanelOpen(true);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
if (buttonId === 'edit-html') {
|
|
810
|
+
void getOuterHtml(sessionId, self.selector).then(r => setHtmlDialog({ open: true, seed: r.html }));
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (buttonId === 'ask-ai') {
|
|
814
|
+
if (!onAskAi)
|
|
815
|
+
return;
|
|
816
|
+
setCommandError(null);
|
|
817
|
+
void getOuterHtml(sessionId, self.selector)
|
|
818
|
+
.then(r => onAskAi(buildAiContext(self, r.html), () => captureAiScreenshot(self.selector)))
|
|
819
|
+
.catch(error => setCommandError(describeCanvasError(error)));
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (buttonId === 'screenshot') {
|
|
823
|
+
setCommandError(null);
|
|
824
|
+
setShot(null);
|
|
825
|
+
setShotUrl(null);
|
|
826
|
+
commands
|
|
827
|
+
.captureSelectedElement(self.selector)
|
|
828
|
+
.then(async (outcome) => {
|
|
829
|
+
setShot(outcome);
|
|
830
|
+
if (!uploadImage)
|
|
831
|
+
return;
|
|
832
|
+
const blob = await (await fetch(outcome.dataUrl)).blob();
|
|
833
|
+
setShotUrl(await uploadImage(blob));
|
|
834
|
+
})
|
|
835
|
+
.catch(error => setCommandError(describeCanvasError(error)));
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (buttonId === 'edit-text') {
|
|
839
|
+
canvasIframeRef.current?.focus();
|
|
840
|
+
setCommandError(null);
|
|
841
|
+
commands
|
|
842
|
+
.startTextEdit(self.selector)
|
|
843
|
+
.catch(error => setCommandError(describeCanvasError(error)));
|
|
844
|
+
}
|
|
845
|
+
}, [clipboard, commands, depsOf, frameProps?.sessionId, selection, t, uploadImage]);
|
|
846
|
+
const self = selection?.chain[0];
|
|
847
|
+
return (_jsxs("div", { className: `${CANVAS_UI_SCOPE} flex h-full min-h-0 flex-col bg-[#F5F7FC]`, children: [_jsx(EditorChrome, { variant: chrome, title: title, versionLabel: versionLabel, versions: versions, activeVersionId: activeVersionId, versionSwitching: switching, onVersionChange: onVersionChange, changeCount: changeList.list.length, onExit: onExit, onSave: () => {
|
|
848
|
+
if (switchingRef.current) {
|
|
849
|
+
setCommandError(t('inlineCanvas.version.switching', '正在切换版本,请稍候'));
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
onSave?.(editor);
|
|
853
|
+
}, onAddVersion: onAddVersion, onRenameVersion: onRenameVersion, onPreview: onPreview, onToggleChangeList: () => setShowChangeList(v => !v), changeListOpen: showChangeList, onToggleAi: onToggleAi, aiOpen: aiOpen }), _jsxs("div", { className: "relative flex min-h-0 flex-1", children: [_jsxs(ResizablePanelGroup, { direction: "horizontal", autoSaveId: "inline-editor-panes-v2", className: "flex min-h-0 w-full flex-1", children: [showLeftPanel && (_jsxs(_Fragment, { children: [_jsx(ResizablePanel, { id: "left", order: 1, defaultSize: 18, minSize: 12, maxSize: 40, className: "flex min-h-0 flex-col", children: _jsxs("aside", { "data-testid": "inline-editor-left-panel", className: "inline-editor-left flex h-full min-h-0 flex-col bg-white", children: [!!libraryComponents && (_jsx("div", { className: "flex h-9 shrink-0 items-center gap-1 border-b\n border-[#EBECF0] px-2", children: [
|
|
854
|
+
['layers', '图层'],
|
|
855
|
+
['assets', '组件']
|
|
856
|
+
]
|
|
857
|
+
.map(([tab, label]) => (_jsx("button", { type: "button", "data-testid": `left-tab-${tab}`, "aria-pressed": leftTab === tab, onClick: () => setLeftTab(tab), className: `rounded px-2 py-1 text-[12px] ${leftTab === tab
|
|
858
|
+
? 'bg-[#F4F5F7] font-semibold text-[#091E42]'
|
|
859
|
+
: 'text-[#6B778C]'}`, children: label }, tab))) })), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: leftTab === 'assets' && libraryComponents ? (_jsx(AssetsPanel, { components: libraryComponents, loading: libraryLoading, canInsert: !!selection?.chain[0] && !switching, onInsert: component => void insertComponent(component) })) : (_jsx(LayersPanel, { tree: tree, selectedSelector: selection?.selector ?? null, onSelect: s => void selectAndReveal(s), onHover: s => void commands.hover(s), onExpand: id => void commands.expandTreeNode(id), onMove: (dragSelector, targetSelector, pos) => uiWrite(() => writes.addMove({
|
|
860
|
+
identifier: dragSelector,
|
|
861
|
+
selector: dragSelector,
|
|
862
|
+
targetIdentifier: targetSelector,
|
|
863
|
+
targetSelector,
|
|
864
|
+
mode: moveModeFromDropPos(pos)
|
|
865
|
+
})) })) })] }) }), _jsx(ResizableHandle, { withHandle: true })] })), _jsx(ResizablePanel, { id: "canvas", order: 2, minSize: 30, children: _jsxs("div", { className: "flex h-full min-h-0 min-w-0 flex-col", children: [_jsx(CanvasContextBar, { url: url, device: device, onDeviceChange: onDeviceChange }), _jsx("div", { ref: canvasAreaRef, className: "relative min-h-0 flex-1 overflow-hidden p-3", children: frameProps && (_jsx(CanvasFrame, { url: frameProps.url, sessionId: frameProps.sessionId, device: frameProps.device, panelWidth: panelWidth, targetViewportWidth: targetViewportWidth[frameProps.device], onFrameLoad: () => { }, domReady: domReady, iframeRef: canvasIframeRef }, frameProps.sessionId)) })] }) })] }), !capturing && shot && (_jsxs("section", { "data-testid": "inline-editor-shot", className: "absolute bottom-4 right-4 z-30 w-[260px] rounded-lg border\n border-[#DFE1E6] bg-white p-3\n shadow-[0_8px_24px_rgba(9,30,66,0.14),0_2px_8px_rgba(9,30,66,0.08)]", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsx("span", { className: "text-[13px] font-semibold text-[#091E42]", children: "\u622A\u56FE" }), _jsx("button", { type: "button", className: "text-[11px] text-[#6B778C]", onClick: () => {
|
|
866
|
+
setShot(null);
|
|
867
|
+
setShotUrl(null);
|
|
868
|
+
}, children: "\u5173\u95ED" })] }), _jsx("img", { src: shot.dataUrl, alt: "", className: "max-h-40 w-full rounded border border-[#EBECF0] object-contain" }), shot.truncated && (_jsx("p", { "data-testid": "inline-editor-shot-truncated", className: "mt-1.5 text-[11px] leading-4 text-[#DE350B]", children: "\u5143\u7D20\u8D85\u51FA\u53EF\u89C6\u533A\uFF0C\u53EA\u622A\u5230\u53EF\u89C1\u90E8\u5206" })), shot.scale < 1 && (_jsxs("p", { "data-testid": "inline-editor-shot-scale", className: "mt-1.5 text-[11px] leading-4 text-[#6B778C]", children: ["\u5F53\u524D\u7A97\u53E3\u8F83\u7A84\uFF0C\u622A\u56FE\u5206\u8FA8\u7387\u7EA6\u4E3A\u539F\u56FE\u7684", ` ${Math.round(shot.scale * 100)}%`] })), shotUrl && (_jsx("a", { "data-testid": "inline-editor-shot-url", href: shotUrl, target: "_blank", rel: "noopener noreferrer", className: "mt-1.5 block truncate text-[11px] text-[#0052CC] underline", children: shotUrl }))] })), _jsx(ChangeListPopover, { open: showChangeList && !capturing, onClose: () => setShowChangeList(false), children: _jsx(ChangeListPanel, { list: changeList.list, onRevert: id => uiWrite(() => writes.revert(id)), onRevertAll: () => uiWrite(() => writes.revertAll()), lastError: changeList.lastError, onHover: s => void commands.hover(s), onSelect: s => void selectAndReveal(s) }) })] }), !capturing && (_jsx(SelectionOverlay, { geometry: geometry.latest, context: overlayContext, selectedLabel: self ? `<${self.tag}>` : undefined })), !capturing && blockOverlay && (_jsx(BlockOverlay, { blocks: blockOverlay.blocks, activeId: blockOverlay.activeId, onHover: blockOverlay.onHover, geometry: geometry.latest, context: overlayContext, scale: editor.mobileFit.scale })), !capturing && commandError && (_jsx("div", { "data-testid": "inline-editor-command-error", className: "fixed z-[2147483003] -translate-x-1/2 rounded bg-[#DE350B] px-2.5\n py-1 text-[11px] leading-4 text-white shadow", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, onClick: () => setCommandError(null), children: commandError })), !capturing && aiApplyNote && (_jsx("div", { "data-testid": "inline-editor-ai-apply-note", className: "fixed z-[2147483003] max-w-[520px] -translate-x-1/2 rounded\n bg-[#253858] px-2.5 py-1 text-[11px] leading-4 text-white shadow", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, onClick: () => setAiApplyNote(null), children: aiApplyNote })), !capturing && move && (_jsxs("div", { "data-testid": "inline-editor-move-bar", className: "fixed z-[2147483003] flex -translate-x-1/2 items-center gap-2\n rounded bg-[#091E42] px-2.5 py-1.5 text-[11px] leading-4 text-white shadow", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, children: [move.target === null ? (_jsx(_Fragment, { children: _jsx("span", { children: move.rejected === 'same-element'
|
|
869
|
+
? '不能移到它自己里面,请另选一个目标'
|
|
870
|
+
: t('inlineCanvas.move.pickTarget') }) })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: t('inlineCanvas.move.position') }), ['top', 'bottom', 'before', 'after'].map(pos => (_jsx("button", { type: "button", "data-testid": `move-position-${pos}`, className: "rounded bg-white/15 px-1.5 py-0.5 hover:bg-white/25", onClick: () => void commands
|
|
871
|
+
.confirmMove(pos)
|
|
872
|
+
.catch(e => setCommandError(describeCanvasError(e))), children: t(`inlineCanvas.move.${pos}`) }, pos)))] })), _jsx("button", { type: "button", "data-testid": "move-cancel", className: "ml-1 text-white/70 underline", onClick: () => commands.cancelMove(), children: t('inlineCanvas.move.cancel') })] })), !capturing && textEditing !== null && (_jsx("div", { "data-testid": "inline-editor-text-editing-hint", className: "pointer-events-none fixed z-[2147483003] -translate-x-1/2 rounded\n bg-[#091E42] px-2.5 py-1 text-[11px] leading-4 text-white shadow", style: {
|
|
873
|
+
left: `${area.x + area.width / 2}px`,
|
|
874
|
+
top: `${area.y + 8}px`
|
|
875
|
+
}, children: "\u6B63\u5728\u7F16\u8F91\u6587\u5B57 \u00B7 \u56DE\u8F66\u63D0\u4EA4 \u00B7 Esc \u53D6\u6D88" })), !capturing && !switching && (_jsx(SelectionToolbar, { selectedRect: selectedRect, area: area, capabilities: selection?.capabilities ?? null, isScrolling: geometry.isScrolling, onAction: onToolbarAction, host: { askAi: !!onAskAi } })), stylePanelOpen && !switching && selectedRect && self && (_jsx("div", { className: "fixed z-[2147483002]", style: {
|
|
876
|
+
left: `${Math.min(selectedRect.x + selectedRect.width + 12, area.x + area.width - 332)}px`,
|
|
877
|
+
top: `${Math.max(area.y + 8, selectedRect.y)}px`
|
|
878
|
+
}, children: _jsx(StylePanel, { tag: self.tag, capabilities: selection?.capabilities ?? null, declaredStyles: selection?.declaredStyles, onClose: () => setStylePanelOpen(false), onApply: (property, value) => uiWrite(() => editStyle(depsOf(self), property, value)) }) })), _jsx(Dialog, { open: pasteDialog, onOpenChange: setPasteDialog, children: _jsxs(DialogContent, { className: "max-w-[480px]", "data-testid": "paste-dialog", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t('inlineCanvas.clipboard.pastePosition') }) }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: (selection?.capabilities ? pasteModes(selection.capabilities) : []).map(mode => (_jsx("button", { type: "button", "data-testid": `paste-mode-${mode}`, className: "rounded border border-[#DFE1E6] px-2 py-1 text-[12px]\n text-[#344563] hover:border-[#00A92E]", onClick: () => {
|
|
879
|
+
if (!self)
|
|
880
|
+
return;
|
|
881
|
+
setPasteDialog(false);
|
|
882
|
+
void commands
|
|
883
|
+
.pasteInto(self.selector, mode)
|
|
884
|
+
.catch(e => setCommandError(describeCanvasError(e)));
|
|
885
|
+
}, children: t(`inlineCanvas.clipboard.${mode}`) }, mode))) })] }) }), _jsx(ImageEditDialog, { open: imageDialog.open, capabilities: selection?.capabilities ?? null, seed: imageDialog.seed, uploadImage: uploadImage, onOpenChange: open => setImageDialog(prev => ({ ...prev, open })), onApply: (input) => self && uiWrite(() => replaceImage(depsOf(self), input)) }), _jsx(ReplaceVideoDialog, { open: videoDialog.open, seed: videoDialog.seed, onOpenChange: open => setVideoDialog(prev => ({ ...prev, open })), onApply: url => self && uiWrite(() => replaceVideo(depsOf(self), url)) }), _jsx(InsertElementDialog, { open: insertDialog, capabilities: selection?.capabilities ?? null, onOpenChange: setInsertDialog, onApply: input => self && uiWrite(() => insertElement(depsOf(self), input)) }), _jsx(ImageSettingDialog, { open: imageSettingDialog.open, seed: imageSettingDialog.seed, onOpenChange: open => setImageSettingDialog(prev => ({ ...prev, open })), onApply: alt => self && uiWrite(() => setImageAlt(depsOf(self), alt)) }), _jsx(ClickActionDialog, { open: actionDialog.open, seed: actionDialog.seed, onOpenChange: open => setActionDialog(prev => ({ ...prev, open })), onApply: action => self && uiWrite(() => setClickAction(depsOf(self), action)) }), _jsx(HtmlEditDialog, { open: htmlDialog.open, seed: htmlDialog.seed, onOpenChange: open => setHtmlDialog(prev => ({ ...prev, open })), onApply: html => self && uiWrite(() => editHtml(depsOf(self), html)) })] }));
|
|
886
|
+
}
|