mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-24 16:29:45 +00:00
feat: show loading spinner and uploading filename during image upload (#9189)
## Summary - Show a canvas-based loading spinner on image upload nodes (LoadImage) during file upload via drag-drop, paste, or file picker - Display the uploading file's name immediately in the filename dropdown instead of showing the previous file's name - Show the uploading audio file's name immediately in the audio widget during upload ## Changes - **`useNodeImageUpload.ts`**: Add `isUploading` flag and `onUploadStart` callback to the upload lifecycle; clear `node.imgs` during upload to prevent stale previews - **`useImagePreviewWidget.ts`**: Add `renderUploadSpinner` that draws an animated arc spinner on the canvas when `node.isUploading` is true; guard against empty `imgs` array - **`useImageUploadWidget.ts`**: Set `fileComboWidget.value` to the new filename on upload start; clear `node.imgs` on combo widget change - **`uploadAudio.ts`**: Set `audioWidget.value` to the new filename on upload start - **`litegraph-augmentation.d.ts`**: Add `isUploading` property to `LGraphNode` https://github.com/user-attachments/assets/818ce529-cb83-428a-8c98-dd900a128343 ## Test plan - [x] Upload an image via file picker on LoadImage node — spinner shows during upload, filename updates immediately - [x] Drag-and-drop an image onto LoadImage node — same behavior - [x] Paste an image onto LoadImage node — same behavior - [x] Change the dropdown selection on LoadImage — old preview clears, new image loads - [x] Upload an audio file — filename updates immediately in the widget 🤖 Generated with [Claude Code](https://claude.com/claude-code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9189-feat-show-loading-spinner-and-uploading-filename-during-image-upload-3126d73d365081e4af27cd7252f34298) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,36 @@ function scheduleDeferredImageRender() {
|
||||
})
|
||||
}
|
||||
|
||||
const TWO_PI = Math.PI * 2
|
||||
const SPINNER_ARC_LENGTH = Math.PI * 1.5
|
||||
|
||||
function renderUploadSpinner(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
node: LGraphNode,
|
||||
shiftY: number,
|
||||
computedHeight: number | undefined
|
||||
) {
|
||||
const dw = node.size[0]
|
||||
const dh = computedHeight ?? 220
|
||||
const centerX = dw / 2
|
||||
const centerY = shiftY + dh / 2
|
||||
const radius = 16
|
||||
const angle = ((Date.now() % 1000) / 1000) * TWO_PI
|
||||
|
||||
ctx.save()
|
||||
ctx.strokeStyle = LiteGraph.NODE_TEXT_COLOR
|
||||
ctx.lineWidth = 3
|
||||
ctx.lineCap = 'round'
|
||||
ctx.beginPath()
|
||||
ctx.arc(centerX, centerY, radius, angle, angle + SPINNER_ARC_LENGTH)
|
||||
ctx.stroke()
|
||||
ctx.restore()
|
||||
|
||||
// Schedule next frame to keep spinner animating continuously.
|
||||
// Only runs while node.isUploading is true (checked by caller).
|
||||
node.graph?.setDirtyCanvas(true)
|
||||
}
|
||||
|
||||
const renderPreview = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
node: LGraphNode,
|
||||
@@ -51,6 +81,11 @@ const renderPreview = (
|
||||
) => {
|
||||
if (!node.size) return
|
||||
|
||||
if (node.isUploading) {
|
||||
renderUploadSpinner(ctx, node, shiftY, computedHeight)
|
||||
return
|
||||
}
|
||||
|
||||
const canvas = useCanvasStore().getCanvas()
|
||||
const mouse = canvas.graph_mouse
|
||||
|
||||
@@ -65,6 +100,8 @@ const renderPreview = (
|
||||
}
|
||||
|
||||
const imgs = node.imgs ?? []
|
||||
if (imgs.length === 0) return
|
||||
|
||||
let { imageIndex } = node
|
||||
const numImages = imgs.length
|
||||
if (numImages === 1 && !imageIndex) {
|
||||
|
||||
@@ -54,12 +54,27 @@ export const useImageUploadWidget = () => {
|
||||
createAnnotatedPath(value, { rootFolder: image_folder })
|
||||
|
||||
// Setup file upload handling
|
||||
let rollback: (() => void) | undefined
|
||||
const { openFileSelection } = useNodeImageUpload(node, {
|
||||
allow_batch,
|
||||
fileFilter,
|
||||
accept,
|
||||
folder,
|
||||
onUploadStart: (files) => {
|
||||
if (files.length > 0) {
|
||||
const prev = fileComboWidget.value
|
||||
fileComboWidget.value = files[0].name
|
||||
rollback = () => {
|
||||
fileComboWidget.value = prev
|
||||
}
|
||||
}
|
||||
},
|
||||
onUploadError: () => {
|
||||
rollback?.()
|
||||
rollback = undefined
|
||||
},
|
||||
onUploadComplete: (output) => {
|
||||
rollback = undefined
|
||||
const annotated = output.map(formatPath)
|
||||
annotated.forEach((path) => {
|
||||
addToComboValues(fileComboWidget, path)
|
||||
@@ -88,6 +103,7 @@ export const useImageUploadWidget = () => {
|
||||
|
||||
// Add our own callback to the combo widget to render an image when it changes
|
||||
fileComboWidget.callback = function () {
|
||||
node.imgs = undefined
|
||||
nodeOutputStore.setNodeOutputs(node, String(fileComboWidget.value), {
|
||||
isAnimated
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user