Skip to content

Commit f0c01a6

Browse files
committed
Mobile queue
1 parent 4547cc1 commit f0c01a6

File tree

13 files changed

+277
-98
lines changed

13 files changed

+277
-98
lines changed

src/lib/components/ComfyApp.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,9 @@ export default class ComfyApp {
403403
return false;
404404

405405
const workflows = state.workflows as SerializedAppState[];
406+
if (workflows.length === 0)
407+
return false;
408+
406409
await Promise.all(workflows.map(w => {
407410
return this.openWorkflow(w, { refreshCombos: defs, warnMissingNodeTypes: false, setActive: false }).catch(error => {
408411
console.error("Failed restoring previous workflow", error)

src/lib/nodes/actions/ComfySendOutputAction.ts

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import notify from "$lib/notify";
88
import workflowState from "$lib/stores/workflowState";
99
import { get } from "svelte/store";
1010
import type ComfyApp from "$lib/components/ComfyApp";
11+
import interfaceState from "$lib/stores/interfaceState";
1112

1213
export interface ComfySendOutputActionProperties extends ComfyGraphNodeProperties {
1314
}
@@ -41,36 +42,8 @@ export default class ComfySendOutputAction extends ComfyGraphNode {
4142

4243
this.isActive = true;
4344

44-
const doSend = (modal: ModalData) => {
45+
interfaceState.querySendOutput(value, type, receiveTargets, () => {
4546
this.isActive = false;
46-
47-
const { workflow, targetNode } = get(modal.state) as SendOutputModalResult;
48-
console.warn("send", workflow, targetNode);
49-
50-
if (workflow == null || targetNode == null)
51-
return
52-
53-
const app = (window as any).app as ComfyApp;
54-
if (app == null) {
55-
console.error("Couldn't get app!")
56-
return
57-
}
58-
59-
targetNode.receiveOutput(value);
60-
workflowState.setActiveWorkflow(app.lCanvas, workflow.id)
61-
}
62-
63-
modalState.pushModal({
64-
title: "Send Output",
65-
closeOnClick: true,
66-
showCloseButton: true,
67-
svelteComponent: SendOutputModal,
68-
svelteProps: {
69-
value,
70-
type,
71-
receiveTargets
72-
},
73-
onClose: doSend
7447
})
7548
};
7649
}

src/lib/notify.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ function notifyf7(text: string, options: NotifyOptions) {
1818
if (!f7)
1919
return;
2020

21-
console.error(options)
2221
let closeTimeout = options.timeout
2322
if (closeTimeout === undefined)
2423
closeTimeout = 3000;

src/lib/stores/interfaceState.ts

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { debounce } from '$lib/utils';
1+
import { debounce, isMobileBrowser } from '$lib/utils';
22
import { get, writable } from 'svelte/store';
33
import type { Readable, Writable } from 'svelte/store';
4-
import type { WorkflowInstID } from './workflowState';
4+
import type { WorkflowInstID, WorkflowReceiveOutputTargets } from './workflowState';
5+
import modalState, { type ModalData } from './modalState';
6+
import type { SlotType } from '@litegraph-ts/core';
7+
import type ComfyApp from '$lib/components/ComfyApp';
8+
import SendOutputModal, { type SendOutputModalResult } from "$lib/components/modal/SendOutputModal.svelte";
9+
import workflowState from './workflowState';
510

611
export type InterfaceState = {
712
// Show a large indicator of the currently editing number value for mobile
@@ -15,13 +20,16 @@ export type InterfaceState = {
1520
isJumpingToNode: boolean,
1621

1722
selectedWorkflowIndex: number | null
18-
showingWorkflow: boolean
23+
showingWorkflow: boolean,
24+
selectedTab: number,
25+
showSheet: boolean,
1926

2027
isDarkMode: boolean
2128
}
2229

2330
type InterfaceStateOps = {
2431
showIndicator: (pointerX: number, pointerY: number, value: any) => void,
32+
querySendOutput: (value: any, type: SlotType, receiveTargets: WorkflowReceiveOutputTargets[], cb: (modal: ModalData) => void) => void,
2533
}
2634

2735
export type WritableInterfaceStateStore = Writable<InterfaceState> & InterfaceStateOps;
@@ -34,6 +42,8 @@ const store: Writable<InterfaceState> = writable(
3442

3543
graphTransitioning: false,
3644
isJumpingToNode: false,
45+
selectedTab: 1,
46+
showSheet: false,
3747

3848
selectedWorkflowIndex: null,
3949
showingWorkflow: false,
@@ -57,9 +67,49 @@ function showIndicator(pointerX: number, pointerY: number, value: any) {
5767
debounceDrag();
5868
}
5969

70+
function querySendOutput(value: any, type: SlotType, receiveTargets: WorkflowReceiveOutputTargets[], cb: (modal: ModalData) => void) {
71+
if (isMobileBrowser(navigator.userAgent)) {
72+
store.update(s => { s.showSheet = true; return s; })
73+
}
74+
else {
75+
const doSend = (modal: ModalData) => {
76+
cb(modal)
77+
78+
const { workflow, targetNode } = get(modal.state) as SendOutputModalResult;
79+
console.warn("send", workflow, targetNode);
80+
81+
if (workflow == null || targetNode == null)
82+
return
83+
84+
const app = (window as any).app as ComfyApp;
85+
if (app == null) {
86+
console.error("Couldn't get app!")
87+
return
88+
}
89+
90+
targetNode.receiveOutput(value);
91+
workflowState.setActiveWorkflow(app.lCanvas, workflow.id)
92+
}
93+
94+
modalState.pushModal({
95+
title: "Send Output",
96+
closeOnClick: true,
97+
showCloseButton: true,
98+
svelteComponent: SendOutputModal,
99+
svelteProps: {
100+
value,
101+
type,
102+
receiveTargets
103+
},
104+
onClose: doSend
105+
})
106+
}
107+
}
108+
60109
const interfaceStateStore: WritableInterfaceStateStore =
61110
{
62111
...store,
63-
showIndicator
112+
showIndicator,
113+
querySendOutput
64114
}
65115
export default interfaceStateStore;

src/lib/stores/queueState.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,6 @@ function queueCleared(type: QueueItemType) {
445445
store.update(s => {
446446
if (type === "queue") {
447447
s.queuePending.set([]);
448-
s.queueRunning.set([]);
449448
s.queueRemaining = 0;
450449
s.runningNodeID = null;
451450
s.progress = null;

src/lib/utils.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import type { FileData as GradioFileData } from "@gradio/upload";
44
import { Subgraph, type LGraph, type LGraphNode, type LLink, type SerializedLGraph, type UUID, type NodeID, type SlotType, type Vector4, type SerializedLGraphNode } from "@litegraph-ts/core";
55
import { get } from "svelte/store";
66
import type { ComfyNodeID } from "./api";
7-
import { type SerializedPrompt } from "./components/ComfyApp";
8-
import workflowState from "./stores/workflowState";
7+
import ComfyApp, { type SerializedPrompt } from "./components/ComfyApp";
8+
import workflowState, { type WorkflowReceiveOutputTargets } from "./stores/workflowState";
99
import { ImageViewer } from "./ImageViewer";
1010
import configState from "$lib/stores/configState";
11+
import SendOutputModal, { type SendOutputModalResult } from "$lib/components/modal/SendOutputModal.svelte";
1112

1213
export function clamp(n: number, min: number, max: number): number {
1314
if (max <= min)
@@ -734,3 +735,9 @@ export function partition<T>(myArray: T[], chunkSize: number): T[] {
734735

735736
return tempArray;
736737
}
738+
739+
const MOBILE_USER_AGENTS = ["iPhone", "iPad", "Android", "BlackBerry", "WebOs"].map(a => new RegExp(a, "i"))
740+
741+
export function isMobileBrowser(userAgent: string): boolean {
742+
return MOBILE_USER_AGENTS.some(a => userAgent.match(a))
743+
}

src/main-desktop.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
const params = new URLSearchParams(window.location.search)
2-
3-
const MOBILE_USER_AGENTS = ["iPhone", "iPad", "Android", "BlackBerry", "WebOs"].map(a => new RegExp(a, "i"))
4-
5-
function isMobileBrowser(userAgent: string): boolean {
6-
return MOBILE_USER_AGENTS.some(a => userAgent.match(a))
7-
}
1+
import { isMobileBrowser } from "$lib/utils"
82

93
const isMobile = isMobileBrowser(navigator.userAgent);
104

5+
const params = new URLSearchParams(window.location.search)
116
if (params.get("desktop") !== "true") {
127
if (isMobile) {
138
window.location.href = "/mobile/"

src/mobile/GenToolbar.svelte

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
<script lang="ts">
22
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
3-
import queueState from "$lib/stores/queueState";
43
import workflowState, { ComfyBoxWorkflow } from "$lib/stores/workflowState";
5-
import { getNodeInfo } from "$lib/utils"
6-
import { Image, LayoutTextSidebarReverse } from "svelte-bootstrap-icons";
74
85
import { Link, Toolbar } from "framework7-svelte"
9-
import ProgressBar from "$lib/components/ProgressBar.svelte";
10-
import Progressbar from "$lib/components/f7/progressbar.svelte";
11-
import Indicator from "./Indicator.svelte";
12-
import interfaceState from "$lib/stores/interfaceState";
13-
import type { WritableLayoutStateStore } from "$lib/stores/layoutStates";
146
157
export let subworkflowID: number = -1;
168
export let app: ComfyApp = undefined;
17-
let layoutState: WritableLayoutStateStore = null;
18-
let fileInput: HTMLInputElement = undefined;
199
let workflow: ComfyBoxWorkflow | null = null;
2010
2111
$: workflow = $workflowState.activeWorkflow;
@@ -24,48 +14,27 @@
2414
navigator.vibrate(20)
2515
app.runDefaultQueueAction()
2616
}
27-
28-
async function refreshCombos() {
29-
navigator.vibrate(20)
30-
await app.refreshComboInNodes()
31-
}
32-
33-
function doLoad(): void {
34-
if (!fileInput)
35-
return;
36-
37-
navigator.vibrate(20)
38-
fileInput.value = null;
39-
fileInput.click();
40-
}
41-
42-
function loadWorkflow(): void {
43-
app.handleFile(fileInput.files[0]);
44-
}
45-
46-
function doSaveLocal(): void {
47-
navigator.vibrate(20)
48-
app.saveStateToLocalStorage();
49-
}
5017
</script>
5118

5219
<Toolbar bottom color="red" style="bottom: calc(var(--f7-toolbar-height))">
5320
{#if workflow != null && workflow.attrs.queuePromptButtonName != ""}
54-
<Link on:click={queuePrompt}>
55-
{workflow.attrs.queuePromptButtonName}
56-
</Link>
21+
<div style:width="100%">
22+
<Link on:click={queuePrompt}>
23+
{workflow.attrs.queuePromptButtonName}
24+
</Link>
25+
</div>
5726
{/if}
58-
<Link on:click={refreshCombos}>🔄</Link>
59-
<Link on:click={doSaveLocal}>Save Local</Link>
60-
<Link on:click={doLoad}>Load</Link>
61-
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
6227
</Toolbar>
6328

6429
<style lang="scss">
6530
#comfy-file-input {
6631
display: none;
6732
}
6833
34+
:global(.toolbar) {
35+
--f7-toolbar-font-size: 13pt;
36+
}
37+
6938
:global(.dark .toolbar.color-red) {
7039
background: var(--neutral-700) !important;
7140
}

src/mobile/MainToolbar.svelte

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@
8787
$: toolbarCount = $interfaceState.showingWorkflow ? 2 : 1;
8888
8989
const ICON_SIZE = "1.5rem";
90+
91+
let selectedTab = 1;
9092
</script>
9193

9294
<div class="bottom" style:--toolbarCount={toolbarCount}>
@@ -108,13 +110,13 @@
108110
</div>
109111
</div>
110112
<Toolbar bottom tabbar color="blue" class={toolbarCount > 1 ? "hasGenToolbar" : ""}>
111-
<Link transition="f7-dive" href="/queue/">
113+
<Link transition="f7-dive" href="/queue/" tabLinkActive={$interfaceState.selectedTab === 0}>
112114
<LayoutTextSidebarReverse width={ICON_SIZE} height={ICON_SIZE} />
113115
</Link>
114-
<Link transition="f7-dive" href={centerHref} tabLinkActive>
116+
<Link transition="f7-dive" href={centerHref} tabLinkActive={$interfaceState.selectedTab === 1}>
115117
<Image width={ICON_SIZE} height={ICON_SIZE} />
116118
</Link>
117-
<Link transition="f7-dive" href="/gallery/">
119+
<Link transition="f7-dive" href="/gallery/" tabLinkActive={$interfaceState.selectedTab === 2}>
118120
<Grid width={ICON_SIZE} height={ICON_SIZE} />
119121
</Link>
120122
</Toolbar>
@@ -143,6 +145,11 @@
143145
border-top: 2px solid var(--neutral-600);
144146
}
145147
148+
:global(.dark .tab-link-active) {
149+
--f7-tabbar-link-active-color: var(--secondary-500);
150+
--f7-tabbar-link-active-bg-color: #283547;
151+
}
152+
146153
.bottom {
147154
--toolbarCount: 1;
148155
position: absolute;

src/mobile/routes/gallery.svelte

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { Page, Navbar, Block, Tabs, Tab, NavLeft, NavTitle, NavRight, Link } from "framework7-svelte"
2+
import { Page, Navbar, Block, Tabs, Tab, NavLeft, NavTitle, NavRight, Link, f7 } from "framework7-svelte"
33
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
44
import type ComfyApp from "$lib/components/ComfyApp";
55
import { writable, type Writable } from "svelte/store";
@@ -11,6 +11,7 @@
1111
import { partition, showLightbox } from "$lib/utils";
1212
import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState";
1313
import { showMobileLightbox } from "$lib/components/utils";
14+
import notify from "$lib/notify";
1415
1516
export let app: ComfyApp
1617
@@ -22,6 +23,10 @@
2223
2324
let gridCols = 3;
2425
26+
function onPageBeforeIn() {
27+
$interfaceState.selectedTab = 2;
28+
}
29+
2530
$: buildImageList(_entries);
2631
2732
function buildImageList(entries: ReadonlyArray<QueueUIEntry>) {
@@ -39,14 +44,16 @@
3944
showMobileLightbox(allImages, index)
4045
}
4146
42-
4347
async function clearHistory() {
44-
await app.clearQueue("history");
45-
uiQueueState.updateEntries(true)
48+
f7.dialog.confirm("Are you sure you want to clear the current history?", async () => {
49+
await app.clearQueue("history");
50+
uiQueueState.updateEntries(true)
51+
notify("History cleared!")
52+
})
4653
}
4754
</script>
4855

49-
<Page name="gallery">
56+
<Page name="gallery" on:pageBeforeIn={onPageBeforeIn}>
5057
<Navbar>
5158
<NavLeft></NavLeft>
5259
<NavTitle>Gallery</NavTitle>

0 commit comments

Comments
 (0)