diff --git a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx index 59b81387d..3de61e49e 100644 --- a/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx +++ b/apps/roam/src/components/canvas/DiscourseRelationShape/DiscourseRelationTool.tsx @@ -10,6 +10,7 @@ import { } from "./DiscourseRelationUtil"; import { discourseContext } from "~/components/canvas/Tldraw"; import { dispatchToastEvent } from "~/components/canvas/ToastListener"; +import { isRelationComplete } from "~/utils/isRelationComplete"; export type AddReferencedNodeType = Record; type ReferenceFormatType = { @@ -341,6 +342,17 @@ export const createAllRelationShapeTools = ( override onEnter = () => { this.didTimeout = false; + const selectedRelations = discourseContext.relations[name] || []; + const hasIncompleteSelectedRelation = selectedRelations.some( + (relation) => !isRelationComplete(relation), + ); + if (hasIncompleteSelectedRelation) { + this.cancelAndWarn( + "Relation type is incomplete. Set label, complement, source, and destination in settings.", + ); + return; + } + const target = this.editor.getShapeAtPoint( this.editor.inputs.currentPagePoint, // { diff --git a/apps/roam/src/components/canvas/overlays/RelationTypeDropdown.tsx b/apps/roam/src/components/canvas/overlays/RelationTypeDropdown.tsx index 758a94033..cba68cb11 100644 --- a/apps/roam/src/components/canvas/overlays/RelationTypeDropdown.tsx +++ b/apps/roam/src/components/canvas/overlays/RelationTypeDropdown.tsx @@ -6,6 +6,7 @@ import { getAllRelations, isDiscourseNodeShape, } from "~/components/canvas/canvasUtils"; +import { isRelationComplete } from "~/utils/isRelationComplete"; type RelationTypeDropdownProps = { sourceId: TLShapeId; @@ -47,6 +48,7 @@ export const RelationTypeDropdown = ({ const seenLabels = new Set(); for (const relation of allRelations) { + if (!isRelationComplete(relation)) continue; const { isDirect: isForward, isReverse } = checkConnectionType( relation, startNodeType, diff --git a/apps/roam/src/components/settings/DiscourseRelationConfigPanel.tsx b/apps/roam/src/components/settings/DiscourseRelationConfigPanel.tsx index 7b767b77c..7ad12d8f5 100644 --- a/apps/roam/src/components/settings/DiscourseRelationConfigPanel.tsx +++ b/apps/roam/src/components/settings/DiscourseRelationConfigPanel.tsx @@ -44,6 +44,7 @@ import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageU import updateBlock from "roamjs-components/writes/updateBlock"; import getTextByBlockUid from "roamjs-components/queries/getTextByBlockUid"; import getDiscourseNodes from "~/utils/getDiscourseNodes"; +import { isRelationComplete } from "~/utils/isRelationComplete"; import { getConditionLabels } from "~/utils/conditionToDatalog"; import { formatHexColor } from "./DiscourseNodeCanvasSettings"; import posthog from "posthog-js"; @@ -516,7 +517,14 @@ export const RelationEditPanel = ({ const relationEl = document.getElementById("relation-label"); relationEl?.focus(); } - }, []); + }, [label]); + + const isEditingRelationComplete = isRelationComplete({ + label, + complement, + source, + destination, + }); return ( <> @@ -535,7 +543,7 @@ export const RelationEditPanel = ({ icon={"floppy-disk"} text={"Save"} intent={Intent.PRIMARY} - disabled={loading || !hasChanges} + disabled={loading || !hasChanges || !isEditingRelationComplete} className="select-none" onClick={() => { setLoading(true); diff --git a/apps/roam/src/utils/isRelationComplete.ts b/apps/roam/src/utils/isRelationComplete.ts new file mode 100644 index 000000000..01fa2d877 --- /dev/null +++ b/apps/roam/src/utils/isRelationComplete.ts @@ -0,0 +1,19 @@ +import type { DiscourseRelation } from "./getDiscourseRelations"; + +const PLACEHOLDER_VALUES = new Set(["?"]); + +const isNonEmptyNonPlaceholder = ( + value: string | null | undefined, +): boolean => { + if (!value) return false; + const trimmed = value.trim(); + return trimmed.length > 0 && !PLACEHOLDER_VALUES.has(trimmed); +}; + +export const isRelationComplete = ( + relation: Partial, +): boolean => + isNonEmptyNonPlaceholder(relation.label) && + isNonEmptyNonPlaceholder(relation.complement) && + isNonEmptyNonPlaceholder(relation.source) && + isNonEmptyNonPlaceholder(relation.destination);