Files
ComfyUI_frontend/apps/desktop-ui/src/components/install/MigrationPicker.vue
filtered 07a74e3cdc Decouple Desktop UI into monorepo app (#5912)
## 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.
2025-10-05 05:04:27 +00:00

131 lines
3.4 KiB
Vue

<template>
<div class="flex flex-col gap-6 w-[600px]">
<!-- Source Location Section -->
<div class="flex flex-col gap-4">
<p class="text-neutral-400 my-0">
{{ $t('install.migrationSourcePathDescription') }}
</p>
<div class="flex gap-2">
<InputText
v-model="sourcePath"
:placeholder="$t('install.locationPicker.migrationPathPlaceholder')"
class="flex-1"
:class="{ 'p-invalid': pathError }"
@update:model-value="validateSource"
/>
<Button icon="pi pi-folder" class="w-12" @click="browsePath" />
</div>
<Message v-if="pathError" severity="error">
{{ pathError }}
</Message>
</div>
<!-- Migration Options -->
<div v-if="isValidSource" class="flex flex-col gap-4 p-4 rounded-lg">
<h3 class="text-lg mt-0 font-medium text-neutral-100">
{{ $t('install.selectItemsToMigrate') }}
</h3>
<div class="flex flex-col gap-3">
<div
v-for="item in migrationItems"
:key="item.id"
class="flex items-center gap-3 p-2 hover:bg-neutral-700 rounded"
@click="item.selected = !item.selected"
>
<Checkbox
v-model="item.selected"
:input-id="item.id"
:binary="true"
@click.stop
/>
<div>
<label :for="item.id" class="text-neutral-200 font-medium">
{{ item.label }}
</label>
<p class="text-sm text-neutral-400 my-1">
{{ item.description }}
</p>
</div>
</div>
</div>
</div>
<!-- Skip Migration -->
<div v-else class="text-neutral-400 italic">
{{ $t('install.migrationOptional') }}
</div>
</div>
</template>
<script setup lang="ts">
import { MigrationItems } from '@comfyorg/comfyui-electron-types'
import Button from 'primevue/button'
import Checkbox from 'primevue/checkbox'
import InputText from 'primevue/inputtext'
import Message from 'primevue/message'
import { computed, ref, watchEffect } from 'vue'
import { useI18n } from 'vue-i18n'
import { electronAPI } from '@/utils/envUtil'
const { t } = useI18n()
const electron = electronAPI()
const sourcePath = defineModel<string>('sourcePath', { required: false })
const migrationItemIds = defineModel<string[]>('migrationItemIds', {
required: false
})
const migrationItems = ref(
MigrationItems.map((item) => ({
...item,
selected: true
}))
)
const pathError = ref('')
const isValidSource = computed(
() => sourcePath.value !== '' && pathError.value === ''
)
const validateSource = async (sourcePath: string | undefined) => {
if (!sourcePath) {
pathError.value = ''
return
}
try {
pathError.value = ''
const validation = await electron.validateComfyUISource(sourcePath)
if (!validation.isValid) pathError.value = validation.error ?? 'ERROR'
} catch (error) {
console.error(error)
pathError.value = t('install.pathValidationFailed')
}
}
const browsePath = async () => {
try {
const result = await electron.showDirectoryPicker()
if (result) {
sourcePath.value = result
await validateSource(result)
}
} catch (error) {
console.error(error)
pathError.value = t('install.failedToSelectDirectory')
}
}
watchEffect(() => {
migrationItemIds.value = migrationItems.value
.filter((item) => item.selected)
.map((item) => item.id)
})
</script>