mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-15 09:57:33 +00:00
Compare commits
1 Commits
fix/load-a
...
workflow-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66fa344367 |
@@ -9,6 +9,15 @@
|
||||
<slot name="alt-title" />
|
||||
</template>
|
||||
<template #tool-buttons>
|
||||
<Button
|
||||
v-tooltip.bottom="$t('g.newFolder')"
|
||||
variant="muted-textonly"
|
||||
size="icon"
|
||||
:aria-label="$t('g.newFolder')"
|
||||
@click="browseTreeRef?.addFolderCommand('root')"
|
||||
>
|
||||
<i class="icon-[lucide--folder-plus] size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
v-tooltip.bottom="$t('g.refresh')"
|
||||
variant="muted-textonly"
|
||||
@@ -107,7 +116,7 @@
|
||||
class="ml-2"
|
||||
/>
|
||||
<TreeExplorer
|
||||
v-if="filteredPersistedWorkflows.length > 0"
|
||||
ref="browseTreeRef"
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
:root="renderTreeNode(workflowsTree, WorkflowTreeType.Browse)"
|
||||
:selection-keys="selectionKeys"
|
||||
@@ -116,7 +125,10 @@
|
||||
<WorkflowTreeLeaf :node="node" />
|
||||
</template>
|
||||
</TreeExplorer>
|
||||
<slot v-else name="empty-state">
|
||||
<slot
|
||||
v-if="filteredPersistedWorkflows.length === 0"
|
||||
name="empty-state"
|
||||
>
|
||||
<NoResultsPlaceholder
|
||||
icon="pi pi-folder"
|
||||
:title="$t('g.empty')"
|
||||
@@ -162,7 +174,11 @@ import {
|
||||
useWorkflowStore
|
||||
} from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import type { TreeExplorerNode, TreeNode } from '@/types/treeExplorerTypes'
|
||||
import type {
|
||||
TreeExplorerDragAndDropData,
|
||||
TreeExplorerNode,
|
||||
TreeNode
|
||||
} from '@/types/treeExplorerTypes'
|
||||
import { ensureWorkflowSuffix, getWorkflowSuffix } from '@/utils/formatUtil'
|
||||
import { buildTree, sortedTree } from '@/utils/treeUtil'
|
||||
|
||||
@@ -193,6 +209,9 @@ const workflowTabsPosition = computed(() =>
|
||||
)
|
||||
|
||||
const searchBoxRef = ref()
|
||||
const browseTreeRef = ref<{ addFolderCommand: (key: string) => void } | null>(
|
||||
null
|
||||
)
|
||||
|
||||
const searchQuery = ref('')
|
||||
const isSearching = computed(() => searchQuery.value.length > 0)
|
||||
@@ -324,7 +343,53 @@ const renderTreeNode = (
|
||||
},
|
||||
draggable: true
|
||||
}
|
||||
: { handleClick }
|
||||
: type === WorkflowTreeType.Browse
|
||||
? {
|
||||
handleClick,
|
||||
...(node.key !== 'root'
|
||||
? {
|
||||
handleRename: async (newName: string) => {
|
||||
const folderKey = node.key.replace(/^root\//, '')
|
||||
const parentKey = folderKey.substring(
|
||||
0,
|
||||
folderKey.lastIndexOf('/')
|
||||
)
|
||||
const newFolderKey = parentKey
|
||||
? parentKey + '/' + newName
|
||||
: newName
|
||||
await workflowStore.renameFolder(
|
||||
'workflows/' + folderKey,
|
||||
'workflows/' + newFolderKey
|
||||
)
|
||||
},
|
||||
handleDelete: async () => {
|
||||
const folderKey = node.key.replace(/^root\//, '')
|
||||
await workflowStore.deleteFolder('workflows/' + folderKey)
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
handleAddFolder: async (name: string) => {
|
||||
const folderPath =
|
||||
node.key === 'root'
|
||||
? 'workflows/' + name
|
||||
: 'workflows/' + node.key.replace(/^root\//, '') + '/' + name
|
||||
await workflowStore.createFolder(folderPath)
|
||||
},
|
||||
droppable: true,
|
||||
handleDrop: async (
|
||||
data: TreeExplorerDragAndDropData<ComfyWorkflow>
|
||||
) => {
|
||||
const droppedWorkflow = data.data.data
|
||||
if (droppedWorkflow) {
|
||||
const destDir =
|
||||
node.key === 'root'
|
||||
? 'workflows'
|
||||
: 'workflows/' + node.key.replace(/^root\//, '')
|
||||
await workflowStore.moveWorkflowToFolder(droppedWorkflow, destDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
: { handleClick }
|
||||
|
||||
const label =
|
||||
node.leaf && labelTransform ? labelTransform(node.label) : node.label
|
||||
|
||||
@@ -899,6 +899,8 @@
|
||||
"dirtyCloseHint": "Hold Shift to close without prompt",
|
||||
"confirmOverwriteTitle": "Overwrite existing file?",
|
||||
"confirmOverwrite": "The file below already exists. Would you like to overwrite it?",
|
||||
"confirmDeleteFolderTitle": "Delete Folder",
|
||||
"confirmDeleteFolderMessage": "Delete this folder and all workflows inside it?",
|
||||
"workflowTreeType": {
|
||||
"browse": "Browse",
|
||||
"bookmarks": "Bookmarks",
|
||||
|
||||
@@ -62,8 +62,15 @@ interface WorkflowStore {
|
||||
workflowData?: ComfyWorkflowJSON
|
||||
) => ComfyWorkflow
|
||||
renameWorkflow: (workflow: ComfyWorkflow, newPath: string) => Promise<void>
|
||||
moveWorkflowToFolder: (
|
||||
workflow: ComfyWorkflow,
|
||||
destDir: string
|
||||
) => Promise<void>
|
||||
deleteWorkflow: (workflow: ComfyWorkflow) => Promise<void>
|
||||
saveWorkflow: (workflow: ComfyWorkflow) => Promise<void>
|
||||
renameFolder: (oldPath: string, newPath: string) => Promise<void>
|
||||
deleteFolder: (path: string) => Promise<void>
|
||||
createFolder: (folderPath: string) => Promise<void>
|
||||
|
||||
workflows: ComfyWorkflow[]
|
||||
bookmarkedWorkflows: ComfyWorkflow[]
|
||||
@@ -521,6 +528,67 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const moveWorkflowToFolder = async (
|
||||
workflow: ComfyWorkflow,
|
||||
destDir: string
|
||||
) => {
|
||||
const newPath = destDir + '/' + workflow.fullFilename
|
||||
await renameWorkflow(workflow, newPath)
|
||||
}
|
||||
|
||||
const renameFolder = async (oldPath: string, newPath: string) => {
|
||||
isBusy.value = true
|
||||
try {
|
||||
const resp = await api.moveUserDataDir(oldPath, newPath)
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(
|
||||
`Failed to rename folder: ${resp.status} ${resp.statusText}`
|
||||
)
|
||||
}
|
||||
await syncWorkflows()
|
||||
} finally {
|
||||
isBusy.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const deleteFolder = async (path: string) => {
|
||||
isBusy.value = true
|
||||
try {
|
||||
const prefix = path.endsWith('/') ? path : path + '/'
|
||||
for (const w of [...openWorkflows.value]) {
|
||||
if (w.path.startsWith(prefix)) {
|
||||
await closeWorkflow(w)
|
||||
}
|
||||
}
|
||||
const resp = await api.deleteUserDataDir(path)
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(
|
||||
`Failed to delete folder: ${resp.status} ${resp.statusText}`
|
||||
)
|
||||
}
|
||||
await syncWorkflows()
|
||||
} finally {
|
||||
isBusy.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const createFolder = async (folderPath: string) => {
|
||||
isBusy.value = true
|
||||
try {
|
||||
const normalizedPath = folderPath.endsWith('/')
|
||||
? folderPath
|
||||
: folderPath + '/'
|
||||
await api.storeUserData(normalizedPath, '', {
|
||||
overwrite: false,
|
||||
stringify: false,
|
||||
throwOnError: true
|
||||
})
|
||||
await syncWorkflows()
|
||||
} finally {
|
||||
isBusy.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a workflow.
|
||||
* @param workflow The workflow to save.
|
||||
@@ -755,9 +823,13 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
||||
createTemporary,
|
||||
createNewTemporary,
|
||||
renameWorkflow,
|
||||
moveWorkflowToFolder,
|
||||
deleteWorkflow,
|
||||
saveAs,
|
||||
saveWorkflow,
|
||||
renameFolder,
|
||||
deleteFolder,
|
||||
createFolder,
|
||||
reorderWorkflows,
|
||||
|
||||
workflows,
|
||||
|
||||
@@ -1183,6 +1183,20 @@ export class ComfyApi extends EventTarget {
|
||||
return resp
|
||||
}
|
||||
|
||||
async moveUserDataDir(source: string, dest: string) {
|
||||
return this.fetchApi('/userdata/dir/move', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ source, dest }),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
|
||||
async deleteUserDataDir(path: string) {
|
||||
return this.fetchApi(`/userdata/dir?path=${encodeURIComponent(path)}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
async listUserDataFullInfo(dir: string): Promise<UserDataFullInfo[]> {
|
||||
const trimmedDir = trimEnd(dir, '/')
|
||||
const resp = await this.fetchApi(
|
||||
|
||||
Reference in New Issue
Block a user