[Electron] Add basic welcome screen (#1491)

* WIP

* Add LogTerminal

* Modify server startup view

* Add installView

* Add basic welcome screen and dev server setup

* nit

* nit

* nit

* nit

* nit
This commit is contained in:
Chenlei Hu
2024-11-10 09:41:32 -05:00
committed by GitHub
parent 31fac3873c
commit d9a34872c3
11 changed files with 216 additions and 15 deletions

View File

@@ -0,0 +1,66 @@
<!-- A simple read-only terminal component that displays logs. -->
<template>
<div class="p-terminal rounded-none h-full w-full">
<ScrollPanel class="h-full w-full" ref="scrollPanelRef">
<pre class="px-4 whitespace-pre-wrap">{{ log }}</pre>
</ScrollPanel>
</div>
</template>
<script setup lang="ts">
import ScrollPanel from 'primevue/scrollpanel'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
const props = defineProps<{
fetchLogs: () => Promise<string>
fetchInterval: number
}>()
const log = ref<string>('')
const scrollPanelRef = ref<InstanceType<typeof ScrollPanel> | null>(null)
/**
* Whether the user has scrolled to the bottom of the terminal.
* This is used to prevent the terminal from scrolling to the bottom
* when new logs are fetched.
*/
const scrolledToBottom = ref(false)
let intervalId: number = 0
onMounted(async () => {
const element = scrollPanelRef.value?.$el
const scrollContainer = element?.querySelector('.p-scrollpanel-content')
if (scrollContainer) {
scrollContainer.addEventListener('scroll', () => {
scrolledToBottom.value =
scrollContainer.scrollTop + scrollContainer.clientHeight ===
scrollContainer.scrollHeight
})
}
const scrollToBottom = () => {
if (scrollContainer) {
scrollContainer.scrollTop = scrollContainer.scrollHeight
}
}
watch(log, () => {
if (scrolledToBottom.value) {
scrollToBottom()
}
})
const fetchLogs = async () => {
log.value = await props.fetchLogs()
}
await fetchLogs()
scrollToBottom()
intervalId = window.setInterval(fetchLogs, props.fetchInterval)
})
onBeforeUnmount(() => {
window.clearInterval(intervalId)
})
</script>

View File

@@ -0,0 +1,47 @@
<template>
<div class="flex flex-col items-center justify-center gap-8 p-8">
<!-- Header -->
<h1 class="text-4xl font-bold text-neutral-100">Welcome to ComfyUI</h1>
<!-- Get Started Button -->
<Button
label="Get Started"
icon="pi pi-arrow-right"
iconPos="right"
size="large"
@click="$emit('start')"
class="p-4 text-lg"
/>
<!-- Installation Steps -->
<div class="w-[600px]">
<Steps :model="installSteps" :readonly="true" />
</div>
</div>
</template>
<script setup lang="ts">
import Steps from 'primevue/steps'
import Button from 'primevue/button'
const installSteps = [
{
label: 'Install Location',
icon: 'pi pi-folder'
},
{
label: 'Migration',
icon: 'pi pi-download'
},
{
label: 'Desktop Settings',
icon: 'pi pi-desktop'
},
{
label: 'Review',
icon: 'pi pi-check'
}
]
defineEmits(['start'])
</script>

View File

@@ -32,6 +32,19 @@ const router = createRouter({
next('/')
}
}
},
{
path: 'install',
name: 'InstallView',
component: () => import('@/views/InstallView.vue'),
beforeEnter: async (to, from, next) => {
// Only allow access to this page in electron environment
if (isElectron()) {
next()
} else {
next('/')
}
}
}
]
}

View File

@@ -1784,6 +1784,8 @@ export class ComfyApp {
*/
async setup(canvasEl: HTMLCanvasElement) {
this.canvasEl = canvasEl
// Show menu container for GraphView.
this.ui.menuContainer.style.display = 'block'
await this.#setUser()
this.resizeCanvas()

View File

@@ -636,6 +636,8 @@ export class ComfyUI {
}
})
]) as HTMLDivElement
// Hide by default on construction so it does not interfere with other views.
this.menuContainer.style.display = 'none'
this.restoreMenuPosition = dragElement(this.menuContainer, this.settings)

View File

@@ -5,5 +5,5 @@ export function isElectron() {
}
export function electronAPI() {
return window['electronAPI'] as ElectronAPI
return (window as any)['electronAPI'] as ElectronAPI
}

11
src/views/InstallView.vue Normal file
View File

@@ -0,0 +1,11 @@
<template>
<div
class="font-sans flex flex-col justify-center items-center h-screen m-0 text-neutral-300 bg-neutral-900 dark-theme pointer-events-auto"
>
<WelcomeScreen />
</div>
</template>
<script setup lang="ts">
import WelcomeScreen from '@/components/install/WelcomeScreen.vue'
</script>

View File

@@ -1,17 +1,44 @@
<!-- This is a dummy page built for electron app only. -->
<!-- Replace this page with the electron side logic on installation & log streaming. -->
<template>
<div
class="absolute inset-0 flex flex-col items-center justify-center h-screen bg-surface-0"
class="font-sans flex flex-col justify-center items-center h-screen m-0 text-neutral-300 bg-neutral-900 dark-theme pointer-events-auto"
>
<ProgressSpinner class="w-16 h-16 mb-4" />
<div class="text-xl">{{ $t('loading') }}{{ counter }}</div>
<h2 class="text-2xl font-bold">{{ ProgressMessages[status] }}</h2>
<LogTerminal :fetch-logs="fetchLogs" :fetch-interval="500" />
</div>
</template>
<script setup lang="ts">
import ProgressSpinner from 'primevue/progressspinner'
import { useInterval } from '@vueuse/core'
import { ref, onMounted } from 'vue'
import LogTerminal from '@/components/common/LogTerminal.vue'
import {
ProgressStatus,
ProgressMessages
} from '@comfyorg/comfyui-electron-types'
import { electronAPI as getElectronAPI } from '@/utils/envUtil'
const counter = useInterval(1000)
const electronAPI = getElectronAPI()
const status = ref<ProgressStatus>(ProgressStatus.INITIAL_STATE)
const logs = ref<string[]>([])
const updateProgress = ({ status: newStatus }: { status: ProgressStatus }) => {
status.value = newStatus
logs.value = [] // Clear logs when status changes
}
const addLogMessage = (message: string) => {
logs.value = [...logs.value, message]
}
const fetchLogs = async () => {
return logs.value.join('\n')
}
onMounted(() => {
electronAPI.sendReady()
electronAPI.onProgressUpdate(updateProgress)
electronAPI.onLogMessage((message: string) => {
addLogMessage(message)
})
})
</script>