@ptengine/inline-editor 0.1.7 → 0.1.8
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 +16 -0
- package/dist/InlineEditor.d.ts +10 -1
- package/dist/InlineEditor.js +68 -19
- package/dist/kernel/CanvasContextBar.d.ts +14 -1
- package/dist/kernel/CanvasContextBar.js +6 -4
- package/dist/kernel/changelist/useChangeList.d.ts +2 -0
- package/dist/kernel/changelist/useChangeList.js +2 -0
- package/dist/kernel/useCanvasSession.d.ts +7 -0
- package/dist/kernel/useCanvasSession.js +1 -1
- package/dist/locales.d.ts +36 -0
- package/dist/locales.js +18 -0
- package/dist/useInlineEditor.d.ts +14 -0
- package/dist/useInlineEditor.js +55 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.8
|
|
4
|
+
|
|
5
|
+
依赖不变(`@ptengine/inline-core` ~0.2.1),可以直接发。
|
|
6
|
+
|
|
7
|
+
**新增**
|
|
8
|
+
|
|
9
|
+
- 交互模式(对应 engage-editor 画布工具条上的「交互模式」):画布条右侧新增开关,进入后关掉拾取,点击、悬停、输入都交给客户页,
|
|
10
|
+
用来打开弹窗、切换页签、展开折叠,再改那些交互之后才看得见的内容。
|
|
11
|
+
- 进入时清掉选中与悬停,收起选中框、工具条、样式面板、移动条与区块框;顶栏整条被「您当前正在交互模式 / 返回编辑模式」提示条盖住,
|
|
12
|
+
图层面板盖一层不可点的遮罩,设备切换禁用,画布发来的与宿主页的 Cmd/Ctrl+Z 都不再触发编辑器撤销;挂 beforeunload 防止页面把产品页带走。
|
|
13
|
+
- 退出时开回拾取、重拉图层树,并把当前改动按原 changeId 撤了重放一遍(页面脚本可能把改过的元素重渲染掉了;id 不变,AI 卡片的撤销仍对得上)。
|
|
14
|
+
- 交互中画布整页跳走过(点了链接 / 提交了表单,SW 派生的 `navAway`),退出时按实验地址重建会话,改动走与切设备相同的快照回放,不往跳走后的页面上放。
|
|
15
|
+
- 切版本、文字编辑、移动拾取、截图进行中时按钮禁用;关拾取失败则不进入。
|
|
16
|
+
- `InlineEditor` 新增 `interactiveMode`(默认 `true`,只读画布可传 `false`)与 `onInteractiveChange`;headless 层 `useInlineEditor` 新增
|
|
17
|
+
`interactive` / `setInteractive(on)`(退出返回 `{ navigated }`)与入参 `reloadKey`;`useChangeList` 新增 `reapplyAll()`。
|
|
18
|
+
|
|
3
19
|
## 0.1.7
|
|
4
20
|
|
|
5
21
|
依赖 `@ptengine/inline-core` ~0.2.1(新增 `historyShortcut` 流事件):**先发 inline-core 0.2.1,再发本版**。
|
package/dist/InlineEditor.d.ts
CHANGED
|
@@ -184,6 +184,15 @@ export interface InlineEditorProps {
|
|
|
184
184
|
activeId?: string | null;
|
|
185
185
|
onHover?: (id: string | null) => void;
|
|
186
186
|
};
|
|
187
|
+
/**
|
|
188
|
+
* 「交互模式」按钮。默认 `true`。分析画布这类只看不改的宿主传 `false` 关掉。
|
|
189
|
+
*
|
|
190
|
+
* 交互模式 = 拾取关掉、页面原样可点(打开弹窗 / 切页签 / 展开折叠后再改那些交互后才
|
|
191
|
+
* 看得见的内容),对应老编辑器(engage-editor)画布工具条上的同名开关。
|
|
192
|
+
*/
|
|
193
|
+
interactiveMode?: boolean;
|
|
194
|
+
/** 进出交互模式时通知宿主(宿主自己的浮层要不要让路由它决定)。 */
|
|
195
|
+
onInteractiveChange?: (on: boolean) => void;
|
|
187
196
|
/**
|
|
188
197
|
* 选中变化 → 新的 AI 上下文。`null` = 选中被清空。
|
|
189
198
|
*
|
|
@@ -354,4 +363,4 @@ export interface AiApplyApi {
|
|
|
354
363
|
/** 当前版本 —— 宿主把 agent 给的版本**名**映射成 id 时要知道现在在哪一版。 */
|
|
355
364
|
readonly activeVersionId?: string;
|
|
356
365
|
}
|
|
357
|
-
export declare function InlineEditor({ url, device, onDeviceChange, title, versionLabel, chrome, onExit, onSave, libraryComponents, libraryLoading, onAddVersion, onRenameVersion, removableVersionIds, onRemoveVersion, onPreview, onToggleAi, aiOpen, uploadImage, initialChanges, onHydrated, onAskAi, onPageContextReady, anchors, pageBlocks, onEditorReady, onSessionReady, onAiContextChange, onAiApplyReady, onAiInsertedRemoved, onAiInsertedRestored, trackAiSelection, renderNodeIcon, versions, activeVersionId, versionChanges, onVersionChange, onVersionLeave, onVersionEnter, resolveChangeId, allowedLinkUrls, viewportWidthOverride, blockOverlay }: InlineEditorProps): import("react").JSX.Element;
|
|
366
|
+
export declare function InlineEditor({ url, device, onDeviceChange, title, versionLabel, chrome, onExit, onSave, libraryComponents, libraryLoading, onAddVersion, onRenameVersion, removableVersionIds, onRemoveVersion, onPreview, onToggleAi, aiOpen, uploadImage, initialChanges, onHydrated, onAskAi, onPageContextReady, anchors, pageBlocks, onEditorReady, onSessionReady, onAiContextChange, onAiApplyReady, onAiInsertedRemoved, onAiInsertedRestored, trackAiSelection, renderNodeIcon, versions, activeVersionId, versionChanges, onVersionChange, onVersionLeave, onVersionEnter, resolveChangeId, allowedLinkUrls, viewportWidthOverride, blockOverlay, interactiveMode, onInteractiveChange }: InlineEditorProps): import("react").JSX.Element;
|
package/dist/InlineEditor.js
CHANGED
|
@@ -56,14 +56,16 @@ function imageTargetOf(caps) {
|
|
|
56
56
|
return branch === 'background' ? 'background' : undefined;
|
|
57
57
|
}
|
|
58
58
|
const AI_ELEMENT_IMAGE_LIMIT = 4;
|
|
59
|
-
export function InlineEditor({ url, device, onDeviceChange, title, versionLabel = 'V1', chrome = 'full', onExit, onSave, libraryComponents, libraryLoading, onAddVersion, onRenameVersion, removableVersionIds, onRemoveVersion, onPreview, onToggleAi, aiOpen, uploadImage, initialChanges, onHydrated, onAskAi, onPageContextReady, anchors, pageBlocks, onEditorReady, onSessionReady, onAiContextChange, onAiApplyReady, onAiInsertedRemoved, onAiInsertedRestored, trackAiSelection, renderNodeIcon, versions, activeVersionId, versionChanges, onVersionChange, onVersionLeave, onVersionEnter, resolveChangeId, allowedLinkUrls, viewportWidthOverride, blockOverlay }) {
|
|
59
|
+
export function InlineEditor({ url, device, onDeviceChange, title, versionLabel = 'V1', chrome = 'full', onExit, onSave, libraryComponents, libraryLoading, onAddVersion, onRenameVersion, removableVersionIds, onRemoveVersion, onPreview, onToggleAi, aiOpen, uploadImage, initialChanges, onHydrated, onAskAi, onPageContextReady, anchors, pageBlocks, onEditorReady, onSessionReady, onAiContextChange, onAiApplyReady, onAiInsertedRemoved, onAiInsertedRestored, trackAiSelection, renderNodeIcon, versions, activeVersionId, versionChanges, onVersionChange, onVersionLeave, onVersionEnter, resolveChangeId, allowedLinkUrls, viewportWidthOverride, blockOverlay, interactiveMode = true, onInteractiveChange }) {
|
|
60
60
|
const { t } = useTranslation();
|
|
61
61
|
const canvasAreaRef = useRef(null);
|
|
62
62
|
const canvasIframeRef = useRef(null);
|
|
63
63
|
const insertedRemovedRef = useRef(null);
|
|
64
64
|
const insertedRestoredRef = useRef(null);
|
|
65
65
|
const historyShortcutRef = useRef(() => { });
|
|
66
|
+
const [reloadKey, setReloadKey] = useState(0);
|
|
66
67
|
const editor = useInlineEditor({
|
|
68
|
+
reloadKey,
|
|
67
69
|
onHistoryShortcut: direction => historyShortcutRef.current(direction),
|
|
68
70
|
onInsertedRemoved: selectors => insertedRemovedRef.current?.(selectors),
|
|
69
71
|
onInsertedRestored: selectors => insertedRestoredRef.current?.(selectors),
|
|
@@ -72,7 +74,7 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
72
74
|
iframeRef: canvasIframeRef,
|
|
73
75
|
renderNodeIcon
|
|
74
76
|
});
|
|
75
|
-
const { selection, tree, clipboard, move, capturing, textEditing, geometry, changeList, commands, frameProps, domReady } = editor;
|
|
77
|
+
const { selection, tree, clipboard, move, capturing, textEditing, geometry, changeList, commands, frameProps, domReady, interactive } = editor;
|
|
76
78
|
const selectAndReveal = useCallback(async (selector) => {
|
|
77
79
|
await commands.select(selector);
|
|
78
80
|
await commands.reveal(selector);
|
|
@@ -197,7 +199,7 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
197
199
|
void run().catch(e => setCommandError(describeCanvasError(e)));
|
|
198
200
|
}, [t]);
|
|
199
201
|
historyShortcutRef.current = direction => {
|
|
200
|
-
if (!domReady || textEditing || switchingRef.current)
|
|
202
|
+
if (!domReady || textEditing || switchingRef.current || interactive)
|
|
201
203
|
return;
|
|
202
204
|
uiWrite(() => enqueue(() => direction === 'undo' ? changeListRef.current.undo() : changeListRef.current.redo()));
|
|
203
205
|
};
|
|
@@ -248,17 +250,20 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
248
250
|
const hydratedSessionRef = useRef(null);
|
|
249
251
|
const sessionIdForHydrate = frameProps?.sessionId;
|
|
250
252
|
const deviceSnapshot = useRef(null);
|
|
253
|
+
const snapshotForRebuild = async () => {
|
|
254
|
+
await changeListRef.current.whenIdle();
|
|
255
|
+
const live = changeListRef.current.peekList();
|
|
256
|
+
deviceSnapshot.current = { version: activeVersionId, changes: live.map(r => r.change) };
|
|
257
|
+
if (activeVersionId)
|
|
258
|
+
versionCallbacksRef.current.onVersionLeave?.(activeVersionId, live);
|
|
259
|
+
};
|
|
251
260
|
const changeDevice = (next) => {
|
|
252
|
-
if (next === device || switchingRef.current)
|
|
261
|
+
if (next === device || switchingRef.current || interactive)
|
|
253
262
|
return;
|
|
254
263
|
switchingRef.current = true;
|
|
255
264
|
setSwitching(true);
|
|
256
265
|
void enqueue(async () => {
|
|
257
|
-
await
|
|
258
|
-
const live = changeListRef.current.peekList();
|
|
259
|
-
deviceSnapshot.current = { version: activeVersionId, changes: live.map(r => r.change) };
|
|
260
|
-
if (activeVersionId)
|
|
261
|
-
versionCallbacksRef.current.onVersionLeave?.(activeVersionId, live);
|
|
266
|
+
await snapshotForRebuild();
|
|
262
267
|
onDeviceChange(next);
|
|
263
268
|
})
|
|
264
269
|
.catch(error => setCommandError(describeCanvasError(error)))
|
|
@@ -267,6 +272,41 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
267
272
|
setSwitching(false);
|
|
268
273
|
});
|
|
269
274
|
};
|
|
275
|
+
const interactiveChangeRef = useRef(onInteractiveChange);
|
|
276
|
+
interactiveChangeRef.current = onInteractiveChange;
|
|
277
|
+
const canEnterInteractive = interactiveMode && domReady && !switching && !capturing && textEditing === null && !move;
|
|
278
|
+
const enterInteractive = () => {
|
|
279
|
+
if (!canEnterInteractive || switchingRef.current)
|
|
280
|
+
return;
|
|
281
|
+
setStylePanelOpen(false);
|
|
282
|
+
setShowChangeList(false);
|
|
283
|
+
setPasteDialog(false);
|
|
284
|
+
void enqueue(() => editor.setInteractive(true))
|
|
285
|
+
.then(() => interactiveChangeRef.current?.(true))
|
|
286
|
+
.catch(error => setCommandError(describeCanvasError(error)));
|
|
287
|
+
};
|
|
288
|
+
const exitInteractive = () => {
|
|
289
|
+
void enqueue(async () => {
|
|
290
|
+
const { navigated } = await editor.setInteractive(false);
|
|
291
|
+
interactiveChangeRef.current?.(false);
|
|
292
|
+
if (navigated) {
|
|
293
|
+
await snapshotForRebuild();
|
|
294
|
+
setReloadKey(n => n + 1);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
await changeListRef.current.reapplyAll();
|
|
298
|
+
}).catch(error => setCommandError(describeCanvasError(error)));
|
|
299
|
+
};
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
if (!interactive)
|
|
302
|
+
return;
|
|
303
|
+
const onBeforeUnload = (event) => {
|
|
304
|
+
event.preventDefault();
|
|
305
|
+
event.returnValue = '';
|
|
306
|
+
};
|
|
307
|
+
window.addEventListener('beforeunload', onBeforeUnload);
|
|
308
|
+
return () => window.removeEventListener('beforeunload', onBeforeUnload);
|
|
309
|
+
}, [interactive]);
|
|
270
310
|
const bootChanges = deviceSnapshot.current?.version === activeVersionId && deviceSnapshot.current
|
|
271
311
|
? deviceSnapshot.current.changes
|
|
272
312
|
: hasVersions && activeVersionId && versionChanges?.[activeVersionId]
|
|
@@ -990,13 +1030,13 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
990
1030
|
}
|
|
991
1031
|
}, [clipboard, commands, depsOf, frameProps?.sessionId, selection, t, uploadImage]);
|
|
992
1032
|
const self = selection?.chain[0];
|
|
993
|
-
return (_jsxs("div", { className: `${CANVAS_UI_SCOPE} flex h-full min-h-0 flex-col bg-[#F5F7FC]`, children: [_jsx(EditorChrome, { slotRight: capturing ? (_jsx("span", { role: "status", className: "text-xs text-muted-foreground", children: t('inlineCanvas.review.generating', '正在生成截图…') })) : undefined, variant: chrome, title: title, versionLabel: versionLabel, versions: versions, activeVersionId: activeVersionId, versionSwitching: switching, onVersionChange: onVersionChange, canUndo: !switching && textEditing === null && changeList.canUndo, canRedo: !switching && textEditing === null && changeList.canRedo, onUndo: () => uiWrite(() => enqueue(() => changeListRef.current.undo())), onRedo: () => uiWrite(() => enqueue(() => changeListRef.current.redo())), changeCount: changeList.list.length, onExit: onExit, onSave: () => {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1033
|
+
return (_jsxs("div", { className: `${CANVAS_UI_SCOPE} flex h-full min-h-0 flex-col bg-[#F5F7FC]`, children: [_jsxs("div", { className: "relative shrink-0", children: [interactive && (_jsxs("div", { role: "status", "data-testid": "inline-editor-interactive-bar", className: "absolute inset-0 z-20 flex items-center justify-center gap-3\n border-b border-[#ececef] bg-white text-[13px] text-[#1a1a1a]\n shadow-[0_4px_12px_0_rgba(9,30,66,0.12)]", children: [_jsx("span", { children: t('inlineCanvas.interactive.active', '您当前正在交互模式') }), _jsx(Button, { size: "sm", variant: "secondary", "data-testid": "inline-editor-interactive-exit", onClick: exitInteractive, children: t('inlineCanvas.interactive.exit', '返回编辑模式') })] })), _jsx(EditorChrome, { slotRight: capturing ? (_jsx("span", { role: "status", className: "text-xs text-muted-foreground", children: t('inlineCanvas.review.generating', '正在生成截图…') })) : undefined, variant: chrome, title: title, versionLabel: versionLabel, versions: versions, activeVersionId: activeVersionId, versionSwitching: switching, onVersionChange: onVersionChange, canUndo: !switching && textEditing === null && changeList.canUndo, canRedo: !switching && textEditing === null && changeList.canRedo, onUndo: () => uiWrite(() => enqueue(() => changeListRef.current.undo())), onRedo: () => uiWrite(() => enqueue(() => changeListRef.current.redo())), changeCount: changeList.list.length, onExit: onExit, onSave: () => {
|
|
1034
|
+
if (switchingRef.current) {
|
|
1035
|
+
setCommandError(t('inlineCanvas.version.switching', '正在切换版本,请稍候'));
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
onSave?.(editor);
|
|
1039
|
+
}, onAddVersion: onAddVersion, onRenameVersion: onRenameVersion, removableVersionIds: removableVersionIds, onRemoveVersion: onRemoveVersion, 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 relative flex h-full min-h-0 flex-col bg-white", children: [interactive && (_jsx("div", { "data-testid": "inline-editor-left-veil", className: "absolute inset-0 z-10 cursor-not-allowed bg-white/60" })), !!libraryComponents && (_jsx("div", { className: "flex h-9 shrink-0 items-center gap-1 border-b\n border-[#EBECF0] px-2", children: [
|
|
1000
1040
|
['layers', '图层'],
|
|
1001
1041
|
['assets', '组件']
|
|
1002
1042
|
].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
|
|
@@ -1007,7 +1047,15 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
1007
1047
|
targetIdentifier: targetSelector,
|
|
1008
1048
|
targetSelector,
|
|
1009
1049
|
mode: moveModeFromDropPos(pos)
|
|
1010
|
-
})) })) })] }) }), _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: changeDevice
|
|
1050
|
+
})) })) })] }) }), _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: changeDevice, deviceDisabled: interactive, interactive: interactiveMode
|
|
1051
|
+
? {
|
|
1052
|
+
active: interactive,
|
|
1053
|
+
disabled: !interactive && !canEnterInteractive,
|
|
1054
|
+
onToggle: () => interactive
|
|
1055
|
+
? exitInteractive()
|
|
1056
|
+
: enterInteractive()
|
|
1057
|
+
}
|
|
1058
|
+
: undefined }), _jsxs("div", { ref: canvasAreaRef, className: "relative min-h-0 flex-1 overflow-hidden p-3", children: [editor.session.state === 'failed' && (_jsxs("div", { role: "alert", "data-testid": "inline-editor-session-failed", className: "absolute inset-3 z-20 flex flex-col items-center justify-center gap-3 rounded-lg bg-white p-6 text-center text-sm", children: [_jsx("p", { className: "font-semibold", children: t('inlineCanvas.review.pageLoadFailed', '目标页面无法加载') }), _jsx("p", { children: [
|
|
1011
1059
|
'NO_EXTENSION',
|
|
1012
1060
|
'EXTENSION_ID_MISSING',
|
|
1013
1061
|
'CONTRACT_MISMATCH'
|
|
@@ -1015,7 +1063,7 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
1015
1063
|
? describeCanvasError(editor.session.reason)
|
|
1016
1064
|
: t('inlineCanvas.review.pageLoadHint', '请返回检查页面地址、网站访问权限和网络连接后重试。') }), _jsx("code", { className: "text-xs text-muted-foreground", children: editor.session.reason }), _jsx(Button, { variant: "secondary", onClick: onExit, children: t('inlineCanvas.review.backFromFailed', '返回修改') })] })), 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 && (_jsx(ScreenshotCard, { dataUrl: shot.dataUrl, truncated: shot.truncated, scale: shot.scale, uploadImage: uploadImage, onClose: () => {
|
|
1017
1065
|
setShot(null);
|
|
1018
|
-
} }, shot.dataUrl)), _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 && (_jsxs(EditorStatus, { tone: "error", "data-testid": "inline-editor-command-error", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, children: [_jsx("span", { children: commandError }), _jsx(Button, { size: "sm", variant: "secondary", onClick: () => setCommandError(null), children: t('inlineCanvas.chrome.close', '关闭') })] })), !capturing && aiApplyNote && (_jsxs(EditorStatus, { "data-testid": "inline-editor-ai-apply-note", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, children: [_jsx("span", { role: "status", children: aiApplyNote }), _jsx(Button, { size: "sm", variant: "secondary", onClick: () => setAiApplyNote(null), children: t('inlineCanvas.chrome.close', '关闭') })] })), !capturing && move && (_jsx("div", { role: "toolbar", "aria-label": t('inlineCanvas.move.title'), "data-testid": "inline-editor-move-bar", className: "fixed z-[903] -translate-x-1/2 rounded-lg border bg-popover p-3 text-xs text-popover-foreground shadow-md", style: {
|
|
1066
|
+
} }, shot.dataUrl)), _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 && !interactive && (_jsx(SelectionOverlay, { geometry: geometry.latest, context: overlayContext, selectedLabel: self ? `<${self.tag}>` : undefined })), !capturing && !interactive && blockOverlay && (_jsx(BlockOverlay, { blocks: blockOverlay.blocks, activeId: blockOverlay.activeId, onHover: blockOverlay.onHover, geometry: geometry.latest, context: overlayContext, scale: editor.mobileFit.scale })), !capturing && commandError && (_jsxs(EditorStatus, { tone: "error", "data-testid": "inline-editor-command-error", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, children: [_jsx("span", { children: commandError }), _jsx(Button, { size: "sm", variant: "secondary", onClick: () => setCommandError(null), children: t('inlineCanvas.chrome.close', '关闭') })] })), !capturing && aiApplyNote && (_jsxs(EditorStatus, { "data-testid": "inline-editor-ai-apply-note", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, children: [_jsx("span", { role: "status", children: aiApplyNote }), _jsx(Button, { size: "sm", variant: "secondary", onClick: () => setAiApplyNote(null), children: t('inlineCanvas.chrome.close', '关闭') })] })), !capturing && !interactive && move && (_jsx("div", { role: "toolbar", "aria-label": t('inlineCanvas.move.title'), "data-testid": "inline-editor-move-bar", className: "fixed z-[903] -translate-x-1/2 rounded-lg border bg-popover p-3 text-xs text-popover-foreground shadow-md", style: {
|
|
1019
1067
|
width: 'min(600px, calc(100vw - 32px))',
|
|
1020
1068
|
left: `clamp(min(316px, 50vw), ${area.x + area.width / 2}px, max(calc(100vw - 316px), 50vw))`,
|
|
1021
1069
|
top: `${area.y + 8}px`
|
|
@@ -1024,7 +1072,8 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
1024
1072
|
: t('inlineCanvas.move.pickTarget') }), _jsx(Button, { size: "default", variant: "secondary", type: "button", "data-testid": "move-cancel", onClick: () => commands.cancelMove(), children: t('inlineCanvas.move.cancel') })] })) : (_jsx(MovePositionControls, { onConfirm: commands.confirmMove, onCancel: commands.cancelMove, onError: e => setCommandError(describeCanvasError(e)) }, move.target)) })), !capturing && textEditing !== null && (_jsx(EditorStatus, { "data-testid": "inline-editor-text-editing-hint", style: {
|
|
1025
1073
|
left: `${area.x + area.width / 2}px`,
|
|
1026
1074
|
top: `${area.y + 8}px`
|
|
1027
|
-
}, children: "\u6B63\u5728\u7F16\u8F91\u6587\u5B57 \u00B7 \u56DE\u8F66\u63D0\u4EA4 \u00B7 Esc \u53D6\u6D88" })), !capturing && !switching && !move && textEditing === null && (_jsx(SelectionToolbar, { selectedRect: selectedRect, area: area, capabilities: selection?.capabilities ?? null, isScrolling: geometry.isScrolling, onAction: onToolbarAction, host: { askAi: !!onAskAi } })), stylePanelOpen &&
|
|
1075
|
+
}, children: "\u6B63\u5728\u7F16\u8F91\u6587\u5B57 \u00B7 \u56DE\u8F66\u63D0\u4EA4 \u00B7 Esc \u53D6\u6D88" })), !capturing && !interactive && !switching && !move && textEditing === null && (_jsx(SelectionToolbar, { selectedRect: selectedRect, area: area, capabilities: selection?.capabilities ?? null, isScrolling: geometry.isScrolling, onAction: onToolbarAction, host: { askAi: !!onAskAi } })), stylePanelOpen &&
|
|
1076
|
+
!interactive &&
|
|
1028
1077
|
!switching &&
|
|
1029
1078
|
selectedRect &&
|
|
1030
1079
|
self &&
|
|
@@ -19,5 +19,18 @@ export interface CanvasContextBarProps {
|
|
|
19
19
|
url: string;
|
|
20
20
|
device: CanvasDevice;
|
|
21
21
|
onDeviceChange: (device: CanvasDevice) => void;
|
|
22
|
+
/** 交互模式下切设备会重建画布、把用户点出来的页面状态整个丢掉,所以一并禁用。 */
|
|
23
|
+
deviceDisabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* 「交互模式」开关,靠右。不传 = 没有这个按钮。
|
|
26
|
+
*
|
|
27
|
+
* 放在画布条而不是顶栏:它改变的是**画布**的行为(点击交给页面),与设备切换同属
|
|
28
|
+
* 「我在怎么看这一页」;老编辑器也是挨着设备切换放的。
|
|
29
|
+
*/
|
|
30
|
+
interactive?: {
|
|
31
|
+
active: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
onToggle: () => void;
|
|
34
|
+
};
|
|
22
35
|
}
|
|
23
|
-
export declare function CanvasContextBar({ url, device, onDeviceChange }: CanvasContextBarProps): import("react").JSX.Element;
|
|
36
|
+
export declare function CanvasContextBar({ url, device, onDeviceChange, deviceDisabled, interactive }: CanvasContextBarProps): import("react").JSX.Element;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import { Link2, Monitor, Smartphone } from 'lucide-react';
|
|
4
|
-
import { ChromeSegment, ChromeSegmented } from '../chrome/topbarPrimitives.js';
|
|
5
|
-
export function CanvasContextBar({ url, device, onDeviceChange }) {
|
|
3
|
+
import { Link2, Monitor, MousePointerClick, Smartphone } from 'lucide-react';
|
|
4
|
+
import { ChromeSegment, ChromeSegmented, ChromeTextButton } from '../chrome/topbarPrimitives.js';
|
|
5
|
+
export function CanvasContextBar({ url, device, onDeviceChange, deviceDisabled, interactive }) {
|
|
6
6
|
const { t } = useTranslation();
|
|
7
7
|
const items = [
|
|
8
8
|
['pc', Monitor, t('inlineCanvas.chrome.pc', 'PC')],
|
|
9
9
|
['mobile', Smartphone, t('inlineCanvas.chrome.mobile', '移动')]
|
|
10
10
|
];
|
|
11
|
-
return (_jsxs("div", { "data-testid": "canvas-context-bar", className: "relative flex h-9 shrink-0 items-center border-b border-[#EBECF0]\n bg-white px-3", children: [url && (_jsxs("a", { "data-testid": "canvas-context-url", href: url, target: "_blank", rel: "noopener noreferrer", title: url, className: "flex max-w-[calc(50%-60px)] items-center gap-1.5 truncate text-xs\n text-[#8a8f98] hover:text-[#52525b] hover:underline", children: [_jsx(Link2, { className: "size-3.5 shrink-0" }), _jsx("span", { className: "truncate", children: url })] })), _jsx(ChromeSegmented, { "data-testid": "device-switch", className: "absolute left-1/2 -translate-x-1/2", children: items.map(([value, Icon, label]) => (_jsxs(ChromeSegment, { "data-testid": `device-${value}`, active: device === value, onClick: () => onDeviceChange(value), children: [_jsx(Icon, { className: "size-3.5" }), label] }, value))) })
|
|
11
|
+
return (_jsxs("div", { "data-testid": "canvas-context-bar", className: "relative flex h-9 shrink-0 items-center border-b border-[#EBECF0]\n bg-white px-3", children: [url && (_jsxs("a", { "data-testid": "canvas-context-url", href: url, target: "_blank", rel: "noopener noreferrer", title: url, className: "flex max-w-[calc(50%-60px)] items-center gap-1.5 truncate text-xs\n text-[#8a8f98] hover:text-[#52525b] hover:underline", children: [_jsx(Link2, { className: "size-3.5 shrink-0" }), _jsx("span", { className: "truncate", children: url })] })), _jsx(ChromeSegmented, { "data-testid": "device-switch", className: "absolute left-1/2 -translate-x-1/2", children: items.map(([value, Icon, label]) => (_jsxs(ChromeSegment, { "data-testid": `device-${value}`, active: device === value, disabled: deviceDisabled, onClick: () => onDeviceChange(value), children: [_jsx(Icon, { className: "size-3.5" }), label] }, value))) }), interactive && (_jsxs(ChromeTextButton, { "data-testid": "canvas-interactive-toggle", className: "ml-auto h-7 gap-1.5 px-2 text-xs", active: interactive.active, disabled: interactive.disabled, title: t('inlineCanvas.interactive.tips', '切换到交互模式可以对页面进行交互,以修改需要交互后才能看到的内容。'), onClick: interactive.onToggle, children: [_jsx(MousePointerClick, { className: "size-3.5" }), interactive.active
|
|
12
|
+
? t('inlineCanvas.interactive.exit', '返回编辑模式')
|
|
13
|
+
: t('inlineCanvas.interactive.enter', '交互模式')] }))] }));
|
|
12
14
|
}
|
|
@@ -82,6 +82,8 @@ export interface UseChangeListResult {
|
|
|
82
82
|
outcome: HydrateOutcome;
|
|
83
83
|
idMap: Map<string, string>;
|
|
84
84
|
}>;
|
|
85
|
+
/** 当前清单原样重放(changeId 不变、不进撤销历史)。交互模式退出时用。 */
|
|
86
|
+
reapplyAll: () => Promise<void>;
|
|
85
87
|
whenIdle: () => Promise<unknown>;
|
|
86
88
|
canUndo: boolean;
|
|
87
89
|
canRedo: boolean;
|
|
@@ -174,6 +174,7 @@ export function useChangeList(sessionId, options = {}) {
|
|
|
174
174
|
target.push(entry);
|
|
175
175
|
refreshHistory(n => n + 1);
|
|
176
176
|
}, false), [guard, restoreSnapshot]);
|
|
177
|
+
const reapplyAll = useCallback(() => guard(() => restoreSnapshot(listRef.current), false), [guard, restoreSnapshot]);
|
|
177
178
|
const undo = useCallback(() => travelHistory('undo'), [travelHistory]);
|
|
178
179
|
const redo = useCallback(() => travelHistory('redo'), [travelHistory]);
|
|
179
180
|
const addStyle = useCallback((input) => guard(() => push(buildStyleChange(input), input.ancestors)), [guard, push]);
|
|
@@ -249,6 +250,7 @@ export function useChangeList(sessionId, options = {}) {
|
|
|
249
250
|
clear,
|
|
250
251
|
hydrate,
|
|
251
252
|
replaceAll,
|
|
253
|
+
reapplyAll,
|
|
252
254
|
whenIdle: () => mutationQueue.current,
|
|
253
255
|
peekList,
|
|
254
256
|
canUndo: past.current.length > 0,
|
|
@@ -32,6 +32,13 @@ export declare function useCanvasSession(input: {
|
|
|
32
32
|
device: CanvasDevice;
|
|
33
33
|
/** agent 主动发来的流(lifecycle / geometry / elementPicked / …)转出去给 useCanvasStream 消费。 */
|
|
34
34
|
onStreamEvent?: (msg: CanvasStreamMessage) => void;
|
|
35
|
+
/**
|
|
36
|
+
* 递增即按同一 url/device **重建会话**(新 sessionId、新 iframe)。
|
|
37
|
+
*
|
|
38
|
+
* 交互模式里用户点链接让画布整页跳走后,退出时要回到实验页:同会话里跳走的文档
|
|
39
|
+
* 不是编辑对象(拾取没开、改动没放),只能整个重开 —— 与切设备同一条路。
|
|
40
|
+
*/
|
|
41
|
+
reloadKey?: number;
|
|
35
42
|
}): {
|
|
36
43
|
state: State;
|
|
37
44
|
reason: LoadFailedReason | undefined;
|
|
@@ -177,6 +177,6 @@ export function useCanvasSession(input) {
|
|
|
177
177
|
void sendCanvasCommand({ command: 'destroy', sessionId }).catch(() => { });
|
|
178
178
|
port?.disconnect();
|
|
179
179
|
};
|
|
180
|
-
}, [input.url, input.device]);
|
|
180
|
+
}, [input.url, input.device, input.reloadKey]);
|
|
181
181
|
return { state, reason, portError, frameProps, handshake, readyPayload, capabilities };
|
|
182
182
|
}
|
package/dist/locales.d.ts
CHANGED
|
@@ -90,6 +90,12 @@ export declare const zhCN: {
|
|
|
90
90
|
pc: string;
|
|
91
91
|
mobile: string;
|
|
92
92
|
};
|
|
93
|
+
interactive: {
|
|
94
|
+
enter: string;
|
|
95
|
+
tips: string;
|
|
96
|
+
active: string;
|
|
97
|
+
exit: string;
|
|
98
|
+
};
|
|
93
99
|
version: {
|
|
94
100
|
switching: string;
|
|
95
101
|
};
|
|
@@ -271,6 +277,12 @@ export declare const enUS: {
|
|
|
271
277
|
pc: string;
|
|
272
278
|
mobile: string;
|
|
273
279
|
};
|
|
280
|
+
interactive: {
|
|
281
|
+
enter: string;
|
|
282
|
+
tips: string;
|
|
283
|
+
active: string;
|
|
284
|
+
exit: string;
|
|
285
|
+
};
|
|
274
286
|
version: {
|
|
275
287
|
switching: string;
|
|
276
288
|
};
|
|
@@ -452,6 +464,12 @@ export declare const jaJP: {
|
|
|
452
464
|
pc: string;
|
|
453
465
|
mobile: string;
|
|
454
466
|
};
|
|
467
|
+
interactive: {
|
|
468
|
+
enter: string;
|
|
469
|
+
tips: string;
|
|
470
|
+
active: string;
|
|
471
|
+
exit: string;
|
|
472
|
+
};
|
|
455
473
|
version: {
|
|
456
474
|
switching: string;
|
|
457
475
|
};
|
|
@@ -683,6 +701,12 @@ export declare const inlineEditorLocales: {
|
|
|
683
701
|
pc: string;
|
|
684
702
|
mobile: string;
|
|
685
703
|
};
|
|
704
|
+
readonly interactive: {
|
|
705
|
+
enter: string;
|
|
706
|
+
tips: string;
|
|
707
|
+
active: string;
|
|
708
|
+
exit: string;
|
|
709
|
+
};
|
|
686
710
|
readonly version: {
|
|
687
711
|
switching: string;
|
|
688
712
|
};
|
|
@@ -913,6 +937,12 @@ export declare const inlineEditorLocales: {
|
|
|
913
937
|
pc: string;
|
|
914
938
|
mobile: string;
|
|
915
939
|
};
|
|
940
|
+
readonly interactive: {
|
|
941
|
+
enter: string;
|
|
942
|
+
tips: string;
|
|
943
|
+
active: string;
|
|
944
|
+
exit: string;
|
|
945
|
+
};
|
|
916
946
|
readonly version: {
|
|
917
947
|
switching: string;
|
|
918
948
|
};
|
|
@@ -1143,6 +1173,12 @@ export declare const inlineEditorLocales: {
|
|
|
1143
1173
|
pc: string;
|
|
1144
1174
|
mobile: string;
|
|
1145
1175
|
};
|
|
1176
|
+
readonly interactive: {
|
|
1177
|
+
enter: string;
|
|
1178
|
+
tips: string;
|
|
1179
|
+
active: string;
|
|
1180
|
+
exit: string;
|
|
1181
|
+
};
|
|
1146
1182
|
readonly version: {
|
|
1147
1183
|
switching: string;
|
|
1148
1184
|
};
|
package/dist/locales.js
CHANGED
|
@@ -80,6 +80,12 @@ export const zhCN = {
|
|
|
80
80
|
pc: 'PC',
|
|
81
81
|
mobile: '移动'
|
|
82
82
|
},
|
|
83
|
+
interactive: {
|
|
84
|
+
enter: '交互模式',
|
|
85
|
+
tips: '切换到交互模式可以对页面进行交互,以修改需要交互后才能看到的内容。',
|
|
86
|
+
active: '您当前正在交互模式',
|
|
87
|
+
exit: '返回编辑模式'
|
|
88
|
+
},
|
|
83
89
|
version: {
|
|
84
90
|
switching: '正在切换版本,请稍候'
|
|
85
91
|
},
|
|
@@ -261,6 +267,12 @@ export const enUS = {
|
|
|
261
267
|
pc: 'Desktop',
|
|
262
268
|
mobile: 'Mobile'
|
|
263
269
|
},
|
|
270
|
+
interactive: {
|
|
271
|
+
enter: 'Interactive mode',
|
|
272
|
+
tips: 'Switch to interactive mode to interact with the page like in a live website.',
|
|
273
|
+
active: 'You are now in interactive mode',
|
|
274
|
+
exit: 'Back to edit mode'
|
|
275
|
+
},
|
|
264
276
|
version: {
|
|
265
277
|
switching: 'Switching version, please wait'
|
|
266
278
|
},
|
|
@@ -442,6 +454,12 @@ export const jaJP = {
|
|
|
442
454
|
pc: 'PC',
|
|
443
455
|
mobile: 'モバイル'
|
|
444
456
|
},
|
|
457
|
+
interactive: {
|
|
458
|
+
enter: '画面操作モード',
|
|
459
|
+
tips: '画面操作できるようになります。表示されていない要素を開き、その後編集モードに切り替えることで隠れた要素を変更することが可能です。',
|
|
460
|
+
active: '現在、画面操作モードです',
|
|
461
|
+
exit: '編集モードに戻る'
|
|
462
|
+
},
|
|
445
463
|
version: {
|
|
446
464
|
switching: 'バージョンを切り替えています。しばらくお待ちください'
|
|
447
465
|
},
|
|
@@ -45,6 +45,8 @@ export interface UseInlineEditorInput {
|
|
|
45
45
|
onHistoryShortcut?: (direction: 'undo' | 'redo') => void;
|
|
46
46
|
url: string;
|
|
47
47
|
device: CanvasDevice;
|
|
48
|
+
/** 递增即按同一 url/device 重建画布会话(见 `useCanvasSession` 的同名参数)。 */
|
|
49
|
+
reloadKey?: number;
|
|
48
50
|
/** change list 变化时回调 —— 宿主自己决定存哪(本 hook 不做持久化)。 */
|
|
49
51
|
onChangesChange?: (changes: InlineChange[]) => void;
|
|
50
52
|
/**
|
|
@@ -110,6 +112,18 @@ export interface UseInlineEditorResult {
|
|
|
110
112
|
*/
|
|
111
113
|
mobileFit: MobileFitState;
|
|
112
114
|
changeList: ReturnType<typeof useChangeList>;
|
|
115
|
+
/**
|
|
116
|
+
* 交互模式:画布上的拾取关掉,点击/悬停/输入原样交给客户页(打开弹窗、切换页签、展开折叠
|
|
117
|
+
* 之后再改那些「交互后才看得见」的内容)。对应老编辑器(engage-editor)顶栏的「交互模式」。
|
|
118
|
+
*/
|
|
119
|
+
interactive: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* 进出交互模式。退出时返回 `navigated`:交互期间画布整页跳走过(点了链接 / 提交了表单)。
|
|
122
|
+
* 跳走的文档不是编辑对象,调用方应重建会话(`reloadKey`)回到实验页,而不是在新页上继续编辑。
|
|
123
|
+
*/
|
|
124
|
+
setInteractive: (on: boolean) => Promise<{
|
|
125
|
+
navigated: boolean;
|
|
126
|
+
}>;
|
|
113
127
|
commands: {
|
|
114
128
|
/** 选中某个元素(点路径选择器的某一级、或程序化选中)。空串 = 清空。 */
|
|
115
129
|
select: (selector: string) => Promise<void>;
|
package/dist/useInlineEditor.js
CHANGED
|
@@ -38,7 +38,12 @@ export function useInlineEditor(input) {
|
|
|
38
38
|
});
|
|
39
39
|
}, []);
|
|
40
40
|
const [readySessionId, setReadySessionId] = useState(null);
|
|
41
|
+
const [interactive, setInteractiveState] = useState(false);
|
|
42
|
+
const interactiveRef = useRef(false);
|
|
43
|
+
const navigatedRef = useRef(false);
|
|
41
44
|
const onLifecycle = useCallback((payload, eventSessionId) => {
|
|
45
|
+
if (payload.phase === 'navAway' && interactiveRef.current)
|
|
46
|
+
navigatedRef.current = true;
|
|
42
47
|
if (payload.phase !== 'domReady' && payload.phase !== 'loaded')
|
|
43
48
|
return;
|
|
44
49
|
if (!eventSessionId)
|
|
@@ -54,6 +59,8 @@ export function useInlineEditor(input) {
|
|
|
54
59
|
historyShortcutRef.current = input.onHistoryShortcut;
|
|
55
60
|
const onStreamEvent = useCallback((msg) => {
|
|
56
61
|
if (msg.namespace === CANVAS_MSG_NAMESPACE && msg.event === 'historyShortcut') {
|
|
62
|
+
if (interactiveRef.current)
|
|
63
|
+
return;
|
|
57
64
|
const direction = msg.payload?.direction;
|
|
58
65
|
if (direction === 'undo' || direction === 'redo')
|
|
59
66
|
historyShortcutRef.current?.(direction);
|
|
@@ -64,7 +71,8 @@ export function useInlineEditor(input) {
|
|
|
64
71
|
const session = useCanvasSession({
|
|
65
72
|
url: input.url,
|
|
66
73
|
device: input.device,
|
|
67
|
-
onStreamEvent
|
|
74
|
+
onStreamEvent,
|
|
75
|
+
reloadKey: input.reloadKey
|
|
68
76
|
});
|
|
69
77
|
const sessionId = session.frameProps?.sessionId;
|
|
70
78
|
const changeList = useChangeList(sessionId, {
|
|
@@ -121,6 +129,9 @@ export function useInlineEditor(input) {
|
|
|
121
129
|
const geometryReset = geometry.reset;
|
|
122
130
|
const streamReset = stream.reset;
|
|
123
131
|
useEffect(() => {
|
|
132
|
+
interactiveRef.current = false;
|
|
133
|
+
navigatedRef.current = false;
|
|
134
|
+
setInteractiveState(false);
|
|
124
135
|
setSelection_(null);
|
|
125
136
|
setTree(null);
|
|
126
137
|
treeForRef.current = null;
|
|
@@ -160,11 +171,13 @@ export function useInlineEditor(input) {
|
|
|
160
171
|
: prev);
|
|
161
172
|
}, [sessionId]);
|
|
162
173
|
const treeLoaded = tree !== null;
|
|
174
|
+
const [treeEpoch, setTreeEpoch] = useState(0);
|
|
163
175
|
const contentRevision = changeList.list
|
|
164
176
|
.filter(record => ['elementContent', 'elementText', 'elementMove'].includes(record.change.type))
|
|
165
177
|
.map(record => record.changeId)
|
|
166
|
-
.join('|')
|
|
167
|
-
|
|
178
|
+
.join('|')
|
|
179
|
+
.concat(`#${treeEpoch}`);
|
|
180
|
+
const refreshedContent = useRef('#0');
|
|
168
181
|
useEffect(() => {
|
|
169
182
|
if (!wantTree ||
|
|
170
183
|
!domReady ||
|
|
@@ -371,6 +384,41 @@ export function useInlineEditor(input) {
|
|
|
371
384
|
}));
|
|
372
385
|
}
|
|
373
386
|
}), [graftAt, input.iframeRef, preserveScroll, refresh, sessionId]);
|
|
387
|
+
const setInteractive = useCallback(async (on) => {
|
|
388
|
+
if (!sessionId)
|
|
389
|
+
return { navigated: false };
|
|
390
|
+
if (on) {
|
|
391
|
+
if (interactiveRef.current)
|
|
392
|
+
return { navigated: false };
|
|
393
|
+
interactiveRef.current = true;
|
|
394
|
+
navigatedRef.current = false;
|
|
395
|
+
setInteractiveState(true);
|
|
396
|
+
setMove(null);
|
|
397
|
+
setSelection_(null);
|
|
398
|
+
await setHoverTarget(sessionId, '').catch(() => { });
|
|
399
|
+
await setSelection(sessionId, '').catch(() => { });
|
|
400
|
+
try {
|
|
401
|
+
await setPickMode(sessionId, false);
|
|
402
|
+
}
|
|
403
|
+
catch (e) {
|
|
404
|
+
interactiveRef.current = false;
|
|
405
|
+
setInteractiveState(false);
|
|
406
|
+
throw e;
|
|
407
|
+
}
|
|
408
|
+
return { navigated: false };
|
|
409
|
+
}
|
|
410
|
+
if (!interactiveRef.current)
|
|
411
|
+
return { navigated: false };
|
|
412
|
+
const navigated = navigatedRef.current;
|
|
413
|
+
interactiveRef.current = false;
|
|
414
|
+
navigatedRef.current = false;
|
|
415
|
+
setInteractiveState(false);
|
|
416
|
+
if (!navigated) {
|
|
417
|
+
await setPickMode(sessionId, true);
|
|
418
|
+
setTreeEpoch(n => n + 1);
|
|
419
|
+
}
|
|
420
|
+
return { navigated };
|
|
421
|
+
}, [sessionId]);
|
|
374
422
|
return useMemo(() => ({
|
|
375
423
|
session,
|
|
376
424
|
frameProps: session.frameProps,
|
|
@@ -384,6 +432,8 @@ export function useInlineEditor(input) {
|
|
|
384
432
|
geometry,
|
|
385
433
|
mobileFit: mobileFit.state,
|
|
386
434
|
changeList,
|
|
435
|
+
interactive,
|
|
436
|
+
setInteractive,
|
|
387
437
|
commands
|
|
388
438
|
}), [
|
|
389
439
|
session,
|
|
@@ -397,6 +447,8 @@ export function useInlineEditor(input) {
|
|
|
397
447
|
geometry,
|
|
398
448
|
mobileFit.state,
|
|
399
449
|
changeList,
|
|
450
|
+
interactive,
|
|
451
|
+
setInteractive,
|
|
400
452
|
commands
|
|
401
453
|
]);
|
|
402
454
|
}
|