Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group pending tasks in queue #2000

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 137 additions & 33 deletions src/components/sidebar/tabs/QueueSidebarTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,51 @@
</template>
<template #body>
<div
v-if="visibleTasks.length > 0"
ref="scrollContainer"
class="scroll-container"
v-if="
Object.values(visibleTasksByType).some((group) => group.groupTotal)
"
ref="sidebarContainer"
class="overflow-hidden h-full flex flex-col"
>
<div class="queue-grid">
<TaskItem
v-for="task in visibleTasks"
:key="task.key"
:task="task"
:isFlatTask="isExpanded || isInFolderView"
@contextmenu="handleContextMenu"
@preview="handlePreview"
@taskOutputLengthClicked="enterFolderView($event)"
/>
</div>
<div ref="loadMoreTrigger" style="height: 1px" />
<Accordion
v-for="taskType in ACCORDION_ORDER"
expandIcon="pi pi-plus"
collapseIcon="pi pi-minus"
:class="expandedGroup === taskType ? 'flex-grow' : 'flex-shrink'"
:value="expandedGroup"
@update:value="(e) => (expandedGroup = e)"
:key="taskType"
>
<AccordionPanel
v-if="visibleTasksByType[taskType]"
:value="taskType"
class="h-full flex flex-col"
>
<AccordionHeader>
<span class="font-bold whitespace-nowrap"
>{{ $t(`sideToolbar.queueTab.${taskType}`) }} ({{
visibleTasksByType[taskType]?.groupTotal
}})
</span>
</AccordionHeader>
<AccordionContent class="h-0 flex-grow">
<div class="scroll-container" ref="scrollContainer">
<div class="queue-grid">
<TaskItem
v-for="task in visibleTasksByType[taskType]?.tasks"
:key="task.key"
:task="task"
:isFlatTask="isExpanded || isInFolderView"
@contextmenu="handleContextMenu"
@preview="handlePreview"
@taskOutputLengthClicked="enterFolderView($event)"
/>
</div>
</div>
<div ref="loadMoreTrigger" style="height: 1px" />
</AccordionContent>
</AccordionPanel>
</Accordion>
</div>
<div v-else-if="queueStore.isLoading">
<ProgressSpinner
Expand Down Expand Up @@ -100,6 +129,10 @@ import ConfirmPopup from 'primevue/confirmpopup'
import ContextMenu from 'primevue/contextmenu'
import type { MenuItem } from 'primevue/menuitem'
import ProgressSpinner from 'primevue/progressspinner'
import Accordion from 'primevue/accordion'
import AccordionPanel from 'primevue/accordionpanel'
import AccordionHeader from 'primevue/accordionheader'
import AccordionContent from 'primevue/accordioncontent'
import TaskItem from './queue/TaskItem.vue'
import ResultGallery from './queue/ResultGallery.vue'
import SidebarTabTemplate from './SidebarTabTemplate.vue'
Expand All @@ -122,8 +155,10 @@ const { t } = useI18n()
// Expanded view: show all outputs in a flat list.
const isExpanded = ref(false)
const visibleTasks = ref<TaskItemImpl[]>([])
const scrollContainer = ref<HTMLElement | null>(null)
const scrollContainer = ref<HTMLElement[] | null>(null)
const sidebarContainer = ref<HTMLElement | null>(null)
const loadMoreTrigger = ref<HTMLElement | null>(null)
const expandedGroup = ref<string | null>(null)
const galleryActiveIndex = ref(-1)
// Folder view: only show outputs from a single selected task.
const folderTask = ref<TaskItemImpl | null>(null)
Expand All @@ -132,6 +167,7 @@ const imageFit = computed<string>(() => settingStore.get(IMAGE_FIT))

const ITEMS_PER_PAGE = 8
const SCROLL_THRESHOLD = 100 // pixels from bottom to trigger load
const ACCORDION_ORDER = ['pending', 'running', 'history']

const allTasks = computed(() =>
isInFolderView.value
Expand All @@ -142,6 +178,46 @@ const allTasks = computed(() =>
? queueStore.flatTasks
: queueStore.tasks
)
const visibleTasksByType = computed<
Record<string, { groupTotal: number; tasks: TaskItemImpl[] }>
>(() => {
const groupedTaks: Record<
string,
{ groupTotal: number; tasks: TaskItemImpl[] }
> = visibleTasks.value.reduce((groups, task) => {
const taskType = task.taskType.toLowerCase()
if (!groups[taskType]) {
groups[taskType] = {
tasks: [],
groupTotal: allTasks.value.filter(
(task) => task.taskType.toLowerCase() === taskType
).length
}
}
groups[taskType].tasks.push(task)
return groups
}, {})
if (queueStore.hasPendingTasks && !('pending' in groupedTaks)) {
groupedTaks.pending = {
tasks: [],
groupTotal: queueStore.pendingTasks.length
}
}
if (queueStore.hasRunningTasks && !('running' in groupedTaks)) {
groupedTaks.running = {
tasks: [],
groupTotal: queueStore.runningTasks.length
}
}
if (queueStore.hasHistoryTasks && !('history' in groupedTaks)) {
groupedTaks.history = {
tasks: [],
groupTotal: queueStore.historyTasks.length
}
}
return groupedTaks
})

const allGalleryItems = computed(() =>
allTasks.value.flatMap((task: TaskItemImpl) => {
const previewOutput = task.previewOutput
Expand All @@ -151,42 +227,63 @@ const allGalleryItems = computed(() =>

const loadMoreItems = () => {
const currentLength = visibleTasks.value.length
const newTasks = allTasks.value.slice(
currentLength,
currentLength + ITEMS_PER_PAGE
)
const newTasks = allTasks.value
.filter((task) => task.taskType.toLowerCase() === expandedGroup.value)
.slice(currentLength, currentLength + ITEMS_PER_PAGE)
visibleTasks.value.push(...newTasks)
}

const checkAndLoadMore = () => {
if (!scrollContainer.value) return
if (!scrollContainer.value?.length) return

const { scrollHeight, scrollTop, clientHeight } = scrollContainer.value
if (scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD) {
loadMoreItems()
}
}
scrollContainer.value.forEach((container) => {
if (!container) return

useInfiniteScroll(
scrollContainer,
() => {
if (visibleTasks.value.length < allTasks.value.length) {
const { scrollHeight, scrollTop, clientHeight } = container
if (
scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD &&
expandedGroup.value
) {
loadMoreItems()
}
})
}

watch(
expandedGroup,
(val) => {
if (val) {
scrollContainer.value.forEach((container) => {
useInfiniteScroll(
container,
() => {
if (visibleTasks.value.length < allTasks.value.length) {
loadMoreItems()
}
},
{ distance: SCROLL_THRESHOLD }
)
nextTick(() => {
updateVisibleTasks()
})
})
}
},
{ distance: SCROLL_THRESHOLD }
{ immediate: true }
)

// Use ResizeObserver to detect container size changes
// This is necessary as the sidebar tab can change size when user drags the splitter.
useResizeObserver(scrollContainer, () => {
useResizeObserver(sidebarContainer, () => {
nextTick(() => {
checkAndLoadMore()
})
})

const updateVisibleTasks = () => {
visibleTasks.value = allTasks.value.slice(0, ITEMS_PER_PAGE)
visibleTasks.value = allTasks.value
.filter((task) => task.taskType.toLowerCase() === expandedGroup.value)
.slice(0, ITEMS_PER_PAGE)
}

const toggleExpanded = () => {
Expand Down Expand Up @@ -342,4 +439,11 @@ watch(
padding: 0.5rem;
gap: 0.5rem;
}

:deep(.p-accordionheader) {
@apply bg-transparent;
}
:deep(.p-accordioncontent-content) {
@apply bg-transparent h-full p-0;
}
</style>
6 changes: 6 additions & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@
"coverImagePreview": "Fit Image Preview",
"clearPendingTasks": "Clear Pending Tasks",
"filter": "Filter Outputs",
"history": "History",
"running": "Running",
"pending": "Pending",
"completed": "Completed",
"failed": "Failed",
"cancelled": "Cancelled",
"filters": {
"hideCached": "Hide Cached",
"hideCanceled": "Hide Canceled"
Expand Down
6 changes: 6 additions & 0 deletions src/locales/ja/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -574,14 +574,20 @@
"queue": "キュー",
"queueTab": {
"backToAllTasks": "すべてのタスクに戻る",
"cancelled": "キャンセル",
"clearPendingTasks": "保留中のタスクをクリア",
"completed": "完了",
"containImagePreview": "画像プレビューを含める",
"coverImagePreview": "画像プレビューに合わせる",
"failed": "失敗",
"filter": "出力をフィルタ",
"filters": {
"hideCached": "キャッシュを非表示",
"hideCanceled": "キャンセル済みを非表示"
},
"history": "履歴",
"pending": "保留中",
"running": "実行中",
"showFlatList": "フラットリストを表示"
},
"themeToggle": "テーマの切り替え",
Expand Down
6 changes: 6 additions & 0 deletions src/locales/ko/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -574,14 +574,20 @@
"queue": "큐",
"queueTab": {
"backToAllTasks": "모든 작업으로 돌아가기",
"cancelled": "취소됨",
"clearPendingTasks": "보류 중인 작업 지우기",
"completed": "완료됨",
"containImagePreview": "이미지 미리보기 채우기",
"coverImagePreview": "이미지 미리보기 맞추기",
"failed": "실패함",
"filter": "출력 필터",
"filters": {
"hideCached": "캐시 숨기기",
"hideCanceled": "취소된 작업 숨기기"
},
"history": "역사",
"pending": "대기 중",
"running": "실행 중",
"showFlatList": "평면 목록 표시"
},
"themeToggle": "테마 전환",
Expand Down
6 changes: 6 additions & 0 deletions src/locales/ru/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -574,14 +574,20 @@
"queue": "Очередь",
"queueTab": {
"backToAllTasks": "Вернуться ко всем задачам",
"cancelled": "Отменено",
"clearPendingTasks": "Очистить отложенные задачи",
"completed": "Завершено",
"containImagePreview": "Предпросмотр заливающего изображения",
"coverImagePreview": "Предпросмотр подходящего изображения",
"failed": "Не удалось",
"filter": "Фильтровать выводы",
"filters": {
"hideCached": "Скрыть кэшированные",
"hideCanceled": "Скрыть отмененные"
},
"history": "История",
"pending": "Ожидание",
"running": "Выполняется",
"showFlatList": "Показать плоский список"
},
"themeToggle": "Переключить тему",
Expand Down
6 changes: 6 additions & 0 deletions src/locales/zh/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -574,14 +574,20 @@
"queue": "队列",
"queueTab": {
"backToAllTasks": "返回",
"cancelled": "已取消",
"clearPendingTasks": "清除待处理任务",
"completed": "已完成",
"containImagePreview": "填充图像预览",
"coverImagePreview": "适应图像预览",
"failed": "失败",
"filter": "过滤输出",
"filters": {
"hideCached": "隐藏缓存",
"hideCanceled": "隐藏已取消"
},
"history": "历史",
"pending": "待处理",
"running": "运行中",
"showFlatList": "平铺结果"
},
"themeToggle": "主题切换",
Expand Down
4 changes: 4 additions & 0 deletions src/stores/queueStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,8 @@ export const useQueueStore = defineStore('queue', () => {
)

const hasPendingTasks = computed<boolean>(() => pendingTasks.value.length > 0)
const hasRunningTasks = computed<boolean>(() => runningTasks.value.length > 0)
const hasHistoryTasks = computed<boolean>(() => historyTasks.value.length > 0)

const update = async () => {
isLoading.value = true
Expand Down Expand Up @@ -423,6 +425,8 @@ export const useQueueStore = defineStore('queue', () => {
flatTasks,
lastHistoryQueueIndex,
hasPendingTasks,
hasRunningTasks,
hasHistoryTasks,

update,
clear,
Expand Down
Loading