From 48109b460d1a308d25d66ac3f51e44219f868553 Mon Sep 17 00:00:00 2001 From: draedful Date: Fri, 16 Oct 2026 17:00:07 +0300 Subject: [PATCH 1/3] fix(storybook): make stories pass strict TypeScript Enable "strict": true in apps/storybook/tsconfig.json and fix the 179 strict diagnostics in 32 story files: null-safe ref access with early returns, explicit parameter types, spread before explicit props, story-local narrower generator types, and nullable refs instead of readonly ref assignment. No library types are changed. Part of #367 Co-Authored-By: Claude Opus 5.5 --- .../ActionBlock/ActionBlockHtml.tsx | 4 +- .../stories/Playground/ActionBlock/index.tsx | 4 +- .../src/stories/Playground/Editor/index.tsx | 20 +++++-- .../src/stories/Playground/Editor/utils.ts | 8 ++- .../stories/Playground/GraphPlayground.tsx | 36 +++++++------ .../src/stories/Playground/Settings.tsx | 2 +- .../Playground/TextBlock/TextBlockHtml.tsx | 2 +- .../stories/Playground/TextBlock/index.tsx | 2 +- .../src/stories/Playground/generateLayout.tsx | 36 ++++++------- .../api/updateBlock/updateBlock.stories.tsx | 6 ++- .../updateConnection.stories.tsx | 6 ++- .../updateGraphColors.stories.tsx | 20 +++++-- .../api/zoomSpeed/zoomSpeed.stories.tsx | 12 ++++- .../api/zoomToBlocks/zoomToBlocks.stories.tsx | 6 ++- .../api/zoomToRect/zoomToRect.stories.tsx | 6 ++- .../zoomToViewPort/zoomToViewPort.stories.tsx | 6 ++- .../stories/configurations/generatePretty.ts | 36 +++++++------ .../stories/configurations/verticalGraph.ts | 4 +- .../connectionLayer.stories.tsx | 48 +++++++++-------- .../imageByBlocks/imageByBlocks.stories.tsx | 17 ++++-- .../imageInsteadBlock.stories.tsx | 54 +++++++++++-------- .../newBlockLayer/newBlockLayer.stories.tsx | 6 +-- .../portConnectionLayer.stories.tsx | 5 +- .../themeChange/themeChange.stories.tsx | 25 +++++++-- .../src/stories/main/GraphEditor.stories.tsx | 30 +++++------ .../src/stories/main/GraphEditor.tsx | 7 +-- .../src/stories/main/ToolboxLayer.tsx | 10 ++-- .../src/stories/plugins/elk/elk.stories.tsx | 9 ++-- .../plugins/elk/generateExampleTree.ts | 8 ++- .../stories/plugins/elk/getExampleConfig.ts | 16 +++--- apps/storybook/tsconfig.json | 2 +- 31 files changed, 276 insertions(+), 177 deletions(-) diff --git a/apps/storybook/src/stories/Playground/ActionBlock/ActionBlockHtml.tsx b/apps/storybook/src/stories/Playground/ActionBlock/ActionBlockHtml.tsx index 4a21477e..f00079b6 100644 --- a/apps/storybook/src/stories/Playground/ActionBlock/ActionBlockHtml.tsx +++ b/apps/storybook/src/stories/Playground/ActionBlock/ActionBlockHtml.tsx @@ -12,7 +12,7 @@ import "./ActionBlock.css"; export function ActionBlockHtml({ graph, block }: { graph: Graph; block: TGravityActionBlock }) { return ( - {block.anchors.map((anchor) => { + {block.anchors?.map((anchor) => { return ( - {block.meta.description} + {block.meta?.description} diff --git a/apps/storybook/src/stories/Playground/ActionBlock/index.tsx b/apps/storybook/src/stories/Playground/ActionBlock/index.tsx index a5ada043..b092cef2 100644 --- a/apps/storybook/src/stories/Playground/ActionBlock/index.tsx +++ b/apps/storybook/src/stories/Playground/ActionBlock/index.tsx @@ -9,7 +9,7 @@ import { ActionBlockHtml } from "./ActionBlockHtml"; import "./ActionBlock.css"; -function getAnchorY(index) { +function getAnchorY(index: number): number { let y = 18 * index; if (index >= 1) { y += 8 * index; @@ -65,7 +65,7 @@ export class ActionBlock extends CanvasBlock { } protected getAnchorsYOffter(type: EAnchorType) { - const anchors = this.connectedState.$state.value.anchors.filter((a) => a.type === type); + const anchors = (this.connectedState.$state.value.anchors ?? []).filter((a) => a.type === type); const { height } = this.getContentRect(); return (height - getAnchorY(anchors.length - 1)) / 2; } diff --git a/apps/storybook/src/stories/Playground/Editor/index.tsx b/apps/storybook/src/stories/Playground/Editor/index.tsx index fc9aa4be..26f2fc3c 100644 --- a/apps/storybook/src/stories/Playground/Editor/index.tsx +++ b/apps/storybook/src/stories/Playground/Editor/index.tsx @@ -36,18 +36,21 @@ export const ConfigEditor = React.forwardRef(function ConfigEditor( props: ConfigEditorProps, ref: Ref ) { - const [errorMarker, setErrorMarker] = useState[0]>>(null); + const [errorMarker, setErrorMarker] = useState[0]> | null>(null); - const monacoRef = useRef[0]>(null); + const monacoRef = useRef[0] | null>(null); const valueRef = useRef<{ blocks: TBlock[]; connections: TConnection[] }>({ blocks: [], connections: [] }); useImperativeHandle(ref, () => ({ - scrollTo: (blockId: string) => { + scrollTo: (blockId: TBlockId) => { if (!monacoRef.current) { return; } const model = monacoRef.current.getModel(); + if (!model) { + return; + } const range = findBlockPositionsMonaco(model, blockId); if (range?.start.column) { @@ -66,6 +69,9 @@ export const ConfigEditor = React.forwardRef(function ConfigEditor( return; } const model = monacoRef.current.getModel(); + if (!model) { + return; + } const edits = blocks.map((block) => { const range = findBlockPositionsMonaco(model, block.id); const text = JSON.stringify({ block: [block] }, null, 2); @@ -96,7 +102,11 @@ export const ConfigEditor = React.forwardRef(function ConfigEditor( const applyChanges = useFn(() => { try { - const data = JSON.parse(monacoRef.current.getModel().getValue()); + const model = monacoRef.current?.getModel(); + if (!model) { + return; + } + const data = JSON.parse(model.getValue()); props?.onChange?.({ blocks: data.blocks, connections: data.conections }); } catch (e) { console.error(e); @@ -147,7 +157,7 @@ export const ConfigEditor = React.forwardRef(function ConfigEditor( onClick={() => { monacoRef.current?.revealLinesInCenter(errorMarker.startLineNumber, errorMarker.endLineNumber, 0); - monacoRef.current.setSelection({ + monacoRef.current?.setSelection({ startColumn: errorMarker.startColumn, startLineNumber: errorMarker.startLineNumber, endColumn: errorMarker.endColumn, diff --git a/apps/storybook/src/stories/Playground/Editor/utils.ts b/apps/storybook/src/stories/Playground/Editor/utils.ts index f6e1c48b..b32a2d97 100644 --- a/apps/storybook/src/stories/Playground/Editor/utils.ts +++ b/apps/storybook/src/stories/Playground/Editor/utils.ts @@ -1,4 +1,10 @@ -export function findBlockPositionsMonaco(model, blockId) { +import type { TBlockId } from "@gravity-ui/graph"; +import type { IPosition, editor } from "monaco-editor/esm/vs/editor/editor.api"; + +export function findBlockPositionsMonaco( + model: editor.ITextModel, + blockId: TBlockId +): { start: IPosition; end: IPosition } { const configString = model.getValue(); const blockSearchStr = `"id": "${blockId}"`; const startIndex = configString.indexOf(blockSearchStr); diff --git a/apps/storybook/src/stories/Playground/GraphPlayground.tsx b/apps/storybook/src/stories/Playground/GraphPlayground.tsx index 015698bc..83d407b8 100644 --- a/apps/storybook/src/stories/Playground/GraphPlayground.tsx +++ b/apps/storybook/src/stories/Playground/GraphPlayground.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from "react"; -import { ConnectionLayer, ECanDrag, Graph, GraphState, TBlock, TGraphConfig } from "@gravity-ui/graph"; +import { ConnectionLayer, ECanDrag, Graph, GraphState, TBlock, TGraphConfig, TPoint } from "@gravity-ui/graph"; import { GraphBlock, GraphCanvas, HookGraphParams, useGraph, useGraphEvent, useLayer } from "@gravity-ui/graph-react"; import { Flex, @@ -25,6 +25,7 @@ import { GravityActionBlockIS, GravityTextBlockIS, TGravityActionBlock, + TGravityActionBlockWithAnchors, TGravityTextBlock, createActionBlock, createTextBlock, @@ -119,7 +120,7 @@ const pointIcon = { }; // Function for drawing connection line -const drawLine = (start, end) => { +const drawLine = (start: TPoint, end: TPoint) => { const path = new Path2D(); path.moveTo(start.x, start.y); path.lineTo(end.x, end.y); @@ -149,7 +150,7 @@ export function GraphPLayground() { const updateVisibleConfig = useFn(() => { const config = graph.rootStore.getAsConfig(); - editorRef?.current.setContent({ + editorRef.current?.setContent({ blocks: config.blocks || [], connections: config.connections || [], }); @@ -170,15 +171,13 @@ export function GraphPLayground() { }); useGraphEvent(graph, "blocks-selection-change", ({ changes }) => { - editorRef?.current.updateBlocks([ - ...changes.add.map((id) => ({ - ...graph.rootStore.blocksList.getBlock(id), - selected: true, - })), - ...changes.removed.map((id) => ({ - ...graph.rootStore.blocksList.getBlock(id), - selected: false, - })), + const withSelection = (id: TBlock["id"], selected: boolean): TBlock[] => { + const block = graph.rootStore.blocksList.getBlock(id); + return block ? [{ ...block, selected }] : []; + }; + editorRef.current?.updateBlocks([ + ...changes.add.flatMap((id) => withSelection(id, true)), + ...changes.removed.flatMap((id) => withSelection(id, false)), ]); }); @@ -215,7 +214,7 @@ export function GraphPLayground() { if (targetBlockId) { return; } - let block: TBlock; + let block: TGravityActionBlockWithAnchors; const pullSourceAnchor = sourceAnchorId === undefined ? undefined @@ -242,7 +241,7 @@ export function GraphPLayground() { } graph.zoomTo([block.id], { transition: 250 }); updateVisibleConfig(); - editorRef?.current.scrollTo(block.id); + editorRef.current?.scrollTo(block.id); }); useLayoutEffect(() => { @@ -266,7 +265,7 @@ export function GraphPLayground() { graph.api.addBlock(block); graph.zoomTo([block.id], { transition: 250 }); updateVisibleConfig(); - editorRef?.current.scrollTo(block.id); + editorRef.current?.scrollTo(block.id); }); const renderBlockFn = useFn((graph: Graph, block: TBlock) => { @@ -286,7 +285,7 @@ export function GraphPLayground() { useGraphEvent(graph, "blocks-selection-change", ({ list }) => { if (list.length === 1) { - editorRef?.current.scrollTo(list[0]); + editorRef.current?.scrollTo(list[0]); } }); @@ -301,7 +300,7 @@ export function GraphPLayground() { return () => document.body.removeEventListener("keydown", fn); }); - const updateGraphSize = useFn, void>((value) => { + const updateGraphSize = useFn>, void>((value) => { let config: TGraphConfig; setGraphSize(value); @@ -325,6 +324,9 @@ export function GraphPLayground() { config = generatePlaygroundActionBlocks(50, 150); break; } + default: { + return; + } } setEntities(config); graph.zoomTo("center", { transition: 500 }); diff --git a/apps/storybook/src/stories/Playground/Settings.tsx b/apps/storybook/src/stories/Playground/Settings.tsx index 7dafe6da..800e4c33 100644 --- a/apps/storybook/src/stories/Playground/Settings.tsx +++ b/apps/storybook/src/stories/Playground/Settings.tsx @@ -26,7 +26,7 @@ const ConnectionArrowsVariants: SegmentedRadioGroupOptionProps[] = [ export function GraphSettings({ className, graph }: { className: string; graph: Graph }) { const rerender = useRerender(); - const settingBtnRef = useRef(); + const settingBtnRef = useRef(null); const [settingsOpened, setSettingsOpened] = useState(false); return ( <> diff --git a/apps/storybook/src/stories/Playground/TextBlock/TextBlockHtml.tsx b/apps/storybook/src/stories/Playground/TextBlock/TextBlockHtml.tsx index f9597ccf..a49681d4 100644 --- a/apps/storybook/src/stories/Playground/TextBlock/TextBlockHtml.tsx +++ b/apps/storybook/src/stories/Playground/TextBlock/TextBlockHtml.tsx @@ -15,7 +15,7 @@ export function TextBlockHtml({ graph, block }: { graph: Graph; block: TGravityT - {block.meta.text} + {block.meta?.text} {/* */} diff --git a/apps/storybook/src/stories/Playground/TextBlock/index.tsx b/apps/storybook/src/stories/Playground/TextBlock/index.tsx index 3bcc3d3f..e76b74b5 100644 --- a/apps/storybook/src/stories/Playground/TextBlock/index.tsx +++ b/apps/storybook/src/stories/Playground/TextBlock/index.tsx @@ -29,7 +29,7 @@ export class TextBlock extends CanvasBlock { protected renderName(ctx: CanvasRenderingContext2D) { ctx.fillStyle = "rgba(189, 142, 75, 1)"; ctx.textAlign = "center"; - this.renderText(this.state.meta.text, ctx); + this.renderText(this.state.meta?.text ?? "", ctx); } public renderHTML() { diff --git a/apps/storybook/src/stories/Playground/generateLayout.tsx b/apps/storybook/src/stories/Playground/generateLayout.tsx index 7627e816..e4494376 100644 --- a/apps/storybook/src/stories/Playground/generateLayout.tsx +++ b/apps/storybook/src/stories/Playground/generateLayout.tsx @@ -1,7 +1,9 @@ -import { EAnchorType, TBlock, TGraphConfig } from "@gravity-ui/graph"; +import { EAnchorType, TAnchor, TBlock, TConnection } from "@gravity-ui/graph"; export const GravityActionBlockIS = "block-action"; export type TGravityActionBlock = TBlock<{ description: string }> & { is: typeof GravityActionBlockIS }; +/** Action block created by `createActionBlock`: anchors are always `[in, out]`. */ +export type TGravityActionBlockWithAnchors = TGravityActionBlock & { anchors: [TAnchor, TAnchor] }; export const GravityTextBlockIS = "block-text"; export type TGravityTextBlock = TBlock<{ text: string }> & { is: typeof GravityTextBlockIS }; @@ -10,7 +12,7 @@ function getActionBlockId(num: number): string { return `action_${num}`; } -export function createActionBlock(x: number, y: number, index: number): TGravityActionBlock { +export function createActionBlock(x: number, y: number, index: number): TGravityActionBlockWithAnchors { const blockId = getActionBlockId(index); return { @@ -40,7 +42,7 @@ export function createActionBlock(x: number, y: number, index: number): TGravity }; } -export function createTextBlock(x: number, y: number, width: number, index: number, text): TGravityTextBlock { +export function createTextBlock(x: number, y: number, width: number, index: number, text: string): TGravityTextBlock { const blockId = `text_${index}`; return { @@ -59,15 +61,16 @@ export function createTextBlock(x: number, y: number, width: number, index: numb }; } -function getRandomArbitrary(min, max) { +function getRandomArbitrary(min: number, max: number): number { return (Math.random() * (max - min) + min) | 0; } -export function generatePlaygroundActionBlocks(layersCount: number, connectionsPerLayer: number) { - const config: TGraphConfig = { - blocks: [], - connections: [], - }; +export function generatePlaygroundActionBlocks( + layersCount: number, + connectionsPerLayer: number +): { blocks: TGravityActionBlock[]; connections: TConnection[] } { + const blocks: TGravityActionBlock[] = []; + const connections: TConnection[] = []; const gapX = 500; const gapY = 200; @@ -87,23 +90,20 @@ export function generatePlaygroundActionBlocks(layersCount: number, connectionsP const block = createActionBlock(layerX, y, ++index); - config.blocks.push(block); + blocks.push(block); currentLayerBlocks.push(block); } if (i > 1) { for (let c = 0; c <= connectionsPerLayer; c++) { const indexSource = getRandomArbitrary( - config.blocks.length - currentLayerBlocks.length - prevLayerBlocks.length - 1, - config.blocks.length - currentLayerBlocks.length - 1 - ); - const indexTarget = getRandomArbitrary( - config.blocks.length - currentLayerBlocks.length - 1, - config.blocks.length - 1 + blocks.length - currentLayerBlocks.length - prevLayerBlocks.length - 1, + blocks.length - currentLayerBlocks.length - 1 ); + const indexTarget = getRandomArbitrary(blocks.length - currentLayerBlocks.length - 1, blocks.length - 1); if (indexSource !== indexTarget) { const sourceBlockId = getActionBlockId(indexSource); const targetBlockId = getActionBlockId(indexTarget); - config.connections.push({ + connections.push({ sourceBlockId: sourceBlockId, sourceAnchorId: `${sourceBlockId}_out`, targetBlockId: targetBlockId, @@ -115,5 +115,5 @@ export function generatePlaygroundActionBlocks(layersCount: number, connectionsP } } - return config; + return { blocks, connections }; } diff --git a/apps/storybook/src/stories/api/updateBlock/updateBlock.stories.tsx b/apps/storybook/src/stories/api/updateBlock/updateBlock.stories.tsx index b89ca9cb..8fe935d4 100644 --- a/apps/storybook/src/stories/api/updateBlock/updateBlock.stories.tsx +++ b/apps/storybook/src/stories/api/updateBlock/updateBlock.stories.tsx @@ -20,7 +20,11 @@ const GraphApp = () => { const [height, setHeight] = useState(blockConfig.height.toString()); const onClick: ButtonButtonProps["onClick"] = useCallback(() => { - graphRef.current.api.updateBlock({ + const graph = graphRef.current; + if (!graph) { + return; + } + graph.api.updateBlock({ ...blockConfig, x: Number(x), y: Number(y), diff --git a/apps/storybook/src/stories/api/updateConnection/updateConnection.stories.tsx b/apps/storybook/src/stories/api/updateConnection/updateConnection.stories.tsx index 61517937..e88539e1 100644 --- a/apps/storybook/src/stories/api/updateConnection/updateConnection.stories.tsx +++ b/apps/storybook/src/stories/api/updateConnection/updateConnection.stories.tsx @@ -51,7 +51,11 @@ const GraphApp = () => { const graphRef = useRef(undefined); const onConnectionChange: TOnConnectionChange = useCallback((connection) => { - graphRef.current.api.updateConnection("OneLeft:TwoRight", connection); + const graph = graphRef.current; + if (!graph) { + return; + } + graph.api.updateConnection("OneLeft:TwoRight", connection); }, []); const onSourceBlockUpdate: SelectProps["onUpdate"] = useCallback( diff --git a/apps/storybook/src/stories/api/updateGraphColors/updateGraphColors.stories.tsx b/apps/storybook/src/stories/api/updateGraphColors/updateGraphColors.stories.tsx index bd10f0dc..aaef0655 100644 --- a/apps/storybook/src/stories/api/updateGraphColors/updateGraphColors.stories.tsx +++ b/apps/storybook/src/stories/api/updateGraphColors/updateGraphColors.stories.tsx @@ -13,24 +13,36 @@ const GraphApp = () => { const [colors, setColors] = useState(); const graphRef = useRef(undefined); - const updateReactComponentsColors = useCallback((colors) => { + const updateReactComponentsColors = useCallback((colors: TGraphColorsPatch) => { setColors(colors); }, []); const onRedClick: ButtonButtonProps["onClick"] = useCallback(() => { - graphRef.current.api.updateGraphColors({ block: { background: "#febcb8" } }); + const graph = graphRef.current; + if (!graph) { + return; + } + graph.api.updateGraphColors({ block: { background: "#febcb8" } }); updateReactComponentsColors({ block: { background: "#febcb8" } }); }, [updateReactComponentsColors]); const onGreenClick: ButtonButtonProps["onClick"] = useCallback(() => { - graphRef.current.api.updateGraphColors({ block: { background: "#b7e6b0" } }); + const graph = graphRef.current; + if (!graph) { + return; + } + graph.api.updateGraphColors({ block: { background: "#b7e6b0" } }); updateReactComponentsColors({ block: { background: "#b7e6b0" } }); }, [updateReactComponentsColors]); const onYellowClick: ButtonButtonProps["onClick"] = useCallback(() => { - graphRef.current.api.updateGraphColors({ block: { background: "#fee0b0" } }); + const graph = graphRef.current; + if (!graph) { + return; + } + graph.api.updateGraphColors({ block: { background: "#fee0b0" } }); updateReactComponentsColors({ block: { background: "#fee0b0" } }); }, [updateReactComponentsColors]); diff --git a/apps/storybook/src/stories/api/zoomSpeed/zoomSpeed.stories.tsx b/apps/storybook/src/stories/api/zoomSpeed/zoomSpeed.stories.tsx index 0a848af3..40617f0a 100644 --- a/apps/storybook/src/stories/api/zoomSpeed/zoomSpeed.stories.tsx +++ b/apps/storybook/src/stories/api/zoomSpeed/zoomSpeed.stories.tsx @@ -19,7 +19,11 @@ const GraphApp = () => { const graphRef = useRef(undefined); const onApplyZoom: ButtonButtonProps["onClick"] = useCallback(() => { - graphRef.current.setConstants({ + const graph = graphRef.current; + if (!graph) { + return; + } + graph.setConstants({ camera: { SPEED: Number(speed), STEP: Number(step), @@ -28,7 +32,11 @@ const GraphApp = () => { }, [speed, step]); const onApplyPan: ButtonButtonProps["onClick"] = useCallback(() => { - graphRef.current.setConstants({ + const graph = graphRef.current; + if (!graph) { + return; + } + graph.setConstants({ camera: { PAN_SPEED: Number(panSpeed), }, diff --git a/apps/storybook/src/stories/api/zoomToBlocks/zoomToBlocks.stories.tsx b/apps/storybook/src/stories/api/zoomToBlocks/zoomToBlocks.stories.tsx index 36ad7a17..c661b10b 100644 --- a/apps/storybook/src/stories/api/zoomToBlocks/zoomToBlocks.stories.tsx +++ b/apps/storybook/src/stories/api/zoomToBlocks/zoomToBlocks.stories.tsx @@ -19,7 +19,11 @@ const GraphApp = () => { const graphRef = useRef(undefined); const onClick: ButtonButtonProps["onClick"] = useCallback(() => { - graphRef.current.api.zoomToBlocks(blocksId.split(/,\s?/g), { + const graph = graphRef.current; + if (!graph) { + return; + } + graph.api.zoomToBlocks(blocksId.split(/,\s?/g), { transition: Number(transition), padding: Number(padding), }); diff --git a/apps/storybook/src/stories/api/zoomToRect/zoomToRect.stories.tsx b/apps/storybook/src/stories/api/zoomToRect/zoomToRect.stories.tsx index a3e686d2..decadd0c 100644 --- a/apps/storybook/src/stories/api/zoomToRect/zoomToRect.stories.tsx +++ b/apps/storybook/src/stories/api/zoomToRect/zoomToRect.stories.tsx @@ -22,7 +22,11 @@ const GraphApp = () => { const graphRef = useRef(undefined); const onClick: ButtonButtonProps["onClick"] = useCallback(() => { - graphRef.current.api.zoomToRect( + const graph = graphRef.current; + if (!graph) { + return; + } + graph.api.zoomToRect( { height: Number(height), width: Number(width), x: Number(x), y: Number(y) }, { transition: Number(transition), padding: Number(padding) } ); diff --git a/apps/storybook/src/stories/api/zoomToViewPort/zoomToViewPort.stories.tsx b/apps/storybook/src/stories/api/zoomToViewPort/zoomToViewPort.stories.tsx index 6bab1712..4e715708 100644 --- a/apps/storybook/src/stories/api/zoomToViewPort/zoomToViewPort.stories.tsx +++ b/apps/storybook/src/stories/api/zoomToViewPort/zoomToViewPort.stories.tsx @@ -18,7 +18,11 @@ const GraphApp = () => { const graphRef = useRef(undefined); const onClick: ButtonButtonProps["onClick"] = useCallback(() => { - graphRef.current.api.zoomToViewPort({ transition: Number(transition), padding: Number(padding) }); + const graph = graphRef.current; + if (!graph) { + return; + } + graph.api.zoomToViewPort({ transition: Number(transition), padding: Number(padding) }); }, [transition, padding]); return ( diff --git a/apps/storybook/src/stories/configurations/generatePretty.ts b/apps/storybook/src/stories/configurations/generatePretty.ts index b5fde3bd..2fc4b28a 100644 --- a/apps/storybook/src/stories/configurations/generatePretty.ts +++ b/apps/storybook/src/stories/configurations/generatePretty.ts @@ -1,10 +1,10 @@ -import { CanvasBlock, TBlock, TGraphConfig } from "@gravity-ui/graph"; +import { CanvasBlock, TBlock, TConnection, TGraphConfig } from "@gravity-ui/graph"; import { random } from "../../storyUtils"; import { storiesSettings } from "./definitions"; -export function createBlock(x: number, y: number, index): TBlock { +export function createBlock(x: number, y: number, index: number | string): TBlock { const blockId = `block_${index}`; return { id: blockId, @@ -19,10 +19,13 @@ export function createBlock(x: number, y: number, index): TBlock { }; } -function getRandomArbitrary(min, max) { +function getRandomArbitrary(min: number, max: number): number { return (Math.random() * (max - min) + min) | 0; } +/** Generated config: `blocks` and `connections` are always present. */ +export type TGeneratedGraphConfig = TGraphConfig & { blocks: TBlock[]; connections: TConnection[] }; + type Props = { layersCount: number; connectionsPerLayer: number; @@ -37,11 +40,13 @@ export function generatePrettyBlocks({ dashedLine, overrideSettings = {}, startIndex = 0, -}: Props) { - const config: TGraphConfig = { +}: Props): TGeneratedGraphConfig { + const blocks: TBlock[] = []; + const connections: TConnection[] = []; + const config: TGeneratedGraphConfig = { configurationName: "power of 2", - blocks: [], - connections: [], + blocks, + connections, rect: { x: -500, y: -2000, @@ -74,22 +79,19 @@ export function generatePrettyBlocks({ const block = createBlock(layerX, y, startIndex + ++index); - config.blocks.push(block); + blocks.push(block); currentLayerBlocks.push(block); } if (i > 1) { for (let c = 0; c <= connectionsPerLayer; c++) { const indexSource = getRandomArbitrary( - config.blocks.length - currentLayerBlocks.length - prevLayerBlocks.length - 1, - config.blocks.length - currentLayerBlocks.length - 1 - ); - const indexTarget = getRandomArbitrary( - config.blocks.length - currentLayerBlocks.length - 1, - config.blocks.length - 1 + blocks.length - currentLayerBlocks.length - prevLayerBlocks.length - 1, + blocks.length - currentLayerBlocks.length - 1 ); + const indexTarget = getRandomArbitrary(blocks.length - currentLayerBlocks.length - 1, blocks.length - 1); const sourceBlockId = `block_${startIndex + indexSource}`; const targetBlockId = `block_${startIndex + indexTarget}`; - config.connections.push({ + connections.push({ id: `${sourceBlockId}/${targetBlockId}`, sourceBlockId: sourceBlockId, targetBlockId: targetBlockId, @@ -101,8 +103,8 @@ export function generatePrettyBlocks({ } } - console.log("Blocks count: ", config.blocks.length); - console.log("Connections count: ", config.connections.length); + console.log("Blocks count: ", blocks.length); + console.log("Connections count: ", connections.length); return config; } diff --git a/apps/storybook/src/stories/configurations/verticalGraph.ts b/apps/storybook/src/stories/configurations/verticalGraph.ts index b4ea0bd8..c68afcbe 100644 --- a/apps/storybook/src/stories/configurations/verticalGraph.ts +++ b/apps/storybook/src/stories/configurations/verticalGraph.ts @@ -1,4 +1,4 @@ -import { CanvasBlock as Block, TPoint } from "@gravity-ui/graph"; +import { CanvasBlock as Block, TGraphConfig, TPoint } from "@gravity-ui/graph"; import { storiesSettings } from "./definitions"; @@ -11,7 +11,7 @@ class VerticalBlock extends Block { } } -export const verticalGraphConfig = { +export const verticalGraphConfig: TGraphConfig = { configurationName: "simple vertical graph", blocks: [ { diff --git a/apps/storybook/src/stories/examples/connectionLayer/connectionLayer.stories.tsx b/apps/storybook/src/stories/examples/connectionLayer/connectionLayer.stories.tsx index f62a8304..127a8942 100644 --- a/apps/storybook/src/stories/examples/connectionLayer/connectionLayer.stories.tsx +++ b/apps/storybook/src/stories/examples/connectionLayer/connectionLayer.stories.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from "react"; -import { ConnectionLayer, Graph, TBlock } from "@gravity-ui/graph"; +import { ConnectionLayer, Graph, TBlock, TPoint } from "@gravity-ui/graph"; import { GraphCanvas, useGraph } from "@gravity-ui/graph-react"; import { Flex, Hotkey, Switch, Text, ThemeProvider } from "@gravity-ui/uikit"; import type { Meta, StoryFn } from "@storybook/react-webpack5"; @@ -24,7 +24,7 @@ const GraphApp = () => { zoomTo("center", { padding: 300 }); }, [graph]); - const connectionLayerRef = useRef(null); + const connectionLayerRef = useRef(null); useLayoutEffect(() => { // Create icon for creating connections @@ -49,7 +49,7 @@ const GraphApp = () => { }; // Function for drawing connection line - const drawLine = (start, end) => { + const drawLine = (start: TPoint, end: TPoint) => { const path = new Path2D(); path.moveTo(start.x, start.y); path.lineTo(end.x, end.y); @@ -95,24 +95,26 @@ const GraphApp = () => { graph.on("connection-create-drop", (event) => { if (!event.detail.targetBlockId) { const source = graph.rootStore.blocksList.getBlock(event.detail.sourceBlockId); - const block = graph.api.addBlock({ - id: `${graph.rootStore.blocksList.$blocksMap.value.size}`, - name: `from ${source.name}`, - x: event.detail.point.x, - y: event.detail.point.y, - width: source.width, - height: source.height, - is: "block", - selected: false, - anchors: [], - }); - graph.api.addConnection({ - id: `${String(source.id)}-${String(block)}`, - sourceBlockId: event.detail.sourceBlockId, - sourceAnchorId: event.detail.sourceAnchorId, - targetBlockId: block, - targetAnchorId: "bottom", - }); + if (source) { + const block = graph.api.addBlock({ + id: `${graph.rootStore.blocksList.$blocksMap.value.size}`, + name: `from ${source.name}`, + x: event.detail.point.x, + y: event.detail.point.y, + width: source.width, + height: source.height, + is: "block", + selected: false, + anchors: [], + }); + graph.api.addConnection({ + id: `${String(source.id)}-${String(block)}`, + sourceBlockId: event.detail.sourceBlockId, + sourceAnchorId: event.detail.sourceAnchorId, + targetBlockId: block, + targetAnchorId: "bottom", + }); + } } console.log("Connection creation completed:", event.detail); }); @@ -120,10 +122,10 @@ const GraphApp = () => { const switchConnectionEnabled = useFn((enabled: boolean) => { if (enabled) { - connectionLayerRef.current.enable(); + connectionLayerRef.current?.enable(); setEnabled(true); } else { - connectionLayerRef.current.disable(); // Обратите внимание, что в оригинале метод назван disabled, а не disable + connectionLayerRef.current?.disable(); // Обратите внимание, что в оригинале метод назван disabled, а не disable setEnabled(false); } }); diff --git a/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx b/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx index 86207a20..22c1921c 100644 --- a/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx +++ b/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx @@ -17,7 +17,11 @@ type TBlockMeta = { class SpecificBlockView extends CanvasBlock> { public override renderSchematicView() { - this.context.ctx.fillStyle = this.state.meta.color; + const meta = this.state.meta; + if (!meta) { + return; + } + this.context.ctx.fillStyle = meta.color; this.context.ctx.fillRect(this.state.x, this.state.y, this.state.width, this.state.height); } } @@ -26,9 +30,10 @@ const SpecificBlockIs = "some-specific-view"; async function generateByImage() { return new Promise((resolve) => { + const blocks: TBlock[] = []; const config: TGraphConfig> = { configurationName: "power of 2", - blocks: [], + blocks, connections: [], rect: { x: -500, @@ -54,6 +59,10 @@ async function generateByImage() { canvas.width = pic.width; canvas.height = pic.height; const ctx = canvas.getContext("2d"); + if (!ctx) { + console.error("imageByBlocks: 2D canvas context is not available"); + return; + } ctx.drawImage(pic, 0, 0); @@ -63,7 +72,7 @@ async function generateByImage() { if (!colorData[0] && !colorData[1] && !colorData[2]) continue; - config.blocks.push({ + blocks.push({ id: `block_${x}-${y}`, is: SpecificBlockIs, x: x * 10, @@ -79,7 +88,7 @@ async function generateByImage() { }); } } - console.log("Blocks count: ", config.blocks.length); + console.log("Blocks count: ", blocks.length); resolve(config); }; }); diff --git a/apps/storybook/src/stories/examples/imageInsteadBlock/imageInsteadBlock.stories.tsx b/apps/storybook/src/stories/examples/imageInsteadBlock/imageInsteadBlock.stories.tsx index ce385ebe..00ff7a1c 100644 --- a/apps/storybook/src/stories/examples/imageInsteadBlock/imageInsteadBlock.stories.tsx +++ b/apps/storybook/src/stories/examples/imageInsteadBlock/imageInsteadBlock.stories.tsx @@ -34,26 +34,30 @@ type TBlockMeta = { class SpecificBlockView extends CanvasBlock> { public override renderSchematicView() { const blockMetaState = this.state.meta; + if (!blockMetaState) { + return; + } if (blockMetaState.shouldInitImage) { - blockMetaState.img = new Image(); + const img = new Image(); + blockMetaState.img = img; - blockMetaState.img.onload = () => { - const hRatio = this.state.width / blockMetaState.img.width; - const vRatio = this.state.height / blockMetaState.img.height; + img.onload = () => { + const hRatio = this.state.width / img.width; + const vRatio = this.state.height / img.height; const imageRatio = Math.min(hRatio, vRatio); - blockMetaState.imageWidth = blockMetaState.img.width * imageRatio; - blockMetaState.imageHeight = blockMetaState.img.width * imageRatio; + blockMetaState.imageWidth = img.width * imageRatio; + blockMetaState.imageHeight = img.width * imageRatio; blockMetaState.imageOffsetX = (this.state.width - blockMetaState.imageWidth) / 2; blockMetaState.imageOffsetY = (this.state.height - blockMetaState.imageHeight) / 2; this.context.ctx.drawImage( - blockMetaState.img, + img, 0, 0, - blockMetaState.img.width, - blockMetaState.img.height, + img.width, + img.height, this.state.x + blockMetaState.imageOffsetX, this.state.y + blockMetaState.imageOffsetY, blockMetaState.imageWidth, @@ -62,14 +66,15 @@ class SpecificBlockView extends CanvasBlock> { blockMetaState.shouldInitImage = false; }; - blockMetaState.img.src = getImageByStatus(this.state.meta.status); - } else { + img.src = getImageByStatus(blockMetaState.status); + } else if (blockMetaState.img) { + const img = blockMetaState.img; this.context.ctx.drawImage( - blockMetaState.img, + img, 0, 0, - blockMetaState.img.width, - blockMetaState.img.height, + img.width, + img.height, this.state.x + blockMetaState.imageOffsetX, this.state.y + blockMetaState.imageOffsetY, blockMetaState.imageWidth, @@ -81,7 +86,7 @@ class SpecificBlockView extends CanvasBlock> { const SpecificBlockIs = "some-specific-view"; -function getImageByStatus(status: EBlockStatus) { +function getImageByStatus(status: EBlockStatus | undefined): string { switch (status) { case EBlockStatus.DONE: { return imageDone; @@ -106,35 +111,38 @@ const GraphApp = () => { useEffect(() => { const newConfig = generatePrettyBlocks({ layersCount: 10, connectionsPerLayer: 100, dashedLine: true }); - newConfig.settings.blockComponents = {}; - newConfig.settings.blockComponents[SpecificBlockIs] = SpecificBlockView; + newConfig.settings = { + ...newConfig.settings, + blockComponents: { [SpecificBlockIs]: SpecificBlockView }, + }; - newConfig.blocks.forEach((block: TBlock) => { + newConfig.blocks.forEach((block) => { block.is = SpecificBlockIs; const randomVal = Math.floor(Math.random() * 4) + 1; - block.meta = { + const meta: TBlockMeta = { shouldInitImage: true, imageWidth: 1, imageHeight: 1, imageOffsetX: 1, imageOffsetY: 1, }; + block.meta = meta; switch (randomVal) { case 1: { - block.meta.status = EBlockStatus.DONE; + meta.status = EBlockStatus.DONE; break; } case 2: { - block.meta.status = EBlockStatus.FAIL; + meta.status = EBlockStatus.FAIL; break; } case 3: { - block.meta.status = EBlockStatus.RUNNING; + meta.status = EBlockStatus.RUNNING; break; } case 4: default: { - block.meta.status = EBlockStatus.WAITING; + meta.status = EBlockStatus.WAITING; break; } } diff --git a/apps/storybook/src/stories/examples/newBlockLayer/newBlockLayer.stories.tsx b/apps/storybook/src/stories/examples/newBlockLayer/newBlockLayer.stories.tsx index 772bb7f9..157f03d9 100644 --- a/apps/storybook/src/stories/examples/newBlockLayer/newBlockLayer.stories.tsx +++ b/apps/storybook/src/stories/examples/newBlockLayer/newBlockLayer.stories.tsx @@ -20,7 +20,7 @@ const GraphApp = () => { zoomTo("center", { padding: 300 }); }, [graph]); - const newBlockLayerRef = useRef(null); + const newBlockLayerRef = useRef(null); useLayoutEffect(() => { newBlockLayerRef.current = addLayer(NewBlockLayer, {}); @@ -33,10 +33,10 @@ const GraphApp = () => { const switchNewBlockEnabled = useFn((addEnabled: boolean) => { if (addEnabled) { - newBlockLayerRef.current.enable(); + newBlockLayerRef.current?.enable(); setEnabled(true); } else { - newBlockLayerRef.current.disable(); + newBlockLayerRef.current?.disable(); setEnabled(false); } }); diff --git a/apps/storybook/src/stories/examples/portConnectionLayer/portConnectionLayer.stories.tsx b/apps/storybook/src/stories/examples/portConnectionLayer/portConnectionLayer.stories.tsx index a96ba26e..a10179a9 100644 --- a/apps/storybook/src/stories/examples/portConnectionLayer/portConnectionLayer.stories.tsx +++ b/apps/storybook/src/stories/examples/portConnectionLayer/portConnectionLayer.stories.tsx @@ -9,6 +9,7 @@ import { IPortConnectionMeta, PortConnectionLayer, TBlock, + TPoint, createAnchorPortId, } from "@gravity-ui/graph"; import { GraphCanvas, useGraph } from "@gravity-ui/graph-react"; @@ -239,7 +240,7 @@ const GraphApp = () => { zoomTo("center", { padding: 300 }); }, [graph]); - const layerRef = useRef(null); + const layerRef = useRef(null); useLayoutEffect(() => { // Create icon for creating connections @@ -264,7 +265,7 @@ const GraphApp = () => { }; // Function for drawing connection line - const drawLine = (start, end) => { + const drawLine = (start: TPoint, end: TPoint) => { const path = new Path2D(); path.moveTo(start.x, start.y); path.lineTo(end.x, end.y); diff --git a/apps/storybook/src/stories/examples/themeChange/themeChange.stories.tsx b/apps/storybook/src/stories/examples/themeChange/themeChange.stories.tsx index b7904f8e..d1667cf5 100644 --- a/apps/storybook/src/stories/examples/themeChange/themeChange.stories.tsx +++ b/apps/storybook/src/stories/examples/themeChange/themeChange.stories.tsx @@ -51,12 +51,23 @@ const graphColors: TGraphColorsPatch = { }, }; +type TColorGroups = Record | undefined>; + function parseTheme(colors: TGraphColorsPatch): TGraphColorsPatch { - const parsed: TGraphColorsPatch = {}; + const groups: TColorGroups = colors; + const parsed: Record> = {}; - for (const element in colors) { - parsed[element] = {}; - Object.keys(colors[element]).forEach((key) => (parsed[element][key] = computeCssVariable(colors[element][key]))); + for (const element in groups) { + const group = groups[element]; + if (!group) continue; + const parsedGroup: Record = {}; + parsed[element] = parsedGroup; + Object.keys(group).forEach((key) => { + const value = group[key]; + if (value !== undefined) { + parsedGroup[key] = computeCssVariable(value); + } + }); } return parsed; @@ -77,7 +88,11 @@ const GraphApp = () => { // The values of css variables occur after the theme is changed. useEffect(() => { - graphRef.current.api.updateGraphColors(parseTheme(graphColors)); + const graph = graphRef.current; + if (!graph) { + return; + } + graph.api.updateGraphColors(parseTheme(graphColors)); }, [theme]); return ( diff --git a/apps/storybook/src/stories/main/GraphEditor.stories.tsx b/apps/storybook/src/stories/main/GraphEditor.stories.tsx index d3dd60cb..fdb557fc 100644 --- a/apps/storybook/src/stories/main/GraphEditor.stories.tsx +++ b/apps/storybook/src/stories/main/GraphEditor.stories.tsx @@ -91,40 +91,40 @@ export default meta; type Story = StoryObj; export const OneBlock: Story = { - render: (args) => , + render: (args) => , }; export const OneStraightConnection: Story = { args: { useBezierConnections: false, }, - render: (args) => , + render: (args) => , }; export const OneBezierConnection: Story = { - render: (args) => , + render: (args) => , }; export const ColoredConnections: Story = { - render: (args) => , + render: (args) => , }; export const WithAnchors: Story = { - render: (args) => , + render: (args) => , }; export const HundredBlocks: Story = { render: (args) => ( ), }; export const ThousandBlocks: Story = { render: (args) => ( - + ), }; @@ -133,7 +133,7 @@ export const FiveThousandsBlocks: Story = { useBezierConnections: false, }, render: (args) => ( - + ), }; @@ -144,12 +144,12 @@ export const GraphStressTest: Story = { render: (args) => { return ( ); }, @@ -158,40 +158,40 @@ export const NirvanaMaxGraphTest: Story = { render: (args) => { return ( ); }, }; export const CustomSchematicBlock: Story = { - render: (args) => , + render: (args) => , }; export const CustomLayer: Story = { - render: (args) => , + render: (args) => , }; export const VerticalGraph: Story = { - render: (args) => , + render: (args) => , }; export const SnappingGraph: Story = { render: (args) => ( ), }; export const EmptyGraph: Story = { - render: (args) => , + render: (args) => , parameters: { docs: { description: { diff --git a/apps/storybook/src/stories/main/GraphEditor.tsx b/apps/storybook/src/stories/main/GraphEditor.tsx index 01521cf9..c1229ff5 100644 --- a/apps/storybook/src/stories/main/GraphEditor.tsx +++ b/apps/storybook/src/stories/main/GraphEditor.tsx @@ -9,15 +9,16 @@ import { BlockStory } from "./Block"; export type TGraphComponentProps = { config: TGraphConfig; - graphRef?: React.MutableRefObject; + graphRef?: React.MutableRefObject; colors?: TGraphColorsPatch; constants?: NonNullable["constants"]; - renderBlock?: (graphObject: Graph, block: T) => React.JSX.Element; + /** Return `null` to fall back to the default `BlockStory`. */ + renderBlock?: (graphObject: Graph, block: T) => React.JSX.Element | null; }; const action = (name: string) => - (...args) => { + (...args: unknown[]) => { console.log(name, "[", ...args, "]"); }; diff --git a/apps/storybook/src/stories/main/ToolboxLayer.tsx b/apps/storybook/src/stories/main/ToolboxLayer.tsx index ebb776e7..17df1412 100644 --- a/apps/storybook/src/stories/main/ToolboxLayer.tsx +++ b/apps/storybook/src/stories/main/ToolboxLayer.tsx @@ -1,6 +1,6 @@ import React, { ChangeEvent, useCallback } from "react"; -import { Graph, Layer } from "@gravity-ui/graph"; +import { CoreComponent, Graph, Layer, LayerProps } from "@gravity-ui/graph"; import ReactDOM from "react-dom/client"; function Toolbox(props: { graph: Graph }) { @@ -18,18 +18,18 @@ function Toolbox(props: { graph: Graph }) { } export class ToolboxLayer extends Layer { - protected reactRoot: ReactDOM.Root; + protected reactRoot?: ReactDOM.Root; - constructor(props, context) { + constructor(props: LayerProps, parent?: CoreComponent) { super( { html: { zIndex: 300, - className: "custom-control", + classNames: ["custom-control"], }, ...props, }, - context + parent ); } diff --git a/apps/storybook/src/stories/plugins/elk/elk.stories.tsx b/apps/storybook/src/stories/plugins/elk/elk.stories.tsx index 880ac53f..2e1cefe3 100644 --- a/apps/storybook/src/stories/plugins/elk/elk.stories.tsx +++ b/apps/storybook/src/stories/plugins/elk/elk.stories.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo } from "react"; import type { TMultipointConnection } from "@gravity-ui/graph"; -import { Graph, GraphState, MultipointConnection, TBlock, TConnection, TGraphConfig } from "@gravity-ui/graph"; +import { Graph, GraphState, MultipointConnection, TBlock, TConnection } from "@gravity-ui/graph"; import { GraphCanvas, useElk, useGraph, useGraphEvent } from "@gravity-ui/graph-react"; import { ThemeProvider } from "@gravity-ui/uikit"; import { Description, Meta as StorybookMeta, Title } from "@storybook/addon-docs/blocks"; @@ -9,6 +9,7 @@ import type { Meta, StoryFn } from "@storybook/react-webpack5"; import ELK, { ElkNode } from "elkjs"; import { useFn } from "../../../useFn"; +import { TGeneratedGraphConfig } from "../../configurations/generatePretty"; import { BlockStory } from "../../main/Block"; import { getExampleConfig } from "./getExampleConfig"; @@ -29,8 +30,8 @@ export enum Algorithm { import "@gravity-ui/uikit/styles/styles.css"; interface GraphAppProps { - elkConfig?: ElkNode; - graphConfig?: TGraphConfig; + elkConfig: ElkNode; + graphConfig: TGeneratedGraphConfig; } const GraphApp = ({ elkConfig, graphConfig }: GraphAppProps) => { @@ -50,7 +51,7 @@ const GraphApp = ({ elkConfig, graphConfig }: GraphAppProps) => { const connections = graphConfig.connections.reduce< (TConnection & Pick)[] >((acc, connection) => { - if (connection.id in result.edges) { + if (connection.id !== undefined && connection.id in result.edges) { acc.push({ ...connection, ...result.edges[connection.id], diff --git a/apps/storybook/src/stories/plugins/elk/generateExampleTree.ts b/apps/storybook/src/stories/plugins/elk/generateExampleTree.ts index 04b8659e..0a86d563 100644 --- a/apps/storybook/src/stories/plugins/elk/generateExampleTree.ts +++ b/apps/storybook/src/stories/plugins/elk/generateExampleTree.ts @@ -1,9 +1,7 @@ -import { TGraphConfig } from "@gravity-ui/graph"; +import { TGeneratedGraphConfig, createBlock } from "../../configurations/generatePretty"; -import { createBlock } from "../../configurations/generatePretty"; - -export function generateExampleTree(levels: number) { - const tree: Pick = { +export function generateExampleTree(levels: number): TGeneratedGraphConfig { + const tree: TGeneratedGraphConfig = { blocks: [], connections: [], }; diff --git a/apps/storybook/src/stories/plugins/elk/getExampleConfig.ts b/apps/storybook/src/stories/plugins/elk/getExampleConfig.ts index 5c4ec1e6..22488b0f 100644 --- a/apps/storybook/src/stories/plugins/elk/getExampleConfig.ts +++ b/apps/storybook/src/stories/plugins/elk/getExampleConfig.ts @@ -1,15 +1,15 @@ -import { TGraphConfig } from "@gravity-ui/graph"; +import { TBlock, TConnection } from "@gravity-ui/graph"; import { ElkExtendedEdge, ElkNode, LayoutOptions } from "elkjs"; import { measureText } from "../../../storyUtils"; -import { createBlock, generatePrettyBlocks } from "../../configurations/generatePretty"; +import { TGeneratedGraphConfig, createBlock, generatePrettyBlocks } from "../../configurations/generatePretty"; import { Algorithm } from "./elk.stories"; import { generateExampleTree } from "./generateExampleTree"; const FONT_SIZE = 14; -const prepareChildren = (blocks: TGraphConfig["blocks"]) => { +const prepareChildren = (blocks: TBlock[]) => { return blocks.map((b) => { return { id: b.id as string, @@ -19,7 +19,7 @@ const prepareChildren = (blocks: TGraphConfig["blocks"]) => { }); }; -const prepareEdges = (connections: TGraphConfig["connections"], skipLabels?: boolean) => { +const prepareEdges = (connections: TConnection[], skipLabels?: boolean) => { return connections.map((c, i) => { const labelText = `label ${i}`; @@ -87,8 +87,12 @@ const ElkOptionalMap: Record = { }, }; -export const getExampleConfig = (algorithm: Algorithm): { elkConfig: ElkNode; graphConfig: TGraphConfig } => { - let config = generatePrettyBlocks({ layersCount: 10, connectionsPerLayer: 30, dashedLine: true }); +export const getExampleConfig = (algorithm: Algorithm): { elkConfig: ElkNode; graphConfig: TGeneratedGraphConfig } => { + let config: TGeneratedGraphConfig = generatePrettyBlocks({ + layersCount: 10, + connectionsPerLayer: 30, + dashedLine: true, + }); const elkConfig = { id: "root", diff --git a/apps/storybook/tsconfig.json b/apps/storybook/tsconfig.json index 672d114b..68afa0ed 100644 --- a/apps/storybook/tsconfig.json +++ b/apps/storybook/tsconfig.json @@ -15,7 +15,7 @@ "types": [ "node" ], - "strict": false, + "strict": true, "noUncheckedSideEffectImports": true }, "include": [ From 9855ad40bb12336268210a2070ac3c6b29ae7d68 Mon Sep 17 00:00:00 2001 From: draedful Date: Fri, 16 Oct 2026 18:38:52 +0300 Subject: [PATCH 2/3] fix(storybook): handle missing canvas context and numeric ids in editor - imageByBlocks: reject the generation promise when the 2D canvas context is unavailable instead of leaving it pending. - Playground editor: search block ids as JSON serializes them (numeric ids are unquoted), require a value boundary so `1` does not match `12`, and skip scroll/update when the id is not found instead of selecting from the document start. Part of #367 Co-Authored-By: Claude Opus 5.5 --- .../src/stories/Playground/Editor/index.tsx | 28 ++++++++++++------- .../src/stories/Playground/Editor/utils.ts | 23 +++++++++++++-- .../imageByBlocks/imageByBlocks.stories.tsx | 8 ++++-- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/apps/storybook/src/stories/Playground/Editor/index.tsx b/apps/storybook/src/stories/Playground/Editor/index.tsx index 26f2fc3c..969df201 100644 --- a/apps/storybook/src/stories/Playground/Editor/index.tsx +++ b/apps/storybook/src/stories/Playground/Editor/index.tsx @@ -52,8 +52,11 @@ export const ConfigEditor = React.forwardRef(function ConfigEditor( return; } const range = findBlockPositionsMonaco(model, blockId); + if (!range) { + return; + } - if (range?.start.column) { + if (range.start.column) { monacoRef.current?.revealLinesInCenter(range.start.lineNumber, range.end.lineNumber, 0); } @@ -72,18 +75,23 @@ export const ConfigEditor = React.forwardRef(function ConfigEditor( if (!model) { return; } - const edits = blocks.map((block) => { + const edits = blocks.flatMap((block) => { const range = findBlockPositionsMonaco(model, block.id); + if (!range) { + return []; + } const text = JSON.stringify({ block: [block] }, null, 2); - return { - range: { - startColumn: range.start.column, - startLineNumber: range.start.lineNumber, - endColumn: range.end.column, - endLineNumber: range.end.lineNumber, + return [ + { + range: { + startColumn: range.start.column, + startLineNumber: range.start.lineNumber, + endColumn: range.end.column, + endLineNumber: range.end.lineNumber, + }, + text: text.slice(19, text.length - 6), }, - text: text.slice(19, text.length - 6), - }; + ]; }); model.applyEdits(edits); diff --git a/apps/storybook/src/stories/Playground/Editor/utils.ts b/apps/storybook/src/stories/Playground/Editor/utils.ts index b32a2d97..e6bc3e93 100644 --- a/apps/storybook/src/stories/Playground/Editor/utils.ts +++ b/apps/storybook/src/stories/Playground/Editor/utils.ts @@ -1,13 +1,30 @@ import type { TBlockId } from "@gravity-ui/graph"; import type { IPosition, editor } from "monaco-editor/esm/vs/editor/editor.api"; +/** Index of `search` followed by the end of a JSON value, so `"id": 1` does not match `"id": 12`. */ +function findValueIndex(text: string, search: string): number { + let index = text.indexOf(search); + while (index !== -1) { + const next = text[index + search.length]; + if (next === undefined || next === "," || next === "}" || /\s/.test(next)) { + return index; + } + index = text.indexOf(search, index + 1); + } + return -1; +} + export function findBlockPositionsMonaco( model: editor.ITextModel, blockId: TBlockId -): { start: IPosition; end: IPosition } { +): { start: IPosition; end: IPosition } | null { const configString = model.getValue(); - const blockSearchStr = `"id": "${blockId}"`; - const startIndex = configString.indexOf(blockSearchStr); + // Match the id as JSON serializes it: numeric ids are not quoted. + const blockSearchStr = `"id": ${JSON.stringify(blockId)}`; + const startIndex = findValueIndex(configString, blockSearchStr); + if (startIndex === -1) { + return null; + } const blockStart = configString.lastIndexOf("{", startIndex); let blockEnd = configString.indexOf("}", startIndex); diff --git a/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx b/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx index 22c1921c..a46153be 100644 --- a/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx +++ b/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx @@ -29,7 +29,7 @@ class SpecificBlockView extends CanvasBlock> { const SpecificBlockIs = "some-specific-view"; async function generateByImage() { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { const blocks: TBlock[] = []; const config: TGraphConfig> = { configurationName: "power of 2", @@ -60,7 +60,7 @@ async function generateByImage() { canvas.height = pic.height; const ctx = canvas.getContext("2d"); if (!ctx) { - console.error("imageByBlocks: 2D canvas context is not available"); + reject(new Error("imageByBlocks: 2D canvas context is not available")); return; } @@ -104,7 +104,9 @@ const GraphApp = () => { }, []); useEffect(() => { - generateByImage().then((newConfig) => setConfig(newConfig)); + generateByImage() + .then((newConfig) => setConfig(newConfig)) + .catch((error: unknown) => console.error(error)); }, []); if (!config) return null; From 561391d50f4cdf06eb0d8162b3671fd078cb00e4 Mon Sep 17 00:00:00 2001 From: draedful Date: Sun, 18 Oct 2026 01:12:49 +0300 Subject: [PATCH 3/3] refactor(storybook): narrow strict fixes to what the diagnostics require - Editor: keep findBlockPositionsMonaco typed only; drop the numeric-id search change (ids are strings by the editor schema), to be handled separately if needed. - Annotate generated configs instead of extracting local arrays. - Use optional chaining for graphRef in api and themeChange stories. - Simplify parseTheme and imageInsteadBlock narrowing. Part of #367 Co-Authored-By: Claude Opus 5.5 --- .../src/stories/Playground/Editor/index.tsx | 28 +++++++------------ .../src/stories/Playground/Editor/utils.ts | 23 ++------------- .../src/stories/Playground/generateLayout.tsx | 28 +++++++++++-------- .../api/updateBlock/updateBlock.stories.tsx | 6 +--- .../updateConnection.stories.tsx | 6 +--- .../updateGraphColors.stories.tsx | 18 ++---------- .../api/zoomSpeed/zoomSpeed.stories.tsx | 12 ++------ .../api/zoomToBlocks/zoomToBlocks.stories.tsx | 6 +--- .../api/zoomToRect/zoomToRect.stories.tsx | 6 +--- .../zoomToViewPort/zoomToViewPort.stories.tsx | 6 +--- .../stories/configurations/generatePretty.ts | 23 +++++++-------- .../imageByBlocks/imageByBlocks.stories.tsx | 9 +++--- .../imageInsteadBlock.stories.tsx | 7 ++--- .../themeChange/themeChange.stories.tsx | 23 +++------------ .../stories/plugins/elk/getExampleConfig.ts | 6 +--- 15 files changed, 63 insertions(+), 144 deletions(-) diff --git a/apps/storybook/src/stories/Playground/Editor/index.tsx b/apps/storybook/src/stories/Playground/Editor/index.tsx index 969df201..26f2fc3c 100644 --- a/apps/storybook/src/stories/Playground/Editor/index.tsx +++ b/apps/storybook/src/stories/Playground/Editor/index.tsx @@ -52,11 +52,8 @@ export const ConfigEditor = React.forwardRef(function ConfigEditor( return; } const range = findBlockPositionsMonaco(model, blockId); - if (!range) { - return; - } - if (range.start.column) { + if (range?.start.column) { monacoRef.current?.revealLinesInCenter(range.start.lineNumber, range.end.lineNumber, 0); } @@ -75,23 +72,18 @@ export const ConfigEditor = React.forwardRef(function ConfigEditor( if (!model) { return; } - const edits = blocks.flatMap((block) => { + const edits = blocks.map((block) => { const range = findBlockPositionsMonaco(model, block.id); - if (!range) { - return []; - } const text = JSON.stringify({ block: [block] }, null, 2); - return [ - { - range: { - startColumn: range.start.column, - startLineNumber: range.start.lineNumber, - endColumn: range.end.column, - endLineNumber: range.end.lineNumber, - }, - text: text.slice(19, text.length - 6), + return { + range: { + startColumn: range.start.column, + startLineNumber: range.start.lineNumber, + endColumn: range.end.column, + endLineNumber: range.end.lineNumber, }, - ]; + text: text.slice(19, text.length - 6), + }; }); model.applyEdits(edits); diff --git a/apps/storybook/src/stories/Playground/Editor/utils.ts b/apps/storybook/src/stories/Playground/Editor/utils.ts index e6bc3e93..b32a2d97 100644 --- a/apps/storybook/src/stories/Playground/Editor/utils.ts +++ b/apps/storybook/src/stories/Playground/Editor/utils.ts @@ -1,30 +1,13 @@ import type { TBlockId } from "@gravity-ui/graph"; import type { IPosition, editor } from "monaco-editor/esm/vs/editor/editor.api"; -/** Index of `search` followed by the end of a JSON value, so `"id": 1` does not match `"id": 12`. */ -function findValueIndex(text: string, search: string): number { - let index = text.indexOf(search); - while (index !== -1) { - const next = text[index + search.length]; - if (next === undefined || next === "," || next === "}" || /\s/.test(next)) { - return index; - } - index = text.indexOf(search, index + 1); - } - return -1; -} - export function findBlockPositionsMonaco( model: editor.ITextModel, blockId: TBlockId -): { start: IPosition; end: IPosition } | null { +): { start: IPosition; end: IPosition } { const configString = model.getValue(); - // Match the id as JSON serializes it: numeric ids are not quoted. - const blockSearchStr = `"id": ${JSON.stringify(blockId)}`; - const startIndex = findValueIndex(configString, blockSearchStr); - if (startIndex === -1) { - return null; - } + const blockSearchStr = `"id": "${blockId}"`; + const startIndex = configString.indexOf(blockSearchStr); const blockStart = configString.lastIndexOf("{", startIndex); let blockEnd = configString.indexOf("}", startIndex); diff --git a/apps/storybook/src/stories/Playground/generateLayout.tsx b/apps/storybook/src/stories/Playground/generateLayout.tsx index e4494376..d49708ec 100644 --- a/apps/storybook/src/stories/Playground/generateLayout.tsx +++ b/apps/storybook/src/stories/Playground/generateLayout.tsx @@ -65,12 +65,13 @@ function getRandomArbitrary(min: number, max: number): number { return (Math.random() * (max - min) + min) | 0; } -export function generatePlaygroundActionBlocks( - layersCount: number, - connectionsPerLayer: number -): { blocks: TGravityActionBlock[]; connections: TConnection[] } { - const blocks: TGravityActionBlock[] = []; - const connections: TConnection[] = []; +type TPlaygroundConfig = { blocks: TGravityActionBlock[]; connections: TConnection[] }; + +export function generatePlaygroundActionBlocks(layersCount: number, connectionsPerLayer: number): TPlaygroundConfig { + const config: TPlaygroundConfig = { + blocks: [], + connections: [], + }; const gapX = 500; const gapY = 200; @@ -90,20 +91,23 @@ export function generatePlaygroundActionBlocks( const block = createActionBlock(layerX, y, ++index); - blocks.push(block); + config.blocks.push(block); currentLayerBlocks.push(block); } if (i > 1) { for (let c = 0; c <= connectionsPerLayer; c++) { const indexSource = getRandomArbitrary( - blocks.length - currentLayerBlocks.length - prevLayerBlocks.length - 1, - blocks.length - currentLayerBlocks.length - 1 + config.blocks.length - currentLayerBlocks.length - prevLayerBlocks.length - 1, + config.blocks.length - currentLayerBlocks.length - 1 + ); + const indexTarget = getRandomArbitrary( + config.blocks.length - currentLayerBlocks.length - 1, + config.blocks.length - 1 ); - const indexTarget = getRandomArbitrary(blocks.length - currentLayerBlocks.length - 1, blocks.length - 1); if (indexSource !== indexTarget) { const sourceBlockId = getActionBlockId(indexSource); const targetBlockId = getActionBlockId(indexTarget); - connections.push({ + config.connections.push({ sourceBlockId: sourceBlockId, sourceAnchorId: `${sourceBlockId}_out`, targetBlockId: targetBlockId, @@ -115,5 +119,5 @@ export function generatePlaygroundActionBlocks( } } - return { blocks, connections }; + return config; } diff --git a/apps/storybook/src/stories/api/updateBlock/updateBlock.stories.tsx b/apps/storybook/src/stories/api/updateBlock/updateBlock.stories.tsx index 8fe935d4..d7d31175 100644 --- a/apps/storybook/src/stories/api/updateBlock/updateBlock.stories.tsx +++ b/apps/storybook/src/stories/api/updateBlock/updateBlock.stories.tsx @@ -20,11 +20,7 @@ const GraphApp = () => { const [height, setHeight] = useState(blockConfig.height.toString()); const onClick: ButtonButtonProps["onClick"] = useCallback(() => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.api.updateBlock({ + graphRef.current?.api.updateBlock({ ...blockConfig, x: Number(x), y: Number(y), diff --git a/apps/storybook/src/stories/api/updateConnection/updateConnection.stories.tsx b/apps/storybook/src/stories/api/updateConnection/updateConnection.stories.tsx index e88539e1..3bbaff62 100644 --- a/apps/storybook/src/stories/api/updateConnection/updateConnection.stories.tsx +++ b/apps/storybook/src/stories/api/updateConnection/updateConnection.stories.tsx @@ -51,11 +51,7 @@ const GraphApp = () => { const graphRef = useRef(undefined); const onConnectionChange: TOnConnectionChange = useCallback((connection) => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.api.updateConnection("OneLeft:TwoRight", connection); + graphRef.current?.api.updateConnection("OneLeft:TwoRight", connection); }, []); const onSourceBlockUpdate: SelectProps["onUpdate"] = useCallback( diff --git a/apps/storybook/src/stories/api/updateGraphColors/updateGraphColors.stories.tsx b/apps/storybook/src/stories/api/updateGraphColors/updateGraphColors.stories.tsx index aaef0655..aaacb098 100644 --- a/apps/storybook/src/stories/api/updateGraphColors/updateGraphColors.stories.tsx +++ b/apps/storybook/src/stories/api/updateGraphColors/updateGraphColors.stories.tsx @@ -18,31 +18,19 @@ const GraphApp = () => { }, []); const onRedClick: ButtonButtonProps["onClick"] = useCallback(() => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.api.updateGraphColors({ block: { background: "#febcb8" } }); + graphRef.current?.api.updateGraphColors({ block: { background: "#febcb8" } }); updateReactComponentsColors({ block: { background: "#febcb8" } }); }, [updateReactComponentsColors]); const onGreenClick: ButtonButtonProps["onClick"] = useCallback(() => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.api.updateGraphColors({ block: { background: "#b7e6b0" } }); + graphRef.current?.api.updateGraphColors({ block: { background: "#b7e6b0" } }); updateReactComponentsColors({ block: { background: "#b7e6b0" } }); }, [updateReactComponentsColors]); const onYellowClick: ButtonButtonProps["onClick"] = useCallback(() => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.api.updateGraphColors({ block: { background: "#fee0b0" } }); + graphRef.current?.api.updateGraphColors({ block: { background: "#fee0b0" } }); updateReactComponentsColors({ block: { background: "#fee0b0" } }); }, [updateReactComponentsColors]); diff --git a/apps/storybook/src/stories/api/zoomSpeed/zoomSpeed.stories.tsx b/apps/storybook/src/stories/api/zoomSpeed/zoomSpeed.stories.tsx index 40617f0a..b0ed01e7 100644 --- a/apps/storybook/src/stories/api/zoomSpeed/zoomSpeed.stories.tsx +++ b/apps/storybook/src/stories/api/zoomSpeed/zoomSpeed.stories.tsx @@ -19,11 +19,7 @@ const GraphApp = () => { const graphRef = useRef(undefined); const onApplyZoom: ButtonButtonProps["onClick"] = useCallback(() => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.setConstants({ + graphRef.current?.setConstants({ camera: { SPEED: Number(speed), STEP: Number(step), @@ -32,11 +28,7 @@ const GraphApp = () => { }, [speed, step]); const onApplyPan: ButtonButtonProps["onClick"] = useCallback(() => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.setConstants({ + graphRef.current?.setConstants({ camera: { PAN_SPEED: Number(panSpeed), }, diff --git a/apps/storybook/src/stories/api/zoomToBlocks/zoomToBlocks.stories.tsx b/apps/storybook/src/stories/api/zoomToBlocks/zoomToBlocks.stories.tsx index c661b10b..dc973f21 100644 --- a/apps/storybook/src/stories/api/zoomToBlocks/zoomToBlocks.stories.tsx +++ b/apps/storybook/src/stories/api/zoomToBlocks/zoomToBlocks.stories.tsx @@ -19,11 +19,7 @@ const GraphApp = () => { const graphRef = useRef(undefined); const onClick: ButtonButtonProps["onClick"] = useCallback(() => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.api.zoomToBlocks(blocksId.split(/,\s?/g), { + graphRef.current?.api.zoomToBlocks(blocksId.split(/,\s?/g), { transition: Number(transition), padding: Number(padding), }); diff --git a/apps/storybook/src/stories/api/zoomToRect/zoomToRect.stories.tsx b/apps/storybook/src/stories/api/zoomToRect/zoomToRect.stories.tsx index decadd0c..b347ccef 100644 --- a/apps/storybook/src/stories/api/zoomToRect/zoomToRect.stories.tsx +++ b/apps/storybook/src/stories/api/zoomToRect/zoomToRect.stories.tsx @@ -22,11 +22,7 @@ const GraphApp = () => { const graphRef = useRef(undefined); const onClick: ButtonButtonProps["onClick"] = useCallback(() => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.api.zoomToRect( + graphRef.current?.api.zoomToRect( { height: Number(height), width: Number(width), x: Number(x), y: Number(y) }, { transition: Number(transition), padding: Number(padding) } ); diff --git a/apps/storybook/src/stories/api/zoomToViewPort/zoomToViewPort.stories.tsx b/apps/storybook/src/stories/api/zoomToViewPort/zoomToViewPort.stories.tsx index 4e715708..b4f5f12f 100644 --- a/apps/storybook/src/stories/api/zoomToViewPort/zoomToViewPort.stories.tsx +++ b/apps/storybook/src/stories/api/zoomToViewPort/zoomToViewPort.stories.tsx @@ -18,11 +18,7 @@ const GraphApp = () => { const graphRef = useRef(undefined); const onClick: ButtonButtonProps["onClick"] = useCallback(() => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.api.zoomToViewPort({ transition: Number(transition), padding: Number(padding) }); + graphRef.current?.api.zoomToViewPort({ transition: Number(transition), padding: Number(padding) }); }, [transition, padding]); return ( diff --git a/apps/storybook/src/stories/configurations/generatePretty.ts b/apps/storybook/src/stories/configurations/generatePretty.ts index 2fc4b28a..4016965f 100644 --- a/apps/storybook/src/stories/configurations/generatePretty.ts +++ b/apps/storybook/src/stories/configurations/generatePretty.ts @@ -41,12 +41,10 @@ export function generatePrettyBlocks({ overrideSettings = {}, startIndex = 0, }: Props): TGeneratedGraphConfig { - const blocks: TBlock[] = []; - const connections: TConnection[] = []; const config: TGeneratedGraphConfig = { configurationName: "power of 2", - blocks, - connections, + blocks: [], + connections: [], rect: { x: -500, y: -2000, @@ -79,19 +77,22 @@ export function generatePrettyBlocks({ const block = createBlock(layerX, y, startIndex + ++index); - blocks.push(block); + config.blocks.push(block); currentLayerBlocks.push(block); } if (i > 1) { for (let c = 0; c <= connectionsPerLayer; c++) { const indexSource = getRandomArbitrary( - blocks.length - currentLayerBlocks.length - prevLayerBlocks.length - 1, - blocks.length - currentLayerBlocks.length - 1 + config.blocks.length - currentLayerBlocks.length - prevLayerBlocks.length - 1, + config.blocks.length - currentLayerBlocks.length - 1 + ); + const indexTarget = getRandomArbitrary( + config.blocks.length - currentLayerBlocks.length - 1, + config.blocks.length - 1 ); - const indexTarget = getRandomArbitrary(blocks.length - currentLayerBlocks.length - 1, blocks.length - 1); const sourceBlockId = `block_${startIndex + indexSource}`; const targetBlockId = `block_${startIndex + indexTarget}`; - connections.push({ + config.connections.push({ id: `${sourceBlockId}/${targetBlockId}`, sourceBlockId: sourceBlockId, targetBlockId: targetBlockId, @@ -103,8 +104,8 @@ export function generatePrettyBlocks({ } } - console.log("Blocks count: ", blocks.length); - console.log("Connections count: ", connections.length); + console.log("Blocks count: ", config.blocks.length); + console.log("Connections count: ", config.connections.length); return config; } diff --git a/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx b/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx index a46153be..e12e0713 100644 --- a/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx +++ b/apps/storybook/src/stories/examples/imageByBlocks/imageByBlocks.stories.tsx @@ -30,10 +30,9 @@ const SpecificBlockIs = "some-specific-view"; async function generateByImage() { return new Promise((resolve, reject) => { - const blocks: TBlock[] = []; - const config: TGraphConfig> = { + const config: TGraphConfig> & { blocks: TBlock[] } = { configurationName: "power of 2", - blocks, + blocks: [], connections: [], rect: { x: -500, @@ -72,7 +71,7 @@ async function generateByImage() { if (!colorData[0] && !colorData[1] && !colorData[2]) continue; - blocks.push({ + config.blocks.push({ id: `block_${x}-${y}`, is: SpecificBlockIs, x: x * 10, @@ -88,7 +87,7 @@ async function generateByImage() { }); } } - console.log("Blocks count: ", blocks.length); + console.log("Blocks count: ", config.blocks.length); resolve(config); }; }); diff --git a/apps/storybook/src/stories/examples/imageInsteadBlock/imageInsteadBlock.stories.tsx b/apps/storybook/src/stories/examples/imageInsteadBlock/imageInsteadBlock.stories.tsx index 00ff7a1c..05eda94e 100644 --- a/apps/storybook/src/stories/examples/imageInsteadBlock/imageInsteadBlock.stories.tsx +++ b/apps/storybook/src/stories/examples/imageInsteadBlock/imageInsteadBlock.stories.tsx @@ -68,13 +68,12 @@ class SpecificBlockView extends CanvasBlock> { img.src = getImageByStatus(blockMetaState.status); } else if (blockMetaState.img) { - const img = blockMetaState.img; this.context.ctx.drawImage( - img, + blockMetaState.img, 0, 0, - img.width, - img.height, + blockMetaState.img.width, + blockMetaState.img.height, this.state.x + blockMetaState.imageOffsetX, this.state.y + blockMetaState.imageOffsetY, blockMetaState.imageWidth, diff --git a/apps/storybook/src/stories/examples/themeChange/themeChange.stories.tsx b/apps/storybook/src/stories/examples/themeChange/themeChange.stories.tsx index d1667cf5..b312d335 100644 --- a/apps/storybook/src/stories/examples/themeChange/themeChange.stories.tsx +++ b/apps/storybook/src/stories/examples/themeChange/themeChange.stories.tsx @@ -51,23 +51,12 @@ const graphColors: TGraphColorsPatch = { }, }; -type TColorGroups = Record | undefined>; - function parseTheme(colors: TGraphColorsPatch): TGraphColorsPatch { - const groups: TColorGroups = colors; const parsed: Record> = {}; - for (const element in groups) { - const group = groups[element]; - if (!group) continue; - const parsedGroup: Record = {}; - parsed[element] = parsedGroup; - Object.keys(group).forEach((key) => { - const value = group[key]; - if (value !== undefined) { - parsedGroup[key] = computeCssVariable(value); - } - }); + for (const [element, group] of Object.entries(colors)) { + parsed[element] = {}; + Object.entries(group ?? {}).forEach(([key, value]) => (parsed[element][key] = computeCssVariable(value))); } return parsed; @@ -88,11 +77,7 @@ const GraphApp = () => { // The values of css variables occur after the theme is changed. useEffect(() => { - const graph = graphRef.current; - if (!graph) { - return; - } - graph.api.updateGraphColors(parseTheme(graphColors)); + graphRef.current?.api.updateGraphColors(parseTheme(graphColors)); }, [theme]); return ( diff --git a/apps/storybook/src/stories/plugins/elk/getExampleConfig.ts b/apps/storybook/src/stories/plugins/elk/getExampleConfig.ts index 22488b0f..b811e61c 100644 --- a/apps/storybook/src/stories/plugins/elk/getExampleConfig.ts +++ b/apps/storybook/src/stories/plugins/elk/getExampleConfig.ts @@ -88,11 +88,7 @@ const ElkOptionalMap: Record = { }; export const getExampleConfig = (algorithm: Algorithm): { elkConfig: ElkNode; graphConfig: TGeneratedGraphConfig } => { - let config: TGeneratedGraphConfig = generatePrettyBlocks({ - layersCount: 10, - connectionsPerLayer: 30, - dashedLine: true, - }); + let config = generatePrettyBlocks({ layersCount: 10, connectionsPerLayer: 30, dashedLine: true }); const elkConfig = { id: "root",