From 335685d581e9183649dbb026e36ab6d3bf0f8858 Mon Sep 17 00:00:00 2001 From: Qing Date: Mon, 13 Jun 2022 16:50:51 +0800 Subject: [PATCH 1/9] add gradually inpainting mode --- lama_cleaner/app/package.json | 1 + lama_cleaner/app/src/adapters/inpainting.ts | 1 - .../app/src/components/Editor/Editor.tsx | 10 ++- .../GraduallyInpaintingSettingBlock.tsx | 32 +++++++++ .../src/components/Settings/SettingBlock.scss | 6 +- .../src/components/Settings/SettingBlock.tsx | 20 +++++- .../src/components/Settings/SettingsModal.tsx | 2 + .../app/src/components/shared/Tooltip.scss | 37 ++++++++++ .../app/src/components/shared/Tooltip.tsx | 29 ++++++++ lama_cleaner/app/src/store/Atoms.tsx | 2 + lama_cleaner/app/src/styles/_Animations.scss | 22 ++++++ lama_cleaner/app/src/styles/_Colors.scss | 5 +- lama_cleaner/app/src/styles/_ColorsDark.scss | 3 + lama_cleaner/app/src/utils.ts | 10 +++ lama_cleaner/app/yarn.lock | 72 +++++++++++++++++++ 15 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 lama_cleaner/app/src/components/Settings/GraduallyInpaintingSettingBlock.tsx create mode 100644 lama_cleaner/app/src/components/shared/Tooltip.tsx diff --git a/lama_cleaner/app/package.json b/lama_cleaner/app/package.json index cb14365..dfddaeb 100644 --- a/lama_cleaner/app/package.json +++ b/lama_cleaner/app/package.json @@ -9,6 +9,7 @@ "@radix-ui/react-select": "0.1.2-rc.27", "@radix-ui/react-switch": "^0.1.5", "@radix-ui/react-toast": "^0.1.1", + "@radix-ui/react-tooltip": "^0.1.7", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", diff --git a/lama_cleaner/app/src/adapters/inpainting.ts b/lama_cleaner/app/src/adapters/inpainting.ts index 3965d54..9c9e459 100644 --- a/lama_cleaner/app/src/adapters/inpainting.ts +++ b/lama_cleaner/app/src/adapters/inpainting.ts @@ -35,7 +35,6 @@ export default async function inpaint( method: 'POST', body: fd, }).then(async r => { - console.log(r) if (r.ok) { return r.blob() } diff --git a/lama_cleaner/app/src/components/Editor/Editor.tsx b/lama_cleaner/app/src/components/Editor/Editor.tsx index 8d08b8b..79c19f9 100644 --- a/lama_cleaner/app/src/components/Editor/Editor.tsx +++ b/lama_cleaner/app/src/components/Editor/Editor.tsx @@ -26,6 +26,7 @@ import { isMidClick, isRightClick, loadImage, + srcToFile, useImage, } from '../../utils' import { settingState, toastState } from '../../store/Atoms' @@ -150,9 +151,16 @@ export default function Editor(props: EditorProps) { setIsInpaintingLoading(true) drawAllLinesOnMask(newLineGroups) + let targetFile = file + if (settings.graduallyInpainting === true && renders.length > 0) { + console.info('gradually inpainting on last result') + const lastRender = renders[renders.length - 1] + targetFile = await srcToFile(lastRender.currentSrc, file.name, file.type) + } + try { const res = await inpaint( - file, + targetFile, maskCanvas.toDataURL(), settings, sizeLimit.toString() diff --git a/lama_cleaner/app/src/components/Settings/GraduallyInpaintingSettingBlock.tsx b/lama_cleaner/app/src/components/Settings/GraduallyInpaintingSettingBlock.tsx new file mode 100644 index 0000000..7487bb0 --- /dev/null +++ b/lama_cleaner/app/src/components/Settings/GraduallyInpaintingSettingBlock.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import { useRecoilState } from 'recoil' +import { settingState } from '../../store/Atoms' +import { Switch, SwitchThumb } from '../shared/Switch' +import SettingBlock from './SettingBlock' + +const GraduallyInpaintingSettingBlock: React.FC = () => { + const [setting, setSettingState] = useRecoilState(settingState) + + const onCheckChange = (checked: boolean) => { + setSettingState(old => { + return { ...old, graduallyInpainting: checked } + }) + } + + return ( + + + + } + /> + ) +} + +export default GraduallyInpaintingSettingBlock diff --git a/lama_cleaner/app/src/components/Settings/SettingBlock.scss b/lama_cleaner/app/src/components/Settings/SettingBlock.scss index eabc7ae..49fa269 100644 --- a/lama_cleaner/app/src/components/Settings/SettingBlock.scss +++ b/lama_cleaner/app/src/components/Settings/SettingBlock.scss @@ -25,8 +25,10 @@ .setting-block-content-title { display: flex; - flex-direction: column; - justify-content: space-between; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 8px; } .setting-block-desc { diff --git a/lama_cleaner/app/src/components/Settings/SettingBlock.tsx b/lama_cleaner/app/src/components/Settings/SettingBlock.tsx index 30bb7a1..026cde3 100644 --- a/lama_cleaner/app/src/components/Settings/SettingBlock.tsx +++ b/lama_cleaner/app/src/components/Settings/SettingBlock.tsx @@ -1,4 +1,5 @@ import React, { ReactNode } from 'react' +import Tooltip from '../shared/Tooltip' interface SettingBlockProps { title: string @@ -15,7 +16,24 @@ function SettingBlock(props: SettingBlockProps) {
{title} - {desc && {desc}} + {desc && ( + {desc}
}> + + + + + )}
{input} diff --git a/lama_cleaner/app/src/components/Settings/SettingsModal.tsx b/lama_cleaner/app/src/components/Settings/SettingsModal.tsx index 8cec752..06d7033 100644 --- a/lama_cleaner/app/src/components/Settings/SettingsModal.tsx +++ b/lama_cleaner/app/src/components/Settings/SettingsModal.tsx @@ -6,6 +6,7 @@ import Modal from '../shared/Modal' import ManualRunInpaintingSettingBlock from './ManualRunInpaintingSettingBlock' import HDSettingBlock from './HDSettingBlock' import ModelSettingBlock from './ModelSettingBlock' +import GraduallyInpaintingSettingBlock from './GraduallyInpaintingSettingBlock' interface SettingModalProps { onClose: () => void @@ -29,6 +30,7 @@ export default function SettingModal(props: SettingModalProps) { show={setting.show} > + diff --git a/lama_cleaner/app/src/components/shared/Tooltip.scss b/lama_cleaner/app/src/components/shared/Tooltip.scss index b55c253..f182d4f 100644 --- a/lama_cleaner/app/src/components/shared/Tooltip.scss +++ b/lama_cleaner/app/src/components/shared/Tooltip.scss @@ -31,3 +31,40 @@ $tooltip-margin: 1.5rem; margin-top: $tooltip-margin; } } + +// radix-ui +.tooltip-trigger { + all: unset; + display: flex; + justify-content: center; + align-items: center; +} + +.tooltip-content { + color: var(--tooltip-text-color); + background-color: var(--tooltip-bg); + padding: 10px 15px; + border-radius: 4px; + box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, + hsl(206 22% 7% / 20%) 0px 10px 20px -15px; + + @media (prefers-reduced-motion: no-preference) { + animation-duration: 400ms; + animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); + animation-fill-mode: forwards; + will-change: transform, opacity; + + &[data-state='delayed-open'] { + &[data-side='top'] { + animation-name: slideDownAndFade; + } + &[data-side='bottom'] { + animation-name: slideUpAndFade; + } + } + } +} + +.tooltip-arrow { + fill: var(--tooltip-bg); +} diff --git a/lama_cleaner/app/src/components/shared/Tooltip.tsx b/lama_cleaner/app/src/components/shared/Tooltip.tsx new file mode 100644 index 0000000..9d2d995 --- /dev/null +++ b/lama_cleaner/app/src/components/shared/Tooltip.tsx @@ -0,0 +1,29 @@ +import React, { ReactNode } from 'react' +import * as TooltipPrimitive from '@radix-ui/react-tooltip' +import { TooltipProps } from '@radix-ui/react-tooltip' + +interface MyTooltipProps extends TooltipProps { + content: string | ReactNode + children: ReactNode +} + +const Tooltip = (props: MyTooltipProps) => { + const { content, children } = props + + return ( + + + + {children} + + + + {content} + + + + + ) +} + +export default Tooltip diff --git a/lama_cleaner/app/src/store/Atoms.tsx b/lama_cleaner/app/src/store/Atoms.tsx index 5317637..0279b1a 100644 --- a/lama_cleaner/app/src/store/Atoms.tsx +++ b/lama_cleaner/app/src/store/Atoms.tsx @@ -32,6 +32,7 @@ export const shortcutsState = atom({ export interface Settings { show: boolean + graduallyInpainting: boolean runInpaintingManually: boolean model: AIModel @@ -48,6 +49,7 @@ export interface Settings { export const settingStateDefault = { show: false, + graduallyInpainting: false, runInpaintingManually: false, model: AIModel.LAMA, ldmSteps: 50, diff --git a/lama_cleaner/app/src/styles/_Animations.scss b/lama_cleaner/app/src/styles/_Animations.scss index 17a2a7e..951ebd2 100644 --- a/lama_cleaner/app/src/styles/_Animations.scss +++ b/lama_cleaner/app/src/styles/_Animations.scss @@ -55,3 +55,25 @@ transform: rotate(360deg); } } + +@keyframes slideUpAndFade { + 0% { + opacity: 0; + transform: translateY(2px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideDownAndFade { + 0% { + opacity: 0; + transform: translateY(-2px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} diff --git a/lama_cleaner/app/src/styles/_Colors.scss b/lama_cleaner/app/src/styles/_Colors.scss index 6abfed1..fe26002 100644 --- a/lama_cleaner/app/src/styles/_Colors.scss +++ b/lama_cleaner/app/src/styles/_Colors.scss @@ -10,7 +10,7 @@ --border-color: rgb(100, 100, 120); --border-color-light: rgba(100, 100, 120, 0.5); --tooltip-bg: rgb(230, 230, 234); - --tooltip-text-color: rgb(0, 0,0); + --tooltip-text-color: rgb(0, 0, 0); --error-color: rgb(239, 68, 68); --success-color: rgb(16, 185, 129); @@ -44,4 +44,7 @@ --switch-root-background-color: rgb(223, 225, 228); --switch-thumb-color: var(--page-bg); --switch-thumb-checked-color: var(--page-bg); + + // tooltip + --tooltip-bg: var(--page-bg); } diff --git a/lama_cleaner/app/src/styles/_ColorsDark.scss b/lama_cleaner/app/src/styles/_ColorsDark.scss index 3fa2dcb..2f12da6 100644 --- a/lama_cleaner/app/src/styles/_ColorsDark.scss +++ b/lama_cleaner/app/src/styles/_ColorsDark.scss @@ -42,4 +42,7 @@ --switch-root-background-color: rgb(60, 63, 68); --switch-thumb-color: rgb(31, 32, 35); --switch-thumb-checked-color: white; + + // tooltip + --tooltip-bg: hsl(197 6.8% 13.6%); } diff --git a/lama_cleaner/app/src/utils.ts b/lama_cleaner/app/src/utils.ts index 7ff4b75..644c4b1 100644 --- a/lama_cleaner/app/src/utils.ts +++ b/lama_cleaner/app/src/utils.ts @@ -192,3 +192,13 @@ export function isMidClick(ev: SyntheticEvent) { const mouseEvent = ev.nativeEvent as MouseEvent return mouseEvent.button === 1 } + +export function srcToFile(src: string, fileName: string, mimeType: string) { + return fetch(src) + .then(function (res) { + return res.arrayBuffer() + }) + .then(function (buf) { + return new File([buf], fileName, { type: mimeType }) + }) +} diff --git a/lama_cleaner/app/yarn.lock b/lama_cleaner/app/yarn.lock index 2fdba23..3944d70 100644 --- a/lama_cleaner/app/yarn.lock +++ b/lama_cleaner/app/yarn.lock @@ -1551,6 +1551,14 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/popper@0.1.0": + version "0.1.0" + resolved "https://registry.npmmirror.com/@radix-ui/popper/-/popper-0.1.0.tgz#c387a38f31b7799e1ea0d2bb1ca0c91c2931b063" + integrity sha512-uzYeElL3w7SeNMuQpXiFlBhTT+JyaNMCwDfjKkrzugEcYrf5n52PHqncNdQPUtR42hJh8V9FsqyEDbDxkeNjJQ== + dependencies: + "@babel/runtime" "^7.13.10" + csstype "^3.0.4" + "@radix-ui/primitive@0.1.0": version "0.1.0" resolved "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-0.1.0.tgz#6206b97d379994f0d1929809db035733b337e543" @@ -1558,6 +1566,14 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-arrow@0.1.4": + version "0.1.4" + resolved "https://registry.npmmirror.com/@radix-ui/react-arrow/-/react-arrow-0.1.4.tgz#a871448a418cd3507d83840fdd47558cb961672b" + integrity sha512-BB6XzAb7Ml7+wwpFdYVtZpK1BlMgqyafSQNGzhIpSZ4uXvXOHPlR5GP8M449JkeQzgQjv9Mp1AsJxFC0KuOtuA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "0.1.4" + "@radix-ui/react-collection@0.1.5-rc.18": version "0.1.5-rc.18" resolved "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-0.1.5-rc.18.tgz#4dc03a8f464643748c0dad781b472f149d671d5c" @@ -1706,6 +1722,21 @@ "@radix-ui/react-id" "0.1.6-rc.18" "@radix-ui/react-primitive" "0.1.5-rc.18" +"@radix-ui/react-popper@0.1.4": + version "0.1.4" + resolved "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-0.1.4.tgz#dfc055dcd7dfae6a2eff7a70d333141d15a5d029" + integrity sha512-18gDYof97t8UQa7zwklG1Dr8jIdj3u+rVOQLzPi9f5i1YQak/pVGkaqw8aY+iDUknKKuZniTk/7jbAJUYlKyOw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/popper" "0.1.0" + "@radix-ui/react-arrow" "0.1.4" + "@radix-ui/react-compose-refs" "0.1.0" + "@radix-ui/react-context" "0.1.1" + "@radix-ui/react-primitive" "0.1.4" + "@radix-ui/react-use-rect" "0.1.1" + "@radix-ui/react-use-size" "0.1.1" + "@radix-ui/rect" "0.1.1" + "@radix-ui/react-portal@0.1.4": version "0.1.4" resolved "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-0.1.4.tgz#17bdce3d7f1a9a0b35cb5e935ab8bc562441a7d2" @@ -1833,6 +1864,27 @@ "@radix-ui/react-use-layout-effect" "0.1.0" "@radix-ui/react-visually-hidden" "0.1.4" +"@radix-ui/react-tooltip@^0.1.7": + version "0.1.7" + resolved "https://registry.npmmirror.com/@radix-ui/react-tooltip/-/react-tooltip-0.1.7.tgz#6f8c00d6e489565d14abf209ce0fb8853c8c8ee3" + integrity sha512-eiBUsVOHenZ0JR16tl970bB0DafJBz6mFgSGfIGIVpflFj0LIsIDiLMsYyvYdx1KwwsIUDTEZtxcPm/sWjPzqA== + 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-id" "0.1.5" + "@radix-ui/react-popper" "0.1.4" + "@radix-ui/react-portal" "0.1.4" + "@radix-ui/react-presence" "0.1.2" + "@radix-ui/react-primitive" "0.1.4" + "@radix-ui/react-slot" "0.1.2" + "@radix-ui/react-use-controllable-state" "0.1.0" + "@radix-ui/react-use-escape-keydown" "0.1.0" + "@radix-ui/react-use-previous" "0.1.1" + "@radix-ui/react-use-rect" "0.1.1" + "@radix-ui/react-visually-hidden" "0.1.4" + "@radix-ui/react-use-body-pointer-events@0.1.1": version "0.1.1" resolved "https://registry.npmmirror.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.1.1.tgz#63e7fd81ca7ffd30841deb584cd2b7f460df2597" @@ -1923,6 +1975,14 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-rect@0.1.1": + version "0.1.1" + resolved "https://registry.npmmirror.com/@radix-ui/react-use-rect/-/react-use-rect-0.1.1.tgz#6c15384beee59c086e75b89a7e66f3d2e583a856" + integrity sha512-kHNNXAsP3/PeszEmM/nxBBS9Jbo93sO+xuMTcRfwzXsmxT5gDXQzAiKbZQ0EecCPtJIzqvr7dlaQi/aP1PKYqQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/rect" "0.1.1" + "@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" @@ -1946,6 +2006,13 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "0.1.5-rc.18" +"@radix-ui/rect@0.1.1": + version "0.1.1" + resolved "https://registry.npmmirror.com/@radix-ui/rect/-/rect-0.1.1.tgz#95b5ba51f469bea6b1b841e2d427e17e37d38419" + integrity sha512-g3hnE/UcOg7REdewduRPAK88EPuLZtaq7sA9ouu8S+YEtnyFRI16jgv6GZYe3VMoQLL1T171ebmEPtDjyxWLzw== + 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" @@ -4496,6 +4563,11 @@ csstype@^3.0.2, csstype@^3.0.6: resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz" integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw== +csstype@^3.0.4: + version "3.1.0" + resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" + integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== + cyclist@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz" From 19e7e816afd1120c7656a8137a6ab246683283cf Mon Sep 17 00:00:00 2001 From: Qing Date: Tue, 14 Jun 2022 21:33:42 +0800 Subject: [PATCH 2/9] start new -> upload new --- .../app/src/components/Editor/Editor.tsx | 18 ++++++++--- .../src/components/Editor/SizeSelector.tsx | 6 +++- .../app/src/components/Header/Header.tsx | 32 ++++++++++++------- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/lama_cleaner/app/src/components/Editor/Editor.tsx b/lama_cleaner/app/src/components/Editor/Editor.tsx index 79c19f9..c6dd917 100644 --- a/lama_cleaner/app/src/components/Editor/Editor.tsx +++ b/lama_cleaner/app/src/components/Editor/Editor.tsx @@ -287,20 +287,30 @@ export default function Editor(props: EditorProps) { // Zoom reset const resetZoom = useCallback(() => { - if (!minScale || !original || !windowSize) { + if (!minScale) { return } const viewport = viewportRef.current if (!viewport) { - throw new Error('no viewport') + return } const offsetX = (windowSize.width - original.width * minScale) / 2 const offsetY = (windowSize.height - original.height * minScale) / 2 - viewport.setTransform(offsetX, offsetY, minScale, 200, 'easeOutQuad') + viewport.setTransform(offsetX, offsetY, minScale, 0, 'easeOutQuad') viewport.state.scale = minScale + setScale(minScale) setPanned(false) - }, [viewportRef, minScale, original, windowSize]) + }, [viewportRef, windowSize, original.width, windowSize.height, minScale]) + + useEffect(() => { + setLineGroups([]) + setCurLineGroup([]) + setRenders([]) + resetZoom() + const imageSizeLimit = Math.max(original.width, original.height) + setSizeLimit(imageSizeLimit) + }, [resetZoom, file, original]) useEffect(() => { window.addEventListener('resize', () => { diff --git a/lama_cleaner/app/src/components/Editor/SizeSelector.tsx b/lama_cleaner/app/src/components/Editor/SizeSelector.tsx index 20ea4b6..933c1b5 100644 --- a/lama_cleaner/app/src/components/Editor/SizeSelector.tsx +++ b/lama_cleaner/app/src/components/Editor/SizeSelector.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import Selector from '../shared/Selector' const sizes = ['720', '1080', '2000', 'Original'] @@ -14,6 +14,10 @@ export default function SizeSelector(props: SizeSelectorProps) { const [activeSize, setActiveSize] = useState('Original') const longSide: number = Math.max(originalWidth, originalHeight) + useEffect(() => { + setActiveSize('Original') + }, [originalHeight, originalWidth]) + const getSizeShowName = useCallback( (size: string) => { if (size === 'Original') { diff --git a/lama_cleaner/app/src/components/Header/Header.tsx b/lama_cleaner/app/src/components/Header/Header.tsx index 58fb600..4d62259 100644 --- a/lama_cleaner/app/src/components/Header/Header.tsx +++ b/lama_cleaner/app/src/components/Header/Header.tsx @@ -1,5 +1,5 @@ -import { ArrowLeftIcon } from '@heroicons/react/outline' -import React from 'react' +import { ArrowLeftIcon, UploadIcon } from '@heroicons/react/outline' +import React, { useState } from 'react' import { useRecoilState } from 'recoil' import { fileState } from '../../store/Atoms' import Button from '../shared/Button' @@ -11,20 +11,30 @@ import SettingIcon from '../Settings/SettingIcon' const Header = () => { const [file, setFile] = useRecoilState(fileState) const resolution = useResolution() + const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`) const renderHeader = () => { return (
- +
From 689d61968cf9730e456eb932d7a2dadefc151abd Mon Sep 17 00:00:00 2001 From: Qing Date: Tue, 14 Jun 2022 22:32:24 +0800 Subject: [PATCH 3/9] add redo --- .../app/src/components/Editor/Editor.tsx | 146 +++++++++++++++++- 1 file changed, 139 insertions(+), 7 deletions(-) diff --git a/lama_cleaner/app/src/components/Editor/Editor.tsx b/lama_cleaner/app/src/components/Editor/Editor.tsx index c6dd917..d3a6047 100644 --- a/lama_cleaner/app/src/components/Editor/Editor.tsx +++ b/lama_cleaner/app/src/components/Editor/Editor.tsx @@ -106,6 +106,11 @@ export default function Editor(props: EditorProps) { const [sliderPos, setSliderPos] = useState(0) + // redo 相关 + const [redoRenders, setRedoRenders] = useState([]) + const [redoCurLines, setRedoCurLines] = useState([]) + const [redoLineGroups, setRedoLineGroups] = useState([]) + const draw = useCallback( (render: HTMLImageElement, lineGroup: LineGroup) => { if (!context) { @@ -175,6 +180,9 @@ export default function Editor(props: EditorProps) { draw(newRender, []) // Only append new LineGroup after inpainting success setLineGroups(newLineGroups) + + // clear redo stack + resetRedoState() } catch (e: any) { setToastState({ open: true, @@ -303,10 +311,19 @@ export default function Editor(props: EditorProps) { setPanned(false) }, [viewportRef, windowSize, original.width, windowSize.height, minScale]) + const resetRedoState = () => { + setRedoCurLines([]) + setRedoLineGroups([]) + setRedoRenders([]) + } + useEffect(() => { setLineGroups([]) setCurLineGroup([]) setRenders([]) + + resetRedoState() + resetZoom() const imageSizeLimit = Math.max(original.width, original.height) setSizeLimit(imageSizeLimit) @@ -445,28 +462,43 @@ export default function Editor(props: EditorProps) { if (curLineGroup.length === 0) { return } - const newLineGroup = curLineGroup.slice(0, curLineGroup.length - 1) + + const lastLine = curLineGroup.pop()! + const newRedoCurLines = [...redoCurLines, lastLine] + setRedoCurLines(newRedoCurLines) + + const newLineGroup = [...curLineGroup] setCurLineGroup(newLineGroup) drawOnCurrentRender(newLineGroup) - }, [curLineGroup, drawOnCurrentRender]) + }, [curLineGroup, redoCurLines, drawOnCurrentRender]) const undoRender = useCallback(() => { if (!renders.length) { return } - const groups = lineGroups.slice(0, lineGroups.length - 1) - setLineGroups(groups) + // save line Group + const lastLineGroup = lineGroups.pop()! + setRedoLineGroups([...redoLineGroups, lastLineGroup]) + // If render is undo, clear strokes + setRedoCurLines([]) + + setLineGroups([...lineGroups]) setCurLineGroup([]) setIsDraging(false) - const newRenders = renders.slice(0, renders.length - 1) + + // save render + const lastRender = renders.pop()! + setRedoRenders([...redoRenders, lastRender]) + + const newRenders = [...renders] setRenders(newRenders) if (newRenders.length === 0) { draw(original, []) } else { draw(newRenders[newRenders.length - 1], []) } - }, [draw, renders, lineGroups, original]) + }, [draw, renders, redoRenders, redoLineGroups, lineGroups, original]) const undo = () => { if (settings.runInpaintingManually && curLineGroup.length !== 0) { @@ -478,13 +510,15 @@ export default function Editor(props: EditorProps) { // Handle Cmd+Z const undoPredicate = (event: KeyboardEvent) => { - const isCmdZ = (event.metaKey || event.ctrlKey) && event.key === 'z' + const isCmdZ = + (event.metaKey || event.ctrlKey) && !event.shiftKey && event.key === 'z' // Handle tab switch if (event.key === 'Tab') { event.preventDefault() } if (isCmdZ) { event.preventDefault() + console.log('undo') return true } return false @@ -493,6 +527,9 @@ export default function Editor(props: EditorProps) { useKey(undoPredicate, undo, undefined, [undoStroke, undoRender]) const disableUndo = () => { + if (isInpaintingLoading) { + return true + } if (renders.length > 0) { return false } @@ -508,6 +545,80 @@ export default function Editor(props: EditorProps) { return false } + const redoStroke = useCallback(() => { + if (redoCurLines.length === 0) { + return + } + const line = redoCurLines.pop()! + setRedoCurLines([...redoCurLines]) + + const newLineGroup = [...curLineGroup, line] + setCurLineGroup(newLineGroup) + drawOnCurrentRender(newLineGroup) + }, [curLineGroup, redoCurLines, drawOnCurrentRender]) + + const redoRender = useCallback(() => { + if (redoRenders.length === 0) { + return + } + const lineGroup = redoLineGroups.pop()! + setRedoLineGroups([...redoLineGroups]) + + setLineGroups([...lineGroups, lineGroup]) + setCurLineGroup([]) + setIsDraging(false) + + const render = redoRenders.pop()! + const newRenders = [...renders, render] + setRenders(newRenders) + draw(newRenders[newRenders.length - 1], []) + }, [draw, renders, redoRenders, redoLineGroups, lineGroups, original]) + + const redo = () => { + if (settings.runInpaintingManually && redoCurLines.length !== 0) { + redoStroke() + } else { + redoRender() + } + } + + // Handle Cmd+shift+Z + const redoPredicate = (event: KeyboardEvent) => { + const isCmdZ = + (event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'z' + // Handle tab switch + if (event.key === 'Tab') { + event.preventDefault() + } + if (isCmdZ) { + event.preventDefault() + console.log('redo') + return true + } + return false + } + + useKey(redoPredicate, redo, undefined, [redoStroke, redoRender]) + + const disableRedo = () => { + if (isInpaintingLoading) { + return true + } + if (redoRenders.length > 0) { + return false + } + + if (settings.runInpaintingManually) { + if (redoCurLines.length === 0) { + return true + } + } else if (redoRenders.length === 0) { + return true + } + + return false + } + useKeyPressEvent( 'Tab', ev => { @@ -781,6 +892,27 @@ export default function Editor(props: EditorProps) { onClick={undo} disabled={disableUndo()} /> +