Workflows can receive images from other workflows/historical prompts
This commit is contained in:
Submodule litegraph updated: 42adb8dba1...3ce3a47871
@@ -6,7 +6,7 @@ export class ImageViewer {
|
||||
currentImages: string[] = []
|
||||
selectedIndex: number = -1;
|
||||
currentGallery: HTMLDivElement | null = null;
|
||||
private static _instance: ImageViewer;
|
||||
static _instance: ImageViewer;
|
||||
|
||||
static get instance(): ImageViewer {
|
||||
if (!ImageViewer._instance)
|
||||
@@ -62,6 +62,18 @@ export class ImageViewer {
|
||||
this.currentGallery = galleryElem;
|
||||
this.setModalImageSrc(imageUrls[index])
|
||||
this.lightboxModal.style.display = "flex";
|
||||
|
||||
const left = this.lightboxModal.querySelector<HTMLElement>(".modalPrev")
|
||||
const right = this.lightboxModal.querySelector<HTMLElement>(".modalNext")
|
||||
if (imageUrls.length <= 1) {
|
||||
left.style.display = "none"
|
||||
right.style.display = "none"
|
||||
}
|
||||
else {
|
||||
left.style.display = "block"
|
||||
right.style.display = "block"
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.modalImage.focus()
|
||||
}, 200)
|
||||
@@ -138,6 +150,9 @@ export class ImageViewer {
|
||||
}
|
||||
|
||||
showLightbox(source: HTMLImageElement) {
|
||||
if (this.lightboxModal.style.display != "none")
|
||||
this.closeModal();
|
||||
|
||||
const initiallyZoomed = true
|
||||
this.modalZoomSet(this.modalImage, initiallyZoomed)
|
||||
|
||||
|
||||
@@ -1,57 +1,39 @@
|
||||
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType, type INodeInputSlot, type NodeID, type NodeTypeSpec, type NodeTypeOpts, type SlotIndex, type UUID } from "@litegraph-ts/core";
|
||||
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
|
||||
import ComfyAPI, { type ComfyAPIStatusResponse, type ComfyBoxPromptExtraData, type ComfyPromptRequest, type ComfyNodeID, type PromptID, type QueueItemType } from "$lib/api"
|
||||
import { importA1111, parsePNGMetadata } from "$lib/pnginfo";
|
||||
import EventEmitter from "events";
|
||||
import type TypedEmitter from "typed-emitter";
|
||||
import ComfyAPI, { type ComfyAPIStatusResponse, type ComfyBoxPromptExtraData, type ComfyNodeID, type ComfyPromptRequest, type PromptID, type QueueItemType } from "$lib/api";
|
||||
import { parsePNGMetadata } from "$lib/pnginfo";
|
||||
import { BuiltInSlotType, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type INodeInputSlot, type LGraphNodeConstructor, type NodeID, type NodeTypeOpts, type SerializedLGraph, type SlotIndex } from "@litegraph-ts/core";
|
||||
import A1111PromptModal from "./modal/A1111PromptModal.svelte";
|
||||
import ConfirmConvertWithMissingNodeTypesModal from "./modal/ConfirmConvertWithMissingNodeTypesModal.svelte";
|
||||
import MissingNodeTypesModal from "./modal/MissingNodeTypesModal.svelte";
|
||||
import WorkflowLoadErrorModal from "./modal/WorkflowLoadErrorModal.svelte";
|
||||
import ConfirmConvertWithMissingNodeTypesModal from "./modal/ConfirmConvertWithMissingNodeTypesModal.svelte";
|
||||
|
||||
import * as nodes from "$lib/nodes/index";
|
||||
|
||||
// Import nodes
|
||||
import "@litegraph-ts/nodes-basic"
|
||||
import "@litegraph-ts/nodes-events"
|
||||
import "@litegraph-ts/nodes-logic"
|
||||
import "@litegraph-ts/nodes-math"
|
||||
import "@litegraph-ts/nodes-strings"
|
||||
import "$lib/nodes/index"
|
||||
import "$lib/nodes/widgets/index"
|
||||
import "$lib/nodes/actions/index"
|
||||
import * as nodes from "$lib/nodes/index"
|
||||
import * as widgets from "$lib/nodes/widgets/index"
|
||||
|
||||
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import { type SvelteComponentDev } from "svelte/internal";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import { defaultWorkflowAttributes, type LayoutState, type SerializedLayoutState, type WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import ComfyGraph from "$lib/ComfyGraph";
|
||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import { tick } from "svelte";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { basename, capitalize, download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range, workflowToGraphVis } from "$lib/utils";
|
||||
import notify from "$lib/notify";
|
||||
import configState from "$lib/stores/configState";
|
||||
import { blankGraph } from "$lib/defaultGraph";
|
||||
import type { SerializedPromptOutput } from "$lib/utils";
|
||||
import ComfyPromptSerializer, { UpstreamNodeLocator, isActiveBackendNode } from "./ComfyPromptSerializer";
|
||||
import { iterateNodeDefInputs, type ComfyNodeDef, isBackendNodeDefInputType, iterateNodeDefOutputs } from "$lib/ComfyNodeDef";
|
||||
import { ComfyComboNode } from "$lib/nodes/widgets";
|
||||
import parseA1111, { type A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import convertA1111ToStdPrompt from "$lib/convertA1111ToStdPrompt";
|
||||
import type { ComfyBoxStdPrompt } from "$lib/ComfyBoxStdPrompt";
|
||||
import ComfyBoxStdPromptSerializer from "$lib/ComfyBoxStdPromptSerializer";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import layoutStates from "$lib/stores/layoutStates";
|
||||
import { ComfyWorkflow, type WorkflowAttributes, type WorkflowInstID } from "$lib/stores/workflowState";
|
||||
import workflowState from "$lib/stores/workflowState";
|
||||
import convertVanillaWorkflow, { type ComfyVanillaWorkflow } from "$lib/convertVanillaWorkflow";
|
||||
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
|
||||
import { isBackendNodeDefInputType, iterateNodeDefInputs, iterateNodeDefOutputs, type ComfyNodeDef } from "$lib/ComfyNodeDef";
|
||||
import convertA1111ToStdPrompt from "$lib/convertA1111ToStdPrompt";
|
||||
import convertVanillaWorkflow from "$lib/convertVanillaWorkflow";
|
||||
import { blankGraph } from "$lib/defaultGraph";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import { ComfyComboNode } from "$lib/nodes/widgets";
|
||||
import notify from "$lib/notify";
|
||||
import parseA1111, { type A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import configState from "$lib/stores/configState";
|
||||
import layoutStates, { defaultWorkflowAttributes, type SerializedLayoutState } from "$lib/stores/layoutStates";
|
||||
import modalState from "$lib/stores/modalState";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import workflowState, { ComfyWorkflow, type WorkflowAttributes, type WorkflowInstID } from "$lib/stores/workflowState";
|
||||
import type { SerializedPromptOutput } from "$lib/utils";
|
||||
import { basename, capitalize, download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range } from "$lib/utils";
|
||||
import { tick } from "svelte";
|
||||
import { type SvelteComponentDev } from "svelte/internal";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import ComfyPromptSerializer, { isActiveBackendNode, UpstreamNodeLocator } from "./ComfyPromptSerializer";
|
||||
|
||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||
|
||||
@@ -556,9 +538,14 @@ export default class ComfyApp {
|
||||
this.ctrlDown = e.ctrlKey;
|
||||
|
||||
// Queue prompt using ctrl or command + enter
|
||||
if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.keyCode === 13 || e.keyCode === 10)) {
|
||||
if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.code === "Enter" || e.keyCode === 10)) {
|
||||
e.preventDefault();
|
||||
this.runDefaultQueueAction();
|
||||
}
|
||||
else if ((e.ctrlKey) && (e.key === "s" || e.code === "KeyS")) {
|
||||
e.preventDefault();
|
||||
this.saveStateToLocalStorage();
|
||||
}
|
||||
});
|
||||
window.addEventListener("keyup", (e) => {
|
||||
this.shiftDown = e.shiftKey;
|
||||
|
||||
@@ -223,8 +223,13 @@
|
||||
expandAll = false
|
||||
}
|
||||
|
||||
$: if(!showModal)
|
||||
selectedPrompt = null;
|
||||
function closeModal() {
|
||||
selectedPrompt = null
|
||||
selectedImages = []
|
||||
showModal = false;
|
||||
expandAll = false;
|
||||
console.warn("CLOSEMODAL")
|
||||
}
|
||||
|
||||
let queued = false
|
||||
$: queued = Boolean($queueState.runningNodeID || $queueState.progress);
|
||||
@@ -238,9 +243,11 @@
|
||||
<div slot="header" class="prompt-modal-header">
|
||||
<h1 style="padding-bottom: 1rem;">Prompt Details</h1>
|
||||
</div>
|
||||
<svelte:fragment let:closeDialog>
|
||||
{#if selectedPrompt}
|
||||
<PromptDisplay prompt={selectedPrompt} images={selectedImages} {expandAll} />
|
||||
<PromptDisplay closeModal={() => { closeModal(); closeDialog(); }} {app} prompt={selectedPrompt} images={selectedImages} {expandAll} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<div slot="buttons" let:closeDialog>
|
||||
<Button variant="secondary" on:click={closeDialog}>
|
||||
Close
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
top:0;
|
||||
right:0.5rem;
|
||||
margin: 0.5rem;
|
||||
z-index: 1000000000;
|
||||
z-index: var(--layer-1);
|
||||
|
||||
opacity: 70%;
|
||||
background: var(--neutral-700);
|
||||
|
||||
@@ -2,19 +2,18 @@
|
||||
import modalState, { type ModalButton, type ModalData } from "$lib/stores/modalState";
|
||||
import { Button } from "@gradio/button";
|
||||
import Modal from "./Modal.svelte";
|
||||
import { SvelteComponentDev } from "svelte/internal";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
function onClose(modal: ModalData | null) {
|
||||
if (modal == null)
|
||||
return;
|
||||
|
||||
if (modal.onClose)
|
||||
modal.onClose()
|
||||
|
||||
modalState.closeModal(modal.id)
|
||||
}
|
||||
|
||||
function onButtonClicked(button: ModalButton, closeDialog: Function) {
|
||||
button.onClick();
|
||||
function onButtonClicked(modal: ModalData, button: ModalButton, closeDialog: Function) {
|
||||
button.onClick(modal);
|
||||
|
||||
if (button.closeOnClick !== false) {
|
||||
closeDialog()
|
||||
@@ -31,13 +30,13 @@
|
||||
</div>
|
||||
<svelte:fragment>
|
||||
{#if modal != null && modal.svelteComponent != null}
|
||||
<svelte:component this={modal.svelteComponent} {...modal.svelteProps}/>
|
||||
<svelte:component this={modal.svelteComponent} {...modal.svelteProps} _modal={modal}/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<div slot="buttons" class="buttons" let:closeDialog>
|
||||
{#if modal != null && modal.buttons?.length > 0}
|
||||
{#each modal.buttons as button}
|
||||
<Button variant={button.variant} on:click={() => onButtonClicked(button, closeDialog)}>
|
||||
<Button variant={button.variant} on:click={() => onButtonClicked(modal, button, closeDialog)}>
|
||||
{button.name}
|
||||
</Button>
|
||||
{/each}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
export let showModal; // boolean
|
||||
export let closeOnClick = true; // boolean
|
||||
export const closeDialog = _ => doClose();
|
||||
export const closeDialog = () => doClose();
|
||||
|
||||
let dialog; // HTMLDialogElement
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
>
|
||||
<div on:click|stopPropagation>
|
||||
<slot name="header" />
|
||||
<slot />
|
||||
<slot {closeDialog} />
|
||||
<div class="button-row">
|
||||
<slot name="buttons" {closeDialog}>
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
|
||||
@@ -8,14 +8,23 @@
|
||||
import Gallery from "$lib/components/gradio/gallery/Gallery.svelte";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import type { Styles } from "@gradio/utils";
|
||||
import { countNewLines } from "$lib/utils";
|
||||
import { comfyFileToComfyBoxMetadata, comfyURLToComfyFile, countNewLines } from "$lib/utils";
|
||||
import ReceiveOutputTargets from "./modal/ReceiveOutputTargets.svelte";
|
||||
import workflowState, { type ComfyWorkflow, type WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
||||
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
||||
import type ComfyApp from "./ComfyApp";
|
||||
|
||||
const splitLength = 50;
|
||||
|
||||
export let prompt: SerializedPromptInputsAll;
|
||||
export let images: string[] = [];
|
||||
export let images: string[] = []; // list of image URLs to ComfyUI's /view? endpoint
|
||||
export let isMobile: boolean = false;
|
||||
export let expandAll: boolean = false;
|
||||
export let closeModal: () => void;
|
||||
export let app: ComfyApp;
|
||||
let isPromptOpen = expandAll;
|
||||
|
||||
let selected_image: number | null = null;
|
||||
|
||||
let galleryStyle: Styles = {
|
||||
grid_cols: [2],
|
||||
@@ -23,6 +32,32 @@
|
||||
height: "var(--size-96)"
|
||||
}
|
||||
|
||||
let receiveTargets: WorkflowReceiveOutputTargets[] = [];
|
||||
let comfyBoxImages = []
|
||||
let litegraphType = "(none)"
|
||||
|
||||
$: if (images.length > 0) {
|
||||
// since the image links come from gradio, have to parse the URL for the
|
||||
// ComfyImageLocation params
|
||||
comfyBoxImages = images.map(comfyURLToComfyFile)
|
||||
.map(comfyFileToComfyBoxMetadata);
|
||||
}
|
||||
else {
|
||||
comfyBoxImages = []
|
||||
}
|
||||
|
||||
$: if (comfyBoxImages.length > 0) {
|
||||
if (selected_image != null)
|
||||
litegraphType = "COMFYBOX_IMAGE"
|
||||
else
|
||||
litegraphType = "COMFYBOX_IMAGES"
|
||||
receiveTargets = workflowState.findReceiveOutputTargets(litegraphType)
|
||||
}
|
||||
else {
|
||||
litegraphType = "(none)"
|
||||
receiveTargets = []
|
||||
}
|
||||
|
||||
function isInputLink(input: SerializedPromptInput): boolean {
|
||||
return Array.isArray(input)
|
||||
&& input.length === 2
|
||||
@@ -65,9 +100,36 @@
|
||||
// TODO dialog renders over it
|
||||
// ImageViewer.instance.showLightbox(e.detail)
|
||||
}
|
||||
|
||||
function sendOutput(workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode) {
|
||||
if (workflow == null || targetNode == null)
|
||||
return
|
||||
|
||||
let value = null;
|
||||
if (targetNode.properties.type === "COMFYBOX_IMAGE") {
|
||||
if (selected_image != null)
|
||||
value = comfyBoxImages[selected_image]
|
||||
else
|
||||
value = comfyBoxImages[0]
|
||||
}
|
||||
else if (targetNode.properties.type === "COMFYBOX_IMAGES") {
|
||||
value = comfyBoxImages
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
targetNode.receiveOutput(value);
|
||||
workflowState.setActiveWorkflow(app.lCanvas, workflow.id)
|
||||
|
||||
closeModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="prompt-display">
|
||||
<div class="prompt-and-sends">
|
||||
<Block>
|
||||
<Accordion label="Prompt" open={isPromptOpen}>
|
||||
<div class="scroll-container">
|
||||
<Block>
|
||||
{#each Object.entries(prompt) as [nodeID, inputs], i}
|
||||
@@ -117,6 +179,21 @@
|
||||
{/each}
|
||||
</Block>
|
||||
</div>
|
||||
</Accordion>
|
||||
</Block>
|
||||
<Block>
|
||||
<Accordion label="Send Outputs To..." open={true}>
|
||||
<Block>
|
||||
<BlockTitle>Output type: {litegraphType}</BlockTitle>
|
||||
{#if receiveTargets.length > 0}
|
||||
<ReceiveOutputTargets {receiveTargets} on:select={(e) => sendOutput(e.detail.workflow, e.detail.targetNode)} />
|
||||
{:else}
|
||||
<div class="outputs-message">No receive output targets found across all workflows.</div>
|
||||
{/if}
|
||||
</Block>
|
||||
</Accordion>
|
||||
</Block>
|
||||
</div>
|
||||
{#if images.length > 0}
|
||||
<div class="image-container">
|
||||
<Block>
|
||||
@@ -128,6 +205,7 @@
|
||||
root={""}
|
||||
root_url={""}
|
||||
on:clicked={onGalleryImageClicked}
|
||||
bind:selected_image
|
||||
/>
|
||||
</Block>
|
||||
</div>
|
||||
@@ -148,6 +226,10 @@
|
||||
@media (min-width: 1600px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.prompt-and-sends {
|
||||
width: 50%;
|
||||
|
||||
.scroll-container {
|
||||
position: relative;
|
||||
@@ -155,21 +237,6 @@
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
flex: 1 1 0%;
|
||||
max-width: 30vw;
|
||||
|
||||
> :global(.block) {
|
||||
height: 100%;
|
||||
|
||||
:global(> .preview) {
|
||||
height: 100%;
|
||||
max-height: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
@@ -220,4 +287,23 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.outputs-message {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
flex: 1 1 0%;
|
||||
width: 50%;
|
||||
|
||||
> :global(.block) {
|
||||
height: 100%;
|
||||
|
||||
:global(> .preview) {
|
||||
height: 100%;
|
||||
max-height: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -104,7 +104,6 @@
|
||||
.accordion {
|
||||
background: var(--panel-background-fill);
|
||||
|
||||
|
||||
:global(> .block .block) {
|
||||
background: var(--panel-background-fill);
|
||||
}
|
||||
|
||||
79
src/lib/components/modal/ReceiveOutputTargets.svelte
Normal file
79
src/lib/components/modal/ReceiveOutputTargets.svelte
Normal file
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
||||
import type { ComfyWorkflow, WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
import { Button } from "@gradio/button";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
select: { workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode };
|
||||
}>();
|
||||
|
||||
export let receiveTargets: WorkflowReceiveOutputTargets[] = [];
|
||||
|
||||
function onSelected( workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode ) {
|
||||
dispatch("select", {
|
||||
workflow,
|
||||
targetNode
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="scroll-container">
|
||||
{#if receiveTargets.length > 0}
|
||||
{#each receiveTargets as { workflow, targetNodes }}
|
||||
<Block>
|
||||
<BlockTitle>{workflow.attrs.title}</BlockTitle>
|
||||
{#each targetNodes as targetNode}
|
||||
<Block>
|
||||
<div class="target">
|
||||
<div class="target-name-and-desc">
|
||||
<div class="target-name">➤ {targetNode.properties.name}</div>
|
||||
{#if targetNode.properties.description}
|
||||
<div class="target-desc">{targetNode.properties.description}</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="send-button">
|
||||
<Button variant="primary" on:click={() => onSelected(workflow, targetNode)}>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Block>
|
||||
{/each}
|
||||
</Block>
|
||||
{/each}
|
||||
{:else}
|
||||
<div>(No receive targets found.)</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.scroll-container {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
flex: 1 1 0%;
|
||||
height: 100%;
|
||||
|
||||
> :global(.block) {
|
||||
background: var(--panel-background-fill);
|
||||
}
|
||||
}
|
||||
|
||||
.target {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
text-align: left;
|
||||
|
||||
.target-name-and-desc {
|
||||
margin: auto auto auto 0;
|
||||
left: 0px;
|
||||
|
||||
.target-desc {
|
||||
opacity: 65%;
|
||||
font-size: 11pt;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
79
src/lib/components/modal/SendOutputModal.svelte
Normal file
79
src/lib/components/modal/SendOutputModal.svelte
Normal file
@@ -0,0 +1,79 @@
|
||||
<script lang="ts" context="module">
|
||||
export type SendOutputModalResult = {
|
||||
workflow?: ComfyWorkflow,
|
||||
targetNode?: ComfyReceiveOutputNode,
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { ModalData, ModalState } from "$lib/stores/modalState";
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
import type { SlotType } from "@litegraph-ts/core";
|
||||
import type { Writable } from "svelte/store";
|
||||
import { StaticImage } from "$lib/components/gradio/image";
|
||||
import type { ComfyWorkflow, WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
||||
import { comfyBoxImageToComfyURL } from "$lib/utils";
|
||||
import { Button } from "@gradio/button";
|
||||
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
||||
import ReceiveOutputTargets from "./ReceiveOutputTargets.svelte";
|
||||
|
||||
export let value: any;
|
||||
export let type: SlotType;
|
||||
export let receiveTargets: WorkflowReceiveOutputTargets[] = [];
|
||||
|
||||
export let _modal: ModalData;
|
||||
|
||||
let images = []
|
||||
|
||||
if (type === "COMFYBOX_IMAGE") {
|
||||
images = [comfyBoxImageToComfyURL(value)];
|
||||
}
|
||||
|
||||
function sendOutput(workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode) {
|
||||
const result: SendOutputModalResult = {
|
||||
workflow,
|
||||
targetNode
|
||||
}
|
||||
_modal.state.set(result)
|
||||
_modal.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="send-output-modal">
|
||||
<div class="targets-container">
|
||||
<Block>
|
||||
<span>Type: {type}</span>
|
||||
</Block>
|
||||
<ReceiveOutputTargets {receiveTargets} on:select={(e) => sendOutput(e.detail.workflow, e.detail.targetNode)} />
|
||||
</div>
|
||||
<div class="output-display">
|
||||
<Block>
|
||||
{#if type === "COMFYBOX_IMAGE"}
|
||||
<StaticImage show_label={false} label="Image" value={images[0]} />
|
||||
{/if}
|
||||
</Block>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.send-output-modal {
|
||||
width: 60vw;
|
||||
height: 70vh;
|
||||
color: none;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow-y: none;
|
||||
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.targets-container {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.output-display {
|
||||
width: 40vw;
|
||||
}
|
||||
</style>
|
||||
9
src/lib/nodeImports.ts
Normal file
9
src/lib/nodeImports.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import "@litegraph-ts/nodes-basic";
|
||||
import "@litegraph-ts/nodes-events";
|
||||
import "@litegraph-ts/nodes-logic";
|
||||
import "@litegraph-ts/nodes-math";
|
||||
import "@litegraph-ts/nodes-strings";
|
||||
|
||||
import "$lib/nodes/index";
|
||||
import "$lib/nodes/widgets/index";
|
||||
import "$lib/nodes/actions/index";
|
||||
@@ -150,9 +150,7 @@ export default class ComfyGraphNode extends LGraphNode {
|
||||
const link = currentNode.getUpstreamLink();
|
||||
if (link !== null) {
|
||||
const node = this.graph.getNodeById(link.origin_id) as ComfyGraphNode;
|
||||
console.warn(node.type)
|
||||
if (node.canInheritSlotTypes) {
|
||||
console.log("REROUTE2", node)
|
||||
if (node === this) {
|
||||
// We've found a circle
|
||||
currentNode.disconnectInput(link.target_slot);
|
||||
@@ -193,7 +191,6 @@ export default class ComfyGraphNode extends LGraphNode {
|
||||
const node = this.graph.getNodeById(link.target_id) as ComfyGraphNode;
|
||||
|
||||
if (node.canInheritSlotTypes) {
|
||||
console.log("REROUTE", node)
|
||||
// Follow reroute nodes
|
||||
nodes.push(node);
|
||||
updateNodes.push(node);
|
||||
|
||||
91
src/lib/nodes/actions/ComfyReceiveOutputNode.ts
Normal file
91
src/lib/nodes/actions/ComfyReceiveOutputNode.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { BuiltInSlotType, LiteGraph, type IComboWidget, type SlotLayout, type SlotType, type ITextWidget, BASE_SLOT_TYPES, LGraphNode, type Vector2, BuiltInSlotShape, LGraphCanvas } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||
import { getLitegraphType } from "$lib/utils";
|
||||
import notify from "$lib/notify";
|
||||
|
||||
export interface ComfyReceiveOutputNodeProperties extends ComfyGraphNodeProperties {
|
||||
name: string,
|
||||
description: string,
|
||||
type: SlotType
|
||||
}
|
||||
|
||||
function getOutputTypes(widget: IComboWidget, node: LGraphNode): string[] {
|
||||
let result = []
|
||||
result = result.concat(Array.from(BASE_SLOT_TYPES))
|
||||
result.push("COMFYBOX_IMAGE")
|
||||
result.push("COMFYBOX_IMAGES")
|
||||
return result
|
||||
}
|
||||
|
||||
export default class ComfyReceiveOutputNode extends ComfyGraphNode {
|
||||
override properties: ComfyReceiveOutputNodeProperties = {
|
||||
tags: [],
|
||||
name: "Image",
|
||||
description: "Generic image input.",
|
||||
type: "COMFYBOX_IMAGE"
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
outputs: [
|
||||
{ name: "received", type: BuiltInSlotType.EVENT }
|
||||
]
|
||||
}
|
||||
|
||||
override size: Vector2 = [180, 90];
|
||||
|
||||
nameWidget: ITextWidget;
|
||||
descriptionWidget: ITextWidget;
|
||||
typeWidget: IComboWidget;
|
||||
|
||||
isActive: boolean = false;
|
||||
|
||||
private _queue: any[] = []
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title)
|
||||
|
||||
this.nameWidget = this.addWidget("text", "Name", this.properties.name, "name");
|
||||
this.descriptionWidget = this.addWidget("text", "Desc.", this.properties.description, "description", { multiline: true });
|
||||
this.typeWidget = this.addWidget<IComboWidget>("combo", "Type", "" + this.properties.type, "type", { values: getOutputTypes });
|
||||
}
|
||||
|
||||
override onPropertyChanged(property: any, value: any) {
|
||||
if (property === "type") {
|
||||
const color = LGraphCanvas.DEFAULT_CONNECTION_COLORS_BY_TYPE[value] || LGraphCanvas.DEFAULT_CONNECTION_COLORS_BY_TYPE[BuiltInSlotType.EVENT];
|
||||
this.outputs[0].color_on = color
|
||||
this.outputs[0].color_off = color
|
||||
}
|
||||
}
|
||||
|
||||
override getTitle(): string {
|
||||
if (this.flags.collapsed) {
|
||||
return this.properties.name;
|
||||
}
|
||||
return this.title;
|
||||
}
|
||||
|
||||
override onExecute() {
|
||||
while (this._queue.length > 0)
|
||||
this.triggerSlot(0, this._queue.splice(0, 1))
|
||||
}
|
||||
|
||||
receiveOutput(value: any) {
|
||||
const type = getLitegraphType(value);
|
||||
console.warn("receive", this.id, value, type)
|
||||
|
||||
if (type !== this.properties.type) {
|
||||
console.error(`Output type mismatch! ${type} != ${this.properties.type}`)
|
||||
notify("Output type mismatch!", { type: "error" })
|
||||
return;
|
||||
}
|
||||
|
||||
this._queue.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfyReceiveOutputNode,
|
||||
title: "Comfy.ReceiveOutput",
|
||||
desc: "Receives a workflow output sent from elsewhere",
|
||||
type: "events/receive_output"
|
||||
})
|
||||
83
src/lib/nodes/actions/ComfySendOutputAction.ts
Normal file
83
src/lib/nodes/actions/ComfySendOutputAction.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import modalState, { type ModalData, type ModalState } from "$lib/stores/modalState";
|
||||
import { getLitegraphType } from "$lib/utils";
|
||||
import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||
|
||||
import SendOutputModal, { type SendOutputModalResult } from "$lib/components/modal/SendOutputModal.svelte";
|
||||
import notify from "$lib/notify";
|
||||
import workflowState from "$lib/stores/workflowState";
|
||||
import { get } from "svelte/store";
|
||||
import type ComfyApp from "$lib/components/ComfyApp";
|
||||
|
||||
export interface ComfySendOutputActionProperties extends ComfyGraphNodeProperties {
|
||||
}
|
||||
|
||||
export default class ComfySendOutputAction extends ComfyGraphNode {
|
||||
override properties: ComfySendOutputActionProperties = {
|
||||
tags: [],
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
inputs: [
|
||||
{ name: "value", type: "*" },
|
||||
{ name: "trigger", type: BuiltInSlotType.ACTION }
|
||||
],
|
||||
}
|
||||
|
||||
isActive: boolean = false;
|
||||
|
||||
override onAction(action: any, param: any) {
|
||||
const value = this.getInputData(0);
|
||||
if (value == null) {
|
||||
notify("No workflow data to send!", { type: "error" })
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isActive)
|
||||
return;
|
||||
|
||||
let type = getLitegraphType(value);
|
||||
const receiveTargets = workflowState.findReceiveOutputTargets(type);
|
||||
|
||||
this.isActive = true;
|
||||
|
||||
const doSend = (modal: ModalData) => {
|
||||
this.isActive = false;
|
||||
|
||||
const { workflow, targetNode } = get(modal.state) as SendOutputModalResult;
|
||||
console.warn("send", workflow, targetNode);
|
||||
|
||||
if (workflow == null || targetNode == null)
|
||||
return
|
||||
|
||||
const app = (window as any).app as ComfyApp;
|
||||
if (app == null) {
|
||||
console.error("Couldn't get app!")
|
||||
return
|
||||
}
|
||||
|
||||
targetNode.receiveOutput(value);
|
||||
workflowState.setActiveWorkflow(app.lCanvas, workflow.id)
|
||||
}
|
||||
|
||||
modalState.pushModal({
|
||||
title: "Send Output",
|
||||
closeOnClick: true,
|
||||
showCloseButton: true,
|
||||
svelteComponent: SendOutputModal,
|
||||
svelteProps: {
|
||||
value,
|
||||
type,
|
||||
receiveTargets
|
||||
},
|
||||
onClose: doSend
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfySendOutputAction,
|
||||
title: "Comfy.SendOutputAction",
|
||||
desc: "Sends a workflow output elsewhere",
|
||||
type: "actions/send_output"
|
||||
})
|
||||
@@ -8,3 +8,5 @@ export { default as ComfySetNodeModeAdvancedAction } from "./ComfySetNodeModeAdv
|
||||
export { default as ComfySetPromptThumbnailsAction } from "./ComfySetPromptThumbnailsAction"
|
||||
export { default as ComfyStoreImagesAction } from "./ComfyStoreImagesAction"
|
||||
export { default as ComfySwapAction } from "./ComfySwapAction"
|
||||
export { default as ComfySendOutputAction } from "./ComfySendOutputAction"
|
||||
export { default as ComfyReceiveOutputNode } from "./ComfyReceiveOutputNode"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "$lib/nodes/ComfyGraphNode";
|
||||
|
||||
export { default as ComfyReroute } from "./ComfyReroute"
|
||||
export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode"
|
||||
export { default as ComfyValueControl } from "./ComfyValueControl"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { default as ComfyWidgetNode } from "./ComfyWidgetNode"
|
||||
|
||||
export { default as ComfyButtonNode } from "./ComfyButtonNode"
|
||||
export { default as ComfyCheckboxNode } from "./ComfyCheckboxNode"
|
||||
export { default as ComfyComboNode } from "./ComfyComboNode"
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import type { SvelteComponentDev } from "svelte/internal";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export type ModalState = Record<string, any>;
|
||||
|
||||
export type ModalButton = {
|
||||
name: string,
|
||||
variant: "primary" | "secondary",
|
||||
onClick: () => void,
|
||||
onClick: (state: ModalData) => void,
|
||||
closeOnClick?: boolean
|
||||
}
|
||||
export interface ModalData {
|
||||
id: string,
|
||||
title?: string,
|
||||
onClose?: () => void,
|
||||
onClose?: (modal: ModalState) => void,
|
||||
svelteComponent?: typeof SvelteComponentDev,
|
||||
svelteProps: Record<string, any>,
|
||||
buttons: ModalButton[],
|
||||
showCloseButton: boolean,
|
||||
closeOnClick: boolean
|
||||
closeOnClick: boolean,
|
||||
state: Writable<ModalState>,
|
||||
close: () => void,
|
||||
}
|
||||
export interface ModalState {
|
||||
export interface ModalStates {
|
||||
activeModals: ModalData[]
|
||||
}
|
||||
|
||||
@@ -28,20 +32,23 @@ export interface ModalStateOps {
|
||||
closeAllModals: () => void,
|
||||
}
|
||||
|
||||
export type WritableModalStateStore = Writable<ModalState> & ModalStateOps;
|
||||
const store: Writable<ModalState> = writable(
|
||||
export type WritableModalStateStore = Writable<ModalStates> & ModalStateOps;
|
||||
const store: Writable<ModalStates> = writable(
|
||||
{
|
||||
activeModals: []
|
||||
})
|
||||
|
||||
function pushModal(data: Partial<ModalData>) {
|
||||
const id = uuidv4()
|
||||
const modal: ModalData = {
|
||||
showCloseButton: true,
|
||||
closeOnClick: true,
|
||||
buttons: [],
|
||||
svelteProps: {},
|
||||
state: writable({}),
|
||||
...data,
|
||||
id: uuidv4(),
|
||||
id,
|
||||
close: () => closeModal(id)
|
||||
}
|
||||
|
||||
store.update(s => {
|
||||
@@ -51,7 +58,13 @@ function pushModal(data: Partial<ModalData>) {
|
||||
}
|
||||
|
||||
function closeModal(id: string) {
|
||||
const modal = get(store).activeModals.find(m => m.id === id);
|
||||
if (modal == null)
|
||||
return;
|
||||
|
||||
store.update(s => {
|
||||
if (modal.onClose)
|
||||
modal.onClose(modal)
|
||||
s.activeModals = s.activeModals.filter(m => m.id !== id)
|
||||
return s;
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SerializedGraphCanvasState } from '$lib/ComfyGraphCanvas';
|
||||
import { clamp, LGraphNode, type LGraphCanvas, type NodeID, type SerializedLGraph, type UUID, LGraph, LiteGraph } from '@litegraph-ts/core';
|
||||
import { clamp, LGraphNode, type LGraphCanvas, type NodeID, type SerializedLGraph, type UUID, LGraph, LiteGraph, type SlotType, NodeMode } from '@litegraph-ts/core';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import { defaultWorkflowAttributes, type SerializedLayoutState, type WritableLayoutStateStore } from './layoutStates';
|
||||
@@ -9,6 +9,7 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import type ComfyGraphCanvas from '$lib/ComfyGraphCanvas';
|
||||
import { blankGraph } from '$lib/defaultGraph';
|
||||
import type { SerializedAppState } from '$lib/components/ComfyApp';
|
||||
import type ComfyReceiveOutputNode from '$lib/nodes/actions/ComfyReceiveOutputNode';
|
||||
|
||||
type ActiveCanvas = {
|
||||
canvas: LGraphCanvas | null;
|
||||
@@ -232,6 +233,11 @@ export type WorkflowState = {
|
||||
activeWorkflow: ComfyWorkflow | null,
|
||||
}
|
||||
|
||||
export type WorkflowReceiveOutputTargets = {
|
||||
workflow: ComfyWorkflow,
|
||||
targetNodes: ComfyReceiveOutputNode[]
|
||||
}
|
||||
|
||||
type WorkflowStateOps = {
|
||||
getWorkflow: (id: WorkflowInstID) => ComfyWorkflow | null
|
||||
getWorkflowByGraph: (graph: LGraph) => ComfyWorkflow | null
|
||||
@@ -243,7 +249,8 @@ type WorkflowStateOps = {
|
||||
addWorkflow: (canvas: ComfyGraphCanvas, data: ComfyWorkflow) => void,
|
||||
closeWorkflow: (canvas: ComfyGraphCanvas, index: number) => void,
|
||||
closeAllWorkflows: (canvas: ComfyGraphCanvas) => void,
|
||||
setActiveWorkflow: (canvas: ComfyGraphCanvas, index: number) => ComfyWorkflow | null
|
||||
setActiveWorkflow: (canvas: ComfyGraphCanvas, index: number | WorkflowInstID) => ComfyWorkflow | null,
|
||||
findReceiveOutputTargets: (type: SlotType | SlotType[]) => WorkflowReceiveOutputTargets[]
|
||||
}
|
||||
|
||||
export type WritableWorkflowStateStore = Writable<WorkflowState> & WorkflowStateOps;
|
||||
@@ -345,7 +352,7 @@ function closeAllWorkflows(canvas: ComfyGraphCanvas) {
|
||||
closeWorkflow(canvas, 0)
|
||||
}
|
||||
|
||||
function setActiveWorkflow(canvas: ComfyGraphCanvas, index: number): ComfyWorkflow | null {
|
||||
function setActiveWorkflow(canvas: ComfyGraphCanvas, index: number | WorkflowInstID): ComfyWorkflow | null {
|
||||
const state = get(store);
|
||||
|
||||
if (state.openedWorkflows.length === 0) {
|
||||
@@ -354,6 +361,10 @@ function setActiveWorkflow(canvas: ComfyGraphCanvas, index: number): ComfyWorkfl
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof index === "string") {
|
||||
index = state.openedWorkflows.findIndex(w => w.id === index)
|
||||
}
|
||||
|
||||
if (index < 0 || index >= state.openedWorkflows.length)
|
||||
return state.activeWorkflow;
|
||||
|
||||
@@ -375,6 +386,31 @@ function setActiveWorkflow(canvas: ComfyGraphCanvas, index: number): ComfyWorkfl
|
||||
return workflow;
|
||||
}
|
||||
|
||||
function findReceiveOutputTargets(type: SlotType | SlotType[]): WorkflowReceiveOutputTargets[] {
|
||||
let result = []
|
||||
|
||||
const state = get(store);
|
||||
|
||||
if (!Array.isArray(type))
|
||||
type = [type]
|
||||
|
||||
const types = new Set(type);
|
||||
|
||||
for (const workflow of state.openedWorkflows) {
|
||||
const targetNodes = workflow.graph
|
||||
// can't use class here because of circular import
|
||||
.findNodesByTypeRecursive("events/receive_output")
|
||||
.filter(n => {
|
||||
return types.has(n.properties.type) && n.mode === NodeMode.ALWAYS
|
||||
})
|
||||
|
||||
if (targetNodes.length > 0)
|
||||
result.push({ workflow, targetNodes });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const workflowStateStore: WritableWorkflowStateStore =
|
||||
{
|
||||
...store,
|
||||
@@ -389,5 +425,6 @@ const workflowStateStore: WritableWorkflowStateStore =
|
||||
closeWorkflow,
|
||||
closeAllWorkflows,
|
||||
setActiveWorkflow,
|
||||
findReceiveOutputTargets
|
||||
}
|
||||
export default workflowStateStore;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type WidgetLayout, type WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||
import { Subgraph, type LGraph, type LGraphNode, type LLink, type SerializedLGraph, type UUID, type NodeID } from "@litegraph-ts/core";
|
||||
import { Subgraph, type LGraph, type LGraphNode, type LLink, type SerializedLGraph, type UUID, type NodeID, type SlotType } from "@litegraph-ts/core";
|
||||
import { get } from "svelte/store";
|
||||
import type { ComfyNodeID } from "./api";
|
||||
import { type SerializedPrompt } from "./components/ComfyApp";
|
||||
@@ -507,6 +507,21 @@ export function comfyBoxImageToComfyURL(image: ComfyBoxImageMetadata): string {
|
||||
return convertComfyOutputToComfyURL(image.comfyUIFile)
|
||||
}
|
||||
|
||||
export function comfyURLToComfyFile(urlString: string): ComfyImageLocation | null {
|
||||
const url = new URL(urlString);
|
||||
const params = new URLSearchParams(url.search);
|
||||
const filename = params.get("filename")
|
||||
const type = params.get("type") as ComfyUploadImageType;
|
||||
const subfolder = params.get("subfolder") || ""
|
||||
|
||||
// If at least filename and type exist then we're good
|
||||
if (filename != null && type != null) {
|
||||
return { filename, type, subfolder }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function showLightbox(images: string[], index: number, e: Event) {
|
||||
e.preventDefault()
|
||||
if (!images)
|
||||
@@ -516,3 +531,31 @@ export function showLightbox(images: string[], index: number, e: Event) {
|
||||
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
export function getLitegraphType(param: any): SlotType | null {
|
||||
if (param == null)
|
||||
return null;
|
||||
|
||||
switch (typeof param) {
|
||||
case "string":
|
||||
return "string"
|
||||
case "number":
|
||||
case "bigint":
|
||||
return "number"
|
||||
case "boolean":
|
||||
return "boolean"
|
||||
case "object":
|
||||
if (isComfyBoxImageMetadata(param)) {
|
||||
return "COMFYBOX_IMAGE"
|
||||
}
|
||||
else if (isComfyBoxImageMetadataArray(param)) {
|
||||
return "COMFYBOX_IMAGES"
|
||||
}
|
||||
return null;
|
||||
case "symbol":
|
||||
case "undefined":
|
||||
case "function":
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import notify from "$lib/notify";
|
||||
import NumberInput from "$lib/components/NumberInput.svelte";
|
||||
import type { ComfyImageEditorNode } from "$lib/nodes/widgets";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
@@ -176,6 +177,17 @@
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function openLightbox() {
|
||||
if (!$nodeValue || $nodeValue.length === 0)
|
||||
return;
|
||||
|
||||
const comfyImage = $nodeValue[0];
|
||||
const comfyURL = comfyBoxImageToComfyURL(comfyImage);
|
||||
const images = [comfyURL]
|
||||
|
||||
ImageViewer.instance.showModal(images, 0);
|
||||
}
|
||||
|
||||
let status = "empty";
|
||||
let uploadError = null;
|
||||
|
||||
@@ -233,7 +245,7 @@
|
||||
on:upload_error={onUploadError}
|
||||
on:clear={onClear}
|
||||
on:change={onChange}
|
||||
on:image_clicked={openImageEditor}
|
||||
on:image_clicked={openLightbox}
|
||||
/>
|
||||
{:else}
|
||||
<div class="comfy-image-editor-panel">
|
||||
@@ -249,7 +261,7 @@
|
||||
on:upload_error={onUploadError}
|
||||
on:clear={onClear}
|
||||
on:change={onChange}
|
||||
on:image_clicked={openImageEditor}
|
||||
on:image_clicked={openLightbox}
|
||||
/>
|
||||
<Modal bind:showModal closeOnClick={false} on:close={disposeEditor} bind:closeDialog>
|
||||
<div>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Run node registration before anthing else, in the proper order
|
||||
import "$lib/nodeImports";
|
||||
|
||||
import ComfyApp from '$lib/components/ComfyApp';
|
||||
import { configureLitegraph } from '$lib/init';
|
||||
import App from './App.svelte';
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Run node registration before anthing else, in the proper order
|
||||
import "$lib/nodeImports";
|
||||
|
||||
import AppMobile from './AppMobile.svelte';
|
||||
import Framework7 from 'framework7/lite-bundle';
|
||||
import Framework7Svelte from 'framework7-svelte';
|
||||
|
||||
Reference in New Issue
Block a user