diff --git a/apps/roam/src/components/settings/AdminPanel.tsx b/apps/roam/src/components/settings/AdminPanel.tsx index 6606cf20e..85ba13269 100644 --- a/apps/roam/src/components/settings/AdminPanel.tsx +++ b/apps/roam/src/components/settings/AdminPanel.tsx @@ -38,6 +38,7 @@ import createBlock from "roamjs-components/writes/createBlock"; import deleteBlock from "roamjs-components/writes/deleteBlock"; import { USE_REIFIED_RELATIONS } from "~/data/userSettings"; import posthog from "posthog-js"; +import { setFeatureFlag } from "~/components/settings/utils/accessors"; const NodeRow = ({ node }: { node: PConceptFull }) => { return ( @@ -377,6 +378,7 @@ const FeatureFlagsTab = (): React.ReactElement => { setSuggestiveModeUid(undefined); } setSuggestiveModeEnabled(false); + setFeatureFlag("Suggestive mode enabled", false); } }} labelElement={ @@ -399,6 +401,7 @@ const FeatureFlagsTab = (): React.ReactElement => { }).then((uid) => { setSuggestiveModeUid(uid); setSuggestiveModeEnabled(true); + setFeatureFlag("Suggestive mode enabled", true); setIsAlertOpen(false); setIsInstructionOpen(true); }); @@ -447,6 +450,7 @@ const FeatureFlagsTab = (): React.ReactElement => { void setSetting(USE_REIFIED_RELATIONS, target.checked).catch( () => undefined, ); + setFeatureFlag("Reified relation triples", target.checked); posthog.capture("Reified Relations: Toggled", { enabled: target.checked, }); diff --git a/apps/roam/src/components/settings/DefaultFilters.tsx b/apps/roam/src/components/settings/DefaultFilters.tsx index f9daeb85f..693e98f74 100644 --- a/apps/roam/src/components/settings/DefaultFilters.tsx +++ b/apps/roam/src/components/settings/DefaultFilters.tsx @@ -3,6 +3,7 @@ import React, { useEffect, useState } from "react"; import type { OnloadArgs } from "roamjs-components/types"; import type { Filters } from "roamjs-components/components/Filter"; import posthog from "posthog-js"; +import { setPersonalSetting } from "~/components/settings/utils/accessors"; // // TODO - REWORK THIS COMPONENT @@ -124,28 +125,27 @@ const DefaultFilters = ({ ); useEffect(() => { - extensionAPI.settings.set( - "default-filters", - Object.fromEntries( - Object.entries(filters).map(([k, v]) => [ - k, - { - includes: Object.fromEntries( - Object.entries(v.includes || {}).map(([k, v]) => [ - k, - Array.from(v), - ]), - ), - excludes: Object.fromEntries( - Object.entries(v.excludes || {}).map(([k, v]) => [ - k, - Array.from(v), - ]), - ), - }, - ]), - ), + const serialized = Object.fromEntries( + Object.entries(filters).map(([k, v]) => [ + k, + { + includes: Object.fromEntries( + Object.entries(v.includes || {}).map(([k, v]) => [ + k, + Array.from(v), + ]), + ), + excludes: Object.fromEntries( + Object.entries(v.excludes || {}).map(([k, v]) => [ + k, + Array.from(v), + ]), + ), + }, + ]), ); + void extensionAPI.settings.set("default-filters", serialized); + setPersonalSetting(["Query", "Default filters"], serialized); }, [filters]); return (
void; onDelete: () => void }) => { + onSync, +}: Attribute & { + onChange: (v: string) => void; + onDelete: () => void; + onSync?: () => void; +}) => { const timeoutRef = useRef(0); return (
@@ -53,7 +60,16 @@ const NodeAttribute = ({ ); }; -const NodeAttributes = ({ uid }: { uid: string }) => { +const toRecord = (attrs: Attribute[]): Record => + Object.fromEntries(attrs.map((a) => [a.label, a.value])); + +const NodeAttributes = ({ + uid, + nodeType, +}: { + uid: string; + nodeType: string; +}) => { const [attributes, setAttributes] = useState(() => getBasicTreeByParentUid(uid).map((t) => ({ uid: t.uid, @@ -61,6 +77,15 @@ const NodeAttributes = ({ uid }: { uid: string }) => { value: t.children[0]?.text, })), ); + const attributesRef = useRef(attributes); + attributesRef.current = attributes; + const syncToBlockProps = () => { + setDiscourseNodeSetting( + nodeType, + ["attributes"], + toRecord(attributesRef.current), + ); + }; const [newAttribute, setNewAttribute] = useState(""); return (
@@ -77,10 +102,17 @@ const NodeAttributes = ({ uid }: { uid: string }) => { ) } onDelete={() => - deleteBlock(a.uid).then(() => - setAttributes(attributes.filter((aa) => a.uid !== aa.uid)), - ) + deleteBlock(a.uid).then(() => { + const updated = attributes.filter((aa) => a.uid !== aa.uid); + setAttributes(updated); + setDiscourseNodeSetting( + nodeType, + ["attributes"], + toRecord(updated), + ); + }) } + onSync={syncToBlockProps} /> ))}
@@ -105,11 +137,17 @@ const NodeAttributes = ({ uid }: { uid: string }) => { parentUid: uid, order: attributes.length, }).then((uid) => { - setAttributes([ + const updated = [ ...attributes, { uid, label: newAttribute, value: DEFAULT }, - ]); + ]; + setAttributes(updated); setNewAttribute(""); + setDiscourseNodeSetting( + nodeType, + ["attributes"], + toRecord(updated), + ); }); }} /> diff --git a/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx b/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx index ebc47d1ad..287f4194b 100644 --- a/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx @@ -11,7 +11,11 @@ import React, { useState, useMemo } from "react"; import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; import getSettingValueFromTree from "roamjs-components/util/getSettingValueFromTree"; import setInputSetting from "roamjs-components/util/setInputSetting"; -import { DiscourseNodeFlagPanel } from "./components/BlockPropSettingPanels"; +import { + DiscourseNodeFlagPanel, + DiscourseNodeTextPanel, +} from "./components/BlockPropSettingPanels"; +import { setDiscourseNodeSetting } from "~/components/settings/utils/accessors"; export const formatHexColor = (color: string) => { if (!color) return ""; @@ -37,9 +41,7 @@ const DiscourseNodeCanvasSettings = ({ const color = getSettingValueFromTree({ tree, key: "color" }); return formatHexColor(color); }); - const [alias, setAlias] = useState(() => - getSettingValueFromTree({ tree, key: "alias" }), - ); + const alias = getSettingValueFromTree({ tree, key: "alias" }); const [queryBuilderAlias, setQueryBuilderAlias] = useState(() => getSettingValueFromTree({ tree, key: "query-builder-alias" }), ); @@ -60,12 +62,18 @@ const DiscourseNodeCanvasSettings = ({ type={"color"} value={color} onChange={(e) => { + const colorValue = e.target.value.replace("#", ""); // remove hash to not create roam link setColor(e.target.value); void setInputSetting({ blockUid: uid, key: "color", - value: e.target.value.replace("#", ""), // remove hash to not create roam link + value: colorValue, }); + setDiscourseNodeSetting( + nodeType, + ["canvasSettings", "color"], + colorValue, + ); }} /> @@ -79,25 +87,30 @@ const DiscourseNodeCanvasSettings = ({ key: "color", value: "", }); + setDiscourseNodeSetting( + nodeType, + ["canvasSettings", "color"], + "", + ); }} />
- + { + void setInputSetting({ + blockUid: uid, + key: "alias", + value: val, + }); + }} + /> @@ -145,12 +163,18 @@ const DiscourseNodeCanvasSettings = ({ disabled={keyImageOption !== "query-builder" || !isKeyImage} value={queryBuilderAlias} onChange={(e) => { - setQueryBuilderAlias(e.target.value); + const val = e.target.value; + setQueryBuilderAlias(val); void setInputSetting({ blockUid: uid, key: "query-builder-alias", - value: e.target.value, + value: val, }); + setDiscourseNodeSetting( + nodeType, + ["canvasSettings", "query-builder-alias"], + val, + ); }} />
diff --git a/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx b/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx index ea5db06cc..8d388189e 100644 --- a/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx @@ -13,9 +13,14 @@ import refreshConfigTree from "~/utils/refreshConfigTree"; import createPage from "roamjs-components/writes/createPage"; import type { CustomField } from "roamjs-components/components/ConfigPanels/types"; import posthog from "posthog-js"; -import getDiscourseRelations from "~/utils/getDiscourseRelations"; +import getDiscourseRelations, { + type DiscourseRelation, +} from "~/utils/getDiscourseRelations"; import { deleteBlock } from "roamjs-components/writes"; import { formatHexColor } from "./DiscourseNodeCanvasSettings"; +import setBlockProps from "~/utils/setBlockProps"; +import { DiscourseNodeSchema } from "./utils/zodSchema"; +import { getGlobalSettings, setGlobalSetting } from "./utils/accessors"; type DiscourseNodeConfigPanelProps = React.ComponentProps< CustomField["options"]["component"] @@ -38,7 +43,9 @@ const DiscourseNodeConfigPanel: React.FC = ({ const [isAlertOpen, setIsAlertOpen] = useState(false); const [alertMessage, setAlertMessage] = useState(""); - const [affectedRelations, setAffectedRelations] = useState([]); + const [affectedRelations, setAffectedRelations] = useState< + DiscourseRelation[] + >([]); const [nodeTypeIdToDelete, setNodeTypeIdToDelete] = useState(""); const navigateToNode = (uid: string) => { if (isPopup) { @@ -72,13 +79,15 @@ const DiscourseNodeConfigPanel: React.FC = ({ className="select-none" disabled={!label} onClick={() => { + const shortcut = label.slice(0, 1).toUpperCase(); + const format = `[[${label.slice(0, 3).toUpperCase()}]] - {content}`; posthog.capture("Discourse Node: Type Created", { label: label }); - createPage({ + void createPage({ title: `discourse-graph/nodes/${label}`, tree: [ { text: "Shortcut", - children: [{ text: label.slice(0, 1).toUpperCase() }], + children: [{ text: shortcut }], }, { text: "Tag", @@ -86,14 +95,20 @@ const DiscourseNodeConfigPanel: React.FC = ({ }, { text: "Format", - children: [ - { - text: `[[${label.slice(0, 3).toUpperCase()}]] - {content}`, - }, - ], + children: [{ text: format }], }, ], }).then((valueUid) => { + setBlockProps( + valueUid, + DiscourseNodeSchema.parse({ + text: label, + type: valueUid, + shortcut, + format, + backedBy: "user", + }), + ); setNodes([ ...nodes, { @@ -222,6 +237,9 @@ const DiscourseNodeConfigPanel: React.FC = ({ throw error; }); } + const relations = { ...getGlobalSettings().Relations }; + for (const rel of affectedRelations) delete relations[rel.id]; + setGlobalSetting(["Relations"], relations); deleteNodeType(nodeTypeIdToDelete); } catch (error) { console.error( diff --git a/apps/roam/src/components/settings/NodeConfig.tsx b/apps/roam/src/components/settings/NodeConfig.tsx index 0c0e5ff7d..20447a426 100644 --- a/apps/roam/src/components/settings/NodeConfig.tsx +++ b/apps/roam/src/components/settings/NodeConfig.tsx @@ -1,30 +1,22 @@ -import React, { - useState, - useCallback, - useRef, - useEffect, - useMemo, -} from "react"; +import React, { useState, useCallback, useEffect, useMemo } from "react"; import { DiscourseNode } from "~/utils/getDiscourseNodes"; -import SelectPanel from "roamjs-components/components/ConfigPanels/SelectPanel"; import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel"; import { getSubTree } from "roamjs-components/util"; import Description from "roamjs-components/components/Description"; -import { Label, Tabs, Tab, TabId, InputGroup } from "@blueprintjs/core"; +import { Label, Tabs, Tab, TabId } from "@blueprintjs/core"; import DiscourseNodeSpecification from "./DiscourseNodeSpecification"; import DiscourseNodeAttributes from "./DiscourseNodeAttributes"; import DiscourseNodeCanvasSettings from "./DiscourseNodeCanvasSettings"; import DiscourseNodeIndex from "./DiscourseNodeIndex"; import { OnloadArgs } from "roamjs-components/types"; import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; -import createBlock from "roamjs-components/writes/createBlock"; -import updateBlock from "roamjs-components/writes/updateBlock"; import DiscourseNodeSuggestiveRules from "./DiscourseNodeSuggestiveRules"; import { getFormattedConfigTree } from "~/utils/discourseConfigRef"; import refreshConfigTree from "~/utils/refreshConfigTree"; import { DiscourseNodeTextPanel, DiscourseNodeFlagPanel, + DiscourseNodeSelectPanel, } from "./components/BlockPropSettingPanels"; const TEMPLATE_SETTING_KEYS = ["template"]; @@ -33,91 +25,6 @@ export const getCleanTagText = (tag: string): string => { return tag.replace(/^#+/, "").trim().toUpperCase(); }; -const ValidatedInputPanel = ({ - label, - description, - value, - onChange, - onBlur, - error, - placeholder, -}: { - label: string; - description: string; - value: string; - onChange: (e: React.ChangeEvent) => void; - onBlur: () => void; - error: string; - placeholder?: string; -}) => ( -
- - {error && ( -
{error}
- )} -
-); - -const useDebouncedRoamUpdater = < - T extends HTMLInputElement | HTMLTextAreaElement, ->( - uid: string, - initialValue: string, - isValid: boolean, -) => { - const [value, setValue] = useState(initialValue); - const debounceRef = useRef(0); - const isValidRef = useRef(isValid); - isValidRef.current = isValid; - - const saveToRoam = useCallback( - (text: string, timeout: boolean) => { - window.clearTimeout(debounceRef.current); - debounceRef.current = window.setTimeout( - () => { - if (!isValidRef.current) { - return; - } - const existingBlock = getBasicTreeByParentUid(uid)[0]; - if (existingBlock) { - if (existingBlock.text !== text) { - void updateBlock({ uid: existingBlock.uid, text }); - } - } else if (text) { - void createBlock({ parentUid: uid, node: { text } }); - } - }, - timeout ? 500 : 0, - ); - }, - [uid], - ); - - const handleChange = useCallback( - (e: React.ChangeEvent) => { - const newValue = e.target.value; - setValue(newValue); - saveToRoam(newValue, true); - }, - [saveToRoam], - ); - - const handleBlur = useCallback(() => { - saveToRoam(value, false); - }, [value, saveToRoam]); - - return { value, handleChange, handleBlur }; -}; - const generateTagPlaceholder = (node: DiscourseNode): string => { // Extract first reference from format like [[CLM]], [[QUE]], [[EVD]] const referenceMatch = node.format.match(/\[\[([A-Z]+)\]\]/); @@ -166,18 +73,9 @@ const NodeConfig = ({ const [selectedTabId, setSelectedTabId] = useState("general"); const [tagError, setTagError] = useState(""); const [formatError, setFormatError] = useState(""); - const isConfigurationValid = !tagError && !formatError; const [tagValue, setTagValue] = useState(node.tag || ""); - const { - value: formatValue, - handleChange: handleFormatChange, - handleBlur: handleFormatBlurFromHook, - } = useDebouncedRoamUpdater( - formatUid, - node.format, - isConfigurationValid, - ); + const [formatValue, setFormatValue] = useState(node.format || ""); const validate = useCallback( ({ tag, @@ -237,11 +135,6 @@ const NodeConfig = ({ validate({ tag: tagValue, format: formatValue }); }, [tagValue, formatValue, validate]); - const handleFormatBlur = useCallback(() => { - handleFormatBlurFromHook(); - validate({ tag: tagValue, format: formatValue }); - }, [handleFormatBlurFromHook, tagValue, formatValue, validate]); - return ( <> -