Mobile queue
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<InterfaceState> & InterfaceStateOps;
|
||||
@@ -34,6 +42,8 @@ const store: Writable<InterfaceState> = 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<T>(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))
|
||||
}
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
<script lang="ts">
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
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";
|
||||
|
||||
export let subworkflowID: number = -1;
|
||||
export let app: ComfyApp = undefined;
|
||||
let layoutState: WritableLayoutStateStore = null;
|
||||
let fileInput: HTMLInputElement = undefined;
|
||||
let workflow: ComfyBoxWorkflow | null = null;
|
||||
|
||||
$: workflow = $workflowState.activeWorkflow;
|
||||
@@ -24,41 +14,16 @@
|
||||
navigator.vibrate(20)
|
||||
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>
|
||||
|
||||
<Toolbar bottom color="red" style="bottom: calc(var(--f7-toolbar-height))">
|
||||
{#if workflow != null && workflow.attrs.queuePromptButtonName != ""}
|
||||
<Link on:click={queuePrompt}>
|
||||
{workflow.attrs.queuePromptButtonName}
|
||||
</Link>
|
||||
<div style:width="100%">
|
||||
<Link on:click={queuePrompt}>
|
||||
{workflow.attrs.queuePromptButtonName}
|
||||
</Link>
|
||||
</div>
|
||||
{/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>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -66,6 +31,10 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
:global(.toolbar) {
|
||||
--f7-toolbar-font-size: 13pt;
|
||||
}
|
||||
|
||||
:global(.dark .toolbar.color-red) {
|
||||
background: var(--neutral-700) !important;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@
|
||||
$: toolbarCount = $interfaceState.showingWorkflow ? 2 : 1;
|
||||
|
||||
const ICON_SIZE = "1.5rem";
|
||||
|
||||
let selectedTab = 1;
|
||||
</script>
|
||||
|
||||
<div class="bottom" style:--toolbarCount={toolbarCount}>
|
||||
@@ -108,13 +110,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<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} />
|
||||
</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} />
|
||||
</Link>
|
||||
<Link transition="f7-dive" href="/gallery/">
|
||||
<Link transition="f7-dive" href="/gallery/" tabLinkActive={$interfaceState.selectedTab === 2}>
|
||||
<Grid width={ICON_SIZE} height={ICON_SIZE} />
|
||||
</Link>
|
||||
</Toolbar>
|
||||
@@ -143,6 +145,11 @@
|
||||
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 {
|
||||
--toolbarCount: 1;
|
||||
position: absolute;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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 type ComfyApp from "$lib/components/ComfyApp";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
@@ -11,6 +11,7 @@
|
||||
import { partition, showLightbox } from "$lib/utils";
|
||||
import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState";
|
||||
import { showMobileLightbox } from "$lib/components/utils";
|
||||
import notify from "$lib/notify";
|
||||
|
||||
export let app: ComfyApp
|
||||
|
||||
@@ -22,6 +23,10 @@
|
||||
|
||||
let gridCols = 3;
|
||||
|
||||
function onPageBeforeIn() {
|
||||
$interfaceState.selectedTab = 2;
|
||||
}
|
||||
|
||||
$: buildImageList(_entries);
|
||||
|
||||
function buildImageList(entries: ReadonlyArray<QueueUIEntry>) {
|
||||
@@ -39,14 +44,16 @@
|
||||
showMobileLightbox(allImages, index)
|
||||
}
|
||||
|
||||
|
||||
async function clearHistory() {
|
||||
await app.clearQueue("history");
|
||||
uiQueueState.updateEntries(true)
|
||||
f7.dialog.confirm("Are you sure you want to clear the current history?", async () => {
|
||||
await app.clearQueue("history");
|
||||
uiQueueState.updateEntries(true)
|
||||
notify("History cleared!")
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Page name="gallery">
|
||||
<Page name="gallery" on:pageBeforeIn={onPageBeforeIn}>
|
||||
<Navbar>
|
||||
<NavLeft></NavLeft>
|
||||
<NavTitle>Gallery</NavTitle>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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 type ComfyApp from "$lib/components/ComfyApp";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
@@ -8,10 +8,108 @@
|
||||
import interfaceState from "$lib/stores/interfaceState";
|
||||
import { onMount } from "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>
|
||||
|
||||
<Page name="queue">
|
||||
<Navbar title="Queue" />
|
||||
<Page name="queue" on:pageBeforeIn={onPageBeforeIn}>
|
||||
<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>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -31,4 +129,27 @@
|
||||
: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%);
|
||||
}
|
||||
}
|
||||
|
||||
.list-entry-message {
|
||||
font-size: 15pt;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<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 type ComfyApp from "$lib/components/ComfyApp";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import { MenuUp } from 'svelte-bootstrap-icons';
|
||||
import type { IDragItem, WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||
import workflowState, { type ComfyBoxWorkflow, type WorkflowInstID } from "$lib/stores/workflowState";
|
||||
import interfaceState from "$lib/stores/interfaceState";
|
||||
import { onMount } from "svelte";
|
||||
import GenToolbar from '../GenToolbar.svelte'
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
export let workflowIndex: number;
|
||||
export let app: ComfyApp
|
||||
@@ -15,6 +17,7 @@
|
||||
let workflow: ComfyBoxWorkflow;
|
||||
let root: IDragItem | null;
|
||||
let title = ""
|
||||
let actionsOpened = false;
|
||||
|
||||
function onPageBeforeIn() {
|
||||
workflow = $workflowState.openedWorkflows[workflowIndex-1]
|
||||
@@ -23,12 +26,23 @@
|
||||
}
|
||||
$interfaceState.selectedWorkflowIndex = workflowIndex
|
||||
$interfaceState.showingWorkflow = true;
|
||||
$interfaceState.selectedTab = 1;
|
||||
}
|
||||
|
||||
function onPageBeforeOut() {
|
||||
$interfaceState.showingWorkflow = false;
|
||||
}
|
||||
|
||||
async function refreshCombos() {
|
||||
navigator.vibrate(20)
|
||||
await app.refreshComboInNodes()
|
||||
}
|
||||
|
||||
function doSaveLocal(): void {
|
||||
navigator.vibrate(20)
|
||||
app.saveStateToLocalStorage();
|
||||
}
|
||||
|
||||
$: layoutState = workflow?.layout;
|
||||
$: title = workflow?.attrs?.title || `Workflow: ${workflow?.id || workflowIndex}`;
|
||||
|
||||
@@ -47,7 +61,9 @@
|
||||
<NavLeft backLink="Back" backLinkUrl="/workflows/" backLinkForce={true}></NavLeft>
|
||||
<NavTitle>{title}</NavTitle>
|
||||
<NavRight>
|
||||
<Link icon="icon-bars" panelOpen="right"></Link>
|
||||
<Link icon="icon-bars" on:click={() => {actionsOpened = true;}}>
|
||||
<MenuUp />
|
||||
</Link>
|
||||
</NavRight>
|
||||
</Navbar>
|
||||
|
||||
@@ -62,6 +78,16 @@
|
||||
Workflow not found.
|
||||
</div>
|
||||
{/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>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -81,4 +107,8 @@
|
||||
:global(.root-container.mobile > .block > .v-pane) {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.demo-sheet-push {
|
||||
bottom: calc(var(--f7-toolbar-height) * 3);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { Page, Navbar, Button, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
||||
|
||||
export let app: ComfyApp | null = null;
|
||||
let fileInput: HTMLInputElement = undefined;
|
||||
|
||||
async function doLoadDefault() {
|
||||
f7.dialog.confirm("Would you like to load the default workflow in a new tab?", async () => {
|
||||
@@ -26,8 +27,22 @@
|
||||
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() {
|
||||
$interfaceState.selectedWorkflowIndex = null;
|
||||
$interfaceState.selectedTab = 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -50,6 +65,10 @@
|
||||
(No workflows opened.)
|
||||
{/if}
|
||||
<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>
|
||||
<input bind:this={fileInput} id="comfy-file-input" style:display="none" type="file" accept=".json" on:change={loadWorkflow} />
|
||||
</Page>
|
||||
|
||||
Reference in New Issue
Block a user