make zoom work

This commit is contained in:
Sanster 2022-02-06 10:37:22 +08:00
parent 8013fc554f
commit 79ccd94ced
4 changed files with 1735 additions and 1671 deletions

View File

@ -24,6 +24,7 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"react-use": "^17.3.1", "react-use": "^17.3.1",
"react-zoom-pan-pinch": "^2.1.3",
"tailwindcss": "2.x", "tailwindcss": "2.x",
"typescript": "4.x" "typescript": "4.x"
}, },

View File

@ -1,5 +1,16 @@
import { DownloadIcon, EyeIcon } from '@heroicons/react/outline' import { DownloadIcon, EyeIcon } from '@heroicons/react/outline'
import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react' import React, {
SyntheticEvent,
useCallback,
useEffect,
useRef,
useState,
} from 'react'
import {
ReactZoomPanPinchRef,
TransformComponent,
TransformWrapper,
} from 'react-zoom-pan-pinch'
import { useWindowSize, useLocalStorage, useKey } from 'react-use' import { useWindowSize, useLocalStorage, useKey } from 'react-use'
import inpaint from './adapters/inpainting' import inpaint from './adapters/inpainting'
import Button from './components/Button' import Button from './components/Button'
@ -58,10 +69,12 @@ export default function Editor(props: EditorProps) {
const [showOriginal, setShowOriginal] = useState(false) const [showOriginal, setShowOriginal] = useState(false)
const [isInpaintingLoading, setIsInpaintingLoading] = useState(false) const [isInpaintingLoading, setIsInpaintingLoading] = useState(false)
const [showSeparator, setShowSeparator] = useState(false) const [showSeparator, setShowSeparator] = useState(false)
const [scale, setScale] = useState(1) const [scale, setScale] = useState<number>(1)
const [minScale, setMinScale] = useState<number>()
// ['1080', '2000', 'Original'] // ['1080', '2000', 'Original']
const [sizeLimit, setSizeLimit] = useLocalStorage('sizeLimit', '1080') const [sizeLimit, setSizeLimit] = useLocalStorage('sizeLimit', '1080')
const windowSize = useWindowSize() const windowSize = useWindowSize()
const viewportRef = useRef<ReactZoomPanPinchRef | undefined | null>()
const [isDraging, setIsDraging] = useState(false) const [isDraging, setIsDraging] = useState(false)
const [isMultiStrokeKeyPressed, setIsMultiStrokeKeyPressed] = useState(false) const [isMultiStrokeKeyPressed, setIsMultiStrokeKeyPressed] = useState(false)
@ -163,23 +176,43 @@ export default function Editor(props: EditorProps) {
// Draw once the original image is loaded // Draw once the original image is loaded
useEffect(() => { useEffect(() => {
if (!context?.canvas) { if (!original) {
return return
} }
if (isOriginalLoaded) { if (isOriginalLoaded) {
context.canvas.width = original.naturalWidth
context.canvas.height = original.naturalHeight
const rW = windowSize.width / original.naturalWidth const rW = windowSize.width / original.naturalWidth
const rH = (windowSize.height - TOOLBAR_SIZE) / original.naturalHeight const rH = (windowSize.height - TOOLBAR_SIZE) / original.naturalHeight
if (rW < 1 || rH < 1) { if (rW < 1 || rH < 1) {
setScale(Math.min(rW, rH)) const s = Math.min(rW, rH)
setMinScale(s)
setScale(s)
} else { } else {
setScale(1) setMinScale(1)
}
if (context?.canvas) {
context.canvas.width = original.naturalWidth
context.canvas.height = original.naturalHeight
} }
draw() draw()
} }
}, [context?.canvas, draw, original, isOriginalLoaded, windowSize]) }, [context?.canvas, draw, original, isOriginalLoaded, windowSize])
// Zoom reset
const resetZoom = useCallback(() => {
if (!minScale || !original || !windowSize) {
return
}
const viewport = viewportRef.current
if (!viewport) {
throw new Error('no viewport')
}
const offsetX = (windowSize.width - original.width * minScale) / 2
const offsetY = (windowSize.height - original.height * minScale) / 2
viewport.setTransform(offsetX, offsetY, minScale, 200, 'easeOutQuad')
}, [minScale, original, windowSize, isOriginalLoaded])
const onPaint = (px: number, py: number) => { const onPaint = (px: number, py: number) => {
const currShowLine = lines4Show[lines4Show.length - 1] const currShowLine = lines4Show[lines4Show.length - 1]
currShowLine.pts.push({ x: px, y: py }) currShowLine.pts.push({ x: px, y: py })
@ -302,81 +335,106 @@ export default function Editor(props: EditorProps) {
} }
} }
if (!original || !scale || !minScale) {
return <></>
}
return ( return (
<div <div
className={[ className="flex flex-col items-center"
'flex flex-col items-center',
isInpaintingLoading ? 'animate-pulse-fast transition-opacity' : '',
scale !== 1 ? 'pb-24' : '',
].join(' ')}
style={{ style={{
height: scale !== 1 ? original.naturalHeight * scale : undefined, // height: minScale !== 1 ? original.naturalHeight * minScale : undefined,
height: '100%',
width: '100%',
}} }}
aria-hidden="true" aria-hidden="true"
onMouseMove={onMouseMove} onMouseMove={onMouseMove}
onMouseUp={onPointerUp} onMouseUp={onPointerUp}
> >
<div <TransformWrapper
className={[scale !== 1 ? '' : 'relative'].join(' ')} ref={r => {
style={{ if (r) {
transform: `scale(${scale})`, viewportRef.current = r
transformOrigin: 'top center', }
borderColor: `${isMultiStrokeKeyPressed ? BRUSH_COLOR : NO_COLOR}`, }}
borderWidth: `${8 / scale}px`, panning={{ disabled: true, velocityDisabled: true }}
wheel={{ step: 0.05 }}
centerZoomedOut
alignmentAnimation={{ disabled: true }}
centerOnInit
limitToBounds={false}
initialScale={minScale}
minScale={minScale}
onZoom={ref => {
setScale(ref.state.scale)
}} }}
> >
<canvas <TransformComponent
className="rounded-sm" wrapperStyle={{
style={showBrush ? { cursor: 'none' } : {}} width: '100%',
onContextMenu={e => { height: '100%',
e.preventDefault() marginBottom: '36px',
}}
onMouseOver={() => toggleShowBrush(true)}
onFocus={() => toggleShowBrush(true)}
onMouseLeave={() => toggleShowBrush(false)}
onMouseDown={onMouseDown}
onMouseMove={onMouseDrag}
ref={r => {
if (r && !context) {
const ctx = r.getContext('2d')
if (ctx) {
setContext(ctx)
}
}
}}
/>
<div
className={[
'absolute top-0 right-0 pointer-events-none',
'overflow-hidden',
'border-primary',
showSeparator ? 'border-l-4' : '',
// showOriginal ? 'border-opacity-100' : 'border-opacity-0',
].join(' ')}
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',
}} }}
contentClass={
isInpaintingLoading
? 'animate-pulse-fast pointer-events-none transition-opacity'
: ''
}
> >
<img <>
className="absolute right-0" <canvas
src={original.src} className="rounded-sm"
alt="original" style={showBrush ? { cursor: 'none' } : {}}
width={`${original.naturalWidth}px`} onContextMenu={e => {
height={`${original.naturalHeight}px`} e.preventDefault()
style={{ }}
width: `${original.naturalWidth}px`, onMouseOver={() => toggleShowBrush(true)}
height: `${original.naturalHeight}px`, onFocus={() => toggleShowBrush(true)}
maxWidth: 'none', onMouseLeave={() => toggleShowBrush(false)}
}} onMouseDown={onMouseDown}
/> onMouseMove={onMouseDrag}
</div> ref={r => {
</div> if (r && !context) {
const ctx = r.getContext('2d')
if (ctx) {
setContext(ctx)
}
}
}}
/>
<div
className={[
'absolute top-0 right-0 pointer-events-none',
'overflow-hidden',
'border-primary',
showSeparator ? 'border-l-4' : '',
].join(' ')}
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',
}}
>
<img
className="absolute right-0"
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>
</>
</TransformComponent>
</TransformWrapper>
{showBrush && !isInpaintingLoading && ( {showBrush && !isInpaintingLoading && (
<div <div
@ -396,7 +454,7 @@ export default function Editor(props: EditorProps) {
'flex items-center w-full max-w-5xl', 'flex items-center w-full max-w-5xl',
'space-x-3 sm:space-x-5', 'space-x-3 sm:space-x-5',
'pb-6', 'pb-6',
scale !== 1 minScale !== 1
? 'absolute bottom-0 justify-evenly' ? 'absolute bottom-0 justify-evenly'
: 'relative justify-evenly sm:justify-between', : 'relative justify-evenly sm:justify-between',
].join(' ')} ].join(' ')}

View File

@ -37,7 +37,7 @@ export default function SizeSelector(props: SizeSelectorProps) {
<div className="relative"> <div className="relative">
<Listbox.Options <Listbox.Options
style={{ top: '-112px' }} style={{ top: '-112px' }}
className="absolute mb-1 w-full overflow-auto text-base bg-black backdrop-blur backdrop-filter md:bg-opacity-10 rounded-md max-h-60 ring-opacity-50 focus:outline-none sm:text-sm" className="absolute mb-1 w-full overflow-auto text-base bg-black backdrop-blur backdrop-filter bg-opacity-10 rounded-md max-h-60 ring-opacity-50 focus:outline-none sm:text-sm"
> >
{sizes.map((size, _) => ( {sizes.map((size, _) => (
<Listbox.Option <Listbox.Option

File diff suppressed because it is too large Load Diff