Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open node bodies in editor instead of full node text #3

Merged
merged 15 commits into from
Jul 26, 2020
Merged
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
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Yarn Loom is a Visual Studio Code extension for editing [yarn files](https://yar
- [Searching](#searching)
- [Quick tag search](#quick-tag-search)
- [Switching between the graph editor and a text editor](#switching-between-the-graph-editor-and-a-text-editor)
- [Theme support](#theme-support)
- [Special Thanks](#special-thanks)

## Installing
Expand Down Expand Up @@ -59,9 +60,11 @@ Changes saved in this text editor will be reflected in the `.yarn` file editor.

### Renaming nodes

A node can be renamed by changing its `title` after opening it up in the text editor.
A node can be renamed by clicking on the rename icon (![rename icon](./loom-editor/src/icons/rename.svg))

Note that when renaming a node that has incoming links, it will automatically be re-created when renamed since the editor will auto-create linked nodes. To rename a node with incoming links, you must rename the outgoing links to the desired name and then delete the existing node. A future update will make this easier.
Any nodes that are linking to a node will have their links automatically updated when a node is renamed.

An error message will be shown if there is already a node with the entered name.

<details>
<summary>Expand for demo of renaming a node</summary>
Expand All @@ -70,9 +73,11 @@ Note that when renaming a node that has incoming links, it will automatically be

### Changing node tags

Tags can be added/removed by changing the `tags` value in the header of a node after opening it up in the text editor.
Tags can be added/removed by clicking the add icon (![add icon](./loom-editor/src/icons/add.svg)) on the bottom of a node.

Existing tags will be shown in a list and can be toggled on and off.

`tags` is a space-separated list of strings.
To add new tags, click the "+ Add Tags" button. You can then enter a list of space-separated tags here and they will all be added to the node.

<details>
<summary>Expand for demo of changing node tags</summary>
Expand Down Expand Up @@ -152,6 +157,17 @@ In this menu, clicking the gear icon will set an editor as the default editor fo
<img src="./images/reopen-with-text-editor.gif" alt="Demo of switching between Yarn Loom and a text editor" />
</details>

### Theme support

This extension should fully support all Visual Studio Code themes. Better and more integrated theme support will be added gradually.

If there is a theme where something doesn't look right or is unreadable, please don't hesitate to [open up a new issue on the GitHub repo](https://github.com/TranquilMarmot/YarnLoom/issues/new/choose).

<details>
<summary>Expand for demo of switching themes</summary>
<img src="./images/theme-change.gif" alt="Demo of switching themes in Visual Studio Code" />
</details>

## Special Thanks

The syntax highlighting portion of this extension was copied over from the [Yarn VSCode Extension](https://github.com/YarnSpinnerTool/VSCodeExtension) and all credit for it goes to [@desplesda](https://github.com/desplesda).
Expand Down
Binary file modified images/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/editing-node.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/editing-tags.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/rename-node.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/theme-change.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions loom-common/EditorActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ export enum YarnEditorMessageTypes {

/** Set the position for a node */
SetNodePosition = "SetNodePosition",

/** Change a node's title */
RenameNode = "RenameNode",

/** Add/remove a tag from a node */
ToggleTagOnNode = "ToggleTagOnNode",

/** Prompt the user for new tags and add them to the node */
PromptForNewTags = "PromptForNewTags",

/** Add a specific tag to the node */
AddTagToNode = "AddTagToNode",
}

export const setNodes = (nodes: YarnNode[]) =>
Expand All @@ -45,3 +57,12 @@ export const setNodeColor = (nodeTitle: string, colorIndex: number) =>

export const setNodePosition = (nodeTitle: string, x: number, y: number) =>
action(YarnEditorMessageTypes.SetNodePosition, { nodeTitle, x, y });

export const renameNode = (nodeTitle: string) =>
action(YarnEditorMessageTypes.RenameNode, { nodeTitle });

export const toggleTagOnNode = (nodeTitle: string, tag: string) =>
action(YarnEditorMessageTypes.ToggleTagOnNode, { nodeTitle, tag });

export const promptForNewTags = (nodeTitle: string) =>
action(YarnEditorMessageTypes.PromptForNewTags, { nodeTitle });
41 changes: 41 additions & 0 deletions loom-common/YarnParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,44 @@ export const buildLinksFromNodes = (

return addedNodes;
};

/**
* Will rename all links that point to a node to a new title.
* @param nodes List of all nodes
* @param oldTitle Title of node to rename links to
* @param newTitle New title of node being renamed
* @returns List of nodes that were changed
*/
export const renameLinksFromNode = (
nodes: YarnNode[],
oldTitle: string,
newTitle: string
): YarnNode[] => {
const updatedNodes: YarnNode[] = [];

nodes.forEach((node) => {
// because this is a changing regex, we have to use the constructor and "double-escape" the literal characters
// links come in two forms:
// [[Some text.|some-node-title]]
// [[some-node-title]]
//
// Here, we're using RegEx capturing groups. The actual regex looks something like:
// [[(.*?\|)(some-node-title)]]
//
// This results in capturing two groups:
// $1 is the text of the link, with the "|" at the end. When there is no text, this is empty.
// $2 is the title of the node being renamed. We throw this out and replace it with the new title.
//
// The replacement of:
// [[$1${newTitle}]]
// is essentially just grabbing the optional node text and then appending the new title
const regex = new RegExp(`\\[\\[(.*?\\|?)(${oldTitle})\\]\\]`, "g");

if (node.body.match(regex)) {
node.body = node.body.replace(regex, `[[$1${newTitle}]]`);
updatedNodes.push(node);
}
});

return updatedNodes;
};
18 changes: 18 additions & 0 deletions loom-editor/src/Styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,21 @@ export const listItemBase = css`
background-color: var(--vscode-list-focusBackground);
}
`;

/** Container for things that overlay the node */
export const nodeOverlayContainer = css`
position: absolute;
left: 0px;
top: 39px;

/* Ideally, these would be calculated but for now we'll leave them as magic numbers... */
height: 161px;
width: 200px;

background: var(--vscode-sideBar-background);

display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
`;
23 changes: 4 additions & 19 deletions loom-editor/src/components/NodeGraphView/NodeBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,16 @@ const bodyStyle = css`
overflow: auto;
padding-left: 3px;

grid-row: 2 / 3;

::-webkit-scrollbar-corner {
background-color: white;
}
`;

/** If there are no tags for the node, this style is mixed in */
const noTagsBodyStyle = css`
grid-row: 2 / 4;
`;

/** If there are tags for the node, this style is mixed in */
const withTagsBodyStyle = css`
grid-row: 2 / 3;
`;

interface NodeBodyProps {
/** Body of node to render */
body: string;

/** List of tags; not actually rendered, but used to determine the height of the body container */
tags?: string;
}

/**
Expand Down Expand Up @@ -122,12 +111,8 @@ const parseBody = (body: string) => {
};

/** Render the body of a node with some basic syntax highlighting. */
const NodeBody: FunctionComponent<NodeBodyProps> = ({ body, tags }) => {
return (
<div css={css`${bodyStyle}${tags ? withTagsBodyStyle : noTagsBodyStyle}`}>
{parseBody(body)}
</div>
);
const NodeBody: FunctionComponent<NodeBodyProps> = ({ body }) => {
return <div css={bodyStyle}>{parseBody(body)}</div>;
};

export default NodeBody;
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ describe("<NodeColorChooser />", () => {

render(<NodeColorChooser onClose={onClose} nodeTitle={nodeTitle} />);

const buttons = await screen.findAllByTestId(
"node-color-chooser-color-button"
);
const buttons = await screen.findAllByLabelText(/Choose color/);

// just... click some buttons
fireEvent.click(buttons[0]);
Expand All @@ -46,7 +44,7 @@ describe("<NodeColorChooser />", () => {

render(<NodeColorChooser onClose={onClose} nodeTitle={nodeTitle} />);

fireEvent.click(screen.getByTestId("node-color-chooser-cancel-button"));
fireEvent.click(screen.getByText("Cancel"));

expect(onClose).toHaveBeenCalledTimes(1);
});
Expand Down
23 changes: 4 additions & 19 deletions loom-editor/src/components/NodeGraphView/NodeColorChooser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,11 @@ import { FunctionComponent } from "react";
import { setNodeColor } from "loom-common/EditorActions";

import { titleColors } from "./index";
import { buttonBase } from "../../Styles";
import { buttonBase, nodeOverlayContainer } from "../../Styles";

const containerStyle = css`
position: absolute;
left: 0px;
top: 39px;
${nodeOverlayContainer}

/* Ideally, these would be calculated but for now we'll leave them as magic numbers... */
height: 161px;
width: 200px;

background: var(--vscode-sideBar-background);

display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
`;

Expand Down Expand Up @@ -69,21 +58,17 @@ const NodeGraphViewColorChooser: FunctionComponent<NodeGraphViewColorChooserProp
<button
onClick={() => onChooseColor(index)}
key={color}
aria-label={`Choose color ${index}`}
css={css`
${buttonStyle}
background-color: ${color};
`}
data-testid="node-color-chooser-color-button"
>
{" "}
</button>
))}
</div>
<button
css={cancelButtonStyle}
onClick={onClose}
data-testid="node-color-chooser-cancel-button"
>
<button css={cancelButtonStyle} onClick={onClose}>
Cancel
</button>
</div>
Expand Down
115 changes: 115 additions & 0 deletions loom-editor/src/components/NodeGraphView/NodeTagChooser.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React from "react";
import { screen, fireEvent } from "@testing-library/react";
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";

describe("<NodeTagChooser />", () => {
const node = {
title: "Test node",
body: "",
tags: "some tags",
};

const stateWithNodes: State = {
...defaultState,
nodes: [
node,
{
title: "Another test node",
body: "",
tags: "some other tags",
},
{
title: "Yet another test node",
body: "",
tags: "more tags",
},
],
};

it("renders", () => {
renderWithProvider(<NodeTagChooser node={node} onClose={() => {}} />);
});

it("renders all tags", () => {
renderWithProvider(
<NodeTagChooser node={node} onClose={() => {}} />,
stateWithNodes
);

// tags we expect: some other tags more
const buttonText = screen.queryAllByTestId("tag-chooser-tag-button-text");

expect(buttonText).toHaveLength(4);

// they should also be sorted
expect(buttonText[0].textContent).toBe("more");
expect(buttonText[1].textContent).toBe("other");
expect(buttonText[2].textContent).toBe("some");
expect(buttonText[3].textContent).toBe("tags");
});

it("chooses tags when they're clicked", async () => {
window.vsCodeApi = { postMessage: jest.fn() };

renderWithProvider(
<NodeTagChooser node={node} onClose={() => {}} />,
stateWithNodes
);

fireEvent.click(await screen.findByText("more"));
fireEvent.click(await screen.findByText("other"));
fireEvent.click(await screen.findByText("some"));
fireEvent.click(await screen.findByText("tags"));

expect(window.vsCodeApi.postMessage).toHaveBeenCalledTimes(4);

expect(window.vsCodeApi.postMessage).toHaveBeenNthCalledWith(
1,
toggleTagOnNode(node.title, "more")
);
expect(window.vsCodeApi.postMessage).toHaveBeenNthCalledWith(
2,
toggleTagOnNode(node.title, "other")
);
expect(window.vsCodeApi.postMessage).toHaveBeenNthCalledWith(
3,
toggleTagOnNode(node.title, "some")
);
expect(window.vsCodeApi.postMessage).toHaveBeenNthCalledWith(
4,
toggleTagOnNode(node.title, "tags")
);
});

it("opens prompts for to add tags when add tagsbutton is clicked", async () => {
window.vsCodeApi = { postMessage: jest.fn() };

renderWithProvider(
<NodeTagChooser node={node} onClose={() => {}} />,
stateWithNodes
);

fireEvent.click(await screen.findByText("Add tags"));

expect(window.vsCodeApi.postMessage).toHaveBeenCalledTimes(1);
expect(window.vsCodeApi.postMessage).toHaveBeenCalledWith(
promptForNewTags(node.title)
);
});

it("closes when close button is clicked", async () => {
const onCloseSpy = jest.fn();

renderWithProvider(<NodeTagChooser node={node} onClose={onCloseSpy} />);

fireEvent.click(await screen.findByText("Close"));

expect(onCloseSpy).toHaveBeenCalledTimes(1);
});
});
Loading