lint: enable oxlint func-style rule and convert function expressions

Enables eslint/func-style in oxlint with declaration mode to enforce
function declarations over function expressions and arrow expressions
assigned to variables. Vendored litegraph is excluded via override.

Converts existing function expressions and variable-initialized arrow
functions to function declarations across src/, browser_tests/, apps/,
packages/, and scripts/. Adjusts a handful of let-reassignable callback
placeholders, narrowed variable patterns, and typed widget constructors
to keep type safety intact.

Pre-existing type-aware oxlint errors (no-console, no-floating-promises,
no-explicit-any) are unchanged from main.
This commit is contained in:
Glary-Bot
2026-05-20 05:34:54 +00:00
parent 7325c715c7
commit 3e56bc925f
692 changed files with 4543 additions and 3923 deletions

View File

@@ -28,6 +28,7 @@
],
"rules": {
"no-async-promise-executor": "off",
"func-style": ["error", "declaration"],
"no-console": [
"error",
{
@@ -124,6 +125,12 @@
"no-console": "allow"
}
},
{
"files": ["src/lib/litegraph/**"],
"rules": {
"func-style": "off"
}
},
{
"files": ["browser_tests/**/*.ts"],
"jsPlugins": ["eslint-plugin-playwright"],

View File

@@ -47,7 +47,7 @@ setup((app) => {
})
// Theme and dialog decorator
export const withTheme = (Story: StoryFn, context: StoryContext) => {
export function withTheme(Story: StoryFn, context: StoryContext) {
const theme = context.globals.theme || 'light'
// Apply theme class to document root

View File

@@ -40,7 +40,7 @@ setup((app) => {
app.use(ToastService)
})
export const withTheme = (Story: StoryFn, context: StoryContext) => {
export function withTheme(Story: StoryFn, context: StoryContext) {
const theme = context.globals.theme || 'light'
if (theme === 'dark') {
document.documentElement.classList.add('dark-theme')

View File

@@ -58,7 +58,7 @@ const tooltipText = computed(() => {
: t('serverStart.copyAllTooltip')
})
const handleCopy = async () => {
async function handleCopy() {
const existingSelection = terminal.getSelection()
const shouldSelectAll = !existingSelection
if (shouldSelectAll) terminal.selectAll()
@@ -76,7 +76,7 @@ const handleCopy = async () => {
}
}
const showContextMenu = (event: MouseEvent) => {
function showContextMenu(event: MouseEvent) {
event.preventDefault()
electronAPI()?.showContextMenu({ type: 'text' })
}

View File

@@ -44,8 +44,9 @@ const emit = defineEmits<{
const validationState = ref<ValidationState>(ValidationState.IDLE)
const cleanInput = (value: string): string =>
value ? value.replace(/\s+/g, '') : ''
function cleanInput(value: string): string {
return value ? value.replace(/\s+/g, '') : ''
}
// Add internal value state
const internalValue = ref(cleanInput(props.modelValue))
@@ -68,14 +69,14 @@ onMounted(async () => {
await validateUrl(props.modelValue)
})
const handleInput = (value: string | undefined) => {
function handleInput(value: string | undefined) {
// Update internal value without emitting
internalValue.value = cleanInput(value ?? '')
// Reset validation state when user types
validationState.value = ValidationState.IDLE
}
const handleBlur = async () => {
async function handleBlur() {
const input = cleanInput(internalValue.value)
let normalizedUrl = input
@@ -91,7 +92,7 @@ const handleBlur = async () => {
}
// Default validation implementation
const defaultValidateUrl = async (url: string): Promise<boolean> => {
async function defaultValidateUrl(url: string): Promise<boolean> {
if (!isValidUrl(url)) return false
try {
return await checkUrlReachable(url)
@@ -100,7 +101,7 @@ const defaultValidateUrl = async (url: string): Promise<boolean> => {
}
}
const validateUrl = async (value: string) => {
async function validateUrl(value: string) {
if (validationState.value === ValidationState.LOADING) return
const url = cleanInput(value)

View File

@@ -127,7 +127,7 @@ const showDialog = ref(false)
const autoUpdate = defineModel<boolean>('autoUpdate', { required: true })
const allowMetrics = defineModel<boolean>('allowMetrics', { required: true })
const showMetricsInfo = () => {
function showMetricsInfo() {
showDialog.value = true
}
</script>

View File

@@ -182,10 +182,12 @@ function getTorchMirrorItem(device: TorchDeviceType): UVMirror {
}
const userIsInChina = ref(false)
const useFallbackMirror = (mirror: UVMirror) => ({
...mirror,
mirror: mirror.fallbackMirror
})
function useFallbackMirror(mirror: UVMirror) {
return {
...mirror,
mirror: mirror.fallbackMirror
}
}
const mirrors = computed<[UVMirror, ModelRef<string>][]>(() =>
(
@@ -212,7 +214,7 @@ onMounted(async () => {
userIsInChina.value = await isInChina()
})
const validatePath = async (path: string | undefined) => {
async function validatePath(path: string | undefined) {
try {
pathError.value = ''
pathExists.value = false
@@ -246,7 +248,7 @@ const validatePath = async (path: string | undefined) => {
}
}
const browsePath = async () => {
async function browsePath() {
try {
const result = await electron.showDirectoryPicker()
if (result) {
@@ -258,7 +260,7 @@ const browsePath = async () => {
}
}
const onFocus = async () => {
async function onFocus() {
if (!inputTouched.value) {
inputTouched.value = true
return

View File

@@ -92,7 +92,7 @@ const isValidSource = computed(
() => sourcePath.value !== '' && pathError.value === ''
)
const validateSource = async (sourcePath: string | undefined) => {
async function validateSource(sourcePath: string | undefined) {
if (!sourcePath) {
pathError.value = ''
return
@@ -109,7 +109,7 @@ const validateSource = async (sourcePath: string | undefined) => {
}
}
const browsePath = async () => {
async function browsePath() {
try {
const result = await electron.showDirectoryPicker()
if (result) {

View File

@@ -82,7 +82,7 @@ const isExecuting = useMinLoadingDurationRef(reactiveExecuting, 250)
// Popover
const infoPopover = ref<InstanceType<typeof Popover> | null>(null)
const toggle = (event: Event) => {
function toggle(event: Event) {
infoPopover.value?.toggle(event)
}
</script>

View File

@@ -67,7 +67,7 @@ defineProps<{
filter: MaintenanceFilter
}>()
const executeTask = async (task: MaintenanceTask) => {
async function executeTask(task: MaintenanceTask) {
let message: string | undefined
try {
@@ -87,7 +87,7 @@ const executeTask = async (task: MaintenanceTask) => {
}
// Commands
const confirmButton = async (event: MouseEvent, task: MaintenanceTask) => {
async function confirmButton(event: MouseEvent, task: MaintenanceTask) {
if (!task.requireConfirm) {
await executeTask(task)
return

View File

@@ -34,10 +34,10 @@ const buffer = useTerminalBuffer()
let xterm: Terminal | null = null
// Created and destroyed with the Drawer - contents copied from hidden buffer
const terminalCreated = (
function terminalCreated(
{ terminal, useAutoSize }: ReturnType<typeof useTerminal>,
root: Ref<HTMLElement | undefined>
) => {
) {
xterm = terminal
useAutoSize({ root, autoRows: true, autoCols: true })
terminal.write(props.defaultMessage)
@@ -49,7 +49,7 @@ const terminalCreated = (
terminal.options.disableStdin = true
}
const terminalUnmounted = () => {
function terminalUnmounted() {
xterm = null
}

View File

@@ -55,14 +55,14 @@ export function useTerminal(element: Ref<HTMLElement | undefined>) {
minRows?: number
onResize?: () => void
}) {
const ensureValidRows = (rows: number | undefined): number => {
function ensureValidRows(rows: number | undefined): number {
if (rows == null || isNaN(rows)) {
return (root.value?.clientHeight ?? 80) / 20
}
return rows
}
const ensureValidCols = (cols: number | undefined): number => {
function ensureValidCols(cols: number | undefined): number {
if (cols == null || isNaN(cols)) {
// Sometimes this is NaN if so, estimate.
return (root.value?.clientWidth ?? 80) / 8
@@ -70,7 +70,7 @@ export function useTerminal(element: Ref<HTMLElement | undefined>) {
return cols
}
const resize = () => {
function resize() {
const dims = fitAddon.proposeDimensions()
// Sometimes propose returns NaN, so we may need to estimate.
terminal.resize(

View File

@@ -6,13 +6,17 @@ export function useTerminalBuffer() {
const serializeAddon = new SerializeAddon()
const terminal = markRaw(new Terminal({ convertEol: true }))
const copyTo = (destinationTerminal: Terminal) => {
function copyTo(destinationTerminal: Terminal) {
destinationTerminal.write(serializeAddon.serialize())
}
const write = (message: string) => terminal.write(message)
function write(message: string) {
return terminal.write(message)
}
const serialize = () => serializeAddon.serialize()
function serialize() {
return serializeAddon.serialize()
}
onMounted(() => {
terminal.loadAddon(serializeAddon)

View File

@@ -5,7 +5,7 @@ import { electronAPI } from '@/utils/envUtil'
const electron = electronAPI()
const openUrl = (url: string) => {
function openUrl(url: string) {
window.open(url, '_blank')
return true
}

View File

@@ -124,13 +124,15 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
* @param task Task to get the matching state object for
* @returns The state object for this task
*/
const getRunner = (task: MaintenanceTask) => taskRunners.value.get(task.id)!
function getRunner(task: MaintenanceTask) {
return taskRunners.value.get(task.id)!
}
/**
* Updates the task list with the latest validation state.
* @param validationUpdate Update details passed in by electron
*/
const processUpdate = (validationUpdate: InstallValidation) => {
function processUpdate(validationUpdate: InstallValidation) {
lastUpdate.value = validationUpdate
const update = validationUpdate as IndexedUpdate
isRefreshing.value = true
@@ -151,19 +153,19 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
}
/** Clears the resolved status of tasks (when changing filters) */
const clearResolved = () => {
function clearResolved() {
for (const task of tasks.value) {
getRunner(task).resolved &&= false
}
}
/** @todo Refreshes Electron tasks only. */
const refreshDesktopTasks = async () => {
async function refreshDesktopTasks() {
isRefreshing.value = true
await electron.Validation.validateInstallation(processUpdate)
}
const execute = async (task: MaintenanceTask) => {
async function execute(task: MaintenanceTask) {
const success = await getRunner(task).execute(task)
if (success && task.isInstallationFix) {
await refreshDesktopTasks()

View File

@@ -7,7 +7,7 @@ import { electronAPI } from './envUtil'
* @param mirror - The mirror to check.
* @returns True if the mirror is reachable, false otherwise.
*/
export const checkMirrorReachable = async (mirror: string) => {
export async function checkMirrorReachable(mirror: string) {
return (
isValidUrl(mirror) && (await electronAPI().NetWork.canAccessUrl(mirror))
)

View File

@@ -36,7 +36,7 @@ import { electronAPI } from '@/utils/envUtil'
const route = useRoute()
const { id, title, message, buttons } = getDialog(route.params.dialogId)
const handleButtonClick = async (button: DialogAction) => {
async function handleButtonClick(button: DialogAction) {
await electronAPI().Dialog.clickButton(button.returnValue)
}
</script>

View File

@@ -52,7 +52,7 @@ const electron = electronAPI()
const terminalVisible = ref(false)
const toggleConsoleDrawer = () => {
function toggleConsoleDrawer() {
terminalVisible.value = !terminalVisible.value
}

View File

@@ -47,11 +47,11 @@ import { useRouter } from 'vue-router'
import BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'
const openGitDownloads = () => {
function openGitDownloads() {
window.open('https://git-scm.com/downloads/', '_blank')
}
const skipGit = async () => {
async function skipGit() {
console.warn('pushing')
const router = useRouter()
await router.push('install')

View File

@@ -8,8 +8,8 @@ import { createMemoryHistory, createRouter } from 'vue-router'
import InstallView from './InstallView.vue'
// Create a mock router for stories
const createMockRouter = () =>
createRouter({
function createMockRouter() {
return createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
@@ -23,6 +23,7 @@ const createMockRouter = () =>
}
]
})
}
const meta: Meta<typeof InstallView> = {
title: 'Desktop/Views/InstallView',

View File

@@ -90,7 +90,7 @@ const currentStep = ref('1')
/** Forces each install step to be visited at least once. */
const highestStep = ref(0)
const handleStepChange = (value: string | number) => {
function handleStepChange(value: string | number) {
setHighestStep(value)
electronAPI().Events.trackEvent('install_stepper_change', {
@@ -98,7 +98,7 @@ const handleStepChange = (value: string | number) => {
})
}
const setHighestStep = (value: string | number) => {
function setHighestStep(value: string | number) {
const int = typeof value === 'number' ? value : parseInt(value, 10)
if (!isNaN(int) && int > highestStep.value) highestStep.value = int
}
@@ -123,7 +123,7 @@ const canProceed = computed(() => {
})
// Navigation methods
const goToNextStep = () => {
function goToNextStep() {
const nextStep = (parseInt(currentStep.value) + 1).toString()
currentStep.value = nextStep
setHighestStep(nextStep)
@@ -132,7 +132,7 @@ const goToNextStep = () => {
})
}
const goToPreviousStep = () => {
function goToPreviousStep() {
const prevStep = (parseInt(currentStep.value) - 1).toString()
currentStep.value = prevStep
electronAPI().Events.trackEvent('install_stepper_change', {
@@ -142,7 +142,7 @@ const goToPreviousStep = () => {
const electron = electronAPI()
const router = useRouter()
const install = async () => {
async function install() {
if (!device.value) return
const options: InstallOptions = {

View File

@@ -35,12 +35,14 @@ const validationState: ValidationState = {
upgradePackages: 'OK'
}
const createMockElectronAPI = () => {
function createMockElectronAPI() {
const logListeners: Array<(message: string) => void> = []
const getValidationUpdate = () => ({
...validationState
})
function getValidationUpdate() {
return {
...validationState
}
}
return {
getPlatform: () => 'darwin',
@@ -76,7 +78,7 @@ const createMockElectronAPI = () => {
}
}
const ensureElectronAPI = () => {
function ensureElectronAPI() {
const globalWindow = window as { electronAPI?: unknown }
if (!globalWindow.electronAPI) {
globalWindow.electronAPI = createMockElectronAPI()

View File

@@ -183,7 +183,7 @@ const unsafeReasonText = computed(() => {
})
/** If valid, leave the validation window. */
const completeValidation = async () => {
async function completeValidation() {
const isValid = await electron.Validation.complete()
if (!isValid) {
toast.add({
@@ -194,7 +194,7 @@ const completeValidation = async () => {
}
}
const toggleConsoleDrawer = () => {
function toggleConsoleDrawer() {
terminalVisible.value = !terminalVisible.value
}

View File

@@ -67,7 +67,9 @@ const electron = electronAPI()
const basePath = ref<string | null>(null)
const sep = ref<'\\' | '/'>('/')
const restartApp = (message?: string) => electron.restartApp(message)
function restartApp(message?: string) {
return electron.restartApp(message)
}
onMounted(async () => {
basePath.value = await electron.getBasePath()

View File

@@ -64,7 +64,7 @@ const allowMetrics = ref(true)
const router = useRouter()
const isUpdating = ref(false)
const updateConsent = async () => {
async function updateConsent() {
isUpdating.value = true
try {
await electronAPI().setMetricsConsent(allowMetrics.value)

View File

@@ -61,19 +61,19 @@ import { useRouter } from 'vue-router'
import BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'
const openDocs = () => {
function openDocs() {
window.open(
'https://github.com/Comfy-Org/desktop#currently-supported-platforms',
'_blank'
)
}
const reportIssue = () => {
function reportIssue() {
window.open('https://forum.comfy.org/c/v1-feedback/', '_blank')
}
const router = useRouter()
const continueToInstall = async () => {
async function continueToInstall() {
await router.push('/install')
}
</script>

View File

@@ -118,7 +118,7 @@ let xterm: Terminal | undefined
/**
* Handles installation stage updates from the desktop
*/
const updateInstallStage = (stageInfo: InstallStageInfo) => {
function updateInstallStage(stageInfo: InstallStageInfo) {
console.warn('[InstallStage.onUpdate] Received:', {
stage: stageInfo.stage,
progress: stageInfo.progress,
@@ -183,17 +183,17 @@ const displayStatusText = computed(() => {
return currentStatusLabel.value
})
const updateProgress = ({ status: newStatus }: { status: ProgressStatus }) => {
function updateProgress({ status: newStatus }: { status: ProgressStatus }) {
status.value = newStatus
// Make critical error screen more obvious.
if (newStatus === ProgressStatus.ERROR) terminalVisible.value = false
}
const terminalCreated = (
function terminalCreated(
{ terminal, useAutoSize }: ReturnType<typeof useTerminal>,
root: Ref<HTMLElement | undefined>
) => {
) {
xterm = terminal
useAutoSize({ root, autoRows: true, autoCols: true })
@@ -206,11 +206,15 @@ const terminalCreated = (
terminal.options.cursorInactiveStyle = 'block'
}
const troubleshoot = () => electron.startTroubleshooting()
const reportIssue = () => {
function troubleshoot() {
return electron.startTroubleshooting()
}
function reportIssue() {
window.open('https://forum.comfy.org/c/v1-feedback/', '_blank')
}
const openLogs = () => electron.openLogsFolder()
function openLogs() {
return electron.openLogsFolder()
}
let cleanupInstallStageListener: (() => void) | undefined

View File

@@ -33,7 +33,7 @@ import { useRouter } from 'vue-router'
import BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'
const router = useRouter()
const navigateTo = async (path: string) => {
async function navigateTo(path: string) {
await router.push(path)
}
</script>

View File

@@ -3,8 +3,9 @@ import { expect, test } from '@playwright/test'
import { demos, getNextDemo } from '../src/config/demos'
import { t } from '../src/i18n/translations'
const escapeRegExp = (value: string): string =>
value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
test.describe('Demo pages @smoke', () => {
for (const demo of demos) {

View File

@@ -111,7 +111,7 @@ async function measureMarqueeLoopGeometry(
`Animation on ${sel} has unusable duration: ${String(duration)}`
)
}
const setAllTimes = (time: number) => {
function setAllTimes(time: number) {
for (const track of tracks) {
for (const anim of track.getAnimations()) {
anim.currentTime = time
@@ -119,7 +119,9 @@ async function measureMarqueeLoopGeometry(
}
void document.body.offsetWidth
}
const readX = () => tracks.map((track) => track.getBoundingClientRect().x)
function readX() {
return tracks.map((track) => track.getBoundingClientRect().x)
}
setAllTimes(0)
const startPositions = readX()
const copyWidths = tracks.map(

View File

@@ -36,7 +36,9 @@ let pendingFrame = 0
const HEADER_OFFSET = -144
const ACTIVATION_OFFSET = 300
const deptElementId = (key: string) => `careers-dept-${key}`
function deptElementId(key: string) {
return `careers-dept-${key}`
}
function pickActiveSection() {
pendingFrame = 0

View File

@@ -58,7 +58,7 @@ onMounted(() => {
})
raw.sort((a, b) => {
const norm = (v: number) => {
function norm(v: number) {
const r = v + Math.PI / 2
return r < 0 ? r + 2 * Math.PI : r
}
@@ -117,7 +117,7 @@ onMounted(() => {
applyToPanel(panels[1], elapsed2)
applyToPanel(panels[2], elapsed3)
const wOf = (elapsed: number) => {
function wOf(elapsed: number) {
const progress = elapsed < PANEL_DURATION ? elapsed / PANEL_DURATION : 1
return lerp(S.w, E.w, ease(progress))
}

View File

@@ -35,7 +35,7 @@ export function useParallax(
const triggerEl = options.trigger?.value
const createAnimations = () => {
function createAnimations() {
const els = elements
.map((r) => r.value)
.filter((el): el is HTMLElement => !!el && el.offsetParent !== null)

View File

@@ -29,7 +29,7 @@ function interpolateY(
contentH: number,
vpH: number
) {
const clampedTarget = (i: number) => {
function clampedTarget(i: number) {
const center = buttonCenters[i] ?? 0
return Math.max(-(contentH - vpH), Math.min(0, vpH / 2 - center))
}

View File

@@ -9,7 +9,7 @@ import { t } from '../../../i18n/translations'
import { loadPacksForBuild } from '../../../utils/cloudNodes.build'
import { escapeJsonLd } from '../../../utils/escapeJsonLd'
export const getStaticPaths: GetStaticPaths = async () => {
export async function getStaticPaths() {
const packs = await loadPacksForBuild()
return packs.map((pack) => ({
params: { pack: pack.id },

View File

@@ -7,7 +7,7 @@ import WhatsNextSection from '../../components/customers/WhatsNextSection.vue'
import { customerStories, getNextStory, getStoryBySlug } from '../../config/customerStories'
import { t } from '../../i18n/translations'
export const getStaticPaths: GetStaticPaths = () => {
export function getStaticPaths() {
return customerStories.map((story) => ({
params: { slug: story.slug }
}))

View File

@@ -8,7 +8,7 @@ import DemoNavSection from '../../components/demos/DemoNavSection.vue'
import { demos, getDemoBySlug, getNextDemo } from '../../config/demos'
import { t } from '../../i18n/translations'
export const getStaticPaths: GetStaticPaths = () => {
export function getStaticPaths() {
return demos.map((demo) => ({
params: { slug: demo.slug }
}))

View File

@@ -5,7 +5,7 @@ import ModelHeroSection from '../../../components/models/ModelHeroSection.vue'
import { models, getModelBySlug } from '../../../config/models'
import { t } from '../../../i18n/translations'
export const getStaticPaths: GetStaticPaths = () => {
export function getStaticPaths() {
return models.map((model) => ({
params: { slug: model.slug }
}))

View File

@@ -9,7 +9,7 @@ import { t } from '../../../../i18n/translations'
import { loadPacksForBuild } from '../../../../utils/cloudNodes.build'
import { escapeJsonLd } from '../../../../utils/escapeJsonLd'
export const getStaticPaths: GetStaticPaths = async () => {
export async function getStaticPaths() {
const packs = await loadPacksForBuild()
return packs.map((pack) => ({
params: { pack: pack.id },

View File

@@ -7,7 +7,7 @@ import WhatsNextSection from '../../../components/customers/WhatsNextSection.vue
import { customerStories, getNextStory, getStoryBySlug } from '../../../config/customerStories'
import { t } from '../../../i18n/translations'
export const getStaticPaths: GetStaticPaths = () => {
export function getStaticPaths() {
return customerStories.map((story) => ({
params: { slug: story.slug }
}))

View File

@@ -8,7 +8,7 @@ import DemoNavSection from '../../../components/demos/DemoNavSection.vue'
import { demos, getDemoBySlug, getNextDemo } from '../../../config/demos'
import { t } from '../../../i18n/translations'
export const getStaticPaths: GetStaticPaths = () => {
export function getStaticPaths() {
return demos.map((demo) => ({
params: { slug: demo.slug }
}))

View File

@@ -35,8 +35,9 @@ const TICK_MS = 200
function readColors() {
const style = getComputedStyle(document.documentElement)
const get = (name: string, fallback: string): string =>
style.getPropertyValue(name).trim() || fallback
function get(name: string, fallback: string): string {
return style.getPropertyValue(name).trim() || fallback
}
return {
bg: get('--color-primary-comfy-ink', '#211927'),
@@ -59,9 +60,12 @@ function requireElement<T extends Element>(
return el
}
const isSVGSVG = (el: Element): el is SVGSVGElement =>
el instanceof SVGSVGElement
const isSVGG = (el: Element): el is SVGGElement => el instanceof SVGGElement
function isSVGSVG(el: Element): el is SVGSVGElement {
return el instanceof SVGSVGElement
}
function isSVGG(el: Element): el is SVGGElement {
return el instanceof SVGGElement
}
function isSVGText(el: Element): el is SVGTextElement {
return el instanceof SVGTextElement
}
@@ -127,8 +131,9 @@ function depth(cell: Cell): number {
function roundedPath(pts: [number, number][], radius: number): string {
const n = pts.length
const fmt = (p: readonly [number, number]) =>
`${p[0].toFixed(2)},${p[1].toFixed(2)}`
function fmt(p: readonly [number, number]) {
return `${p[0].toFixed(2)},${p[1].toFixed(2)}`
}
let d = ''
for (let i = 0; i < n; i++) {
const prev = pts[(i - 1 + n) % n]
@@ -206,7 +211,7 @@ function triggerExplosion() {
const cx = ((COLS - ROWS) * STEP_X) / 2
const cy = ((COLS + ROWS - 2) * STEP_Y) / 2
const launchParticle = (i: number, j: number, fill: string): Particle => {
function launchParticle(i: number, j: number, fill: string): Particle {
const [sx, sy] = iso(i, j)
const baseAngle = Math.atan2(sy - cy, sx - cx)
const angle = baseAngle + (Math.random() - 0.5) * 1.2
@@ -239,7 +244,9 @@ function triggerExplosion() {
const DROP_DURATION_MS = 450
const DROP_HEIGHT = 600
let foodDropStart = 0
const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3)
function easeOutCubic(t: number) {
return 1 - Math.pow(1 - t, 3)
}
function foodDropOffset(now = performance.now()): number {
if (!foodDropStart) return 0
@@ -252,7 +259,7 @@ function foodDropOffset(now = performance.now()): number {
const REBIRTH_STAGGER_MS = 90
const REBIRTH_GROW_MS = 260
let rebirthStart = 0
const easeOutBack = (t: number) => {
function easeOutBack(t: number) {
const c1 = 1.70158
const c3 = c1 + 1
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2)
@@ -271,7 +278,9 @@ function rebirthScaleFor(idx: number, now = performance.now()): number {
const CHOMP_DURATION_MS = 220
const CHOMP_PEAK_SCALE = 1.15
let chompStart = 0
const easeOut = (t: number) => 1 - (1 - t) * (1 - t)
function easeOut(t: number) {
return 1 - (1 - t) * (1 - t)
}
function chompScale(now = performance.now()): number {
if (!chompStart) return 1
@@ -299,7 +308,7 @@ function isAnimating(): boolean {
function ensureAnimationLoop() {
if (animationHandle !== null) return
const tick = () => {
function tick() {
if (
explodeStart &&
performance.now() - explodeStart >= EXPLODE_DURATION_MS
@@ -411,8 +420,12 @@ function updateScoreDisplay() {
scoreBestEl.textContent = String(best)
}
const cellsEqual = (a: Cell, b: Cell) => a.i === b.i && a.j === b.j
const inBounds = (c: Cell) => c.i >= 0 && c.j >= 0 && c.i < COLS && c.j < ROWS
function cellsEqual(a: Cell, b: Cell) {
return a.i === b.i && a.j === b.j
}
function inBounds(c: Cell) {
return c.i >= 0 && c.j >= 0 && c.i < COLS && c.j < ROWS
}
function reset() {
const j0 = Math.floor(ROWS / 2)

View File

@@ -158,7 +158,7 @@ export class AssetHelper {
statusCode: number,
error: string = 'Internal Server Error'
): Promise<void> {
const handler = async (route: Route) => {
async function handler(route: Route) {
return route.fulfill({
status: statusCode,
json: { error }

View File

@@ -325,7 +325,7 @@ export class AssetsHelper {
await this.page.unroute(pattern, existingHandler)
}
const handler = async (route: Route) => {
async function handler(route: Route) {
await route.fulfill({
status: 200,
contentType: 'application/json',

View File

@@ -42,7 +42,7 @@ export async function setupNodeReplacement(
options?: AddEventListenerOptions | boolean
) {
if (type === 'message' && typeof listener === 'function') {
const wrapped = function (this: WebSocket, event: Event) {
function wrapped(this: WebSocket, event: Event) {
const msgEvent = event as MessageEvent
if (typeof msgEvent.data === 'string') {
try {

View File

@@ -606,7 +606,7 @@ export class SubgraphHelper {
]
): { warnings: string[]; dispose: () => void } {
const warnings: string[] = []
const handler = (msg: ConsoleMessage) => {
function handler(msg: ConsoleMessage) {
const text = msg.text()
if (patterns.some((p) => text.includes(p))) {
warnings.push(text)

View File

@@ -150,7 +150,7 @@ export class TemplateHelper {
}
async mockThumbnails(): Promise<void> {
const thumbnailHandler = async (route: Route) => {
async function thumbnailHandler(route: Route) {
await route.fulfill({
status: 200,
path: 'browser_tests/assets/example.webp',

View File

@@ -130,10 +130,12 @@ export const sharedWorkflowImportFixture = base.extend<{
async function mockSharedWorkflowImportFlow(
page: Page
): Promise<SharedWorkflowImportMocks> {
function noopResolveResponse() {}
let isRecording = false
let importEndpointCalled = false
let importBody: ImportPublishedAssetsRequest | undefined
let resolvePublicInclusiveInputAssetResponseAfterImport: () => void = () => {}
let resolvePublicInclusiveInputAssetResponseAfterImport: () => void =
noopResolveResponse
let publicInclusiveInputAssetResponseAfterImport = new Promise<void>(
(resolve) => {
resolvePublicInclusiveInputAssetResponseAfterImport = resolve

View File

@@ -7,7 +7,7 @@ import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
import type { Position, Size } from '@e2e/fixtures/types'
import { VueNodeFixture } from '@e2e/fixtures/utils/vueNodeFixtures'
export const getMiddlePoint = (pos1: Position, pos2: Position) => {
export function getMiddlePoint(pos1: Position, pos2: Position) {
return {
x: (pos1.x + pos2.x) / 2,
y: (pos1.y + pos2.y) / 2

View File

@@ -45,7 +45,7 @@ test.describe('Actionbar', { tag: '@ui' }, () => {
const requestPromise = comfyPage.page.waitForResponse('**/api/prompt')
// Find and set the width on the latent node
const triggerChange = async (value: number) => {
async function triggerChange(value: number) {
return await comfyPage.page.evaluate((value) => {
const node = window.app!.graph!._nodes.find(
(n) => n.type === 'EmptyLatentImage'
@@ -59,7 +59,7 @@ test.describe('Actionbar', { tag: '@ui' }, () => {
}
// Trigger a status websocket message
const triggerStatus = (queueSize: number) => {
function triggerStatus(queueSize: number) {
ws.send(
JSON.stringify({
type: 'status',
@@ -75,7 +75,7 @@ test.describe('Actionbar', { tag: '@ui' }, () => {
}
// Extract the width from the queue response
const getQueuedWidth = async (resp: Promise<Response>) => {
async function getQueuedWidth(resp: Promise<Response>) {
const obj = await (await resp).json()
return obj['__request']['prompt']['5']['inputs']['width']
}

View File

@@ -5,16 +5,18 @@ import {
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
import type { Size } from '@e2e/fixtures/types'
const expectedGroupSize = (
function expectedGroupSize(
nodeBounds: Size,
padding: number,
titleHeight: number
): Size => ({
width: nodeBounds.width + padding * 2,
// Group height adds one title row above the contained node bounds (which
// themselves already include the node's own title), independent of padding.
height: nodeBounds.height + padding * 2 + titleHeight
})
): Size {
return {
width: nodeBounds.width + padding * 2,
// Group height adds one title row above the contained node bounds (which
// themselves already include the node's own title), independent of padding.
height: nodeBounds.height + padding * 2 + titleHeight
}
}
test.describe('Canvas layout settings', { tag: '@canvas' }, () => {
test.describe('Comfy.SnapToGrid.GridSize', () => {
@@ -24,7 +26,7 @@ test.describe('Canvas layout settings', { tag: '@canvas' }, () => {
await comfyPage.nodeOps.clearGraph()
})
const createNode = async (comfyPage: ComfyPage) => {
async function createNode(comfyPage: ComfyPage) {
const note = await comfyPage.nodeOps.addNode('Note', undefined, {
x: 0,
y: 0
@@ -79,10 +81,10 @@ test.describe('Canvas layout settings', { tag: '@canvas' }, () => {
await comfyPage.workflow.loadWorkflow('nodes/single_ksampler')
})
const groupAroundAllNodesWithPadding = async (
async function groupAroundAllNodesWithPadding(
comfyPage: ComfyPage,
padding: number
): Promise<Size> => {
): Promise<Size> {
await comfyPage.settings.setSetting(
'Comfy.GroupSelectedNodes.Padding',
padding
@@ -126,15 +128,16 @@ test.describe('Canvas layout settings', { tag: '@canvas' }, () => {
test.describe('LiteGraph.ContextMenu.Scaling', () => {
const ZOOM_SCALE = 2
const litegraphContextMenu = (comfyPage: ComfyPage) =>
comfyPage.page.locator('.litecontextmenu')
function litegraphContextMenu(comfyPage: ComfyPage) {
return comfyPage.page.locator('.litecontextmenu')
}
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.workflow.loadWorkflow('widgets/load_image_widget')
await comfyPage.canvasOps.setScale(ZOOM_SCALE)
})
const openComboMenu = async (comfyPage: ComfyPage) => {
async function openComboMenu(comfyPage: ComfyPage) {
const loadImage = (
await comfyPage.nodeOps.getNodeRefsByType('LoadImage')
)[0]

View File

@@ -3,12 +3,14 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
const getLocators = (page: Page) => ({
trigger: page.getByRole('button', { name: 'Canvas Mode' }),
menu: page.getByRole('menu', { name: 'Canvas Mode' }),
selectItem: page.getByRole('menuitemradio', { name: 'Select' }),
handItem: page.getByRole('menuitemradio', { name: 'Hand' })
})
function getLocators(page: Page) {
return {
trigger: page.getByRole('button', { name: 'Canvas Mode' }),
menu: page.getByRole('menu', { name: 'Canvas Mode' }),
selectItem: page.getByRole('menuitemradio', { name: 'Select' }),
handItem: page.getByRole('menuitemradio', { name: 'Hand' })
}
}
const MODES = [
{

View File

@@ -7,7 +7,7 @@ import { sleep } from '@e2e/fixtures/utils/timing'
const CLIP_NODE_COUNT = 2
const getClipNodesDragBox = async (comfyPage: ComfyPage) => {
async function getClipNodesDragBox(comfyPage: ComfyPage) {
const clipNodes = await comfyPage.nodeOps.getNodeRefsByType('CLIPTextEncode')
expect(
clipNodes,
@@ -242,11 +242,11 @@ test.describe('Canvas settings', { tag: '@canvas' }, () => {
* hold), nudge by `(dx, dy)` absolute pixels, then release. Spec-local
* because it exists only to probe the CanvasPointer timing thresholds.
*/
const holdDragAt = async (
async function holdDragAt(
comfyPage: ComfyPage,
pos: { x: number; y: number },
opts: { dx: number; dy: number; holdMs: number }
) => {
) {
const abs = await comfyPage.canvasOps.toAbsolute(pos)
await comfyPage.page.mouse.move(abs.x, abs.y)
await comfyPage.page.mouse.down()
@@ -383,8 +383,9 @@ test.describe('Canvas settings', { tag: '@canvas' }, () => {
// (CI jitter, background throttling, canvas-idle behaviour). Assert the
// render-loop throttle value instead — that is what actually governs
// frame cadence.
const getFrameGap = (comfyPage: ComfyPage) =>
comfyPage.page.evaluate(() => window.app!.canvas.maximumFps * 1000)
function getFrameGap(comfyPage: ComfyPage) {
return comfyPage.page.evaluate(() => window.app!.canvas.maximumFps * 1000)
}
test('caps the render loop frame gap', async ({ comfyPage }) => {
await comfyPage.settings.setSetting('LiteGraph.Canvas.MaximumFps', 30)

View File

@@ -219,7 +219,7 @@ test.describe('Change Tracker', { tag: '@workflow' }, () => {
comfyPage
}) => {
const node = (await comfyPage.nodeOps.getFirstNodeRef())!
const bypassAndPin = async () => {
async function bypassAndPin() {
await beforeChange(comfyPage)
await comfyPage.keyboard.bypass()
await expect(node).toBeBypassed()
@@ -228,14 +228,14 @@ test.describe('Change Tracker', { tag: '@workflow' }, () => {
await afterChange(comfyPage)
}
const collapse = async () => {
async function collapse() {
await beforeChange(comfyPage)
await node.click('collapse', { moveMouseToEmptyArea: true })
await expect(node).toBeCollapsed()
await afterChange(comfyPage)
}
const multipleChanges = async () => {
async function multipleChanges() {
await beforeChange(comfyPage)
// Call other actions that uses begin/endChange
await node.click('title')

View File

@@ -133,10 +133,11 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
await node.click('title')
// Normal mode is ALWAYS (0)
const getMode = () =>
comfyPage.page.evaluate((nodeId) => {
function getMode() {
return comfyPage.page.evaluate((nodeId) => {
return window.app!.canvas.graph!.getNodeById(nodeId)!.mode
}, node.id)
}
await expect.poll(() => getMode()).toBe(0)

View File

@@ -290,7 +290,7 @@ test('Blueprint overwrite', { tag: ['@subgraph'] }, async ({ comfyPage }) => {
const confirmDialog = comfyPage.confirmDialog.root
const { incrementButton } = comfyPage.vueNodes.getInputNumberControls(steps)
const dirtyGraphAndSave = async () => {
async function dirtyGraphAndSave() {
await incrementButton.click()
await comfyPage.page.keyboard.press('Control+s')
}

View File

@@ -21,13 +21,14 @@ test.describe('Group Copy Paste', { tag: ['@canvas'] }, () => {
await comfyPage.clipboard.paste()
await comfyPage.nextFrame()
const getGroupPositions = () =>
comfyPage.page.evaluate(() =>
function getGroupPositions() {
return comfyPage.page.evaluate(() =>
window.app!.graph.groups.map((g: { pos: number[] }) => ({
x: g.pos[0],
y: g.pos[1]
}))
)
}
await expect.poll(getGroupPositions).toHaveLength(2)

View File

@@ -137,7 +137,7 @@ test.describe('Group Node', { tag: '@node' }, () => {
test('Manage group opens with the correct group selected', async ({
comfyPage
}) => {
const makeGroup = async (name: string, type1: string, type2: string) => {
async function makeGroup(name: string, type1: string, type2: string) {
const node1 = (await comfyPage.nodeOps.getNodeRefsByType(type1))[0]
const node2 = (await comfyPage.nodeOps.getNodeRefsByType(type2))[0]
await node1.click('title')
@@ -204,7 +204,7 @@ test.describe('Group Node', { tag: '@node' }, () => {
test('Reconnects inputs after configuration changed via manage dialog save', async ({
comfyPage
}) => {
const expectSingleNode = async (type: string) => {
async function expectSingleNode(type: string) {
const nodes = await comfyPage.nodeOps.getNodeRefsByType(type)
expect(nodes).toHaveLength(1)
return nodes[0]
@@ -255,13 +255,13 @@ test.describe('Group Node', { tag: '@node' }, () => {
const GROUP_NODE_NAME = 'group_node' // Node name in given workflow
const GROUP_NODE_TYPE = `${GROUP_NODE_PREFIX}${GROUP_NODE_NAME}`
const isRegisteredLitegraph = async (comfyPage: ComfyPage) => {
async function isRegisteredLitegraph(comfyPage: ComfyPage) {
return await comfyPage.page.evaluate((nodeType: string) => {
return !!window.LiteGraph!.registered_node_types[nodeType]
}, GROUP_NODE_TYPE)
}
const isRegisteredNodeDefStore = async (comfyPage: ComfyPage) => {
async function isRegisteredNodeDefStore(comfyPage: ComfyPage) {
await comfyPage.menu.nodeLibraryTab.open()
const groupNodesFolderCt = await comfyPage.menu.nodeLibraryTab
.getFolder(GROUP_NODE_CATEGORY)
@@ -269,10 +269,10 @@ test.describe('Group Node', { tag: '@node' }, () => {
return groupNodesFolderCt === 1
}
const verifyNodeLoaded = async (
async function verifyNodeLoaded(
comfyPage: ComfyPage,
expectedCount: number
) => {
) {
expect(
await comfyPage.nodeOps.getNodeRefsByType(GROUP_NODE_TYPE)
).toHaveLength(expectedCount)

View File

@@ -510,7 +510,7 @@ test.describe('Image Compare', { tag: ['@widget', '@vue-nodes'] }, () => {
const brokenAfter = 'http://127.0.0.1:1/broken2.png'
const pageErrors: Error[] = []
const onPageError = (err: Error) => {
function onPageError(err: Error) {
pageErrors.push(err)
}
comfyPage.page.on('pageerror', onPageError)

View File

@@ -82,10 +82,10 @@ test.describe('Node Interaction', () => {
}
)
const dragSelectNodes = async (
async function dragSelectNodes(
comfyPage: ComfyPage,
clipNodes: NodeReference[]
) => {
) {
const clipNode1Pos = await clipNodes[0].getPosition()
const clipNode2Pos = await clipNodes[1].getPosition()
const offset = 64
@@ -117,15 +117,16 @@ test.describe('Node Interaction', () => {
}) => {
const clipNodes =
await comfyPage.nodeOps.getNodeRefsByType('CLIPTextEncode')
const getPositions = () =>
Promise.all(clipNodes.map((node) => node.getPosition()))
const testDirection = async ({
function getPositions() {
return Promise.all(clipNodes.map((node) => node.getPosition()))
}
async function testDirection({
direction,
expectedPosition
}: {
direction: string
expectedPosition: (originalPosition: Position) => Position
}) => {
}) {
const originalPositions = await getPositions()
await dragSelectNodes(comfyPage, clipNodes)
await comfyPage.command.executeCommand(
@@ -671,7 +672,7 @@ test.describe('Canvas Interaction', { tag: '@screenshot' }, () => {
})
test('Cursor style changes when panning', async ({ comfyPage }) => {
const getCursorStyle = async () => {
async function getCursorStyle() {
return await comfyPage.page.evaluate(() => {
return (
document.getElementById('graph-canvas')!.style.cursor || 'default'
@@ -703,7 +704,7 @@ test.describe('Canvas Interaction', { tag: '@screenshot' }, () => {
test('Properly resets dragging state after pan mode sequence', async ({
comfyPage
}) => {
const getCursorStyle = async () => {
async function getCursorStyle() {
return await comfyPage.page.evaluate(() => {
return (
document.getElementById('graph-canvas')!.style.cursor || 'default'
@@ -878,8 +879,9 @@ test.describe('Load workflow', { tag: '@screenshot' }, () => {
)
})
const generateUniqueFilename = (extension = '') =>
`${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}${extension}`
function generateUniqueFilename(extension = '') {
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}${extension}`
}
test.describe('Restore all open workflows on reload', () => {
let workflowA: string
@@ -1077,7 +1079,7 @@ test.describe('Viewport settings', () => {
comfyPage,
comfyMouse
}) => {
const changeTab = async (tab: Locator) => {
async function changeTab(tab: Locator) {
await tab.click()
await comfyPage.nextFrame()
await comfyMouse.move(DefaultGraphPositions.emptySpace)
@@ -1406,7 +1408,7 @@ test.describe('Canvas Navigation', { tag: '@screenshot' }, () => {
test('Cursor changes appropriately in different modes', async ({
comfyPage
}) => {
const getCursorStyle = async () => {
async function getCursorStyle() {
return await comfyPage.page.evaluate(() => {
return (
document.getElementById('graph-canvas')!.style.cursor || 'default'

View File

@@ -3,14 +3,15 @@ import type { Page } from '@playwright/test'
import { load3dTest as test } from '@e2e/fixtures/helpers/Load3DFixtures'
const getGizmoConfig = (page: Page) =>
page.evaluate(() => {
function getGizmoConfig(page: Page) {
return page.evaluate(() => {
const n = window.app!.graph.getNodeById(1)
const modelConfig = n?.properties?.['Model Config'] as
| { gizmo?: { enabled: boolean; mode: string } }
| undefined
return modelConfig?.gizmo
})
}
test.describe('Load3D Gizmo Controls', () => {
test(

View File

@@ -155,7 +155,7 @@ test.describe('Load3D', () => {
async ({ comfyPage, load3d }) => {
await expect(load3d.uploadBackgroundImageButton).toBeVisible()
const node = await comfyPage.nodeOps.getNodeRefById(1)
const readBackgroundImage = async () => {
async function readBackgroundImage() {
const properties =
await node.getProperty<Record<string, { backgroundImage?: string }>>(
'properties'
@@ -222,7 +222,7 @@ test.describe('Load3D', () => {
await expect(load3d.gridToggleButton).toBeVisible()
const node = await comfyPage.nodeOps.getNodeRefById(1)
const readShowGrid = async () => {
async function readShowGrid() {
const properties =
await node.getProperty<Record<string, { showGrid?: boolean }>>(
'properties'

View File

@@ -55,7 +55,7 @@ async function setLocaleAndWaitForWorkflowReload(
const waitForReload = new Promise<void>((resolve, reject) => {
const timeoutAt = performance.now() + 5000
const tick = () => {
function tick() {
if (changeTracker.isLoadingGraph) {
sawLoading = true
}

View File

@@ -166,10 +166,10 @@ test.describe('Node search box', { tag: '@node' }, () => {
})
test.describe('Filtering', () => {
const expectFilterChips = async (
async function expectFilterChips(
comfyPage: ComfyPage,
expectedTexts: string[]
) => {
) {
const chips = comfyPage.searchBox.filterChips
// Check that the number of chips matches the expected count

View File

@@ -243,15 +243,18 @@ test.describe('Node search box V2', { tag: '@node' }, () => {
comfyPage
}) => {
const { searchBoxV2 } = comfyPage
const switchToDesktop = () =>
comfyPage.page.setViewportSize({ width: 1280, height: 800 })
const switchToMobile = () =>
comfyPage.page.setViewportSize({ width: 360, height: 800 })
const expectExpanded = (value: 'true' | 'false') =>
expect(searchBoxV2.sidebarToggle).toHaveAttribute(
function switchToDesktop() {
return comfyPage.page.setViewportSize({ width: 1280, height: 800 })
}
function switchToMobile() {
return comfyPage.page.setViewportSize({ width: 360, height: 800 })
}
function expectExpanded(value: 'true' | 'false') {
return expect(searchBoxV2.sidebarToggle).toHaveAttribute(
'aria-expanded',
value
)
}
await switchToDesktop()
await searchBoxV2.open()

View File

@@ -312,7 +312,9 @@ test.describe('Node search box V2 extended', { tag: '@node' }, () => {
test.describe('Search behavior', () => {
test('Search narrows results progressively', async ({ comfyPage }) => {
const { searchBoxV2 } = comfyPage
const getCount = () => searchBoxV2.results.count()
function getCount() {
return searchBoxV2.results.count()
}
await searchBoxV2.open()

View File

@@ -758,8 +758,8 @@ test.describe('Painter', { tag: ['@widget', '@vue-nodes'] }, () => {
await drawStroke(comfyPage.page, canvas, { yPct: 0.75 })
await comfyPage.nextFrame()
const hasContentAtRow = (yFraction: number) =>
canvas.evaluate((el: HTMLCanvasElement, y: number) => {
function hasContentAtRow(yFraction: number) {
return canvas.evaluate((el: HTMLCanvasElement, y: number) => {
const ctx = el.getContext('2d')
if (!ctx) return false
const cy = Math.floor(el.height * y)
@@ -769,6 +769,7 @@ test.describe('Painter', { tag: ['@widget', '@vue-nodes'] }, () => {
}
return false
}, yFraction)
}
await expect
.poll(() => hasContentAtRow(0.25), {

View File

@@ -77,7 +77,7 @@ const cloudUploadRaceTest = comfyPageFixture.extend<{
}
cloudUploadAssetStateByPage.set(page, state)
const assetsRouteHandler = async (route: Route) => {
async function assetsRouteHandler(route: Route) {
const allAssets = [
cloudDefaultGraphInputAsset,
...(state.isUploadedAssetAvailable ? [cloudUploadedVideoAsset] : [])
@@ -149,7 +149,7 @@ async function delayNextUpload(comfyPage: ComfyPage) {
releaseUpload = resolve
})
const uploadRouteHandler = async (route: Route) => {
async function uploadRouteHandler(route: Route) {
resolveUploadStarted()
await release
await route.continue()

View File

@@ -15,7 +15,9 @@ const REQUEST_ID_SECONDARY = 2
const REQUEST_ID_MISMATCH = 999
let nextRequestId = 1000
const newRequestId = () => nextRequestId++
function newRequestId() {
return nextRequestId++
}
function bannerLocator(page: Page) {
return page.getByTestId(TestIds.queue.notificationBanner)

View File

@@ -6,11 +6,11 @@ import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
test.describe('Remote COMBO Widget', { tag: '@widget' }, () => {
const mockOptions = ['d', 'c', 'b', 'a']
const addRemoteWidgetNode = async (
async function addRemoteWidgetNode(
comfyPage: ComfyPage,
nodeName: string,
count: number = 1
) => {
) {
const tab = comfyPage.menu.nodeLibraryTab
await tab.open()
await tab.getFolder('DevTools').click()
@@ -21,24 +21,24 @@ test.describe('Remote COMBO Widget', { tag: '@widget' }, () => {
}
}
const getWidgetOptions = async (
async function getWidgetOptions(
comfyPage: ComfyPage,
nodeName: string
): Promise<string[] | undefined> => {
): Promise<string[] | undefined> {
return await comfyPage.page.evaluate((name) => {
const node = window.app!.graph!.nodes.find((node) => node.title === name)
return node!.widgets![0].options.values as string[] | undefined
}, nodeName)
}
const getWidgetValue = async (comfyPage: ComfyPage, nodeName: string) => {
async function getWidgetValue(comfyPage: ComfyPage, nodeName: string) {
return await comfyPage.page.evaluate((name) => {
const node = window.app!.graph!.nodes.find((node) => node.title === name)
return node!.widgets![0].value
}, nodeName)
}
const clickRefreshButton = (comfyPage: ComfyPage, nodeName: string) => {
function clickRefreshButton(comfyPage: ComfyPage, nodeName: string) {
return comfyPage.page.evaluate((name) => {
const node = window.app!.graph!.nodes.find((node) => node.title === name)
const buttonWidget = node!.widgets!.find((w) => w.name === 'refresh')

View File

@@ -13,16 +13,21 @@ test.beforeEach(async ({ comfyPage }) => {
const BLUE_COLOR = 'rgb(51, 51, 85)'
const RED_COLOR = 'rgb(85, 51, 51)'
const getColorPickerButton = (comfyPage: { page: Page }) =>
comfyPage.page.getByTestId(TestIds.selectionToolbox.colorPickerButton)
function getColorPickerButton(comfyPage: { page: Page }) {
return comfyPage.page.getByTestId(TestIds.selectionToolbox.colorPickerButton)
}
const getColorPickerCurrentColor = (comfyPage: { page: Page }) =>
comfyPage.page.getByTestId(TestIds.selectionToolbox.colorPickerCurrentColor)
function getColorPickerCurrentColor(comfyPage: { page: Page }) {
return comfyPage.page.getByTestId(
TestIds.selectionToolbox.colorPickerCurrentColor
)
}
const getColorPickerGroup = (comfyPage: { page: Page }) =>
comfyPage.page.getByRole('group').filter({
function getColorPickerGroup(comfyPage: { page: Page }) {
return comfyPage.page.getByRole('group').filter({
has: comfyPage.page.getByTestId(TestIds.selectionToolbox.colorBlue)
})
}
test.describe('Selection Toolbox', { tag: ['@screenshot', '@ui'] }, () => {
test.beforeEach(async ({ comfyPage }) => {

View File

@@ -19,8 +19,9 @@ test.describe(
await comfyPage.nextFrame()
})
const openMoreOptions = (comfyPage: ComfyPage) =>
openMoreOptionsMenu(comfyPage, 'KSampler')
function openMoreOptions(comfyPage: ComfyPage) {
return openMoreOptionsMenu(comfyPage, 'KSampler')
}
test('hides Node Info from More Options menu when the new menu is disabled', async ({
comfyPage

View File

@@ -114,11 +114,12 @@ test.describe('Sidebar splitter width independence', () => {
await dragGutter(comfyPage, 80)
// Check that saved sizes sum to ~100%
const getSidebarSizes = () =>
comfyPage.page.evaluate(() => {
function getSidebarSizes() {
return comfyPage.page.evaluate(() => {
const raw = localStorage.getItem('unified-sidebar')
return raw ? (JSON.parse(raw) as number[]) : null
})
}
await expect
.poll(async () => {

View File

@@ -25,7 +25,7 @@ const MISSING_NODES_SUBGRAPH_NODE_ID = '2'
* the root graph, then the inner subgraph node that appears inside. Matches
* how a user navigates via the canvas.
*/
const enterNestedSubgraphs = async (comfyPage: ComfyPage) => {
async function enterNestedSubgraphs(comfyPage: ComfyPage) {
const outerNode = await comfyPage.nodeOps.getNodeRefById(
OUTER_SUBGRAPH_NODE_ID_IN_NESTED
)

View File

@@ -689,7 +689,9 @@ test('Can intermix linked and proxy @vue-nodes', async ({ comfyPage }) => {
const fromSlot = ksampler.getSlot('steps')
const toPos = await comfyPage.subgraph.getInputSlot().getOpenSlotPosition()
await fromSlot.dragTo(comfyPage.canvas, { targetPosition: toPos })
const isConnected = () => comfyPage.vueNodes.isSlotConnected(fromSlot)
function isConnected() {
return comfyPage.vueNodes.isSlotConnected(fromSlot)
}
await expect.poll(isConnected).toBe(true)
await comfyPage.subgraph.exitViaBreadcrumb()
@@ -735,7 +737,9 @@ test('Link already promoted widget @vue-nodes', async ({ comfyPage }) => {
const fromSlot = ksampler.getSlot('steps')
const toPos = await comfyPage.subgraph.getInputSlot().getOpenSlotPosition()
await fromSlot.dragTo(comfyPage.canvas, { targetPosition: toPos })
const isConnected = () => comfyPage.vueNodes.isSlotConnected(fromSlot)
function isConnected() {
return comfyPage.vueNodes.isSlotConnected(fromSlot)
}
await expect.poll(isConnected).toBe(true)
await comfyPage.subgraph.exitViaBreadcrumb()
@@ -812,7 +816,9 @@ test('Linked widgets can not be demoted @vue-nodes', async ({ comfyPage }) => {
const fromSlot = ksampler.getSlot('steps')
const toPos = await comfyPage.subgraph.getInputSlot().getOpenSlotPosition()
await fromSlot.dragTo(comfyPage.canvas, { targetPosition: toPos })
const isConnected = () => comfyPage.vueNodes.isSlotConnected(fromSlot)
function isConnected() {
return comfyPage.vueNodes.isSlotConnected(fromSlot)
}
await expect.poll(isConnected).toBe(true)
await comfyPage.subgraph.exitViaBreadcrumb()

View File

@@ -368,15 +368,16 @@ test.describe('Subgraph Serialization', { tag: ['@subgraph'] }, () => {
]
const SENTINEL_IDS = new Set([-1, -10, -20])
const isSentinelNodeId = (id: number | string): id is number =>
typeof id === 'number' && SENTINEL_IDS.has(id)
function isSentinelNodeId(id: number | string): id is number {
return typeof id === 'number' && SENTINEL_IDS.has(id)
}
const checkEndpoint = (
function checkEndpoint(
label: string,
kind: 'origin_id' | 'target_id',
id: number | string,
g: typeof graph
): string | null => {
): string | null {
if (isSentinelNodeId(id)) return null
if (typeof id !== 'number' || !g._nodes_by_id[id]) {
return `${label}: ${kind} ${id} invalid or not found`

View File

@@ -14,7 +14,7 @@ test.describe(
)
await comfyPage.vueNodes.waitForNodes()
const assertInSubgraph = async (inSubgraph: boolean) => {
async function assertInSubgraph(inSubgraph: boolean) {
await expect
.poll(() => comfyPage.subgraph.isInSubgraph())
.toBe(inSubgraph)

View File

@@ -7,9 +7,9 @@ test.describe('Version Mismatch Warnings', { tag: '@slow' }, () => {
const ALWAYS_AHEAD_OF_INSTALLED_VERSION = '100.100.100'
const ALWAYS_BEHIND_INSTALLED_VERSION = '0.0.0'
const createMockSystemStatsRes = (
function createMockSystemStatsRes(
requiredFrontendVersion: string
): SystemStats => {
): SystemStats {
return {
system: {
os: 'posix',

View File

@@ -79,7 +79,7 @@ async function getNodeGroupCenteringErrors(
const nodeRect = nodeElement.getBoundingClientRect()
const getCenteringError = (group: GraphGroup): NodeGroupCenteringError => {
function getCenteringError(group: GraphGroup): NodeGroupCenteringError {
const [groupStartX, groupStartY] = app.canvasPosToClientPos([
group.pos[0],
group.pos[1]

View File

@@ -1151,10 +1151,11 @@ test.describe('Vue Node Widget Link Position', { tag: '@vue-nodes' }, () => {
const ksampler = await comfyPage.page.evaluate(() => {
const node = window.app!.graph.nodes.find((n) => n.type === 'KSampler')
if (!node) return null
const findIndex = (name: string) =>
node.inputs.findIndex(
function findIndex(name: string) {
return node.inputs.findIndex(
(input) => input.name === name || input.widget?.name === name
)
}
return {
id: node.id,
denoiseIndex: findIndex('denoise'),

View File

@@ -8,10 +8,10 @@ import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
import type { Position } from '@e2e/fixtures/types'
test.describe('Vue Node Moving', { tag: '@vue-nodes' }, () => {
const getHeaderPos = async (
async function getHeaderPos(
comfyPage: ComfyPage,
title: string
): Promise<{ x: number; y: number; width: number; height: number }> => {
): Promise<{ x: number; y: number; width: number; height: number }> {
const box = await comfyPage.vueNodes
.getNodeByTitle(title)
.getByTestId('node-title')
@@ -21,27 +21,30 @@ test.describe('Vue Node Moving', { tag: '@vue-nodes' }, () => {
return box
}
const getLoadCheckpointHeaderPos = async (comfyPage: ComfyPage) =>
getHeaderPos(comfyPage, 'Load Checkpoint')
async function getLoadCheckpointHeaderPos(comfyPage: ComfyPage) {
return getHeaderPos(comfyPage, 'Load Checkpoint')
}
const expectPosChanged = async (pos1: Position, pos2: Position) => {
async function expectPosChanged(pos1: Position, pos2: Position) {
const diffX = Math.abs(pos2.x - pos1.x)
const diffY = Math.abs(pos2.y - pos1.y)
expect(diffX).toBeGreaterThan(0)
expect(diffY).toBeGreaterThan(0)
}
const deltaBetween = (before: Position, after: Position) => ({
x: after.x - before.x,
y: after.y - before.y
})
function deltaBetween(before: Position, after: Position) {
return {
x: after.x - before.x,
y: after.y - before.y
}
}
const expectSameDelta = (a: Position, b: Position, tol = 2) => {
function expectSameDelta(a: Position, b: Position, tol = 2) {
expect(Math.abs(a.x - b.x)).toBeLessThanOrEqual(tol)
expect(Math.abs(a.y - b.y)).toBeLessThanOrEqual(tol)
}
const dragFromTabButton = async (comfyPage: ComfyPage, button: Locator) => {
async function dragFromTabButton(comfyPage: ComfyPage, button: Locator) {
const box = await button.boundingBox()
if (!box) throw new Error('Tab button has no bounding box')
const start = {
@@ -172,7 +175,7 @@ test.describe('Vue Node Moving', { tag: '@vue-nodes' }, () => {
const dx = 120
const dy = 80
const clickNodeTitleWithMeta = async (title: string) => {
async function clickNodeTitleWithMeta(title: string) {
await comfyPage.vueNodes
.getNodeByTitle(title)
.getByTestId('node-title')

View File

@@ -15,10 +15,11 @@ test('@vue-nodes In App Mode, widget width updates with panel size', async ({
await comfyPage.appMode.enterAppModeWithInputs([['10', 'legacy_widget']])
})
const getWidth = () =>
comfyPage.page.evaluate(
function getWidth() {
return comfyPage.page.evaluate(
() => graph!.getNodeById(10)!.widgets![0].width ?? 0
)
}
await test.step('Mouse clicks resolve to button regions', async () => {
const legacyWidget = comfyPage.appMode.linearWidgets.locator('canvas')

View File

@@ -5,11 +5,15 @@ import {
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
test.describe('Vue Multiline String Widget', { tag: '@vue-nodes' }, () => {
const getFirstClipNode = (comfyPage: ComfyPage) =>
comfyPage.vueNodes.getNodeByTitle('CLIP Text Encode (Prompt)').first()
function getFirstClipNode(comfyPage: ComfyPage) {
return comfyPage.vueNodes
.getNodeByTitle('CLIP Text Encode (Prompt)')
.first()
}
const getFirstMultilineStringWidget = (comfyPage: ComfyPage) =>
getFirstClipNode(comfyPage).getByRole('textbox', { name: 'text' })
function getFirstMultilineStringWidget(comfyPage: ComfyPage) {
return getFirstClipNode(comfyPage).getByRole('textbox', { name: 'text' })
}
test('should allow entering text', async ({ comfyPage }) => {
const textarea = getFirstMultilineStringWidget(comfyPage)

View File

@@ -56,8 +56,8 @@ test.describe('Combo text widget', { tag: ['@screenshot', '@widget'] }, () => {
test('should refresh combo values of optional inputs', async ({
comfyPage
}) => {
const getComboValues = async () =>
comfyPage.page.evaluate(() => {
async function getComboValues() {
return comfyPage.page.evaluate(() => {
return window
.app!.graph!.nodes.find(
(node) => node.title === 'Node With Optional Combo Input'
@@ -65,6 +65,7 @@ test.describe('Combo text widget', { tag: ['@screenshot', '@widget'] }, () => {
.widgets!.find((widget) => widget.name === 'optional_combo_input')!
.options.values
})
}
await comfyPage.workflow.loadWorkflow('inputs/optional_combo_input')
const initialComboValues = await getComboValues()
@@ -82,8 +83,8 @@ test.describe('Combo text widget', { tag: ['@screenshot', '@widget'] }, () => {
test('Should refresh combo values of nodes with v2 combo input spec', async ({
comfyPage
}) => {
const getComboValues = async () =>
comfyPage.page.evaluate(() => {
async function getComboValues() {
return comfyPage.page.evaluate(() => {
return window
.app!.graph!.nodes.find(
(node) => node.title === 'Node With V2 Combo Input'
@@ -91,6 +92,7 @@ test.describe('Combo text widget', { tag: ['@screenshot', '@widget'] }, () => {
.widgets!.find((widget) => widget.name === 'combo_input')!.options
.values
})
}
await comfyPage.workflow.loadWorkflow('inputs/node_with_v2_combo_input')
// click canvas to focus

View File

@@ -213,10 +213,11 @@ test.describe('Workflow Persistence', () => {
.poll(() => comfyPage.nodeOps.getNodeCount())
.toBeGreaterThanOrEqual(2)
const getNodeTypes = () =>
comfyPage.page.evaluate(() =>
function getNodeTypes() {
return comfyPage.page.evaluate(() =>
window.app!.graph.nodes.map((n: { type: string }) => n.type)
)
}
await expect.poll(getNodeTypes).toContain('KSampler')
await expect.poll(getNodeTypes).toContain('EmptyLatentImage')
await expect
@@ -552,11 +553,12 @@ test.describe('Workflow Persistence', () => {
await comfyPage.setup({ clearStorage: false })
await comfyPage.nextFrame()
const getSplitterSizes = () =>
comfyPage.page.evaluate(() => {
function getSplitterSizes() {
return comfyPage.page.evaluate(() => {
const raw = localStorage.getItem('Comfy.Splitter.MainSplitter')
return raw ? (JSON.parse(raw) as number[]) : null
})
}
await expect
.poll(async () => {

View File

@@ -3,9 +3,11 @@ import path from 'path'
type PathParts = readonly [string, ...string[]]
const getBackupPath = (originalPath: string): string => `${originalPath}.bak`
function getBackupPath(originalPath: string): string {
return `${originalPath}.bak`
}
const resolvePathIfExists = (pathParts: PathParts): string | null => {
function resolvePathIfExists(pathParts: PathParts): string | null {
const resolvedPath = path.resolve(...pathParts)
if (!fs.pathExistsSync(resolvedPath)) {
console.warn(`Path not found: ${resolvedPath}`)
@@ -14,7 +16,7 @@ const resolvePathIfExists = (pathParts: PathParts): string | null => {
return resolvedPath
}
const createScaffoldingCopy = (srcDir: string, destDir: string) => {
function createScaffoldingCopy(srcDir: string, destDir: string) {
// Get all items (files and directories) in the source directory
const items = fs.readdirSync(srcDir, { withFileTypes: true })

View File

@@ -195,7 +195,7 @@ export function processDynamicPrompt(input: string): string {
let result = ''
input = stripComments(input)
const handleEscape = () => {
function handleEscape() {
const nextChar = input[i++]
return '\\' + nextChar
}
@@ -347,7 +347,7 @@ export function formatDate(text: string, date: Date) {
* Generate a cache key from parameters
* Sorts the parameters to ensure consistent keys regardless of parameter order
*/
export const paramsToCacheKey = (params: unknown): string => {
export function paramsToCacheKey(params: unknown): string {
if (typeof params === 'string') return params
if (typeof params === 'object' && params !== null)
return Object.keys(params)
@@ -362,7 +362,7 @@ export const paramsToCacheKey = (params: unknown): string => {
* Generates a RFC4122 compliant UUID v4 using the native crypto API when available
* @returns A properly formatted UUID string
*/
export const generateUUID = (): string => {
export function generateUUID(): string {
// Use native crypto.randomUUID() if available (modern browsers)
if (
typeof crypto !== 'undefined' &&
@@ -379,18 +379,21 @@ export const generateUUID = (): string => {
})
}
const isCivitaiHost = (hostname: string): boolean =>
hostname === 'civitai.com' ||
hostname.endsWith('.civitai.com') ||
hostname === 'civitai.red' ||
hostname.endsWith('.civitai.red')
function isCivitaiHost(hostname: string): boolean {
return (
hostname === 'civitai.com' ||
hostname.endsWith('.civitai.com') ||
hostname === 'civitai.red' ||
hostname.endsWith('.civitai.red')
)
}
/**
* Checks if a URL belongs to any Civitai domain (civitai.com or civitai.red).
* Use this for source-name detection; use `isCivitaiModelUrl` when the URL
* must also match a specific model API path format.
*/
export const isCivitaiUrl = (url: string): boolean => {
export function isCivitaiUrl(url: string): boolean {
if (!isValidUrl(url)) return false
return isCivitaiHost(new URL(url).hostname.toLowerCase())
}
@@ -403,7 +406,7 @@ export const isCivitaiUrl = (url: string): boolean => {
* isCivitaiModelUrl('https://civitai.com/api/v1/models-versions/15342') // true
* isCivitaiModelUrl('https://example.com/model.safetensors') // false
*/
export const isCivitaiModelUrl = (url: string): boolean => {
export function isCivitaiModelUrl(url: string): boolean {
if (!isValidUrl(url)) return false
const urlObj = new URL(url)
@@ -426,7 +429,7 @@ export const isCivitaiModelUrl = (url: string): boolean => {
* 'https://huggingface.co/bfl/FLUX.1/resolve/main/flux1-canny-dev.safetensors?download=true'
* ) // https://huggingface.co/bfl/FLUX.1
*/
export const downloadUrlToHfRepoUrl = (url: string): string => {
export function downloadUrlToHfRepoUrl(url: string): string {
try {
const urlObj = new URL(url)
const pathname = urlObj.pathname

View File

@@ -1,7 +1,7 @@
import axios from 'axios'
const VALID_STATUS_CODES = [200, 201, 301, 302, 307, 308]
export const checkUrlReachable = async (url: string): Promise<boolean> => {
export async function checkUrlReachable(url: string): Promise<boolean> {
try {
const response = await axios.head(url)
// Additional check for successful response

View File

@@ -103,8 +103,9 @@ function shouldIgnoreKey(key: string): boolean {
// Search for key usage in source files
function isKeyUsed(key: string, sourceFiles: string[]): boolean {
// Escape special regex characters
const escapeRegex = (str: string) =>
str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
function escapeRegex(str: string) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
const escapedKey = escapeRegex(key)
const lastPart = key.split('.').pop()
const escapedLastPart = lastPart ? escapeRegex(lastPart) : ''

View File

@@ -18,7 +18,7 @@ const localePath = './src/locales/en/main.json'
const commandsPath = './src/locales/en/commands.json'
const settingsPath = './src/locales/en/settings.json'
const extractMenuCommandLocaleStrings = (): Set<string> => {
function extractMenuCommandLocaleStrings(): Set<string> {
const labels = new Set<string>()
for (const [category, _] of CORE_MENU_COMMANDS) {
category.forEach((category) => labels.add(category))

View File

@@ -93,7 +93,9 @@ async function buildBundleReport() {
* @param {string[]} files
* @returns {string[]}
*/
const filterFiles = (files) => files.filter((file) => file.endsWith('.json'))
function filterFiles(files) {
return files.filter((file) => file.endsWith('.json'))
}
const currFiles = filterFiles(await readdir(currDir))
const baselineFiles = existsSync(prevDir)

View File

@@ -34,7 +34,7 @@ watch(
{ flush: 'post' }
)
const showContextMenu = (event: MouseEvent) => {
function showContextMenu(event: MouseEvent) {
const { target } = event
switch (true) {
case target instanceof HTMLTextAreaElement:

View File

@@ -70,7 +70,7 @@ export function downloadBlob(filename: string, blob: Blob): void {
* @param url - The URL to extract filename from
* @returns The extracted filename or null if not found
*/
const extractFilenameFromUrl = (url: string): string | null => {
function extractFilenameFromUrl(url: string): string | null {
try {
const urlObj = new URL(url, window.location.origin)
return urlObj.searchParams.get('filename')

View File

@@ -3,7 +3,7 @@ const DEFAULT_NUMBER_FORMAT: Intl.NumberFormatOptions = {
maximumFractionDigits: 2
}
const formatNumber = ({
function formatNumber({
value,
locale,
options
@@ -11,7 +11,7 @@ const formatNumber = ({
value: number
locale?: string
options?: Intl.NumberFormatOptions
}): string => {
}): string {
const merged: Intl.NumberFormatOptions = {
...DEFAULT_NUMBER_FORMAT,
...options
@@ -31,19 +31,25 @@ const formatNumber = ({
export const CREDITS_PER_USD = 211
export const COMFY_CREDIT_RATE_CENTS = CREDITS_PER_USD / 100 // credits per cent
export const usdToCents = (usd: number): number => Math.round(usd * 100)
export function usdToCents(usd: number): number {
return Math.round(usd * 100)
}
export const centsToCredits = (cents: number): number =>
Math.round(cents * COMFY_CREDIT_RATE_CENTS)
export function centsToCredits(cents: number): number {
return Math.round(cents * COMFY_CREDIT_RATE_CENTS)
}
export const creditsToCents = (credits: number): number =>
Math.round(credits / COMFY_CREDIT_RATE_CENTS)
export function creditsToCents(credits: number): number {
return Math.round(credits / COMFY_CREDIT_RATE_CENTS)
}
export const usdToCredits = (usd: number): number =>
Math.round(usd * CREDITS_PER_USD)
export function usdToCredits(usd: number): number {
return Math.round(usd * CREDITS_PER_USD)
}
export const creditsToUsd = (credits: number): number =>
Math.round((credits / CREDITS_PER_USD) * 100) / 100
export function creditsToUsd(credits: number): number {
return Math.round((credits / CREDITS_PER_USD) * 100) / 100
}
export type FormatOptions = {
value: number
@@ -63,63 +69,68 @@ export type FormatFromUsdOptions = {
numberOptions?: Intl.NumberFormatOptions
}
export const formatCredits = ({
export function formatCredits({
value,
locale,
numberOptions
}: FormatOptions): string =>
formatNumber({ value, locale, options: numberOptions })
}: FormatOptions): string {
return formatNumber({ value, locale, options: numberOptions })
}
export const formatCreditsFromCents = ({
export function formatCreditsFromCents({
cents,
locale,
numberOptions
}: FormatFromCentsOptions): string =>
formatCredits({
}: FormatFromCentsOptions): string {
return formatCredits({
value: centsToCredits(cents),
locale,
numberOptions
})
}
export const formatCreditsFromUsd = ({
export function formatCreditsFromUsd({
usd,
locale,
numberOptions
}: FormatFromUsdOptions): string =>
formatCredits({
}: FormatFromUsdOptions): string {
return formatCredits({
value: usdToCredits(usd),
locale,
numberOptions
})
}
export const formatUsd = ({
export function formatUsd({
value,
locale,
numberOptions
}: FormatOptions): string =>
formatNumber({
}: FormatOptions): string {
return formatNumber({
value,
locale,
options: numberOptions
})
}
export const formatUsdFromCents = ({
export function formatUsdFromCents({
cents,
locale,
numberOptions
}: FormatFromCentsOptions): string =>
formatUsd({
}: FormatFromCentsOptions): string {
return formatUsd({
value: cents / 100,
locale,
numberOptions
})
}
/**
* Clamps a USD value to the allowed range for credit purchases
* @param value - The USD amount to clamp
* @returns The clamped value between $1 and $1000, or 0 if NaN
*/
export const clampUsd = (value: number): number => {
export function clampUsd(value: number): number {
if (Number.isNaN(value)) return 0
return Math.min(1000, Math.max(1, value))
}

View File

@@ -14,7 +14,10 @@
* Components that intercept wheel events should suppress the default for
* these gestures even when they otherwise let the browser scroll natively.
*/
export const isCanvasGestureWheel = (event: WheelEvent): boolean =>
event.ctrlKey ||
event.metaKey ||
Math.abs(event.deltaX) > Math.abs(event.deltaY)
export function isCanvasGestureWheel(event: WheelEvent): boolean {
return (
event.ctrlKey ||
event.metaKey ||
Math.abs(event.deltaX) > Math.abs(event.deltaY)
)
}

View File

@@ -29,7 +29,7 @@ import { showNativeSystemMenu } from '@/utils/envUtil'
const workspaceState = useWorkspaceStore()
const settingStore = useSettingStore()
const exitFocusMode = () => {
function exitFocusMode() {
workspaceState.focusMode = false
}

View File

@@ -328,11 +328,11 @@ describe('TopMenuSection', () => {
})
describe('inline progress summary', () => {
const configureSettings = (
function configureSettings(
pinia: ReturnType<typeof createTestingPinia>,
qpoV2Enabled: boolean,
showRunProgressBar = true
) => {
) {
const settingStore = useSettingStore(pinia)
vi.mocked(settingStore.get).mockImplementation((key) => {
if (key === 'Comfy.Queue.QPOV2') return qpoV2Enabled
@@ -413,10 +413,10 @@ describe('TopMenuSection', () => {
})
describe(QueueNotificationBannerHost, () => {
const configureSettings = (
function configureSettings(
pinia: ReturnType<typeof createTestingPinia>,
qpoV2Enabled: boolean
) => {
) {
const settingStore = useSettingStore(pinia)
vi.mocked(settingStore.get).mockImplementation((key) => {
if (key === 'Comfy.Queue.QPOV2') return qpoV2Enabled

View File

@@ -327,7 +327,7 @@ onBeforeUnmount(() => {
legacyContentCheckRafId = null
})
const openCustomNodeManager = async () => {
async function openCustomNodeManager() {
try {
await managerState.openManager({
initialTab: ManagerTab.All,

View File

@@ -92,38 +92,39 @@ watch(batchCount, (nextBatchCount) => {
}
})
const clampBatchCount = (nextBatchCount: number): number =>
Math.min(Math.max(nextBatchCount, minQueueCount), maxQueueCount.value)
function clampBatchCount(nextBatchCount: number): number {
return Math.min(Math.max(nextBatchCount, minQueueCount), maxQueueCount.value)
}
const setBatchCount = (nextBatchCount: number) => {
function setBatchCount(nextBatchCount: number) {
batchCount.value = clampBatchCount(nextBatchCount)
batchCountInput.value = String(batchCount.value)
}
const incrementBatchCount = () => {
function incrementBatchCount() {
setBatchCount(batchCount.value * 2)
}
const decrementBatchCount = () => {
function decrementBatchCount() {
setBatchCount(Math.floor(batchCount.value / 2))
}
const onInputFocus = () => {
function onInputFocus() {
isEditing.value = true
}
const onInput = (event: Event) => {
function onInput(event: Event) {
const input = event.target as HTMLInputElement
batchCountInput.value = input.value.replace(/[^0-9]/g, '')
}
const onInputBlur = () => {
function onInputBlur() {
isEditing.value = false
const parsedInput = Number.parseInt(batchCountInput.value, 10)
setBatchCount(Number.isNaN(parsedInput) ? minQueueCount : parsedInput)
}
const onInputEnter = () => {
function onInputEnter() {
batchCountInputRef.value?.blur()
}
</script>

View File

@@ -7,10 +7,10 @@ import ComfyActionbar from '@/components/actionbar/ComfyActionbar.vue'
import { i18n } from '@/i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
const configureSettings = (
function configureSettings(
pinia: ReturnType<typeof createTestingPinia>,
showRunProgressBar: boolean
) => {
) {
const settingStore = useSettingStore(pinia)
vi.mocked(settingStore.get).mockImplementation((key) => {
if (key === 'Comfy.UseNewMenu') return 'Top'
@@ -20,7 +20,7 @@ const configureSettings = (
})
}
const renderActionbar = (showRunProgressBar: boolean) => {
function renderActionbar(showRunProgressBar: boolean) {
const topMenuContainer = document.createElement('div')
document.body.appendChild(topMenuContainer)

Some files were not shown because too many files have changed in this diff Show More