@tldiagram/core-ui 1.94.2 → 1.94.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +9 -1
- package/dist/components/NodeHoverCard.d.ts +3 -2
- package/dist/components/ViewFloatingMenu.d.ts +3 -1
- package/dist/demo/viewEditor.d.ts +3 -1
- package/dist/index.js +5269 -5212
- package/dist/pages/InfiniteZoom.d.ts +1 -1
- package/package.json +1 -1
- package/src/api/client.ts +89 -1
- package/src/components/NodeHoverCard.tsx +20 -15
- package/src/components/ViewFloatingMenu.tsx +46 -34
- package/src/demo/viewEditor.ts +7 -36
- package/src/pages/InfiniteZoom.tsx +19 -14
- package/src/pages/ViewEditor/index.tsx +11 -1
- package/src/utils/toast.ts +8 -0
|
@@ -7,4 +7,4 @@ export interface InfiniteZoomHandle {
|
|
|
7
7
|
}
|
|
8
8
|
declare const InfiniteZoom: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<InfiniteZoomHandle>>;
|
|
9
9
|
export default InfiniteZoom;
|
|
10
|
-
export declare
|
|
10
|
+
export declare const SharedInfiniteZoom: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<InfiniteZoomHandle>>;
|
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ExploreData,
|
|
9
9
|
LibraryElement,
|
|
10
10
|
PlacedElement,
|
|
11
|
+
Tag,
|
|
11
12
|
View,
|
|
12
13
|
ViewConnector,
|
|
13
14
|
ViewLayer,
|
|
@@ -91,6 +92,7 @@ interface ProtoDiagram {
|
|
|
91
92
|
updatedAt?: string
|
|
92
93
|
updated_at?: string
|
|
93
94
|
parent_view_id?: number | null
|
|
95
|
+
parentViewId?: number | null
|
|
94
96
|
children?: ProtoDiagram[]
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -107,7 +109,9 @@ function mapDiagram(d: ProtoDiagram): ViewTreeNode {
|
|
|
107
109
|
depth: d.depth ?? 0,
|
|
108
110
|
created_at: d.createdAt ?? d.created_at ?? '',
|
|
109
111
|
updated_at: d.updatedAt ?? d.updated_at ?? '',
|
|
110
|
-
parent_view_id: d.parent_view_id
|
|
112
|
+
parent_view_id: d.parentViewId != null || d.parent_view_id != null
|
|
113
|
+
? Number(d.parentViewId ?? d.parent_view_id)
|
|
114
|
+
: null,
|
|
111
115
|
children: (d.children ?? []).map(mapDiagram),
|
|
112
116
|
}
|
|
113
117
|
}
|
|
@@ -333,6 +337,12 @@ export const api = {
|
|
|
333
337
|
},
|
|
334
338
|
|
|
335
339
|
workspace: {
|
|
340
|
+
orgs: {
|
|
341
|
+
tagColors: {
|
|
342
|
+
list: (): Promise<Tag[]> => Promise.resolve([]),
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
|
|
336
346
|
elements: {
|
|
337
347
|
list: (params?: { limit?: number; offset?: number; search?: string }): Promise<LibraryElement[]> =>
|
|
338
348
|
api.elements.list(params),
|
|
@@ -641,6 +651,84 @@ export const api = {
|
|
|
641
651
|
password_required: false,
|
|
642
652
|
}
|
|
643
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
|
+
},
|
|
644
732
|
},
|
|
645
733
|
|
|
646
734
|
import: {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Box, Divider, Flex, HStack, Tag, Text, VStack } from '@chakra-ui/react'
|
|
2
|
-
import { TYPE_COLORS, type PlacedElement } from '../types'
|
|
2
|
+
import { TYPE_COLORS, type PlacedElement, type Tag as TagType } from '../types'
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
data: PlacedElement & { hasChildLink?: boolean }
|
|
6
6
|
/** Bounding rect of the node element in screen (viewport) coordinates */
|
|
7
7
|
anchorRect: DOMRect
|
|
8
|
+
tagColors?: Record<string, TagType>
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export default function NodeHoverCard({ data, anchorRect }: Props) {
|
|
11
|
+
export default function NodeHoverCard({ data, anchorRect, tagColors }: Props) {
|
|
11
12
|
const color = TYPE_COLORS[data.kind ?? ''] ?? 'gray'
|
|
12
13
|
|
|
13
14
|
// Position the card centred above the node using fixed coordinates so it
|
|
@@ -101,19 +102,23 @@ export default function NodeHoverCard({ data, anchorRect }: Props) {
|
|
|
101
102
|
{/* Tags */}
|
|
102
103
|
{data.tags && data.tags.length > 0 && (
|
|
103
104
|
<HStack spacing={1} flexWrap="wrap">
|
|
104
|
-
{data.tags.map((tag) =>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
105
|
+
{data.tags.map((tag) => {
|
|
106
|
+
const tagColor = tagColors?.[tag]?.color
|
|
107
|
+
return (
|
|
108
|
+
<Tag
|
|
109
|
+
key={tag}
|
|
110
|
+
size="sm"
|
|
111
|
+
variant="subtle"
|
|
112
|
+
bg={tagColor ? `color-mix(in srgb, ${tagColor} 12%, transparent)` : 'whiteAlpha.100'}
|
|
113
|
+
color={tagColor || 'gray.400'}
|
|
114
|
+
fontSize="9px"
|
|
115
|
+
borderColor={tagColor ? `color-mix(in srgb, ${tagColor} 30%, transparent)` : 'rgba(255,255,255,0.1)'}
|
|
116
|
+
borderWidth="1px"
|
|
117
|
+
>
|
|
118
|
+
{tag}
|
|
119
|
+
</Tag>
|
|
120
|
+
)
|
|
121
|
+
})}
|
|
117
122
|
</HStack>
|
|
118
123
|
)}
|
|
119
124
|
|
|
@@ -49,6 +49,8 @@ export interface ViewFloatingMenuProps extends ViewFloatingMenuSlots {
|
|
|
49
49
|
setHighlightColor: (color: string | null) => void
|
|
50
50
|
|
|
51
51
|
toolbarSlot?: React.ReactNode
|
|
52
|
+
hideFocusView?: boolean
|
|
53
|
+
hideExpandExtras?: boolean
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/**
|
|
@@ -83,6 +85,8 @@ function ViewFloatingMenu({
|
|
|
83
85
|
setHighlightColor,
|
|
84
86
|
shareSlot,
|
|
85
87
|
toolbarSlot,
|
|
88
|
+
hideFocusView = false,
|
|
89
|
+
hideExpandExtras = false,
|
|
86
90
|
}: ViewFloatingMenuProps) {
|
|
87
91
|
const { canEdit } = useViewEditorContext()
|
|
88
92
|
const { isOpen: isTagsOpen, onClose: onTagsClose, onToggle: onTagsToggle } = useDisclosure()
|
|
@@ -127,24 +131,28 @@ function ViewFloatingMenu({
|
|
|
127
131
|
</Button>
|
|
128
132
|
</Tooltip>
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
134
|
+
{!hideFocusView && (
|
|
135
|
+
<>
|
|
136
|
+
<Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
|
|
137
|
+
<Tooltip label={focusMode ? 'Show context' : 'Focus on this view'} placement="top" openDelay={200}>
|
|
138
|
+
<Button
|
|
139
|
+
variant="ghost"
|
|
140
|
+
h="28px"
|
|
141
|
+
px={2.5}
|
|
142
|
+
color={focusMode ? 'var(--accent)' : 'gray.300'}
|
|
143
|
+
bg={focusMode ? 'rgba(var(--accent-rgb), 0.12)' : 'transparent'}
|
|
144
|
+
_hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
|
|
145
|
+
onClick={() => onFocusModeChange(!focusMode)}
|
|
146
|
+
>
|
|
147
|
+
<HStack spacing={1.5}>
|
|
148
|
+
<FocusSvg />
|
|
149
|
+
<Text fontSize="11px" fontWeight="normal">Focus View</Text>
|
|
150
|
+
<Box w="6px" h="6px" rounded="full" bg={focusMode ? 'var(--accent)' : 'gray.500'} />
|
|
151
|
+
</HStack>
|
|
152
|
+
</Button>
|
|
153
|
+
</Tooltip>
|
|
154
|
+
</>
|
|
155
|
+
)}
|
|
148
156
|
<Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
|
|
149
157
|
|
|
150
158
|
{(allTags.length > 0 || layers.length > 0) && (
|
|
@@ -356,22 +364,26 @@ function ViewFloatingMenu({
|
|
|
356
364
|
</>
|
|
357
365
|
)}
|
|
358
366
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
367
|
+
{!hideExpandExtras && (
|
|
368
|
+
<>
|
|
369
|
+
<Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
|
|
370
|
+
<Button
|
|
371
|
+
variant="ghost"
|
|
372
|
+
h="28px"
|
|
373
|
+
minW="36px"
|
|
374
|
+
px={2}
|
|
375
|
+
display="inline-flex"
|
|
376
|
+
alignItems="center"
|
|
377
|
+
justifyContent="center"
|
|
378
|
+
color="gray.300"
|
|
379
|
+
_hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
|
|
380
|
+
onClick={() => setExtrasOpen((prev) => !prev)}
|
|
381
|
+
aria-label={extrasOpen ? 'Collapse extras' : 'Expand extras'}
|
|
382
|
+
>
|
|
383
|
+
{extrasOpen ? <CollapseExtrasSvg /> : <ExpandExtrasSvg />}
|
|
384
|
+
</Button>
|
|
385
|
+
</>
|
|
386
|
+
)}
|
|
375
387
|
</HStack>
|
|
376
388
|
)
|
|
377
389
|
}
|
package/src/demo/viewEditor.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo,
|
|
1
|
+
import { useCallback, useEffect, useMemo, type MutableRefObject, type RefObject } from 'react'
|
|
2
2
|
import { getNodesBounds, getViewportForBounds, type Node as RFNode, type ReactFlowInstance } from 'reactflow'
|
|
3
3
|
|
|
4
4
|
export interface ViewEditorDemoOptions {
|
|
@@ -6,24 +6,19 @@ export interface ViewEditorDemoOptions {
|
|
|
6
6
|
disableImportExport?: boolean
|
|
7
7
|
hideFlowControls?: boolean
|
|
8
8
|
disableOnboarding?: boolean
|
|
9
|
+
hideFocusView?: boolean
|
|
10
|
+
hideExpandExtras?: boolean
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export const DEMO_VIEW_EDITOR_OPTIONS: Omit<ViewEditorDemoOptions, 'revealProgress'> = {
|
|
12
14
|
disableImportExport: true,
|
|
13
15
|
hideFlowControls: true,
|
|
14
16
|
disableOnboarding: true,
|
|
17
|
+
hideFocusView: true,
|
|
18
|
+
hideExpandExtras: true,
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
function getCenteredViewport(bounds: { x: number; y: number; width: number; height: number }, width: number, height: number, zoom: number) {
|
|
18
|
-
const centerX = bounds.x + bounds.width / 2
|
|
19
|
-
const centerY = bounds.y + bounds.height / 2
|
|
20
21
|
|
|
21
|
-
return {
|
|
22
|
-
x: width / 2 - centerX * zoom,
|
|
23
|
-
y: height / 2 - centerY * zoom,
|
|
24
|
-
zoom,
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
22
|
|
|
28
23
|
interface UseDemoRevealViewportArgs {
|
|
29
24
|
demoOptions?: ViewEditorDemoOptions
|
|
@@ -44,19 +39,12 @@ export function useDemoRevealViewport({
|
|
|
44
39
|
needsFitViewRef,
|
|
45
40
|
computedMinZoom,
|
|
46
41
|
setViewport,
|
|
47
|
-
resetKey,
|
|
48
42
|
}: UseDemoRevealViewportArgs) {
|
|
49
43
|
const clampedRevealProgress = useMemo(() => {
|
|
50
44
|
if (typeof demoOptions?.revealProgress !== 'number') return null
|
|
51
45
|
return Math.max(0, Math.min(1, demoOptions.revealProgress))
|
|
52
46
|
}, [demoOptions?.revealProgress])
|
|
53
47
|
|
|
54
|
-
const revealZoomRef = useRef<number | null>(null)
|
|
55
|
-
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
revealZoomRef.current = null
|
|
58
|
-
}, [resetKey, clampedRevealProgress])
|
|
59
|
-
|
|
60
48
|
const applyDemoRevealViewport = useCallback(() => {
|
|
61
49
|
if (clampedRevealProgress === null) return false
|
|
62
50
|
|
|
@@ -73,24 +61,7 @@ export function useDemoRevealViewport({
|
|
|
73
61
|
const fittedViewport = getViewportForBounds(bounds, width, height, computedMinZoom, 2, 0.1)
|
|
74
62
|
if (![fittedViewport.x, fittedViewport.y, fittedViewport.zoom].every(Number.isFinite)) return false
|
|
75
63
|
|
|
76
|
-
|
|
77
|
-
revealZoomRef.current = fittedViewport.zoom
|
|
78
|
-
setViewport(fittedViewport, { duration: 0 })
|
|
79
|
-
return true
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const fixedZoom = revealZoomRef.current ?? fittedViewport.zoom
|
|
83
|
-
revealZoomRef.current = fixedZoom
|
|
84
|
-
|
|
85
|
-
const centeredViewport = getCenteredViewport(bounds, width, height, fixedZoom)
|
|
86
|
-
const reveal = 1 - Math.pow(1 - clampedRevealProgress, 3)
|
|
87
|
-
const hiddenOffsetX = Math.max(width * 1.15, bounds.width * fixedZoom * 0.75)
|
|
88
|
-
|
|
89
|
-
setViewport({
|
|
90
|
-
x: centeredViewport.x + hiddenOffsetX * (1 - reveal),
|
|
91
|
-
y: centeredViewport.y,
|
|
92
|
-
zoom: fixedZoom,
|
|
93
|
-
}, { duration: 0 })
|
|
64
|
+
setViewport(fittedViewport, { duration: 0 })
|
|
94
65
|
|
|
95
66
|
return true
|
|
96
67
|
}, [clampedRevealProgress, computedMinZoom, containerRef, rfNodesRef, setViewport])
|
|
@@ -107,4 +78,4 @@ export function useDemoRevealViewport({
|
|
|
107
78
|
disableImportExport: demoOptions?.disableImportExport ?? false,
|
|
108
79
|
hideFlowControls: demoOptions?.hideFlowControls ?? false,
|
|
109
80
|
}
|
|
110
|
-
}
|
|
81
|
+
}
|
|
@@ -132,7 +132,7 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
132
132
|
}
|
|
133
133
|
}, [showMiniOnboarding])
|
|
134
134
|
useEffect(() => {
|
|
135
|
-
const loader = api.explore.load()
|
|
135
|
+
const loader = sharedToken ? api.explore.loadShared(sharedToken) : api.explore.load()
|
|
136
136
|
loader.then((d) => {
|
|
137
137
|
if (d.password_required) {
|
|
138
138
|
setLoading(false)
|
|
@@ -147,23 +147,27 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
147
147
|
// Fetch tag colors and layers once data is loaded (authenticated users only).
|
|
148
148
|
// Only fetch from root tree nodes child/nested diagrams would duplicate the same layers.
|
|
149
149
|
useEffect(() => {
|
|
150
|
-
if (!data) return
|
|
150
|
+
if (!data || sharedToken) return
|
|
151
151
|
let cancelled = false
|
|
152
152
|
const rootIds = (data.tree ?? []).map(n => n.id)
|
|
153
153
|
const fetchTagData = async () => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
try {
|
|
155
|
+
const diagramLayers = await Promise.all(
|
|
156
|
+
rootIds.map(id => api.workspace.views.layers.list(id)),
|
|
157
|
+
)
|
|
158
|
+
if (!cancelled) {
|
|
159
|
+
// Deduplicate by layer ID in case of any API overlap
|
|
160
|
+
const seen = new Set<number>()
|
|
161
|
+
const unique = diagramLayers.flat().filter(l => seen.has(l.id) ? false : (seen.add(l.id), true))
|
|
162
|
+
setLayers(unique)
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// intentionally empty: layers are not available for public shared pages
|
|
162
166
|
}
|
|
163
167
|
}
|
|
164
168
|
void fetchTagData()
|
|
165
169
|
return () => { cancelled = true }
|
|
166
|
-
}, [data])
|
|
170
|
+
}, [data, sharedToken])
|
|
167
171
|
|
|
168
172
|
const handleCanvasReady = useCallback(() => {
|
|
169
173
|
setCanvasReady(true)
|
|
@@ -400,7 +404,8 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
400
404
|
const InfiniteZoom = forwardRef<InfiniteZoomHandle, Props>(InfiniteZoomInner)
|
|
401
405
|
export default InfiniteZoom
|
|
402
406
|
|
|
403
|
-
export
|
|
407
|
+
export const SharedInfiniteZoom = forwardRef<InfiniteZoomHandle, Props>((props, ref) => {
|
|
404
408
|
const { token } = useParams()
|
|
405
|
-
|
|
406
|
-
}
|
|
409
|
+
const effectiveToken = props.sharedToken ?? token
|
|
410
|
+
return <InfiniteZoom {...props} ref={ref} sharedToken={effectiveToken} />
|
|
411
|
+
})
|
|
@@ -260,7 +260,15 @@ function ViewEditorInner({
|
|
|
260
260
|
const [tagColors, setTagColors] = useState<Record<string, Tag>>({})
|
|
261
261
|
|
|
262
262
|
useEffect(() => {
|
|
263
|
-
|
|
263
|
+
api.workspace.orgs.tagColors.list().then((res) => {
|
|
264
|
+
if (Array.isArray(res)) {
|
|
265
|
+
const next: Record<string, Tag> = {}
|
|
266
|
+
res.forEach(t => { next[t.name] = t })
|
|
267
|
+
setTagColors(next)
|
|
268
|
+
} else {
|
|
269
|
+
setTagColors(res)
|
|
270
|
+
}
|
|
271
|
+
}).catch(() => { /* skip */ })
|
|
264
272
|
}, [])
|
|
265
273
|
|
|
266
274
|
const [layers, setLayers] = useState<import('../../types').ViewLayer[]>([])
|
|
@@ -1369,6 +1377,8 @@ function ViewEditorInner({
|
|
|
1369
1377
|
setHighlightColor={setHoveredLayerColor}
|
|
1370
1378
|
shareSlot={shareSlot}
|
|
1371
1379
|
toolbarSlot={toolbarSlot}
|
|
1380
|
+
hideFocusView={demoOptions?.hideFocusView}
|
|
1381
|
+
hideExpandExtras={demoOptions?.hideExpandExtras}
|
|
1372
1382
|
/>
|
|
1373
1383
|
</Box>
|
|
1374
1384
|
</Flex>
|
package/src/utils/toast.ts
CHANGED
|
@@ -52,6 +52,14 @@ const toast: CustomToast = (options: UseToastOptions) => {
|
|
|
52
52
|
const status = options.status || 'error'
|
|
53
53
|
|
|
54
54
|
if (status === 'error') {
|
|
55
|
+
// Silence error toasts if we are on a demo route
|
|
56
|
+
const isDemoRoute = window.location.pathname.includes('/demo') ||
|
|
57
|
+
window.location.pathname.includes('/app/demo');
|
|
58
|
+
|
|
59
|
+
if (isDemoRoute) {
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
55
63
|
const summary = getErrorSummary(options)
|
|
56
64
|
|
|
57
65
|
// Check if an error toast is already active
|