Basic preview support
(as of latest PR commit)
This commit is contained in:
@@ -101,6 +101,7 @@ export type ComfyUIPromptExtraData = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ComfyAPIEvents = {
|
type ComfyAPIEvents = {
|
||||||
|
// JSON
|
||||||
status: (status: ComfyAPIStatusResponse | null, error?: Error | null) => void,
|
status: (status: ComfyAPIStatusResponse | null, error?: Error | null) => void,
|
||||||
progress: (progress: Progress) => void,
|
progress: (progress: Progress) => void,
|
||||||
reconnecting: () => void,
|
reconnecting: () => void,
|
||||||
@@ -111,6 +112,9 @@ type ComfyAPIEvents = {
|
|||||||
execution_cached: (promptID: PromptID, nodes: ComfyNodeID[]) => void,
|
execution_cached: (promptID: PromptID, nodes: ComfyNodeID[]) => void,
|
||||||
execution_interrupted: (error: ComfyInterruptedError) => void,
|
execution_interrupted: (error: ComfyInterruptedError) => void,
|
||||||
execution_error: (error: ComfyExecutionError) => void,
|
execution_error: (error: ComfyExecutionError) => void,
|
||||||
|
|
||||||
|
// Binary
|
||||||
|
b_preview: (imageBlob: Blob) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ComfyAPI {
|
export default class ComfyAPI {
|
||||||
@@ -126,7 +130,7 @@ export default class ComfyAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Poll status for colab and other things that don't support websockets.
|
* Poll status for colab and other things that don't support websockets.
|
||||||
*/
|
*/
|
||||||
private pollQueue() {
|
private pollQueue() {
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
@@ -176,6 +180,7 @@ export default class ComfyAPI {
|
|||||||
this.socket = new WebSocket(
|
this.socket = new WebSocket(
|
||||||
`ws${window.location.protocol === "https:" ? "s" : ""}://${hostname}:${port}/ws${existingSession}`
|
`ws${window.location.protocol === "https:" ? "s" : ""}://${hostname}:${port}/ws${existingSession}`
|
||||||
);
|
);
|
||||||
|
this.socket.binaryType = "arraybuffer";
|
||||||
|
|
||||||
this.socket.addEventListener("open", () => {
|
this.socket.addEventListener("open", () => {
|
||||||
opened = true;
|
opened = true;
|
||||||
@@ -204,38 +209,64 @@ export default class ComfyAPI {
|
|||||||
|
|
||||||
this.socket.addEventListener("message", (event) => {
|
this.socket.addEventListener("message", (event) => {
|
||||||
try {
|
try {
|
||||||
const msg = JSON.parse(event.data);
|
if (event.data instanceof ArrayBuffer) {
|
||||||
switch (msg.type) {
|
const view = new DataView(event.data);
|
||||||
case "status":
|
const eventType = view.getUint32(0);
|
||||||
if (msg.data.sid) {
|
const buffer = event.data.slice(4);
|
||||||
this.clientId = msg.data.sid;
|
switch (eventType) {
|
||||||
sessionStorage["Comfy.SessionId"] = this.clientId;
|
case 1:
|
||||||
}
|
const view2 = new DataView(event.data);
|
||||||
this.eventBus.emit("status", { execInfo: { queueRemaining: msg.data.status.exec_info.queue_remaining } });
|
const imageType = view2.getUint32(0)
|
||||||
break;
|
let imageMime: string
|
||||||
case "progress":
|
switch (imageType) {
|
||||||
this.eventBus.emit("progress", msg.data as Progress);
|
case 1:
|
||||||
break;
|
default:
|
||||||
case "executing":
|
imageMime = "image/jpeg";
|
||||||
this.eventBus.emit("executing", msg.data.prompt_id, msg.data.node);
|
break;
|
||||||
break;
|
case 2:
|
||||||
case "executed":
|
imageMime = "image/png"
|
||||||
this.eventBus.emit("executed", msg.data.prompt_id, msg.data.node, msg.data.output);
|
}
|
||||||
break;
|
const imageBlob = new Blob([buffer.slice(4)], { type: imageMime });
|
||||||
case "execution_start":
|
this.eventBus.emit("b_preview", imageBlob);
|
||||||
this.eventBus.emit("execution_start", msg.data.prompt_id);
|
break;
|
||||||
break;
|
default:
|
||||||
case "execution_cached":
|
throw new Error(`Unknown binary websocket message of type ${eventType}`);
|
||||||
this.eventBus.emit("execution_cached", msg.data.prompt_id, msg.data.nodes);
|
}
|
||||||
break;
|
}
|
||||||
case "execution_interrupted":
|
else {
|
||||||
this.eventBus.emit("execution_interrupted", msg.data);
|
const msg = JSON.parse(event.data);
|
||||||
break;
|
switch (msg.type) {
|
||||||
case "execution_error":
|
case "status":
|
||||||
this.eventBus.emit("execution_error", msg.data);
|
if (msg.data.sid) {
|
||||||
break;
|
this.clientId = msg.data.sid;
|
||||||
default:
|
sessionStorage["Comfy.SessionId"] = this.clientId;
|
||||||
console.warn("Unhandled message:", event.data);
|
}
|
||||||
|
this.eventBus.emit("status", { execInfo: { queueRemaining: msg.data.status.exec_info.queue_remaining } });
|
||||||
|
break;
|
||||||
|
case "progress":
|
||||||
|
this.eventBus.emit("progress", msg.data as Progress);
|
||||||
|
break;
|
||||||
|
case "executing":
|
||||||
|
this.eventBus.emit("executing", msg.data.prompt_id, msg.data.node);
|
||||||
|
break;
|
||||||
|
case "executed":
|
||||||
|
this.eventBus.emit("executed", msg.data.prompt_id, msg.data.node, msg.data.output);
|
||||||
|
break;
|
||||||
|
case "execution_start":
|
||||||
|
this.eventBus.emit("execution_start", msg.data.prompt_id);
|
||||||
|
break;
|
||||||
|
case "execution_cached":
|
||||||
|
this.eventBus.emit("execution_cached", msg.data.prompt_id, msg.data.nodes);
|
||||||
|
break;
|
||||||
|
case "execution_interrupted":
|
||||||
|
this.eventBus.emit("execution_interrupted", msg.data);
|
||||||
|
break;
|
||||||
|
case "execution_error":
|
||||||
|
this.eventBus.emit("execution_error", msg.data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("Unhandled message:", event.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error handling message", event.data, error);
|
console.error("Error handling message", event.data, error);
|
||||||
|
|||||||
@@ -651,6 +651,10 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.api.addEventListener("b_preview", (imageBlob: Blob) => {
|
||||||
|
queueState.previewUpdated(imageBlob);
|
||||||
|
});
|
||||||
|
|
||||||
const config = get(configState);
|
const config = get(configState);
|
||||||
|
|
||||||
if (config.pollSystemStatsInterval > 0) {
|
if (config.pollSystemStatsInterval > 0) {
|
||||||
|
|||||||
@@ -386,6 +386,9 @@
|
|||||||
<span style="display: inline-flex !important; padding: 0 0.75rem;">
|
<span style="display: inline-flex !important; padding: 0 0.75rem;">
|
||||||
<Checkbox label="Auto-Add UI" bind:value={$uiState.autoAddUI}/>
|
<Checkbox label="Auto-Add UI" bind:value={$uiState.autoAddUI}/>
|
||||||
</span>
|
</span>
|
||||||
|
<span style="display: inline-flex !important; padding: 0 0.75rem;">
|
||||||
|
<Checkbox label="Hide Previews" bind:value={$uiState.hidePreviews}/>
|
||||||
|
</span>
|
||||||
<!-- <span class="label" for="ui-edit-mode">
|
<!-- <span class="label" for="ui-edit-mode">
|
||||||
<BlockTitle>UI Edit mode</BlockTitle>
|
<BlockTitle>UI Edit mode</BlockTitle>
|
||||||
<select id="ui-edit-mode" name="ui-edit-mode" bind:value={$uiState.uiEditMode}>
|
<select id="ui-edit-mode" name="ui-edit-mode" bind:value={$uiState.uiEditMode}>
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import ComfyWidgetNode from "./ComfyWidgetNode";
|
|||||||
export interface ComfyGalleryProperties extends ComfyWidgetProperties {
|
export interface ComfyGalleryProperties extends ComfyWidgetProperties {
|
||||||
index: number | null,
|
index: number | null,
|
||||||
updateMode: "replace" | "append",
|
updateMode: "replace" | "append",
|
||||||
autoSelectOnUpdate: boolean
|
autoSelectOnUpdate: boolean,
|
||||||
|
showPreviews: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ComfyGalleryNode extends ComfyWidgetNode<ComfyBoxImageMetadata[]> {
|
export default class ComfyGalleryNode extends ComfyWidgetNode<ComfyBoxImageMetadata[]> {
|
||||||
@@ -18,7 +19,8 @@ export default class ComfyGalleryNode extends ComfyWidgetNode<ComfyBoxImageMetad
|
|||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
index: 0,
|
index: 0,
|
||||||
updateMode: "replace",
|
updateMode: "replace",
|
||||||
autoSelectOnUpdate: true
|
autoSelectOnUpdate: true,
|
||||||
|
showPreviews: true
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
|
|||||||
@@ -615,6 +615,14 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
validNodeTypes: ["ui/gallery"],
|
validNodeTypes: ["ui/gallery"],
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "showPreviews",
|
||||||
|
type: "boolean",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
validNodeTypes: ["ui/gallery"],
|
||||||
|
defaultValue: true
|
||||||
|
},
|
||||||
|
|
||||||
// ImageUpload
|
// ImageUpload
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type QueueStateOps = {
|
|||||||
executionCached: (promptID: PromptID, nodes: ComfyNodeID[]) => void,
|
executionCached: (promptID: PromptID, nodes: ComfyNodeID[]) => void,
|
||||||
executionError: (error: ComfyExecutionError) => CompletedQueueEntry | null,
|
executionError: (error: ComfyExecutionError) => CompletedQueueEntry | null,
|
||||||
progressUpdated: (progress: Progress) => void
|
progressUpdated: (progress: Progress) => void
|
||||||
|
previewUpdated: (imageBlob: Blob) => void
|
||||||
getQueueEntry: (promptID: PromptID) => QueueEntry | null;
|
getQueueEntry: (promptID: PromptID) => QueueEntry | null;
|
||||||
afterQueued: (workflowID: WorkflowInstID, promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void
|
afterQueued: (workflowID: WorkflowInstID, promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void
|
||||||
queueItemDeleted: (type: QueueItemType, id: PromptID) => void;
|
queueItemDeleted: (type: QueueItemType, id: PromptID) => void;
|
||||||
@@ -88,6 +89,11 @@ export type QueueState = {
|
|||||||
*/
|
*/
|
||||||
runningNodeID: ComfyNodeID | null;
|
runningNodeID: ComfyNodeID | null;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Currently executing prompt if any
|
||||||
|
*/
|
||||||
|
runningPromptID: PromptID | null;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Nodes which should be rendered as "executing" in the frontend (green border).
|
* Nodes which should be rendered as "executing" in the frontend (green border).
|
||||||
* This includes the running node and all its parent subgraphs
|
* This includes the running node and all its parent subgraphs
|
||||||
@@ -98,6 +104,12 @@ export type QueueState = {
|
|||||||
* Progress for the current node reported by the frontend
|
* Progress for the current node reported by the frontend
|
||||||
*/
|
*/
|
||||||
progress: Progress | null,
|
progress: Progress | null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Image preview URL
|
||||||
|
*/
|
||||||
|
previewURL: string | null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, user pressed the "Interrupt" button in the frontend. Disable the
|
* If true, user pressed the "Interrupt" button in the frontend. Disable the
|
||||||
* button and wait until the next prompt starts running to re-enable it
|
* button and wait until the next prompt starts running to re-enable it
|
||||||
@@ -115,6 +127,7 @@ const store: Writable<QueueState> = writable({
|
|||||||
runningNodeID: null,
|
runningNodeID: null,
|
||||||
executingNodes: new Set(),
|
executingNodes: new Set(),
|
||||||
progress: null,
|
progress: null,
|
||||||
|
preview: null,
|
||||||
isInterrupting: false
|
isInterrupting: false
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -171,6 +184,19 @@ function progressUpdated(progress: Progress) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function previewUpdated(imageBlob: Blob) {
|
||||||
|
console.debug("[queueState] previewUpdated", imageBlob?.type)
|
||||||
|
store.update(s => {
|
||||||
|
if (s.runningNodeID == null) {
|
||||||
|
s.previewURL = null;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.previewURL = URL.createObjectURL(imageBlob);
|
||||||
|
return s;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function statusUpdated(status: ComfyAPIStatusResponse | null) {
|
function statusUpdated(status: ComfyAPIStatusResponse | null) {
|
||||||
console.debug("[queueState] statusUpdated", status)
|
console.debug("[queueState] statusUpdated", status)
|
||||||
store.update((s) => {
|
store.update((s) => {
|
||||||
@@ -296,6 +322,7 @@ function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null)
|
|||||||
entry.nodesRan.add(runningNodeID)
|
entry.nodesRan.add(runningNodeID)
|
||||||
}
|
}
|
||||||
s.runningNodeID = runningNodeID;
|
s.runningNodeID = runningNodeID;
|
||||||
|
s.runningPromptID = promptID;
|
||||||
|
|
||||||
if (entry?.extraData?.workflowID) {
|
if (entry?.extraData?.workflowID) {
|
||||||
const workflow = workflowState.getWorkflow(entry.extraData.workflowID);
|
const workflow = workflowState.getWorkflow(entry.extraData.workflowID);
|
||||||
@@ -337,7 +364,9 @@ function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null)
|
|||||||
console.debug("[queueState] Could not find in pending! (executingUpdated)", promptID)
|
console.debug("[queueState] Could not find in pending! (executingUpdated)", promptID)
|
||||||
}
|
}
|
||||||
s.progress = null;
|
s.progress = null;
|
||||||
|
s.previewURL = null;
|
||||||
s.runningNodeID = null;
|
s.runningNodeID = null;
|
||||||
|
s.runningPromptID = null;
|
||||||
s.executingNodes.clear();
|
s.executingNodes.clear();
|
||||||
}
|
}
|
||||||
entry_ = entry;
|
entry_ = entry;
|
||||||
@@ -362,7 +391,9 @@ function executionCached(promptID: PromptID, nodes: ComfyNodeID[]) {
|
|||||||
}
|
}
|
||||||
s.isInterrupting = false; // TODO move to start
|
s.isInterrupting = false; // TODO move to start
|
||||||
s.progress = null;
|
s.progress = null;
|
||||||
|
s.previewURL = null;
|
||||||
s.runningNodeID = null;
|
s.runningNodeID = null;
|
||||||
|
s.runningPromptID = null;
|
||||||
s.executingNodes.clear();
|
s.executingNodes.clear();
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
@@ -380,7 +411,9 @@ function executionError(error: ComfyExecutionError): CompletedQueueEntry | null
|
|||||||
console.error("[queueState] Could not find in pending! (executionError)", error.prompt_id)
|
console.error("[queueState] Could not find in pending! (executionError)", error.prompt_id)
|
||||||
}
|
}
|
||||||
s.progress = null;
|
s.progress = null;
|
||||||
|
s.previewURL = null;
|
||||||
s.runningNodeID = null;
|
s.runningNodeID = null;
|
||||||
|
s.runningPromptID = null;
|
||||||
s.executingNodes.clear();
|
s.executingNodes.clear();
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
@@ -416,6 +449,7 @@ function executionStart(promptID: PromptID) {
|
|||||||
}
|
}
|
||||||
s.isInterrupting = false;
|
s.isInterrupting = false;
|
||||||
s.runningNodeID = null;
|
s.runningNodeID = null;
|
||||||
|
s.runningPromptID = promptID;
|
||||||
s.executingNodes.clear();
|
s.executingNodes.clear();
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
@@ -480,7 +514,9 @@ function queueCleared(type: QueueItemType) {
|
|||||||
s.queuePending.set([]);
|
s.queuePending.set([]);
|
||||||
s.queueRemaining = 0;
|
s.queueRemaining = 0;
|
||||||
s.runningNodeID = null;
|
s.runningNodeID = null;
|
||||||
|
s.runningPromptID = null;
|
||||||
s.progress = null;
|
s.progress = null;
|
||||||
|
s.previewURL = null;
|
||||||
s.executingNodes.clear();
|
s.executingNodes.clear();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -535,6 +571,7 @@ const queueStateStore: WritableQueueStateStore =
|
|||||||
historyUpdated,
|
historyUpdated,
|
||||||
statusUpdated,
|
statusUpdated,
|
||||||
progressUpdated,
|
progressUpdated,
|
||||||
|
previewUpdated,
|
||||||
executionStart,
|
executionStart,
|
||||||
executingUpdated,
|
executingUpdated,
|
||||||
executionCached,
|
executionCached,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type UIState = {
|
|||||||
autoAddUI: boolean,
|
autoAddUI: boolean,
|
||||||
uiUnlocked: boolean,
|
uiUnlocked: boolean,
|
||||||
uiEditMode: UIEditMode,
|
uiEditMode: UIEditMode,
|
||||||
|
hidePreviews: boolean,
|
||||||
|
|
||||||
reconnecting: boolean,
|
reconnecting: boolean,
|
||||||
forceSaveUserState: boolean | null,
|
forceSaveUserState: boolean | null,
|
||||||
@@ -30,6 +31,7 @@ const store: Writable<UIState> = writable(
|
|||||||
autoAddUI: true,
|
autoAddUI: true,
|
||||||
uiUnlocked: false,
|
uiUnlocked: false,
|
||||||
uiEditMode: "widgets",
|
uiEditMode: "widgets",
|
||||||
|
hidePreviews: false,
|
||||||
|
|
||||||
reconnecting: false,
|
reconnecting: false,
|
||||||
forceSaveUserState: null,
|
forceSaveUserState: null,
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
import { clamp, comfyBoxImageToComfyURL, type ComfyBoxImageMetadata } from "$lib/utils";
|
import { clamp, comfyBoxImageToComfyURL, type ComfyBoxImageMetadata } from "$lib/utils";
|
||||||
import { f7 } from "framework7-svelte";
|
import { f7 } from "framework7-svelte";
|
||||||
import type { ComfyGalleryNode } from "$lib/nodes/widgets";
|
import type { ComfyGalleryNode } from "$lib/nodes/widgets";
|
||||||
import { showMobileLightbox } from "$lib/components/utils";
|
import { showMobileLightbox } from "$lib/components/utils";
|
||||||
|
import queueState from "$lib/stores/queueState";
|
||||||
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import { loadImage } from "./utils";
|
||||||
|
import Spinner from "$lib/components/Spinner.svelte";
|
||||||
|
|
||||||
export let widget: WidgetLayout | null = null;
|
export let widget: WidgetLayout | null = null;
|
||||||
export let isMobile: boolean = false;
|
export let isMobile: boolean = false;
|
||||||
@@ -25,6 +29,47 @@
|
|||||||
|
|
||||||
$: widget && setNodeValue(widget);
|
$: widget && setNodeValue(widget);
|
||||||
|
|
||||||
|
function tagsMatch(tags: string[] | null): boolean {
|
||||||
|
if(tags != null && tags.length > 0)
|
||||||
|
return node.properties.tags.length > 0 && node.properties.tags.every(t => tags.includes(t));
|
||||||
|
else
|
||||||
|
return node.properties.tags.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let previewURL: string | null;
|
||||||
|
let previewImage: HTMLImageElement | null = null;
|
||||||
|
let previewElem: HTMLImageElement | null = null
|
||||||
|
$: {
|
||||||
|
previewURL = $queueState.previewURL;
|
||||||
|
|
||||||
|
if (previewURL && $queueState.runningPromptID != null && !$uiState.hidePreviews && node.properties.showPreviews) {
|
||||||
|
const queueEntry = queueState.getQueueEntry($queueState.runningPromptID)
|
||||||
|
if (queueEntry != null) {
|
||||||
|
const tags = queueEntry.extraData?.extra_pnginfo?.comfyBoxPrompt?.subgraphs;
|
||||||
|
if (tagsMatch(tags)) {
|
||||||
|
loadImage(previewURL).then((img) => {
|
||||||
|
previewImage = img;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
previewImage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
previewImage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
previewImage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPreview() {
|
||||||
|
}
|
||||||
|
|
||||||
|
function hidePreview() {
|
||||||
|
}
|
||||||
|
|
||||||
function setNodeValue(widget: WidgetLayout) {
|
function setNodeValue(widget: WidgetLayout) {
|
||||||
if (widget) {
|
if (widget) {
|
||||||
node = widget.node as ComfyGalleryNode
|
node = widget.node as ComfyGalleryNode
|
||||||
@@ -34,6 +79,8 @@
|
|||||||
imageHeight = node.imageHeight
|
imageHeight = node.imageHeight
|
||||||
selected_image = node.selectedImage;
|
selected_image = node.selectedImage;
|
||||||
forceSelectImage = node.forceSelectImage;
|
forceSelectImage = node.forceSelectImage;
|
||||||
|
previewURL = null;
|
||||||
|
previewImage = null;
|
||||||
|
|
||||||
if ($nodeValue != null) {
|
if ($nodeValue != null) {
|
||||||
if (node.properties.index < 0 || node.properties.index >= $nodeValue.length) {
|
if (node.properties.index < 0 || node.properties.index >= $nodeValue.length) {
|
||||||
@@ -108,6 +155,11 @@
|
|||||||
<div class="wrapper comfy-gallery-widget gradio-gallery" style={widget.attrs.style || ""}>
|
<div class="wrapper comfy-gallery-widget gradio-gallery" style={widget.attrs.style || ""}>
|
||||||
<Block variant="solid" padding={false}>
|
<Block variant="solid" padding={false}>
|
||||||
<div class="padding">
|
<div class="padding">
|
||||||
|
{#if previewImage}
|
||||||
|
<div class="comfy-gallery-preview" on:mouseover={hidePreview} on:mouseout={showPreview} >
|
||||||
|
<img src={previewImage.src} bind:this={previewElem} on:mouseout={showPreview} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<Gallery
|
<Gallery
|
||||||
value={images}
|
value={images}
|
||||||
label={widget.attrs.title}
|
label={widget.attrs.title}
|
||||||
@@ -153,6 +205,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover .comfy-gallery-preview {
|
||||||
|
opacity: 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comfy-gallery-preview {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: var(--layer-top);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.1s linear;
|
||||||
|
opacity: 100%;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
width: var(--size-full);
|
||||||
|
height: var(--size-full);
|
||||||
|
object-fit: contain;
|
||||||
|
border: 5px dashed var(--secondary-400);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.padding {
|
.padding {
|
||||||
|
|||||||
Reference in New Issue
Block a user