diff --git a/apps/roam/src/components/Export.tsx b/apps/roam/src/components/Export.tsx index 928e459e1..cbe721e9b 100644 --- a/apps/roam/src/components/Export.tsx +++ b/apps/roam/src/components/Export.tsx @@ -62,7 +62,8 @@ import { } from "@tldraw/editor"; import calcCanvasNodeSizeAndImg from "~/utils/calcCanvasNodeSizeAndImg"; import { - createNodeShapeUtils, + DiscourseNodeUtil, + DISCOURSE_NODE_SHAPE_TYPE, DiscourseNodeShape, } from "~/components/canvas/DiscourseNodeUtil"; import { discourseContext, MAX_WIDTH } from "~/components/canvas/Tldraw"; @@ -360,7 +361,7 @@ const ExportDialog: ExportDialogComponent = ({ ); // UTILS - const discourseNodeUtils = createNodeShapeUtils(allNodes); + const discourseNodeUtils = [DiscourseNodeUtil]; const discourseRelationUtils = createAllRelationShapeUtils(allRelationIds); const referencedNodeUtils = createAllReferencedNodeUtils( @@ -551,12 +552,13 @@ const ExportDialog: ExportDialogComponent = ({ index: currentIndex, rotation: 0, isLocked: false, - type: nodeType, + type: DISCOURSE_NODE_SHAPE_TYPE, props: { w, h, uid: r.uid, title: String(r[firstColumnKey]), + nodeTypeId: nodeType, imageUrl, size: "s", fontFamily: "sans", diff --git a/apps/roam/src/components/canvas/Clipboard.tsx b/apps/roam/src/components/canvas/Clipboard.tsx index d5254313f..a28ff0a79 100644 --- a/apps/roam/src/components/canvas/Clipboard.tsx +++ b/apps/roam/src/components/canvas/Clipboard.tsx @@ -47,6 +47,7 @@ import isDiscourseNode from "~/utils/isDiscourseNode"; import { DiscourseNodeShape, DEFAULT_STYLE_PROPS, + DISCOURSE_NODE_SHAPE_TYPE, FONT_SIZES, } from "./DiscourseNodeUtil"; import { openBlockInSidebar, createBlock } from "roamjs-components/writes"; @@ -658,7 +659,7 @@ const ClipboardPageSection = ({ const shapeId = createShapeId(); const shape = { id: shapeId, - type: nodeType.type, + type: DISCOURSE_NODE_SHAPE_TYPE, x: pagePoint.x - w / 2, y: pagePoint.y - h / 2, props: { @@ -669,6 +670,7 @@ const ClipboardPageSection = ({ imageUrl, size: "s" as TLDefaultSizeStyle, fontFamily: "sans" as TLDefaultFontStyle, + nodeTypeId: nodeType.type, }, }; editor.createShape(shape); diff --git a/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx b/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx index fc2f1d742..6631c4b0d 100644 --- a/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx +++ b/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx @@ -4,7 +4,6 @@ import { TLBaseShape, useEditor, DefaultColorStyle, - Editor, createShapeId, TLDefaultHorizontalAlignStyle, TLDefaultVerticalAlignStyle, @@ -16,6 +15,7 @@ import { DefaultSizeStyle, T, FONT_FAMILIES, + TLShape, TLDefaultFontStyle, DefaultFontStyle, toDomPrecision, @@ -92,6 +92,8 @@ export const COLOR_PALETTE: Record = { }; /* eslint-disable @typescript-eslint/naming-convention */ +export const DISCOURSE_NODE_SHAPE_TYPE = "discourse-node"; + const getRelationIds = () => new Set( Object.values(discourseContext.relations).flatMap((rs) => @@ -106,7 +108,7 @@ export const createNodeShapeTools = ( return class DiscourseNodeTool extends StateNode { static id = n.type; static initial = "idle"; - shapeType = n.type; + nodeTypeId = n.type; override onEnter = () => { this.editor.setCursor({ @@ -120,10 +122,14 @@ export const createNodeShapeTools = ( const shapeId = createShapeId(); this.editor.createShape({ id: shapeId, - type: this.shapeType, + type: DISCOURSE_NODE_SHAPE_TYPE, x: currentPagePoint.x, y: currentPagePoint.y, - props: { fontFamily: "sans", size: "s" }, + props: { + fontFamily: "sans", + size: "s", + nodeTypeId: this.nodeTypeId, + }, }); this.editor.setEditingShape(shapeId); this.editor.setCurrentTool("select"); @@ -132,23 +138,24 @@ export const createNodeShapeTools = ( }); }; -export const createNodeShapeUtils = (nodes: DiscourseNode[]) => { - return nodes.map((node) => { - class DiscourseNodeUtil extends BaseDiscourseNodeUtil { - constructor(editor: Editor) { - super(editor, node.type); - } - static override type = node.type; // removing this gives undefined error - // getDefaultProps(): DiscourseNodeShape["props"] { - // const baseProps = super.getDefaultProps(); - // return { - // ...baseProps, - // color: node.color, - // }; - // } - } - return DiscourseNodeUtil; - }); +type ShapeWithOptionalNodeTypeId = TLShape & { + props?: { + nodeTypeId?: string; + }; +}; + +export const getDiscourseNodeTypeId = ({ + shape, +}: { + shape: ShapeWithOptionalNodeTypeId; +}): string => { + return shape.props?.nodeTypeId || shape.type; +}; + +export const isDiscourseNodeShape = ( + shape: TLShape, +): shape is DiscourseNodeShape => { + return shape.type === DISCOURSE_NODE_SHAPE_TYPE; }; export type DiscourseNodeShape = TLBaseShape< @@ -159,18 +166,14 @@ export type DiscourseNodeShape = TLBaseShape< // opacity: TLOpacityType; uid: string; title: string; + nodeTypeId: string; imageUrl?: string; size: TLDefaultSizeStyle; fontFamily: TLDefaultFontStyle; } >; -export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil { - type: string; - - constructor(editor: Editor, type: string) { - super(editor); - this.type = type; - } +export class DiscourseNodeUtil extends BaseBoxShapeUtil { + static override type = DISCOURSE_NODE_SHAPE_TYPE; static override props = { w: T.number, @@ -178,6 +181,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil // opacity: T.number, uid: T.string, title: T.string, + nodeTypeId: T.string, imageUrl: T.optional(T.string), size: DefaultSizeStyle, fontFamily: DefaultFontStyle, @@ -194,6 +198,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil h: 64, uid: window.roamAlphaAPI.util.generateUID(), title: "", + nodeTypeId: "", size: "s", fontFamily: "sans", }; @@ -239,7 +244,11 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil const nodesInCanvas = Object.fromEntries( allRecords .filter((r): r is DiscourseNodeShape => { - return r.typeName === "shape" && nodeIds.has(r.type); + if (r.typeName !== "shape") return false; + const nodeTypeId = getDiscourseNodeTypeId({ shape: r }); + return ( + r.typeName === "shape" && !!nodeTypeId && nodeIds.has(nodeTypeId) + ); }) .map((r) => [r.props.uid, r] as const), ); @@ -330,12 +339,14 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil editor.createShapes(shapesToCreate).createBindings(bindingsToCreate); } - getColors() { - return getDiscourseNodeColors({ nodeType: this.type }); + getColors(shape: DiscourseNodeShape) { + return getDiscourseNodeColors({ + nodeType: getDiscourseNodeTypeId({ shape }), + }); } async toSvg(shape: DiscourseNodeShape): Promise { - const { backgroundColor, textColor } = this.getColors(); + const { backgroundColor, textColor } = this.getColors(shape); const padding = Number(DEFAULT_STYLE_PROPS.padding.replace("px", "")); const props = shape.props; const bounds = new Box(0, 0, props.w, props.h); @@ -432,7 +443,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil const extensionAPI = useExtensionAPI(); const { canvasSettings: { alias = "", "key-image": isKeyImage = "" } = {}, - } = discourseContext.nodes[shape.type] || {}; + } = discourseContext.nodes[getDiscourseNodeTypeId({ shape })] || {}; // eslint-disable-next-line react-hooks/rules-of-hooks const isOverlayEnabled = useMemo( () => getSetting(DISCOURSE_CONTEXT_OVERLAY_IN_CANVAS_KEY, false), @@ -465,7 +476,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil } }, [setLoaded, loaded, contentRef, shape.props.uid]); - const { backgroundColor, textColor } = this.getColors(); + const { backgroundColor, textColor } = this.getColors(shape); // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { @@ -482,7 +493,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil const { h, w, imageUrl } = await calcCanvasNodeSizeAndImg({ nodeText: text, uid, - nodeType: this.type, + nodeType: getDiscourseNodeTypeId({ shape }), extensionAPI, }); this.updateProps(shape.id, shape.type, { h, w, imageUrl }); @@ -490,7 +501,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil renderModifyNodeDialog({ mode: isCreating ? "create" : "edit", - nodeType: shape.type, + nodeType: getDiscourseNodeTypeId({ shape }), initialValue: { text: shape.props.title, uid: shape.props.uid }, // Only pass it when editing an existing node that has a valid Roam block UID sourceBlockUid: @@ -514,6 +525,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil this.updateProps(shape.id, shape.type, { title: text, uid, + nodeTypeId: getDiscourseNodeTypeId({ shape }), }); const autoCanvasRelations = getSetting( diff --git a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx index 3e809406b..67c8726c7 100644 --- a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx +++ b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx @@ -11,6 +11,7 @@ import { } from "./DiscourseRelationUtil"; import { discourseContext } from "~/components/canvas/Tldraw"; import { dispatchToastEvent } from "~/components/canvas/ToastListener"; +import { getDiscourseNodeTypeId } from "~/components/canvas/DiscourseNodeUtil"; export type AddReferencedNodeType = Record; type ReferenceFormatType = { @@ -77,7 +78,10 @@ export const createAllReferencedNodeTools = ( const sourceType = allAddReferencedNodeByAction[action][0].sourceType; const sourceName = allAddReferencedNodeByAction[action][0].sourceName; - if (target?.type === sourceType) { + if ( + target && + getDiscourseNodeTypeId({ shape: target }) === sourceType + ) { this.shapeType = `${action}`; } else { this.cancelAndWarn(`Starting node must be one of ${sourceName}`); @@ -362,8 +366,11 @@ export const createAllRelationShapeTools = ( // } ); + const targetNodeTypeId = target + ? getDiscourseNodeTypeId({ shape: target }) + : undefined; const relation = discourseContext.relations[name].find( - (r) => r.source === target?.type, + (r) => r.source === targetNodeTypeId, ); if (relation) { this.shapeType = relation.id; diff --git a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationUtil.tsx b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationUtil.tsx index 5e40a22ad..ad03d02f5 100644 --- a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationUtil.tsx +++ b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationUtil.tsx @@ -103,8 +103,10 @@ import createPage from "roamjs-components/writes/createPage"; import openBlockInSidebar from "roamjs-components/writes/openBlockInSidebar"; import triplesToBlocks from "~/utils/triplesToBlocks"; import { - BaseDiscourseNodeUtil, + DiscourseNodeUtil, DiscourseNodeShape, + getDiscourseNodeTypeId, + isDiscourseNodeShape as isDiscourseNodeShapeTypeGuard, } from "~/components/canvas/DiscourseNodeUtil"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; import { AddReferencedNodeType } from "./DiscourseRelationTool"; @@ -146,8 +148,7 @@ export const createAllReferencedNodeUtils = ( static override type = action; isDiscourseNodeShape(shape: TLShape): shape is DiscourseNodeShape { - const shapeUtil = this.editor.getShapeUtil(shape.type); - return shapeUtil instanceof BaseDiscourseNodeUtil; + return isDiscourseNodeShapeTypeGuard(shape); } handleCreateRelationsInRoam = async ({ @@ -177,7 +178,11 @@ export const createAllReferencedNodeUtils = ( const possibleTargets = allAddReferencedNodeByAction[arrow.type].map( (action) => action.destinationType, ); - if (!possibleTargets.includes(target.type)) { + if ( + !possibleTargets.includes( + getDiscourseNodeTypeId({ shape: target }) || "", + ) + ) { return deleteAndWarn( `Target node must be of type ${possibleTargets .map((t) => discourseContext.nodes[t].text) @@ -587,7 +592,7 @@ const asDiscourseNodeShape = ( editor: Editor, ): DiscourseNodeShape | null => { const shapeUtil = editor.getShapeUtil(shape.type); - return shapeUtil instanceof BaseDiscourseNodeUtil + return shapeUtil instanceof DiscourseNodeUtil ? (shape as DiscourseNodeShape) : null; }; @@ -628,7 +633,11 @@ export const createAllRelationShapeUtils = ( .filter((r) => r.source === relation.source) .map((r) => r.destination); - if (!possibleTargets.includes(target.type)) { + if ( + !possibleTargets.includes( + getDiscourseNodeTypeId({ shape: target }) || "", + ) + ) { const uniqueTargets = [...new Set(possibleTargets)]; const uniqueTargetTexts = uniqueTargets.map( (t) => discourseContext.nodes[t].text, @@ -637,8 +646,9 @@ export const createAllRelationShapeUtils = ( `Target node must be of type ${uniqueTargetTexts.join(", ")}`, ); } - if (arrow.type !== target.type) { - editor.updateShapes([{ id: arrow.id, type: target.type }]); + const targetNodeTypeId = getDiscourseNodeTypeId({ shape: target }); + if (targetNodeTypeId && arrow.type !== targetNodeTypeId) { + editor.updateShapes([{ id: arrow.id, type: targetNodeTypeId }]); } if (getStoredRelationsEnabled()) { const sourceAsDNS = asDiscourseNodeShape(source, editor); diff --git a/apps/roam/src/components/canvas/DiscourseRelationShape/discourseRelationMigrations.ts b/apps/roam/src/components/canvas/DiscourseRelationShape/discourseRelationMigrations.ts index 7c94474fc..72be02143 100644 --- a/apps/roam/src/components/canvas/DiscourseRelationShape/discourseRelationMigrations.ts +++ b/apps/roam/src/components/canvas/DiscourseRelationShape/discourseRelationMigrations.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ @@ -14,6 +15,7 @@ import { import { createMigrationIds } from "tldraw"; import { RelationBinding } from "./DiscourseRelationBindings"; import { getRelationColor } from "./DiscourseRelationUtil"; +import { DISCOURSE_NODE_SHAPE_TYPE } from "~/components/canvas/DiscourseNodeUtil"; const SEQUENCE_ID_BASE = "com.roam-research.discourse-graphs"; @@ -35,6 +37,7 @@ export const createMigrations = ({ "2.3.0": 2, AddSizeAndFontFamily: 3, RemoveNullAssetFileSize: 4, + MigrateNodeTypeToDiscourseNode: 5, }); return createMigrationSequence({ sequenceId: `${SEQUENCE_ID_BASE}`, @@ -156,6 +159,16 @@ export const createMigrations = ({ delete asset.props.fileSize; }, }, + { + id: versions["MigrateNodeTypeToDiscourseNode"], + scope: "record", + filter: (r: any) => + r.typeName === "shape" && allNodeTypes.includes(r.type), + up: (shape: any) => { + shape.props.nodeTypeId = shape.type; + shape.type = DISCOURSE_NODE_SHAPE_TYPE; + }, + }, ], }); }; diff --git a/apps/roam/src/components/canvas/DiscourseToolPanel.tsx b/apps/roam/src/components/canvas/DiscourseToolPanel.tsx index 5241973bb..240ae58b8 100644 --- a/apps/roam/src/components/canvas/DiscourseToolPanel.tsx +++ b/apps/roam/src/components/canvas/DiscourseToolPanel.tsx @@ -15,7 +15,11 @@ import { useAtom } from "@tldraw/state-react"; import { TOOL_ARROW_ICON_SVG, NODE_COLOR_ICON_SVG } from "~/icons"; import { getDiscourseNodeColors } from "~/utils/getDiscourseNodeColors"; import { DEFAULT_WIDTH, DEFAULT_HEIGHT } from "./Tldraw"; -import { DEFAULT_STYLE_PROPS, FONT_SIZES } from "./DiscourseNodeUtil"; +import { + DEFAULT_STYLE_PROPS, + DISCOURSE_NODE_SHAPE_TYPE, + FONT_SIZES, +} from "./DiscourseNodeUtil"; export type DiscourseGraphPanelProps = { nodes: DiscourseNode[]; @@ -184,10 +188,14 @@ const DiscourseGraphPanel = ({ const shapeId = createShapeId(); editor.createShape({ id: shapeId, - type: current.item.id, + type: DISCOURSE_NODE_SHAPE_TYPE, x: pagePoint.x - offsetX, y: pagePoint.y - offsetY, - props: { fontFamily: "sans", size: "s" }, + props: { + fontFamily: "sans", + size: "s", + nodeTypeId: current.item.id, + }, }); editor.setEditingShape(shapeId); editor.setCurrentTool("select"); diff --git a/apps/roam/src/components/canvas/Tldraw.tsx b/apps/roam/src/components/canvas/Tldraw.tsx index e77e91dcd..538b51e88 100644 --- a/apps/roam/src/components/canvas/Tldraw.tsx +++ b/apps/roam/src/components/canvas/Tldraw.tsx @@ -38,7 +38,6 @@ import { TLAssetId, getHashForString, TLShapeId, - TLShape, TLStore, TLStoreWithStatus, useToasts, @@ -65,9 +64,11 @@ import { createUiOverrides, } from "./uiOverrides"; import { - BaseDiscourseNodeUtil, + DiscourseNodeUtil, DiscourseNodeShape, createNodeShapeTools, + DISCOURSE_NODE_SHAPE_TYPE, + isDiscourseNodeShape, } from "./DiscourseNodeUtil"; import { useRoamStore } from "./useRoamStore"; import { @@ -506,12 +507,6 @@ const TldrawCanvasShared = ({ ); }; - const isDiscourseNodeShape = ( - shape: TLShape, - ): shape is DiscourseNodeShape => { - return allNodes.some((node) => node.type === shape.type); - }; - // Add state for tracking relation creation const relationCreationRef = useRef<{ isCreating: boolean; @@ -807,13 +802,14 @@ const TldrawCanvasShared = ({ if (nodeType) { app.createShapes([ { - type: nodeType.type, + type: DISCOURSE_NODE_SHAPE_TYPE, id: createShapeId(), props: { uid: e.detail.uid, title: e.detail.val, size: "s", fontFamily: "sans", + nodeTypeId: nodeType.type, }, ...position, }, @@ -1161,7 +1157,7 @@ const InsideEditorAndUiContext = ({ editor.createShapes([ { id: createShapeId(), - type: nodeType, + type: DISCOURSE_NODE_SHAPE_TYPE, x: position.x - w / 2, y: position.y - h / 2, props: { @@ -1172,6 +1168,7 @@ const InsideEditorAndUiContext = ({ ...(imageUrl && { imageUrl }), size: "s", fontFamily: "sans", + nodeTypeId: nodeType, }, }, ]); @@ -1439,7 +1436,7 @@ const InsideEditorAndUiContext = ({ const removeAfterCreateHandler = editor.sideEffects.registerAfterCreateHandler("shape", (shape) => { const util = editor.getShapeUtil(shape); - if (util instanceof BaseDiscourseNodeUtil) { + if (util instanceof DiscourseNodeUtil) { const autoCanvasRelations = getSetting( AUTO_CANVAS_RELATIONS_KEY, false, diff --git a/apps/roam/src/components/canvas/uiOverrides.tsx b/apps/roam/src/components/canvas/uiOverrides.tsx index 049c84edf..af7ad1466 100644 --- a/apps/roam/src/components/canvas/uiOverrides.tsx +++ b/apps/roam/src/components/canvas/uiOverrides.tsx @@ -57,6 +57,7 @@ import { CustomDefaultToolbar } from "./CustomDefaultToolbar"; import { renderModifyNodeDialog } from "~/components/ModifyNodeDialog"; import { CanvasSyncMode } from "./canvasSyncMode"; import posthog from "posthog-js"; +import { DISCOURSE_NODE_SHAPE_TYPE } from "./DiscourseNodeUtil"; const SyncModeMenuSwitchItem = ({ checked, @@ -178,7 +179,7 @@ export const getOnSelectForShape = ({ }); editor.createShapes([ { - type: nodeType, + type: DISCOURSE_NODE_SHAPE_TYPE, id: createShapeId(), props: { uid, @@ -188,6 +189,7 @@ export const getOnSelectForShape = ({ imageUrl: nodeImageUrl, fontFamily: "sans", size: "s", + nodeTypeId: nodeType, }, x, y, diff --git a/apps/roam/src/components/canvas/useCanvasStoreAdapterArgs.ts b/apps/roam/src/components/canvas/useCanvasStoreAdapterArgs.ts index 4791d6f1d..045e29568 100644 --- a/apps/roam/src/components/canvas/useCanvasStoreAdapterArgs.ts +++ b/apps/roam/src/components/canvas/useCanvasStoreAdapterArgs.ts @@ -5,7 +5,7 @@ import { TLAnyShapeUtilConstructor, } from "tldraw"; import { DiscourseNode } from "~/utils/getDiscourseNodes"; -import { createNodeShapeUtils } from "./DiscourseNodeUtil"; +import { DiscourseNodeUtil } from "./DiscourseNodeUtil"; import { createAllReferencedNodeUtils, createAllRelationShapeUtils, @@ -56,7 +56,6 @@ const getUtilTypes = ({ }; const createShapeUtils = ({ - allNodes, allRelationIds, allAddReferencedNodeByAction, }: { @@ -65,7 +64,7 @@ const createShapeUtils = ({ allAddReferencedNodeByAction: AddReferencedNodeType; }): TLAnyShapeUtilConstructor[] => { return [ - ...createNodeShapeUtils(allNodes), + DiscourseNodeUtil, ...createAllRelationShapeUtils(allRelationIds), ...createAllReferencedNodeUtils(allAddReferencedNodeByAction), ]; diff --git a/apps/roam/src/utils/syncCanvasNodeTitlesOnLoad.ts b/apps/roam/src/utils/syncCanvasNodeTitlesOnLoad.ts index acd74df5d..de4f94f3a 100644 --- a/apps/roam/src/utils/syncCanvasNodeTitlesOnLoad.ts +++ b/apps/roam/src/utils/syncCanvasNodeTitlesOnLoad.ts @@ -1,5 +1,8 @@ import type { Editor } from "tldraw"; -import type { DiscourseNodeShape } from "~/components/canvas/DiscourseNodeUtil"; +import { + DISCOURSE_NODE_SHAPE_TYPE, + type DiscourseNodeShape, +} from "~/components/canvas/DiscourseNodeUtil"; /** * Query Roam for current :node/title or :block/string for each uid. @@ -64,12 +67,13 @@ export const syncCanvasNodeTitlesOnLoad = async ( const nodeTypeSet = new Set(nodeTypeIds); const relationIds = new Set(relationShapeTypeIds); const allRecords = editor.store.allRecords(); - const discourseNodeShapes = allRecords.filter( - (r) => - r.typeName === "shape" && - nodeTypeSet.has((r as DiscourseNodeShape).type) && - typeof (r as DiscourseNodeShape).props?.uid === "string", - ) as DiscourseNodeShape[]; + const discourseNodeShapes = allRecords.filter((r) => { + if (r.typeName !== "shape") return false; + if (r.type !== DISCOURSE_NODE_SHAPE_TYPE) return false; + const shape = r as DiscourseNodeShape; + if (!nodeTypeSet.has(shape.props.nodeTypeId)) return false; + return typeof shape.props?.uid === "string"; + }) as DiscourseNodeShape[]; const uids = [...new Set(discourseNodeShapes.map((s) => s.props.uid))]; if (uids.length === 0) return;