Merge pull request #12 from caranicas/beta-react-ui

Beta react UI
This commit is contained in:
caranicas 2022-09-15 13:37:22 -04:00 committed by GitHub
commit e0a139d083
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1020 additions and 292 deletions

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,8 @@
"dependencies": { "dependencies": {
"@tanstack/react-query": "^4.2.3", "@tanstack/react-query": "^4.2.3",
"@tanstack/react-query-devtools": "^4.2.3", "@tanstack/react-query-devtools": "^4.2.3",
"@vanilla-extract/css": "^1.9.0",
"@vanilla-extract/vite-plugin": "^3.5.0",
"immer": "^9.0.15", "immer": "^9.0.15",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@ -26,5 +28,10 @@
"prettier": "^2.7.1", "prettier": "^2.7.1",
"typescript": "^4.6.4", "typescript": "^4.6.4",
"vite": "^3.0.7" "vite": "^3.0.7"
},
"overrides": {
"@vanilla-extract/vite-plugin": {
"vite": "^3"
}
} }
} }

View File

@ -1,62 +0,0 @@
.App {
position: relative;
width: 100%;
height: 100%;
pointer-events: auto;
display: grid;
background-color: rgb(32, 33, 36);
grid-template-columns: 360px 1fr;
grid-template-rows: 100px 1fr 300px;
grid-template-areas:
"header header header"
"create display display"
"footer footer footer";
}
/* Very basic mobile stacked UI*/
@media screen and (max-width: 768px) {
.App {
grid-template-columns: 1fr;
grid-template-rows: 100px 1fr 1fr 300px;
grid-template-areas:
"header"
"create"
"display"
"footer";
}
}
.header-layout {
grid-area: header;
}
.create-layout {
grid-area: create;
}
.display-layout {
grid-area: display;
}
.footer-layout {
grid-area: footer;
}
/* Copypasta from Bootstrap, makes content visually hidden but still accessible for screenreaders */
.visually-hidden,
.visually-hidden-focusable:not(:focus):not(:focus-within) {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
/* TODO proper utility classes */
.mb-4 {
margin-bottom: 1rem;
}

View File

@ -1,3 +0,0 @@
input[size="4"] {
width: 4.5rem;
}

View File

@ -1,101 +0,0 @@
import React, { ChangeEvent } from "react";
import MakeButton from "./makeButton";
import AdvancedSettings from "./advancedSettings";
import ImageModifiers from "./imageModifiers";
import ModifierTag from "./modierTag";
import { useImageCreate } from "../../store/imageCreateStore";
import "./creationPanel.css";
export default function CreationPanel() {
const promptText = useImageCreate((state) =>
state.getValueForRequestKey("prompt")
);
const init_image = useImageCreate((state) =>
state.getValueForRequestKey("init_image")
);
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
const selectedtags = useImageCreate((state) => state.selectedTags());
const handlePromptChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setRequestOption("prompt", event.target.value);
};
const _handleFileSelect = (event: ChangeEvent<HTMLInputElement>) => {
//@ts-ignore
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
if (e.target) {
setRequestOption("init_image", e.target.result);
}
};
reader.readAsDataURL(file);
}
};
const _handleClearImage = () => {
setRequestOption("init_image", undefined);
};
return (
<div className="create-panel">
<div className="basic-create">
<div className="prompt">
<p>Prompt </p>
<textarea value={promptText} onChange={handlePromptChange}></textarea>
</div>
<div id="editor-inputs-init-image" className="row">
<label>
<b>Initial Image:</b> (optional){" "}
</label>
<input
id="init_image"
name="init_image"
type="file"
onChange={_handleFileSelect}
/>
<br />
{init_image && (
<div id="init_image_preview" className="image_preview">
<img
id="init_image_preview"
src={init_image}
width="100"
height="100"
/>
<button id="init_image_clear" className="image_clear_btn" onClick={_handleClearImage}>
X
</button>
</div>
)}
</div>
<MakeButton></MakeButton>
<div className="selected-tags">
<p>Active Tags</p>
<ul>
{selectedtags.map((tag) => (
<li key={tag}>
<ModifierTag name={tag}></ModifierTag>
</li>
))}
</ul>
</div>
</div>
<div className="advanced-create">
<AdvancedSettings></AdvancedSettings>
<ImageModifiers></ImageModifiers>
</div>
</div>
);
}

View File

@ -1,15 +1,23 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import "./App.css";
import {
AppLayout,
HeaderLayout,
CreateLayout,
DisplayLayout,
FooterLayout
} // @ts-ignore
from './app.css.ts';
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { getSaveDirectory } from "./api"; import { getSaveDirectory } from "../../api";
import { useImageCreate } from "./store/imageCreateStore"; import { useImageCreate } from "../../store/imageCreateStore";
// Todo - import components here // Todo - import components here
import HeaderDisplay from "./components/headerDisplay"; import HeaderDisplay from "../organisms/headerDisplay";
import CreationPanel from "./components/creationPanel"; import CreationPanel from "../organisms/creationPanel";
import DisplayPanel from "./components/displayPanel"; import DisplayPanel from "../organisms/displayPanel";
import FooterDisplay from "./components/footerDisplay"; import FooterDisplay from "../organisms/footerDisplay";
function App() { function App() {
// Get the original save directory // Get the original save directory
@ -22,17 +30,17 @@ function App() {
}, [setRequestOption, status, data]); }, [setRequestOption, status, data]);
return ( return (
<div className="App"> <div className={AppLayout}>
<header className="header-layout"> <header className={HeaderLayout}>
<HeaderDisplay></HeaderDisplay> <HeaderDisplay></HeaderDisplay>
</header> </header>
<nav className="create-layout"> <nav className={CreateLayout}>
<CreationPanel></CreationPanel> <CreationPanel></CreationPanel>
</nav> </nav>
<main className="display-layout"> <main className={DisplayLayout}>
<DisplayPanel></DisplayPanel> <DisplayPanel></DisplayPanel>
</main> </main>
<footer className="footer-layout"> <footer className={FooterLayout}>
<FooterDisplay></FooterDisplay> <FooterDisplay></FooterDisplay>
</footer> </footer>
</div> </div>

View File

@ -0,0 +1,48 @@
import { style } from '@vanilla-extract/css';
export const AppLayout = style({
position: 'relative',
width: '100%',
height: '100%',
pointerEvents: 'auto',
display: 'grid',
backgroundColor: 'rgb(32, 33, 36)',
gridTemplateColumns: '400px 1fr',
gridTemplateRows: '100px 1fr 50px',
gridTemplateAreas: `
"header header header"
"create display display"
"create footer footer"
`,
'@media': {
'screen and (max-width: 800px)': {
gridTemplateColumns: '1fr',
gridTemplateRows: '100px 1fr 1fr 50px',
gridTemplateAreas: `
"header"
"create"
"display"
"footer"
`,
},
},
});
export const HeaderLayout = style({
gridArea: 'header',
});
export const CreateLayout = style({
gridArea: 'create',
overflow: 'auto',
});
export const DisplayLayout = style({
gridArea: 'display',
overflow: 'auto',
});
export const FooterLayout = style({
gridArea: 'footer',
});

View File

@ -1,6 +1,6 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useImageCreate } from "../../../store/imageCreateStore"; import { useImageCreate } from "../../../../store/imageCreateStore";
import "./advancedSettings.css"; // import "./advancedSettings.css";
// todo: move this someplace more global // todo: move this someplace more global
const IMAGE_DIMENSIONS = [ const IMAGE_DIMENSIONS = [

View File

@ -0,0 +1,20 @@
import React from "react";
import { useImageCreate } from "../../../../../store/imageCreateStore";
import ModifierTag from "../../../../atoms/modifierTag";
export default function ActiveTags() {
const selectedtags = useImageCreate((state) => state.selectedTags());
return (
<div className="selected-tags">
<p>Active Tags</p>
<ul>
{selectedtags.map((tag) => (
<li key={tag}>
<ModifierTag name={tag}></ModifierTag>
</li>
))}
</ul>
</div>
);
}

View File

@ -0,0 +1,28 @@
import { style, globalStyle } from '@vanilla-extract/css';
export const CreationBasicMain = style({
position: 'relative',
width: '100%',
});
globalStyle(`${CreationBasicMain} > *`, {
marginBottom: '10px'
});
export const PromptDisplay = style({
});
globalStyle(`${PromptDisplay} > p`, {
fontSize: '1.5em',
fontWeight: 'bold',
marginBottom: '10px'
});
globalStyle(`${PromptDisplay} > textarea`, {
fontSize: '1.2em',
fontWeight: 'bold',
fontFamily: 'Arial',
width: '100%',
resize:'vertical',
height: '100px',
});

View File

@ -0,0 +1,43 @@
import React, {ChangeEvent} from "react";
import { useImageCreate } from "../../../../store/imageCreateStore";
import {
CreationBasicMain,
PromptDisplay,
} // @ts-ignore
from "./basicCreation.css.ts";
import SeedImage from "./seedImage";
import ActiveTags from "./activeTags";
import MakeButton from "./makeButton";
export default function BasicCreation() {
const promptText = useImageCreate((state) =>
state.getValueForRequestKey("prompt")
);
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
const handlePromptChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setRequestOption("prompt", event.target.value);
};
return (
<div className={CreationBasicMain}>
<div className={PromptDisplay}>
<p>Prompt </p>
<textarea value={promptText} onChange={handlePromptChange}></textarea>
</div>
<SeedImage></SeedImage>
<ActiveTags></ActiveTags>
<MakeButton></MakeButton>
</div>
)
}

View File

@ -1,10 +1,16 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useImageCreate } from "../../../store/imageCreateStore"; import { useImageCreate } from "../../../../../store/imageCreateStore";
import { useImageQueue } from "../../../store/imageQueueStore"; import { useImageQueue } from "../../../../../store/imageQueueStore";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { useRandomSeed } from "../../../utils"; import { useRandomSeed } from "../../../../../utils";
import {
MakeButtonStyle
} from // @ts-ignore
"./makeButton.css.ts";
export default function MakeButton() { export default function MakeButton() {
const parallelCount = useImageCreate((state) => state.parallelCount); const parallelCount = useImageCreate((state) => state.parallelCount);
@ -68,5 +74,5 @@ export default function MakeButton() {
}; };
return <button onClick={makeImages}>Make</button>; return <button className={MakeButtonStyle} onClick={makeImages}>Make</button>;
} }

View File

@ -0,0 +1,11 @@
import { style } from '@vanilla-extract/css';
export const MakeButtonStyle = style({
width: '100%',
backgroundColor: 'rgb(38, 77, 141)',
fontSize: '1.5em',
fontWeight: 'bold',
color: 'white',
padding:'8px',
borderRadius: '5px',
});

View File

@ -0,0 +1,87 @@
import React , {useRef, ChangeEvent} from "react";
import {
ImageInputDisplay,
InputLabel,
ImageInput,
ImageInputButton,
ImageFixer,
XButton
} from // @ts-ignore
"./seedImage.css.ts";
import { useImageCreate } from "../../../../../store/imageCreateStore";
// TODO : figure out why this needs props to be passed in.. fixes a type error
// when the component is used in the parent component
export default function SeedImage(_props:any) {
const imageInputRef = useRef<HTMLInputElement>(null);
const init_image = useImageCreate((state) =>
state.getValueForRequestKey("init_image")
);
const setRequestOption = useImageCreate((state) => state.setRequestOptions);
const _startFileSelect = () => {
imageInputRef.current?.click();
}
const _handleFileSelect = (event: ChangeEvent<HTMLInputElement>) => {
//@ts-ignore
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
if (e.target) {
setRequestOption("init_image", e.target.result);
}
};
reader.readAsDataURL(file);
}
};
const _handleClearImage = () => {
setRequestOption("init_image", undefined);
};
return (
<div className={ImageInputDisplay}>
<div>
<label className={InputLabel}>
<b>Initial Image:</b> (optional)
</label>
<input
ref={imageInputRef}
className={ImageInput}
name="init_image"
type="file"
onChange={_handleFileSelect}
/>
<button className={ImageInputButton} onClick={_startFileSelect}>
Select File
</button>
</div>
<div className={ImageFixer}>
{init_image && (
<>
<img
src={init_image}
width="100"
height="100"
/>
<button className={XButton} onClick={_handleClearImage}>
X
</button>
</>
)}
</div>
</div>
);
};

View File

@ -0,0 +1,48 @@
import { style } from '@vanilla-extract/css';
export const ImageInputDisplay = style({
display: 'flex',
// justifyContent:'space-around',
});
export const InputLabel = style({
marginBottom: '5px',
display:'block'
});
export const ImageInput = style({
display:'none',
});
export const ImageInputButton = style({
backgroundColor: 'rgb(38, 77, 141)',
fontSize: '1.2em',
fontWeight: 'bold',
color: 'white',
padding:'8px',
borderRadius: '5px',
});
// this is needed to fix an issue with the image input text
// when that is a drag an drop we can remove this
export const ImageFixer = style({
marginLeft: '20px',
});
export const XButton = style({
position: 'absolute',
transform: 'translateX(-50%) translateY(-35%)',
background: 'black',
color: 'white',
border: '2pt solid #ccc',
padding: '0',
cursor: 'pointer',
outline: 'inherit',
borderRadius: '8pt',
width: '16pt',
height: '16pt',
fontFamily: 'Verdana',
fontSize: '8pt',
});

View File

@ -0,0 +1,8 @@
import { style } from '@vanilla-extract/css';
export const CreationPaneMain = style({
position: 'relative',
width: '100%',
height: '100%',
padding:'0 10px',
});

View File

@ -1,11 +1,11 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { loadModifications } from "../../../api"; import { loadModifications } from "../../../../api";
import { useImageCreate } from "../../../store/imageCreateStore"; import { useImageCreate } from "../../../../store/imageCreateStore";
import ModifierTag from "../modierTag"; import ModifierTag from "../../../atoms/modifierTag";
type ModifierListProps = { type ModifierListProps = {
tags: string[]; tags: string[];

View File

@ -0,0 +1,27 @@
import React, { ChangeEvent } from "react";
import MakeButton from "./basicCreation/makeButton";
import AdvancedSettings from "./advancedSettings";
import ImageModifiers from "./imageModifiers";
import "./creationPanel.css";
// @ts-ignore
import { CreationPaneMain } from "./creationpane.css.ts";
import BasicCreation from "./basicCreation";
export default function CreationPanel() {
return (
<div className={CreationPaneMain}>
<BasicCreation></BasicCreation>
<div className="advanced-create">
<AdvancedSettings></AdvancedSettings>
<ImageModifiers></ImageModifiers>
</div>
</div>
);
}

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { API_URL } from "../../../api"; import { API_URL } from "../../../../api";
const url = `${API_URL}/ding.mp3`; const url = `${API_URL}/ding.mp3`;

View File

@ -1,6 +1,6 @@
import React, { useCallback } from "react"; import React, { useCallback } from "react";
import { ImageRequest, useImageCreate } from "../../../store/imageCreateStore"; import { ImageRequest, useImageCreate } from "../../../../store/imageCreateStore";
import "./generatedImage.css"; import "./generatedImage.css";

View File

@ -1,11 +1,11 @@
import React, { useEffect, useState, useRef } from "react"; import React, { useEffect, useState, useRef } from "react";
import { useImageQueue } from "../../store/imageQueueStore"; import { useImageQueue } from "../../../store/imageQueueStore";
import { ImageRequest, useImageCreate } from "../../store/imageCreateStore"; import { ImageRequest, useImageCreate } from "../../../store/imageCreateStore";
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { doMakeImage, MakeImageKey } from "../../api"; import { doMakeImage, MakeImageKey } from "../../../api";
import AudioDing from "./audioDing"; import AudioDing from "./audioDing";

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { healthPing, HEALTH_PING_INTERVAL } from "../../../api"; import { healthPing, HEALTH_PING_INTERVAL } from "../../../../api";
const startingMessage = "Stable Diffusion is starting..."; const startingMessage = "Stable Diffusion is starting...";
const successMessage = "Stable Diffusion is ready to use!"; const successMessage = "Stable Diffusion is ready to use!";

View File

@ -1,13 +0,0 @@
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
#root {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}

View File

@ -6,8 +6,9 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { enableMapSet } from "immer"; import { enableMapSet } from "immer";
import App from "./App"; import App from "./components/layouts/App";
import "./index.css";
import './styles.css.ts';
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {

View File

@ -0,0 +1,32 @@
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
margin: 0,
minWidth: '320px',
minHeight: '100vh',
});
globalStyle('#root', {
position: 'absolute',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
overflow: 'hidden',
});
globalStyle(`*`, {
boxSizing: 'border-box',
});
globalStyle(`p`, {
margin: 0,
});
globalStyle(`textarea`, {
margin: 0,
padding: 0,
border: 'none',
});

View File

@ -1,12 +1,20 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [
react(),
vanillaExtractPlugin({
// configuration
})
],
server: { server: {
port: 9001, port: 9001,
}, },
build: { build: {
// make sure everythign is in the same directory // make sure everythign is in the same directory
outDir: "../dist", outDir: "../dist",
@ -18,6 +26,7 @@ export default defineConfig({
chunkFileNames: `[name].js`, chunkFileNames: `[name].js`,
assetFileNames: `[name].[ext]`, assetFileNames: `[name].[ext]`,
}, },
}, },
}, },
}); });