Enhanced notification & configure default queue action
This commit is contained in:
Submodule litegraph updated: 6ee800cc72...ed3ae7c477
@@ -17,7 +17,7 @@
|
|||||||
import GraphPage from './mobile/routes/graph.svelte';
|
import GraphPage from './mobile/routes/graph.svelte';
|
||||||
import ListSubWorkflowsPage from './mobile/routes/list-subworkflows.svelte';
|
import ListSubWorkflowsPage from './mobile/routes/list-subworkflows.svelte';
|
||||||
import SubWorkflowPage from './mobile/routes/subworkflow.svelte';
|
import SubWorkflowPage from './mobile/routes/subworkflow.svelte';
|
||||||
import type { Framework7Parameters } from "framework7/types";
|
import type { Framework7Parameters, Modal } from "framework7/types";
|
||||||
|
|
||||||
export let app: ComfyApp;
|
export let app: ComfyApp;
|
||||||
|
|
||||||
@@ -26,6 +26,25 @@
|
|||||||
// exitApp();
|
// exitApp();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
} else {
|
} else {
|
||||||
|
const $ = f7.$
|
||||||
|
const modalIn = $('.modal-in');
|
||||||
|
if (modalIn.length && "f7Modal" in modalIn[0]) {
|
||||||
|
(modalIn[0].f7Modal as Modal.Modal).close(true);
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($('.panel-active').length) {
|
||||||
|
f7.panel.close();
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const photoBrowserClose = $('.photo-browser-page a.link.popup-close')
|
||||||
|
if (photoBrowserClose.length > 0) {
|
||||||
|
(photoBrowserClose[0] as HTMLElement).click();
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
f7.dialog.close()
|
f7.dialog.close()
|
||||||
f7.view.main.router.back()
|
f7.view.main.router.back()
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -117,12 +117,15 @@
|
|||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit > :global(.v-pane > .block) {
|
&.edit {
|
||||||
border-color: var(--color-pink-500);
|
border-color: var(--color-pink-500);
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
border-style: dashed !important;
|
border-style: dashed !important;
|
||||||
margin: 0.2em;
|
margin: 2em 0.2em;
|
||||||
padding: 1.4em;
|
|
||||||
|
:global(> .v-pane) {
|
||||||
|
padding: 1.4em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* :global(.hide-block > .v-pane > .block) {
|
/* :global(.hide-block > .v-pane > .block) {
|
||||||
|
|||||||
@@ -65,6 +65,10 @@
|
|||||||
let graphSize = 0;
|
let graphSize = 0;
|
||||||
let graphTransitioning = false;
|
let graphTransitioning = false;
|
||||||
|
|
||||||
|
function queuePrompt() {
|
||||||
|
app.runDefaultQueueAction()
|
||||||
|
}
|
||||||
|
|
||||||
function toggleGraph() {
|
function toggleGraph() {
|
||||||
if (graphSize == 0) {
|
if (graphSize == 0) {
|
||||||
graphSize = 50;
|
graphSize = 50;
|
||||||
@@ -103,26 +107,7 @@
|
|||||||
if (!app?.lGraph)
|
if (!app?.lGraph)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const promptFilename = true; // TODO
|
app.querySave()
|
||||||
|
|
||||||
let filename = "workflow.json";
|
|
||||||
if (promptFilename) {
|
|
||||||
filename = prompt("Save workflow as:", filename);
|
|
||||||
if (!filename) return;
|
|
||||||
if (!filename.toLowerCase().endsWith(".json")) {
|
|
||||||
filename += ".json";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const date = new Date();
|
|
||||||
const formattedDate = date.toISOString().replace(/:/g, '-').replace(/\.\d{3}/g, '').replace('T', '_').replace("Z", "");
|
|
||||||
filename = `workflow-${formattedDate}.json`
|
|
||||||
}
|
|
||||||
|
|
||||||
const indent = 2
|
|
||||||
const json = JSON.stringify(app.serialize(), null, indent)
|
|
||||||
|
|
||||||
download(filename, json, "application/json")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function doLoad(): void {
|
function doLoad(): void {
|
||||||
@@ -241,6 +226,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="bottombar">
|
<div id="bottombar">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
|
{#if $layoutState.attrs.queuePromptButtonName != ""}
|
||||||
|
<Button variant="primary" on:click={queuePrompt}>
|
||||||
|
{$layoutState.attrs.queuePromptButtonName}
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
<Button variant="secondary" on:click={toggleGraph}>
|
<Button variant="secondary" on:click={toggleGraph}>
|
||||||
Toggle Graph
|
Toggle Graph
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import ComfyGraph from "$lib/ComfyGraph";
|
|||||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
import { promptToGraphVis, workflowToGraphVis } from "$lib/utils";
|
import { download, promptToGraphVis, workflowToGraphVis } from "$lib/utils";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
|
|
||||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||||
@@ -151,6 +151,7 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error loading previous workflow", err);
|
console.error("Error loading previous workflow", err);
|
||||||
|
notify(`Error loading previous workflow:\n${err}`, { type: "error", timeout: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
// We failed to restore a workflow so load the default
|
// We failed to restore a workflow so load the default
|
||||||
@@ -172,6 +173,8 @@ export default class ComfyApp {
|
|||||||
this.resizeCanvas();
|
this.resizeCanvas();
|
||||||
window.addEventListener("resize", this.resizeCanvas.bind(this));
|
window.addEventListener("resize", this.resizeCanvas.bind(this));
|
||||||
|
|
||||||
|
this.requestPermissions();
|
||||||
|
|
||||||
this.alreadySetup = true;
|
this.alreadySetup = true;
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -348,6 +351,13 @@ export default class ComfyApp {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private requestPermissions() {
|
||||||
|
if (Notification.permission === "default") {
|
||||||
|
Notification.requestPermission()
|
||||||
|
.then((result) => console.log("Notification status:", result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private setupColorScheme() {
|
private setupColorScheme() {
|
||||||
const setColor = (type: any, color: string) => {
|
const setColor = (type: any, color: string) => {
|
||||||
LGraphCanvas.DEFAULT_LINK_TYPE_COLORS[type] = color
|
LGraphCanvas.DEFAULT_LINK_TYPE_COLORS[type] = color
|
||||||
@@ -454,6 +464,37 @@ export default class ComfyApp {
|
|||||||
layoutState.initDefaultLayout();
|
layoutState.initDefaultLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runDefaultQueueAction() {
|
||||||
|
for (const node of this.lGraph.iterateNodesInOrder()) {
|
||||||
|
if ("onDefaultQueueAction" in node) {
|
||||||
|
(node as ComfyGraphNode).onDefaultQueueAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
querySave() {
|
||||||
|
const promptFilename = true; // TODO
|
||||||
|
|
||||||
|
let filename = "workflow.json";
|
||||||
|
if (promptFilename) {
|
||||||
|
filename = prompt("Save workflow as:", filename);
|
||||||
|
if (!filename) return;
|
||||||
|
if (!filename.toLowerCase().endsWith(".json")) {
|
||||||
|
filename += ".json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const date = new Date();
|
||||||
|
const formattedDate = date.toISOString().replace(/:/g, '-').replace(/\.\d{3}/g, '').replace('T', '_').replace("Z", "");
|
||||||
|
filename = `workflow-${formattedDate}.json`
|
||||||
|
}
|
||||||
|
|
||||||
|
const indent = 2
|
||||||
|
const json = JSON.stringify(this.serialize(), null, indent)
|
||||||
|
|
||||||
|
download(filename, json, "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the current graph workflow for sending to the API
|
* Converts the current graph workflow for sending to the API
|
||||||
* @returns The workflow and node links
|
* @returns The workflow and node links
|
||||||
@@ -644,7 +685,7 @@ export default class ComfyApp {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// this.ui.dialog.show(error.response || error.toString());
|
// this.ui.dialog.show(error.response || error.toString());
|
||||||
const mes = error.response || error.toString()
|
const mes = error.response || error.toString()
|
||||||
notify(`Error queuing prompt:\n${mes}`, null, "error")
|
notify(`Error queuing prompt:\n${mes}`, { type: "error" })
|
||||||
console.error(promptToGraphVis(p))
|
console.error(promptToGraphVis(p))
|
||||||
console.error("Error queuing prompt", mes, num, p)
|
console.error("Error queuing prompt", mes, num, p)
|
||||||
break;
|
break;
|
||||||
@@ -682,7 +723,7 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.error("No metadata found in image file.", pngInfo)
|
console.error("No metadata found in image file.", pngInfo)
|
||||||
notify("No metadata found in image file.")
|
notify("No metadata found in image file.", { type: "error" })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (file.type === "application/json" || file.name.endsWith(".json")) {
|
} else if (file.type === "application/json" || file.name.endsWith(".json")) {
|
||||||
@@ -727,7 +768,7 @@ export default class ComfyApp {
|
|||||||
if (inputNode && "doAutoConfig" in inputNode && comfyInput.widgetNodeType === inputNode.type) {
|
if (inputNode && "doAutoConfig" in inputNode && comfyInput.widgetNodeType === inputNode.type) {
|
||||||
console.debug("[ComfyApp] Reconfiguring combo widget", inputNode.type, comfyInput.config.values)
|
console.debug("[ComfyApp] Reconfiguring combo widget", inputNode.type, comfyInput.config.values)
|
||||||
const comfyComboNode = inputNode as nodes.ComfyComboNode;
|
const comfyComboNode = inputNode as nodes.ComfyComboNode;
|
||||||
comfyComboNode.doAutoConfig(comfyInput)
|
comfyComboNode.doAutoConfig(comfyInput, { includeProperties: new Set(["values"]), setWidgetTitle: false })
|
||||||
if (!comfyInput.config.values.includes(get(comfyComboNode.value))) {
|
if (!comfyInput.config.values.includes(get(comfyComboNode.value))) {
|
||||||
comfyComboNode.setValue(comfyInput.config.defaultValue || comfyInput.config.values[0])
|
comfyComboNode.setValue(comfyInput.config.defaultValue || comfyInput.config.values[0])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,24 +6,27 @@ import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWid
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||||
import type { ComfyWidgetNode, GalleryOutput } from "./ComfyWidgetNodes";
|
import type { ComfyWidgetNode, GalleryOutput } from "./ComfyWidgetNodes";
|
||||||
|
import type { NotifyOptions } from "$lib/notify";
|
||||||
|
import { convertComfyOutputToGradio } from "$lib/utils";
|
||||||
|
|
||||||
export class ComfyQueueEvents extends ComfyGraphNode {
|
export class ComfyQueueEvents extends ComfyGraphNode {
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
outputs: [
|
outputs: [
|
||||||
{ name: "beforeQueued", type: BuiltInSlotType.EVENT },
|
{ name: "beforeQueued", type: BuiltInSlotType.EVENT },
|
||||||
{ name: "afterQueued", type: BuiltInSlotType.EVENT }
|
{ name: "afterQueued", type: BuiltInSlotType.EVENT },
|
||||||
|
{ name: "onDefaultQueueAction", type: BuiltInSlotType.EVENT },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
private getActionParams(subgraph: string | null): any {
|
private getActionParams(subgraph: string | null): any {
|
||||||
let queue = get(queueState)
|
let queue = get(queueState)
|
||||||
let remaining = 0;
|
let queueRemaining = 0;
|
||||||
|
|
||||||
if (typeof queue.queueRemaining === "number")
|
if (typeof queue.queueRemaining === "number")
|
||||||
remaining = queue.queueRemaining
|
queueRemaining = queue.queueRemaining
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queueRemaining: remaining,
|
queueRemaining,
|
||||||
subgraph
|
subgraph
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,6 +39,16 @@ export class ComfyQueueEvents extends ComfyGraphNode {
|
|||||||
this.triggerSlot(1, this.getActionParams(subgraph))
|
this.triggerSlot(1, this.getActionParams(subgraph))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override onDefaultQueueAction() {
|
||||||
|
let queue = get(queueState)
|
||||||
|
let queueRemaining = 0;
|
||||||
|
|
||||||
|
if (typeof queue.queueRemaining === "number")
|
||||||
|
queueRemaining = queue.queueRemaining
|
||||||
|
|
||||||
|
this.triggerSlot(2, { queueRemaining })
|
||||||
|
}
|
||||||
|
|
||||||
override onSerialize(o: SerializedLGraphNode) {
|
override onSerialize(o: SerializedLGraphNode) {
|
||||||
super.onSerialize(o)
|
super.onSerialize(o)
|
||||||
}
|
}
|
||||||
@@ -54,6 +67,7 @@ export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperti
|
|||||||
|
|
||||||
export class ComfyStoreImagesAction extends ComfyGraphNode {
|
export class ComfyStoreImagesAction extends ComfyGraphNode {
|
||||||
override properties: ComfyStoreImagesActionProperties = {
|
override properties: ComfyStoreImagesActionProperties = {
|
||||||
|
tags: [],
|
||||||
images: null
|
images: null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,13 +189,15 @@ LiteGraph.registerNodeType({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export interface ComfyNotifyActionProperties extends ComfyGraphNodeProperties {
|
export interface ComfyNotifyActionProperties extends ComfyGraphNodeProperties {
|
||||||
message: string
|
message: string,
|
||||||
|
type: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyNotifyAction extends ComfyGraphNode {
|
export class ComfyNotifyAction extends ComfyGraphNode {
|
||||||
override properties: ComfyNotifyActionProperties = {
|
override properties: ComfyNotifyActionProperties = {
|
||||||
|
tags: [],
|
||||||
message: "Nya.",
|
message: "Nya.",
|
||||||
tags: []
|
type: "info"
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
@@ -192,10 +208,27 @@ export class ComfyNotifyAction extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override onAction(action: any, param: any) {
|
override onAction(action: any, param: any) {
|
||||||
const message = this.getInputData(0);
|
const message = this.getInputData(0) || this.properties.message;
|
||||||
if (message) {
|
if (!message)
|
||||||
notify(message);
|
return;
|
||||||
|
|
||||||
|
const options: NotifyOptions = {
|
||||||
|
type: this.properties.type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this event was triggered from a backend node and has the
|
||||||
|
// onExecuted arguments. If so then use the first image as the icon for
|
||||||
|
// native notifications.
|
||||||
|
if (param != null && typeof param === "object") {
|
||||||
|
if ("images" in param) {
|
||||||
|
const output = param as GalleryOutput;
|
||||||
|
const converted = convertComfyOutputToGradio(output);
|
||||||
|
if (converted.length > 0)
|
||||||
|
options.imageUrl = converted[0].data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(message, options);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +239,40 @@ LiteGraph.registerNodeType({
|
|||||||
type: "actions/notify"
|
type: "actions/notify"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export interface ComfyPlaySoundActionProperties extends ComfyGraphNodeProperties {
|
||||||
|
sound: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ComfyPlaySoundAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfyPlaySoundActionProperties = {
|
||||||
|
tags: [],
|
||||||
|
sound: "notification.mp3"
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "sound", type: "string" },
|
||||||
|
{ name: "trigger", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
const sound = this.getInputData(0) || this.properties.sound;
|
||||||
|
if (sound) {
|
||||||
|
const url = `${location.origin}/sound/${sound}`;
|
||||||
|
const audio = new Audio(url);
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyPlaySoundAction,
|
||||||
|
title: "Comfy.PlaySoundAction",
|
||||||
|
desc: "Plays a sound located under the sound/ directory.",
|
||||||
|
type: "actions/play_sound"
|
||||||
|
})
|
||||||
|
|
||||||
export interface ComfyExecuteSubgraphActionProperties extends ComfyGraphNodeProperties {
|
export interface ComfyExecuteSubgraphActionProperties extends ComfyGraphNodeProperties {
|
||||||
targetTag: string
|
targetTag: string
|
||||||
}
|
}
|
||||||
|
|||||||
47
src/lib/nodes/ComfyConfigureQueuePromptButton.ts
Normal file
47
src/lib/nodes/ComfyConfigureQueuePromptButton.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import layoutState from "$lib/stores/layoutState"
|
||||||
|
import { BuiltInSlotType, LGraphNode, LiteGraph, type ITextWidget, type OptionalSlots, type PropertyLayout, type SlotLayout, type Vector2 } from "@litegraph-ts/core"
|
||||||
|
|
||||||
|
export interface ComfyConfigureQueuePromptButtonProperties extends Record<string, any> {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ComfyConfigureQueuePromptButton extends LGraphNode {
|
||||||
|
override properties: ComfyConfigureQueuePromptButtonProperties = {
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "config", type: BuiltInSlotType.ACTION },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
static propertyLayout: PropertyLayout = [
|
||||||
|
]
|
||||||
|
|
||||||
|
static optionalSlots: OptionalSlots = {
|
||||||
|
}
|
||||||
|
|
||||||
|
override size: Vector2 = [60, 30];
|
||||||
|
|
||||||
|
constructor(title?: string) {
|
||||||
|
super(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any, options: { action_call?: string }) {
|
||||||
|
if (action === "config" && param != null) {
|
||||||
|
layoutState.update(state => {
|
||||||
|
if (typeof param === "string")
|
||||||
|
state.attrs.queuePromptButtonName = param || ""
|
||||||
|
else if (typeof param === "object" && "buttonName" in param)
|
||||||
|
state.attrs.queuePromptButtonName = param.buttonName || ""
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyConfigureQueuePromptButton,
|
||||||
|
title: "Comfy.ConfigureQueuePromptButton",
|
||||||
|
desc: "Sets the properties of the global queue prompt button",
|
||||||
|
type: "workflow/configure_queue_prompt_button"
|
||||||
|
})
|
||||||
@@ -28,10 +28,36 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
|
|
||||||
isBackendNode?: boolean;
|
isBackendNode?: boolean;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Triggered when the user presses the global "Queue Prompt" button in the fixed toolbar.
|
||||||
|
*/
|
||||||
|
onDefaultQueueAction?(): void;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Triggered before a prompt containing this node is passed to the backend.
|
||||||
|
*/
|
||||||
beforeQueued?(subgraph: string | null): void;
|
beforeQueued?(subgraph: string | null): void;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Triggered after a prompt containing this node is passed to the backend.
|
||||||
|
*/
|
||||||
afterQueued?(prompt: SerializedPrompt, subgraph: string | null): void;
|
afterQueued?(prompt: SerializedPrompt, subgraph: string | null): void;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Triggered when the backend sends a finished output back with this node's ID.
|
||||||
|
* Valid for output nodes like SaveImage and PreviewImage.
|
||||||
|
*/
|
||||||
onExecuted?(output: any): void;
|
onExecuted?(output: any): void;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allows you to manually specify an auto-config for certain input slot
|
||||||
|
* indices, so that when a ComfyWidgetNode is connected to the input slot it
|
||||||
|
* receives the specified min/max/values/etc.
|
||||||
|
* Otherwise the config passed from the backend is used.
|
||||||
|
*
|
||||||
|
* Use this if you're creating a frontend-only node and want some input
|
||||||
|
* slots to have auto-configs, like for connected combo box widgets.
|
||||||
|
*/
|
||||||
defaultWidgets?: DefaultWidgetLayout
|
defaultWidgets?: DefaultWidgetLayout
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -71,6 +97,12 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
this.addProperty("tags", [], "array")
|
this.addProperty("tags", [], "array")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adjusts output slot types to have the same type as the first connected
|
||||||
|
* input. Used for frontend-only nodes with inputs and outputs that act as
|
||||||
|
* wildcards, so that they can be connected to ComfyBackendNodes without
|
||||||
|
* rejection.
|
||||||
|
*/
|
||||||
private inheritSlotTypes(type: LConnectionKind, isConnected: boolean) {
|
private inheritSlotTypes(type: LConnectionKind, isConnected: boolean) {
|
||||||
// Prevent multiple connections to different types when we have no input
|
// Prevent multiple connections to different types when we have no input
|
||||||
if (isConnected && type === LConnectionKind.OUTPUT) {
|
if (isConnected && type === LConnectionKind.OUTPUT) {
|
||||||
@@ -229,6 +261,7 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override onResize(size: Vector2) {
|
override onResize(size: Vector2) {
|
||||||
|
// Snap to grid if shift is held down.
|
||||||
if ((window as any)?.app?.shiftDown) {
|
if ((window as any)?.app?.shiftDown) {
|
||||||
const w = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.size[0] / LiteGraph.CANVAS_GRID_SIZE);
|
const w = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.size[0] / LiteGraph.CANVAS_GRID_SIZE);
|
||||||
const h = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.size[1] / LiteGraph.CANVAS_GRID_SIZE);
|
const h = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.size[1] / LiteGraph.CANVAS_GRID_SIZE);
|
||||||
@@ -241,6 +274,8 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override onSerialize(o: SerializedLGraphNode) {
|
override onSerialize(o: SerializedLGraphNode) {
|
||||||
|
// Resync the widget node types for each input.
|
||||||
|
// This is so combo widget nodes will be correctly detected by ComfyApp.refreshComboInNodes().
|
||||||
for (let index = 0; index < this.inputs.length; index++) {
|
for (let index = 0; index < this.inputs.length; index++) {
|
||||||
const input = this.inputs[index]
|
const input = this.inputs[index]
|
||||||
const serInput = o.inputs[index]
|
const serInput = o.inputs[index]
|
||||||
@@ -254,6 +289,7 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
(serInput as any).defaultWidgetNode = null
|
(serInput as any).defaultWidgetNode = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(o as any).saveUserState = this.saveUserState
|
(o as any).saveUserState = this.saveUserState
|
||||||
if (!this.saveUserState) {
|
if (!this.saveUserState) {
|
||||||
this.stripUserState(o)
|
this.stripUserState(o)
|
||||||
@@ -262,6 +298,7 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override onConfigure(o: SerializedLGraphNode) {
|
override onConfigure(o: SerializedLGraphNode) {
|
||||||
|
// Save the litegraph type of the default ComfyWidgetNode for each input.
|
||||||
for (let index = 0; index < this.inputs.length; index++) {
|
for (let index = 0; index < this.inputs.length; index++) {
|
||||||
const input = this.inputs[index]
|
const input = this.inputs[index]
|
||||||
const serInput = o.inputs[index]
|
const serInput = o.inputs[index]
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ import CheckboxWidget from "$lib/widgets/CheckboxWidget.svelte";
|
|||||||
import RadioWidget from "$lib/widgets/RadioWidget.svelte";
|
import RadioWidget from "$lib/widgets/RadioWidget.svelte";
|
||||||
import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
|
import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
|
||||||
|
|
||||||
|
export type AutoConfigOptions = {
|
||||||
|
includeProperties?: Set<string> | null,
|
||||||
|
setDefaultValue?: boolean
|
||||||
|
setWidgetTitle?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NOTE: If you want to add a new widget but it has the same input/output type
|
* NOTE: If you want to add a new widget but it has the same input/output type
|
||||||
* as another one of the existing widgets, best to create a new "variant" of
|
* as another one of the existing widgets, best to create a new "variant" of
|
||||||
@@ -165,7 +171,9 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
setValue(value: any, noChangedEvent: boolean = false) {
|
setValue(value: any, noChangedEvent: boolean = false) {
|
||||||
if (noChangedEvent)
|
if (noChangedEvent)
|
||||||
this._noChangedEvent = true;
|
this._noChangedEvent = true;
|
||||||
this.value.set(this.parseValue(value))
|
|
||||||
|
const parsed = this.parseValue(value)
|
||||||
|
this.value.set(parsed)
|
||||||
|
|
||||||
// In case value.set() does not trigger onValueUpdated, we need to reset
|
// In case value.set() does not trigger onValueUpdated, we need to reset
|
||||||
// the counter here also.
|
// the counter here also.
|
||||||
@@ -222,8 +230,6 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
if ("noChangedEvent" in param)
|
if ("noChangedEvent" in param)
|
||||||
noChangedEvent = Boolean(param.noChangedEvent)
|
noChangedEvent = Boolean(param.noChangedEvent)
|
||||||
}
|
}
|
||||||
value = this.parseValue(value);
|
|
||||||
console.warn("[Widget] Store!", param, "=>", value, noChangedEvent)
|
|
||||||
this.setValue(value, noChangedEvent)
|
this.setValue(value, noChangedEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,18 +250,24 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
doAutoConfig(input: IComfyInputSlot) {
|
doAutoConfig(input: IComfyInputSlot, options: AutoConfigOptions = { setDefaultValue: true, setWidgetTitle: true }) {
|
||||||
// Copy properties from default config in input slot
|
// Copy properties from default config in input slot
|
||||||
const comfyInput = input as IComfyInputSlot;
|
const comfyInput = input as IComfyInputSlot;
|
||||||
for (const key in comfyInput.config)
|
for (const key in comfyInput.config) {
|
||||||
this.setProperty(key, comfyInput.config[key])
|
if (options.includeProperties == null || options.includeProperties.has(key))
|
||||||
|
this.setProperty(key, comfyInput.config[key])
|
||||||
|
}
|
||||||
|
|
||||||
if ("defaultValue" in this.properties)
|
if (options.setDefaultValue) {
|
||||||
this.setValue(this.properties.defaultValue)
|
if ("defaultValue" in this.properties)
|
||||||
|
this.setValue(this.properties.defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
const widget = layoutState.findLayoutForNode(this.id)
|
if (options.setWidgetTitle) {
|
||||||
if (widget && input.name !== "") {
|
const widget = layoutState.findLayoutForNode(this.id)
|
||||||
widget.attrs.title = input.name;
|
if (widget && input.name !== "") {
|
||||||
|
widget.attrs.title = input.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug("Property copy", input, this.properties)
|
console.debug("Property copy", input, this.properties)
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
export { default as ComfyReroute } from "./ComfyReroute"
|
export { default as ComfyReroute } from "./ComfyReroute"
|
||||||
export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes"
|
export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes"
|
||||||
export { ComfyQueueEvents, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyStoreImagesAction, ComfyExecuteSubgraphAction } from "./ComfyActionNodes"
|
export {
|
||||||
|
ComfyQueueEvents,
|
||||||
|
ComfyCopyAction,
|
||||||
|
ComfySwapAction,
|
||||||
|
ComfyNotifyAction,
|
||||||
|
ComfyPlaySoundAction,
|
||||||
|
ComfyStoreImagesAction,
|
||||||
|
ComfyExecuteSubgraphAction,
|
||||||
|
ComfySetNodeModeAction,
|
||||||
|
ComfySetNodeModeAdvancedAction
|
||||||
|
} from "./ComfyActionNodes"
|
||||||
export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode"
|
export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode"
|
||||||
export { default as ComfyValueControl } from "./ComfyValueControl"
|
export { default as ComfyValueControl } from "./ComfyValueControl"
|
||||||
export { default as ComfySelector } from "./ComfySelector"
|
export { default as ComfySelector } from "./ComfySelector"
|
||||||
export { default as ComfyImageCacheNode } from "./ComfyImageCacheNode"
|
export { default as ComfyImageCacheNode } from "./ComfyImageCacheNode"
|
||||||
export { default as ComfyTriggerNewEventNode } from "./ComfyTriggerNewEventNode"
|
export { default as ComfyTriggerNewEventNode } from "./ComfyTriggerNewEventNode"
|
||||||
|
export { default as ComfyConfigureQueuePromptButton } from "./ComfyConfigureQueuePromptButton"
|
||||||
|
|||||||
@@ -1,40 +1,82 @@
|
|||||||
import { toast } from "@zerodevx/svelte-toast";
|
import { toast } from "@zerodevx/svelte-toast";
|
||||||
import type { SvelteToastOptions } from "@zerodevx/svelte-toast/stores";
|
import type { SvelteToastOptions } from "@zerodevx/svelte-toast/stores";
|
||||||
|
import { type Notification } from "framework7/components/notification"
|
||||||
import { f7 } from "framework7-svelte"
|
import { f7 } from "framework7-svelte"
|
||||||
|
|
||||||
let notification;
|
export type NotifyOptions = {
|
||||||
|
title?: string,
|
||||||
|
type?: "neutral" | "info" | "warning" | "error" | "success",
|
||||||
|
imageUrl?: string,
|
||||||
|
timeout?: number | null
|
||||||
|
}
|
||||||
|
|
||||||
function notifyf7(text: string, title?: string, type?: string) {
|
function notifyf7(text: string, options: NotifyOptions) {
|
||||||
if (!f7)
|
if (!f7)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!notification) {
|
let closeTimeout = options.timeout
|
||||||
notification = f7.notification.create({
|
if (closeTimeout === undefined)
|
||||||
title: title,
|
closeTimeout = 3000;
|
||||||
titleRightText: 'now',
|
|
||||||
// subtitle: 'Notification with close on click',
|
const notification = f7.notification.create({
|
||||||
text: text,
|
title: options.title,
|
||||||
closeOnClick: true,
|
titleRightText: 'now',
|
||||||
closeTimeout: 3000,
|
// subtitle: 'Notification with close on click',
|
||||||
});
|
text: text,
|
||||||
}
|
closeOnClick: true,
|
||||||
// Open it
|
closeTimeout
|
||||||
|
});
|
||||||
notification.open();
|
notification.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyToast(text: string, title?: string, type?: string) {
|
function notifyToast(text: string, options: NotifyOptions) {
|
||||||
const options: SvelteToastOptions = {}
|
const toastOptions: SvelteToastOptions = {
|
||||||
|
dismissable: options.timeout !== null,
|
||||||
|
}
|
||||||
|
|
||||||
if (type === "error") {
|
if (options.type === "success") {
|
||||||
options.theme = {
|
toastOptions.theme = {
|
||||||
|
'--toastBackground': 'var(--color-green-600)',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (options.type === "info") {
|
||||||
|
toastOptions.theme = {
|
||||||
|
'--toastBackground': 'var(--color-blue-500)',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (options.type === "error") {
|
||||||
|
toastOptions.theme = {
|
||||||
'--toastBackground': 'var(--color-red-500)',
|
'--toastBackground': 'var(--color-red-500)',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.push(text, options);
|
toast.push(text, toastOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function notify(text: string, title?: string, type?: string) {
|
function notifyNative(text: string, options: NotifyOptions) {
|
||||||
notifyf7(text, title, type);
|
if (document.hasFocus())
|
||||||
notifyToast(text, title, type);
|
return;
|
||||||
|
|
||||||
|
const title = options.title || "ComfyBox"
|
||||||
|
const nativeOptions: NotificationOptions = {
|
||||||
|
body: text,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.imageUrl) {
|
||||||
|
nativeOptions.icon = options.imageUrl
|
||||||
|
nativeOptions.image = options.imageUrl
|
||||||
|
}
|
||||||
|
if (options.timeout === null) {
|
||||||
|
nativeOptions.requireInteraction = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notification = new Notification(title, nativeOptions);
|
||||||
|
|
||||||
|
notification.onclick = () => window.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function notify(text: string, options: NotifyOptions = {}) {
|
||||||
|
notifyf7(text, options);
|
||||||
|
notifyToast(text, options);
|
||||||
|
notifyNative(text, options)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,13 +28,9 @@ type DragItemEntry = {
|
|||||||
*/
|
*/
|
||||||
export type LayoutAttributes = {
|
export type LayoutAttributes = {
|
||||||
/*
|
/*
|
||||||
* Default subgraph to run when the "Queue Prompt" button in the bottom bar
|
* Name of the "Queue Prompt" button. Set to blank to hide the button.
|
||||||
* is pressed.
|
|
||||||
*
|
|
||||||
* If it's an empty string, all backend nodes will be included in the prompt
|
|
||||||
* instead.
|
|
||||||
*/
|
*/
|
||||||
defaultSubgraph: string
|
queuePromptButtonName: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -522,11 +518,11 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
|
|
||||||
// Workflow
|
// Workflow
|
||||||
{
|
{
|
||||||
name: "defaultSubgraph",
|
name: "queuePromptButtonName",
|
||||||
type: "string",
|
type: "string",
|
||||||
location: "workflow",
|
location: "workflow",
|
||||||
editable: true,
|
editable: true,
|
||||||
defaultValue: ""
|
defaultValue: "Queue Prompt"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -544,10 +540,16 @@ for (const cat of Object.values(ALL_ATTRIBUTES)) {
|
|||||||
export { ALL_ATTRIBUTES };
|
export { ALL_ATTRIBUTES };
|
||||||
|
|
||||||
const defaultWidgetAttributes: Attributes = {} as any
|
const defaultWidgetAttributes: Attributes = {} as any
|
||||||
|
const defaultWorkflowAttributes: LayoutAttributes = {} as any
|
||||||
for (const cat of Object.values(ALL_ATTRIBUTES)) {
|
for (const cat of Object.values(ALL_ATTRIBUTES)) {
|
||||||
for (const spec of Object.values(cat.specs)) {
|
for (const spec of Object.values(cat.specs)) {
|
||||||
if (spec.location === "widget" && spec.defaultValue != null) {
|
if (spec.defaultValue != null) {
|
||||||
defaultWidgetAttributes[spec.name] = spec.defaultValue;
|
if (spec.location === "widget") {
|
||||||
|
defaultWidgetAttributes[spec.name] = spec.defaultValue;
|
||||||
|
}
|
||||||
|
else if (spec.location === "workflow") {
|
||||||
|
defaultWorkflowAttributes[spec.name] = spec.defaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,7 +636,7 @@ const store: Writable<LayoutState> = writable({
|
|||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: true,
|
isConfiguring: true,
|
||||||
attrs: {
|
attrs: {
|
||||||
defaultSubgraph: ""
|
...defaultWorkflowAttributes
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -890,7 +892,7 @@ function initDefaultLayout() {
|
|||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: false,
|
isConfiguring: false,
|
||||||
attrs: {
|
attrs: {
|
||||||
defaultSubgraph: ""
|
...defaultWorkflowAttributes
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1004,7 +1006,7 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
|||||||
currentSelectionNodes: [],
|
currentSelectionNodes: [],
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: false,
|
isConfiguring: false,
|
||||||
attrs: data.attrs
|
attrs: { ...defaultWorkflowAttributes, ...data.attrs }
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug("[layoutState] deserialize", data, state)
|
console.debug("[layoutState] deserialize", data, state)
|
||||||
|
|||||||
@@ -54,11 +54,11 @@
|
|||||||
> .inner {
|
> .inner {
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: min-content;
|
|
||||||
|
|
||||||
:global(> label) {
|
:global(> .block > label) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
history.pushState({ type: "gallery" }, "");
|
||||||
|
|
||||||
mobileLightbox = f7.photoBrowser.create({
|
mobileLightbox = f7.photoBrowser.create({
|
||||||
photos: images,
|
photos: images,
|
||||||
@@ -152,7 +153,7 @@
|
|||||||
$: node.anyImageSelected = selected_image != null;
|
$: node.anyImageSelected = selected_image != null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if widget && node && nodeValue}
|
{#if widget && node && nodeValue && $nodeValue}
|
||||||
{#if widget.attrs.variant === "image"}
|
{#if widget.attrs.variant === "image"}
|
||||||
<div class="wrapper comfy-image-widget" style={widget.attrs.style || ""} bind:this={element}>
|
<div class="wrapper comfy-image-widget" style={widget.attrs.style || ""} bind:this={element}>
|
||||||
<Block variant="solid" padding={false}>
|
<Block variant="solid" padding={false}>
|
||||||
|
|||||||
@@ -156,7 +156,7 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
notify(response.error, null, "error")
|
notify(response.error, { type: "error" })
|
||||||
}
|
}
|
||||||
|
|
||||||
$nodeValue = normalise_file(_value, root, root_url) as GradioFileData[];
|
$nodeValue = normalise_file(_value, root, root_url) as GradioFileData[];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import layoutState from "$lib/stores/layoutState";
|
||||||
import queueState from "$lib/stores/queueState";
|
import queueState from "$lib/stores/queueState";
|
||||||
import { getNodeInfo } from "$lib/utils"
|
import { getNodeInfo } from "$lib/utils"
|
||||||
|
|
||||||
@@ -16,14 +17,28 @@
|
|||||||
let fileInput: HTMLInputElement = undefined;
|
let fileInput: HTMLInputElement = undefined;
|
||||||
|
|
||||||
function queuePrompt() {
|
function queuePrompt() {
|
||||||
app.queuePrompt(0, 1);
|
navigator.vibrate(20)
|
||||||
notify("Prompt was queued", "Queued");
|
app.runDefaultQueueAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshCombos() {
|
||||||
|
navigator.vibrate(20)
|
||||||
|
app.refreshComboInNodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSave(): void {
|
||||||
|
if (!app?.lGraph || !fileInput)
|
||||||
|
return;
|
||||||
|
|
||||||
|
navigator.vibrate(20)
|
||||||
|
app.querySave()
|
||||||
}
|
}
|
||||||
|
|
||||||
function doLoad(): void {
|
function doLoad(): void {
|
||||||
if (!app?.lGraph || !fileInput)
|
if (!app?.lGraph || !fileInput)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
navigator.vibrate(20)
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +46,15 @@
|
|||||||
app.handleFile(fileInput.files[0]);
|
app.handleFile(fileInput.files[0]);
|
||||||
fileInput.files = null;
|
fileInput.files = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function doSaveLocal(): void {
|
||||||
|
if (!app?.lGraph)
|
||||||
|
return;
|
||||||
|
|
||||||
|
navigator.vibrate(20)
|
||||||
|
app.saveStateToLocalStorage();
|
||||||
|
notify("Saved to local storage.")
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
@@ -51,7 +75,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Toolbar bottom>
|
<Toolbar bottom>
|
||||||
<Link on:click={() => app.refreshComboInNodes()}>🔄</Link>
|
{#if $layoutState.attrs.queuePromptButtonName != ""}
|
||||||
|
<Link on:click={queuePrompt}>
|
||||||
|
{$layoutState.attrs.queuePromptButtonName}
|
||||||
|
</Link>
|
||||||
|
{/if}
|
||||||
|
<Link on:click={refreshCombos}>🔄</Link>
|
||||||
|
<Link on:click={doSave}>Save</Link>
|
||||||
|
<Link on:click={doSaveLocal}>Save Local</Link>
|
||||||
<Link on:click={doLoad}>Load</Link>
|
<Link on:click={doLoad}>Load</Link>
|
||||||
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
|
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { get } from "svelte/store";
|
|
||||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
|
||||||
import { Button } from "@gradio/button";
|
|
||||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||||
import { Checkbox } from "@gradio/form"
|
|
||||||
import uiState from "$lib/stores/uiState";
|
|
||||||
import { ImageViewer } from "$lib/ImageViewer";
|
|
||||||
import { download } from "$lib/utils"
|
|
||||||
|
|
||||||
import { LGraph, LGraphNode } from "@litegraph-ts/core";
|
import { Page, Navbar, Button, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
||||||
import type { ComfyAPIStatus } from "$lib/api";
|
import defaultGraph from "$lib/defaultGraph";
|
||||||
import queueState from "$lib/stores/queueState";
|
|
||||||
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
|
||||||
|
|
||||||
export let app: ComfyApp | null = null;
|
export let app: ComfyApp | null = null;
|
||||||
|
|
||||||
|
async function doLoadDefault() {
|
||||||
|
var confirmed = confirm("Are you sure you want to clear the current workflow and load the default graph?");
|
||||||
|
if (confirmed) {
|
||||||
|
await app.deserialize(defaultGraph)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Page name="home">
|
<Page name="home">
|
||||||
@@ -34,4 +30,7 @@
|
|||||||
<i class="icon icon-f7" slot="media" />
|
<i class="icon icon-f7" slot="media" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
<Block strong outlineIos>
|
||||||
|
<Button fill={true} onClick={doLoadDefault}>Load Default Graph</Button>
|
||||||
|
</Block>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -21,6 +21,9 @@
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.container {
|
.container {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
// Disable pull to refresh
|
||||||
|
overscroll-behavior-y: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO generalize this to all properties!
|
// TODO generalize this to all properties!
|
||||||
|
|||||||
@@ -6,3 +6,7 @@ body {
|
|||||||
// Disable pull to refresh
|
// Disable pull to refresh
|
||||||
overscroll-behavior-y: contain;
|
overscroll-behavior-y: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-blue-500: #3985f5;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user