Files
ComfyUI_frontend/src/scripts/utils.ts
Chenlei Hu c56533bb23 Workflow Management Reworked (#1406)
* Merge temp userfile

Basic migration

Remove deprecated isFavourite

Rename

nit

nit

Rework open/load

Refactor save

Refactor delete

Remove workflow dep on manager

WIP

Change map to record

Fix directory

nit

isActive

Move

nit

Add unload

Add close workflow

Remove workflowManager.closeWorkflow

nit

Remove workflowManager.storePrompt

move from commandStore

move more from commandStore

nit

Use workflowservice

nit

nit

implement setWorkflow

nit

Remove workflows.ts

Fix strict errors

nit

nit

Resolves circular dep

nit

nit

Fix workflow switching

Add openworkflowPaths

Fix store

Fix key

Serialize by default

Fix proxy

nit

Update path

Proper sync

Fix tabs

WIP

nit

Resolve merge conflict

Fix userfile store tests

Update jest test

Update tabs

patch tests

Fix changeTracker init

Move insert to service

nit

Fix insert

nit

Handle bookmark rename

Refactor tests

Add delete workflow

Add test on deleting workflow

Add closeWorkflow tests

nit

* Fix path

* Move load next/previous

* Move logic from store to service

* nit

* nit

* nit

* nit

* nit

* Add ChangeTracker.initialState

* ChangeTracker load/unload

* Remove app.changeWorkflow

* Hook to app.ts

* Changetracker restore

* nit

* nit

* nit

* Add debug logs

* Remove unnecessary checkState on graphLoad

* nit

* Fix strict

* Fix temp workflow name

* Track ismodified

* Fix reactivity

* nit

* Fix graph equal

* nit

* update test

* nit

* nit

* Fix modified state

* nit

* Fix modified state

* Sidebar force close

* tabs force close

* Fix save

* Add load remote workflow test

* Force save

* Add save test

* nit

* Correctly handle delete last opened workflow

* nit

* Fix workflow rename

* Fix save

* Fix tests

* Fix strict

* Update playwright tests

* Fix filename conflict handling

* nit

* Merge temporary and persisted ref

* Update playwright expectations

* nit

* nit

* Fix saveAs

* Add playwright test

* nit
2024-11-05 11:03:27 -05:00

172 lines
4.2 KiB
TypeScript

// @ts-strict-ignore
import { api } from './api'
import type { ComfyApp } from './app'
import { $el } from './ui'
// Simple date formatter
const parts = {
d: (d) => d.getDate(),
M: (d) => d.getMonth() + 1,
h: (d) => d.getHours(),
m: (d) => d.getMinutes(),
s: (d) => d.getSeconds()
}
const format =
Object.keys(parts)
.map((k) => k + k + '?')
.join('|') + '|yyy?y?'
function formatDate(text: string, date: Date) {
return text.replace(new RegExp(format, 'g'), (text: string): string => {
if (text === 'yy') return (date.getFullYear() + '').substring(2)
if (text === 'yyyy') return date.getFullYear().toString()
if (text[0] in parts) {
const p = parts[text[0]](date)
return (p + '').padStart(text.length, '0')
}
return text
})
}
export function clone(obj: any) {
try {
if (typeof structuredClone !== 'undefined') {
return structuredClone(obj)
}
} catch (error) {
// structuredClone is stricter than using JSON.parse/stringify so fallback to that
}
return JSON.parse(JSON.stringify(obj))
}
export function applyTextReplacements(app: ComfyApp, value: string): string {
return value.replace(/%([^%]+)%/g, function (match, text) {
const split = text.split('.')
if (split.length !== 2) {
// Special handling for dates
if (split[0].startsWith('date:')) {
return formatDate(split[0].substring(5), new Date())
}
if (text !== 'width' && text !== 'height') {
// Dont warn on standard replacements
console.warn('Invalid replacement pattern', text)
}
return match
}
// Find node with matching S&R property name
let nodes = app.graph.nodes.filter(
(n) => n.properties?.['Node name for S&R'] === split[0]
)
// If we cant, see if there is a node with that title
if (!nodes.length) {
nodes = app.graph.nodes.filter((n) => n.title === split[0])
}
if (!nodes.length) {
console.warn('Unable to find node', split[0])
return match
}
if (nodes.length > 1) {
console.warn('Multiple nodes matched', split[0], 'using first match')
}
const node = nodes[0]
const widget = node.widgets?.find((w) => w.name === split[1])
if (!widget) {
console.warn('Unable to find widget', split[1], 'on node', split[0], node)
return match
}
return ((widget.value ?? '') + '').replaceAll(/\/|\\/g, '_')
})
}
export async function addStylesheet(
urlOrFile: string,
relativeTo?: string
): Promise<void> {
return new Promise((res, rej) => {
let url
if (urlOrFile.endsWith('.js')) {
url = urlOrFile.substr(0, urlOrFile.length - 2) + 'css'
} else {
url = new URL(
urlOrFile,
relativeTo ?? `${window.location.protocol}//${window.location.host}`
).toString()
}
$el('link', {
parent: document.head,
rel: 'stylesheet',
type: 'text/css',
href: url,
onload: res,
onerror: rej
})
})
}
/**
* @param { string } filename
* @param { Blob } blob
*/
export function downloadBlob(filename, blob) {
const url = URL.createObjectURL(blob)
const a = $el('a', {
href: url,
download: filename,
style: { display: 'none' },
parent: document.body
})
a.click()
setTimeout(function () {
a.remove()
window.URL.revokeObjectURL(url)
}, 0)
}
export function prop<T>(
target: object,
name: string,
defaultValue: T,
onChanged?: (
currentValue: T,
previousValue: T,
target: object,
name: string
) => void
): T {
let currentValue
Object.defineProperty(target, name, {
get() {
return currentValue
},
set(newValue) {
const prevValue = currentValue
currentValue = newValue
onChanged?.(currentValue, prevValue, target, name)
}
})
return defaultValue
}
export function getStorageValue(id: string) {
const clientId = api.clientId ?? api.initialClientId
return (
(clientId && sessionStorage.getItem(`${id}:${clientId}`)) ??
localStorage.getItem(id)
)
}
export function setStorageValue(id: string, value: string) {
const clientId = api.clientId ?? api.initialClientId
if (clientId) {
sessionStorage.setItem(`${id}:${clientId}`, value)
}
localStorage.setItem(id, value)
}