diff --git a/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx b/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx index 0c4150b7..90ca4395 100644 --- a/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx +++ b/apps/roam/src/components/canvas/DiscourseNodeUtil.tsx @@ -33,7 +33,6 @@ import createDiscourseNode from "~/utils/createDiscourseNode"; import { DiscourseNode } from "~/utils/getDiscourseNodes"; import { isPageUid } from "./Tldraw"; import LabelDialog from "./LabelDialog"; -import { colord } from "colord"; import { discourseContext } from "./Tldraw"; import getDiscourseContextResults from "~/utils/getDiscourseContextResults"; import calcCanvasNodeSizeAndImg from "~/utils/calcCanvasNodeSizeAndImg"; @@ -46,7 +45,7 @@ import { } from "~/data/userSettings"; import { getSetting } from "~/utils/extensionSettings"; import DiscourseContextOverlay from "~/components/DiscourseContextOverlay"; -import getPleasingColors from "@repo/utils/getPleasingColors"; +import { getDiscourseNodeColors } from "~/utils/getDiscourseNodeColors"; // TODO REPLACE WITH TLDRAW DEFAULTS // https://github.com/tldraw/tldraw/pull/1580/files @@ -58,7 +57,7 @@ const TEXT_PROPS = { padding: "0px", maxWidth: "auto", }; -const FONT_SIZES: Record = { +export const FONT_SIZES: Record = { m: 25, l: 38, xl: 48, @@ -343,26 +342,7 @@ export class BaseDiscourseNodeUtil extends ShapeUtil { } getColors() { - const { - canvasSettings: { color: setColor = "" } = {}, - index: discourseNodeIndex = -1, - } = discourseContext.nodes[this.type] || {}; - const paletteColor = - COLOR_ARRAY[ - discourseNodeIndex >= 0 && discourseNodeIndex < COLOR_ARRAY.length - 1 - ? discourseNodeIndex - : 0 - ]; - const formattedTextColor = - setColor && !setColor.startsWith("#") ? `#${setColor}` : setColor; - - const canvasSelectedColor = formattedTextColor - ? formattedTextColor - : COLOR_PALETTE[paletteColor]; - const pleasingColors = getPleasingColors(colord(canvasSelectedColor)); - const backgroundColor = pleasingColors.background; - const textColor = pleasingColors.text; - return { backgroundColor, textColor }; + return getDiscourseNodeColors({ nodeType: this.type }); } async toSvg(shape: DiscourseNodeShape): Promise { diff --git a/apps/roam/src/components/canvas/DiscourseToolPanel.tsx b/apps/roam/src/components/canvas/DiscourseToolPanel.tsx index ca19bcca..25b7ea54 100644 --- a/apps/roam/src/components/canvas/DiscourseToolPanel.tsx +++ b/apps/roam/src/components/canvas/DiscourseToolPanel.tsx @@ -6,12 +6,16 @@ import { Vec, Box, createShapeId, + FONT_FAMILIES, } from "tldraw"; import { DiscourseNode } from "~/utils/getDiscourseNodes"; import { formatHexColor } from "~/components/settings/DiscourseNodeCanvasSettings"; import { getRelationColor } from "./DiscourseRelationShape/DiscourseRelationUtil"; import { useAtom } from "@tldraw/state"; 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"; export type DiscourseGraphPanelProps = { nodes: DiscourseNode[]; @@ -30,6 +34,8 @@ type DragState = type: "node" | "relation"; id: string; text: string; + backgroundColor: string; + textColor: string; color: string; }; startPosition: Vec; @@ -40,6 +46,8 @@ type DragState = type: "node" | "relation"; id: string; text: string; + backgroundColor: string; + textColor: string; color: string; }; currentPosition: Vec; @@ -72,20 +80,32 @@ const DiscourseGraphPanel = ({ ); const panelItems = useMemo(() => { - const nodeItems = nodes.map((node) => ({ - type: "node" as const, - id: node.type, - text: node.text, - color: formatHexColor(node.canvasSettings.color) || "black", - shortcut: node.shortcut, - })); - - const relationItems = uniqueRelations.map((relation, index) => ({ - type: "relation" as const, - id: relation, - text: relation, - color: getRelationColor(relation, index), - })); + const nodeItems = nodes.map((node) => { + const { backgroundColor, textColor } = getDiscourseNodeColors({ + nodeType: node.type, + }); + return { + type: "node" as const, + id: node.type, + text: node.text, + backgroundColor: backgroundColor, + textColor: textColor, + color: formatHexColor(node.canvasSettings.color) || "black", + shortcut: node.shortcut, + }; + }); + + const relationItems = uniqueRelations.map((relation, index) => { + const color = getRelationColor(relation, index); + return { + type: "relation" as const, + id: relation, + text: relation, + backgroundColor: color, + textColor: "black", + color: color, + }; + }); return [...nodeItems, ...relationItems]; }, [nodes, uniqueRelations]); @@ -108,6 +128,10 @@ const DiscourseGraphPanel = ({ break; } case "pointing_item": { + // Relations should not be draggable + if (current.item.type === "relation") { + break; + } const dist = Vec.Dist(screenPoint, current.startPosition); if (dist > 10) { dragState.set({ @@ -153,14 +177,16 @@ const DiscourseGraphPanel = ({ case "dragging": { // When dragging ends, create the shape at the drop position const pagePoint = editor.screenToPage(current.currentPosition); + const offsetX = DEFAULT_WIDTH / 2; + const offsetY = DEFAULT_HEIGHT / 2; if (current.item.type === "node") { const shapeId = createShapeId(); editor.createShape({ id: shapeId, type: current.item.id, - x: pagePoint.x, - y: pagePoint.y, + x: pagePoint.x - offsetX, + y: pagePoint.y - offsetY, props: { fontFamily: "sans", size: "s" }, }); editor.setEditingShape(shapeId); @@ -198,8 +224,12 @@ const DiscourseGraphPanel = ({ startPosition, }); - target.addEventListener("pointermove", handlePointerMove); - document.addEventListener("keydown", handleKeyDown); + // Relations should not be draggable, only clickable + // So we don't add the pointermove listener for relations + if (item.type !== "relation") { + target.addEventListener("pointermove", handlePointerMove); + document.addEventListener("keydown", handleKeyDown); + } }; const handleKeyDown = (e: KeyboardEvent) => { @@ -228,6 +258,11 @@ const DiscourseGraphPanel = ({ const state = useValue("dragState", () => dragState.get(), [dragState]); + const zoomLevel = Math.max( + 0.5, + useValue("clipboardZoomLevel", () => editor.getZoomLevel(), [editor]), + ); + // Drag preview management useQuickReactor( "drag-image-style", @@ -244,6 +279,11 @@ const DiscourseGraphPanel = ({ break; } case "dragging": { + // Relations should not be draggable + if (current.item.type === "relation") { + imageRef.style.display = "none"; + break; + } const panelContainerRect = panelContainerRef.getBoundingClientRect(); const box = new Box( panelContainerRect.x, @@ -251,32 +291,34 @@ const DiscourseGraphPanel = ({ panelContainerRect.width, panelContainerRect.height, ); - const viewportScreenBounds = editor.getViewportScreenBounds(); + + const zoomLevel = Math.max(0.5, editor.getZoomLevel()); + const height = DEFAULT_HEIGHT * zoomLevel; + const width = DEFAULT_WIDTH * zoomLevel; const isInside = Box.ContainsPoint(box, current.currentPosition); if (isInside) { imageRef.style.display = "none"; } else { - imageRef.style.display = "block"; - imageRef.style.position = "absolute"; + const viewportScreenBounds = editor.getViewportScreenBounds(); + imageRef.style.display = "flex"; + imageRef.style.position = "fixed"; imageRef.style.pointerEvents = "none"; imageRef.style.left = "0px"; imageRef.style.top = "0px"; - imageRef.style.transform = `translate(${current.currentPosition.x - viewportScreenBounds.x - 25}px, ${current.currentPosition.y - viewportScreenBounds.y - 25}px)`; - imageRef.style.width = "50px"; - imageRef.style.height = "50px"; - imageRef.style.fontSize = "40px"; - imageRef.style.display = "flex"; - imageRef.style.alignItems = "center"; - imageRef.style.justifyContent = "center"; - imageRef.style.borderRadius = "8px"; - imageRef.style.backgroundColor = current.item.color; - imageRef.style.color = "white"; - imageRef.style.fontWeight = "bold"; + imageRef.style.transform = `translate(${current.currentPosition.x - viewportScreenBounds.x - width / 2}px, ${current.currentPosition.y - viewportScreenBounds.y - height / 2}px)`; + imageRef.style.width = `${width}px`; + imageRef.style.height = `${height}px`; + imageRef.style.zIndex = "9999"; + imageRef.style.borderRadius = `${16 * zoomLevel}px`; + imageRef.style.backgroundColor = current.item.backgroundColor; + imageRef.style.color = current.item.textColor; + imageRef.className = + "roamjs-tldraw-node pointer-events-none flex fixed items-center justify-center overflow-hidden"; } } } }, - [dragState], + [dragState, editor], ); // If it's a node tool, show only that node @@ -384,15 +426,11 @@ const DiscourseGraphPanel = ({ {state.name === "dragging" && (
{state.item.text} diff --git a/apps/roam/src/components/canvas/Tldraw.tsx b/apps/roam/src/components/canvas/Tldraw.tsx index f58727d9..db5522e7 100644 --- a/apps/roam/src/components/canvas/Tldraw.tsx +++ b/apps/roam/src/components/canvas/Tldraw.tsx @@ -114,8 +114,8 @@ export const discourseContext: DiscourseContextType = { lastActions: [], }; -const DEFAULT_WIDTH = 160; -const DEFAULT_HEIGHT = 64; +export const DEFAULT_WIDTH = 160; +export const DEFAULT_HEIGHT = 64; export const MAX_WIDTH = "400px"; export const isPageUid = (uid: string) => diff --git a/apps/roam/src/utils/getDiscourseNodeColors.ts b/apps/roam/src/utils/getDiscourseNodeColors.ts new file mode 100644 index 00000000..2a70badf --- /dev/null +++ b/apps/roam/src/utils/getDiscourseNodeColors.ts @@ -0,0 +1,41 @@ +import { colord } from "colord"; +import getPleasingColors from "@repo/utils/getPleasingColors"; +import { + COLOR_ARRAY, + COLOR_PALETTE, +} from "~/components/canvas/DiscourseNodeUtil"; +import getDiscourseNodes, { DiscourseNode } from "./getDiscourseNodes"; + +type GetDiscourseNodeColorsParams = { + nodeType?: string; + discourseNodes?: DiscourseNode[]; +}; + +export const getDiscourseNodeColors = ({ + nodeType, + discourseNodes = getDiscourseNodes(), +}: GetDiscourseNodeColorsParams): { + backgroundColor: string; + textColor: string; +} => { + const discourseNodeIndex = + discourseNodes.findIndex((node) => node.type === nodeType) ?? -1; + const color = discourseNodes[discourseNodeIndex]?.canvasSettings?.color ?? ""; + + const paletteColor = + COLOR_ARRAY[ + discourseNodeIndex >= 0 && discourseNodeIndex < COLOR_ARRAY.length + ? discourseNodeIndex + : 0 + ]; + const formattedTextColor = + color && !color.startsWith("#") ? `#${color}` : color; + + const canvasSelectedColor = formattedTextColor + ? formattedTextColor + : COLOR_PALETTE[paletteColor]; + const pleasingColors = getPleasingColors(colord(canvasSelectedColor)); + const backgroundColor = pleasingColors.background; + const textColor = pleasingColors.text; + return { backgroundColor, textColor }; +};