mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
Compare commits
9 Commits
austin/dyn
...
fix/drag-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5e9ed15b3 | ||
|
|
1962e3aab1 | ||
|
|
6b0ebe6112 | ||
|
|
60b6f78397 | ||
|
|
d4efb095dc | ||
|
|
6c7c3ea006 | ||
|
|
771f68f92a | ||
|
|
8d99cfbf0d | ||
|
|
543d21f6c6 |
4
.github/workflows/ci-tests-e2e.yaml
vendored
4
.github/workflows/ci-tests-e2e.yaml
vendored
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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 }"
|
||||
|
||||
@@ -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>>
|
||||
|
||||
@@ -61,6 +61,13 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
},
|
||||
commandId: 'Comfy.ToggleLinear'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
alt: true,
|
||||
key: 'µ'
|
||||
},
|
||||
commandId: 'Comfy.ToggleLinear'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
key: 's',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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
31
src/utils/mimeTypeUtil.ts
Normal 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] ?? ''
|
||||
}
|
||||
Reference in New Issue
Block a user