Compare commits

...

9 Commits

Author SHA1 Message Date
Johnpaul Chiwetelu
f5e9ed15b3 Merge branch 'main' into fix/drag-drop-url-filename 2026-03-26 03:58:00 +01:00
Johnpaul
1962e3aab1 refactor: consolidate mimeTypeUtil into src/utils
Move MIME type mapping to src/utils/mimeTypeUtil.ts with both
getMimeType (ext→mime) and getExtension (mime→ext) derived from a
single map. Browser test helpers now import from the shared util.
2026-03-26 03:57:12 +01:00
Johnpaul
6b0ebe6112 refactor: move MIME mapping to shared mimeTypeUtil
Address review feedback: extract MIME-to-extension map into a shared
util with both getMimeType and getExtension derived from a single map.
2026-03-26 03:57:12 +01:00
Johnpaul Chiwetelu
60b6f78397 chore: bump CI container to 0.0.16 (#10527) 2026-03-26 02:34:27 +01:00
Alexander Brown
d4efb095dc Merge branch 'main' into fix/drag-drop-url-filename 2026-03-25 18:14:24 -07:00
Yourz
6c7c3ea006 fix: tree explorer row height and width overflow (#10501)
## Summary

Fix tree explorer row sizing: consistent row height and prevent
horizontal overflow.

## Changes

- **What**:
1. Reduce node bookmark button from `size-6` (24px) to `size-5` (20px)
so node and folder rows both have 36px height, matching
`TreeVirtualizer` estimate-size and fixing tree list overlap.
2. Change row width from `w-full` to `w-[calc(100%-var(--spacing)*4)]`
to prevent horizontal overflow while keeping `mx-2` margin.

## Review Focus

Pure UI change — no test coverage needed. Verify tree rows render at
consistent height and no horizontal overflow occurs.

## Screenshots (if applicable)

| Before    | After |
| -------- | ------- |
|<img width="1218" height="1662" alt="image"
src="https://github.com/user-attachments/assets/89c799ab-cef3-40ee-88ca-900f5d3c7890"
/>|<img width="407" height="758" alt="image"
src="https://github.com/user-attachments/assets/f9aa4569-aaf8-467f-9dde-a187151af9aa"
/>|

N/A

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10501-fix-tree-explorer-row-height-and-width-overflow-32e6d73d3650819aa645c2262693ec62)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-03-26 09:20:42 +09:00
pythongosssss
771f68f92a fix: App mode - workaround for alt+m producing alt+μ on mac (#10528)
## Summary

Adds a second keybinding for app mode as on Mac Alt+M produces Alt+μ

## Changes

- **What**: add extra binding entry

## Screenshots (if applicable)

Still shows alt + m in the keybinding panel
<img width="840" height="206" alt="image"
src="https://github.com/user-attachments/assets/f1906bc2-e7c2-4eac-b3ca-5a8a207cc93c"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10528-fix-App-mode-workaround-for-alt-m-producing-alt-on-mac-32e6d73d36508176b7d6d918c5bb88f3)
by [Unito](https://www.unito.io)
2026-03-25 16:15:41 -07:00
Johnpaul
8d99cfbf0d fix: let URI drops on Vue nodes bubble to document handler
The @drop.prevent modifier called preventDefault() unconditionally,
causing the document-level drop handler to skip the event.
2026-03-25 23:30:57 +01:00
Johnpaul
543d21f6c6 fix: extract filename from URL when uploading dropped images
Dropping an image URL onto the canvas used the raw URL as the filename
in the multipart upload, causing a 500 error from the backend.
2026-03-25 22:26:44 +01:00
12 changed files with 88 additions and 26 deletions

View File

@@ -39,7 +39,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
container:
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.13
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.16
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
@@ -87,7 +87,7 @@ jobs:
needs: setup
runs-on: ubuntu-latest
container:
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.13
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.16
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -77,7 +77,7 @@ jobs:
needs: setup
runs-on: ubuntu-latest
container:
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.13
image: ghcr.io/comfy-org/comfyui-ci-container:0.0.16
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -4,7 +4,7 @@ import { basename } from 'path'
import type { Locator, Page } from '@playwright/test'
import type { KeyboardHelper } from './KeyboardHelper'
import { getMimeType } from './mimeTypeUtil'
import { getMimeType } from '../../../src/utils/mimeTypeUtil'
export class ClipboardHelper {
constructor(

View File

@@ -3,7 +3,7 @@ import { readFileSync } from 'fs'
import type { Page } from '@playwright/test'
import type { Position } from '../types'
import { getMimeType } from './mimeTypeUtil'
import { getMimeType } from '../../../src/utils/mimeTypeUtil'
export class DragDropHelper {
constructor(

View File

@@ -1,13 +0,0 @@
export function getMimeType(fileName: string): string {
const name = fileName.toLowerCase()
if (name.endsWith('.png')) return 'image/png'
if (name.endsWith('.jpg') || name.endsWith('.jpeg')) return 'image/jpeg'
if (name.endsWith('.webp')) return 'image/webp'
if (name.endsWith('.svg')) return 'image/svg+xml'
if (name.endsWith('.avif')) return 'image/avif'
if (name.endsWith('.webm')) return 'video/webm'
if (name.endsWith('.mp4')) return 'video/mp4'
if (name.endsWith('.json')) return 'application/json'
if (name.endsWith('.glb')) return 'model/gltf-binary'
return 'application/octet-stream'
}

View File

@@ -8,7 +8,7 @@
:get-children="
(item) => (item.children?.length ? item.children : undefined)
"
class="m-0 min-w-0 p-0 pb-2"
class="m-0 min-w-0 p-0 px-2 pb-2"
>
<TreeVirtualizer
v-slot="{ item }"

View File

@@ -28,7 +28,7 @@
<button
:class="
cn(
'hover:text-foreground flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm border-none bg-transparent text-muted-foreground',
'hover:text-foreground flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-sm border-none bg-transparent text-muted-foreground',
'opacity-0 group-hover/tree-node:opacity-100'
)
"
@@ -105,7 +105,7 @@ defineOptions({
})
const ROW_CLASS =
'group/tree-node flex w-full min-w-0 cursor-pointer select-none items-center gap-3 overflow-hidden py-2 outline-none hover:bg-comfy-input mx-2 rounded'
'group/tree-node flex w-full min-w-0 cursor-pointer select-none items-center gap-3 overflow-hidden py-2 outline-none hover:bg-comfy-input rounded'
const { item } = defineProps<{
item: FlattenedItem<RenderedTreeExplorerNode<ComfyNodeDefImpl>>

View File

@@ -61,6 +61,13 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
},
commandId: 'Comfy.ToggleLinear'
},
{
combo: {
alt: true,
key: 'µ'
},
commandId: 'Comfy.ToggleLinear'
},
{
combo: {
key: 's',

View File

@@ -33,7 +33,7 @@
@contextmenu="handleContextMenu"
@dragover.prevent="handleDragOver"
@dragleave="handleDragLeave"
@drop.prevent="handleDrop"
@drop="handleDrop"
>
<!-- Selection/Execution Outline Overlay -->
<AppOutput
@@ -834,6 +834,9 @@ function handleDrop(event: DragEvent) {
if (!node?.onDragDrop) return
const handled = node.onDragDrop(event)
if (handled === true) event.stopPropagation()
if (handled === true) {
event.preventDefault()
event.stopPropagation()
}
}
</script>

View File

@@ -108,8 +108,8 @@ describe('eventUtils', () => {
expect(actual).toEqual([file1, file2])
})
it('should fetch URI and return as File when text/uri-list is present', async () => {
const uri = 'https://example.com/api/view?filename=test.png&type=input'
it('should fetch URI and return as File with extracted filename', async () => {
const uri = 'https://example.com/images/photo.png?w=1200&format=auto'
const imageBlob = new Blob([new Uint8Array([0x89, 0x50])], {
type: 'image/png'
})
@@ -126,6 +126,25 @@ describe('eventUtils', () => {
expect(actual).toHaveLength(1)
expect(actual[0]).toBeInstanceOf(File)
expect(actual[0].type).toBe('image/png')
expect(actual[0].name).toBe('photo.png')
})
it('should use fallback filename when URL path has no extension', async () => {
const uri = 'https://example.com/api/view?filename=test.png&type=input'
const imageBlob = new Blob([new Uint8Array([0x89, 0x50])], {
type: 'image/png'
})
fetchSpy.mockResolvedValue(new Response(imageBlob))
const dataTransfer = new DataTransfer()
dataTransfer.setData('text/uri-list', uri)
const actual = await extractFilesFromDragEvent(
new FakeDragEvent('drop', { dataTransfer })
)
expect(actual).toHaveLength(1)
expect(actual[0].name).toBe('downloaded.png')
})
it('should handle text/x-moz-url type', async () => {

View File

@@ -1,3 +1,17 @@
import { getExtension } from '@/utils/mimeTypeUtil'
function extractFilenameFromUri(uri: string, mimeType: string): string {
try {
const pathname = new URL(uri).pathname
const basename = pathname.split('/').pop()
if (basename && basename.includes('.')) return basename
} catch {
// Not a valid URL, fall through
}
return `downloaded${getExtension(mimeType)}`
}
export async function extractFilesFromDragEvent(
event: DragEvent
): Promise<File[]> {
@@ -23,7 +37,8 @@ export async function extractFilesFromDragEvent(
try {
const response = await fetch(uri)
const blob = await response.blob()
return [new File([blob], uri, { type: blob.type })]
const filename = extractFilenameFromUri(uri, blob.type)
return [new File([blob], filename, { type: blob.type })]
} catch {
return []
}

31
src/utils/mimeTypeUtil.ts Normal file
View File

@@ -0,0 +1,31 @@
const EXT_TO_MIME: Record<string, string> = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.webp': 'image/webp',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.bmp': 'image/bmp',
'.avif': 'image/avif',
'.mp3': 'audio/mpeg',
'.wav': 'audio/wav',
'.ogg': 'audio/ogg',
'.mp4': 'video/mp4',
'.webm': 'video/webm'
}
const MIME_TO_EXT: Record<string, string> = Object.fromEntries(
Object.entries(EXT_TO_MIME)
.filter(([ext]) => ext !== '.jpeg')
.map(([ext, mime]) => [mime, ext])
)
export function getMimeType(fileName: string): string {
const name = fileName.toLowerCase()
const ext = name.slice(name.lastIndexOf('.'))
return EXT_TO_MIME[ext] ?? 'application/octet-stream'
}
export function getExtension(mimeType: string): string {
return MIME_TO_EXT[mimeType] ?? ''
}