@arronqzy/react-view 1.0.3 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/styles.css +555 -15
- package/package.json +14 -7
- package/readme.md +74 -5
- package/src/core/template.tsx +12 -2
- package/src/panel/ReactViewOnlinePreview.tsx +18 -8
- package/src/panel/ReactViewPanel.tsx +403 -177
- package/src/panel/ai/AssistantChatPanel.tsx +895 -0
- package/src/panel/ai/AssistantMarkdown.tsx +256 -0
- package/src/panel/ai/applyAssistantAction.ts +652 -0
- package/src/panel/ai/revealConfigField.ts +137 -0
- package/src/panel/components/ConfigHintIcon.tsx +50 -0
- package/src/panel/components/ElementsLayer.tsx +39 -22
- package/src/panel/components/MaterialSidebar.tsx +157 -109
- package/src/panel/components/MoveableLayer.tsx +75 -61
- package/src/panel/components/PanelCanvas.tsx +81 -64
- package/src/panel/components/PanelConfigSidebar.tsx +471 -448
- package/src/panel/components/SelectLayer.tsx +1 -0
- package/src/panel/components/ViewElementScopePanel.tsx +13 -12
- package/src/panel/components/WorkspaceConfigSidebar.tsx +5 -2
- package/src/panel/components/WorkspaceProjectNav.tsx +25 -14
- package/src/panel/components/WorkspaceStageSplit.tsx +3 -1
- package/src/panel/components/scope-config/ScopeConfigContext.tsx +9 -0
- package/src/panel/components/scope-config/ScopeTemplatePreviewHost.tsx +242 -0
- package/src/panel/components/scope-config/ScopeTemplateUsageHint.tsx +12 -0
- package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.tsx +6 -3
- package/src/panel/components/table/ConditionEditor.tsx +205 -0
- package/src/panel/components/table/JsonCodeEditor.tsx +115 -0
- package/src/panel/components/table/PanelConfigTableSection.tsx +2192 -0
- package/src/panel/components/table/TableNodeContent.tsx +544 -0
- package/src/panel/constants/messages.ts +76 -34
- package/src/panel/hooks/usePanelElements.ts +64 -52
- package/src/panel/hooks/useRafThrottledScroll.ts +30 -0
- package/src/panel/hooks/useWorkspaceProjects.ts +11 -8
- package/src/panel/types.ts +6 -0
- package/src/panel/utils/chartOptionBuilder.ts +12 -1
- package/src/panel/utils/panelElementDefaults.ts +69 -39
- package/src/panel/utils/scope-field-labels.ts +51 -37
- package/src/panel/utils/scope-template-chart.ts +92 -0
- package/src/panel/utils/scope-template-preview.ts +128 -0
- package/src/panel/utils/scope-template-spread.ts +252 -0
- package/src/panel/utils/scope-template-warnings.ts +131 -11
- package/src/panel/utils/scope-template.ts +105 -3
- package/src/tailwind.css +36 -15
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
1
|
+
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
2
3
|
import { createPortal } from "react-dom";
|
|
3
4
|
import Moveable from "react-moveable";
|
|
4
5
|
|
|
@@ -41,8 +42,8 @@ export type MoveableLayerProps = {
|
|
|
41
42
|
) => void;
|
|
42
43
|
/** 画布平移/缩放等视口变化时递增或变化,用于触发控制框重算 */
|
|
43
44
|
refreshToken?: number | string;
|
|
44
|
-
/**
|
|
45
|
-
|
|
45
|
+
/** 平移后立即同步控制框(由 PanelCanvas 写入,绕过 React scroll 节流) */
|
|
46
|
+
viewportSyncRef?: React.MutableRefObject<(() => void) | null>;
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
function getGridSlotLayout(grid: PanelElement) {
|
|
@@ -155,6 +156,17 @@ function isDescendantGrid(
|
|
|
155
156
|
return false;
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
function readLiveCanvasScale(
|
|
160
|
+
canvasContainer: HTMLElement | null,
|
|
161
|
+
fallback: number
|
|
162
|
+
): number {
|
|
163
|
+
if (!canvasContainer) return fallback;
|
|
164
|
+
const raw = canvasContainer.dataset.canvasScale;
|
|
165
|
+
if (!raw) return fallback;
|
|
166
|
+
const parsed = Number(raw);
|
|
167
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
168
|
+
}
|
|
169
|
+
|
|
158
170
|
export function MoveableLayer({
|
|
159
171
|
zoomX,
|
|
160
172
|
zoomY,
|
|
@@ -164,8 +176,10 @@ export function MoveableLayer({
|
|
|
164
176
|
elementsById,
|
|
165
177
|
updateElement,
|
|
166
178
|
refreshToken,
|
|
167
|
-
|
|
179
|
+
viewportSyncRef,
|
|
168
180
|
}: MoveableLayerProps) {
|
|
181
|
+
const { t } = useI18n();
|
|
182
|
+
|
|
169
183
|
const moveableRef = useRef<any>(null);
|
|
170
184
|
const targets = useMemo(() => {
|
|
171
185
|
const validTargets = selectedTargets.filter(
|
|
@@ -210,15 +224,13 @@ export function MoveableLayer({
|
|
|
210
224
|
[getId, movableTargets]
|
|
211
225
|
);
|
|
212
226
|
const moveableRenderKey = useMemo(() => movableIds.join(","), [movableIds]);
|
|
213
|
-
const
|
|
227
|
+
const propCanvasScale = useMemo(
|
|
214
228
|
() => uniformViewportZoom({ x: zoomX, y: zoomY }),
|
|
215
229
|
[zoomX, zoomY]
|
|
216
230
|
);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
[canvasScale]
|
|
221
|
-
);
|
|
231
|
+
const canvasScale = readLiveCanvasScale(canvasContainer, propCanvasScale);
|
|
232
|
+
/** 在 scale 画布内渲染,用倒数补偿,保证锚点/边线屏幕像素恒定 */
|
|
233
|
+
const moveableControlZoom = 1 / Math.max(0.0001, canvasScale);
|
|
222
234
|
const pointerRoot = dragContainer ?? canvasContainer;
|
|
223
235
|
const resolveSingleEventId = useCallback(
|
|
224
236
|
(target: HTMLElement | undefined | null) => {
|
|
@@ -270,9 +282,10 @@ export function MoveableLayer({
|
|
|
270
282
|
const [lockBadgeScreen, setLockBadgeScreen] = useState<{ x: number; y: number } | null>(
|
|
271
283
|
null
|
|
272
284
|
);
|
|
273
|
-
|
|
285
|
+
const syncRafRef = useRef<number | null>(null);
|
|
286
|
+
const syncLockBadge = useCallback(() => {
|
|
274
287
|
const id = lockedSelected[0];
|
|
275
|
-
if (!id || !
|
|
288
|
+
if (!id || !pointerRoot) {
|
|
276
289
|
setLockBadgeScreen(null);
|
|
277
290
|
return;
|
|
278
291
|
}
|
|
@@ -283,33 +296,37 @@ export function MoveableLayer({
|
|
|
283
296
|
setLockBadgeScreen(null);
|
|
284
297
|
return;
|
|
285
298
|
}
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
+
const rect = target.getBoundingClientRect();
|
|
300
|
+
const rootRect = pointerRoot.getBoundingClientRect();
|
|
301
|
+
setLockBadgeScreen({
|
|
302
|
+
x: rect.left - rootRect.left,
|
|
303
|
+
y: rect.top - rootRect.top,
|
|
304
|
+
});
|
|
305
|
+
}, [lockedSelected, pointerRoot, selectedTargets]);
|
|
306
|
+
|
|
307
|
+
const scheduleViewportSync = useCallback(() => {
|
|
308
|
+
if (syncRafRef.current != null) return;
|
|
309
|
+
syncRafRef.current = requestAnimationFrame(() => {
|
|
310
|
+
syncRafRef.current = null;
|
|
311
|
+
moveableRef.current?.updateRect?.();
|
|
312
|
+
syncLockBadge();
|
|
313
|
+
});
|
|
314
|
+
}, [syncLockBadge]);
|
|
315
|
+
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (!viewportSyncRef) return;
|
|
318
|
+
viewportSyncRef.current = scheduleViewportSync;
|
|
299
319
|
return () => {
|
|
300
|
-
|
|
301
|
-
canvasContainer.removeEventListener("scroll", sync);
|
|
320
|
+
viewportSyncRef.current = null;
|
|
302
321
|
};
|
|
303
|
-
}, [
|
|
322
|
+
}, [scheduleViewportSync, viewportSyncRef]);
|
|
304
323
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
});
|
|
312
|
-
}, []);
|
|
324
|
+
useLayoutEffect(() => {
|
|
325
|
+
moveableRef.current?.updateRect?.();
|
|
326
|
+
syncLockBadge();
|
|
327
|
+
}, [canvasScale, syncLockBadge]);
|
|
328
|
+
|
|
329
|
+
const updateRectNextFrame = scheduleViewportSync;
|
|
313
330
|
const getGridDirectChildren = useCallback(
|
|
314
331
|
(gridId: string) => {
|
|
315
332
|
const result: PanelElement[] = [];
|
|
@@ -421,22 +438,16 @@ export function MoveableLayer({
|
|
|
421
438
|
useEffect(() => {
|
|
422
439
|
if (!targets) return;
|
|
423
440
|
updateRectNextFrame();
|
|
424
|
-
}, [targets, updateRectNextFrame]);
|
|
425
|
-
|
|
426
|
-
useEffect(() => {
|
|
427
|
-
if (!targets) return;
|
|
428
|
-
updateRectNextFrame();
|
|
429
|
-
}, [refreshToken, targets, updateRectNextFrame]);
|
|
441
|
+
}, [moveableRenderKey, refreshToken, targets, updateRectNextFrame]);
|
|
430
442
|
|
|
431
|
-
useEffect(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}, [targets, updateRectNextFrame, zoomX, zoomY]);
|
|
443
|
+
useEffect(
|
|
444
|
+
() => () => {
|
|
445
|
+
if (syncRafRef.current != null) {
|
|
446
|
+
cancelAnimationFrame(syncRafRef.current);
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
[]
|
|
450
|
+
);
|
|
440
451
|
|
|
441
452
|
if (!canvasContainer) return null;
|
|
442
453
|
|
|
@@ -445,19 +456,22 @@ export function MoveableLayer({
|
|
|
445
456
|
<div
|
|
446
457
|
className="pointer-events-none absolute z-[80] inline-flex select-none items-center gap-1 rounded border border-border bg-background/95 px-1.5 py-0.5 text-[11px] text-foreground shadow-sm"
|
|
447
458
|
style={{ left: lockBadgeScreen.x + 6, top: lockBadgeScreen.y - 22 }}
|
|
448
|
-
title="
|
|
449
|
-
>
|
|
459
|
+
title={t("panel.config.nodeLockedTitle")}>
|
|
450
460
|
<LockGlyph />
|
|
451
|
-
<span
|
|
461
|
+
<span>{t("panel.config.nodeLockedBadge")}</span>
|
|
452
462
|
</div>
|
|
453
463
|
) : null;
|
|
454
464
|
|
|
455
465
|
if (!targets) {
|
|
456
|
-
return lockBadgeOnly
|
|
466
|
+
return lockBadgeOnly && pointerRoot
|
|
467
|
+
? createPortal(lockBadgeOnly, pointerRoot)
|
|
468
|
+
: null;
|
|
457
469
|
}
|
|
458
470
|
|
|
459
471
|
if (!movableTargets) {
|
|
460
|
-
return lockBadgeOnly
|
|
472
|
+
return lockBadgeOnly && pointerRoot
|
|
473
|
+
? createPortal(lockBadgeOnly, pointerRoot)
|
|
474
|
+
: null;
|
|
461
475
|
}
|
|
462
476
|
|
|
463
477
|
const layer = (
|
|
@@ -469,9 +483,10 @@ export function MoveableLayer({
|
|
|
469
483
|
target={movableTargets as unknown as HTMLElement[]}
|
|
470
484
|
rootContainer={canvasContainer}
|
|
471
485
|
container={canvasContainer}
|
|
472
|
-
dragContainer={pointerRoot
|
|
486
|
+
dragContainer={pointerRoot}
|
|
473
487
|
className="rv-moveable-layer"
|
|
474
488
|
zoom={moveableControlZoom}
|
|
489
|
+
hideChildMoveableDefaultLines={false}
|
|
475
490
|
// 允许在控制框内部区域拖动(不必必须点到某个节点本体)
|
|
476
491
|
dragArea
|
|
477
492
|
draggable
|
|
@@ -789,15 +804,14 @@ export function MoveableLayer({
|
|
|
789
804
|
<div
|
|
790
805
|
className="pointer-events-none absolute z-[80] inline-flex select-none items-center gap-1 rounded border border-border bg-background/95 px-1.5 py-0.5 text-[11px] text-foreground shadow-sm"
|
|
791
806
|
style={{ left: lockBadgeScreen.x + 6, top: lockBadgeScreen.y - 22 }}
|
|
792
|
-
title="
|
|
793
|
-
>
|
|
807
|
+
title={t("panel.config.lockedExcludedTitle")}>
|
|
794
808
|
<LockGlyph />
|
|
795
|
-
<span
|
|
809
|
+
<span>{t("panel.config.lockedExcludedBadge")}</span>
|
|
796
810
|
</div>
|
|
797
811
|
) : null}
|
|
798
812
|
</>
|
|
799
813
|
);
|
|
800
814
|
|
|
801
|
-
return createPortal(layer, canvasContainer);
|
|
815
|
+
return canvasContainer ? createPortal(layer, canvasContainer) : null;
|
|
802
816
|
}
|
|
803
817
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
1
|
+
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import InfiniteViewer from "react-infinite-viewer";
|
|
3
|
-
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
|
|
4
3
|
|
|
5
4
|
import {
|
|
6
5
|
clampViewportZoom,
|
|
@@ -23,6 +22,8 @@ export type PanelCanvasProps = {
|
|
|
23
22
|
onDropMaterial?: (payload: { materialId: string; x: number; y: number }) => void;
|
|
24
23
|
/** 渲染在 InfiniteViewer 内、画布 scale 层之外(如 Moveable 控制框) */
|
|
25
24
|
viewportOverlay?: React.ReactNode;
|
|
25
|
+
/** 平移等视口变换后同步 Moveable 控制框(绕过 React scroll 节流) */
|
|
26
|
+
viewportSyncRef?: React.MutableRefObject<(() => void) | null>;
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
@@ -40,12 +41,12 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
40
41
|
onCanvasContextMenuCapture,
|
|
41
42
|
onDropMaterial,
|
|
42
43
|
viewportOverlay,
|
|
44
|
+
viewportSyncRef,
|
|
43
45
|
},
|
|
44
46
|
scrollRef
|
|
45
47
|
) => {
|
|
46
48
|
const viewportRef = useRef<HTMLDivElement | null>(null);
|
|
47
49
|
const viewerRef = useRef<any>(null);
|
|
48
|
-
const transformRef = useRef<any>(null);
|
|
49
50
|
const canvasElRef = useRef<HTMLDivElement | null>(null);
|
|
50
51
|
const syncingZoomRef = useRef(false);
|
|
51
52
|
const didAutoCenterRef = useRef(false);
|
|
@@ -76,6 +77,14 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
76
77
|
height: number;
|
|
77
78
|
} | null>(null);
|
|
78
79
|
|
|
80
|
+
const applyCanvasTransform = useCallback((scale: number) => {
|
|
81
|
+
const el = canvasElRef.current;
|
|
82
|
+
if (!el) return;
|
|
83
|
+
el.style.transform = `scale(${scale})`;
|
|
84
|
+
el.style.transformOrigin = "0 0";
|
|
85
|
+
el.dataset.canvasScale = String(scale);
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
79
88
|
const commitScroll = useCallback(
|
|
80
89
|
(left: number, top: number) => {
|
|
81
90
|
const next = { left, top };
|
|
@@ -85,6 +94,10 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
85
94
|
[onScrollChange]
|
|
86
95
|
);
|
|
87
96
|
|
|
97
|
+
const notifyViewportSync = useCallback(() => {
|
|
98
|
+
viewportSyncRef?.current?.();
|
|
99
|
+
}, [viewportSyncRef]);
|
|
100
|
+
|
|
88
101
|
useEffect(() => {
|
|
89
102
|
zoomRef.current = zoom;
|
|
90
103
|
}, [zoom]);
|
|
@@ -155,8 +168,6 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
155
168
|
if (!el) return;
|
|
156
169
|
|
|
157
170
|
const onWheelZoom = (e: WheelEvent) => {
|
|
158
|
-
const api = transformRef.current;
|
|
159
|
-
if (!api) return;
|
|
160
171
|
const viewer = viewerRef.current as any;
|
|
161
172
|
if (!viewer) return;
|
|
162
173
|
const target = e.target as HTMLElement | null;
|
|
@@ -189,10 +200,12 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
189
200
|
const worldY = (pointerY + currentScrollTop) / Math.max(0.0001, zy);
|
|
190
201
|
const nextScrollLeft = worldX * nextZoom.x - pointerX;
|
|
191
202
|
const nextScrollTop = worldY * nextZoom.y - pointerY;
|
|
203
|
+
const nextScale = uniformViewportZoom(nextZoom);
|
|
192
204
|
|
|
193
205
|
syncingZoomRef.current = true;
|
|
206
|
+
zoomRef.current = nextZoom;
|
|
207
|
+
applyCanvasTransform(nextScale);
|
|
194
208
|
viewer.scrollTo(nextScrollLeft, nextScrollTop);
|
|
195
|
-
commitScroll(nextScrollLeft, nextScrollTop);
|
|
196
209
|
const { width: vw, height: vh } = viewportSizeRef.current;
|
|
197
210
|
resizeLayoutAnchorRef.current = {
|
|
198
211
|
viewportWidth: vw,
|
|
@@ -208,25 +221,22 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
208
221
|
zoomX: nextZoom.x,
|
|
209
222
|
zoomY: nextZoom.y,
|
|
210
223
|
};
|
|
211
|
-
zoomRef.current = nextZoom;
|
|
212
224
|
onZoomChange({
|
|
213
225
|
x: Number(nextZoom.x.toFixed(4)),
|
|
214
226
|
y: Number(nextZoom.y.toFixed(4)),
|
|
215
227
|
});
|
|
216
|
-
requestAnimationFrame(() => {
|
|
217
|
-
syncingZoomRef.current = false;
|
|
218
|
-
});
|
|
219
228
|
};
|
|
220
229
|
|
|
221
230
|
el.addEventListener("wheel", onWheelZoom, { passive: false, capture: true });
|
|
222
231
|
return () => {
|
|
223
232
|
el.removeEventListener("wheel", onWheelZoom, true);
|
|
224
233
|
};
|
|
225
|
-
}, [
|
|
234
|
+
}, [applyCanvasTransform, onZoomChange, viewport.width, viewport.height]);
|
|
226
235
|
|
|
227
236
|
const applyViewportResizeZoom = useCallback(
|
|
228
237
|
(w: number, h: number) => {
|
|
229
238
|
if (w < 8 || h < 8) return;
|
|
239
|
+
if (syncingZoomRef.current) return;
|
|
230
240
|
|
|
231
241
|
const viewer = viewerRef.current as {
|
|
232
242
|
scrollTo?: (x: number, y: number) => void;
|
|
@@ -285,8 +295,9 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
285
295
|
const nextZoom = { x: nextZoomValue, y: nextZoomValue };
|
|
286
296
|
|
|
287
297
|
syncingZoomRef.current = true;
|
|
298
|
+
zoomRef.current = nextZoom;
|
|
299
|
+
applyCanvasTransform(nextZoomValue);
|
|
288
300
|
viewer.scrollTo?.(nextScrollLeft, nextScrollTop);
|
|
289
|
-
commitScroll(nextScrollLeft, nextScrollTop);
|
|
290
301
|
resizeLayoutAnchorRef.current = {
|
|
291
302
|
viewportWidth: w,
|
|
292
303
|
viewportHeight: h,
|
|
@@ -301,16 +312,12 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
301
312
|
zoomX: nextZoomValue,
|
|
302
313
|
zoomY: nextZoomValue,
|
|
303
314
|
};
|
|
304
|
-
zoomRef.current = nextZoom;
|
|
305
315
|
onZoomChange({
|
|
306
316
|
x: Number(nextZoomValue.toFixed(4)),
|
|
307
317
|
y: Number(nextZoomValue.toFixed(4)),
|
|
308
318
|
});
|
|
309
|
-
requestAnimationFrame(() => {
|
|
310
|
-
syncingZoomRef.current = false;
|
|
311
|
-
});
|
|
312
319
|
},
|
|
313
|
-
[
|
|
320
|
+
[applyCanvasTransform, onZoomChange]
|
|
314
321
|
);
|
|
315
322
|
|
|
316
323
|
const syncViewportElement = useCallback(
|
|
@@ -326,11 +333,14 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
326
333
|
const syncCanvasElement = useCallback(
|
|
327
334
|
(el: HTMLDivElement | null) => {
|
|
328
335
|
canvasElRef.current = el;
|
|
336
|
+
if (el) {
|
|
337
|
+
applyCanvasTransform(uniformViewportZoom(zoomRef.current));
|
|
338
|
+
}
|
|
329
339
|
if (!canvasRef) return;
|
|
330
340
|
if (typeof canvasRef === "function") canvasRef(el);
|
|
331
341
|
else (canvasRef as React.MutableRefObject<HTMLDivElement | null>).current = el;
|
|
332
342
|
},
|
|
333
|
-
[canvasRef]
|
|
343
|
+
[applyCanvasTransform, canvasRef]
|
|
334
344
|
);
|
|
335
345
|
|
|
336
346
|
useEffect(() => {
|
|
@@ -465,15 +475,9 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
465
475
|
const nextLeft = panRef.current.startLeft - dx;
|
|
466
476
|
const nextTop = panRef.current.startTop - dy;
|
|
467
477
|
viewer.scrollTo(nextLeft, nextTop);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
: nextLeft;
|
|
472
|
-
const top =
|
|
473
|
-
typeof viewer.getScrollTop === "function"
|
|
474
|
-
? viewer.getScrollTop()
|
|
475
|
-
: nextTop;
|
|
476
|
-
commitScroll(left, top);
|
|
478
|
+
lastScrollRef.current = { left: nextLeft, top: nextTop };
|
|
479
|
+
onScrollChange?.({ left: nextLeft, top: nextTop });
|
|
480
|
+
notifyViewportSync();
|
|
477
481
|
};
|
|
478
482
|
|
|
479
483
|
const up = (ev: MouseEvent) => {
|
|
@@ -498,7 +502,7 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
498
502
|
el.removeEventListener("contextmenu", onContextMenu, true);
|
|
499
503
|
el.removeEventListener("mousedown", onMouseDown, true);
|
|
500
504
|
};
|
|
501
|
-
}, [clearWindowPanListeners, commitScroll]);
|
|
505
|
+
}, [clearWindowPanListeners, commitScroll, notifyViewportSync, onScrollChange]);
|
|
502
506
|
|
|
503
507
|
const resolvedContentSize = useMemo(() => {
|
|
504
508
|
if (contentSize) return contentSize;
|
|
@@ -511,6 +515,35 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
511
515
|
|
|
512
516
|
const canvasScale = useMemo(() => uniformViewportZoom(zoom), [zoom.x, zoom.y]);
|
|
513
517
|
|
|
518
|
+
const reconcileCanvasTransform = useCallback(() => {
|
|
519
|
+
const scale = syncingZoomRef.current
|
|
520
|
+
? uniformViewportZoom(zoomRef.current)
|
|
521
|
+
: uniformViewportZoom(zoom);
|
|
522
|
+
applyCanvasTransform(scale);
|
|
523
|
+
}, [applyCanvasTransform, zoom.x, zoom.y]);
|
|
524
|
+
|
|
525
|
+
useLayoutEffect(() => {
|
|
526
|
+
reconcileCanvasTransform();
|
|
527
|
+
}, [canvasScale, reconcileCanvasTransform]);
|
|
528
|
+
|
|
529
|
+
useEffect(() => {
|
|
530
|
+
if (
|
|
531
|
+
Math.abs(zoom.x - zoomRef.current.x) < 0.0001 &&
|
|
532
|
+
Math.abs(zoom.y - zoomRef.current.y) < 0.0001
|
|
533
|
+
) {
|
|
534
|
+
syncingZoomRef.current = false;
|
|
535
|
+
}
|
|
536
|
+
}, [zoom.x, zoom.y]);
|
|
537
|
+
|
|
538
|
+
const worldStyle = useMemo<React.CSSProperties>(
|
|
539
|
+
() => ({
|
|
540
|
+
width: resolvedContentSize.width,
|
|
541
|
+
height: resolvedContentSize.height,
|
|
542
|
+
position: "relative",
|
|
543
|
+
}),
|
|
544
|
+
[resolvedContentSize.height, resolvedContentSize.width]
|
|
545
|
+
);
|
|
546
|
+
|
|
514
547
|
const style = useMemo<React.CSSProperties>(
|
|
515
548
|
() => ({
|
|
516
549
|
width: resolvedContentSize.width,
|
|
@@ -518,10 +551,9 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
518
551
|
minWidth: "100%",
|
|
519
552
|
minHeight: "100%",
|
|
520
553
|
position: "relative",
|
|
521
|
-
transform: `scale(${canvasScale})`,
|
|
522
554
|
transformOrigin: "0 0",
|
|
523
555
|
}),
|
|
524
|
-
[
|
|
556
|
+
[resolvedContentSize.height, resolvedContentSize.width]
|
|
525
557
|
);
|
|
526
558
|
|
|
527
559
|
const hasMaterialPayload = useCallback((types: ArrayLike<string> | null | undefined) => {
|
|
@@ -613,44 +645,29 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
|
|
|
613
645
|
const next = { left: e.scrollLeft ?? 0, top: e.scrollTop ?? 0 };
|
|
614
646
|
lastScrollRef.current = next;
|
|
615
647
|
onScrollChange?.(next);
|
|
648
|
+
if (!syncingZoomRef.current) {
|
|
649
|
+
notifyViewportSync();
|
|
650
|
+
}
|
|
616
651
|
}}
|
|
617
652
|
>
|
|
618
|
-
<
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
pinch={{ disabled: true }}
|
|
628
|
-
wheel={{ step: 0.008, disabled: true, wheelDisabled: true }}
|
|
629
|
-
centerZoomedOut={false}
|
|
630
|
-
>
|
|
631
|
-
<TransformComponent
|
|
632
|
-
// 始终占满操作容器,但不在这一层裁切放大后的内容
|
|
633
|
-
wrapperStyle={{ width: "100%", height: "100%", overflow: "visible" }}
|
|
634
|
-
contentStyle={{ width: "100%", height: "100%", overflow: "visible" }}
|
|
653
|
+
<div style={worldStyle}>
|
|
654
|
+
<div
|
|
655
|
+
ref={syncCanvasElement}
|
|
656
|
+
data-panel-canvas
|
|
657
|
+
style={style}
|
|
658
|
+
className="h-full w-full"
|
|
659
|
+
onMouseDownCapture={onCanvasMouseDownCapture}
|
|
660
|
+
onClickCapture={onCanvasClickCapture}
|
|
661
|
+
onContextMenuCapture={onCanvasContextMenuCapture}
|
|
635
662
|
>
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
onMouseDownCapture={onCanvasMouseDownCapture}
|
|
642
|
-
onClickCapture={onCanvasClickCapture}
|
|
643
|
-
onContextMenuCapture={onCanvasContextMenuCapture}
|
|
644
|
-
>
|
|
645
|
-
{children}
|
|
646
|
-
{viewportOverlay ? (
|
|
647
|
-
<div className="rv-viewport-overlay pointer-events-none absolute inset-0 z-[70] overflow-visible [&_.moveable-control-box]:pointer-events-auto">
|
|
648
|
-
{viewportOverlay}
|
|
649
|
-
</div>
|
|
650
|
-
) : null}
|
|
663
|
+
{children}
|
|
664
|
+
</div>
|
|
665
|
+
{viewportOverlay ? (
|
|
666
|
+
<div className="rv-viewport-overlay pointer-events-none absolute inset-0 z-[70] overflow-visible [&_.moveable-control-box]:pointer-events-auto [&_.moveable-group]:pointer-events-auto [&_.moveable-area]:pointer-events-auto">
|
|
667
|
+
{viewportOverlay}
|
|
651
668
|
</div>
|
|
652
|
-
|
|
653
|
-
</
|
|
669
|
+
) : null}
|
|
670
|
+
</div>
|
|
654
671
|
</InfiniteViewer>
|
|
655
672
|
);
|
|
656
673
|
}
|