Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 9 additions & 19 deletions apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
Tooltip,
} from "@blueprintjs/core";
import React, { useState } from "react";
import getDiscourseNodes from "~/utils/getDiscourseNodes";
import getDiscourseNodes, {
excludeDefaultNodes,
} from "~/utils/getDiscourseNodes";
import refreshConfigTree from "~/utils/refreshConfigTree";
import type { CustomField } from "roamjs-components/components/ConfigPanels/types";
import posthog from "posthog-js";
import getDiscourseRelations, {
type DiscourseRelation,
Expand All @@ -24,20 +25,12 @@ import {
} from "./utils/accessors";
import { GLOBAL_KEYS } from "./utils/settingKeys";
import { invalidateDiscourseNodeTypeCaches } from "~/utils/discourseNodeTypeCache";
import { useSettingsNav } from "./navigation/SettingsNavContext";

type DiscourseNodeConfigPanelProps = React.ComponentProps<
CustomField["options"]["component"]
> & {
isPopup?: boolean;
setSelectedTabId: (id: string) => void;
};

const DiscourseNodeConfigPanel: React.FC<DiscourseNodeConfigPanelProps> = ({
isPopup,
setSelectedTabId,
}) => {
const DiscourseNodeConfigPanel: React.FC = () => {
const { push } = useSettingsNav();
const [nodes, setNodes] = useState(() =>
getDiscourseNodes().filter((n) => n.backedBy === "user"),
getDiscourseNodes().filter(excludeDefaultNodes),
);
const [label, setLabel] = useState("");
const [isCreating, setIsCreating] = useState(false);
Expand All @@ -52,11 +45,8 @@ const DiscourseNodeConfigPanel: React.FC<DiscourseNodeConfigPanelProps> = ({
>([]);
const [nodeTypeIdToDelete, setNodeTypeIdToDelete] = useState<string>("");
const navigateToNode = (uid: string) => {
if (isPopup) {
setSelectedTabId(uid);
} else {
window.roamAlphaAPI.ui.mainWindow.openPage({ page: { uid } });
}
push(uid);
posthog.capture("Settings: Node Type Opened", { nodeTypeUid: uid });
};

const createNodeType = async (): Promise<void> => {
Expand Down
27 changes: 14 additions & 13 deletions apps/roam/src/components/settings/DiscourseNodeSpecification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ const NodeSpecification = ({
</style>
<DiscourseNodeFlagPanel
nodeType={node.type}
title="enabled"
title="Enabled"
blockKey="enabled"
description=""
settingKeys={[
DISCOURSE_NODE_KEYS.specification,
Expand All @@ -170,18 +171,18 @@ const NodeSpecification = ({
parentSetEnabled?.(checked);
}}
/>
<div
className={`${enabled ? "" : "bg-gray-200 opacity-75"} overflow-auto`}
>
<QueryEditor
parentUid={parentUid}
key={Number(migrated)}
hideCustomSwitch
discourseNodeType={node.type}
settingKey="specification"
returnNode={node.text}
/>
</div>
{enabled && (
<div className="overflow-auto">
<QueryEditor
parentUid={parentUid}
key={Number(migrated)}
hideCustomSwitch
discourseNodeType={node.type}
settingKey="specification"
returnNode={node.text}
/>
</div>
)}
</div>
);
};
Expand Down
51 changes: 38 additions & 13 deletions apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useMemo } from "react";
import { Button, Intent } from "@blueprintjs/core";
import { Button, Collapse, Intent } from "@blueprintjs/core";
import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel";
import getSubTree from "roamjs-components/util/getSubTree";
import { DiscourseNode } from "~/utils/getDiscourseNodes";
Expand All @@ -17,6 +17,7 @@ import {
TEMPLATE_SETTING_KEYS,
} from "~/components/settings/utils/settingKeys";
import { RenderRoamBlock } from "~/utils/roamReactComponents";
import Description from "~/components/settings/SettingsDescription";
import { ROAM_DOCS, withDocsLink } from "./utils/docs";

const DiscourseNodeSuggestiveRules = ({
Expand All @@ -42,6 +43,7 @@ const DiscourseNodeSuggestiveRules = ({
}).uid || "",
[nodeUid],
);
const [isTemplateOpen, setIsTemplateOpen] = useState(false);

const [isUpdating, setIsUpdating] = useState(false);

Expand All @@ -68,18 +70,41 @@ const DiscourseNodeSuggestiveRules = ({
};

return (
<div className="flex flex-col gap-4 p-4">
<DualWriteBlocksPanel
nodeType={node.type}
title="Template"
description={withDocsLink(
`The template that auto fills ${node.text} page when generated.`,
ROAM_DOCS.creatingNodes,
)}
settingKeys={TEMPLATE_SETTING_KEYS}
uid={templateUid}
defaultValue={node.template}
/>
<div className="flex flex-col gap-4">
<div>
<div className="flex items-center">
<Button
minimal
small
icon={isTemplateOpen ? "chevron-down" : "chevron-right"}
text="Template"
onClick={() => setIsTemplateOpen((open) => !open)}
/>
<Description
description={withDocsLink(
`The template that auto fills ${node.text} page when generated.`,
ROAM_DOCS.creatingNodes,
)}
/>
</div>
{/* Collapse unmounts its children, so the editor's ephemeral buffer block is only
created while the template is actually open. */}
<Collapse isOpen={isTemplateOpen}>
{/* The toggle above is this setting's header, so the panel omits its own. */}
<div className="pl-6 pt-2">
<DualWriteBlocksPanel
nodeType={node.type}
description={withDocsLink(
`The template that auto fills ${node.text} page when generated.`,
ROAM_DOCS.creatingNodes,
)}
settingKeys={TEMPLATE_SETTING_KEYS}
uid={templateUid}
defaultValue={node.template}
/>
</div>
</Collapse>
</div>

<DiscourseNodeTextPanel
nodeType={nodeUid}
Expand Down
76 changes: 76 additions & 0 deletions apps/roam/src/components/settings/GrammarNodesRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useEffect } from "react";
import { OnloadArgs } from "roamjs-components/types";
import getDiscourseNodes, {
excludeDefaultNodes,
} from "~/utils/getDiscourseNodes";
import { formatHexColor } from "./DiscourseNodeCanvasSettings";
import { nodeConfigSegmentIds } from "./utils/settingsNavigation";
import { useSettingsNav } from "./navigation/SettingsNavContext";
import SettingsPageHeader from "./navigation/SettingsPageHeader";
import DiscourseNodeConfigPanel from "./DiscourseNodeConfigPanel";
import NodeConfig from "./NodeConfig";
import NodeIndexPage from "./NodeIndexPage";
import NodeTemplatePage from "./NodeTemplatePage";

const NODES_ANCESTOR_LABELS = ["Grammar"] as const;

const SUB_PAGE_LABELS: Record<string, string | undefined> = {
[nodeConfigSegmentIds.index]: "Index",
[nodeConfigSegmentIds.template]: "Template",
};

const GrammarNodesRoute = ({
onloadArgs,
}: {
onloadArgs: OnloadArgs;
}): JSX.Element => {
const { segments, goToDepth } = useSettingsNav();
const nodes = getDiscourseNodes().filter(excludeDefaultNodes);

const [nodeTypeUid, subPage] = segments;
const node = nodeTypeUid
? nodes.find((n) => n.type === nodeTypeUid)
: undefined;

// A deleted node type or stale deep link resolves to nothing; return to the list.
const isStalePath = Boolean(nodeTypeUid) && !node;
useEffect(() => {
if (isStalePath) goToDepth(0);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path outlives this panel — renderActiveTabPanelOnly unmounts and remounts it on every tab switch — so it can point at a node type deleted in the meantime, or arrive stale from a saved link. goToDepth(0) rather than pop() so a depth-2 stale path converges in one dispatch instead of two.

}, [isStalePath, goToDepth]);

const resolveLabel = (segment: string, segmentIndex: number): string =>
segmentIndex === 0
? (nodes.find((n) => n.type === segment)?.text ?? segment)
: (SUB_PAGE_LABELS[segment] ?? segment);

// Sub-pages fall through to the stylesheet's default dot colour.
const dotColor = subPage
? undefined
: formatHexColor(node?.canvasSettings?.color ?? "") || undefined;

return (
<div className="dg-settings-route">
<SettingsPageHeader
ancestorLabels={NODES_ANCESTOR_LABELS}
rootLabel="Nodes"
resolveLabel={resolveLabel}
dotColor={dotColor}
/>
<div className="dg-settings-route__body">
{!node ? (
<div className="p-1">
<DiscourseNodeConfigPanel />
</div>
) : subPage === nodeConfigSegmentIds.index ? (
<NodeIndexPage node={node} onloadArgs={onloadArgs} />
) : subPage === nodeConfigSegmentIds.template ? (
<NodeTemplatePage node={node} />
) : (
<NodeConfig node={node} />
)}
</div>
</div>
);
};

export default GrammarNodesRoute;
Loading
Loading