Compare commits

...

16 Commits

Author SHA1 Message Date
Terry Jia
84a675de88 expose Vue 2025-04-13 08:00:37 -04:00
Chenlei Hu
e45f5bdebb [lint] Fix remaining vue lint warnings (#3435) 2025-04-12 22:29:29 -04:00
Dr.Lt.Data
c270e7734a refine locales/ko (#3434) 2025-04-12 22:19:11 -04:00
Chenlei Hu
8d7a21e008 [lint] Add eslint-plugin-prettier (#3433) 2025-04-12 22:12:50 -04:00
Comfy Org PR Bot
29e63baca6 1.16.7 (#3430)
Co-authored-by: huchenlei <20929282+huchenlei@users.noreply.github.com>
2025-04-12 21:22:02 -04:00
Benjamin Lu
b22713daf0 Add source prop to commands (#3429)
Co-authored-by: Benjamin Lu <templu1107@proton.me>
Co-authored-by: github-actions <github-actions@github.com>
2025-04-12 21:20:53 -04:00
Benjamin Lu
c8b8953e0a Add reset individual keybind button (#3423)
Co-authored-by: Benjamin Lu <templu1107@proton.me>
2025-04-12 16:59:00 -04:00
Benjamin Lu
731ce8599d Allow scrolling on .settings-content (#3427)
Co-authored-by: Benjamin Lu <templu1107@proton.me>
2025-04-12 16:58:08 -04:00
filtered
ec8e55c1c1 Allow zooming inside multi-line string widgets (#3422)
Co-authored-by: github-actions <github-actions@github.com>
2025-04-12 13:13:00 -04:00
dependabot[bot]
04d38f2538 Bump vite from 5.4.17 to 5.4.18 (#3426)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-12 12:19:10 -04:00
Chenlei Hu
1c41db75f8 [Type] Enable strict schema type for setting entries (#3425) 2025-04-12 11:34:02 -04:00
Christian Byrne
c7a7397000 [Types] Make more types available directly in @comfyorg/comfyui-frontend-types package (#3418) 2025-04-12 11:33:34 -04:00
Terry Jia
e660e1d678 [3d] add support to output camera state (#3421) 2025-04-12 11:27:05 -04:00
Christian Byrne
fb19752389 [Types] Adds missing settings fields types (#3417) 2025-04-12 11:12:07 -04:00
Christian Byrne
d098d6ae4e [Types] Move enum to types file (#3416) 2025-04-12 11:11:41 -04:00
Christian Byrne
e4a5355f58 [Manager] Fix loading state on uninstall button (#3413) 2025-04-11 22:14:49 -04:00
37 changed files with 654 additions and 194 deletions

View File

@@ -1,4 +1,5 @@
import pluginJs from '@eslint/js'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import unusedImports from 'eslint-plugin-unused-imports'
import pluginVue from 'eslint-plugin-vue'
import globals from 'globals'
@@ -33,6 +34,7 @@ export default [
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs['flat/recommended'],
eslintPluginPrettierRecommended,
{
files: ['src/**/*.vue'],
languageOptions: {
@@ -50,7 +52,8 @@ export default [
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/prefer-as-const': 'off',
'unused-imports/no-unused-imports': 'error'
'unused-imports/no-unused-imports': 'error',
'vue/no-v-html': 'off'
}
}
]

4
global.d.ts vendored
View File

@@ -15,3 +15,7 @@ interface Navigator {
visible: boolean
}
}
interface Window {
Vue: typeof import('vue')
}

117
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.16.6",
"version": "1.16.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@comfyorg/comfyui-frontend",
"version": "1.16.6",
"version": "1.16.7",
"license": "GPL-3.0-only",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@@ -61,6 +61,8 @@
"autoprefixer": "^10.4.19",
"chalk": "^5.3.0",
"eslint": "^9.12.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-vue": "^9.27.0",
"fs-extra": "^11.2.0",
@@ -77,7 +79,7 @@
"typescript-eslint": "^8.0.0",
"unplugin-icons": "^0.19.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.17",
"vite": "^5.4.18",
"vite-plugin-dts": "^4.3.0",
"vitest": "^2.0.0",
"vue-tsc": "^2.1.10",
@@ -1791,6 +1793,19 @@
"node": ">=14"
}
},
"node_modules/@pkgr/core": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.2.tgz",
"integrity": "sha512-25L86MyPvnlQoX2MTIV2OiUcb6vJ6aRbFa9pbwByn95INKD5mFH2smgjDhq+fwJoqAgvgbdJLj6Tz7V9X5CFAQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/@playwright/test": {
"version": "1.45.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz",
@@ -5620,6 +5635,50 @@
}
}
},
"node_modules/eslint-config-prettier": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz",
"integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz",
"integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.11.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint-plugin-prettier"
},
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
"@types/eslint": {
"optional": true
},
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-plugin-unused-imports": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz",
@@ -6078,6 +6137,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@@ -10275,6 +10341,19 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
@@ -11663,6 +11742,23 @@
"optional": true,
"peer": true
},
"node_modules/synckit": {
"version": "0.11.3",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.3.tgz",
"integrity": "sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.1",
"tslib": "^2.8.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/tailwindcss": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
@@ -11954,10 +12050,11 @@
}
},
"node_modules/tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"dev": true
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.16.2",
@@ -12559,9 +12656,9 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "5.4.17",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.17.tgz",
"integrity": "sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==",
"version": "5.4.18",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz",
"integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.16.6",
"version": "1.16.7",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -44,6 +44,8 @@
"autoprefixer": "^10.4.19",
"chalk": "^5.3.0",
"eslint": "^9.12.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-vue": "^9.27.0",
"fs-extra": "^11.2.0",
@@ -60,7 +62,7 @@
"typescript-eslint": "^8.0.0",
"unplugin-icons": "^0.19.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.17",
"vite": "^5.4.18",
"vite-plugin-dts": "^4.3.0",
"vitest": "^2.0.0",
"vue-tsc": "^2.1.10",

View File

@@ -1,7 +1,7 @@
<template>
<div class="flex flex-col h-full">
<Tabs v-model:value="bottomPanelStore.activeBottomPanelTabId">
<TabList pt:tabList="border-none">
<TabList pt:tab-list="border-none">
<div class="w-full flex justify-between">
<div class="tabs-container">
<Tab

View File

@@ -293,6 +293,10 @@ watch(activeCategory, (_, oldValue) => {
overflow: hidden;
}
.settings-content {
overflow-x: auto;
}
@media (max-width: 768px) {
.settings-container {
flex-direction: column;

View File

@@ -6,12 +6,12 @@
'w-full': fullWidth,
'w-min-content': !fullWidth
}"
:disabled="isInstalling"
:disabled="loading"
v-bind="$attrs"
@click="onClick"
>
<span class="py-2.5 px-3">
<template v-if="isInstalling">
<template v-if="loading">
{{ loadingMessage ?? $t('g.loading') }}
</template>
<template v-else>
@@ -23,9 +23,6 @@
<script setup lang="ts">
import Button from 'primevue/button'
import { inject, ref } from 'vue'
import { IsInstallingKey } from '@/types/comfyManagerTypes'
const {
label,
@@ -33,6 +30,7 @@ const {
fullWidth = false
} = defineProps<{
label: string
loading?: boolean
loadingMessage?: string
fullWidth?: boolean
}>()
@@ -45,10 +43,7 @@ defineOptions({
inheritAttrs: false
})
const isInstalling = inject(IsInstallingKey, ref(false))
const onClick = (): void => {
isInstalling.value = true
emit('action')
}
</script>

View File

@@ -5,8 +5,10 @@
nodePacks.length > 1 ? $t('manager.installSelected') : $t('g.install')
"
severity="secondary"
:loading="isInstalling"
:loading-message="$t('g.installing')"
@action="installAllPacks"
@click="onClick"
/>
</template>
@@ -31,6 +33,10 @@ const { nodePacks } = defineProps<{
const isInstalling = inject(IsInstallingKey, ref(false))
const onClick = (): void => {
isInstalling.value = true
}
const managerStore = useComfyManagerStore()
const createPayload = (installItem: NodePack) => {

View File

@@ -31,7 +31,7 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { ref, watch } from 'vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue'
@@ -46,7 +46,14 @@ const { nodePacks } = defineProps<{
const managerStore = useComfyManagerStore()
const isAllInstalled = computed(() =>
nodePacks.every((nodePack) => managerStore.isPackInstalled(nodePack.id))
const isAllInstalled = ref(false)
watch(
[() => nodePacks, () => managerStore.installedPacks],
() => {
isAllInstalled.value = nodePacks.every((nodePack) =>
managerStore.isPackInstalled(nodePack.id)
)
},
{ immediate: true }
)
</script>

View File

@@ -27,6 +27,14 @@
class="p-button-text"
@click="editKeybinding(slotProps.data)"
/>
<Button
icon="pi pi-replay"
class="p-button-text p-button-warn"
:disabled="
!keybindingStore.isCommandKeybindingModified(slotProps.data.id)
"
@click="resetKeybinding(slotProps.data)"
/>
<Button
icon="pi pi-trash"
class="p-button-text p-button-danger"
@@ -63,6 +71,13 @@
<span v-else>-</span>
</template>
</Column>
<Column field="source" :header="$t('g.source')">
<template #body="slotProps">
<span class="overflow-hidden text-ellipsis">{{
slotProps.data.source || '-'
}}</span>
</template>
</Column>
</DataTable>
<Dialog
@@ -152,6 +167,7 @@ interface ICommandData {
id: string
keybinding: KeybindingImpl | null
label: string
source?: string
}
const commandsData = computed<ICommandData[]>(() => {
@@ -161,7 +177,8 @@ const commandsData = computed<ICommandData[]>(() => {
`commands.${normalizeI18nKey(command.id)}.label`,
command.label ?? ''
),
keybinding: keybindingStore.getKeybindingByCommandId(command.id)
keybinding: keybindingStore.getKeybindingByCommandId(command.id),
source: command.source
}))
})
@@ -254,6 +271,16 @@ async function saveKeybinding() {
cancelEdit()
}
async function resetKeybinding(commandData: ICommandData) {
if (keybindingStore.resetKeybindingForCommand(commandData.id)) {
await keybindingService.persistUserKeybindings()
} else {
console.warn(
`No changes made when resetting keybinding for command: ${commandData.id}`
)
}
}
const toast = useToast()
async function resetAllKeybindings() {
keybindingStore.resetAllKeybindings()

View File

@@ -1,25 +1,24 @@
<template>
<Button
v-tooltip="{ value: props.tooltip, showDelay: 300, hideDelay: 300 }"
:class="props.class"
v-tooltip="{ value: tooltip, showDelay: 300, hideDelay: 300 }"
text
:pt="{
root: {
class: `side-bar-button ${
props.selected
selected
? 'p-button-primary side-bar-button-selected'
: 'p-button-secondary'
}`,
'aria-label': props.tooltip
'aria-label': tooltip
}
}"
@click="emit('click', $event)"
>
<template #icon>
<OverlayBadge v-if="shouldShowBadge" :value="overlayValue">
<i :class="props.icon + ' side-bar-button-icon'" />
<i :class="icon + ' side-bar-button-icon'" />
</OverlayBadge>
<i v-else :class="props.icon + ' side-bar-button-icon'" />
<i v-else :class="icon + ' side-bar-button-icon'" />
</template>
</Button>
</template>
@@ -27,32 +26,25 @@
<script setup lang="ts">
import Button from 'primevue/button'
import OverlayBadge from 'primevue/overlaybadge'
import { PropType, computed } from 'vue'
import { computed } from 'vue'
// Add this line to import PropsType
const {
icon = '',
selected = false,
tooltip = '',
iconBadge = ''
} = defineProps<{
icon?: string
selected?: boolean
tooltip?: string
iconBadge?: string | (() => string | null)
}>()
const props = defineProps({
icon: String,
selected: Boolean,
tooltip: {
type: String,
default: ''
},
class: {
type: String,
default: ''
},
iconBadge: {
type: [String, Function] as PropType<string | (() => string | null)>,
default: ''
}
})
const emit = defineEmits(['click'])
const emit = defineEmits<{
(e: 'click', event: MouseEvent): void
}>()
const overlayValue = computed(() =>
typeof props.iconBadge === 'function'
? props.iconBadge() || ''
: props.iconBadge
typeof iconBadge === 'function' ? iconBadge() ?? '' : iconBadge
)
const shouldShowBadge = computed(() => !!overlayValue.value)
</script>

View File

@@ -38,12 +38,15 @@
>
<template #node="{ node }">
<TreeExplorerTreeNode :node="node">
<template #before-label="{ node }">
<span v-if="node.data.isModified || !node.data.isPersisted"
<template #before-label="{ node: treeNode }">
<span
v-if="
treeNode.data?.isModified || !treeNode.data?.isPersisted
"
>*</span
>
</template>
<template #actions="{ node }">
<template #actions="{ node: treeNode }">
<Button
class="close-workflow-button"
icon="pi pi-times"
@@ -52,7 +55,9 @@
workspaceStore.shiftDown ? 'danger' : 'secondary'
"
size="small"
@click.stop="handleCloseWorkflow(node.data)"
@click.stop="
handleCloseWorkflow(treeNode.data as ComfyWorkflow)
"
/>
</template>
</TreeExplorerTreeNode>

View File

@@ -1,12 +1,12 @@
<template>
<TreeExplorerTreeNode :node="node">
<template #actions="{ node }">
<template #actions>
<Button
:icon="isBookmarked ? 'pi pi-bookmark-fill' : 'pi pi-bookmark'"
text
severity="secondary"
size="small"
@click.stop="workflowBookmarkStore.toggleBookmarked(node.data.path)"
@click.stop="handleBookmarkClick"
/>
</template>
</TreeExplorerTreeNode>
@@ -28,4 +28,10 @@ const workflowBookmarkStore = useWorkflowBookmarkStore()
const isBookmarked = computed(
() => node.data && workflowBookmarkStore.isBookmarked(node.data.path)
)
const handleBookmarkClick = async () => {
if (node.data) {
await workflowBookmarkStore.toggleBookmarked(node.data.path)
}
}
</script>

View File

@@ -7,7 +7,7 @@
class: 'p-0 w-full',
onwheel: handleWheel
}"
pt:barX="h-1"
pt:bar-x="h-1"
>
<SelectButton
class="workflow-tabs bg-transparent"

View File

@@ -55,7 +55,9 @@ export function useCoreCommands(): ComfyCommand[] {
})
}
return [
const commonProps = { source: 'System' }
const commands = [
{
id: 'Comfy.NewBlankWorkflow',
icon: 'pi pi-plus',
@@ -616,4 +618,6 @@ export function useCoreCommands(): ComfyCommand[] {
}
}
]
return commands.map((command) => ({ ...command, ...commonProps }))
}

View File

@@ -2,6 +2,7 @@ import { useEventListener, whenever } from '@vueuse/core'
import { computed, readonly, ref } from 'vue'
import { api } from '@/scripts/api'
import { ManagerWsQueueStatus } from '@/types/comfyManagerTypes'
type QueuedTask<T> = {
task: () => Promise<T>
@@ -10,11 +11,6 @@ type QueuedTask<T> = {
const MANAGER_WS_MSG_TYPE = 'cm-queue-status'
enum ManagerWsQueueStatus {
DONE = 'done',
IN_PROGRESS = 'in_progress'
}
export const useManagerQueue = () => {
const clientQueueItems = ref<QueuedTask<unknown>[]>([])
const clientQueueLength = computed(() => clientQueueItems.value.length)

View File

@@ -35,6 +35,7 @@ function addMultilineWidget(
widget.callback?.(widget.value)
})
// Allow middle mouse button panning
inputEl.addEventListener('pointerdown', (event: PointerEvent) => {
if (event.button === 1) {
app.canvas.processMouseDown(event)
@@ -53,6 +54,40 @@ function addMultilineWidget(
}
})
/** Timer reference. `null` when the timer completes. */
let ignoreEventsTimer: ReturnType<typeof setTimeout> | null = null
/** Total number of events ignored since the timer started. */
let ignoredEvents = 0
// Pass wheel events to the canvas when appropriate
inputEl.addEventListener('wheel', (event: WheelEvent) => {
if (!Object.is(event.deltaX, -0)) return
// If the textarea has focus, require more effort to activate pass-through
const multiplier = document.activeElement === inputEl ? 2 : 1
const maxScrollHeight = inputEl.scrollHeight - inputEl.clientHeight
if (
(event.deltaY < 0 && inputEl.scrollTop === 0) ||
(event.deltaY > 0 && inputEl.scrollTop === maxScrollHeight)
) {
// Attempting to scroll past the end of the textarea
if (!ignoreEventsTimer || ignoredEvents > 25 * multiplier) {
app.canvas.processMouseWheel(event)
} else {
ignoredEvents++
}
} else if (event.deltaY !== 0) {
// Start timer whenever a successful scroll occurs
ignoredEvents = 0
if (ignoreEventsTimer) clearTimeout(ignoreEventsTimer)
ignoreEventsTimer = setTimeout(() => {
ignoreEventsTimer = null
}, 800 * multiplier)
}
})
return widget
}

View File

@@ -153,7 +153,8 @@ useExtensionService().registerExtension({
image: `threed/${data.name} [temp]`,
mask: `threed/${dataMask.name} [temp]`,
normal: `threed/${dataNormal.name} [temp]`,
lineart: `threed/${dataLineart.name} [temp]`
lineart: `threed/${dataLineart.name} [temp]`,
camera_info: node.properties['Camera Info']
}
}
}
@@ -293,7 +294,8 @@ useExtensionService().registerExtension({
return {
image: `threed/${data.name} [temp]`,
mask: `threed/${dataMask.name} [temp]`,
normal: `threed/${dataNormal.name} [temp]`
normal: `threed/${dataNormal.name} [temp]`,
camera_info: node.properties['Camera Info']
}
}
}
@@ -350,7 +352,7 @@ useExtensionService().registerExtension({
node.onExecuted = function (message: any) {
onExecuted?.apply(this, arguments as any)
let filePath = message.model_file[0]
let filePath = message.result[0]
if (!filePath) {
const msg = t('toastMessages.unableToGetModelFilePath')
@@ -360,6 +362,8 @@ useExtensionService().registerExtension({
const load3d = useLoad3dService().getLoad3d(node)
let cameraState = message.result[1]
const modelWidget = node.widgets?.find(
(w: IWidget) => w.name === 'model_file'
)
@@ -369,7 +373,7 @@ useExtensionService().registerExtension({
const config = new Load3DConfiguration(load3d)
config.configure('output', modelWidget)
config.configure('output', modelWidget, cameraState)
}
}
}
@@ -425,7 +429,7 @@ useExtensionService().registerExtension({
node.onExecuted = function (message: any) {
onExecuted?.apply(this, arguments as any)
let filePath = message.model_file[0]
let filePath = message.result[0]
if (!filePath) {
const msg = t('toastMessages.unableToGetModelFilePath')
@@ -433,6 +437,8 @@ useExtensionService().registerExtension({
useToastStore().addAlert(msg)
}
let cameraState = message.result[1]
const load3d = useLoad3dService().getLoad3d(node)
const modelWidget = node.widgets?.find(
@@ -443,7 +449,7 @@ useExtensionService().registerExtension({
const config = new Load3DConfiguration(load3d)
config.configure('output', modelWidget)
config.configure('output', modelWidget, cameraState)
}
}
}

View File

@@ -94,6 +94,7 @@
"name": "Name",
"category": "Category",
"sort": "Sort",
"source": "Source",
"filter": "Filter",
"apply": "Apply",
"enabled": "Enabled",

View File

@@ -215,6 +215,7 @@
"settings": "Configuraciones",
"showReport": "Mostrar informe",
"sort": "Ordenar",
"source": "Fuente",
"status": "Estado",
"success": "Éxito",
"systemInfo": "Información del sistema",

View File

@@ -215,6 +215,7 @@
"settings": "Paramètres",
"showReport": "Afficher le rapport",
"sort": "Trier",
"source": "Source",
"status": "Statut",
"success": "Succès",
"systemInfo": "Informations système",

View File

@@ -215,6 +215,7 @@
"settings": "設定",
"showReport": "レポートを表示",
"sort": "並び替え",
"source": "ソース",
"status": "ステータス",
"success": "成功",
"systemInfo": "システム情報",

View File

@@ -114,7 +114,7 @@
"defaultTitle": "오류가 발생했습니다",
"extensionFileHint": "다음 스크립트 때문일 수 있습니다",
"loadWorkflowTitle": "워크플로우 데이터를 다시 로드하는 중 오류로 인해 로드가 중단되었습니다",
"noStackTrace": "스택 추적 사용할 수 없습니다",
"noStackTrace": "스택 추적 사용할 수 없습니다",
"promptExecutionError": "프롬프트 실행 실패"
},
"g": {
@@ -176,7 +176,7 @@
"loadWorkflow": "워크플로 로드",
"loading": "로딩 중",
"logs": "로그",
"migrate": "마이그레이트",
"migrate": "이전(migrate)",
"missing": "누락됨",
"name": "이름",
"newFolder": "새 폴더",
@@ -215,6 +215,7 @@
"settings": "설정",
"showReport": "보고서 보기",
"sort": "정렬",
"source": "소스",
"status": "상태",
"success": "성공",
"systemInfo": "시스템 정보",
@@ -371,7 +372,7 @@
"materialMode": "재질 모드",
"materialModes": {
"depth": "깊이",
"normal": "정상",
"normal": "노멀(normal)",
"original": "원본",
"wireframe": "와이어프레임"
},
@@ -407,7 +408,7 @@
"title": "유지 보수"
},
"manager": {
"changingVersion": "{from}에서 {to}로 버전 변경 중",
"changingVersion": "{from}에서 {to}(으)로 버전 변경 중",
"createdBy": "작성자",
"dependencies": "의존성",
"discoverCommunityContent": "커뮤니티에서 만든 노드 팩 및 확장 프로그램을 찾아보세요...",
@@ -441,7 +442,7 @@
"sort": {
"created": "최신",
"downloads": "가장 인기 있는",
"publisher": "출판사",
"publisher": "배포자",
"updated": "최근 업데이트"
},
"status": {
@@ -842,8 +843,8 @@
"Pointer": "포인터",
"Queue": "실행 큐",
"QueueButton": "실행 큐 버튼",
"Reroute": "재경로",
"RerouteBeta": "경로재설정 베타",
"Reroute": "경유점",
"RerouteBeta": "경유점 (베타)",
"Server": "서버",
"Server-Config": "서버 구성",
"Settings": "설정",
@@ -1009,7 +1010,7 @@
"fileLoadError": "{fileName}에서 워크플로우를 찾을 수 없습니다",
"fileUploadFailed": "파일 업로드에 실패했습니다",
"interrupted": "실행이 중단되었습니다",
"migrateToLitegraphReroute": "미래 버전에서는 Reroute 노드가 제거됩니다. litegraph-native reroute로 마이그레이트하려면 클릭하세요.",
"migrateToLitegraphReroute": "향후 버전에서는 Reroute 노드가 제거됩니다. LiteGraph 에서 자체 제공하는 경유점으로 변환하려면 클릭하세요.",
"no3dScene": "텍스처를 적용할 3D 장면이 없습니다",
"no3dSceneToExport": "내보낼 3D 장면이 없습니다",
"noTemplatesToExport": "내보낼 템플릿이 없습니다",

View File

@@ -199,7 +199,7 @@
"name": "노드 제목을 두 번 클릭하여 편집"
},
"Comfy_Node_MiddleClickRerouteNode": {
"name": "중간 클릭으로 새 경로재설정 노드 생성"
"name": "중간 클릭으로 새 경유점 생성"
},
"Comfy_Node_Opacity": {
"name": "노드 불투명도"
@@ -345,8 +345,8 @@
"name": "툴팁 지연"
},
"LiteGraph_Reroute_SplineOffset": {
"name": "Reroute 스플라인 오프셋",
"tooltip": "재경로 중심에서 베지어 제어점까지의 오프셋"
"name": "경유점 스플라인 오프셋",
"tooltip": "경유점 중심에서 베지어 제어점까지의 오프셋"
},
"pysssss_SnapToGrid": {
"name": "항상 그리드에 스냅"

View File

@@ -215,6 +215,7 @@
"settings": "Настройки",
"showReport": "Показать отчёт",
"sort": "Сортировать",
"source": "Источник",
"status": "Статус",
"success": "Успех",
"systemInfo": "Информация о системе",

View File

@@ -215,6 +215,7 @@
"settings": "设置",
"showReport": "显示报告",
"sort": "排序",
"source": "来源",
"status": "状态",
"success": "成功",
"systemInfo": "系统信息",

View File

@@ -9,6 +9,7 @@ import ConfirmationService from 'primevue/confirmationservice'
import ToastService from 'primevue/toastservice'
import Tooltip from 'primevue/tooltip'
import { createApp } from 'vue'
import * as Vue from 'vue'
import '@/assets/css/style.css'
import router from '@/router'
@@ -59,3 +60,5 @@ app
.use(pinia)
.use(i18n)
.mount('#vue-app')
window.Vue = Vue

View File

@@ -332,102 +332,122 @@ const zNodeBadgeMode = z.enum(
Object.values(NodeBadgeMode) as [string, ...string[]]
)
const zSettings = z.record(z.any()).and(
z
.object({
'Comfy.ColorPalette': z.string(),
'Comfy.CustomColorPalettes': colorPalettesSchema,
'Comfy.ConfirmClear': z.boolean(),
'Comfy.DevMode': z.boolean(),
'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(),
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
'Comfy.DisableFloatRounding': z.boolean(),
'Comfy.DisableSliders': z.boolean(),
'Comfy.DOMClippingEnabled': z.boolean(),
'Comfy.EditAttention.Delta': z.number(),
'Comfy.EnableTooltips': z.boolean(),
'Comfy.EnableWorkflowViewRestore': z.boolean(),
'Comfy.FloatRoundingPrecision': z.number(),
'Comfy.Graph.CanvasInfo': z.boolean(),
'Comfy.Graph.CanvasMenu': z.boolean(),
'Comfy.Graph.CtrlShiftZoom': z.boolean(),
'Comfy.Graph.LinkMarkers': z.nativeEnum(LinkMarkerShape),
'Comfy.Graph.ZoomSpeed': z.number(),
'Comfy.Group.DoubleClickTitleToEdit': z.boolean(),
'Comfy.GroupSelectedNodes.Padding': z.number(),
'Comfy.InvertMenuScrolling': z.boolean(),
'Comfy.Locale': z.string(),
'Comfy.Logging.Enabled': z.boolean(),
'Comfy.NodeLibrary.Bookmarks': z.array(z.string()),
'Comfy.NodeLibrary.Bookmarks.V2': z.array(z.string()),
'Comfy.NodeLibrary.BookmarksCustomization': z.record(
z.string(),
zBookmarkCustomization
),
'Comfy.LinkRelease.Action': zLinkReleaseTriggerAction,
'Comfy.LinkRelease.ActionShift': zLinkReleaseTriggerAction,
'Comfy.ModelLibrary.AutoLoadAll': z.boolean(),
'Comfy.ModelLibrary.NameFormat': z.enum(['filename', 'title']),
'Comfy.NodeSearchBoxImpl.NodePreview': z.boolean(),
'Comfy.NodeSearchBoxImpl': z.enum(['default', 'simple']),
'Comfy.NodeSearchBoxImpl.ShowCategory': z.boolean(),
'Comfy.NodeSearchBoxImpl.ShowIdName': z.boolean(),
'Comfy.NodeSearchBoxImpl.ShowNodeFrequency': z.boolean(),
'Comfy.NodeSuggestions.number': z.number(),
'Comfy.Node.BypassAllLinksOnDelete': z.boolean(),
'Comfy.Node.Opacity': z.number(),
'Comfy.Node.MiddleClickRerouteNode': z.boolean(),
'Comfy.Node.ShowDeprecated': z.boolean(),
'Comfy.Node.ShowExperimental': z.boolean(),
'Comfy.Pointer.ClickBufferTime': z.number(),
'Comfy.Pointer.ClickDrift': z.number(),
'Comfy.Pointer.DoubleClickTime': z.number(),
'Comfy.PreviewFormat': z.string(),
'Comfy.PromptFilename': z.boolean(),
'Comfy.Sidebar.Location': z.enum(['left', 'right']),
'Comfy.Sidebar.Size': z.enum(['small', 'normal']),
'Comfy.SwitchUser': z.any(),
'Comfy.SnapToGrid.GridSize': z.number(),
'Comfy.TextareaWidget.FontSize': z.number(),
'Comfy.TextareaWidget.Spellcheck': z.boolean(),
'Comfy.UseNewMenu': z.enum(['Disabled', 'Top', 'Bottom']),
'Comfy.TreeExplorer.ItemPadding': z.number(),
'Comfy.Validation.Workflows': z.boolean(),
'Comfy.Validation.NodeDefs': z.boolean(),
'Comfy.Workflow.SortNodeIdOnSave': z.boolean(),
'Comfy.Queue.ImageFit': z.enum(['contain', 'cover']),
'Comfy.Workflow.WorkflowTabsPosition': z.enum([
'Sidebar',
'Topbar',
'Topbar (2nd-row)'
]),
'Comfy.Node.DoubleClickTitleToEdit': z.boolean(),
'Comfy.WidgetControlMode': z.enum(['before', 'after']),
'Comfy.Window.UnloadConfirmation': z.boolean(),
'Comfy.NodeBadge.NodeSourceBadgeMode': zNodeBadgeMode,
'Comfy.NodeBadge.NodeIdBadgeMode': zNodeBadgeMode,
'Comfy.NodeBadge.NodeLifeCycleBadgeMode': zNodeBadgeMode,
'Comfy.QueueButton.BatchCountLimit': z.number(),
'Comfy.Queue.MaxHistoryItems': z.number(),
'Comfy.Keybinding.UnsetBindings': z.array(zKeybinding),
'Comfy.Keybinding.NewBindings': z.array(zKeybinding),
'Comfy.Extension.Disabled': z.array(z.string()),
'Comfy.Settings.ExtensionPanel': z.boolean(),
'Comfy.LinkRenderMode': z.number(),
'Comfy.Node.AutoSnapLinkToSlot': z.boolean(),
'Comfy.Node.SnapHighlightsNode': z.boolean(),
'Comfy.Server.ServerConfigValues': z.record(z.string(), z.any()),
'Comfy.Server.LaunchArgs': z.record(z.string(), z.string()),
'LiteGraph.Canvas.MaximumFps': z.number(),
'Comfy.Workflow.ConfirmDelete': z.boolean(),
'Comfy.Workflow.AutoSaveDelay': z.number(),
'Comfy.Workflow.AutoSave': z.enum(['off', 'after delay']),
'Comfy.RerouteBeta': z.boolean(),
'LiteGraph.Canvas.LowQualityRenderingZoomThreshold': z.number(),
'Comfy.Canvas.SelectionToolbox': z.boolean()
})
.optional()
)
const zSettings = z.object({
'Comfy.ColorPalette': z.string(),
'Comfy.CustomColorPalettes': colorPalettesSchema,
'Comfy.ConfirmClear': z.boolean(),
'Comfy.DevMode': z.boolean(),
'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(),
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
'Comfy.DisableFloatRounding': z.boolean(),
'Comfy.DisableSliders': z.boolean(),
'Comfy.DOMClippingEnabled': z.boolean(),
'Comfy.EditAttention.Delta': z.number(),
'Comfy.EnableTooltips': z.boolean(),
'Comfy.EnableWorkflowViewRestore': z.boolean(),
'Comfy.FloatRoundingPrecision': z.number(),
'Comfy.Graph.CanvasInfo': z.boolean(),
'Comfy.Graph.CanvasMenu': z.boolean(),
'Comfy.Graph.CtrlShiftZoom': z.boolean(),
'Comfy.Graph.LinkMarkers': z.nativeEnum(LinkMarkerShape),
'Comfy.Graph.ZoomSpeed': z.number(),
'Comfy.Group.DoubleClickTitleToEdit': z.boolean(),
'Comfy.GroupSelectedNodes.Padding': z.number(),
'Comfy.InvertMenuScrolling': z.boolean(),
'Comfy.Locale': z.string(),
'Comfy.Logging.Enabled': z.boolean(),
'Comfy.NodeLibrary.Bookmarks': z.array(z.string()),
'Comfy.NodeLibrary.Bookmarks.V2': z.array(z.string()),
'Comfy.NodeLibrary.BookmarksCustomization': z.record(
z.string(),
zBookmarkCustomization
),
'Comfy.LinkRelease.Action': zLinkReleaseTriggerAction,
'Comfy.LinkRelease.ActionShift': zLinkReleaseTriggerAction,
'Comfy.ModelLibrary.AutoLoadAll': z.boolean(),
'Comfy.ModelLibrary.NameFormat': z.enum(['filename', 'title']),
'Comfy.NodeSearchBoxImpl.NodePreview': z.boolean(),
'Comfy.NodeSearchBoxImpl': z.enum(['default', 'simple']),
'Comfy.NodeSearchBoxImpl.ShowCategory': z.boolean(),
'Comfy.NodeSearchBoxImpl.ShowIdName': z.boolean(),
'Comfy.NodeSearchBoxImpl.ShowNodeFrequency': z.boolean(),
'Comfy.NodeSuggestions.number': z.number(),
'Comfy.Node.BypassAllLinksOnDelete': z.boolean(),
'Comfy.Node.Opacity': z.number(),
'Comfy.Node.MiddleClickRerouteNode': z.boolean(),
'Comfy.Node.ShowDeprecated': z.boolean(),
'Comfy.Node.ShowExperimental': z.boolean(),
'Comfy.Pointer.ClickBufferTime': z.number(),
'Comfy.Pointer.ClickDrift': z.number(),
'Comfy.Pointer.DoubleClickTime': z.number(),
'Comfy.PreviewFormat': z.string(),
'Comfy.PromptFilename': z.boolean(),
'Comfy.Sidebar.Location': z.enum(['left', 'right']),
'Comfy.Sidebar.Size': z.enum(['small', 'normal']),
'Comfy.SwitchUser': z.any(),
'Comfy.SnapToGrid.GridSize': z.number(),
'Comfy.TextareaWidget.FontSize': z.number(),
'Comfy.TextareaWidget.Spellcheck': z.boolean(),
'Comfy.UseNewMenu': z.enum(['Disabled', 'Top', 'Bottom']),
'Comfy.TreeExplorer.ItemPadding': z.number(),
'Comfy.Validation.Workflows': z.boolean(),
'Comfy.Validation.NodeDefs': z.boolean(),
'Comfy.Workflow.SortNodeIdOnSave': z.boolean(),
'Comfy.Queue.ImageFit': z.enum(['contain', 'cover']),
'Comfy.Workflow.WorkflowTabsPosition': z.enum([
'Sidebar',
'Topbar',
'Topbar (2nd-row)'
]),
'Comfy.Node.DoubleClickTitleToEdit': z.boolean(),
'Comfy.WidgetControlMode': z.enum(['before', 'after']),
'Comfy.Window.UnloadConfirmation': z.boolean(),
'Comfy.NodeBadge.NodeSourceBadgeMode': zNodeBadgeMode,
'Comfy.NodeBadge.NodeIdBadgeMode': zNodeBadgeMode,
'Comfy.NodeBadge.NodeLifeCycleBadgeMode': zNodeBadgeMode,
'Comfy.QueueButton.BatchCountLimit': z.number(),
'Comfy.Queue.MaxHistoryItems': z.number(),
'Comfy.Keybinding.UnsetBindings': z.array(zKeybinding),
'Comfy.Keybinding.NewBindings': z.array(zKeybinding),
'Comfy.Extension.Disabled': z.array(z.string()),
'Comfy.Settings.ExtensionPanel': z.boolean(),
'Comfy.LinkRenderMode': z.number(),
'Comfy.Node.AutoSnapLinkToSlot': z.boolean(),
'Comfy.Node.SnapHighlightsNode': z.boolean(),
'Comfy.Server.ServerConfigValues': z.record(z.string(), z.any()),
'Comfy.Server.LaunchArgs': z.record(z.string(), z.string()),
'LiteGraph.Canvas.MaximumFps': z.number(),
'Comfy.Workflow.ConfirmDelete': z.boolean(),
'Comfy.Workflow.AutoSaveDelay': z.number(),
'Comfy.Workflow.AutoSave': z.enum(['off', 'after delay']),
'Comfy.RerouteBeta': z.boolean(),
'LiteGraph.Canvas.LowQualityRenderingZoomThreshold': z.number(),
'Comfy.Canvas.SelectionToolbox': z.boolean(),
'LiteGraph.Node.TooltipDelay': z.number(),
'Comfy.ComfirmClear': z.boolean(),
'LiteGraph.ContextMenu.Scaling': z.boolean(),
'LiteGraph.Reroute.SplineOffset': z.number(),
'Comfy.Toast.DisableReconnectingToast': z.boolean(),
'Comfy.Workflow.Persist': z.boolean(),
'Comfy.TutorialCompleted': z.boolean(),
'Comfy.Node.AllowImageSizeDraw': z.boolean(),
'Comfy-Desktop.AutoUpdate': z.boolean(),
'Comfy-Desktop.SendStatistics': z.boolean(),
'Comfy-Desktop.WindowStyle': z.string(),
'Comfy-Desktop.UV.PythonInstallMirror': z.string(),
'Comfy-Desktop.UV.PypiInstallMirror': z.string(),
'Comfy-Desktop.UV.TorchInstallMirror': z.string(),
'Comfy.MaskEditor.UseNewEditor': z.boolean(),
'Comfy.MaskEditor.BrushAdjustmentSpeed': z.number(),
'Comfy.MaskEditor.UseDominantAxis': z.boolean(),
'pysssss.SnapToGrid': z.boolean(),
/** VHS setting is used for queue video preview support. */
'VHS.AdvancedPreviews': z.boolean(),
/** Settings used for testing */
'test.setting': z.any(),
'main.sub.setting.name': z.any(),
'single.setting': z.any()
})
export type EmbeddingsResponse = z.infer<typeof zEmbeddingsResponse>
export type ExtensionsResponse = z.infer<typeof zExtensionsResponse>

View File

@@ -13,11 +13,10 @@ export interface ComfyCommand {
label?: string | (() => string)
icon?: string | (() => string)
tooltip?: string | (() => string)
/** Menubar item label, if different from command label */
menubarLabel?: string | (() => string)
menubarLabel?: string | (() => string) // Menubar item label, if different from command label
versionAdded?: string
/** If non-nullish, this command will prompt for confirmation. */
confirmation?: string
confirmation?: string // If non-nullish, this command will prompt for confirmation
source?: string
}
export class ComfyCommandImpl implements ComfyCommand {
@@ -29,6 +28,7 @@ export class ComfyCommandImpl implements ComfyCommand {
_menubarLabel?: string | (() => string)
versionAdded?: string
confirmation?: string
source?: string
constructor(command: ComfyCommand) {
this.id = command.id
@@ -39,6 +39,7 @@ export class ComfyCommandImpl implements ComfyCommand {
this._menubarLabel = command.menubarLabel ?? command.label
this.versionAdded = command.versionAdded
this.confirmation = command.confirmation
this.source = command.source
}
get label() {
@@ -105,7 +106,10 @@ export const useCommandStore = defineStore('command', () => {
const loadExtensionCommands = (extension: ComfyExtension) => {
if (extension.commands) {
for (const command of extension.commands) {
registerCommand(command)
registerCommand({
...command,
source: extension.name
})
}
}
}

View File

@@ -283,6 +283,47 @@ export const useKeybindingStore = defineStore('keybinding', () => {
userUnsetKeybindings.value = {}
}
/**
* Resets the keybinding for a given command to its default value.
*
* @param commandId - The commandId of the keybind to be reset
* @returns `true` if changes were made, `false` if not
*/
function resetKeybindingForCommand(commandId: string): boolean {
const currentKeybinding = getKeybindingByCommandId(commandId)
const defaultKeybinding =
defaultKeybindingsByCommandId.value[commandId]?.[0]
// No default keybinding exists, need to remove any user binding
if (!defaultKeybinding) {
if (currentKeybinding) {
unsetKeybinding(currentKeybinding)
return true
}
return false
}
// Current binding equals default binding, no changes needed
if (currentKeybinding?.equals(defaultKeybinding)) {
return false
}
// Unset current keybinding if exists
if (currentKeybinding) {
unsetKeybinding(currentKeybinding)
}
// Remove the unset record if it exists
const serializedCombo = defaultKeybinding.combo.serialize()
if (
userUnsetKeybindings.value[serializedCombo]?.equals(defaultKeybinding)
) {
delete userUnsetKeybindings.value[serializedCombo]
}
return true
}
function isCommandKeybindingModified(commandId: string): boolean {
const currentKeybinding: KeybindingImpl | undefined =
getKeybindingByCommandId(commandId)
@@ -307,6 +348,7 @@ export const useKeybindingStore = defineStore('keybinding', () => {
unsetKeybinding,
updateKeybindingOnCommand,
resetAllKeybindings,
resetKeybindingForCommand,
isCommandKeybindingModified
}
})

View File

@@ -49,7 +49,8 @@ export const useBottomPanelStore = defineStore('bottomPanel', () => {
id: `Workspace.ToggleBottomPanelTab.${tab.id}`,
icon: 'pi pi-list',
label: `Toggle ${tab.title} Bottom Panel`,
function: () => toggleBottomPanelTab(tab.id)
function: () => toggleBottomPanelTab(tab.id),
source: 'System'
})
}

View File

@@ -33,7 +33,8 @@ export const useSidebarTabStore = defineStore('sidebarTab', () => {
versionAdded: '1.3.9',
function: () => {
toggleSidebarTab(tab.id)
}
},
source: 'System'
})
}

View File

@@ -1,6 +1,7 @@
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import type { Settings } from '@/schemas/apiSchema'
import { useColorPaletteService } from '@/services/colorPaletteService'
import { useDialogService } from '@/services/dialogService'
import type { SidebarTabExtension, ToastManager } from '@/types/extensionTypes'
@@ -31,8 +32,11 @@ export const useWorkspaceStore = defineStore('workspace', () => {
const sidebarTab = computed(() => useSidebarTabStore())
const setting = computed(() => ({
settings: useSettingStore().settingsById,
get: useSettingStore().get,
set: useSettingStore().set
// Allow generic key access to settings as custom nodes may add their
// own settings which is not tracked by the `Setting` schema.
get: (key: string) => useSettingStore().get(key as keyof Settings),
set: (key: string, value: unknown) =>
useSettingStore().set(key as keyof Settings, value)
}))
const workflow = computed(() => useWorkflowStore())
const colorPalette = useColorPaletteService()

View File

@@ -10,6 +10,11 @@ export type PackField = keyof RegistryPack | null
export const IsInstallingKey: InjectionKey<Ref<boolean>> =
Symbol('isInstalling')
export enum ManagerWsQueueStatus {
DONE = 'done',
IN_PROGRESS = 'in_progress'
}
export enum ManagerTab {
All = 'all',
Installed = 'installed',

View File

@@ -1,8 +1,57 @@
import type {
DeviceStats,
EmbeddingsResponse,
ExtensionsResponse,
LogEntry,
LogsRawResponse,
NodeError,
PromptResponse,
Settings,
SystemStats,
TerminalSize,
User,
UserData,
UserDataFullInfo
} from '@/schemas/apiSchema'
import { ComfyApp } from '@/scripts/app'
import type {
BottomPanelExtension,
CommandManager,
ExtensionManager,
SidebarTabExtension,
ToastManager,
ToastMessageOptions
} from './extensionTypes'
export type { ComfyExtension } from './comfy'
export type { ComfyApi } from '@/scripts/api'
export type { ComfyApp } from '@/scripts/app'
export type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
export type { InputSpec } from '@/schemas/nodeDefSchema'
export type {
EmbeddingsResponse,
ExtensionsResponse,
PromptResponse,
NodeError,
Settings,
DeviceStats,
SystemStats,
User,
UserData,
UserDataFullInfo,
TerminalSize,
LogEntry,
LogsRawResponse
}
export type {
SidebarTabExtension,
BottomPanelExtension,
ToastManager,
ExtensionManager,
CommandManager,
ToastMessageOptions
}
declare global {
const app: ComfyApp

View File

@@ -286,4 +286,139 @@ describe('useKeybindingStore', () => {
expect(store.getKeybinding(newKeybinding.combo)?.commandId).toBe('command2')
expect(store.getKeybindingsByCommandId('command1')).toHaveLength(0)
})
it('should return false when no default or current keybinding exists during reset', () => {
const store = useKeybindingStore()
const result = store.resetKeybindingForCommand('nonexistent.command')
expect(result).toBe(false)
})
it('should return false when current keybinding equals default keybinding', () => {
const store = useKeybindingStore()
const defaultKeybinding = new KeybindingImpl({
commandId: 'test.command',
combo: { key: 'L', ctrl: true }
})
store.addDefaultKeybinding(defaultKeybinding)
const result = store.resetKeybindingForCommand('test.command')
expect(result).toBe(false)
expect(store.keybindings).toHaveLength(1)
expect(store.getKeybindingByCommandId('test.command')).toEqual(
defaultKeybinding
)
})
it('should unset user keybinding when no default keybinding exists and return true', () => {
const store = useKeybindingStore()
const userKeybinding = new KeybindingImpl({
commandId: 'test.command',
combo: { key: 'M', ctrl: true }
})
store.addUserKeybinding(userKeybinding)
expect(store.keybindings).toHaveLength(1)
const result = store.resetKeybindingForCommand('test.command')
expect(result).toBe(true)
expect(store.keybindings).toHaveLength(0)
})
it('should restore default keybinding when user has overridden it and return true', () => {
const store = useKeybindingStore()
const defaultKeybinding = new KeybindingImpl({
commandId: 'test.command',
combo: { key: 'N', ctrl: true }
})
const userKeybinding = new KeybindingImpl({
commandId: 'test.command',
combo: { key: 'O', alt: true }
})
store.addDefaultKeybinding(defaultKeybinding)
store.updateKeybindingOnCommand(userKeybinding)
expect(store.keybindings).toHaveLength(1)
expect(store.getKeybindingByCommandId('test.command')).toEqual(
userKeybinding
)
const result = store.resetKeybindingForCommand('test.command')
expect(result).toBe(true)
expect(store.keybindings).toHaveLength(1)
expect(store.getKeybindingByCommandId('test.command')).toEqual(
defaultKeybinding
)
})
it('should remove unset record and restore default keybinding when user has unset it', () => {
const store = useKeybindingStore()
const defaultKeybinding = new KeybindingImpl({
commandId: 'test.command',
combo: { key: 'P', ctrl: true }
})
store.addDefaultKeybinding(defaultKeybinding)
store.unsetKeybinding(defaultKeybinding)
expect(store.keybindings).toHaveLength(0)
const serializedCombo = defaultKeybinding.combo.serialize()
const userUnsetKeybindings = store.getUserUnsetKeybindings()
expect(userUnsetKeybindings[serializedCombo]).toBeTruthy()
expect(
userUnsetKeybindings[serializedCombo].equals(defaultKeybinding)
).toBe(true)
const result = store.resetKeybindingForCommand('test.command')
expect(result).toBe(true)
expect(store.keybindings).toHaveLength(1)
expect(store.getKeybindingByCommandId('test.command')).toEqual(
defaultKeybinding
)
expect(store.getUserUnsetKeybindings()[serializedCombo]).toBeUndefined()
})
it('should handle complex scenario with both unset and user keybindings', () => {
const store = useKeybindingStore()
// Create default keybinding
const defaultKeybinding = new KeybindingImpl({
commandId: 'test.command',
combo: { key: 'Q', ctrl: true }
})
store.addDefaultKeybinding(defaultKeybinding)
// Unset default keybinding
store.unsetKeybinding(defaultKeybinding)
expect(store.keybindings).toHaveLength(0)
// Add user keybinding with different combo
const userKeybinding = new KeybindingImpl({
commandId: 'test.command',
combo: { key: 'R', alt: true }
})
store.addUserKeybinding(userKeybinding)
expect(store.keybindings).toHaveLength(1)
expect(store.getKeybindingByCommandId('test.command')).toEqual(
userKeybinding
)
// Reset keybinding to default
const result = store.resetKeybindingForCommand('test.command')
expect(result).toBe(true)
expect(store.keybindings).toHaveLength(1)
expect(store.getKeybindingByCommandId('test.command')).toEqual(
defaultKeybinding
)
})
})

View File

@@ -189,11 +189,11 @@ describe('useSettingStore', () => {
expect(storedValue).toEqual({ foo: 'bar', nested: { value: 123 } })
})
it('should prevent mutations of retrieved objects', () => {
it('should prevent mutations of retrieved objects', async () => {
const initialValue = { foo: 'bar', nested: { value: 123 } }
// Set initial value
store.set('test.setting', initialValue)
await store.set('test.setting', initialValue)
// Get the value and try to mutate it
const retrievedValue = store.get('test.setting')
@@ -230,11 +230,11 @@ describe('useSettingStore', () => {
expect(storedValue).toEqual([1, 2, { value: 3 }])
})
it('should prevent mutations of retrieved arrays', () => {
it('should prevent mutations of retrieved arrays', async () => {
const initialArray = [1, 2, { value: 3 }]
// Set initial value
store.set('test.setting', initialArray)
await store.set('test.setting', initialArray)
// Get the value and try to mutate it
const retrievedArray = store.get('test.setting')