Skip to content

Commit

Permalink
Merge pull request #713 from tone-row/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
rob-gordon authored Aug 12, 2024
2 parents 55067f3 + 73cd3cc commit b63f34f
Show file tree
Hide file tree
Showing 28 changed files with 505 additions and 116 deletions.
4 changes: 2 additions & 2 deletions app/e2e/not-logged-in.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ test("capabilities", async ({ page }) => {
).toBeVisible();
await page.click('[data-testid="close-dialog"]');

// Expect sandbox window to show (about 1 minute wait time)
// Expect sandbox warning to show
await expect(page.getByTestId("sandbox-warning")).toBeVisible({
timeout: 60000,
timeout: 25000, // 25 seconds
});
// Click on test id sandbox-warning-learn-more
await page.getByTestId("sandbox-warning-learn-more").click();
Expand Down
2 changes: 1 addition & 1 deletion app/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const BASE_URL = process.env.E2E_START_URL ?? "http://localhost:3000";
const EMAIL_DOMAINS_LIST: string[] = [];

export async function goToPath(page: Page, path = "") {
await page.goto(`${BASE_URL}${path ? `/${path}` : ""}?skipAnimation=true`);
await page.goto(`${BASE_URL}${path ? `/${path}` : ""}?isE2E=true`);

// If we're on the root route, also wait until the .monaco-editor is present
if (path === "") {
Expand Down
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "app",
"version": "1.54.4",
"version": "1.55.0",
"main": "module/module.js",
"license": "MIT",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/AiToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function AiToolbar() {
const showAcceptDiffButton = diff && !isRunning;

return (
<div className="bg-purple-300/60 dark:bg-purple-800/20">
<div className="bg-purple-600/10 dark:bg-purple-800/20">
<div className="flex items-center justify-between p-2">
<div className="flex items-center space-x-2">
<MagicWand
Expand Down
20 changes: 20 additions & 0 deletions app/src/components/Graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,26 @@ function initializeGraph({
}
});

// Store selected nodes
cyCurrent.on("select", "node", () => {
useGraphStore.setState({
selectedNodes: cyCurrent
.$(":selected")
.nodes()
.toArray()
.map((n) => n.id()),
});
});
cyCurrent.on("unselect", "node", () => {
useGraphStore.setState({
selectedNodes: cyCurrent
.$(":selected")
.nodes()
.toArray()
.map((n) => n.id()),
});
});

document.getElementById("cy")?.addEventListener("mouseout", handleMouseOut);

return () => {
Expand Down
55 changes: 53 additions & 2 deletions app/src/components/GraphFloatingMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ import {
MagnifyingGlass,
Minus,
Plus,
AlignCenterHorizontal,
AlignCenterVertical,
SquaresFour,
} from "phosphor-react";
import { useCallback } from "react";
import { useCallback, useEffect } from "react";
import { FaRegSnowflake } from "react-icons/fa";

import { lockZoomToGraph, useGraphStore } from "../lib/useGraphStore";
import { unfreezeDoc, useIsFrozen } from "../lib/useIsFrozen";
import { resetGraph } from "../lib/useUnmountStore";
import { IconButton2, IconToggleButton, Tooltip2 } from "../ui/Shared";
import { alignNodes } from "../lib/alignNodes";
import {
alignNodes,
alignNodesHorizontally,
alignNodesVertically,
} from "../lib/alignNodes";

const ZOOM_STEP = 0.5;

Expand All @@ -39,6 +45,27 @@ export function GraphFloatingMenu() {
const isFrozen = useIsFrozen();
const autoFit = useGraphStore((s) => s.autoFit);

const selectedNodes = useGraphStore((s) => s.selectedNodes);
const alignButtonsEnabled = isFrozen && selectedNodes.length > 1;

useEffect(() => {
if (alignButtonsEnabled) {
const handleKeyPress = (event: KeyboardEvent) => {
if (event.key === "h") {
alignNodesHorizontally(selectedNodes);
} else if (event.key === "v") {
alignNodesVertically(selectedNodes);
}
};

window.addEventListener("keydown", handleKeyPress);

return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}
}, [alignButtonsEnabled, selectedNodes]);

return (
<div className="absolute bottom-4 right-4 flex bg-white shadow-md rounded-lg overflow-hidden gap-1 p-1 items-center dark:bg-neutral-600">
<Tooltip2 content={t`Reset`}>
Expand Down Expand Up @@ -108,6 +135,30 @@ export function GraphFloatingMenu() {
data-testid="Align Nodes"
data-session-activity="align-nodes"
disabled={!isFrozen}
>
<SquaresFour size={16} />
</IconButton2>
</Tooltip2>
<Tooltip2 content={t`Align Horizontally` + " (h)"}>
<IconButton2
size="xs"
onClick={() => alignNodesHorizontally(selectedNodes)}
aria-label={t`Align Horizontally`}
data-testid="Align Horizontally"
data-session-activity="align-nodes-horizontally"
disabled={!alignButtonsEnabled}
>
<AlignCenterHorizontal size={16} />
</IconButton2>
</Tooltip2>
<Tooltip2 content={t`Align Vertically` + " (v)"}>
<IconButton2
size="xs"
onClick={() => alignNodesVertically(selectedNodes)}
aria-label={t`Align Vertically`}
data-testid="Align Vertically"
data-session-activity="align-nodes-vertically"
disabled={!alignButtonsEnabled}
>
<AlignCenterVertical size={16} />
</IconButton2>
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/SandboxWarning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,12 @@ export function SandboxWarning() {
<Trans>Upgrade Now - Save My Work</Trans>
</Link>
</Button2>
<button
<Dialog.DialogClose
className="text-xs text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300 transition-colors"
onClick={() => useSandboxWarning.setState({ isOpen: false })}
>
<Trans>Continue in Sandbox (Resets daily, work not saved)</Trans>
</button>
</Dialog.DialogClose>
</Content>
</Dialog.Portal>
</Dialog.Root>
Expand Down
31 changes: 30 additions & 1 deletion app/src/components/WithGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { memo, ReactNode, Suspense, useCallback, useState } from "react";
import {
memo,
ReactNode,
Suspense,
useCallback,
useEffect,
useState,
} from "react";

import { useHasProAccess, useFullscreen } from "../lib/hooks";
import { useUnmountStore } from "../lib/useUnmountStore";
Expand All @@ -10,6 +17,7 @@ import styles from "./WithGraph.module.css";
import TabPane from "./TabPane";
import { useMobileStore } from "../lib/useMobileStore";
import { useTabsStore } from "../lib/useTabsStore";
import { redo, undo } from "../lib/undoStack";

type MainProps = {
children?: ReactNode;
Expand All @@ -25,6 +33,27 @@ const WithGraph = memo(({ children }: MainProps) => {
const tab = useMobileStore((state) => state.tab);
const selectedTab = useTabsStore((state) => state.selectedTab);

useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.metaKey || event.ctrlKey) {
if (event.key === "z") {
if (event.shiftKey) {
redo();
} else {
undo();
}
event.preventDefault();
} else if (event.key === "y") {
redo();
event.preventDefault();
}
}
};

window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, []);

return (
<div
className="relative grid grid-rows-[[main]_minmax(0,1fr)_auto] grid-cols-[[main]_minmax(0,1fr)] md:flex md:shadow-xl"
Expand Down
156 changes: 156 additions & 0 deletions app/src/lib/alignNodes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NodePositions } from "../components/getNodePositionsFromCy";
import { useDoc } from "./useDoc";
import { addToUndoStack } from "./undoStack";

/**
* This function tries to align nodes vertical and horiontal on their center
Expand All @@ -15,6 +16,9 @@ export function alignNodes() {
const threshold = 40; // Adjust this value to change the alignment sensitivity
const alignedPositions: NodePositions = {};

// Store the original positions for undo
const originalPositions = { ...nodePositions };

// Iterate through all nodes
Object.entries(nodePositions).forEach(([nodeId, position]) => {
let closestHorizontal: cytoscape.Position | null = null;
Expand Down Expand Up @@ -56,4 +60,156 @@ export function alignNodes() {
nodePositions: alignedPositions,
},
}));

// Add the action to the undo stack
addToUndoStack({
undo: () => {
useDoc.setState((state) => ({
meta: {
...state.meta,
nodePositions: originalPositions,
},
}));
},
redo: () => {
useDoc.setState((state) => ({
meta: {
...state.meta,
nodePositions: alignedPositions,
},
}));
},
});
}

/**
* This function horizontally aligns the set of nodes for the given list of node ids
* by finding the average x position of the nodes and then setting the x position of
* each node to the average x position.
*/
export function alignNodesHorizontally(nodeIds: string[]) {
const meta = useDoc.getState().meta;
const nodePositions = meta.nodePositions as NodePositions;
if (!nodePositions) return;

// Store the original positions for undo
const originalPositions = { ...nodePositions };

// Calculate the average x position
let sumX = 0;
let count = 0;
for (const nodeId of nodeIds) {
if (nodePositions[nodeId]) {
sumX += nodePositions[nodeId].x;
count++;
}
}
const averageX = count > 0 ? sumX / count : 0;

// Create a new object with updated positions
const alignedPositions: NodePositions = { ...nodePositions };

// Set the x position of each node to the average x position
for (const nodeId of nodeIds) {
if (alignedPositions[nodeId]) {
alignedPositions[nodeId] = {
...alignedPositions[nodeId],
x: averageX,
};
}
}

// Update the node positions in the document state
useDoc.setState((state) => ({
meta: {
...state.meta,
nodePositions: alignedPositions,
},
}));

// Add the action to the undo stack
addToUndoStack({
undo: () => {
useDoc.setState((state) => ({
meta: {
...state.meta,
nodePositions: originalPositions,
},
}));
},
redo: () => {
useDoc.setState((state) => ({
meta: {
...state.meta,
nodePositions: alignedPositions,
},
}));
},
});
}

/**
* This function vertically aligns the set of nodes for the given list of node ids
* by finding the average y position of the nodes and then setting the y position of
* each node to the average y position.
*/
export function alignNodesVertically(nodeIds: string[]) {
const meta = useDoc.getState().meta;
const nodePositions = meta.nodePositions as NodePositions;
if (!nodePositions) return;

// Store the original positions for undo
const originalPositions = { ...nodePositions };

// Calculate the average y position
let sumY = 0;
let count = 0;
for (const nodeId of nodeIds) {
if (nodePositions[nodeId]) {
sumY += nodePositions[nodeId].y;
count++;
}
}
const averageY = count > 0 ? sumY / count : 0;

// Create a new object with updated positions
const alignedPositions: NodePositions = { ...nodePositions };

// Set the y position of each node to the average y position
for (const nodeId of nodeIds) {
if (alignedPositions[nodeId]) {
alignedPositions[nodeId] = {
...alignedPositions[nodeId],
y: averageY,
};
}
}

// Update the node positions in the document state
useDoc.setState((state) => ({
meta: {
...state.meta,
nodePositions: alignedPositions,
},
}));

// Add the action to the undo stack
addToUndoStack({
undo: () => {
useDoc.setState((state) => ({
meta: {
...state.meta,
nodePositions: originalPositions,
},
}));
},
redo: () => {
useDoc.setState((state) => ({
meta: {
...state.meta,
nodePositions: alignedPositions,
},
}));
},
});
}
Loading

0 comments on commit b63f34f

Please sign in to comment.