Files
tolaria/src/hooks/useImageDrop.ts

136 lines
4.6 KiB
TypeScript
Raw Normal View History

fix: drag-and-drop images into editor — add drop overlay and copy-to-attachments (#150) * feat: add copy_image_to_vault Rust command for native drag-drop Adds a new Tauri command that copies an image file from a source path (provided by Tauri's drag-drop event) directly into vault/attachments/. More efficient than base64 encoding for filesystem drag-drop. Also adds core:webview:allow-on-drag-drop-event permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: handle Tauri native drag-drop for filesystem images Listen for Tauri's onDragDropEvent to intercept OS-level file drops that bypass the webview's HTML5 DnD API. When image files are dropped: 1. Copy to vault/attachments via copy_image_to_vault command 2. Insert image block into BlockNote at cursor position Also passes vaultPath through Editor → EditorContent → SingleEditorView so the hook can invoke the Tauri command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove nonexistent drag-drop permission from capabilities The onDragDropEvent API works through core:event:default (included in core:default), not a separate webview permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct DragDropEvent type handling for 'over' events Tauri's DragDropEvent discriminated union only provides `paths` on 'drop' events, not 'over'. Show drag overlay unconditionally on 'over' since we can't filter by image paths at that stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: drag-drop-images wireframes (idle + drag-over overlay) * style: rustfmt mcp.rs and image.rs --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-28 21:31:47 +01:00
import { useEffect, useRef, useState, type RefObject } from 'react'
import { invoke, convertFileSrc } from '@tauri-apps/api/core'
import { isTauri } from '../mock-tauri'
import { useTauriDragDropEvent, type TauriDragDropEvent } from './useTauriDragDropEvent'
const IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
fix: drag-and-drop images into editor — add drop overlay and copy-to-attachments (#150) * feat: add copy_image_to_vault Rust command for native drag-drop Adds a new Tauri command that copies an image file from a source path (provided by Tauri's drag-drop event) directly into vault/attachments/. More efficient than base64 encoding for filesystem drag-drop. Also adds core:webview:allow-on-drag-drop-event permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: handle Tauri native drag-drop for filesystem images Listen for Tauri's onDragDropEvent to intercept OS-level file drops that bypass the webview's HTML5 DnD API. When image files are dropped: 1. Copy to vault/attachments via copy_image_to_vault command 2. Insert image block into BlockNote at cursor position Also passes vaultPath through Editor → EditorContent → SingleEditorView so the hook can invoke the Tauri command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove nonexistent drag-drop permission from capabilities The onDragDropEvent API works through core:event:default (included in core:default), not a separate webview permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct DragDropEvent type handling for 'over' events Tauri's DragDropEvent discriminated union only provides `paths` on 'drop' events, not 'over'. Show drag overlay unconditionally on 'over' since we can't filter by image paths at that stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: drag-drop-images wireframes (idle + drag-over overlay) * style: rustfmt mcp.rs and image.rs --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-28 21:31:47 +01:00
const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'tiff']
2026-04-25 10:07:43 +02:00
type ImageUrlHandler = (url: string) => void
function hasImageFiles(dt: DataTransfer): boolean {
for (let i = 0; i < dt.items.length; i++) {
if (dt.items[i].kind === 'file' && IMAGE_MIME_TYPES.includes(dt.items[i].type)) return true
}
return false
}
fix: drag-and-drop images into editor — add drop overlay and copy-to-attachments (#150) * feat: add copy_image_to_vault Rust command for native drag-drop Adds a new Tauri command that copies an image file from a source path (provided by Tauri's drag-drop event) directly into vault/attachments/. More efficient than base64 encoding for filesystem drag-drop. Also adds core:webview:allow-on-drag-drop-event permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: handle Tauri native drag-drop for filesystem images Listen for Tauri's onDragDropEvent to intercept OS-level file drops that bypass the webview's HTML5 DnD API. When image files are dropped: 1. Copy to vault/attachments via copy_image_to_vault command 2. Insert image block into BlockNote at cursor position Also passes vaultPath through Editor → EditorContent → SingleEditorView so the hook can invoke the Tauri command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove nonexistent drag-drop permission from capabilities The onDragDropEvent API works through core:event:default (included in core:default), not a separate webview permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct DragDropEvent type handling for 'over' events Tauri's DragDropEvent discriminated union only provides `paths` on 'drop' events, not 'over'. Show drag overlay unconditionally on 'over' since we can't filter by image paths at that stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: drag-drop-images wireframes (idle + drag-over overlay) * style: rustfmt mcp.rs and image.rs --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-28 21:31:47 +01:00
function isImagePath(path: string): boolean {
const ext = path.split('.').pop()?.toLowerCase() ?? ''
return IMAGE_EXTENSIONS.includes(ext)
}
/** Upload an image file — saves to vault/attachments in Tauri, returns data URL in browser */
export async function uploadImageFile(file: File, vaultPath?: string): Promise<string> {
if (isTauri() && vaultPath) {
const buf = await file.arrayBuffer()
const bytes = new Uint8Array(buf)
let binary = ''
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i])
const base64 = btoa(binary)
const savedPath = await invoke<string>('save_image', {
vaultPath,
filename: file.name,
data: base64,
})
return convertFileSrc(savedPath)
}
return new Promise<string>((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result as string)
reader.onerror = () => reject(reader.error)
reader.readAsDataURL(file)
})
}
fix: drag-and-drop images into editor — add drop overlay and copy-to-attachments (#150) * feat: add copy_image_to_vault Rust command for native drag-drop Adds a new Tauri command that copies an image file from a source path (provided by Tauri's drag-drop event) directly into vault/attachments/. More efficient than base64 encoding for filesystem drag-drop. Also adds core:webview:allow-on-drag-drop-event permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: handle Tauri native drag-drop for filesystem images Listen for Tauri's onDragDropEvent to intercept OS-level file drops that bypass the webview's HTML5 DnD API. When image files are dropped: 1. Copy to vault/attachments via copy_image_to_vault command 2. Insert image block into BlockNote at cursor position Also passes vaultPath through Editor → EditorContent → SingleEditorView so the hook can invoke the Tauri command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove nonexistent drag-drop permission from capabilities The onDragDropEvent API works through core:event:default (included in core:default), not a separate webview permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct DragDropEvent type handling for 'over' events Tauri's DragDropEvent discriminated union only provides `paths` on 'drop' events, not 'over'. Show drag overlay unconditionally on 'over' since we can't filter by image paths at that stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: drag-drop-images wireframes (idle + drag-over overlay) * style: rustfmt mcp.rs and image.rs --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-28 21:31:47 +01:00
/** Copy a dropped file (by OS path) into vault/attachments and return its asset URL. */
async function copyImageToVault(sourcePath: string, vaultPath: string): Promise<string> {
const savedPath = await invoke<string>('copy_image_to_vault', { vaultPath, sourcePath })
return convertFileSrc(savedPath)
}
2026-04-25 10:07:43 +02:00
function insertDroppedImages(
imagePaths: string[],
vaultPath: string | undefined,
onImageUrl: ImageUrlHandler | undefined,
): void {
if (imagePaths.length === 0) return
if (!vaultPath || !onImageUrl) return
for (const sourcePath of imagePaths) {
void copyImageToVault(sourcePath, vaultPath).then(onImageUrl)
}
}
interface UseImageDropOptions {
containerRef: RefObject<HTMLDivElement | null>
fix: drag-and-drop images into editor — add drop overlay and copy-to-attachments (#150) * feat: add copy_image_to_vault Rust command for native drag-drop Adds a new Tauri command that copies an image file from a source path (provided by Tauri's drag-drop event) directly into vault/attachments/. More efficient than base64 encoding for filesystem drag-drop. Also adds core:webview:allow-on-drag-drop-event permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: handle Tauri native drag-drop for filesystem images Listen for Tauri's onDragDropEvent to intercept OS-level file drops that bypass the webview's HTML5 DnD API. When image files are dropped: 1. Copy to vault/attachments via copy_image_to_vault command 2. Insert image block into BlockNote at cursor position Also passes vaultPath through Editor → EditorContent → SingleEditorView so the hook can invoke the Tauri command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove nonexistent drag-drop permission from capabilities The onDragDropEvent API works through core:event:default (included in core:default), not a separate webview permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct DragDropEvent type handling for 'over' events Tauri's DragDropEvent discriminated union only provides `paths` on 'drop' events, not 'over'. Show drag overlay unconditionally on 'over' since we can't filter by image paths at that stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: drag-drop-images wireframes (idle + drag-over overlay) * style: rustfmt mcp.rs and image.rs --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-28 21:31:47 +01:00
/** Called with an asset URL for each image dropped via Tauri native drag-drop. */
onImageUrl?: (url: string) => void
vaultPath?: string
}
function useLatestImageDropRefs(onImageUrl: ImageUrlHandler | undefined, vaultPath: string | undefined) {
fix: drag-and-drop images into editor — add drop overlay and copy-to-attachments (#150) * feat: add copy_image_to_vault Rust command for native drag-drop Adds a new Tauri command that copies an image file from a source path (provided by Tauri's drag-drop event) directly into vault/attachments/. More efficient than base64 encoding for filesystem drag-drop. Also adds core:webview:allow-on-drag-drop-event permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: handle Tauri native drag-drop for filesystem images Listen for Tauri's onDragDropEvent to intercept OS-level file drops that bypass the webview's HTML5 DnD API. When image files are dropped: 1. Copy to vault/attachments via copy_image_to_vault command 2. Insert image block into BlockNote at cursor position Also passes vaultPath through Editor → EditorContent → SingleEditorView so the hook can invoke the Tauri command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove nonexistent drag-drop permission from capabilities The onDragDropEvent API works through core:event:default (included in core:default), not a separate webview permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct DragDropEvent type handling for 'over' events Tauri's DragDropEvent discriminated union only provides `paths` on 'drop' events, not 'over'. Show drag overlay unconditionally on 'over' since we can't filter by image paths at that stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: drag-drop-images wireframes (idle + drag-over overlay) * style: rustfmt mcp.rs and image.rs --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-28 21:31:47 +01:00
const onImageUrlRef = useRef(onImageUrl)
const vaultPathRef = useRef(vaultPath)
useEffect(() => { onImageUrlRef.current = onImageUrl }, [onImageUrl])
fix: drag-and-drop images into editor — add drop overlay and copy-to-attachments (#150) * feat: add copy_image_to_vault Rust command for native drag-drop Adds a new Tauri command that copies an image file from a source path (provided by Tauri's drag-drop event) directly into vault/attachments/. More efficient than base64 encoding for filesystem drag-drop. Also adds core:webview:allow-on-drag-drop-event permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: handle Tauri native drag-drop for filesystem images Listen for Tauri's onDragDropEvent to intercept OS-level file drops that bypass the webview's HTML5 DnD API. When image files are dropped: 1. Copy to vault/attachments via copy_image_to_vault command 2. Insert image block into BlockNote at cursor position Also passes vaultPath through Editor → EditorContent → SingleEditorView so the hook can invoke the Tauri command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove nonexistent drag-drop permission from capabilities The onDragDropEvent API works through core:event:default (included in core:default), not a separate webview permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct DragDropEvent type handling for 'over' events Tauri's DragDropEvent discriminated union only provides `paths` on 'drop' events, not 'over'. Show drag overlay unconditionally on 'over' since we can't filter by image paths at that stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: drag-drop-images wireframes (idle + drag-over overlay) * style: rustfmt mcp.rs and image.rs --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-28 21:31:47 +01:00
useEffect(() => { vaultPathRef.current = vaultPath }, [vaultPath])
return { onImageUrlRef, vaultPathRef }
}
function useHtmlImageDropFeedback(
containerRef: RefObject<HTMLDivElement | null>,
setIsDragOver: (isDragOver: boolean) => void,
) {
useEffect(() => {
const container = containerRef.current
if (!container) return
const handleDragOver = (e: DragEvent) => {
if (!e.dataTransfer || !hasImageFiles(e.dataTransfer)) return
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
setIsDragOver(true)
}
const handleDragLeave = (e: DragEvent) => {
if (!container.contains(e.relatedTarget as Node)) setIsDragOver(false)
}
const handleDrop = () => setIsDragOver(false)
container.addEventListener('dragover', handleDragOver)
container.addEventListener('dragleave', handleDragLeave)
container.addEventListener('drop', handleDrop)
return () => {
container.removeEventListener('dragover', handleDragOver)
container.removeEventListener('dragleave', handleDragLeave)
container.removeEventListener('drop', handleDrop)
}
}, [containerRef, setIsDragOver])
}
function handleNativeImageDrop(
event: TauriDragDropEvent,
vaultPath: string | undefined,
onImageUrl: ImageUrlHandler | undefined,
): void {
if (event.payload.type !== 'drop') return
insertDroppedImages(event.payload.paths.filter(isImagePath), vaultPath, onImageUrl)
}
fix: drag-and-drop images into editor — add drop overlay and copy-to-attachments (#150) * feat: add copy_image_to_vault Rust command for native drag-drop Adds a new Tauri command that copies an image file from a source path (provided by Tauri's drag-drop event) directly into vault/attachments/. More efficient than base64 encoding for filesystem drag-drop. Also adds core:webview:allow-on-drag-drop-event permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: handle Tauri native drag-drop for filesystem images Listen for Tauri's onDragDropEvent to intercept OS-level file drops that bypass the webview's HTML5 DnD API. When image files are dropped: 1. Copy to vault/attachments via copy_image_to_vault command 2. Insert image block into BlockNote at cursor position Also passes vaultPath through Editor → EditorContent → SingleEditorView so the hook can invoke the Tauri command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove nonexistent drag-drop permission from capabilities The onDragDropEvent API works through core:event:default (included in core:default), not a separate webview permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct DragDropEvent type handling for 'over' events Tauri's DragDropEvent discriminated union only provides `paths` on 'drop' events, not 'over'. Show drag overlay unconditionally on 'over' since we can't filter by image paths at that stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: drag-drop-images wireframes (idle + drag-over overlay) * style: rustfmt mcp.rs and image.rs --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-28 21:31:47 +01:00
export function useImageDrop({ containerRef, onImageUrl, vaultPath }: UseImageDropOptions) {
const [isDragOver, setIsDragOver] = useState(false)
const { onImageUrlRef, vaultPathRef } = useLatestImageDropRefs(onImageUrl, vaultPath)
useHtmlImageDropFeedback(containerRef, setIsDragOver)
useTauriDragDropEvent((event) => {
setIsDragOver(false)
handleNativeImageDrop(event, vaultPathRef.current, onImageUrlRef.current)
})
fix: drag-and-drop images into editor — add drop overlay and copy-to-attachments (#150) * feat: add copy_image_to_vault Rust command for native drag-drop Adds a new Tauri command that copies an image file from a source path (provided by Tauri's drag-drop event) directly into vault/attachments/. More efficient than base64 encoding for filesystem drag-drop. Also adds core:webview:allow-on-drag-drop-event permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: handle Tauri native drag-drop for filesystem images Listen for Tauri's onDragDropEvent to intercept OS-level file drops that bypass the webview's HTML5 DnD API. When image files are dropped: 1. Copy to vault/attachments via copy_image_to_vault command 2. Insert image block into BlockNote at cursor position Also passes vaultPath through Editor → EditorContent → SingleEditorView so the hook can invoke the Tauri command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove nonexistent drag-drop permission from capabilities The onDragDropEvent API works through core:event:default (included in core:default), not a separate webview permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct DragDropEvent type handling for 'over' events Tauri's DragDropEvent discriminated union only provides `paths` on 'drop' events, not 'over'. Show drag overlay unconditionally on 'over' since we can't filter by image paths at that stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: drag-drop-images wireframes (idle + drag-over overlay) * style: rustfmt mcp.rs and image.rs --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-28 21:31:47 +01:00
return { isDragOver }
}