From 927c0bc1f4011324b439553deae2f238ccb0449e Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Fri, 9 Feb 2024 21:27:43 -0600 Subject: [PATCH 1/2] init --- src/components/CanvasDrawer.tsx | 130 ++++++++++++++++++++++++++++++++ src/index.ts | 39 +++++++++- 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/components/CanvasDrawer.tsx diff --git a/src/components/CanvasDrawer.tsx b/src/components/CanvasDrawer.tsx new file mode 100644 index 00000000..e12ddd0a --- /dev/null +++ b/src/components/CanvasDrawer.tsx @@ -0,0 +1,130 @@ +import React, { useEffect, useMemo, useState } from "react"; +import ResizableDrawer from "./ResizableDrawer"; +import renderOverlay from "roamjs-components/util/renderOverlay"; +import { DiscourseNodeShape } from "./TldrawCanvas"; +import { Button, Collapse, Checkbox } from "@blueprintjs/core"; +import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; +import MenuItemSelect from "roamjs-components/components/MenuItemSelect"; + +export type GroupedShapes = Record; + +type Props = { + groupedShapes: GroupedShapes; + pageUid: string; +}; + +const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => { + const pageTitle = useMemo(() => getPageTitleByPageUid(pageUid), []); + const noResults = Object.keys(groupedShapes).length === 0; + + const [openSections, setOpenSections] = useState>({}); + const [showDuplicates, setShowDuplicates] = useState(false); + const [filterType, setFilterType] = useState("All"); + const [filteredShapes, setFilteredShapes] = useState({}); + + useEffect(() => { + const filtered = Object.entries(groupedShapes).reduce( + (acc, [uid, shapes]) => { + const filteredShapes = shapes.filter((shape) => { + if (filterType === "All") return true; + const matchesType = filterType ? shape.type === filterType : true; + return matchesType; + }); + + if ( + filteredShapes.length > 0 && + (!showDuplicates || filteredShapes.length > 1) + ) { + acc[uid] = filteredShapes; + } + + return acc; + }, + {} + ); + + setFilteredShapes(filtered); + }, [groupedShapes, showDuplicates, filterType]); + + const toggleCollapse = (uid: string) => { + setOpenSections((prevState) => ({ + ...prevState, + [uid]: !prevState[uid], + })); + }; + + return ( +
+
+ setFilterType(type)} + activeItem={filterType} + items={["All", "type1", "type2", "type3"]} + /> + setShowDuplicates(!showDuplicates)} + /> +
+ {noResults ? ( +
No nodes found for {pageTitle}
+ ) : ( + Object.entries(filteredShapes).map(([uid, shapes]) => { + const title = shapes[0].props.title; + const isExpandable = shapes.length > 1; + return ( +
+ + +
+ {shapes.map((shape) => ( + + ))} +
+
+
+ ); + }) + )} +
+ ); +}; + +const CanvasDrawer = ({ + onClose, + ...props +}: { onClose: () => void } & Props) => ( + + + +); + +export const render = (props: Props) => + renderOverlay({ Overlay: CanvasDrawer, props }); + +export default CanvasDrawer; diff --git a/src/index.ts b/src/index.ts index ba2c08fe..8cccce85 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,10 +29,14 @@ import initializeDiscourseGraphsMode, { } from "./discourseGraphsMode"; import getPageMetadata from "./utils/getPageMetadata"; import { render as queryRender } from "./components/QueryDrawer"; +import { render as canvasDrawerRender } from "./components/CanvasDrawer"; import createPage from "roamjs-components/writes/createPage"; import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; import isLiveBlock from "roamjs-components/queries/isLiveBlock"; -import { renderTldrawCanvas } from "./components/TldrawCanvas"; +import { + DiscourseNodeShape, + renderTldrawCanvas, +} from "./components/TldrawCanvas"; import { QBGlobalRefs } from "./utils/types"; import localStorageSet from "roamjs-components/util/localStorageSet"; import localStorageGet from "roamjs-components/util/localStorageGet"; @@ -45,6 +49,9 @@ import getBlockProps, { json } from "./utils/getBlockProps"; import resolveQueryBuilderRef from "./utils/resolveQueryBuilderRef"; import getBlockUidFromTarget from "roamjs-components/dom/getBlockUidFromTarget"; import { render as renderToast } from "roamjs-components/components/Toast"; +import getCurrentPageUid from "roamjs-components/dom/getCurrentPageUid"; +import { TLBaseShape, TLStore } from "@tldraw/tldraw"; +import { GroupedShapes } from "./components/CanvasDrawer"; const loadedElsewhere = document.currentScript ? document.currentScript.getAttribute("data-source") === "discourse-graph" @@ -595,6 +602,35 @@ svg.rs-svg-container { ).map((b) => ({ uid: b[0][":block/uid"] || "" })), }; + extensionAPI.ui.commandPalette.addCommand({ + label: "Show All Canvas Nodes", + callback: () => { + const pageUid = getCurrentPageUid(); + const props = getBlockProps(pageUid) as Record; + const rjsqb = props["roamjs-query-builder"] as Record; + const tldraw = (rjsqb?.tldraw as Record) || {}; + const shapes = Object.values(tldraw).filter((s) => { + const shape = s as TLBaseShape; + const uid = shape.props?.uid; + return !!uid; + }) as DiscourseNodeShape[]; + + const groupShapesByUid = (shapes: DiscourseNodeShape[]) => { + const groupedShapes = shapes.reduce((acc: GroupedShapes, shape) => { + const uid = shape.props.uid; + if (!acc[uid]) acc[uid] = []; + acc[uid].push(shape); + return acc; + }, {}); + + return groupedShapes; + }; + + const groupedShapes = groupShapesByUid(shapes); + canvasDrawerRender({ groupedShapes, pageUid }); + }, + }); + extensionAPI.ui.commandPalette.addCommand({ label: "Open Query Drawer", callback: () => @@ -606,6 +642,7 @@ svg.rs-svg-container { ).then((blockUid) => queryRender({ blockUid, + // @ts-ignore clearOnClick, onloadArgs, }) From c4520c7d4d962ce7d4a55b85ea3e3bb38696ce59 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Sun, 11 Feb 2024 21:34:11 -0600 Subject: [PATCH 2/2] add filter by type and move to camera --- src/components/CanvasDrawer.tsx | 76 +++++++++++++++++++++++---------- src/components/TldrawCanvas.tsx | 70 ++++++++++++++++++++++++------ src/index.ts | 3 +- 3 files changed, 112 insertions(+), 37 deletions(-) diff --git a/src/components/CanvasDrawer.tsx b/src/components/CanvasDrawer.tsx index e12ddd0a..ffd634cc 100644 --- a/src/components/CanvasDrawer.tsx +++ b/src/components/CanvasDrawer.tsx @@ -5,6 +5,7 @@ import { DiscourseNodeShape } from "./TldrawCanvas"; import { Button, Collapse, Checkbox } from "@blueprintjs/core"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; import MenuItemSelect from "roamjs-components/components/MenuItemSelect"; +import getDiscourseNodes from "../utils/getDiscourseNodes"; export type GroupedShapes = Record; @@ -14,37 +15,53 @@ type Props = { }; const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => { - const pageTitle = useMemo(() => getPageTitleByPageUid(pageUid), []); - const noResults = Object.keys(groupedShapes).length === 0; - const [openSections, setOpenSections] = useState>({}); const [showDuplicates, setShowDuplicates] = useState(false); const [filterType, setFilterType] = useState("All"); const [filteredShapes, setFilteredShapes] = useState({}); + const pageTitle = useMemo(() => getPageTitleByPageUid(pageUid), []); + const noResults = Object.keys(groupedShapes).length === 0; + const typeToTitleMap = useMemo(() => { + const nodes = getDiscourseNodes(); + const map: { [key: string]: string } = {}; + nodes.forEach((node) => { + map[node.type] = node.text; + }); + return map; + }, []); + const shapeTypes = useMemo(() => { + const allTypes = new Set(["All"]); + Object.values(groupedShapes).forEach((shapes) => + shapes.forEach((shape) => + allTypes.add(typeToTitleMap[shape.type] || shape.type) + ) + ); + return Array.from(allTypes); + }, [groupedShapes, typeToTitleMap]); + const hasDuplicates = useMemo(() => { + return Object.values(groupedShapes).some((shapes) => shapes.length > 1); + }, [groupedShapes]); + useEffect(() => { const filtered = Object.entries(groupedShapes).reduce( (acc, [uid, shapes]) => { - const filteredShapes = shapes.filter((shape) => { - if (filterType === "All") return true; - const matchesType = filterType ? shape.type === filterType : true; - return matchesType; - }); - + const filteredShapes = shapes.filter( + (shape) => + filterType === "All" || typeToTitleMap[shape.type] === filterType + ); if ( filteredShapes.length > 0 && (!showDuplicates || filteredShapes.length > 1) ) { acc[uid] = filteredShapes; } - return acc; }, {} ); - setFilteredShapes(filtered); - }, [groupedShapes, showDuplicates, filterType]); + }, [groupedShapes, showDuplicates, filterType, typeToTitleMap]); const toggleCollapse = (uid: string) => { setOpenSections((prevState) => ({ @@ -52,6 +69,16 @@ const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => { [uid]: !prevState[uid], })); }; + const moveCameraToShape = (shapeId: string) => { + document.dispatchEvent( + new CustomEvent("roamjs:query-builder:action", { + detail: { + action: "move-camera-to-shape", + shapeId, + }, + }) + ); + }; return (
@@ -59,13 +86,15 @@ const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => { setFilterType(type)} activeItem={filterType} - items={["All", "type1", "type2", "type3"]} - /> - setShowDuplicates(!showDuplicates)} + items={shapeTypes} /> + {hasDuplicates && ( + setShowDuplicates(!showDuplicates)} + /> + )}
{noResults ? (
No nodes found for {pageTitle}
@@ -74,9 +103,12 @@ const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => { const title = shapes[0].props.title; const isExpandable = shapes.length > 1; return ( -
+