WIP
This commit is contained in:
parent
e570e85e64
commit
aa411c7524
@ -17,6 +17,7 @@
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"react/jsx-props-no-spreading": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"react/jsx-no-bind": "off",
|
||||
"react/jsx-filename-extension": [
|
||||
|
@ -5,6 +5,7 @@
|
||||
"proxy": "http://localhost:8080",
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^1.0.4",
|
||||
"@radix-ui/react-switch": "^0.1.5",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.1.2",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
|
@ -23,3 +23,11 @@ header {
|
||||
gap: 12px;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.header-icons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
justify-self: end;
|
||||
}
|
@ -6,6 +6,7 @@ import Button from '../shared/Button'
|
||||
import Shortcuts from '../Shortcuts/Shortcuts'
|
||||
import useResolution from '../../hooks/useResolution'
|
||||
import { ThemeChanger } from './ThemeChanger'
|
||||
import SettingIcon from '../Setting/SettingIcon'
|
||||
|
||||
const Header = () => {
|
||||
const [file, setFile] = useRecoilState(fileState)
|
||||
@ -26,7 +27,11 @@ const Header = () => {
|
||||
</Button>
|
||||
</div>
|
||||
<div className="header-icons-wrapper">
|
||||
<div style={{ visibility: file ? 'visible' : 'hidden' }}>
|
||||
<div
|
||||
className="header-icons"
|
||||
style={{ visibility: file ? 'visible' : 'hidden' }}
|
||||
>
|
||||
<SettingIcon />
|
||||
<Shortcuts />
|
||||
</div>
|
||||
<ThemeChanger />
|
||||
|
137
lama_cleaner/app/src/components/Setting/HDSettingBlock.tsx
Normal file
137
lama_cleaner/app/src/components/Setting/HDSettingBlock.tsx
Normal file
@ -0,0 +1,137 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { settingState } from '../../store/Atoms'
|
||||
import NumberInput from '../shared/NumberInput'
|
||||
import Selector from '../shared/Selector'
|
||||
import SettingBlock from './SettingBlock'
|
||||
|
||||
export enum HDStrategy {
|
||||
ORIGINAL = 'Original',
|
||||
REISIZE = 'Resize',
|
||||
CROP = 'Crop',
|
||||
}
|
||||
|
||||
interface PixelSizeInputProps {
|
||||
title: string
|
||||
value: string
|
||||
onValue: (val: string) => void
|
||||
}
|
||||
|
||||
function PixelSizeInputSetting(props: PixelSizeInputProps) {
|
||||
const { title, value, onValue } = props
|
||||
|
||||
return (
|
||||
<SettingBlock
|
||||
title={title}
|
||||
input={
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
}}
|
||||
>
|
||||
<NumberInput
|
||||
style={{ width: '80px' }}
|
||||
value={`${value}`}
|
||||
onValue={onValue}
|
||||
/>
|
||||
<span>pixel</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function HDSettingBlock() {
|
||||
const [setting, setSettingState] = useRecoilState(settingState)
|
||||
|
||||
const onStrategyChange = (value: HDStrategy) => {
|
||||
setSettingState(old => {
|
||||
return { ...old, hdStrategy: value }
|
||||
})
|
||||
}
|
||||
|
||||
const onResizeLimitChange = (value: string) => {
|
||||
setSettingState(old => {
|
||||
return { ...old, hdStrategyResizeLimit: value }
|
||||
})
|
||||
}
|
||||
|
||||
const onCropTriggerSizeChange = (value: string) => {
|
||||
setSettingState(old => {
|
||||
return { ...old, hdStrategyCropTrigerSize: value }
|
||||
})
|
||||
}
|
||||
|
||||
const renderOriginalOptionDesc = () => {
|
||||
return (
|
||||
<div>
|
||||
Use the original resolution of the picture, suitable for picture size
|
||||
below 2K, of course you can try it on higher resolution pictures
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderResizeOptionDesc = () => {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
Resize the longer side of the image to a specific size(keep ratio),
|
||||
then do inpainting on the entire resized image.
|
||||
</div>
|
||||
<PixelSizeInputSetting
|
||||
title="Size limit"
|
||||
value={`${setting.hdStrategyResizeLimit}`}
|
||||
onValue={onResizeLimitChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderCropOptionDesc = () => {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
Crop masking area from the original image to do inpainting, and paste
|
||||
the result back. Mainly for performance and memory reasons on high
|
||||
resolution image.
|
||||
</div>
|
||||
<PixelSizeInputSetting
|
||||
title="Trigger size"
|
||||
value={`${setting.hdStrategyCropTrigerSize}`}
|
||||
onValue={onCropTriggerSizeChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHDStrategyOptionDesc = (): ReactNode => {
|
||||
switch (setting.hdStrategy) {
|
||||
case HDStrategy.ORIGINAL:
|
||||
return renderOriginalOptionDesc()
|
||||
case HDStrategy.CROP:
|
||||
return renderCropOptionDesc()
|
||||
case HDStrategy.REISIZE:
|
||||
return renderResizeOptionDesc()
|
||||
default:
|
||||
return renderOriginalOptionDesc()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingBlock
|
||||
title="High Resolution Strategy"
|
||||
input={
|
||||
<Selector
|
||||
options={Object.values(HDStrategy)}
|
||||
onChange={val => onStrategyChange(val as HDStrategy)}
|
||||
/>
|
||||
}
|
||||
optionDesc={renderHDStrategyOptionDesc()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default HDSettingBlock
|
@ -0,0 +1,19 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { Switch, SwitchThumb } from '../shared/Switch'
|
||||
import SettingBlock from './SettingBlock'
|
||||
|
||||
function SavePathSettingBlock() {
|
||||
return (
|
||||
<SettingBlock
|
||||
title="Download image at same folder of origin image"
|
||||
input={
|
||||
<Switch defaultChecked>
|
||||
<SwitchThumb />
|
||||
</Switch>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default SavePathSettingBlock
|
19
lama_cleaner/app/src/components/Setting/Setting.scss
Normal file
19
lama_cleaner/app/src/components/Setting/Setting.scss
Normal file
@ -0,0 +1,19 @@
|
||||
@use '../../styles/Mixins/' as *;
|
||||
@import './SettingBlock.scss';
|
||||
|
||||
.modal-setting {
|
||||
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);
|
||||
min-height: 450px;
|
||||
width: 700px;
|
||||
|
||||
@include mobile {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-top: -11rem;
|
||||
animation: slideDown 0.2s ease-out;
|
||||
}
|
||||
}
|
31
lama_cleaner/app/src/components/Setting/SettingBlock.scss
Normal file
31
lama_cleaner/app/src/components/Setting/SettingBlock.scss
Normal file
@ -0,0 +1,31 @@
|
||||
.setting-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 12px;
|
||||
|
||||
.option-desc {
|
||||
margin-top: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.3rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-block-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12rem;
|
||||
}
|
||||
|
||||
.setting-block-content-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.setting-block-desc {
|
||||
font-size: 1rem;
|
||||
margin-top: 8px;
|
||||
color: var(--text-color-gray);
|
||||
}
|
27
lama_cleaner/app/src/components/Setting/SettingBlock.tsx
Normal file
27
lama_cleaner/app/src/components/Setting/SettingBlock.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
|
||||
interface SettingBlockProps {
|
||||
title: string
|
||||
desc?: string
|
||||
input: ReactNode
|
||||
optionDesc?: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
function SettingBlock(props: SettingBlockProps) {
|
||||
const { title, desc, input, optionDesc, className } = props
|
||||
return (
|
||||
<div className={`setting-block ${className}`}>
|
||||
<div className="setting-block-content">
|
||||
<div className="setting-block-content-title">
|
||||
<span>{title}</span>
|
||||
{desc && <span className="setting-block-desc">{desc}</span>}
|
||||
</div>
|
||||
{input}
|
||||
</div>
|
||||
{optionDesc && <div className="option-desc">{optionDesc}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SettingBlock
|
46
lama_cleaner/app/src/components/Setting/SettingIcon.tsx
Normal file
46
lama_cleaner/app/src/components/Setting/SettingIcon.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React from 'react'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { settingState } from '../../store/Atoms'
|
||||
import Button from '../shared/Button'
|
||||
|
||||
const SettingIcon = () => {
|
||||
const [setting, setSettingState] = useRecoilState(settingState)
|
||||
|
||||
const onClick = () => {
|
||||
setSettingState({ ...setting, show: !setting.show })
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
onClick={onClick}
|
||||
style={{ border: 0 }}
|
||||
icon={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
role="img"
|
||||
width="28"
|
||||
height="28"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SettingIcon
|
29
lama_cleaner/app/src/components/Setting/SettingModal.tsx
Normal file
29
lama_cleaner/app/src/components/Setting/SettingModal.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { settingState } from '../../store/Atoms'
|
||||
import Modal from '../shared/Modal'
|
||||
import HDSettingBlock from './HDSettingBlock'
|
||||
import SavePathSettingBlock from './SavePathSettingBlock'
|
||||
|
||||
export default function SettingModal() {
|
||||
const [setting, setSettingState] = useRecoilState(settingState)
|
||||
|
||||
const onClose = () => {
|
||||
setSettingState(old => {
|
||||
return { ...old, show: false }
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={onClose}
|
||||
title="Settings"
|
||||
className="modal-setting"
|
||||
show={setting.show}
|
||||
>
|
||||
<SavePathSettingBlock />
|
||||
<HDSettingBlock />
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import { useSetRecoilState } from 'recoil'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { shortcutsState } from '../../store/Atoms'
|
||||
import Modal from '../shared/Modal'
|
||||
|
||||
@ -19,13 +19,8 @@ function ShortCut(props: Shortcut) {
|
||||
)
|
||||
}
|
||||
|
||||
interface ShortcutsModalProps {
|
||||
show: boolean
|
||||
}
|
||||
|
||||
export default function ShortcutsModal(props: ShortcutsModalProps) {
|
||||
const { show } = props
|
||||
const setShortcutState = useSetRecoilState(shortcutsState)
|
||||
export default function ShortcutsModal() {
|
||||
const [shortcutsShow, setShortcutState] = useRecoilState(shortcutsState)
|
||||
|
||||
const shortcutStateHandler = () => {
|
||||
setShortcutState(false)
|
||||
@ -36,7 +31,7 @@ export default function ShortcutsModal(props: ShortcutsModalProps) {
|
||||
onClose={shortcutStateHandler}
|
||||
title="Hotkeys"
|
||||
className="modal-shortcuts"
|
||||
show={show}
|
||||
show={shortcutsShow}
|
||||
>
|
||||
<div className="shortcut-options">
|
||||
<ShortCut content="Enable multi-stroke mask drawing">
|
||||
|
@ -3,17 +3,18 @@ import { useRecoilValue } from 'recoil'
|
||||
import Editor from './Editor/Editor'
|
||||
import { shortcutsState } from '../store/Atoms'
|
||||
import ShortcutsModal from './Shortcuts/ShortcutsModal'
|
||||
import SettingModal from './Setting/SettingModal'
|
||||
|
||||
interface WorkspaceProps {
|
||||
file: File
|
||||
}
|
||||
|
||||
const Workspace = ({ file }: WorkspaceProps) => {
|
||||
const shortcutVisbility = useRecoilValue(shortcutsState)
|
||||
return (
|
||||
<>
|
||||
<Editor file={file} />
|
||||
<ShortcutsModal show={shortcutVisbility} />
|
||||
<SettingModal />
|
||||
<ShortcutsModal />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { XIcon } from '@heroicons/react/outline'
|
||||
import React, { ReactNode, useRef } from 'react'
|
||||
import { useClickAway, useKey } from 'react-use'
|
||||
import { useClickAway, useKey, useKeyPress, useKeyPressEvent } from 'react-use'
|
||||
import Button from './Button'
|
||||
|
||||
export interface ModalProps {
|
||||
@ -19,8 +19,8 @@ export default function Modal(props: ModalProps) {
|
||||
onClose?.()
|
||||
})
|
||||
|
||||
useKey('Escape', onClose, {
|
||||
event: 'keydown',
|
||||
useKeyPressEvent('Escape', e => {
|
||||
onClose?.()
|
||||
})
|
||||
|
||||
return (
|
||||
@ -30,7 +30,7 @@ export default function Modal(props: ModalProps) {
|
||||
>
|
||||
<div ref={ref} className={`modal ${className}`}>
|
||||
<div className="modal-header">
|
||||
<h3>{title}</h3>
|
||||
<h2>{title}</h2>
|
||||
<Button icon={<XIcon />} onClick={onClose} />
|
||||
</div>
|
||||
{children}
|
||||
|
12
lama_cleaner/app/src/components/shared/NumberInput.scss
Normal file
12
lama_cleaner/app/src/components/shared/NumberInput.scss
Normal file
@ -0,0 +1,12 @@
|
||||
.number-input {
|
||||
all: unset;
|
||||
flex: 1 0 auto;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.2rem 0.8rem;
|
||||
line-height: 1;
|
||||
outline: 1px solid var(--border-color);
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid var(--yellow-accent);
|
||||
}
|
||||
}
|
31
lama_cleaner/app/src/components/shared/NumberInput.tsx
Normal file
31
lama_cleaner/app/src/components/shared/NumberInput.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { FormEvent, InputHTMLAttributes } from 'react'
|
||||
|
||||
interface NumberInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
value: string
|
||||
onValue?: (val: string) => void
|
||||
}
|
||||
|
||||
const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
||||
(props: NumberInputProps, forwardedRef) => {
|
||||
const { value, onValue, ...itemProps } = props
|
||||
|
||||
const handleOnInput = (evt: FormEvent<HTMLInputElement>) => {
|
||||
const target = evt.target as HTMLInputElement
|
||||
const val = target.value.replace(/\D/g, '')
|
||||
onValue?.(val)
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
value={value}
|
||||
onInput={handleOnInput}
|
||||
className="number-input"
|
||||
{...itemProps}
|
||||
ref={forwardedRef}
|
||||
type="text"
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default NumberInput
|
74
lama_cleaner/app/src/components/shared/Selector.scss
Normal file
74
lama_cleaner/app/src/components/shared/Selector.scss
Normal file
@ -0,0 +1,74 @@
|
||||
@use '../../styles/Mixins' as *;
|
||||
|
||||
.selector {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.selector-main {
|
||||
@include accented-display(var(white));
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
gap: 8px;
|
||||
padding: 0.2rem 0.8rem;
|
||||
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--options-text-color);
|
||||
|
||||
svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.selector-options {
|
||||
@include accented-display(var(--btn-primary-bg));
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
justify-self: center;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 3rem;
|
||||
|
||||
color: var(--options-text-color);
|
||||
background-color: var(--page-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
border-radius: 0.6rem;
|
||||
|
||||
@include mobile {
|
||||
bottom: 11.5rem;
|
||||
}
|
||||
|
||||
.selector-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
padding: 0.5rem 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);
|
||||
}
|
||||
}
|
||||
}
|
87
lama_cleaner/app/src/components/shared/Selector.tsx
Normal file
87
lama_cleaner/app/src/components/shared/Selector.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React, { MutableRefObject, useCallback, useRef, useState } from 'react'
|
||||
import { useClickAway, useKeyPressEvent } from 'react-use'
|
||||
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/outline'
|
||||
|
||||
type SelectorChevronDirection = 'up' | 'down'
|
||||
|
||||
type SelectorProps = {
|
||||
minWidth?: number
|
||||
chevronDirection?: SelectorChevronDirection
|
||||
options: string[]
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
const selectorDefaultProps = {
|
||||
minWidth: 128,
|
||||
chevronDirection: 'down',
|
||||
}
|
||||
|
||||
function Selector(props: SelectorProps) {
|
||||
const { minWidth, chevronDirection, options, onChange } = props
|
||||
const [showOptions, setShowOptions] = useState<boolean>(false)
|
||||
const [index, setIndex] = useState<number>(0)
|
||||
const selectorRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const showOptionsHandler = () => {
|
||||
// console.log(selectorRef.current?.focus)
|
||||
// selectorRef?.current?.focus()
|
||||
setShowOptions(currentShowOptionsState => !currentShowOptionsState)
|
||||
}
|
||||
|
||||
useClickAway(selectorRef, () => {
|
||||
setShowOptions(false)
|
||||
})
|
||||
|
||||
// TODO: how to prevent Modal close?
|
||||
// useKeyPressEvent('Escape', (e: KeyboardEvent) => {
|
||||
// if (showOptions === true) {
|
||||
// console.log(`selector ${e}`)
|
||||
// e.preventDefault()
|
||||
// e.stopPropagation()
|
||||
// setShowOptions(false)
|
||||
// }
|
||||
// })
|
||||
|
||||
const onOptionClick = (e: any, newIndex: number) => {
|
||||
const currentRes = e.target.textContent.split('x')
|
||||
onChange(currentRes[0])
|
||||
setShowOptions(false)
|
||||
setIndex(newIndex)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="selector" ref={selectorRef} style={{ minWidth }}>
|
||||
<div
|
||||
className="selector-main"
|
||||
role="button"
|
||||
onClick={showOptionsHandler}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<p>{options[index]}</p>
|
||||
<div className="selector-icon">
|
||||
{chevronDirection === 'up' ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showOptions && (
|
||||
<div className="selector-options">
|
||||
{options.map((val, _index) => (
|
||||
<div
|
||||
className="selector-option"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
key={val}
|
||||
onClick={e => onOptionClick(e, _index)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{val}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Selector.defaultProps = selectorDefaultProps
|
||||
export default Selector
|
36
lama_cleaner/app/src/components/shared/Switch.scss
Normal file
36
lama_cleaner/app/src/components/shared/Switch.scss
Normal file
@ -0,0 +1,36 @@
|
||||
.switch-root {
|
||||
all: 'unset';
|
||||
width: 42px;
|
||||
height: 25px;
|
||||
background-color: var(--switch-root-background-color);
|
||||
border-radius: 9999px;
|
||||
border: none;
|
||||
position: relative;
|
||||
transition: background-color 100ms;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.switch-root[data-state='checked'] {
|
||||
background-color: var(--yellow-accent);
|
||||
}
|
||||
|
||||
.switch-thumb {
|
||||
display: block;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
background-color: var(--switch-thumb-color);
|
||||
border-radius: 9999px;
|
||||
transition: transform 100ms;
|
||||
transform: translateX(4px);
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.switch-thumb[data-state='checked'] {
|
||||
transform: translateX(21px);
|
||||
background-color: var(--switch-thumb-checked-color);
|
||||
outline: 1px solid rgb(100, 100, 120, 0.5);
|
||||
}
|
34
lama_cleaner/app/src/components/shared/Switch.tsx
Normal file
34
lama_cleaner/app/src/components/shared/Switch.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React from 'react'
|
||||
import * as SwitchPrimitive from '@radix-ui/react-switch'
|
||||
|
||||
const Switch = React.forwardRef<
|
||||
React.ElementRef<typeof SwitchPrimitive.Root>,
|
||||
React.ComponentProps<typeof SwitchPrimitive.Root>
|
||||
>((props, forwardedRef) => {
|
||||
const { className, ...itemProps } = props
|
||||
|
||||
return (
|
||||
<SwitchPrimitive.Root
|
||||
{...itemProps}
|
||||
ref={forwardedRef}
|
||||
className={`switch-root ${className}`}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const SwitchThumb = React.forwardRef<
|
||||
React.ElementRef<typeof SwitchPrimitive.Thumb>,
|
||||
React.ComponentProps<typeof SwitchPrimitive.Thumb>
|
||||
>((props, forwardedRef) => {
|
||||
const { className, ...itemProps } = props
|
||||
|
||||
return (
|
||||
<SwitchPrimitive.Thumb
|
||||
{...itemProps}
|
||||
ref={forwardedRef}
|
||||
className={`switch-thumb ${className}`}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export { Switch, SwitchThumb }
|
@ -1,4 +1,5 @@
|
||||
import { atom } from 'recoil'
|
||||
import { HDStrategy } from '../components/Setting/HDSettingBlock'
|
||||
|
||||
export const fileState = atom<File | undefined>({
|
||||
key: 'fileState',
|
||||
@ -9,3 +10,20 @@ export const shortcutsState = atom<boolean>({
|
||||
key: 'shortcutsState',
|
||||
default: false,
|
||||
})
|
||||
|
||||
export interface Setting {
|
||||
show: boolean
|
||||
hdStrategy: HDStrategy
|
||||
hdStrategyResizeLimit: string
|
||||
hdStrategyCropTrigerSize: string
|
||||
}
|
||||
|
||||
export const settingState = atom<Setting>({
|
||||
key: 'settingsState',
|
||||
default: {
|
||||
show: false,
|
||||
hdStrategy: HDStrategy.ORIGINAL,
|
||||
hdStrategyResizeLimit: '2048',
|
||||
hdStrategyCropTrigerSize: '2048',
|
||||
},
|
||||
})
|
||||
|
@ -17,13 +17,22 @@
|
||||
--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);
|
||||
--model-mask-bg: rgba(209, 213, 219, 0.4);
|
||||
|
||||
// Text
|
||||
--text-color: #040404;
|
||||
--text-color-gray: rgb(107, 111, 118);
|
||||
|
||||
// Shared
|
||||
--btn-primary-bg: rgb(210, 210, 220);
|
||||
--btn-text-color: black;
|
||||
--btn-text-hover-color: black;
|
||||
--btn-text-color: var(--text-color);
|
||||
--btn-text-hover-color: #040404;
|
||||
--btn-border-color: rgb(100, 100, 120);
|
||||
--btn-primary-hover-bg: var(--yellow-accent);
|
||||
--animation-pulsing-bg: rgb(255, 255, 255, 0.5);
|
||||
|
||||
// switch
|
||||
--switch-root-background-color: rgb(223, 225, 228);
|
||||
--switch-thumb-color: var(--page-bg);
|
||||
--switch-thumb-checked-color: var(--page-bg);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
// Theme
|
||||
--page-bg: #040404;
|
||||
--page-text-color: #F9F9F9;
|
||||
--page-text-color: #f9f9f9;
|
||||
--yellow-accent: #ffcc00;
|
||||
--link-color: var(--yellow-accent);
|
||||
--border-color: rgb(100, 100, 120);
|
||||
@ -17,14 +17,23 @@
|
||||
--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);;
|
||||
--modal-hotkey-border-color: var(--page-text-color);
|
||||
--model-mask-bg: rgba(76, 76, 87, 0.4);
|
||||
|
||||
// Text
|
||||
--text-color: white;
|
||||
--text-color-gray: rgb(138, 143, 152);
|
||||
|
||||
// Shared
|
||||
--btn-primary-bg: rgb(140, 140, 180);
|
||||
--btn-text-color: white;
|
||||
--btn-text-color: var(--text-color);
|
||||
--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);
|
||||
|
||||
// switch
|
||||
--switch-root-background-color: rgb(60, 63, 68);
|
||||
--switch-thumb-color: rgb(31, 32, 35);
|
||||
--switch-thumb-checked-color: white;
|
||||
}
|
||||
|
@ -11,11 +11,15 @@
|
||||
@use '../components/Header/Header';
|
||||
@use '../components/Header/ThemeChanger';
|
||||
@use '../components/Shortcuts/Shortcuts';
|
||||
@use '../components/Setting/Setting.scss';
|
||||
|
||||
// Shared
|
||||
@use '../components/FileSelect/FileSelect';
|
||||
@use '../components/shared/Button';
|
||||
@use '../components/shared/Modal';
|
||||
@use '../components/shared/Selector';
|
||||
@use '../components/shared/Switch';
|
||||
@use '../components/shared/NumberInput';
|
||||
|
||||
// Main CSS
|
||||
*,
|
||||
|
@ -1164,6 +1164,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.13.10":
|
||||
version "7.17.9"
|
||||
resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
|
||||
integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.10.4", "@babel/template@^7.15.4", "@babel/template@^7.3.3":
|
||||
version "7.15.4"
|
||||
resolved "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz"
|
||||
@ -1537,6 +1544,113 @@
|
||||
schema-utils "^2.6.5"
|
||||
source-map "^0.7.3"
|
||||
|
||||
"@radix-ui/primitive@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-0.1.0.tgz#6206b97d379994f0d1929809db035733b337e543"
|
||||
integrity sha512-tqxZKybwN5Fa3VzZry4G6mXAAb9aAqKmPtnVbZpL0vsBwvOHTBwsjHVPXylocYLwEtBY9SCe665bYnNB515uoA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-compose-refs@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.1.0.tgz#cff6e780a0f73778b976acff2c2a5b6551caab95"
|
||||
integrity sha512-eyclbh+b77k+69Dk72q3694OHrn9B3QsoIRx7ywX341U9RK1ThgQjMFZoPtmZNQTksXHLNEiefR8hGVeFyInGg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-context@0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-0.1.1.tgz#06996829ea124d9a1bc1dbe3e51f33588fab0875"
|
||||
integrity sha512-PkyVX1JsLBioeu0jB9WvRpDBBLtLZohVDT3BB5CTSJqActma8S8030P57mWZb4baZifMvN7KKWPAA40UmWKkQg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-id@0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-0.1.5.tgz#010d311bedd5a2884c1e9bb6aaaa4e6cc1d1d3b8"
|
||||
integrity sha512-IPc4H/63bes0IZ1GJJozSEkSWcDyhNGtKFWUpJ+XtaLyQ1X3x7Mf6fWwWhDcpqlYEP+5WtAvfqcyEsyjP+ZhBQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "0.1.0"
|
||||
|
||||
"@radix-ui/react-label@0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-label/-/react-label-0.1.5.tgz#12cd965bfc983e0148121d4c99fb8e27a917c45c"
|
||||
integrity sha512-Au9+n4/DhvjR0IHhvZ1LPdx/OW+3CGDie30ZyCkbSHIuLp4/CV4oPPGBwJ1vY99Jog3zyQhsGww9MXj8O9Aj/A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
"@radix-ui/react-context" "0.1.1"
|
||||
"@radix-ui/react-id" "0.1.5"
|
||||
"@radix-ui/react-primitive" "0.1.4"
|
||||
|
||||
"@radix-ui/react-primitive@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-0.1.4.tgz#6c233cf08b0cb87fecd107e9efecb3f21861edc1"
|
||||
integrity sha512-6gSl2IidySupIMJFjYnDIkIWRyQdbu/AHK7rbICPani+LW4b0XdxBXc46og/iZvuwW8pjCS8I2SadIerv84xYA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "0.1.2"
|
||||
|
||||
"@radix-ui/react-slot@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-0.1.2.tgz#e6f7ad9caa8ce81cc8d532c854c56f9b8b6307c8"
|
||||
integrity sha512-ADkqfL+agEzEguU3yS26jfB50hRrwf7U4VTwAOZEmi/g+ITcBWe12yM46ueS/UCIMI9Py+gFUaAdxgxafFvY2Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
|
||||
"@radix-ui/react-switch@^0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-switch/-/react-switch-0.1.5.tgz#071ffa19a17a47fdc5c5e6f371bd5901c9fef2f4"
|
||||
integrity sha512-ITtslJPK+Yi34iNf7K9LtsPaLD76oRIVzn0E8JpEO5HW8gpRBGb2NNI9mxKtEB30TVqIcdjdL10AmuIfOMwjtg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "0.1.0"
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
"@radix-ui/react-context" "0.1.1"
|
||||
"@radix-ui/react-label" "0.1.5"
|
||||
"@radix-ui/react-primitive" "0.1.4"
|
||||
"@radix-ui/react-use-controllable-state" "0.1.0"
|
||||
"@radix-ui/react-use-previous" "0.1.1"
|
||||
"@radix-ui/react-use-size" "0.1.1"
|
||||
|
||||
"@radix-ui/react-use-callback-ref@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-0.1.0.tgz#934b6e123330f5b3a6b116460e6662cbc663493f"
|
||||
integrity sha512-Va041McOFFl+aV+sejvl0BS2aeHx86ND9X/rVFmEFQKTXCp6xgUK0NGUAGcgBlIjnJSbMYPGEk1xKSSlVcN2Aw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-controllable-state@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-0.1.0.tgz#4fced164acfc69a4e34fb9d193afdab973a55de1"
|
||||
integrity sha512-zv7CX/PgsRl46a52Tl45TwqwVJdmqnlQEQhaYMz/yBOD2sx2gCkCFSoF/z9mpnYWmS6DTLNTg5lIps3fV6EnXg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref" "0.1.0"
|
||||
|
||||
"@radix-ui/react-use-layout-effect@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz#ebf71bd6d2825de8f1fbb984abf2293823f0f223"
|
||||
integrity sha512-+wdeS51Y+E1q1Wmd+1xSSbesZkpVj4jsg0BojCbopWvgq5iBvixw5vgemscdh58ep98BwUbsFYnrywFhV9yrVg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-previous@0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-use-previous/-/react-use-previous-0.1.1.tgz#0226017f72267200f6e832a7103760e96a6db5d0"
|
||||
integrity sha512-O/ZgrDBr11dR8rhO59ED8s5zIXBRFi8MiS+CmFGfi7MJYdLbfqVOmQU90Ghf87aifEgWe6380LA69KBneaShAg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-size@0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmmirror.com/@radix-ui/react-use-size/-/react-use-size-0.1.1.tgz#f6b75272a5d41c3089ca78c8a2e48e5f204ef90f"
|
||||
integrity sha512-pTgWM5qKBu6C7kfKxrKPoBI2zZYZmp2cSXzpUiGM3qEBQlMLtYhaY2JXdXUCxz+XmD1YEjc8oRwvyfsD4AG4WA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@rollup/plugin-node-resolve@^7.1.1":
|
||||
version "7.1.3"
|
||||
resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user