add settings slice and allow resizing image history
This commit is contained in:
parent
2b562d9464
commit
5888cae298
|
@ -1,3 +1,4 @@
|
||||||
|
bundle/main.css
|
||||||
bundle/main.js
|
bundle/main.js
|
||||||
config.json
|
config.json
|
||||||
index.html
|
index.html
|
||||||
|
|
|
@ -22,6 +22,7 @@ docs-local:
|
||||||
|
|
||||||
build: deps
|
build: deps
|
||||||
yarn tsc
|
yarn tsc
|
||||||
|
cp -v src/components/main.css out/src/components/
|
||||||
|
|
||||||
build-shebang: build
|
build-shebang: build
|
||||||
sed -i '1s;^;#! /usr/bin/env node\n\n;g' $(shell pwd)/out/src/main.js
|
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
|
# copy everything into the server's default path
|
||||||
cp -v src/index.html ../api/gui/
|
cp -v src/index.html ../api/gui/
|
||||||
cp -v src/config.json ../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/
|
cp -v out/bundle/main.js ../api/gui/bundle/
|
||||||
|
|
||||||
COVER_OPTS := --all \
|
COVER_OPTS := --all \
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"@tanstack/react-query": "^4.0.5",
|
"@tanstack/react-query": "^4.0.5",
|
||||||
"@types/lodash": "^4.14.192",
|
"@types/lodash": "^4.14.192",
|
||||||
"@types/node": "^18.15.11",
|
"@types/node": "^18.15.11",
|
||||||
|
"allotment": "^1.19.5",
|
||||||
"browser-bunyan": "^1.8.0",
|
"browser-bunyan": "^1.8.0",
|
||||||
"exifreader": "^4.13.0",
|
"exifreader": "^4.13.0",
|
||||||
"i18next": "^22.4.14",
|
"i18next": "^22.4.14",
|
||||||
|
|
|
@ -48,8 +48,13 @@ export function ImageHistory(props: ImageHistoryProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
return <Grid
|
||||||
return <Grid container spacing={2}>{children.map(([key, child]) => <Grid item key={key} xs={12 / width}>{child}</Grid>)}</Grid>;
|
container
|
||||||
|
spacing={2}
|
||||||
|
>{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||||
|
children.map(([key, child]) => <Grid item key={key} xs={12 / width}>{child}</Grid>)
|
||||||
|
}</Grid>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectActions(state: OnnxState) {
|
export function selectActions(state: OnnxState) {
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
/* eslint-disable @typescript-eslint/no-magic-numbers */
|
/* eslint-disable @typescript-eslint/no-magic-numbers */
|
||||||
import { mustExist } from '@apextoaster/js-utils';
|
import { mustExist } from '@apextoaster/js-utils';
|
||||||
import { TabContext, TabList, TabPanel } from '@mui/lab';
|
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 { Breakpoint, SxProps, Theme, ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
|
import { Allotment } from 'allotment';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useContext, useMemo } from 'react';
|
import { useContext, useMemo } from 'react';
|
||||||
import { useHash } from 'react-use/lib/useHash';
|
import { useHash } from 'react-use/lib/useHash';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
|
import { shallow } from 'zustand/shallow';
|
||||||
|
|
||||||
|
import { Motd } from '../Motd.js';
|
||||||
import { OnnxState, StateContext } from '../state/full.js';
|
import { OnnxState, StateContext } from '../state/full.js';
|
||||||
import { ImageHistory } from './ImageHistory.js';
|
import { ImageHistory } from './ImageHistory.js';
|
||||||
import { Logo } from './Logo.js';
|
import { Logo } from './Logo.js';
|
||||||
|
@ -19,7 +22,9 @@ import { Settings } from './tab/Settings.js';
|
||||||
import { Txt2Img } from './tab/Txt2Img.js';
|
import { Txt2Img } from './tab/Txt2Img.js';
|
||||||
import { Upscale } from './tab/Upscale.js';
|
import { Upscale } from './tab/Upscale.js';
|
||||||
import { TAB_LABELS, getTab, getTheme } from './utils.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 {
|
export interface OnnxWebProps {
|
||||||
motd: boolean;
|
motd: boolean;
|
||||||
|
@ -30,7 +35,7 @@ export function OnnxWeb(props: OnnxWebProps) {
|
||||||
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
|
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
|
||||||
const store = mustExist(useContext(StateContext));
|
const store = mustExist(useContext(StateContext));
|
||||||
const stateTheme = useStore(store, selectTheme);
|
const stateTheme = useStore(store, selectTheme);
|
||||||
const layout = useStore(store, selectLayout);
|
const layout = useStore(store, selectLayout, shallow);
|
||||||
|
|
||||||
const theme = useMemo(
|
const theme = useMemo(
|
||||||
() => createTheme({
|
() => createTheme({
|
||||||
|
@ -41,13 +46,7 @@ export function OnnxWeb(props: OnnxWebProps) {
|
||||||
[prefersDarkMode, stateTheme],
|
[prefersDarkMode, stateTheme],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [hash, setHash] = useHash();
|
const historyStyle: SxProps<Theme> = LAYOUT_STYLES[layout.direction].history.style;
|
||||||
|
|
||||||
const historyStyle: SxProps<Theme> = {
|
|
||||||
mx: 4,
|
|
||||||
my: 4,
|
|
||||||
...LAYOUT_STYLES[layout.direction].history.style,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
|
@ -57,44 +56,7 @@ export function OnnxWeb(props: OnnxWebProps) {
|
||||||
<Logo />
|
<Logo />
|
||||||
</Box>
|
</Box>
|
||||||
{props.motd && <Motd />}
|
{props.motd && <Motd />}
|
||||||
<Stack direction={LAYOUT_STYLES[layout.direction].direction} spacing={2}>
|
{layout.direction === 'vertical' ? <VerticalBody layout={layout} style={historyStyle} /> : <HorizontalBody layout={layout} style={historyStyle} />}
|
||||||
<Stack direction='column'>
|
|
||||||
<TabContext value={getTab(hash)}>
|
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
|
||||||
<TabList onChange={(_e, idx) => {
|
|
||||||
setHash(idx);
|
|
||||||
}}>
|
|
||||||
{TAB_LABELS.map((name) => <Tab key={name} label={name} value={name} />)}
|
|
||||||
</TabList>
|
|
||||||
</Box>
|
|
||||||
<TabPanel value='txt2img'>
|
|
||||||
<Txt2Img />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value='img2img'>
|
|
||||||
<Img2Img />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value='inpaint'>
|
|
||||||
<Inpaint />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value='upscale'>
|
|
||||||
<Upscale />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value='blend'>
|
|
||||||
<Blend />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value='models'>
|
|
||||||
<Models />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value='settings'>
|
|
||||||
<Settings />
|
|
||||||
</TabPanel>
|
|
||||||
</TabContext>
|
|
||||||
</Stack>
|
|
||||||
<Divider flexItem variant='middle' orientation={LAYOUT_STYLES[layout.direction].divider} />
|
|
||||||
<Box sx={historyStyle}>
|
|
||||||
<ImageHistory width={layout.width} />
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</Container>
|
</Container>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
@ -114,10 +76,14 @@ export function selectLayout(state: OnnxState) {
|
||||||
export const LAYOUT_STYLES = {
|
export const LAYOUT_STYLES = {
|
||||||
horizontal: {
|
horizontal: {
|
||||||
container: false,
|
container: false,
|
||||||
|
control: {
|
||||||
|
width: '30%',
|
||||||
|
},
|
||||||
direction: 'row',
|
direction: 'row',
|
||||||
divider: 'vertical',
|
divider: 'vertical',
|
||||||
history: {
|
history: {
|
||||||
style: {
|
style: {
|
||||||
|
marginLeft: 4,
|
||||||
maxHeight: '85vb',
|
maxHeight: '85vb',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
},
|
},
|
||||||
|
@ -126,11 +92,91 @@ export const LAYOUT_STYLES = {
|
||||||
},
|
},
|
||||||
vertical: {
|
vertical: {
|
||||||
container: 'lg' as Breakpoint,
|
container: 'lg' as Breakpoint,
|
||||||
|
control: {
|
||||||
|
width: undefined,
|
||||||
|
},
|
||||||
direction: 'column',
|
direction: 'column',
|
||||||
divider: 'horizontal',
|
divider: 'horizontal',
|
||||||
history: {
|
history: {
|
||||||
style: {},
|
style: {
|
||||||
|
mx: 4,
|
||||||
|
my: 4,
|
||||||
|
},
|
||||||
width: 2,
|
width: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// used for both horizontal and vertical
|
||||||
|
export interface BodyProps {
|
||||||
|
layout: ReturnType<typeof selectLayout>;
|
||||||
|
style: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HorizontalBody(props: BodyProps) {
|
||||||
|
const store = mustExist(useContext(StateContext));
|
||||||
|
const {direction} = useStore(store, selectLayout, shallow);
|
||||||
|
const layout = LAYOUT_STYLES[direction];
|
||||||
|
|
||||||
|
return <Allotment separator className='body-allotment' minSize={300}>
|
||||||
|
<TabGroup />
|
||||||
|
<Box sx={layout.history.style}>
|
||||||
|
<ImageHistory width={props.layout.width} />
|
||||||
|
</Box>
|
||||||
|
</Allotment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VerticalBody(props: BodyProps) {
|
||||||
|
const store = mustExist(useContext(StateContext));
|
||||||
|
const {direction, width} = useStore(store, selectLayout, shallow);
|
||||||
|
const layout = LAYOUT_STYLES[direction];
|
||||||
|
|
||||||
|
return <Stack direction={layout.direction} spacing={2}>
|
||||||
|
<TabGroup />
|
||||||
|
<Divider flexItem variant='middle' orientation={layout.divider} />
|
||||||
|
<Box sx={layout.history.style}>
|
||||||
|
<ImageHistory width={width} />
|
||||||
|
</Box>
|
||||||
|
</Stack>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TabGroup() {
|
||||||
|
const store = mustExist(useContext(StateContext));
|
||||||
|
const {direction} = useStore(store, selectLayout, shallow);
|
||||||
|
const layout = LAYOUT_STYLES[direction];
|
||||||
|
|
||||||
|
const [hash, setHash] = useHash();
|
||||||
|
|
||||||
|
return <Stack direction='column' minWidth={layout.control.width} sx={{ mx: 4 }}>
|
||||||
|
<TabContext value={getTab(hash)}>
|
||||||
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||||
|
<TabList onChange={(_e, idx) => {
|
||||||
|
setHash(idx);
|
||||||
|
}}>
|
||||||
|
{TAB_LABELS.map((name) => <Tab key={name} label={name} value={name} />)}
|
||||||
|
</TabList>
|
||||||
|
</Box>
|
||||||
|
<TabPanel value='txt2img'>
|
||||||
|
<Txt2Img />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value='img2img'>
|
||||||
|
<Img2Img />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value='inpaint'>
|
||||||
|
<Inpaint />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value='upscale'>
|
||||||
|
<Upscale />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value='blend'>
|
||||||
|
<Blend />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value='models'>
|
||||||
|
<Models />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value='settings'>
|
||||||
|
<Settings />
|
||||||
|
</TabPanel>
|
||||||
|
</TabContext>
|
||||||
|
</Stack>;
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { OnnxState, StateContext } from '../state/full.js';
|
||||||
import { ImageMetadata } from '../types/api.js';
|
import { ImageMetadata } from '../types/api.js';
|
||||||
import { DeepPartial } from '../types/model.js';
|
import { DeepPartial } from '../types/model.js';
|
||||||
import { BaseImgParams, HighresParams, ModelParams, Txt2ImgParams, UpscaleParams } from '../types/params.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 ALLOWED_EXTENSIONS = ['.json','.jpg','.jpeg','.png','.txt','.webp'];
|
||||||
export const EXTENSION_FILTER = ALLOWED_EXTENSIONS.join(',');
|
export const EXTENSION_FILTER = ALLOWED_EXTENSIONS.join(',');
|
||||||
|
@ -119,10 +120,10 @@ export function Profiles(props: ProfilesProps) {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
saveProfile({
|
saveProfile({
|
||||||
model: props.selectModel(state),
|
|
||||||
params: props.selectParams(state),
|
|
||||||
name: profileName,
|
name: profileName,
|
||||||
highres: props.selectHighres(state),
|
highres: props.selectHighres(state),
|
||||||
|
model: props.selectModel(state),
|
||||||
|
params: props.selectParams(state),
|
||||||
upscale: props.selectUpscale(state),
|
upscale: props.selectUpscale(state),
|
||||||
});
|
});
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
|
@ -166,9 +167,9 @@ export function Profiles(props: ProfilesProps) {
|
||||||
</Button>
|
</Button>
|
||||||
<Button component='label' variant='contained' onClick={() => {
|
<Button component='label' variant='contained' onClick={() => {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
downloadParamsAsFile({
|
downloadAsJson({
|
||||||
// TODO: save model parameters
|
name: 'web-ui-profile',
|
||||||
// model: props.selectModel(state),
|
model: props.selectModel(state),
|
||||||
params: props.selectParams(state),
|
params: props.selectParams(state),
|
||||||
highres: props.selectHighres(state),
|
highres: props.selectHighres(state),
|
||||||
upscale: props.selectUpscale(state),
|
upscale: props.selectUpscale(state),
|
||||||
|
@ -210,19 +211,6 @@ export async function loadParamsFromFile(file: File): Promise<DeepPartial<ImageM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* from https://stackoverflow.com/a/30800715
|
|
||||||
*/
|
|
||||||
export function downloadParamsAsFile(data: DeepPartial<ImageMetadata>): 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', 'parameters.json');
|
|
||||||
document.body.appendChild(elem); // required for firefox
|
|
||||||
elem.click();
|
|
||||||
elem.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function parseImageParams(file: File): Promise<DeepPartial<ImageMetadata>> {
|
export async function parseImageParams(file: File): Promise<DeepPartial<ImageMetadata>> {
|
||||||
const tags = await ExifReader.load(file);
|
const tags = await ExifReader.load(file);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.body-allotment {
|
||||||
|
height: 85vb;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { doesExist, mustExist } from '@apextoaster/js-utils';
|
import { doesExist, mustExist } from '@apextoaster/js-utils';
|
||||||
import { Refresh } from '@mui/icons-material';
|
import { Download, Refresh } from '@mui/icons-material';
|
||||||
import { Alert, Button, FormControlLabel, Stack, Switch, TextField, useMediaQuery } from '@mui/material';
|
import { Alert, Button, FormControlLabel, Stack, Switch, TextField, useMediaQuery } from '@mui/material';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
|
@ -10,6 +10,7 @@ import { getApiRoot } from '../../config.js';
|
||||||
import { ConfigContext, StateContext, STATE_KEY } from '../../state/full.js';
|
import { ConfigContext, StateContext, STATE_KEY } from '../../state/full.js';
|
||||||
import { getTheme } from '../utils.js';
|
import { getTheme } from '../utils.js';
|
||||||
import { NumericField } from '../input/NumericField.js';
|
import { NumericField } from '../input/NumericField.js';
|
||||||
|
import { downloadAsJson } from '../../utils.js';
|
||||||
|
|
||||||
function removeBlobs(key: string, value: unknown): unknown {
|
function removeBlobs(key: string, value: unknown): unknown {
|
||||||
if (value instanceof Blob || value instanceof File) {
|
if (value instanceof Blob || value instanceof File) {
|
||||||
|
@ -55,7 +56,7 @@ export function Settings() {
|
||||||
value={state.historyWidth}
|
value={state.historyWidth}
|
||||||
onChange={(value) => state.setWidth(value)}
|
onChange={(value) => state.setWidth(value)}
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => state.setLayout(state.layout === 'horizontal' ? 'vertical' : 'horizontal')}>Toggle Layout</Button>
|
<Button variant='contained' onClick={() => state.setLayout(state.layout === 'horizontal' ? 'vertical' : 'horizontal')}>Toggle Layout</Button>
|
||||||
<TextField variant='outlined' label={t('setting.prompt')} value={state.defaults.prompt} onChange={(event) => {
|
<TextField variant='outlined' label={t('setting.prompt')} value={state.defaults.prompt} onChange={(event) => {
|
||||||
state.setDefaults({
|
state.setDefaults({
|
||||||
prompt: event.target.value,
|
prompt: event.target.value,
|
||||||
|
@ -82,14 +83,19 @@ export function Settings() {
|
||||||
</Alert>
|
</Alert>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack direction='row' spacing={2}>
|
<Stack direction='row' spacing={2}>
|
||||||
<TextField variant='outlined' label={t('setting.state')} value={json} onChange={(event) => {
|
<TextField variant='outlined' label={t('setting.state.label')} value={json} onChange={(event) => {
|
||||||
setJson(event.target.value);
|
setJson(event.target.value);
|
||||||
}} />
|
}} />
|
||||||
<Button variant='contained' startIcon={<Refresh />} onClick={() => {
|
<Button variant='contained' startIcon={<Refresh />} onClick={() => {
|
||||||
window.localStorage.setItem(STATE_KEY, json);
|
window.localStorage.setItem(STATE_KEY, json);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}}>
|
}}>
|
||||||
{t('setting.loadState')}
|
{t('setting.state.load')}
|
||||||
|
</Button>
|
||||||
|
<Button variant='contained' startIcon={<Download />} onClick={() => {
|
||||||
|
downloadAsJson(state, 'state.json');
|
||||||
|
}}>
|
||||||
|
{t('setting.state.save')}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
<FormControlLabel control={
|
<FormControlLabel control={
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>ONNX Web</title>
|
<title>ONNX Web</title>
|
||||||
|
<link href="./bundle/main.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
|
@ -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<TState extends SettingsSlice>(): Slice<TState, SettingsSlice> {
|
||||||
|
return (set) => ({
|
||||||
|
...DEFAULT_LAYOUT,
|
||||||
|
setLayout(layout) {
|
||||||
|
set((prev) => ({
|
||||||
|
...prev,
|
||||||
|
layout,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
setWidth(width) {
|
||||||
|
set((prev) => ({
|
||||||
|
...prev,
|
||||||
|
historyWidth: width,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
|
@ -216,7 +216,6 @@ export const I18N_STRINGS_DE = {
|
||||||
limit: 'Bildgeschichte',
|
limit: 'Bildgeschichte',
|
||||||
width: '',
|
width: '',
|
||||||
},
|
},
|
||||||
loadState: 'Laden',
|
|
||||||
prompt: 'Standard-Eingabeaufforderung',
|
prompt: 'Standard-Eingabeaufforderung',
|
||||||
reset: {
|
reset: {
|
||||||
all: 'Alles zurücksetzen',
|
all: 'Alles zurücksetzen',
|
||||||
|
@ -226,7 +225,11 @@ export const I18N_STRINGS_DE = {
|
||||||
},
|
},
|
||||||
scheduler: 'Standardplaner',
|
scheduler: 'Standardplaner',
|
||||||
server: 'API-Server',
|
server: 'API-Server',
|
||||||
state: 'Kundenstatus',
|
state: {
|
||||||
|
label: 'Kundenstatus',
|
||||||
|
load: 'Laden',
|
||||||
|
save: '',
|
||||||
|
},
|
||||||
darkMode: 'Dunkelmodus',
|
darkMode: 'Dunkelmodus',
|
||||||
},
|
},
|
||||||
sourceFilter: {
|
sourceFilter: {
|
||||||
|
|
|
@ -279,7 +279,6 @@ export const I18N_STRINGS_EN = {
|
||||||
limit: 'Image History Length',
|
limit: 'Image History Length',
|
||||||
width: 'Image History Width',
|
width: 'Image History Width',
|
||||||
},
|
},
|
||||||
loadState: 'Load',
|
|
||||||
prompt: 'Default Prompt',
|
prompt: 'Default Prompt',
|
||||||
reset: {
|
reset: {
|
||||||
all: 'Reset All',
|
all: 'Reset All',
|
||||||
|
@ -289,7 +288,11 @@ export const I18N_STRINGS_EN = {
|
||||||
},
|
},
|
||||||
scheduler: 'Default Scheduler',
|
scheduler: 'Default Scheduler',
|
||||||
server: 'API Server',
|
server: 'API Server',
|
||||||
state: 'Client State',
|
state: {
|
||||||
|
label: 'Client State',
|
||||||
|
save: 'Save',
|
||||||
|
load: 'Load',
|
||||||
|
},
|
||||||
darkMode: 'Dark Mode',
|
darkMode: 'Dark Mode',
|
||||||
},
|
},
|
||||||
scheduler: {
|
scheduler: {
|
||||||
|
|
|
@ -216,7 +216,6 @@ export const I18N_STRINGS_ES = {
|
||||||
limit: 'Historia de la imagen',
|
limit: 'Historia de la imagen',
|
||||||
width: '',
|
width: '',
|
||||||
},
|
},
|
||||||
loadState: 'Carga estado',
|
|
||||||
prompt: 'Solicitud predeterminada',
|
prompt: 'Solicitud predeterminada',
|
||||||
reset: {
|
reset: {
|
||||||
all: 'Resetear todo',
|
all: 'Resetear todo',
|
||||||
|
@ -226,7 +225,11 @@ export const I18N_STRINGS_ES = {
|
||||||
},
|
},
|
||||||
scheduler: 'Programador predeterminado',
|
scheduler: 'Programador predeterminado',
|
||||||
server: 'Servidor API',
|
server: 'Servidor API',
|
||||||
state: 'Estado del cliente',
|
state: {
|
||||||
|
label: 'Estado del cliente',
|
||||||
|
load: 'Carga estado',
|
||||||
|
save: '',
|
||||||
|
},
|
||||||
darkMode: 'Modo Oscuro',
|
darkMode: 'Modo Oscuro',
|
||||||
},
|
},
|
||||||
sourceFilter: {
|
sourceFilter: {
|
||||||
|
|
|
@ -216,7 +216,6 @@ export const I18N_STRINGS_FR = {
|
||||||
limit: '',
|
limit: '',
|
||||||
width: '',
|
width: '',
|
||||||
},
|
},
|
||||||
loadState: '',
|
|
||||||
prompt: '',
|
prompt: '',
|
||||||
reset: {
|
reset: {
|
||||||
all: '',
|
all: '',
|
||||||
|
@ -226,7 +225,11 @@ export const I18N_STRINGS_FR = {
|
||||||
},
|
},
|
||||||
scheduler: '',
|
scheduler: '',
|
||||||
server: '',
|
server: '',
|
||||||
state: '',
|
state: {
|
||||||
|
label: '',
|
||||||
|
load: '',
|
||||||
|
save: '',
|
||||||
|
},
|
||||||
darkMode: 'Mode Sombre',
|
darkMode: 'Mode Sombre',
|
||||||
},
|
},
|
||||||
sourceFilter: {
|
sourceFilter: {
|
||||||
|
|
|
@ -26,3 +26,16 @@ export function trimHash(val: string): string {
|
||||||
|
|
||||||
return val;
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -389,6 +389,11 @@
|
||||||
"@jridgewell/resolve-uri" "3.1.0"
|
"@jridgewell/resolve-uri" "3.1.0"
|
||||||
"@jridgewell/sourcemap-codec" "1.4.14"
|
"@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":
|
"@mochajs/multi-reporter@^1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@mochajs/multi-reporter/-/multi-reporter-1.1.0.tgz#378aafd9b9ecbd612753899a3be35026b79b62a5"
|
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"
|
json-schema-traverse "^0.4.1"
|
||||||
uri-js "^4.2.2"
|
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:
|
ansi-colors@4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
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:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
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:
|
cliui@^7.0.2:
|
||||||
version "7.0.4"
|
version "7.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
|
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"
|
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
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:
|
exifreader@^4.13.0:
|
||||||
version "4.13.0"
|
version "4.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/exifreader/-/exifreader-4.13.0.tgz#f380b33cfc85630a0dbd56edd41e28710a9e9679"
|
resolved "https://registry.yarnpkg.com/exifreader/-/exifreader-4.13.0.tgz#f380b33cfc85630a0dbd56edd41e28710a9e9679"
|
||||||
|
@ -2233,11 +2260,26 @@ locate-path@^6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate "^5.0.0"
|
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:
|
lodash.get@^4.4.2:
|
||||||
version "4.4.2"
|
version "4.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||||
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
|
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:
|
lodash.merge@^4.6.2:
|
||||||
version "4.6.2"
|
version "4.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||||
|
@ -3101,6 +3143,13 @@ uri-js@^4.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.0"
|
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:
|
use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||||
|
|
Loading…
Reference in New Issue