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 { Grid, Typography } from '@mui/material';
import { ReactNode, useContext } from 'react'; import { ReactNode, useContext } from 'react';
import * as React from 'react'; import * as React from 'react';
@ -12,7 +12,13 @@ import { ImageCard } from './card/ImageCard.js';
import { LoadingCard } from './card/LoadingCard.js'; import { LoadingCard } from './card/LoadingCard.js';
import { JobStatus } from '../types/api-v2.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 store = mustExist(useContext(StateContext));
const { history, limit } = useStore(store, selectParams, shallow); const { history, limit } = useStore(store, selectParams, shallow);
const { removeHistory } = useStore(store, selectActions, 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) { 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 { mustExist } from '@apextoaster/js-utils';
import { TabContext, TabList, TabPanel } from '@mui/lab'; import { TabContext, TabList, TabPanel } from '@mui/lab';
import { Box, Container, CssBaseline, Divider, Tab, useMediaQuery } from '@mui/material'; import { Box, Button, Container, CssBaseline, Divider, Stack, Tab, useMediaQuery } from '@mui/material';
import { ThemeProvider, createTheme } from '@mui/material/styles'; import { Breakpoint, SxProps, Theme, ThemeProvider, createTheme } from '@mui/material/styles';
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';
@ -29,6 +30,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 theme = useMemo( const theme = useMemo(
() => createTheme({ () => createTheme({
@ -41,14 +43,22 @@ export function OnnxWeb(props: OnnxWebProps) {
const [hash, setHash] = useHash(); const [hash, setHash] = useHash();
const historyStyle: SxProps<Theme> = {
mx: 4,
my: 4,
...LAYOUT_STYLES[layout.direction].history.style,
};
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
<Container> <Container maxWidth={LAYOUT_STYLES[layout.direction].container}>
<Box sx={{ my: 4 }}> <Box sx={{ my: 4 }}>
<Logo /> <Logo />
</Box> </Box>
{props.motd && <Motd />} {props.motd && <Motd />}
<Stack direction={LAYOUT_STYLES[layout.direction].direction} spacing={2}>
<Stack direction='column'>
<TabContext value={getTab(hash)}> <TabContext value={getTab(hash)}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}> <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<TabList onChange={(_e, idx) => { <TabList onChange={(_e, idx) => {
@ -79,10 +89,12 @@ export function OnnxWeb(props: OnnxWebProps) {
<Settings /> <Settings />
</TabPanel> </TabPanel>
</TabContext> </TabContext>
<Divider variant='middle' /> </Stack>
<Box sx={{ mx: 4, my: 4 }}> <Divider flexItem variant='middle' orientation={LAYOUT_STYLES[layout.direction].divider} />
<ImageHistory /> <Box sx={historyStyle}>
<ImageHistory width={layout.width} />
</Box> </Box>
</Stack>
</Container> </Container>
</ThemeProvider> </ThemeProvider>
); );
@ -91,3 +103,34 @@ export function OnnxWeb(props: OnnxWebProps) {
export function selectTheme(state: OnnxState) { export function selectTheme(state: OnnxState) {
return state.theme; 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) { export function getImageErrorReason(image: FailedJobResponse | UnknownJobResponse) {
if (image.status === JobStatus.FAILED) { if (image.status === JobStatus.FAILED) {
const error = image.reason; const error = image.reason;
if (doesExist(error) && error.startsWith(ANY_ERROR)) { if (doesExist(error)) {
return error; return error;
} }
return `${IMAGE_ERROR}${error}`;
} }
return UNKNOWN_ERROR; return UNKNOWN_ERROR;

View File

@ -40,13 +40,22 @@ export function Settings() {
return <Stack spacing={2}> return <Stack spacing={2}>
<NumericField <NumericField
label={t('setting.history')} label={t('setting.history.limit')}
min={2} min={2}
max={20} max={40}
step={1} step={1}
value={state.limit} value={state.limit}
onChange={(value) => state.setLimit(value)} 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) => { <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,

View File

@ -21,11 +21,11 @@ export const DEFAULT_HISTORY = {
/** /**
* The number of images to be shown. * 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 * 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. * 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, createBlendSlice,
createResetSlice, createResetSlice,
createProfileSlice, createProfileSlice,
createSettingsSlice,
} = createStateSlices(params); } = createStateSlices(params);
const state = createStore<OnnxState, [['zustand/persist', OnnxState]]>(persist((...slice) => ({ const state = createStore<OnnxState, [['zustand/persist', OnnxState]]>(persist((...slice) => ({
...createDefaultSlice(...slice), ...createDefaultSlice(...slice),
@ -77,6 +78,7 @@ export async function renderApp(config: Config, params: ServerParams, logger: Lo
...createBlendSlice(...slice), ...createBlendSlice(...slice),
...createResetSlice(...slice), ...createResetSlice(...slice),
...createProfileSlice(...slice), ...createProfileSlice(...slice),
...createSettingsSlice(...slice),
}), { }), {
migrate(persistedState, version) { migrate(persistedState, version) {
return applyStateMigrations(params, persistedState as UnknownState, version, logger); 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 { ModelSlice, createModelSlice } from './model.js';
import { ProfileSlice, createProfileSlice } from './profile.js'; import { ProfileSlice, createProfileSlice } from './profile.js';
import { ResetSlice, createResetSlice } from './reset.js'; import { ResetSlice, createResetSlice } from './reset.js';
import { SettingsSlice, createSettingsSlice } from './settings.js';
import { Txt2ImgSlice, createTxt2ImgSlice } from './txt2img.js'; import { Txt2ImgSlice, createTxt2ImgSlice } from './txt2img.js';
import { UpscaleSlice, createUpscaleSlice } from './upscale.js'; import { UpscaleSlice, createUpscaleSlice } from './upscale.js';
import { import {
@ -39,7 +40,8 @@ export type OnnxState
& UpscaleSlice & UpscaleSlice
& BlendSlice & BlendSlice
& ResetSlice & ResetSlice
& ProfileSlice; & ProfileSlice
& SettingsSlice;
/** /**
* React context binding for API client. * React context binding for API client.
@ -69,7 +71,7 @@ export const STATE_KEY = 'onnx-web';
/** /**
* Current state version for zustand persistence. * Current state version for zustand persistence.
*/ */
export const STATE_VERSION = 11; export const STATE_VERSION = 13;
export function baseParamsFromServer(defaults: ServerParams): Required<BaseImgParams> { export function baseParamsFromServer(defaults: ServerParams): Required<BaseImgParams> {
return { return {
@ -144,6 +146,7 @@ export function createStateSlices(server: ServerParams) {
createModelSlice: createModelSlice(), createModelSlice: createModelSlice(),
createProfileSlice: createProfileSlice(), createProfileSlice: createProfileSlice(),
createResetSlice: createResetSlice(), createResetSlice: createResetSlice(),
createSettingsSlice: createSettingsSlice(),
createTxt2ImgSlice: createTxt2ImgSlice(server, defaultParams, defaultHighres, defaultModel, defaultUpscale, defaultGrid), createTxt2ImgSlice: createTxt2ImgSlice(server, defaultParams, defaultHighres, defaultModel, defaultUpscale, defaultGrid),
createUpscaleSlice: createUpscaleSlice(defaultParams, defaultHighres, defaultModel, defaultUpscale), createUpscaleSlice: createUpscaleSlice(defaultParams, defaultHighres, defaultModel, defaultUpscale),
}; };

View File

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

View File

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

View File

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

View File

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

View File

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