Fixes for UI
This commit is contained in:
@@ -1,343 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
|
||||||
import { Block } from "@gradio/atoms";
|
|
||||||
import { TextBox } from "@gradio/form";
|
|
||||||
import Row from "$lib/components/gradio/app/Row.svelte";
|
|
||||||
import { get, type Writable } from "svelte/store";
|
|
||||||
import Modal from "$lib/components/Modal.svelte";
|
|
||||||
import { Button } from "@gradio/button";
|
|
||||||
import type { ComfyImageEditorNode, ComfyImageLocation } from "$lib/nodes/ComfyWidgetNodes";
|
|
||||||
import { Embed as Klecks } from "klecks";
|
|
||||||
|
|
||||||
import "klecks/style/style.scss";
|
|
||||||
import ImageUpload from "$lib/components/ImageUpload.svelte";
|
|
||||||
import { uploadImageToComfyUI, type ComfyBoxImageMetadata, comfyFileToComfyBoxMetadata, comfyBoxImageToComfyURL, comfyBoxImageToComfyFile, type ComfyUploadImageType } from "$lib/utils";
|
|
||||||
import notify from "$lib/notify";
|
|
||||||
import NumberInput from "$lib/components/NumberInput.svelte";
|
|
||||||
|
|
||||||
export let widget: WidgetLayout | null = null;
|
|
||||||
export let isMobile: boolean = false;
|
|
||||||
let node: ComfyImageEditorNode | null = null;
|
|
||||||
let nodeValue: Writable<ComfyBoxImageMetadata[]> | null = null;
|
|
||||||
let attrsChanged: Writable<number> | null = null;
|
|
||||||
|
|
||||||
let imgWidth: number = 0;
|
|
||||||
let imgHeight: number = 0;
|
|
||||||
|
|
||||||
$: widget && setNodeValue(widget);
|
|
||||||
|
|
||||||
$: if ($nodeValue && $nodeValue.length > 0) {
|
|
||||||
// TODO improve
|
|
||||||
if (imgWidth > 0 && imgHeight > 0) {
|
|
||||||
$nodeValue[0].width = imgWidth
|
|
||||||
$nodeValue[0].height = imgHeight
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$nodeValue[0].width = 0
|
|
||||||
$nodeValue[0].height = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNodeValue(widget: WidgetLayout) {
|
|
||||||
if (widget) {
|
|
||||||
node = widget.node as ComfyImageEditorNode
|
|
||||||
nodeValue = node.value;
|
|
||||||
attrsChanged = widget.attrsChanged;
|
|
||||||
status = $nodeValue && $nodeValue.length > 0 ? "uploaded" : "empty"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let editorRoot: HTMLDivElement | null = null;
|
|
||||||
let showModal = false;
|
|
||||||
let kl: Klecks | null = null;
|
|
||||||
|
|
||||||
function disposeEditor() {
|
|
||||||
console.warn("[ImageEditorWidget] CLOSING", widget, $nodeValue)
|
|
||||||
|
|
||||||
if (editorRoot) {
|
|
||||||
while (editorRoot.firstChild) {
|
|
||||||
editorRoot.removeChild(editorRoot.firstChild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kl = null;
|
|
||||||
showModal = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateBlankCanvas(width: number, height: number, fill: string = "#fff"): HTMLCanvasElement {
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = fill,
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.restore();
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadImage(imageURL: string): Promise<HTMLImageElement> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const e = new Image();
|
|
||||||
e.setAttribute('crossorigin', 'anonymous'); // Don't taint the canvas from loading files on-disk
|
|
||||||
e.addEventListener("load", () => { resolve(e); });
|
|
||||||
e.src = imageURL;
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateImageCanvas(imageURL: string): Promise<[HTMLCanvasElement, number, number]> {
|
|
||||||
const image = await loadImage(imageURL);
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = image.width;
|
|
||||||
canvas.height = image.height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = "rgba(255, 255, 255, 0.0)";
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.drawImage(image, 0, 0);
|
|
||||||
ctx.restore();
|
|
||||||
return [canvas, image.width, image.height];
|
|
||||||
}
|
|
||||||
|
|
||||||
const FILENAME: string = "ComfyUITemp.png";
|
|
||||||
const SUBFOLDER: string = "ComfyBox_Editor";
|
|
||||||
const DIRECTORY: ComfyUploadImageType = "input";
|
|
||||||
|
|
||||||
async function submitKlecksToComfyUI(onSuccess: () => void, onError: () => void) {
|
|
||||||
const blob = kl.getPNG();
|
|
||||||
|
|
||||||
status = "uploading"
|
|
||||||
|
|
||||||
await uploadImageToComfyUI(blob, FILENAME, DIRECTORY, SUBFOLDER)
|
|
||||||
.then((entry: ComfyImageLocation) => {
|
|
||||||
const meta: ComfyBoxImageMetadata = comfyFileToComfyBoxMetadata(entry);
|
|
||||||
$nodeValue = [meta] // TODO more than one image
|
|
||||||
status = "uploaded"
|
|
||||||
notify("Saved image to ComfyUI!", { type: "success" })
|
|
||||||
onSuccess();
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
notify(`Failed to upload image from editor: ${err}`, { type: "error", timeout: 10000 })
|
|
||||||
status = "error"
|
|
||||||
uploadError = err;
|
|
||||||
$nodeValue = []
|
|
||||||
onError();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let closeDialog = null;
|
|
||||||
|
|
||||||
async function saveAndClose() {
|
|
||||||
console.log(closeDialog, kl)
|
|
||||||
if (!closeDialog || !kl)
|
|
||||||
return;
|
|
||||||
|
|
||||||
submitKlecksToComfyUI(() => {}, () => {});
|
|
||||||
closeDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
let blankImageWidth = 512;
|
|
||||||
let blankImageHeight = 512;
|
|
||||||
|
|
||||||
async function openImageEditor() {
|
|
||||||
if (!editorRoot)
|
|
||||||
return;
|
|
||||||
|
|
||||||
showModal = true;
|
|
||||||
|
|
||||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
|
||||||
|
|
||||||
kl = new Klecks({
|
|
||||||
embedUrl: url,
|
|
||||||
onSubmit: submitKlecksToComfyUI,
|
|
||||||
targetEl: editorRoot,
|
|
||||||
warnOnPageClose: false
|
|
||||||
});
|
|
||||||
|
|
||||||
console.warn("[ImageEditorWidget] OPENING", widget, $nodeValue)
|
|
||||||
|
|
||||||
let canvas = null;
|
|
||||||
let width = blankImageWidth;
|
|
||||||
let height = blankImageHeight;
|
|
||||||
|
|
||||||
if ($nodeValue && $nodeValue.length > 0) {
|
|
||||||
const comfyImage = $nodeValue[0];
|
|
||||||
const comfyURL = comfyBoxImageToComfyURL(comfyImage);
|
|
||||||
[canvas, width, height] = await generateImageCanvas(comfyURL);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
canvas = generateBlankCanvas(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
kl.openProject({
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
layers: [{
|
|
||||||
name: 'Image',
|
|
||||||
opacity: 1,
|
|
||||||
mixModeStr: 'source-over',
|
|
||||||
image: canvas
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
kl.klApp?.out("yo");
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = "empty";
|
|
||||||
let uploadError = null;
|
|
||||||
|
|
||||||
function onUploading() {
|
|
||||||
console.warn("UPLOADING!!!")
|
|
||||||
uploadError = null;
|
|
||||||
status = "uploading"
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUploaded(e: CustomEvent<ComfyImageLocation[]>) {
|
|
||||||
console.warn("UPLOADED!!!")
|
|
||||||
uploadError = null;
|
|
||||||
status = "uploaded"
|
|
||||||
$nodeValue = e.detail.map(comfyFileToComfyBoxMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClear() {
|
|
||||||
console.warn("CLEAR!!!")
|
|
||||||
uploadError = null;
|
|
||||||
status = "empty"
|
|
||||||
$nodeValue = []
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUploadError(e: CustomEvent<any>) {
|
|
||||||
console.warn("ERROR!!!")
|
|
||||||
status = "error"
|
|
||||||
uploadError = e.detail
|
|
||||||
$nodeValue = []
|
|
||||||
notify(`Failed to upload image to ComfyUI: ${uploadError}`, { type: "error", timeout: 10000 })
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChange(e: CustomEvent<ComfyImageLocation[]>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
let _value: ComfyImageLocation[] = []
|
|
||||||
$: if ($nodeValue)
|
|
||||||
_value = $nodeValue.map(comfyBoxImageToComfyFile)
|
|
||||||
else
|
|
||||||
_value = []
|
|
||||||
|
|
||||||
$: canEdit = status === "empty" || status === "uploaded";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="wrapper comfy-image-editor">
|
|
||||||
{#if widget.attrs.variant === "fileUpload" || isMobile}
|
|
||||||
<ImageUpload value={_value}
|
|
||||||
bind:imgWidth
|
|
||||||
bind:imgHeight
|
|
||||||
fileCount={"single"}
|
|
||||||
elem_classes={[]}
|
|
||||||
style={""}
|
|
||||||
label={widget.attrs.title}
|
|
||||||
on:uploading={onUploading}
|
|
||||||
on:uploaded={onUploaded}
|
|
||||||
on:upload_error={onUploadError}
|
|
||||||
on:clear={onClear}
|
|
||||||
on:change={onChange}
|
|
||||||
on:image_clicked={openImageEditor}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<div class="comfy-image-editor-panel">
|
|
||||||
<ImageUpload value={_value}
|
|
||||||
bind:imgWidth
|
|
||||||
bind:imgHeight
|
|
||||||
fileCount={"single"}
|
|
||||||
elem_classes={[]}
|
|
||||||
style={""}
|
|
||||||
label={widget.attrs.title}
|
|
||||||
on:uploading={onUploading}
|
|
||||||
on:uploaded={onUploaded}
|
|
||||||
on:upload_error={onUploadError}
|
|
||||||
on:clear={onClear}
|
|
||||||
on:change={onChange}
|
|
||||||
on:image_clicked={openImageEditor}
|
|
||||||
/>
|
|
||||||
<Modal bind:showModal closeOnClick={false} on:close={disposeEditor} bind:closeDialog>
|
|
||||||
<div>
|
|
||||||
<div id="klecks-loading-screen">
|
|
||||||
<span id="klecks-loading-screen-text"></span>
|
|
||||||
</div>
|
|
||||||
<div class="image-editor-root" bind:this={editorRoot} />
|
|
||||||
</div>
|
|
||||||
<div slot="buttons">
|
|
||||||
<Button variant="primary" on:click={saveAndClose}>
|
|
||||||
Save and Close
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" on:click={closeDialog}>
|
|
||||||
Discard Edits
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
<Block>
|
|
||||||
{#if !$nodeValue || $nodeValue.length === 0}
|
|
||||||
<Row>
|
|
||||||
<Row>
|
|
||||||
<Button variant="secondary" disabled={!canEdit} on:click={openImageEditor}>
|
|
||||||
Create Image
|
|
||||||
</Button>
|
|
||||||
<div>
|
|
||||||
<TextBox show_label={false} disabled={true} value="Status: {status}"/>
|
|
||||||
</div>
|
|
||||||
{#if uploadError}
|
|
||||||
<div>
|
|
||||||
Upload error: {uploadError}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<NumberInput label={"Width"} min={64} max={2048} step={64} bind:value={blankImageWidth} />
|
|
||||||
<NumberInput label={"Height"} min={64} max={2048} step={64} bind:value={blankImageHeight} />
|
|
||||||
</Row>
|
|
||||||
</Row>
|
|
||||||
{:else}
|
|
||||||
<Row>
|
|
||||||
<Button variant="secondary" disabled={!canEdit} on:click={openImageEditor}>
|
|
||||||
Edit Image
|
|
||||||
</Button>
|
|
||||||
<div>
|
|
||||||
<TextBox show_label={false} disabled={true} value="Status: {status}"/>
|
|
||||||
</div>
|
|
||||||
{#if uploadError}
|
|
||||||
<div>
|
|
||||||
Upload error: {uploadError}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</Row>
|
|
||||||
{/if}
|
|
||||||
</Block>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.image-editor-root {
|
|
||||||
width: 75vw;
|
|
||||||
height: 75vh;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
:global(> .g-root) {
|
|
||||||
height: calc(100% - 59px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.comfy-image-editor {
|
|
||||||
:global(> dialog) {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.kl-popup) {
|
|
||||||
z-index: 999999999999;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -82,6 +82,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
|
|
||||||
let state = get(queueState);
|
let state = get(queueState);
|
||||||
let ss = get(selectionState);
|
let ss = get(selectionState);
|
||||||
|
const isRunningNode = node.id === state.runningNodeID
|
||||||
|
|
||||||
let color = null;
|
let color = null;
|
||||||
let thickness = 1;
|
let thickness = 1;
|
||||||
@@ -92,16 +93,22 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
if (ss.currentHoveredNodes.has(node.id)) {
|
if (ss.currentHoveredNodes.has(node.id)) {
|
||||||
color = "lightblue";
|
color = "lightblue";
|
||||||
}
|
}
|
||||||
else if (node.id === +state.runningNodeID) {
|
else if (isRunningNode) {
|
||||||
color = "#0f0";
|
color = "#0f0";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (color) {
|
if (color) {
|
||||||
this.drawNodeOutline(node, ctx, state.progress, size, fgColor, bgColor, color, thickness)
|
this.drawNodeOutline(node, ctx, size, fgColor, bgColor, color, thickness)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRunningNode && state.progress) {
|
||||||
|
ctx.fillStyle = "green";
|
||||||
|
ctx.fillRect(0, 0, size[0] * (state.progress.value / state.progress.max), 6);
|
||||||
|
ctx.fillStyle = bgColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawNodeOutline(node: LGraphNode, ctx: CanvasRenderingContext2D, progress?: Progress, size: Vector2, fgColor: string, bgColor: string, outlineColor: string, outlineThickness: number) {
|
private drawNodeOutline(node: LGraphNode, ctx: CanvasRenderingContext2D, size: Vector2, fgColor: string, bgColor: string, outlineColor: string, outlineThickness: number) {
|
||||||
const shape = node.shape || BuiltInSlotShape.ROUND_SHAPE;
|
const shape = node.shape || BuiltInSlotShape.ROUND_SHAPE;
|
||||||
ctx.lineWidth = outlineThickness;
|
ctx.lineWidth = outlineThickness;
|
||||||
ctx.globalAlpha = 0.8;
|
ctx.globalAlpha = 0.8;
|
||||||
@@ -131,12 +138,6 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.strokeStyle = fgColor;
|
ctx.strokeStyle = fgColor;
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
|
|
||||||
if (progress) {
|
|
||||||
ctx.fillStyle = "green";
|
|
||||||
ctx.fillRect(0, 0, size[0] * (progress.value / progress.max), 6);
|
|
||||||
ctx.fillStyle = bgColor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private alignToGrid(node: LGraphNode, ctx: CanvasRenderingContext2D) {
|
private alignToGrid(node: LGraphNode, ctx: CanvasRenderingContext2D) {
|
||||||
|
|||||||
@@ -137,45 +137,22 @@ export class ImageViewer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupGalleryImageForLightbox(e: HTMLImageElement) {
|
showLightbox(source: HTMLImageElement) {
|
||||||
if (e.dataset.modded === "true")
|
const initiallyZoomed = true
|
||||||
|
this.modalZoomSet(this.modalImage, initiallyZoomed)
|
||||||
|
|
||||||
|
const galleryElem = source.closest<HTMLDivElement>("div.block")
|
||||||
|
console.debug("[ImageViewer] showModal", event, source, galleryElem);
|
||||||
|
if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) {
|
||||||
|
console.error("No buttons found on gallery element!", galleryElem)
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.dataset.modded = "true";
|
let urls = ImageViewer.get_gallery_urls(galleryElem)
|
||||||
e.style.cursor = 'pointer'
|
const [_currentButton, index] = ImageViewer.selected_gallery_button(galleryElem)
|
||||||
e.style.userSelect = 'none'
|
console.warn("Gallery!", index, urls, galleryElem)
|
||||||
|
|
||||||
var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1
|
|
||||||
|
|
||||||
// For Firefox, listening on click first switched to next image then shows the lightbox.
|
|
||||||
// If you know how to fix this without switching to mousedown event, please.
|
|
||||||
// For other browsers the event is click to make it possiblr to drag picture.
|
|
||||||
var event = isFirefox ? 'mousedown' : 'click'
|
|
||||||
|
|
||||||
e.addEventListener(event, (evt) => {
|
|
||||||
// if (!opts.js_modal_lightbox || evt.button != 0) return;
|
|
||||||
|
|
||||||
const initiallyZoomed = true
|
|
||||||
this.modalZoomSet(this.modalImage, initiallyZoomed)
|
|
||||||
evt.preventDefault()
|
|
||||||
|
|
||||||
const source = evt.target as HTMLImageElement;
|
|
||||||
|
|
||||||
const galleryElem = source.closest<HTMLDivElement>("div.block")
|
|
||||||
console.debug("[ImageViewer] showModal", event, source, galleryElem);
|
|
||||||
if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) {
|
|
||||||
console.error("No buttons found on gallery element!", galleryElem)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let urls = ImageViewer.get_gallery_urls(galleryElem)
|
|
||||||
const [_currentButton, index] = ImageViewer.selected_gallery_button(galleryElem)
|
|
||||||
console.warn("Gallery!", index, urls, galleryElem)
|
|
||||||
|
|
||||||
this.showModal(urls, index, galleryElem)
|
|
||||||
evt.stopPropagation();
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
|
this.showModal(urls, index, galleryElem)
|
||||||
}
|
}
|
||||||
|
|
||||||
modalZoomSet(modalImage: HTMLImageElement, enable: boolean) {
|
modalZoomSet(modalImage: HTMLImageElement, enable: boolean) {
|
||||||
|
|||||||
@@ -65,9 +65,11 @@
|
|||||||
dateStr = formatDate(date);
|
dateStr = formatDate(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subgraphs: string[] | null = entry.extraData?.extra_pnginfo?.comfyBoxSubgraphs;
|
||||||
|
|
||||||
let message = "Prompt";
|
let message = "Prompt";
|
||||||
if (entry.extraData.subgraphs)
|
if (subgraphs?.length > 0)
|
||||||
message = `Prompt: ${entry.extraData.subgraphs.join(', ')}`
|
message = `Prompt: ${subgraphs.join(', ')}`
|
||||||
|
|
||||||
let submessage = `Nodes: ${Object.keys(entry.prompt).length}`
|
let submessage = `Nodes: ${Object.keys(entry.prompt).length}`
|
||||||
if (Object.keys(entry.outputs).length > 0) {
|
if (Object.keys(entry.outputs).length > 0) {
|
||||||
@@ -156,12 +158,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let showModal = false;
|
let showModal = false;
|
||||||
|
let expandAll = false;
|
||||||
let selectedPrompt = null;
|
let selectedPrompt = null;
|
||||||
let selectedImages = [];
|
let selectedImages = [];
|
||||||
function showPrompt(entry: QueueUIEntry, e: MouseEvent) {
|
function showPrompt(entry: QueueUIEntry, e: MouseEvent) {
|
||||||
selectedPrompt = entry.entry.prompt;
|
selectedPrompt = entry.entry.prompt;
|
||||||
selectedImages = entry.images;
|
selectedImages = entry.images;
|
||||||
showModal = true;
|
showModal = true;
|
||||||
|
expandAll = false
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if(!showModal)
|
$: if(!showModal)
|
||||||
@@ -180,8 +184,16 @@
|
|||||||
<h1 style="padding-bottom: 1rem;">Prompt Details</h1>
|
<h1 style="padding-bottom: 1rem;">Prompt Details</h1>
|
||||||
</div>
|
</div>
|
||||||
{#if selectedPrompt}
|
{#if selectedPrompt}
|
||||||
<PromptDisplay prompt={selectedPrompt} images={selectedImages} />
|
<PromptDisplay prompt={selectedPrompt} images={selectedImages} {expandAll} />
|
||||||
{/if}
|
{/if}
|
||||||
|
<div slot="buttons" let:closeDialog>
|
||||||
|
<Button variant="secondary" on:click={closeDialog}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" on:click={() => (expandAll = !expandAll)}>
|
||||||
|
Expand All
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<div class="queue">
|
<div class="queue">
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function doClose() {
|
function doClose() {
|
||||||
|
showModal = false;
|
||||||
dialog.close();
|
dialog.close();
|
||||||
dispatch("close")
|
dispatch("close")
|
||||||
}
|
}
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
<slot />
|
<slot />
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
<slot name="buttons">
|
<slot name="buttons" {closeDialog}>
|
||||||
<!-- svelte-ignore a11y-autofocus -->
|
<!-- svelte-ignore a11y-autofocus -->
|
||||||
<Button variant="secondary" on:click={doClose}>Close</Button>
|
<Button variant="secondary" on:click={doClose}>Close</Button>
|
||||||
</slot>
|
</slot>
|
||||||
|
|||||||
@@ -6,16 +6,20 @@
|
|||||||
import { JSON as JSONIcon, Copy, Check } from "@gradio/icons";
|
import { JSON as JSONIcon, Copy, Check } from "@gradio/icons";
|
||||||
import Accordion from "$lib/components/gradio/app/Accordion.svelte";
|
import Accordion from "$lib/components/gradio/app/Accordion.svelte";
|
||||||
import Gallery from "$lib/components/gradio/gallery/Gallery.svelte";
|
import Gallery from "$lib/components/gradio/gallery/Gallery.svelte";
|
||||||
import type { Styles } from "@gradio/utils";
|
import { ImageViewer } from "$lib/ImageViewer";
|
||||||
|
import type { Styles } from "@gradio/utils";
|
||||||
|
|
||||||
const splitLength = 50;
|
const splitLength = 50;
|
||||||
|
|
||||||
export let prompt: SerializedPromptInputsAll;
|
export let prompt: SerializedPromptInputsAll;
|
||||||
export let images: string[] = [];
|
export let images: string[] = [];
|
||||||
|
export let isMobile: boolean = false;
|
||||||
|
export let expandAll: boolean = false;
|
||||||
|
|
||||||
let galleryStyle: Styles = {
|
let galleryStyle: Styles = {
|
||||||
grid_cols: [2],
|
grid_cols: [2],
|
||||||
object_fit: "cover",
|
object_fit: "cover",
|
||||||
|
height: "var(--size-96)"
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInputLink(input: SerializedPromptInput): boolean {
|
function isInputLink(input: SerializedPromptInput): boolean {
|
||||||
@@ -59,6 +63,11 @@
|
|||||||
copyFeedback(nodeID, inputName);
|
copyFeedback(nodeID, inputName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onGalleryImageClicked(e: CustomEvent<HTMLImageElement>) {
|
||||||
|
// TODO dialog renders over it
|
||||||
|
// ImageViewer.instance.showLightbox(e.detail)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="prompt-display">
|
<div class="prompt-display">
|
||||||
@@ -70,7 +79,7 @@
|
|||||||
{#if filtered.length > 0}
|
{#if filtered.length > 0}
|
||||||
<div class="accordion">
|
<div class="accordion">
|
||||||
<Block padding={true}>
|
<Block padding={true}>
|
||||||
<Accordion label="Node {i+1}: {classType}" open={false}>
|
<Accordion label="Node {i+1}: {classType}" open={expandAll}>
|
||||||
{#each filtered as [inputName, input]}
|
{#each filtered as [inputName, input]}
|
||||||
<Block>
|
<Block>
|
||||||
<button class="copy-button" on:click={() => handleCopy(nodeID, inputName, input)}>
|
<button class="copy-button" on:click={() => handleCopy(nodeID, inputName, input)}>
|
||||||
@@ -121,6 +130,7 @@
|
|||||||
style={galleryStyle}
|
style={galleryStyle}
|
||||||
root={""}
|
root={""}
|
||||||
root_url={""}
|
root_url={""}
|
||||||
|
on:clicked={onGalleryImageClicked}
|
||||||
/>
|
/>
|
||||||
</Block>
|
</Block>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
select: SelectData;
|
select: SelectData;
|
||||||
|
clicked: HTMLImageElement
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// tracks whether the value of the gallery was reset
|
// tracks whether the value of the gallery was reset
|
||||||
@@ -142,6 +143,14 @@
|
|||||||
|
|
||||||
let height = 0;
|
let height = 0;
|
||||||
let window_height = 0;
|
let window_height = 0;
|
||||||
|
|
||||||
|
let imgElem = null;
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
// selected_image = next
|
||||||
|
if (imgElem)
|
||||||
|
dispatch("clicked", imgElem)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window bind:innerHeight={window_height} />
|
<svelte:window bind:innerHeight={window_height} />
|
||||||
@@ -166,7 +175,8 @@
|
|||||||
<ModifyUpload on:clear={() => (selected_image = null)} />
|
<ModifyUpload on:clear={() => (selected_image = null)} />
|
||||||
|
|
||||||
<img
|
<img
|
||||||
on:click={() => (selected_image = next)}
|
on:click={onClick}
|
||||||
|
bind:this={imgElem}
|
||||||
src={_value[selected_image][0].data}
|
src={_value[selected_image][0].data}
|
||||||
alt={_value[selected_image][1] || ""}
|
alt={_value[selected_image][1] || ""}
|
||||||
title={_value[selected_image][1] || null}
|
title={_value[selected_image][1] || null}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
let mobileLightbox = null;
|
let mobileLightbox = null;
|
||||||
|
|
||||||
function showMobileLightbox(event: Event) {
|
function showMobileLightbox(source: HTMLImageElement) {
|
||||||
if (!f7)
|
if (!f7)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -58,16 +58,14 @@
|
|||||||
mobileLightbox = null;
|
mobileLightbox = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = (event.target || event.srcElement) as HTMLImageElement;
|
|
||||||
const galleryElem = source.closest<HTMLDivElement>("div.block")
|
const galleryElem = source.closest<HTMLDivElement>("div.block")
|
||||||
console.debug("[ImageViewer] showModal", event, source, galleryElem);
|
console.debug("[ImageViewer] showModal", source, galleryElem);
|
||||||
if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) {
|
if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) {
|
||||||
console.error("No buttons found on gallery element!", galleryElem)
|
console.error("No buttons found on gallery element!", galleryElem)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allGalleryButtons = ImageViewer.all_gallery_buttons(galleryElem);
|
const allGalleryButtons = ImageViewer.all_gallery_buttons(galleryElem);
|
||||||
const selectedSource = source.src
|
|
||||||
|
|
||||||
const images = allGalleryButtons.map(button => {
|
const images = allGalleryButtons.map(button => {
|
||||||
return {
|
return {
|
||||||
@@ -84,47 +82,18 @@
|
|||||||
type: 'popup',
|
type: 'popup',
|
||||||
});
|
});
|
||||||
mobileLightbox.open(selected_image)
|
mobileLightbox.open(selected_image)
|
||||||
|
|
||||||
event.stopPropagation()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupImageForMobileLightbox(e: HTMLImageElement) {
|
function onClicked(e: CustomEvent<HTMLImageElement>) {
|
||||||
if (e.dataset.modded === "true")
|
if (isMobile) {
|
||||||
return;
|
showMobileLightbox(e.detail)
|
||||||
|
}
|
||||||
e.dataset.modded = "true";
|
else {
|
||||||
e.style.cursor = "pointer";
|
ImageViewer.instance.showLightbox(e.detail)
|
||||||
e.style.userSelect = "none";
|
}
|
||||||
|
|
||||||
var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1
|
|
||||||
|
|
||||||
// For Firefox, listening on click first switched to next image then shows the lightbox.
|
|
||||||
// If you know how to fix this without switching to mousedown event, please.
|
|
||||||
// For other browsers the event is click to make it possiblr to drag picture.
|
|
||||||
var event = isFirefox ? 'mousedown' : 'click'
|
|
||||||
|
|
||||||
e.addEventListener(event, (evt) => {
|
|
||||||
evt.preventDefault()
|
|
||||||
showMobileLightbox(evt)
|
|
||||||
}, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSelect(e: CustomEvent<GradioSelectData>) {
|
function onSelect(e: CustomEvent<GradioSelectData>) {
|
||||||
// Setup lightbox
|
|
||||||
// Wait for gradio gallery to show the large preview image, if no timeout then
|
|
||||||
// the event might fire too early
|
|
||||||
|
|
||||||
const callback = isMobile ? setupImageForMobileLightbox
|
|
||||||
: ImageViewer.instance.setupGalleryImageForLightbox.bind(ImageViewer.instance)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
const images = element.querySelectorAll<HTMLImageElement>('div.block div > img')
|
|
||||||
if (images != null) {
|
|
||||||
images.forEach(callback);
|
|
||||||
}
|
|
||||||
ImageViewer.instance.refreshImages();
|
|
||||||
}, 200)
|
|
||||||
|
|
||||||
// Update index
|
// Update index
|
||||||
node.setProperty("index", e.detail.index as number)
|
node.setProperty("index", e.detail.index as number)
|
||||||
}
|
}
|
||||||
@@ -176,6 +145,7 @@
|
|||||||
root={""}
|
root={""}
|
||||||
root_url={""}
|
root_url={""}
|
||||||
on:select={onSelect}
|
on:select={onSelect}
|
||||||
|
on:clicked={onClicked}
|
||||||
bind:imageWidth={$imageWidth}
|
bind:imageWidth={$imageWidth}
|
||||||
bind:imageHeight={$imageHeight}
|
bind:imageHeight={$imageHeight}
|
||||||
bind:selected_image
|
bind:selected_image
|
||||||
|
|||||||
Reference in New Issue
Block a user