ydb-embedded-ui 20.4.0 → 21.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.
Files changed (134) hide show
  1. package/README.md +41 -0
  2. package/dist/components/AutoRefreshControl/i18n/index.d.ts +1 -1
  3. package/dist/components/FullNodeViewer/i18n/index.d.ts +1 -1
  4. package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.d.ts +7 -0
  5. package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.js +30 -0
  6. package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.js.map +1 -0
  7. package/dist/components/Graph/BlockComponents/QueryBlockComponent.d.ts +5 -0
  8. package/dist/components/Graph/BlockComponents/QueryBlockComponent.js +5 -0
  9. package/dist/components/Graph/BlockComponents/QueryBlockComponent.js.map +1 -0
  10. package/dist/components/Graph/BlockComponents/ResultBlockComponent.d.ts +7 -0
  11. package/dist/components/Graph/BlockComponents/ResultBlockComponent.js +5 -0
  12. package/dist/components/Graph/BlockComponents/ResultBlockComponent.js.map +1 -0
  13. package/dist/components/Graph/BlockComponents/StageBlockComponent.d.ts +7 -0
  14. package/dist/components/Graph/BlockComponents/StageBlockComponent.js +17 -0
  15. package/dist/components/Graph/BlockComponents/StageBlockComponent.js.map +1 -0
  16. package/dist/components/Graph/FullViewportBackground.d.ts +2 -0
  17. package/dist/components/Graph/FullViewportBackground.js +67 -0
  18. package/dist/components/Graph/FullViewportBackground.js.map +1 -0
  19. package/dist/components/Graph/GraphControls.d.ts +6 -0
  20. package/dist/components/Graph/GraphControls.js +24 -0
  21. package/dist/components/Graph/GraphControls.js.map +1 -0
  22. package/dist/components/Graph/GravityGraph.d.ts +9 -0
  23. package/dist/components/Graph/GravityGraph.js +122 -0
  24. package/dist/components/Graph/GravityGraph.js.map +1 -0
  25. package/dist/components/Graph/GravityGraph.scss +131 -0
  26. package/dist/components/Graph/NonSelectableConnection.d.ts +9 -0
  27. package/dist/components/Graph/NonSelectableConnection.js +16 -0
  28. package/dist/components/Graph/NonSelectableConnection.js.map +1 -0
  29. package/dist/components/Graph/TooltipComponent.d.ts +8 -0
  30. package/dist/components/Graph/TooltipComponent.js +63 -0
  31. package/dist/components/Graph/TooltipComponent.js.map +1 -0
  32. package/dist/components/Graph/colorsConfig.d.ts +19 -0
  33. package/dist/components/Graph/colorsConfig.js +20 -0
  34. package/dist/components/Graph/colorsConfig.js.map +1 -0
  35. package/dist/components/Graph/i18n/en.json +5 -0
  36. package/dist/components/Graph/i18n/index.d.ts +2 -0
  37. package/dist/components/Graph/i18n/index.js +5 -0
  38. package/dist/components/Graph/i18n/index.js.map +1 -0
  39. package/dist/components/Graph/runTreeLayout.d.ts +12 -0
  40. package/dist/components/Graph/runTreeLayout.js +75 -0
  41. package/dist/components/Graph/runTreeLayout.js.map +1 -0
  42. package/dist/components/Graph/treeLayout.d.ts +2 -0
  43. package/dist/components/Graph/treeLayout.js +284 -0
  44. package/dist/components/Graph/treeLayout.js.map +1 -0
  45. package/dist/components/Graph/types.d.ts +52 -0
  46. package/dist/components/Graph/types.js +2 -0
  47. package/dist/components/Graph/types.js.map +1 -0
  48. package/dist/components/Graph/utils.d.ts +6 -0
  49. package/dist/components/Graph/utils.js +88 -0
  50. package/dist/components/Graph/utils.js.map +1 -0
  51. package/dist/components/InfoViewer/formatters/pqGroup.d.ts +5 -1
  52. package/dist/components/InfoViewer/formatters/pqGroup.js +24 -2
  53. package/dist/components/InfoViewer/formatters/pqGroup.js.map +1 -1
  54. package/dist/components/InfoViewer/i18n/en.json +8 -1
  55. package/dist/components/InfoViewer/i18n/index.d.ts +1 -1
  56. package/dist/components/PDiskInfo/i18n/index.d.ts +1 -1
  57. package/dist/components/VDiskInfo/i18n/index.d.ts +1 -1
  58. package/dist/containers/App/Content.js +9 -2
  59. package/dist/containers/App/Content.js.map +1 -1
  60. package/dist/containers/App/clusterRouteGuard.d.ts +5 -0
  61. package/dist/containers/App/clusterRouteGuard.js +13 -0
  62. package/dist/containers/App/clusterRouteGuard.js.map +1 -0
  63. package/dist/containers/Clusters/constants.d.ts +1 -1
  64. package/dist/containers/Nodes/i18n/index.d.ts +1 -1
  65. package/dist/containers/Operations/constants.d.ts +1 -1
  66. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/index.d.ts +1 -1
  67. package/dist/containers/Tenant/Diagnostics/Overview/TransferInfo/Credentials.d.ts +1 -1
  68. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.js +17 -3
  69. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.js.map +1 -1
  70. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/useTenantStorageNewData.d.ts +2 -2
  71. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.js +9 -4
  72. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.js.map +1 -1
  73. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +2 -0
  74. package/dist/containers/Tenant/TenantNavigation/useTenantNavigation.d.ts +1 -1
  75. package/dist/graphLayout.worker.d.ts +1 -0
  76. package/dist/graphLayout.worker.js +17 -0
  77. package/dist/graphLayout.worker.js.map +1 -0
  78. package/dist/lib.d.ts +1 -1
  79. package/dist/lib.js.map +1 -1
  80. package/dist/store/reducers/query/parsers/preparePlanData.js.map +1 -1
  81. package/dist/store/reducers/query/types.d.ts +38 -1
  82. package/dist/store/reducers/settings/utils.d.ts +2 -2
  83. package/dist/types/api/schema/persQueueGroup.d.ts +16 -0
  84. package/dist/types/api/schema/persQueueGroup.js +7 -0
  85. package/dist/types/api/schema/persQueueGroup.js.map +1 -1
  86. package/dist/uiFactory/types.d.ts +3 -0
  87. package/dist/utils/disks/helpers.d.ts +2 -2
  88. package/dist/utils/hooks/useChangedQuerySettings.d.ts +2 -2
  89. package/dist/utils/hooks/useLastQueryExecutionSettings.d.ts +2 -2
  90. package/dist/utils/hooks/useQueryExecutionSettings.d.ts +2 -2
  91. package/dist/utils/prepareQueryExplain.d.ts +1 -2
  92. package/dist/utils/prepareQueryExplain.js +10 -2
  93. package/dist/utils/prepareQueryExplain.js.map +1 -1
  94. package/dist/utils/query.d.ts +1 -1
  95. package/package.json +3 -3
  96. package/src/components/Graph/BlockComponents/ConnectionBlockComponent.tsx +49 -0
  97. package/src/components/Graph/BlockComponents/QueryBlockComponent.tsx +7 -0
  98. package/src/components/Graph/BlockComponents/ResultBlockComponent.tsx +10 -0
  99. package/src/components/Graph/BlockComponents/StageBlockComponent.tsx +35 -0
  100. package/src/components/Graph/FullViewportBackground.ts +122 -0
  101. package/src/components/Graph/GraphControls.tsx +49 -0
  102. package/src/components/Graph/GravityGraph.scss +131 -0
  103. package/src/components/Graph/GravityGraph.tsx +174 -0
  104. package/src/components/Graph/NonSelectableConnection.tsx +14 -0
  105. package/src/components/Graph/TooltipComponent.tsx +114 -0
  106. package/src/components/Graph/colorsConfig.ts +21 -0
  107. package/src/components/Graph/i18n/en.json +5 -0
  108. package/src/components/Graph/i18n/index.ts +7 -0
  109. package/src/components/Graph/runTreeLayout.ts +105 -0
  110. package/src/components/Graph/treeLayout.ts +378 -0
  111. package/src/components/Graph/types.ts +56 -0
  112. package/src/components/Graph/utils.ts +112 -0
  113. package/src/components/InfoViewer/formatters/pqGroup.ts +30 -2
  114. package/src/components/InfoViewer/i18n/en.json +8 -1
  115. package/src/containers/App/Content.tsx +9 -1
  116. package/src/containers/App/clusterRouteGuard.ts +24 -0
  117. package/src/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +23 -2
  118. package/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +2 -0
  119. package/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx +13 -7
  120. package/src/graphLayout.worker.ts +21 -0
  121. package/src/lib.ts +1 -0
  122. package/src/store/reducers/query/parsers/preparePlanData.ts +1 -3
  123. package/src/store/reducers/query/types.ts +42 -2
  124. package/src/types/api/schema/persQueueGroup.ts +19 -0
  125. package/src/uiFactory/types.ts +4 -0
  126. package/src/utils/prepareQueryExplain.ts +13 -5
  127. package/dist/components/Graph/Graph.d.ts +0 -12
  128. package/dist/components/Graph/Graph.js +0 -44
  129. package/dist/components/Graph/Graph.js.map +0 -1
  130. package/dist/components/Graph/colors.d.ts +0 -10
  131. package/dist/components/Graph/colors.js +0 -53
  132. package/dist/components/Graph/colors.js.map +0 -1
  133. package/src/components/Graph/Graph.tsx +0 -70
  134. package/src/components/Graph/colors.ts +0 -75
@@ -0,0 +1,174 @@
1
+ import React from 'react';
2
+
3
+ import {GraphState} from '@gravity-ui/graph';
4
+ import type {TBlockId} from '@gravity-ui/graph';
5
+ import type {HookGraphParams} from '@gravity-ui/graph/react';
6
+ import {GraphBlock, GraphCanvas, useGraph, useGraphEvent} from '@gravity-ui/graph/react';
7
+
8
+ import {uiFactory} from '../../uiFactory/uiFactory';
9
+ import {cn} from '../../utils/cn';
10
+
11
+ const b = cn('ydb-gravity-graph');
12
+ const GRAPH_FIT_PADDING = 40;
13
+
14
+ import {ConnectionBlockComponent} from './BlockComponents/ConnectionBlockComponent';
15
+ import {QueryBlockComponent} from './BlockComponents/QueryBlockComponent';
16
+ import {ResultBlockComponent} from './BlockComponents/ResultBlockComponent';
17
+ import {StageBlockComponent} from './BlockComponents/StageBlockComponent';
18
+ import {FullViewportBackground} from './FullViewportBackground';
19
+ import {GraphControls} from './GraphControls';
20
+ import {NonSelectableConnection} from './NonSelectableConnection';
21
+ import {graphColorsConfig} from './colorsConfig';
22
+ import {runTreeLayout} from './runTreeLayout';
23
+ import type {Data} from './types';
24
+ import {parseCustomPropertyValue} from './utils';
25
+
26
+ import './GravityGraph.scss';
27
+
28
+ interface Props<T> {
29
+ data: Data<T>;
30
+ onError: (error: Error) => void;
31
+ theme?: string;
32
+ }
33
+
34
+ const config: HookGraphParams = {
35
+ settings: {
36
+ background: FullViewportBackground,
37
+ connection: NonSelectableConnection,
38
+ showConnectionArrows: false,
39
+ },
40
+ };
41
+
42
+ const createGraphLayoutWorker = () => {
43
+ if (uiFactory.createGraphLayoutWorker) {
44
+ return uiFactory.createGraphLayoutWorker();
45
+ }
46
+
47
+ return new Worker(new URL('../../graphLayout.worker', import.meta.url));
48
+ };
49
+
50
+ const renderBlockFn = (graph: any, block: any) => {
51
+ const map: Record<string, React.ComponentType<any>> = {
52
+ query: QueryBlockComponent,
53
+ result: ResultBlockComponent,
54
+ stage: StageBlockComponent,
55
+ connection: ConnectionBlockComponent,
56
+ };
57
+
58
+ const Component = map[block.is as keyof typeof map];
59
+
60
+ return (
61
+ <GraphBlock graph={graph} block={block} className={b('block')}>
62
+ {Component ? (
63
+ <React.Fragment>
64
+ <Component
65
+ graph={graph}
66
+ block={block}
67
+ className={b('block-content', block.is)}
68
+ />
69
+ {block.planNodeId !== undefined && block.is !== 'result' && (
70
+ <div className={b('block-id')}>#{block.planNodeId}</div>
71
+ )}
72
+ </React.Fragment>
73
+ ) : (
74
+ block.id
75
+ )}
76
+ </GraphBlock>
77
+ );
78
+ };
79
+
80
+ export function GravityGraph<T>({data, onError, theme}: Props<T>) {
81
+ const {graph, start} = useGraph(config);
82
+ const cameraSizeRef = React.useRef({width: 0, height: 0});
83
+ const graphBlockIdsRef = React.useRef<TBlockId[]>([]);
84
+ const fitFrameRef = React.useRef<number>();
85
+ const fitGraphToViewport = React.useCallback(() => {
86
+ const {width, height} = graph.cameraService.getCameraState();
87
+
88
+ if (
89
+ graph.state !== GraphState.READY ||
90
+ width <= 0 ||
91
+ height <= 0 ||
92
+ graphBlockIdsRef.current.length === 0
93
+ ) {
94
+ return;
95
+ }
96
+
97
+ graph.zoomTo(graphBlockIdsRef.current, {padding: GRAPH_FIT_PADDING});
98
+ }, [graph]);
99
+ const scheduleGraphFit = React.useCallback(() => {
100
+ if (fitFrameRef.current !== undefined) {
101
+ globalThis.cancelAnimationFrame(fitFrameRef.current);
102
+ }
103
+
104
+ // camera-change is emitted before its new size is committed.
105
+ fitFrameRef.current = globalThis.requestAnimationFrame(() => {
106
+ fitFrameRef.current = undefined;
107
+ fitGraphToViewport();
108
+ });
109
+ }, [fitGraphToViewport]);
110
+
111
+ React.useEffect(() => {
112
+ return () => {
113
+ if (fitFrameRef.current !== undefined) {
114
+ globalThis.cancelAnimationFrame(fitFrameRef.current);
115
+ }
116
+ };
117
+ }, []);
118
+
119
+ React.useEffect(() => {
120
+ return runTreeLayout({
121
+ request: {
122
+ nodes: data.nodes,
123
+ links: data.links,
124
+ },
125
+ createWorker: createGraphLayoutWorker,
126
+ onResult: ({layout, edges}) => {
127
+ graphBlockIdsRef.current = layout.map(({id}) => id);
128
+ graph.setEntities({
129
+ blocks: layout,
130
+ connections: edges,
131
+ });
132
+ scheduleGraphFit();
133
+ },
134
+ onError,
135
+ });
136
+ }, [data.nodes, data.links, graph, onError, scheduleGraphFit]);
137
+
138
+ React.useEffect(() => {
139
+ graph.setColors(parseCustomPropertyValue(graphColorsConfig));
140
+ }, [graph, theme]);
141
+
142
+ useGraphEvent(graph, 'state-change', ({state}) => {
143
+ if (state === GraphState.ATTACHED) {
144
+ graph.cameraService.set({
145
+ scale: 1,
146
+ scaleMax: 1.5,
147
+ });
148
+ graph.setConstants({
149
+ block: {
150
+ SCALES: [0.125, 0.225, 0.5], // Detailed view stays until zoom = 0.5
151
+ },
152
+ });
153
+ start();
154
+ } else if (state === GraphState.READY) {
155
+ scheduleGraphFit();
156
+ }
157
+ });
158
+
159
+ useGraphEvent(graph, 'camera-change', ({width, height}) => {
160
+ const previousSize = cameraSizeRef.current;
161
+ cameraSizeRef.current = {width, height};
162
+
163
+ if (previousSize.width !== width || previousSize.height !== height) {
164
+ scheduleGraphFit();
165
+ }
166
+ });
167
+
168
+ return (
169
+ <React.Fragment>
170
+ <GraphCanvas graph={graph} renderBlock={renderBlockFn} />
171
+ <GraphControls graph={graph} />
172
+ </React.Fragment>
173
+ );
174
+ }
@@ -0,0 +1,14 @@
1
+ import {MultipointConnection} from '@gravity-ui/graph/react';
2
+
3
+ /**
4
+ * Кастомный класс соединения, который отключает визуальное выделение
5
+ * Наследуется от MultipointConnection и переопределяет поведение
6
+ */
7
+ export class NonSelectableConnection extends MultipointConnection {
8
+ override cursor: 'pointer' = 'pointer';
9
+
10
+ // Переопределяем метод для предотвращения выделения при клике
11
+ protected override handleEvent(event: Event) {
12
+ event.stopPropagation();
13
+ }
14
+ }
@@ -0,0 +1,114 @@
1
+ import React from 'react';
2
+
3
+ import {Popover, Tab, TabList, TabPanel, TabProvider} from '@gravity-ui/uikit';
4
+
5
+ import type {
6
+ TopologyNodeDataStatsItem,
7
+ TopologyNodeDataStatsSection,
8
+ } from '../../store/reducers/query/types';
9
+ import {cn} from '../../utils/cn';
10
+
11
+ import type {ExtendedTBlock} from './types';
12
+
13
+ const b = cn('ydb-gravity-graph');
14
+ type Props = {
15
+ block: ExtendedTBlock;
16
+ children: React.ReactElement<React.HTMLAttributes<HTMLElement>>;
17
+ };
18
+
19
+ const getStatsContent = (stat: TopologyNodeDataStatsItem | TopologyNodeDataStatsSection) => {
20
+ if ('value' in stat) {
21
+ return (
22
+ <p className={b('tooltip-stat-row')} key={stat.name}>
23
+ <span>{stat.name}:</span>
24
+ <span>{stat.value}</span>
25
+ </p>
26
+ );
27
+ }
28
+
29
+ return (
30
+ <section className={b('tooltip-stat-group')} key={stat.name}>
31
+ <div className={b('tooltip-stat-group-name')}>{stat.name}:</div>
32
+ {stat.items?.map(({name, value}: TopologyNodeDataStatsItem) => (
33
+ <p className={b('tooltip-stat-row')} key={name}>
34
+ <span>{name}:</span>
35
+ <span>{value}</span>
36
+ </p>
37
+ ))}
38
+ </section>
39
+ );
40
+ };
41
+
42
+ const useTooltipContent = (block: ExtendedTBlock) => {
43
+ const firstTab = block?.stats?.[0]?.group || '';
44
+ const [activeTab, setActiveTab] = React.useState(firstTab);
45
+ const validatedActiveTab = block.stats?.some(({group}) => group === activeTab)
46
+ ? activeTab
47
+ : firstTab;
48
+
49
+ React.useEffect(() => {
50
+ if (activeTab !== validatedActiveTab) {
51
+ setActiveTab(validatedActiveTab);
52
+ }
53
+ }, [activeTab, validatedActiveTab]);
54
+
55
+ return React.useMemo(
56
+ () => (
57
+ <TabProvider value={validatedActiveTab} onUpdate={setActiveTab}>
58
+ <TabList className={b('tooltip-tabs')}>
59
+ {block?.stats?.map((item) => (
60
+ <Tab value={item.group} key={item.group}>
61
+ {item.group}
62
+ </Tab>
63
+ ))}
64
+ </TabList>
65
+ {block?.stats?.map((item) => (
66
+ <TabPanel value={item.group} key={item.group}>
67
+ {item.stats?.map(getStatsContent)}
68
+ </TabPanel>
69
+ ))}
70
+ </TabProvider>
71
+ ),
72
+ [block?.stats, validatedActiveTab],
73
+ );
74
+ };
75
+
76
+ export const TooltipComponent = ({block, children}: Props) => {
77
+ const content = useTooltipContent(block);
78
+ const trigger = React.cloneElement(children, {
79
+ role: children.props.role ?? 'button',
80
+ tabIndex: children.props.tabIndex ?? 0,
81
+ onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => {
82
+ children.props.onKeyDown?.(event);
83
+ if (event.defaultPrevented) {
84
+ return;
85
+ }
86
+
87
+ if (event.key === 'Enter') {
88
+ event.preventDefault();
89
+ event.currentTarget.click();
90
+ } else if (event.key === ' ') {
91
+ event.preventDefault();
92
+ }
93
+ },
94
+ onKeyUp: (event: React.KeyboardEvent<HTMLElement>) => {
95
+ children.props.onKeyUp?.(event);
96
+ if (!event.defaultPrevented && event.key === ' ') {
97
+ event.currentTarget.click();
98
+ }
99
+ },
100
+ });
101
+
102
+ return (
103
+ <Popover
104
+ content={content}
105
+ hasArrow
106
+ trigger="click"
107
+ placement="right-start"
108
+ className="ydb-gravity-graph__tooltip-content"
109
+ strategy="fixed"
110
+ >
111
+ {trigger}
112
+ </Popover>
113
+ );
114
+ };
@@ -0,0 +1,21 @@
1
+ export type AbstractGraphColorsConfig = Partial<Record<string, Partial<Record<string, string>>>>;
2
+
3
+ export const graphColorsConfig = {
4
+ // Default @gravity-ui/graph colors
5
+ canvas: {
6
+ belowLayerBackground: '#0000',
7
+ border: '#0000',
8
+ dots: 'var(--g-color-line-generic)',
9
+ layerBackground: 'var(--g-color-base-background)',
10
+ },
11
+ block: {
12
+ text: 'var(--g-color-text-primary)',
13
+ background: 'var(--g-color-base-float)',
14
+ border: 'var(--g-color-line-generic-solid)',
15
+ },
16
+ connection: {
17
+ background: 'var(--g-color-line-generic-solid)',
18
+ hoverBackground: 'var(--g-color-line-generic-solid)',
19
+ selectedBackground: 'var(--g-color-line-generic-solid)',
20
+ },
21
+ } as const satisfies AbstractGraphColorsConfig;
@@ -0,0 +1,5 @@
1
+ {
2
+ "action_zoom-in": "Zoom in",
3
+ "action_zoom-out": "Zoom out",
4
+ "label_tables": "Tables"
5
+ }
@@ -0,0 +1,7 @@
1
+ import {registerKeysets} from '../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+
5
+ const COMPONENT = 'ydb-gravity-graph';
6
+
7
+ export default registerKeysets(COMPONENT, {en});
@@ -0,0 +1,105 @@
1
+ import {prepareTreeLayout} from './treeLayout';
2
+ import type {TreeLayoutRequest, TreeLayoutResult, TreeLayoutWorkerResponse} from './types';
3
+
4
+ type Schedule = (callback: () => void) => () => void;
5
+
6
+ interface RunTreeLayoutOptions {
7
+ request: TreeLayoutRequest;
8
+ createWorker: () => Worker;
9
+ calculateLayout?: () => TreeLayoutResult;
10
+ onResult: (result: TreeLayoutResult) => void;
11
+ onError: (error: Error) => void;
12
+ schedule?: Schedule;
13
+ }
14
+
15
+ const scheduleOnMainThread: Schedule = (callback) => {
16
+ const timeoutId = globalThis.setTimeout(callback, 0);
17
+
18
+ return () => globalThis.clearTimeout(timeoutId);
19
+ };
20
+
21
+ const normalizeError = (error: unknown) => {
22
+ return error instanceof Error ? error : new Error(String(error));
23
+ };
24
+
25
+ export function runTreeLayout({
26
+ request,
27
+ createWorker,
28
+ calculateLayout = () => prepareTreeLayout(request),
29
+ onResult,
30
+ onError,
31
+ schedule = scheduleOnMainThread,
32
+ }: RunTreeLayoutOptions) {
33
+ let worker: Worker | undefined;
34
+ let cancelFallback: (() => void) | undefined;
35
+ let cancelled = false;
36
+ let settled = false;
37
+
38
+ const terminateWorker = () => {
39
+ worker?.terminate();
40
+ worker = undefined;
41
+ };
42
+
43
+ const reportError = (error: unknown) => {
44
+ if (cancelled || settled) {
45
+ return;
46
+ }
47
+
48
+ settled = true;
49
+ terminateWorker();
50
+ onError(normalizeError(error));
51
+ };
52
+
53
+ const complete = (result: TreeLayoutResult) => {
54
+ if (cancelled || settled) {
55
+ return;
56
+ }
57
+
58
+ settled = true;
59
+ terminateWorker();
60
+ onResult(result);
61
+ };
62
+
63
+ const scheduleFallback = () => {
64
+ if (cancelled || settled || cancelFallback) {
65
+ return;
66
+ }
67
+
68
+ terminateWorker();
69
+ cancelFallback = schedule(() => {
70
+ cancelFallback = undefined;
71
+
72
+ if (cancelled || settled) {
73
+ return;
74
+ }
75
+
76
+ try {
77
+ complete(calculateLayout());
78
+ } catch (error) {
79
+ reportError(error);
80
+ }
81
+ });
82
+ };
83
+
84
+ try {
85
+ worker = createWorker();
86
+ worker.onmessage = ({data}: MessageEvent<TreeLayoutWorkerResponse>) => {
87
+ if (data.type === 'success') {
88
+ complete(data.result);
89
+ } else {
90
+ reportError(new Error(data.error));
91
+ }
92
+ };
93
+ worker.onerror = scheduleFallback;
94
+ worker.onmessageerror = scheduleFallback;
95
+ worker.postMessage(request);
96
+ } catch {
97
+ scheduleFallback();
98
+ }
99
+
100
+ return () => {
101
+ cancelled = true;
102
+ cancelFallback?.();
103
+ terminateWorker();
104
+ };
105
+ }