From e58b27062c5b64b5b703478c43cd3d237d872c02 Mon Sep 17 00:00:00 2001 From: Yuqi Tang Date: Wed, 2 Oct 2024 05:45:19 -0700 Subject: [PATCH] feat: improve notes design (#3938) * 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 --- .../components/NodeDescription/index.tsx | 2 +- .../src/CustomNodes/NoteNode/index.tsx | 25 +--- src/frontend/src/constants/constants.ts | 10 ++ .../components/PageComponent/index.tsx | 116 ++++++++++++------ src/frontend/src/style/index.css | 14 +++ src/frontend/tailwind.config.mjs | 12 ++ .../extended/features/sticky-notes.spec.ts | 25 ++-- 7 files changed, 130 insertions(+), 74 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeDescription/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeDescription/index.tsx index fef0ac14666..d723da40c9a 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeDescription/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeDescription/index.tsx @@ -146,7 +146,7 @@ export default function NodeDescription({ ) : ( diff --git a/src/frontend/src/CustomNodes/NoteNode/index.tsx b/src/frontend/src/CustomNodes/NoteNode/index.tsx index 64c63272b3c..234f213e5b8 100644 --- a/src/frontend/src/CustomNodes/NoteNode/index.tsx +++ b/src/frontend/src/CustomNodes/NoteNode/index.tsx @@ -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, @@ -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, }); } }, []); @@ -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" @@ -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", )} > -
-
-
- -
- -
- -
-
-
(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) { @@ -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 (
@@ -483,6 +545,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element { panActivationKeyCode={""} proOptions={{ hideAttribution: true }} onPaneClick={onPaneClick} + onPaneMouseMove={onPaneMouseMove} > {!view && ( @@ -490,42 +553,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element { { - 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" > @@ -550,6 +578,16 @@ export default function Page({ view }: { view?: boolean }): JSX.Element { }} /> +
) : (
diff --git a/src/frontend/src/style/index.css b/src/frontend/src/style/index.css index 287afead6c9..98459efdaca 100644 --- a/src/frontend/src/style/index.css +++ b/src/frontend/src/style/index.css @@ -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%) */ diff --git a/src/frontend/tailwind.config.mjs b/src/frontend/tailwind.config.mjs index 201f55b20ea..d16237b4b74 100644 --- a/src/frontend/tailwind.config.mjs +++ b/src/frontend/tailwind.config.mjs @@ -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, diff --git a/src/frontend/tests/extended/features/sticky-notes.spec.ts b/src/frontend/tests/extended/features/sticky-notes.spec.ts index a3358ebf50c..51304d074c9 100644 --- a/src/frontend/tests/extended/features/sticky-notes.spec.ts +++ b/src/frontend/tests/extended/features/sticky-notes.spec.ts @@ -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. @@ -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); @@ -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(); @@ -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; @@ -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);