Compare commits

..

1 Commits

Author SHA1 Message Date
CodeRabbit Fixer
7cb09b823d fix: Refactor previewParam and rand to use URLSearchParams in imagePreviewStore (#9346)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:40:14 +01:00
3 changed files with 16 additions and 117 deletions

View File

@@ -9,21 +9,6 @@ interface UseCurveEditorOptions {
modelValue: Ref<CurvePoint[]>
}
function insertSorted(
points: CurvePoint[],
point: CurvePoint
): [CurvePoint[], number] {
let lo = 0
let hi = points.length
while (lo < hi) {
const mid = (lo + hi) >>> 1
if (points[mid][0] < point[0]) lo = mid + 1
else hi = mid
}
const result = [...points.slice(0, lo), point, ...points.slice(lo)]
return [result, lo]
}
export function useCurveEditor({ svgRef, modelValue }: UseCurveEditorOptions) {
const dragIndex = ref(-1)
let cleanupDrag: (() => void) | null = null
@@ -92,10 +77,11 @@ export function useCurveEditor({ svgRef, modelValue }: UseCurveEditorOptions) {
if (e.ctrlKey) return
const newPoint: CurvePoint = [x, y]
const [newPoints, insertIndex] = insertSorted(modelValue.value, newPoint)
const newPoints: CurvePoint[] = [...modelValue.value, newPoint]
newPoints.sort((a, b) => a[0] - b[0])
modelValue.value = newPoints
startDrag(insertIndex, e)
startDrag(newPoints.indexOf(newPoint), e)
}
function startDrag(index: number, e: PointerEvent) {
@@ -120,10 +106,11 @@ export function useCurveEditor({ svgRef, modelValue }: UseCurveEditorOptions) {
if (dragIndex.value < 0) return
const [x, y] = svgCoords(ev)
const movedPoint: CurvePoint = [x, y]
const remaining = modelValue.value.filter((_, i) => i !== dragIndex.value)
const [newPoints, newIndex] = insertSorted(remaining, movedPoint)
const newPoints = [...modelValue.value]
newPoints[dragIndex.value] = movedPoint
newPoints.sort((a, b) => a[0] - b[0])
modelValue.value = newPoints
dragIndex.value = newIndex
dragIndex.value = newPoints.indexOf(movedPoint)
}
const endDrag = () => {

View File

@@ -7,18 +7,11 @@ import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import type { ExecutedWsMessage } from '@/schemas/apiSchema'
import { app } from '@/scripts/app'
import { useNodeOutputStore } from '@/stores/nodeOutputStore'
import * as litegraphUtil from '@/utils/litegraphUtil'
vi.mock('@/utils/litegraphUtil', () => ({
isAnimatedOutput: vi.fn(),
isVideoNode: vi.fn()
}))
const mockGetNodeById = vi.fn()
vi.mock('@/scripts/app', () => ({
app: {
getPreviewFormatParam: vi.fn(() => '&format=test_webp'),
rootGraph: {
getNodeById: (...args: unknown[]) => mockGetNodeById(...args)
},
@@ -151,76 +144,6 @@ describe('nodeOutputStore restoreOutputs', () => {
})
})
describe('nodeOutputStore getPreviewParam', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))
vi.clearAllMocks()
vi.mocked(litegraphUtil.isAnimatedOutput).mockReturnValue(false)
vi.mocked(litegraphUtil.isVideoNode).mockReturnValue(false)
})
it('should return empty string if output is animated', () => {
const store = useNodeOutputStore()
vi.mocked(litegraphUtil.isAnimatedOutput).mockReturnValue(true)
const node = createMockNode()
const outputs = createMockOutputs([{ filename: 'img.png' }])
expect(store.getPreviewParam(node, outputs)).toBe('')
expect(vi.mocked(app).getPreviewFormatParam).not.toHaveBeenCalled()
})
it('should return empty string if isVideoNode returns true', () => {
const store = useNodeOutputStore()
vi.mocked(litegraphUtil.isVideoNode).mockReturnValue(true)
const node = createMockNode()
const outputs = createMockOutputs([{ filename: 'img.png' }])
expect(store.getPreviewParam(node, outputs)).toBe('')
expect(vi.mocked(app).getPreviewFormatParam).not.toHaveBeenCalled()
})
it('should return empty string if outputs.images is undefined', () => {
const store = useNodeOutputStore()
const node = createMockNode()
const outputs: ExecutedWsMessage['output'] = {}
expect(store.getPreviewParam(node, outputs)).toBe('')
expect(vi.mocked(app).getPreviewFormatParam).not.toHaveBeenCalled()
})
it('should return empty string if outputs.images is empty', () => {
const store = useNodeOutputStore()
const node = createMockNode()
const outputs = createMockOutputs([])
expect(store.getPreviewParam(node, outputs)).toBe('')
expect(vi.mocked(app).getPreviewFormatParam).not.toHaveBeenCalled()
})
it('should return empty string if outputs.images contains SVG images', () => {
const store = useNodeOutputStore()
const node = createMockNode()
const outputs = createMockOutputs([{ filename: 'img.svg' }])
expect(store.getPreviewParam(node, outputs)).toBe('')
expect(vi.mocked(app).getPreviewFormatParam).not.toHaveBeenCalled()
})
it('should return format param for standard image outputs', () => {
const store = useNodeOutputStore()
const node = createMockNode()
const outputs = createMockOutputs([{ filename: 'img.png' }])
expect(store.getPreviewParam(node, outputs)).toBe('&format=test_webp')
expect(vi.mocked(app).getPreviewFormatParam).toHaveBeenCalledTimes(1)
})
it('should return format param for multiple standard images', () => {
const store = useNodeOutputStore()
const node = createMockNode()
const outputs = createMockOutputs([
{ filename: 'img1.png' },
{ filename: 'img2.jpg' }
])
expect(store.getPreviewParam(node, outputs)).toBe('&format=test_webp')
expect(vi.mocked(app).getPreviewFormatParam).toHaveBeenCalledTimes(1)
})
})
describe('nodeOutputStore syncLegacyNodeImgs', () => {
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))

View File

@@ -11,6 +11,8 @@ import type {
ResultItemType
} from '@/schemas/apiSchema'
import { appendCloudResParam } from '@/platform/distribution/cloudPreviewUtil'
import { isCloud } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { clone } from '@/scripts/utils'
@@ -97,20 +99,6 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
return true
}
/**
* Get the preview param for the node's outputs.
*
* If the output is an image, use the user's preferred format (from settings).
* For non-image outputs, return an empty string, as including the preview param
* will force the server to load the output file as an image.
*/
function getPreviewParam(
node: LGraphNode,
outputs: ExecutedWsMessage['output']
): string {
return isImageOutputs(node, outputs) ? app.getPreviewFormatParam() : ''
}
function getNodeImageUrls(node: LGraphNode): string[] | undefined {
const previews = getNodePreviews(node)
if (previews?.length) return previews
@@ -118,14 +106,17 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
const outputs = getNodeOutputs(node)
if (!outputs?.images?.length) return
const rand = app.getRandParam()
const previewParam = getPreviewParam(node, outputs)
const isImage = isImageOutputs(node, outputs)
return outputs.images.map((image) => {
const params = new URLSearchParams(image)
if (isImage) appendCloudResParam(params, image.filename)
return api.apiURL(`/view?${params}${previewParam}${rand}`)
if (isImage) {
appendCloudResParam(params, image.filename)
const previewFormat = useSettingStore().get('Comfy.PreviewFormat')
if (previewFormat) params.set('preview', previewFormat)
}
if (!isCloud) params.set('rand', String(Math.random()))
return api.apiURL(`/view?${params}`)
})
}
@@ -443,8 +434,6 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
getNodeOutputs,
getNodeImageUrls,
getNodePreviews,
getPreviewParam,
// Setters
setNodeOutputs,
setNodeOutputsByExecutionId,