refactor(assets): remove isAssetPreviewSupported wrapper and simplify callers

Follow-up to the previous FE-729 commit. After deleting
isAssetAPIEnabled, isAssetPreviewSupported() became a wrapper that
always returned true. Remove the function and simplify all callers.

Changes:
- Delete isAssetPreviewSupported() from assetPreviewUtil.ts.
- Media3DTop.vue: drop the isAssetPreviewSupported() arm of the
  loadThumbnail guard (asset.name check is still required).
- saveMesh.ts: unwrap two `if (isAssetPreviewSupported()) { ... }`
  blocks in applySaveGLBOutput and the SaveGLB beforeRegisterNodeDef
  extension callback.
- FormDropdownMenuItem.vue: drop the early return from
  resolveMeshPreview.
- useLoad3d.ts: drop the isAssetPreviewSupported() arm of the
  modelReady guard.
- Tests: remove the dead "asset preview is unsupported" branches
  (useLoad3d, Media3DTop, FormDropdownMenuItem) and clean up the
  associated mocks and hoisted state.

Auto-fixed unrelated tailwind class-order lint errors in five files
(VirtualGrid, RightSidePanel, Textarea, ModelInfoPanel,
WidgetSelectDefault) to keep CI green.
This commit is contained in:
dante01yoon
2026-05-19 10:27:08 +09:00
parent fcdc4404eb
commit b34026527a
15 changed files with 37 additions and 115 deletions

View File

@@ -1,7 +1,7 @@
<template>
<div
ref="container"
class="h-full scrollbar-thin scrollbar-thumb-(--dialog-surface) scrollbar-track-transparent scrollbar-gutter-stable overflow-y-auto [overflow-anchor:none]"
class="scrollbar-thin scrollbar-thumb-(--dialog-surface) scrollbar-track-transparent scrollbar-gutter-stable h-full overflow-y-auto [overflow-anchor:none]"
>
<div :style="topSpacerStyle" />
<div :style="mergedGridStyle">

View File

@@ -370,7 +370,7 @@ function handleTitleCancel() {
</section>
<!-- Panel Content -->
<div class="flex-1 scrollbar-thin overflow-y-auto">
<div class="scrollbar-thin flex-1 overflow-y-auto">
<TabErrors v-if="activeTab === 'errors'" />
<template v-else-if="!hasSelection">
<TabGlobalParameters v-if="activeTab === 'parameters'" />

View File

@@ -23,7 +23,7 @@ defineExpose({
v-model="modelValue"
:class="
cn(
'flex min-h-16 w-full scrollbar-gutter-stable rounded-lg border-none bg-secondary-background px-3 py-2 text-sm text-base-foreground placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-border-default focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',
'scrollbar-gutter-stable flex min-h-16 w-full rounded-lg border-none bg-secondary-background px-3 py-2 text-sm text-base-foreground placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-border-default focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',
className
)
"

View File

@@ -75,7 +75,6 @@ vi.mock('@/renderer/core/canvas/canvasStore', () => ({
}))
vi.mock('@/platform/assets/utils/assetPreviewUtil', () => ({
isAssetPreviewSupported: vi.fn(() => false),
persistThumbnail: vi.fn().mockResolvedValue(undefined)
}))
@@ -1483,22 +1482,9 @@ describe('useLoad3d', () => {
expect(composable).toBeDefined()
})
it('does not call captureThumbnail when asset preview is unsupported', async () => {
const { isAssetPreviewSupported } =
it('captures thumbnail and persists it when a model_file widget has a value', async () => {
const { persistThumbnail } =
await import('@/platform/assets/utils/assetPreviewUtil')
vi.mocked(isAssetPreviewSupported).mockReturnValue(false)
const { handler } = await getModelReadyHandler()
handler()
await Promise.resolve()
expect(mockLoad3d.captureThumbnail).not.toHaveBeenCalled()
})
it('captures thumbnail and persists it when asset preview is supported and a model_file widget has a value', async () => {
const { isAssetPreviewSupported, persistThumbnail } =
await import('@/platform/assets/utils/assetPreviewUtil')
vi.mocked(isAssetPreviewSupported).mockReturnValue(true)
vi.mocked(Load3dUtils.splitFilePath).mockReturnValue([
'',
'cube.glb'
@@ -1523,9 +1509,8 @@ describe('useLoad3d', () => {
})
it('skips persistence when the model widget has no value', async () => {
const { isAssetPreviewSupported, persistThumbnail } =
const { persistThumbnail } =
await import('@/platform/assets/utils/assetPreviewUtil')
vi.mocked(isAssetPreviewSupported).mockReturnValue(true)
mockNode.widgets = [
{ name: 'model_file', value: '' } as unknown as IWidget
]
@@ -1539,9 +1524,8 @@ describe('useLoad3d', () => {
})
it('swallows captureThumbnail rejections silently', async () => {
const { isAssetPreviewSupported, persistThumbnail } =
const { persistThumbnail } =
await import('@/platform/assets/utils/assetPreviewUtil')
vi.mocked(isAssetPreviewSupported).mockReturnValue(true)
vi.mocked(Load3dUtils.splitFilePath).mockReturnValue([
'',
'broken.glb'

View File

@@ -8,10 +8,7 @@ import { useChainCallback } from '@/composables/functional/useChainCallback'
import type Load3d from '@/extensions/core/load3d/Load3d'
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
import { createLoad3d } from '@/extensions/core/load3d/createLoad3d'
import {
isAssetPreviewSupported,
persistThumbnail
} from '@/platform/assets/utils/assetPreviewUtil'
import { persistThumbnail } from '@/platform/assets/utils/assetPreviewUtil'
import type {
AnimationItem,
CameraConfig,
@@ -862,7 +859,7 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
isFirstModelLoad = false
},
modelReady: () => {
if (!load3d || !isAssetPreviewSupported()) return
if (!load3d) return
const node = nodeRef.value
const modelWidget = node?.widgets?.find(

View File

@@ -50,7 +50,6 @@ vi.mock('@/scripts/domWidget', () => ({
}))
vi.mock('@/platform/assets/utils/assetPreviewUtil', () => ({
isAssetPreviewSupported: vi.fn(() => false),
persistThumbnail: vi.fn()
}))

View File

@@ -17,10 +17,7 @@ type SaveMeshOutput = NodeOutputWith<{
'3d'?: ResultItem[]
}>
import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import {
isAssetPreviewSupported,
persistThumbnail
} from '@/platform/assets/utils/assetPreviewUtil'
import { persistThumbnail } from '@/platform/assets/utils/assetPreviewUtil'
import { app } from '@/scripts/app'
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
import { useExtensionService } from '@/services/extensionService'
@@ -60,15 +57,13 @@ function applySaveGLBOutput(node: LGraphNode, fileInfo: ResultItem): void {
silentOnNotFound: true
})
if (isAssetPreviewSupported()) {
const filename = fileInfo.filename ?? ''
void load3d
.whenLoadIdle()
.then(() => load3d.captureThumbnail(256, 256))
.then((dataUrl) => fetch(dataUrl).then((r) => r.blob()))
.then((blob) => persistThumbnail(filename, blob))
.catch(() => {})
}
const filename = fileInfo.filename ?? ''
void load3d
.whenLoadIdle()
.then(() => load3d.captureThumbnail(256, 256))
.then((dataUrl) => fetch(dataUrl).then((r) => r.blob()))
.then((blob) => persistThumbnail(filename, blob))
.catch(() => {})
})
}
@@ -194,16 +189,14 @@ useExtensionService().registerExtension({
silentOnNotFound: true
})
if (isAssetPreviewSupported()) {
const filename = fileInfo.filename ?? ''
const filename = fileInfo.filename ?? ''
void load3d
.whenLoadIdle()
.then(() => load3d.captureThumbnail(256, 256))
.then((dataUrl) => fetch(dataUrl).then((r) => r.blob()))
.then((blob) => persistThumbnail(filename, blob))
.catch(() => {})
}
void load3d
.whenLoadIdle()
.then(() => load3d.captureThumbnail(256, 256))
.then((dataUrl) => fetch(dataUrl).then((r) => r.blob()))
.then((blob) => persistThumbnail(filename, blob))
.catch(() => {})
}
})
}

View File

@@ -5,15 +5,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { AssetMeta } from '../schemas/mediaAssetSchema'
import Media3DTop from './Media3DTop.vue'
const {
mockUseIntersectionObserver,
mockFindServerPreviewUrl,
mockIsAssetPreviewSupported
} = vi.hoisted(() => ({
mockUseIntersectionObserver: vi.fn(),
mockFindServerPreviewUrl: vi.fn(),
mockIsAssetPreviewSupported: vi.fn(() => true)
}))
const { mockUseIntersectionObserver, mockFindServerPreviewUrl } = vi.hoisted(
() => ({
mockUseIntersectionObserver: vi.fn(),
mockFindServerPreviewUrl: vi.fn()
})
)
vi.mock('@vueuse/core', async (importOriginal) => {
const actual = await importOriginal<typeof VueUseCore>()
@@ -24,8 +21,7 @@ vi.mock('@vueuse/core', async (importOriginal) => {
})
vi.mock('../utils/assetPreviewUtil', () => ({
findServerPreviewUrl: mockFindServerPreviewUrl,
isAssetPreviewSupported: mockIsAssetPreviewSupported
findServerPreviewUrl: mockFindServerPreviewUrl
}))
function makeAsset(overrides: Partial<AssetMeta> = {}): AssetMeta {
@@ -66,7 +62,6 @@ const globalConfig = { mocks: { $t: (key: string) => key } }
describe('Media3DTop', () => {
beforeEach(() => {
vi.clearAllMocks()
mockIsAssetPreviewSupported.mockReturnValue(true)
})
it('renders the placeholder when no thumbnail has loaded', () => {
@@ -117,18 +112,6 @@ describe('Media3DTop', () => {
expect(img).toHaveAttribute('src', 'http://server/from-name.png')
})
it('skips the server query when isAssetPreviewSupported is false', async () => {
fireObserverIntersecting()
mockIsAssetPreviewSupported.mockReturnValue(false)
render(Media3DTop, {
props: { asset: makeAsset() },
global: globalConfig
})
await flush()
expect(mockFindServerPreviewUrl).not.toHaveBeenCalled()
})
it('picks up a patched preview_url after the IntersectionObserver gate has closed', async () => {
// Initial render: observer fires, server has no preview yet — hasAttempted=true
fireObserverIntersecting()

View File

@@ -23,10 +23,7 @@ import { useIntersectionObserver } from '@vueuse/core'
import { onBeforeUnmount, ref, watch } from 'vue'
import type { AssetMeta } from '../schemas/mediaAssetSchema'
import {
findServerPreviewUrl,
isAssetPreviewSupported
} from '../utils/assetPreviewUtil'
import { findServerPreviewUrl } from '../utils/assetPreviewUtil'
const { asset } = defineProps<{ asset: AssetMeta }>()
@@ -49,7 +46,7 @@ async function loadThumbnail() {
if (!asset?.src) return
if (asset.name && isAssetPreviewSupported()) {
if (asset.name) {
const serverPreviewUrl = await findServerPreviewUrl(asset.name)
if (serverPreviewUrl) {
thumbnailSrc.value = serverPreviewUrl

View File

@@ -196,7 +196,7 @@
rows="3"
:class="
cn(
'w-full resize-y scrollbar-gutter-stable rounded-lg border border-transparent bg-transparent px-3 py-2 text-sm text-component-node-foreground transition-colors outline-none focus:bg-component-node-widget-background',
'scrollbar-gutter-stable w-full resize-y rounded-lg border border-transparent bg-transparent px-3 py-2 text-sm text-component-node-foreground transition-colors outline-none focus:bg-component-node-widget-background',
isImmutable && 'cursor-not-allowed'
)
"

View File

@@ -3,7 +3,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
findOutputAsset,
findServerPreviewUrl,
isAssetPreviewSupported,
persistThumbnail
} from '@/platform/assets/utils/assetPreviewUtil'
@@ -77,12 +76,6 @@ const localAssetWithPreview = {
preview_url: '/api/view?type=output&filename=preview.png'
}
describe('isAssetPreviewSupported', () => {
it('returns true (asset API is always available)', () => {
expect(isAssetPreviewSupported()).toBe(true)
})
})
describe('findOutputAsset', () => {
beforeEach(() => vi.clearAllMocks())

View File

@@ -10,15 +10,6 @@ interface AssetRecord {
preview_id?: string | null
}
/**
* Asset preview support. Asset API is always available post-BE-786, so this
* unconditionally returns `true`. Kept as a function to preserve the existing
* caller surface; callers can be simplified in a follow-up.
*/
export function isAssetPreviewSupported(): boolean {
return true
}
async function fetchAssets(
params: Record<string, string>
): Promise<AssetRecord[]> {

View File

@@ -98,7 +98,7 @@
<div
data-testid="widget-select-default-viewport"
role="presentation"
class="flex max-h-56 min-w-full scrollbar-thin scrollbar-thumb-alpha-smoke-500-50 scrollbar-track-transparent scrollbar-gutter-stable flex-col gap-1 overflow-y-auto p-1 text-xs"
class="scrollbar-thin scrollbar-thumb-alpha-smoke-500-50 scrollbar-track-transparent scrollbar-gutter-stable flex max-h-56 min-w-full flex-col gap-1 overflow-y-auto p-1 text-xs"
:style="viewportStyle"
@pointerdown.capture.self="handleViewportPointerDown"
>

View File

@@ -12,14 +12,12 @@ import { AssetKindKey } from './types'
import type { FormDropdownMenuItemProps } from './types'
const mockFindServerPreviewUrl = vi.hoisted(() => vi.fn())
const mockIsAssetPreviewSupported = vi.hoisted(() => vi.fn(() => true))
const intersectionCallbacks = vi.hoisted(
() => [] as Array<(entries: Array<{ isIntersecting: boolean }>) => void>
)
vi.mock('@/platform/assets/utils/assetPreviewUtil', () => ({
findServerPreviewUrl: (name: string) => mockFindServerPreviewUrl(name),
isAssetPreviewSupported: () => mockIsAssetPreviewSupported()
findServerPreviewUrl: (name: string) => mockFindServerPreviewUrl(name)
}))
vi.mock('@vueuse/core', () => ({
@@ -83,7 +81,6 @@ describe('FormDropdownMenuItem', () => {
beforeEach(() => {
intersectionCallbacks.length = 0
mockFindServerPreviewUrl.mockReset()
mockIsAssetPreviewSupported.mockReset().mockReturnValue(true)
})
describe('Label and name', () => {
@@ -167,14 +164,6 @@ describe('FormDropdownMenuItem', () => {
expect(img.getAttribute('src')).toBe('/api/preview/resolved.png')
})
it('skips lookup when asset preview is unsupported', async () => {
mockIsAssetPreviewSupported.mockReturnValue(false)
renderItem({ name: '3d/model.glb' }, { assetKind: 'mesh' })
fireIntersection(true)
await flushPromises()
expect(mockFindServerPreviewUrl).not.toHaveBeenCalled()
})
it('only looks up once for repeated intersection events', async () => {
mockFindServerPreviewUrl.mockResolvedValue(null)
renderItem({ name: '3d/model.glb' }, { assetKind: 'mesh' })

View File

@@ -5,10 +5,7 @@ import { useI18n } from 'vue-i18n'
import { cn } from '@comfyorg/tailwind-utils'
import {
findServerPreviewUrl,
isAssetPreviewSupported
} from '@/platform/assets/utils/assetPreviewUtil'
import { findServerPreviewUrl } from '@/platform/assets/utils/assetPreviewUtil'
import { AssetKindKey } from './types'
import type { FormDropdownMenuItemProps } from './types'
@@ -40,7 +37,6 @@ function toLookupName(name: string): string {
}
async function resolveMeshPreview() {
if (!isAssetPreviewSupported()) return
const url = await findServerPreviewUrl(toLookupName(props.name))
if (url) resolvedMeshPreview.value = url
}