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

@@ -1,17 +1,8 @@
<script lang="ts">
import { onMount } from "svelte";
import { get } from "svelte/store";
import { Button } from "@gradio/button";
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
import { Checkbox } from "@gradio/form"
import uiState from "$lib/stores/uiState";
import { ImageViewer } from "$lib/ImageViewer";
import { download } from "$lib/utils"
import { LGraph, LGraphNode } from "@litegraph-ts/core";
import type { ComfyAPIStatus } from "$lib/api";
import queueState from "$lib/stores/queueState";
import { App, View, Toolbar, Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
import { App, View } from "framework7-svelte"
import { f7, f7ready } from 'framework7-svelte';
@@ -27,6 +18,9 @@
import ListSubWorkflowsPage from './mobile/routes/list-subworkflows.svelte';
import SubWorkflowPage from './mobile/routes/subworkflow.svelte';
import HellPage from './mobile/routes/hell.svelte';
import type { Framework7Parameters } from "framework7/types";
export let app: ComfyApp;
function onBackKeyDown(e) {
if(f7.view.current.router.currentRoute.path == '/'){
@@ -40,6 +34,8 @@
}
onMount(async () => {
await app.setup();
(window as any).app = app;
window.addEventListener("backbutton", onBackKeyDown, false);
window.addEventListener("popstate", onBackKeyDown, false);
});
@@ -49,11 +45,14 @@
We need to pass them along with the F7 app parameters to <App> component
*/
let f7params = {
let f7params: Framework7Parameters = {
routes: [
{
path: '/',
component: HomePage,
options: {
props: { app }
}
},
{
path: '/about/',
@@ -66,18 +65,30 @@
{
path: '/graph/',
component: GraphPage,
options: {
props: { app }
}
},
{
path: '/subworkflows/',
component: ListSubWorkflowsPage,
options: {
props: { app }
}
},
{
path: '/subworkflows/:subworkflowID/',
component: SubWorkflowPage,
options: {
props: { app }
}
},
{
path: '/hell/',
component: HellPage,
options: {
props: { app }
}
},
],
popup: {
@@ -105,7 +116,10 @@
browserHistory=true,
browserHistoryRoot="/mobile/"
>
<GenToolbar/>
<GenToolbar {app} />
</View>
</App>
<div class="canvas-wrapper pane-wrapper" style="display: none">
<canvas id="graph-canvas" />
</div>
{/if}

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);
}
}
}

View File

@@ -2,12 +2,23 @@ import AppMobile from './AppMobile.svelte';
import Framework7 from 'framework7/lite-bundle';
import Framework7Svelte from 'framework7-svelte';
import { f7 } from 'framework7-svelte';
import ComfyApp from '$lib/components/ComfyApp';
import uiState from '$lib/stores/uiState';
import { LiteGraph } from '@litegraph-ts/core';
Framework7.use(Framework7Svelte);
LiteGraph.dialog_close_on_mouse_leave = false;
LiteGraph.search_hide_on_mouse_leave = false;
LiteGraph.pointerevents_method = "pointer";
const comfyApp = new ComfyApp();
uiState.update(s => { s.app = comfyApp; return s; })
const app = new AppMobile({
target: document.getElementById('app'),
target: document.getElementById('app'),
props: { app: comfyApp }
})
export default app;

View File

@@ -7,14 +7,13 @@
import { Link, Toolbar } from "framework7-svelte"
import ProgressBar from "$lib/components/ProgressBar.svelte";
import Indicator from "./Indicator.svelte";
import interfaceState from "$lib/stores/interfaceState";
export let subworkflowID: number = -1;
let app: ComfyApp = undefined;
export let app: ComfyApp = undefined;
let fileInput: HTMLInputElement = undefined;
$: if (!app)
app = $uiState.app
function queuePrompt() {
app.queuePrompt(0, 1);
notify("Prompt was queued", "Queued");
@@ -56,6 +55,9 @@
<Link on:click={doLoad}>Load</Link>
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
</Toolbar>
{#if $interfaceState.showIndicator}
<Indicator value={$interfaceState.indicatorValue} />
{/if}
<style lang="scss">
#comfy-file-input {

View File

@@ -0,0 +1,45 @@
<script lang="ts">
import interfaceState from "$lib/stores/interfaceState";
export let value: any = null;
</script>
<div style="position: relative; z-index: 10;">
<div class="indicator"
class:top={true}
class:bottom={false}
class:left={!$interfaceState.pointerNearLeft || !$interfaceState.pointerNearTop}
class:right={$interfaceState.pointerNearLeft && $interfaceState.pointerNearTop}>
<span>
{value}
</span>
</div>
</div>
<style lang="scss">
.indicator {
position: fixed;
align-content: left;
padding: 1rem;
font-size: xxx-large;
background: var(--neutral-300);
color: var(--neutral-800);
border-radius: 1rem;
border: 0.2rem solid var(--neutral-400);
z-index: var(--layer-top) !important;
&.top {
top: calc(1rem + var(--f7-navbar-height) + var(--f7-safe-area-top));
}
&.bottom {
bottom: 5rem
}
&.left {
left: 1rem;
}
&.right {
right: 1rem;
}
}
</style>

View File

@@ -6,13 +6,10 @@
import { onMount } from 'svelte';
import uiState from "$lib/stores/uiState"
let app: ComfyApp | null = null;
export let app: ComfyApp | null = null;
let lCanvas: ComfyGraphCanvas | null = null;
let canvasEl: HTMLCanvasElement | null = null;
$: if (!app)
app = $uiState.app
function resizeCanvas() {
canvasEl.width = canvasEl.parentElement.offsetWidth;
canvasEl.height = canvasEl.parentElement.offsetHeight;
@@ -21,13 +18,10 @@
lCanvas.draw(true, true);
}
$: if (app != null && canvasEl != null) {
$: if (app != null && app.lGraph && canvasEl != null) {
if (!lCanvas) {
lCanvas = new ComfyGraphCanvas(app, canvasEl);
lCanvas.allow_interaction = false;
LiteGraph.dialog_close_on_mouse_leave = false;
LiteGraph.search_hide_on_mouse_leave = false;
LiteGraph.pointerevents_method = "pointer";
app.lGraph.eventBus.on("afterExecute", () => lCanvas.draw(true))
}
resizeCanvas();
@@ -47,5 +41,10 @@
width: 100%;
height: 100%;
background-color: #333;
> canvas {
// Don't try to scroll the page when scrolling on canvas
touch-action: none;
}
}
</style>

View File

@@ -14,22 +14,7 @@
import queueState from "$lib/stores/queueState";
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
let app: ComfyApp | null = null;
onMount(async () => {
if (app)
return
app = $uiState.app = new ComfyApp();
app.api.addEventListener("status", (ev: CustomEvent) => {
queueState.statusUpdated(ev.detail as ComfyAPIStatus);
});
await app.setup();
(window as any).app = app;
});
export let app: ComfyApp | null = null;
</script>
@@ -52,8 +37,4 @@
<i class="icon icon-f7" slot="media" />
</ListItem>
</List>
<div class="canvas-wrapper pane-wrapper" style="display: none">
<canvas id="graph-canvas" />
</div>
</Page>

View File

@@ -1,19 +1,8 @@
<script lang="ts">
import { onMount } from "svelte";
import { get } from "svelte/store";
import { Pane, Splitpanes } from 'svelte-splitpanes';
import { Button } from "@gradio/button";
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
import { Checkbox } from "@gradio/form"
import uiState from "$lib/stores/uiState";
import { ImageViewer } from "$lib/ImageViewer";
import { download } from "$lib/utils"
import { LGraph, LGraphNode } from "@litegraph-ts/core";
import type { ComfyAPIStatus } from "$lib/api";
import queueState from "$lib/stores/queueState";
import ComfyApp from "$lib/components/ComfyApp";
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
export let app: ComfyApp;
</script>
<Page name="subworkflows">

View File

@@ -3,8 +3,10 @@
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem, Toolbar } from "framework7-svelte"
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
import type ComfyApp from "$lib/components/ComfyApp";
export let subworkflowID: number = -1;
export let app: ComfyApp
</script>