Files
ComfyUI_frontend/src/components/sidebar/tabs/WorkflowsSidebarTab.vue
Chenlei Hu f4d4cc3439 Move workflow dropdown to sidebar tab (#893)
* Initial move to sidebar

Remove broken CSS

Move action buttons

Migrate open workflows

Add basic browse

WIP

Add insert support

Remove legacy workflow manager

Remove unused CSS

Reorder

Remove legacy workflow UI

nit

* Support bookmark

Add workflow bookmark store

nit

Add back bookmark functionality

Correctly load bookmarks

nit

Fix many other issues

Fix this binding

style divider

* Extract tree leaf component

* Hide bookmark section when no bookmarks

* nit

* Fix save

* Add workflows searchbox

* Add search support

* Show total opened

* Add basic test

* Add more tests

* Fix redo/undo test

* Temporarily disable browser tab title test
2024-09-25 16:01:50 +09:00

226 lines
6.6 KiB
Vue

<template>
<SidebarTabTemplate :title="$t('sideToolbar.workflows')">
<template #tool-buttons>
<Button
class="browse-workflows-button"
icon="pi pi-folder-open"
v-tooltip="'Browse for an image or exported workflow'"
text
@click="browse"
/>
<Button
class="new-default-workflow-button"
icon="pi pi-code"
v-tooltip="'Load default workflow'"
text
@click="loadDefault"
/>
<Button
class="new-blank-workflow-button"
icon="pi pi-plus"
v-tooltip="'Create a new blank workflow'"
@click="createBlank"
text
/>
</template>
<template #body>
<SearchBox
class="workflows-search-box mx-4 my-4"
v-model:modelValue="searchQuery"
@search="handleSearch"
:placeholder="$t('searchWorkflows') + '...'"
/>
<div class="comfyui-workflows-panel" v-if="!isSearching">
<div class="comfyui-workflows-open">
<TextDivider text="Open" type="dashed" class="ml-2" />
<TreeExplorer
:roots="renderTreeNode(workflowStore.openWorkflowsTree).children"
v-model:selectionKeys="selectionKeys"
>
<template #node="{ node }">
<TreeExplorerTreeNode :node="node">
<template #before-label="{ node }">
<span v-if="node.data.unsaved">*</span>
</template>
<template #actions="{ node }">
<Button
icon="pi pi-times"
text
severity="secondary"
size="small"
@click.stop="app.workflowManager.closeWorkflow(node.data)"
/>
</template>
</TreeExplorerTreeNode>
</template>
</TreeExplorer>
</div>
<div
class="comfyui-workflows-bookmarks"
v-show="workflowStore.bookmarkedWorkflows.length > 0"
>
<TextDivider text="Bookmarks" type="dashed" class="ml-2" />
<TreeExplorer
:roots="
renderTreeNode(workflowStore.bookmarkedWorkflowsTree).children
"
>
<template #node="{ node }">
<WorkflowTreeLeaf :node="node" />
</template>
</TreeExplorer>
</div>
<div class="comfyui-workflows-browse">
<TextDivider text="Browse" type="dashed" class="ml-2" />
<TreeExplorer
:roots="renderTreeNode(workflowStore.workflowsTree).children"
v-model:expandedKeys="expandedKeys"
>
<template #node="{ node }">
<WorkflowTreeLeaf :node="node" />
</template>
</TreeExplorer>
</div>
</div>
<div class="comfyui-workflows-search-panel" v-else>
<TreeExplorer
:roots="renderTreeNode(filteredRoot).children"
v-model:expandedKeys="expandedKeys"
>
<template #node="{ node }">
<WorkflowTreeLeaf :node="node" />
</template>
</TreeExplorer>
</div>
</template>
</SidebarTabTemplate>
</template>
<script setup lang="ts">
import SearchBox from '@/components/common/SearchBox.vue'
import WorkflowTreeLeaf from '@/components/sidebar/tabs/workflows/WorkflowTreeLeaf.vue'
import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue'
import TreeExplorer from '@/components/common/TreeExplorer.vue'
import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
import Button from 'primevue/button'
import TextDivider from '@/components/common/TextDivider.vue'
import { app } from '@/scripts/app'
import { computed, nextTick, ref } from 'vue'
import { useWorkflowStore } from '@/stores/workflowStore'
import type { TreeNode } from 'primevue/treenode'
import { TreeExplorerNode } from '@/types/treeExplorerTypes'
import { ComfyWorkflow } from '@/scripts/workflows'
import { useI18n } from 'vue-i18n'
import { useTreeExpansion } from '@/hooks/treeHooks'
const searchQuery = ref('')
const isSearching = computed(() => searchQuery.value.length > 0)
const filteredWorkflows = ref<ComfyWorkflow[]>([])
const filteredRoot = computed<TreeNode>(() => {
return workflowStore.buildWorkflowTree(
filteredWorkflows.value as ComfyWorkflow[]
)
})
const handleSearch = (query: string) => {
if (query.length === 0) {
filteredWorkflows.value = []
expandedKeys.value = {}
return
}
filteredWorkflows.value = workflowStore.workflows.filter((workflow) => {
return workflow.name.includes(query)
})
nextTick(() => {
expandNode(filteredRoot.value)
})
}
const loadDefault = () => {
app.loadGraphData()
app.resetView()
}
const browse = () => {
app.ui.loadFile()
}
const createBlank = () => {
app.workflowManager.setWorkflow(null)
app.clean()
app.graph.clear()
app.workflowManager.activeWorkflow.track()
}
const workflowStore = useWorkflowStore()
const { t } = useI18n()
const expandedKeys = ref<Record<string, boolean>>({})
const { expandNode, toggleNodeOnEvent } = useTreeExpansion(expandedKeys)
const renderTreeNode = (node: TreeNode): TreeExplorerNode<ComfyWorkflow> => {
const children = node.children?.map(renderTreeNode)
const workflow: ComfyWorkflow = node.data
const handleClick = (
node: TreeExplorerNode<ComfyWorkflow>,
e: MouseEvent
) => {
if (node.leaf) {
const workflow = node.data
workflow.load()
} else {
toggleNodeOnEvent(e, node)
}
}
const actions = node.leaf
? {
handleClick,
handleRename: (
node: TreeExplorerNode<ComfyWorkflow>,
newName: string
) => {
const workflow = node.data
workflow.rename(newName)
},
handleDelete: workflow.isTemporary
? undefined
: (node: TreeExplorerNode<ComfyWorkflow>) => {
const workflow = node.data
workflow.delete()
},
contextMenuItems: (node: TreeExplorerNode<ComfyWorkflow>) => {
return [
{
label: t('insert'),
icon: 'pi pi-file-export',
command: () => {
const workflow = node.data
workflow.insert()
}
}
]
}
}
: { handleClick }
return {
key: node.key,
label: node.label,
leaf: node.leaf,
data: node.data,
children,
...actions
}
}
const selectionKeys = computed(() => ({
[`root/${workflowStore.activeWorkflow?.name}.json`]: true
}))
</script>
<style scoped>
:deep(.comfy-vue-side-bar-body) {
background: var(--p-tree-background);
}
</style>