Skip to content

Commit

Permalink
Enhancements to the queue image feed
Browse files Browse the repository at this point in the history
 - Change flat list icon
 - Add cover/contain mode
 - Add right click -> go to node
 - Add go to node link on detail
  • Loading branch information
pythongosssss authored and huchenlei committed Aug 27, 2024
1 parent 84662ad commit 255a7fe
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 28 deletions.
71 changes: 60 additions & 11 deletions src/components/common/ComfyImage.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
<!-- A image with placeholder fallback on error -->
<template>
<img
:src="src"
@error="handleImageError"
:class="[{ 'broken-image': imageBroken }, ...classArray]"
/>
<span
v-if="!imageBroken"
class="comfy-image-wrap"
:class="[{ contain: contain }]"
>
<img
v-if="contain"
:src="src"
:data-test="src"
class="comfy-image-blur"
:style="{ 'background-image': `url(${src})` }"
/>
<img
:src="src"
@error="handleImageError"
class="comfy-image-main"
:class="[...classArray]"
/>
</span>
<div v-if="imageBroken" class="broken-image-placeholder">
<i class="pi pi-image"></i>
<span>{{ $t('imageFailedToLoad') }}</span>
Expand All @@ -14,10 +28,16 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
const props = defineProps<{
src: string
class?: string | string[] | object
}>()
const props = withDefaults(
defineProps<{
src: string
class?: string | string[] | object
contain: boolean
}>(),
{
contain: false
}
)
const imageBroken = ref(false)
const handleImageError = (e: Event) => {
Expand All @@ -37,8 +57,37 @@ const classArray = computed(() => {
</script>

<style scoped>
.broken-image {
display: none;
.comfy-image-wrap {
display: contents;
}
.comfy-image-blur {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.comfy-image-main {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
z-index: 1;
}
.contain .comfy-image-wrap {
position: relative;
width: 100%;
height: 100%;
}
.contain .comfy-image-main {
object-fit: contain;
backdrop-filter: blur(10px);
position: absolute;
}
.broken-image-placeholder {
Expand Down
35 changes: 33 additions & 2 deletions src/components/sidebar/tabs/QueueSidebarTab.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
<template>
<SidebarTabTemplate :title="$t('sideToolbar.queue')">
<template #tool-buttons>
<Button
:icon="
imageFit === 'cover'
? 'pi pi-arrow-down-left-and-arrow-up-right-to-center'
: 'pi pi-arrow-up-right-and-arrow-down-left-from-center'
"
text
severity="secondary"
@click="toggleImageFit"
class="toggle-expanded-button"
v-tooltip="$t(`sideToolbar.queueTab.${imageFit}ImagePreview`)"
/>
<Button
v-if="isInFolderView"
icon="pi pi-arrow-left"
Expand All @@ -12,7 +24,7 @@
/>
<template v-else>
<Button
:icon="isExpanded ? 'pi pi-chevron-up' : 'pi pi-chevron-down'"
:icon="isExpanded ? 'pi pi-images' : 'pi pi-image'"
text
severity="secondary"
@click="toggleExpanded"
Expand Down Expand Up @@ -80,10 +92,14 @@ import SidebarTabTemplate from './SidebarTabTemplate.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import { TaskItemImpl, useQueueStore } from '@/stores/queueStore'
import { api } from '@/scripts/api'
import { ComfyNode } from '@/types/comfyWorkflow'
import { useSettingStore } from '@/stores/settingStore'
const IMAGE_FIT = 'Comfy.Queue.ImageFit'
const confirm = useConfirm()
const toast = useToast()
const queueStore = useQueueStore()
const settingStore = useSettingStore()
const { t } = useI18n()
// Expanded view: show all outputs in a flat list.
Expand All @@ -95,6 +111,7 @@ 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 imageFit = computed<string>(() => settingStore.get(IMAGE_FIT))
const ITEMS_PER_PAGE = 8
const SCROLL_THRESHOLD = 100 // pixels from bottom to trigger load
Expand Down Expand Up @@ -204,6 +221,7 @@ const onStatus = async () => {
const menu = ref(null)
const menuTargetTask = ref<TaskItemImpl | null>(null)
const menuTargetNode = ref<ComfyNode | null>(null)
const menuItems = computed<MenuItem[]>(() => [
{
label: t('delete'),
Expand All @@ -215,17 +233,26 @@ const menuItems = computed<MenuItem[]>(() => [
label: t('loadWorkflow'),
icon: 'pi pi-file-export',
command: () => menuTargetTask.value?.loadWorkflow()
},
{
label: t('goToNode'),
icon: 'pi pi-arrow-circle-right',
command: () => menuTargetTask.value?.goToNode(menuTargetNode.value),
visible: !!menuTargetNode.value
}
])
const handleContextMenu = ({
task,
event
event,
node
}: {
task: TaskItemImpl
event: Event
node?: ComfyNode
}) => {
menuTargetTask.value = task
menuTargetNode.value = node
menu.value?.show(event)
}
Expand All @@ -245,6 +272,10 @@ const exitFolderView = () => {
updateVisibleTasks()
}
const toggleImageFit = () => {
settingStore.set(IMAGE_FIT, imageFit.value === 'cover' ? 'contain' : 'cover')
}
onMounted(() => {
api.addEventListener('status', onStatus)
queueStore.update()
Expand Down
7 changes: 6 additions & 1 deletion src/components/sidebar/tabs/queue/ResultGallery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
:showThumbnails="false"
>
<template #item="{ item }">
<ComfyImage :key="item.url" :src="item.url" class="galleria-image" />
<ComfyImage
:key="item.url"
:src="item.url"
:contain="false"
class="galleria-image"
/>
</template>
</Galleria>
</template>
Expand Down
21 changes: 12 additions & 9 deletions src/components/sidebar/tabs/queue/ResultItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
<template
v-if="result.mediaType === 'images' || result.mediaType === 'gifs'"
>
<ComfyImage
:src="result.url"
class="task-output-image"
:contain="imageFit === 'contain'"
/>
<div class="image-preview-mask">
<Button
icon="pi pi-eye"
Expand All @@ -11,7 +16,6 @@
rounded
/>
</div>
<ComfyImage :src="result.url" class="task-output-image" />
</template>
<!-- TODO: handle more media types -->
<div v-else class="task-result-preview">
Expand All @@ -25,7 +29,8 @@
import { ResultItemImpl } from '@/stores/queueStore'
import ComfyImage from '@/components/common/ComfyImage.vue'
import Button from 'primevue/button'
import { onMounted, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useSettingStore } from '@/stores/settingStore'
const props = defineProps<{
result: ResultItemImpl
Expand All @@ -36,6 +41,10 @@ const emit = defineEmits<{
}>()
const resultContainer = ref<HTMLElement | null>(null)
const settingStore = useSettingStore()
const imageFit = computed<string>(() =>
settingStore.get('Comfy.Queue.ImageFit')
)
onMounted(() => {
if (props.result.mediaType === 'images') {
Expand All @@ -58,13 +67,6 @@ onMounted(() => {
align-items: center;
}
:deep(.task-output-image) {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.image-preview-mask {
position: absolute;
left: 50%;
Expand All @@ -75,6 +77,7 @@ onMounted(() => {
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 1;
}
.result-container:hover .image-preview-mask {
Expand Down
20 changes: 17 additions & 3 deletions src/components/sidebar/tabs/queue/TaskItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@
<div class="task-item-details">
<div class="tag-wrapper status-tag-group">
<Tag v-if="isFlatTask && task.isHistory" class="node-name-tag">
{{ node?.type }} (#{{ node?.id }})
<Button
class="task-node-link"
:label="`${node?.type} (#${node?.id})`"
link
size="small"
@click="task.goToNode(node?.id)"
/>
</Tag>
<Tag :severity="taskTagSeverity(task.displayStatus)">
<span v-html="taskStatusText(task.displayStatus)"></span>
Expand Down Expand Up @@ -77,13 +83,16 @@ const node: ComfyNode | null = flatOutputs.length
: null
const emit = defineEmits<{
(e: 'contextmenu', value: { task: TaskItemImpl; event: MouseEvent }): void
(
e: 'contextmenu',
value: { task: TaskItemImpl; event: MouseEvent; node?: ComfyNode }
): void
(e: 'preview', value: TaskItemImpl): void
(e: 'task-output-length-clicked', value: TaskItemImpl): void
}>()
const handleContextMenu = (e: MouseEvent) => {
emit('contextmenu', { task: props.task, event: e })
emit('contextmenu', { task: props.task, event: e, node })
}
const handlePreview = () => {
Expand Down Expand Up @@ -164,6 +173,11 @@ const formatTime = (time?: number) => {
justify-content: space-between;
align-items: center;
width: 100%;
z-index: 1;
}
.task-node-link {
padding: 2px;
}
/* In dark mode, transparent background color for tags is not ideal for tags that
Expand Down
5 changes: 4 additions & 1 deletion src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const messages = {
experimental: 'BETA',
deprecated: 'DEPR',
loadWorkflow: 'Load Workflow',
goToNode: 'Go to Node',
settings: 'Settings',
searchSettings: 'Search Settings',
searchNodes: 'Search Nodes',
Expand All @@ -45,7 +46,9 @@ const messages = {
},
queueTab: {
showFlatList: 'Show Flat List',
backToAllTasks: 'Back to All Tasks'
backToAllTasks: 'Back to All Tasks',
containImagePreview: 'Fill Image Preview',
coverImagePreview: 'Fit Image Preview'
}
}
},
Expand Down
9 changes: 8 additions & 1 deletion src/stores/queueStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
TaskOutput,
ResultItem
} from '@/types/apiTypes'
import type { NodeId } from '@/types/comfyWorkflow'
import type { ComfyNode, NodeId } from '@/types/comfyWorkflow'
import { plainToClass } from 'class-transformer'
import _ from 'lodash'
import { defineStore } from 'pinia'
Expand Down Expand Up @@ -220,6 +220,13 @@ export class TaskItemImpl {
}
}

public goToNode(node: ComfyNode | string | number) {
const id = typeof node === 'object' ? node.id : node
const graphNode = app.graph.getNodeById(id as number)
if (!graphNode) return
app.canvas.centerOnNode(graphNode)
}

public flatten(): TaskItemImpl[] {
if (this.displayStatus !== TaskItemDisplayStatus.Completed) {
return [this]
Expand Down
8 changes: 8 additions & 0 deletions src/stores/settingStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,14 @@ export const useSettingStore = defineStore('setting', {
type: 'hidden',
defaultValue: {}
})

// Hidden setting used by the queue for how to fit images
app.ui.settings.addSetting({
id: 'Comfy.Queue.ImageFit',
name: 'Queue image fit',
type: 'hidden',
defaultValue: 'cover'
})
},

set<K extends keyof Settings>(key: K, value: Settings[K]) {
Expand Down

0 comments on commit 255a7fe

Please sign in to comment.