From 4b451f561e7191c7a4e2afcad35a2fb692c7a862 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Tue, 4 Nov 2025 14:57:12 +0100 Subject: [PATCH] add note color picker --- .../src/lib/components/common/button/model.ts | 2 +- .../flows/map/FlowStickyNode.svelte | 2 +- frontend/src/lib/components/flows/types.ts | 4 +- .../lib/components/graph/FlowGraphV2.svelte | 11 +- .../components/graph/NoteColorPicker.svelte | 50 ++++++++ .../src/lib/components/graph/noteColors.ts | 112 ++++++++++++++++++ .../graph/renderers/nodes/NoteNode.svelte | 89 ++++++++++---- 7 files changed, 242 insertions(+), 28 deletions(-) create mode 100644 frontend/src/lib/components/graph/NoteColorPicker.svelte create mode 100644 frontend/src/lib/components/graph/noteColors.ts diff --git a/frontend/src/lib/components/common/button/model.ts b/frontend/src/lib/components/common/button/model.ts index 5eb75ba531..28b3012a97 100644 --- a/frontend/src/lib/components/common/button/model.ts +++ b/frontend/src/lib/components/common/button/model.ts @@ -194,7 +194,7 @@ export namespace ButtonType { accent: 'bg-red-500 dark:bg-red-600 hover:bg-red-600 dark:hover:bg-red-700 focus-visible:bg-red-700 text-white focus-visible:ring-red-300', default: - 'border border-border-light bg-transparent hover:bg-red-500 dark:hover:bg-red-600 hover:text-white dark:hover:bg-red-900/20 text-primary focus-visible:bg-red-100 dark:focus-visible:bg-red-900/30 focus-visible:ring-red-300', + 'border border-border-light bg-transparent hover:bg-red-500 dark:hover:bg-red-600 hover:text-white dark:hover:bg-red-600 text-primary focus-visible:bg-red-100 dark:focus-visible:bg-red-900/30 focus-visible:ring-red-300', subtle: 'bg-transparent hover:bg-red-500 hover:text-white dark:hover:bg-red-600 text-primary focus-visible:bg-red-100 dark:focus-visible:bg-red-900/30 focus-visible:ring-red-300' } diff --git a/frontend/src/lib/components/flows/map/FlowStickyNode.svelte b/frontend/src/lib/components/flows/map/FlowStickyNode.svelte index 335b339876..ef8e3f7ab6 100644 --- a/frontend/src/lib/components/flows/map/FlowStickyNode.svelte +++ b/frontend/src/lib/components/flows/map/FlowStickyNode.svelte @@ -92,7 +92,7 @@ onclick={() => toggleNoteMode?.()} iconOnly variant="default" - size="sm" + unifiedSize="sm" startIcon={{ icon: StickyNote }} selected={noteMode} > diff --git a/frontend/src/lib/components/flows/types.ts b/frontend/src/lib/components/flows/types.ts index 3e84ed4a35..3fc87ba272 100644 --- a/frontend/src/lib/components/flows/types.ts +++ b/frontend/src/lib/components/flows/types.ts @@ -15,13 +15,15 @@ import type ResourceEditorDrawer from '../ResourceEditorDrawer.svelte' import type { ModulesTestStates } from '../modulesTest.svelte' import type { ButtonProp } from '$lib/components/DiffEditor.svelte' +import type { NoteColor } from '../graph/noteColors' + // Type for flow notes stored in the UI field export type Note = { id: string text: string position: { x: number; y: number } size: { width: number; height: number } - color: string + color: NoteColor } export type FlowInput = Record< diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index e43bb8aeda..a16a222802 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -2,6 +2,7 @@ import { FlowService, type FlowModule, type Job } from '../../gen' import { NODE, type GraphModuleState } from '.' import type { Note } from '../flows/types' + import { DEFAULT_NOTE_COLOR, type NoteColor } from './noteColors' import { getContext, onDestroy, setContext, tick, untrack, type Snippet } from 'svelte' import { get, writable, type Writable } from 'svelte/store' @@ -420,7 +421,7 @@ text: '', position: newNoteFromTool.position, size: newNoteFromTool.size || { width: 200, height: 100 }, - color: 'oklch(96.7% 0.067 122.328)' + color: DEFAULT_NOTE_COLOR } onNotesChange([...notes, newNote]) nextNoteId += 1 @@ -455,6 +456,13 @@ } } + function updateNoteColor(noteId: string, color: NoteColor) { + if (onNotesChange) { + onNotesChange(notes.map((note) => (note.id === noteId ? { ...note, color } : note))) + } + updateStores() + } + function convertNotesToNodes(): Node[] { return notes.map((note) => ({ id: note.id, @@ -465,6 +473,7 @@ color: note.color, onUpdate: (text: string) => updateNoteText(note.id, text), onDelete: () => deleteNote(note.id), + onColorChange: (color: NoteColor) => updateNoteColor(note.id, color), onSizeChange: (size: { width: number; height: number }) => updateNoteSize(note.id, size) }, style: `width: ${note.size.width}px; height: ${note.size.height}px;`, diff --git a/frontend/src/lib/components/graph/NoteColorPicker.svelte b/frontend/src/lib/components/graph/NoteColorPicker.svelte new file mode 100644 index 0000000000..b632edc53a --- /dev/null +++ b/frontend/src/lib/components/graph/NoteColorPicker.svelte @@ -0,0 +1,50 @@ + + + + {#snippet trigger()} + + {/each} + + {/snippet} + diff --git a/frontend/src/lib/components/graph/noteColors.ts b/frontend/src/lib/components/graph/noteColors.ts new file mode 100644 index 0000000000..5c2e5f41d8 --- /dev/null +++ b/frontend/src/lib/components/graph/noteColors.ts @@ -0,0 +1,112 @@ +// Note color definitions with Tailwind classes for light and dark mode +export enum NoteColor { + YELLOW = 'yellow', + BLUE = 'blue', + GREEN = 'green', + PURPLE = 'purple', + PINK = 'pink', + ORANGE = 'orange', + RED = 'red', + CYAN = 'cyan', + LIME = 'lime', + GRAY = 'gray' +} + +export interface NoteColorConfig { + background: string + outline: string + outlineHover: string + text: string + hover: string +} + +// Color configurations for each note color with dark mode support +export const NOTE_COLORS: Record = { + [NoteColor.YELLOW]: { + background: 'bg-yellow-100 dark:bg-yellow-900', + outline: 'outline-yellow-300 dark:outline-yellow-600', + outlineHover: 'outline-yellow-300/60 dark:outline-yellow-600/60', + text: 'text-yellow-900 dark:text-yellow-100', + hover: 'hover:bg-yellow-200 dark:hover:bg-yellow-800' + }, + [NoteColor.BLUE]: { + background: 'bg-blue-100 dark:bg-blue-900', + outline: 'outline-blue-300 dark:outline-blue-600', + outlineHover: 'outline-blue-300/60 dark:outline-blue-600/60', + text: 'text-blue-900 dark:text-blue-100', + hover: 'hover:bg-blue-200 dark:hover:bg-blue-800' + }, + [NoteColor.GREEN]: { + background: 'bg-green-100 dark:bg-green-900', + outline: 'outline-green-300 dark:outline-green-600', + outlineHover: 'outline-green-300/60 dark:outline-green-600/60', + text: 'text-green-900 dark:text-green-100', + hover: 'hover:bg-green-200 dark:hover:bg-green-800' + }, + [NoteColor.PURPLE]: { + background: 'bg-purple-100 dark:bg-purple-900/30', + outline: 'outline-purple-300 dark:outline-purple-600', + outlineHover: 'outline-purple-300/60 dark:outline-purple-600/60', + text: 'text-purple-900 dark:text-purple-100', + hover: 'hover:bg-purple-200 dark:hover:bg-purple-800' + }, + [NoteColor.PINK]: { + background: 'bg-pink-100 dark:bg-pink-900', + outline: 'outline-pink-300 dark:outline-pink-600', + outlineHover: 'outline-pink-300/60 dark:outline-pink-600/60', + text: 'text-pink-900 dark:text-pink-100', + hover: 'hover:bg-pink-200 dark:hover:bg-pink-800' + }, + [NoteColor.ORANGE]: { + background: 'bg-orange-100 dark:bg-orange-900', + outline: 'outline-orange-300 dark:outline-orange-600', + outlineHover: 'outline-orange-300/60 dark:outline-orange-600/60', + text: 'text-orange-900 dark:text-orange-100', + hover: 'hover:bg-orange-200 dark:hover:bg-orange-800' + }, + [NoteColor.RED]: { + background: 'bg-red-100 dark:bg-red-900', + outline: 'outline-red-300 dark:outline-red-600', + outlineHover: 'outline-red-300/60 dark:outline-red-600/60', + text: 'text-red-900 dark:text-red-100', + hover: 'hover:bg-red-200 dark:hover:bg-red-800' + }, + [NoteColor.CYAN]: { + background: 'bg-cyan-100 dark:bg-cyan-900', + outline: 'outline-cyan-300 dark:outline-cyan-600', + outlineHover: 'outline-cyan-300/60 dark:outline-cyan-600/60', + text: 'text-cyan-900 dark:text-cyan-100', + hover: 'hover:bg-cyan-200 dark:hover:bg-cyan-800' + }, + [NoteColor.LIME]: { + background: 'bg-lime-100 dark:bg-lime-900', + outline: 'outline-lime-300 dark:outline-lime-600', + outlineHover: 'outline-lime-300/60 dark:outline-lime-600/60', + text: 'text-lime-900 dark:text-lime-100', + hover: 'hover:bg-lime-200 dark:hover:bg-lime-800' + }, + [NoteColor.GRAY]: { + background: 'bg-gray-100 dark:bg-gray-800', + outline: 'outline-gray-300 dark:outline-gray-600', + outlineHover: 'outline-gray-300/60 dark:outline-gray-600/60', + text: 'text-gray-900 dark:text-gray-100', + hover: 'hover:bg-gray-200 dark:hover:bg-gray-700' + } +} + +// Color swatch colors for the picker (solid colors for the palette dots) +export const NOTE_COLOR_SWATCHES: Record = { + [NoteColor.YELLOW]: 'bg-yellow-400', + [NoteColor.BLUE]: 'bg-blue-400', + [NoteColor.GREEN]: 'bg-green-400', + [NoteColor.PURPLE]: 'bg-purple-400', + [NoteColor.PINK]: 'bg-pink-400', + [NoteColor.ORANGE]: 'bg-orange-400', + [NoteColor.RED]: 'bg-red-400', + [NoteColor.CYAN]: 'bg-cyan-400', + [NoteColor.LIME]: 'bg-lime-400', + [NoteColor.GRAY]: 'bg-gray-400' +} + +// Default note color +export const DEFAULT_NOTE_COLOR = NoteColor.YELLOW diff --git a/frontend/src/lib/components/graph/renderers/nodes/NoteNode.svelte b/frontend/src/lib/components/graph/renderers/nodes/NoteNode.svelte index 23d2069ebd..dd88be558d 100644 --- a/frontend/src/lib/components/graph/renderers/nodes/NoteNode.svelte +++ b/frontend/src/lib/components/graph/renderers/nodes/NoteNode.svelte @@ -4,13 +4,17 @@ import { twMerge } from 'tailwind-merge' import GfmMarkdown from '$lib/components/GfmMarkdown.svelte' import { fade } from 'svelte/transition' + import NoteColorPicker from '../../NoteColorPicker.svelte' + import { NoteColor, NOTE_COLORS, DEFAULT_NOTE_COLOR } from '../../noteColors' + import { Button } from '$lib/components/common' interface Props { data: { text: string - color: string + color: NoteColor onUpdate?: (text: string) => void onDelete?: () => void + onColorChange?: (color: NoteColor) => void onSizeChange?: (size: { width: number; height: number }) => void } selected?: boolean @@ -29,13 +33,20 @@ data.onUpdate?.(textContent) } - function handleDelete(event: Event) { - event.preventDefault() - event.stopPropagation() + function handleDelete(event?: Event) { + event?.preventDefault?.() + event?.stopPropagation?.() // Call the delete callback data.onDelete?.() } + function handleColorChange(color: NoteColor) { + data.onColorChange?.(color) + } + + // Get color configuration for current color + const colorConfig = $derived(NOTE_COLORS[data.color] || NOTE_COLORS[DEFAULT_NOTE_COLOR]) + function handleDoubleClick(event: Event) { console.log('Double click detected', { editMode, selected, dragging }) event.preventDefault() @@ -63,14 +74,20 @@ editMode = false } }) + + let colorPickerIsOpen = $state(false)
{ dragging = false }} @@ -84,42 +101,62 @@ onmouseleave={handleMouseLeave} role="note" > - - + +
+
+ + +
+
- {#if hovering} + {#if hovering || selected} {#if !editMode && data.text}
Double click to edit
{:else}
GH Markdown
{/if} {/if} -
+
{#if editMode}