Skip to content

Commit

Permalink
Render nodes differently when zoomed out (#8)
Browse files Browse the repository at this point in the history
This changes it so that, when zoomed out, nodes will not render their bodies and will instead just render their title. Tags are also rendered until you zoom out far enough, then you just get the title.

This also fixes long node titles causing the rename/change color/delete buttons to be cut off.
  • Loading branch information
Nate Moore authored Jun 19, 2021
1 parent deb1a5a commit 03219c5
Show file tree
Hide file tree
Showing 22 changed files with 480 additions and 111 deletions.
3 changes: 3 additions & 0 deletions loom-editor/src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ export interface State {

/** Node that is currently being focused on */
focusedNode?: string;

/** Current zoom in the graph */
currentZoom?: number;
}
7 changes: 5 additions & 2 deletions loom-editor/src/components/NodeGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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))
}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Original file line number Diff line number Diff line change
@@ -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";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,6 +19,9 @@ const titleStyle = css`

const titleLabelStyle = css`
flex: 1;
max-width: 94px;
overflow-x: hidden;
`;

const settingsButtonStyle = css`
Expand Down
Original file line number Diff line number Diff line change
@@ -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("<NodeTagChooser />", () => {
const node = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
Original file line number Diff line number Diff line change
@@ -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("<NodeWithBody />", () => {
const mockNode: YarnNode = {
title: "Mock Node",
body: "Some body",
tags: "cool tags",
};

const mockNodeColor = "blue";
const mockNodeColorIsDark = true;

it("renders", () => {
renderWithProvider(
<NodeWithBody
yarnNode={mockNode}
nodeColor={mockNodeColor}
nodeColorIsDark={mockNodeColorIsDark}
/>
);
});

it("renders no tags if there are none", () => {
const nodeWithNoTags = {
...mockNode,
tags: "",
};
renderWithProvider(
<NodeWithBody
yarnNode={nodeWithNoTags}
nodeColor={mockNodeColor}
nodeColorIsDark={mockNodeColorIsDark}
/>
);
expect(screen.queryByTestId("node-graph-view-tags")).toBeNull();
});

it("opens the color chooser", () => {
renderWithProvider(
<NodeWithBody
yarnNode={mockNode}
nodeColor={mockNodeColor}
nodeColorIsDark={mockNodeColorIsDark}
/>
);

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(
<NodeWithBody
yarnNode={mockNode}
nodeColor={mockNodeColor}
nodeColorIsDark={mockNodeColorIsDark}
/>
);

expect(screen.queryByTestId("node-tag-chooser")).toBeNull();

fireEvent.click(screen.getByTitle("Manage node tags"));

expect(screen.queryByTestId("node-tag-chooser")).not.toBeNull();
});
});
84 changes: 84 additions & 0 deletions loom-editor/src/components/NodeGraphView/NodeWithBody/index.tsx
Original file line number Diff line number Diff line change
@@ -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 <NodeColorChooser onClose={closeColorChooser} nodeTitle={title} />;
}

if (tagChooserOpen) {
return <NodeTagChooser onClose={closeTagChooser} node={node} />;
}

return <NodeBody body={body} />;
};

interface NodeWithBodyProps {
yarnNode: YarnNode;
nodeColor: string;
nodeColorIsDark: boolean;
}

const NodeWithBody: FunctionComponent<NodeWithBodyProps> = ({
yarnNode,
nodeColor,
nodeColorIsDark,
}) => {
const [colorChooserOpen, setColorChooserOpen] = useState(false);
const [tagChooserOpen, setTagChooserOpen] = useState(false);

const { title } = yarnNode;

return (
<Fragment>
<NodeHeader
title={title}
nodeColor={nodeColor}
nodeColorIsDark={nodeColorIsDark}
onOpenColorChooser={() => setColorChooserOpen(!colorChooserOpen)}
/>
{renderBody(
colorChooserOpen,
() => setColorChooserOpen(false),
tagChooserOpen,
() => setTagChooserOpen(false),
yarnNode
)}
<NodeFooter
node={yarnNode}
nodeColor={nodeColor}
nodeColorIsDark={nodeColorIsDark}
onOpenTagChooser={() => setTagChooserOpen(true)}
data-testid="node-graph-view-tags"
/>
</Fragment>
);
};

export default NodeWithBody;
53 changes: 53 additions & 0 deletions loom-editor/src/components/NodeGraphView/ZoomedOutNode.test.tsx
Original file line number Diff line number Diff line change
@@ -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("<ZoomedOutNode />", () => {
const mockNode: YarnNode = {
title: "Mock Node",
body: "Some body",
tags: "cool tags",
};

it("renders", () => {
render(
<ZoomedOutNode
yarnNode={mockNode}
nodeColor={"blue"}
nodeColorIsDark={true}
currentZoom={0.5}
/>
);
});

it("shows tags when not super zoomed out", () => {
render(
<ZoomedOutNode
yarnNode={mockNode}
nodeColor={"blue"}
nodeColorIsDark={true}
currentZoom={0.5}
/>
);

// 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(
<ZoomedOutNode
yarnNode={mockNode}
nodeColor={"blue"}
nodeColorIsDark={true}
currentZoom={0.005}
/>
);

// these don't render if we're REALLY zoomed out
expect(screen.queryAllByTestId("zoomed-out-node-tag")).toHaveLength(0);
});
});
Loading

0 comments on commit 03219c5

Please sign in to comment.