@tldiagram/core-ui 1.95.1 → 2.0.0
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/api/client.d.ts +184 -3
- package/dist/components/ConnectorPanel.d.ts +5 -1
- package/dist/components/CrossBranchControls.d.ts +4 -3
- package/dist/components/ElementNode.d.ts +5 -0
- package/dist/components/ElementPanel.d.ts +6 -1
- package/dist/components/LayoutSection.d.ts +2 -1
- package/dist/components/MergeDialog.d.ts +16 -0
- package/dist/components/NodeContainer.d.ts +2 -0
- package/dist/components/ProxyConnectorPanel.d.ts +4 -1
- package/dist/components/ViewExplorer/index.d.ts +1 -1
- package/dist/components/ViewFloatingMenu-vscode.d.ts +5 -0
- package/dist/components/ViewFloatingMenu.d.ts +8 -1
- package/dist/components/ViewGridNode.d.ts +3 -0
- package/dist/components/ViewPanel.d.ts +2 -1
- package/dist/components/WorkspacePanel.d.ts +2 -0
- package/dist/components/ZUI/ZUICanvas.d.ts +4 -0
- package/dist/components/ZUI/focus.d.ts +32 -0
- package/dist/components/ZUI/focus.test.d.ts +1 -0
- package/dist/components/ZUI/layout.d.ts +2 -2
- package/dist/components/ZUI/proxy.d.ts +20 -4
- package/dist/components/ZUI/renderer.d.ts +35 -1
- package/dist/components/ZUI/types.d.ts +6 -0
- package/dist/components/ZUI/useZUIInteraction.d.ts +1 -0
- package/dist/context/WorkspaceVersionContext.d.ts +49 -0
- package/dist/crossBranch/resolve.d.ts +39 -2
- package/dist/crossBranch/resolve.test.d.ts +1 -0
- package/dist/crossBranch/settings.d.ts +6 -1
- package/dist/crossBranch/types.d.ts +8 -0
- package/dist/hooks/useElementSearch.d.ts +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16529 -14030
- package/dist/pages/InfiniteZoom.d.ts +1 -0
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +6 -1
- package/dist/pages/ViewEditor/hooks/useViewContextNeighbours.d.ts +2 -0
- package/dist/pages/ViewEditor/hooks/useViewData.d.ts +4 -2
- package/dist/pages/ViewEditor/hooks/useViewEditHistory.d.ts +13 -0
- package/dist/pages/viewsJumpSearch.d.ts +22 -0
- package/dist/pages/viewsJumpSearch.test.d.ts +1 -0
- package/dist/store/useStore.d.ts +3 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/utils/elementIcon.d.ts +2 -0
- package/dist/utils/elementIcon.test.d.ts +1 -0
- package/dist/utils/sourceEditor.d.ts +7 -0
- package/dist/utils/watchDiffSummary.d.ts +34 -0
- package/package.json +2 -2
- package/src/App.tsx +12 -8
- package/src/api/client.ts +488 -26
- package/src/components/CodePreviewPanel.tsx +90 -16
- package/src/components/ConnectorPanel.tsx +34 -3
- package/src/components/ContextNeighborElement.tsx +2 -5
- package/src/components/CrossBranchControls.tsx +46 -17
- package/src/components/ElementNode.tsx +98 -47
- package/src/components/ElementPanel.tsx +62 -25
- package/src/components/InlineElementAdder.tsx +8 -3
- package/src/components/LayoutSection.tsx +4 -1
- package/src/components/MergeDialog.tsx +269 -0
- package/src/components/NodeContainer.tsx +55 -17
- package/src/components/ProxyConnectorPanel.tsx +58 -16
- package/src/components/ViewBezierConnector.tsx +116 -21
- package/src/components/ViewExplorer/index.tsx +1 -1
- package/src/components/ViewFloatingMenu-vscode.tsx +5 -0
- package/src/components/ViewFloatingMenu.tsx +110 -1
- package/src/components/ViewGridNode.tsx +59 -8
- package/src/components/ViewPanel.tsx +3 -2
- package/src/components/WorkspacePanel.tsx +938 -0
- package/src/components/ZUI/ZUICanvas.tsx +216 -122
- package/src/components/ZUI/focus.test.ts +534 -0
- package/src/components/ZUI/focus.ts +293 -0
- package/src/components/ZUI/layout.ts +7 -11
- package/src/components/ZUI/proxy.ts +470 -114
- package/src/components/ZUI/renderer.ts +510 -134
- package/src/components/ZUI/types.ts +6 -0
- package/src/components/ZUI/useZUIInteraction.ts +66 -29
- package/src/context/WorkspaceVersionContext.tsx +126 -0
- package/src/crossBranch/resolve.test.ts +342 -0
- package/src/crossBranch/resolve.ts +368 -68
- package/src/crossBranch/settings.ts +49 -3
- package/src/crossBranch/types.ts +9 -0
- package/src/hooks/useElementSearch.ts +45 -0
- package/src/index.css +11 -0
- package/src/index.ts +7 -0
- package/src/pages/AppearanceSettings.tsx +24 -1
- package/src/pages/Dependencies.tsx +231 -65
- package/src/pages/InfiniteZoom.tsx +41 -19
- package/src/pages/Settings.tsx +1 -1
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +103 -24
- package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +102 -6
- package/src/pages/ViewEditor/hooks/useViewData.ts +42 -26
- package/src/pages/ViewEditor/hooks/useViewEditHistory.ts +62 -0
- package/src/pages/ViewEditor/index.tsx +549 -59
- package/src/pages/Views.tsx +112 -41
- package/src/pages/ViewsGrid.tsx +332 -113
- package/src/pages/viewsJumpSearch.test.ts +193 -0
- package/src/pages/viewsJumpSearch.ts +111 -0
- package/src/store/useStore.ts +58 -0
- package/src/types/index.ts +10 -0
- package/src/utils/elementIcon.test.ts +28 -0
- package/src/utils/elementIcon.ts +20 -0
- package/src/utils/sourceEditor.ts +46 -0
- package/src/utils/watchDiffSummary.ts +159 -0
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
getVisualHandleSlot,
|
|
19
19
|
} from '../../../utils/edgeDistribution'
|
|
20
20
|
import { buildViewContentLinks, useStore } from '../../../store/useStore'
|
|
21
|
+
import type { WorkspaceVersionFollowTarget, WorkspaceVersionPreview } from '../../../context/WorkspaceVersionContext'
|
|
21
22
|
|
|
22
23
|
interface ViewDataOptions {
|
|
23
24
|
viewId: number | null
|
|
@@ -29,6 +30,8 @@ interface ViewDataOptions {
|
|
|
29
30
|
hoveredLayerTags: string[] | null
|
|
30
31
|
hoveredLayerColor: string | null
|
|
31
32
|
tagColors: Record<string, Tag>
|
|
33
|
+
versionPreview?: WorkspaceVersionPreview | null
|
|
34
|
+
versionFollowTarget?: WorkspaceVersionFollowTarget | null
|
|
32
35
|
// Node-level callbacks (stable refs from parent)
|
|
33
36
|
stableOnZoomIn: (elementId: number) => Promise<void>
|
|
34
37
|
stableOnZoomOut: (elementId: number) => Promise<void>
|
|
@@ -52,6 +55,7 @@ function alphaColor(color: string, opacity: number): string {
|
|
|
52
55
|
// letting structural-sharing fast-path bail out without rebuilding the node.
|
|
53
56
|
const HIDDEN_STYLE: CSSProperties = { opacity: 0.1, pointerEvents: 'none' }
|
|
54
57
|
const SOFT_FOCUS_STYLE: CSSProperties = { opacity: 0.2 }
|
|
58
|
+
const VERSION_DIM_STYLE: CSSProperties = { opacity: 0.1 }
|
|
55
59
|
const EMPTY_ARRAY: readonly never[] = Object.freeze([])
|
|
56
60
|
const EMPTY_NODE_CONNECTION_META = Object.freeze({
|
|
57
61
|
key: '',
|
|
@@ -158,6 +162,8 @@ export function useViewData({
|
|
|
158
162
|
hoveredLayerTags,
|
|
159
163
|
hoveredLayerColor,
|
|
160
164
|
tagColors,
|
|
165
|
+
versionPreview,
|
|
166
|
+
versionFollowTarget,
|
|
161
167
|
stableOnZoomIn,
|
|
162
168
|
stableOnZoomOut,
|
|
163
169
|
stableOnNavigateToView,
|
|
@@ -189,7 +195,6 @@ export function useViewData({
|
|
|
189
195
|
const incomingLinks = useStore((state) => state.incomingLinks)
|
|
190
196
|
const treeData = useStore((state) => state.treeData)
|
|
191
197
|
const allElements = useStore((state) => state.allElements)
|
|
192
|
-
const setAllElements = useStore((state) => state.setAllElements)
|
|
193
198
|
const hydrateViewContent = useStore((state) => state.hydrateViewContent)
|
|
194
199
|
const resetCanvas = useStore((state) => state.resetCanvas)
|
|
195
200
|
const removeElementPlacement = useStore((state) => state.removeElementPlacement)
|
|
@@ -216,13 +221,14 @@ export function useViewData({
|
|
|
216
221
|
|
|
217
222
|
// ── Fetch tree ─────────────────────────────────────────────────────────────
|
|
218
223
|
const refreshGrid = useCallback(async () => {
|
|
224
|
+
if (viewId === null) return
|
|
219
225
|
const tree = await queryClient.fetchQuery({
|
|
220
|
-
queryKey: ['workspace', 'views', 'tree'],
|
|
221
|
-
queryFn: () => api.workspace.views.
|
|
226
|
+
queryKey: ['workspace', 'views', viewId, 'editor-tree'],
|
|
227
|
+
queryFn: () => api.workspace.views.treeAround(viewId, { ancestorLevels: 2, descendantLevels: 2 }),
|
|
222
228
|
staleTime: 0,
|
|
223
229
|
}).catch(() => null)
|
|
224
230
|
if (tree) useStore.getState().setTreeData(tree)
|
|
225
|
-
}, [queryClient])
|
|
231
|
+
}, [queryClient, viewId])
|
|
226
232
|
|
|
227
233
|
// ── Fetch view content ──────────────────────────────────────────────────
|
|
228
234
|
const viewContentQuery = useQuery({
|
|
@@ -233,7 +239,7 @@ export function useViewData({
|
|
|
233
239
|
const [diag, content, tree] = await Promise.all([
|
|
234
240
|
api.workspace.views.get(viewId),
|
|
235
241
|
api.workspace.views.content(viewId),
|
|
236
|
-
api.workspace.views.
|
|
242
|
+
api.workspace.views.treeAround(viewId, { ancestorLevels: 2, descendantLevels: 2 }),
|
|
237
243
|
])
|
|
238
244
|
const viewElements = content.placements || []
|
|
239
245
|
const connectors = content.connectors || []
|
|
@@ -264,16 +270,6 @@ export function useViewData({
|
|
|
264
270
|
resetCanvas()
|
|
265
271
|
}, [resetCanvas, viewId])
|
|
266
272
|
|
|
267
|
-
// ── Keep all-org elements for inline adder ──────────────────────────────────
|
|
268
|
-
const allElementsQuery = useQuery({
|
|
269
|
-
queryKey: ['elements', 'list'],
|
|
270
|
-
queryFn: () => api.elements.list(),
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
useEffect(() => {
|
|
274
|
-
if (allElementsQuery.data) setAllElements(allElementsQuery.data)
|
|
275
|
-
}, [allElementsQuery.data, setAllElements])
|
|
276
|
-
|
|
277
273
|
// ── Refresh elements ────────────────────────────────────────────────────────
|
|
278
274
|
const refreshElements = useCallback(async () => {
|
|
279
275
|
if (viewId === null) return
|
|
@@ -428,6 +424,9 @@ export function useViewData({
|
|
|
428
424
|
const activeSet = activeTags.length > 0 ? new Set(activeTags) : null
|
|
429
425
|
const hoveredSet = hoveredLayerTags !== null ? new Set(hoveredLayerTags) : null
|
|
430
426
|
const isClickConnectMode = clickConnectMode !== null
|
|
427
|
+
const versionElementChanges = versionPreview?.elementChanges
|
|
428
|
+
const versionElementLineDeltas = versionPreview?.elementLineDeltas
|
|
429
|
+
const versionActive = !!versionPreview
|
|
431
430
|
|
|
432
431
|
return viewElements.map((obj) => {
|
|
433
432
|
const nodeId = String(obj.element_id)
|
|
@@ -438,13 +437,21 @@ export function useViewData({
|
|
|
438
437
|
const isInactive = isHiddenByLayer || (activeSet !== null && !objTags.some((t) => activeSet.has(t)))
|
|
439
438
|
const isLayerHighlighted = hoveredSet !== null && objTags.some((t) => hoveredSet.has(t))
|
|
440
439
|
const isSoftFocused = hoveredSet !== null && !isLayerHighlighted
|
|
441
|
-
|
|
442
|
-
const
|
|
440
|
+
const versionChangeType = versionElementChanges?.get(obj.element_id)
|
|
441
|
+
const versionLineDelta = versionElementLineDeltas?.get(obj.element_id)
|
|
442
|
+
const versionPulseChangeType = versionFollowTarget?.resourceType === 'element' && versionFollowTarget.resourceId === obj.element_id
|
|
443
|
+
? versionFollowTarget.changeType ?? versionChangeType
|
|
444
|
+
: undefined
|
|
445
|
+
const isDimmedByVersionPreview = versionActive && !versionChangeType
|
|
446
|
+
|
|
447
|
+
const newZIndex = versionPulseChangeType ? 20 : isLayerHighlighted ? 10 : interactionSourceId === obj.element_id ? 1000 : 0
|
|
443
448
|
const newStyle = isInactive
|
|
444
449
|
? HIDDEN_STYLE
|
|
445
450
|
: isSoftFocused
|
|
446
451
|
? SOFT_FOCUS_STYLE
|
|
447
|
-
:
|
|
452
|
+
: isDimmedByVersionPreview
|
|
453
|
+
? VERSION_DIM_STYLE
|
|
454
|
+
: undefined
|
|
448
455
|
const layerHighlightColor = isLayerHighlighted ? (hoveredLayerColor ?? undefined) : undefined
|
|
449
456
|
const position = existing?.dragging ? existing.position : { x: obj.position_x ?? 0, y: obj.position_y ?? 0 }
|
|
450
457
|
const isZoomHovered = hoveredZoomRef.current?.elementId === obj.element_id ? hoveredZoomRef.current.type : null
|
|
@@ -487,7 +494,9 @@ export function useViewData({
|
|
|
487
494
|
existing.data.connectedHandleIds === connectionMeta.connectedHandleIds &&
|
|
488
495
|
existing.data.selectedHandleIds === connectionMeta.selectedHandleIds &&
|
|
489
496
|
existing.data.reconnectCandidates === connectionMeta.reconnectCandidates &&
|
|
490
|
-
existing.data.isConnectorHighlighted === connectionMeta.isConnectorHighlighted
|
|
497
|
+
existing.data.isConnectorHighlighted === connectionMeta.isConnectorHighlighted &&
|
|
498
|
+
existing.data.versionChangeType === versionPulseChangeType &&
|
|
499
|
+
existing.data.versionLineDelta === versionLineDelta
|
|
491
500
|
) {
|
|
492
501
|
return existing
|
|
493
502
|
}
|
|
@@ -527,6 +536,8 @@ export function useViewData({
|
|
|
527
536
|
selectedHandleIds: connectionMeta.selectedHandleIds,
|
|
528
537
|
reconnectCandidates: connectionMeta.reconnectCandidates,
|
|
529
538
|
isConnectorHighlighted: connectionMeta.isConnectorHighlighted,
|
|
539
|
+
versionChangeType: versionPulseChangeType,
|
|
540
|
+
versionLineDelta,
|
|
530
541
|
},
|
|
531
542
|
}
|
|
532
543
|
})
|
|
@@ -537,7 +548,7 @@ export function useViewData({
|
|
|
537
548
|
stableOnZoomIn, stableOnZoomOut, stableOnNavigateToView, stableOnSelect,
|
|
538
549
|
stableOnInteractionStart, stableOnConnectTo, stableOnStartHandleReconnect, stableOnRemoveElement, stableOnHoverZoom,
|
|
539
550
|
stableOnOpenCodePreview, hoveredZoomRef, activeTags, hiddenLayerTags, hoveredLayerTags, hoveredLayerColor, tagColors,
|
|
540
|
-
nodeConnectionMetaByElementId, setRfNodes,
|
|
551
|
+
nodeConnectionMetaByElementId, setRfNodes, versionPreview, versionFollowTarget,
|
|
541
552
|
])
|
|
542
553
|
|
|
543
554
|
// ── Derive RF connectors ────────────────────────────────────────────────────────
|
|
@@ -545,6 +556,8 @@ export function useViewData({
|
|
|
545
556
|
const hiddenSet = hiddenLayerTags.length > 0 ? new Set(hiddenLayerTags) : null
|
|
546
557
|
const activeSet = activeTags.length > 0 ? new Set(activeTags) : null
|
|
547
558
|
const hoveredSet = hoveredLayerTags !== null ? new Set(hoveredLayerTags) : null
|
|
559
|
+
const versionConnectorChanges = versionPreview?.connectorChanges
|
|
560
|
+
const versionActive = !!versionPreview
|
|
548
561
|
|
|
549
562
|
setRfEdges((prevConnectors) => {
|
|
550
563
|
const prevEdgeMap = new Map(prevConnectors.map((e) => [e.id, e]))
|
|
@@ -572,11 +585,13 @@ export function useViewData({
|
|
|
572
585
|
!srcTags.some((t) => hoveredSet.has(t)) ||
|
|
573
586
|
!tgtTags.some((t) => hoveredSet.has(t))
|
|
574
587
|
)
|
|
575
|
-
const
|
|
576
|
-
const
|
|
588
|
+
const versionChangeType = versionConnectorChanges?.get(e.id)
|
|
589
|
+
const isDimmedByVersionPreview = versionActive && !versionChangeType
|
|
590
|
+
const edgeOpacity = isInactive || isDimmedByVersionPreview ? 0.1 : isSoftFocused ? 0.2 : 0.8
|
|
591
|
+
const markerOpacity = isInactive || isDimmedByVersionPreview ? 0.1 : isSoftFocused ? 0.2 : 1
|
|
577
592
|
const newZIndex = selectedEdgeId !== null && edgeId === String(selectedEdgeId) ? 1000 : 100
|
|
578
593
|
const pointerEvents = (isInactive || isSoftFocused) ? 'none' : 'auto'
|
|
579
|
-
const labelBgOpacity = isInactive ? 0.1 : isSoftFocused ? 0.2 : 0.95
|
|
594
|
+
const labelBgOpacity = isInactive || isDimmedByVersionPreview ? 0.1 : isSoftFocused ? 0.2 : 0.95
|
|
580
595
|
|
|
581
596
|
// Structural sharing: when all user-visible outputs match prev exactly, reuse prev ref.
|
|
582
597
|
// We match on the underlying connector ref plus every computed visibility/layout value.
|
|
@@ -594,7 +609,8 @@ export function useViewData({
|
|
|
594
609
|
(existing.data as { sourceGroupIndex?: number }).sourceGroupIndex === layout.sourceGroupIndex &&
|
|
595
610
|
(existing.data as { targetGroupIndex?: number }).targetGroupIndex === layout.targetGroupIndex &&
|
|
596
611
|
(existing.data as { sourceGroupCount?: number }).sourceGroupCount === layout.sourceGroupCount &&
|
|
597
|
-
(existing.data as { targetGroupCount?: number }).targetGroupCount === layout.targetGroupCount
|
|
612
|
+
(existing.data as { targetGroupCount?: number }).targetGroupCount === layout.targetGroupCount &&
|
|
613
|
+
(existing.data as { versionChangeType?: string }).versionChangeType === versionChangeType
|
|
598
614
|
) {
|
|
599
615
|
return existing
|
|
600
616
|
}
|
|
@@ -620,6 +636,7 @@ export function useViewData({
|
|
|
620
636
|
targetHandleSide: layout.targetHandleSide,
|
|
621
637
|
sourceHandleSlot: layout.sourceHandleSlot,
|
|
622
638
|
targetHandleSlot: layout.targetHandleSlot,
|
|
639
|
+
versionChangeType,
|
|
623
640
|
},
|
|
624
641
|
|
|
625
642
|
style: { stroke: 'var(--accent)', strokeWidth: 2, opacity: edgeOpacity, pointerEvents },
|
|
@@ -632,7 +649,7 @@ export function useViewData({
|
|
|
632
649
|
}
|
|
633
650
|
})
|
|
634
651
|
})
|
|
635
|
-
}, [connectorLayouts, selectedEdgeId, activeTags, hiddenLayerTags, hoveredLayerTags, elementMap, setRfEdges])
|
|
652
|
+
}, [connectorLayouts, selectedEdgeId, activeTags, hiddenLayerTags, hoveredLayerTags, elementMap, setRfEdges, versionPreview])
|
|
636
653
|
|
|
637
654
|
|
|
638
655
|
// ── Boost z-index of selected connector ────────────────────────────────────────
|
|
@@ -685,6 +702,5 @@ export function useViewData({
|
|
|
685
702
|
handleElementDeleted,
|
|
686
703
|
handleElementPermanentlyDeleted,
|
|
687
704
|
handleElementSaved,
|
|
688
|
-
setAllElements,
|
|
689
705
|
}
|
|
690
706
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
const MAX_HISTORY_ITEMS = 20
|
|
4
|
+
|
|
5
|
+
export interface ViewEditHistoryAction {
|
|
6
|
+
undo: () => Promise<void>
|
|
7
|
+
redo: () => Promise<void>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useViewEditHistory() {
|
|
11
|
+
const [undoStack, setUndoStack] = useState<ViewEditHistoryAction[]>([])
|
|
12
|
+
const [redoStack, setRedoStack] = useState<ViewEditHistoryAction[]>([])
|
|
13
|
+
const [isApplyingHistory, setIsApplyingHistory] = useState(false)
|
|
14
|
+
|
|
15
|
+
const pushAction = useCallback((action: ViewEditHistoryAction) => {
|
|
16
|
+
setUndoStack((stack) => [...stack, action].slice(-MAX_HISTORY_ITEMS))
|
|
17
|
+
setRedoStack([])
|
|
18
|
+
}, [])
|
|
19
|
+
|
|
20
|
+
const clearHistory = useCallback(() => {
|
|
21
|
+
setUndoStack([])
|
|
22
|
+
setRedoStack([])
|
|
23
|
+
}, [])
|
|
24
|
+
|
|
25
|
+
const undo = useCallback(async () => {
|
|
26
|
+
if (isApplyingHistory) return
|
|
27
|
+
const action = undoStack[undoStack.length - 1]
|
|
28
|
+
if (!action) return
|
|
29
|
+
setIsApplyingHistory(true)
|
|
30
|
+
try {
|
|
31
|
+
await action.undo()
|
|
32
|
+
setUndoStack((stack) => stack.slice(0, -1))
|
|
33
|
+
setRedoStack((stack) => [...stack, action].slice(-MAX_HISTORY_ITEMS))
|
|
34
|
+
} finally {
|
|
35
|
+
setIsApplyingHistory(false)
|
|
36
|
+
}
|
|
37
|
+
}, [isApplyingHistory, undoStack])
|
|
38
|
+
|
|
39
|
+
const redo = useCallback(async () => {
|
|
40
|
+
if (isApplyingHistory) return
|
|
41
|
+
const action = redoStack[redoStack.length - 1]
|
|
42
|
+
if (!action) return
|
|
43
|
+
setIsApplyingHistory(true)
|
|
44
|
+
try {
|
|
45
|
+
await action.redo()
|
|
46
|
+
setRedoStack((stack) => stack.slice(0, -1))
|
|
47
|
+
setUndoStack((stack) => [...stack, action].slice(-MAX_HISTORY_ITEMS))
|
|
48
|
+
} finally {
|
|
49
|
+
setIsApplyingHistory(false)
|
|
50
|
+
}
|
|
51
|
+
}, [isApplyingHistory, redoStack])
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
canUndo: undoStack.length > 0,
|
|
55
|
+
canRedo: redoStack.length > 0,
|
|
56
|
+
isApplyingHistory,
|
|
57
|
+
pushAction,
|
|
58
|
+
clearHistory,
|
|
59
|
+
undo,
|
|
60
|
+
redo,
|
|
61
|
+
}
|
|
62
|
+
}
|