Compare commits

...

10 Commits

Author SHA1 Message Date
Chenlei Hu
aeed923735 1.6.18 (#2177) 2025-01-06 16:11:32 -05:00
bymyself
ca33015ced Fix saved keybinding persistence (#2176) 2025-01-06 16:09:55 -05:00
Alexander Piskun
3195e8a697 (fix): added missing slash to "loadWorkflow" templates endpoint (#2174) 2025-01-06 16:09:41 -05:00
Chenlei Hu
66f3ccd7a8 Update litegraph 0.8.60 (#2164) 2025-01-06 16:09:23 -05:00
Chenlei Hu
31c46fe2b1 Prevent reference sharing on settingStore content (#2160) 2025-01-06 16:09:11 -05:00
Chenlei Hu
a16061d670 1.6.17 release (#2149)
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
2025-01-03 16:10:09 -05:00
Chenlei Hu
77d45d8eff 1.6.16 (#2142) 2025-01-03 11:21:52 -05:00
Chenlei Hu
5972a07cfe 1.6 patches (#2141) 2025-01-03 11:20:25 -05:00
huchenlei
9d8633aafa Update release.yaml to target core/* branch 2025-01-03 11:17:16 -05:00
huchenlei
06292bccfc 1.6.15 2025-01-02 17:52:11 -05:00
16 changed files with 289 additions and 23 deletions

View File

@@ -6,6 +6,7 @@ on:
branches:
- main
- master
- core/*
paths:
- "package.json"

View File

@@ -1,5 +1,6 @@
import { expect } from '@playwright/test'
import { Keybinding } from '../src/types/keyBindingTypes'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Load workflow warning', () => {
@@ -103,4 +104,52 @@ test.describe('Settings', () => {
expect(await comfyPage.getSetting('Comfy.Graph.ZoomSpeed')).toBe(maxSpeed)
})
})
test('Should persist keybinding setting', async ({ comfyPage }) => {
// Open the settings dialog
await comfyPage.page.keyboard.press('Control+,')
await comfyPage.page.waitForSelector('.settings-container')
// Open the keybinding tab
await comfyPage.page.getByLabel('Keybinding').click()
await comfyPage.page.waitForSelector(
'[placeholder="Search Keybindings..."]'
)
// Focus the 'New Blank Workflow' row
const newBlankWorkflowRow = comfyPage.page.locator('tr', {
has: comfyPage.page.getByRole('cell', { name: 'New Blank Workflow' })
})
await newBlankWorkflowRow.click()
// Click edit button
const editKeybindingButton = newBlankWorkflowRow.locator('.pi-pencil')
await editKeybindingButton.click()
// Set new keybinding
const input = comfyPage.page.getByPlaceholder('Press keys for new binding')
await input.press('Alt+n')
const requestPromise = comfyPage.page.waitForRequest(
'**/api/settings/Comfy.Keybinding.NewBindings'
)
// Save keybinding
const saveButton = comfyPage.page
.getByLabel('Comfy.NewBlankWorkflow')
.getByLabel('Save')
await saveButton.click()
const request = await requestPromise
const expectedSetting: Keybinding = {
commandId: 'Comfy.NewBlankWorkflow',
combo: {
key: 'n',
ctrl: false,
alt: true,
shift: false
}
}
expect(request.postData()).toContain(JSON.stringify(expectedSetting))
})
})

View File

@@ -821,6 +821,11 @@ export class ComfyPage {
async getNodeRefById(id: NodeId) {
return new NodeReference(id, this)
}
async getNodes() {
return await this.page.evaluate(() => {
return window['app'].graph.nodes
})
}
async getNodeRefsByType(type: string): Promise<NodeReference[]> {
return Promise.all(
(

View File

@@ -152,6 +152,13 @@ export class WorkflowsSidebarTab extends SidebarTab {
await this.page.keyboard.press('Enter')
await this.page.waitForTimeout(300)
}
async insertWorkflow(locator: Locator) {
await locator.click({ button: 'right' })
await this.page
.locator('.p-contextmenu-item-content', { hasText: 'Insert' })
.click()
}
}
export class QueueSidebarTab extends SidebarTab {

View File

@@ -469,6 +469,43 @@ test.describe('Canvas Interaction', () => {
expect(await getCursorStyle()).toBe('default')
})
// https://github.com/Comfy-Org/litegraph.js/pull/424
test('Properly resets dragging state after pan mode sequence', async ({
comfyPage
}) => {
const getCursorStyle = async () => {
return await comfyPage.page.evaluate(() => {
return (
document.getElementById('graph-canvas')!.style.cursor || 'default'
)
})
}
// Initial state check
await comfyPage.page.mouse.move(10, 10)
expect(await getCursorStyle()).toBe('default')
// Click and hold
await comfyPage.page.mouse.down()
expect(await getCursorStyle()).toBe('grabbing')
// Press space while holding click
await comfyPage.page.keyboard.down('Space')
expect(await getCursorStyle()).toBe('grabbing')
// Release click while space is still down
await comfyPage.page.mouse.up()
expect(await getCursorStyle()).toBe('grab')
// Release space
await comfyPage.page.keyboard.up('Space')
expect(await getCursorStyle()).toBe('default')
// Move mouse - cursor should remain default
await comfyPage.page.mouse.move(20, 20)
expect(await getCursorStyle()).toBe('default')
})
test('Can pan when dragging a link', async ({ comfyPage }) => {
const posSlot1 = comfyPage.clipTextEncodeNode1InputSlot
await comfyPage.page.mouse.move(posSlot1.x, posSlot1.y)

View File

@@ -429,6 +429,26 @@ test.describe('Menu', () => {
])
})
test('Can open workflow after insert', async ({ comfyPage }) => {
await comfyPage.setupWorkflowsDirectory({
'workflow1.json': 'single_ksampler.json'
})
await comfyPage.setup()
const tab = comfyPage.menu.workflowsTab
await tab.open()
await comfyPage.executeCommand('Comfy.LoadDefaultWorkflow')
const originalNodeCount = (await comfyPage.getNodes()).length
await tab.insertWorkflow(tab.getPersistedItem('workflow1.json'))
await comfyPage.nextFrame()
expect((await comfyPage.getNodes()).length).toEqual(originalNodeCount + 1)
await tab.getPersistedItem('workflow1.json').click()
await comfyPage.nextFrame()
expect((await comfyPage.getNodes()).length).toEqual(1)
})
test('Can rename nested workflow from opened workflow item', async ({
comfyPage
}) => {

12
package-lock.json generated
View File

@@ -1,17 +1,17 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.6.14",
"version": "1.6.18",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@comfyorg/comfyui-frontend",
"version": "1.6.14",
"version": "1.6.18",
"license": "GPL-3.0-only",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.3",
"@comfyorg/litegraph": "^0.8.58",
"@comfyorg/litegraph": "^0.8.60",
"@primevue/themes": "^4.0.5",
"@tiptap/core": "^2.10.4",
"@tiptap/extension-link": "^2.10.4",
@@ -1940,9 +1940,9 @@
"license": "GPL-3.0-only"
},
"node_modules/@comfyorg/litegraph": {
"version": "0.8.58",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.58.tgz",
"integrity": "sha512-V/4yC8i5QOpDV20ZiEMiZP6KnmYD5d15El3V4tmH/MkhjOxjc6owAFMyAVgpxphYdcBF2qj1QTNTrZLgC6x2VQ==",
"version": "0.8.60",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.60.tgz",
"integrity": "sha512-LkZalBcka1xVxkL7JnkF/1EzyvspLyrSthzyN9ZumWJw7kAaZkO9omraXv2t/UiFsqwMr5M/AV5UY915Vq8cxQ==",
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.6.14",
"version": "1.6.18",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -82,7 +82,7 @@
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.3",
"@comfyorg/litegraph": "^0.8.58",
"@comfyorg/litegraph": "^0.8.60",
"@primevue/themes": "^4.0.5",
"@tiptap/core": "^2.10.4",
"@tiptap/extension-link": "^2.10.4",

View File

@@ -112,7 +112,7 @@ const loadWorkflow = async (id: string) => {
let json
if (selectedTab.value.moduleName === 'default') {
// Default templates provided by frontend are served on this separate endpoint
json = await fetch(api.fileURL(`templates/${id}.json`)).then((r) =>
json = await fetch(api.fileURL(`/templates/${id}.json`)).then((r) =>
r.json()
)
} else {

View File

@@ -25,11 +25,11 @@ app.registerExtension({
if (!this.properties) {
this.properties = { text: '' }
}
ComfyWidgets.MARKDOWN(
ComfyWidgets.STRING(
// Should we extends LGraphNode? Yesss
this,
'',
['', { default: this.properties.text }],
['', { default: this.properties.text, multiline: true }],
app
)
@@ -50,5 +50,33 @@ app.registerExtension({
)
NoteNode.category = 'utils'
/** Markdown variant of NoteNode */
class MarkdownNoteNode extends LGraphNode {
static title = 'Markdown Note'
color = LGraphCanvas.node_colors.yellow.color
bgcolor = LGraphCanvas.node_colors.yellow.bgcolor
groupcolor = LGraphCanvas.node_colors.yellow.groupcolor
constructor(title?: string) {
super(title)
if (!this.properties) {
this.properties = { text: '' }
}
ComfyWidgets.MARKDOWN(
this,
'',
['', { default: this.properties.text }],
app
)
this.serialize_widgets = true
this.isVirtualNode = true
}
}
LiteGraph.registerNodeType('MarkdownNote', MarkdownNoteNode)
MarkdownNoteNode.category = 'utils'
}
})

View File

@@ -49,7 +49,7 @@ import {
} from './pnginfo'
import { $el, ComfyUI } from './ui'
import { ComfyAppMenu } from './ui/menu/index'
import { getStorageValue } from './utils'
import { clone, getStorageValue } from './utils'
import { type ComfyWidgetConstructor, ComfyWidgets } from './widgets'
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
@@ -1271,11 +1271,7 @@ export class ComfyApp {
reset_invalid_values = true
}
if (typeof structuredClone === 'undefined') {
graphData = JSON.parse(JSON.stringify(graphData))
} else {
graphData = structuredClone(graphData)
}
graphData = clone(graphData)
if (useSettingStore().get('Comfy.Validation.Workflows')) {
// TODO: Show validation error in a dialog.

View File

@@ -84,11 +84,11 @@ export const useKeybindingService = () => {
// Allow setting multiple values at once in settingStore
await settingStore.set(
'Comfy.Keybinding.NewBindings',
Object.values(keybindingStore.userKeybindings.value)
Object.values(keybindingStore.getUserKeybindings())
)
await settingStore.set(
'Comfy.Keybinding.UnsetBindings',
Object.values(keybindingStore.userUnsetKeybindings.value)
Object.values(keybindingStore.getUserUnsetKeybindings())
)
}

View File

@@ -105,6 +105,20 @@ export const useKeybindingStore = defineStore('keybinding', () => {
*/
const userUnsetKeybindings = ref<Record<string, KeybindingImpl>>({})
/**
* Get user-defined keybindings.
*/
function getUserKeybindings() {
return userKeybindings.value
}
/**
* Get user-defined keybindings that unset default keybindings.
*/
function getUserUnsetKeybindings() {
return userUnsetKeybindings.value
}
const keybindingByKeyCombo = computed<Record<string, KeybindingImpl>>(() => {
const result: Record<string, KeybindingImpl> = {
...defaultKeybindings.value
@@ -262,8 +276,8 @@ export const useKeybindingStore = defineStore('keybinding', () => {
return {
keybindings,
userKeybindings,
userUnsetKeybindings,
getUserKeybindings,
getUserUnsetKeybindings,
getKeybinding,
getKeybindingsByCommandId,
getKeybindingByCommandId,

View File

@@ -289,6 +289,19 @@ export const SYSTEM_NODE_DEFS: Record<string, ComfyNodeDef> = {
output_node: false,
python_module: 'nodes',
description: 'Node that add notes to your project'
},
MarkdownNote: {
name: 'MarkdownNote',
display_name: 'Markdown Note',
category: 'utils',
input: { required: {}, optional: {} },
output: [],
output_name: [],
output_is_list: [],
output_node: false,
python_module: 'nodes',
description:
'Node that add notes to your project. Reformats text as markdown.'
}
}

View File

@@ -1,3 +1,4 @@
import _ from 'lodash'
import { defineStore } from 'pinia'
import type { TreeNode } from 'primevue/treenode'
import { computed, ref } from 'vue'
@@ -74,7 +75,12 @@ export const useSettingStore = defineStore('setting', () => {
* @param value - The value to set.
*/
async function set<K extends keyof Settings>(key: K, value: Settings[K]) {
const newValue = tryMigrateDeprecatedValue(settingsById.value[key], value)
// Clone the incoming value to prevent external mutations
const clonedValue = _.cloneDeep(value)
const newValue = tryMigrateDeprecatedValue(
settingsById.value[key],
clonedValue
)
const oldValue = get(key)
if (newValue === oldValue) return
@@ -89,7 +95,8 @@ export const useSettingStore = defineStore('setting', () => {
* @returns The value of the setting.
*/
function get<K extends keyof Settings>(key: K): Settings[K] {
return settingValues.value[key] ?? getDefaultValue(key)
// Clone the value when returning to prevent external mutations
return _.cloneDeep(settingValues.value[key] ?? getDefaultValue(key))
}
/**

View File

@@ -160,6 +160,95 @@ describe('useSettingStore', () => {
'differentvalue'
)
})
describe('object mutation prevention', () => {
beforeEach(() => {
const setting: SettingParams = {
id: 'test.setting',
name: 'Test setting',
type: 'hidden',
defaultValue: {}
}
store.addSetting(setting)
})
it('should prevent mutations of objects after set', async () => {
const originalObject = { foo: 'bar', nested: { value: 123 } }
await store.set('test.setting', originalObject)
// Attempt to mutate the original object
originalObject.foo = 'changed'
originalObject.nested.value = 456
// Get the stored value
const storedValue = store.get('test.setting')
// Verify the stored value wasn't affected by the mutation
expect(storedValue).toEqual({ foo: 'bar', nested: { value: 123 } })
})
it('should prevent mutations of retrieved objects', () => {
const initialValue = { foo: 'bar', nested: { value: 123 } }
// Set initial value
store.set('test.setting', initialValue)
// Get the value and try to mutate it
const retrievedValue = store.get('test.setting')
retrievedValue.foo = 'changed'
if (retrievedValue.nested) {
retrievedValue.nested.value = 456
}
// Get the value again
const newRetrievedValue = store.get('test.setting')
// Verify the stored value wasn't affected by the mutation
expect(newRetrievedValue).toEqual({
foo: 'bar',
nested: { value: 123 }
})
})
it('should prevent mutations of arrays after set', async () => {
const originalArray = [1, 2, { value: 3 }]
await store.set('test.setting', originalArray)
// Attempt to mutate the original array
originalArray.push(4)
if (typeof originalArray[2] === 'object') {
originalArray[2].value = 999
}
// Get the stored value
const storedValue = store.get('test.setting')
// Verify the stored value wasn't affected by the mutation
expect(storedValue).toEqual([1, 2, { value: 3 }])
})
it('should prevent mutations of retrieved arrays', () => {
const initialArray = [1, 2, { value: 3 }]
// Set initial value
store.set('test.setting', initialArray)
// Get the value and try to mutate it
const retrievedArray = store.get('test.setting')
retrievedArray.push(4)
if (typeof retrievedArray[2] === 'object') {
retrievedArray[2].value = 999
}
// Get the value again
const newRetrievedValue = store.get('test.setting')
// Verify the stored value wasn't affected by the mutation
expect(newRetrievedValue).toEqual([1, 2, { value: 3 }])
})
})
})
})