mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-21 21:09:00 +00:00
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:
@@ -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"],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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' })
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -52,7 +52,7 @@ const electron = electronAPI()
|
||||
|
||||
const terminalVisible = ref(false)
|
||||
|
||||
const toggleConsoleDrawer = () => {
|
||||
function toggleConsoleDrawer() {
|
||||
terminalVisible.value = !terminalVisible.value
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 }
|
||||
}))
|
||||
|
||||
@@ -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 }
|
||||
}))
|
||||
|
||||
@@ -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 }
|
||||
}))
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 }
|
||||
}))
|
||||
|
||||
@@ -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 }
|
||||
}))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 = [
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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), {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 })
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) : ''
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -34,7 +34,7 @@ watch(
|
||||
{ flush: 'post' }
|
||||
)
|
||||
|
||||
const showContextMenu = (event: MouseEvent) => {
|
||||
function showContextMenu(event: MouseEvent) {
|
||||
const { target } = event
|
||||
switch (true) {
|
||||
case target instanceof HTMLTextAreaElement:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import { showNativeSystemMenu } from '@/utils/envUtil'
|
||||
|
||||
const workspaceState = useWorkspaceStore()
|
||||
const settingStore = useSettingStore()
|
||||
const exitFocusMode = () => {
|
||||
function exitFocusMode() {
|
||||
workspaceState.focusMode = false
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -327,7 +327,7 @@ onBeforeUnmount(() => {
|
||||
legacyContentCheckRafId = null
|
||||
})
|
||||
|
||||
const openCustomNodeManager = async () => {
|
||||
async function openCustomNodeManager() {
|
||||
try {
|
||||
await managerState.openManager({
|
||||
initialTab: ManagerTab.All,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user