From a6091e90e1a63ecd15d22ae0b7c22aedaa17b7ea Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Sun, 15 Mar 2026 19:41:03 -0600 Subject: [PATCH] Refactor Discourse Node Handling and Introduce Node Type ID - Replaced the use of createNodeShapeUtils with DiscourseNodeUtil across components to streamline discourse node management. - Introduced DISCOURSE_NODE_SHAPE_TYPE constant for consistent node type identification. - Updated various components to utilize nodeTypeId for better clarity and functionality in shape creation and manipulation. - Enhanced migration logic to accommodate new node type structure, ensuring backward compatibility and improved data integrity. This refactor improves the maintainability and readability of the codebase while ensuring that new features can be integrated more seamlessly. --- apps/roam/src/components/Export.tsx | 8 +- apps/roam/src/components/canvas/Clipboard.tsx | 4 +- .../components/canvas/DiscourseNodeUtil.tsx | 91 +++++++++++-------- .../DiscourseRelationTool.tsx | 13 ++- .../DiscourseRelationUtil.tsx | 31 ++++--- .../discourseRelationMigrations.ts | 13 +++ .../components/canvas/DiscourseToolPanel.tsx | 14 ++- apps/roam/src/components/canvas/Tldraw.tsx | 12 ++- .../roam/src/components/canvas/canvasUtils.ts | 4 +- .../src/components/canvas/uiOverrides.tsx | 4 +- .../canvas/useCanvasStoreAdapterArgs.ts | 5 +- .../src/utils/syncCanvasNodeTitlesOnLoad.ts | 18 ++-- 12 files changed, 140 insertions(+), 77 deletions(-) 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 fcd86b194..1d4d7f3fa 100644 --- a/apps/roam/src/components/canvas/Clipboard.tsx +++ b/apps/roam/src/components/canvas/Clipboard.tsx @@ -49,6 +49,7 @@ import getAllReferencesOnPage from "~/utils/getAllReferencesOnPage"; import { DiscourseNodeShape, DEFAULT_STYLE_PROPS, + DISCOURSE_NODE_SHAPE_TYPE, FONT_SIZES, } from "./DiscourseNodeUtil"; import { openBlockInSidebar, createBlock } from "roamjs-components/writes"; @@ -702,7 +703,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: { @@ -713,6 +714,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 4c1cb211a..bcf52d6d7 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, @@ -94,6 +94,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) => @@ -109,7 +111,7 @@ export const createNodeShapeTools = ( static id = n.type; static initial = "idle"; static isLockable = true; - shapeType = n.type; + nodeTypeId = n.type; override onEnter = () => { this.editor.setCursor({ @@ -123,10 +125,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); }; @@ -134,23 +140,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< @@ -161,18 +168,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, @@ -180,6 +183,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, @@ -196,6 +200,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil h: 64, uid: window.roamAlphaAPI.util.generateUID(), title: "", + nodeTypeId: "", size: "s", fontFamily: "sans", }; @@ -241,7 +246,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), ); @@ -332,12 +341,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); @@ -434,7 +445,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( () => getPersonalSetting([PERSONAL_KEYS.overlayInCanvas]), @@ -450,7 +461,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil // Detect discourse node tags in block text for blck-node shapes // eslint-disable-next-line react-hooks/rules-of-hooks const matchedNodeForConversion = useMemo(() => { - if (shape.type !== "blck-node") return null; + if (getDiscourseNodeTypeId({ shape }) !== "blck-node") return null; if (!isLiveBlock(shape.props.uid)) return null; const blockText = getTextByBlockUid(shape.props.uid); if (!blockText) return null; @@ -471,9 +482,9 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil } } return null; - }, [shape.type, shape.props.uid]); + }, [shape]); - const { backgroundColor, textColor } = this.getColors(); + const { backgroundColor, textColor } = this.getColors(shape); const showEmbeddedRoamBlock = !isPageUid(shape.props.uid) && isLiveBlock(shape.props.uid); @@ -492,7 +503,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 }); @@ -513,7 +524,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: @@ -538,6 +549,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil this.updateProps(shape.id, shape.type, { title: text, uid, + nodeTypeId: getDiscourseNodeTypeId({ shape }), }); const autoCanvasRelations = getPersonalSetting([ @@ -660,7 +672,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil }); editor.createShapes([ { - type: node.type, + type: DISCOURSE_NODE_SHAPE_TYPE, id: createShapeId(), props: { uid, @@ -670,6 +682,7 @@ export class BaseDiscourseNodeUtil extends BaseBoxShapeUtil imageUrl: nodeImageUrl, fontFamily: "sans", size: "s", + nodeTypeId: node.type, }, x, y, diff --git a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx index 801ce1cff..933e2ed69 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 = { @@ -78,7 +79,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}`); @@ -360,8 +364,13 @@ export const createAllRelationShapeTools = ( // } ); + const targetNodeTypeId = target + ? getDiscourseNodeTypeId({ shape: target }) + : undefined; const relation = discourseContext.relations[name].find( - (r) => r.source === target?.type || r.destination === target?.type, + (r) => + r.source === targetNodeTypeId || + r.destination === 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 69fee29d3..707841f01 100644 --- a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationUtil.tsx +++ b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationUtil.tsx @@ -104,8 +104,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 { checkConnectionType } from "~/components/canvas/canvasUtils"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; @@ -148,8 +150,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 ({ @@ -179,7 +180,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) @@ -589,7 +594,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; }; @@ -627,8 +632,8 @@ export const createAllRelationShapeUtils = ( const relation = relations.find((r) => r.id === arrow.type); if (!relation) return; - const sourceNodeType = source.type; - const targetNodeType = target.type; + const sourceNodeType = getDiscourseNodeTypeId({ shape: source }); + const targetNodeType = getDiscourseNodeTypeId({ shape: target }); // Check all relations with the same label for a match const { @@ -892,8 +897,10 @@ export const createAllRelationShapeUtils = ( ) { const sourceNodeId = otherBinding.toId; const sourceNode = this.editor.getShape(sourceNodeId); - const targetNodeType = target.type; - const sourceNodeType = sourceNode?.type; + const targetNodeType = getDiscourseNodeTypeId({ shape: target }); + const sourceNodeType = sourceNode + ? getDiscourseNodeTypeId({ shape: sourceNode }) + : undefined; if (sourceNodeType && targetNodeType && shape.type) { const isValidConnection = this.isValidNodeConnection( @@ -997,8 +1004,10 @@ export const createAllRelationShapeUtils = ( const endNode = this.editor.getShape(newBindings.end.toId); if (startNode && endNode) { - const startNodeType = startNode.type; - const endNodeType = endNode.type; + const startNodeType = getDiscourseNodeTypeId({ + shape: startNode, + }); + const endNodeType = getDiscourseNodeTypeId({ shape: endNode }); const { isReverse, matchingRelation } = this.checkConnectionTypeAcrossLabel( diff --git a/apps/roam/src/components/canvas/DiscourseRelationShape/discourseRelationMigrations.ts b/apps/roam/src/components/canvas/DiscourseRelationShape/discourseRelationMigrations.ts index 7584dea72..bece10b5c 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 c30dbc714..e9fda93f8 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); } else { diff --git a/apps/roam/src/components/canvas/Tldraw.tsx b/apps/roam/src/components/canvas/Tldraw.tsx index 92ce8ae74..4fe6db681 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, @@ -67,9 +66,10 @@ import { createUiOverrides, } from "./uiOverrides"; import { - BaseDiscourseNodeUtil, + DiscourseNodeUtil, DiscourseNodeShape, createNodeShapeTools, + DISCOURSE_NODE_SHAPE_TYPE, } from "./DiscourseNodeUtil"; import { useRoamStore } from "./useRoamStore"; import { @@ -847,13 +847,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, }, @@ -1210,7 +1211,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: { @@ -1221,6 +1222,7 @@ const InsideEditorAndUiContext = ({ ...(imageUrl && { imageUrl }), size: "s", fontFamily: "sans", + nodeTypeId: nodeType, }, }, ]); @@ -1488,7 +1490,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 = getPersonalSetting([ PERSONAL_KEYS.autoCanvasRelations, ]); diff --git a/apps/roam/src/components/canvas/canvasUtils.ts b/apps/roam/src/components/canvas/canvasUtils.ts index 8f8845cac..5bb71dbbc 100644 --- a/apps/roam/src/components/canvas/canvasUtils.ts +++ b/apps/roam/src/components/canvas/canvasUtils.ts @@ -1,6 +1,6 @@ import { Editor, TLShape } from "tldraw"; import { - BaseDiscourseNodeUtil, + DiscourseNodeUtil, DiscourseNodeShape, } from "~/components/canvas/DiscourseNodeUtil"; import { discourseContext } from "~/components/canvas/Tldraw"; @@ -10,7 +10,7 @@ export const isDiscourseNodeShape = ( shape: TLShape, ): shape is DiscourseNodeShape => { try { - return editor.getShapeUtil(shape) instanceof BaseDiscourseNodeUtil; + return editor.getShapeUtil(shape) instanceof DiscourseNodeUtil; } catch { return false; } diff --git a/apps/roam/src/components/canvas/uiOverrides.tsx b/apps/roam/src/components/canvas/uiOverrides.tsx index cc0dee602..77c68118a 100644 --- a/apps/roam/src/components/canvas/uiOverrides.tsx +++ b/apps/roam/src/components/canvas/uiOverrides.tsx @@ -60,6 +60,7 @@ import { CanvasSyncMode } from "./canvasSyncMode"; import { getPersonalSetting } from "~/components/settings/utils/accessors"; import { PERSONAL_KEYS } from "~/components/settings/utils/settingKeys"; import posthog from "posthog-js"; +import { DISCOURSE_NODE_SHAPE_TYPE } from "./DiscourseNodeUtil"; const SyncModeMenuSwitchItem = ({ checked, @@ -182,7 +183,7 @@ export const getOnSelectForShape = ({ }); editor.createShapes([ { - type: nodeType, + type: DISCOURSE_NODE_SHAPE_TYPE, id: createShapeId(), props: { uid, @@ -192,6 +193,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;