Mobile queue
This commit is contained in:
@@ -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