commit
3fc0008f0a
@ -55,7 +55,7 @@ Frontend code are modified from [cleanup.pictures](https://github.com/initml/cle
|
||||
great online services [here](https://cleanup.pictures/).
|
||||
|
||||
- Install dependencies:`cd lama_cleaner/app/ && yarn`
|
||||
- Start development server: `yarn dev`
|
||||
- Start development server: `yarn start`
|
||||
- Build: `yarn build`
|
||||
|
||||
## Docker
|
||||
|
@ -1,16 +1,17 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.1144a0ea.chunk.css",
|
||||
"main.js": "/static/js/main.98890b3e.chunk.js",
|
||||
"main.css": "/static/css/main.ba23cb34.chunk.css",
|
||||
"main.js": "/static/js/main.03e0a3ad.chunk.js",
|
||||
"runtime-main.js": "/static/js/runtime-main.5e86ac81.js",
|
||||
"static/js/2.d3149f41.chunk.js": "/static/js/2.d3149f41.chunk.js",
|
||||
"static/js/2.8c938027.chunk.js": "/static/js/2.8c938027.chunk.js",
|
||||
"index.html": "/index.html",
|
||||
"static/js/2.d3149f41.chunk.js.LICENSE.txt": "/static/js/2.d3149f41.chunk.js.LICENSE.txt"
|
||||
"static/js/2.8c938027.chunk.js.LICENSE.txt": "/static/js/2.8c938027.chunk.js.LICENSE.txt",
|
||||
"static/media/_index.scss": "/static/media/WorkSans-SemiBold.1e98db4e.ttf"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.5e86ac81.js",
|
||||
"static/js/2.d3149f41.chunk.js",
|
||||
"static/css/main.1144a0ea.chunk.css",
|
||||
"static/js/main.98890b3e.chunk.js"
|
||||
"static/js/2.8c938027.chunk.js",
|
||||
"static/css/main.ba23cb34.chunk.css",
|
||||
"static/js/main.03e0a3ad.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.1144a0ea.chunk.css" rel="stylesheet"></head><body class="h-screen"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="h-full"></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.d3149f41.chunk.js"></script><script src="/static/js/main.98890b3e.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.ba23cb34.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.8c938027.chunk.js"></script><script src="/static/js/main.03e0a3ad.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
2
lama_cleaner/app/build/static/js/2.8c938027.chunk.js
Normal file
2
lama_cleaner/app/build/static/js/2.8c938027.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
1
lama_cleaner/app/build/static/js/main.03e0a3ad.chunk.js
Normal file
1
lama_cleaner/app/build/static/js/main.03e0a3ad.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
BIN
lama_cleaner/app/build/static/media/WorkSans-Black.67c2c5a1.ttf
Normal file
BIN
lama_cleaner/app/build/static/media/WorkSans-Black.67c2c5a1.ttf
Normal file
Binary file not shown.
BIN
lama_cleaner/app/build/static/media/WorkSans-Bold.2bea7a7f.ttf
Normal file
BIN
lama_cleaner/app/build/static/media/WorkSans-Bold.2bea7a7f.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -4,7 +4,6 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:8080",
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.4.2",
|
||||
"@heroicons/react": "^1.0.4",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.1.2",
|
||||
@ -13,30 +12,21 @@
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/react": "^17.0.30",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"autoprefixer": "10.x",
|
||||
"cross-env": "7.x",
|
||||
"delay-cli": "^1.1.0",
|
||||
"npm-run-all": "4.x",
|
||||
"postcss": "8.x",
|
||||
"postcss-cli": "8.x",
|
||||
"postcss-preset-env": "6.x",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"react-use": "^17.3.1",
|
||||
"react-zoom-pan-pinch": "^2.1.3",
|
||||
"tailwindcss": "2.x",
|
||||
"recoil": "^0.6.1",
|
||||
"typescript": "4.x"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "run-p watch:css react-scripts:start",
|
||||
"build": "run-s build:css react-scripts:build",
|
||||
"start": "react-scripts start",
|
||||
"build": "cross-env GENERATE_SOURCEMAP=false react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"build:css": "cross-env TAILWIND_MODE=build NODE_ENV=production postcss src/styles/tailwind.css -o src/styles/index.css",
|
||||
"watch:css": "cross-env TAILWIND_MODE=watch NODE_ENV=development postcss src/styles/tailwind.css -o src/styles/index.css --watch",
|
||||
"react-scripts:start": "delay 5 && react-scripts start",
|
||||
"react-scripts:build": "cross-env GENERATE_SOURCEMAP=false react-scripts build"
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
@ -62,6 +52,7 @@
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.26.1",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"prettier": "^2.4.1"
|
||||
"prettier": "^2.4.1",
|
||||
"sass": "^1.49.9"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('tailwindcss'),
|
||||
require('postcss-preset-env'),
|
||||
],
|
||||
};
|
@ -19,9 +19,9 @@
|
||||
-->
|
||||
<title>lama-cleaner - Image inpainting powered by LaMa</title>
|
||||
</head>
|
||||
<body class="h-screen">
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root" class="h-full"></div>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
if (location.hostname === 'localhost') {
|
||||
self.FIREBASE_APPCHECK_DEBUG_TOKEN = true
|
||||
|
@ -1,133 +1,38 @@
|
||||
import { ArrowLeftIcon } from '@heroicons/react/outline'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useToggle, useWindowSize } from 'react-use'
|
||||
import Button from './components/Button'
|
||||
import FileSelect from './components/FileSelect'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useKeyPressEvent } from 'react-use'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import useInputImage from './hooks/useInputImage'
|
||||
import ShortcutsModal from './components/ShortcutsModal'
|
||||
import Editor from './Editor'
|
||||
import LandingPage from './components/LandingPage/LandingPage'
|
||||
import { themeState } from './components/Header/ThemeChanger'
|
||||
import Workspace from './components/Workspace'
|
||||
import { fileState } from './store/Atoms'
|
||||
import { keepGUIAlive } from './utils'
|
||||
import Header from './components/Header/Header'
|
||||
|
||||
// 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] = useState<File>()
|
||||
const [showShortcuts, toggleShowShortcuts] = useToggle(false)
|
||||
const windowSize = useWindowSize()
|
||||
const [file, setFile] = useRecoilState(fileState)
|
||||
const [theme, setTheme] = useRecoilState(themeState)
|
||||
const userInputImage = useInputImage()
|
||||
|
||||
// Set Input Image
|
||||
useEffect(() => {
|
||||
setFile(userInputImage)
|
||||
}, [userInputImage])
|
||||
}, [userInputImage, setFile])
|
||||
|
||||
// Dark Mode Hotkey
|
||||
useKeyPressEvent('D', ev => {
|
||||
ev?.preventDefault()
|
||||
const newTheme = theme === 'light' ? 'dark' : 'light'
|
||||
setTheme(newTheme)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="h-full full-visible-h-safari flex flex-col">
|
||||
<header className="absolute z-10 flex w-full p-1 justify-center sm:justify-between items-center sm:items-start bg-white backdrop-blur backdrop-filter bg-opacity-30">
|
||||
{file ? (
|
||||
<Button
|
||||
icon={<ArrowLeftIcon className="w-6 h-6" />}
|
||||
onClick={() => {
|
||||
setFile(undefined)
|
||||
}}
|
||||
>
|
||||
{windowSize.width > 640 ? 'Start new' : undefined}
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{file ? (
|
||||
<Button
|
||||
onClick={toggleShowShortcuts}
|
||||
icon={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
width="28"
|
||||
height="28"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="none"
|
||||
stroke="none"
|
||||
/>
|
||||
<g fill="currentColor">
|
||||
<path d="M14 5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h12zM2 4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H2z" />
|
||||
<path d="M13 10.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm0-2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-5 0A.25.25 0 0 1 8.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 8 8.75v-.5zm2 0a.25.25 0 0 1 .25-.25h1.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-1.5a.25.25 0 0 1-.25-.25v-.5zm1 2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-5-2A.25.25 0 0 1 6.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 6 8.75v-.5zm-2 0A.25.25 0 0 1 4.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 4 8.75v-.5zm-2 0A.25.25 0 0 1 2.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 2 8.75v-.5zm11-2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-2 0a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-2 0A.25.25 0 0 1 9.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 9 6.75v-.5zm-2 0A.25.25 0 0 1 7.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 7 6.75v-.5zm-2 0A.25.25 0 0 1 5.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 5 6.75v-.5zm-3 0A.25.25 0 0 1 2.25 6h1.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-1.5A.25.25 0 0 1 2 6.75v-.5zm0 4a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm2 0a.25.25 0 0 1 .25-.25h5.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-5.5a.25.25 0 0 1-.25-.25v-.5z" />
|
||||
</g>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{showShortcuts && <ShortcutsModal onClose={toggleShowShortcuts} />}
|
||||
|
||||
<main
|
||||
className={[
|
||||
'h-full flex flex-1 flex-col sm:items-center sm:justify-center overflow-hidden',
|
||||
'items-center justify-center',
|
||||
].join(' ')}
|
||||
>
|
||||
{file ? (
|
||||
<Editor file={file} />
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={[
|
||||
'flex flex-col sm:flex-row items-center',
|
||||
'space-y-5 sm:space-y-0 sm:space-x-6 p-5 pt-0 pb-10',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="max-w-xl flex flex-col items-center sm:items-start p-0 m-0 space-y-5">
|
||||
<h1 className="text-center sm:text-left text-xl sm:text-3xl">
|
||||
Image inpainting powered by 🦙
|
||||
<u>
|
||||
<a href="https://github.com/saic-mdal/lama">LaMa</a>
|
||||
</u>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="h-20 sm:h-52 px-4 w-full"
|
||||
style={{ maxWidth: '800px' }}
|
||||
>
|
||||
<FileSelect
|
||||
onSelection={async f => {
|
||||
setFile(f)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
<div className="lama-cleaner" data-theme={theme}>
|
||||
<Header />
|
||||
{file ? <Workspace file={file} /> : <LandingPage />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
190
lama_cleaner/app/src/components/Editor/Editor.scss
Normal file
190
lama_cleaner/app/src/components/Editor/Editor.scss
Normal file
@ -0,0 +1,190 @@
|
||||
@use '../../styles/Mixins' as *;
|
||||
|
||||
.editor-container {
|
||||
grid-area: main-content;
|
||||
display: grid;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.react-transform-wrapper {
|
||||
display: grid !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.editor-canvas-container {
|
||||
display: grid;
|
||||
grid-template-areas: 'editor-content';
|
||||
row-gap: 1rem;
|
||||
}
|
||||
|
||||
.editor-canvas {
|
||||
grid-area: editor-content;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.original-image-container {
|
||||
grid-area: editor-content;
|
||||
pointer-events: none;
|
||||
display: grid;
|
||||
grid-template-areas: 'original-image-content';
|
||||
|
||||
img {
|
||||
grid-area: original-image-content;
|
||||
}
|
||||
|
||||
.editor-slider {
|
||||
grid-area: original-image-content;
|
||||
height: 100%;
|
||||
width: 6px;
|
||||
justify-self: end;
|
||||
background-color: var(--yellow-accent);
|
||||
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-canvas-loading {
|
||||
pointer-events: none;
|
||||
animation: pulsing 750ms infinite;
|
||||
}
|
||||
|
||||
.editor-toolkit-panel {
|
||||
// width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0.5rem;
|
||||
// border: 1px solid rgb(100, 100, 120, 0.5);
|
||||
border-radius: 3rem;
|
||||
padding: 1rem 3rem;
|
||||
display: grid;
|
||||
grid-template-areas: 'toolkit-size-selector toolkit-brush-slider toolkit-btns';
|
||||
column-gap: 2rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--editor-toolkit-bg);
|
||||
backdrop-filter: blur(12px);
|
||||
animation: slideUp 0.2s ease-out;
|
||||
border: 1px solid rgb(100, 100, 120, 0.4);
|
||||
|
||||
@include mobile {
|
||||
padding: 1rem 2rem;
|
||||
grid-template-areas:
|
||||
'toolkit-size-selector toolkit-size-selector'
|
||||
'toolkit-brush-slider toolkit-brush-slider'
|
||||
'toolkit-btns toolkit-btns';
|
||||
row-gap: 2rem;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.eyeicon-active {
|
||||
background-color: var(--yellow-accent);
|
||||
color: var(--btn-text-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
.editor-brush-slider {
|
||||
grid-area: toolkit-brush-slider;
|
||||
user-select: none;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
height: max-content;
|
||||
column-gap: 1rem;
|
||||
align-items: center;
|
||||
|
||||
@include slider-bar;
|
||||
}
|
||||
|
||||
.editor-toolkit-btns {
|
||||
grid-area: toolkit-btns;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.brush-shape {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-color: #ffcc00bb;
|
||||
border: 1px solid var(--yellow-accent);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.editor-size-selector-options {
|
||||
position: fixed;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.editor-size-selector {
|
||||
grid-area: toolkit-size-selector;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.editor-size-selector-main {
|
||||
@include accented-display(var(white));
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
gap: 8px;
|
||||
width: 128px;
|
||||
|
||||
border: 1px solid var(--editor-size-border-color);
|
||||
color: var(--options-text-color);
|
||||
|
||||
svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-size-options {
|
||||
@include accented-display(var(--btn-primary-bg));
|
||||
width: 128px;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
justify-self: center;
|
||||
position: fixed;
|
||||
bottom: 4rem;
|
||||
cursor: pointer;
|
||||
|
||||
color: var(--options-text-color);
|
||||
background-color: var(--page-bg);
|
||||
border: 1px solid var(--editor-size-border-color);
|
||||
|
||||
border-radius: 0.6rem;
|
||||
|
||||
@include mobile {
|
||||
bottom: 11.5rem;
|
||||
}
|
||||
|
||||
.editor-size-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
user-select: none;
|
||||
padding: 0.2rem 0.8rem;
|
||||
|
||||
|
||||
&:first-of-type {
|
||||
border-top-right-radius: 0.5rem;
|
||||
border-top-left-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
border-bottom-right-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--yellow-accent);
|
||||
color: var(--btn-text-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,21 +15,15 @@ import {
|
||||
TransformComponent,
|
||||
TransformWrapper,
|
||||
} from 'react-zoom-pan-pinch'
|
||||
import {
|
||||
useWindowSize,
|
||||
useLocalStorage,
|
||||
useKey,
|
||||
useKeyPressEvent,
|
||||
} from 'react-use'
|
||||
import inpaint from './adapters/inpainting'
|
||||
import Button from './components/Button'
|
||||
import Slider from './components/Slider'
|
||||
import SizeSelector from './components/SizeSelector'
|
||||
import { downloadImage, loadImage, useImage } from './utils'
|
||||
import { useWindowSize, useKey, useKeyPressEvent } from 'react-use'
|
||||
import inpaint from '../../adapters/inpainting'
|
||||
import Button from '../shared/Button'
|
||||
import Slider from './Slider'
|
||||
import SizeSelector from './SizeSelector'
|
||||
import { downloadImage, loadImage, useImage } from '../../utils'
|
||||
|
||||
const TOOLBAR_SIZE = 200
|
||||
const BRUSH_COLOR = 'rgba(189, 255, 1, 0.75)'
|
||||
// const NO_COLOR = 'rgba(255,255,255,0)'
|
||||
const BRUSH_COLOR = '#ffcc00bb'
|
||||
|
||||
interface EditorProps {
|
||||
file: File
|
||||
@ -78,17 +72,17 @@ export default function Editor(props: EditorProps) {
|
||||
const [isPanning, setIsPanning] = useState<boolean>(false)
|
||||
const [showOriginal, setShowOriginal] = useState(false)
|
||||
const [isInpaintingLoading, setIsInpaintingLoading] = useState(false)
|
||||
const [showSeparator, setShowSeparator] = 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
|
||||
@ -127,11 +121,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)
|
||||
@ -232,6 +229,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
|
||||
@ -240,6 +240,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) {
|
||||
@ -411,16 +422,22 @@ export default function Editor(props: EditorProps) {
|
||||
ev?.preventDefault()
|
||||
ev?.stopPropagation()
|
||||
if (hadRunInpainting()) {
|
||||
setShowSeparator(true)
|
||||
setShowOriginal(true)
|
||||
setShowOriginal(() => {
|
||||
window.setTimeout(() => {
|
||||
setSliderPos(100)
|
||||
}, 10)
|
||||
return true
|
||||
})
|
||||
}
|
||||
},
|
||||
ev => {
|
||||
ev?.preventDefault()
|
||||
ev?.stopPropagation()
|
||||
if (hadRunInpainting()) {
|
||||
setShowOriginal(false)
|
||||
setTimeout(() => setShowSeparator(false), 300)
|
||||
setSliderPos(0)
|
||||
window.setTimeout(() => {
|
||||
setShowOriginal(false)
|
||||
}, 350)
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -430,8 +447,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)
|
||||
}
|
||||
|
||||
@ -512,11 +528,7 @@ export default function Editor(props: EditorProps) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col items-center"
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
className="editor-container"
|
||||
aria-hidden="true"
|
||||
onMouseMove={onMouseMove}
|
||||
onMouseUp={onPointerUp}
|
||||
@ -541,20 +553,16 @@ export default function Editor(props: EditorProps) {
|
||||
}}
|
||||
>
|
||||
<TransformComponent
|
||||
wrapperStyle={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
contentClass={
|
||||
isInpaintingLoading
|
||||
? 'animate-pulse-fast pointer-events-none transition-opacity'
|
||||
: ''
|
||||
}
|
||||
contentClass={isInpaintingLoading ? 'editor-canvas-loading' : ''}
|
||||
>
|
||||
<>
|
||||
<div className="editor-canvas-container">
|
||||
<canvas
|
||||
className="rounded-sm"
|
||||
style={{ cursor: getCursor() }}
|
||||
className="editor-canvas"
|
||||
style={{
|
||||
cursor: getCursor(),
|
||||
clipPath: `inset(0 ${sliderPos}% 0 0)`,
|
||||
transition: 'clip-path 350ms ease-in-out',
|
||||
}}
|
||||
onContextMenu={e => {
|
||||
e.preventDefault()
|
||||
}}
|
||||
@ -573,138 +581,105 @@ export default function Editor(props: EditorProps) {
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={[
|
||||
'absolute top-0 right-0 pointer-events-none',
|
||||
'overflow-hidden',
|
||||
'border-primary',
|
||||
showSeparator ? 'border-l-4' : '',
|
||||
].join(' ')}
|
||||
className="original-image-container"
|
||||
style={{
|
||||
width: showOriginal
|
||||
? `${Math.round(original.naturalWidth)}px`
|
||||
: '0px',
|
||||
height: original.naturalHeight,
|
||||
transitionProperty: 'width, height',
|
||||
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transitionDuration: '300ms',
|
||||
width: `${original.naturalWidth}px`,
|
||||
height: `${original.naturalHeight}px`,
|
||||
}}
|
||||
>
|
||||
{showOriginal && (
|
||||
<div
|
||||
className="editor-slider"
|
||||
style={{
|
||||
marginRight: `${sliderPos}%`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<img
|
||||
className="absolute right-0"
|
||||
className="original-image"
|
||||
src={original.src}
|
||||
alt="original"
|
||||
width={`${original.naturalWidth}px`}
|
||||
height={`${original.naturalHeight}px`}
|
||||
style={{
|
||||
width: `${original.naturalWidth}px`,
|
||||
height: `${original.naturalHeight}px`,
|
||||
maxWidth: 'none',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</TransformComponent>
|
||||
</TransformWrapper>
|
||||
|
||||
{showBrush && !isInpaintingLoading && !isPanning && (
|
||||
<div
|
||||
className="hidden sm:block absolute rounded-full border border-primary bg-primary bg-opacity-80 pointer-events-none"
|
||||
style={getBrushStyle()}
|
||||
/>
|
||||
<div className="brush-shape" style={getBrushStyle()} />
|
||||
)}
|
||||
|
||||
<div
|
||||
className="fixed w-full bottom-0 flex items-center justify-center"
|
||||
style={{ height: '90px' }}
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
'flex items-center justify-center space-x-6',
|
||||
'',
|
||||
// 'bg-black backdrop-blur backdrop-filter bg-opacity-10',
|
||||
].join(' ')}
|
||||
>
|
||||
<SizeSelector
|
||||
value={sizeLimit || '1080'}
|
||||
onChange={onSizeLimitChange}
|
||||
originalWidth={original.naturalWidth}
|
||||
originalHeight={original.naturalHeight}
|
||||
<div className="editor-toolkit-panel">
|
||||
<SizeSelector
|
||||
onChange={onSizeLimitChange}
|
||||
originalWidth={original.naturalWidth}
|
||||
originalHeight={original.naturalHeight}
|
||||
/>
|
||||
<Slider
|
||||
label="Brush"
|
||||
min={10}
|
||||
max={150}
|
||||
value={brushSize}
|
||||
onChange={setBrushSize}
|
||||
/>
|
||||
<div className="editor-toolkit-btns">
|
||||
<Button
|
||||
icon={<ArrowsExpandIcon />}
|
||||
disabled={scale === minScale}
|
||||
onClick={resetZoom}
|
||||
/>
|
||||
<Slider
|
||||
label={
|
||||
<span>
|
||||
<span className="hidden md:inline">Brush</span>
|
||||
</span>
|
||||
<Button
|
||||
icon={
|
||||
<svg
|
||||
width="19"
|
||||
height="9"
|
||||
viewBox="0 0 19 9"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1H2ZM1 8H0V9H1V8ZM8 9C8.55228 9 9 8.55229 9 8C9 7.44771 8.55228 7 8 7V9ZM16.5963 7.42809C16.8327 7.92721 17.429 8.14016 17.9281 7.90374C18.4272 7.66731 18.6402 7.07103 18.4037 6.57191L16.5963 7.42809ZM16.9468 5.83205L17.8505 5.40396L16.9468 5.83205ZM0 1V8H2V1H0ZM1 9H8V7H1V9ZM1.66896 8.74329L6.66896 4.24329L5.33104 2.75671L0.331035 7.25671L1.66896 8.74329ZM16.043 6.26014L16.5963 7.42809L18.4037 6.57191L17.8505 5.40396L16.043 6.26014ZM6.65079 4.25926C9.67554 1.66661 14.3376 2.65979 16.043 6.26014L17.8505 5.40396C15.5805 0.61182 9.37523 -0.710131 5.34921 2.74074L6.65079 4.25926Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
min={10}
|
||||
max={150}
|
||||
value={brushSize}
|
||||
onChange={setBrushSize}
|
||||
onClick={undo}
|
||||
disabled={renders.length === 0}
|
||||
/>
|
||||
<div>
|
||||
<Button
|
||||
className="mr-2"
|
||||
icon={<ArrowsExpandIcon className="w-6 h-6" />}
|
||||
disabled={scale === minScale}
|
||||
onClick={resetZoom}
|
||||
/>
|
||||
<Button
|
||||
className="mr-2"
|
||||
icon={
|
||||
<svg
|
||||
width="19"
|
||||
height="9"
|
||||
viewBox="0 0 19 9"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-6 h-6"
|
||||
>
|
||||
<path
|
||||
d="M2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1H2ZM1 8H0V9H1V8ZM8 9C8.55228 9 9 8.55229 9 8C9 7.44771 8.55228 7 8 7V9ZM16.5963 7.42809C16.8327 7.92721 17.429 8.14016 17.9281 7.90374C18.4272 7.66731 18.6402 7.07103 18.4037 6.57191L16.5963 7.42809ZM16.9468 5.83205L17.8505 5.40396L16.9468 5.83205ZM0 1V8H2V1H0ZM1 9H8V7H1V9ZM1.66896 8.74329L6.66896 4.24329L5.33104 2.75671L0.331035 7.25671L1.66896 8.74329ZM16.043 6.26014L16.5963 7.42809L18.4037 6.57191L17.8505 5.40396L16.043 6.26014ZM6.65079 4.25926C9.67554 1.66661 14.3376 2.65979 16.043 6.26014L17.8505 5.40396C15.5805 0.61182 9.37523 -0.710131 5.34921 2.74074L6.65079 4.25926Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
onClick={undo}
|
||||
disabled={renders.length === 0}
|
||||
/>
|
||||
<Button
|
||||
className="mr-2"
|
||||
icon={<EyeIcon className="w-6 h-6" />}
|
||||
onDown={ev => {
|
||||
ev.preventDefault()
|
||||
setShowSeparator(true)
|
||||
setShowOriginal(true)
|
||||
}}
|
||||
onUp={() => {
|
||||
setShowOriginal(false)
|
||||
setTimeout(() => setShowSeparator(false), 300)
|
||||
}}
|
||||
disabled={renders.length === 0}
|
||||
>
|
||||
{undefined}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
icon={<DownloadIcon className="w-6 h-6" />}
|
||||
disabled={!renders.length}
|
||||
onClick={download}
|
||||
>
|
||||
{undefined}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute bg-black backdrop-blur backdrop-filter bg-opacity-10 rounded-xl"
|
||||
style={{
|
||||
height: '58px',
|
||||
width: '600px',
|
||||
zIndex: -1,
|
||||
marginLeft: '-1px',
|
||||
<Button
|
||||
icon={<EyeIcon />}
|
||||
className={showOriginal ? 'eyeicon-active' : ''}
|
||||
onDown={ev => {
|
||||
ev.preventDefault()
|
||||
setShowOriginal(() => {
|
||||
window.setTimeout(() => {
|
||||
setSliderPos(100)
|
||||
}, 10)
|
||||
return true
|
||||
})
|
||||
}}
|
||||
onUp={() => {
|
||||
setSliderPos(0)
|
||||
window.setTimeout(() => {
|
||||
setShowOriginal(false)
|
||||
}, 350)
|
||||
}}
|
||||
disabled={renders.length === 0}
|
||||
>
|
||||
{undefined}
|
||||
</div>
|
||||
</Button>
|
||||
<Button
|
||||
icon={<DownloadIcon />}
|
||||
disabled={!renders.length}
|
||||
onClick={download}
|
||||
>
|
||||
{undefined}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
102
lama_cleaner/app/src/components/Editor/SizeSelector.tsx
Normal file
102
lama_cleaner/app/src/components/Editor/SizeSelector.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { useClickAway } from 'react-use'
|
||||
import { ChevronUpIcon } from '@heroicons/react/outline'
|
||||
|
||||
const sizes = ['720', '1080', '2000', 'Original']
|
||||
|
||||
type SizeSelectorProps = {
|
||||
originalWidth: number
|
||||
originalHeight: number
|
||||
onChange: (value: number) => void
|
||||
}
|
||||
|
||||
export default function SizeSelector(props: SizeSelectorProps) {
|
||||
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 getValidSizes = useCallback(() => {
|
||||
const validSizes: string[] = []
|
||||
for (let i = 0; i < sizes.length; i += 1) {
|
||||
if (sizes[i] === 'Original') {
|
||||
validSizes.push(sizes[i])
|
||||
}
|
||||
if (parseInt(sizes[i], 10) < longSide) {
|
||||
validSizes.push(sizes[i])
|
||||
}
|
||||
}
|
||||
return validSizes
|
||||
}, [longSide])
|
||||
|
||||
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) => {
|
||||
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" ref={sizeSelectorRef}>
|
||||
<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-icon">
|
||||
<ChevronUpIcon />
|
||||
</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,14 +14,9 @@ export default function Slider(props: SliderProps) {
|
||||
const step = ((max || 100) - (min || 0)) / 100
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-center space-x-4 text-black">
|
||||
<div className="editor-brush-slider">
|
||||
<span>{label}</span>
|
||||
<input
|
||||
className={[
|
||||
'appearance-none rounded-lg h-4',
|
||||
'bg-primary',
|
||||
'w-24 md:w-auto',
|
||||
].join(' ')}
|
||||
type="range"
|
||||
step={step}
|
||||
min={min}
|
35
lama_cleaner/app/src/components/FileSelect/FileSelect.scss
Normal file
35
lama_cleaner/app/src/components/FileSelect/FileSelect.scss
Normal file
@ -0,0 +1,35 @@
|
||||
@use '../../styles/Mixins/' as *;
|
||||
|
||||
.file-select-label {
|
||||
display: grid;
|
||||
cursor: pointer;
|
||||
border: 2px dashed var(--border-color);
|
||||
border-radius: 0.5rem;
|
||||
min-width: 600px;
|
||||
|
||||
@include mobile {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
.file-select-label-hover {
|
||||
color: black;
|
||||
background-color: var(--yellow-accent);
|
||||
}
|
||||
}
|
||||
|
||||
.file-select-container {
|
||||
display: grid;
|
||||
padding: 4rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.file-select-message {
|
||||
font-family: 'WorkSans';
|
||||
text-align: center;
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react'
|
||||
import useResolution from '../../hooks/useResolution'
|
||||
|
||||
type FileSelectProps = {
|
||||
onSelection: (file: File) => void
|
||||
@ -10,6 +11,8 @@ export default function FileSelect(props: FileSelectProps) {
|
||||
const [dragHover, setDragHover] = useState(false)
|
||||
const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`)
|
||||
|
||||
const resolution = useResolution()
|
||||
|
||||
function onFileSelected(file: File) {
|
||||
if (!file) {
|
||||
return
|
||||
@ -94,17 +97,11 @@ export default function FileSelect(props: FileSelectProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<label
|
||||
htmlFor={uploadElemId}
|
||||
className="block w-full h-full group relative cursor-pointer rounded-md font-medium focus-within:outline-none"
|
||||
>
|
||||
<label htmlFor={uploadElemId} className="file-select-label">
|
||||
<div
|
||||
className={[
|
||||
'w-full h-full flex items-center justify-center px-6 pt-5 pb-6 text-md',
|
||||
'border-2 border-dashed rounded-md',
|
||||
'hover:border-black hover:bg-primary',
|
||||
'text-center',
|
||||
dragHover ? 'border-black bg-primary' : 'bg-gray-100 border-gray-300',
|
||||
'file-select-container',
|
||||
dragHover ? 'file-select-label-hover' : '',
|
||||
].join(' ')}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={ev => {
|
||||
@ -118,7 +115,6 @@ export default function FileSelect(props: FileSelectProps) {
|
||||
id={uploadElemId}
|
||||
name={uploadElemId}
|
||||
type="file"
|
||||
className="sr-only"
|
||||
onChange={ev => {
|
||||
const file = ev.currentTarget.files?.[0]
|
||||
if (file) {
|
||||
@ -127,8 +123,11 @@ export default function FileSelect(props: FileSelectProps) {
|
||||
}}
|
||||
accept="image/png, image/jpeg"
|
||||
/>
|
||||
<p className="hidden sm:block">Click here or drag an image file</p>
|
||||
<p className="sm:hidden">Tap here to load your picture</p>
|
||||
<p className="file-select-message">
|
||||
{resolution === 'desktop'
|
||||
? 'Click here or drag an image file'
|
||||
: 'Tap here to load your picture'}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
)
|
25
lama_cleaner/app/src/components/Header/Header.scss
Normal file
25
lama_cleaner/app/src/components/Header/Header.scss
Normal file
@ -0,0 +1,25 @@
|
||||
header {
|
||||
height: 60px;
|
||||
padding: 1rem 2rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
z-index: 20;
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgb(100, 100, 120, 0.2);
|
||||
}
|
||||
|
||||
.shortcuts {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.header-icons-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
justify-self: end;
|
||||
}
|
40
lama_cleaner/app/src/components/Header/Header.tsx
Normal file
40
lama_cleaner/app/src/components/Header/Header.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { ArrowLeftIcon } from '@heroicons/react/outline'
|
||||
import React from 'react'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { fileState } from '../../store/Atoms'
|
||||
import Button from '../shared/Button'
|
||||
import Shortcuts from '../Shortcuts/Shortcuts'
|
||||
import useResolution from '../../hooks/useResolution'
|
||||
import { ThemeChanger } from './ThemeChanger'
|
||||
|
||||
const Header = () => {
|
||||
const [file, setFile] = useRecoilState(fileState)
|
||||
const resolution = useResolution()
|
||||
|
||||
const renderHeader = () => {
|
||||
return (
|
||||
<header>
|
||||
<div style={{ visibility: file ? 'visible' : 'hidden' }}>
|
||||
<Button
|
||||
icon={<ArrowLeftIcon />}
|
||||
onClick={() => {
|
||||
setFile(undefined)
|
||||
}}
|
||||
style={{ border: 0 }}
|
||||
>
|
||||
{resolution === 'desktop' ? 'Start New' : undefined}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="header-icons-wrapper">
|
||||
<div style={{ visibility: file ? 'visible' : 'hidden' }}>
|
||||
<Shortcuts />
|
||||
</div>
|
||||
<ThemeChanger />
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
return renderHeader()
|
||||
}
|
||||
|
||||
export default Header
|
18
lama_cleaner/app/src/components/Header/ThemeChanger.scss
Normal file
18
lama_cleaner/app/src/components/Header/ThemeChanger.scss
Normal file
@ -0,0 +1,18 @@
|
||||
.theme-toggle-ui {
|
||||
z-index: 10;
|
||||
transition: all 0.2s ease-in;
|
||||
user-select: none;
|
||||
|
||||
.theme-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
44
lama_cleaner/app/src/components/Header/ThemeChanger.tsx
Normal file
44
lama_cleaner/app/src/components/Header/ThemeChanger.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { atom, useRecoilState } from 'recoil'
|
||||
import { SunIcon, MoonIcon } from '@heroicons/react/outline'
|
||||
|
||||
export const themeState = atom({
|
||||
key: 'themeState',
|
||||
default: 'light',
|
||||
})
|
||||
|
||||
export const ThemeChanger = () => {
|
||||
const [theme, setTheme] = useRecoilState(themeState)
|
||||
|
||||
useEffect(() => {
|
||||
const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
if (darkThemeMq.matches) {
|
||||
setTheme('dark')
|
||||
} else {
|
||||
setTheme('light')
|
||||
}
|
||||
}, [])
|
||||
|
||||
const themeSwitchHandler = () => {
|
||||
const newTheme = theme === 'light' ? 'dark' : 'light'
|
||||
setTheme(newTheme)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="theme-toggle-ui">
|
||||
<div
|
||||
className="theme-btn"
|
||||
onClick={themeSwitchHandler}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{theme === 'light' ? (
|
||||
<MoonIcon />
|
||||
) : (
|
||||
<SunIcon style={{ color: '#ffcc00' }} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
30
lama_cleaner/app/src/components/LandingPage/LandingPage.scss
Normal file
30
lama_cleaner/app/src/components/LandingPage/LandingPage.scss
Normal file
@ -0,0 +1,30 @@
|
||||
@use '../../styles/Mixins/' as *;
|
||||
|
||||
.landing-page {
|
||||
display: grid;
|
||||
place-self: center;
|
||||
justify-items: center;
|
||||
row-gap: 2rem;
|
||||
grid-auto-rows: max-content;
|
||||
|
||||
@include mobile {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 1.4rem;
|
||||
|
||||
@include mobile {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color);
|
||||
}
|
||||
}
|
||||
|
||||
.landing-file-selector {
|
||||
display: grid;
|
||||
}
|
26
lama_cleaner/app/src/components/LandingPage/LandingPage.tsx
Normal file
26
lama_cleaner/app/src/components/LandingPage/LandingPage.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
import { useSetRecoilState } from 'recoil'
|
||||
import { fileState } from '../../store/Atoms'
|
||||
import FileSelect from '../FileSelect/FileSelect'
|
||||
|
||||
const LandingPage = () => {
|
||||
const setFile = useSetRecoilState(fileState)
|
||||
|
||||
return (
|
||||
<div className="landing-page">
|
||||
<h1>
|
||||
Image inpainting powered by 🦙
|
||||
<a href="https://github.com/saic-mdal/lama">LaMa</a>
|
||||
</h1>
|
||||
<div className="landing-file-selector">
|
||||
<FileSelect
|
||||
onSelection={async f => {
|
||||
setFile(f)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LandingPage
|
@ -1,74 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function MadeWidthBadge() {
|
||||
return (
|
||||
<svg
|
||||
width="101"
|
||||
height="43"
|
||||
viewBox="0 0 101 43"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0.158081 28.5402C0.158081 26.12 2.07738 24.1581 4.44495 24.1581H5.62173C6.08596 24.1581 6.46229 24.5428 6.46229 25.0173V25.7907C6.46229 26.2652 6.08596 26.6499 5.62173 26.6499H4.44495C3.47006 26.6499 2.4276 27.5437 2.4276 28.5402V29.7432C2.4276 30.2177 2.05127 30.6024 1.58704 30.6024H0.998643C0.534413 30.6024 0.158081 30.2177 0.158081 29.7432L0.158081 28.5402Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M15.6244 35.2423C15.6244 37.6625 13.7051 39.6244 11.3376 39.6244H10.1608C9.69654 39.6244 9.3202 39.2397 9.3202 38.7652V37.9919C9.3202 37.5173 9.69654 37.1326 10.1608 37.1326H11.3376C12.3124 37.1326 13.3549 36.2388 13.3549 35.2423V34.0394C13.3549 33.5648 13.7312 33.1801 14.1955 33.1801H14.7839C15.2481 33.1801 15.6244 33.5648 15.6244 34.0394V35.2423Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M11.3376 24.1581C13.7051 24.1581 15.6244 26.12 15.6244 28.5402V29.7432C15.6244 30.2177 15.2481 30.6024 14.7839 30.6024H14.0274C13.5631 30.6024 13.1868 30.2177 13.1868 29.7432V28.5402C13.1868 27.5437 12.3124 26.478 11.3376 26.478H10.1608C9.69654 26.478 9.3202 26.0934 9.3202 25.6188V25.0173C9.3202 24.5428 9.69654 24.1581 10.1608 24.1581L11.3376 24.1581Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.44495 39.6244C2.07738 39.6244 0.158081 37.6625 0.158081 35.2423L0.158082 34.0394C0.158082 33.5648 0.534414 33.1801 0.998643 33.1801H1.75515C2.21938 33.1801 2.59571 33.5648 2.59571 34.0394L2.59571 35.2423C2.59571 36.2388 3.47006 37.3045 4.44495 37.3045L5.62173 37.3045C6.08596 37.3045 6.46229 37.6892 6.46229 38.1637V38.7652C6.46229 39.2397 6.08596 39.6244 5.62173 39.6244L4.44495 39.6244Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M36.6965 29.5082C36.289 26.5653 34.0253 24.8578 31.0888 24.8578C27.6285 24.8578 25.0219 27.3802 25.0219 31.662C25.0219 35.9373 27.5961 38.4663 31.0888 38.4663C34.2387 38.4663 36.3279 36.4289 36.6965 33.8999L34.2775 33.887C33.9735 35.4393 32.7058 36.3125 31.1212 36.3125C28.9738 36.3125 27.4345 34.7019 27.4345 31.662C27.4345 28.6738 28.9609 27.0116 31.1276 27.0116C32.7382 27.0116 33.9994 27.9236 34.2775 29.5082H36.6965Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M41.1319 25.0389H38.7905V38.2852H41.1319V25.0389Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M43.5396 38.2852H45.881V28.3504H43.5396V38.2852ZM44.7168 26.9404C45.4606 26.9404 46.0686 26.3712 46.0686 25.6727C46.0686 24.9677 45.4606 24.3985 44.7168 24.3985C43.9665 24.3985 43.3585 24.9677 43.3585 25.6727C43.3585 26.3712 43.9665 26.9404 44.7168 26.9404Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M48.2887 42.0107H50.6301V36.7199H50.7271C51.0958 37.4443 51.8654 38.4598 53.573 38.4598C55.9144 38.4598 57.6672 36.6035 57.6672 33.3307C57.6672 30.0192 55.8626 28.2211 53.5665 28.2211C51.8137 28.2211 51.0828 29.2754 50.7271 29.9933H50.5913V28.3504H48.2887V42.0107ZM50.5848 33.3178C50.5848 31.3904 51.4127 30.1421 52.9197 30.1421C54.4785 30.1421 55.2805 31.468 55.2805 33.3178C55.2805 35.1806 54.4656 36.5388 52.9197 36.5388C51.4256 36.5388 50.5848 35.2453 50.5848 33.3178Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M64.2645 38.2852C68.3005 38.2852 70.6936 35.7886 70.6936 31.6491C70.6936 27.5225 68.3005 25.0389 64.355 25.0389H59.7757V38.2852H64.2645ZM62.1753 36.209V27.1151H64.2192C66.9099 27.1151 68.3134 28.6156 68.3134 31.6491C68.3134 34.6955 66.9099 36.209 64.1481 36.209H62.1753Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M72.8701 38.2852H75.2115V32.4446C75.2115 31.1834 76.1622 30.2908 77.4494 30.2908C77.8439 30.2908 78.3355 30.362 78.536 30.4266V28.2728C78.3225 28.234 77.9539 28.2081 77.6951 28.2081C76.5568 28.2081 75.606 28.8549 75.2438 30.0062H75.1403V28.3504H72.8701V38.2852Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M84.0159 38.4792C86.9265 38.4792 88.7763 36.4289 88.7763 33.3566C88.7763 30.2779 86.9265 28.2211 84.0159 28.2211C81.1054 28.2211 79.2555 30.2779 79.2555 33.3566C79.2555 36.4289 81.1054 38.4792 84.0159 38.4792ZM84.0289 36.6035C82.4183 36.6035 81.6293 35.1676 81.6293 33.3501C81.6293 31.5327 82.4183 30.0774 84.0289 30.0774C85.6135 30.0774 86.4026 31.5327 86.4026 33.3501C86.4026 35.1676 85.6135 36.6035 84.0289 36.6035Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M90.7636 42.0107H93.105V36.7199H93.202C93.5707 37.4443 94.3404 38.4598 96.0479 38.4598C98.3893 38.4598 100.142 36.6035 100.142 33.3307C100.142 30.0192 98.3375 28.2211 96.0414 28.2211C94.2886 28.2211 93.5577 29.2754 93.202 29.9933H93.0662V28.3504H90.7636V42.0107ZM93.0597 33.3178C93.0597 31.3904 93.8876 30.1421 95.3946 30.1421C96.9534 30.1421 97.7554 31.468 97.7554 33.3178C97.7554 35.1806 96.9405 36.5388 95.3946 36.5388C93.9005 36.5388 93.0597 35.2453 93.0597 33.3178Z"
|
||||
fill="black"
|
||||
/>
|
||||
<path
|
||||
d="M1.03871 3.54545V13H2.39595V6.15376H2.48366L5.27202 12.9862H6.39844L9.18679 6.15838H9.2745V13H10.6317V3.54545H8.90057L5.89062 10.8949H5.77983L2.76989 3.54545H1.03871ZM14.6585 13.157C15.8311 13.157 16.4912 12.5614 16.7544 12.0305H16.8097V13H18.1578V8.29119C18.1578 6.22763 16.5328 5.81676 15.4063 5.81676C14.123 5.81676 12.9411 6.33381 12.4795 7.62642L13.7767 7.92188C13.9798 7.41868 14.4969 6.93395 15.4248 6.93395C16.3158 6.93395 16.7728 7.40021 16.7728 8.20348V8.2358C16.7728 8.73899 16.2558 8.72976 14.9816 8.87749C13.6382 9.03445 12.2625 9.3853 12.2625 10.9964C12.2625 12.3906 13.3105 13.157 14.6585 13.157ZM14.9585 12.049C14.1784 12.049 13.6151 11.6982 13.6151 11.0149C13.6151 10.2763 14.2707 10.0131 15.0693 9.90696C15.5171 9.84695 16.5789 9.72692 16.7774 9.52841V10.4425C16.7774 11.2827 16.108 12.049 14.9585 12.049ZM22.6507 13.1385C23.9434 13.1385 24.4512 12.3491 24.7005 11.8967H24.8159V13H26.1639V3.54545H24.7836V7.05859H24.7005C24.4512 6.62003 23.9803 5.81676 22.66 5.81676C20.9473 5.81676 19.687 7.16939 19.687 9.46839C19.687 11.7628 20.9288 13.1385 22.6507 13.1385ZM22.9554 11.9613C21.7228 11.9613 21.0811 10.8764 21.0811 9.45455C21.0811 8.04652 21.709 6.98935 22.9554 6.98935C24.1603 6.98935 24.8066 7.97266 24.8066 9.45455C24.8066 10.9457 24.1465 11.9613 22.9554 11.9613ZM31.1901 13.1431C32.7366 13.1431 33.8307 12.3814 34.1446 11.2273L32.8382 10.9918C32.5889 11.6612 31.9887 12.0028 31.2039 12.0028C30.0221 12.0028 29.2281 11.2365 29.1911 9.87003H34.2323V9.38068C34.2323 6.81854 32.6997 5.81676 31.0931 5.81676C29.1173 5.81676 27.8154 7.32173 27.8154 9.50071C27.8154 11.7028 29.0988 13.1431 31.1901 13.1431ZM29.1958 8.83594C29.2512 7.82955 29.9806 6.95703 31.1024 6.95703C32.1734 6.95703 32.8751 7.75107 32.8797 8.83594H29.1958ZM40.7416 13H42.145L43.5853 7.88033H43.6915L45.1318 13H46.5399L48.6219 5.90909H47.1954L45.8151 11.0934H45.7458L44.3609 5.90909H42.9344L41.5402 11.1165H41.471L40.0814 5.90909H38.6549L40.7416 13ZM49.9318 13H51.3121V5.90909H49.9318V13ZM50.6289 4.81499C51.1044 4.81499 51.5014 4.44567 51.5014 3.99325C51.5014 3.54084 51.1044 3.1669 50.6289 3.1669C50.1488 3.1669 49.7564 3.54084 49.7564 3.99325C49.7564 4.44567 50.1488 4.81499 50.6289 4.81499ZM56.4791 5.90909H55.0249V4.21023H53.6446V5.90909H52.6059V7.01705H53.6446V11.2042C53.64 12.4922 54.6233 13.1154 55.7128 13.0923C56.1514 13.0877 56.4468 13.0046 56.6084 12.9446L56.3591 11.8043C56.2668 11.8228 56.096 11.8643 55.8744 11.8643C55.4266 11.8643 55.0249 11.7166 55.0249 10.918V7.01705H56.4791V5.90909ZM59.5387 8.78977C59.5387 7.65874 60.2543 7.01243 61.2376 7.01243C62.1886 7.01243 62.7564 7.61719 62.7564 8.65589V13H64.1367V8.4897C64.1367 6.72159 63.1673 5.81676 61.7085 5.81676C60.6051 5.81676 59.9403 6.29688 59.608 7.06321H59.5202V3.54545H58.1584V13H59.5387V8.78977ZM75.1389 13.1477L79.8847 8.40199C81.0203 7.26633 81.0111 5.39204 79.8847 4.28409C78.7306 3.14844 76.9255 3.15767 75.7668 4.28409L75.1389 4.89347L74.5111 4.28409C73.3524 3.15767 71.5473 3.14844 70.3932 4.28409C69.2668 5.39204 69.2575 7.26633 70.3932 8.40199L75.1389 13.1477ZM86.2623 13H87.6104V11.8967H87.7258C87.9751 12.3491 88.4829 13.1385 89.7755 13.1385C91.4928 13.1385 92.7393 11.7628 92.7393 9.46839C92.7393 7.16939 91.4743 5.81676 89.7616 5.81676C88.4459 5.81676 87.9704 6.62003 87.7258 7.05859H87.6427V3.54545H86.2623V13ZM87.615 9.45455C87.615 7.97266 88.2613 6.98935 89.4662 6.98935C90.7172 6.98935 91.3451 8.04652 91.3451 9.45455C91.3451 10.8764 90.6988 11.9613 89.4662 11.9613C88.2797 11.9613 87.615 10.9457 87.615 9.45455ZM94.9078 15.6591C96.0481 15.6591 96.7683 15.0636 97.1792 13.9464L100.111 5.92294L98.6195 5.90909L96.8237 11.4119H96.7498L94.954 5.90909H93.4767L96.0712 13.0923L95.9004 13.5632C95.5495 14.505 95.0556 14.5835 94.2985 14.3757L93.9661 15.5067C94.1323 15.5806 94.4924 15.6591 94.9078 15.6591Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import { XIcon } from '@heroicons/react/outline'
|
||||
import React, { ReactNode, useRef } from 'react'
|
||||
import { useClickAway, useKey } from 'react-use'
|
||||
import Button from './Button'
|
||||
|
||||
interface ModalProps {
|
||||
children?: ReactNode
|
||||
onClose?: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function Modal(props: ModalProps) {
|
||||
const { children, onClose, className } = props
|
||||
const ref = useRef(null)
|
||||
|
||||
useClickAway(ref, () => {
|
||||
onClose?.()
|
||||
})
|
||||
|
||||
useKey('Escape', onClose, {
|
||||
event: 'keydown',
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
'absolute w-full h-full flex justify-center items-center',
|
||||
'z-20',
|
||||
'bg-gray-300 bg-opacity-40 backdrop-filter backdrop-blur-md',
|
||||
].join(' ')}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
className={`bg-white max-w-4xl relative rounded-md shadow-md ${
|
||||
className || 'p-8 sm:p-12'
|
||||
}`}
|
||||
>
|
||||
<Button
|
||||
icon={<XIcon className="w-6 h-6" />}
|
||||
className={[
|
||||
'absolute right-4 top-4 rounded-full bg-gray-100 w-10 h-10',
|
||||
'flex justify-center items-center py-0 px-0 sm:px-0',
|
||||
].join(' ')}
|
||||
onClick={onClose}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
59
lama_cleaner/app/src/components/Shortcuts/Shortcuts.scss
Normal file
59
lama_cleaner/app/src/components/Shortcuts/Shortcuts.scss
Normal file
@ -0,0 +1,59 @@
|
||||
@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 {
|
||||
display: grid;
|
||||
row-gap: 1rem;
|
||||
|
||||
.shortcut-option {
|
||||
display: grid;
|
||||
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 {
|
||||
justify-self: end;
|
||||
font-family: 'WorkSans-Bold';
|
||||
border: 1px solid var(--modal-hotkey-border-color);
|
||||
padding: 0.4rem 1rem;
|
||||
width: max-content;
|
||||
border-radius: 0.4rem;
|
||||
|
||||
@include mobile {
|
||||
padding: 0.2rem 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-description {
|
||||
justify-self: start;
|
||||
text-align: left;
|
||||
width: 18rem;
|
||||
|
||||
@include mobile {
|
||||
text-align: left;
|
||||
width: auto;
|
||||
justify-self: start;
|
||||
}
|
||||
}
|
||||
}
|
56
lama_cleaner/app/src/components/Shortcuts/Shortcuts.tsx
Normal file
56
lama_cleaner/app/src/components/Shortcuts/Shortcuts.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React from 'react'
|
||||
import { useKeyPressEvent } from 'react-use'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { shortcutsState } from '../../store/Atoms'
|
||||
import Button from '../shared/Button'
|
||||
|
||||
const Shortcuts = () => {
|
||||
const [shortcutVisibility, setShortcutState] = useRecoilState(shortcutsState)
|
||||
|
||||
const shortcutStateHandler = () => {
|
||||
setShortcutState(prevShortcutState => {
|
||||
return !prevShortcutState
|
||||
})
|
||||
}
|
||||
|
||||
useKeyPressEvent('h', ev => {
|
||||
ev?.preventDefault()
|
||||
shortcutStateHandler()
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="shortcuts">
|
||||
<Button
|
||||
onClick={shortcutStateHandler}
|
||||
disabled={shortcutVisibility}
|
||||
style={{ border: 0 }}
|
||||
icon={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
width="28"
|
||||
height="28"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="none"
|
||||
stroke="none"
|
||||
/>
|
||||
<g fill="currentColor">
|
||||
<path d="M14 5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h12zM2 4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H2z" />
|
||||
<path d="M13 10.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm0-2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-5 0A.25.25 0 0 1 8.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 8 8.75v-.5zm2 0a.25.25 0 0 1 .25-.25h1.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-1.5a.25.25 0 0 1-.25-.25v-.5zm1 2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-5-2A.25.25 0 0 1 6.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 6 8.75v-.5zm-2 0A.25.25 0 0 1 4.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 4 8.75v-.5zm-2 0A.25.25 0 0 1 2.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 2 8.75v-.5zm11-2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-2 0a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-2 0A.25.25 0 0 1 9.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 9 6.75v-.5zm-2 0A.25.25 0 0 1 7.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 7 6.75v-.5zm-2 0A.25.25 0 0 1 5.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 5 6.75v-.5zm-3 0A.25.25 0 0 1 2.25 6h1.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-1.5A.25.25 0 0 1 2 6.75v-.5zm0 4a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm2 0a.25.25 0 0 1 .25-.25h5.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-5.5a.25.25 0 0 1-.25-.25v-.5z" />
|
||||
</g>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Shortcuts
|
69
lama_cleaner/app/src/components/Shortcuts/ShortcutsModal.tsx
Normal file
69
lama_cleaner/app/src/components/Shortcuts/ShortcutsModal.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import { useSetRecoilState } from 'recoil'
|
||||
import { shortcutsState } from '../../store/Atoms'
|
||||
import Modal from '../shared/Modal'
|
||||
|
||||
interface Shortcut {
|
||||
children: ReactNode
|
||||
content: string
|
||||
}
|
||||
|
||||
function ShortCut(props: Shortcut) {
|
||||
const { children, content } = props
|
||||
|
||||
return (
|
||||
<div className="shortcut-option">
|
||||
<div className="shortcut-description">{content}</div>
|
||||
<div className="shortcut-key">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function ShortcutsModal() {
|
||||
const setShortcutState = useSetRecoilState(shortcutsState)
|
||||
|
||||
const shortcutStateHandler = () => {
|
||||
setShortcutState(prevShortcutState => !prevShortcutState)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={shortcutStateHandler}
|
||||
title="Hotkeys"
|
||||
className="modal-shortcuts"
|
||||
>
|
||||
<div className="shortcut-options">
|
||||
<ShortCut content="Enable multi-stroke mask drawing">
|
||||
<p>Hold Cmd/Ctrl</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Undo inpainting">
|
||||
<p>Cmd/Ctrl + Z</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Pan">
|
||||
<p>Space & Drag</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="View original image">
|
||||
<p>Hold Tab</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Reset zoom/pan">
|
||||
<p>Esc</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Cancel mask drawing">
|
||||
<p>Esc</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Decrease Brush Size">
|
||||
<p>[</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Increase Brush Size">
|
||||
<p>]</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Toggle Dark Mode">
|
||||
<p>Shift + D</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Toggle Hotkeys Panel">
|
||||
<p>H</p>
|
||||
</ShortCut>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import Modal from './Modal'
|
||||
|
||||
interface Shortcut {
|
||||
children: ReactNode
|
||||
content: string
|
||||
}
|
||||
|
||||
function ShortCut(props: Shortcut) {
|
||||
const { children, content } = props
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-row space-x-6 justify-between">
|
||||
<div className="mr-12 border-2 rounded-xl px-2 py-1">{children}</div>
|
||||
<div className="flex flex-col justify-center">{content}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface ShortcutsModalProps {
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
export default function ShortcutsModal(props: ShortcutsModalProps) {
|
||||
const { onClose } = props
|
||||
return (
|
||||
<Modal onClose={onClose} className="h-full sm:h-auto p-0 sm:p-0">
|
||||
<div className="h-full sm:h-auto flex flex-col sm:flex-row">
|
||||
<div className="flex sm:p-14 flex flex-col justify-center space-y-6">
|
||||
<ShortCut content="Enable multi-stroke mask drawing">
|
||||
<p>Hold Cmd/Ctrl</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Undo inpainting">
|
||||
<p>Cmd/Ctrl + z</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Pan">
|
||||
<p>Space & Drag</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="View original image">
|
||||
<p>Hold Tab</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Reset zoom/pan & Cancel mask drawing">
|
||||
<p>Esc</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Decrease Brush Size">
|
||||
<p>[</p>
|
||||
</ShortCut>
|
||||
<ShortCut content="Increase Brush Size">
|
||||
<p>]</p>
|
||||
</ShortCut>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
import React, { FocusEvent, useCallback } from 'react'
|
||||
import { Listbox } from '@headlessui/react'
|
||||
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'
|
||||
|
||||
const sizes = ['720', '1080', '2000', 'Original']
|
||||
|
||||
type SizeSelectorProps = {
|
||||
value: string
|
||||
originalWidth: number
|
||||
originalHeight: number
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
export default function SizeSelector(props: SizeSelectorProps) {
|
||||
const { value, originalHeight, originalWidth, onChange } = props
|
||||
|
||||
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 = []
|
||||
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)
|
||||
}
|
||||
}
|
||||
return validSizes
|
||||
}, [originalHeight, originalWidth])
|
||||
|
||||
const getValidSize = useCallback(() => {
|
||||
if (getValidSizes().indexOf(value) === -1) {
|
||||
return getValidSizes()[0]
|
||||
}
|
||||
return value
|
||||
}, [value, getValidSizes])
|
||||
|
||||
return (
|
||||
<div className="w-32">
|
||||
<Listbox value={getValidSize()} onChange={onChange}>
|
||||
<div className="relative">
|
||||
<Listbox.Options
|
||||
style={{ top: `-${getValidSizes().length * 40 + 5}px` }}
|
||||
className="absolute mb-1 w-full overflow-auto text-base bg-opacity-10 bg-black backdrop-blur rounded-md max-h-60 outline-none sm:text-sm"
|
||||
>
|
||||
{getValidSizes().map(size => (
|
||||
<Listbox.Option
|
||||
key={size}
|
||||
className={({ active }) =>
|
||||
`${active ? 'bg-black bg-opacity-10' : 'text-gray-900'}
|
||||
cursor-default select-none relative py-2 pl-4 pr-4`
|
||||
}
|
||||
value={size}
|
||||
>
|
||||
{({ selected, active }) => (
|
||||
<>
|
||||
<span
|
||||
className={`${
|
||||
selected ? 'font-medium' : 'font-normal'
|
||||
} block truncate`}
|
||||
>
|
||||
{getSizeShowName(size)}
|
||||
</span>
|
||||
{/* {selected ? (
|
||||
<span
|
||||
className={`${
|
||||
active ? 'text-amber-600' : 'text-amber-600'
|
||||
}
|
||||
absolute inset-y-0 left-0 flex items-center pl-3`}
|
||||
>
|
||||
<CheckIcon className="w-5 h-5" aria-hidden="true" />
|
||||
</span>
|
||||
) : null} */}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
<Listbox.Button
|
||||
onFocus={onButtonFocus}
|
||||
className="relative w-full inline-flex w-full px-4 py-2 text-sm font-medium bg-black rounded-md bg-opacity-10 focus:outline-none "
|
||||
>
|
||||
<span className="block truncate">
|
||||
{getSizeShowName(getValidSize())}
|
||||
</span>
|
||||
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||
<SelectorIcon
|
||||
className="w-5 h-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
)
|
||||
}
|
21
lama_cleaner/app/src/components/Workspace.tsx
Normal file
21
lama_cleaner/app/src/components/Workspace.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import Editor from './Editor/Editor'
|
||||
import { shortcutsState } from '../store/Atoms'
|
||||
import ShortcutsModal from './Shortcuts/ShortcutsModal'
|
||||
|
||||
interface WorkspaceProps {
|
||||
file: File
|
||||
}
|
||||
|
||||
const Workspace = ({ file }: WorkspaceProps) => {
|
||||
const shortcutVisbility = useRecoilValue(shortcutsState)
|
||||
return (
|
||||
<>
|
||||
<Editor file={file} />
|
||||
{shortcutVisbility ? <ShortcutsModal /> : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Workspace
|
29
lama_cleaner/app/src/components/shared/Button.scss
Normal file
29
lama_cleaner/app/src/components/shared/Button.scss
Normal file
@ -0,0 +1,29 @@
|
||||
.btn-primary {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
column-gap: 1rem;
|
||||
border: 1px solid var(--btn-border-color);
|
||||
color: var(--btn-text-color);
|
||||
font-family: 'WorkSans', sans-serif;
|
||||
width: max-content;
|
||||
padding: 0.5rem;
|
||||
place-items: center;
|
||||
border-radius: 0.5rem;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--btn-primary-hover-bg);
|
||||
color: var(--btn-text-hover-color);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary-disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
import React, { ReactNode, useState } from 'react'
|
||||
import React, { ReactNode } from 'react'
|
||||
|
||||
interface ButtonProps {
|
||||
children?: ReactNode
|
||||
className?: string
|
||||
icon?: ReactNode
|
||||
primary?: boolean
|
||||
disabled?: boolean
|
||||
onKeyDown?: () => void
|
||||
onClick?: () => void
|
||||
onDown?: (ev: PointerEvent) => void
|
||||
onUp?: (ev: PointerEvent) => void
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export default function Button(props: ButtonProps) {
|
||||
@ -18,23 +18,12 @@ export default function Button(props: ButtonProps) {
|
||||
className,
|
||||
disabled,
|
||||
icon,
|
||||
primary,
|
||||
onKeyDown,
|
||||
onClick,
|
||||
onDown,
|
||||
onUp,
|
||||
style,
|
||||
} = props
|
||||
const [active, setActive] = useState(false)
|
||||
let background = ''
|
||||
if (primary && !disabled) {
|
||||
background = 'bg-primary hover:bg-black hover:text-white'
|
||||
}
|
||||
if (active) {
|
||||
background = 'bg-black text-white'
|
||||
}
|
||||
if (!primary && !active) {
|
||||
background = 'hover:bg-primary'
|
||||
}
|
||||
|
||||
const blurOnClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.currentTarget.blur()
|
||||
@ -44,27 +33,25 @@ export default function Button(props: ButtonProps) {
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
style={style}
|
||||
onKeyDown={onKeyDown}
|
||||
onClick={blurOnClick}
|
||||
onPointerDown={(ev: React.PointerEvent<HTMLDivElement>) => {
|
||||
setActive(true)
|
||||
onDown?.(ev.nativeEvent)
|
||||
}}
|
||||
onPointerUp={(ev: React.PointerEvent<HTMLDivElement>) => {
|
||||
setActive(false)
|
||||
onUp?.(ev.nativeEvent)
|
||||
}}
|
||||
tabIndex={-1}
|
||||
className={[
|
||||
'inline-flex py-3 px-3 rounded-md cursor-pointer',
|
||||
children ? 'space-x-3' : '',
|
||||
background,
|
||||
disabled ? 'pointer-events-none opacity-50' : '',
|
||||
'btn-primary',
|
||||
children ? 'btn-primary-content' : '',
|
||||
disabled ? 'btn-primary-disabled' : '',
|
||||
className,
|
||||
].join(' ')}
|
||||
>
|
||||
{icon}
|
||||
<span className="whitespace-nowrap select-none">{children}</span>
|
||||
{children ? <span>{children}</span> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -7,9 +7,5 @@ interface LinkProps {
|
||||
|
||||
export default function Link(props: LinkProps) {
|
||||
const { children, href } = props
|
||||
return (
|
||||
<a href={href} className="font-black underline">
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
return <a href={href}>{children}</a>
|
||||
}
|
31
lama_cleaner/app/src/components/shared/Modal.scss
Normal file
31
lama_cleaner/app/src/components/shared/Modal.scss
Normal file
@ -0,0 +1,31 @@
|
||||
.modal-mask {
|
||||
z-index: 9999;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
background-color: var(--model-mask-bg);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: grid;
|
||||
grid-auto-rows: max-content;
|
||||
row-gap: 2rem;
|
||||
place-self: center;
|
||||
padding: 2rem;
|
||||
border-radius: 0.95rem;
|
||||
|
||||
.modal-header {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, auto);
|
||||
align-items: center;
|
||||
|
||||
.btn-primary {
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
}
|
36
lama_cleaner/app/src/components/shared/Modal.tsx
Normal file
36
lama_cleaner/app/src/components/shared/Modal.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { XIcon } from '@heroicons/react/outline'
|
||||
import React, { ReactNode, useRef } from 'react'
|
||||
import { useClickAway, useKey } from 'react-use'
|
||||
import Button from './Button'
|
||||
|
||||
interface ModalProps {
|
||||
children?: ReactNode
|
||||
onClose?: () => void
|
||||
title: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function Modal(props: ModalProps) {
|
||||
const { children, onClose, className, title } = props
|
||||
const ref = useRef(null)
|
||||
|
||||
useClickAway(ref, () => {
|
||||
onClose?.()
|
||||
})
|
||||
|
||||
useKey('Escape', onClose, {
|
||||
event: 'keydown',
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="modal-mask">
|
||||
<div ref={ref} className={`modal ${className}`}>
|
||||
<div className="modal-header">
|
||||
<h3>{title}</h3>
|
||||
<Button icon={<XIcon />} onClick={onClose} />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
31
lama_cleaner/app/src/hooks/useResolution.tsx
Normal file
31
lama_cleaner/app/src/hooks/useResolution.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
const useResolution = () => {
|
||||
const [width, setWidth] = useState(window.innerWidth)
|
||||
|
||||
const windowSizeHandler = useCallback(() => {
|
||||
setWidth(window.innerWidth)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', windowSizeHandler)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', windowSizeHandler)
|
||||
}
|
||||
})
|
||||
|
||||
if (width < 768) {
|
||||
return 'mobile'
|
||||
}
|
||||
|
||||
if (width >= 768 && width < 1224) {
|
||||
return 'tablet'
|
||||
}
|
||||
|
||||
if (width >= 1224) {
|
||||
return 'desktop'
|
||||
}
|
||||
}
|
||||
|
||||
export default useResolution
|
@ -1,6 +1,14 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './styles/index.css'
|
||||
import './styles/_index.scss'
|
||||
import { RecoilRoot } from 'recoil'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<RecoilRoot>
|
||||
<App />
|
||||
</RecoilRoot>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
|
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Black.ttf
Normal file
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Black.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Bold.ttf
Normal file
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Bold.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Italic.ttf
Normal file
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Italic.ttf
Normal file
Binary file not shown.
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Light.ttf
Normal file
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Light.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Medium.ttf
Normal file
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Medium.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Regular.ttf
Normal file
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Regular.ttf
Normal file
Binary file not shown.
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-SemiBold.ttf
Normal file
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-SemiBold.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Thin.ttf
Normal file
BIN
lama_cleaner/app/src/media/fonts/Work_Sans/WorkSans-Thin.ttf
Normal file
Binary file not shown.
Binary file not shown.
11
lama_cleaner/app/src/store/Atoms.tsx
Normal file
11
lama_cleaner/app/src/store/Atoms.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { atom } from 'recoil'
|
||||
|
||||
export const fileState = atom<File | undefined>({
|
||||
key: 'fileState',
|
||||
default: undefined,
|
||||
})
|
||||
|
||||
export const shortcutsState = atom<boolean>({
|
||||
key: 'shortcutsState',
|
||||
default: false,
|
||||
})
|
11
lama_cleaner/app/src/styles/App.scss
Normal file
11
lama_cleaner/app/src/styles/App.scss
Normal file
@ -0,0 +1,11 @@
|
||||
.lama-cleaner {
|
||||
display: grid;
|
||||
grid-template-areas: 'main-content';
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: var(--page-bg);
|
||||
color: var(--page-text-color);
|
||||
transition-property: background-color, color;
|
||||
transition-duration: 0.2s;
|
||||
transition-timing-function: repeat(2, ease-out);
|
||||
}
|
40
lama_cleaner/app/src/styles/Mixins/_MediaQueries.scss
Normal file
40
lama_cleaner/app/src/styles/Mixins/_MediaQueries.scss
Normal file
@ -0,0 +1,40 @@
|
||||
// Define Breakpoints
|
||||
$breakpoints: (
|
||||
mobile-res: 768px,
|
||||
desktop-res: 1224px,
|
||||
);
|
||||
|
||||
// Calcualte Min and Max Widths Based on Breakpoints
|
||||
$sizes: (
|
||||
mobile-max-width: calc(map-get($breakpoints, mobile-res) - 1px),
|
||||
tablet-min-width: map-get($breakpoints, mobile-res),
|
||||
tablet-max-width: calc(map-get($breakpoints, desktop-res) - 1px),
|
||||
desktop-min-width: map-get($breakpoints, desktop-res),
|
||||
);
|
||||
|
||||
// Mobile Mixin
|
||||
@mixin mobile {
|
||||
@media screen and (max-width: map-get($sizes, mobile-max-width)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Tablet Mixin
|
||||
@mixin tablet {
|
||||
@media screen and (min-width: map-get($sizes, tablet-min-width)) and (max-width: map-get($sizes, tablet-max-width)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Desktop Mixin
|
||||
@mixin desktop {
|
||||
@media screen and (min-width: map-get($sizes, desktop-min-width)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin large {
|
||||
@media screen and (min-width: map-get($sizes, tablet-min-width)) {
|
||||
@content;
|
||||
}
|
||||
}
|
53
lama_cleaner/app/src/styles/Mixins/_Mixins.scss
Normal file
53
lama_cleaner/app/src/styles/Mixins/_Mixins.scss
Normal file
@ -0,0 +1,53 @@
|
||||
@mixin accented-display($bg-color) {
|
||||
background: $bg-color;
|
||||
color: rgb(0, 0, 0);
|
||||
font-family: 'WorkSans';
|
||||
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: 1px 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']::-moz-range-progress {
|
||||
background: var(--yellow-accent);
|
||||
}
|
||||
}
|
2
lama_cleaner/app/src/styles/Mixins/_index.scss
Normal file
2
lama_cleaner/app/src/styles/Mixins/_index.scss
Normal file
@ -0,0 +1,2 @@
|
||||
@forward './Mixins';
|
||||
@forward './MediaQueries';
|
39
lama_cleaner/app/src/styles/_Animations.scss
Normal file
39
lama_cleaner/app/src/styles/_Animations.scss
Normal file
@ -0,0 +1,39 @@
|
||||
@keyframes pulsing {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.75;
|
||||
background-color: var(--animation-pulsing-bg);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes opacityReveal {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
29
lama_cleaner/app/src/styles/_Colors.scss
Normal file
29
lama_cleaner/app/src/styles/_Colors.scss
Normal file
@ -0,0 +1,29 @@
|
||||
:root {
|
||||
// General
|
||||
|
||||
// Theme
|
||||
--page-bg: rgb(255, 255, 255);
|
||||
--page-text-color: #040404;
|
||||
--yellow-accent: #ffcc00;
|
||||
--link-color: rgb(0, 0, 0);
|
||||
--border-color: rgb(100, 100, 120);
|
||||
|
||||
// Editor
|
||||
--editor-toolkit-bg: rgba(255, 255, 255, 0.5);
|
||||
--options-text-color: var(--page-text-color);
|
||||
--editor-size-border-color: var(--border-color);
|
||||
|
||||
// Modal
|
||||
--modal-bg: var(--page-bg);
|
||||
--modal-text-color: rgb(0, 0, 0);
|
||||
--modal-hotkey-border-color: rgb(0, 0, 0);
|
||||
--model-mask-bg: rgba(209,213,219,0.4);
|
||||
|
||||
// Shared
|
||||
--btn-primary-bg: rgb(210, 210, 220);
|
||||
--btn-text-color: black;
|
||||
--btn-text-hover-color: black;
|
||||
--btn-border-color: rgb(100, 100, 120);
|
||||
--btn-primary-hover-bg: var(--yellow-accent);
|
||||
--animation-pulsing-bg: rgb(255, 255, 255, 0.5);
|
||||
}
|
30
lama_cleaner/app/src/styles/_ColorsDark.scss
Normal file
30
lama_cleaner/app/src/styles/_ColorsDark.scss
Normal file
@ -0,0 +1,30 @@
|
||||
[data-theme='dark'] {
|
||||
// General
|
||||
|
||||
// Theme
|
||||
--page-bg: #040404;
|
||||
--page-text-color: #F9F9F9;
|
||||
--yellow-accent: #ffcc00;
|
||||
--link-color: var(--yellow-accent);
|
||||
--border-color: rgb(100, 100, 120);
|
||||
|
||||
// Editor
|
||||
--editor-toolkit-bg: rgba(0, 0, 0, 0.5);
|
||||
--options-text-color: var(--page-text-color);
|
||||
--editor-size-border-color: var(--yellow-accent);
|
||||
|
||||
// Modal
|
||||
--modal-bg: var(--page-bg);
|
||||
--modal-text-color: var(--page-text-color);
|
||||
// --modal-hotkey-bg: rgb(60, 60, 90);
|
||||
--modal-hotkey-border-color: var(--page-text-color);;
|
||||
--model-mask-bg: rgba(76, 76, 87, 0.4);
|
||||
|
||||
// Shared
|
||||
--btn-primary-bg: rgb(140, 140, 180);
|
||||
--btn-text-color: white;
|
||||
--btn-text-hover-color: var(--page-bg);
|
||||
--btn-border-color: var(--yellow-accent);
|
||||
--btn-primary-hover-bg: var(--yellow-accent);
|
||||
--animation-pulsing-bg: rgb(240, 240, 255);
|
||||
}
|
19
lama_cleaner/app/src/styles/_Fonts.scss
Normal file
19
lama_cleaner/app/src/styles/_Fonts.scss
Normal file
@ -0,0 +1,19 @@
|
||||
@font-face {
|
||||
font-family: 'WorkSans';
|
||||
src: url('../media/fonts/Work_Sans/WorkSans-Regular.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'WorkSans-Semibold';
|
||||
src: url('../media/fonts/Work_Sans/WorkSans-SemiBold.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'WorkSans-Bold';
|
||||
src: url('../media/fonts/Work_Sans/WorkSans-Bold.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'WorkSans-Black';
|
||||
src: url('../media/fonts/Work_Sans/WorkSans-Black.ttf');
|
||||
}
|
32
lama_cleaner/app/src/styles/_index.scss
Normal file
32
lama_cleaner/app/src/styles/_index.scss
Normal file
@ -0,0 +1,32 @@
|
||||
// General
|
||||
@use './Fonts';
|
||||
@use './Colors';
|
||||
@use './ColorsDark';
|
||||
@use './Animations';
|
||||
|
||||
// App
|
||||
@use './App';
|
||||
@use '../components/Editor/Editor';
|
||||
@use '../components/LandingPage/LandingPage';
|
||||
@use '../components/Header/Header';
|
||||
@use '../components/Header/ThemeChanger';
|
||||
@use '../components/Shortcuts/Shortcuts';
|
||||
|
||||
// Shared
|
||||
@use '../components/FileSelect/FileSelect';
|
||||
@use '../components/shared/Button';
|
||||
@use '../components/shared/Modal';
|
||||
|
||||
// Main CSS
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: 'WorkSans', sans-serif;
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
@tailwind base;
|
||||
|
||||
/* Your own custom base styles */
|
||||
|
||||
/* Start purging... */
|
||||
@tailwind components;
|
||||
/* Stop purging. */
|
||||
|
||||
/* Your own custom component styles */
|
||||
|
||||
/* Start purging... */
|
||||
@tailwind utilities;
|
||||
/* Stop purging. */
|
||||
|
||||
/* Your own custom utilities */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:wght@600;900&display=swap');
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: 'Work Sans';
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
background: black;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
*:not(input):not(textarea) {
|
||||
user-select: none; /* disable selection/Copy of UIWebView */
|
||||
-webkit-user-select: none; /* disable selection/Copy of UIWebView */
|
||||
-webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */
|
||||
}
|
||||
|
||||
@supports (-webkit-touch-callout: none) {
|
||||
.full-visible-h-safari {
|
||||
height: calc(100% - 80px); /* -webkit-fill-available;*/
|
||||
}
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
module.exports = {
|
||||
mode: 'jit',
|
||||
theme: {
|
||||
extend: {
|
||||
animation: {
|
||||
'pulse-fast': 'pulse 0.7s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||
},
|
||||
colors: {
|
||||
primary: '#BDFF01',
|
||||
},
|
||||
keyframes: {
|
||||
pulse: {
|
||||
'0%, 100%': { opacity: 0.8 },
|
||||
'50%': { opacity: 0.7 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [],
|
||||
purge: {
|
||||
// Filenames to scan for classes
|
||||
content: [
|
||||
'./src/**/*.html',
|
||||
'./src/**/*.js',
|
||||
'./src/**/*.jsx',
|
||||
'./src/**/*.ts',
|
||||
'./src/**/*.tsx',
|
||||
'./public/index.html',
|
||||
],
|
||||
// Options passed to PurgeCSS
|
||||
options: {
|
||||
// Whitelist specific selectors by name
|
||||
// safelist: [],
|
||||
},
|
||||
},
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -35,6 +35,7 @@ class LaMa:
|
||||
else:
|
||||
model_path = download_model(LAMA_MODEL_URL)
|
||||
|
||||
print(f"Load LaMa model from: {model_path}")
|
||||
model = torch.jit.load(model_path, map_location="cpu")
|
||||
model = model.to(device)
|
||||
model.eval()
|
||||
|
Loading…
Reference in New Issue
Block a user