Mobile queue

This commit is contained in:
space-nuko
2023-05-31 22:25:53 -05:00
parent 4547cc1a27
commit f0c01a66ce
13 changed files with 277 additions and 98 deletions

View File

@@ -403,6 +403,9 @@ export default class ComfyApp {
return false; return false;
const workflows = state.workflows as SerializedAppState[]; const workflows = state.workflows as SerializedAppState[];
if (workflows.length === 0)
return false;
await Promise.all(workflows.map(w => { await Promise.all(workflows.map(w => {
return this.openWorkflow(w, { refreshCombos: defs, warnMissingNodeTypes: false, setActive: false }).catch(error => { return this.openWorkflow(w, { refreshCombos: defs, warnMissingNodeTypes: false, setActive: false }).catch(error => {
console.error("Failed restoring previous workflow", error) console.error("Failed restoring previous workflow", error)

View File

@@ -8,6 +8,7 @@ import notify from "$lib/notify";
import workflowState from "$lib/stores/workflowState"; import workflowState from "$lib/stores/workflowState";
import { get } from "svelte/store"; import { get } from "svelte/store";
import type ComfyApp from "$lib/components/ComfyApp"; import type ComfyApp from "$lib/components/ComfyApp";
import interfaceState from "$lib/stores/interfaceState";
export interface ComfySendOutputActionProperties extends ComfyGraphNodeProperties { export interface ComfySendOutputActionProperties extends ComfyGraphNodeProperties {
} }
@@ -41,36 +42,8 @@ export default class ComfySendOutputAction extends ComfyGraphNode {
this.isActive = true; this.isActive = true;
const doSend = (modal: ModalData) => { interfaceState.querySendOutput(value, type, receiveTargets, () => {
this.isActive = false; 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
}) })
}; };
} }

View File

@@ -18,7 +18,6 @@ function notifyf7(text: string, options: NotifyOptions) {
if (!f7) if (!f7)
return; return;
console.error(options)
let closeTimeout = options.timeout let closeTimeout = options.timeout
if (closeTimeout === undefined) if (closeTimeout === undefined)
closeTimeout = 3000; closeTimeout = 3000;

View File

@@ -1,7 +1,12 @@
import { debounce } from '$lib/utils'; import { debounce, isMobileBrowser } from '$lib/utils';
import { get, writable } from 'svelte/store'; import { get, writable } from 'svelte/store';
import type { Readable, 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 = { export type InterfaceState = {
// Show a large indicator of the currently editing number value for mobile // Show a large indicator of the currently editing number value for mobile
@@ -15,13 +20,16 @@ export type InterfaceState = {
isJumpingToNode: boolean, isJumpingToNode: boolean,
selectedWorkflowIndex: number | null selectedWorkflowIndex: number | null
showingWorkflow: boolean showingWorkflow: boolean,
selectedTab: number,
showSheet: boolean,
isDarkMode: boolean isDarkMode: boolean
} }
type InterfaceStateOps = { type InterfaceStateOps = {
showIndicator: (pointerX: number, pointerY: number, value: any) => void, showIndicator: (pointerX: number, pointerY: number, value: any) => void,
querySendOutput: (value: any, type: SlotType, receiveTargets: WorkflowReceiveOutputTargets[], cb: (modal: ModalData) => void) => void,
} }
export type WritableInterfaceStateStore = Writable<InterfaceState> & InterfaceStateOps; export type WritableInterfaceStateStore = Writable<InterfaceState> & InterfaceStateOps;
@@ -34,6 +42,8 @@ const store: Writable<InterfaceState> = writable(
graphTransitioning: false, graphTransitioning: false,
isJumpingToNode: false, isJumpingToNode: false,
selectedTab: 1,
showSheet: false,
selectedWorkflowIndex: null, selectedWorkflowIndex: null,
showingWorkflow: false, showingWorkflow: false,
@@ -57,9 +67,49 @@ function showIndicator(pointerX: number, pointerY: number, value: any) {
debounceDrag(); 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 = const interfaceStateStore: WritableInterfaceStateStore =
{ {
...store, ...store,
showIndicator showIndicator,
querySendOutput
} }
export default interfaceStateStore; export default interfaceStateStore;

View File

@@ -445,7 +445,6 @@ function queueCleared(type: QueueItemType) {
store.update(s => { store.update(s => {
if (type === "queue") { if (type === "queue") {
s.queuePending.set([]); s.queuePending.set([]);
s.queueRunning.set([]);
s.queueRemaining = 0; s.queueRemaining = 0;
s.runningNodeID = null; s.runningNodeID = null;
s.progress = null; s.progress = null;

View File

@@ -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 { 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 { get } from "svelte/store";
import type { ComfyNodeID } from "./api"; import type { ComfyNodeID } from "./api";
import { type SerializedPrompt } from "./components/ComfyApp"; import ComfyApp, { type SerializedPrompt } from "./components/ComfyApp";
import workflowState from "./stores/workflowState"; import workflowState, { type WorkflowReceiveOutputTargets } from "./stores/workflowState";
import { ImageViewer } from "./ImageViewer"; import { ImageViewer } from "./ImageViewer";
import configState from "$lib/stores/configState"; 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 { export function clamp(n: number, min: number, max: number): number {
if (max <= min) if (max <= min)
@@ -734,3 +735,9 @@ export function partition<T>(myArray: T[], chunkSize: number): T[] {
return tempArray; 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))
}

View File

@@ -1,13 +1,8 @@
const params = new URLSearchParams(window.location.search) import { isMobileBrowser } from "$lib/utils"
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); const isMobile = isMobileBrowser(navigator.userAgent);
const params = new URLSearchParams(window.location.search)
if (params.get("desktop") !== "true") { if (params.get("desktop") !== "true") {
if (isMobile) { if (isMobile) {
window.location.href = "/mobile/" window.location.href = "/mobile/"

View File

@@ -1,21 +1,11 @@
<script lang="ts"> <script lang="ts">
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp"; import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
import queueState from "$lib/stores/queueState";
import workflowState, { ComfyBoxWorkflow } from "$lib/stores/workflowState"; 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 { 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";
export let subworkflowID: number = -1; export let subworkflowID: number = -1;
export let app: ComfyApp = undefined; export let app: ComfyApp = undefined;
let layoutState: WritableLayoutStateStore = null;
let fileInput: HTMLInputElement = undefined;
let workflow: ComfyBoxWorkflow | null = null; let workflow: ComfyBoxWorkflow | null = null;
$: workflow = $workflowState.activeWorkflow; $: workflow = $workflowState.activeWorkflow;
@@ -24,41 +14,16 @@
navigator.vibrate(20) navigator.vibrate(20)
app.runDefaultQueueAction() app.runDefaultQueueAction()
} }
async function refreshCombos() {
navigator.vibrate(20)
await app.refreshComboInNodes()
}
function doLoad(): void {
if (!fileInput)
return;
navigator.vibrate(20)
fileInput.value = null;
fileInput.click();
}
function loadWorkflow(): void {
app.handleFile(fileInput.files[0]);
}
function doSaveLocal(): void {
navigator.vibrate(20)
app.saveStateToLocalStorage();
}
</script> </script>
<Toolbar bottom color="red" style="bottom: calc(var(--f7-toolbar-height))"> <Toolbar bottom color="red" style="bottom: calc(var(--f7-toolbar-height))">
{#if workflow != null && workflow.attrs.queuePromptButtonName != ""} {#if workflow != null && workflow.attrs.queuePromptButtonName != ""}
<Link on:click={queuePrompt}> <div style:width="100%">
{workflow.attrs.queuePromptButtonName} <Link on:click={queuePrompt}>
</Link> {workflow.attrs.queuePromptButtonName}
</Link>
</div>
{/if} {/if}
<Link on:click={refreshCombos}>🔄</Link>
<Link on:click={doSaveLocal}>Save Local</Link>
<Link on:click={doLoad}>Load</Link>
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
</Toolbar> </Toolbar>
<style lang="scss"> <style lang="scss">
@@ -66,6 +31,10 @@
display: none; display: none;
} }
:global(.toolbar) {
--f7-toolbar-font-size: 13pt;
}
:global(.dark .toolbar.color-red) { :global(.dark .toolbar.color-red) {
background: var(--neutral-700) !important; background: var(--neutral-700) !important;
} }

View File

@@ -87,6 +87,8 @@
$: toolbarCount = $interfaceState.showingWorkflow ? 2 : 1; $: toolbarCount = $interfaceState.showingWorkflow ? 2 : 1;
const ICON_SIZE = "1.5rem"; const ICON_SIZE = "1.5rem";
let selectedTab = 1;
</script> </script>
<div class="bottom" style:--toolbarCount={toolbarCount}> <div class="bottom" style:--toolbarCount={toolbarCount}>
@@ -108,13 +110,13 @@
</div> </div>
</div> </div>
<Toolbar bottom tabbar color="blue" class={toolbarCount > 1 ? "hasGenToolbar" : ""}> <Toolbar bottom tabbar color="blue" class={toolbarCount > 1 ? "hasGenToolbar" : ""}>
<Link transition="f7-dive" href="/queue/"> <Link transition="f7-dive" href="/queue/" tabLinkActive={$interfaceState.selectedTab === 0}>
<LayoutTextSidebarReverse width={ICON_SIZE} height={ICON_SIZE} /> <LayoutTextSidebarReverse width={ICON_SIZE} height={ICON_SIZE} />
</Link> </Link>
<Link transition="f7-dive" href={centerHref} tabLinkActive> <Link transition="f7-dive" href={centerHref} tabLinkActive={$interfaceState.selectedTab === 1}>
<Image width={ICON_SIZE} height={ICON_SIZE} /> <Image width={ICON_SIZE} height={ICON_SIZE} />
</Link> </Link>
<Link transition="f7-dive" href="/gallery/"> <Link transition="f7-dive" href="/gallery/" tabLinkActive={$interfaceState.selectedTab === 2}>
<Grid width={ICON_SIZE} height={ICON_SIZE} /> <Grid width={ICON_SIZE} height={ICON_SIZE} />
</Link> </Link>
</Toolbar> </Toolbar>
@@ -143,6 +145,11 @@
border-top: 2px solid var(--neutral-600); border-top: 2px solid var(--neutral-600);
} }
:global(.dark .tab-link-active) {
--f7-tabbar-link-active-color: var(--secondary-500);
--f7-tabbar-link-active-bg-color: #283547;
}
.bottom { .bottom {
--toolbarCount: 1; --toolbarCount: 1;
position: absolute; position: absolute;

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Page, Navbar, Block, Tabs, Tab, NavLeft, NavTitle, NavRight, Link } from "framework7-svelte" import { Page, Navbar, Block, Tabs, Tab, NavLeft, NavTitle, NavRight, Link, f7 } from "framework7-svelte"
import WidgetContainer from "$lib/components/WidgetContainer.svelte"; import WidgetContainer from "$lib/components/WidgetContainer.svelte";
import type ComfyApp from "$lib/components/ComfyApp"; import type ComfyApp from "$lib/components/ComfyApp";
import { writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
@@ -11,6 +11,7 @@
import { partition, showLightbox } from "$lib/utils"; import { partition, showLightbox } from "$lib/utils";
import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState"; import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState";
import { showMobileLightbox } from "$lib/components/utils"; import { showMobileLightbox } from "$lib/components/utils";
import notify from "$lib/notify";
export let app: ComfyApp export let app: ComfyApp
@@ -22,6 +23,10 @@
let gridCols = 3; let gridCols = 3;
function onPageBeforeIn() {
$interfaceState.selectedTab = 2;
}
$: buildImageList(_entries); $: buildImageList(_entries);
function buildImageList(entries: ReadonlyArray<QueueUIEntry>) { function buildImageList(entries: ReadonlyArray<QueueUIEntry>) {
@@ -39,14 +44,16 @@
showMobileLightbox(allImages, index) showMobileLightbox(allImages, index)
} }
async function clearHistory() { async function clearHistory() {
await app.clearQueue("history"); f7.dialog.confirm("Are you sure you want to clear the current history?", async () => {
uiQueueState.updateEntries(true) await app.clearQueue("history");
uiQueueState.updateEntries(true)
notify("History cleared!")
})
} }
</script> </script>
<Page name="gallery"> <Page name="gallery" on:pageBeforeIn={onPageBeforeIn}>
<Navbar> <Navbar>
<NavLeft></NavLeft> <NavLeft></NavLeft>
<NavTitle>Gallery</NavTitle> <NavTitle>Gallery</NavTitle>

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Page, Navbar, Tabs, Tab, NavLeft, NavTitle, NavRight, Link } from "framework7-svelte" import { Page, Navbar, Block, Tabs, Tab, NavLeft, NavTitle, NavRight, Link, List, ListItem, Card, CardHeader, CardContent, CardFooter, f7 } from "framework7-svelte"
import WidgetContainer from "$lib/components/WidgetContainer.svelte"; import WidgetContainer from "$lib/components/WidgetContainer.svelte";
import type ComfyApp from "$lib/components/ComfyApp"; import type ComfyApp from "$lib/components/ComfyApp";
import { writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
@@ -8,10 +8,108 @@
import interfaceState from "$lib/stores/interfaceState"; import interfaceState from "$lib/stores/interfaceState";
import { onMount } from "svelte"; import { onMount } from "svelte";
import GenToolbar from '../GenToolbar.svelte' import GenToolbar from '../GenToolbar.svelte'
import { partition, showLightbox, truncateString } from "$lib/utils";
import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState";
import { showMobileLightbox } from "$lib/components/utils";
import queueState from "$lib/stores/queueState";
import notify from "$lib/notify";
export let app: ComfyApp
let _entries: ReadonlyArray<QueueUIEntry> = []
$: _entries = $uiQueueState.queueUIEntries;
function onPageBeforeIn() {
$interfaceState.selectedTab = 0;
}
async function interrupt() {
await app.interrupt();
}
async function clearQueue() {
f7.dialog.confirm("Are you sure you want to clear the current queue?", async () => {
await app.clearQueue("queue");
uiQueueState.updateEntries(true)
notify("Queue cleared!")
})
}
function findPrompt(entry: QueueUIEntry): string {
let s = ""
for (const inputs of Object.values(entry.entry.prompt)) {
if (inputs.class_type === "CLIPTextEncode") {
for (const [key, value] of Object.entries(inputs.inputs)) {
if (key === "text") {
s += value + "\n"
}
}
}
}
return truncateString(s, 240);
}
async function doCancel(entry: QueueUIEntry) {
if ($queueState.isInterrupting) {
return;
}
// TODO support interrupting from multiple running items!
if (entry.status === "running") {
await app.interrupt();
}
else {
await app.deleteQueueItem("queue", entry.entry.promptID);
}
notify("Queue item canceled.")
uiQueueState.updateEntries(true)
}
function getCardImage(entry: QueueUIEntry): string {
if (entry.images.length > 0)
return entry.images[0]
return "https://cdn.framework7.io/placeholder/nature-1000x600-3.jpg"
}
</script> </script>
<Page name="queue"> <Page name="queue" on:pageBeforeIn={onPageBeforeIn}>
<Navbar title="Queue" /> <Navbar>
<NavLeft></NavLeft>
<NavTitle>Queue</NavTitle>
<NavRight>
<Link on:click={interrupt}>🛑️</Link>
<Link on:click={clearQueue}>🗑️</Link>
</NavRight>
</Navbar>
<Block>
<List>
{#each _entries as entry, i}
{@const prompt = findPrompt(entry)}
<ListItem>
<Card outlineMd class="demo-card-header-pic">
<CardHeader valign="bottom"
style="background-image: url({getCardImage(entry)})">
{entry.message}
</CardHeader>
<CardContent>
<p class="list-entry-message">{entry.submessage}</p>
<p>
{prompt}
</p>
</CardContent>
<CardFooter>
<Link on:click={() => doCancel(entry)}>Cancel</Link>
{#if entry.date}
<p>{entry.date}</p>
{/if}
</CardFooter>
</Card>
</ListItem>
{/each}
</List>
</Block>
</Page> </Page>
<style lang="scss"> <style lang="scss">
@@ -31,4 +129,27 @@
:global(.root-container.mobile > .block > .v-pane) { :global(.root-container.mobile > .block > .v-pane) {
flex-direction: column !important; 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%);
}
}
.list-entry-message {
font-size: 15pt;
}
</style> </style>

View File

@@ -1,13 +1,15 @@
<script lang="ts"> <script lang="ts">
import { Page, Navbar, Tabs, Tab, NavLeft, NavTitle, NavRight, Link } from "framework7-svelte" import { Page, Navbar, Tabs, Tab, NavLeft, NavTitle, NavRight, Link, Actions, ActionsGroup, ActionsButton, ActionsLabel, Sheet, Toolbar, PageContent, Block } from "framework7-svelte"
import WidgetContainer from "$lib/components/WidgetContainer.svelte"; import WidgetContainer from "$lib/components/WidgetContainer.svelte";
import type ComfyApp from "$lib/components/ComfyApp"; import type ComfyApp from "$lib/components/ComfyApp";
import { writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
import { MenuUp } from 'svelte-bootstrap-icons';
import type { IDragItem, WritableLayoutStateStore } from "$lib/stores/layoutStates"; import type { IDragItem, WritableLayoutStateStore } from "$lib/stores/layoutStates";
import workflowState, { type ComfyBoxWorkflow, type WorkflowInstID } from "$lib/stores/workflowState"; import workflowState, { type ComfyBoxWorkflow, type WorkflowInstID } from "$lib/stores/workflowState";
import interfaceState from "$lib/stores/interfaceState"; import interfaceState from "$lib/stores/interfaceState";
import { onMount } from "svelte"; import { onMount } from "svelte";
import GenToolbar from '../GenToolbar.svelte' import GenToolbar from '../GenToolbar.svelte'
import { onDestroy } from "svelte";
export let workflowIndex: number; export let workflowIndex: number;
export let app: ComfyApp export let app: ComfyApp
@@ -15,6 +17,7 @@
let workflow: ComfyBoxWorkflow; let workflow: ComfyBoxWorkflow;
let root: IDragItem | null; let root: IDragItem | null;
let title = "" let title = ""
let actionsOpened = false;
function onPageBeforeIn() { function onPageBeforeIn() {
workflow = $workflowState.openedWorkflows[workflowIndex-1] workflow = $workflowState.openedWorkflows[workflowIndex-1]
@@ -23,12 +26,23 @@
} }
$interfaceState.selectedWorkflowIndex = workflowIndex $interfaceState.selectedWorkflowIndex = workflowIndex
$interfaceState.showingWorkflow = true; $interfaceState.showingWorkflow = true;
$interfaceState.selectedTab = 1;
} }
function onPageBeforeOut() { function onPageBeforeOut() {
$interfaceState.showingWorkflow = false; $interfaceState.showingWorkflow = false;
} }
async function refreshCombos() {
navigator.vibrate(20)
await app.refreshComboInNodes()
}
function doSaveLocal(): void {
navigator.vibrate(20)
app.saveStateToLocalStorage();
}
$: layoutState = workflow?.layout; $: layoutState = workflow?.layout;
$: title = workflow?.attrs?.title || `Workflow: ${workflow?.id || workflowIndex}`; $: title = workflow?.attrs?.title || `Workflow: ${workflow?.id || workflowIndex}`;
@@ -47,7 +61,9 @@
<NavLeft backLink="Back" backLinkUrl="/workflows/" backLinkForce={true}></NavLeft> <NavLeft backLink="Back" backLinkUrl="/workflows/" backLinkForce={true}></NavLeft>
<NavTitle>{title}</NavTitle> <NavTitle>{title}</NavTitle>
<NavRight> <NavRight>
<Link icon="icon-bars" panelOpen="right"></Link> <Link icon="icon-bars" on:click={() => {actionsOpened = true;}}>
<MenuUp />
</Link>
</NavRight> </NavRight>
</Navbar> </Navbar>
@@ -62,6 +78,16 @@
Workflow not found. Workflow not found.
</div> </div>
{/if} {/if}
<Actions bind:opened={actionsOpened}>
<ActionsGroup>
<ActionsLabel>Actions</ActionsLabel>
<ActionsButton strong on:click={refreshCombos}>Refresh Dropdowns</ActionsButton>
<ActionsButton strong on:click={doSaveLocal}>Save to Local Storage</ActionsButton>
<!-- <ActionsButton>Button 2</ActionsButton> -->
<ActionsButton color="red">Cancel</ActionsButton>
</ActionsGroup>
</Actions>
</Page> </Page>
<style lang="scss"> <style lang="scss">
@@ -81,4 +107,8 @@
:global(.root-container.mobile > .block > .v-pane) { :global(.root-container.mobile > .block > .v-pane) {
flex-direction: column !important; flex-direction: column !important;
} }
.demo-sheet-push {
bottom: calc(var(--f7-toolbar-height) * 3);
}
</style> </style>

View File

@@ -9,6 +9,7 @@
import { Page, Navbar, Button, BlockTitle, Block, List, ListItem } from "framework7-svelte" import { Page, Navbar, Button, BlockTitle, Block, List, ListItem } from "framework7-svelte"
export let app: ComfyApp | null = null; export let app: ComfyApp | null = null;
let fileInput: HTMLInputElement = undefined;
async function doLoadDefault() { async function doLoadDefault() {
f7.dialog.confirm("Would you like to load the default workflow in a new tab?", async () => { f7.dialog.confirm("Would you like to load the default workflow in a new tab?", async () => {
@@ -26,8 +27,22 @@
app.saveStateToLocalStorage(false); app.saveStateToLocalStorage(false);
})} })}
function doLoad(): void {
if (!fileInput)
return;
navigator.vibrate(20)
fileInput.value = null;
fileInput.click();
}
function loadWorkflow(): void {
app.handleFile(fileInput.files[0]);
}
function onPageBeforeIn() { function onPageBeforeIn() {
$interfaceState.selectedWorkflowIndex = null; $interfaceState.selectedWorkflowIndex = null;
$interfaceState.selectedTab = 1;
} }
</script> </script>
@@ -50,6 +65,10 @@
(No workflows opened.) (No workflows opened.)
{/if} {/if}
<Block strong outlineIos> <Block strong outlineIos>
<Button fill={true} onClick={doLoadDefault}>Load Default Graph</Button> <p class="grid grid-cols-2 grid-gap">
<Button outline onClick={doLoadDefault}>Load default graph</Button>
<Button outline onClick={doLoad}>Load from file...</Button>
</p>
</Block> </Block>
<input bind:this={fileInput} id="comfy-file-input" style:display="none" type="file" accept=".json" on:change={loadWorkflow} />
</Page> </Page>