Complete GUI Refactor # Patch 1
This commit is contained in:
parent
eea85b834e
commit
b282421c98
@ -1,17 +1,17 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.54fbc69f.chunk.css",
|
||||
"main.js": "/static/js/main.d346743e.chunk.js",
|
||||
"main.css": "/static/css/main.08f8fee9.chunk.css",
|
||||
"main.js": "/static/js/main.4e51b2a1.chunk.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",
|
||||
"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"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.5e86ac81.js",
|
||||
"static/js/2.2516aa7d.chunk.js",
|
||||
"static/css/main.54fbc69f.chunk.css",
|
||||
"static/js/main.d346743e.chunk.js"
|
||||
"static/js/2.9608d3ec.chunk.js",
|
||||
"static/css/main.08f8fee9.chunk.css",
|
||||
"static/js/main.4e51b2a1.chunk.js"
|
||||
]
|
||||
}
|
@ -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
2
lama_cleaner/app/build/static/js/2.9608d3ec.chunk.js
Normal file
2
lama_cleaner/app/build/static/js/2.9608d3ec.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
lama_cleaner/app/build/static/js/main.4e51b2a1.chunk.js
Normal file
1
lama_cleaner/app/build/static/js/main.4e51b2a1.chunk.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user