Skip to content

Commit

Permalink
feat: improve notes design (#3938)
Browse files Browse the repository at this point in the history
* fix notes style

* fix notes style

* adding ghost node

* [autofix.ci] apply automated fixes

* change cursor position

* update notes related test

* [autofix.ci] apply automated fixes

* adjust shadow block width

* move cursor to middle:

* [autofix.ci] apply automated fixes

* fix padding

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
  • Loading branch information
3 people authored Oct 2, 2024
1 parent e19d90b commit e58b270
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export default function NodeDescription({
) : (
<Markdown
className={cn(
"markdown prose flex h-full w-full flex-col text-primary word-break-break-word dark:prose-invert",
"markdown prose flex h-full w-full flex-col text-primary word-break-break-word note-node-markdown dark:prose-invert",
mdClassName,
)}
>
Expand Down
25 changes: 4 additions & 21 deletions src/frontend/src/CustomNodes/NoteNode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import { noteDataType } from "@/types/flow";
import { cn } from "@/utils/utils";
import { useEffect, useMemo, useRef, useState } from "react";
import { NodeResizer, NodeToolbar } from "reactflow";
import IconComponent from "../../components/genericIconComponent";
import NodeDescription from "../GenericNode/components/NodeDescription";
import NodeName from "../GenericNode/components/NodeName";
import NoteToolbarComponent from "./NoteToolbarComponent";
function NoteNode({
data,
Expand All @@ -28,8 +26,8 @@ function NoteNode({
useEffect(() => {
if (nodeDiv.current) {
setSize({
width: nodeDiv.current.offsetWidth - 43,
height: nodeDiv.current.offsetHeight - 80,
width: nodeDiv.current.offsetWidth - 25,
height: nodeDiv.current.offsetHeight - 25,
});
}
}, []);
Expand All @@ -51,7 +49,7 @@ function NoteNode({
maxWidth={NOTE_NODE_MAX_WIDTH}
onResize={(_, params) => {
const { width, height } = params;
setSize({ width: width - 43, height: height - 80 });
setSize({ width: width - 25, height: height - 25 });
}}
isVisible={selected}
lineClassName="border-[3px] border-border"
Expand All @@ -67,25 +65,10 @@ function NoteNode({
}}
ref={nodeDiv}
className={cn(
"flex h-full w-full flex-col gap-3 rounded-md border border-b p-5 transition-all",
"flex h-full w-full flex-col gap-3 border border-b p-3 transition-all",
selected ? "" : "-z-50 shadow-sm",
)}
>
<div className="flex h-fit w-full items-center align-middle">
<div className="flex w-full gap-2">
<div data-testid="note_icon">
<IconComponent name="SquarePen" className="min-w-fit" />
</div>

<div className="w-11/12">
<NodeName
nodeId={data.id}
selected={selected}
display_name={data.node?.display_name || "Note"}
/>
</div>
</div>
</div>
<div
style={{
width: size.width,
Expand Down
10 changes: 10 additions & 0 deletions src/frontend/src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -910,10 +910,20 @@ export const NOTE_NODE_MAX_HEIGHT = 800;
export const NOTE_NODE_MAX_WIDTH = 600;

export const COLOR_OPTIONS = {
default: "var(--note-default)",
indigo: "var(--note-indigo)",
emerald: "var(--note-emerald)",
amber: "var(--note-amber)",
red: "var(--note-red)",
};

export const SHADOW_COLOR_OPTIONS = {
default: "var(--note-default-opacity)",
indigo: "var(--note-indigo-opacity)",
emerald: "var(--note-emerald-opacity)",
amber: "var(--note-amber-opacity)",
red: "var(--note-red-opacity)",
};

export const maxSizeFilesInBytes = 10 * 1024 * 1024; // 10MB in bytes
export const MAX_TEXT_LENGTH = 99999;
116 changes: 77 additions & 39 deletions src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import NoteNode from "@/CustomNodes/NoteNode";
import IconComponent from "@/components/genericIconComponent";
import LoadingComponent from "@/components/loadingComponent";
import ShadTooltip from "@/components/shadTooltipComponent";
import {
NOTE_NODE_MIN_HEIGHT,
NOTE_NODE_MIN_WIDTH,
SHADOW_COLOR_OPTIONS,
} from "@/constants/constants";
import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
import { track } from "@/customization/utils/analytics";
import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow";
Expand Down Expand Up @@ -109,6 +114,14 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
useState<OnSelectionChangeParams | null>(null);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);

const [isAddingNote, setIsAddingNote] = useState(false);

const zoomLevel = reactFlowInstance?.getZoom();
const shadowBoxWidth = NOTE_NODE_MIN_WIDTH * (zoomLevel || 1);
const shadowBoxHeight = NOTE_NODE_MIN_HEIGHT * (zoomLevel || 1);
const shadowBoxBackgroundColor =
SHADOW_COLOR_OPTIONS[Object.keys(SHADOW_COLOR_OPTIONS)[0]];

function handleGroupNode() {
takeSnapshot();
if (validateSelection(lastSelection!, edges).length === 0) {
Expand Down Expand Up @@ -442,9 +455,58 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
[],
);

const onPaneClick = useCallback(() => {
setFilterEdge([]);
}, []);
const onPaneClick = useCallback(
(event: React.MouseEvent) => {
setFilterEdge([]);
if (isAddingNote) {
const shadowBox = document.getElementById("shadow-box");
if (shadowBox) {
shadowBox.style.display = "none";
}
const position = reactFlowInstance?.screenToFlowPosition({
x: event.clientX - shadowBoxWidth / 2,
y: event.clientY - shadowBoxHeight / 2,
});
const data = {
node: {
description: "",
display_name: "",
documentation: "",
template: {},
},
type: "note",
};
const newId = getNodeId(data.type);

const newNode: NodeType = {
id: newId,
type: "noteNode",
position: position || { x: 0, y: 0 },
data: {
...data,
id: newId,
},
};
setNodes((nds) => nds.concat(newNode));
setIsAddingNote(false);
}
},
[isAddingNote, setNodes, reactFlowInstance, getNodeId, setFilterEdge],
);

const onPaneMouseMove = useCallback(
(event: React.MouseEvent) => {
if (isAddingNote) {
const shadowBox = document.getElementById("shadow-box");
if (shadowBox) {
shadowBox.style.display = "block";
shadowBox.style.left = `${event.clientX - shadowBoxWidth / 2}px`;
shadowBox.style.top = `${event.clientY - shadowBoxHeight / 2}px`;
}
}
},
[isAddingNote],
);

return (
<div className="h-full w-full" ref={reactFlowWrapper}>
Expand Down Expand Up @@ -483,49 +545,15 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
panActivationKeyCode={""}
proOptions={{ hideAttribution: true }}
onPaneClick={onPaneClick}
onPaneMouseMove={onPaneMouseMove}
>
<Background className="" />
{!view && (
<Controls className="fill-foreground stroke-foreground text-primary [&>button]:border-b-border [&>button]:bg-muted hover:[&>button]:bg-border">
<ControlButton
data-testid="add_note"
onClick={() => {
const wrapper = reactFlowWrapper.current!;
const viewport = reactFlowInstance?.getViewport();
const x = wrapper.getBoundingClientRect().width / 2;
const y = wrapper.getBoundingClientRect().height / 2;
const nodePosition =
reactFlowInstance?.screenToFlowPosition({ x, y })!;

const data = {
node: {
description: "",
display_name: "",
documentation: "",
template: {},
},
type: "note",
};
const newId = getNodeId(data.type);

const newNode: NodeType = {
id: newId,
type: "noteNode",
position: { x: 0, y: 0 },
data: {
...data,
id: newId,
},
};
paste(
{ nodes: [newNode], edges: [] },
{
x: nodePosition.x,
y: nodePosition?.y,
paneX: wrapper.getBoundingClientRect().x,
paneY: wrapper.getBoundingClientRect().y,
},
);
setIsAddingNote(true);
}}
className="postion react-flow__controls absolute -top-10"
>
Expand All @@ -550,6 +578,16 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
}}
/>
</ReactFlow>
<div
id="shadow-box"
style={{
position: "absolute",
width: `${shadowBoxWidth}px`,
height: `${shadowBoxHeight}px`,
backgroundColor: `${shadowBoxBackgroundColor}`,
pointerEvents: "none",
}}
></div>
</div>
) : (
<div className="flex h-full w-full items-center justify-center">
Expand Down
14 changes: 14 additions & 0 deletions src/frontend/src/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,33 @@
--status-blue: #2563eb;
--status-gray: #6b7280;
--connection: #555;
--note-default: #f1f5f9;
--note-indigo: #e0e7ff;
--note-emerald: #d1fae5;
--note-amber: #fef3c7;
--note-red: #fee2e2;

--note-default-opacity: #f1f5f980;
--note-indigo-opacity: #312e8180;
--note-emerald-opacity: #064e3b80;
--note-amber-opacity: #78350f80;
--note-red-opacity: #7f1d1d80;
}

.dark {
--note-default: #0f172a;
--note-indigo: #312e81;
--note-emerald: #064e3b;
--note-amber: #78350f;
--note-red: #7f1d1d;
--note-placeholder: 216 12% 84%; /* hsl(216 12% 84%) */

--note-default-opacity: #0f172a80;
--note-indigo-opacity: #312e8180;
--note-emerald-opacity: #064e3b80;
--note-amber-opacity: #78350f80;
--note-red-opacity: #7f1d1d80;

--node-selected: 234 89% 74%;
--background: 224 28% 7.5%; /* hsl(224 10% 7.5%) */
--foreground: 213 31% 80%; /* hsl(213 31% 91%) */
Expand Down
12 changes: 12 additions & 0 deletions src/frontend/tailwind.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,18 @@ const config = {
outline: "none !important",
outlineOffset: "0px !important",
},
".note-node-markdown": {
lineHeight: "1",
"& ul li::marker": {
color: "black",
},
"& ol li::marker": {
color: "black",
},
"& h1, & h2, & h3, & h4, & h5, & h6, & p, & ul, & ol": {
marginBottom: "0.25rem",
},
},
});
}),
tailwindcssTypography,
Expand Down
25 changes: 12 additions & 13 deletions src/frontend/tests/extended/features/sticky-notes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,15 @@ test("user should be able to interact with sticky notes", async ({ page }) => {
control = "Meta";
}

const noteText = `
Artificial Intelligence (AI) has rapidly evolved from a speculative concept in science fiction to a transformative force reshaping industries and everyday life. The term AI encompasses a broad range of technologies, from simple algorithms designed to perform specific tasks to complex systems capable of learning and adapting independently. As AI continues to advance, its applications are becoming increasingly diverse, impacting everything from healthcare to finance, entertainment, and beyond.
const randomTitle = Math.random()
.toString(36)
.substring(7)
.padEnd(8, "x")
.substring(0, 8);

const noteText = `# ${randomTitle}
Artificial Intelligence (AI) has rapidly evolved from a speculative concept in science fiction to a transformative force reshaping industries and everyday life. The term AI encompasses a broad range of technologies, from simple algorithms designed to perform specific tasks to complex systems capable of learning and adapting independently. As AI continues to advance, its applications are becoming increasingly diverse, impacting everything from healthcare to finance, entertainment, and beyond.
At its core, AI is about creating systems that can perform tasks that would typically require human intelligence. This includes abilities such as visual perception, speech recognition, decision-making, and even language translation. The development of AI can be traced back to the mid-20th century, when pioneers like Alan Turing began exploring the idea of machines that could think. Turing's famous "Turing Test" proposed a benchmark for AI, where a machine would be considered intelligent if it could engage in a conversation with a human without being detected as a machine.
Expand All @@ -50,8 +57,6 @@ Despite its many benefits, AI also raises important ethical and societal questio
The future of AI is both exciting and uncertain. As the technology continues to advance, it will undoubtedly bring about profound changes in society. The challenge will be to harness AI's potential for good while addressing the ethical and societal issues that arise. Whether it's through smarter healthcare, more efficient transportation, or enhanced creativity, AI has the potential to reshape the world in ways we are only beginning to imagine. The journey of AI is far from over, and its impact will be felt for generations to come.
`;

const randomTitle = Math.random().toString(36).substring(7);

while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(3000);
Expand All @@ -70,6 +75,7 @@ The future of AI is both exciting and uncertain. As the technology continues to
await page.waitForTimeout(1000);

const targetElement = await page.locator('//*[@id="react-flow-id"]');
await targetElement.click();

await page.mouse.up();
await page.mouse.down();
Expand All @@ -84,19 +90,12 @@ The future of AI is both exciting and uncertain. As the technology continues to

await page.getByTestId("note_node").click();

await page.getByTestId("title-Note").dblclick();
await page.waitForTimeout(1000);
await page.getByTestId("popover-anchor-input-title-Note").fill(randomTitle);

await page.getByTestId("note_icon").first().dblclick();

await page.locator(".generic-node-desc").last().dblclick();
await page.getByTestId("textarea").fill(noteText);

expect(await page.getByText("2500/2500")).toBeVisible();

await page.getByTestId("note_icon").first().dblclick();

await targetElement.click();
const textMarkdown = await page.locator(".markdown").innerText();

const textLength = textMarkdown.length;
Expand All @@ -110,7 +109,7 @@ The future of AI is both exciting and uncertain. As the technology continues to

let hasStyles = await element?.evaluate((el) => {
const style = window.getComputedStyle(el);
return style.backgroundColor === "rgb(224, 231, 255)";
return style.backgroundColor === "rgb(241, 245, 249)";
});
expect(hasStyles).toBe(true);

Expand Down

0 comments on commit e58b270

Please sign in to comment.