Mobile gallery

This commit is contained in:
space-nuko
2023-05-31 19:19:23 -05:00
parent 3cd623fd20
commit 4547cc1a27
12 changed files with 167 additions and 87 deletions

View File

@@ -58,16 +58,19 @@
$: f7 && f7.setDarkMode($interfaceState.isDarkMode)
onMount(async () => {
let isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
$interfaceState.isDarkMode = isDarkMode;
// let isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
$interfaceState.isDarkMode = true;
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
$interfaceState.isDarkMode = event.matches;
});
appSetupPromise = app.setup().then(() => {
// Autosave every minute
setInterval(() => app.saveStateToLocalStorage(false), 60 * 1000)
loading = false
});
window.addEventListener("backbutton", onBackKeyDown, false);
window.addEventListener("popstate", onBackKeyDown, false);
@@ -107,10 +110,16 @@
{
path: '/queue/',
component: QueuePage,
options: {
props: { app }
}
},
{
path: '/gallery/',
component: GalleryPage,
options: {
props: { app }
}
},
// {
// path: '/graph/',

View File

@@ -368,7 +368,7 @@ export default class ComfyApp {
}
}
saveStateToLocalStorage() {
saveStateToLocalStorage(doNotify: boolean = true) {
try {
uiState.update(s => { s.forceSaveUserState = true; return s; })
const state = get(workflowState)
@@ -380,10 +380,12 @@ export default class ComfyApp {
for (const workflow of workflows)
workflow.isModified = false;
workflowState.set(get(workflowState));
notify("Saved to local storage.")
if (doNotify)
notify("Saved to local storage.")
}
catch (err) {
notify(`Failed saving to local storage:\n${err}`, { type: "error" })
if (doNotify)
notify(`Failed saving to local storage:\n${err}`, { type: "error" })
}
finally {
uiState.update(s => { s.forceSaveUserState = null; return s; })

View File

@@ -2,6 +2,9 @@ import type ComfyGraphCanvas from "$lib/ComfyGraphCanvas";
import { type ContainerLayout, type IDragItem, type TemplateLayout, type WritableLayoutStateStore } from "$lib/stores/layoutStates"
import type { LGraphCanvas, Vector2 } from "@litegraph-ts/core";
import { get } from "svelte/store";
import { PhotoBrowser, f7 } from "framework7-svelte";
import { ImageViewer } from "$lib/ImageViewer";
import interfaceState from "$lib/stores/interfaceState";
export function handleContainerConsider(layoutState: WritableLayoutStateStore, container: ContainerLayout, evt: CustomEvent<DndEvent<IDragItem>>): IDragItem[] {
return layoutState.updateChildren(container, evt.detail.items)
@@ -45,3 +48,25 @@ function doInsertTemplate(layoutState: WritableLayoutStateStore, droppedTemplate
return get(layoutState).allItems[container.id].children;
}
let mobileLightbox = null;
export function showMobileLightbox(images: any[], selectedImage: number, options: Partial<PhotoBrowser["params"]> = {}) {
if (!f7)
return
if (mobileLightbox) {
mobileLightbox.destroy();
mobileLightbox = null;
}
history.pushState({ type: "gallery" }, "");
mobileLightbox = f7.photoBrowser.create({
photos: images,
theme: get(interfaceState).isDarkMode ? "dark" : "light",
type: 'popup',
...options
});
mobileLightbox.open(selectedImage)
}

View File

@@ -18,6 +18,7 @@ function notifyf7(text: string, options: NotifyOptions) {
if (!f7)
return;
console.error(options)
let closeTimeout = options.timeout
if (closeTimeout === undefined)
closeTimeout = 3000;
@@ -27,6 +28,11 @@ function notifyf7(text: string, options: NotifyOptions) {
on.click = () => options.onClick();
}
let icon = null;
if (options.imageUrl) {
icon = `<img src="${options.imageUrl}"/>`
}
const notification = f7.notification.create({
title: options.title,
titleRightText: 'now',
@@ -35,7 +41,7 @@ function notifyf7(text: string, options: NotifyOptions) {
closeOnClick: true,
closeTimeout,
on,
icon: options.imageUrl
icon
});
notification.open();
}

View File

@@ -721,3 +721,16 @@ export async function getSafetensorsMetadata(folder: string, filename: string):
return fetch(new Request(url + `/view_metadata/${folder}?` + params)).then(r => r.json())
}
export function partition<T>(myArray: T[], chunkSize: number): T[] {
let index = 0;
const arrayLength = myArray.length;
const tempArray = [];
for (index = 0; index < arrayLength; index += chunkSize) {
const myChunk = myArray.slice(index, index + chunkSize);
tempArray.push(myChunk);
}
return tempArray;
}

View File

@@ -11,6 +11,7 @@
import { clamp, comfyBoxImageToComfyURL, type ComfyBoxImageMetadata } from "$lib/utils";
import { f7 } from "framework7-svelte";
import type { ComfyGalleryNode } from "$lib/nodes/widgets";
import { showMobileLightbox } from "$lib/components/utils";
export let widget: WidgetLayout | null = null;
export let isMobile: boolean = false;
@@ -47,19 +48,8 @@
object_fit: "cover",
// preview: true
}
let element: HTMLDivElement;
let mobileLightbox = null;
function showMobileLightbox(source: HTMLImageElement) {
if (!f7)
return
if (mobileLightbox) {
mobileLightbox.destroy();
mobileLightbox = null;
}
function showMobileLightbox_(source: HTMLImageElement, selectedImage: number) {
const galleryElem = source.closest<HTMLDivElement>("div.block")
console.debug("[ImageViewer] showModal", source, galleryElem);
if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) {
@@ -72,23 +62,16 @@
const images = allGalleryButtons.map(button => {
return {
url: (button.children[0] as HTMLImageElement).src,
caption: "Image"
// caption: "Image"
}
})
history.pushState({ type: "gallery" }, "");
mobileLightbox = f7.photoBrowser.create({
photos: images,
thumbs: images.map(i => i.url),
type: 'popup',
});
mobileLightbox.open($selected_image)
showMobileLightbox(images, selectedImage, { thumbs: images });
}
function onClicked(e: CustomEvent<HTMLImageElement>) {
if (isMobile) {
showMobileLightbox(e.detail)
showMobileLightbox_(e.detail, $selected_image)
}
else {
ImageViewer.instance.showLightbox(e.detail)
@@ -103,7 +86,7 @@
{#if widget && node && nodeValue && $nodeValue}
{#if widget.attrs.variant === "image"}
<div class="wrapper comfy-image-widget" style={widget.attrs.style || ""} bind:this={element}>
<div class="wrapper comfy-image-widget" style={widget.attrs.style || ""}>
<Block variant="solid" padding={false}>
{#if $nodeValue && $nodeValue.length > 0}
{@const value = $nodeValue[$nodeValue.length-1]}
@@ -122,7 +105,7 @@
</div>
{:else}
{@const images = $nodeValue.map(comfyBoxImageToComfyURL)}
<div class="wrapper comfy-gallery-widget gradio-gallery" style={widget.attrs.style || ""} bind:this={element}>
<div class="wrapper comfy-gallery-widget gradio-gallery" style={widget.attrs.style || ""}>
<Block variant="solid" padding={false}>
<div class="padding">
<Gallery

View File

@@ -30,14 +30,6 @@
await app.refreshComboInNodes()
}
function doSave(): void {
if (!fileInput)
return;
navigator.vibrate(20)
app.querySave()
}
function doLoad(): void {
if (!fileInput)
return;
@@ -55,25 +47,6 @@
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 = "??.?%"
}
</script>
<Toolbar bottom color="red" style="bottom: calc(var(--f7-toolbar-height))">
@@ -83,7 +56,6 @@
</Link>
{/if}
<Link on:click={refreshCombos}>🔄</Link>
<Link on:click={doSave}>Save</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} />

View File

@@ -76,7 +76,7 @@
}
let centerHref = "/workflows/"
$: if ($interfaceState.selectedWorkflowIndex) {
$: if ($interfaceState.selectedWorkflowIndex && !$interfaceState.showingWorkflow) {
centerHref = `/workflows/${$interfaceState.selectedWorkflowIndex}/`
}
else {

View File

@@ -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<QueueUIEntry> = []
$: _entries = $uiQueueState.historyUIEntries;
let allEntries: [QueueUIEntry, string][][] = []
let allImages: string[] = []
let gridCols = 3;
$: buildImageList(_entries);
function buildImageList(entries: ReadonlyArray<QueueUIEntry>) {
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)
}
</script>
<Page name="gallery">
<Navbar title="Gallery" />
<Navbar>
<NavLeft></NavLeft>
<NavTitle>Gallery</NavTitle>
<NavRight>
<Link on:click={clearHistory}>🗑️</Link>
</NavRight>
</Navbar>
<Block>
<div class="grid grid-cols-3 grid-gap">
<div>3 cols</div>
<div>3 cols</div>
<div>3 cols</div>
</div>
<div class="grid grid-cols-3 grid-gap">
<div>3 cols</div>
<div>3 cols</div>
<div>3 cols</div>
</div>
<div class="grid grid-cols-3 grid-gap">
<div>3 cols</div>
<div>3 cols</div>
<div>3 cols</div>
</div>
<div class="grid grid-cols-3 grid-gap">
<div>3 cols</div>
<div>3 cols</div>
<div>3 cols</div>
</div>
<div class="grid grid-cols-3 grid-gap">
<div>3 cols</div>
<div>3 cols</div>
<div>3 cols</div>
</div>
{#each allEntries as group, i}
<div class="grid grid-cols-{gridCols} grid-gap">
{#each group as [entry, image], j}
{@const index = i * gridCols + j}
<div class="grid-entry">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<img class="grid-entry-image"
on:click={(e) => handleClick(e, entry, index)}
src={image}
loading="lazy"
alt="thumbnail" />
</div>
{/each}
</div>
{/each}
</Block>
</Page>
@@ -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%);
}
}
</style>

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);
}