diff --git a/package-lock.json b/package-lock.json index 1ac3bc41..7ef60249 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "query-builder", - "version": "1.12.0", + "version": "1.13.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "query-builder", - "version": "1.12.0", + "version": "1.13.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 81887fdf..43b8e95c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "query-builder", - "version": "1.12.0", + "version": "1.13.0", "description": "Introduces new user interfaces for building queries in Roam", "main": "./build/main.js", "author": { diff --git a/src/components/DiscourseNodeCanvasSettings.tsx b/src/components/DiscourseNodeCanvasSettings.tsx index a7c690a9..075cdb53 100644 --- a/src/components/DiscourseNodeCanvasSettings.tsx +++ b/src/components/DiscourseNodeCanvasSettings.tsx @@ -6,6 +6,7 @@ import { Switch, Tooltip, Icon, + ControlGroup, } from "@blueprintjs/core"; import React, { useRef, useState, useMemo } from "react"; import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; @@ -14,9 +15,17 @@ import setInputSetting from "roamjs-components/util/setInputSetting"; const DiscourseNodeCanvasSettings = ({ uid }: { uid: string }) => { const tree = useMemo(() => getBasicTreeByParentUid(uid), [uid]); - const [color, setColor] = useState(() => - getSettingValueFromTree({ tree, key: "color" }) - ); + const [color, setColor] = useState(() => { + const color = getSettingValueFromTree({ tree, key: "color" }); + const COLOR_TEST = /^[0-9a-f]{6}$/i; + if (color.startsWith("#")) { + return color; + // handle legacy color format + } else if (COLOR_TEST.test(color)) { + return "#" + color; + } + return ""; + }); const [alias, setAlias] = useState(() => getSettingValueFromTree({ tree, key: "alias" }) ); @@ -31,21 +40,38 @@ const DiscourseNodeCanvasSettings = ({ uid }: { uid: string }) => { ); return (
- +
+ + + { + setColor(e.target.value); + setInputSetting({ + blockUid: uid, + key: "color", + value: e.target.value.replace("#", ""), // remove hash to not create roam link + }); + }} + /> + + { + setColor(""); + setInputSetting({ + blockUid: uid, + key: "color", + value: "", + }); + }} + /> + + +
- {/* */} { ? discourseNodeIndex : 0 ]; - const backgroundInfo = backgroundColor - ? { backgroundColor, backgroundCss: backgroundColor } + const formattedBackgroundColor = + backgroundColor && !backgroundColor.startsWith("#") + ? `#${backgroundColor}` + : backgroundColor; + + const backgroundInfo = formattedBackgroundColor + ? { + backgroundColor: formattedBackgroundColor, + backgroundCss: formattedBackgroundColor, + } : { backgroundColor: COLOR_PALETTE[paletteColor], backgroundCss: `var(--palette-${paletteColor})`, @@ -637,7 +651,7 @@ class DiscourseNodeUtil extends TLBoxUtil { }); // set attributes for the text - text.setAttribute("font-family", DEFAULT_STYLE_PROPS.fontFamily); + text.setAttribute("font-family", SVG_FONT_FAMILY); text.setAttribute("font-size", DEFAULT_STYLE_PROPS.fontSize + "px"); text.setAttribute("font-weight", DEFAULT_STYLE_PROPS.fontWeight); text.setAttribute("fill", textColor); diff --git a/src/components/TldrawCanvasLabelDialog.tsx b/src/components/TldrawCanvasLabelDialog.tsx index ea4a959f..0afe40d4 100644 --- a/src/components/TldrawCanvasLabelDialog.tsx +++ b/src/components/TldrawCanvasLabelDialog.tsx @@ -13,9 +13,10 @@ import { import fireQuery from "../utils/fireQuery"; import fuzzy from "fuzzy"; import { RoamOverlayProps } from "roamjs-components/util/renderOverlay"; -import { QBClause, Result } from "../utils/types"; +import { Result } from "../utils/types"; import AutocompleteInput from "roamjs-components/components/AutocompleteInput"; import { DiscourseContextType } from "./TldrawCanvas"; +import { getPlainTitleFromSpecification } from "../discourseGraphsMode"; const LabelDialogAutocomplete = ({ setLabel, @@ -124,20 +125,7 @@ const LabelDialog = ({ if (_label) return _label; const { specification, text } = discourseContext.nodes[nodeType]; if (!specification.length) return ""; - // CURRENT ASSUMPTIONS: - // - conditions are properly ordered - // - there is a has title condition somewhere - const titleCondition = specification.find( - (s): s is QBClause => - s.type === "clause" && s.relation === "has title" && s.source === text - ); - if (!titleCondition) return ""; - return titleCondition.target - .replace(/^\/(\^)?/, "") - .replace(/(\$)?\/$/, "") - .replace(/\\\[/g, "[") - .replace(/\\\]/g, "]") - .replace(/\(\.[\*\+](\?)?\)/g, ""); + return getPlainTitleFromSpecification({ specification, text }); }, [_label, nodeType]); const initialValue = useMemo(() => { return { text: initialLabel, uid: initialUid }; diff --git a/src/discourseGraphsMode.ts b/src/discourseGraphsMode.ts index 530121b6..479ddb51 100644 --- a/src/discourseGraphsMode.ts +++ b/src/discourseGraphsMode.ts @@ -5,6 +5,7 @@ import { import { CustomField, Field, + FieldPanel, FlagField, SelectField, TextField, @@ -52,6 +53,7 @@ import DiscourseNodeCanvasSettings from "./components/DiscourseNodeCanvasSetting import CanvasReferences from "./components/CanvasReferences"; import fireQuery from "./utils/fireQuery"; import { render as renderGraphOverviewExport } from "./components/ExportDiscourseContext"; +import { Condition, QBClause } from "./utils/types"; export const SETTING = "discourse-graphs"; @@ -237,6 +239,13 @@ export const renderDiscourseNodeTypeConfigPage = ({ component: DiscourseNodeCanvasSettings, }, } as Field, + // @ts-ignore + { + title: "Graph Overview", + Panel: FlagPanel, + description: `Whether to color the node in the graph overview based on canvas color`, + defaultValue: true, + } as FieldPanel, ], }); @@ -244,6 +253,29 @@ export const renderDiscourseNodeTypeConfigPage = ({ } }; +export const getPlainTitleFromSpecification = ({ + specification, + text, +}: { + specification: Condition[]; + text: string; +}) => { + // Assumptions: + // - Conditions are properly ordered + // - There is a 'has title' condition somewhere + const titleCondition = specification.find( + (s): s is QBClause => + s.type === "clause" && s.relation === "has title" && s.source === text + ); + if (!titleCondition) return ""; + return titleCondition.target + .replace(/^\/(\^)?/, "") + .replace(/(\$)?\/$/, "") + .replace(/\\\[/g, "[") + .replace(/\\\]/g, "]") + .replace(/\(\.[\*\+](\?)?\)/g, ""); +}; + const initializeDiscourseGraphsMode = async (args: OnloadArgs) => { const unloads = new Set<() => void>(); const toggle = async (flag: boolean) => { @@ -760,6 +792,67 @@ const initializeDiscourseGraphsMode = async (args: OnloadArgs) => { unloads.delete(removeQueryPage); }); + type SigmaRenderer = { + setSetting: (settingName: string, value: any) => void; + getSetting: (settingName: string) => any; + }; + type nodeData = { + x: number; + y: number; + label: string; + size: number; + }; + + window.roamAlphaAPI.ui.graphView.wholeGraph.addCallback({ + label: "discourse-node-styling", + callback: ({ "sigma-renderer": sigma }) => { + const sig = sigma as SigmaRenderer; + const allNodes = getDiscourseNodes(); + const prefixColors = allNodes.map((n) => { + const formattedTitle = getPlainTitleFromSpecification({ + specification: n.specification, + text: n.text, + }); + const formattedBackgroundColor = + n.canvasSettings.color && !n.canvasSettings.color.startsWith("#") + ? `#${n.canvasSettings.color}` + : n.canvasSettings.color; + + return { + prefix: formattedTitle, + color: formattedBackgroundColor, + showInGraphOverview: n.graphOverview, + }; + }); + + const originalReducer = sig.getSetting("nodeReducer"); + sig.setSetting("nodeReducer", (id: string, nodeData: nodeData) => { + let modifiedData = originalReducer + ? originalReducer(id, nodeData) + : nodeData; + + const { label } = modifiedData; + + for (const { prefix, color, showInGraphOverview } of prefixColors) { + if (showInGraphOverview && label.startsWith(prefix)) { + return { + ...modifiedData, + color, + }; + } + } + + return modifiedData; + }); + }, + }); + unloads.add(function removeGraphViewCallback() { + window.roamAlphaAPI.ui.graphView.wholeGraph.removeCallback({ + label: "discourse-node-styling", + }); + unloads.delete(removeGraphViewCallback); + }); + window.roamAlphaAPI.ui.commandPalette.addCommand({ label: "Export Discourse Graph", callback: () => { diff --git a/src/utils/getDiscourseNodes.ts b/src/utils/getDiscourseNodes.ts index bbf9cd10..287c4db4 100644 --- a/src/utils/getDiscourseNodes.ts +++ b/src/utils/getDiscourseNodes.ts @@ -17,6 +17,7 @@ export type DiscourseNode = { }; // @deprecated - use specification instead format: string; + graphOverview?: boolean; }; const DEFAULT_NODES: DiscourseNode[] = [ @@ -78,6 +79,8 @@ const getDiscourseNodes = (relations = getDiscourseRelations()) => { (c) => [c.text, c.children[0]?.text || ""] as const ) ), + graphOverview: + children.filter((c) => c.text === "Graph Overview").length > 0, }; }) .concat(