mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
web: add basic support for attachment previews
This commit is contained in:
committed by
Abdullah Atta
parent
09098410a5
commit
083e4e1150
4
apps/web/desktop/package-lock.json
generated
4
apps/web/desktop/package-lock.json
generated
@@ -7,6 +7,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "@notesnook/desktop",
|
"name": "@notesnook/desktop",
|
||||||
"version": "2.4.11",
|
"version": "2.4.11",
|
||||||
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diary": "^0.3.1",
|
"diary": "^0.3.1",
|
||||||
"electron-updater": "^5.3.0",
|
"electron-updater": "^5.3.0",
|
||||||
@@ -4519,7 +4520,8 @@
|
|||||||
"version": "3.5.2",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
|
|||||||
19398
apps/web/package-lock.json
generated
19398
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,7 @@
|
|||||||
"react-hot-toast": "^2.2.0",
|
"react-hot-toast": "^2.2.0",
|
||||||
"react-loading-skeleton": "^3.1.0",
|
"react-loading-skeleton": "^3.1.0",
|
||||||
"react-modal": "^3.12.1",
|
"react-modal": "^3.12.1",
|
||||||
|
"react-pdf": "^6.2.2",
|
||||||
"react-qrcode-logo": "^2.2.1",
|
"react-qrcode-logo": "^2.2.1",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"react-scroll-sync": "^0.9.0",
|
"react-scroll-sync": "^0.9.0",
|
||||||
|
|||||||
75390
apps/web/public/pdf.worker.js
vendored
Normal file
75390
apps/web/public/pdf.worker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
apps/web/public/sample.pdf
Normal file
BIN
apps/web/public/sample.pdf
Normal file
Binary file not shown.
@@ -24,6 +24,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
PropsWithChildren
|
PropsWithChildren
|
||||||
} from "react";
|
} from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
import { Box, Button, Flex, Text } from "@theme-ui/components";
|
import { Box, Button, Flex, Text } from "@theme-ui/components";
|
||||||
import Properties from "../properties";
|
import Properties from "../properties";
|
||||||
import { useStore, store as editorstore } from "../../stores/editor-store";
|
import { useStore, store as editorstore } from "../../stores/editor-store";
|
||||||
@@ -49,6 +50,9 @@ import useTablet from "../../hooks/use-tablet";
|
|||||||
import Config from "../../utils/config";
|
import Config from "../../utils/config";
|
||||||
import { AnimatedFlex } from "../animated";
|
import { AnimatedFlex } from "../animated";
|
||||||
import { EditorLoader } from "../loaders/editor-loader";
|
import { EditorLoader } from "../loaders/editor-loader";
|
||||||
|
import { Lightbox } from "../lightbox";
|
||||||
|
import ThemeProviderWrapper from "../theme-provider";
|
||||||
|
import { Document, Page } from "react-pdf";
|
||||||
|
|
||||||
type PreviewSession = {
|
type PreviewSession = {
|
||||||
content: { data: string; type: string };
|
content: { data: string; type: string };
|
||||||
@@ -317,6 +321,24 @@ export function Editor(props: EditorProps) {
|
|||||||
onDownloadAttachment={(attachment) =>
|
onDownloadAttachment={(attachment) =>
|
||||||
downloadAttachment(attachment.hash)
|
downloadAttachment(attachment.hash)
|
||||||
}
|
}
|
||||||
|
onPreviewAttachment={({ hash, dataurl }) => {
|
||||||
|
const attachment = db.attachments?.attachment(hash);
|
||||||
|
if (attachment && attachment.metadata.type.startsWith("image/")) {
|
||||||
|
const container = document.getElementById("dialogContainer");
|
||||||
|
if (!(container instanceof HTMLElement)) return;
|
||||||
|
ReactDOM.render(
|
||||||
|
<ThemeProviderWrapper>
|
||||||
|
<Lightbox
|
||||||
|
image={dataurl}
|
||||||
|
onClose={() => {
|
||||||
|
ReactDOM.unmountComponentAtNode(container);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ThemeProviderWrapper>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
onInsertAttachment={(type) => {
|
onInsertAttachment={(type) => {
|
||||||
const mime = type === "file" ? "*/*" : "image/*";
|
const mime = type === "file" ? "*/*" : "image/*";
|
||||||
insertAttachment(mime).then((file) => {
|
insertAttachment(mime).then((file) => {
|
||||||
@@ -369,46 +391,67 @@ function EditorChrome(
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
<FlexScrollContainer
|
<Flex sx={{ justifyContent: "center", overflow: "hidden", flex: 1 }}>
|
||||||
className="editorScroll"
|
<FlexScrollContainer
|
||||||
style={{ display: "flex", flexDirection: "column", flex: 1 }}
|
className="editorScroll"
|
||||||
>
|
style={{ display: "flex", flexDirection: "column", flex: 1 }}
|
||||||
<Flex
|
|
||||||
variant="columnFill"
|
|
||||||
className="editor"
|
|
||||||
sx={{
|
|
||||||
alignSelf: ["stretch", focusMode ? "center" : "stretch", "center"],
|
|
||||||
maxWidth: editorMargins ? "min(100%, 850px)" : "auto",
|
|
||||||
width: "100%"
|
|
||||||
}}
|
|
||||||
px={6}
|
|
||||||
onClick={onRequestFocus}
|
|
||||||
>
|
>
|
||||||
{!isMobile && (
|
<Flex
|
||||||
<Box
|
variant="columnFill"
|
||||||
id="editorToolbar"
|
className="editor"
|
||||||
sx={{
|
sx={{
|
||||||
display: readonly ? "none" : "flex",
|
alignSelf: [
|
||||||
bg: "background",
|
"stretch",
|
||||||
position: "sticky",
|
focusMode ? "center" : "stretch",
|
||||||
top: 0,
|
"center"
|
||||||
mb: 1,
|
],
|
||||||
zIndex: 2
|
maxWidth: editorMargins ? "min(100%, 850px)" : "auto",
|
||||||
}}
|
width: "100%"
|
||||||
/>
|
}}
|
||||||
)}
|
px={6}
|
||||||
<Titlebox readonly={readonly || false} />
|
onClick={onRequestFocus}
|
||||||
<Header readonly={readonly} />
|
|
||||||
<AnimatedFlex
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: isLoading ? 0 : 1 }}
|
|
||||||
transition={{ duration: 0.3, ease: "easeInOut" }}
|
|
||||||
>
|
>
|
||||||
{children}
|
{!isMobile && (
|
||||||
</AnimatedFlex>
|
<Box
|
||||||
|
id="editorToolbar"
|
||||||
|
sx={{
|
||||||
|
display: readonly ? "none" : "flex",
|
||||||
|
bg: "background",
|
||||||
|
position: "sticky",
|
||||||
|
top: 0,
|
||||||
|
mb: 1,
|
||||||
|
zIndex: 2
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Titlebox readonly={readonly || false} />
|
||||||
|
<Header readonly={readonly} />
|
||||||
|
<AnimatedFlex
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: isLoading ? 0 : 1 }}
|
||||||
|
transition={{ duration: 0.3, ease: "easeInOut" }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AnimatedFlex>
|
||||||
|
</Flex>
|
||||||
|
</FlexScrollContainer>
|
||||||
|
<Flex
|
||||||
|
id="editorSidebar"
|
||||||
|
sx={{
|
||||||
|
flexDirection: "column",
|
||||||
|
overflow: "hidden",
|
||||||
|
borderLeft: "1px solid var(--border)"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Document
|
||||||
|
file="/sample.pdf"
|
||||||
|
onLoadSuccess={() => {}}
|
||||||
|
onLoadError={console.error}
|
||||||
|
>
|
||||||
|
<Page pageNumber={1} />
|
||||||
|
</Document>
|
||||||
</Flex>
|
</Flex>
|
||||||
</FlexScrollContainer>
|
</Flex>
|
||||||
|
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<Box
|
<Box
|
||||||
id="editorToolbar"
|
id="editorToolbar"
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ type TipTapProps = {
|
|||||||
onContentChange?: () => void;
|
onContentChange?: () => void;
|
||||||
onInsertAttachment?: (type: AttachmentType) => void;
|
onInsertAttachment?: (type: AttachmentType) => void;
|
||||||
onDownloadAttachment?: (attachment: Attachment) => void;
|
onDownloadAttachment?: (attachment: Attachment) => void;
|
||||||
|
onPreviewAttachment?: (attachment: Attachment) => void;
|
||||||
onAttachFile?: (file: File) => void;
|
onAttachFile?: (file: File) => void;
|
||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
content?: string;
|
content?: string;
|
||||||
@@ -109,6 +110,7 @@ function TipTap(props: TipTapProps) {
|
|||||||
onChange,
|
onChange,
|
||||||
onInsertAttachment,
|
onInsertAttachment,
|
||||||
onDownloadAttachment,
|
onDownloadAttachment,
|
||||||
|
onPreviewAttachment,
|
||||||
onAttachFile,
|
onAttachFile,
|
||||||
onContentChange,
|
onContentChange,
|
||||||
onFocus = () => {},
|
onFocus = () => {},
|
||||||
@@ -253,6 +255,10 @@ function TipTap(props: TipTapProps) {
|
|||||||
onDownloadAttachment?.(attachment);
|
onDownloadAttachment?.(attachment);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
onPreviewAttachment(_editor, attachment) {
|
||||||
|
onPreviewAttachment?.(attachment);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
onOpenLink: (url) => {
|
onOpenLink: (url) => {
|
||||||
window.open(url, "_blank");
|
window.open(url, "_blank");
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -197,7 +197,11 @@ import {
|
|||||||
mdiFileVideoOutline,
|
mdiFileVideoOutline,
|
||||||
mdiWeb,
|
mdiWeb,
|
||||||
mdiUploadOutline,
|
mdiUploadOutline,
|
||||||
mdiLinkOff
|
mdiLinkOff,
|
||||||
|
mdiMagnifyPlusOutline,
|
||||||
|
mdiMagnifyMinusOutline,
|
||||||
|
mdiRotateRight,
|
||||||
|
mdiRotateLeft
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { Theme } from "@notesnook/theme";
|
import { Theme } from "@notesnook/theme";
|
||||||
@@ -490,3 +494,8 @@ export const FileVideo = createIcon(mdiFileVideoOutline);
|
|||||||
export const FileGeneral = createIcon(mdiFileOutline);
|
export const FileGeneral = createIcon(mdiFileOutline);
|
||||||
export const FileWebClip = createIcon(mdiWeb);
|
export const FileWebClip = createIcon(mdiWeb);
|
||||||
export const Unlink = createIcon(mdiLinkOff);
|
export const Unlink = createIcon(mdiLinkOff);
|
||||||
|
export const ZoomIn = createIcon(mdiMagnifyPlusOutline);
|
||||||
|
export const ZoomOut = createIcon(mdiMagnifyMinusOutline);
|
||||||
|
export const RotateCW = createIcon(mdiRotateRight);
|
||||||
|
export const RotateACW = createIcon(mdiRotateLeft);
|
||||||
|
export const Reset = createIcon(mdiRestore);
|
||||||
|
|||||||
416
apps/web/src/components/lightbox/index.tsx
Normal file
416
apps/web/src/components/lightbox/index.tsx
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Button, Flex, Image } from "@theme-ui/components";
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Close,
|
||||||
|
Icon,
|
||||||
|
Loading,
|
||||||
|
Reset,
|
||||||
|
RotateACW,
|
||||||
|
RotateCW,
|
||||||
|
ZoomIn,
|
||||||
|
ZoomOut
|
||||||
|
} from "../icons";
|
||||||
|
|
||||||
|
const DEFAULT_ZOOM_STEP = 0.3;
|
||||||
|
const DEFAULT_LARGE_ZOOM = 4;
|
||||||
|
|
||||||
|
function getXY(e: React.MouseEvent | React.TouchEvent) {
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
if ("touches" in e && e.touches.length) {
|
||||||
|
x = e.touches[0].pageX;
|
||||||
|
y = e.touches[0].pageY;
|
||||||
|
} else if ("pageX" in e && "pageY" in e) {
|
||||||
|
x = e.pageX;
|
||||||
|
y = e.pageY;
|
||||||
|
}
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
type Image = { url: string; title?: string };
|
||||||
|
export type LightboxProps = {
|
||||||
|
image?: string;
|
||||||
|
// title?: string;
|
||||||
|
zoomStep?: number;
|
||||||
|
images?: Image[];
|
||||||
|
startIndex?: number;
|
||||||
|
keyboardInteraction?: boolean;
|
||||||
|
doubleClickZoom?: number;
|
||||||
|
// showTitle?: boolean;
|
||||||
|
// buttonAlign?: "flex-end" | "flex-start" | "center";
|
||||||
|
allowZoom?: boolean;
|
||||||
|
allowReset?: boolean;
|
||||||
|
allowRotate?: boolean;
|
||||||
|
onNavigateImage?: (index: number) => void;
|
||||||
|
clickOutsideToExit?: boolean;
|
||||||
|
onClose?: (e: React.MouseEvent | KeyboardEvent) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Lightbox extends React.Component<LightboxProps> {
|
||||||
|
initX = 0;
|
||||||
|
initY = 0;
|
||||||
|
lastX = 0;
|
||||||
|
lastY = 0;
|
||||||
|
_cont = React.createRef<HTMLDivElement>();
|
||||||
|
state = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
zoom: 1,
|
||||||
|
rotate: 0,
|
||||||
|
loading: true,
|
||||||
|
moving: false,
|
||||||
|
current: this.props?.startIndex ?? 0,
|
||||||
|
multi: this.props?.images?.length ? true : false
|
||||||
|
};
|
||||||
|
|
||||||
|
createTransform = (x: number, y: number, zoom: number, rotate: number) =>
|
||||||
|
`translate3d(${x}px,${y}px,0px) scale(${zoom}) rotate(${rotate}deg)`;
|
||||||
|
|
||||||
|
stopSideEffect = (
|
||||||
|
e: React.KeyboardEvent | React.MouseEvent | KeyboardEvent | MouseEvent
|
||||||
|
) => e.stopPropagation();
|
||||||
|
|
||||||
|
getCurrentImage = () => {
|
||||||
|
if (!this.state.multi) return this.props.image ?? "";
|
||||||
|
return this.props.images?.[this.state.current]?.url ?? "";
|
||||||
|
};
|
||||||
|
|
||||||
|
resetZoom = () => this.setState({ x: 0, y: 0, zoom: 1 });
|
||||||
|
shockZoom = (e: React.MouseEvent) => {
|
||||||
|
const {
|
||||||
|
zoomStep = DEFAULT_ZOOM_STEP,
|
||||||
|
allowZoom = true,
|
||||||
|
doubleClickZoom = DEFAULT_LARGE_ZOOM
|
||||||
|
} = this.props;
|
||||||
|
if (!allowZoom || !doubleClickZoom) return false;
|
||||||
|
this.stopSideEffect(e);
|
||||||
|
if (this.state.zoom > 1) return this.resetZoom();
|
||||||
|
const _z =
|
||||||
|
(zoomStep < 1 ? Math.ceil(doubleClickZoom / zoomStep) : zoomStep) *
|
||||||
|
zoomStep;
|
||||||
|
const _xy = getXY(e);
|
||||||
|
const _cbr = this._cont.current?.getBoundingClientRect?.();
|
||||||
|
if (!_cbr) return false;
|
||||||
|
const _ccx = _cbr.x + _cbr.width / 2;
|
||||||
|
const _ccy = _cbr.y + _cbr.height / 2;
|
||||||
|
const x = (_xy.x - _ccx) * -1 * _z;
|
||||||
|
const y = (_xy.y - _ccy) * -1 * _z;
|
||||||
|
this.setState({ x, y, zoom: _z });
|
||||||
|
};
|
||||||
|
navigateImage = (
|
||||||
|
direction: "next" | "prev",
|
||||||
|
e: React.KeyboardEvent | React.MouseEvent | KeyboardEvent | MouseEvent
|
||||||
|
) => {
|
||||||
|
if (!this.props.images) return;
|
||||||
|
|
||||||
|
this.stopSideEffect(e);
|
||||||
|
let current = 0;
|
||||||
|
switch (direction) {
|
||||||
|
case "next":
|
||||||
|
current = this.state.current + 1;
|
||||||
|
break;
|
||||||
|
case "prev":
|
||||||
|
current = this.state.current - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (current >= this.props.images.length) current = 0;
|
||||||
|
else if (current < 0) current = this.props.images.length - 1;
|
||||||
|
this.setState({ current, x: 0, y: 0, zoom: 1, rotate: 0, loading: true });
|
||||||
|
if (typeof this.props.onNavigateImage === "function") {
|
||||||
|
this.props.onNavigateImage(current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
startMove = (e: React.MouseEvent | React.TouchEvent) => {
|
||||||
|
if (this.state.zoom <= 1) return false;
|
||||||
|
this.setState({ moving: true });
|
||||||
|
const xy = getXY(e);
|
||||||
|
this.initX = xy.x - this.lastX;
|
||||||
|
this.initY = xy.y - this.lastY;
|
||||||
|
};
|
||||||
|
duringMove = (e: React.MouseEvent | React.TouchEvent) => {
|
||||||
|
if (!this.state.moving) return false;
|
||||||
|
const xy = getXY(e);
|
||||||
|
this.lastX = xy.x - this.initX;
|
||||||
|
this.lastY = xy.y - this.initY;
|
||||||
|
this.setState({
|
||||||
|
x: xy.x - this.initX,
|
||||||
|
y: xy.y - this.initY
|
||||||
|
});
|
||||||
|
};
|
||||||
|
endMove = () => this.setState({ moving: false });
|
||||||
|
applyZoom = (type: "in" | "out" | "reset") => {
|
||||||
|
const { zoomStep = DEFAULT_ZOOM_STEP } = this.props;
|
||||||
|
switch (type) {
|
||||||
|
case "in":
|
||||||
|
this.setState({ zoom: this.state.zoom + zoomStep });
|
||||||
|
break;
|
||||||
|
case "out": {
|
||||||
|
const newZoom = this.state.zoom - zoomStep;
|
||||||
|
if (newZoom < 1) break;
|
||||||
|
else if (newZoom === 1) this.setState({ x: 0, y: 0, zoom: 1 });
|
||||||
|
else this.setState({ zoom: newZoom });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "reset":
|
||||||
|
this.resetZoom();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
applyRotate = (type: "cw" | "acw") => {
|
||||||
|
switch (type) {
|
||||||
|
case "cw":
|
||||||
|
this.setState({ rotate: this.state.rotate + 90 });
|
||||||
|
break;
|
||||||
|
case "acw":
|
||||||
|
this.setState({ rotate: this.state.rotate - 90 });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reset = (e: React.MouseEvent | KeyboardEvent) => {
|
||||||
|
this.stopSideEffect(e);
|
||||||
|
this.setState({ x: 0, y: 0, zoom: 1, rotate: 0 });
|
||||||
|
};
|
||||||
|
exit = (e: React.MouseEvent | KeyboardEvent) => {
|
||||||
|
if (typeof this.props.onClose === "function") return this.props.onClose(e);
|
||||||
|
console.error(
|
||||||
|
"No Exit function passed on prop: onClose. Clicking the close button will do nothing"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
shouldShowReset = () =>
|
||||||
|
this.state.x ||
|
||||||
|
this.state.y ||
|
||||||
|
this.state.zoom !== 1 ||
|
||||||
|
this.state.rotate !== 0;
|
||||||
|
canvasClick = (e: React.MouseEvent) => {
|
||||||
|
const { clickOutsideToExit = true } = this.props;
|
||||||
|
if (clickOutsideToExit && this.state.zoom <= 1) return this.exit(e);
|
||||||
|
};
|
||||||
|
keyboardNavigation = (e: KeyboardEvent) => {
|
||||||
|
const { allowZoom = true, allowReset = true } = this.props;
|
||||||
|
const { multi, x, y, zoom } = this.state;
|
||||||
|
switch (e.key) {
|
||||||
|
case "ArrowLeft":
|
||||||
|
if (multi && zoom === 1) this.navigateImage("prev", e);
|
||||||
|
else if (zoom > 1) this.setState({ x: x - 20 });
|
||||||
|
break;
|
||||||
|
case "ArrowRight":
|
||||||
|
if (multi && zoom === 1) this.navigateImage("next", e);
|
||||||
|
else if (zoom > 1) this.setState({ x: x + 20 });
|
||||||
|
break;
|
||||||
|
case "ArrowUp":
|
||||||
|
if (zoom > 1) this.setState({ y: y + 20 });
|
||||||
|
break;
|
||||||
|
case "ArrowDown":
|
||||||
|
if (zoom > 1) this.setState({ y: y - 20 });
|
||||||
|
break;
|
||||||
|
case "+":
|
||||||
|
if (allowZoom) this.applyZoom("in");
|
||||||
|
break;
|
||||||
|
case "-":
|
||||||
|
if (allowZoom) this.applyZoom("out");
|
||||||
|
break;
|
||||||
|
case "Escape":
|
||||||
|
if (allowReset && this.shouldShowReset()) this.reset(e);
|
||||||
|
else this.exit(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
componentDidMount() {
|
||||||
|
document.body.classList.add("lb-open-lightbox");
|
||||||
|
const { keyboardInteraction = true } = this.props;
|
||||||
|
if (keyboardInteraction)
|
||||||
|
document.addEventListener("keyup", this.keyboardNavigation);
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.body.classList.remove("lb-open-lightbox");
|
||||||
|
const { keyboardInteraction = true } = this.props;
|
||||||
|
if (keyboardInteraction)
|
||||||
|
document.removeEventListener("keyup", this.keyboardNavigation);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const image = this.getCurrentImage();
|
||||||
|
if (!image) {
|
||||||
|
console.warn("Not showing lightbox because no image(s) was supplied");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
allowZoom = true,
|
||||||
|
allowRotate = true,
|
||||||
|
allowReset = true,
|
||||||
|
onClose
|
||||||
|
} = this.props;
|
||||||
|
const { x, y, zoom, rotate, multi, loading, moving } = this.state;
|
||||||
|
const _reset = allowReset && this.shouldShowReset();
|
||||||
|
|
||||||
|
const tools: {
|
||||||
|
title: string;
|
||||||
|
icon: Icon;
|
||||||
|
enabled: boolean;
|
||||||
|
onClick: (e: React.MouseEvent) => void;
|
||||||
|
hidden?: boolean;
|
||||||
|
hideOnMobile?: boolean;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
title: "Reset",
|
||||||
|
icon: Reset,
|
||||||
|
enabled: true,
|
||||||
|
hidden: !allowReset,
|
||||||
|
onClick: (e) => this.reset(e)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Rotate left",
|
||||||
|
icon: RotateACW,
|
||||||
|
enabled: true,
|
||||||
|
hidden: !allowRotate,
|
||||||
|
onClick: () => this.applyRotate("acw")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Rotate rigth",
|
||||||
|
icon: RotateCW,
|
||||||
|
enabled: true,
|
||||||
|
hidden: !allowRotate,
|
||||||
|
onClick: () => this.applyRotate("cw")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Zoom out",
|
||||||
|
icon: ZoomOut,
|
||||||
|
enabled: zoom > 1,
|
||||||
|
hidden: !allowZoom,
|
||||||
|
onClick: () => this.applyZoom("out")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Zoom in",
|
||||||
|
icon: ZoomIn,
|
||||||
|
enabled: true,
|
||||||
|
hidden: !allowZoom,
|
||||||
|
onClick: () => this.applyZoom("in")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Close",
|
||||||
|
icon: Close,
|
||||||
|
enabled: !!onClose,
|
||||||
|
onClick: (e) => this.exit(e)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
zIndex: 50000,
|
||||||
|
position: "fixed",
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
bg: "#000000a1",
|
||||||
|
flexDirection: "column"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
zIndex: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
bg="bgSecondary"
|
||||||
|
sx={{
|
||||||
|
borderRadius: "0px 0px 0px 5px",
|
||||||
|
overflow: "hidden",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tools.map((tool) => (
|
||||||
|
<Button
|
||||||
|
data-test-id={tool.title}
|
||||||
|
disabled={!tool.enabled}
|
||||||
|
variant="tool"
|
||||||
|
bg="transparent"
|
||||||
|
title={tool.title}
|
||||||
|
key={tool.title}
|
||||||
|
sx={{
|
||||||
|
borderRadius: 0,
|
||||||
|
display: [
|
||||||
|
tool.hideOnMobile ? "none" : "flex",
|
||||||
|
tool.hidden ? "none" : "flex"
|
||||||
|
],
|
||||||
|
color: tool.enabled ? "text" : "disabled",
|
||||||
|
cursor: tool.enabled ? "pointer" : "not-allowed",
|
||||||
|
flexDirection: "row",
|
||||||
|
flexShrink: 0,
|
||||||
|
alignItems: "center"
|
||||||
|
}}
|
||||||
|
onClick={tool.onClick}
|
||||||
|
>
|
||||||
|
<tool.icon
|
||||||
|
size={18}
|
||||||
|
color={tool.enabled ? "text" : "disabled"}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflow: "hidden",
|
||||||
|
maxWidth: "100%",
|
||||||
|
maxHeight: "100%",
|
||||||
|
position: "relative"
|
||||||
|
}}
|
||||||
|
ref={this._cont}
|
||||||
|
onClick={(e) => this.canvasClick(e)}
|
||||||
|
>
|
||||||
|
{loading ? <Loading color="static" size={60} /> : null}
|
||||||
|
<Image
|
||||||
|
draggable="false"
|
||||||
|
sx={{
|
||||||
|
transform: this.createTransform(x, y, zoom, rotate),
|
||||||
|
cursor: zoom > 1 ? "grab" : "unset",
|
||||||
|
transition: moving ? "none" : "all 0.1s",
|
||||||
|
maxWidth: "80vw",
|
||||||
|
maxHeight: "80vh",
|
||||||
|
minWidth: "100px",
|
||||||
|
minHeight: "100px",
|
||||||
|
backgroundSize: "50px",
|
||||||
|
transformOrigin: "center center"
|
||||||
|
}}
|
||||||
|
onMouseDown={(e) => this.startMove(e)}
|
||||||
|
onTouchStart={(e) => this.startMove(e)}
|
||||||
|
onMouseMove={(e) => this.duringMove(e)}
|
||||||
|
onTouchMove={(e) => this.duringMove(e)}
|
||||||
|
onMouseUp={() => this.endMove()}
|
||||||
|
onMouseLeave={() => this.endMove()}
|
||||||
|
onTouchEnd={() => this.endMove()}
|
||||||
|
onClick={(e) => this.stopSideEffect(e)}
|
||||||
|
onDoubleClick={(e) => this.shockZoom(e)}
|
||||||
|
onLoad={() => this.setState({ loading: false })}
|
||||||
|
src={image}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user