From c537cb71bf455a595039b4c29b9d47933581daf9 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Wed, 31 May 2023 11:41:48 -0500 Subject: [PATCH 1/8] Proper mobile redirection --- index.html | 11 ----------- src/App.svelte | 12 ++++++------ src/lib/ComfyGraphCanvas.ts | 20 ++++++++++++++++++-- src/lib/api.ts | 13 +++++++++++-- src/lib/components/ComfyApp.ts | 8 +++++++- src/lib/stores/configState.ts | 7 ++++++- src/lib/utils.ts | 12 ++++++++++++ src/lib/widgets/ComboWidget.svelte | 1 + src/main-desktop.ts | 19 ++++++++++++++++++- vite.config.ts | 1 + 10 files changed, 80 insertions(+), 24 deletions(-) diff --git a/index.html b/index.html index 28202f3..b628d53 100644 --- a/index.html +++ b/index.html @@ -7,17 +7,6 @@ -
diff --git a/src/App.svelte b/src/App.svelte index 11b98f9..8aed45d 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -4,12 +4,12 @@ import "@litegraph-ts/core/css/litegraph.css"; import "./scss/global.scss"; - import { onMount } from 'svelte'; - export let app: ComfyAppState; + export let isMobile: boolean - - - +{#if isMobile} +
Redirecting...
+{:else} + +{/if} diff --git a/src/lib/ComfyGraphCanvas.ts b/src/lib/ComfyGraphCanvas.ts index ae686bb..220d85e 100644 --- a/src/lib/ComfyGraphCanvas.ts +++ b/src/lib/ComfyGraphCanvas.ts @@ -333,7 +333,8 @@ export default class ComfyGraphCanvas extends LGraphCanvas { /** * Handle keypress * - * Ctrl + M mute/unmute selected nodes + * Ctrl + M mute/unmute selected nodes + * Ctrl + Space open node searchbox */ override processKey(e: KeyboardEvent): boolean | undefined { const res = super.processKey(e); @@ -353,7 +354,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { } if (e.type == "keydown") { - // Ctrl + M mute/unmute + // Ctrl + M - mute/unmute if (e.keyCode == 77 && e.ctrlKey) { if (this.selected_nodes) { for (var i in this.selected_nodes) { @@ -366,6 +367,21 @@ export default class ComfyGraphCanvas extends LGraphCanvas { } block_default = true; } + // Ctrl + Space - open node searchbox + else if (e.keyCode == 32 && e.ctrlKey) { + const event = new MouseEvent("click"); + const searchBox = this.showSearchBox(event); + const rect = this.canvas.getBoundingClientRect(); + const sbRect = searchBox.getBoundingClientRect(); + const clientX = rect.left + rect.width / 2 - sbRect.width / 2; + const clientY = rect.top + rect.height / 2 - sbRect.height / 2 + searchBox.style.left = `${clientX}px`; + searchBox.style.top = `${clientY}px`; + // TODO better API + event.initMouseEvent("click", true, true, window, 1, clientX, clientY, clientX, clientY, false, false, false, false, 0, null); + this.adjustMouseEvent(event); + block_default = true; + } } this.graph.change(); diff --git a/src/lib/api.ts b/src/lib/api.ts index 7d7f47d..dc8765b 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -125,8 +125,17 @@ export default class ComfyAPI { }, 1000); } + private getHostname(): string { + let hostname = this.hostname || location.hostname; + if (hostname === "localhost") { + // For dev use, assume same hostname as connected server + hostname = location.hostname; + } + return hostname; + } + private getBackendUrl(): string { - const hostname = this.hostname || location.hostname; + const hostname = this.getHostname() const port = this.port || location.port; return `${window.location.protocol}//${hostname}:${port}` } @@ -146,7 +155,7 @@ export default class ComfyAPI { existingSession = "?clientId=" + existingSession; } - const hostname = this.hostname || location.hostname; + const hostname = this.getHostname() const port = this.port || location.port; this.socket = new WebSocket( diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 6eb6d7f..73f840d 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -211,7 +211,13 @@ export default class ComfyApp { this.lCanvas.allow_interaction = uiUnlocked; // await this.#invokeExtensionsAsync("init"); - const defs = await this.api.getNodeDefs(); + let defs; + try { + defs = await this.api.getNodeDefs(); + } + catch (error) { + throw new Error(`Could not reach ComfyUI at ${this.api.getBackendUrl()}`); + } await this.registerNodes(defs); // Load previous workflow diff --git a/src/lib/stores/configState.ts b/src/lib/stores/configState.ts index e6fe1e9..75069b1 100644 --- a/src/lib/stores/configState.ts +++ b/src/lib/stores/configState.ts @@ -24,7 +24,12 @@ let changedOptions: Partial> = {} function getBackendURL(): string { const state = get(store); - return `${window.location.protocol}//${state.comfyUIHostname}:${state.comfyUIPort}` + let hostname = state.comfyUIHostname + if (hostname === "localhost") { + // For dev use, assume same hostname as connected server + hostname = location.hostname; + } + return `${window.location.protocol}//${hostname}:${state.comfyUIPort}` } function canShowNotificationText(): boolean { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e340256..bb5ee75 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -618,6 +618,9 @@ export async function readFileToText(file: File): Promise { reader.onload = async () => { resolve(reader.result as string); }; + reader.onerror = async () => { + reject(reader.error); + } reader.readAsText(file); }) } @@ -709,3 +712,12 @@ export function canvasToBlob(canvas: HTMLCanvasElement): Promise { canvas.toBlob(resolve); }); } + +export type SafetensorsMetadata = Record + +export async function getSafetensorsMetadata(folder: string, filename: string): Promise { + const url = configState.getBackendURL(); + const params = new URLSearchParams({ filename }) + + return fetch(new Request(url + `/view_metadata/${folder}?` + params)).then(r => r.json()) +} diff --git a/src/lib/widgets/ComboWidget.svelte b/src/lib/widgets/ComboWidget.svelte index 63816c1..ef04d9a 100644 --- a/src/lib/widgets/ComboWidget.svelte +++ b/src/lib/widgets/ComboWidget.svelte @@ -8,6 +8,7 @@ import { type WidgetLayout } from "$lib/stores/layoutStates"; import { get, writable, type Writable } from "svelte/store"; import { isDisabled } from "./utils" + import { getSafetensorsMetadata } from '$lib/utils'; export let widget: WidgetLayout | null = null; export let isMobile: boolean = false; let node: ComfyComboNode | null = null; diff --git a/src/main-desktop.ts b/src/main-desktop.ts index f91c7f9..77e0916 100644 --- a/src/main-desktop.ts +++ b/src/main-desktop.ts @@ -1,3 +1,20 @@ +const params = new URLSearchParams(window.location.search) + +const MOBILE_USER_AGENTS = ["iPhone", "iPad", "Android", "BlackBerry", "WebOs"].map(a => new RegExp(a, "i")) + +function isMobileBrowser(userAgent: string): boolean { + return MOBILE_USER_AGENTS.some(a => userAgent.match(a)) +} + +const isMobile = isMobileBrowser(navigator.userAgent); + +if (params.get("desktop") !== "true") { + if (isMobile) { + window.location.href = "/mobile/" + } +} + + // Run node registration before anthing else, in the proper order import "$lib/nodeImports"; @@ -12,7 +29,7 @@ const comfyApp = new ComfyApp(); const app = new App({ target: document.getElementById("app-root"), - props: { app: comfyApp } + props: { app: comfyApp, isMobile } }) export default app; diff --git a/vite.config.ts b/vite.config.ts index 25f1aab..1373ffd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -55,6 +55,7 @@ export default defineConfig({ }, }, build: { + minify: isProduction, sourcemap: true, rollupOptions: { input: { From 54746870418ea3e839b8823419288db36d6ebb78 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Wed, 31 May 2023 14:28:56 -0500 Subject: [PATCH 2/8] Progressbar on mobile improvement --- package.json | 2 +- pnpm-lock.yaml | 81 ++++++---- src/AppMobile.svelte | 144 ++++++++++++------ src/lib/components/f7/progressbar.svelte | 58 +++++++ src/lib/nodes/widgets/ComfyMarkdownNode.ts | 1 + src/lib/widgets/ComboWidget.svelte | 34 ++--- src/lib/widgets/MarkdownWidget.svelte | 16 +- src/mobile/GenToolbar.svelte | 71 ++++++--- src/mobile/routes/home.svelte | 42 +++-- .../{subworkflow.svelte => workflow.svelte} | 37 +++-- 10 files changed, 333 insertions(+), 153 deletions(-) create mode 100644 src/lib/components/f7/progressbar.svelte rename src/mobile/routes/{subworkflow.svelte => workflow.svelte} (50%) diff --git a/package.json b/package.json index 670e59f..a258134 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "prettier-plugin-svelte": "^2.10.0", "rollup-plugin-visualizer": "^5.9.0", "sass": "^1.61.0", - "svelte": "^3.58.0", + "svelte": "^3.59.0", "svelte-check": "^3.2.0", "svelte-dnd-action": "^0.9.22", "typescript": "^5.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f8345e..b3a1120 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,7 +27,7 @@ importers: version: 6.11.0 '@dogagenc/svelte-markdown': specifier: ^0.2.4 - version: 0.2.4(svelte@3.58.0) + version: 0.2.4(svelte@3.59.1) '@gradio/accordion': specifier: workspace:* version: link:gradio/js/accordion @@ -93,7 +93,7 @@ importers: version: link:litegraph/packages/tsconfig '@sveltejs/vite-plugin-svelte': specifier: ^2.1.1 - version: 2.1.1(svelte@3.58.0)(vite@4.3.8) + version: 2.1.1(svelte@3.59.1)(vite@4.3.8) '@tsconfig/svelte': specifier: ^4.0.1 version: 4.0.1 @@ -156,13 +156,13 @@ importers: version: 1.5.2 svelte-preprocess: specifier: ^5.0.3 - version: 5.0.3(sass@1.61.0)(svelte@3.58.0)(typescript@5.0.3) + version: 5.0.3(sass@1.61.0)(svelte@3.59.1)(typescript@5.0.3) svelte-select: specifier: ^5.5.3 version: 5.5.3 svelte-splitpanes: specifier: ^0.7.13 - version: 0.7.13(svelte@3.58.0) + version: 0.7.13(svelte@3.59.1) svelte-tiny-virtual-list: specifier: ^2.0.5 version: 2.0.5 @@ -190,7 +190,7 @@ importers: version: 1.2.8 '@zerodevx/svelte-toast': specifier: ^0.9.3 - version: 0.9.3(svelte@3.58.0) + version: 0.9.3(svelte@3.59.1) eslint: specifier: ^8.37.0 version: 8.37.0 @@ -199,7 +199,7 @@ importers: version: 8.8.0(eslint@8.37.0) eslint-plugin-svelte3: specifier: ^4.0.0 - version: 4.0.0(eslint@8.37.0)(svelte@3.58.0) + version: 4.0.0(eslint@8.37.0)(svelte@3.59.1) happy-dom: specifier: ^9.18.3 version: 9.18.3 @@ -211,7 +211,7 @@ importers: version: 2.8.7 prettier-plugin-svelte: specifier: ^2.10.0 - version: 2.10.0(prettier@2.8.7)(svelte@3.58.0) + version: 2.10.0(prettier@2.8.7)(svelte@3.59.1) rollup-plugin-visualizer: specifier: ^5.9.0 version: 5.9.0 @@ -219,14 +219,14 @@ importers: specifier: ^1.61.0 version: 1.61.0 svelte: - specifier: ^3.58.0 - version: 3.58.0 + specifier: ^3.59.0 + version: 3.59.1 svelte-check: specifier: ^3.2.0 - version: 3.2.0(sass@1.61.0)(svelte@3.58.0) + version: 3.2.0(sass@1.61.0)(svelte@3.59.1) svelte-dnd-action: specifier: ^0.9.22 - version: 0.9.22(svelte@3.58.0) + version: 0.9.22(svelte@3.59.1) typescript: specifier: ^5.0.3 version: 5.0.3 @@ -1363,6 +1363,7 @@ packages: '@codemirror/language': ^6.0.0 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 + '@lezer/common': ^1.0.0 dependencies: '@codemirror/language': 6.6.0 '@codemirror/state': 6.2.0 @@ -1506,14 +1507,14 @@ packages: w3c-keyname: 2.2.6 dev: false - /@dogagenc/svelte-markdown@0.2.4(svelte@3.58.0): + /@dogagenc/svelte-markdown@0.2.4(svelte@3.59.1): resolution: {integrity: sha512-UmmHHZ7rilAbBYiNsxuL5d8Ac79EhFXrhjsUNr30BPzn+T7ohJR8kHMFjDYDQc0tOQOfKbICvkPAQ6cprqS3Eg==} peerDependencies: svelte: ^3.0.0 dependencies: '@types/marked': 4.3.1 marked: 4.3.0 - svelte: 3.58.0 + svelte: 3.59.1 dev: false /@esbuild/android-arm64@0.17.18: @@ -2275,7 +2276,7 @@ packages: - supports-color dev: true - /@sveltejs/vite-plugin-svelte@2.1.1(svelte@3.58.0)(vite@4.3.8): + /@sveltejs/vite-plugin-svelte@2.1.1(svelte@3.59.1)(vite@4.3.8): resolution: {integrity: sha512-7YeBDt4us0FiIMNsVXxyaP4Hwyn2/v9x3oqStkHU3ZdIc5O22pGwUwH33wUqYo+7Itdmo8zxJ45Qvfm3H7UUjQ==} engines: {node: ^14.18.0 || >= 16} peerDependencies: @@ -2286,8 +2287,8 @@ packages: deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.0 - svelte: 3.58.0 - svelte-hmr: 0.15.1(svelte@3.58.0) + svelte: 3.59.1 + svelte-hmr: 0.15.1(svelte@3.59.1) vite: 4.3.8(sass@1.61.0) vitefu: 0.2.4(vite@4.3.8) transitivePeerDependencies: @@ -2604,12 +2605,12 @@ packages: pretty-format: 27.5.1 dev: false - /@zerodevx/svelte-toast@0.9.3(svelte@3.58.0): + /@zerodevx/svelte-toast@0.9.3(svelte@3.59.1): resolution: {integrity: sha512-VPKWR4A9y01fyXRscu9HiTj7tV2hFrpRKZvGwMmaPXfHIXR1D9+NNsz0HXcQ7qZ0C5UaHS3n9uNtPtIcAXT7RQ==} peerDependencies: svelte: ^3.57.0 dependencies: - svelte: 3.58.0 + svelte: 3.59.1 dev: true /abab@2.0.6: @@ -3117,6 +3118,8 @@ packages: '@codemirror/search': 6.4.0 '@codemirror/state': 6.2.0 '@codemirror/view': 6.11.0 + transitivePeerDependencies: + - '@lezer/common' dev: false /codemirror@6.0.1(@lezer/common@1.0.2): @@ -3941,14 +3944,14 @@ packages: - typescript dev: true - /eslint-plugin-svelte3@4.0.0(eslint@8.37.0)(svelte@3.58.0): + /eslint-plugin-svelte3@4.0.0(eslint@8.37.0)(svelte@3.59.1): resolution: {integrity: sha512-OIx9lgaNzD02+MDFNLw0GEUbuovNcglg+wnd/UY0fbZmlQSz7GlQiQ1f+yX0XvC07XPcDOnFcichqI3xCwp71g==} peerDependencies: eslint: '>=8.0.0' svelte: ^3.2.0 dependencies: eslint: 8.37.0 - svelte: 3.58.0 + svelte: 3.59.1 dev: true /eslint-scope@5.1.1: @@ -5988,14 +5991,14 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier-plugin-svelte@2.10.0(prettier@2.8.7)(svelte@3.58.0): + /prettier-plugin-svelte@2.10.0(prettier@2.8.7)(svelte@3.59.1): resolution: {integrity: sha512-GXMY6t86thctyCvQq+jqElO+MKdB09BkL3hexyGP3Oi8XLKRFaJP1ud/xlWCZ9ZIa2BxHka32zhHfcuU+XsRQg==} peerDependencies: prettier: ^1.16.4 || ^2.0.0 svelte: ^3.2.0 dependencies: prettier: 2.8.7 - svelte: 3.58.0 + svelte: 3.59.1 dev: true /prettier@2.8.7: @@ -6656,7 +6659,7 @@ packages: - sugarss dev: true - /svelte-check@3.2.0(sass@1.61.0)(svelte@3.58.0): + /svelte-check@3.2.0(sass@1.61.0)(svelte@3.59.1): resolution: {integrity: sha512-6ZnscN8dHEN5Eq5LgIzjj07W9nc9myyBH+diXsUAuiY/3rt0l65/LCIQYlIuoFEjp2F1NhXqZiJwV9omPj9tMw==} hasBin: true peerDependencies: @@ -6668,8 +6671,8 @@ packages: import-fresh: 3.3.0 picocolors: 1.0.0 sade: 1.8.1 - svelte: 3.58.0 - svelte-preprocess: 5.0.3(sass@1.61.0)(svelte@3.58.0)(typescript@5.0.3) + svelte: 3.59.1 + svelte-preprocess: 5.0.3(sass@1.61.0)(svelte@3.59.1)(typescript@5.0.3) typescript: 5.0.3 transitivePeerDependencies: - '@babel/core' @@ -6683,18 +6686,18 @@ packages: - sugarss dev: true - /svelte-dnd-action@0.9.22(svelte@3.58.0): + /svelte-dnd-action@0.9.22(svelte@3.59.1): resolution: {integrity: sha512-lOQJsNLM1QWv5mdxIkCVtk6k4lHCtLgfE59y8rs7iOM6erchbLC9hMEFYSveZz7biJV0mpg7yDSs4bj/RT/YkA==} peerDependencies: svelte: '>=3.23.0' dependencies: - svelte: 3.58.0 + svelte: 3.59.1 dev: true /svelte-feather-icons@4.0.0: resolution: {integrity: sha512-4ieUsjp+VYa1r6y80jDt9zRiRUZyJNbESpRdHdJJhiBubyuXX96A7f1UZSK4olxzP6Qsg5ZAuyZlnmvD+/swAA==} dependencies: - svelte: 3.58.0 + svelte: 3.59.1 dev: false /svelte-floating-ui@1.2.8: @@ -6718,6 +6721,16 @@ packages: svelte: '>=3.19.0' dependencies: svelte: 3.58.0 + dev: true + + /svelte-hmr@0.15.1(svelte@3.59.1): + resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==} + engines: {node: ^12.20 || ^14.13.1 || >= 16} + peerDependencies: + svelte: '>=3.19.0' + dependencies: + svelte: 3.59.1 + dev: false /svelte-preprocess@4.10.1(postcss-load-config@3.1.4)(postcss@8.4.21)(svelte@3.58.0)(typescript@4.5.4): resolution: {integrity: sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==} @@ -6825,7 +6838,7 @@ packages: typescript: 5.0.3 dev: true - /svelte-preprocess@5.0.3(sass@1.61.0)(svelte@3.58.0)(typescript@5.0.3): + /svelte-preprocess@5.0.3(sass@1.61.0)(svelte@3.59.1)(typescript@5.0.3): resolution: {integrity: sha512-GrHF1rusdJVbOZOwgPWtpqmaexkydznKzy5qIC2FabgpFyKN57bjMUUUqPRfbBXK5igiEWn1uO/DXsa2vJ5VHA==} engines: {node: '>= 14.10.0'} requiresBuild: true @@ -6869,7 +6882,7 @@ packages: sass: 1.61.0 sorcery: 0.11.0 strip-indent: 3.0.0 - svelte: 3.58.0 + svelte: 3.59.1 typescript: 5.0.3 /svelte-range-slider-pips@2.0.1: @@ -6883,13 +6896,13 @@ packages: svelte-floating-ui: 1.2.8 dev: false - /svelte-splitpanes@0.7.13(svelte@3.58.0): + /svelte-splitpanes@0.7.13(svelte@3.59.1): resolution: {integrity: sha512-LiAf4OEZqRJanoax9mextXtQ0JzrdCqX2tOgVO+yJu2XNyGz5j5fGbw8+5AXgOasPi/m1nv8n2Lt+XYFRfvIGg==} peerDependencies: svelte: ^3.54.0 dependencies: esm-env-robust: 0.0.3 - svelte: 3.58.0 + svelte: 3.59.1 dev: false /svelte-tiny-virtual-list@2.0.5: @@ -6912,6 +6925,10 @@ packages: resolution: {integrity: sha512-brIBNNB76mXFmU/Kerm4wFnkskBbluBDCjx/8TcpYRb298Yh2dztS2kQ6bhtjMcvUhd5ynClfwpz5h2gnzdQ1A==} engines: {node: '>= 8'} + /svelte@3.59.1: + resolution: {integrity: sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==} + engines: {node: '>= 8'} + /swiper@9.2.4: resolution: {integrity: sha512-L7y3K/iiMXNYQ94FbfcJn7jex4QPnS4+voXGupTdC+UHW4XrR40QDdm4c9hXJ+Br0Il7PP0vP1W3goM9/Ly6Sg==} engines: {node: '>= 4.7.0'} diff --git a/src/AppMobile.svelte b/src/AppMobile.svelte index a120c51..85de615 100644 --- a/src/AppMobile.svelte +++ b/src/AppMobile.svelte @@ -2,7 +2,7 @@ import { onMount } from "svelte"; import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp"; - import { App, View } from "framework7-svelte" + import { App, View, Preloader } from "framework7-svelte" import { f7, f7ready } from 'framework7-svelte'; @@ -15,8 +15,7 @@ import AboutPage from './mobile/routes/about.svelte'; import LoginPage from './mobile/routes/login.svelte'; import GraphPage from './mobile/routes/graph.svelte'; - import ListSubWorkflowsPage from './mobile/routes/list-subworkflows.svelte'; - import SubWorkflowPage from './mobile/routes/subworkflow.svelte'; + import WorkflowPage from './mobile/routes/workflow.svelte'; import type { Framework7Parameters, Modal } from "framework7/types"; export let app: ComfyApp; @@ -51,18 +50,35 @@ } } + let appSetupPromise: Promise = null; + let loading = true; + let lastSize = Number.POSITIVE_INFINITY; + onMount(async () => { - await app.setup(); + appSetupPromise = app.setup().then(() => { + loading = false + }); window.addEventListener("backbutton", onBackKeyDown, false); window.addEventListener("popstate", onBackKeyDown, false); - }); - /* - Now we need to map components to routes. - We need to pass them along with the F7 app parameters to component - */ + // Blur any input elements when the virtual keyboard closes + // Otherwise tapping on other input events can refocus the input from way + // off the screen + window.visualViewport.addEventListener("resize", function(e) { + if (e.target.height > lastSize) { + // Assume keyboard was hidden + (document.activeElement as HTMLElement)?.blur(); + } + lastSize = e.target.height + }) + }) - let f7params: Framework7Parameters = { + /* + Now we need to map components to routes. + We need to pass them along with the F7 app parameters to component + */ + + let f7params: Framework7Parameters = { routes: [ { path: '/', @@ -79,23 +95,16 @@ path: '/login/', component: LoginPage, }, + // { + // path: '/graph/', + // component: GraphPage, + // options: { + // props: { app } + // } + // }, { - path: '/graph/', - component: GraphPage, - options: { - props: { app } - } - }, - { - path: '/subworkflows/', - component: ListSubWorkflowsPage, - options: { - props: { app } - } - }, - { - path: '/subworkflows/:subworkflowID/', - component: SubWorkflowPage, + path: '/workflows/:workflowID/', + component: WorkflowPage, options: { props: { app } } @@ -113,23 +122,72 @@ actions: { closeOnEscape: true, }, + touch: { + tapHold: true + } } -{#if app} - - - - - - -{/if} + + {#if appSetupPromise} + {#await appSetupPromise} +
+
+ +
+
+ {:then} + + + + {:catch error} +
+
+ Error loading app +
+
{error}
+ {#if error != null && error.stack} + {@const lines = error.stack.split("\n")} + {#each lines as line} +
{line}
+ {/each} + {/if} +
+ {/await} + {/if} +
+ + + diff --git a/src/lib/components/f7/progressbar.svelte b/src/lib/components/f7/progressbar.svelte new file mode 100644 index 0000000..367f7ef --- /dev/null +++ b/src/lib/components/f7/progressbar.svelte @@ -0,0 +1,58 @@ + + + + + + diff --git a/src/lib/nodes/widgets/ComfyMarkdownNode.ts b/src/lib/nodes/widgets/ComfyMarkdownNode.ts index 8d41bf5..27de2f0 100644 --- a/src/lib/nodes/widgets/ComfyMarkdownNode.ts +++ b/src/lib/nodes/widgets/ComfyMarkdownNode.ts @@ -42,6 +42,7 @@ export default class ComfyMarkdownNode extends ComfyWidgetNode { }, { multiline: true, + inputStyle: { fontFamily: "monospace" } } ) diff --git a/src/lib/widgets/ComboWidget.svelte b/src/lib/widgets/ComboWidget.svelte index ef04d9a..672f926 100644 --- a/src/lib/widgets/ComboWidget.svelte +++ b/src/lib/widgets/ComboWidget.svelte @@ -8,7 +8,7 @@ import { type WidgetLayout } from "$lib/stores/layoutStates"; import { get, writable, type Writable } from "svelte/store"; import { isDisabled } from "./utils" - import { getSafetensorsMetadata } from '$lib/utils'; + import { getSafetensorsMetadata } from '$lib/utils'; export let widget: WidgetLayout | null = null; export let isMobile: boolean = false; let node: ComfyComboNode | null = null; @@ -74,26 +74,10 @@ } } - function onSelect(e: CustomEvent) { - if (input) - input.blur(); - navigator.vibrate(20) - - const item = e.detail - - console.debug("[ComboWidget] SELECT", item, item.index) - $nodeValue = item.value; - activeIndex = item.index; - listOpen = false; - } - let activeIndex = null; let hoverItemIndex = null; let filterText = ""; let listOpen = null; - let scrollToIndex = null; - let start = 0; - let end = 0; function handleHover(index: number) { // console.warn("HOV", index) @@ -108,7 +92,9 @@ $nodeValue = item.value listOpen = false; filterText = "" - input?.blur() + setTimeout(() => { + input?.blur(); + }, 100) } function onFilter() { @@ -174,7 +160,10 @@ on:select={(e) => handleSelect(e.detail.index)} on:blur on:filter={onFilter}> -
+
{#if filteredItems.length > 0} {@const itemSize = isMobile ? 50 : 25} {@const itemsToShow = isMobile ? 10 : 30} @@ -292,9 +281,14 @@ .comfy-select-list { --maxLabelWidth: 100; + --maxListWidth: 50vw; + &.mobile { + --maxListWidth: 80vw; + } + font-size: 14px; - width: min(calc((var(--maxLabelWidth) + 10) * 1ch), 50vw); color: var(--item-color); + width: min(calc((var(--maxLabelWidth) + 10) * 1ch), var(--maxListWidth)); > :global(.virtual-list-wrapper) { box-shadow: var(--block-shadow); diff --git a/src/lib/widgets/MarkdownWidget.svelte b/src/lib/widgets/MarkdownWidget.svelte index f99524b..a42acaa 100644 --- a/src/lib/widgets/MarkdownWidget.svelte +++ b/src/lib/widgets/MarkdownWidget.svelte @@ -5,7 +5,7 @@ import type { ComfyMarkdownNode } from "$lib/nodes/widgets"; import SvelteMarkdown from "@dogagenc/svelte-markdown" import NullMarkdownRenderer from "./markdown/NullMarkdownRenderer.svelte" - import { SvelteComponentDev } from "svelte/internal"; + import { SvelteComponentDev } from "svelte/internal"; export let widget: WidgetLayout | null = null; export let isMobile: boolean = false; @@ -69,7 +69,7 @@ } /* headings - –––––––––––––––––––––––––––––––––––––––––––––––––– */ + –––––––––––––––––––––––––––––––––––––––––––––––––– */ .prose h1, .prose h2, @@ -107,7 +107,7 @@ } /* lists - –––––––––––––––––––––––––––––––––––––––––––––––––– */ + –––––––––––––––––––––––––––––––––––––––––––––––––– */ .prose ul { list-style: circle inside; } @@ -136,7 +136,7 @@ } /* code - –––––––––––––––––––––––––––––––––––––––––––––––––– */ + –––––––––––––––––––––––––––––––––––––––––––––––––– */ .prose code { border: 1px solid var(--border-color-primary); border-radius: var(--radius-sm); @@ -153,7 +153,7 @@ } /* tables - –––––––––––––––––––––––––––––––––––––––––––––––––– */ + –––––––––––––––––––––––––––––––––––––––––––––––––– */ .prose th, .prose td { border-bottom: 1px solid #e1e1e1; @@ -170,7 +170,7 @@ } /* spacing - –––––––––––––––––––––––––––––––––––––––––––––––––– */ + –––––––––––––––––––––––––––––––––––––––––––––––––– */ .prose button, .prose .button { margin-bottom: var(--spacing-sm); @@ -194,7 +194,7 @@ } /* links - –––––––––––––––––––––––––––––––––––––––––––––––––– */ + –––––––––––––––––––––––––––––––––––––––––––––––––– */ .prose a { color: var(--link-text-color); text-decoration: underline; @@ -212,7 +212,7 @@ } /* misc - –––––––––––––––––––––––––––––––––––––––––––––––––– */ + –––––––––––––––––––––––––––––––––––––––––––––––––– */ .prose hr { margin-top: 3em; diff --git a/src/mobile/GenToolbar.svelte b/src/mobile/GenToolbar.svelte index 22063c3..ded4825 100644 --- a/src/mobile/GenToolbar.svelte +++ b/src/mobile/GenToolbar.svelte @@ -6,6 +6,7 @@ import { Link, Toolbar } from "framework7-svelte" import ProgressBar from "$lib/components/ProgressBar.svelte"; + import Progressbar from "$lib/components/f7/progressbar.svelte"; import Indicator from "./Indicator.svelte"; import interfaceState from "$lib/stores/interfaceState"; import type { WritableLayoutStateStore } from "$lib/stores/layoutStates"; @@ -53,29 +54,50 @@ navigator.vibrate(20) app.saveStateToLocalStorage(); } + + let queued: false; + $: queued = Boolean($queueState.runningNodeID || $queueState.progress) + + let running = false; + $: running = typeof $queueState.queueRemaining === "number" && $queueState.queueRemaining > 0; + + let progress; + $: progress = $queueState.progress + + let progressPercent = 0 + let progressText = "" + $: if (progress) { + progressPercent = (progress.value / progress.max) * 100; + progressText = progressPercent.toFixed(1) + "%"; + } else { + progressPercent = 0 + progressText = "??.?%" + } +
- {#if $queueState.runningNodeID || $queueState.progress} -
- Node: {getNodeInfo($queueState.runningNodeID)} -
-
- -
- {/if} - {#if typeof $queueState.queueRemaining === "number" && $queueState.queueRemaining > 0} -
-
- Queued prompts: {$queueState.queueRemaining}. +
+ {#if queued} +
+ Node: {getNodeInfo($queueState.runningNodeID)} ({progressText})
-
- {/if} + {/if} +
+
+ {#if queued} + {#if progress} + + {:else if running} + + {/if} + {/if} +
{#if workflow != null && workflow.attrs.queuePromptButtonName != ""} - {workflow.attrs.queuePromptButtonName} + {workflow.attrs.queuePromptButtonName} {/if} 🔄 @@ -94,19 +116,26 @@ } .bottom { - display: flex; - flex-direction: row; position: absolute; text-align: center; width: 100%; - height: 2rem; - bottom: calc(var(--f7-toolbar-height) + var(--f7-safe-area-bottom)); + font-size: 13pt; + bottom: calc(var(--f7-toolbar-height)); z-index: var(--layer-top); - background-color: grey; + } + + .bars { + display: flex; + flex-direction: row; + + .bars { + display: flex; + flex-direction: row; + } .node-name { flex-grow: 1; - background-color: var(--color-red-300); + background-color: var(--secondary-300); padding: 0.2em; display: flex; justify-content: center; diff --git a/src/mobile/routes/home.svelte b/src/mobile/routes/home.svelte index 71c64d8..ec7fe31 100644 --- a/src/mobile/routes/home.svelte +++ b/src/mobile/routes/home.svelte @@ -1,34 +1,44 @@ - Yo - -
{app} Nodes
-
- - - - - - - - - + {#if $workflowState.openedWorkflows} + + {#each $workflowState.openedWorkflows as workflow} + + +
onClickDelete(workflow, e)}> + +
+
+
+ {/each} +
+ {:else} + (No workflows opened.) + {/if} diff --git a/src/mobile/routes/subworkflow.svelte b/src/mobile/routes/workflow.svelte similarity index 50% rename from src/mobile/routes/subworkflow.svelte rename to src/mobile/routes/workflow.svelte index a302ada..908839c 100644 --- a/src/mobile/routes/subworkflow.svelte +++ b/src/mobile/routes/workflow.svelte @@ -4,25 +4,38 @@ import type ComfyApp from "$lib/components/ComfyApp"; import { writable, type Writable } from "svelte/store"; import type { WritableLayoutStateStore } from "$lib/stores/layoutStates"; - import workflowState, { type ComfyBoxWorkflow } from "$lib/stores/workflowState"; + import workflowState, { type ComfyBoxWorkflow, type WorkflowInstID } from "$lib/stores/workflowState"; - export let subworkflowID: number = -1; + export let workflowID: WorkflowInstID; export let app: ComfyApp - // TODO move - let workflow: ComfyBoxWorkflow | null = null - let layoutState: WritableLayoutStateStore | null = null; + let workflow: ComfyBoxWorkflow; + let root: IDragItem | null; + let title = "" - $: workflow = $workflowState.activeWorkflow; - $: layoutState = workflow ? workflow.layout : null; + $: workflow = workflowState.getWorkflow(workflowID); + $: layoutState = workflow?.layout; + $: title = workflow?.attrs?.title || `Workflow: ${workflowID}`; + + $: if (layoutState && $layoutState.root) { + root = $layoutState.root + } else { + root = null; + } - - + + - {#if layoutState} -
- + {#if workflow} + {#if root} +
+ +
+ {/if} + {:else} +
+ Workflow not found.
{/if} From 6f3275da0053891b230823113e9efa6186fd1a03 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Wed, 31 May 2023 15:49:45 -0500 Subject: [PATCH 3/8] Mobile overhaul --- src/AppMobile.svelte | 20 ++- src/lib/stores/interfaceState.ts | 11 +- src/mobile/GenToolbar.svelte | 84 ++------- src/mobile/MainToolbar.svelte | 167 ++++++++++++++++++ src/mobile/routes/list-subworkflows.svelte | 16 -- src/mobile/routes/workflow.svelte | 36 +++- .../routes/{home.svelte => workflows.svelte} | 10 +- 7 files changed, 245 insertions(+), 99 deletions(-) create mode 100644 src/mobile/MainToolbar.svelte delete mode 100644 src/mobile/routes/list-subworkflows.svelte rename src/mobile/routes/{home.svelte => workflows.svelte} (82%) diff --git a/src/AppMobile.svelte b/src/AppMobile.svelte index 85de615..5de223d 100644 --- a/src/AppMobile.svelte +++ b/src/AppMobile.svelte @@ -9,14 +9,16 @@ import "framework7/css/bundle" import "./scss/global.scss"; + import MainToolbar from './mobile/MainToolbar.svelte' import GenToolbar from './mobile/GenToolbar.svelte' - import HomePage from './mobile/routes/home.svelte'; + import WorkflowsPage from './mobile/routes/workflows.svelte'; import AboutPage from './mobile/routes/about.svelte'; import LoginPage from './mobile/routes/login.svelte'; import GraphPage from './mobile/routes/graph.svelte'; import WorkflowPage from './mobile/routes/workflow.svelte'; import type { Framework7Parameters, Modal } from "framework7/types"; + import interfaceState from "$lib/stores/interfaceState"; export let app: ComfyApp; @@ -82,7 +84,14 @@ routes: [ { path: '/', - component: HomePage, + component: WorkflowsPage, + options: { + props: { app } + } + }, + { + path: '/workflows', + component: WorkflowsPage, options: { props: { app } } @@ -138,14 +147,17 @@
{:then} - + + {#if $interfaceState.selectedWorkflowID && $interfaceState.showingWorkflow} + + {/if} {:catch error}
diff --git a/src/lib/stores/interfaceState.ts b/src/lib/stores/interfaceState.ts index c792dd5..61c43dc 100644 --- a/src/lib/stores/interfaceState.ts +++ b/src/lib/stores/interfaceState.ts @@ -1,6 +1,7 @@ import { debounce } from '$lib/utils'; import { get, writable } from 'svelte/store'; import type { Readable, Writable } from 'svelte/store'; +import type { WorkflowInstID } from './workflowState'; export type InterfaceState = { // Show a large indicator of the currently editing number value for mobile @@ -10,8 +11,11 @@ export type InterfaceState = { showIndicator: boolean, indicatorValue: any, - graphTransitioning: boolean - isJumpingToNode: boolean + graphTransitioning: boolean, + isJumpingToNode: boolean, + + selectedWorkflowID: WorkflowInstID | null + showingWorkflow: boolean } type InterfaceStateOps = { @@ -28,6 +32,9 @@ const store: Writable = writable( graphTransitioning: false, isJumpingToNode: false, + + selectedWorkflowID: null, + showingWorkflow: false }) const debounceDrag = debounce(() => { store.update(s => { s.showIndicator = false; return s }) }, 1000) diff --git a/src/mobile/GenToolbar.svelte b/src/mobile/GenToolbar.svelte index ded4825..6112ac1 100644 --- a/src/mobile/GenToolbar.svelte +++ b/src/mobile/GenToolbar.svelte @@ -3,13 +3,14 @@ import queueState from "$lib/stores/queueState"; import workflowState, { ComfyBoxWorkflow } from "$lib/stores/workflowState"; import { getNodeInfo } from "$lib/utils" + import { Image, LayoutTextSidebarReverse } from "svelte-bootstrap-icons"; import { Link, Toolbar } from "framework7-svelte" import ProgressBar from "$lib/components/ProgressBar.svelte"; import Progressbar from "$lib/components/f7/progressbar.svelte"; - import Indicator from "./Indicator.svelte"; - import interfaceState from "$lib/stores/interfaceState"; - import type { WritableLayoutStateStore } from "$lib/stores/layoutStates"; + import Indicator from "./Indicator.svelte"; + import interfaceState from "$lib/stores/interfaceState"; + import type { WritableLayoutStateStore } from "$lib/stores/layoutStates"; export let subworkflowID: number = -1; export let app: ComfyApp = undefined; @@ -74,31 +75,21 @@ progressText = "??.?%" } + let centerHref = "/workflows/" + $: if ($interfaceState.selectedWorkflowID) { + centerHref = `/workflows/${$interfaceState.selectedWorkflowID}/` + } + else { + centerHref = "/workflows/"; + } + -
-
- {#if queued} -
- Node: {getNodeInfo($queueState.runningNodeID)} ({progressText}) -
- {/if} -
-
- {#if queued} - {#if progress} - - {:else if running} - - {/if} - {/if} -
-
- + {#if workflow != null && workflow.attrs.queuePromptButtonName != ""} {workflow.attrs.queuePromptButtonName} - + {/if} 🔄 Save @@ -106,56 +97,9 @@ Load -{#if $interfaceState.showIndicator} - -{/if} diff --git a/src/mobile/MainToolbar.svelte b/src/mobile/MainToolbar.svelte new file mode 100644 index 0000000..0d32306 --- /dev/null +++ b/src/mobile/MainToolbar.svelte @@ -0,0 +1,167 @@ + + +
+
+ {#if queued} +
+ Node: {getNodeInfo($queueState.runningNodeID)} ({progressText}) +
+ {/if} +
+
+ {#if queued} + {#if progress} + + {:else if running} + + {/if} + {/if} +
+
+ + Tab 1 + + + +{#if $interfaceState.showIndicator} + +{/if} + + diff --git a/src/mobile/routes/list-subworkflows.svelte b/src/mobile/routes/list-subworkflows.svelte deleted file mode 100644 index 1e2138a..0000000 --- a/src/mobile/routes/list-subworkflows.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - diff --git a/src/mobile/routes/workflow.svelte b/src/mobile/routes/workflow.svelte index 908839c..b1909d0 100644 --- a/src/mobile/routes/workflow.svelte +++ b/src/mobile/routes/workflow.svelte @@ -1,10 +1,13 @@ - - + + + + {title} + + + + {#if workflow} {#if root} diff --git a/src/mobile/routes/home.svelte b/src/mobile/routes/workflows.svelte similarity index 82% rename from src/mobile/routes/home.svelte rename to src/mobile/routes/workflows.svelte index ec7fe31..9899647 100644 --- a/src/mobile/routes/home.svelte +++ b/src/mobile/routes/workflows.svelte @@ -1,6 +1,8 @@ - + {#if $workflowState.openedWorkflows} {#each $workflowState.openedWorkflows as workflow} - +
onClickDelete(workflow, e)}> From d8ac97cb8766ac6bb33ddf5d315824c327168e2f Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Wed, 31 May 2023 16:58:34 -0500 Subject: [PATCH 4/8] More mobile overhauling --- src/AppMobile.svelte | 38 ++++++++++++++++++++++++------ src/lib/notify.ts | 3 ++- src/lib/stores/interfaceState.ts | 6 ++++- src/mobile/GenToolbar.svelte | 4 ++++ src/mobile/MainToolbar.svelte | 36 +++++++++++++++++++++++----- src/mobile/routes/workflow.svelte | 18 +++++++------- src/mobile/routes/workflows.svelte | 4 ++-- src/scss/global.scss | 6 ++--- 8 files changed, 85 insertions(+), 30 deletions(-) diff --git a/src/AppMobile.svelte b/src/AppMobile.svelte index 5de223d..67a3e21 100644 --- a/src/AppMobile.svelte +++ b/src/AppMobile.svelte @@ -56,7 +56,16 @@ let loading = true; let lastSize = Number.POSITIVE_INFINITY; + $: f7 && f7.setDarkMode($interfaceState.isDarkMode) + onMount(async () => { + let isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + $interfaceState.isDarkMode = isDarkMode; + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { + $interfaceState.isDarkMode = event.matches; + }); + appSetupPromise = app.setup().then(() => { loading = false }); @@ -75,12 +84,12 @@ }) }) - /* - Now we need to map components to routes. - We need to pass them along with the F7 app parameters to component - */ + /* + Now we need to map components to routes. + We need to pass them along with the F7 app parameters to component + */ - let f7params: Framework7Parameters = { + let f7params: Framework7Parameters = { routes: [ { path: '/', @@ -112,7 +121,7 @@ // } // }, { - path: '/workflows/:workflowID/', + path: '/workflows/:workflowIndex/', component: WorkflowPage, options: { props: { app } @@ -135,8 +144,23 @@ tapHold: true } } + + let body; + const bindBody = (node) => (body = node); + function setDarkClass(isDark: boolean) { + if (!body) + return; + if (isDark) { + body.classList.add("dark"); + } else { + body.classList.remove("dark"); + } + }; + $: setDarkClass($interfaceState.isDarkMode); + + {#if appSetupPromise} {#await appSetupPromise} @@ -159,7 +183,7 @@ {/if} - {:catch error} + {:catch error}
Error loading app diff --git a/src/lib/notify.ts b/src/lib/notify.ts index 3de3405..378c2ce 100644 --- a/src/lib/notify.ts +++ b/src/lib/notify.ts @@ -34,7 +34,8 @@ function notifyf7(text: string, options: NotifyOptions) { text: text, closeOnClick: true, closeTimeout, - on + on, + icon: options.imageUrl }); notification.open(); } diff --git a/src/lib/stores/interfaceState.ts b/src/lib/stores/interfaceState.ts index 61c43dc..de43ecf 100644 --- a/src/lib/stores/interfaceState.ts +++ b/src/lib/stores/interfaceState.ts @@ -16,6 +16,8 @@ export type InterfaceState = { selectedWorkflowID: WorkflowInstID | null showingWorkflow: boolean + + isDarkMode: boolean } type InterfaceStateOps = { @@ -34,7 +36,9 @@ const store: Writable = writable( isJumpingToNode: false, selectedWorkflowID: null, - showingWorkflow: false + showingWorkflow: false, + + isDarkMode: false, }) const debounceDrag = debounce(() => { store.update(s => { s.showIndicator = false; return s }) }, 1000) diff --git a/src/mobile/GenToolbar.svelte b/src/mobile/GenToolbar.svelte index 6112ac1..16634ba 100644 --- a/src/mobile/GenToolbar.svelte +++ b/src/mobile/GenToolbar.svelte @@ -102,4 +102,8 @@ #comfy-file-input { display: none; } + + :global(.dark .toolbar.color-red) { + background: var(--neutral-700) !important; + } diff --git a/src/mobile/MainToolbar.svelte b/src/mobile/MainToolbar.svelte index 0d32306..6b19a71 100644 --- a/src/mobile/MainToolbar.svelte +++ b/src/mobile/MainToolbar.svelte @@ -3,7 +3,7 @@ import queueState from "$lib/stores/queueState"; import workflowState, { ComfyBoxWorkflow } from "$lib/stores/workflowState"; import { getNodeInfo } from "$lib/utils" - import { Image, LayoutTextSidebarReverse } from "svelte-bootstrap-icons"; + import { LayoutTextSidebarReverse, Image, Grid } from "svelte-bootstrap-icons"; import { Link, Toolbar } from "framework7-svelte" import ProgressBar from "$lib/components/ProgressBar.svelte"; @@ -86,6 +86,7 @@ let toolbarCount = 0; $: toolbarCount = $interfaceState.showingWorkflow ? 2 : 1; + const ICON_SIZE = "1.5rem";
@@ -106,10 +107,16 @@ {/if}
- - Tab 1 - - + 1 ? "hasGenToolbar" : ""}> + + + + + + + + + {#if $interfaceState.showIndicator} @@ -120,6 +127,22 @@ display: none; } + :global(.progressbar.color-blue) { + background: var(--neutral-400) !important; + } + + :global(.dark .progressbar.color-blue) { + background: var(--neutral-500) !important; + } + + :global(.dark .toolbar.color-blue) { + background: var(--neutral-800) !important; + } + + :global(.dark .toolbar.color-blue.hasGenToolbar) { + border-top: 2px solid var(--neutral-600); + } + .bottom { --toolbarCount: 1; position: absolute; @@ -141,7 +164,8 @@ .node-name { flex-grow: 1; - background-color: var(--secondary-300); + background-color: var(--comfy-node-name-background); + color: var(--comfy-node-name-foreground); padding: 0.2em; display: flex; justify-content: center; diff --git a/src/mobile/routes/workflow.svelte b/src/mobile/routes/workflow.svelte index b1909d0..3696d58 100644 --- a/src/mobile/routes/workflow.svelte +++ b/src/mobile/routes/workflow.svelte @@ -9,7 +9,7 @@ import { onMount } from "svelte"; import GenToolbar from '../GenToolbar.svelte' - export let workflowID: WorkflowInstID; + export let workflowIndex: number; export let app: ComfyApp let workflow: ComfyBoxWorkflow; @@ -17,7 +17,13 @@ let title = "" function onPageBeforeIn() { - $interfaceState.selectedWorkflowID = workflowID; + workflow = $workflowState.openedWorkflows[workflowIndex-1] + if (workflow) { + $interfaceState.selectedWorkflowID = workflow.id; + } + else { + $interfaceState.selectedWorkflowID = null; + } $interfaceState.showingWorkflow = true; } @@ -25,14 +31,8 @@ $interfaceState.showingWorkflow = false; } - $: { - workflow = workflowState.getWorkflow(workflowID); - if (workflow) { - workflowState.setActiveWorkflow(app.lCanvas, workflow.id); - } - } $: layoutState = workflow?.layout; - $: title = workflow?.attrs?.title || `Workflow: ${workflowID}`; + $: title = workflow?.attrs?.title || `Workflow: ${workflow?.id || workflowIndex}`; $: if (layoutState && $layoutState.root) { root = $layoutState.root diff --git a/src/mobile/routes/workflows.svelte b/src/mobile/routes/workflows.svelte index 9899647..f51135e 100644 --- a/src/mobile/routes/workflows.svelte +++ b/src/mobile/routes/workflows.svelte @@ -32,8 +32,8 @@ {#if $workflowState.openedWorkflows} - {#each $workflowState.openedWorkflows as workflow} - + {#each $workflowState.openedWorkflows as workflow, i} +
onClickDelete(workflow, e)}> diff --git a/src/scss/global.scss b/src/scss/global.scss index 1f967ca..8e5d873 100644 --- a/src/scss/global.scss +++ b/src/scss/global.scss @@ -77,11 +77,9 @@ body { --comfy-node-name-foreground: var(--body-text-color); --comfy-spinner-main-color: var(--neutral-600); --comfy-spinner-accent-color: var(--secondary-600); -} -.mobile { - --comfy-progress-bar-background: lightgrey; - --comfy-progress-bar-foreground: #B3D8A9 + --f7-navbar-color: var(--body-text-color); + --f7-navbar-bg-color: var(--neutral-800); } @mixin square-button { From 263d62cb34605acf47a15b8398ea0ea301929ce2 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Wed, 31 May 2023 17:04:00 -0500 Subject: [PATCH 5/8] By index instead of workflow ID --- src/AppMobile.svelte | 15 ++++++------- src/lib/stores/interfaceState.ts | 4 ++-- src/mobile/GenToolbar.svelte | 9 -------- src/mobile/MainToolbar.svelte | 8 +++---- src/mobile/routes/gallery.svelte | 34 ++++++++++++++++++++++++++++++ src/mobile/routes/queue.svelte | 34 ++++++++++++++++++++++++++++++ src/mobile/routes/workflow.svelte | 7 +----- src/mobile/routes/workflows.svelte | 2 +- 8 files changed, 83 insertions(+), 30 deletions(-) create mode 100644 src/mobile/routes/gallery.svelte create mode 100644 src/mobile/routes/queue.svelte diff --git a/src/AppMobile.svelte b/src/AppMobile.svelte index 67a3e21..bfb44c4 100644 --- a/src/AppMobile.svelte +++ b/src/AppMobile.svelte @@ -13,9 +13,8 @@ import GenToolbar from './mobile/GenToolbar.svelte' import WorkflowsPage from './mobile/routes/workflows.svelte'; - import AboutPage from './mobile/routes/about.svelte'; - import LoginPage from './mobile/routes/login.svelte'; - import GraphPage from './mobile/routes/graph.svelte'; + import QueuePage from './mobile/routes/queue.svelte'; + import GalleryPage from './mobile/routes/gallery.svelte'; import WorkflowPage from './mobile/routes/workflow.svelte'; import type { Framework7Parameters, Modal } from "framework7/types"; import interfaceState from "$lib/stores/interfaceState"; @@ -106,12 +105,12 @@ } }, { - path: '/about/', - component: AboutPage, + path: '/queue/', + component: QueuePage, }, { - path: '/login/', - component: LoginPage, + path: '/gallery/', + component: GalleryPage, }, // { // path: '/graph/', @@ -179,7 +178,7 @@ browserHistoryRoot="/mobile/" > - {#if $interfaceState.selectedWorkflowID && $interfaceState.showingWorkflow} + {#if $interfaceState.selectedWorkflowIndex && $interfaceState.showingWorkflow} {/if} diff --git a/src/lib/stores/interfaceState.ts b/src/lib/stores/interfaceState.ts index de43ecf..0d70b14 100644 --- a/src/lib/stores/interfaceState.ts +++ b/src/lib/stores/interfaceState.ts @@ -14,7 +14,7 @@ export type InterfaceState = { graphTransitioning: boolean, isJumpingToNode: boolean, - selectedWorkflowID: WorkflowInstID | null + selectedWorkflowIndex: number | null showingWorkflow: boolean isDarkMode: boolean @@ -35,7 +35,7 @@ const store: Writable = writable( graphTransitioning: false, isJumpingToNode: false, - selectedWorkflowID: null, + selectedWorkflowIndex: null, showingWorkflow: false, isDarkMode: false, diff --git a/src/mobile/GenToolbar.svelte b/src/mobile/GenToolbar.svelte index 16634ba..61d063e 100644 --- a/src/mobile/GenToolbar.svelte +++ b/src/mobile/GenToolbar.svelte @@ -74,15 +74,6 @@ progressPercent = 0 progressText = "??.?%" } - - let centerHref = "/workflows/" - $: if ($interfaceState.selectedWorkflowID) { - centerHref = `/workflows/${$interfaceState.selectedWorkflowID}/` - } - else { - centerHref = "/workflows/"; - } - diff --git a/src/mobile/MainToolbar.svelte b/src/mobile/MainToolbar.svelte index 6b19a71..b923f6a 100644 --- a/src/mobile/MainToolbar.svelte +++ b/src/mobile/MainToolbar.svelte @@ -76,8 +76,8 @@ } let centerHref = "/workflows/" - $: if ($interfaceState.selectedWorkflowID) { - centerHref = `/workflows/${$interfaceState.selectedWorkflowID}/` + $: if ($interfaceState.selectedWorkflowIndex) { + centerHref = `/workflows/${$interfaceState.selectedWorkflowIndex}/` } else { centerHref = "/workflows/"; @@ -108,13 +108,13 @@
1 ? "hasGenToolbar" : ""}> - + - + diff --git a/src/mobile/routes/gallery.svelte b/src/mobile/routes/gallery.svelte new file mode 100644 index 0000000..27bd613 --- /dev/null +++ b/src/mobile/routes/gallery.svelte @@ -0,0 +1,34 @@ + + + + + + + diff --git a/src/mobile/routes/queue.svelte b/src/mobile/routes/queue.svelte new file mode 100644 index 0000000..7ca7871 --- /dev/null +++ b/src/mobile/routes/queue.svelte @@ -0,0 +1,34 @@ + + + + + + + diff --git a/src/mobile/routes/workflow.svelte b/src/mobile/routes/workflow.svelte index 3696d58..aba64e7 100644 --- a/src/mobile/routes/workflow.svelte +++ b/src/mobile/routes/workflow.svelte @@ -18,12 +18,7 @@ function onPageBeforeIn() { workflow = $workflowState.openedWorkflows[workflowIndex-1] - if (workflow) { - $interfaceState.selectedWorkflowID = workflow.id; - } - else { - $interfaceState.selectedWorkflowID = null; - } + $interfaceState.selectedWorkflowIndex = workflowIndex $interfaceState.showingWorkflow = true; } diff --git a/src/mobile/routes/workflows.svelte b/src/mobile/routes/workflows.svelte index f51135e..94d2b8c 100644 --- a/src/mobile/routes/workflows.svelte +++ b/src/mobile/routes/workflows.svelte @@ -23,7 +23,7 @@ () => { app.closeWorkflow(workflow.id); })} function onPageBeforeIn() { - $interfaceState.selectedWorkflowID = null; + $interfaceState.selectedWorkflowIndex = null; } From 3cd623fd20806acc8aa7844f0f8de1927318c16c Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Wed, 31 May 2023 17:34:14 -0500 Subject: [PATCH 6/8] Refactor UI queue state --- src/lib/components/ComfyQueue.svelte | 140 ++---------------- src/lib/stores/queueState.ts | 1 + src/lib/stores/uiQueueState.ts | 207 +++++++++++++++++++++++++++ src/mobile/routes/gallery.svelte | 30 +++- 4 files changed, 252 insertions(+), 126 deletions(-) create mode 100644 src/lib/stores/uiQueueState.ts diff --git a/src/lib/components/ComfyQueue.svelte b/src/lib/components/ComfyQueue.svelte index e71a73e..2b8c40d 100644 --- a/src/lib/components/ComfyQueue.svelte +++ b/src/lib/components/ComfyQueue.svelte @@ -30,6 +30,7 @@ import ComfyQueueListDisplay from "./ComfyQueueListDisplay.svelte"; import ComfyQueueGridDisplay from "./ComfyQueueGridDisplay.svelte"; import { WORKFLOWS_VIEW } from "./ComfyBoxWorkflowsView.svelte"; + import uiQueueState from "$lib/stores/uiQueueState"; export let app: ComfyApp; @@ -52,46 +53,34 @@ let displayMode: DisplayModeType = "list"; let imageSize: number = 40; let gridColumns: number = 3; - let changed = true; function switchMode(newMode: QueueItemType) { - changed = mode !== newMode + const changed = mode !== newMode mode = newMode if (changed) { - _queuedEntries = [] - _runningEntries = [] - _entries = [] + uiQueueState.updateEntries(); } } function switchDisplayMode(newDisplayMode: DisplayModeType) { - // changed = displayMode !== newDisplayMode displayMode = newDisplayMode - // if (changed) { - // _queuedEntries = [] - // _runningEntries = [] - // _entries = [] - // } } - let _queuedEntries: QueueUIEntry[] = [] - let _runningEntries: QueueUIEntry[] = [] - let _entries: QueueUIEntry[] = [] - - $: if (mode === "queue" && (changed || $queuePending.length != _queuedEntries.length || $queueRunning.length != _runningEntries.length)) { + let _entries: ReadonlyArray = [] + $: if(mode === "queue") { + _entries = $uiQueueState.queueUIEntries updateFromQueue(); - changed = false; } - else if (mode === "history" && (changed || $queueCompleted.length != _entries.length)) { + else { + _entries = $uiQueueState.historyUIEntries; updateFromHistory(); - changed = false; } $: if (mode === "queue" && !$queuePending && !$queueRunning) { - _queuedEntries = [] - _runningEntries = [] - _entries = []; - changed = true + uiQueueState.clearQueue(); + } + else if (mode === "history" && !$queueCompleted) { + uiQueueState.clearHistory(); } async function deleteEntry(entry: QueueUIEntry, event: MouseEvent) { @@ -106,125 +95,26 @@ await app.deleteQueueItem(mode, entry.entry.promptID); } - if (mode === "queue") { - _queuedEntries = [] - _runningEntries = [] - } - - _entries = []; - changed = true; + uiQueueState.updateEntries(true) } async function clearQueue() { await app.clearQueue(mode); - - if (mode === "queue") { - _queuedEntries = [] - _runningEntries = [] - } - - _entries = []; - changed = true; - } - - function formatDate(date: Date): string { - const time = date.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }); - const day = date.toLocaleString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }).replace(',', ''); - return [time, day].join(", ") - } - - function convertEntry(entry: QueueEntry, status: QueueUIEntryStatus): QueueUIEntry { - let date = entry.finishedAt || entry.queuedAt; - let dateStr = null; - if (date) { - dateStr = formatDate(date); - } - - const subgraphs: string[] | null = entry.extraData?.extra_pnginfo?.comfyBoxPrompt?.subgraphs; - - let message = "Prompt"; - if (entry.extraData?.workflowTitle != null) { - message = `${entry.extraData.workflowTitle}` - } - - if (subgraphs && subgraphs.length > 0) { - const subgraphsString = subgraphs.join(', ') - message += ` (${subgraphsString})` - } - - let submessage = `Nodes: ${Object.keys(entry.prompt).length}` - - if (Object.keys(entry.outputs).length > 0) { - const imageCount = Object.values(entry.outputs).filter(o => o.images).flatMap(o => o.images).length - submessage = `Images: ${imageCount}` - } - - return { - entry, - message, - submessage, - date: dateStr, - status, - images: [] - } - } - - function convertPendingEntry(entry: QueueEntry, status: QueueUIEntryStatus): QueueUIEntry { - const result = convertEntry(entry, status); - - const thumbnails = entry.extraData?.thumbnails - if (thumbnails) { - result.images = thumbnails.map(convertComfyOutputToComfyURL); - } - - const outputs = Object.values(entry.outputs) - .filter(o => o.images) - .flatMap(o => o.images) - .map(convertComfyOutputToComfyURL); - if (outputs) { - result.images = result.images.concat(outputs) - } - - return result; - } - - function convertCompletedEntry(entry: CompletedQueueEntry): QueueUIEntry { - const result = convertEntry(entry.entry, entry.status); - - const images = Object.values(entry.entry.outputs) - .filter(o => o.images) - .flatMap(o => o.images) - .map(convertComfyOutputToComfyURL); - result.images = images - - if (entry.message) - result.submessage = entry.message - else if (entry.status === "interrupted" || entry.status === "all_cached") - result.submessage = "Prompt was interrupted." - if (entry.error) - result.error = entry.error - - return result; + uiQueueState.updateEntries(true) } async function updateFromQueue() { - // newest entries appear at the top - _queuedEntries = $queuePending.map((e) => convertPendingEntry(e, "pending")).reverse(); - _runningEntries = $queueRunning.map((e) => convertPendingEntry(e, "running")).reverse(); - _entries = [..._queuedEntries, ..._runningEntries] if (queueList) { await tick(); // Wait for list size to be recalculated queueList.scroll({ top: queueList.scrollHeight }) } - console.warn("[ComfyQueue] BUILDQUEUE", _entries.length, $queuePending.length, $queueRunning.length) } async function updateFromHistory() { - _entries = $queueCompleted.map(convertCompletedEntry).reverse(); if (queueList) { + await tick(); // Wait for list size to be recalculated queueList.scrollTo(0, 0); } - console.warn("[ComfyQueue] BUILDHISTORY", _entries.length, $queueCompleted.length) } async function interrupt() { diff --git a/src/lib/stores/queueState.ts b/src/lib/stores/queueState.ts index 56cf239..eda8da3 100644 --- a/src/lib/stores/queueState.ts +++ b/src/lib/stores/queueState.ts @@ -8,6 +8,7 @@ import { get, writable, type Writable } from "svelte/store"; import { v4 as uuidv4 } from "uuid"; import workflowState, { type WorkflowError, type WorkflowExecutionError, type WorkflowInstID, type WorkflowValidationError } from "./workflowState"; import configState from "./configState"; +import uiQueueState from "./uiQueueState"; export type QueueEntryStatus = "success" | "validation_failed" | "error" | "interrupted" | "all_cached" | "unknown"; diff --git a/src/lib/stores/uiQueueState.ts b/src/lib/stores/uiQueueState.ts new file mode 100644 index 0000000..a361c22 --- /dev/null +++ b/src/lib/stores/uiQueueState.ts @@ -0,0 +1,207 @@ +import type { PromptID, QueueItemType } from '$lib/api'; +import { get, writable } from 'svelte/store'; +import type { Readable, Writable } from 'svelte/store'; +import queueState, { type CompletedQueueEntry, type QueueEntry } from './queueState'; +import type { WorkflowError } from './workflowState'; +import { convertComfyOutputToComfyURL } from '$lib/utils'; + +export type QueueUIEntryStatus = QueueEntryStatus | "pending" | "running"; + +export type QueueUIEntry = { + entry: QueueEntry, + message: string, + submessage: string, + date?: string, + status: QueueUIEntryStatus, + images?: string[], // URLs + details?: string, // shown in a tooltip on hover + error?: WorkflowError +} + +export type UIQueueState = { + mode: QueueItemType, + + queuedEntries: QueueUIEntry[], + runningEntries: QueueUIEntry[], + + queueUIEntries: QueueUIEntry[], + historyUIEntries: QueueUIEntry[], +} + +type UIQueueStateOps = { + updateEntries: (force?: boolean) => void + clearAll: () => void + clearQueue: () => void + clearHistory: () => void +} + +export type WritableUIQueueStateStore = Writable & UIQueueStateOps; +const store: Writable = writable( + { + mode: "queue", + queuedEntries: [], + runningEntries: [], + completedEntries: [], + + queueUIEntries: [], + historyUIEntries: [], + }) + +function formatDate(date: Date): string { + const time = date.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }); + const day = date.toLocaleString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }).replace(',', ''); + return [time, day].join(", ") +} + +function convertEntry(entry: QueueEntry, status: QueueUIEntryStatus): QueueUIEntry { + let date = entry.finishedAt || entry.queuedAt; + let dateStr = null; + if (date) { + dateStr = formatDate(date); + } + + const subgraphs: string[] | null = entry.extraData?.extra_pnginfo?.comfyBoxPrompt?.subgraphs; + + let message = "Prompt"; + if (entry.extraData?.workflowTitle != null) { + message = `${entry.extraData.workflowTitle}` + } + + if (subgraphs && subgraphs.length > 0) { + const subgraphsString = subgraphs.join(', ') + message += ` (${subgraphsString})` + } + + let submessage = `Nodes: ${Object.keys(entry.prompt).length}` + + if (Object.keys(entry.outputs).length > 0) { + const imageCount = Object.values(entry.outputs).filter(o => o.images).flatMap(o => o.images).length + submessage = `Images: ${imageCount}` + } + + return { + entry, + message, + submessage, + date: dateStr, + status, + images: [] + } +} + +function convertPendingEntry(entry: QueueEntry, status: QueueUIEntryStatus): QueueUIEntry { + const result = convertEntry(entry, status); + + const thumbnails = entry.extraData?.thumbnails + if (thumbnails) { + result.images = thumbnails.map(convertComfyOutputToComfyURL); + } + + const outputs = Object.values(entry.outputs) + .filter(o => o.images) + .flatMap(o => o.images) + .map(convertComfyOutputToComfyURL); + if (outputs) { + result.images = result.images.concat(outputs) + } + + return result; +} + +function convertCompletedEntry(entry: CompletedQueueEntry): QueueUIEntry { + const result = convertEntry(entry.entry, entry.status); + + const images = Object.values(entry.entry.outputs) + .filter(o => o.images) + .flatMap(o => o.images) + .map(convertComfyOutputToComfyURL); + result.images = images + + if (entry.message) + result.submessage = entry.message + else if (entry.status === "interrupted" || entry.status === "all_cached") + result.submessage = "Prompt was interrupted." + if (entry.error) + result.error = entry.error + + return result; +} + +function updateFromQueue(queuePending: QueueEntry[], queueRunning: QueueEntry[]) { + store.update(s => { + // newest entries appear at the top + s.queuedEntries = queuePending.map((e) => convertPendingEntry(e, "pending")).reverse(); + s.runningEntries = queueRunning.map((e) => convertPendingEntry(e, "running")).reverse(); + s.queueUIEntries = s.queuedEntries.concat(s.runningEntries); + console.warn("[ComfyQueue] BUILDQUEUE", s.queuedEntries.length, s.runningEntries.length) + return s; + }) +} + +function updateFromHistory(queueCompleted: CompletedQueueEntry[]) { + store.update(s => { + s.historyUIEntries = queueCompleted.map(convertCompletedEntry).reverse(); + console.warn("[ComfyQueue] BUILDHISTORY", s.historyUIEntries.length) + return s + }) +} + +function updateEntries(force: boolean = false) { + const state = get(store) + const qs = get(queueState) + const queuePending = qs.queuePending + const queueRunning = qs.queueRunning + const queueCompleted = qs.queueCompleted + + const queueChanged = get(queuePending).length != state.queuedEntries.length + || get(queueRunning).length != state.runningEntries.length; + const historyChanged = get(queueCompleted).length != state.historyUIEntries.length; + + if (queueChanged || force) { + updateFromQueue(get(queuePending), get(queueRunning)); + } + if (historyChanged || force) { + updateFromHistory(get(queueCompleted)); + } +} + +function clearAll() { + store.update(s => { + s.queuedEntries = [] + s.runningEntries = [] + s.historyUIEntries = [] + return s + }) + updateEntries(true); +} + +function clearQueue() { + store.update(s => { + s.queuedEntries = [] + s.runningEntries = [] + return s + }) + updateEntries(true); +} + +function clearHistory() { + store.update(s => { + s.historyUIEntries = [] + return s + }) + updateEntries(true); +} + +queueState.subscribe(s => { + updateEntries(); +}) + +const uiStateStore: WritableUIQueueStateStore = +{ + ...store, + updateEntries, + clearAll, + clearQueue, + clearHistory, +} +export default uiStateStore; diff --git a/src/mobile/routes/gallery.svelte b/src/mobile/routes/gallery.svelte index 27bd613..2f1b79a 100644 --- a/src/mobile/routes/gallery.svelte +++ b/src/mobile/routes/gallery.svelte @@ -1,5 +1,5 @@ @@ -83,7 +56,6 @@ {/if} 🔄 - Save Save Local Load diff --git a/src/mobile/MainToolbar.svelte b/src/mobile/MainToolbar.svelte index b923f6a..8cc5bb2 100644 --- a/src/mobile/MainToolbar.svelte +++ b/src/mobile/MainToolbar.svelte @@ -76,7 +76,7 @@ } let centerHref = "/workflows/" - $: if ($interfaceState.selectedWorkflowIndex) { + $: if ($interfaceState.selectedWorkflowIndex && !$interfaceState.showingWorkflow) { centerHref = `/workflows/${$interfaceState.selectedWorkflowIndex}/` } else { diff --git a/src/mobile/routes/gallery.svelte b/src/mobile/routes/gallery.svelte index 2f1b79a..fcbf96a 100644 --- a/src/mobile/routes/gallery.svelte +++ b/src/mobile/routes/gallery.svelte @@ -8,37 +8,69 @@ import interfaceState from "$lib/stores/interfaceState"; import { onMount } from "svelte"; import GenToolbar from '../GenToolbar.svelte' + import { partition, showLightbox } from "$lib/utils"; + import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState"; + import { showMobileLightbox } from "$lib/components/utils"; + + export let app: ComfyApp + + let _entries: ReadonlyArray = [] + $: _entries = $uiQueueState.historyUIEntries; + + let allEntries: [QueueUIEntry, string][][] = [] + let allImages: string[] = [] + + let gridCols = 3; + + $: buildImageList(_entries); + + function buildImageList(entries: ReadonlyArray) { + const _allEntries = [] + for (const entry of entries) { + for (const image of entry.images) { + _allEntries.push([entry, image]); + } + } + allEntries = partition(_allEntries, gridCols); + allImages = _allEntries.map(p => p[1]); + } + + function handleClick(e: MouseEvent, entry: QueueUIEntry, index: number) { + showMobileLightbox(allImages, index) + } + + + async function clearHistory() { + await app.clearQueue("history"); + uiQueueState.updateEntries(true) + } - + + + Gallery + + 🗑️ + + -
-
3 cols
-
3 cols
-
3 cols
-
-
-
3 cols
-
3 cols
-
3 cols
-
-
-
3 cols
-
3 cols
-
3 cols
-
-
-
3 cols
-
3 cols
-
3 cols
-
-
-
3 cols
-
3 cols
-
3 cols
-
+ {#each allEntries as group, i} +
+ {#each group as [entry, image], j} + {@const index = i * gridCols + j} +
+ + handleClick(e, entry, index)} + src={image} + loading="lazy" + alt="thumbnail" /> +
+ {/each} +
+ {/each}
@@ -59,4 +91,23 @@ :global(.root-container.mobile > .block > .v-pane) { flex-direction: column !important; } + + .grid-entry { + display: flex; + justify-content: center; + align-items: center; + aspect-ratio: 1 / 1; + object-fit: cover; + } + + .grid-entry-image { + aspect-ratio: 1 / 1; + object-fit: cover; + margin-bottom: var(--f7-grid-gap); + + &:hover { + cursor: pointer; + filter: brightness(120%) contrast(120%); + } + } diff --git a/src/mobile/routes/workflow.svelte b/src/mobile/routes/workflow.svelte index aba64e7..91984d1 100644 --- a/src/mobile/routes/workflow.svelte +++ b/src/mobile/routes/workflow.svelte @@ -18,6 +18,9 @@ function onPageBeforeIn() { workflow = $workflowState.openedWorkflows[workflowIndex-1] + if (workflow) { + workflowState.setActiveWorkflow(app.lCanvas, workflow.id) + } $interfaceState.selectedWorkflowIndex = workflowIndex $interfaceState.showingWorkflow = true; } diff --git a/src/mobile/routes/workflows.svelte b/src/mobile/routes/workflows.svelte index 94d2b8c..ef332ea 100644 --- a/src/mobile/routes/workflows.svelte +++ b/src/mobile/routes/workflows.svelte @@ -13,6 +13,7 @@ async function doLoadDefault() { f7.dialog.confirm("Would you like to load the default workflow in a new tab?", async () => { await app.initDefaultWorkflow(); + app.saveStateToLocalStorage(false); }) } @@ -20,7 +21,10 @@ e.preventDefault(); e.stopImmediatePropagation(); f7.dialog.confirm("Are you sure you want to delete this workflow?", workflow.attrs.title || `Workflow: ${workflow.id}`, - () => { app.closeWorkflow(workflow.id); })} + () => { + app.closeWorkflow(workflow.id); + app.saveStateToLocalStorage(false); + })} function onPageBeforeIn() { $interfaceState.selectedWorkflowIndex = null; diff --git a/src/scss/global.scss b/src/scss/global.scss index 8e5d873..8e738b4 100644 --- a/src/scss/global.scss +++ b/src/scss/global.scss @@ -41,7 +41,7 @@ body { --comfy-dropdown-item-background-active: var(--secondary-600); --comfy-progress-bar-background: var(--neutral-300); --comfy-progress-bar-foreground: var(--secondary-300); - --comfy-node-name-background: var(--color-red-300); + --comfy-node-name-background: var(--color-blue-200); --comfy-node-name-foreground: var(--body-text-color); --comfy-spinner-main-color: var(--neutral-400); --comfy-spinner-accent-color: var(--secondary-500); @@ -250,3 +250,15 @@ button { :global([data-is-dnd-shadow-item]) { min-height: 5rem; } + +:global(.dark .photo-browser-popup) { + background: var(--neutral-700); +} + +:global(.dark .photo-browser-popup-) { + background: var(--neutral-700); +} + +:global(.photo-browser-exposed .toolbar ~ .toolbar.photo-browser-thumbs) { + transform: translate3d(0, calc(var(--f7-toolbar-height) * 2), 0); +} From f0c01a66ce12ae3b61144c0fda3e4d32612d3f90 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Wed, 31 May 2023 22:25:53 -0500 Subject: [PATCH 8/8] Mobile queue --- src/lib/components/ComfyApp.ts | 3 + .../nodes/actions/ComfySendOutputAction.ts | 31 +---- src/lib/notify.ts | 1 - src/lib/stores/interfaceState.ts | 58 +++++++- src/lib/stores/queueState.ts | 1 - src/lib/utils.ts | 11 +- src/main-desktop.ts | 9 +- src/mobile/GenToolbar.svelte | 49 ++----- src/mobile/MainToolbar.svelte | 13 +- src/mobile/routes/gallery.svelte | 17 ++- src/mobile/routes/queue.svelte | 127 +++++++++++++++++- src/mobile/routes/workflow.svelte | 34 ++++- src/mobile/routes/workflows.svelte | 21 ++- 13 files changed, 277 insertions(+), 98 deletions(-) diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 72d96b7..2c77775 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -403,6 +403,9 @@ export default class ComfyApp { return false; const workflows = state.workflows as SerializedAppState[]; + if (workflows.length === 0) + return false; + await Promise.all(workflows.map(w => { return this.openWorkflow(w, { refreshCombos: defs, warnMissingNodeTypes: false, setActive: false }).catch(error => { console.error("Failed restoring previous workflow", error) diff --git a/src/lib/nodes/actions/ComfySendOutputAction.ts b/src/lib/nodes/actions/ComfySendOutputAction.ts index 1f6fece..7b744b2 100644 --- a/src/lib/nodes/actions/ComfySendOutputAction.ts +++ b/src/lib/nodes/actions/ComfySendOutputAction.ts @@ -8,6 +8,7 @@ import notify from "$lib/notify"; import workflowState from "$lib/stores/workflowState"; import { get } from "svelte/store"; import type ComfyApp from "$lib/components/ComfyApp"; +import interfaceState from "$lib/stores/interfaceState"; export interface ComfySendOutputActionProperties extends ComfyGraphNodeProperties { } @@ -41,36 +42,8 @@ export default class ComfySendOutputAction extends ComfyGraphNode { this.isActive = true; - const doSend = (modal: ModalData) => { + interfaceState.querySendOutput(value, type, receiveTargets, () => { this.isActive = false; - - const { workflow, targetNode } = get(modal.state) as SendOutputModalResult; - console.warn("send", workflow, targetNode); - - if (workflow == null || targetNode == null) - return - - const app = (window as any).app as ComfyApp; - if (app == null) { - console.error("Couldn't get app!") - return - } - - targetNode.receiveOutput(value); - workflowState.setActiveWorkflow(app.lCanvas, workflow.id) - } - - modalState.pushModal({ - title: "Send Output", - closeOnClick: true, - showCloseButton: true, - svelteComponent: SendOutputModal, - svelteProps: { - value, - type, - receiveTargets - }, - onClose: doSend }) }; } diff --git a/src/lib/notify.ts b/src/lib/notify.ts index 1246045..80a0f2c 100644 --- a/src/lib/notify.ts +++ b/src/lib/notify.ts @@ -18,7 +18,6 @@ function notifyf7(text: string, options: NotifyOptions) { if (!f7) return; - console.error(options) let closeTimeout = options.timeout if (closeTimeout === undefined) closeTimeout = 3000; diff --git a/src/lib/stores/interfaceState.ts b/src/lib/stores/interfaceState.ts index 0d70b14..ec1c04a 100644 --- a/src/lib/stores/interfaceState.ts +++ b/src/lib/stores/interfaceState.ts @@ -1,7 +1,12 @@ -import { debounce } from '$lib/utils'; +import { debounce, isMobileBrowser } from '$lib/utils'; import { get, writable } from 'svelte/store'; import type { Readable, Writable } from 'svelte/store'; -import type { WorkflowInstID } from './workflowState'; +import type { WorkflowInstID, WorkflowReceiveOutputTargets } from './workflowState'; +import modalState, { type ModalData } from './modalState'; +import type { SlotType } from '@litegraph-ts/core'; +import type ComfyApp from '$lib/components/ComfyApp'; +import SendOutputModal, { type SendOutputModalResult } from "$lib/components/modal/SendOutputModal.svelte"; +import workflowState from './workflowState'; export type InterfaceState = { // Show a large indicator of the currently editing number value for mobile @@ -15,13 +20,16 @@ export type InterfaceState = { isJumpingToNode: boolean, selectedWorkflowIndex: number | null - showingWorkflow: boolean + showingWorkflow: boolean, + selectedTab: number, + showSheet: boolean, isDarkMode: boolean } type InterfaceStateOps = { showIndicator: (pointerX: number, pointerY: number, value: any) => void, + querySendOutput: (value: any, type: SlotType, receiveTargets: WorkflowReceiveOutputTargets[], cb: (modal: ModalData) => void) => void, } export type WritableInterfaceStateStore = Writable & InterfaceStateOps; @@ -34,6 +42,8 @@ const store: Writable = writable( graphTransitioning: false, isJumpingToNode: false, + selectedTab: 1, + showSheet: false, selectedWorkflowIndex: null, showingWorkflow: false, @@ -57,9 +67,49 @@ function showIndicator(pointerX: number, pointerY: number, value: any) { debounceDrag(); } +function querySendOutput(value: any, type: SlotType, receiveTargets: WorkflowReceiveOutputTargets[], cb: (modal: ModalData) => void) { + if (isMobileBrowser(navigator.userAgent)) { + store.update(s => { s.showSheet = true; return s; }) + } + else { + const doSend = (modal: ModalData) => { + cb(modal) + + const { workflow, targetNode } = get(modal.state) as SendOutputModalResult; + console.warn("send", workflow, targetNode); + + if (workflow == null || targetNode == null) + return + + const app = (window as any).app as ComfyApp; + if (app == null) { + console.error("Couldn't get app!") + return + } + + targetNode.receiveOutput(value); + workflowState.setActiveWorkflow(app.lCanvas, workflow.id) + } + + modalState.pushModal({ + title: "Send Output", + closeOnClick: true, + showCloseButton: true, + svelteComponent: SendOutputModal, + svelteProps: { + value, + type, + receiveTargets + }, + onClose: doSend + }) + } +} + const interfaceStateStore: WritableInterfaceStateStore = { ...store, - showIndicator + showIndicator, + querySendOutput } export default interfaceStateStore; diff --git a/src/lib/stores/queueState.ts b/src/lib/stores/queueState.ts index eda8da3..f50048a 100644 --- a/src/lib/stores/queueState.ts +++ b/src/lib/stores/queueState.ts @@ -445,7 +445,6 @@ function queueCleared(type: QueueItemType) { store.update(s => { if (type === "queue") { s.queuePending.set([]); - s.queueRunning.set([]); s.queueRemaining = 0; s.runningNodeID = null; s.progress = null; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 76906d7..68bdff3 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,10 +4,11 @@ import type { FileData as GradioFileData } from "@gradio/upload"; import { Subgraph, type LGraph, type LGraphNode, type LLink, type SerializedLGraph, type UUID, type NodeID, type SlotType, type Vector4, type SerializedLGraphNode } from "@litegraph-ts/core"; import { get } from "svelte/store"; import type { ComfyNodeID } from "./api"; -import { type SerializedPrompt } from "./components/ComfyApp"; -import workflowState from "./stores/workflowState"; +import ComfyApp, { type SerializedPrompt } from "./components/ComfyApp"; +import workflowState, { type WorkflowReceiveOutputTargets } from "./stores/workflowState"; import { ImageViewer } from "./ImageViewer"; import configState from "$lib/stores/configState"; +import SendOutputModal, { type SendOutputModalResult } from "$lib/components/modal/SendOutputModal.svelte"; export function clamp(n: number, min: number, max: number): number { if (max <= min) @@ -734,3 +735,9 @@ export function partition(myArray: T[], chunkSize: number): T[] { return tempArray; } + +const MOBILE_USER_AGENTS = ["iPhone", "iPad", "Android", "BlackBerry", "WebOs"].map(a => new RegExp(a, "i")) + +export function isMobileBrowser(userAgent: string): boolean { + return MOBILE_USER_AGENTS.some(a => userAgent.match(a)) +} diff --git a/src/main-desktop.ts b/src/main-desktop.ts index 77e0916..998ed84 100644 --- a/src/main-desktop.ts +++ b/src/main-desktop.ts @@ -1,13 +1,8 @@ -const params = new URLSearchParams(window.location.search) - -const MOBILE_USER_AGENTS = ["iPhone", "iPad", "Android", "BlackBerry", "WebOs"].map(a => new RegExp(a, "i")) - -function isMobileBrowser(userAgent: string): boolean { - return MOBILE_USER_AGENTS.some(a => userAgent.match(a)) -} +import { isMobileBrowser } from "$lib/utils" const isMobile = isMobileBrowser(navigator.userAgent); +const params = new URLSearchParams(window.location.search) if (params.get("desktop") !== "true") { if (isMobile) { window.location.href = "/mobile/" diff --git a/src/mobile/GenToolbar.svelte b/src/mobile/GenToolbar.svelte index 91edc85..2abf932 100644 --- a/src/mobile/GenToolbar.svelte +++ b/src/mobile/GenToolbar.svelte @@ -1,21 +1,11 @@ {#if workflow != null && workflow.attrs.queuePromptButtonName != ""} - - {workflow.attrs.queuePromptButtonName} - +
+ + {workflow.attrs.queuePromptButtonName} + +
{/if} - 🔄 - Save Local - Load -
diff --git a/src/mobile/routes/workflow.svelte b/src/mobile/routes/workflow.svelte index 91984d1..4cc6dcb 100644 --- a/src/mobile/routes/workflow.svelte +++ b/src/mobile/routes/workflow.svelte @@ -1,13 +1,15 @@ @@ -50,6 +65,10 @@ (No workflows opened.) {/if} - +

+ + +

+