@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.
@@ -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 function SharedInfiniteZoom(props: Props): import("react/jsx-runtime").JSX.Element;
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[]>>;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tldiagram/core-ui",
3
- "version": "1.94.1",
3
+ "version": "1.94.3",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
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 ?? null,
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 LibraryElement['technology_connectors'],
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 PlacedElement['technology_connectors'],
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, refresh, fetchElements])
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
  })()