@tldiagram/core-ui 1.87.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/App.d.ts +1 -0
- package/dist/api/client.d.ts +143 -0
- package/dist/api/transport-vscode.d.ts +8 -0
- package/dist/api/transport.d.ts +1 -0
- package/dist/components/CodePreviewPanel-vscode.d.ts +7 -0
- package/dist/components/CodePreviewPanel.d.ts +9 -0
- package/dist/components/ConfirmDialog.d.ts +12 -0
- package/dist/components/ConnectorPanel.d.ts +21 -0
- package/dist/components/ContextBoundaryElement.d.ts +11 -0
- package/dist/components/ContextNeighborElement.d.ts +29 -0
- package/dist/components/ContextStraightConnector.d.ts +4 -0
- package/dist/components/CrossBranchControls.d.ts +9 -0
- package/dist/components/DependenciesOnboarding.d.ts +5 -0
- package/dist/components/DrawingCanvas.d.ts +39 -0
- package/dist/components/ElementLibrary-vscode.d.ts +7 -0
- package/dist/components/ElementLibrary.d.ts +22 -0
- package/dist/components/ElementNode.d.ts +36 -0
- package/dist/components/ElementPanel.d.ts +25 -0
- package/dist/components/ExploreOnboarding.d.ts +5 -0
- package/dist/components/ExplorePageOnboarding.d.ts +5 -0
- package/dist/components/ExportModal.d.ts +16 -0
- package/dist/components/FloatingEdge.d.ts +9 -0
- package/dist/components/GitSourceLinker.d.ts +8 -0
- package/dist/components/HeaderContext.d.ts +16 -0
- package/dist/components/Icons.d.ts +95 -0
- package/dist/components/ImportModal.d.ts +10 -0
- package/dist/components/InlineElementAdder.d.ts +17 -0
- package/dist/components/LayoutSection.d.ts +7 -0
- package/dist/components/LocalSourceLinker.d.ts +8 -0
- package/dist/components/MiniZoomOnboarding.d.ts +5 -0
- package/dist/components/NavBreadcrumb.d.ts +6 -0
- package/dist/components/NodeBody.d.ts +12 -0
- package/dist/components/NodeContainer.d.ts +8 -0
- package/dist/components/NodeHoverCard.d.ts +10 -0
- package/dist/components/PanelHeader.d.ts +8 -0
- package/dist/components/PanelUI.d.ts +3 -0
- package/dist/components/ProxyConnectorEdge.d.ts +4 -0
- package/dist/components/ProxyConnectorPanel.d.ts +9 -0
- package/dist/components/SafeBackground.d.ts +13 -0
- package/dist/components/ScrollIndicatorWrapper.d.ts +8 -0
- package/dist/components/SetChildModal.d.ts +10 -0
- package/dist/components/SetParentModal.d.ts +10 -0
- package/dist/components/SlidingPanel.d.ts +16 -0
- package/dist/components/TagUpsert.d.ts +8 -0
- package/dist/components/TopMenuBar.d.ts +8 -0
- package/dist/components/ViewBezierConnector.d.ts +4 -0
- package/dist/components/ViewDrawMenu.d.ts +22 -0
- package/dist/components/ViewEditorEdgeLabelLayout.d.ts +16 -0
- package/dist/components/ViewEditorOnboarding.d.ts +5 -0
- package/dist/components/ViewExplorer/TagManager/ColorPicker.d.ts +7 -0
- package/dist/components/ViewExplorer/TagManager/GroupNamingPopover.d.ts +10 -0
- package/dist/components/ViewExplorer/TagManager/LayerItem.d.ts +27 -0
- package/dist/components/ViewExplorer/TagManager/TagItem.d.ts +25 -0
- package/dist/components/ViewExplorer/TagManager/index.d.ts +21 -0
- package/dist/components/ViewExplorer/ViewNavigator.d.ts +11 -0
- package/dist/components/ViewExplorer/ViewSearch.d.ts +8 -0
- package/dist/components/ViewExplorer/ViewTree.d.ts +18 -0
- package/dist/components/ViewExplorer/index.d.ts +31 -0
- package/dist/components/ViewExplorer/types.d.ts +11 -0
- package/dist/components/ViewExplorer/utils.d.ts +6 -0
- package/dist/components/ViewExplorer-vscode.d.ts +6 -0
- package/dist/components/ViewFloatingMenu-vscode.d.ts +27 -0
- package/dist/components/ViewFloatingMenu.d.ts +39 -0
- package/dist/components/ViewGridNode.d.ts +29 -0
- package/dist/components/ViewHeaderButton.d.ts +11 -0
- package/dist/components/ViewPanel.d.ts +18 -0
- package/dist/components/ViewsGridOnboarding.d.ts +5 -0
- package/dist/components/ZUI/ZUICanvas.d.ts +18 -0
- package/dist/components/ZUI/index.d.ts +2 -0
- package/dist/components/ZUI/layout.d.ts +18 -0
- package/dist/components/ZUI/proxy.d.ts +25 -0
- package/dist/components/ZUI/renderer.d.ts +30 -0
- package/dist/components/ZUI/types.d.ts +140 -0
- package/dist/components/ZUI/useZUIInteraction.d.ts +21 -0
- package/dist/config/runtime-vscode.d.ts +22 -0
- package/dist/config/runtime.d.ts +5 -0
- package/dist/constants/colors.d.ts +27 -0
- package/dist/constants/diagramColors.d.ts +1 -0
- package/dist/context/ThemeContext.d.ts +27 -0
- package/dist/crossBranch/graph.d.ts +13 -0
- package/dist/crossBranch/resolve.d.ts +22 -0
- package/dist/crossBranch/settings.d.ts +6 -0
- package/dist/crossBranch/store.d.ts +11 -0
- package/dist/crossBranch/types.d.ts +96 -0
- package/dist/demo/DemoPage.d.ts +9 -0
- package/dist/demo/seed.d.ts +9 -0
- package/dist/demo/store.d.ts +137 -0
- package/dist/demo/viewEditor.d.ts +26 -0
- package/dist/favicon.svg +35 -0
- package/dist/hooks/useSafeFitView.d.ts +16 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +19966 -0
- package/dist/lib/vscodeBridge-vscode.d.ts +13 -0
- package/dist/lib/vscodeBridge.d.ts +5 -0
- package/dist/logo-120.png +0 -0
- package/dist/logo-bw.png +0 -0
- package/dist/logo-bw.svg +15 -0
- package/dist/logo-text.svg +51 -0
- package/dist/logo.svg +35 -0
- package/dist/pages/AppearanceSettings.d.ts +3 -0
- package/dist/pages/Dependencies.d.ts +1 -0
- package/dist/pages/InfiniteZoom.d.ts +7 -0
- package/dist/pages/Settings.d.ts +7 -0
- package/dist/pages/ViewEditor/components/EditorMenus.d.ts +24 -0
- package/dist/pages/ViewEditor/components/EditorOverlays.d.ts +30 -0
- package/dist/pages/ViewEditor/components/EmptyCanvasState.d.ts +7 -0
- package/dist/pages/ViewEditor/context.d.ts +13 -0
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +201 -0
- package/dist/pages/ViewEditor/hooks/useDrawingEngine.d.ts +40 -0
- package/dist/pages/ViewEditor/hooks/useViewContextNeighbours.d.ts +20 -0
- package/dist/pages/ViewEditor/hooks/useViewData.d.ts +74 -0
- package/dist/pages/ViewEditor/index.d.ts +8 -0
- package/dist/pages/ViewEditor/utils.d.ts +14 -0
- package/dist/pages/Views.d.ts +6 -0
- package/dist/pages/ViewsGrid.d.ts +6 -0
- package/dist/pkg/importer/mermaid.d.ts +7 -0
- package/dist/pkg/importer/mermaid.test.d.ts +1 -0
- package/dist/platform/PlatformContext.d.ts +6 -0
- package/dist/platform/context.d.ts +3 -0
- package/dist/platform/local.d.ts +2 -0
- package/dist/platform/types.d.ts +17 -0
- package/dist/slots.d.ts +67 -0
- package/dist/theme.d.ts +2 -0
- package/dist/types/index.d.ts +193 -0
- package/dist/types/vscode-messages.d.ts +60 -0
- package/dist/utils/edgeDistribution.d.ts +34 -0
- package/dist/utils/githubApi.d.ts +4 -0
- package/dist/utils/githubCache.d.ts +17 -0
- package/dist/utils/ids.d.ts +2 -0
- package/dist/utils/technologyCatalog.d.ts +15 -0
- package/dist/utils/toast.d.ts +15 -0
- package/dist/utils/treesitter.d.ts +13 -0
- package/dist/utils/url.d.ts +12 -0
- package/package.json +159 -0
- package/src/App.tsx +141 -0
- package/src/api/client.ts +618 -0
- package/src/api/transport-vscode.ts +28 -0
- package/src/api/transport.ts +7 -0
- package/src/assets/logo-mark.svg +31 -0
- package/src/assets/logo-wordmark.svg +22 -0
- package/src/assets/logo.svg +35 -0
- package/src/components/CodePreviewPanel-vscode.tsx +85 -0
- package/src/components/CodePreviewPanel.tsx +384 -0
- package/src/components/ConfirmDialog.tsx +66 -0
- package/src/components/ConnectorPanel.tsx +403 -0
- package/src/components/ContextBoundaryElement.tsx +35 -0
- package/src/components/ContextNeighborElement.tsx +282 -0
- package/src/components/ContextStraightConnector.tsx +144 -0
- package/src/components/CrossBranchControls.tsx +105 -0
- package/src/components/DependenciesOnboarding.tsx +427 -0
- package/src/components/DrawingCanvas.tsx +391 -0
- package/src/components/ElementLibrary-vscode.tsx +9 -0
- package/src/components/ElementLibrary.tsx +512 -0
- package/src/components/ElementNode.tsx +1033 -0
- package/src/components/ElementPanel.tsx +928 -0
- package/src/components/ExploreOnboarding.tsx +347 -0
- package/src/components/ExplorePageOnboarding.tsx +383 -0
- package/src/components/ExportModal.tsx +132 -0
- package/src/components/FloatingEdge.tsx +115 -0
- package/src/components/GitSourceLinker.tsx +1053 -0
- package/src/components/HeaderContext.tsx +30 -0
- package/src/components/Icons.tsx +245 -0
- package/src/components/ImportModal.tsx +219 -0
- package/src/components/InlineElementAdder.tsx +216 -0
- package/src/components/LayoutSection.tsx +624 -0
- package/src/components/LocalSourceLinker.tsx +330 -0
- package/src/components/MiniZoomOnboarding.tsx +78 -0
- package/src/components/NavBreadcrumb.tsx +24 -0
- package/src/components/NodeBody.tsx +89 -0
- package/src/components/NodeContainer.tsx +58 -0
- package/src/components/NodeHoverCard.tsx +135 -0
- package/src/components/PanelHeader.tsx +36 -0
- package/src/components/PanelUI.tsx +24 -0
- package/src/components/ProxyConnectorEdge.tsx +169 -0
- package/src/components/ProxyConnectorPanel.tsx +130 -0
- package/src/components/SafeBackground.tsx +19 -0
- package/src/components/ScrollIndicatorWrapper.tsx +117 -0
- package/src/components/SetChildModal.tsx +191 -0
- package/src/components/SetParentModal.tsx +187 -0
- package/src/components/SlidingPanel.tsx +114 -0
- package/src/components/TagUpsert.tsx +142 -0
- package/src/components/TopMenuBar.tsx +380 -0
- package/src/components/ViewBezierConnector.tsx +143 -0
- package/src/components/ViewDrawMenu.tsx +270 -0
- package/src/components/ViewEditorEdgeLabelLayout.ts +189 -0
- package/src/components/ViewEditorOnboarding.tsx +445 -0
- package/src/components/ViewExplorer/TagManager/ColorPicker.tsx +49 -0
- package/src/components/ViewExplorer/TagManager/GroupNamingPopover.tsx +96 -0
- package/src/components/ViewExplorer/TagManager/LayerItem.tsx +228 -0
- package/src/components/ViewExplorer/TagManager/TagItem.tsx +242 -0
- package/src/components/ViewExplorer/TagManager/index.tsx +418 -0
- package/src/components/ViewExplorer/ViewNavigator.tsx +121 -0
- package/src/components/ViewExplorer/ViewSearch.tsx +33 -0
- package/src/components/ViewExplorer/ViewTree.tsx +98 -0
- package/src/components/ViewExplorer/index.tsx +384 -0
- package/src/components/ViewExplorer/types.ts +13 -0
- package/src/components/ViewExplorer/utils.ts +56 -0
- package/src/components/ViewExplorer-vscode.tsx +8 -0
- package/src/components/ViewFloatingMenu-vscode.tsx +248 -0
- package/src/components/ViewFloatingMenu.tsx +379 -0
- package/src/components/ViewGridNode.tsx +451 -0
- package/src/components/ViewHeaderButton.tsx +60 -0
- package/src/components/ViewPanel.tsx +162 -0
- package/src/components/ViewsGridOnboarding.tsx +400 -0
- package/src/components/ZUI/ZUICanvas.tsx +853 -0
- package/src/components/ZUI/index.ts +3 -0
- package/src/components/ZUI/layout.ts +323 -0
- package/src/components/ZUI/proxy.ts +278 -0
- package/src/components/ZUI/renderer.ts +1189 -0
- package/src/components/ZUI/types.ts +150 -0
- package/src/components/ZUI/useZUIInteraction.ts +720 -0
- package/src/config/runtime-vscode.ts +46 -0
- package/src/config/runtime.ts +30 -0
- package/src/constants/colors.ts +80 -0
- package/src/constants/diagramColors.ts +9 -0
- package/src/context/ThemeContext.tsx +158 -0
- package/src/crossBranch/graph.ts +207 -0
- package/src/crossBranch/resolve.ts +643 -0
- package/src/crossBranch/settings.ts +59 -0
- package/src/crossBranch/store.ts +71 -0
- package/src/crossBranch/types.ts +102 -0
- package/src/demo/DemoPage.tsx +184 -0
- package/src/demo/seed.ts +67 -0
- package/src/demo/store.ts +536 -0
- package/src/demo/viewEditor.ts +110 -0
- package/src/hooks/useSafeFitView.ts +60 -0
- package/src/index.css +309 -0
- package/src/index.ts +184 -0
- package/src/kafka-ss.png +0 -0
- package/src/lib/vscodeBridge-vscode.ts +27 -0
- package/src/lib/vscodeBridge.ts +7 -0
- package/src/main.tsx +46 -0
- package/src/pages/AppearanceSettings.tsx +135 -0
- package/src/pages/Dependencies.tsx +926 -0
- package/src/pages/InfiniteZoom.tsx +404 -0
- package/src/pages/Settings.tsx +91 -0
- package/src/pages/ViewEditor/EDGE_DISTRIBUTION.md +64 -0
- package/src/pages/ViewEditor/components/EditorMenus.tsx +112 -0
- package/src/pages/ViewEditor/components/EditorOverlays.tsx +172 -0
- package/src/pages/ViewEditor/components/EmptyCanvasState.tsx +42 -0
- package/src/pages/ViewEditor/context.tsx +21 -0
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +1349 -0
- package/src/pages/ViewEditor/hooks/useDrawingEngine.ts +127 -0
- package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +501 -0
- package/src/pages/ViewEditor/hooks/useViewData.ts +491 -0
- package/src/pages/ViewEditor/index.tsx +1366 -0
- package/src/pages/ViewEditor/utils.ts +88 -0
- package/src/pages/Views.tsx +171 -0
- package/src/pages/ViewsGrid.tsx +1310 -0
- package/src/pkg/importer/mermaid.test.ts +141 -0
- package/src/pkg/importer/mermaid.ts +76 -0
- package/src/platform/PlatformContext.tsx +17 -0
- package/src/platform/context.ts +9 -0
- package/src/platform/local.tsx +15 -0
- package/src/platform/types.ts +19 -0
- package/src/slots.ts +92 -0
- package/src/styles/editor-panels.css +66 -0
- package/src/styles/theme.css +56 -0
- package/src/theme.ts +336 -0
- package/src/types/index.ts +234 -0
- package/src/types/offline-ambient.d.ts +14 -0
- package/src/types/vscode-messages.ts +32 -0
- package/src/utils/edgeDistribution.ts +103 -0
- package/src/utils/githubApi.ts +121 -0
- package/src/utils/githubCache.ts +108 -0
- package/src/utils/ids.ts +9 -0
- package/src/utils/technologyCatalog.ts +143 -0
- package/src/utils/toast.ts +100 -0
- package/src/utils/treesitter.ts +147 -0
- package/src/utils/url.ts +72 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react'
|
|
2
|
+
import { Position } from 'reactflow'
|
|
3
|
+
|
|
4
|
+
export const HANDLE_SLOT_COUNT = 5
|
|
5
|
+
export const HANDLE_SLOT_GAP = 12
|
|
6
|
+
export const HANDLE_SLOT_CENTER_INDEX = Math.floor(HANDLE_SLOT_COUNT / 2)
|
|
7
|
+
|
|
8
|
+
export type LogicalHandleSide = 'top' | 'bottom' | 'left' | 'right'
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_SOURCE_HANDLE_SIDE: LogicalHandleSide = 'right'
|
|
11
|
+
export const DEFAULT_TARGET_HANDLE_SIDE: LogicalHandleSide = 'left'
|
|
12
|
+
|
|
13
|
+
function clampSlot(slot: number) {
|
|
14
|
+
return Math.max(0, Math.min(HANDLE_SLOT_COUNT - 1, slot))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getLogicalHandleId(
|
|
18
|
+
handleId: string | null | undefined,
|
|
19
|
+
fallback: LogicalHandleSide | null = null,
|
|
20
|
+
): LogicalHandleSide | null {
|
|
21
|
+
if (!handleId) return fallback
|
|
22
|
+
const side = handleId.split('-', 1)[0]
|
|
23
|
+
if (side === 'top' || side === 'bottom' || side === 'left' || side === 'right') return side
|
|
24
|
+
return fallback
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getVisualHandleSlot(groupIndex: number, groupCount: number) {
|
|
28
|
+
if (groupIndex < 0) return HANDLE_SLOT_CENTER_INDEX
|
|
29
|
+
if (groupCount <= 1) return HANDLE_SLOT_CENTER_INDEX
|
|
30
|
+
return clampSlot(Math.round((groupIndex * (HANDLE_SLOT_COUNT - 1)) / (groupCount - 1)))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getVisualHandleSlotFromId(handleId: string | null | undefined) {
|
|
34
|
+
if (!handleId) return null
|
|
35
|
+
const parts = handleId.split('-')
|
|
36
|
+
if (parts.length < 2) return null
|
|
37
|
+
const slot = Number(parts[1])
|
|
38
|
+
return Number.isInteger(slot) ? clampSlot(slot) : null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getVisualHandleId(side: LogicalHandleSide, slot: number) {
|
|
42
|
+
return `${side}-${clampSlot(slot)}`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getVisualHandleIdForGroup(side: LogicalHandleSide, groupIndex: number, groupCount: number) {
|
|
46
|
+
return getVisualHandleId(side, getVisualHandleSlot(groupIndex, groupCount))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function ensureVisualHandleId(
|
|
50
|
+
handleId: string | null | undefined,
|
|
51
|
+
fallback: LogicalHandleSide,
|
|
52
|
+
) {
|
|
53
|
+
const side = getLogicalHandleId(handleId, fallback)
|
|
54
|
+
if (!side) return null
|
|
55
|
+
const slot = getVisualHandleSlotFromId(handleId) ?? HANDLE_SLOT_CENTER_INDEX
|
|
56
|
+
return getVisualHandleId(side, slot)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getHandleSlotOffset(slot: number) {
|
|
60
|
+
return (clampSlot(slot) - HANDLE_SLOT_CENTER_INDEX) * HANDLE_SLOT_GAP
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getHandleSlotOffsetFromId(handleId: string | null | undefined) {
|
|
64
|
+
const slot = getVisualHandleSlotFromId(handleId)
|
|
65
|
+
if (slot === null) return 0
|
|
66
|
+
return getHandleSlotOffset(slot)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getVisualHandleStyle(position: Position, slot: number): CSSProperties {
|
|
70
|
+
const offset = getHandleSlotOffset(slot)
|
|
71
|
+
|
|
72
|
+
switch (position) {
|
|
73
|
+
case Position.Top:
|
|
74
|
+
case Position.Bottom:
|
|
75
|
+
return { left: `calc(50% + ${offset}px)` }
|
|
76
|
+
case Position.Left:
|
|
77
|
+
case Position.Right:
|
|
78
|
+
return { top: `calc(50% + ${offset}px)` }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getHandleFlowPosition(
|
|
83
|
+
nodeX: number,
|
|
84
|
+
nodeY: number,
|
|
85
|
+
width: number,
|
|
86
|
+
height: number,
|
|
87
|
+
handleId: string | null | undefined,
|
|
88
|
+
fallback: LogicalHandleSide,
|
|
89
|
+
) {
|
|
90
|
+
const side = getLogicalHandleId(handleId, fallback) ?? fallback
|
|
91
|
+
const offset = getHandleSlotOffsetFromId(handleId)
|
|
92
|
+
|
|
93
|
+
switch (side) {
|
|
94
|
+
case 'top':
|
|
95
|
+
return { x: nodeX + width / 2 + offset, y: nodeY, side }
|
|
96
|
+
case 'bottom':
|
|
97
|
+
return { x: nodeX + width / 2 + offset, y: nodeY + height, side }
|
|
98
|
+
case 'left':
|
|
99
|
+
return { x: nodeX, y: nodeY + height / 2 + offset, side }
|
|
100
|
+
case 'right':
|
|
101
|
+
return { x: nodeX + width, y: nodeY + height / 2 + offset, side }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const GITHUB_API_BASE = 'https://api.github.com'
|
|
2
|
+
const MIN_REQUEST_GAP_MS = 300
|
|
3
|
+
|
|
4
|
+
type RepoVisibility = 'public' | 'private' | 'unknown'
|
|
5
|
+
|
|
6
|
+
let queue: Promise<void> = Promise.resolve()
|
|
7
|
+
let lastRequestAt = 0
|
|
8
|
+
let rateLimitedUntil = 0
|
|
9
|
+
|
|
10
|
+
const visibilityInFlight = new Map<string, Promise<RepoVisibility>>()
|
|
11
|
+
|
|
12
|
+
function wait(ms: number): Promise<void> {
|
|
13
|
+
if (ms <= 0) return Promise.resolve()
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
window.setTimeout(resolve, ms)
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function toNumber(value: string | null): number | null {
|
|
20
|
+
if (!value) return null
|
|
21
|
+
const parsed = Number(value)
|
|
22
|
+
return Number.isFinite(parsed) ? parsed : null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getAuthHeader(): string | null {
|
|
26
|
+
const clientId = import.meta.env.VITE_GH_CLIENT_ID?.trim()
|
|
27
|
+
const clientSecret = import.meta.env.VITE_GH_CLIENT_SECRET?.trim()
|
|
28
|
+
if (!clientId || !clientSecret) return null
|
|
29
|
+
return `Basic ${window.btoa(`${clientId}:${clientSecret}`)}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function updateRateLimitState(response: Response) {
|
|
33
|
+
const now = Date.now()
|
|
34
|
+
const retryAfterSeconds = toNumber(response.headers.get('retry-after'))
|
|
35
|
+
const remaining = toNumber(response.headers.get('x-ratelimit-remaining'))
|
|
36
|
+
const resetUnixSeconds = toNumber(response.headers.get('x-ratelimit-reset'))
|
|
37
|
+
|
|
38
|
+
if (retryAfterSeconds !== null && retryAfterSeconds > 0) {
|
|
39
|
+
rateLimitedUntil = Math.max(rateLimitedUntil, now + retryAfterSeconds * 1000)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (remaining === 0 && resetUnixSeconds !== null) {
|
|
43
|
+
// Add a small safety buffer after reset time to avoid immediate re-limit.
|
|
44
|
+
rateLimitedUntil = Math.max(rateLimitedUntil, resetUnixSeconds * 1000 + 500)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if ((response.status === 403 || response.status === 429) && retryAfterSeconds === null && remaining === 0) {
|
|
48
|
+
rateLimitedUntil = Math.max(rateLimitedUntil, now + 60_000)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function enqueueRequest<T>(request: () => Promise<T>): Promise<T> {
|
|
53
|
+
const run = async () => {
|
|
54
|
+
const now = Date.now()
|
|
55
|
+
if (rateLimitedUntil > now) {
|
|
56
|
+
await wait(rateLimitedUntil - now)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const elapsedSinceLast = Date.now() - lastRequestAt
|
|
60
|
+
if (elapsedSinceLast < MIN_REQUEST_GAP_MS) {
|
|
61
|
+
await wait(MIN_REQUEST_GAP_MS - elapsedSinceLast)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
lastRequestAt = Date.now()
|
|
65
|
+
return request()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const chained = queue.then(run, run)
|
|
69
|
+
queue = chained.then(() => undefined, () => undefined)
|
|
70
|
+
return chained
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function githubRequest(path: string): Promise<Response> {
|
|
74
|
+
return enqueueRequest(async () => {
|
|
75
|
+
const headers = new Headers({
|
|
76
|
+
Accept: 'application/vnd.github+json',
|
|
77
|
+
})
|
|
78
|
+
const authHeader = getAuthHeader()
|
|
79
|
+
if (authHeader) headers.set('Authorization', authHeader)
|
|
80
|
+
|
|
81
|
+
let response = await fetch(`${GITHUB_API_BASE}${path}`, {
|
|
82
|
+
method: 'GET',
|
|
83
|
+
headers,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// If the request failed with 401 but we provided credentials, retry without credentials.
|
|
87
|
+
// This handles cases where client ID/secret are misconfigured but the resource is public.
|
|
88
|
+
if (response.status === 401 && authHeader) {
|
|
89
|
+
headers.delete('Authorization')
|
|
90
|
+
response = await fetch(`${GITHUB_API_BASE}${path}`, {
|
|
91
|
+
method: 'GET',
|
|
92
|
+
headers,
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
updateRateLimitState(response)
|
|
97
|
+
return response
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getGithubRepoVisibility(repoSlug: string): Promise<RepoVisibility> {
|
|
102
|
+
const inFlight = visibilityInFlight.get(repoSlug)
|
|
103
|
+
if (inFlight) return inFlight
|
|
104
|
+
|
|
105
|
+
const promise = (async () => {
|
|
106
|
+
const response = await githubRequest(`/repos/${repoSlug}`)
|
|
107
|
+
if (response.status === 200) return 'public'
|
|
108
|
+
if (response.status === 404 || response.status === 401) return 'private'
|
|
109
|
+
if (response.status === 403 || response.status === 429) {
|
|
110
|
+
const remaining = toNumber(response.headers.get('x-ratelimit-remaining'))
|
|
111
|
+
if (remaining === 0 || Date.now() < rateLimitedUntil) return 'unknown'
|
|
112
|
+
return 'private'
|
|
113
|
+
}
|
|
114
|
+
return 'unknown'
|
|
115
|
+
})().finally(() => {
|
|
116
|
+
visibilityInFlight.delete(repoSlug)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
visibilityInFlight.set(repoSlug, promise)
|
|
120
|
+
return promise
|
|
121
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
interface CacheEntry<T> {
|
|
2
|
+
data: T
|
|
3
|
+
timestamp: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const CACHE_TTL = 10 * 60 * 1000 // 10 minutes
|
|
7
|
+
const STORAGE_KEY = 'diag_github_cache'
|
|
8
|
+
|
|
9
|
+
interface CacheData {
|
|
10
|
+
branches: Record<string, CacheEntry<string[]>>
|
|
11
|
+
trees: Record<string, CacheEntry<string[]>>
|
|
12
|
+
contents: Record<string, CacheEntry<string>>
|
|
13
|
+
repoVisibility: Record<string, CacheEntry<boolean>>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class GithubCache {
|
|
17
|
+
private cache: CacheData
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
this.cache = this.load()
|
|
21
|
+
this.cleanup()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private load(): CacheData {
|
|
25
|
+
try {
|
|
26
|
+
const stored = localStorage.getItem(STORAGE_KEY)
|
|
27
|
+
if (stored) return JSON.parse(stored)
|
|
28
|
+
} catch { /* empty */ }
|
|
29
|
+
return { branches: {}, trees: {}, contents: {}, repoVisibility: {} }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private save() {
|
|
33
|
+
try {
|
|
34
|
+
// Basic size limit check - clear if too big (simple for now)
|
|
35
|
+
const str = JSON.stringify(this.cache)
|
|
36
|
+
if (str.length > 4 * 1024 * 1024) { // 4MB
|
|
37
|
+
this.cache = { branches: {}, trees: {}, contents: {}, repoVisibility: {} }
|
|
38
|
+
}
|
|
39
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.cache))
|
|
40
|
+
} catch { /* empty */ }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private cleanup() {
|
|
44
|
+
const now = Date.now()
|
|
45
|
+
let changed = false
|
|
46
|
+
const clean = <T>(map: Record<string, CacheEntry<T>>) => {
|
|
47
|
+
for (const [k, v] of Object.entries(map)) {
|
|
48
|
+
if (now - v.timestamp > CACHE_TTL) { delete map[k]; changed = true }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
clean(this.cache.branches)
|
|
52
|
+
clean(this.cache.trees)
|
|
53
|
+
clean(this.cache.contents)
|
|
54
|
+
if (!this.cache.repoVisibility) this.cache.repoVisibility = {}
|
|
55
|
+
clean(this.cache.repoVisibility)
|
|
56
|
+
if (changed) this.save()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getBranches(repo: string): string[] | null {
|
|
60
|
+
const entry = this.cache.branches[repo]
|
|
61
|
+
if (entry && Date.now() - entry.timestamp < CACHE_TTL) return entry.data
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setBranches(repo: string, data: string[]) {
|
|
66
|
+
this.cache.branches[repo] = { data, timestamp: Date.now() }
|
|
67
|
+
this.save()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getTree(repo: string, branch: string): string[] | null {
|
|
71
|
+
const key = `${repo}/${branch}`
|
|
72
|
+
const entry = this.cache.trees[key]
|
|
73
|
+
if (entry && Date.now() - entry.timestamp < CACHE_TTL) return entry.data
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setTree(repo: string, branch: string, data: string[]) {
|
|
78
|
+
const key = `${repo}/${branch}`
|
|
79
|
+
this.cache.trees[key] = { data, timestamp: Date.now() }
|
|
80
|
+
this.save()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getContent(rawUrl: string): string | null {
|
|
84
|
+
const entry = this.cache.contents[rawUrl]
|
|
85
|
+
if (entry && Date.now() - entry.timestamp < CACHE_TTL) return entry.data
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setContent(rawUrl: string, data: string) {
|
|
90
|
+
this.cache.contents[rawUrl] = { data, timestamp: Date.now() }
|
|
91
|
+
this.save()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getRepoPublic(slug: string): boolean | null {
|
|
95
|
+
if (!this.cache.repoVisibility) return null
|
|
96
|
+
const entry = this.cache.repoVisibility[slug]
|
|
97
|
+
if (entry && Date.now() - entry.timestamp < CACHE_TTL) return entry.data
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setRepoPublic(slug: string, isPublic: boolean) {
|
|
102
|
+
if (!this.cache.repoVisibility) this.cache.repoVisibility = {}
|
|
103
|
+
this.cache.repoVisibility[slug] = { data: isPublic, timestamp: Date.now() }
|
|
104
|
+
this.save()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const githubCache = new GithubCache()
|
package/src/utils/ids.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function parseNumericId(value: string | null | undefined): number | null {
|
|
2
|
+
if (value == null) return null
|
|
3
|
+
const n = Number(value)
|
|
4
|
+
return Number.isInteger(n) && n > 0 ? n : null
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function idToString(value: number | null | undefined): string {
|
|
8
|
+
return value == null ? '' : String(value)
|
|
9
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { TechnologyCatalogItem } from '../types'
|
|
2
|
+
import { isNativeApp } from '../config/runtime'
|
|
3
|
+
|
|
4
|
+
interface SearchableCatalogItem {
|
|
5
|
+
item: TechnologyCatalogItem
|
|
6
|
+
haystack: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface TechnologyCatalogIndex {
|
|
10
|
+
items: TechnologyCatalogItem[]
|
|
11
|
+
searchable: SearchableCatalogItem[]
|
|
12
|
+
bySlug: Map<string, TechnologyCatalogItem>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let indexPromise: Promise<TechnologyCatalogIndex> | null = null
|
|
16
|
+
|
|
17
|
+
export function resolveWithBase(urlOrPath: string): string {
|
|
18
|
+
if (!urlOrPath) return urlOrPath
|
|
19
|
+
if (urlOrPath.startsWith('http://') || urlOrPath.startsWith('https://') || urlOrPath.startsWith('data:')) {
|
|
20
|
+
return urlOrPath
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// When running inside the native mobile app (Capacitor), or inside an embedded webview
|
|
24
|
+
// that serves content from localhost, avoid prefixing the app BASE_URL. Mobile builds and
|
|
25
|
+
// the local webview often serve/resolve static assets from the web root (\"/\"), so
|
|
26
|
+
// returning the path without adding import.meta.env.BASE_URL avoids requests to
|
|
27
|
+
// unexpected URLs like \"https://localhost/app/icons/...\" which can 404.
|
|
28
|
+
const runningOnLocalhost = typeof window !== 'undefined' && (() => {
|
|
29
|
+
// file: is unequivocally a native / packaged environment (Capacitor)
|
|
30
|
+
if (window.location.protocol === 'file:') return true
|
|
31
|
+
|
|
32
|
+
// Only treat plain localhost/127.0.0.1 as the native webview when it's
|
|
33
|
+
// served in a way that matches Capacitor's webview (typically https
|
|
34
|
+
// without an explicit dev port). We must NOT treat the dev server
|
|
35
|
+
// (e.g. http://localhost:5173) as native.
|
|
36
|
+
const hostIsLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
|
|
37
|
+
|
|
38
|
+
if (!hostIsLocal) return false
|
|
39
|
+
|
|
40
|
+
// If served over HTTPS on localhost (Capacitor uses https://localhost) assume native/webview.
|
|
41
|
+
if (window.location.protocol === 'https:') return true
|
|
42
|
+
|
|
43
|
+
// If there's no explicit port (or it's the default HTTPS port), treat as native/webview.
|
|
44
|
+
// Dev servers usually expose a non-default port (like 5173) - those should NOT be treated as native.
|
|
45
|
+
const port = (window.location.port || '').trim()
|
|
46
|
+
if (!port || port === '443') return true
|
|
47
|
+
|
|
48
|
+
return false
|
|
49
|
+
})()
|
|
50
|
+
if (isNativeApp || runningOnLocalhost) {
|
|
51
|
+
// Ensure the path is absolute so it resolves correctly from the app's web root.
|
|
52
|
+
return urlOrPath.startsWith('/') ? urlOrPath : `/${urlOrPath}`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const base = import.meta.env.BASE_URL || '/'
|
|
56
|
+
const normalizedBase = base.endsWith('/') ? base : `${base}/`
|
|
57
|
+
if (urlOrPath.startsWith(normalizedBase) || urlOrPath === normalizedBase.slice(0, -1)) {
|
|
58
|
+
return urlOrPath
|
|
59
|
+
}
|
|
60
|
+
const normalizedPath = urlOrPath.startsWith('/') ? urlOrPath.slice(1) : urlOrPath
|
|
61
|
+
return `${normalizedBase}${normalizedPath}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeText(value: string): string {
|
|
65
|
+
return value.toLowerCase().trim()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createHaystack(item: TechnologyCatalogItem): string {
|
|
69
|
+
return normalizeText([
|
|
70
|
+
item.name,
|
|
71
|
+
item.nameShort,
|
|
72
|
+
item.provider,
|
|
73
|
+
item.defaultSlug,
|
|
74
|
+
].filter(Boolean).join(' '))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function loadCatalogItems(): Promise<TechnologyCatalogItem[]> {
|
|
78
|
+
const response = await fetch(resolveWithBase('icons.json'), { cache: 'force-cache' })
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new Error('Failed to load technology catalog')
|
|
81
|
+
}
|
|
82
|
+
const data = await response.json()
|
|
83
|
+
if (!Array.isArray(data)) return []
|
|
84
|
+
|
|
85
|
+
return data as TechnologyCatalogItem[]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function getTechnologyCatalogIndex(): Promise<TechnologyCatalogIndex> {
|
|
89
|
+
if (!indexPromise) {
|
|
90
|
+
indexPromise = loadCatalogItems().then((items) => {
|
|
91
|
+
const bySlug = new Map<string, TechnologyCatalogItem>()
|
|
92
|
+
const searchable: SearchableCatalogItem[] = []
|
|
93
|
+
|
|
94
|
+
for (const item of items) {
|
|
95
|
+
bySlug.set(item.defaultSlug, item)
|
|
96
|
+
searchable.push({ item, haystack: createHaystack(item) })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { items, searchable, bySlug }
|
|
100
|
+
}).catch((error) => {
|
|
101
|
+
indexPromise = null
|
|
102
|
+
throw error
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return indexPromise
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function searchTechnologyCatalog(query: string, maxResults = 12): Promise<TechnologyCatalogItem[]> {
|
|
110
|
+
const normalizedQuery = query.trim()
|
|
111
|
+
if (!normalizedQuery) return []
|
|
112
|
+
|
|
113
|
+
const index = await getTechnologyCatalogIndex()
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const regex = new RegExp(normalizedQuery, 'i')
|
|
117
|
+
const matches: TechnologyCatalogItem[] = []
|
|
118
|
+
for (const entry of index.searchable) {
|
|
119
|
+
if (regex.test(entry.haystack)) {
|
|
120
|
+
matches.push(entry.item)
|
|
121
|
+
if (matches.length >= maxResults) break
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return matches
|
|
125
|
+
} catch {
|
|
126
|
+
const needle = normalizeText(normalizedQuery)
|
|
127
|
+
const matches: TechnologyCatalogItem[] = []
|
|
128
|
+
for (const entry of index.searchable) {
|
|
129
|
+
if (entry.haystack.includes(needle)) {
|
|
130
|
+
matches.push(entry.item)
|
|
131
|
+
if (matches.length >= maxResults) break
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return matches
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function getTechnologyCatalogItemBySlug(slug: string): Promise<TechnologyCatalogItem | null> {
|
|
139
|
+
const cleanSlug = slug.trim()
|
|
140
|
+
if (!cleanSlug) return null
|
|
141
|
+
const index = await getTechnologyCatalogIndex()
|
|
142
|
+
return index.bySlug.get(cleanSlug) ?? null
|
|
143
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createStandaloneToast, UseToastOptions, ToastId } from '@chakra-ui/react'
|
|
2
|
+
import theme from '../theme'
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
const { toast: chakraToast, ToastContainer }: any = createStandaloneToast({
|
|
6
|
+
theme,
|
|
7
|
+
defaultOptions: {
|
|
8
|
+
status: 'error',
|
|
9
|
+
duration: 5000,
|
|
10
|
+
isClosable: true,
|
|
11
|
+
position: 'bottom-right',
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
let errorToastId: ToastId | null = null
|
|
16
|
+
let errorCount = 0
|
|
17
|
+
const errorSummaries = new Set<string>()
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extracts a short error code or summary from a ConnectRPC error message.
|
|
21
|
+
* ConnectRPC messages often look like "[unavailable] upstream request timeout"
|
|
22
|
+
* or REST responses like "HTTP 500 Internal Server Error"
|
|
23
|
+
*/
|
|
24
|
+
function getErrorSummary(options: UseToastOptions): string {
|
|
25
|
+
const text = (options.description || options.title || 'Unknown error').toString()
|
|
26
|
+
|
|
27
|
+
// ConnectRPC: matches "[code] message"
|
|
28
|
+
const connectMatch = text.match(/^\[(\w+)\]/)
|
|
29
|
+
if (connectMatch) return connectMatch[1]
|
|
30
|
+
|
|
31
|
+
// HTTP status: "HTTP 500"
|
|
32
|
+
const httpMatch = text.match(/HTTP \d+/)
|
|
33
|
+
if (httpMatch) return httpMatch[0]
|
|
34
|
+
|
|
35
|
+
// Generic fallback: first part of message (before colon or newline)
|
|
36
|
+
return text.split(':')[0].split('\n')[0].substring(0, 32).trim()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Global toast utility that intercepts 'error' status toasts to combine multiple
|
|
41
|
+
* backend errors into a single notification with a counter.
|
|
42
|
+
*/
|
|
43
|
+
interface CustomToast {
|
|
44
|
+
(options: UseToastOptions): ToastId | undefined;
|
|
45
|
+
close: typeof chakraToast.close;
|
|
46
|
+
closeAll: typeof chakraToast.closeAll;
|
|
47
|
+
update: typeof chakraToast.update;
|
|
48
|
+
isActive: typeof chakraToast.isActive;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const toast: CustomToast = (options: UseToastOptions) => {
|
|
52
|
+
const status = options.status || 'error'
|
|
53
|
+
|
|
54
|
+
if (status === 'error') {
|
|
55
|
+
const summary = getErrorSummary(options)
|
|
56
|
+
|
|
57
|
+
// Check if an error toast is already active
|
|
58
|
+
if (errorToastId && chakraToast.isActive(errorToastId)) {
|
|
59
|
+
errorCount++
|
|
60
|
+
errorSummaries.add(summary)
|
|
61
|
+
|
|
62
|
+
chakraToast.update(errorToastId, {
|
|
63
|
+
...options,
|
|
64
|
+
title: `${errorCount} Requests Failed`,
|
|
65
|
+
description: `Errors: ${Array.from(errorSummaries).join(', ')}`,
|
|
66
|
+
duration: 5000, // Refresh the timer
|
|
67
|
+
})
|
|
68
|
+
return errorToastId;
|
|
69
|
+
} else {
|
|
70
|
+
// First error or previous toast was closed
|
|
71
|
+
errorCount = 1
|
|
72
|
+
errorSummaries.clear()
|
|
73
|
+
errorSummaries.add(summary)
|
|
74
|
+
|
|
75
|
+
const originalOnCloseComplete = options.onCloseComplete
|
|
76
|
+
|
|
77
|
+
errorToastId = chakraToast({
|
|
78
|
+
...options,
|
|
79
|
+
title: options.title || 'Request Failed',
|
|
80
|
+
onCloseComplete: () => {
|
|
81
|
+
if (originalOnCloseComplete) originalOnCloseComplete()
|
|
82
|
+
errorToastId = null
|
|
83
|
+
errorCount = 0
|
|
84
|
+
errorSummaries.clear()
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
return errorToastId;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return chakraToast(options)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Proxy standard chakra toast methods for complete compatibility
|
|
95
|
+
toast.close = chakraToast.close
|
|
96
|
+
toast.closeAll = chakraToast.closeAll
|
|
97
|
+
toast.update = chakraToast.update
|
|
98
|
+
toast.isActive = chakraToast.isActive
|
|
99
|
+
|
|
100
|
+
export { toast, ToastContainer }
|