Complete GUI Refactor # Patch 1

This commit is contained in:
blessedcoolant 2022-03-30 12:45:34 +13:00
parent eea85b834e
commit b282421c98
23 changed files with 389 additions and 177 deletions

View File

@ -1,17 +1,17 @@
{ {
"files": { "files": {
"main.css": "/static/css/main.54fbc69f.chunk.css", "main.css": "/static/css/main.08f8fee9.chunk.css",
"main.js": "/static/js/main.d346743e.chunk.js", "main.js": "/static/js/main.4e51b2a1.chunk.js",
"runtime-main.js": "/static/js/runtime-main.5e86ac81.js", "runtime-main.js": "/static/js/runtime-main.5e86ac81.js",
"static/js/2.2516aa7d.chunk.js": "/static/js/2.2516aa7d.chunk.js", "static/js/2.9608d3ec.chunk.js": "/static/js/2.9608d3ec.chunk.js",
"index.html": "/index.html", "index.html": "/index.html",
"static/js/2.2516aa7d.chunk.js.LICENSE.txt": "/static/js/2.2516aa7d.chunk.js.LICENSE.txt", "static/js/2.9608d3ec.chunk.js.LICENSE.txt": "/static/js/2.9608d3ec.chunk.js.LICENSE.txt",
"static/media/_index.scss": "/static/media/WorkSans-SemiBold.1e98db4e.ttf" "static/media/_index.scss": "/static/media/WorkSans-SemiBold.1e98db4e.ttf"
}, },
"entrypoints": [ "entrypoints": [
"static/js/runtime-main.5e86ac81.js", "static/js/runtime-main.5e86ac81.js",
"static/js/2.2516aa7d.chunk.js", "static/js/2.9608d3ec.chunk.js",
"static/css/main.54fbc69f.chunk.css", "static/css/main.08f8fee9.chunk.css",
"static/js/main.d346743e.chunk.js" "static/js/main.4e51b2a1.chunk.js"
] ]
} }

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/><meta name="theme-color" content="#ffffff"/><title>lama-cleaner - Image inpainting powered by LaMa</title><link href="/static/css/main.54fbc69f.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>"localhost"===location.hostname&&(self.FIREBASE_APPCHECK_DEBUG_TOKEN=!0)</script><script>!function(e){function r(r){for(var n,l,a=r[0],f=r[1],i=r[2],p=0,s=[];p<a.length;p++)l=a[p],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var f=t[a];0!==o[f]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var a=this["webpackJsonplama-cleaner"]=this["webpackJsonplama-cleaner"]||[],f=a.push.bind(a);a.push=r,a=a.slice();for(var i=0;i<a.length;i++)r(a[i]);var c=f;t()}([])</script><script src="/static/js/2.2516aa7d.chunk.js"></script><script src="/static/js/main.d346743e.chunk.js"></script></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/><meta name="theme-color" content="#ffffff"/><title>lama-cleaner - Image inpainting powered by LaMa</title><link href="/static/css/main.08f8fee9.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>"localhost"===location.hostname&&(self.FIREBASE_APPCHECK_DEBUG_TOKEN=!0)</script><script>!function(e){function r(r){for(var n,l,a=r[0],f=r[1],i=r[2],p=0,s=[];p<a.length;p++)l=a[p],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var f=t[a];0!==o[f]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var a=this["webpackJsonplama-cleaner"]=this["webpackJsonplama-cleaner"]||[],f=a.push.bind(a);a.push=r,a=a.slice();for(var i=0;i<a.length;i++)r(a[i]);var c=f;t()}([])</script><script src="/static/js/2.9608d3ec.chunk.js"></script><script src="/static/js/main.4e51b2a1.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,27 +6,10 @@ import LandingPage from './components/LandingPage/LandingPage'
import { ThemeChanger, themeState } from './components/shared/ThemeChanger' import { ThemeChanger, themeState } from './components/shared/ThemeChanger'
import Workspace from './components/Workspace' import Workspace from './components/Workspace'
import { fileState } from './store/Atoms' import { fileState } from './store/Atoms'
import { keepGUIAlive } from './utils'
// Keeping GUI Window Open // Keeping GUI Window Open
async function getRequest(url = '') { keepGUIAlive()
const response = await fetch(url, {
method: 'GET',
cache: 'no-cache',
})
return response.json()
}
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'production') {
document.addEventListener('DOMContentLoaded', function () {
const url = document.location
const route = '/flaskwebgui-keep-server-alive'
const intervalRequest = 3 * 1000
function keepAliveServer() {
getRequest(url + route).then(data => console.log(data))
}
setInterval(keepAliveServer, intervalRequest)
})
}
function App() { function App() {
const [file, setFile] = useRecoilState(fileState) const [file, setFile] = useRecoilState(fileState)
@ -39,7 +22,8 @@ function App() {
}, [userInputImage, setFile]) }, [userInputImage, setFile])
// Dark Mode Hotkey // Dark Mode Hotkey
useKeyPressEvent('D', () => { useKeyPressEvent('D', ev => {
ev?.preventDefault()
const newTheme = theme === 'light' ? 'dark' : 'light' const newTheme = theme === 'light' ? 'dark' : 'light'
setTheme(newTheme) setTheme(newTheme)
}) })

View File

@ -22,12 +22,28 @@
.editor-canvas { .editor-canvas {
grid-area: editor-content; grid-area: editor-content;
z-index: 2;
} }
.original-image-container { .original-image-container {
grid-area: editor-content; grid-area: editor-content;
pointer-events: none; pointer-events: none;
animation: opacityReveal 350ms ease-in-out; display: grid;
grid-template-areas: 'original-image-content';
img {
grid-area: original-image-content;
}
.editor-slider {
grid-area: original-image-content;
height: 100%;
width: 4px;
justify-self: end;
background-color: var(--yellow-accent);
transition: all 350ms ease-in-out;
z-index: 2;
}
} }
.editor-canvas-loading { .editor-canvas-loading {
@ -40,13 +56,13 @@
bottom: 0; bottom: 0;
padding: 1rem 4rem; padding: 1rem 4rem;
display: grid; display: grid;
// grid-template-columns: repeat(4, max-content);
grid-template-areas: 'toolkit-image-type toolkit-size-selector toolkit-brush-slider toolkit-btns'; grid-template-areas: 'toolkit-image-type toolkit-size-selector toolkit-brush-slider toolkit-btns';
column-gap: 2rem; column-gap: 2rem;
align-items: center; align-items: center;
background-color: var(--editor-toolkit-bg); background-color: var(--editor-toolkit-bg);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border-radius: 0.5rem 0.5rem 0 0; border-radius: 0.5rem 0.5rem 0 0;
animation: slideUp 0.2s ease-out;
@include mobile { @include mobile {
padding: 1rem 2rem; padding: 1rem 2rem;
@ -68,9 +84,7 @@
column-gap: 1rem; column-gap: 1rem;
align-items: center; align-items: center;
input[type='range'] { @include slider-bar;
outline: none;
}
} }
.editor-toolkit-btns { .editor-toolkit-btns {
@ -83,15 +97,11 @@
.brush-shape { .brush-shape {
position: absolute; position: absolute;
border-radius: 50%; border-radius: 50%;
background: rgb(255, 255, 255, 0.25); background: rgba(255, 190, 0, 0.75);
border: 1px dashed var(--border-color); border: 1px dashed var(--border-color);
pointer-events: none; pointer-events: none;
} }
.editor-size-selector {
display: grid;
}
.editor-size-selector-options { .editor-size-selector-options {
position: fixed; position: fixed;
display: grid; display: grid;
@ -103,31 +113,57 @@
grid-template-columns: repeat(2, max-content); grid-template-columns: repeat(2, max-content);
align-items: center; align-items: center;
column-gap: 0.5rem; column-gap: 0.5rem;
}
select { .editor-size-selector-main {
appearance: none; @include accented-display(var(--yellow-accent));
-webkit-appearance: none; display: grid;
-moz-appearance: none; grid-template-columns: repeat(2, max-content);
background: var(--yellow-accent); column-gap: 0.25rem;
outline: none; align-items: center;
border: 1px dashed var(--border-color); cursor: pointer;
outline: none;
svg {
width: 1rem;
height: 1rem;
margin-top: 0.25rem;
}
}
.editor-size-options {
@include accented-display(var(--btn-primary-bg));
padding: 0;
display: grid;
justify-self: center;
margin-left: 2.7rem;
position: fixed;
bottom: 4rem;
@include mobile {
bottom: 11.5rem;
margin-left: 2.9rem;
}
.editor-size-option {
padding: 0.2rem 0.8rem;
border-bottom: 1px dashed var(--border-color);
border-radius: 0.5rem; border-radius: 0.5rem;
font-family: 'WorkSans-Bold';
font-size: 1rem; &:last-of-type {
padding: 0.5rem; border-bottom: none;
text-align: center; }
color: rgb(0, 0, 0);
&:hover {
background-color: var(--yellow-accent);
}
} }
} }
.image-type-tag { .image-type-tag {
@include accented-display(var(--yellow-accent));
grid-area: toolkit-image-type; grid-area: toolkit-image-type;
z-index: 2; z-index: 2;
background-color: var(--yellow-accent);
padding: 0.5rem;
border-radius: 0.5rem;
width: 100px; width: 100px;
text-align: center; text-align: center;
font-family: 'WorkSans-Bold';
color: rgb(0, 0, 0);
} }

View File

@ -15,12 +15,7 @@ import {
TransformComponent, TransformComponent,
TransformWrapper, TransformWrapper,
} from 'react-zoom-pan-pinch' } from 'react-zoom-pan-pinch'
import { import { useWindowSize, useKey, useKeyPressEvent } from 'react-use'
useWindowSize,
useLocalStorage,
useKey,
useKeyPressEvent,
} from 'react-use'
import inpaint from '../../adapters/inpainting' import inpaint from '../../adapters/inpainting'
import Button from '../shared/Button' import Button from '../shared/Button'
import Slider from './Slider' import Slider from './Slider'
@ -28,7 +23,7 @@ import SizeSelector from './SizeSelector'
import { downloadImage, loadImage, useImage } from '../../utils' import { downloadImage, loadImage, useImage } from '../../utils'
const TOOLBAR_SIZE = 200 const TOOLBAR_SIZE = 200
const BRUSH_COLOR = 'rgba(189, 255, 1, 0.75)' const BRUSH_COLOR = 'rgba(255, 190, 0, 0.65)'
// const NO_COLOR = 'rgba(255,255,255,0)' // const NO_COLOR = 'rgba(255,255,255,0)'
interface EditorProps { interface EditorProps {
@ -80,14 +75,15 @@ export default function Editor(props: EditorProps) {
const [isInpaintingLoading, setIsInpaintingLoading] = useState(false) const [isInpaintingLoading, setIsInpaintingLoading] = useState(false)
const [scale, setScale] = useState<number>(1) const [scale, setScale] = useState<number>(1)
const [minScale, setMinScale] = useState<number>() const [minScale, setMinScale] = useState<number>()
// ['1080', '2000', 'Original'] const [sizeLimit, setSizeLimit] = useState<number>(1080)
const [sizeLimit, setSizeLimit] = useLocalStorage('sizeLimit', '1080')
const windowSize = useWindowSize() const windowSize = useWindowSize()
const viewportRef = useRef<ReactZoomPanPinchRef | undefined | null>() const viewportRef = useRef<ReactZoomPanPinchRef | undefined | null>()
const [isDraging, setIsDraging] = useState(false) const [isDraging, setIsDraging] = useState(false)
const [isMultiStrokeKeyPressed, setIsMultiStrokeKeyPressed] = useState(false) const [isMultiStrokeKeyPressed, setIsMultiStrokeKeyPressed] = useState(false)
const [sliderPos, setSliderPos] = useState<number>(0)
const draw = useCallback(() => { const draw = useCallback(() => {
if (!context) { if (!context) {
return return
@ -126,11 +122,14 @@ export default function Editor(props: EditorProps) {
setIsInpaintingLoading(true) setIsInpaintingLoading(true)
refreshCanvasMask() refreshCanvasMask()
try { try {
const res = await inpaint(file, maskCanvas.toDataURL(), sizeLimit) const res = await inpaint(
file,
maskCanvas.toDataURL(),
sizeLimit.toString()
)
if (!res) { if (!res) {
throw new Error('empty response') throw new Error('empty response')
} }
// TODO: fix the render if it failed loading
const newRender = new Image() const newRender = new Image()
await loadImage(newRender, res) await loadImage(newRender, res)
renders.push(newRender) renders.push(newRender)
@ -231,6 +230,9 @@ export default function Editor(props: EditorProps) {
setMinScale(1) setMinScale(1)
} }
const imageSizeLimit = Math.max(original.width, original.height)
setSizeLimit(imageSizeLimit)
if (context?.canvas) { if (context?.canvas) {
context.canvas.width = original.naturalWidth context.canvas.width = original.naturalWidth
context.canvas.height = original.naturalHeight context.canvas.height = original.naturalHeight
@ -239,6 +241,17 @@ export default function Editor(props: EditorProps) {
} }
}, [context?.canvas, draw, original, isOriginalLoaded, windowSize]) }, [context?.canvas, draw, original, isOriginalLoaded, windowSize])
useEffect(() => {
window.addEventListener('resize', () => {
resetZoom()
})
return () => {
window.removeEventListener('resize', () => {
resetZoom()
})
}
}, [windowSize])
// Zoom reset // Zoom reset
const resetZoom = useCallback(() => { const resetZoom = useCallback(() => {
if (!minScale || !original || !windowSize) { if (!minScale || !original || !windowSize) {
@ -410,14 +423,22 @@ export default function Editor(props: EditorProps) {
ev?.preventDefault() ev?.preventDefault()
ev?.stopPropagation() ev?.stopPropagation()
if (hadRunInpainting()) { if (hadRunInpainting()) {
setShowOriginal(true) setShowOriginal(() => {
window.setTimeout(() => {
setSliderPos(100)
}, 10)
return true
})
} }
}, },
ev => { ev => {
ev?.preventDefault() ev?.preventDefault()
ev?.stopPropagation() ev?.stopPropagation()
if (hadRunInpainting()) { if (hadRunInpainting()) {
setShowOriginal(false) setSliderPos(0)
window.setTimeout(() => {
setShowOriginal(false)
}, 350)
} }
} }
) )
@ -427,7 +448,7 @@ export default function Editor(props: EditorProps) {
const currRender = renders[renders.length - 1] const currRender = renders[renders.length - 1]
downloadImage(currRender.currentSrc, name) downloadImage(currRender.currentSrc, name)
} }
const onSizeLimitChange = (_sizeLimit: string) => { const onSizeLimitChange = (_sizeLimit: number) => {
setSizeLimit(_sizeLimit) setSizeLimit(_sizeLimit)
} }
@ -538,7 +559,11 @@ export default function Editor(props: EditorProps) {
<div className="editor-canvas-container"> <div className="editor-canvas-container">
<canvas <canvas
className="editor-canvas" className="editor-canvas"
style={{ cursor: getCursor() }} style={{
cursor: getCursor(),
clipPath: `inset(0 ${sliderPos}% 0 0)`,
transition: 'clip-path 350ms ease-in-out',
}}
onContextMenu={e => { onContextMenu={e => {
e.preventDefault() e.preventDefault()
}} }}
@ -556,25 +581,32 @@ export default function Editor(props: EditorProps) {
} }
}} }}
/> />
{showOriginal ? ( <div
<div className="original-image-container"
className="original-image-container" style={{
width: `${original.naturalWidth}px`,
height: `${original.naturalHeight}px`,
}}
>
{showOriginal && (
<div
className="editor-slider"
style={{
marginRight: `${sliderPos}%`,
}}
/>
)}
<img
className="original-image"
src={original.src}
alt="original"
style={{ style={{
width: `${original.naturalWidth}px`, width: `${original.naturalWidth}px`,
height: `${original.naturalHeight}px`, height: `${original.naturalHeight}px`,
}} }}
> />
<img </div>
className="original-image"
src={original.src}
alt="original"
style={{
width: `${original.naturalWidth}px`,
height: `${original.naturalHeight}px`,
}}
/>
</div>
) : null}
</div> </div>
</TransformComponent> </TransformComponent>
</TransformWrapper> </TransformWrapper>
@ -588,7 +620,6 @@ export default function Editor(props: EditorProps) {
{showOriginal ? 'Original' : 'Inpainted'} {showOriginal ? 'Original' : 'Inpainted'}
</p> </p>
<SizeSelector <SizeSelector
value={sizeLimit || '1080'}
onChange={onSizeLimitChange} onChange={onSizeLimitChange}
originalWidth={original.naturalWidth} originalWidth={original.naturalWidth}
originalHeight={original.naturalHeight} originalHeight={original.naturalHeight}
@ -628,10 +659,18 @@ export default function Editor(props: EditorProps) {
icon={<EyeIcon />} icon={<EyeIcon />}
onDown={ev => { onDown={ev => {
ev.preventDefault() ev.preventDefault()
setShowOriginal(true) setShowOriginal(() => {
window.setTimeout(() => {
setSliderPos(100)
}, 10)
return true
})
}} }}
onUp={() => { onUp={() => {
setShowOriginal(false) setSliderPos(0)
window.setTimeout(() => {
setShowOriginal(false)
}, 350)
}} }}
disabled={renders.length === 0} disabled={renders.length === 0}
> >

View File

@ -1,77 +1,103 @@
import React, { FocusEvent, useCallback, useRef } from 'react' import React, { useCallback, useRef, useState } from 'react'
import ChevronDoubleDownIcon from '@heroicons/react/solid/ChevronDoubleDownIcon'
import { useClickAway } from 'react-use'
const sizes = ['720', '1080', '2000', 'Original'] const sizes = ['720', '1080', '2000', 'Original']
type SizeSelectorProps = { type SizeSelectorProps = {
value: string
originalWidth: number originalWidth: number
originalHeight: number originalHeight: number
onChange: (value: string) => void onChange: (value: number) => void
} }
export default function SizeSelector(props: SizeSelectorProps) { export default function SizeSelector(props: SizeSelectorProps) {
const { value, originalHeight, originalWidth, onChange } = props const { originalHeight, originalWidth, onChange } = props
const selectRef = useRef() const [showOptions, setShowOptions] = useState<boolean>(false)
const sizeSelectorRef = useRef(null)
const [activeSize, setActiveSize] = useState<string>('Original')
const longSide: number = Math.max(originalWidth, originalHeight)
const getSizeShowName = (size: string) => { const getValidSizes = useCallback(() => {
if (size === 'Original') { const validSizes: string[] = []
return `${originalWidth}x${originalHeight}`
}
const length: number = parseInt(size, 10)
const longSide: number =
originalWidth > originalHeight ? originalWidth : originalHeight
const scale = length / longSide
if (originalWidth > originalHeight) {
const newHeight = Math.ceil(scale * originalHeight)
return `${size}x${newHeight}`
}
const newWidth = Math.ceil(scale * originalWidth)
return `${newWidth}x${size}`
}
const onButtonFocus = (e: FocusEvent<any>) => {
e.currentTarget.blur()
}
const getValidSizes = useCallback((): string[] => {
const longSide: number =
originalWidth > originalHeight ? originalWidth : originalHeight
const validSizes = []
for (let i = 0; i < sizes.length; i += 1) { for (let i = 0; i < sizes.length; i += 1) {
const s = sizes[i] if (sizes[i] === 'Original') {
if (s === 'Original') { validSizes.push(sizes[i])
validSizes.push(s) }
} else if (parseInt(s, 10) <= longSide) { if (parseInt(sizes[i], 10) < longSide) {
validSizes.push(s) validSizes.push(sizes[i])
} }
} }
return validSizes return validSizes
}, [originalHeight, originalWidth]) }, [longSide])
const getValidSize = useCallback(() => { const getSizeShowName = useCallback(
if (getValidSizes().indexOf(value) === -1) { (size: string) => {
return getValidSizes()[0] if (size === 'Original') {
} return `${originalWidth}x${originalHeight}`
return value }
}, [value, getValidSizes]) const scale = parseInt(size, 10) / longSide
if (originalWidth > originalHeight) {
const newHeight = Math.ceil(originalHeight * scale)
return `${size}x${newHeight}`
}
const newWidth = Math.ceil(originalWidth * scale)
return `${newWidth}x${size}`
},
[originalWidth, originalHeight, longSide]
)
const showOptionsHandler = () => {
setShowOptions(currentShowOptionsState => !currentShowOptionsState)
}
useClickAway(sizeSelectorRef, () => {
setShowOptions(false)
})
const sizeChangeHandler = (e: any) => { const sizeChangeHandler = (e: any) => {
onChange(e.target.value) const currentRes = e.target.textContent.split('x')
e.target.blur() if (originalWidth > originalHeight) {
setActiveSize(currentRes[0])
onChange(currentRes[0])
} else {
setActiveSize(currentRes[1])
onChange(currentRes[1])
}
setShowOptions(!showOptions)
} }
return ( return (
<div className="editor-size-selector"> <div className="editor-size-selector" ref={sizeSelectorRef}>
<p>Size:</p> <p>Size:</p>
<select value={getValidSize()} onChange={sizeChangeHandler}> <div
{getValidSizes().map(size => ( className="editor-size-selector-main"
<option key={size} value={size}> role="button"
{getSizeShowName(size)} tabIndex={0}
</option> onClick={showOptionsHandler}
))} aria-hidden="true"
</select> >
<p>{getSizeShowName(activeSize.toString())}</p>
<div className="editor-size-selector-chevron">
<ChevronDoubleDownIcon />
</div>
</div>
{showOptions && (
<div className="editor-size-options">
{getValidSizes().map(size => (
<div
className="editor-size-option"
role="button"
tabIndex={0}
key={size}
onClick={sizeChangeHandler}
aria-hidden="true"
>
{getSizeShowName(size)}
</div>
))}
</div>
)}
</div> </div>
) )
} }

View File

@ -14,7 +14,7 @@ const Header = () => {
return ( return (
<header> <header>
<Button <Button
icon={<ArrowLeftIcon className="w-6 h-6" />} icon={<ArrowLeftIcon />}
onClick={() => { onClick={() => {
setFile(undefined) setFile(undefined)
}} }}

View File

@ -1,8 +1,18 @@
@use '../../styles/Mixins/' as *;
.modal-shortcuts { .modal-shortcuts {
grid-area: main-content; grid-area: main-content;
background-color: var(--modal-bg); background-color: var(--modal-bg);
color: var(--modal-text-color); color: var(--modal-text-color);
box-shadow: 0px 0px 20px rgb(0, 0, 40, 0.2); box-shadow: 0px 0px 20px rgb(0, 0, 40, 0.2);
@include mobile {
display: grid;
width: 100%;
height: auto;
margin-top: -11rem;
animation: slideDown 0.2s ease-out;
}
} }
.shortcut-options { .shortcut-options {
@ -14,6 +24,12 @@
grid-template-columns: repeat(2, auto); grid-template-columns: repeat(2, auto);
column-gap: 6rem; column-gap: 6rem;
align-items: center; align-items: center;
@include mobile {
grid-template-columns: auto;
column-gap: 0;
row-gap: 0.6rem;
}
} }
.shortcut-key { .shortcut-key {
@ -22,11 +38,21 @@
padding: 0.4rem 1rem; padding: 0.4rem 1rem;
width: max-content; width: max-content;
border-radius: 0.4rem; border-radius: 0.4rem;
@include mobile {
padding: 0.2rem 0.4rem;
}
} }
.shortcut-description { .shortcut-description {
justify-self: end; justify-self: end;
text-align: right; text-align: right;
width: 15rem; width: 15rem;
@include mobile {
text-align: left;
width: auto;
justify-self: start;
}
} }
} }

View File

@ -13,7 +13,8 @@ const Shortcuts = () => {
}) })
} }
useKeyPressEvent('h', () => { useKeyPressEvent('h', ev => {
ev?.preventDefault()
shortcutStateHandler() shortcutStateHandler()
}) })

View File

@ -1,29 +1,18 @@
.theme-changer { .theme-toggle-ui {
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
background: transparent;
box-shadow: inset 4px 10px 0px rgb(80, 80, 80);
transform: rotate(-75deg);
transition: all 0.2s ease-in;
margin: 1rem;
position: absolute; position: absolute;
right: 1rem; right: 2.5rem;
top: 0.25rem; top: 1rem;
z-index: 10; z-index: 10;
outline: none; transition: all 0.2s ease-in;
user-select: none;
&:hover { .theme-btn {
cursor: pointer; cursor: pointer;
}
}
[data-theme='dark'] {
.theme-changer {
background: rgb(255, 190, 0);
box-shadow: none;
transform: rotate(-75deg);
outline: none; outline: none;
svg {
width: 36px;
height: 36px;
}
} }
} }

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import { atom, useRecoilState } from 'recoil' import { atom, useRecoilState } from 'recoil'
import { SunIcon, MoonIcon } from '@heroicons/react/outline'
export const themeState = atom({ export const themeState = atom({
key: 'themeState', key: 'themeState',
@ -15,11 +16,20 @@ export const ThemeChanger = () => {
} }
return ( return (
<button <div className="theme-toggle-ui">
type="button" <div
className="theme-changer" className="theme-btn"
onClick={themeSwitchHandler} onClick={themeSwitchHandler}
aria-label="Switch Theme" role="button"
/> tabIndex={0}
aria-hidden="true"
>
{theme === 'light' ? (
<MoonIcon />
) : (
<SunIcon style={{ color: 'rgb(255, 190, 0)' }} />
)}
</div>
</div>
) )
} }

View File

@ -0,0 +1,57 @@
@mixin accented-display($bg-color) {
background: $bg-color;
color: rgb(0, 0, 0);
font-family: 'WorkSans-Bold';
padding: 0.5rem;
border-radius: 0.5rem;
}
@mixin slider-bar {
input[type='range'] {
-webkit-appearance: none;
appearance: none;
width: 100%;
cursor: pointer;
background: transparent;
border-color: transparent;
color: transparent;
}
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
}
input[type='range']:focus {
outline: none;
}
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
height: 1.2rem;
width: 1.2rem;
border-radius: 50%;
border: 2px solid rgb(0, 0, 0);
z-index: 2;
background: var(--yellow-accent);
margin-top: -0.5rem;
}
input[type='range']::-webkit-slider-runnable-track {
border-radius: 2rem;
height: 0.2rem;
background: var(--btn-primary-bg);
}
input[type='range']::-moz-range-track {
border-radius: 2rem;
background: var(--btn-primary-bg);
}
input[type='range']::-ms-fill-lower {
background-color: red;
}
input[type='range']::-moz-range-progress {
background: var(--yellow-accent);
}
}

View File

@ -19,3 +19,21 @@
opacity: 1; opacity: 1;
} }
} }
@keyframes slideDown {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0);
}
}
@keyframes slideUp {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}

View File

@ -9,7 +9,7 @@
--border-color: rgb(100, 100, 120); --border-color: rgb(100, 100, 120);
// Editor // Editor
--editor-toolkit-bg: rgb(240, 240, 250, 0.15); --editor-toolkit-bg: rgb(240, 240, 250, 0.5);
// Modal // Modal
--modal-bg: var(--page-bg); --modal-bg: var(--page-bg);

View File

@ -9,7 +9,7 @@
--border-color: rgb(100, 100, 120); --border-color: rgb(100, 100, 120);
// Editor // Editor
--editor-toolkit-bg: rgb(20, 20, 30, 0.15); --editor-toolkit-bg: rgb(20, 20, 30, 0.5);
// Modal // Modal
--modal-bg: var(--page-bg); --modal-bg: var(--page-bg);

View File

@ -156,3 +156,29 @@ export function resizeImageFile(
reader.readAsDataURL(file) reader.readAsDataURL(file)
}) })
} }
export function keepGUIAlive() {
async function getRequest(url = '') {
const response = await fetch(url, {
method: 'GET',
cache: 'no-cache',
})
return response.json()
}
const keepAliveServer = () => {
const url = document.location
const route = '/flaskwebgui-keep-server-alive'
getRequest(url + route).then(data => {
return data
})
}
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'production') {
document.addEventListener('DOMContentLoaded', () => {
const intervalRequest = 3 * 1000
keepAliveServer()
setInterval(keepAliveServer, intervalRequest)
})
}
}