diff --git a/api/gui/.gitignore b/api/gui/.gitignore
index e9b37fd2..229bf879 100644
--- a/api/gui/.gitignore
+++ b/api/gui/.gitignore
@@ -1,3 +1,4 @@
+bundle/main.css
bundle/main.js
config.json
index.html
diff --git a/gui/Makefile b/gui/Makefile
index b4d21f7a..f35c134c 100644
--- a/gui/Makefile
+++ b/gui/Makefile
@@ -22,6 +22,7 @@ docs-local:
build: deps
yarn tsc
+ cp -v src/components/main.css out/src/components/
build-shebang: build
sed -i '1s;^;#! /usr/bin/env node\n\n;g' $(shell pwd)/out/src/main.js
@@ -35,6 +36,7 @@ bundle: build
# copy everything into the server's default path
cp -v src/index.html ../api/gui/
cp -v src/config.json ../api/gui/
+ cp -v out/bundle/main.css ../api/gui/bundle/
cp -v out/bundle/main.js ../api/gui/bundle/
COVER_OPTS := --all \
diff --git a/gui/package.json b/gui/package.json
index afc832b5..a02e18e7 100644
--- a/gui/package.json
+++ b/gui/package.json
@@ -16,6 +16,7 @@
"@tanstack/react-query": "^4.0.5",
"@types/lodash": "^4.14.192",
"@types/node": "^18.15.11",
+ "allotment": "^1.19.5",
"browser-bunyan": "^1.8.0",
"exifreader": "^4.13.0",
"i18next": "^22.4.14",
diff --git a/gui/src/components/ImageHistory.tsx b/gui/src/components/ImageHistory.tsx
index 03c82dcd..648dffce 100644
--- a/gui/src/components/ImageHistory.tsx
+++ b/gui/src/components/ImageHistory.tsx
@@ -48,8 +48,13 @@ export function ImageHistory(props: ImageHistoryProps) {
}
}
- // eslint-disable-next-line @typescript-eslint/no-magic-numbers
- return {children.map(([key, child]) => {child})};
+ return {
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+ children.map(([key, child]) => {child})
+ };
}
export function selectActions(state: OnnxState) {
diff --git a/gui/src/components/OnnxWeb.tsx b/gui/src/components/OnnxWeb.tsx
index 53adb35a..2e37174d 100644
--- a/gui/src/components/OnnxWeb.tsx
+++ b/gui/src/components/OnnxWeb.tsx
@@ -1,13 +1,16 @@
/* eslint-disable @typescript-eslint/no-magic-numbers */
import { mustExist } from '@apextoaster/js-utils';
import { TabContext, TabList, TabPanel } from '@mui/lab';
-import { Box, Button, Container, CssBaseline, Divider, Stack, Tab, useMediaQuery } from '@mui/material';
+import { Box, Container, CssBaseline, Divider, Stack, Tab, useMediaQuery } from '@mui/material';
import { Breakpoint, SxProps, Theme, ThemeProvider, createTheme } from '@mui/material/styles';
+import { Allotment } from 'allotment';
import * as React from 'react';
import { useContext, useMemo } from 'react';
import { useHash } from 'react-use/lib/useHash';
import { useStore } from 'zustand';
+import { shallow } from 'zustand/shallow';
+import { Motd } from '../Motd.js';
import { OnnxState, StateContext } from '../state/full.js';
import { ImageHistory } from './ImageHistory.js';
import { Logo } from './Logo.js';
@@ -19,7 +22,9 @@ import { Settings } from './tab/Settings.js';
import { Txt2Img } from './tab/Txt2Img.js';
import { Upscale } from './tab/Upscale.js';
import { TAB_LABELS, getTab, getTheme } from './utils.js';
-import { Motd } from '../Motd.js';
+
+import 'allotment/dist/style.css';
+import './main.css';
export interface OnnxWebProps {
motd: boolean;
@@ -30,7 +35,7 @@ export function OnnxWeb(props: OnnxWebProps) {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const store = mustExist(useContext(StateContext));
const stateTheme = useStore(store, selectTheme);
- const layout = useStore(store, selectLayout);
+ const layout = useStore(store, selectLayout, shallow);
const theme = useMemo(
() => createTheme({
@@ -41,13 +46,7 @@ export function OnnxWeb(props: OnnxWebProps) {
[prefersDarkMode, stateTheme],
);
- const [hash, setHash] = useHash();
-
- const historyStyle: SxProps = {
- mx: 4,
- my: 4,
- ...LAYOUT_STYLES[layout.direction].history.style,
- };
+ const historyStyle: SxProps = LAYOUT_STYLES[layout.direction].history.style;
return (
@@ -57,44 +56,7 @@ export function OnnxWeb(props: OnnxWebProps) {
{props.motd && }
-
-
-
-
- {
- setHash(idx);
- }}>
- {TAB_LABELS.map((name) => )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {layout.direction === 'vertical' ? : }
);
@@ -114,10 +76,14 @@ export function selectLayout(state: OnnxState) {
export const LAYOUT_STYLES = {
horizontal: {
container: false,
+ control: {
+ width: '30%',
+ },
direction: 'row',
divider: 'vertical',
history: {
style: {
+ marginLeft: 4,
maxHeight: '85vb',
overflowY: 'auto',
},
@@ -126,11 +92,91 @@ export const LAYOUT_STYLES = {
},
vertical: {
container: 'lg' as Breakpoint,
+ control: {
+ width: undefined,
+ },
direction: 'column',
divider: 'horizontal',
history: {
- style: {},
+ style: {
+ mx: 4,
+ my: 4,
+ },
width: 2,
},
},
} as const;
+
+// used for both horizontal and vertical
+export interface BodyProps {
+ layout: ReturnType;
+ style: SxProps;
+}
+
+export function HorizontalBody(props: BodyProps) {
+ const store = mustExist(useContext(StateContext));
+ const {direction} = useStore(store, selectLayout, shallow);
+ const layout = LAYOUT_STYLES[direction];
+
+ return
+
+
+
+
+ ;
+}
+
+export function VerticalBody(props: BodyProps) {
+ const store = mustExist(useContext(StateContext));
+ const {direction, width} = useStore(store, selectLayout, shallow);
+ const layout = LAYOUT_STYLES[direction];
+
+ return
+
+
+
+
+
+ ;
+}
+
+export function TabGroup() {
+ const store = mustExist(useContext(StateContext));
+ const {direction} = useStore(store, selectLayout, shallow);
+ const layout = LAYOUT_STYLES[direction];
+
+ const [hash, setHash] = useHash();
+
+ return
+
+
+ {
+ setHash(idx);
+ }}>
+ {TAB_LABELS.map((name) => )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ;
+}
diff --git a/gui/src/components/Profiles.tsx b/gui/src/components/Profiles.tsx
index f24104ac..0cf839c3 100644
--- a/gui/src/components/Profiles.tsx
+++ b/gui/src/components/Profiles.tsx
@@ -25,6 +25,7 @@ import { OnnxState, StateContext } from '../state/full.js';
import { ImageMetadata } from '../types/api.js';
import { DeepPartial } from '../types/model.js';
import { BaseImgParams, HighresParams, ModelParams, Txt2ImgParams, UpscaleParams } from '../types/params.js';
+import { downloadAsJson } from '../utils.js';
export const ALLOWED_EXTENSIONS = ['.json','.jpg','.jpeg','.png','.txt','.webp'];
export const EXTENSION_FILTER = ALLOWED_EXTENSIONS.join(',');
@@ -119,10 +120,10 @@ export function Profiles(props: ProfilesProps) {
onClick={() => {
const state = store.getState();
saveProfile({
- model: props.selectModel(state),
- params: props.selectParams(state),
name: profileName,
highres: props.selectHighres(state),
+ model: props.selectModel(state),
+ params: props.selectParams(state),
upscale: props.selectUpscale(state),
});
setDialogOpen(false);
@@ -166,9 +167,9 @@ export function Profiles(props: ProfilesProps) {
+ } onClick={() => {
+ downloadAsJson(state, 'state.json');
+ }}>
+ {t('setting.state.save')}
ONNX Web
+
diff --git a/gui/src/state/settings.ts b/gui/src/state/settings.ts
new file mode 100644
index 00000000..02822dd5
--- /dev/null
+++ b/gui/src/state/settings.ts
@@ -0,0 +1,34 @@
+import { Slice } from './types.js';
+
+export type Layout = 'horizontal' | 'vertical';
+
+export const DEFAULT_LAYOUT = {
+ historyWidth: 4,
+ layout: 'vertical' as Layout,
+} as const;
+
+export interface SettingsSlice {
+ historyWidth: number;
+ layout: Layout;
+
+ setLayout(layout: Layout): void;
+ setWidth(width: number): void;
+}
+
+export function createSettingsSlice(): Slice {
+ return (set) => ({
+ ...DEFAULT_LAYOUT,
+ setLayout(layout) {
+ set((prev) => ({
+ ...prev,
+ layout,
+ }));
+ },
+ setWidth(width) {
+ set((prev) => ({
+ ...prev,
+ historyWidth: width,
+ }));
+ },
+ });
+}
diff --git a/gui/src/strings/de.ts b/gui/src/strings/de.ts
index 31a4975b..d2904913 100644
--- a/gui/src/strings/de.ts
+++ b/gui/src/strings/de.ts
@@ -216,7 +216,6 @@ export const I18N_STRINGS_DE = {
limit: 'Bildgeschichte',
width: '',
},
- loadState: 'Laden',
prompt: 'Standard-Eingabeaufforderung',
reset: {
all: 'Alles zurücksetzen',
@@ -226,7 +225,11 @@ export const I18N_STRINGS_DE = {
},
scheduler: 'Standardplaner',
server: 'API-Server',
- state: 'Kundenstatus',
+ state: {
+ label: 'Kundenstatus',
+ load: 'Laden',
+ save: '',
+ },
darkMode: 'Dunkelmodus',
},
sourceFilter: {
diff --git a/gui/src/strings/en.ts b/gui/src/strings/en.ts
index 30f1d75d..17f24ac6 100644
--- a/gui/src/strings/en.ts
+++ b/gui/src/strings/en.ts
@@ -279,7 +279,6 @@ export const I18N_STRINGS_EN = {
limit: 'Image History Length',
width: 'Image History Width',
},
- loadState: 'Load',
prompt: 'Default Prompt',
reset: {
all: 'Reset All',
@@ -289,7 +288,11 @@ export const I18N_STRINGS_EN = {
},
scheduler: 'Default Scheduler',
server: 'API Server',
- state: 'Client State',
+ state: {
+ label: 'Client State',
+ save: 'Save',
+ load: 'Load',
+ },
darkMode: 'Dark Mode',
},
scheduler: {
diff --git a/gui/src/strings/es.ts b/gui/src/strings/es.ts
index 6dd12274..d9670d48 100644
--- a/gui/src/strings/es.ts
+++ b/gui/src/strings/es.ts
@@ -216,7 +216,6 @@ export const I18N_STRINGS_ES = {
limit: 'Historia de la imagen',
width: '',
},
- loadState: 'Carga estado',
prompt: 'Solicitud predeterminada',
reset: {
all: 'Resetear todo',
@@ -226,7 +225,11 @@ export const I18N_STRINGS_ES = {
},
scheduler: 'Programador predeterminado',
server: 'Servidor API',
- state: 'Estado del cliente',
+ state: {
+ label: 'Estado del cliente',
+ load: 'Carga estado',
+ save: '',
+ },
darkMode: 'Modo Oscuro',
},
sourceFilter: {
diff --git a/gui/src/strings/fr.ts b/gui/src/strings/fr.ts
index 8f6e2b35..779d4b25 100644
--- a/gui/src/strings/fr.ts
+++ b/gui/src/strings/fr.ts
@@ -216,7 +216,6 @@ export const I18N_STRINGS_FR = {
limit: '',
width: '',
},
- loadState: '',
prompt: '',
reset: {
all: '',
@@ -226,7 +225,11 @@ export const I18N_STRINGS_FR = {
},
scheduler: '',
server: '',
- state: '',
+ state: {
+ label: '',
+ load: '',
+ save: '',
+ },
darkMode: 'Mode Sombre',
},
sourceFilter: {
diff --git a/gui/src/utils.ts b/gui/src/utils.ts
index 6fbdbab9..31faf53d 100644
--- a/gui/src/utils.ts
+++ b/gui/src/utils.ts
@@ -26,3 +26,16 @@ export function trimHash(val: string): string {
return val;
}
+
+/**
+ * from https://stackoverflow.com/a/30800715
+ */
+export function downloadAsJson(data: object, filename = 'parameters.json'): void {
+ const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(data));
+ const elem = document.createElement('a');
+ elem.setAttribute('href', dataStr);
+ elem.setAttribute('download', filename);
+ document.body.appendChild(elem); // required for firefox
+ elem.click();
+ elem.remove();
+}
diff --git a/gui/yarn.lock b/gui/yarn.lock
index f93b5e70..d36e3edc 100644
--- a/gui/yarn.lock
+++ b/gui/yarn.lock
@@ -389,6 +389,11 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
+"@juggle/resize-observer@^3.3.1":
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
+ integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==
+
"@mochajs/multi-reporter@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@mochajs/multi-reporter/-/multi-reporter-1.1.0.tgz#378aafd9b9ecbd612753899a3be35026b79b62a5"
@@ -806,6 +811,18 @@ ajv@^6.10.0, ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
+allotment@^1.19.5:
+ version "1.19.5"
+ resolved "https://registry.yarnpkg.com/allotment/-/allotment-1.19.5.tgz#238e5457cba8ebfc5a6a59c411b4c563048b38ed"
+ integrity sha512-lQDeuqMkEEzITT56NAXNOWxRXycqVyV62w3X7jVZQS9n+cznDx6RLJMD1cnWcRMfCdPUmAj07FAIR69ueytxDQ==
+ dependencies:
+ classnames "^2.3.0"
+ eventemitter3 "^5.0.0"
+ lodash.clamp "^4.0.0"
+ lodash.debounce "^4.0.0"
+ lodash.isequal "^4.5.0"
+ use-resize-observer "^9.0.0"
+
ansi-colors@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@@ -1056,6 +1073,11 @@ chokidar@3.5.3, chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
+classnames@^2.3.0:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
+ integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
+
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
@@ -1568,6 +1590,11 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+eventemitter3@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
+ integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
+
exifreader@^4.13.0:
version "4.13.0"
resolved "https://registry.yarnpkg.com/exifreader/-/exifreader-4.13.0.tgz#f380b33cfc85630a0dbd56edd41e28710a9e9679"
@@ -2233,11 +2260,26 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
+lodash.clamp@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/lodash.clamp/-/lodash.clamp-4.0.3.tgz#5c24bedeeeef0753560dc2b4cb4671f90a6ddfaa"
+ integrity sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==
+
+lodash.debounce@^4.0.0:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+ integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
+
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
+lodash.isequal@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+ integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
+
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@@ -3101,6 +3143,13 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
+use-resize-observer@^9.0.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-9.1.0.tgz#14735235cf3268569c1ea468f8a90c5789fc5c6c"
+ integrity sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==
+ dependencies:
+ "@juggle/resize-observer" "^3.3.1"
+
use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"