@tldiagram/core-ui 1.95.0 → 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/MiniZoomOnboarding.d.ts +2 -1
- 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 +5 -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 +14597 -12083
- 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/MiniZoomOnboarding.tsx +29 -22
- 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 +226 -127
- 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 +76 -27
- 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
package/src/pages/Views.tsx
CHANGED
|
@@ -29,16 +29,25 @@ import { motion, AnimatePresence } from 'framer-motion'
|
|
|
29
29
|
import ViewsGrid from './ViewsGrid'
|
|
30
30
|
import InfiniteZoom, { type InfiniteZoomHandle } from './InfiniteZoom'
|
|
31
31
|
import { ZoomInIcon } from '../components/Icons'
|
|
32
|
+
import { WATCH_REPRESENTATION_UPDATED_EVENT } from '../components/WorkspacePanel'
|
|
32
33
|
import { api } from '../api/client'
|
|
33
34
|
import { toast } from '../utils/toast'
|
|
34
|
-
import type { ViewTreeNode } from '../types'
|
|
35
|
+
import type { ExploreData, ViewTreeNode } from '../types'
|
|
36
|
+
import {
|
|
37
|
+
buildJumpSearchResults,
|
|
38
|
+
flattenTree,
|
|
39
|
+
jumpResultActionLabel,
|
|
40
|
+
jumpResultSubtitle,
|
|
41
|
+
type JumpSearchResult,
|
|
42
|
+
type JumpViewMode,
|
|
43
|
+
} from './viewsJumpSearch'
|
|
35
44
|
|
|
36
45
|
interface Props {
|
|
37
46
|
shareSlot?: React.ReactNode
|
|
38
47
|
onShareView?: (viewId: number) => void
|
|
39
48
|
}
|
|
40
49
|
|
|
41
|
-
type ViewType =
|
|
50
|
+
type ViewType = JumpViewMode
|
|
42
51
|
|
|
43
52
|
const MotionBox = motion.create(Box)
|
|
44
53
|
|
|
@@ -56,24 +65,14 @@ function HierarchyModeIcon({ size = 13 }: { size?: number }) {
|
|
|
56
65
|
)
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
function flattenTree(roots: ViewTreeNode[]): ViewTreeNode[] {
|
|
60
|
-
const result: ViewTreeNode[] = []
|
|
61
|
-
const traverse = (node: ViewTreeNode) => {
|
|
62
|
-
result.push(node)
|
|
63
|
-
node.children.forEach(traverse)
|
|
64
|
-
}
|
|
65
|
-
roots.forEach(traverse)
|
|
66
|
-
return result
|
|
67
|
-
}
|
|
68
|
-
|
|
69
68
|
interface DiagramJumpToolbarProps {
|
|
70
69
|
view: ViewType
|
|
71
70
|
searchTerm: string
|
|
72
|
-
searchResults:
|
|
71
|
+
searchResults: JumpSearchResult[]
|
|
73
72
|
activeSearchIndex: number
|
|
74
73
|
onSearchChange: (term: string) => void
|
|
75
74
|
onSearchKeyDown: (e: React.KeyboardEvent) => void
|
|
76
|
-
onResultClick: (result:
|
|
75
|
+
onResultClick: (result: JumpSearchResult) => void
|
|
77
76
|
onViewChange: (view: ViewType) => void
|
|
78
77
|
onCreateOpen: () => void
|
|
79
78
|
}
|
|
@@ -328,7 +327,7 @@ function DiagramJumpToolbar({
|
|
|
328
327
|
>
|
|
329
328
|
{searchResults.map((result, idx) => (
|
|
330
329
|
<Flex
|
|
331
|
-
key={result.
|
|
330
|
+
key={result.key}
|
|
332
331
|
px={4}
|
|
333
332
|
py={2.5}
|
|
334
333
|
align="center"
|
|
@@ -352,13 +351,13 @@ function DiagramJumpToolbar({
|
|
|
352
351
|
{result.name}
|
|
353
352
|
</Text>
|
|
354
353
|
<Text color="whiteAlpha.500" fontSize="10px" textTransform="uppercase" letterSpacing="0.05em">
|
|
355
|
-
|
|
354
|
+
{jumpResultSubtitle(result)}
|
|
356
355
|
</Text>
|
|
357
356
|
</Box>
|
|
358
357
|
{idx === activeSearchIndex && (
|
|
359
358
|
<HStack spacing={1} opacity={0.8}>
|
|
360
359
|
<Text color="var(--accent)" fontSize="9px" fontWeight="800" letterSpacing="0.1em">
|
|
361
|
-
{view
|
|
360
|
+
{jumpResultActionLabel(view)}
|
|
362
361
|
</Text>
|
|
363
362
|
<Text color="whiteAlpha.400" fontSize="9px">↵</Text>
|
|
364
363
|
</HStack>
|
|
@@ -384,15 +383,33 @@ export default function ViewsPage({ shareSlot, onShareView }: Props) {
|
|
|
384
383
|
const [treeLoading, setTreeLoading] = useState(true)
|
|
385
384
|
const [focusedHierarchyId, setFocusedHierarchyId] = useState<number | null>(null)
|
|
386
385
|
const [searchTerm, setSearchTerm] = useState('')
|
|
387
|
-
const [searchResults, setSearchResults] = useState<
|
|
386
|
+
const [searchResults, setSearchResults] = useState<JumpSearchResult[]>([])
|
|
388
387
|
const [activeSearchIndex, setActiveSearchIndex] = useState(-1)
|
|
388
|
+
const [exploreSearchData, setExploreSearchData] = useState<ExploreData | null>(null)
|
|
389
389
|
const { isOpen: isCreateOpen, onOpen: onCreateOpen, onClose: onCreateClose } = useDisclosure()
|
|
390
390
|
const [newName, setNewName] = useState('')
|
|
391
391
|
const [isCreating, setIsCreating] = useState(false)
|
|
392
392
|
const exploreRef = useRef<InfiniteZoomHandle>(null)
|
|
393
|
+
const exploreSearchLoadRef = useRef<Promise<ExploreData | null> | null>(null)
|
|
393
394
|
|
|
394
395
|
const flatTree = useMemo(() => flattenTree(treeData), [treeData])
|
|
395
396
|
|
|
397
|
+
const ensureExploreSearchData = useCallback(() => {
|
|
398
|
+
if (exploreSearchData || exploreSearchLoadRef.current) return
|
|
399
|
+
exploreSearchLoadRef.current = api.explore.load()
|
|
400
|
+
.then((data) => {
|
|
401
|
+
if (!data.password_required) {
|
|
402
|
+
setExploreSearchData(data)
|
|
403
|
+
return data
|
|
404
|
+
}
|
|
405
|
+
return null
|
|
406
|
+
})
|
|
407
|
+
.catch(() => null)
|
|
408
|
+
.finally(() => {
|
|
409
|
+
exploreSearchLoadRef.current = null
|
|
410
|
+
})
|
|
411
|
+
}, [exploreSearchData])
|
|
412
|
+
|
|
396
413
|
const handleViewChange = useCallback((newView: ViewType) => {
|
|
397
414
|
setView(newView)
|
|
398
415
|
const newParams = new URLSearchParams(searchParams)
|
|
@@ -411,8 +428,43 @@ export default function ViewsPage({ shareSlot, onShareView }: Props) {
|
|
|
411
428
|
}
|
|
412
429
|
}, [searchParams])
|
|
413
430
|
|
|
431
|
+
useEffect(() => {
|
|
432
|
+
const focusId = Number(searchParams.get('focus') ?? 0)
|
|
433
|
+
const elementId = Number(searchParams.get('element') ?? 0)
|
|
434
|
+
if (view !== 'explore' || !Number.isFinite(focusId) || focusId <= 0) return
|
|
435
|
+
let attempts = 0
|
|
436
|
+
let timer: number | null = null
|
|
437
|
+
const focus = () => {
|
|
438
|
+
attempts += 1
|
|
439
|
+
if (Number.isFinite(elementId) && elementId > 0) {
|
|
440
|
+
if (exploreRef.current?.focusElement(focusId, elementId)) return
|
|
441
|
+
if (attempts < 12) timer = window.setTimeout(focus, 150)
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
if (exploreRef.current?.focusDiagram(focusId)) return
|
|
445
|
+
if (attempts < 12) timer = window.setTimeout(focus, 150)
|
|
446
|
+
}
|
|
447
|
+
focus()
|
|
448
|
+
return () => {
|
|
449
|
+
if (timer !== null) window.clearTimeout(timer)
|
|
450
|
+
}
|
|
451
|
+
}, [searchParams, view])
|
|
452
|
+
|
|
453
|
+
useEffect(() => {
|
|
454
|
+
const trimmed = searchTerm.trim()
|
|
455
|
+
if (trimmed.length < 3) return
|
|
456
|
+
|
|
457
|
+
const matches = buildJumpSearchResults(trimmed, flatTree, exploreSearchData)
|
|
458
|
+
setSearchResults(matches)
|
|
459
|
+
setActiveSearchIndex(matches.length > 0 ? 0 : -1)
|
|
460
|
+
if (view === 'hierarchy' && matches[0]) {
|
|
461
|
+
setFocusedHierarchyId(matches[0].viewId)
|
|
462
|
+
}
|
|
463
|
+
}, [exploreSearchData, flatTree, searchTerm, view])
|
|
464
|
+
|
|
414
465
|
const refreshTree = useCallback(async () => {
|
|
415
466
|
setTreeLoading(true)
|
|
467
|
+
setExploreSearchData(null)
|
|
416
468
|
const tree = await api.workspace.views.tree().catch(() => null)
|
|
417
469
|
if (tree) {
|
|
418
470
|
setTreeData(tree)
|
|
@@ -452,17 +504,38 @@ export default function ViewsPage({ shareSlot, onShareView }: Props) {
|
|
|
452
504
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
453
505
|
}, [])
|
|
454
506
|
|
|
455
|
-
|
|
507
|
+
useEffect(() => {
|
|
508
|
+
const refresh = () => {
|
|
509
|
+
void refreshTree()
|
|
510
|
+
}
|
|
511
|
+
window.addEventListener(WATCH_REPRESENTATION_UPDATED_EVENT, refresh)
|
|
512
|
+
return () => window.removeEventListener(WATCH_REPRESENTATION_UPDATED_EVENT, refresh)
|
|
513
|
+
}, [refreshTree])
|
|
514
|
+
|
|
515
|
+
const commitSearchResult = useCallback((result: JumpSearchResult) => {
|
|
456
516
|
if (view === 'explore') {
|
|
457
|
-
|
|
517
|
+
const newParams = new URLSearchParams(searchParams)
|
|
518
|
+
newParams.set('view', 'explore')
|
|
519
|
+
newParams.set('focus', String(result.viewId))
|
|
520
|
+
if (result.type === 'element') {
|
|
521
|
+
newParams.set('element', String(result.elementId))
|
|
522
|
+
exploreRef.current?.focusElement(result.viewId, result.elementId)
|
|
523
|
+
} else {
|
|
524
|
+
newParams.delete('element')
|
|
525
|
+
exploreRef.current?.focusDiagram(result.viewId)
|
|
526
|
+
}
|
|
527
|
+
setSearchParams(newParams, { replace: true })
|
|
528
|
+
} else if (result.type === 'element') {
|
|
529
|
+
setFocusedHierarchyId(result.viewId)
|
|
530
|
+
navigate(`/views/${result.viewId}?element=${result.elementId}`)
|
|
458
531
|
} else {
|
|
459
|
-
setFocusedHierarchyId(result.
|
|
460
|
-
navigate(`/views/${result.
|
|
532
|
+
setFocusedHierarchyId(result.viewId)
|
|
533
|
+
navigate(`/views/${result.viewId}`)
|
|
461
534
|
}
|
|
462
535
|
setSearchResults([])
|
|
463
536
|
setActiveSearchIndex(-1)
|
|
464
537
|
setSearchTerm('')
|
|
465
|
-
}, [navigate, view])
|
|
538
|
+
}, [navigate, searchParams, setSearchParams, view])
|
|
466
539
|
|
|
467
540
|
const handleSearchChange = useCallback((term: string) => {
|
|
468
541
|
setSearchTerm(term)
|
|
@@ -471,20 +544,8 @@ export default function ViewsPage({ shareSlot, onShareView }: Props) {
|
|
|
471
544
|
setActiveSearchIndex(-1)
|
|
472
545
|
return
|
|
473
546
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const matches = flatTree
|
|
477
|
-
.filter((n) => n.name.toLowerCase().includes(normalized))
|
|
478
|
-
.slice(0, 5)
|
|
479
|
-
|
|
480
|
-
setSearchResults(matches)
|
|
481
|
-
if (matches.length > 0) {
|
|
482
|
-
setActiveSearchIndex(0)
|
|
483
|
-
if (view === 'hierarchy') setFocusedHierarchyId(matches[0].id)
|
|
484
|
-
} else {
|
|
485
|
-
setActiveSearchIndex(-1)
|
|
486
|
-
}
|
|
487
|
-
}, [flatTree, view])
|
|
547
|
+
ensureExploreSearchData()
|
|
548
|
+
}, [ensureExploreSearchData])
|
|
488
549
|
|
|
489
550
|
const handleSearchKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
490
551
|
if (e.key === 'Escape') {
|
|
@@ -498,12 +559,12 @@ export default function ViewsPage({ shareSlot, onShareView }: Props) {
|
|
|
498
559
|
e.preventDefault()
|
|
499
560
|
const nextIndex = (activeSearchIndex + 1) % searchResults.length
|
|
500
561
|
setActiveSearchIndex(nextIndex)
|
|
501
|
-
if (view === 'hierarchy') setFocusedHierarchyId(searchResults[nextIndex].
|
|
562
|
+
if (view === 'hierarchy') setFocusedHierarchyId(searchResults[nextIndex].viewId)
|
|
502
563
|
} else if (e.key === 'ArrowUp') {
|
|
503
564
|
e.preventDefault()
|
|
504
565
|
const nextIndex = (activeSearchIndex - 1 + searchResults.length) % searchResults.length
|
|
505
566
|
setActiveSearchIndex(nextIndex)
|
|
506
|
-
if (view === 'hierarchy') setFocusedHierarchyId(searchResults[nextIndex].
|
|
567
|
+
if (view === 'hierarchy') setFocusedHierarchyId(searchResults[nextIndex].viewId)
|
|
507
568
|
} else if (e.key === 'Enter' && activeSearchIndex >= 0) {
|
|
508
569
|
e.preventDefault()
|
|
509
570
|
commitSearchResult(searchResults[activeSearchIndex])
|
|
@@ -514,7 +575,17 @@ export default function ViewsPage({ shareSlot, onShareView }: Props) {
|
|
|
514
575
|
if (!newName.trim()) return
|
|
515
576
|
setIsCreating(true)
|
|
516
577
|
try {
|
|
517
|
-
|
|
578
|
+
let d
|
|
579
|
+
if (treeData.length > 0) {
|
|
580
|
+
// Root view already exists. Create a new element in the root view to own this new diagram.
|
|
581
|
+
const name = newName.trim()
|
|
582
|
+
const element = await api.workspace.elements.create({ name })
|
|
583
|
+
const root = treeData[0]
|
|
584
|
+
await api.workspace.views.placements.add(root.id, element.id, 100, 100)
|
|
585
|
+
d = await api.workspace.views.create({ name, parent_view_id: element.id })
|
|
586
|
+
} else {
|
|
587
|
+
d = await api.workspace.views.create({ name: newName.trim() })
|
|
588
|
+
}
|
|
518
589
|
await refreshTree()
|
|
519
590
|
navigate(`/views/${d.id}`)
|
|
520
591
|
onCreateClose()
|
|
@@ -524,7 +595,7 @@ export default function ViewsPage({ shareSlot, onShareView }: Props) {
|
|
|
524
595
|
} finally {
|
|
525
596
|
setIsCreating(false)
|
|
526
597
|
}
|
|
527
|
-
}, [navigate, newName, onCreateClose, refreshTree])
|
|
598
|
+
}, [navigate, newName, onCreateClose, refreshTree, treeData])
|
|
528
599
|
|
|
529
600
|
if (initializing) {
|
|
530
601
|
return (
|