Task output folder view (#579)

* Change to button

* Folder view
This commit is contained in:
Chenlei Hu
2024-08-21 16:47:30 -04:00
committed by GitHub
parent f2de9b0d3c
commit 5542845710
4 changed files with 102 additions and 51 deletions

View File

@@ -1,21 +1,32 @@
<template> <template>
<SideBarTabTemplate :title="$t('sideToolbar.queue')"> <SidebarTabTemplate :title="$t('sideToolbar.queue')">
<template #tool-buttons> <template #tool-buttons>
<Button <Button
:icon="isExpanded ? 'pi pi-chevron-up' : 'pi pi-chevron-down'" v-if="isInFolderView"
icon="pi pi-arrow-left"
text text
severity="secondary" severity="secondary"
@click="toggleExpanded" @click="exitFolderView"
class="toggle-expanded-button" class="back-button"
v-tooltip="$t('sideToolbar.queueTab.showFlatList')" v-tooltip="$t('sideToolbar.queueTab.backToAllTasks')"
/>
<Button
icon="pi pi-trash"
text
severity="primary"
@click="confirmRemoveAll($event)"
class="clear-all-button"
/> />
<template v-else>
<Button
:icon="isExpanded ? 'pi pi-chevron-up' : 'pi pi-chevron-down'"
text
severity="secondary"
@click="toggleExpanded"
class="toggle-expanded-button"
v-tooltip="$t('sideToolbar.queueTab.showFlatList')"
/>
<Button
icon="pi pi-trash"
text
severity="primary"
@click="confirmRemoveAll($event)"
class="clear-all-button"
/>
</template>
</template> </template>
<template #body> <template #body>
<div <div
@@ -28,9 +39,10 @@
v-for="task in visibleTasks" v-for="task in visibleTasks"
:key="task.key" :key="task.key"
:task="task" :task="task"
:isFlatTask="isExpanded" :isFlatTask="isExpanded || isInFolderView"
@contextmenu="handleContextMenu" @contextmenu="handleContextMenu"
@preview="handlePreview" @preview="handlePreview"
@taskOutputLengthClicked="enterFolderView($event)"
/> />
</div> </div>
<div ref="loadMoreTrigger" style="height: 1px" /> <div ref="loadMoreTrigger" style="height: 1px" />
@@ -43,7 +55,7 @@
/> />
</div> </div>
</template> </template>
</SideBarTabTemplate> </SidebarTabTemplate>
<ConfirmPopup /> <ConfirmPopup />
<ContextMenu ref="menu" :model="menuItems" /> <ContextMenu ref="menu" :model="menuItems" />
<ResultGallery <ResultGallery
@@ -64,7 +76,7 @@ import ContextMenu from 'primevue/contextmenu'
import type { MenuItem } from 'primevue/menuitem' import type { MenuItem } from 'primevue/menuitem'
import TaskItem from './queue/TaskItem.vue' import TaskItem from './queue/TaskItem.vue'
import ResultGallery from './queue/ResultGallery.vue' import ResultGallery from './queue/ResultGallery.vue'
import SideBarTabTemplate from './SidebarTabTemplate.vue' import SidebarTabTemplate from './SidebarTabTemplate.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue' import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import { TaskItemImpl, useQueueStore } from '@/stores/queueStore' import { TaskItemImpl, useQueueStore } from '@/stores/queueStore'
import { api } from '@/scripts/api' import { api } from '@/scripts/api'
@@ -74,17 +86,27 @@ const toast = useToast()
const queueStore = useQueueStore() const queueStore = useQueueStore()
const { t } = useI18n() const { t } = useI18n()
// Expanded view: show all outputs in a flat list.
const isExpanded = ref(false) const isExpanded = ref(false)
const visibleTasks = ref<TaskItemImpl[]>([]) const visibleTasks = ref<TaskItemImpl[]>([])
const scrollContainer = ref<HTMLElement | null>(null) const scrollContainer = ref<HTMLElement | null>(null)
const loadMoreTrigger = ref<HTMLElement | null>(null) const loadMoreTrigger = ref<HTMLElement | null>(null)
const galleryActiveIndex = ref(-1) const galleryActiveIndex = ref(-1)
// Folder view: only show outputs from a single selected task.
const folderTask = ref<TaskItemImpl | null>(null)
const isInFolderView = computed(() => folderTask.value !== null)
const ITEMS_PER_PAGE = 8 const ITEMS_PER_PAGE = 8
const SCROLL_THRESHOLD = 100 // pixels from bottom to trigger load const SCROLL_THRESHOLD = 100 // pixels from bottom to trigger load
const allTasks = computed(() => const allTasks = computed(() =>
isExpanded.value ? queueStore.flatTasks : queueStore.tasks isInFolderView.value
? folderTask.value
? folderTask.value.flatten()
: []
: isExpanded.value
? queueStore.flatTasks
: queueStore.tasks
) )
const allGalleryItems = computed(() => const allGalleryItems = computed(() =>
allTasks.value.flatMap((task: TaskItemImpl) => { allTasks.value.flatMap((task: TaskItemImpl) => {
@@ -129,9 +151,13 @@ useResizeObserver(scrollContainer, () => {
}) })
}) })
const updateVisibleTasks = () => {
visibleTasks.value = allTasks.value.slice(0, ITEMS_PER_PAGE)
}
const toggleExpanded = () => { const toggleExpanded = () => {
isExpanded.value = !isExpanded.value isExpanded.value = !isExpanded.value
visibleTasks.value = allTasks.value.slice(0, ITEMS_PER_PAGE) updateVisibleTasks()
} }
const removeTask = (task: TaskItemImpl) => { const removeTask = (task: TaskItemImpl) => {
@@ -173,7 +199,7 @@ const confirmRemoveAll = (event: Event) => {
const onStatus = async () => { const onStatus = async () => {
await queueStore.update() await queueStore.update()
visibleTasks.value = allTasks.value.slice(0, ITEMS_PER_PAGE) updateVisibleTasks()
} }
const menu = ref(null) const menu = ref(null)
@@ -182,7 +208,8 @@ const menuItems = computed<MenuItem[]>(() => [
{ {
label: t('delete'), label: t('delete'),
icon: 'pi pi-trash', icon: 'pi pi-trash',
command: () => menuTargetTask.value && removeTask(menuTargetTask.value) command: () => menuTargetTask.value && removeTask(menuTargetTask.value),
disabled: isExpanded.value || isInFolderView.value
}, },
{ {
label: t('loadWorkflow'), label: t('loadWorkflow'),
@@ -208,6 +235,16 @@ const handlePreview = (task: TaskItemImpl) => {
) )
} }
const enterFolderView = (task: TaskItemImpl) => {
folderTask.value = task
updateVisibleTasks()
}
const exitFolderView = () => {
folderTask.value = null
updateVisibleTasks()
}
onMounted(() => { onMounted(() => {
api.addEventListener('status', onStatus) api.addEventListener('status', onStatus)
queueStore.update() queueStore.update()
@@ -225,7 +262,7 @@ watch(
visibleTasks.value.length === 0 || visibleTasks.value.length === 0 ||
visibleTasks.value.length > newTasks.length visibleTasks.value.length > newTasks.length
) { ) {
visibleTasks.value = newTasks.slice(0, ITEMS_PER_PAGE) updateVisibleTasks()
} }
nextTick(() => { nextTick(() => {

View File

@@ -38,15 +38,20 @@
</Tag> </Tag>
</div> </div>
<div class="tag-wrapper"> <div class="tag-wrapper">
<Tag v-if="task.isHistory && flatOutputs.length > 1"> <Button
<span>{{ flatOutputs.length }}</span> v-if="task.isHistory && flatOutputs.length > 1"
</Tag> outlined
@click="handleOutputLengthClick"
>
<span style="font-weight: bold">{{ flatOutputs.length }}</span>
</Button>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Button from 'primevue/button'
import Tag from 'primevue/tag' import Tag from 'primevue/tag'
import ResultItem from './ResultItem.vue' import ResultItem from './ResultItem.vue'
import { TaskItemDisplayStatus, type TaskItemImpl } from '@/stores/queueStore' import { TaskItemDisplayStatus, type TaskItemImpl } from '@/stores/queueStore'
@@ -61,6 +66,7 @@ const flatOutputs = props.task.flatOutputs
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'contextmenu', value: { task: TaskItemImpl; event: MouseEvent }): void (e: 'contextmenu', value: { task: TaskItemImpl; event: MouseEvent }): void
(e: 'preview', value: TaskItemImpl): void (e: 'preview', value: TaskItemImpl): void
(e: 'task-output-length-clicked', value: TaskItemImpl): void
}>() }>()
const handleContextMenu = (e: MouseEvent) => { const handleContextMenu = (e: MouseEvent) => {
@@ -71,6 +77,10 @@ const handlePreview = () => {
emit('preview', props.task) emit('preview', props.task)
} }
const handleOutputLengthClick = () => {
emit('task-output-length-clicked', props.task)
}
const taskTagSeverity = (status: TaskItemDisplayStatus) => { const taskTagSeverity = (status: TaskItemDisplayStatus) => {
switch (status) { switch (status) {
case TaskItemDisplayStatus.Pending: case TaskItemDisplayStatus.Pending:

View File

@@ -25,7 +25,8 @@ const messages = {
sortOrder: 'Sort Order' sortOrder: 'Sort Order'
}, },
queueTab: { queueTab: {
showFlatList: 'Show Flat List' showFlatList: 'Show Flat List',
backToAllTasks: 'Back to All Tasks'
} }
} }
}, },
@@ -51,7 +52,8 @@ const messages = {
sortOrder: '排序顺序' sortOrder: '排序顺序'
}, },
queueTab: { queueTab: {
showFlatList: '平铺结果' showFlatList: '平铺结果',
backToAllTasks: '返回'
} }
} }
} }

View File

@@ -219,6 +219,33 @@ export class TaskItemImpl {
app.nodeOutputs = toRaw(this.outputs) app.nodeOutputs = toRaw(this.outputs)
} }
} }
public flatten(): TaskItemImpl[] {
if (this.displayStatus !== TaskItemDisplayStatus.Completed) {
return [this]
}
return this.flatOutputs.map(
(output: ResultItemImpl, i: number) =>
new TaskItemImpl(
this.taskType,
[
this.queueIndex,
`${this.promptId}-${i}`,
this.promptInputs,
this.extraData,
this.outputsToExecute
],
this.status,
{
[output.nodeId]: {
[output.mediaType]: [output]
}
},
[output]
)
)
}
} }
interface State { interface State {
@@ -244,32 +271,7 @@ export const useQueueStore = defineStore('queue', {
] ]
}, },
flatTasks(): TaskItemImpl[] { flatTasks(): TaskItemImpl[] {
return this.tasks.flatMap((task: TaskItemImpl) => { return this.tasks.flatMap((task: TaskItemImpl) => task.flatten())
if (task.displayStatus !== TaskItemDisplayStatus.Completed) {
return [task]
}
return task.flatOutputs.map(
(output: ResultItemImpl, i: number) =>
new TaskItemImpl(
task.taskType,
[
task.queueIndex,
`${task.promptId}-${i}`,
task.promptInputs,
task.extraData,
task.outputsToExecute
],
task.status,
{
[output.nodeId]: {
[output.mediaType]: [output]
}
},
[output]
)
)
})
}, },
lastHistoryQueueIndex(state) { lastHistoryQueueIndex(state) {
return state.historyTasks.length ? state.historyTasks[0].queueIndex : -1 return state.historyTasks.length ? state.historyTasks[0].queueIndex : -1