feat: resize images in super editor (#2052)

This commit is contained in:
Aman Harwara
2022-11-24 21:06:09 +05:30
committed by GitHub
parent 2e517c985d
commit 2485bed350
7 changed files with 185 additions and 41 deletions

View File

@@ -8,13 +8,15 @@ import { isFileTypePreviewable } from './isFilePreviewable'
import PreviewComponent from './PreviewComponent'
import Button from '../Button/Button'
import { ProtectedIllustration } from '@standardnotes/icons'
import { ImageZoomLevelProps } from './ImageZoomLevelProps'
type Props = {
application: WebApplication
file: FileItem
}
isEmbeddedInSuper?: boolean
} & ImageZoomLevelProps
const FilePreview = ({ file, application }: Props) => {
const FilePreview = ({ file, application, isEmbeddedInSuper = false, imageZoomLevel, setImageZoomLevel }: Props) => {
const [isAuthorized, setIsAuthorized] = useState(application.isAuthorizedToRenderItem(file))
const isFilePreviewable = useMemo(() => {
@@ -112,7 +114,14 @@ const FilePreview = ({ file, application }: Props) => {
<span className="mt-3">Loading file...</span>
</div>
) : downloadedBytes ? (
<PreviewComponent application={application} file={file} bytes={downloadedBytes} />
<PreviewComponent
application={application}
file={file}
bytes={downloadedBytes}
isEmbeddedInSuper={isEmbeddedInSuper}
imageZoomLevel={imageZoomLevel}
setImageZoomLevel={setImageZoomLevel}
/>
) : (
<FilePreviewError
file={file}

View File

@@ -1,24 +1,66 @@
import { IconType } from '@standardnotes/snjs'
import { FunctionComponent, useRef, useState } from 'react'
import { classNames, IconType } from '@standardnotes/snjs'
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
import IconButton from '../Button/IconButton'
import { ImageZoomLevelProps } from './ImageZoomLevelProps'
type Props = {
objectUrl: string
}
isEmbeddedInSuper: boolean
} & ImageZoomLevelProps
const ImagePreview: FunctionComponent<Props> = ({ objectUrl }) => {
const initialImgHeightRef = useRef<number>()
const MinimumZoomPercent = 10
const DefaultZoomPercent = 100
const MaximumZoomPercent = 1000
const ZoomPercentModifier = 10
const PercentageDivisor = 100
const [imageZoomPercent, setImageZoomPercent] = useState(100)
const ImagePreview: FunctionComponent<Props> = ({
objectUrl,
isEmbeddedInSuper,
imageZoomLevel,
setImageZoomLevel,
}) => {
const [imageHeight, setImageHeight] = useState<number>(0)
const [imageZoomPercent, setImageZoomPercent] = useState(imageZoomLevel ? imageZoomLevel : DefaultZoomPercent)
const [isZoomInputVisible, setIsZoomInputVisible] = useState(false)
useEffect(() => {
setImageZoomPercent(imageZoomLevel ? imageZoomLevel : DefaultZoomPercent)
}, [imageZoomLevel])
const setImageZoom = useCallback(
(zoomLevel: number) => {
setImageZoomPercent(zoomLevel)
setImageZoomLevel?.(zoomLevel)
},
[setImageZoomLevel],
)
useEffect(() => {
const image = new Image()
image.src = objectUrl
image.onload = () => {
setImageHeight(image.height)
}
}, [objectUrl])
const heightIfEmbedded = imageHeight * (imageZoomPercent / PercentageDivisor)
return (
<div className="flex h-full min-h-0 w-full items-center justify-center">
<div className="relative flex h-full w-full items-center justify-center overflow-auto">
<div className="group flex h-full min-h-0 w-full items-center justify-center">
<div
className="relative flex h-full w-full items-center justify-center overflow-auto"
style={{
height: isEmbeddedInSuper ? `${heightIfEmbedded}px` : '',
}}
>
<img
src={objectUrl}
style={{
height: `${imageZoomPercent}%`,
...(imageZoomPercent <= 100
height: isEmbeddedInSuper ? `${heightIfEmbedded}px` : `${imageZoomPercent}%`,
...(isEmbeddedInSuper
? {}
: imageZoomPercent <= DefaultZoomPercent
? {
minWidth: '100%',
objectFit: 'contain',
@@ -31,39 +73,70 @@ const ImagePreview: FunctionComponent<Props> = ({ objectUrl }) => {
maxWidth: 'none',
}),
}}
ref={(imgElement) => {
if (!initialImgHeightRef.current) {
initialImgHeightRef.current = imgElement?.height
}
}}
/>
</div>
<div className="absolute left-1/2 bottom-6 flex -translate-x-1/2 items-center rounded border border-solid border-border bg-default py-1 px-3">
<span className="mr-1.5">Zoom:</span>
<div
className={classNames(
isEmbeddedInSuper ? 'hidden focus-within:flex group-hover:flex' : '',
'absolute left-1/2 bottom-6 flex -translate-x-1/2 items-center rounded border border-solid border-border bg-default py-1 px-3',
)}
>
<span className="mr-1.5">{isEmbeddedInSuper ? 'Size' : 'Zoom'}:</span>
<IconButton
className="rounded p-1 hover:bg-contrast"
icon={'subtract' as IconType}
title="Zoom Out"
title={isEmbeddedInSuper ? 'Decrease size' : 'Zoom Out'}
focusable={true}
onClick={() => {
setImageZoomPercent((percent) => {
const newPercent = percent - 10
if (newPercent >= 10) {
return newPercent
} else {
return percent
}
})
const newPercent = imageZoomPercent - ZoomPercentModifier
if (newPercent >= ZoomPercentModifier) {
setImageZoom(newPercent)
} else {
setImageZoom(imageZoomPercent)
}
}}
/>
<span className="mx-2">{imageZoomPercent}%</span>
{isZoomInputVisible ? (
<div className="mx-2">
<input
type="number"
className="w-10 text-center"
defaultValue={imageZoomPercent}
onKeyDown={(event) => {
event.stopPropagation()
if (event.key === 'Enter') {
const value = parseInt(event.currentTarget.value)
if (value >= MinimumZoomPercent && value <= MaximumZoomPercent) {
setImageZoom(value)
}
setIsZoomInputVisible(false)
}
}}
onBlur={(event) => {
setIsZoomInputVisible(false)
const value = parseInt(event.currentTarget.value)
if (value >= MinimumZoomPercent && value <= MaximumZoomPercent) {
setImageZoom(value)
}
}}
/>
%
</div>
) : (
<button
className="mx-1 rounded py-1 px-1.5 hover:bg-contrast"
onClick={() => setIsZoomInputVisible((visible) => !visible)}
>
{imageZoomPercent}%
</button>
)}
<IconButton
className="rounded p-1 hover:bg-contrast"
icon="add"
title="Zoom In"
title={isEmbeddedInSuper ? 'Increase size' : 'Zoom In'}
focusable={true}
onClick={() => {
setImageZoomPercent((percent) => percent + 10)
setImageZoom(imageZoomPercent + ZoomPercentModifier)
}}
/>
</div>

View File

@@ -0,0 +1,4 @@
export type ImageZoomLevelProps = {
imageZoomLevel?: number
setImageZoomLevel?: (zoomLevel: number) => void
}

View File

@@ -7,6 +7,7 @@ import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { createObjectURLWithRef } from './CreateObjectURLWithRef'
import ImagePreview from './ImagePreview'
import { ImageZoomLevelProps } from './ImageZoomLevelProps'
import { PreviewableTextFileTypes, RequiresNativeFilePreview } from './isFilePreviewable'
import TextPreview from './TextPreview'
@@ -14,9 +15,17 @@ type Props = {
application: WebApplication
file: FileItem
bytes: Uint8Array
}
isEmbeddedInSuper: boolean
} & ImageZoomLevelProps
const PreviewComponent: FunctionComponent<Props> = ({ application, file, bytes }) => {
const PreviewComponent: FunctionComponent<Props> = ({
application,
file,
bytes,
isEmbeddedInSuper,
imageZoomLevel,
setImageZoomLevel,
}) => {
const { selectedPane } = useResponsiveAppPane()
const objectUrlRef = useRef<string>()
@@ -73,7 +82,14 @@ const PreviewComponent: FunctionComponent<Props> = ({ application, file, bytes }
}
if (file.mimeType.startsWith('image/')) {
return <ImagePreview objectUrl={objectUrl} />
return (
<ImagePreview
objectUrl={objectUrl}
isEmbeddedInSuper={isEmbeddedInSuper}
imageZoomLevel={imageZoomLevel}
setImageZoomLevel={setImageZoomLevel}
/>
)
}
if (file.mimeType.startsWith('video/')) {