mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
Rearrange workflow tabs (#2026)
This commit is contained in:
108
src/components/topbar/WorkflowTab.vue
Normal file
108
src/components/topbar/WorkflowTab.vue
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex p-2 gap-2 workflow-tab" ref="workflowTabRef" v-bind="$attrs">
|
||||||
|
<span
|
||||||
|
class="workflow-label text-sm max-w-[150px] truncate inline-block"
|
||||||
|
v-tooltip.bottom="workflowOption.workflow.key"
|
||||||
|
>
|
||||||
|
{{ workflowOption.workflow.filename }}
|
||||||
|
</span>
|
||||||
|
<div class="relative">
|
||||||
|
<span
|
||||||
|
class="status-indicator"
|
||||||
|
v-if="
|
||||||
|
!workspaceStore.shiftDown &&
|
||||||
|
(workflowOption.workflow.isModified ||
|
||||||
|
!workflowOption.workflow.isPersisted)
|
||||||
|
"
|
||||||
|
>•</span
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
class="close-button p-0 w-auto"
|
||||||
|
icon="pi pi-times"
|
||||||
|
text
|
||||||
|
severity="secondary"
|
||||||
|
size="small"
|
||||||
|
@click.stop="onCloseWorkflow(workflowOption)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ComfyWorkflow } from '@/stores/workflowStore'
|
||||||
|
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||||
|
import Button from 'primevue/button'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { workflowService } from '@/services/workflowService'
|
||||||
|
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||||
|
import { usePragmaticDraggable, usePragmaticDroppable } from '@/hooks/dndHooks'
|
||||||
|
|
||||||
|
interface WorkflowOption {
|
||||||
|
value: string
|
||||||
|
workflow: ComfyWorkflow
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: string
|
||||||
|
workflowOption: WorkflowOption
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const workspaceStore = useWorkspaceStore()
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
|
const workflowTabRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
const closeWorkflows = async (options: WorkflowOption[]) => {
|
||||||
|
for (const opt of options) {
|
||||||
|
if (
|
||||||
|
!(await workflowService.closeWorkflow(opt.workflow, {
|
||||||
|
warnIfUnsaved: !workspaceStore.shiftDown
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
// User clicked cancel
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCloseWorkflow = (option: WorkflowOption) => {
|
||||||
|
closeWorkflows([option])
|
||||||
|
}
|
||||||
|
const tabGetter = () => workflowTabRef.value as HTMLElement
|
||||||
|
|
||||||
|
usePragmaticDraggable(tabGetter, {
|
||||||
|
getInitialData: () => {
|
||||||
|
return {
|
||||||
|
workflowKey: props.workflowOption.workflow.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
usePragmaticDroppable(tabGetter, {
|
||||||
|
getData: () => {
|
||||||
|
return {
|
||||||
|
workflowKey: props.workflowOption.workflow.key
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDrop: (e) => {
|
||||||
|
const fromIndex = workflowStore.openWorkflows.findIndex(
|
||||||
|
(wf) => wf.key === e.source.data.workflowKey
|
||||||
|
)
|
||||||
|
const toIndex = workflowStore.openWorkflows.findIndex(
|
||||||
|
(wf) => wf.key === e.location.current.dropTargets[0]?.data.workflowKey
|
||||||
|
)
|
||||||
|
if (fromIndex !== toIndex) {
|
||||||
|
workflowStore.reorderWorkflows(fromIndex, toIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.status-indicator {
|
||||||
|
@apply absolute font-bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -9,36 +9,11 @@
|
|||||||
dataKey="value"
|
dataKey="value"
|
||||||
>
|
>
|
||||||
<template #option="{ option }">
|
<template #option="{ option }">
|
||||||
<div
|
<WorkflowTab
|
||||||
class="flex p-2 gap-2"
|
|
||||||
@contextmenu="showContextMenu($event, option)"
|
@contextmenu="showContextMenu($event, option)"
|
||||||
@click.middle="onCloseWorkflow(option)"
|
@click.middle="onCloseWorkflow(option)"
|
||||||
>
|
:workflow-option="option"
|
||||||
<span
|
/>
|
||||||
class="workflow-label text-sm max-w-[150px] truncate inline-block"
|
|
||||||
v-tooltip.bottom="option.workflow.key"
|
|
||||||
>
|
|
||||||
{{ option.workflow.filename }}
|
|
||||||
</span>
|
|
||||||
<div class="relative">
|
|
||||||
<span
|
|
||||||
class="status-indicator"
|
|
||||||
v-if="
|
|
||||||
!workspaceStore.shiftDown &&
|
|
||||||
(option.workflow.isModified || !option.workflow.isPersisted)
|
|
||||||
"
|
|
||||||
>•</span
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
class="close-button p-0 w-auto"
|
|
||||||
icon="pi pi-times"
|
|
||||||
text
|
|
||||||
severity="secondary"
|
|
||||||
size="small"
|
|
||||||
@click.stop="onCloseWorkflow(option)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</SelectButton>
|
</SelectButton>
|
||||||
<Button
|
<Button
|
||||||
@@ -52,6 +27,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import WorkflowTab from '@/components/topbar/WorkflowTab.vue'
|
||||||
import { ComfyWorkflow } from '@/stores/workflowStore'
|
import { ComfyWorkflow } from '@/stores/workflowStore'
|
||||||
import { useWorkflowStore } from '@/stores/workflowStore'
|
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||||
import { useCommandStore } from '@/stores/commandStore'
|
import { useCommandStore } from '@/stores/commandStore'
|
||||||
@@ -78,11 +54,6 @@ const workflowStore = useWorkflowStore()
|
|||||||
const rightClickedTab = ref<WorkflowOption>(null)
|
const rightClickedTab = ref<WorkflowOption>(null)
|
||||||
const menu = ref()
|
const menu = ref()
|
||||||
|
|
||||||
const showContextMenu = (event, option) => {
|
|
||||||
rightClickedTab.value = option
|
|
||||||
menu.value.show(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflowToOption = (workflow: ComfyWorkflow): WorkflowOption => ({
|
const workflowToOption = (workflow: ComfyWorkflow): WorkflowOption => ({
|
||||||
value: workflow.path,
|
value: workflow.path,
|
||||||
workflow
|
workflow
|
||||||
@@ -126,6 +97,10 @@ const onCloseWorkflow = (option: WorkflowOption) => {
|
|||||||
closeWorkflows([option])
|
closeWorkflows([option])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showContextMenu = (event, option) => {
|
||||||
|
rightClickedTab.value = option
|
||||||
|
menu.value.show(event)
|
||||||
|
}
|
||||||
const contextMenuItems = computed(() => {
|
const contextMenuItems = computed(() => {
|
||||||
const tab = rightClickedTab.value as WorkflowOption
|
const tab = rightClickedTab.value as WorkflowOption
|
||||||
if (!tab) return []
|
if (!tab) return []
|
||||||
@@ -166,7 +141,6 @@ const contextMenuItems = computed(() => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const commandStore = useCommandStore()
|
const commandStore = useCommandStore()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -189,14 +163,6 @@ const commandStore = useCommandStore()
|
|||||||
@apply visible;
|
@apply visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-indicator {
|
|
||||||
@apply absolute font-bold;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.p-togglebutton:hover) .status-indicator {
|
:deep(.p-togglebutton:hover) .status-indicator {
|
||||||
@apply hidden;
|
@apply hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ export interface WorkflowStore {
|
|||||||
modifiedWorkflows: ComfyWorkflow[]
|
modifiedWorkflows: ComfyWorkflow[]
|
||||||
getWorkflowByPath: (path: string) => ComfyWorkflow | null
|
getWorkflowByPath: (path: string) => ComfyWorkflow | null
|
||||||
syncWorkflows: (dir?: string) => Promise<void>
|
syncWorkflows: (dir?: string) => Promise<void>
|
||||||
|
reorderWorkflows: (from: number, to: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useWorkflowStore = defineStore('workflow', () => {
|
export const useWorkflowStore = defineStore('workflow', () => {
|
||||||
@@ -202,6 +203,11 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
|||||||
const openWorkflows = computed(() =>
|
const openWorkflows = computed(() =>
|
||||||
openWorkflowPaths.value.map((path) => workflowLookup.value[path])
|
openWorkflowPaths.value.map((path) => workflowLookup.value[path])
|
||||||
)
|
)
|
||||||
|
const reorderWorkflows = (from: number, to: number) => {
|
||||||
|
const movedTab = openWorkflowPaths.value[from]
|
||||||
|
openWorkflowPaths.value.splice(from, 1)
|
||||||
|
openWorkflowPaths.value.splice(to, 0, movedTab)
|
||||||
|
}
|
||||||
const isOpen = (workflow: ComfyWorkflow) =>
|
const isOpen = (workflow: ComfyWorkflow) =>
|
||||||
openWorkflowPathSet.value.has(workflow.path)
|
openWorkflowPathSet.value.has(workflow.path)
|
||||||
|
|
||||||
@@ -388,6 +394,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
|||||||
renameWorkflow,
|
renameWorkflow,
|
||||||
deleteWorkflow,
|
deleteWorkflow,
|
||||||
saveWorkflow,
|
saveWorkflow,
|
||||||
|
reorderWorkflows,
|
||||||
|
|
||||||
workflows,
|
workflows,
|
||||||
bookmarkedWorkflows,
|
bookmarkedWorkflows,
|
||||||
|
|||||||
Reference in New Issue
Block a user