mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-28 10:12:11 +00:00
add List view for workflow tempalte (#3710)
Co-authored-by: Chenlei Hu <hcl@comfy.org> Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
69
src/components/templates/TemplateWorkflowList.vue
Normal file
69
src/components/templates/TemplateWorkflowList.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<DataTable
|
||||||
|
v-model:selection="selectedTemplate"
|
||||||
|
:value="templates"
|
||||||
|
striped-rows
|
||||||
|
selection-mode="single"
|
||||||
|
>
|
||||||
|
<Column field="title" :header="t('g.title')">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<span :title="getTemplateTitle(slotProps.data)">{{
|
||||||
|
getTemplateTitle(slotProps.data)
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="description" :header="t('g.description')">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<span :title="slotProps.data.description.replace(/[-_]/g, ' ')">
|
||||||
|
{{ slotProps.data.description.replace(/[-_]/g, ' ') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column field="actions" header="" class="w-12">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-arrow-right"
|
||||||
|
text
|
||||||
|
rounded
|
||||||
|
size="small"
|
||||||
|
:loading="loading === slotProps.data.name"
|
||||||
|
@click="emit('loadWorkflow', slotProps.data.name)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Button from 'primevue/button'
|
||||||
|
import Column from 'primevue/column'
|
||||||
|
import DataTable from 'primevue/datatable'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { st, t } from '@/i18n'
|
||||||
|
import type { TemplateInfo } from '@/types/workflowTemplateTypes'
|
||||||
|
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||||
|
|
||||||
|
const { sourceModule, categoryTitle, loading, templates } = defineProps<{
|
||||||
|
sourceModule: string
|
||||||
|
categoryTitle: string
|
||||||
|
loading: string | null
|
||||||
|
templates: TemplateInfo[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const selectedTemplate = ref(null)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
loadWorkflow: [name: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const getTemplateTitle = (template: TemplateInfo) => {
|
||||||
|
const fallback = template.title ?? template.name ?? `${sourceModule} Template`
|
||||||
|
return sourceModule === 'default'
|
||||||
|
? st(
|
||||||
|
`templateWorkflows.template.${normalizeI18nKey(categoryTitle)}.${normalizeI18nKey(template.name)}`,
|
||||||
|
fallback
|
||||||
|
)
|
||||||
|
: fallback
|
||||||
|
}
|
||||||
|
</script>
|
||||||
82
src/components/templates/TemplateWorkflowView.vue
Normal file
82
src/components/templates/TemplateWorkflowView.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<DataView
|
||||||
|
:value="templates"
|
||||||
|
:layout="layout"
|
||||||
|
data-key="name"
|
||||||
|
:lazy="true"
|
||||||
|
pt:root="h-full grid grid-rows-[auto_1fr]"
|
||||||
|
pt:content="p-2 overflow-auto"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-lg">{{ title }}</h2>
|
||||||
|
<SelectButton
|
||||||
|
v-model="layout"
|
||||||
|
:options="['grid', 'list']"
|
||||||
|
:allow-empty="false"
|
||||||
|
>
|
||||||
|
<template #option="{ option }">
|
||||||
|
<i :class="[option === 'list' ? 'pi pi-bars' : 'pi pi-table']" />
|
||||||
|
</template>
|
||||||
|
</SelectButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #list="{ items }">
|
||||||
|
<TemplateWorkflowList
|
||||||
|
:source-module="sourceModule"
|
||||||
|
:templates="items"
|
||||||
|
:loading="loading"
|
||||||
|
:category-title="categoryTitle"
|
||||||
|
@load-workflow="onLoadWorkflow"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #grid="{ items }">
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-[repeat(auto-fill,minmax(16rem,1fr))] auto-rows-fr gap-8 justify-items-center"
|
||||||
|
>
|
||||||
|
<TemplateWorkflowCard
|
||||||
|
v-for="template in items"
|
||||||
|
:key="template.name"
|
||||||
|
:source-module="sourceModule"
|
||||||
|
:template="template"
|
||||||
|
:loading="loading === template.name"
|
||||||
|
:category-title="categoryTitle"
|
||||||
|
@load-workflow="onLoadWorkflow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</DataView>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
|
import DataView from 'primevue/dataview'
|
||||||
|
import SelectButton from 'primevue/selectbutton'
|
||||||
|
|
||||||
|
import TemplateWorkflowCard from '@/components/templates/TemplateWorkflowCard.vue'
|
||||||
|
import TemplateWorkflowList from '@/components/templates/TemplateWorkflowList.vue'
|
||||||
|
import type { TemplateInfo } from '@/types/workflowTemplateTypes'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
title: string
|
||||||
|
sourceModule: string
|
||||||
|
categoryTitle: string
|
||||||
|
loading: string | null
|
||||||
|
templates: TemplateInfo[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const layout = useLocalStorage<'grid' | 'list'>(
|
||||||
|
'Comfy.TemplateWorkflow.Layout',
|
||||||
|
'grid'
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
loadWorkflow: [name: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const onLoadWorkflow = (name: string) => {
|
||||||
|
emit('loadWorkflow', name)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -30,36 +30,22 @@
|
|||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
<div
|
<div
|
||||||
class="flex-1 overflow-auto transition-all duration-300"
|
class="flex-1 transition-all duration-300"
|
||||||
:class="{
|
:class="{
|
||||||
'pl-80': isSideNavOpen || !isSmallScreen,
|
'pl-80': isSideNavOpen || !isSmallScreen,
|
||||||
'pl-8': !isSideNavOpen && isSmallScreen
|
'pl-8': !isSideNavOpen && isSmallScreen
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div v-if="isReady && selectedTab" class="flex flex-col px-12 pb-4">
|
<TemplateWorkflowView
|
||||||
<div class="py-3 text-left">
|
v-if="isReady && selectedTab"
|
||||||
<h2 class="text-lg">
|
class="px-12 py-4"
|
||||||
{{ selectedTab.title }}
|
:title="selectedTab.title"
|
||||||
</h2>
|
:source-module="selectedTab.moduleName"
|
||||||
</div>
|
:templates="selectedTab.templates"
|
||||||
<div
|
:loading="workflowLoading"
|
||||||
class="grid grid-cols-[repeat(auto-fill,minmax(16rem,1fr))] auto-rows-fr gap-8 justify-items-center"
|
:category-title="selectedTab.title"
|
||||||
>
|
@load-workflow="loadWorkflow"
|
||||||
<div
|
/>
|
||||||
v-for="template in selectedTab.templates"
|
|
||||||
:key="template.name"
|
|
||||||
class="h-full"
|
|
||||||
>
|
|
||||||
<TemplateWorkflowCard
|
|
||||||
:source-module="selectedTab.moduleName"
|
|
||||||
:template="template"
|
|
||||||
:loading="template.name === workflowLoading"
|
|
||||||
:category-title="selectedTab.title"
|
|
||||||
@load-workflow="loadWorkflow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,7 +59,7 @@ import ProgressSpinner from 'primevue/progressspinner'
|
|||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import TemplateWorkflowCard from '@/components/templates/TemplateWorkflowCard.vue'
|
import TemplateWorkflowView from '@/components/templates/TemplateWorkflowView.vue'
|
||||||
import TemplateWorkflowsSideNav from '@/components/templates/TemplateWorkflowsSideNav.vue'
|
import TemplateWorkflowsSideNav from '@/components/templates/TemplateWorkflowsSideNav.vue'
|
||||||
import { useResponsiveCollapse } from '@/composables/element/useResponsiveCollapse'
|
import { useResponsiveCollapse } from '@/composables/element/useResponsiveCollapse'
|
||||||
import { api } from '@/scripts/api'
|
import { api } from '@/scripts/api'
|
||||||
|
|||||||
@@ -113,7 +113,8 @@
|
|||||||
"login": "Login",
|
"login": "Login",
|
||||||
"learnMore": "Learn more",
|
"learnMore": "Learn more",
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"unknownError": "Unknown error"
|
"unknownError": "Unknown error",
|
||||||
|
"title": "Title"
|
||||||
},
|
},
|
||||||
"manager": {
|
"manager": {
|
||||||
"title": "Custom Nodes Manager",
|
"title": "Custom Nodes Manager",
|
||||||
|
|||||||
@@ -308,6 +308,7 @@
|
|||||||
"success": "Éxito",
|
"success": "Éxito",
|
||||||
"systemInfo": "Información del sistema",
|
"systemInfo": "Información del sistema",
|
||||||
"terminal": "Terminal",
|
"terminal": "Terminal",
|
||||||
|
"title": "Título",
|
||||||
"unknownError": "Error desconocido",
|
"unknownError": "Error desconocido",
|
||||||
"update": "Actualizar",
|
"update": "Actualizar",
|
||||||
"updateAvailable": "Actualización Disponible",
|
"updateAvailable": "Actualización Disponible",
|
||||||
|
|||||||
@@ -308,6 +308,7 @@
|
|||||||
"success": "Succès",
|
"success": "Succès",
|
||||||
"systemInfo": "Informations système",
|
"systemInfo": "Informations système",
|
||||||
"terminal": "Terminal",
|
"terminal": "Terminal",
|
||||||
|
"title": "Titre",
|
||||||
"unknownError": "Erreur inconnue",
|
"unknownError": "Erreur inconnue",
|
||||||
"update": "Mettre à jour",
|
"update": "Mettre à jour",
|
||||||
"updateAvailable": "Mise à jour disponible",
|
"updateAvailable": "Mise à jour disponible",
|
||||||
|
|||||||
@@ -308,6 +308,7 @@
|
|||||||
"success": "成功",
|
"success": "成功",
|
||||||
"systemInfo": "システム情報",
|
"systemInfo": "システム情報",
|
||||||
"terminal": "ターミナル",
|
"terminal": "ターミナル",
|
||||||
|
"title": "タイトル",
|
||||||
"unknownError": "不明なエラー",
|
"unknownError": "不明なエラー",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
"updateAvailable": "更新が利用可能",
|
"updateAvailable": "更新が利用可能",
|
||||||
|
|||||||
@@ -308,6 +308,7 @@
|
|||||||
"success": "성공",
|
"success": "성공",
|
||||||
"systemInfo": "시스템 정보",
|
"systemInfo": "시스템 정보",
|
||||||
"terminal": "터미널",
|
"terminal": "터미널",
|
||||||
|
"title": "제목",
|
||||||
"unknownError": "알 수 없는 오류",
|
"unknownError": "알 수 없는 오류",
|
||||||
"update": "업데이트",
|
"update": "업데이트",
|
||||||
"updateAvailable": "업데이트 가능",
|
"updateAvailable": "업데이트 가능",
|
||||||
|
|||||||
@@ -308,6 +308,7 @@
|
|||||||
"success": "Успех",
|
"success": "Успех",
|
||||||
"systemInfo": "Информация о системе",
|
"systemInfo": "Информация о системе",
|
||||||
"terminal": "Терминал",
|
"terminal": "Терминал",
|
||||||
|
"title": "Заголовок",
|
||||||
"unknownError": "Неизвестная ошибка",
|
"unknownError": "Неизвестная ошибка",
|
||||||
"update": "Обновить",
|
"update": "Обновить",
|
||||||
"updateAvailable": "Доступно обновление",
|
"updateAvailable": "Доступно обновление",
|
||||||
|
|||||||
@@ -308,6 +308,7 @@
|
|||||||
"success": "成功",
|
"success": "成功",
|
||||||
"systemInfo": "系统信息",
|
"systemInfo": "系统信息",
|
||||||
"terminal": "终端",
|
"terminal": "终端",
|
||||||
|
"title": "标题",
|
||||||
"unknownError": "未知错误",
|
"unknownError": "未知错误",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
"updateAvailable": "有更新可用",
|
"updateAvailable": "有更新可用",
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ export default defineConfig({
|
|||||||
target: DEV_SERVER_COMFYUI_URL
|
target: DEV_SERVER_COMFYUI_URL
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'/templates': {
|
||||||
|
target: DEV_SERVER_COMFYUI_URL
|
||||||
|
},
|
||||||
|
|
||||||
'/testsubrouteindex': {
|
'/testsubrouteindex': {
|
||||||
target: 'http://localhost:5173',
|
target: 'http://localhost:5173',
|
||||||
rewrite: (path) => path.substring('/testsubrouteindex'.length)
|
rewrite: (path) => path.substring('/testsubrouteindex'.length)
|
||||||
|
|||||||
Reference in New Issue
Block a user