mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 08:00:45 +00:00
24f9115dc0
* Add note component
* save note size and position
* move add note button up
* nit
* Add markdown support
* wip
* fix add sticky note button
* fix text update
* Add sticky note to saved flow data
* add note color picker
* Introduce node multiselect
* Add group notes
* Adapt layout to group node
* create a note manager class
* clean reactivity
* clean
* improve adaptive layout to group note
* modify layout based on cached text height
* fined grained graph rendering for notes
* separate noteManager into editor and render
* separate noteManager into editor and render
* create a note change observer
* render note node from context
* simplify note state managment
* show note in flow viewer
* clean dirty changes
* clean selection manager
* fix layout check
* improve bg surface select
* Handle z-index for stacked group notes
* clean selection manager
* exclude notes from rect select
* Allow switch between selection modes with keyboard keys
* improve selection box styling
* prevent dragging note when editing
* nit
* Simplify selection using svelte flow built in feature
* handle note selection separately
* Add min size for notes
* improve selection toggle
* improve mode switch
* make size and position optional for group notes
* Improve initial viewport position
* Add context menu for the canevas
* nit
* Add node context menu
* improve note select
* use clickoutside for note deselect
* use pointerdown outside to close context menu
* nit
* fix selection issues
* make edges non selectable
* improve color palette
* fix backend
* fix backend check
* cargo lock restore
* Add toggle to display notes
* fix note selection
* nit
* account for css offset in for loop
* fix multiple selection pannel styling
* clear flow selection when creating note
* Improve placeholder and note default text
* Escape note edit mode when pressing Esc
* Allow note edition in local dev
* clean
* Handle subflow selection
* prevent group note resizing
* nit
* allow notes in flow expand
* Improve multi select panel
* Allow context menu in note mode
* Add event listenner to fix pane click deselect
* prevent zoom in text area in notes
* improve bounding box styling
* Use control for box selection for non mac users
* nit
* clean notes groups
* nit
* use portal for note actions
* handle assets node when computing note layout
* Simplify layout compute for notes
* use smart color choice for notes
* Switch display note when adding a new note
* clean code
* improve group note bound size calculation
* simplify AI tool nodes and asset handling
* nit
* nit
* improve flow centering
* create group note button
* Improve selection of nodes
* Revert "Improve selection of nodes"
This reverts commit d2c40d82b1.
* refert backend changes
* nit
* improve graph selection
* clean
* make backend work except job runs
* fix notSelectable
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
217 lines
5.4 KiB
Svelte
217 lines
5.4 KiB
Svelte
<script lang="ts">
|
|
import { useSvelteFlow, type XYPosition } from '@xyflow/svelte'
|
|
import { getNoteEditorContext } from './noteEditor.svelte'
|
|
import { DEFAULT_NOTE_COLOR, MIN_NOTE_WIDTH, MIN_NOTE_HEIGHT } from './noteColors'
|
|
import { StickyNote } from 'lucide-svelte'
|
|
import ContextMenu, { type ContextMenuItem } from '../common/contextmenu/ContextMenu.svelte'
|
|
|
|
interface Props {
|
|
exitNoteMode?: () => void
|
|
yOffset: number
|
|
}
|
|
|
|
let { exitNoteMode, yOffset }: Props = $props()
|
|
|
|
// Get NoteEditor context for direct note creation
|
|
const noteEditorContext = getNoteEditorContext()
|
|
|
|
const { screenToFlowPosition, getViewport } = useSvelteFlow()
|
|
|
|
let isDrawing = $state(false)
|
|
let startPosition: XYPosition | null = $state(null)
|
|
let endPosition: XYPosition | null = $state(null)
|
|
let rect: DOMRect | null = $state(null)
|
|
let contextMenuPosition: XYPosition | null = $state(null)
|
|
|
|
function onPointerDown(event: PointerEvent) {
|
|
// Capture pointer to continue tracking outside the element
|
|
const target = event.currentTarget as Element
|
|
target?.setPointerCapture?.(event.pointerId)
|
|
|
|
// Use page coordinates as reference
|
|
rect = target.getBoundingClientRect()
|
|
startPosition = {
|
|
x: event.pageX - rect.left,
|
|
y: event.pageY - rect.top
|
|
}
|
|
isDrawing = true
|
|
}
|
|
|
|
function onPointerMove(event: PointerEvent) {
|
|
if (event.buttons !== 1) return
|
|
|
|
// Use page coordinates as reference
|
|
const target = event.currentTarget as Element
|
|
rect = target.getBoundingClientRect()
|
|
endPosition = {
|
|
x: event.pageX - rect.left,
|
|
y: event.pageY - rect.top
|
|
}
|
|
}
|
|
|
|
function onPointerUp() {
|
|
if (!isDrawing || !startPosition || !endPosition || !rect) return
|
|
|
|
// We need to convert the start and end positions to absolute positions to then convert to flow positions
|
|
const absoluteStartPosition = {
|
|
x: startPosition.x + rect.left,
|
|
y: startPosition.y + rect.top
|
|
}
|
|
const absoluteEndPosition = {
|
|
x: endPosition.x + rect.left,
|
|
y: endPosition.y + rect.top
|
|
}
|
|
|
|
const flowPosition = screenToFlowPosition({
|
|
x: Math.min(absoluteStartPosition.x, absoluteEndPosition.x),
|
|
y: Math.min(absoluteStartPosition.y, absoluteEndPosition.y)
|
|
})
|
|
const position = {
|
|
x: flowPosition.x,
|
|
y: flowPosition.y - (yOffset || 0)
|
|
}
|
|
|
|
const zoom = getViewport().zoom
|
|
const size = {
|
|
width: Math.max(
|
|
MIN_NOTE_WIDTH,
|
|
Math.abs(absoluteEndPosition.x - absoluteStartPosition.x) / zoom
|
|
),
|
|
height: Math.max(
|
|
MIN_NOTE_HEIGHT,
|
|
Math.abs(absoluteEndPosition.y - absoluteStartPosition.y) / zoom
|
|
)
|
|
}
|
|
|
|
// Create the actual note using NoteEditor context
|
|
if (noteEditorContext?.noteEditor) {
|
|
noteEditorContext.noteEditor.addNote({
|
|
text: '### Free note\nDouble click to edit me',
|
|
position,
|
|
size,
|
|
color: DEFAULT_NOTE_COLOR,
|
|
type: 'free',
|
|
locked: false
|
|
})
|
|
}
|
|
|
|
// Exit note mode after creating note
|
|
exitNoteMode?.()
|
|
|
|
// Reset state
|
|
isDrawing = false
|
|
startPosition = null
|
|
}
|
|
|
|
function handleAddStickyNote() {
|
|
if (!noteEditorContext?.noteEditor || !contextMenuPosition) return
|
|
|
|
noteEditorContext.noteEditor.addNote({
|
|
text: '### Free note\nDouble click to edit me',
|
|
position: contextMenuPosition,
|
|
size: { width: 300, height: 200 },
|
|
color: DEFAULT_NOTE_COLOR,
|
|
type: 'free',
|
|
locked: false
|
|
})
|
|
|
|
// Clear the position after use
|
|
contextMenuPosition = null
|
|
|
|
exitNoteMode?.()
|
|
}
|
|
|
|
function handleItemClick(item: ContextMenuItem) {
|
|
if (item.id === 'add-sticky-note') {
|
|
handleAddStickyNote()
|
|
}
|
|
}
|
|
|
|
const contextMenuItems: ContextMenuItem[] = [
|
|
{
|
|
id: 'add-sticky-note',
|
|
label: 'Add sticky note',
|
|
icon: StickyNote,
|
|
onClick: handleAddStickyNote
|
|
}
|
|
]
|
|
|
|
const previewNote = $derived(
|
|
startPosition && endPosition
|
|
? {
|
|
position: {
|
|
x: Math.min(startPosition['x'], endPosition['x']),
|
|
y: Math.min(startPosition['y'], endPosition['y'])
|
|
},
|
|
size: {
|
|
width: Math.abs(endPosition['x'] - startPosition['x']),
|
|
height: Math.abs(endPosition['y'] - startPosition['y'])
|
|
}
|
|
}
|
|
: null
|
|
)
|
|
</script>
|
|
|
|
<ContextMenu items={contextMenuItems} onItemClick={handleItemClick}>
|
|
<div
|
|
class="tool-overlay"
|
|
onpointerdown={onPointerDown}
|
|
onpointermove={onPointerMove}
|
|
onpointerup={onPointerUp}
|
|
oncontextmenu={(e) => {
|
|
// Capture the position when context menu is triggered
|
|
const flowPosition = screenToFlowPosition({
|
|
x: e.clientX,
|
|
y: e.clientY
|
|
})
|
|
contextMenuPosition = {
|
|
x: flowPosition.x,
|
|
y: flowPosition.y - yOffset
|
|
}
|
|
}}
|
|
role="button"
|
|
tabindex="0"
|
|
aria-label="Click and drag to create a note, or right-click to add a sticky note"
|
|
onkeydown={(e) => {
|
|
if (e.key === 'Escape') {
|
|
if (isDrawing) {
|
|
// Cancel current drawing
|
|
isDrawing = false
|
|
startPosition = null
|
|
} else {
|
|
// Exit note mode
|
|
exitNoteMode?.()
|
|
}
|
|
}
|
|
}}
|
|
>
|
|
<!-- Preview note while drawing -->
|
|
{#if previewNote}
|
|
<div
|
|
class="absolute border-2 border-dashed border-lime-400 bg-lime-100 bg-opacity-50 rounded-md pointer-events-none"
|
|
style="
|
|
width: {previewNote.size.width}px;
|
|
height: {previewNote.size.height}px;
|
|
transform: translate({previewNote.position.x}px, {previewNote.position.y}px);
|
|
"
|
|
>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</ContextMenu>
|
|
|
|
<style>
|
|
.tool-overlay {
|
|
pointer-events: auto;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
z-index: 4;
|
|
height: 100%;
|
|
width: 100%;
|
|
transform-origin: top left;
|
|
cursor: crosshair;
|
|
touch-action: none;
|
|
}
|
|
</style>
|