Skip to content

Commit 64d41c6

Browse files
authored
ENG-1107 Update drag and drop image for nodes in discourse toolbar (#578)
* Refactor color retrieval in BaseDiscourseNodeUtil: replace manual color logic with getDiscourseNodeColors utility function for improved maintainability and clarity. * Enhance DiscourseToolPanel: Integrate getDiscourseNodeColors for improved color handling, adjust drag behavior for relations, and refine drag preview styling based on zoom level. * Refactor DiscourseToolPanel: Simplify drag behavior by removing zoom-dependent offsets, adjust padding in drag preview, and enhance color retrieval logic in getDiscourseNodeColors for improved palette handling. * Refactor DiscourseToolPanel: Update drag behavior to ensure relations are only clickable, preventing pointermove listener from being added for relation items.
1 parent 83a18be commit 64d41c6

4 files changed

Lines changed: 126 additions & 67 deletions

File tree

apps/roam/src/components/canvas/DiscourseNodeUtil.tsx

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import createDiscourseNode from "~/utils/createDiscourseNode";
3333
import { DiscourseNode } from "~/utils/getDiscourseNodes";
3434
import { isPageUid } from "./Tldraw";
3535
import LabelDialog from "./LabelDialog";
36-
import { colord } from "colord";
3736
import { discourseContext } from "./Tldraw";
3837
import getDiscourseContextResults from "~/utils/getDiscourseContextResults";
3938
import calcCanvasNodeSizeAndImg from "~/utils/calcCanvasNodeSizeAndImg";
@@ -46,7 +45,7 @@ import {
4645
} from "~/data/userSettings";
4746
import { getSetting } from "~/utils/extensionSettings";
4847
import DiscourseContextOverlay from "~/components/DiscourseContextOverlay";
49-
import getPleasingColors from "@repo/utils/getPleasingColors";
48+
import { getDiscourseNodeColors } from "~/utils/getDiscourseNodeColors";
5049

5150
// TODO REPLACE WITH TLDRAW DEFAULTS
5251
// https://github.com/tldraw/tldraw/pull/1580/files
@@ -58,7 +57,7 @@ const TEXT_PROPS = {
5857
padding: "0px",
5958
maxWidth: "auto",
6059
};
61-
const FONT_SIZES: Record<TLDefaultSizeStyle, number> = {
60+
export const FONT_SIZES: Record<TLDefaultSizeStyle, number> = {
6261
m: 25,
6362
l: 38,
6463
xl: 48,
@@ -343,26 +342,7 @@ export class BaseDiscourseNodeUtil extends ShapeUtil<DiscourseNodeShape> {
343342
}
344343

345344
getColors() {
346-
const {
347-
canvasSettings: { color: setColor = "" } = {},
348-
index: discourseNodeIndex = -1,
349-
} = discourseContext.nodes[this.type] || {};
350-
const paletteColor =
351-
COLOR_ARRAY[
352-
discourseNodeIndex >= 0 && discourseNodeIndex < COLOR_ARRAY.length - 1
353-
? discourseNodeIndex
354-
: 0
355-
];
356-
const formattedTextColor =
357-
setColor && !setColor.startsWith("#") ? `#${setColor}` : setColor;
358-
359-
const canvasSelectedColor = formattedTextColor
360-
? formattedTextColor
361-
: COLOR_PALETTE[paletteColor];
362-
const pleasingColors = getPleasingColors(colord(canvasSelectedColor));
363-
const backgroundColor = pleasingColors.background;
364-
const textColor = pleasingColors.text;
365-
return { backgroundColor, textColor };
345+
return getDiscourseNodeColors({ nodeType: this.type });
366346
}
367347

368348
async toSvg(shape: DiscourseNodeShape): Promise<JSX.Element> {

apps/roam/src/components/canvas/DiscourseToolPanel.tsx

Lines changed: 80 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ import {
66
Vec,
77
Box,
88
createShapeId,
9+
FONT_FAMILIES,
910
} from "tldraw";
1011
import { DiscourseNode } from "~/utils/getDiscourseNodes";
1112
import { formatHexColor } from "~/components/settings/DiscourseNodeCanvasSettings";
1213
import { getRelationColor } from "./DiscourseRelationShape/DiscourseRelationUtil";
1314
import { useAtom } from "@tldraw/state";
1415
import { TOOL_ARROW_ICON_SVG, NODE_COLOR_ICON_SVG } from "~/icons";
16+
import { getDiscourseNodeColors } from "~/utils/getDiscourseNodeColors";
17+
import { DEFAULT_WIDTH, DEFAULT_HEIGHT } from "./Tldraw";
18+
import { DEFAULT_STYLE_PROPS, FONT_SIZES } from "./DiscourseNodeUtil";
1519

1620
export type DiscourseGraphPanelProps = {
1721
nodes: DiscourseNode[];
@@ -30,6 +34,8 @@ type DragState =
3034
type: "node" | "relation";
3135
id: string;
3236
text: string;
37+
backgroundColor: string;
38+
textColor: string;
3339
color: string;
3440
};
3541
startPosition: Vec;
@@ -40,6 +46,8 @@ type DragState =
4046
type: "node" | "relation";
4147
id: string;
4248
text: string;
49+
backgroundColor: string;
50+
textColor: string;
4351
color: string;
4452
};
4553
currentPosition: Vec;
@@ -72,20 +80,32 @@ const DiscourseGraphPanel = ({
7280
);
7381

7482
const panelItems = useMemo(() => {
75-
const nodeItems = nodes.map((node) => ({
76-
type: "node" as const,
77-
id: node.type,
78-
text: node.text,
79-
color: formatHexColor(node.canvasSettings.color) || "black",
80-
shortcut: node.shortcut,
81-
}));
82-
83-
const relationItems = uniqueRelations.map((relation, index) => ({
84-
type: "relation" as const,
85-
id: relation,
86-
text: relation,
87-
color: getRelationColor(relation, index),
88-
}));
83+
const nodeItems = nodes.map((node) => {
84+
const { backgroundColor, textColor } = getDiscourseNodeColors({
85+
nodeType: node.type,
86+
});
87+
return {
88+
type: "node" as const,
89+
id: node.type,
90+
text: node.text,
91+
backgroundColor: backgroundColor,
92+
textColor: textColor,
93+
color: formatHexColor(node.canvasSettings.color) || "black",
94+
shortcut: node.shortcut,
95+
};
96+
});
97+
98+
const relationItems = uniqueRelations.map((relation, index) => {
99+
const color = getRelationColor(relation, index);
100+
return {
101+
type: "relation" as const,
102+
id: relation,
103+
text: relation,
104+
backgroundColor: color,
105+
textColor: "black",
106+
color: color,
107+
};
108+
});
89109

90110
return [...nodeItems, ...relationItems];
91111
}, [nodes, uniqueRelations]);
@@ -108,6 +128,10 @@ const DiscourseGraphPanel = ({
108128
break;
109129
}
110130
case "pointing_item": {
131+
// Relations should not be draggable
132+
if (current.item.type === "relation") {
133+
break;
134+
}
111135
const dist = Vec.Dist(screenPoint, current.startPosition);
112136
if (dist > 10) {
113137
dragState.set({
@@ -153,14 +177,16 @@ const DiscourseGraphPanel = ({
153177
case "dragging": {
154178
// When dragging ends, create the shape at the drop position
155179
const pagePoint = editor.screenToPage(current.currentPosition);
180+
const offsetX = DEFAULT_WIDTH / 2;
181+
const offsetY = DEFAULT_HEIGHT / 2;
156182

157183
if (current.item.type === "node") {
158184
const shapeId = createShapeId();
159185
editor.createShape({
160186
id: shapeId,
161187
type: current.item.id,
162-
x: pagePoint.x,
163-
y: pagePoint.y,
188+
x: pagePoint.x - offsetX,
189+
y: pagePoint.y - offsetY,
164190
props: { fontFamily: "sans", size: "s" },
165191
});
166192
editor.setEditingShape(shapeId);
@@ -198,8 +224,12 @@ const DiscourseGraphPanel = ({
198224
startPosition,
199225
});
200226

201-
target.addEventListener("pointermove", handlePointerMove);
202-
document.addEventListener("keydown", handleKeyDown);
227+
// Relations should not be draggable, only clickable
228+
// So we don't add the pointermove listener for relations
229+
if (item.type !== "relation") {
230+
target.addEventListener("pointermove", handlePointerMove);
231+
document.addEventListener("keydown", handleKeyDown);
232+
}
203233
};
204234

205235
const handleKeyDown = (e: KeyboardEvent) => {
@@ -228,6 +258,11 @@ const DiscourseGraphPanel = ({
228258

229259
const state = useValue("dragState", () => dragState.get(), [dragState]);
230260

261+
const zoomLevel = Math.max(
262+
0.5,
263+
useValue("clipboardZoomLevel", () => editor.getZoomLevel(), [editor]),
264+
);
265+
231266
// Drag preview management
232267
useQuickReactor(
233268
"drag-image-style",
@@ -244,39 +279,46 @@ const DiscourseGraphPanel = ({
244279
break;
245280
}
246281
case "dragging": {
282+
// Relations should not be draggable
283+
if (current.item.type === "relation") {
284+
imageRef.style.display = "none";
285+
break;
286+
}
247287
const panelContainerRect = panelContainerRef.getBoundingClientRect();
248288
const box = new Box(
249289
panelContainerRect.x,
250290
panelContainerRect.y,
251291
panelContainerRect.width,
252292
panelContainerRect.height,
253293
);
254-
const viewportScreenBounds = editor.getViewportScreenBounds();
294+
295+
const zoomLevel = Math.max(0.5, editor.getZoomLevel());
296+
const height = DEFAULT_HEIGHT * zoomLevel;
297+
const width = DEFAULT_WIDTH * zoomLevel;
255298
const isInside = Box.ContainsPoint(box, current.currentPosition);
256299
if (isInside) {
257300
imageRef.style.display = "none";
258301
} else {
259-
imageRef.style.display = "block";
260-
imageRef.style.position = "absolute";
302+
const viewportScreenBounds = editor.getViewportScreenBounds();
303+
imageRef.style.display = "flex";
304+
imageRef.style.position = "fixed";
261305
imageRef.style.pointerEvents = "none";
262306
imageRef.style.left = "0px";
263307
imageRef.style.top = "0px";
264-
imageRef.style.transform = `translate(${current.currentPosition.x - viewportScreenBounds.x - 25}px, ${current.currentPosition.y - viewportScreenBounds.y - 25}px)`;
265-
imageRef.style.width = "50px";
266-
imageRef.style.height = "50px";
267-
imageRef.style.fontSize = "40px";
268-
imageRef.style.display = "flex";
269-
imageRef.style.alignItems = "center";
270-
imageRef.style.justifyContent = "center";
271-
imageRef.style.borderRadius = "8px";
272-
imageRef.style.backgroundColor = current.item.color;
273-
imageRef.style.color = "white";
274-
imageRef.style.fontWeight = "bold";
308+
imageRef.style.transform = `translate(${current.currentPosition.x - viewportScreenBounds.x - width / 2}px, ${current.currentPosition.y - viewportScreenBounds.y - height / 2}px)`;
309+
imageRef.style.width = `${width}px`;
310+
imageRef.style.height = `${height}px`;
311+
imageRef.style.zIndex = "9999";
312+
imageRef.style.borderRadius = `${16 * zoomLevel}px`;
313+
imageRef.style.backgroundColor = current.item.backgroundColor;
314+
imageRef.style.color = current.item.textColor;
315+
imageRef.className =
316+
"roamjs-tldraw-node pointer-events-none flex fixed items-center justify-center overflow-hidden";
275317
}
276318
}
277319
}
278320
},
279-
[dragState],
321+
[dragState, editor],
280322
);
281323

282324
// If it's a node tool, show only that node
@@ -384,15 +426,11 @@ const DiscourseGraphPanel = ({
384426
{state.name === "dragging" && (
385427
<div
386428
style={{
387-
backgroundColor: state.item.color,
388-
color: "white",
389-
fontWeight: "bold",
390-
borderRadius: "8px",
391-
display: "flex",
392-
alignItems: "center",
393-
justifyContent: "center",
394-
width: "100%",
395-
height: "100%",
429+
...DEFAULT_STYLE_PROPS,
430+
maxWidth: "",
431+
fontFamily: FONT_FAMILIES.sans,
432+
fontSize: `${FONT_SIZES.s * zoomLevel}px`,
433+
padding: `0px`,
396434
}}
397435
>
398436
{state.item.text}

apps/roam/src/components/canvas/Tldraw.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ export const discourseContext: DiscourseContextType = {
114114
lastActions: [],
115115
};
116116

117-
const DEFAULT_WIDTH = 160;
118-
const DEFAULT_HEIGHT = 64;
117+
export const DEFAULT_WIDTH = 160;
118+
export const DEFAULT_HEIGHT = 64;
119119
export const MAX_WIDTH = "400px";
120120

121121
export const isPageUid = (uid: string) =>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { colord } from "colord";
2+
import getPleasingColors from "@repo/utils/getPleasingColors";
3+
import {
4+
COLOR_ARRAY,
5+
COLOR_PALETTE,
6+
} from "~/components/canvas/DiscourseNodeUtil";
7+
import getDiscourseNodes, { DiscourseNode } from "./getDiscourseNodes";
8+
9+
type GetDiscourseNodeColorsParams = {
10+
nodeType?: string;
11+
discourseNodes?: DiscourseNode[];
12+
};
13+
14+
export const getDiscourseNodeColors = ({
15+
nodeType,
16+
discourseNodes = getDiscourseNodes(),
17+
}: GetDiscourseNodeColorsParams): {
18+
backgroundColor: string;
19+
textColor: string;
20+
} => {
21+
const discourseNodeIndex =
22+
discourseNodes.findIndex((node) => node.type === nodeType) ?? -1;
23+
const color = discourseNodes[discourseNodeIndex]?.canvasSettings?.color ?? "";
24+
25+
const paletteColor =
26+
COLOR_ARRAY[
27+
discourseNodeIndex >= 0 && discourseNodeIndex < COLOR_ARRAY.length
28+
? discourseNodeIndex
29+
: 0
30+
];
31+
const formattedTextColor =
32+
color && !color.startsWith("#") ? `#${color}` : color;
33+
34+
const canvasSelectedColor = formattedTextColor
35+
? formattedTextColor
36+
: COLOR_PALETTE[paletteColor];
37+
const pleasingColors = getPleasingColors(colord(canvasSelectedColor));
38+
const backgroundColor = pleasingColors.background;
39+
const textColor = pleasingColors.text;
40+
return { backgroundColor, textColor };
41+
};

0 commit comments

Comments
 (0)