Complete GUI Refactor # Patch 1
This commit is contained in:
@@ -6,27 +6,10 @@ import LandingPage from './components/LandingPage/LandingPage'
|
||||
import { ThemeChanger, themeState } from './components/shared/ThemeChanger'
|
||||
import Workspace from './components/Workspace'
|
||||
import { fileState } from './store/Atoms'
|
||||
import { keepGUIAlive } from './utils'
|
||||
|
||||
// Keeping GUI Window Open
|
||||
async function getRequest(url = '') {
|
||||
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)
|
||||
})
|
||||
}
|
||||
keepGUIAlive()
|
||||
|
||||
function App() {
|
||||
const [file, setFile] = useRecoilState(fileState)
|
||||
@@ -39,7 +22,8 @@ function App() {
|
||||
}, [userInputImage, setFile])
|
||||
|
||||
// Dark Mode Hotkey
|
||||
useKeyPressEvent('D', () => {
|
||||
useKeyPressEvent('D', ev => {
|
||||
ev?.preventDefault()
|
||||
const newTheme = theme === 'light' ? 'dark' : 'light'
|
||||
setTheme(newTheme)
|
||||
})
|
||||
|
||||
@@ -22,12 +22,28 @@
|
||||
|
||||
.editor-canvas {
|
||||
grid-area: editor-content;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.original-image-container {
|
||||
grid-area: editor-content;
|
||||
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 {
|
||||
@@ -40,13 +56,13 @@
|
||||
bottom: 0;
|
||||
padding: 1rem 4rem;
|
||||
display: grid;
|
||||
// grid-template-columns: repeat(4, max-content);
|
||||
grid-template-areas: 'toolkit-image-type toolkit-size-selector toolkit-brush-slider toolkit-btns';
|
||||
column-gap: 2rem;
|
||||
align-items: center;
|
||||
background-color: var(--editor-toolkit-bg);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
animation: slideUp 0.2s ease-out;
|
||||
|
||||
@include mobile {
|
||||
padding: 1rem 2rem;
|
||||
@@ -68,9 +84,7 @@
|
||||
column-gap: 1rem;
|
||||
align-items: center;
|
||||
|
||||
input[type='range'] {
|
||||
outline: none;
|
||||
}
|
||||
@include slider-bar;
|
||||
}
|
||||
|
||||
.editor-toolkit-btns {
|
||||
@@ -83,15 +97,11 @@
|
||||
.brush-shape {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgb(255, 255, 255, 0.25);
|
||||
background: rgba(255, 190, 0, 0.75);
|
||||
border: 1px dashed var(--border-color);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.editor-size-selector {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.editor-size-selector-options {
|
||||
position: fixed;
|
||||
display: grid;
|
||||
@@ -103,31 +113,57 @@
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
align-items: center;
|
||||
column-gap: 0.5rem;
|
||||
}
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background: var(--yellow-accent);
|
||||
outline: none;
|
||||
border: 1px dashed var(--border-color);
|
||||
.editor-size-selector-main {
|
||||
@include accented-display(var(--yellow-accent));
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
column-gap: 0.25rem;
|
||||
align-items: center;
|
||||
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;
|
||||
font-family: 'WorkSans-Bold';
|
||||
font-size: 1rem;
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
color: rgb(0, 0, 0);
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--yellow-accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-type-tag {
|
||||
@include accented-display(var(--yellow-accent));
|
||||
grid-area: toolkit-image-type;
|
||||
z-index: 2;
|
||||
background-color: var(--yellow-accent);
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
font-family: 'WorkSans-Bold';
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
@@ -15,12 +15,7 @@ import {
|
||||
TransformComponent,
|
||||
TransformWrapper,
|
||||
} from 'react-zoom-pan-pinch'
|
||||
import {
|
||||
useWindowSize,
|
||||
useLocalStorage,
|
||||
useKey,
|
||||
useKeyPressEvent,
|
||||
} from 'react-use'
|
||||
import { useWindowSize, useKey, useKeyPressEvent } from 'react-use'
|
||||
import inpaint from '../../adapters/inpainting'
|
||||
import Button from '../shared/Button'
|
||||
import Slider from './Slider'
|
||||
@@ -28,7 +23,7 @@ import SizeSelector from './SizeSelector'
|
||||
import { downloadImage, loadImage, useImage } from '../../utils'
|
||||
|
||||
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)'
|
||||
|
||||
interface EditorProps {
|
||||
@@ -80,14 +75,15 @@ export default function Editor(props: EditorProps) {
|
||||
const [isInpaintingLoading, setIsInpaintingLoading] = useState(false)
|
||||
const [scale, setScale] = useState<number>(1)
|
||||
const [minScale, setMinScale] = useState<number>()
|
||||
// ['1080', '2000', 'Original']
|
||||
const [sizeLimit, setSizeLimit] = useLocalStorage('sizeLimit', '1080')
|
||||
const [sizeLimit, setSizeLimit] = useState<number>(1080)
|
||||
const windowSize = useWindowSize()
|
||||
const viewportRef = useRef<ReactZoomPanPinchRef | undefined | null>()
|
||||
|
||||
const [isDraging, setIsDraging] = useState(false)
|
||||
const [isMultiStrokeKeyPressed, setIsMultiStrokeKeyPressed] = useState(false)
|
||||
|
||||
const [sliderPos, setSliderPos] = useState<number>(0)
|
||||
|
||||
const draw = useCallback(() => {
|
||||
if (!context) {
|
||||
return
|
||||
@@ -126,11 +122,14 @@ export default function Editor(props: EditorProps) {
|
||||
setIsInpaintingLoading(true)
|
||||
refreshCanvasMask()
|
||||
try {
|
||||
const res = await inpaint(file, maskCanvas.toDataURL(), sizeLimit)
|
||||
const res = await inpaint(
|
||||
file,
|
||||
maskCanvas.toDataURL(),
|
||||
sizeLimit.toString()
|
||||
)
|
||||
if (!res) {
|
||||
throw new Error('empty response')
|
||||
}
|
||||
// TODO: fix the render if it failed loading
|
||||
const newRender = new Image()
|
||||
await loadImage(newRender, res)
|
||||
renders.push(newRender)
|
||||
@@ -231,6 +230,9 @@ export default function Editor(props: EditorProps) {
|
||||
setMinScale(1)
|
||||
}
|
||||
|
||||
const imageSizeLimit = Math.max(original.width, original.height)
|
||||
setSizeLimit(imageSizeLimit)
|
||||
|
||||
if (context?.canvas) {
|
||||
context.canvas.width = original.naturalWidth
|
||||
context.canvas.height = original.naturalHeight
|
||||
@@ -239,6 +241,17 @@ export default function Editor(props: EditorProps) {
|
||||
}
|
||||
}, [context?.canvas, draw, original, isOriginalLoaded, windowSize])
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
resetZoom()
|
||||
})
|
||||
return () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
resetZoom()
|
||||
})
|
||||
}
|
||||
}, [windowSize])
|
||||
|
||||
// Zoom reset
|
||||
const resetZoom = useCallback(() => {
|
||||
if (!minScale || !original || !windowSize) {
|
||||
@@ -410,14 +423,22 @@ export default function Editor(props: EditorProps) {
|
||||
ev?.preventDefault()
|
||||
ev?.stopPropagation()
|
||||
if (hadRunInpainting()) {
|
||||
setShowOriginal(true)
|
||||
setShowOriginal(() => {
|
||||
window.setTimeout(() => {
|
||||
setSliderPos(100)
|
||||
}, 10)
|
||||
return true
|
||||
})
|
||||
}
|
||||
},
|
||||
ev => {
|
||||
ev?.preventDefault()
|
||||
ev?.stopPropagation()
|
||||
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]
|
||||
downloadImage(currRender.currentSrc, name)
|
||||
}
|
||||
const onSizeLimitChange = (_sizeLimit: string) => {
|
||||
const onSizeLimitChange = (_sizeLimit: number) => {
|
||||
setSizeLimit(_sizeLimit)
|
||||
}
|
||||
|
||||
@@ -538,7 +559,11 @@ export default function Editor(props: EditorProps) {
|
||||
<div className="editor-canvas-container">
|
||||
<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 => {
|
||||
e.preventDefault()
|
||||
}}
|
||||
@@ -556,25 +581,32 @@ export default function Editor(props: EditorProps) {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{showOriginal ? (
|
||||
<div
|
||||
className="original-image-container"
|
||||
<div
|
||||
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={{
|
||||
width: `${original.naturalWidth}px`,
|
||||
height: `${original.naturalHeight}px`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className="original-image"
|
||||
src={original.src}
|
||||
alt="original"
|
||||
style={{
|
||||
width: `${original.naturalWidth}px`,
|
||||
height: `${original.naturalHeight}px`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TransformComponent>
|
||||
</TransformWrapper>
|
||||
@@ -588,7 +620,6 @@ export default function Editor(props: EditorProps) {
|
||||
{showOriginal ? 'Original' : 'Inpainted'}
|
||||
</p>
|
||||
<SizeSelector
|
||||
value={sizeLimit || '1080'}
|
||||
onChange={onSizeLimitChange}
|
||||
originalWidth={original.naturalWidth}
|
||||
originalHeight={original.naturalHeight}
|
||||
@@ -628,10 +659,18 @@ export default function Editor(props: EditorProps) {
|
||||
icon={<EyeIcon />}
|
||||
onDown={ev => {
|
||||
ev.preventDefault()
|
||||
setShowOriginal(true)
|
||||
setShowOriginal(() => {
|
||||
window.setTimeout(() => {
|
||||
setSliderPos(100)
|
||||
}, 10)
|
||||
return true
|
||||
})
|
||||
}}
|
||||
onUp={() => {
|
||||
setShowOriginal(false)
|
||||
setSliderPos(0)
|
||||
window.setTimeout(() => {
|
||||
setShowOriginal(false)
|
||||
}, 350)
|
||||
}}
|
||||
disabled={renders.length === 0}
|
||||
>
|
||||
|
||||
@@ -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']
|
||||
|
||||
type SizeSelectorProps = {
|
||||
value: string
|
||||
originalWidth: number
|
||||
originalHeight: number
|
||||
onChange: (value: string) => void
|
||||
onChange: (value: number) => void
|
||||
}
|
||||
|
||||
export default function SizeSelector(props: SizeSelectorProps) {
|
||||
const { value, originalHeight, originalWidth, onChange } = props
|
||||
const selectRef = useRef()
|
||||
const { originalHeight, originalWidth, onChange } = props
|
||||
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) => {
|
||||
if (size === 'Original') {
|
||||
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 = []
|
||||
const getValidSizes = useCallback(() => {
|
||||
const validSizes: string[] = []
|
||||
for (let i = 0; i < sizes.length; i += 1) {
|
||||
const s = sizes[i]
|
||||
if (s === 'Original') {
|
||||
validSizes.push(s)
|
||||
} else if (parseInt(s, 10) <= longSide) {
|
||||
validSizes.push(s)
|
||||
if (sizes[i] === 'Original') {
|
||||
validSizes.push(sizes[i])
|
||||
}
|
||||
if (parseInt(sizes[i], 10) < longSide) {
|
||||
validSizes.push(sizes[i])
|
||||
}
|
||||
}
|
||||
return validSizes
|
||||
}, [originalHeight, originalWidth])
|
||||
}, [longSide])
|
||||
|
||||
const getValidSize = useCallback(() => {
|
||||
if (getValidSizes().indexOf(value) === -1) {
|
||||
return getValidSizes()[0]
|
||||
}
|
||||
return value
|
||||
}, [value, getValidSizes])
|
||||
const getSizeShowName = useCallback(
|
||||
(size: string) => {
|
||||
if (size === 'Original') {
|
||||
return `${originalWidth}x${originalHeight}`
|
||||
}
|
||||
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) => {
|
||||
onChange(e.target.value)
|
||||
e.target.blur()
|
||||
const currentRes = e.target.textContent.split('x')
|
||||
if (originalWidth > originalHeight) {
|
||||
setActiveSize(currentRes[0])
|
||||
onChange(currentRes[0])
|
||||
} else {
|
||||
setActiveSize(currentRes[1])
|
||||
onChange(currentRes[1])
|
||||
}
|
||||
setShowOptions(!showOptions)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="editor-size-selector">
|
||||
<div className="editor-size-selector" ref={sizeSelectorRef}>
|
||||
<p>Size:</p>
|
||||
<select value={getValidSize()} onChange={sizeChangeHandler}>
|
||||
{getValidSizes().map(size => (
|
||||
<option key={size} value={size}>
|
||||
{getSizeShowName(size)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div
|
||||
className="editor-size-selector-main"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={showOptionsHandler}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const Header = () => {
|
||||
return (
|
||||
<header>
|
||||
<Button
|
||||
icon={<ArrowLeftIcon className="w-6 h-6" />}
|
||||
icon={<ArrowLeftIcon />}
|
||||
onClick={() => {
|
||||
setFile(undefined)
|
||||
}}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
@use '../../styles/Mixins/' as *;
|
||||
|
||||
.modal-shortcuts {
|
||||
grid-area: main-content;
|
||||
background-color: var(--modal-bg);
|
||||
color: var(--modal-text-color);
|
||||
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 {
|
||||
@@ -14,6 +24,12 @@
|
||||
grid-template-columns: repeat(2, auto);
|
||||
column-gap: 6rem;
|
||||
align-items: center;
|
||||
|
||||
@include mobile {
|
||||
grid-template-columns: auto;
|
||||
column-gap: 0;
|
||||
row-gap: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-key {
|
||||
@@ -22,11 +38,21 @@
|
||||
padding: 0.4rem 1rem;
|
||||
width: max-content;
|
||||
border-radius: 0.4rem;
|
||||
|
||||
@include mobile {
|
||||
padding: 0.2rem 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-description {
|
||||
justify-self: end;
|
||||
text-align: right;
|
||||
width: 15rem;
|
||||
|
||||
@include mobile {
|
||||
text-align: left;
|
||||
width: auto;
|
||||
justify-self: start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ const Shortcuts = () => {
|
||||
})
|
||||
}
|
||||
|
||||
useKeyPressEvent('h', () => {
|
||||
useKeyPressEvent('h', ev => {
|
||||
ev?.preventDefault()
|
||||
shortcutStateHandler()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,29 +1,18 @@
|
||||
.theme-changer {
|
||||
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;
|
||||
.theme-toggle-ui {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 0.25rem;
|
||||
right: 2.5rem;
|
||||
top: 1rem;
|
||||
z-index: 10;
|
||||
outline: none;
|
||||
transition: all 0.2s ease-in;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
.theme-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
.theme-changer {
|
||||
background: rgb(255, 190, 0);
|
||||
box-shadow: none;
|
||||
transform: rotate(-75deg);
|
||||
outline: none;
|
||||
|
||||
svg {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import { atom, useRecoilState } from 'recoil'
|
||||
import { SunIcon, MoonIcon } from '@heroicons/react/outline'
|
||||
|
||||
export const themeState = atom({
|
||||
key: 'themeState',
|
||||
@@ -15,11 +16,20 @@ export const ThemeChanger = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="theme-changer"
|
||||
onClick={themeSwitchHandler}
|
||||
aria-label="Switch Theme"
|
||||
/>
|
||||
<div className="theme-toggle-ui">
|
||||
<div
|
||||
className="theme-btn"
|
||||
onClick={themeSwitchHandler}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{theme === 'light' ? (
|
||||
<MoonIcon />
|
||||
) : (
|
||||
<SunIcon style={{ color: 'rgb(255, 190, 0)' }} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,3 +19,21 @@
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
--border-color: rgb(100, 100, 120);
|
||||
|
||||
// Editor
|
||||
--editor-toolkit-bg: rgb(240, 240, 250, 0.15);
|
||||
--editor-toolkit-bg: rgb(240, 240, 250, 0.5);
|
||||
|
||||
// Modal
|
||||
--modal-bg: var(--page-bg);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
--border-color: rgb(100, 100, 120);
|
||||
|
||||
// Editor
|
||||
--editor-toolkit-bg: rgb(20, 20, 30, 0.15);
|
||||
--editor-toolkit-bg: rgb(20, 20, 30, 0.5);
|
||||
|
||||
// Modal
|
||||
--modal-bg: var(--page-bg);
|
||||
|
||||
@@ -156,3 +156,29 @@ export function resizeImageFile(
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user