Improve mobile app initialization and range widget corner indicator

This commit is contained in:
space-nuko
2023-05-07 16:10:44 -05:00
parent efb0010a0e
commit 6dd2627f2c
16 changed files with 230 additions and 70 deletions

View File

@@ -11,7 +11,7 @@ export type SerializedGraphCanvasState = {
}
export default class ComfyGraphCanvas extends LGraphCanvas {
app: ComfyApp
app: ComfyApp | null;
constructor(
app: ComfyApp,
@@ -60,7 +60,8 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
let color = null;
if (node.id === +state.runningNodeId) {
color = "#0f0";
} else if (this.app.dragOverNode && node.id === this.app.dragOverNode.id) {
// this.app can be null inside the constructor if rendering is taking place already
} else if (this.app && this.app.dragOverNode && node.id === this.app.dragOverNode.id) {
color = "dodgerblue";
}

View File

@@ -175,10 +175,6 @@
})
}
app.api.addEventListener("status", (ev: CustomEvent) => {
queueState.statusUpdated(ev.detail as ComfyAPIStatus);
});
$: if (app.rootEl && !imageViewer) {
imageViewer = new ImageViewer(app.rootEl);
}

View File

@@ -1,6 +1,6 @@
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType } from "@litegraph-ts/core";
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
import ComfyAPI from "$lib/api"
import ComfyAPI, { type ComfyAPIQueueStatus } from "$lib/api"
import defaultGraph from "$lib/defaultGraph"
import { getPngMetadata, importA1111 } from "$lib/pnginfo";
import EventEmitter from "events";
@@ -293,6 +293,10 @@ export default class ComfyApp {
this.lGraph.setDirtyCanvas(true, false);
});
this.api.addEventListener("status", (ev: CustomEvent) => {
queueState.statusUpdated(ev.detail as ComfyAPIQueueStatus);
});
this.api.addEventListener("executed", ({ detail }: CustomEvent) => {
this.nodeOutputs[detail.node] = detail.output;
const node = this.lGraph.getNodeById(detail.node) as ComfyGraphNode;

View File

@@ -74,6 +74,9 @@
{step}
{disabled}
on:mouseup={handle_release}
on:pointerdown
on:pointerup
on:pointermove
/>
<style lang="scss">

View File

@@ -0,0 +1,48 @@
import { debounce } from '$lib/utils';
import { get, writable } from 'svelte/store';
import type { Readable, Writable } from 'svelte/store';
export type InterfaceState = {
// Show a large indicator of the currently editing number value for mobile
// use (sliders).
pointerNearTop: boolean,
pointerNearLeft: boolean,
showIndicator: boolean,
indicatorValue: any,
}
type InterfaceStateOps = {
showIndicator: (pointerX: number, pointerY: number, value: any) => void,
}
export type WritableInterfaceStateStore = Writable<InterfaceState> & InterfaceStateOps;
const store: Writable<InterfaceState> = writable(
{
pointerNearTop: false,
pointerNearLeft: false,
showIndicator: false,
indicatorValue: null,
})
const debounceDrag = debounce(() => { store.update(s => { s.showIndicator = false; return s }) }, 1000)
function showIndicator(pointerX: number, pointerY: number, value: any) {
if (!window)
return;
const state = get(store)
let middleWidth = window.innerWidth / 2;
let middleHeight = window.innerHeight / 2;
const pointerNearLeft = pointerX < middleWidth;
const pointerNearTop = pointerY < middleHeight;
store.update(s => { return { ...s, pointerNearTop, pointerNearLeft, showIndicator: true, indicatorValue: value } });
debounceDrag();
}
const interfaceStateStore: WritableInterfaceStateStore =
{
...store,
showIndicator
}
export default interfaceStateStore;

View File

@@ -1,11 +1,9 @@
import { writable } from 'svelte/store';
import type { Readable, Writable } from 'svelte/store';
import type ComfyApp from "$lib/components/ComfyApp"
export type UIEditMode = "widgets" | "containers" | "layout";
export type UIState = {
app: ComfyApp,
nodesLocked: boolean,
graphLocked: boolean,
autoAddUI: boolean,
@@ -16,7 +14,6 @@ export type UIState = {
export type WritableUIStateStore = Writable<UIState>;
const store: WritableUIStateStore = writable(
{
app: null,
graphLocked: false,
nodesLocked: false,
autoAddUI: true,

View File

@@ -108,3 +108,13 @@ export function getNodeInfo(nodeId: number): string {
const title = app.lGraph.getNodeById(nodeId)?.title || String(nodeId);
return title + " (" + nodeId + ")"
}
export const debounce = (callback: Function, wait = 250) => {
let timeout: NodeJS.Timeout | null = null;
return (...args: Array<unknown>) => {
const next = () => callback(...args);
if (timeout) clearTimeout(timeout);
timeout = setTimeout(next, wait);
};
};

View File

@@ -3,12 +3,15 @@
import { type WidgetLayout } from "$lib/stores/layoutState";
import { Range } from "$lib/components/gradio/form";
import { get, type Writable } from "svelte/store";
import { debounce } from "$lib/utils";
import interfaceState from "$lib/stores/interfaceState";
export let widget: WidgetLayout | null = null;
export let isMobile: boolean = false;
let node: ComfySliderNode | null = null;
let nodeValue: Writable<number> | null = null;
let propsChanged: Writable<number> | null = null;
let option: number | null = null;
let isDragging: boolean = false;
$: widget && setNodeValue(widget);
@@ -19,6 +22,7 @@
propsChanged = node.propsChanged;
setOption($nodeValue); // don't react on option
}
isDragging = false;
};
// I don't know why but this is necessary to watch for changes to node
@@ -33,6 +37,23 @@
option = value;
}
function setBackgroundSize(input: HTMLInputElement) {
input.style.setProperty("--background-size", `${getBackgroundSize(input)}%`);
}
function getBackgroundSize(input: HTMLInputElement) {
const min = +input.min || 0;
const max = +input.max || 100;
const value = +input.value;
return (value - min) / (max - min) * 100;
}
function updateSliderForMobile() {
const target = elem.querySelector<HTMLInputElement>("input[type=range]");
setBackgroundSize(target);
}
function onRelease(e: Event) {
if (nodeValue && option) {
$nodeValue = option
@@ -42,6 +63,10 @@
let elem: HTMLDivElement = null;
$: if (elem) {
updateSliderForMobile()
}
$: if (elem && node !== null && option !== null && (!$propsChanged || $propsChanged)) {
const slider = elem.querySelector("input[type='range']") as any
//const range_selectors = "[id$='_clone']:is(input[type='range'])";
@@ -51,6 +76,14 @@
const style = elem.style;
style.setProperty('--ae-slider-bg-overlay', 'repeating-linear-gradient( 90deg, transparent, transparent '+tsp+', var(--ae-input-border-color) '+tsp+', var(--ae-input-border-color) '+fsp+' )');
}
function onPointerDown(e: PointerEvent) {
interfaceState.showIndicator(e.clientX, e.clientY, option);
}
function onPointerMove(e: PointerEvent) {
interfaceState.showIndicator(e.clientX, e.clientY, option);
}
</script>
<div class="wrapper gradio-slider" class:mobile={isMobile} bind:this={elem}>
@@ -64,7 +97,9 @@
label={widget.attrs.title}
show_label={true}
on:release={onRelease}
on:change
on:change={updateSliderForMobile}
on:pointerdown={onPointerDown}
on:pointermove={onPointerMove}
/>
{/if}
</div>
@@ -77,9 +112,32 @@
// Prevent swiping on the slider track from accidentally changing the value
&.mobile :global(input[type="range"]) {
pointer-events: none;
-webkit-appearance: none;
appearance: none;
cursor: default;
height: 0.6rem;
padding: initial;
border: initial;
margin: 0.8rem 0;
width: 100%;
background: linear-gradient(to right, var(--color-blue-600), var(--color-blue-600)), #D7D7D7;
background-size: var(--background-size, 0%) 100%;
background-repeat: no-repeat;
border-radius: 1rem;
border: 1px solid var(--neutral-400);
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
pointer-events: all;
width: 1.75rem;
height: 1.75rem;
border-radius: 50%;
background: var(--color-blue-600);
cursor: pointer;
border: 2px solid var(--neutral-100);
box-shadow: 0px 0px 0px 1px var(--neutral-400);
}
}
}