diff --git a/apps/roam/src/components/settings/NodeConfig.tsx b/apps/roam/src/components/settings/NodeConfig.tsx index 3078f4c72..92e68d5dd 100644 --- a/apps/roam/src/components/settings/NodeConfig.tsx +++ b/apps/roam/src/components/settings/NodeConfig.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useEffect } from "react"; +import React, { useState, useCallback, useEffect, useRef } from "react"; import getDiscourseNodes, { DiscourseNode } from "~/utils/getDiscourseNodes"; import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel"; import { getSubTree } from "roamjs-components/util"; @@ -45,6 +45,106 @@ export const getCleanTagText = (tag: string): string => { return tag.replace(/^#+/, "").trim().toUpperCase(); }; +const COLOR_WRITE_DEBOUNCE_MS = 150; + +type DiscourseNodeColorSettingProps = { + canvasUid: string; + nodeType: string; + initialColor?: string; + tagValue: string; +}; + +const DiscourseNodeColorSetting = ({ + canvasUid, + nodeType, + initialColor, + tagValue, +}: DiscourseNodeColorSettingProps): React.ReactElement => { + const [color, setColor] = useState(() => + formatHexColor(initialColor ?? ""), + ); + const colorWriteTimeoutRef = useRef(null); + + const persistColorValue = useCallback( + (colorValue: string): void => { + void setInputSetting({ + blockUid: canvasUid, + key: "color", + value: colorValue, + }); + setDiscourseNodeSetting( + nodeType, + [DISCOURSE_NODE_KEYS.canvasSettings, CANVAS_KEYS.color], + colorValue, + ); + }, + [canvasUid, nodeType], + ); + + useEffect(() => { + return () => { + if (!colorWriteTimeoutRef.current) return; + + window.clearTimeout(colorWriteTimeoutRef.current); + }; + }, []); + + const persistColorAfterPause = (colorValue: string): void => { + if (colorWriteTimeoutRef.current) { + window.clearTimeout(colorWriteTimeoutRef.current); + colorWriteTimeoutRef.current = null; + } + colorWriteTimeoutRef.current = window.setTimeout(() => { + persistColorValue(colorValue); + colorWriteTimeoutRef.current = null; + }, COLOR_WRITE_DEBOUNCE_MS); + }; + + return ( + <> + {tagValue && ( +
+ Preview: + + #{tagValue.replace(/^#/, "")} + +
+ )} + + + ); +}; + const generateTagPlaceholder = (node: DiscourseNode): string => { // Extract first reference from format like [[CLM]], [[QUE]], [[EVD]] const referenceMatch = node.format.match(/\[\[([A-Z]+)\]\]/); @@ -86,10 +186,6 @@ const NodeConfig = ({ key: "Attributes", }); - const [color, setColor] = useState(() => - formatHexColor(node.canvasSettings?.color ?? ""), - ); - const [selectedTabId, setSelectedTabId] = useState("general"); const [tagError, setTagError] = useState(""); const [formatError, setFormatError] = useState(""); @@ -253,69 +349,13 @@ const NodeConfig = ({ parentUid={node.type} uid={tagUid} /> - {tagValue && ( -
- - Preview: - - - #{tagValue.replace(/^#/, "")} - -
- )} - <> - - + } />