mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
## Summary Extracts desktop UI into apps/desktop-ui package with minimal changes. ## Changes - **What**: - Separates desktop-specific code into standalone package with independent Vite config, router, and i18n - Drastically simplifies the main app router by removing all desktop routes - Adds a some code duplication, most due to the existing design - Some duplication can be refactored to be *simpler* on either side - no need to split things by `isElectron()` - Rudimentary storybook support has been added - **Breaking**: Stacked PR for publishing must be merged before this PR makes it to stable core (but publishing _could_ be done manually) - #5915 - **Dependencies**: Takes full advantage of pnpm catalog. No additional dependencies added. ## Review Focus - Should be no changes to normal frontend operation - Scripts added to root package.json are acceptable - The duplication in this PR is copied as is, wherever possible. Any corrections or fix-ups beyond the scope of simply migrating the functionality as-is, can be addressed in later PRs. That said, if any changes are made, it instantly becomes more difficult to separate the duplicated code out into a shared utility. - Tracking issue to address concerns: #5925 ### i18n Fixing i18n is out of scope for this PR. It is a larger task that we should consider carefully and implement properly. Attempting to isolate the desktop i18n and duplicate the _current_ localisation scripts would be wasted energy.
130 lines
3.3 KiB
Vue
130 lines
3.3 KiB
Vue
<template>
|
|
<IconField class="w-full">
|
|
<InputText
|
|
v-bind="$attrs"
|
|
:model-value="internalValue"
|
|
class="w-full"
|
|
:invalid="validationState === ValidationState.INVALID"
|
|
@update:model-value="handleInput"
|
|
@blur="handleBlur"
|
|
/>
|
|
<InputIcon
|
|
:class="{
|
|
'pi pi-spin pi-spinner text-neutral-400':
|
|
validationState === ValidationState.LOADING,
|
|
'pi pi-check text-green-500 cursor-pointer':
|
|
validationState === ValidationState.VALID,
|
|
'pi pi-times text-red-500 cursor-pointer':
|
|
validationState === ValidationState.INVALID
|
|
}"
|
|
@click="validateUrl(props.modelValue)"
|
|
/>
|
|
</IconField>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { isValidUrl } from '@comfyorg/shared-frontend-utils/formatUtil'
|
|
import { checkUrlReachable } from '@comfyorg/shared-frontend-utils/networkUtil'
|
|
import IconField from 'primevue/iconfield'
|
|
import InputIcon from 'primevue/inputicon'
|
|
import InputText from 'primevue/inputtext'
|
|
import { onMounted, ref, watch } from 'vue'
|
|
|
|
import { ValidationState } from '@/utils/validationUtil'
|
|
|
|
const props = defineProps<{
|
|
modelValue: string
|
|
validateUrlFn?: (url: string) => Promise<boolean>
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: string]
|
|
'state-change': [state: ValidationState]
|
|
}>()
|
|
|
|
const validationState = ref<ValidationState>(ValidationState.IDLE)
|
|
|
|
const cleanInput = (value: string): string =>
|
|
value ? value.replace(/\s+/g, '') : ''
|
|
|
|
// Add internal value state
|
|
const internalValue = ref(cleanInput(props.modelValue))
|
|
|
|
// Watch for external modelValue changes
|
|
watch(
|
|
() => props.modelValue,
|
|
async (newValue: string) => {
|
|
internalValue.value = cleanInput(newValue)
|
|
await validateUrl(newValue)
|
|
}
|
|
)
|
|
|
|
watch(validationState, (newState) => {
|
|
emit('state-change', newState)
|
|
})
|
|
|
|
// Validate on mount
|
|
onMounted(async () => {
|
|
await validateUrl(props.modelValue)
|
|
})
|
|
|
|
const 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 () => {
|
|
const input = cleanInput(internalValue.value)
|
|
|
|
let normalizedUrl = input
|
|
try {
|
|
const url = new URL(input)
|
|
normalizedUrl = url.toString()
|
|
} catch {
|
|
// If URL parsing fails, just use the cleaned input
|
|
}
|
|
|
|
// Emit the update only on blur
|
|
emit('update:modelValue', normalizedUrl)
|
|
}
|
|
|
|
// Default validation implementation
|
|
const defaultValidateUrl = async (url: string): Promise<boolean> => {
|
|
if (!isValidUrl(url)) return false
|
|
try {
|
|
return await checkUrlReachable(url)
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
const validateUrl = async (value: string) => {
|
|
if (validationState.value === ValidationState.LOADING) return
|
|
|
|
const url = cleanInput(value)
|
|
|
|
// Reset state
|
|
validationState.value = ValidationState.IDLE
|
|
|
|
// Skip validation if empty
|
|
if (!url) return
|
|
|
|
validationState.value = ValidationState.LOADING
|
|
try {
|
|
const isValid = await (props.validateUrlFn ?? defaultValidateUrl)(url)
|
|
validationState.value = isValid
|
|
? ValidationState.VALID
|
|
: ValidationState.INVALID
|
|
} catch {
|
|
validationState.value = ValidationState.INVALID
|
|
}
|
|
}
|
|
|
|
// Add inheritAttrs option to prevent attrs from being applied to root element
|
|
defineOptions({
|
|
inheritAttrs: false
|
|
})
|
|
</script>
|