Improve mobile app initialization and range widget corner indicator
This commit is contained in:
@@ -1,17 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { get } from "svelte/store";
|
|
||||||
import { Button } from "@gradio/button";
|
|
||||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
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 { App, View } from "framework7-svelte"
|
||||||
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 { f7, f7ready } from 'framework7-svelte';
|
import { f7, f7ready } from 'framework7-svelte';
|
||||||
|
|
||||||
@@ -27,6 +18,9 @@
|
|||||||
import ListSubWorkflowsPage from './mobile/routes/list-subworkflows.svelte';
|
import ListSubWorkflowsPage from './mobile/routes/list-subworkflows.svelte';
|
||||||
import SubWorkflowPage from './mobile/routes/subworkflow.svelte';
|
import SubWorkflowPage from './mobile/routes/subworkflow.svelte';
|
||||||
import HellPage from './mobile/routes/hell.svelte';
|
import HellPage from './mobile/routes/hell.svelte';
|
||||||
|
import type { Framework7Parameters } from "framework7/types";
|
||||||
|
|
||||||
|
export let app: ComfyApp;
|
||||||
|
|
||||||
function onBackKeyDown(e) {
|
function onBackKeyDown(e) {
|
||||||
if(f7.view.current.router.currentRoute.path == '/'){
|
if(f7.view.current.router.currentRoute.path == '/'){
|
||||||
@@ -40,6 +34,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
await app.setup();
|
||||||
|
(window as any).app = app;
|
||||||
window.addEventListener("backbutton", onBackKeyDown, false);
|
window.addEventListener("backbutton", onBackKeyDown, false);
|
||||||
window.addEventListener("popstate", 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
|
We need to pass them along with the F7 app parameters to <App> component
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let f7params = {
|
let f7params: Framework7Parameters = {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: HomePage,
|
component: HomePage,
|
||||||
|
options: {
|
||||||
|
props: { app }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about/',
|
path: '/about/',
|
||||||
@@ -66,18 +65,30 @@
|
|||||||
{
|
{
|
||||||
path: '/graph/',
|
path: '/graph/',
|
||||||
component: GraphPage,
|
component: GraphPage,
|
||||||
|
options: {
|
||||||
|
props: { app }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/subworkflows/',
|
path: '/subworkflows/',
|
||||||
component: ListSubWorkflowsPage,
|
component: ListSubWorkflowsPage,
|
||||||
|
options: {
|
||||||
|
props: { app }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/subworkflows/:subworkflowID/',
|
path: '/subworkflows/:subworkflowID/',
|
||||||
component: SubWorkflowPage,
|
component: SubWorkflowPage,
|
||||||
|
options: {
|
||||||
|
props: { app }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/hell/',
|
path: '/hell/',
|
||||||
component: HellPage,
|
component: HellPage,
|
||||||
|
options: {
|
||||||
|
props: { app }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
popup: {
|
popup: {
|
||||||
@@ -105,7 +116,10 @@
|
|||||||
browserHistory=true,
|
browserHistory=true,
|
||||||
browserHistoryRoot="/mobile/"
|
browserHistoryRoot="/mobile/"
|
||||||
>
|
>
|
||||||
<GenToolbar/>
|
<GenToolbar {app} />
|
||||||
</View>
|
</View>
|
||||||
</App>
|
</App>
|
||||||
|
<div class="canvas-wrapper pane-wrapper" style="display: none">
|
||||||
|
<canvas id="graph-canvas" />
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export type SerializedGraphCanvasState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class ComfyGraphCanvas extends LGraphCanvas {
|
export default class ComfyGraphCanvas extends LGraphCanvas {
|
||||||
app: ComfyApp
|
app: ComfyApp | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
app: ComfyApp,
|
app: ComfyApp,
|
||||||
@@ -60,7 +60,8 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
let color = null;
|
let color = null;
|
||||||
if (node.id === +state.runningNodeId) {
|
if (node.id === +state.runningNodeId) {
|
||||||
color = "#0f0";
|
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";
|
color = "dodgerblue";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,10 +175,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
app.api.addEventListener("status", (ev: CustomEvent) => {
|
|
||||||
queueState.statusUpdated(ev.detail as ComfyAPIStatus);
|
|
||||||
});
|
|
||||||
|
|
||||||
$: if (app.rootEl && !imageViewer) {
|
$: if (app.rootEl && !imageViewer) {
|
||||||
imageViewer = new ImageViewer(app.rootEl);
|
imageViewer = new ImageViewer(app.rootEl);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { 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 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 defaultGraph from "$lib/defaultGraph"
|
||||||
import { getPngMetadata, importA1111 } from "$lib/pnginfo";
|
import { getPngMetadata, importA1111 } from "$lib/pnginfo";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
@@ -293,6 +293,10 @@ export default class ComfyApp {
|
|||||||
this.lGraph.setDirtyCanvas(true, false);
|
this.lGraph.setDirtyCanvas(true, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.api.addEventListener("status", (ev: CustomEvent) => {
|
||||||
|
queueState.statusUpdated(ev.detail as ComfyAPIQueueStatus);
|
||||||
|
});
|
||||||
|
|
||||||
this.api.addEventListener("executed", ({ detail }: CustomEvent) => {
|
this.api.addEventListener("executed", ({ detail }: CustomEvent) => {
|
||||||
this.nodeOutputs[detail.node] = detail.output;
|
this.nodeOutputs[detail.node] = detail.output;
|
||||||
const node = this.lGraph.getNodeById(detail.node) as ComfyGraphNode;
|
const node = this.lGraph.getNodeById(detail.node) as ComfyGraphNode;
|
||||||
|
|||||||
@@ -74,6 +74,9 @@
|
|||||||
{step}
|
{step}
|
||||||
{disabled}
|
{disabled}
|
||||||
on:mouseup={handle_release}
|
on:mouseup={handle_release}
|
||||||
|
on:pointerdown
|
||||||
|
on:pointerup
|
||||||
|
on:pointermove
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
48
src/lib/stores/interfaceState.ts
Normal file
48
src/lib/stores/interfaceState.ts
Normal 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;
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import type { Readable, 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 UIEditMode = "widgets" | "containers" | "layout";
|
||||||
|
|
||||||
export type UIState = {
|
export type UIState = {
|
||||||
app: ComfyApp,
|
|
||||||
nodesLocked: boolean,
|
nodesLocked: boolean,
|
||||||
graphLocked: boolean,
|
graphLocked: boolean,
|
||||||
autoAddUI: boolean,
|
autoAddUI: boolean,
|
||||||
@@ -16,7 +14,6 @@ export type UIState = {
|
|||||||
export type WritableUIStateStore = Writable<UIState>;
|
export type WritableUIStateStore = Writable<UIState>;
|
||||||
const store: WritableUIStateStore = writable(
|
const store: WritableUIStateStore = writable(
|
||||||
{
|
{
|
||||||
app: null,
|
|
||||||
graphLocked: false,
|
graphLocked: false,
|
||||||
nodesLocked: false,
|
nodesLocked: false,
|
||||||
autoAddUI: true,
|
autoAddUI: true,
|
||||||
|
|||||||
@@ -108,3 +108,13 @@ export function getNodeInfo(nodeId: number): string {
|
|||||||
const title = app.lGraph.getNodeById(nodeId)?.title || String(nodeId);
|
const title = app.lGraph.getNodeById(nodeId)?.title || String(nodeId);
|
||||||
return title + " (" + 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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,12 +3,15 @@
|
|||||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||||
import { Range } from "$lib/components/gradio/form";
|
import { Range } from "$lib/components/gradio/form";
|
||||||
import { get, type Writable } from "svelte/store";
|
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 widget: WidgetLayout | null = null;
|
||||||
export let isMobile: boolean = false;
|
export let isMobile: boolean = false;
|
||||||
let node: ComfySliderNode | null = null;
|
let node: ComfySliderNode | null = null;
|
||||||
let nodeValue: Writable<number> | null = null;
|
let nodeValue: Writable<number> | null = null;
|
||||||
let propsChanged: Writable<number> | null = null;
|
let propsChanged: Writable<number> | null = null;
|
||||||
let option: number | null = null;
|
let option: number | null = null;
|
||||||
|
let isDragging: boolean = false;
|
||||||
|
|
||||||
$: widget && setNodeValue(widget);
|
$: widget && setNodeValue(widget);
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@
|
|||||||
propsChanged = node.propsChanged;
|
propsChanged = node.propsChanged;
|
||||||
setOption($nodeValue); // don't react on option
|
setOption($nodeValue); // don't react on option
|
||||||
}
|
}
|
||||||
|
isDragging = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// I don't know why but this is necessary to watch for changes to node
|
// I don't know why but this is necessary to watch for changes to node
|
||||||
@@ -33,6 +37,23 @@
|
|||||||
option = value;
|
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) {
|
function onRelease(e: Event) {
|
||||||
if (nodeValue && option) {
|
if (nodeValue && option) {
|
||||||
$nodeValue = option
|
$nodeValue = option
|
||||||
@@ -42,6 +63,10 @@
|
|||||||
|
|
||||||
let elem: HTMLDivElement = null;
|
let elem: HTMLDivElement = null;
|
||||||
|
|
||||||
|
$: if (elem) {
|
||||||
|
updateSliderForMobile()
|
||||||
|
}
|
||||||
|
|
||||||
$: if (elem && node !== null && option !== null && (!$propsChanged || $propsChanged)) {
|
$: if (elem && node !== null && option !== null && (!$propsChanged || $propsChanged)) {
|
||||||
const slider = elem.querySelector("input[type='range']") as any
|
const slider = elem.querySelector("input[type='range']") as any
|
||||||
//const range_selectors = "[id$='_clone']:is(input[type='range'])";
|
//const range_selectors = "[id$='_clone']:is(input[type='range'])";
|
||||||
@@ -51,6 +76,14 @@
|
|||||||
const style = elem.style;
|
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+' )');
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper gradio-slider" class:mobile={isMobile} bind:this={elem}>
|
<div class="wrapper gradio-slider" class:mobile={isMobile} bind:this={elem}>
|
||||||
@@ -64,7 +97,9 @@
|
|||||||
label={widget.attrs.title}
|
label={widget.attrs.title}
|
||||||
show_label={true}
|
show_label={true}
|
||||||
on:release={onRelease}
|
on:release={onRelease}
|
||||||
on:change
|
on:change={updateSliderForMobile}
|
||||||
|
on:pointerdown={onPointerDown}
|
||||||
|
on:pointermove={onPointerMove}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -77,9 +112,32 @@
|
|||||||
// Prevent swiping on the slider track from accidentally changing the value
|
// Prevent swiping on the slider track from accidentally changing the value
|
||||||
&.mobile :global(input[type="range"]) {
|
&.mobile :global(input[type="range"]) {
|
||||||
pointer-events: none;
|
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-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
pointer-events: all;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,23 @@ import AppMobile from './AppMobile.svelte';
|
|||||||
import Framework7 from 'framework7/lite-bundle';
|
import Framework7 from 'framework7/lite-bundle';
|
||||||
import Framework7Svelte from 'framework7-svelte';
|
import Framework7Svelte from 'framework7-svelte';
|
||||||
import { f7 } 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);
|
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({
|
const app = new AppMobile({
|
||||||
target: document.getElementById('app'),
|
target: document.getElementById('app'),
|
||||||
|
props: { app: comfyApp }
|
||||||
})
|
})
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -7,14 +7,13 @@
|
|||||||
|
|
||||||
import { Link, Toolbar } from "framework7-svelte"
|
import { Link, Toolbar } from "framework7-svelte"
|
||||||
import ProgressBar from "$lib/components/ProgressBar.svelte";
|
import ProgressBar from "$lib/components/ProgressBar.svelte";
|
||||||
|
import Indicator from "./Indicator.svelte";
|
||||||
|
import interfaceState from "$lib/stores/interfaceState";
|
||||||
|
|
||||||
export let subworkflowID: number = -1;
|
export let subworkflowID: number = -1;
|
||||||
let app: ComfyApp = undefined;
|
export let app: ComfyApp = undefined;
|
||||||
let fileInput: HTMLInputElement = undefined;
|
let fileInput: HTMLInputElement = undefined;
|
||||||
|
|
||||||
$: if (!app)
|
|
||||||
app = $uiState.app
|
|
||||||
|
|
||||||
function queuePrompt() {
|
function queuePrompt() {
|
||||||
app.queuePrompt(0, 1);
|
app.queuePrompt(0, 1);
|
||||||
notify("Prompt was queued", "Queued");
|
notify("Prompt was queued", "Queued");
|
||||||
@@ -56,6 +55,9 @@
|
|||||||
<Link on:click={doLoad}>Load</Link>
|
<Link on:click={doLoad}>Load</Link>
|
||||||
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
|
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
{#if $interfaceState.showIndicator}
|
||||||
|
<Indicator value={$interfaceState.indicatorValue} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#comfy-file-input {
|
#comfy-file-input {
|
||||||
|
|||||||
45
src/mobile/Indicator.svelte
Normal file
45
src/mobile/Indicator.svelte
Normal 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>
|
||||||
@@ -6,13 +6,10 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import uiState from "$lib/stores/uiState"
|
import uiState from "$lib/stores/uiState"
|
||||||
|
|
||||||
let app: ComfyApp | null = null;
|
export let app: ComfyApp | null = null;
|
||||||
let lCanvas: ComfyGraphCanvas | null = null;
|
let lCanvas: ComfyGraphCanvas | null = null;
|
||||||
let canvasEl: HTMLCanvasElement | null = null;
|
let canvasEl: HTMLCanvasElement | null = null;
|
||||||
|
|
||||||
$: if (!app)
|
|
||||||
app = $uiState.app
|
|
||||||
|
|
||||||
function resizeCanvas() {
|
function resizeCanvas() {
|
||||||
canvasEl.width = canvasEl.parentElement.offsetWidth;
|
canvasEl.width = canvasEl.parentElement.offsetWidth;
|
||||||
canvasEl.height = canvasEl.parentElement.offsetHeight;
|
canvasEl.height = canvasEl.parentElement.offsetHeight;
|
||||||
@@ -21,13 +18,10 @@
|
|||||||
lCanvas.draw(true, true);
|
lCanvas.draw(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (app != null && canvasEl != null) {
|
$: if (app != null && app.lGraph && canvasEl != null) {
|
||||||
if (!lCanvas) {
|
if (!lCanvas) {
|
||||||
lCanvas = new ComfyGraphCanvas(app, canvasEl);
|
lCanvas = new ComfyGraphCanvas(app, canvasEl);
|
||||||
lCanvas.allow_interaction = false;
|
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))
|
app.lGraph.eventBus.on("afterExecute", () => lCanvas.draw(true))
|
||||||
}
|
}
|
||||||
resizeCanvas();
|
resizeCanvas();
|
||||||
@@ -47,5 +41,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
|
|
||||||
|
> canvas {
|
||||||
|
// Don't try to scroll the page when scrolling on canvas
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,22 +14,7 @@
|
|||||||
import queueState from "$lib/stores/queueState";
|
import queueState from "$lib/stores/queueState";
|
||||||
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
||||||
|
|
||||||
let app: ComfyApp | null = null;
|
export 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;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -52,8 +37,4 @@
|
|||||||
<i class="icon icon-f7" slot="media" />
|
<i class="icon icon-f7" slot="media" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<div class="canvas-wrapper pane-wrapper" style="display: none">
|
|
||||||
<canvas id="graph-canvas" />
|
|
||||||
</div>
|
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,19 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import ComfyApp from "$lib/components/ComfyApp";
|
||||||
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 { Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
||||||
|
|
||||||
|
export let app: ComfyApp;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Page name="subworkflows">
|
<Page name="subworkflows">
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem, Toolbar } from "framework7-svelte"
|
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem, Toolbar } from "framework7-svelte"
|
||||||
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
|
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
|
||||||
|
import type ComfyApp from "$lib/components/ComfyApp";
|
||||||
|
|
||||||
export let subworkflowID: number = -1;
|
export let subworkflowID: number = -1;
|
||||||
|
export let app: ComfyApp
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user