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 <gabriel@langflow.org>
This commit is contained in:
parent
e19d90bd6c
commit
e58b27062c
7 changed files with 130 additions and 74 deletions
|
|
@ -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,
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)}
|
||||
>
|
||||
<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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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}>
|
||||
|
|
@ -483,6 +545,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
|
|||
panActivationKeyCode={""}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
onPaneClick={onPaneClick}
|
||||
onPaneMouseMove={onPaneMouseMove}
|
||||
>
|
||||
<Background className="" />
|
||||
{!view && (
|
||||
|
|
@ -490,42 +553,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
|
|||
<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"
|
||||
>
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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%) */
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue