mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-06 16:10:09 +00:00
* Basic tab switching * Closing tabs * Style buttons * wip * Fix scroll style * Add setting * Add playwright test * Add unsaved status * nit
230 lines
7.2 KiB
Vue
230 lines
7.2 KiB
Vue
<template>
|
|
<SidebarTabTemplate :title="$t('sideToolbar.workflows')">
|
|
<template #tool-buttons>
|
|
<Button
|
|
class="browse-templates-button"
|
|
icon="pi pi-th-large"
|
|
v-tooltip="$t('sideToolbar.browseTemplates')"
|
|
text
|
|
@click="() => commandStore.getCommand('Comfy.BrowseTemplates')()"
|
|
/>
|
|
<Button
|
|
class="browse-workflows-button"
|
|
icon="pi pi-folder-open"
|
|
v-tooltip="'Browse for an image or exported workflow'"
|
|
text
|
|
@click="() => commandStore.getCommand('Comfy.OpenWorkflow')()"
|
|
/>
|
|
<Button
|
|
class="new-default-workflow-button"
|
|
icon="pi pi-code"
|
|
v-tooltip="'Load default workflow'"
|
|
text
|
|
@click="() => commandStore.getCommand('Comfy.LoadDefaultWorkflow')()"
|
|
/>
|
|
<Button
|
|
class="new-blank-workflow-button"
|
|
icon="pi pi-plus"
|
|
v-tooltip="'Create a new blank workflow'"
|
|
@click="() => commandStore.getCommand('Comfy.NewBlankWorkflow')()"
|
|
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"
|
|
v-if="workflowTabsPosition === 'Sidebar'"
|
|
>
|
|
<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 { useCommandStore } from '@/stores/commandStore'
|
|
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'
|
|
import { useSettingStore } from '@/stores/settingStore'
|
|
|
|
const settingStore = useSettingStore()
|
|
const workflowTabsPosition = computed(() =>
|
|
settingStore.get('Comfy.Workflow.WorkflowTabsPosition')
|
|
)
|
|
|
|
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
|
|
}
|
|
const lowerQuery = query.toLocaleLowerCase()
|
|
filteredWorkflows.value = workflowStore.workflows.filter((workflow) => {
|
|
return workflow.name.toLocaleLowerCase().includes(lowerQuery)
|
|
})
|
|
nextTick(() => {
|
|
expandNode(filteredRoot.value)
|
|
})
|
|
}
|
|
|
|
const commandStore = useCommandStore()
|
|
|
|
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>
|