diff --git a/web/src/lib/attachments/drag-and-drop.svelte.ts b/web/src/lib/attachments/drag-and-drop.svelte.ts new file mode 100644 index 0000000000..950e8e5b80 --- /dev/null +++ b/web/src/lib/attachments/drag-and-drop.svelte.ts @@ -0,0 +1,105 @@ +import type { Attachment } from 'svelte/attachments'; + +export interface DragAndDropOptions { + index: number; + onDragStart?: (index: number) => void; + onDragEnter?: (index: number) => void; + onDrop?: (e: DragEvent, index: number) => void; + onDragEnd?: () => void; + isDragging?: boolean; + isDragOver?: boolean; +} + +export function dragAndDrop(options: DragAndDropOptions): Attachment { + return (node: Element) => { + const element = node as HTMLElement; + const { index, onDragStart, onDragEnter, onDrop, onDragEnd, isDragging, isDragOver } = options; + + const isFormElement = (el: HTMLElement) => { + return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT'; + }; + + const handleDragStart = (e: DragEvent) => { + // Prevent drag if it originated from an input, textarea, or select element + const target = e.target as HTMLElement; + if (isFormElement(target)) { + e.preventDefault(); + return; + } + onDragStart?.(index); + }; + + const handleDragEnter = () => { + onDragEnter?.(index); + }; + + const handleDragOver = (e: DragEvent) => { + e.preventDefault(); + }; + + const handleDrop = (e: DragEvent) => { + onDrop?.(e, index); + }; + + const handleDragEnd = () => { + onDragEnd?.(); + }; + + // Disable draggable when focusing on form elements (fixes Firefox input interaction) + const handleFocusIn = (e: FocusEvent) => { + const target = e.target as HTMLElement; + if (isFormElement(target)) { + element.setAttribute('draggable', 'false'); + } + }; + + const handleFocusOut = (e: FocusEvent) => { + const target = e.target as HTMLElement; + if (isFormElement(target)) { + element.setAttribute('draggable', 'true'); + } + }; + + // Update classes based on drag state + const updateClasses = (dragging: boolean, dragOver: boolean) => { + // Remove all drag-related classes first + element.classList.remove('opacity-50', 'border-light-500', 'border-solid'); + + // Add back only the active ones + if (dragging) { + element.classList.add('opacity-50'); + } + + if (dragOver) { + element.classList.add('border-light-500', 'border-solid'); + element.classList.remove('border-transparent'); + } else { + element.classList.add('border-transparent'); + } + }; + + element.setAttribute('draggable', 'true'); + element.setAttribute('role', 'button'); + element.setAttribute('tabindex', '0'); + + element.addEventListener('dragstart', handleDragStart); + element.addEventListener('dragenter', handleDragEnter); + element.addEventListener('dragover', handleDragOver); + element.addEventListener('drop', handleDrop); + element.addEventListener('dragend', handleDragEnd); + element.addEventListener('focusin', handleFocusIn); + element.addEventListener('focusout', handleFocusOut); + + updateClasses(isDragging || false, isDragOver || false); + + return () => { + element.removeEventListener('dragstart', handleDragStart); + element.removeEventListener('dragenter', handleDragEnter); + element.removeEventListener('dragover', handleDragOver); + element.removeEventListener('drop', handleDrop); + element.removeEventListener('dragend', handleDragEnd); + element.removeEventListener('focusin', handleFocusIn); + element.removeEventListener('focusout', handleFocusOut); + }; + }; +} diff --git a/web/src/lib/components/workflows/WorkflowPickerField.svelte b/web/src/lib/components/workflows/WorkflowPickerField.svelte index 7fcc26e40f..b8ebadaca4 100644 --- a/web/src/lib/components/workflows/WorkflowPickerField.svelte +++ b/web/src/lib/components/workflows/WorkflowPickerField.svelte @@ -1,11 +1,11 @@ -{#snippet pickerItemCard(item: AlbumResponseDto | PersonResponseDto, onRemove: () => void)} - - -
- {#if isAlbum && 'albumThumbnailAssetId' in item} - {#if item.albumThumbnailAssetId} - {item.albumName} - {:else} -
- {/if} - {:else if !isAlbum && 'name' in item} - {item.name} - {/if} -
-
- - {isAlbum && 'albumName' in item ? item.albumName : 'name' in item ? item.name : ''} - - {#if isAlbum && 'assetCount' in item} - - {$t('items_count', { values: { count: item.assetCount } })} - - {/if} -
- - -
-
-{/snippet} -
{#if pickerMetadata && !Array.isArray(pickerMetadata)} - {@render pickerItemCard(pickerMetadata, removeSelection)} + {:else if pickerMetadata && Array.isArray(pickerMetadata) && pickerMetadata.length > 0}
{#each pickerMetadata as item (item.id)} - {@render pickerItemCard(item, () => removeItemFromSelection(item.id))} + removeItemFromSelection(item.id)} /> {/each}
{/if} diff --git a/web/src/lib/components/workflows/WorkflowPickerItemCard.svelte b/web/src/lib/components/workflows/WorkflowPickerItemCard.svelte new file mode 100644 index 0000000000..7b9a3088ea --- /dev/null +++ b/web/src/lib/components/workflows/WorkflowPickerItemCard.svelte @@ -0,0 +1,57 @@ + + + + +
+ {#if isAlbum && 'albumThumbnailAssetId' in item} + {#if item.albumThumbnailAssetId} + {item.albumName} + {:else} +
+ {/if} + {:else if !isAlbum && 'name' in item} + {item.name} + {/if} +
+
+ + {isAlbum && 'albumName' in item ? item.albumName : 'name' in item ? item.name : ''} + + {#if isAlbum && 'assetCount' in item} + + {$t('items_count', { values: { count: item.assetCount } })} + + {/if} +
+ + +
+
diff --git a/web/src/routes/(user)/utilities/workflows/+page.svelte b/web/src/routes/(user)/utilities/workflows/+page.svelte index 5b16d0da04..39f131aa29 100644 --- a/web/src/routes/(user)/utilities/workflows/+page.svelte +++ b/web/src/routes/(user)/utilities/workflows/+page.svelte @@ -248,7 +248,6 @@ icon: mdiPencil, onAction: () => void handleEditWorkflow(workflow), }, - { title: expandedWorkflows.has(workflow.id) ? $t('hide_schema') : $t('show_schema'), icon: mdiCodeJson, diff --git a/web/src/routes/(user)/utilities/workflows/[workflowId]/+page.svelte b/web/src/routes/(user)/utilities/workflows/[workflowId]/+page.svelte index db55305f02..6248d5ae10 100644 --- a/web/src/routes/(user)/utilities/workflows/[workflowId]/+page.svelte +++ b/web/src/routes/(user)/utilities/workflows/[workflowId]/+page.svelte @@ -1,6 +1,6 @@ {#snippet cardOrder(index: number)} -
+
{index + 1} @@ -455,7 +455,7 @@ {@render stepSeparator()} {/if}
@@ -524,7 +524,7 @@ {@render stepSeparator()} {/if}
{@render cardOrder(index)}