Complete GUI Refactor # Patch 1
This commit is contained in:
parent
eea85b834e
commit
b282421c98
@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -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 { 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)
|
||||||
})
|
})
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
>
|
>
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}}
|
}}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ const Shortcuts = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useKeyPressEvent('h', () => {
|
useKeyPressEvent('h', ev => {
|
||||||
|
ev?.preventDefault()
|
||||||
shortcutStateHandler()
|
shortcutStateHandler()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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;
|
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);
|
--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);
|
||||||
|
@ -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);
|
||||||
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user