1
0
Fork 0

feat(gui): make layout direction and history width persist

This commit is contained in:
Sean Sube 2024-01-10 21:47:21 -06:00
parent 055f6e2956
commit 2b562d9464
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
12 changed files with 127 additions and 52 deletions

View File

@ -1,4 +1,4 @@
import { doesExist, mustExist } from '@apextoaster/js-utils';
import { mustExist } from '@apextoaster/js-utils';
import { Grid, Typography } from '@mui/material';
import { ReactNode, useContext } from 'react';
import * as React from 'react';
@ -12,7 +12,13 @@ import { ImageCard } from './card/ImageCard.js';
import { LoadingCard } from './card/LoadingCard.js';
import { JobStatus } from '../types/api-v2.js';
export function ImageHistory() {
export interface ImageHistoryProps {
width: number;
}
export function ImageHistory(props: ImageHistoryProps) {
const { width } = props;
const store = mustExist(useContext(StateContext));
const { history, limit } = useStore(store, selectParams, shallow);
const { removeHistory } = useStore(store, selectActions, shallow);
@ -42,7 +48,8 @@ export function ImageHistory() {
}
}
return <Grid container spacing={2}>{children.map(([key, child]) => <Grid item key={key} xs={6}>{child}</Grid>)}</Grid>;
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
return <Grid container spacing={2}>{children.map(([key, child]) => <Grid item key={key} xs={12 / width}>{child}</Grid>)}</Grid>;
}
export function selectActions(state: OnnxState) {

View File

@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-magic-numbers */
import { mustExist } from '@apextoaster/js-utils';
import { TabContext, TabList, TabPanel } from '@mui/lab';
import { Box, Container, CssBaseline, Divider, Tab, useMediaQuery } from '@mui/material';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { Box, Button, Container, CssBaseline, Divider, Stack, Tab, useMediaQuery } from '@mui/material';
import { Breakpoint, SxProps, Theme, ThemeProvider, createTheme } from '@mui/material/styles';
import * as React from 'react';
import { useContext, useMemo } from 'react';
import { useHash } from 'react-use/lib/useHash';
@ -29,6 +30,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 theme = useMemo(
() => createTheme({
@ -41,14 +43,22 @@ export function OnnxWeb(props: OnnxWebProps) {
const [hash, setHash] = useHash();
const historyStyle: SxProps<Theme> = {
mx: 4,
my: 4,
...LAYOUT_STYLES[layout.direction].history.style,
};
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Container>
<Container maxWidth={LAYOUT_STYLES[layout.direction].container}>
<Box sx={{ my: 4 }}>
<Logo />
</Box>
{props.motd && <Motd />}
<Stack direction={LAYOUT_STYLES[layout.direction].direction} spacing={2}>
<Stack direction='column'>
<TabContext value={getTab(hash)}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<TabList onChange={(_e, idx) => {
@ -79,10 +89,12 @@ export function OnnxWeb(props: OnnxWebProps) {
<Settings />
</TabPanel>
</TabContext>
<Divider variant='middle' />
<Box sx={{ mx: 4, my: 4 }}>
<ImageHistory />
</Stack>
<Divider flexItem variant='middle' orientation={LAYOUT_STYLES[layout.direction].divider} />
<Box sx={historyStyle}>
<ImageHistory width={layout.width} />
</Box>
</Stack>
</Container>
</ThemeProvider>
);
@ -91,3 +103,34 @@ export function OnnxWeb(props: OnnxWebProps) {
export function selectTheme(state: OnnxState) {
return state.theme;
}
export function selectLayout(state: OnnxState) {
return {
direction: state.layout,
width: state.historyWidth,
};
}
export const LAYOUT_STYLES = {
horizontal: {
container: false,
direction: 'row',
divider: 'vertical',
history: {
style: {
maxHeight: '85vb',
overflowY: 'auto',
},
width: 4,
},
},
vertical: {
container: 'lg' as Breakpoint,
direction: 'column',
divider: 'horizontal',
history: {
style: {},
width: 2,
},
},
} as const;

View File

@ -90,11 +90,9 @@ export const UNKNOWN_ERROR = `${IMAGE_ERROR}unknown`;
export function getImageErrorReason(image: FailedJobResponse | UnknownJobResponse) {
if (image.status === JobStatus.FAILED) {
const error = image.reason;
if (doesExist(error) && error.startsWith(ANY_ERROR)) {
if (doesExist(error)) {
return error;
}
return `${IMAGE_ERROR}${error}`;
}
return UNKNOWN_ERROR;

View File

@ -40,13 +40,22 @@ export function Settings() {
return <Stack spacing={2}>
<NumericField
label={t('setting.history')}
label={t('setting.history.limit')}
min={2}
max={20}
max={40}
step={1}
value={state.limit}
onChange={(value) => state.setLimit(value)}
/>
<NumericField
label={t('setting.history.width')}
min={2}
max={6}
step={1}
value={state.historyWidth}
onChange={(value) => state.setWidth(value)}
/>
<Button onClick={() => state.setLayout(state.layout === 'horizontal' ? 'vertical' : 'horizontal')}>Toggle Layout</Button>
<TextField variant='outlined' label={t('setting.prompt')} value={state.defaults.prompt} onChange={(event) => {
state.setDefaults({
prompt: event.target.value,

View File

@ -21,11 +21,11 @@ export const DEFAULT_HISTORY = {
/**
* The number of images to be shown.
*/
limit: 4,
limit: 8,
/**
* The number of additional images to be kept in history, so they can scroll
* back into view when you delete one. Does not include deleted images.
*/
scrollback: 2,
scrollback: 4,
};

View File

@ -65,6 +65,7 @@ export async function renderApp(config: Config, params: ServerParams, logger: Lo
createBlendSlice,
createResetSlice,
createProfileSlice,
createSettingsSlice,
} = createStateSlices(params);
const state = createStore<OnnxState, [['zustand/persist', OnnxState]]>(persist((...slice) => ({
...createDefaultSlice(...slice),
@ -77,6 +78,7 @@ export async function renderApp(config: Config, params: ServerParams, logger: Lo
...createBlendSlice(...slice),
...createResetSlice(...slice),
...createProfileSlice(...slice),
...createSettingsSlice(...slice),
}), {
migrate(persistedState, version) {
return applyStateMigrations(params, persistedState as UnknownState, version, logger);

View File

@ -17,6 +17,7 @@ import { InpaintSlice, createInpaintSlice } from './inpaint.js';
import { ModelSlice, createModelSlice } from './model.js';
import { ProfileSlice, createProfileSlice } from './profile.js';
import { ResetSlice, createResetSlice } from './reset.js';
import { SettingsSlice, createSettingsSlice } from './settings.js';
import { Txt2ImgSlice, createTxt2ImgSlice } from './txt2img.js';
import { UpscaleSlice, createUpscaleSlice } from './upscale.js';
import {
@ -39,7 +40,8 @@ export type OnnxState
& UpscaleSlice
& BlendSlice
& ResetSlice
& ProfileSlice;
& ProfileSlice
& SettingsSlice;
/**
* React context binding for API client.
@ -69,7 +71,7 @@ export const STATE_KEY = 'onnx-web';
/**
* Current state version for zustand persistence.
*/
export const STATE_VERSION = 11;
export const STATE_VERSION = 13;
export function baseParamsFromServer(defaults: ServerParams): Required<BaseImgParams> {
return {
@ -144,6 +146,7 @@ export function createStateSlices(server: ServerParams) {
createModelSlice: createModelSlice(),
createProfileSlice: createProfileSlice(),
createResetSlice: createResetSlice(),
createSettingsSlice: createSettingsSlice(),
createTxt2ImgSlice: createTxt2ImgSlice(server, defaultParams, defaultHighres, defaultModel, defaultUpscale, defaultGrid),
createUpscaleSlice: createUpscaleSlice(defaultParams, defaultHighres, defaultModel, defaultUpscale),
};

View File

@ -21,6 +21,7 @@ export interface HistorySlice {
pushHistory(image: JobResponse, retry?: RetryParams): void;
removeHistory(image: JobResponse): void;
setLimit(limit: number): void;
setReady(image: JobResponse): void;
}

View File

@ -212,7 +212,10 @@ export const I18N_STRINGS_DE = {
},
setting: {
connectServer: 'verbinden zum Server',
history: 'Bildgeschichte',
history: {
limit: 'Bildgeschichte',
width: '',
},
loadState: 'Laden',
prompt: 'Standard-Eingabeaufforderung',
reset: {

View File

@ -275,7 +275,10 @@ export const I18N_STRINGS_EN = {
},
setting: {
connectServer: 'Connect',
history: 'Image History',
history: {
limit: 'Image History Length',
width: 'Image History Width',
},
loadState: 'Load',
prompt: 'Default Prompt',
reset: {

View File

@ -212,7 +212,10 @@ export const I18N_STRINGS_ES = {
},
setting: {
connectServer: 'Conectar al servidor',
history: 'Historia de la imagen',
history: {
limit: 'Historia de la imagen',
width: '',
},
loadState: 'Carga estado',
prompt: 'Solicitud predeterminada',
reset: {

View File

@ -212,7 +212,10 @@ export const I18N_STRINGS_FR = {
},
setting: {
connectServer: '',
history: '',
history: {
limit: '',
width: '',
},
loadState: '',
prompt: '',
reset: {