make zoom work
This commit is contained in:
parent
8013fc554f
commit
79ccd94ced
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -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(' ')}
|
||||||
|
@ -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
Loading…
Reference in New Issue
Block a user