@tldiagram/core-ui 2.0.7 → 2.0.9
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/transport-vscode.d.ts +0 -1
- package/dist/index.js +3561 -3537
- package/dist/lib/vscodeBridge.d.ts +9 -2
- package/package.json +3 -2
- package/src/App.tsx +13 -8
- package/src/api/client.ts +8 -7
- package/src/api/transport-vscode.ts +3 -11
- package/src/components/ElementLibrary.tsx +1 -0
- package/src/components/ElementNode.tsx +15 -3
- package/src/config/runtime-vscode.ts +2 -4
- package/src/lib/vscodeBridge.ts +22 -3
- package/src/types/offline-ambient.d.ts +1 -0
- package/src/utils/technologyCatalog.ts +5 -0
- package/src/utils/url.ts +7 -0
- package/src/vscode-entry.tsx +56 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { ExtensionToWebviewMessage, WebviewToExtensionMessage } from '../types/vscode-messages';
|
|
2
|
+
declare global {
|
|
3
|
+
interface Window {
|
|
4
|
+
__TLD_VSCODE_API__?: {
|
|
5
|
+
postMessage: (msg: unknown) => void;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
}
|
|
2
9
|
export declare const vscodeBridge: {
|
|
3
|
-
postMessage: (
|
|
4
|
-
onMessage: (
|
|
10
|
+
postMessage: (msg: WebviewToExtensionMessage) => void;
|
|
11
|
+
onMessage: (handler: (msg: ExtensionToWebviewMessage) => void) => (() => void);
|
|
5
12
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldiagram/core-ui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,13 +30,14 @@
|
|
|
30
30
|
"build:lib": "vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json",
|
|
31
31
|
"prebuild:app": "npm run prepare:icons",
|
|
32
32
|
"build:app": "tsc && vite build --config vite.config.ts",
|
|
33
|
+
"build:vscode": "vite build --config vite.vscode.config.ts",
|
|
33
34
|
"lint": "eslint .",
|
|
34
35
|
"lint:fix": "eslint . --fix",
|
|
35
36
|
"preview": "vite preview",
|
|
36
37
|
"test": "vitest run"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@buf/tldiagramcom_diagram.bufbuild_es": "^2.12.0-
|
|
40
|
+
"@buf/tldiagramcom_diagram.bufbuild_es": "^2.12.0-20260516210401-05aeb295f766.1",
|
|
40
41
|
"@bufbuild/protobuf": "^2.11.0",
|
|
41
42
|
"esbuild": "^0.25.12",
|
|
42
43
|
"zustand": "^5.0.12"
|
package/src/App.tsx
CHANGED
|
@@ -20,17 +20,22 @@ function AppLayout() {
|
|
|
20
20
|
const header = useHeader()
|
|
21
21
|
const node = header && typeof header === 'object' && 'node' in header ? (header as { node: React.ReactNode }).node : header
|
|
22
22
|
const hideMobileBar = header && typeof header === 'object' && 'hideMobileBar' in header ? !!(header as { hideMobileBar?: boolean }).hideMobileBar : false
|
|
23
|
+
const hideTopBar = typeof window !== 'undefined' && !!window.__TLD_VSCODE__
|
|
23
24
|
|
|
24
25
|
return (
|
|
25
26
|
<Box h="100dvh" display="flex" flexDirection="column" bg="var(--bg-canvas)" overflow="hidden">
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
{!hideTopBar && (
|
|
28
|
+
<>
|
|
29
|
+
<TopMenuBar hideMobileBar={hideMobileBar} rightSlot={<WorkspacePanel />}>
|
|
30
|
+
{node}
|
|
31
|
+
</TopMenuBar>
|
|
32
|
+
<Box
|
|
33
|
+
h={{ base: 'var(--topbar-h-mobile-total)', sm: 'var(--topbar-h-total)' }}
|
|
34
|
+
mb={{ base: 'var(--topbar-content-gap)', sm: '0px' }}
|
|
35
|
+
flexShrink={0}
|
|
36
|
+
/>
|
|
37
|
+
</>
|
|
38
|
+
)}
|
|
34
39
|
<Box flex="1" minH={0} overflow="hidden" position="relative">
|
|
35
40
|
<Outlet />
|
|
36
41
|
</Box>
|
package/src/api/client.ts
CHANGED
|
@@ -650,15 +650,16 @@ export const api = {
|
|
|
650
650
|
}),
|
|
651
651
|
|
|
652
652
|
content: (id: number): Promise<{ placements: PlacedElement[]; connectors: Connector[] }> =>
|
|
653
|
-
(async () => {
|
|
654
|
-
const res = await
|
|
655
|
-
|
|
656
|
-
|
|
653
|
+
rpc(async () => {
|
|
654
|
+
const res = await workspaceClient.getView({ viewId: id, includeContent: true })
|
|
655
|
+
const json = j<{
|
|
656
|
+
content?: { placements?: Record<string, unknown>[]; connectors?: Record<string, unknown>[] }
|
|
657
|
+
}>(GetViewResponseSchema, res)
|
|
657
658
|
return {
|
|
658
|
-
placements: (json.placements ?? []).map(protoPlacedElement),
|
|
659
|
-
connectors: (json.connectors ?? []).map(protoConnector),
|
|
659
|
+
placements: (json.content?.placements ?? []).map(protoPlacedElement),
|
|
660
|
+
connectors: (json.content?.connectors ?? []).map(protoConnector),
|
|
660
661
|
}
|
|
661
|
-
})
|
|
662
|
+
}),
|
|
662
663
|
|
|
663
664
|
tree: (): Promise<ViewTreeNode[]> =>
|
|
664
665
|
rpc(async () => {
|
|
@@ -1,28 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* VS Code webview transport.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* The API key and server URL are injected by the extension host
|
|
6
|
-
* via window globals before this script runs.
|
|
4
|
+
* Uses the local tld CLI HTTP server URL injected by the extension host.
|
|
7
5
|
*/
|
|
8
6
|
import { createConnectTransport } from '@connectrpc/connect-web'
|
|
9
7
|
|
|
10
8
|
declare global {
|
|
11
9
|
interface Window {
|
|
12
|
-
__TLD_API_KEY__?: string
|
|
13
10
|
__TLD_SERVER_URL__?: string
|
|
14
11
|
__TLD_DIAGRAM_ID__?: number
|
|
15
12
|
}
|
|
16
13
|
}
|
|
17
14
|
|
|
18
|
-
const serverUrl = (window.__TLD_SERVER_URL__ ?? '
|
|
19
|
-
const apiKey = window.__TLD_API_KEY__ ?? ''
|
|
15
|
+
const serverUrl = (window.__TLD_SERVER_URL__ ?? 'http://127.0.0.1:8060').replace(/\/$/, '')
|
|
20
16
|
|
|
21
17
|
export const transport = createConnectTransport({
|
|
22
18
|
baseUrl: `${serverUrl}/api`,
|
|
23
|
-
fetch: (input, init) =>
|
|
24
|
-
const headers = new Headers(init?.headers)
|
|
25
|
-
if (apiKey) headers.set('Authorization', `Bearer ${apiKey}`)
|
|
26
|
-
return fetch(input, { ...init, headers })
|
|
27
|
-
},
|
|
19
|
+
fetch: (input, init) => fetch(input, init),
|
|
28
20
|
})
|
|
@@ -18,7 +18,17 @@ import {
|
|
|
18
18
|
HANDLE_SLOT_COUNT,
|
|
19
19
|
} from '../utils/edgeDistribution'
|
|
20
20
|
|
|
21
|
-
function VscodeCodePreview({
|
|
21
|
+
function VscodeCodePreview({
|
|
22
|
+
filePath,
|
|
23
|
+
fallbackSymbolName,
|
|
24
|
+
fallbackSymbolKind,
|
|
25
|
+
isCanvasMoving,
|
|
26
|
+
}: {
|
|
27
|
+
filePath: string
|
|
28
|
+
fallbackSymbolName?: string
|
|
29
|
+
fallbackSymbolKind?: string | null
|
|
30
|
+
isCanvasMoving?: boolean
|
|
31
|
+
}) {
|
|
22
32
|
const [content, setContent] = useState<string | null>(null)
|
|
23
33
|
const [startLineOffset, setStartLineOffset] = useState(0)
|
|
24
34
|
const [isOpen, setIsOpen] = useState(false)
|
|
@@ -33,8 +43,8 @@ function VscodeCodePreview({ filePath, isCanvasMoving }: { filePath: string; isC
|
|
|
33
43
|
}
|
|
34
44
|
}, [anchorStr])
|
|
35
45
|
const startLine = typeof anchor?.startLine === 'number' ? anchor.startLine : undefined
|
|
36
|
-
const symbolName = typeof anchor?.name === 'string' ? anchor.name :
|
|
37
|
-
const symbolKind = typeof anchor?.type === 'string' ? anchor.type : undefined
|
|
46
|
+
const symbolName = typeof anchor?.name === 'string' ? anchor.name : fallbackSymbolName
|
|
47
|
+
const symbolKind = typeof anchor?.type === 'string' ? anchor.type : fallbackSymbolKind ?? undefined
|
|
38
48
|
|
|
39
49
|
useEffect(() => {
|
|
40
50
|
if (!isOpen) return
|
|
@@ -806,6 +816,8 @@ function ElementNode({ data, selected }: Props) {
|
|
|
806
816
|
)}
|
|
807
817
|
<VscodeCodePreview
|
|
808
818
|
filePath={data.file_path}
|
|
819
|
+
fallbackSymbolName={data.name}
|
|
820
|
+
fallbackSymbolKind={data.kind}
|
|
809
821
|
isCanvasMoving={data.isCanvasMoving}
|
|
810
822
|
/>
|
|
811
823
|
</HStack>
|
|
@@ -16,7 +16,7 @@ function trimTrailingSlash(value: string): string {
|
|
|
16
16
|
return value.replace(/\/+$/, '')
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const serverUrl = trimTrailingSlash(window.__TLD_SERVER_URL__ ?? '
|
|
19
|
+
const serverUrl = trimTrailingSlash(window.__TLD_SERVER_URL__ ?? 'http://127.0.0.1:8060')
|
|
20
20
|
|
|
21
21
|
export const appBase = '/app/'
|
|
22
22
|
export const routerBasename = undefined
|
|
@@ -29,9 +29,7 @@ export function apiUrl(path: string): string {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function fetchApiAsset(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
|
32
|
-
|
|
33
|
-
if (window.__TLD_API_KEY__) headers.set('Authorization', `Bearer ${window.__TLD_API_KEY__}`)
|
|
34
|
-
return fetch(input, { ...init, headers })
|
|
32
|
+
return fetch(input, init)
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
export function oauthGoogleStartUrl(): string {
|
package/src/lib/vscodeBridge.ts
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
import type { ExtensionToWebviewMessage, WebviewToExtensionMessage } from '../types/vscode-messages'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
__TLD_VSCODE_API__?: {
|
|
6
|
+
postMessage: (msg: unknown) => void
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Runtime-aware bridge. It is a no-op in web/native builds, but works when the
|
|
12
|
+
// core UI is bundled directly into the VS Code webview.
|
|
4
13
|
export const vscodeBridge = {
|
|
5
|
-
postMessage: (
|
|
6
|
-
|
|
14
|
+
postMessage: (msg: WebviewToExtensionMessage) => {
|
|
15
|
+
window.__TLD_VSCODE_API__?.postMessage(msg)
|
|
16
|
+
},
|
|
17
|
+
onMessage: (handler: (msg: ExtensionToWebviewMessage) => void): (() => void) => {
|
|
18
|
+
const listener = (event: MessageEvent) => {
|
|
19
|
+
if (event.data && typeof event.data === 'object' && 'type' in event.data) {
|
|
20
|
+
handler(event.data as ExtensionToWebviewMessage)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
window.addEventListener('message', listener)
|
|
24
|
+
return () => window.removeEventListener('message', listener)
|
|
25
|
+
},
|
|
7
26
|
}
|
|
@@ -19,6 +19,11 @@ export function resolveWithBase(urlOrPath: string): string {
|
|
|
19
19
|
if (urlOrPath.startsWith('http://') || urlOrPath.startsWith('https://') || urlOrPath.startsWith('data:')) {
|
|
20
20
|
return urlOrPath
|
|
21
21
|
}
|
|
22
|
+
const vscodeServerUrl = typeof window !== 'undefined' ? window.__TLD_SERVER_URL__?.replace(/\/+$/, '') : undefined
|
|
23
|
+
if (window.__TLD_VSCODE__ && vscodeServerUrl) {
|
|
24
|
+
const normalizedPath = urlOrPath.startsWith('/') ? urlOrPath : `/${urlOrPath}`
|
|
25
|
+
return `${vscodeServerUrl}${normalizedPath}`
|
|
26
|
+
}
|
|
22
27
|
|
|
23
28
|
// When running inside the native mobile app (Capacitor), or inside an embedded webview
|
|
24
29
|
// that serves content from localhost, avoid prefixing the app BASE_URL. Mobile builds and
|
package/src/utils/url.ts
CHANGED
|
@@ -9,6 +9,13 @@ export function resolveIconPath(path: string | null | undefined): string {
|
|
|
9
9
|
|
|
10
10
|
// Absolute URLs and data URIs are returned as-is
|
|
11
11
|
if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('data:')) return path
|
|
12
|
+
const vscodeServerUrl = typeof window !== 'undefined' ? window.__TLD_SERVER_URL__?.replace(/\/+$/, '') : undefined
|
|
13
|
+
const isVsCode = typeof window !== 'undefined' && !!window.__TLD_VSCODE__
|
|
14
|
+
if (isVsCode && vscodeServerUrl) {
|
|
15
|
+
const stripped = path.startsWith('/app/') ? path.slice('/app'.length) : path
|
|
16
|
+
const normalizedPath = stripped.startsWith('/') ? stripped : `/${stripped}`
|
|
17
|
+
return `${vscodeServerUrl}${normalizedPath}`
|
|
18
|
+
}
|
|
12
19
|
|
|
13
20
|
// If running inside the native mobile app (Capacitor) OR inside an embedded
|
|
14
21
|
// webview that serves content from localhost (e.g. Capacitor production webview),
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { ChakraProvider } from '@chakra-ui/react'
|
|
4
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
5
|
+
import { MemoryRouter } from 'react-router-dom'
|
|
6
|
+
import App from './App'
|
|
7
|
+
import theme from './theme'
|
|
8
|
+
import { ToastContainer } from './utils/toast'
|
|
9
|
+
import { PlatformProvider } from './platform/PlatformContext'
|
|
10
|
+
import { platform as localPlatform } from './platform/local'
|
|
11
|
+
import './index.css'
|
|
12
|
+
|
|
13
|
+
declare global {
|
|
14
|
+
interface Window {
|
|
15
|
+
__TLD_DIAGRAM_ID__?: number
|
|
16
|
+
__TLD_VSCODE__?: boolean
|
|
17
|
+
__TLD_VSCODE_API__?: {
|
|
18
|
+
postMessage: (msg: unknown) => void
|
|
19
|
+
}
|
|
20
|
+
__TLD_SERVER_URL__?: string
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const diagramId = window.__TLD_DIAGRAM_ID__
|
|
25
|
+
const initialPath = diagramId != null ? `/views/${diagramId}` : '/views'
|
|
26
|
+
|
|
27
|
+
const queryClient = new QueryClient({
|
|
28
|
+
defaultOptions: {
|
|
29
|
+
queries: {
|
|
30
|
+
staleTime: 5_000,
|
|
31
|
+
refetchOnWindowFocus: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
createRoot(document.getElementById('root')!).render(
|
|
37
|
+
<StrictMode>
|
|
38
|
+
<QueryClientProvider client={queryClient}>
|
|
39
|
+
<ChakraProvider theme={theme}>
|
|
40
|
+
<PlatformProvider platform={localPlatform}>
|
|
41
|
+
<MemoryRouter
|
|
42
|
+
initialEntries={[initialPath]}
|
|
43
|
+
future={{
|
|
44
|
+
v7_startTransition: true,
|
|
45
|
+
v7_relativeSplatPath: true,
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<App />
|
|
49
|
+
</MemoryRouter>
|
|
50
|
+
<ToastContainer />
|
|
51
|
+
</PlatformProvider>
|
|
52
|
+
</ChakraProvider>
|
|
53
|
+
</QueryClientProvider>
|
|
54
|
+
</StrictMode>,
|
|
55
|
+
)
|
|
56
|
+
|