mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary Fix HoneyToast footer content (text + buttons) being squished when collapsed due to `sm:w-min` on the outer container. ## Changes - **What**: Replace `sm:w-min sm:min-w-0` with state-aware sizing (`sm:w-fit` when collapsed, `sm:w-[max(400px,40vw)]` when expanded) on the HoneyToast container. Add `gap-4` between footer text and buttons in ManagerProgressToast, `min-w-0` on text area, and `shrink-0` on button area to prevent overlap. ## Review Focus - HoneyToast is used in 3 places: ManagerProgressToast, ModelImportProgressDialog, AssetExportProgressDialog. The change moves width control from the inner content div to the outer container, which should have no negative impact on the other two consumers. ## As-is <img width="481" height="146" alt="스크린샷 2026-03-10 오전 10 40 52" src="https://github.com/user-attachments/assets/b5e12e20-23ea-4f11-9778-ad4e6c10a425" /> ## To-be <img width="506" height="62" alt="스크린샷 2026-03-10 오후 1 46 18" src="https://github.com/user-attachments/assets/f2b7963d-eedb-4885-bc57-f8b377962e92" /> <img width="630" height="83" alt="스크린샷 2026-03-10 오후 1 46 10" src="https://github.com/user-attachments/assets/e9c35f1c-3441-4fb2-8fa4-f66b7d53b3e5" /> <img width="683" height="103" alt="스크린샷 2026-03-10 오후 1 46 02" src="https://github.com/user-attachments/assets/afb94a16-cfba-4da9-8676-35f4d3133b57" />
310 lines
9.4 KiB
Vue
310 lines
9.4 KiB
Vue
<script setup lang="ts">
|
|
import { useScroll, whenever } from '@vueuse/core'
|
|
import Panel from 'primevue/panel'
|
|
import TabMenu from 'primevue/tabmenu'
|
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import DotSpinner from '@/components/common/DotSpinner.vue'
|
|
import HoneyToast from '@/components/honeyToast/HoneyToast.vue'
|
|
import Button from '@/components/ui/button/Button.vue'
|
|
import { useApplyChanges } from '@/workbench/extensions/manager/composables/useApplyChanges'
|
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
|
|
|
const { t } = useI18n()
|
|
const comfyManagerStore = useComfyManagerStore()
|
|
const { isRestarting, isRestartCompleted, applyChanges } = useApplyChanges()
|
|
|
|
const isExpanded = ref(false)
|
|
const activeTabIndex = ref(0)
|
|
|
|
const tabs = computed(() => [
|
|
{ label: t('manager.installationQueue') },
|
|
{
|
|
label: t('manager.failed', {
|
|
count: comfyManagerStore.failedTasksIds.length
|
|
})
|
|
}
|
|
])
|
|
|
|
const focusedLogs = computed(() => {
|
|
if (activeTabIndex.value === 0) {
|
|
return comfyManagerStore.succeededTasksLogs
|
|
}
|
|
return comfyManagerStore.failedTasksLogs
|
|
})
|
|
|
|
const visible = computed(() => comfyManagerStore.taskLogs.length > 0)
|
|
|
|
const isInProgress = computed(
|
|
() => comfyManagerStore.isProcessingTasks || isRestarting.value
|
|
)
|
|
|
|
const isTaskInProgress = (index: number) => {
|
|
const log = focusedLogs.value[index]
|
|
if (!log) return false
|
|
|
|
const taskQueue = comfyManagerStore.taskQueue
|
|
if (!taskQueue) return false
|
|
|
|
const allQueueTasks = [
|
|
...(taskQueue.running_queue || []),
|
|
...(taskQueue.pending_queue || [])
|
|
]
|
|
|
|
return allQueueTasks.some((task) => task.ui_id === log.taskId)
|
|
}
|
|
|
|
const completedTasksCount = computed(() => {
|
|
return (
|
|
comfyManagerStore.succeededTasksIds.length +
|
|
comfyManagerStore.failedTasksIds.length
|
|
)
|
|
})
|
|
|
|
const totalTasksCount = computed(() => {
|
|
const completedTasks = Object.keys(comfyManagerStore.taskHistory).length
|
|
const taskQueue = comfyManagerStore.taskQueue
|
|
const queuedTasks = taskQueue
|
|
? (taskQueue.running_queue?.length || 0) +
|
|
(taskQueue.pending_queue?.length || 0)
|
|
: 0
|
|
return completedTasks + queuedTasks
|
|
})
|
|
|
|
const currentTaskName = computed(() => {
|
|
if (isRestarting.value) {
|
|
return t('manager.restartingBackend')
|
|
}
|
|
if (isRestartCompleted.value) {
|
|
return t('manager.extensionsSuccessfullyInstalled')
|
|
}
|
|
if (!comfyManagerStore.taskLogs.length)
|
|
return t('manager.installingDependencies')
|
|
const task = comfyManagerStore.taskLogs.at(-1)
|
|
return task?.taskName ?? t('manager.installingDependencies')
|
|
})
|
|
|
|
const collapsedPanels = ref<Record<number, boolean>>({})
|
|
function togglePanel(index: number) {
|
|
collapsedPanels.value[index] = !collapsedPanels.value[index]
|
|
}
|
|
|
|
const sectionsContainerRef = ref<HTMLElement | null>(null)
|
|
const { y: scrollY } = useScroll(sectionsContainerRef, {
|
|
eventListenerOptions: { passive: true }
|
|
})
|
|
|
|
const lastPanelRef = ref<HTMLElement | null>(null)
|
|
const isUserScrolling = ref(false)
|
|
const lastPanelLogs = computed(() => focusedLogs.value?.at(-1)?.logs)
|
|
|
|
function isAtBottom(el: HTMLElement | null) {
|
|
if (!el) return false
|
|
const threshold = 20
|
|
return Math.abs(el.scrollHeight - el.scrollTop - el.clientHeight) < threshold
|
|
}
|
|
|
|
function scrollLastPanelToBottom() {
|
|
if (!lastPanelRef.value || isUserScrolling.value) return
|
|
lastPanelRef.value.scrollTop = lastPanelRef.value.scrollHeight
|
|
}
|
|
|
|
function scrollContentToBottom() {
|
|
scrollY.value = sectionsContainerRef.value?.scrollHeight ?? 0
|
|
}
|
|
|
|
function resetUserScrolling() {
|
|
isUserScrolling.value = false
|
|
}
|
|
|
|
function handleScroll(e: Event) {
|
|
const target = e.target as HTMLElement
|
|
if (target !== lastPanelRef.value) return
|
|
isUserScrolling.value = !isAtBottom(target)
|
|
}
|
|
|
|
function onLogsAdded() {
|
|
if (isUserScrolling.value) return
|
|
scrollLastPanelToBottom()
|
|
}
|
|
|
|
whenever(lastPanelLogs, onLogsAdded, { flush: 'post', deep: true })
|
|
whenever(() => isExpanded.value, scrollContentToBottom)
|
|
whenever(() => !isExpanded.value, resetUserScrolling)
|
|
|
|
function closeToast() {
|
|
comfyManagerStore.resetTaskState()
|
|
isExpanded.value = false
|
|
}
|
|
|
|
async function handleRestart() {
|
|
try {
|
|
await applyChanges(closeToast)
|
|
} catch (err) {
|
|
console.error('[ManagerProgressToast] Restart failed:', err)
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
scrollContentToBottom()
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
isExpanded.value = false
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<HoneyToast v-model:expanded="isExpanded" :visible>
|
|
<template #default>
|
|
<div v-if="isExpanded" class="flex items-center px-4 py-2">
|
|
<TabMenu
|
|
v-model:active-index="activeTabIndex"
|
|
:model="tabs"
|
|
class="w-full border-none"
|
|
:pt="{
|
|
menu: { class: 'border-none' },
|
|
menuitem: { class: 'font-medium' },
|
|
action: { class: 'px-4 py-2' }
|
|
}"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
ref="sectionsContainerRef"
|
|
class="scroll-container max-h-[450px] overflow-y-auto px-6 py-4"
|
|
:style="{
|
|
scrollbarWidth: 'thin',
|
|
scrollbarColor: 'rgba(156, 163, 175, 0.5) transparent'
|
|
}"
|
|
>
|
|
<div v-for="(log, index) in focusedLogs" :key="index">
|
|
<Panel
|
|
:expanded="collapsedPanels[index] === true"
|
|
toggleable
|
|
class="shadow-elevation-1 mt-2 rounded-lg"
|
|
>
|
|
<template #header>
|
|
<div class="flex w-full items-center justify-between py-2">
|
|
<div class="flex flex-col text-sm/normal font-medium">
|
|
<span>{{ log.taskName }}</span>
|
|
<span class="text-muted">
|
|
{{
|
|
isTaskInProgress(index)
|
|
? t('g.inProgress')
|
|
: t('g.completedWithCheckmark')
|
|
}}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #toggleicon>
|
|
<Button
|
|
variant="textonly"
|
|
class="text-neutral-300"
|
|
@click="togglePanel(index)"
|
|
>
|
|
<i
|
|
:class="
|
|
collapsedPanels[index]
|
|
? 'pi pi-chevron-right'
|
|
: 'pi pi-chevron-down'
|
|
"
|
|
/>
|
|
</Button>
|
|
</template>
|
|
<div
|
|
:ref="
|
|
index === focusedLogs.length - 1
|
|
? (el) => (lastPanelRef = el as HTMLElement)
|
|
: undefined
|
|
"
|
|
class="h-64 overflow-y-auto rounded-lg bg-black"
|
|
:class="{
|
|
'h-64': index !== focusedLogs.length - 1,
|
|
grow: index === focusedLogs.length - 1
|
|
}"
|
|
@scroll="handleScroll"
|
|
>
|
|
<div class="h-full">
|
|
<div
|
|
v-for="(logLine, logIndex) in log.logs"
|
|
:key="logIndex"
|
|
class="text-muted"
|
|
>
|
|
<pre class="wrap-break-word whitespace-pre-wrap">{{
|
|
logLine
|
|
}}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Panel>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template #footer="{ toggle }">
|
|
<div
|
|
class="flex w-full items-center justify-between gap-4 px-6 py-2 shadow-lg"
|
|
>
|
|
<div class="flex min-w-0 items-center text-base leading-none">
|
|
<div class="flex items-center">
|
|
<template v-if="isInProgress">
|
|
<DotSpinner duration="1s" class="mr-2" />
|
|
<span>{{ currentTaskName }}</span>
|
|
</template>
|
|
<template v-else-if="isRestartCompleted">
|
|
<span class="mr-2">🎉</span>
|
|
<span>{{ currentTaskName }}</span>
|
|
</template>
|
|
<template v-else>
|
|
<span class="mr-2">✅</span>
|
|
<span>{{ t('manager.restartToApplyChanges') }}</span>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div class="flex shrink-0 items-center gap-4">
|
|
<span v-if="isInProgress" class="text-sm text-muted-foreground">
|
|
{{ completedTasksCount }} {{ t('g.progressCountOf') }}
|
|
{{ totalTasksCount }}
|
|
</span>
|
|
<div class="flex items-center">
|
|
<Button
|
|
v-if="!isInProgress && !isRestartCompleted"
|
|
variant="secondary"
|
|
class="mr-4 rounded-full border-2 border-base-foreground px-3 text-base-foreground hover:bg-secondary-background-hover"
|
|
@click="handleRestart"
|
|
>
|
|
{{ t('manager.applyChanges') }}
|
|
</Button>
|
|
<Button
|
|
v-else-if="!isRestartCompleted"
|
|
variant="muted-textonly"
|
|
size="sm"
|
|
class="rounded-full font-bold"
|
|
:aria-label="
|
|
t(isExpanded ? 'contextMenu.Collapse' : 'contextMenu.Expand')
|
|
"
|
|
@click.stop="toggle"
|
|
>
|
|
<i
|
|
:class="isExpanded ? 'pi pi-chevron-up' : 'pi pi-chevron-down'"
|
|
/>
|
|
</Button>
|
|
<Button
|
|
variant="muted-textonly"
|
|
size="sm"
|
|
class="rounded-full font-bold"
|
|
:aria-label="t('g.close')"
|
|
@click.stop="closeToast"
|
|
>
|
|
<i class="pi pi-times" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</HoneyToast>
|
|
</template>
|