@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/api/client.ts
CHANGED
|
@@ -9,11 +9,13 @@ import type {
|
|
|
9
9
|
LibraryElement,
|
|
10
10
|
PlacedElement,
|
|
11
11
|
Tag,
|
|
12
|
+
TechnologyConnector,
|
|
12
13
|
View,
|
|
13
14
|
ViewConnector,
|
|
14
15
|
ViewLayer,
|
|
15
16
|
ViewPlacement,
|
|
16
17
|
ViewTreeNode,
|
|
18
|
+
VisibilityOverride,
|
|
17
19
|
} from '../types'
|
|
18
20
|
import {
|
|
19
21
|
WorkspaceService,
|
|
@@ -46,19 +48,161 @@ import {
|
|
|
46
48
|
import {
|
|
47
49
|
ImportService,
|
|
48
50
|
} from '@buf/tldiagramcom_diagram.bufbuild_es/diag/v1/import_service_pb'
|
|
51
|
+
import {
|
|
52
|
+
WorkspaceVersionService,
|
|
53
|
+
type WorkspaceVersionInfo,
|
|
54
|
+
} from '@buf/tldiagramcom_diagram.bufbuild_es/diag/v1/workspace_version_service_pb'
|
|
55
|
+
import {
|
|
56
|
+
OrgService,
|
|
57
|
+
ListTagColorsResponseSchema,
|
|
58
|
+
} from '@buf/tldiagramcom_diagram.bufbuild_es/diag/v1/org_service_pb'
|
|
49
59
|
import { transport } from './transport'
|
|
50
60
|
import { apiUrl, fetchApiAsset } from '../config/runtime'
|
|
51
61
|
|
|
62
|
+
async function responseError(res: Response, fallback: string): Promise<Error> {
|
|
63
|
+
const body = await res.json().catch(() => null) as { error?: string } | null
|
|
64
|
+
return new Error(body?.error || `${fallback}: ${res.statusText}`)
|
|
65
|
+
}
|
|
66
|
+
|
|
52
67
|
export interface DependenciesResponse {
|
|
53
68
|
elements: DependencyElement[]
|
|
54
69
|
connectors: DependencyConnector[]
|
|
70
|
+
totalCount?: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface WatchRepository {
|
|
74
|
+
id: number
|
|
75
|
+
remote_url: string | null
|
|
76
|
+
repo_root: string
|
|
77
|
+
display_name: string
|
|
78
|
+
branch: string | null
|
|
79
|
+
head_commit: string | null
|
|
80
|
+
identity_status: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface WatchLock {
|
|
84
|
+
id: number
|
|
85
|
+
repository_id: number
|
|
86
|
+
pid: number
|
|
87
|
+
started_at: string
|
|
88
|
+
heartbeat_at: string
|
|
89
|
+
status: 'active' | 'paused' | 'stopping' | 'stale' | 'released' | string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface WatchStatus {
|
|
93
|
+
active: boolean
|
|
94
|
+
repository?: WatchRepository
|
|
95
|
+
lock?: WatchLock
|
|
96
|
+
connected_clients?: number
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface WatchRepresentationSummary {
|
|
100
|
+
repository_id: number
|
|
101
|
+
raw_graph_hash?: string
|
|
102
|
+
filter_settings_hash?: string
|
|
103
|
+
representation_hash?: string
|
|
104
|
+
last_status?: string
|
|
105
|
+
last_started_at?: string
|
|
106
|
+
last_finished_at?: string
|
|
107
|
+
elements_created: number
|
|
108
|
+
elements_updated: number
|
|
109
|
+
connectors_created: number
|
|
110
|
+
connectors_updated: number
|
|
111
|
+
views_created: number
|
|
112
|
+
diffs?: WatchDiff[]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface WatchContextActionResponse {
|
|
116
|
+
repository_id: number
|
|
117
|
+
action: 'show' | 'hide' | 'clean' | string
|
|
118
|
+
policies_created: number
|
|
119
|
+
policies_updated: number
|
|
120
|
+
policies_deactivated: number
|
|
121
|
+
owners_affected: number
|
|
122
|
+
tier_before: number
|
|
123
|
+
tier_after: number
|
|
124
|
+
max_tier: number
|
|
125
|
+
elements_added: number
|
|
126
|
+
connectors_added: number
|
|
127
|
+
views_added: number
|
|
128
|
+
elements_removed: number
|
|
129
|
+
connectors_removed: number
|
|
130
|
+
views_removed: number
|
|
131
|
+
representation: {
|
|
132
|
+
repository_id: number
|
|
133
|
+
representation_run_id: number
|
|
134
|
+
filter_run_id: number
|
|
135
|
+
raw_graph_hash: string
|
|
136
|
+
filter_settings_hash: string
|
|
137
|
+
representation_hash: string
|
|
138
|
+
}
|
|
139
|
+
summary: WatchRepresentationSummary
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface WatchEvent {
|
|
143
|
+
type: string
|
|
144
|
+
repository_id?: number
|
|
145
|
+
message?: string
|
|
146
|
+
at: string
|
|
147
|
+
data?: unknown
|
|
148
|
+
phase?: string
|
|
149
|
+
watcher_mode?: string
|
|
150
|
+
languages?: string[]
|
|
151
|
+
changed_files?: number
|
|
152
|
+
warnings?: string[]
|
|
55
153
|
}
|
|
56
154
|
|
|
155
|
+
export interface WatchVersion {
|
|
156
|
+
id: number
|
|
157
|
+
repository_id: number
|
|
158
|
+
commit_hash: string
|
|
159
|
+
commit_message?: string
|
|
160
|
+
parent_commit_hash?: string
|
|
161
|
+
branch?: string
|
|
162
|
+
representation_hash: string
|
|
163
|
+
workspace_version_id?: number
|
|
164
|
+
created_at: string
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface WatchDiff {
|
|
168
|
+
id: number
|
|
169
|
+
version_id: number
|
|
170
|
+
owner_type: string
|
|
171
|
+
owner_key: string
|
|
172
|
+
change_type: string
|
|
173
|
+
before_hash?: string
|
|
174
|
+
after_hash?: string
|
|
175
|
+
resource_type?: string
|
|
176
|
+
resource_id?: number
|
|
177
|
+
language?: string
|
|
178
|
+
summary?: string
|
|
179
|
+
added_lines?: number
|
|
180
|
+
removed_lines?: number
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface WorkspaceVersion {
|
|
184
|
+
id: string
|
|
185
|
+
version_id: string
|
|
186
|
+
source: string
|
|
187
|
+
parent_version_id?: string
|
|
188
|
+
view_count: number
|
|
189
|
+
element_count: number
|
|
190
|
+
connector_count: number
|
|
191
|
+
description?: string
|
|
192
|
+
workspace_hash?: string
|
|
193
|
+
created_at: string
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export type SourceEditor = 'zed' | 'vscode'
|
|
197
|
+
|
|
57
198
|
// ─── RPC clients ─────────────────────────────────────────────────────────────
|
|
58
199
|
|
|
59
200
|
const workspaceClient = createClient(WorkspaceService, transport)
|
|
60
201
|
const dependencyClient = createClient(DependencyService, transport)
|
|
61
202
|
const importClient = createClient(ImportService, transport)
|
|
203
|
+
const workspaceVersionClient = createClient(WorkspaceVersionService, transport)
|
|
204
|
+
const orgClient = createClient(OrgService, transport)
|
|
205
|
+
let dependencyConnectorsCache: Promise<DependencyConnector[]> | null = null
|
|
62
206
|
|
|
63
207
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
64
208
|
|
|
@@ -75,6 +219,49 @@ function j<T>(schema: Parameters<typeof toJson>[0], msg: Parameters<typeof toJso
|
|
|
75
219
|
return toJson(schema, msg, { useProtoFieldName: true, emitDefaultValues: true }) as unknown as T
|
|
76
220
|
}
|
|
77
221
|
|
|
222
|
+
function timestampToISOString(value: WorkspaceVersionInfo['createdAt']): string {
|
|
223
|
+
if (!value) return ''
|
|
224
|
+
const seconds = typeof value.seconds === 'bigint' ? Number(value.seconds) : Number(value.seconds ?? 0)
|
|
225
|
+
const nanos = Number(value.nanos ?? 0)
|
|
226
|
+
return new Date(seconds * 1000 + Math.floor(nanos / 1_000_000)).toISOString()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function mapWorkspaceVersion(version: WorkspaceVersionInfo): WorkspaceVersion {
|
|
230
|
+
return {
|
|
231
|
+
id: version.id,
|
|
232
|
+
version_id: version.versionId,
|
|
233
|
+
source: version.source,
|
|
234
|
+
parent_version_id: version.parentVersionId,
|
|
235
|
+
view_count: version.viewCount,
|
|
236
|
+
element_count: version.elementCount,
|
|
237
|
+
connector_count: version.connectorCount,
|
|
238
|
+
description: version.description,
|
|
239
|
+
workspace_hash: version.workspaceHash,
|
|
240
|
+
created_at: timestampToISOString(version.createdAt),
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function fetchWorkspaceRaw(body: Record<string, unknown>) {
|
|
245
|
+
const res = await fetchApiAsset(apiUrl('/diag.v1.WorkspaceService/GetWorkspace'), {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers: {
|
|
248
|
+
'Content-Type': 'application/json',
|
|
249
|
+
'Connect-Protocol-Version': '1',
|
|
250
|
+
},
|
|
251
|
+
body: JSON.stringify(body),
|
|
252
|
+
})
|
|
253
|
+
if (!res.ok) {
|
|
254
|
+
throw new Error(`GetWorkspace failed: ${res.statusText}`)
|
|
255
|
+
}
|
|
256
|
+
return res.json() as Promise<{
|
|
257
|
+
views?: ProtoDiagram[]
|
|
258
|
+
total_count?: number
|
|
259
|
+
totalCount?: number
|
|
260
|
+
content?: Record<string, { placements?: Record<string, unknown>[]; connectors?: Record<string, unknown>[] }>
|
|
261
|
+
navigations?: Record<string, unknown>[]
|
|
262
|
+
}>
|
|
263
|
+
}
|
|
264
|
+
|
|
78
265
|
// ─── Proto → frontend type mappers ───────────────────────────────────────────
|
|
79
266
|
|
|
80
267
|
interface ProtoDiagram {
|
|
@@ -139,10 +326,10 @@ function protoElementToLibrary(e: Record<string, unknown>): LibraryElement {
|
|
|
139
326
|
technology: (e.technology ?? null) as string | null,
|
|
140
327
|
url: (e.url ?? null) as string | null,
|
|
141
328
|
logo_url: (e.logo_url ?? e.logoUrl ?? null) as string | null,
|
|
142
|
-
technology_connectors: ((e.technology_connectors ?? e.technologyLinks ?? []) as
|
|
143
|
-
type: tl.type,
|
|
329
|
+
technology_connectors: ((e.technology_connectors ?? e.technologyLinks ?? []) as Array<{ type?: string; slug?: string; label?: string; is_primary_icon?: boolean; isPrimaryIcon?: boolean }>).map(tl => ({
|
|
330
|
+
type: (tl.type ?? 'custom') as TechnologyConnector['type'],
|
|
144
331
|
slug: tl.slug,
|
|
145
|
-
label: tl.label,
|
|
332
|
+
label: tl.label ?? '',
|
|
146
333
|
is_primary_icon: !!(tl.is_primary_icon ?? tl.isPrimaryIcon),
|
|
147
334
|
})),
|
|
148
335
|
tags: (e.tags ?? []) as string[],
|
|
@@ -157,6 +344,26 @@ function protoElementToLibrary(e: Record<string, unknown>): LibraryElement {
|
|
|
157
344
|
}
|
|
158
345
|
}
|
|
159
346
|
|
|
347
|
+
function libraryElementToDependency(element: LibraryElement): DependencyElement {
|
|
348
|
+
return {
|
|
349
|
+
id: String(element.id),
|
|
350
|
+
name: element.name,
|
|
351
|
+
type: element.kind,
|
|
352
|
+
description: element.description,
|
|
353
|
+
technology: element.technology,
|
|
354
|
+
url: element.url,
|
|
355
|
+
logo_url: element.logo_url,
|
|
356
|
+
technology_connectors: element.technology_connectors,
|
|
357
|
+
tags: element.tags,
|
|
358
|
+
repo: element.repo,
|
|
359
|
+
branch: element.branch,
|
|
360
|
+
language: element.language,
|
|
361
|
+
file_path: element.file_path,
|
|
362
|
+
created_at: element.created_at,
|
|
363
|
+
updated_at: element.updated_at,
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
160
367
|
function protoPlacedElement(p: Record<string, unknown>): PlacedElement {
|
|
161
368
|
return {
|
|
162
369
|
id: Number(p.id ?? 0),
|
|
@@ -170,10 +377,10 @@ function protoPlacedElement(p: Record<string, unknown>): PlacedElement {
|
|
|
170
377
|
technology: (p.technology ?? null) as string | null,
|
|
171
378
|
url: (p.url ?? null) as string | null,
|
|
172
379
|
logo_url: (p.logo_url ?? p.logoUrl ?? null) as string | null,
|
|
173
|
-
technology_connectors: ((p.technology_connect_ors ?? p.technology_connectors ?? p.technologyLinks ?? []) as
|
|
174
|
-
type: tl.type,
|
|
380
|
+
technology_connectors: ((p.technology_connect_ors ?? p.technology_connectors ?? p.technologyLinks ?? []) as Array<{ type?: string; slug?: string; label?: string; is_primary_icon?: boolean; isPrimaryIcon?: boolean }>).map(tl => ({
|
|
381
|
+
type: (tl.type ?? 'custom') as TechnologyConnector['type'],
|
|
175
382
|
slug: tl.slug,
|
|
176
|
-
label: tl.label,
|
|
383
|
+
label: tl.label ?? '',
|
|
177
384
|
is_primary_icon: !!(tl.is_primary_icon ?? tl.isPrimaryIcon),
|
|
178
385
|
})),
|
|
179
386
|
tags: (p.tags ?? []) as string[],
|
|
@@ -205,6 +412,25 @@ function protoConnector(e: Record<string, unknown>): Connector {
|
|
|
205
412
|
}
|
|
206
413
|
}
|
|
207
414
|
|
|
415
|
+
function protoDependencyConnector(e: Record<string, unknown>): DependencyConnector {
|
|
416
|
+
return {
|
|
417
|
+
id: String(e.id ?? 0),
|
|
418
|
+
view_id: String(e.view_id ?? e.viewId ?? 0),
|
|
419
|
+
source_element_id: String(e.source_element_id ?? e.sourceElementId ?? 0),
|
|
420
|
+
target_element_id: String(e.target_element_id ?? e.targetElementId ?? 0),
|
|
421
|
+
label: (e.label ?? null) as string | null,
|
|
422
|
+
description: (e.description ?? null) as string | null,
|
|
423
|
+
relationship_type: (e.relationship_type ?? e.relationshipType ?? e.relationship ?? null) as string | null,
|
|
424
|
+
direction: String(e.direction ?? 'forward'),
|
|
425
|
+
connector_type: String(e.connector_type ?? e.connectorType ?? e.style ?? 'solid'),
|
|
426
|
+
url: (e.url ?? null) as string | null,
|
|
427
|
+
source_handle: (e.source_handle ?? e.sourceHandle ?? null) as string | null,
|
|
428
|
+
target_handle: (e.target_handle ?? e.targetHandle ?? null) as string | null,
|
|
429
|
+
created_at: String(e.created_at ?? e.createdAt ?? ''),
|
|
430
|
+
updated_at: String(e.updated_at ?? e.updatedAt ?? ''),
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
208
434
|
function protoNavigation(n: Record<string, unknown>): ViewConnector {
|
|
209
435
|
return {
|
|
210
436
|
id: Number(n.id ?? 0),
|
|
@@ -328,6 +554,27 @@ export const api = {
|
|
|
328
554
|
delete: (_orgId: string, id: number): Promise<void> =>
|
|
329
555
|
rpc(async () => { await workspaceClient.deleteElement({ orgId: '', elementId: id }) }),
|
|
330
556
|
|
|
557
|
+
merge: (sourceId: number, survivorId: number, resolved: Partial<{
|
|
558
|
+
kind: string | null
|
|
559
|
+
description: string | null
|
|
560
|
+
repo: string | null
|
|
561
|
+
branch: string | null
|
|
562
|
+
file_path: string | null
|
|
563
|
+
language: string | null
|
|
564
|
+
}>): Promise<{ survivor: LibraryElement; deleted_id: number }> =>
|
|
565
|
+
rpc(async () => {
|
|
566
|
+
const res = await fetch(apiUrl('/elements/merge'), {
|
|
567
|
+
method: 'POST',
|
|
568
|
+
headers: { 'Content-Type': 'application/json' },
|
|
569
|
+
body: JSON.stringify({ source_id: sourceId, survivor_id: survivorId, resolved }),
|
|
570
|
+
})
|
|
571
|
+
if (!res.ok) {
|
|
572
|
+
throw await responseError(res, 'Merge failed')
|
|
573
|
+
}
|
|
574
|
+
const json = await res.json() as { survivor: Record<string, unknown>; deleted_id: number }
|
|
575
|
+
return { survivor: protoElementToLibrary(json.survivor), deleted_id: json.deleted_id }
|
|
576
|
+
}),
|
|
577
|
+
|
|
331
578
|
placements: (id: number): Promise<ViewPlacement[]> =>
|
|
332
579
|
rpc(async () => {
|
|
333
580
|
const res = await workspaceClient.listElementPlacements({ elementId: id })
|
|
@@ -339,7 +586,20 @@ export const api = {
|
|
|
339
586
|
workspace: {
|
|
340
587
|
orgs: {
|
|
341
588
|
tagColors: {
|
|
342
|
-
list: (): Promise<Tag
|
|
589
|
+
list: (): Promise<Record<string, Tag>> =>
|
|
590
|
+
rpc(async () => {
|
|
591
|
+
const res = await orgClient.listTagColors({})
|
|
592
|
+
const json = j<{ tags?: Record<string, { color?: string; description?: string | null }> }>(ListTagColorsResponseSchema, res)
|
|
593
|
+
const tags: Record<string, Tag> = {}
|
|
594
|
+
Object.entries(json.tags ?? {}).forEach(([name, tag]) => {
|
|
595
|
+
tags[name] = { name, color: tag.color ?? '#A0AEC0', description: tag.description ?? null }
|
|
596
|
+
})
|
|
597
|
+
return tags
|
|
598
|
+
}),
|
|
599
|
+
update: (name: string, color: string, description?: string | null): Promise<void> =>
|
|
600
|
+
rpc(async () => {
|
|
601
|
+
await orgClient.updateTag({ tag: name, color, description: description ?? undefined })
|
|
602
|
+
}),
|
|
343
603
|
},
|
|
344
604
|
},
|
|
345
605
|
|
|
@@ -384,18 +644,15 @@ export const api = {
|
|
|
384
644
|
}),
|
|
385
645
|
|
|
386
646
|
content: (id: number): Promise<{ placements: PlacedElement[]; connectors: Connector[] }> =>
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
])
|
|
392
|
-
const placementJson = j<{ placements: Record<string, unknown>[] }>(ListPlacementsResponseSchema, placementsRes)
|
|
393
|
-
const connectorJson = j<{ connectors: Record<string, unknown>[] }>(ListConnectorsResponseSchema, connectorsRes)
|
|
647
|
+
(async () => {
|
|
648
|
+
const res = await fetch(apiUrl(`/views/${id}/projected-content`))
|
|
649
|
+
if (!res.ok) throw new Error('Failed to load view content')
|
|
650
|
+
const json = await res.json() as { placements?: Record<string, unknown>[]; connectors?: Record<string, unknown>[] }
|
|
394
651
|
return {
|
|
395
|
-
placements: (
|
|
396
|
-
connectors: (
|
|
652
|
+
placements: (json.placements ?? []).map(protoPlacedElement),
|
|
653
|
+
connectors: (json.connectors ?? []).map(protoConnector),
|
|
397
654
|
}
|
|
398
|
-
}),
|
|
655
|
+
})(),
|
|
399
656
|
|
|
400
657
|
tree: (): Promise<ViewTreeNode[]> =>
|
|
401
658
|
rpc(async () => {
|
|
@@ -434,6 +691,60 @@ export const api = {
|
|
|
434
691
|
return (json.views ?? []).map(mapDiagram)
|
|
435
692
|
}),
|
|
436
693
|
|
|
694
|
+
treeAround: async (
|
|
695
|
+
viewId: number,
|
|
696
|
+
opts: { ancestorLevels?: number; descendantLevels?: number } = {},
|
|
697
|
+
): Promise<ViewTreeNode[]> => {
|
|
698
|
+
const ancestorLevels = opts.ancestorLevels ?? 2
|
|
699
|
+
const descendantLevels = opts.descendantLevels ?? 2
|
|
700
|
+
const current = await api.workspace.views.get(viewId)
|
|
701
|
+
|
|
702
|
+
const ancestors: ViewTreeNode[] = []
|
|
703
|
+
let cursor: ViewTreeNode = current
|
|
704
|
+
for (let depth = 0; depth < ancestorLevels && cursor.parent_view_id != null; depth += 1) {
|
|
705
|
+
const parent = await api.workspace.views.get(cursor.parent_view_id)
|
|
706
|
+
ancestors.unshift(parent)
|
|
707
|
+
cursor = parent
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const withDescendants = async (node: ViewTreeNode, remainingDepth: number): Promise<ViewTreeNode> => {
|
|
711
|
+
const scoped: ViewTreeNode = { ...node, children: [] }
|
|
712
|
+
if (remainingDepth <= 0) return scoped
|
|
713
|
+
const children = await api.workspace.views.treeChildren(node.id)
|
|
714
|
+
scoped.children = await Promise.all(children.map((child) => withDescendants(child, remainingDepth - 1)))
|
|
715
|
+
return scoped
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
let scoped = await withDescendants(current, descendantLevels)
|
|
719
|
+
for (let index = ancestors.length - 1; index >= 0; index -= 1) {
|
|
720
|
+
scoped = { ...ancestors[index], children: [scoped] }
|
|
721
|
+
}
|
|
722
|
+
return [scoped]
|
|
723
|
+
},
|
|
724
|
+
|
|
725
|
+
gridData: (): Promise<{
|
|
726
|
+
views: ViewTreeNode[]
|
|
727
|
+
content: Record<number, { placements: PlacedElement[]; connectors: Connector[] }>
|
|
728
|
+
}> =>
|
|
729
|
+
rpc(async () => {
|
|
730
|
+
const json = await fetchWorkspaceRaw({
|
|
731
|
+
includeContent: true,
|
|
732
|
+
hasView: true,
|
|
733
|
+
})
|
|
734
|
+
return {
|
|
735
|
+
views: (json.views ?? []).map(mapDiagram),
|
|
736
|
+
content: Object.fromEntries(
|
|
737
|
+
Object.entries(json.content ?? {}).map(([key, value]) => [
|
|
738
|
+
Number(key),
|
|
739
|
+
{
|
|
740
|
+
placements: (value.placements ?? []).map(protoPlacedElement),
|
|
741
|
+
connectors: (value.connectors ?? []).map(protoConnector),
|
|
742
|
+
},
|
|
743
|
+
])
|
|
744
|
+
),
|
|
745
|
+
}
|
|
746
|
+
}),
|
|
747
|
+
|
|
437
748
|
get: (id: number): Promise<ViewTreeNode> =>
|
|
438
749
|
rpc(async () => {
|
|
439
750
|
const res = await workspaceClient.getView({ viewId: id })
|
|
@@ -470,6 +781,60 @@ export const api = {
|
|
|
470
781
|
setLevel: (id: number, level: number): Promise<void> =>
|
|
471
782
|
rpc(async () => { await workspaceClient.setViewLevel({ viewId: id, level }) }),
|
|
472
783
|
|
|
784
|
+
density: {
|
|
785
|
+
get: async (id: number): Promise<number> => {
|
|
786
|
+
const res = await fetch(apiUrl(`/views/${id}/density`))
|
|
787
|
+
if (!res.ok) throw new Error('Failed to load density')
|
|
788
|
+
const json = await res.json() as { density_level?: number }
|
|
789
|
+
return Number(json.density_level ?? 0)
|
|
790
|
+
},
|
|
791
|
+
set: async (id: number, densityLevel: number): Promise<number> => {
|
|
792
|
+
const res = await fetch(apiUrl(`/views/${id}/density`), {
|
|
793
|
+
method: 'PUT',
|
|
794
|
+
headers: { 'Content-Type': 'application/json' },
|
|
795
|
+
body: JSON.stringify({ density_level: densityLevel }),
|
|
796
|
+
})
|
|
797
|
+
if (!res.ok) throw new Error('Failed to save density')
|
|
798
|
+
const json = await res.json() as { density_level?: number }
|
|
799
|
+
return Number(json.density_level ?? densityLevel)
|
|
800
|
+
},
|
|
801
|
+
},
|
|
802
|
+
|
|
803
|
+
visibilityOverrides: {
|
|
804
|
+
list: async (id: number): Promise<VisibilityOverride[]> => {
|
|
805
|
+
const res = await fetch(apiUrl(`/views/${id}/visibility-overrides`))
|
|
806
|
+
if (!res.ok) throw new Error('Failed to load visibility overrides')
|
|
807
|
+
const json = await res.json() as { overrides?: VisibilityOverride[] }
|
|
808
|
+
return json.overrides ?? []
|
|
809
|
+
},
|
|
810
|
+
set: async (id: number, resourceType: VisibilityOverride['resource_type'], resourceId: number, levelDelta: number): Promise<VisibilityOverride> => {
|
|
811
|
+
const res = await fetch(apiUrl(`/views/${id}/visibility-overrides`), {
|
|
812
|
+
method: 'PUT',
|
|
813
|
+
headers: { 'Content-Type': 'application/json' },
|
|
814
|
+
body: JSON.stringify({ resource_type: resourceType, resource_id: resourceId, level_delta: levelDelta }),
|
|
815
|
+
})
|
|
816
|
+
if (!res.ok) throw new Error('Failed to save visibility override')
|
|
817
|
+
const json = await res.json() as { override?: VisibilityOverride }
|
|
818
|
+
return json.override ?? { view_id: id, resource_type: resourceType, resource_id: resourceId, level_delta: levelDelta }
|
|
819
|
+
},
|
|
820
|
+
promote: async (id: number, resourceType: VisibilityOverride['resource_type'], resourceId: number): Promise<VisibilityOverride> => {
|
|
821
|
+
const res = await fetch(apiUrl(`/views/${id}/visibility-overrides/${resourceType}/${resourceId}/promote`), { method: 'POST' })
|
|
822
|
+
if (!res.ok) throw new Error('Failed to promote visibility')
|
|
823
|
+
const json = await res.json() as { override?: VisibilityOverride }
|
|
824
|
+
return json.override ?? { view_id: id, resource_type: resourceType, resource_id: resourceId, level_delta: 1 }
|
|
825
|
+
},
|
|
826
|
+
demote: async (id: number, resourceType: VisibilityOverride['resource_type'], resourceId: number): Promise<VisibilityOverride> => {
|
|
827
|
+
const res = await fetch(apiUrl(`/views/${id}/visibility-overrides/${resourceType}/${resourceId}/demote`), { method: 'POST' })
|
|
828
|
+
if (!res.ok) throw new Error('Failed to demote visibility')
|
|
829
|
+
const json = await res.json() as { override?: VisibilityOverride }
|
|
830
|
+
return json.override ?? { view_id: id, resource_type: resourceType, resource_id: resourceId, level_delta: -1 }
|
|
831
|
+
},
|
|
832
|
+
reset: async (id: number, resourceType: VisibilityOverride['resource_type'], resourceId: number): Promise<void> => {
|
|
833
|
+
const res = await fetch(apiUrl(`/views/${id}/visibility-overrides/${resourceType}/${resourceId}`), { method: 'DELETE' })
|
|
834
|
+
if (!res.ok) throw new Error('Failed to reset visibility override')
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
|
|
473
838
|
delete: (_orgId: string, id: number): Promise<void> =>
|
|
474
839
|
rpc(async () => { await workspaceClient.deleteView({ orgId: '', viewId: id }) }),
|
|
475
840
|
|
|
@@ -620,8 +985,36 @@ export const api = {
|
|
|
620
985
|
},
|
|
621
986
|
|
|
622
987
|
dependencies: {
|
|
623
|
-
list: (): Promise<DependenciesResponse> =>
|
|
988
|
+
list: (params?: { limit?: number; offset?: number; search?: string }): Promise<DependenciesResponse> =>
|
|
624
989
|
rpc(async () => {
|
|
990
|
+
if (params) {
|
|
991
|
+
if (!dependencyConnectorsCache) {
|
|
992
|
+
dependencyConnectorsCache = workspaceClient.listConnectors({ viewId: 0 })
|
|
993
|
+
.then((res) => {
|
|
994
|
+
const connectorJson = j<{ connectors: Record<string, unknown>[] }>(ListConnectorsResponseSchema, res)
|
|
995
|
+
return (connectorJson.connectors ?? []).map(protoDependencyConnector)
|
|
996
|
+
})
|
|
997
|
+
}
|
|
998
|
+
const [elements, connectors] = await Promise.all([
|
|
999
|
+
workspaceClient.listElements({
|
|
1000
|
+
limit: params.limit ?? 0,
|
|
1001
|
+
offset: params.offset ?? 0,
|
|
1002
|
+
search: params.search ?? '',
|
|
1003
|
+
}).then((res) => {
|
|
1004
|
+
const json = j<{ elements: Record<string, unknown>[] }>(ListElementsResponseSchema, res)
|
|
1005
|
+
return {
|
|
1006
|
+
elements: (json.elements ?? []).map(protoElementToLibrary),
|
|
1007
|
+
totalCount: res.pagination ? Number(res.pagination.totalCount) : undefined,
|
|
1008
|
+
}
|
|
1009
|
+
}),
|
|
1010
|
+
dependencyConnectorsCache,
|
|
1011
|
+
])
|
|
1012
|
+
return {
|
|
1013
|
+
elements: elements.elements.map(libraryElementToDependency),
|
|
1014
|
+
connectors,
|
|
1015
|
+
totalCount: elements.totalCount,
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
625
1018
|
const res = await dependencyClient.listDependencies({})
|
|
626
1019
|
return j<DependenciesResponse>(ListDependenciesResponseSchema, res)
|
|
627
1020
|
}),
|
|
@@ -665,8 +1058,8 @@ export const api = {
|
|
|
665
1058
|
throw new Error(`Failed to load shared diagram: ${res.statusText}`)
|
|
666
1059
|
}
|
|
667
1060
|
const data = await res.json() as {
|
|
668
|
-
tree:
|
|
669
|
-
views: Record<string, { elements:
|
|
1061
|
+
tree: ProtoDiagram[]
|
|
1062
|
+
views: Record<string, { elements: Record<string, unknown>[]; connectors: Record<string, unknown>[] }>
|
|
670
1063
|
password_required?: boolean
|
|
671
1064
|
}
|
|
672
1065
|
|
|
@@ -683,7 +1076,7 @@ export const api = {
|
|
|
683
1076
|
|
|
684
1077
|
// Ensure that the share root is treated as a root (no parent) so that computeLayout
|
|
685
1078
|
// picks it up even if it was nested in the original workspace.
|
|
686
|
-
const
|
|
1079
|
+
const _sharedRoot = tree.find(n => String(n.id) === String(data.views[token]?.elements?.[0]?.view_id ?? ''))
|
|
687
1080
|
// Backend actually returns the shareToken.ViewID as the root of the tree it builds.
|
|
688
1081
|
// We should find the node in 'tree' that has no parent *within the returned set*.
|
|
689
1082
|
// For shared explore, the backend typically returns a tree starting at the shared view.
|
|
@@ -695,9 +1088,9 @@ export const api = {
|
|
|
695
1088
|
}
|
|
696
1089
|
})
|
|
697
1090
|
const navigations: ViewConnector[] = []
|
|
698
|
-
const elementToChildView = new Map<number,
|
|
699
|
-
const allViews:
|
|
700
|
-
const flatTree = (nodes:
|
|
1091
|
+
const elementToChildView = new Map<number, ViewTreeNode>()
|
|
1092
|
+
const allViews: ViewTreeNode[] = []
|
|
1093
|
+
const flatTree = (nodes: ViewTreeNode[]) => {
|
|
701
1094
|
nodes.forEach(n => {
|
|
702
1095
|
allViews.push(n)
|
|
703
1096
|
if (n.owner_element_id) elementToChildView.set(n.owner_element_id, n)
|
|
@@ -706,8 +1099,8 @@ export const api = {
|
|
|
706
1099
|
}
|
|
707
1100
|
flatTree(tree)
|
|
708
1101
|
|
|
709
|
-
Object.values(views).forEach((v
|
|
710
|
-
v.placements.forEach((p
|
|
1102
|
+
Object.values(views).forEach((v) => {
|
|
1103
|
+
v.placements.forEach((p) => {
|
|
711
1104
|
const childView = elementToChildView.get(p.element_id)
|
|
712
1105
|
if (childView) {
|
|
713
1106
|
navigations.push({
|
|
@@ -751,4 +1144,73 @@ export const api = {
|
|
|
751
1144
|
}
|
|
752
1145
|
}),
|
|
753
1146
|
},
|
|
1147
|
+
|
|
1148
|
+
versions: {
|
|
1149
|
+
list: (limit = 50): Promise<WorkspaceVersion[]> =>
|
|
1150
|
+
rpc(async () => {
|
|
1151
|
+
const res = await workspaceVersionClient.listVersions({ limit })
|
|
1152
|
+
return (res.versions ?? []).map(mapWorkspaceVersion)
|
|
1153
|
+
}),
|
|
1154
|
+
},
|
|
1155
|
+
|
|
1156
|
+
watch: {
|
|
1157
|
+
status: async (): Promise<WatchStatus> => {
|
|
1158
|
+
const res = await fetch(apiUrl('/watch/status'))
|
|
1159
|
+
if (!res.ok) throw new Error(`Failed to load watch status: ${res.statusText}`)
|
|
1160
|
+
return res.json()
|
|
1161
|
+
},
|
|
1162
|
+
websocketUrl: (): string => {
|
|
1163
|
+
const url = new URL(apiUrl('/watch/ws'), window.location.href)
|
|
1164
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
1165
|
+
return url.toString()
|
|
1166
|
+
},
|
|
1167
|
+
repositories: async (): Promise<WatchRepository[]> => {
|
|
1168
|
+
const res = await fetch(apiUrl('/watch/repositories'))
|
|
1169
|
+
if (!res.ok) throw new Error(`Failed to load watch repositories: ${res.statusText}`)
|
|
1170
|
+
return res.json()
|
|
1171
|
+
},
|
|
1172
|
+
versions: async (repositoryId: number): Promise<WatchVersion[]> => {
|
|
1173
|
+
const res = await fetch(apiUrl(`/watch/repositories/${repositoryId}/versions`))
|
|
1174
|
+
if (!res.ok) throw new Error(`Failed to load watch versions: ${res.statusText}`)
|
|
1175
|
+
return res.json()
|
|
1176
|
+
},
|
|
1177
|
+
diffs: async (versionId: number, filters?: { owner_type?: string; change_type?: string; resource_type?: string; language?: string }): Promise<WatchDiff[]> => {
|
|
1178
|
+
const params = new URLSearchParams()
|
|
1179
|
+
if (filters?.owner_type) params.set('owner_type', filters.owner_type)
|
|
1180
|
+
if (filters?.change_type) params.set('change_type', filters.change_type)
|
|
1181
|
+
if (filters?.resource_type) params.set('resource_type', filters.resource_type)
|
|
1182
|
+
if (filters?.language) params.set('language', filters.language)
|
|
1183
|
+
const suffix = params.toString() ? `?${params}` : ''
|
|
1184
|
+
const res = await fetch(apiUrl(`/watch/versions/${versionId}/diffs${suffix}`))
|
|
1185
|
+
if (!res.ok) throw new Error(`Failed to load watch diffs: ${res.statusText}`)
|
|
1186
|
+
return res.json()
|
|
1187
|
+
},
|
|
1188
|
+
cleanContext: async (repositoryId: number, input: { resource_type: 'element' | 'view'; resource_id: number }): Promise<WatchContextActionResponse> => {
|
|
1189
|
+
const res = await fetch(apiUrl(`/watch/repositories/${repositoryId}/context/clean`), {
|
|
1190
|
+
method: 'POST',
|
|
1191
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1192
|
+
body: JSON.stringify(input),
|
|
1193
|
+
})
|
|
1194
|
+
if (!res.ok) throw await responseError(res, 'Failed to clean watch context')
|
|
1195
|
+
return res.json()
|
|
1196
|
+
},
|
|
1197
|
+
},
|
|
1198
|
+
|
|
1199
|
+
editor: {
|
|
1200
|
+
open: async (input: { editor: SourceEditor; repo?: string | null; file_path: string; line?: number | null }): Promise<void> => {
|
|
1201
|
+
const res = await fetch(apiUrl('/editor/open'), {
|
|
1202
|
+
method: 'POST',
|
|
1203
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1204
|
+
body: JSON.stringify({
|
|
1205
|
+
editor: input.editor,
|
|
1206
|
+
repo: input.repo ?? '',
|
|
1207
|
+
file_path: input.file_path,
|
|
1208
|
+
line: input.line ?? 0,
|
|
1209
|
+
}),
|
|
1210
|
+
})
|
|
1211
|
+
if (!res.ok) {
|
|
1212
|
+
throw await responseError(res, 'Failed to open editor')
|
|
1213
|
+
}
|
|
1214
|
+
},
|
|
1215
|
+
},
|
|
754
1216
|
}
|