@tldiagram/core-ui 1.94.1 → 1.94.3
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 +9 -1
- package/dist/components/ElementLibrary.d.ts +1 -2
- package/dist/components/NodeHoverCard.d.ts +3 -2
- package/dist/index.js +7612 -7440
- package/dist/pages/InfiniteZoom.d.ts +1 -1
- package/dist/pages/ViewEditor/hooks/useViewData.d.ts +0 -2
- package/dist/store/useStore.d.ts +0 -2
- package/package.json +1 -1
- package/src/api/client.ts +113 -15
- package/src/components/ContextNeighborElement.tsx +1 -1
- package/src/components/ElementLibrary.tsx +1 -3
- package/src/components/ElementNode.tsx +1 -1
- package/src/components/ElementPanel.tsx +243 -88
- package/src/components/Icons.tsx +2 -7
- package/src/components/NodeHoverCard.tsx +20 -15
- package/src/components/ZUI/layout.ts +1 -1
- package/src/pages/InfiniteZoom.tsx +19 -14
- package/src/pages/ViewEditor/hooks/useViewData.ts +1 -5
- package/src/pages/ViewEditor/index.tsx +11 -3
- package/src/store/useStore.test.ts +3 -5
- package/src/store/useStore.ts +2 -6
|
@@ -7,4 +7,4 @@ export interface InfiniteZoomHandle {
|
|
|
7
7
|
}
|
|
8
8
|
declare const InfiniteZoom: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<InfiniteZoomHandle>>;
|
|
9
9
|
export default InfiniteZoom;
|
|
10
|
-
export declare
|
|
10
|
+
export declare const SharedInfiniteZoom: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<InfiniteZoomHandle>>;
|
|
@@ -56,8 +56,6 @@ export declare function useViewData({ viewId, interactionSourceId, clickConnectM
|
|
|
56
56
|
incomingLinks: import("../../..").IncomingViewConnector[];
|
|
57
57
|
treeData: ViewTreeNode[];
|
|
58
58
|
allElements: LibraryElement[];
|
|
59
|
-
libraryRefresh: number;
|
|
60
|
-
setLibraryRefresh: (next: import("../../../store/useStore").StoreSetter<number>) => void;
|
|
61
59
|
existingElementIds: Set<number>;
|
|
62
60
|
viewElementsRef: import("react").MutableRefObject<PlacedElement[]>;
|
|
63
61
|
linksMapRef: import("react").MutableRefObject<Record<number, import("../../..").ViewConnector[]>>;
|
package/dist/store/useStore.d.ts
CHANGED
|
@@ -32,7 +32,6 @@ export type CanvasStoreState = ViewEditorUiState & {
|
|
|
32
32
|
incomingLinks: IncomingViewConnector[];
|
|
33
33
|
treeData: ViewTreeNode[];
|
|
34
34
|
allElements: LibraryElement[];
|
|
35
|
-
libraryRefresh: number;
|
|
36
35
|
setViewEditorUi: (patch: Partial<ViewEditorUiState>) => void;
|
|
37
36
|
setSnapToGrid: (snapToGrid: boolean) => void;
|
|
38
37
|
setSelectedElement: (selectedElement: LibraryElement | null) => void;
|
|
@@ -47,7 +46,6 @@ export type CanvasStoreState = ViewEditorUiState & {
|
|
|
47
46
|
setIncomingLinks: (next: StoreSetter<IncomingViewConnector[]>) => void;
|
|
48
47
|
setTreeData: (next: StoreSetter<ViewTreeNode[]>) => void;
|
|
49
48
|
setAllElements: (next: StoreSetter<LibraryElement[]>) => void;
|
|
50
|
-
setLibraryRefresh: (next: StoreSetter<number>) => void;
|
|
51
49
|
resetCanvas: () => void;
|
|
52
50
|
hydrateViewContent: (payload: ViewContentPayload) => void;
|
|
53
51
|
updateElementPosition: (elementId: number, x: number, y: number) => void;
|
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ExploreData,
|
|
9
9
|
LibraryElement,
|
|
10
10
|
PlacedElement,
|
|
11
|
+
Tag,
|
|
11
12
|
View,
|
|
12
13
|
ViewConnector,
|
|
13
14
|
ViewLayer,
|
|
@@ -91,6 +92,7 @@ interface ProtoDiagram {
|
|
|
91
92
|
updatedAt?: string
|
|
92
93
|
updated_at?: string
|
|
93
94
|
parent_view_id?: number | null
|
|
95
|
+
parentViewId?: number | null
|
|
94
96
|
children?: ProtoDiagram[]
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -107,7 +109,9 @@ function mapDiagram(d: ProtoDiagram): ViewTreeNode {
|
|
|
107
109
|
depth: d.depth ?? 0,
|
|
108
110
|
created_at: d.createdAt ?? d.created_at ?? '',
|
|
109
111
|
updated_at: d.updatedAt ?? d.updated_at ?? '',
|
|
110
|
-
parent_view_id: d.parent_view_id
|
|
112
|
+
parent_view_id: d.parentViewId != null || d.parent_view_id != null
|
|
113
|
+
? Number(d.parentViewId ?? d.parent_view_id)
|
|
114
|
+
: null,
|
|
111
115
|
children: (d.children ?? []).map(mapDiagram),
|
|
112
116
|
}
|
|
113
117
|
}
|
|
@@ -134,41 +138,51 @@ function protoElementToLibrary(e: Record<string, unknown>): LibraryElement {
|
|
|
134
138
|
description: (e.description ?? null) as string | null,
|
|
135
139
|
technology: (e.technology ?? null) as string | null,
|
|
136
140
|
url: (e.url ?? null) as string | null,
|
|
137
|
-
logo_url: (e.logo_url ?? null) as string | null,
|
|
138
|
-
technology_connectors: (e.technology_connectors ?? []) as
|
|
141
|
+
logo_url: (e.logo_url ?? e.logoUrl ?? null) as string | null,
|
|
142
|
+
technology_connectors: ((e.technology_connectors ?? e.technologyLinks ?? []) as any[]).map(tl => ({
|
|
143
|
+
type: tl.type,
|
|
144
|
+
slug: tl.slug,
|
|
145
|
+
label: tl.label,
|
|
146
|
+
is_primary_icon: !!(tl.is_primary_icon ?? tl.isPrimaryIcon),
|
|
147
|
+
})),
|
|
139
148
|
tags: (e.tags ?? []) as string[],
|
|
140
149
|
repo: (e.repo ?? null) as string | null,
|
|
141
150
|
branch: (e.branch ?? null) as string | null,
|
|
142
151
|
file_path: (e.file_path ?? null) as string | null,
|
|
143
152
|
language: (e.language ?? null) as string | null,
|
|
144
|
-
created_at: String(e.created_at ?? new Date().toISOString()),
|
|
145
|
-
updated_at: String(e.updated_at ?? new Date().toISOString()),
|
|
146
|
-
has_view: Boolean(e.has_view ?? false),
|
|
147
|
-
view_label: (e.view_label ?? null) as string | null,
|
|
153
|
+
created_at: String(e.created_at ?? e.createdAt ?? new Date().toISOString()),
|
|
154
|
+
updated_at: String(e.updated_at ?? e.updatedAt ?? new Date().toISOString()),
|
|
155
|
+
has_view: Boolean(e.has_view ?? e.hasView ?? false),
|
|
156
|
+
view_label: (e.view_label ?? e.viewLabel ?? null) as string | null,
|
|
148
157
|
}
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
function protoPlacedElement(p: Record<string, unknown>): PlacedElement {
|
|
152
161
|
return {
|
|
153
162
|
id: Number(p.id ?? 0),
|
|
154
|
-
view_id: Number(p.view_id ?? 0),
|
|
155
|
-
element_id: Number(p.element_id ?? 0),
|
|
156
|
-
position_x: Number(p.position_x ?? 0),
|
|
157
|
-
position_y: Number(p.position_y ?? 0),
|
|
163
|
+
view_id: Number(p.view_id ?? p.viewId ?? 0),
|
|
164
|
+
element_id: Number(p.element_id ?? p.elementId ?? 0),
|
|
165
|
+
position_x: Number(p.position_x ?? p.positionX ?? 0),
|
|
166
|
+
position_y: Number(p.position_y ?? p.positionY ?? 0),
|
|
158
167
|
name: String(p.name ?? ''),
|
|
159
168
|
description: (p.description ?? null) as string | null,
|
|
160
169
|
kind: (p.kind ?? null) as string | null,
|
|
161
170
|
technology: (p.technology ?? null) as string | null,
|
|
162
171
|
url: (p.url ?? null) as string | null,
|
|
163
|
-
logo_url: (p.logo_url ?? null) as string | null,
|
|
164
|
-
technology_connectors: (p.technology_connectors ?? []) as
|
|
172
|
+
logo_url: (p.logo_url ?? p.logoUrl ?? null) as string | null,
|
|
173
|
+
technology_connectors: ((p.technology_connect_ors ?? p.technology_connectors ?? p.technologyLinks ?? []) as any[]).map(tl => ({
|
|
174
|
+
type: tl.type,
|
|
175
|
+
slug: tl.slug,
|
|
176
|
+
label: tl.label,
|
|
177
|
+
is_primary_icon: !!(tl.is_primary_icon ?? tl.isPrimaryIcon),
|
|
178
|
+
})),
|
|
165
179
|
tags: (p.tags ?? []) as string[],
|
|
166
180
|
repo: (p.repo ?? null) as string | null,
|
|
167
181
|
branch: (p.branch ?? null) as string | null,
|
|
168
182
|
file_path: (p.file_path ?? null) as string | null,
|
|
169
183
|
language: (p.language ?? null) as string | null,
|
|
170
|
-
has_view: Boolean(p.has_view ?? false),
|
|
171
|
-
view_label: (p.view_label ?? null) as string | null,
|
|
184
|
+
has_view: Boolean(p.has_view ?? p.hasView ?? false),
|
|
185
|
+
view_label: (p.view_label ?? p.viewLabel ?? null) as string | null,
|
|
172
186
|
}
|
|
173
187
|
}
|
|
174
188
|
|
|
@@ -323,6 +337,12 @@ export const api = {
|
|
|
323
337
|
},
|
|
324
338
|
|
|
325
339
|
workspace: {
|
|
340
|
+
orgs: {
|
|
341
|
+
tagColors: {
|
|
342
|
+
list: (): Promise<Tag[]> => Promise.resolve([]),
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
|
|
326
346
|
elements: {
|
|
327
347
|
list: (params?: { limit?: number; offset?: number; search?: string }): Promise<LibraryElement[]> =>
|
|
328
348
|
api.elements.list(params),
|
|
@@ -631,6 +651,84 @@ export const api = {
|
|
|
631
651
|
password_required: false,
|
|
632
652
|
}
|
|
633
653
|
}),
|
|
654
|
+
|
|
655
|
+
loadShared: async (token: string, password?: string): Promise<ExploreData & { password_required?: boolean }> => {
|
|
656
|
+
const init: RequestInit = {
|
|
657
|
+
method: password ? 'POST' : 'GET',
|
|
658
|
+
headers: { 'Content-Type': 'application/json' },
|
|
659
|
+
}
|
|
660
|
+
if (password) {
|
|
661
|
+
init.body = JSON.stringify({ password })
|
|
662
|
+
}
|
|
663
|
+
const res = await fetch(apiUrl(`/shared/explore/${token}`), init)
|
|
664
|
+
if (!res.ok) {
|
|
665
|
+
throw new Error(`Failed to load shared diagram: ${res.statusText}`)
|
|
666
|
+
}
|
|
667
|
+
const data = await res.json() as {
|
|
668
|
+
tree: any[]
|
|
669
|
+
views: Record<string, { elements: any[]; connectors: any[] }>
|
|
670
|
+
password_required?: boolean
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const tree = (data.tree ?? []).map(mapDiagram)
|
|
674
|
+
const views = Object.fromEntries(
|
|
675
|
+
Object.entries(data.views ?? {}).map(([key, value]) => [
|
|
676
|
+
key,
|
|
677
|
+
{
|
|
678
|
+
placements: (value.elements ?? []).map(protoPlacedElement),
|
|
679
|
+
connectors: (value.connectors ?? []).map(protoConnector),
|
|
680
|
+
},
|
|
681
|
+
])
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
// Ensure that the share root is treated as a root (no parent) so that computeLayout
|
|
685
|
+
// picks it up even if it was nested in the original workspace.
|
|
686
|
+
const sharedRoot = tree.find(n => String(n.id) === String(data.views[token]?.elements?.[0]?.view_id ?? ''))
|
|
687
|
+
// Backend actually returns the shareToken.ViewID as the root of the tree it builds.
|
|
688
|
+
// We should find the node in 'tree' that has no parent *within the returned set*.
|
|
689
|
+
// For shared explore, the backend typically returns a tree starting at the shared view.
|
|
690
|
+
tree.forEach(node => {
|
|
691
|
+
// If the node's parent is not in our tree, it's a root for this shared view.
|
|
692
|
+
const parentInTree = tree.find(n => n.id === node.parent_view_id)
|
|
693
|
+
if (!parentInTree) {
|
|
694
|
+
node.parent_view_id = null
|
|
695
|
+
}
|
|
696
|
+
})
|
|
697
|
+
const navigations: ViewConnector[] = []
|
|
698
|
+
const elementToChildView = new Map<number, any>()
|
|
699
|
+
const allViews: any[] = []
|
|
700
|
+
const flatTree = (nodes: any[]) => {
|
|
701
|
+
nodes.forEach(n => {
|
|
702
|
+
allViews.push(n)
|
|
703
|
+
if (n.owner_element_id) elementToChildView.set(n.owner_element_id, n)
|
|
704
|
+
if (n.children) flatTree(n.children)
|
|
705
|
+
})
|
|
706
|
+
}
|
|
707
|
+
flatTree(tree)
|
|
708
|
+
|
|
709
|
+
Object.values(views).forEach((v: any) => {
|
|
710
|
+
v.placements.forEach((p: any) => {
|
|
711
|
+
const childView = elementToChildView.get(p.element_id)
|
|
712
|
+
if (childView) {
|
|
713
|
+
navigations.push({
|
|
714
|
+
id: 0,
|
|
715
|
+
element_id: p.element_id,
|
|
716
|
+
from_view_id: p.view_id,
|
|
717
|
+
to_view_id: childView.id,
|
|
718
|
+
to_view_name: childView.name,
|
|
719
|
+
relation_type: 'child',
|
|
720
|
+
})
|
|
721
|
+
}
|
|
722
|
+
})
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
return {
|
|
726
|
+
tree,
|
|
727
|
+
views,
|
|
728
|
+
navigations,
|
|
729
|
+
password_required: data.password_required,
|
|
730
|
+
}
|
|
731
|
+
},
|
|
634
732
|
},
|
|
635
733
|
|
|
636
734
|
import: {
|
|
@@ -62,7 +62,7 @@ function ContextNeighborNode({ data }: Props) {
|
|
|
62
62
|
|
|
63
63
|
const logoUrl = useMemo(() => {
|
|
64
64
|
if (data.logo_url) return resolveIconPath(data.logo_url)
|
|
65
|
-
const selected = data.technology_connectors?.find((link) => link.type === 'catalog' && !!link.is_primary_icon && !!link.slug)
|
|
65
|
+
const selected = data.technology_connectors?.find((link) => link.type === 'catalog' && !!(link.is_primary_icon ?? (link as any).isPrimaryIcon) && !!link.slug)
|
|
66
66
|
if (!selected?.slug) return undefined
|
|
67
67
|
return resolveIconPath(`/icons/${selected.slug}.png`)
|
|
68
68
|
}, [data.logo_url, data.technology_connectors])
|
|
@@ -34,7 +34,6 @@ interface Props {
|
|
|
34
34
|
existingElementIds: Set<number>
|
|
35
35
|
existingElements?: LibraryElement[]
|
|
36
36
|
onCreateNew: () => void
|
|
37
|
-
refresh: number
|
|
38
37
|
isOpen: boolean
|
|
39
38
|
onClose: () => void
|
|
40
39
|
onTapAdd?: (obj: LibraryElement) => void
|
|
@@ -89,7 +88,6 @@ function ElementLibrary({
|
|
|
89
88
|
existingElementIds,
|
|
90
89
|
existingElements = [],
|
|
91
90
|
onCreateNew,
|
|
92
|
-
refresh,
|
|
93
91
|
isOpen,
|
|
94
92
|
onClose,
|
|
95
93
|
onTapAdd,
|
|
@@ -134,7 +132,7 @@ function ElementLibrary({
|
|
|
134
132
|
if (isOpen) {
|
|
135
133
|
fetchElements(0, searchRef.current, true)
|
|
136
134
|
}
|
|
137
|
-
}, [isOpen,
|
|
135
|
+
}, [isOpen, fetchElements])
|
|
138
136
|
|
|
139
137
|
// Debounced search
|
|
140
138
|
useEffect(() => {
|
|
@@ -317,7 +317,7 @@ function ElementNode({ data, selected }: Props) {
|
|
|
317
317
|
}, [data.reconnectCandidates])
|
|
318
318
|
|
|
319
319
|
const derivedPrimaryIconPath = (() => {
|
|
320
|
-
const selected = data.technology_connectors?.find((link) => link.type === 'catalog' && !!link.is_primary_icon && !!link.slug)
|
|
320
|
+
const selected = data.technology_connectors?.find((link) => link.type === 'catalog' && !!(link.is_primary_icon ?? (link as any).isPrimaryIcon) && !!link.slug)
|
|
321
321
|
if (!selected?.slug) return undefined
|
|
322
322
|
return resolveIconPath(`/icons/${selected.slug}.png`)
|
|
323
323
|
})()
|