diff --git a/loom-editor/src/Types.ts b/loom-editor/src/Types.ts index 3bcf489..97f150c 100644 --- a/loom-editor/src/Types.ts +++ b/loom-editor/src/Types.ts @@ -27,4 +27,7 @@ export interface State { /** Node that is currently being focused on */ focusedNode?: string; + + /** Current zoom in the graph */ + currentZoom?: number; } diff --git a/loom-editor/src/components/NodeGraph.tsx b/loom-editor/src/components/NodeGraph.tsx index 262338c..cd5adea 100644 --- a/loom-editor/src/components/NodeGraph.tsx +++ b/loom-editor/src/components/NodeGraph.tsx @@ -16,7 +16,7 @@ import { openNode, setNodePosition } from "loom-common/EditorActions"; import { useYarnState } from "../state/YarnContext"; import NodeGraphView, { NodeSizePx } from "./NodeGraphView"; import { getNodes, getFocusedNode } from "../state/Selectors"; -import { setFocusedNode } from "../state/UiActions"; +import { setFocusedNode, setCurrentZoom } from "../state/UiActions"; const containerStyle = css` width: 100%; @@ -130,7 +130,6 @@ const NodeGraph: FunctionComponent = () => { height: containerNode.offsetHeight, }); - // @ts-ignore https://github.com/Microsoft/TypeScript/issues/28502 const resizeObserver = new ResizeObserver((entries) => { for (let entry of entries) { if (entry.contentRect) { @@ -163,6 +162,10 @@ const NodeGraph: FunctionComponent = () => { onDoubleClickNode={onNodeDoubleClicked} onNodePositionChange={onNodePositionChange} onClickGraph={() => dispatch(setFocusedNode(undefined))} + // @ts-expect-error until react-d3-graph is updated and https://github.com/DefinitelyTyped/DefinitelyTyped/pull/46632 is merged + onZoomChange={(previousZoom, newZoom) => + dispatch(setCurrentZoom(newZoom)) + } /> ); diff --git a/loom-editor/src/components/NodeGraphView/NodeBody.test.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeBody.test.tsx similarity index 100% rename from loom-editor/src/components/NodeGraphView/NodeBody.test.tsx rename to loom-editor/src/components/NodeGraphView/NodeWithBody/NodeBody.test.tsx diff --git a/loom-editor/src/components/NodeGraphView/NodeBody.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeBody.tsx similarity index 100% rename from loom-editor/src/components/NodeGraphView/NodeBody.tsx rename to loom-editor/src/components/NodeGraphView/NodeWithBody/NodeBody.tsx diff --git a/loom-editor/src/components/NodeGraphView/NodeColorChooser.test.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeColorChooser.test.tsx similarity index 100% rename from loom-editor/src/components/NodeGraphView/NodeColorChooser.test.tsx rename to loom-editor/src/components/NodeGraphView/NodeWithBody/NodeColorChooser.test.tsx diff --git a/loom-editor/src/components/NodeGraphView/NodeColorChooser.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeColorChooser.tsx similarity index 93% rename from loom-editor/src/components/NodeGraphView/NodeColorChooser.tsx rename to loom-editor/src/components/NodeGraphView/NodeWithBody/NodeColorChooser.tsx index c7880a5..1a79703 100644 --- a/loom-editor/src/components/NodeGraphView/NodeColorChooser.tsx +++ b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeColorChooser.tsx @@ -4,8 +4,8 @@ import { FunctionComponent } from "react"; import { setNodeColor } from "loom-common/EditorActions"; -import { nodeColors } from "./index"; -import { buttonBase, nodeOverlayContainer } from "../../Styles"; +import { nodeColors } from "../index"; +import { buttonBase, nodeOverlayContainer } from "../../../Styles"; const containerStyle = css` ${nodeOverlayContainer} diff --git a/loom-editor/src/components/NodeGraphView/NodeFooter.test.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeFooter.test.tsx similarity index 94% rename from loom-editor/src/components/NodeGraphView/NodeFooter.test.tsx rename to loom-editor/src/components/NodeGraphView/NodeWithBody/NodeFooter.test.tsx index bbe1038..47c459b 100644 --- a/loom-editor/src/components/NodeGraphView/NodeFooter.test.tsx +++ b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeFooter.test.tsx @@ -1,8 +1,8 @@ import React from "react"; import { screen, fireEvent } from "@testing-library/react"; -import { renderWithProvider } from "../../utils/test-utils"; +import { renderWithProvider } from "../../../utils/test-utils"; -import { searchForTag } from "../../state/UiActions"; +import { searchForTag } from "../../../state/UiActions"; import NodeFooter from "./NodeFooter"; import { YarnNode } from "loom-common/YarnNode"; diff --git a/loom-editor/src/components/NodeGraphView/NodeFooter.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeFooter.tsx similarity index 89% rename from loom-editor/src/components/NodeGraphView/NodeFooter.tsx rename to loom-editor/src/components/NodeGraphView/NodeWithBody/NodeFooter.tsx index b8562f5..e4fda6c 100644 --- a/loom-editor/src/components/NodeGraphView/NodeFooter.tsx +++ b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeFooter.tsx @@ -2,11 +2,11 @@ import { css } from "@emotion/react/macro"; import { FunctionComponent } from "react"; -import { useYarnState } from "../../state/YarnContext"; -import { searchForTag } from "../../state/UiActions"; -import UiActionType from "../../state/UiActionType"; +import { useYarnState } from "../../../state/YarnContext"; +import { searchForTag } from "../../../state/UiActions"; +import UiActionType from "../../../state/UiActionType"; -import { ReactComponent as AddIcon } from "../../icons/add.svg"; +import { ReactComponent as AddIcon } from "../../../icons/add.svg"; import { YarnNode } from "loom-common/YarnNode"; const containerStyle = css` diff --git a/loom-editor/src/components/NodeGraphView/NodeHeader.test.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeHeader.test.tsx similarity index 96% rename from loom-editor/src/components/NodeGraphView/NodeHeader.test.tsx rename to loom-editor/src/components/NodeGraphView/NodeWithBody/NodeHeader.test.tsx index 0c595fe..31810a8 100644 --- a/loom-editor/src/components/NodeGraphView/NodeHeader.test.tsx +++ b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeHeader.test.tsx @@ -1,6 +1,6 @@ import React from "react"; import { screen, fireEvent } from "@testing-library/react"; -import { renderWithProvider } from "../../utils/test-utils"; +import { renderWithProvider } from "../../../utils/test-utils"; import { deleteNode, renameNode } from "loom-common/EditorActions"; diff --git a/loom-editor/src/components/NodeGraphView/NodeHeader.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeHeader.tsx similarity index 88% rename from loom-editor/src/components/NodeGraphView/NodeHeader.tsx rename to loom-editor/src/components/NodeGraphView/NodeWithBody/NodeHeader.tsx index 138575d..d9bea08 100644 --- a/loom-editor/src/components/NodeGraphView/NodeHeader.tsx +++ b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeHeader.tsx @@ -4,9 +4,9 @@ import { FunctionComponent } from "react"; import { deleteNode, renameNode } from "loom-common/EditorActions"; -import { ReactComponent as RenameIcon } from "../../icons/rename.svg"; -import { ReactComponent as TrashIcon } from "../../icons/trash.svg"; -import { ReactComponent as ColorIcon } from "../../icons/symbol-color.svg"; +import { ReactComponent as RenameIcon } from "../../../icons/rename.svg"; +import { ReactComponent as TrashIcon } from "../../../icons/trash.svg"; +import { ReactComponent as ColorIcon } from "../../../icons/symbol-color.svg"; const titleStyle = css` padding: 10px; @@ -19,6 +19,9 @@ const titleStyle = css` const titleLabelStyle = css` flex: 1; + + max-width: 94px; + overflow-x: hidden; `; const settingsButtonStyle = css` diff --git a/loom-editor/src/components/NodeGraphView/NodeTagChooser.test.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeTagChooser.test.tsx similarity index 94% rename from loom-editor/src/components/NodeGraphView/NodeTagChooser.test.tsx rename to loom-editor/src/components/NodeGraphView/NodeWithBody/NodeTagChooser.test.tsx index 8ebdc98..2c32fc1 100644 --- a/loom-editor/src/components/NodeGraphView/NodeTagChooser.test.tsx +++ b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeTagChooser.test.tsx @@ -1,12 +1,12 @@ import React from "react"; import { screen, fireEvent } from "@testing-library/react"; -import { renderWithProvider } from "../../utils/test-utils"; +import { renderWithProvider } from "../../../utils/test-utils"; import { toggleTagOnNode, promptForNewTags } from "loom-common/EditorActions"; import NodeTagChooser from "./NodeTagChooser"; -import { defaultState } from "../../state/YarnContext"; -import { State } from "../../Types"; +import { defaultState } from "../../../state/YarnContext"; +import { State } from "../../../Types"; describe("", () => { const node = { diff --git a/loom-editor/src/components/NodeGraphView/NodeTagChooser.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeTagChooser.tsx similarity index 88% rename from loom-editor/src/components/NodeGraphView/NodeTagChooser.tsx rename to loom-editor/src/components/NodeGraphView/NodeWithBody/NodeTagChooser.tsx index 5fda180..2890ec4 100644 --- a/loom-editor/src/components/NodeGraphView/NodeTagChooser.tsx +++ b/loom-editor/src/components/NodeGraphView/NodeWithBody/NodeTagChooser.tsx @@ -4,13 +4,13 @@ import { FunctionComponent } from "react"; import { YarnNode } from "loom-common/YarnNode"; -import { getNodes } from "../../state/Selectors"; -import { useYarnState } from "../../state/YarnContext"; +import { getNodes } from "../../../state/Selectors"; +import { useYarnState } from "../../../state/YarnContext"; -import { ReactComponent as PlusIcon } from "../../icons/add.svg"; -import { ReactComponent as CheckIcon } from "../../icons/check.svg"; +import { ReactComponent as PlusIcon } from "../../../icons/add.svg"; +import { ReactComponent as CheckIcon } from "../../../icons/check.svg"; -import { listItemBase, nodeOverlayContainer } from "../../Styles"; +import { listItemBase, nodeOverlayContainer } from "../../../Styles"; import { toggleTagOnNode, promptForNewTags } from "loom-common/EditorActions"; diff --git a/loom-editor/src/components/NodeGraphView/NodeWithBody/index.test.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/index.test.tsx new file mode 100644 index 0000000..c36835b --- /dev/null +++ b/loom-editor/src/components/NodeGraphView/NodeWithBody/index.test.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { renderWithProvider } from "../../../utils/test-utils"; +import { screen, fireEvent } from "@testing-library/react"; + +import { YarnNode } from "loom-common/YarnNode"; + +import NodeWithBody from "./"; + +describe("", () => { + const mockNode: YarnNode = { + title: "Mock Node", + body: "Some body", + tags: "cool tags", + }; + + const mockNodeColor = "blue"; + const mockNodeColorIsDark = true; + + it("renders", () => { + renderWithProvider( + + ); + }); + + it("renders no tags if there are none", () => { + const nodeWithNoTags = { + ...mockNode, + tags: "", + }; + renderWithProvider( + + ); + expect(screen.queryByTestId("node-graph-view-tags")).toBeNull(); + }); + + it("opens the color chooser", () => { + renderWithProvider( + + ); + + expect(screen.queryByTestId("node-title-color-chooser")).toBeNull(); + + fireEvent.click(screen.getByTitle("Change node color")); + + expect(screen.queryByTestId("node-title-color-chooser")).not.toBeNull(); + }); + + it("opens the tag chooser", () => { + renderWithProvider( + + ); + + expect(screen.queryByTestId("node-tag-chooser")).toBeNull(); + + fireEvent.click(screen.getByTitle("Manage node tags")); + + expect(screen.queryByTestId("node-tag-chooser")).not.toBeNull(); + }); +}); diff --git a/loom-editor/src/components/NodeGraphView/NodeWithBody/index.tsx b/loom-editor/src/components/NodeGraphView/NodeWithBody/index.tsx new file mode 100644 index 0000000..57a6040 --- /dev/null +++ b/loom-editor/src/components/NodeGraphView/NodeWithBody/index.tsx @@ -0,0 +1,84 @@ +import React, { Fragment, FunctionComponent, useState } from "react"; + +import { YarnNode } from "loom-common/YarnNode"; + +import NodeHeader from "./NodeHeader"; +import NodeFooter from "./NodeFooter"; +import NodeBody from "./NodeBody"; +import NodeColorChooser from "./NodeColorChooser"; +import NodeTagChooser from "./NodeTagChooser"; + +/** + * Render tha body of the node. + * If the color or tag chooser are open, those are rendered instead. + * (because of rendering bugs on Ubuntu, we have to render them instead of the body...) + * + * @param colorChooserOpen Whether or not the color chooser is open + * @param closeColorChooser Function to call to open the color chooser + * @param tagChooserOpen Whether or not the tag chooser is open + * @param closeTagChooser Function to call to close the tag chooser + * @param node Node to render body for + */ +const renderBody = ( + colorChooserOpen: boolean, + closeColorChooser: () => void, + tagChooserOpen: boolean, + closeTagChooser: () => void, + node: YarnNode +) => { + const { title, body } = node; + + if (colorChooserOpen) { + return ; + } + + if (tagChooserOpen) { + return ; + } + + return ; +}; + +interface NodeWithBodyProps { + yarnNode: YarnNode; + nodeColor: string; + nodeColorIsDark: boolean; +} + +const NodeWithBody: FunctionComponent = ({ + yarnNode, + nodeColor, + nodeColorIsDark, +}) => { + const [colorChooserOpen, setColorChooserOpen] = useState(false); + const [tagChooserOpen, setTagChooserOpen] = useState(false); + + const { title } = yarnNode; + + return ( + + setColorChooserOpen(!colorChooserOpen)} + /> + {renderBody( + colorChooserOpen, + () => setColorChooserOpen(false), + tagChooserOpen, + () => setTagChooserOpen(false), + yarnNode + )} + setTagChooserOpen(true)} + data-testid="node-graph-view-tags" + /> + + ); +}; + +export default NodeWithBody; diff --git a/loom-editor/src/components/NodeGraphView/ZoomedOutNode.test.tsx b/loom-editor/src/components/NodeGraphView/ZoomedOutNode.test.tsx new file mode 100644 index 0000000..2d9965b --- /dev/null +++ b/loom-editor/src/components/NodeGraphView/ZoomedOutNode.test.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; + +import { YarnNode } from "loom-common/YarnNode"; + +import ZoomedOutNode from "./ZoomedOutNode"; + +describe("", () => { + const mockNode: YarnNode = { + title: "Mock Node", + body: "Some body", + tags: "cool tags", + }; + + it("renders", () => { + render( + + ); + }); + + it("shows tags when not super zoomed out", () => { + render( + + ); + + // 2 because we have "cool tags" in our mock node + expect(screen.queryAllByTestId("zoomed-out-node-tag")).toHaveLength(2); + }); + + it("hides tags when really zoomed out", () => { + render( + + ); + + // these don't render if we're REALLY zoomed out + expect(screen.queryAllByTestId("zoomed-out-node-tag")).toHaveLength(0); + }); +}); diff --git a/loom-editor/src/components/NodeGraphView/ZoomedOutNode.tsx b/loom-editor/src/components/NodeGraphView/ZoomedOutNode.tsx new file mode 100644 index 0000000..4fd3cb8 --- /dev/null +++ b/loom-editor/src/components/NodeGraphView/ZoomedOutNode.tsx @@ -0,0 +1,164 @@ +/** @jsxImportSource @emotion/react */ +import { css } from "@emotion/react/macro"; +import { FunctionComponent } from "react"; + +import { YarnNode } from "loom-common/YarnNode"; + +// NOTE: This is also defined in NodeGraphView, but it cannot be imported because +// something funny happens when the package is built and it gets erased 💥 +export const NodeSizePx = 200; + +/** The zoom distance at which to switch to "real big' fonts */ +const ExtraZoomedOutNodeDistance = 0.2; + +const containerStyle = css` + grid-row: 1 / 3; /* fills the whole container */ + + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + + width: ${NodeSizePx}px; +`; + +const titleStyle = css` + font-size: 32px; + font-weight: 500; + + word-wrap: break-word; + display: inline-block; + + width: ${NodeSizePx}px; + max-height: 147px; +`; + +/** Used when we're "extra" zoomed out and only showing the title (not the tags) */ +const extraZoomedOutTitleStyle = css` + ${titleStyle} + + max-height: 100%; +`; + +const tagsContainerStyle = css` + width: ${NodeSizePx}px; + font-size: 22px; + margin-top: 15px; + + display: flex; + flex-wrap: wrap; +`; + +const tagStyle = css` + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + + padding-top: 2px; + padding-bottom: 2px; + padding-left: 5px; + padding-right: 5px; + + margin: 2px; +`; + +interface ZoomedOutNodeProps { + yarnNode: YarnNode; + nodeColor: string; + nodeColorIsDark: boolean; + currentZoom: number; +} + +/** + * This is a bad approximation of the correct font-size for when showing the + * "extra zoomed out" node. This isn't pretty but it works Good Enough™. + * + * @param titleLength Length of title + */ +const getFontSizePxForExtraZoomedOutTitle = (titleLength: number): number => { + if (titleLength > 40) { + return 36; + } + + if (titleLength > 30) { + return 45; + } + + if (titleLength >= 15) { + return 50; + } + + if (titleLength >= 12) { + return 70; + } + + return 75; +}; + +/** + * Get the style to use for the title string. + * + * @param extraZoomedOut Whether or not we're currently "extra" zoomed out + * @param nodeTitleLength The length of the title string + */ +const getTitleStyle = (extraZoomedOut: boolean, nodeTitleLength: number) => { + // not extra zoomed out; return the regular style + if (!extraZoomedOut) { + return titleStyle; + } + + // calculate our font size for the extra-zoomed-out title + return css` + ${extraZoomedOutTitleStyle} + font-size: ${getFontSizePxForExtraZoomedOutTitle(nodeTitleLength)}px; + `; +}; + +/** + * This is rendered instead of NodeWithBody when the graph is sufficiently zoomed out. + * By default, this shows tags. When zoomed out even further, it hides the tags. + */ +const ZoomedOutNode: FunctionComponent = ({ + yarnNode, + nodeColor, + nodeColorIsDark, + currentZoom, +}) => { + const fontColor = nodeColorIsDark ? "white" : "black"; + + const extraZoomedOut = currentZoom <= ExtraZoomedOutNodeDistance; + + return ( +
+
+ {yarnNode.title} +
+ + {/* Don't show tags if we're zoomed out far enough */} + {!extraZoomedOut && ( +
+ {yarnNode.tags.split(" ").map( + (tag) => + tag.length !== 0 && ( +
+ {tag} +
+ ) + )} +
+ )} +
+ ); +}; + +export default ZoomedOutNode; diff --git a/loom-editor/src/components/NodeGraphView/index.test.tsx b/loom-editor/src/components/NodeGraphView/index.test.tsx index 9bea9f6..cc621b0 100644 --- a/loom-editor/src/components/NodeGraphView/index.test.tsx +++ b/loom-editor/src/components/NodeGraphView/index.test.tsx @@ -1,9 +1,11 @@ import React from "react"; +import { screen } from "@testing-library/react"; + import { renderWithProvider } from "../../utils/test-utils"; -import { screen, fireEvent } from "@testing-library/react"; import NodeGraphView from "./"; import { YarnGraphNode } from "../NodeGraph"; +import { defaultState } from "../../state/YarnContext"; describe("", () => { const mockNode: YarnGraphNode = { @@ -19,36 +21,26 @@ describe("", () => { renderWithProvider(); }); - it("renders no tags if there are none", () => { - const nodeWithNoTags = { - ...mockNode, - yarnNode: { - ...mockNode.yarnNode, - tags: "", - }, - }; - renderWithProvider(); - expect(screen.queryByTestId("node-graph-view-tags")).toBeNull(); - }); - - it("opens the color chooser", () => { - renderWithProvider(); - - expect(screen.queryByTestId("node-title-color-chooser")).toBeNull(); - - fireEvent.click(screen.getByTitle("Change node color")); - - expect(screen.queryByTestId("node-title-color-chooser")).not.toBeNull(); - }); - - it("opens the tag chooser", () => { - renderWithProvider(); + describe("zooming", () => { + it("renders NodeWithBody when not zoomed out", () => { + renderWithProvider(, { + ...defaultState, + currentZoom: 1.0, + }); - expect(screen.queryByTestId("node-tag-chooser")).toBeNull(); + screen.getByTestId("node-body-text"); + expect(screen.queryByTestId("zoomed-out-node")).toBeNull(); + }); - fireEvent.click(screen.getByTitle("Manage node tags")); + it("renders ZoomedOutNode when zoomed out", () => { + renderWithProvider(, { + ...defaultState, + currentZoom: 0.05, + }); - expect(screen.queryByTestId("node-tag-chooser")).not.toBeNull(); + screen.getByTestId("zoomed-out-node"); + expect(screen.queryByTestId("node-body-text")).toBeNull(); + }); }); describe("searching", () => { diff --git a/loom-editor/src/components/NodeGraphView/index.tsx b/loom-editor/src/components/NodeGraphView/index.tsx index 1fefeeb..9902f91 100644 --- a/loom-editor/src/components/NodeGraphView/index.tsx +++ b/loom-editor/src/components/NodeGraphView/index.tsx @@ -1,6 +1,8 @@ /** @jsxImportSource @emotion/react */ import { css } from "@emotion/react/macro"; -import { FunctionComponent, useState } from "react"; +import { FunctionComponent } from "react"; + +import { YarnNode } from "loom-common/YarnNode"; import { useYarnState } from "../../state/YarnContext"; import { @@ -10,17 +12,15 @@ import { getSearchString, getCaseSensitivityEnabled, getRegexEnabled, + getCurrentZoom, } from "../../state/Selectors"; import { YarnGraphNode } from "../NodeGraph"; -import NodeHeader from "./NodeHeader"; -import NodeFooter from "./NodeFooter"; -import NodeBody from "./NodeBody"; -import NodeColorChooser from "./NodeColorChooser"; -import { YarnNode } from "loom-common/YarnNode"; -import NodeTagChooser from "./NodeTagChooser"; import { isDark } from "../../Util"; +import NodeWithBody from "./NodeWithBody"; +import ZoomedOutNode from "./ZoomedOutNode"; + /** CSS colors to cycle through for the "colorID" of a yarn node */ export const nodeColors = [ "#EBEBEB", @@ -34,9 +34,18 @@ export const nodeColors = [ "#000000", ]; -/** The width and height of the node's wrapper container */ +/** + * The width and height of the node's wrapper container + * + * NOTE: While this is exported, the export can NOT be used in + * CSS template strings or it will be ignored. This has something to do with + * the way that emotion creates its packaged CSS. + */ export const NodeSizePx = 200; +/** The zoom distance at which to switch from NodeWithBody to ZoomedOutNode */ +const ZoomedOutNodeDistance = 0.5; + const containerStyle = css` background: white; color: black; @@ -139,37 +148,6 @@ const isSearched = ( return searched; }; -/** - * Render tha body of the node. - * If the color or tag chooser are open, those are rendered instead. - * (because of rendering bugs on Ubuntu, we have to render them instead of the body...) - * - * @param colorChooserOpen Whether or not the color chooser is open - * @param closeColorChooser Function to call to open the color chooser - * @param tagChooserOpen Whether or not the tag chooser is open - * @param closeTagChooser Function to call to close the tag chooser - * @param node Node to render body for - */ -const renderBody = ( - colorChooserOpen: boolean, - closeColorChooser: () => void, - tagChooserOpen: boolean, - closeTagChooser: () => void, - node: YarnNode -) => { - const { title, body } = node; - - if (colorChooserOpen) { - return ; - } - - if (tagChooserOpen) { - return ; - } - - return ; -}; - interface NodeGraphViewProps { node: YarnGraphNode; } @@ -178,10 +156,8 @@ const NodeGraphView: FunctionComponent = ({ node: { yarnNode }, }) => { const [state] = useYarnState(); - const [colorChooserOpen, setColorChooserOpen] = useState(false); - const [tagChooserOpen, setTagChooserOpen] = useState(false); - const { colorID, title } = yarnNode; + const { colorID } = yarnNode; if (!state) { return null; @@ -193,6 +169,7 @@ const NodeGraphView: FunctionComponent = ({ const caseSensitivityEnabled = getCaseSensitivityEnabled(state); const regexEnabled = getRegexEnabled(state); const searchString = getSearchString(state); + const currentZoom = getCurrentZoom(state); // if we're searching for something, and this node matches that something, // then this will be true... if this is false, the node is rendered as "dimmed" @@ -206,6 +183,8 @@ const NodeGraphView: FunctionComponent = ({ regexEnabled ); + const zoomedOut = currentZoom && currentZoom <= ZoomedOutNodeDistance; + // grab the color by its ID and determine if it is dark or not const nodeColor = nodeColors[colorID || 0]; const nodeColorIsDark = isDark(nodeColor); @@ -219,26 +198,20 @@ const NodeGraphView: FunctionComponent = ({ searched ? "node-graph-view-searched" : "node-graph-view-not-searched" } > - setColorChooserOpen(!colorChooserOpen)} - /> - {renderBody( - colorChooserOpen, - () => setColorChooserOpen(false), - tagChooserOpen, - () => setTagChooserOpen(false), - yarnNode + {zoomedOut ? ( + + ) : ( + )} - setTagChooserOpen(true)} - data-testid="node-graph-view-tags" - /> ); }; diff --git a/loom-editor/src/state/Reducer.test.ts b/loom-editor/src/state/Reducer.test.ts index a5687ec..d812a71 100644 --- a/loom-editor/src/state/Reducer.test.ts +++ b/loom-editor/src/state/Reducer.test.ts @@ -11,6 +11,7 @@ import { searchForTag, setSearchCaseSensitive, setSearchRegexEnabled, + setCurrentZoom, } from "./UiActions"; describe("Reducer", () => { @@ -142,4 +143,10 @@ describe("Reducer", () => { expect(reduced.search.searchingTags).toBe(true); }); }); + + it("handles UiActionType.SetCurrentZoom", () => { + expect(startState.currentZoom).toBeUndefined(); + const reduced = reducer(startState, setCurrentZoom(0.5)); + expect(reduced.currentZoom).toBe(0.5); + }); }); diff --git a/loom-editor/src/state/Reducer.ts b/loom-editor/src/state/Reducer.ts index 3fbc57c..39ead71 100644 --- a/loom-editor/src/state/Reducer.ts +++ b/loom-editor/src/state/Reducer.ts @@ -111,6 +111,11 @@ const reducer = (state: State, action: EditorActions | UiActions): State => { }; case UiMessageTypes.SearchForTag: return searchForTag(state, action.payload.tag); + case UiMessageTypes.SetCurrentZoom: + return { + ...state, + currentZoom: action.payload.zoom, + }; default: return state; } diff --git a/loom-editor/src/state/Selectors.ts b/loom-editor/src/state/Selectors.ts index 1d19858..0458426 100644 --- a/loom-editor/src/state/Selectors.ts +++ b/loom-editor/src/state/Selectors.ts @@ -11,3 +11,4 @@ export const getRegexEnabled = (state: State) => state.search.regexEnabled; export const getSearchString = (state: State) => state.search.searchString; export const getFocusedNode = (state?: State) => state?.focusedNode; +export const getCurrentZoom = (state?: State) => state?.currentZoom; diff --git a/loom-editor/src/state/UiActions.ts b/loom-editor/src/state/UiActions.ts index 9ec2da1..666553a 100644 --- a/loom-editor/src/state/UiActions.ts +++ b/loom-editor/src/state/UiActions.ts @@ -25,6 +25,9 @@ export enum UiMessageTypes { /** Search for a specific tag */ SearchForTag = "SearchForTag", + + /** Set the current zoom level of the graph */ + SetCurrentZoom = "SetCurrentZoom", } export const setSearchingNodeTitles = (searchingTitle: boolean) => @@ -50,3 +53,6 @@ export const setFocusedNode = (nodeTitle?: string) => export const searchForTag = (tag: string) => action(UiMessageTypes.SearchForTag, { tag }); + +export const setCurrentZoom = (zoom: number) => + action(UiMessageTypes.SetCurrentZoom, { zoom });