mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +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",
|
||||
"version": "2.4.11",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"diary": "^0.3.1",
|
||||
"electron-updater": "^5.3.0",
|
||||
@@ -4519,7 +4520,8 @@
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"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-loading-skeleton": "^3.1.0",
|
||||
"react-modal": "^3.12.1",
|
||||
"react-pdf": "^6.2.2",
|
||||
"react-qrcode-logo": "^2.2.1",
|
||||
"react-scripts": "^5.0.1",
|
||||
"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,
|
||||
PropsWithChildren
|
||||
} from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Box, Button, Flex, Text } from "@theme-ui/components";
|
||||
import Properties from "../properties";
|
||||
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 { AnimatedFlex } from "../animated";
|
||||
import { EditorLoader } from "../loaders/editor-loader";
|
||||
import { Lightbox } from "../lightbox";
|
||||
import ThemeProviderWrapper from "../theme-provider";
|
||||
import { Document, Page } from "react-pdf";
|
||||
|
||||
type PreviewSession = {
|
||||
content: { data: string; type: string };
|
||||
@@ -317,6 +321,24 @@ export function Editor(props: EditorProps) {
|
||||
onDownloadAttachment={(attachment) =>
|
||||
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) => {
|
||||
const mime = type === "file" ? "*/*" : "image/*";
|
||||
insertAttachment(mime).then((file) => {
|
||||
@@ -369,46 +391,67 @@ function EditorChrome(
|
||||
) : null}
|
||||
|
||||
<Toolbar />
|
||||
<FlexScrollContainer
|
||||
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}
|
||||
<Flex sx={{ justifyContent: "center", overflow: "hidden", flex: 1 }}>
|
||||
<FlexScrollContainer
|
||||
className="editorScroll"
|
||||
style={{ display: "flex", flexDirection: "column", flex: 1 }}
|
||||
>
|
||||
{!isMobile && (
|
||||
<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" }}
|
||||
<Flex
|
||||
variant="columnFill"
|
||||
className="editor"
|
||||
sx={{
|
||||
alignSelf: [
|
||||
"stretch",
|
||||
focusMode ? "center" : "stretch",
|
||||
"center"
|
||||
],
|
||||
maxWidth: editorMargins ? "min(100%, 850px)" : "auto",
|
||||
width: "100%"
|
||||
}}
|
||||
px={6}
|
||||
onClick={onRequestFocus}
|
||||
>
|
||||
{children}
|
||||
</AnimatedFlex>
|
||||
{!isMobile && (
|
||||
<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>
|
||||
</FlexScrollContainer>
|
||||
|
||||
</Flex>
|
||||
{isMobile && (
|
||||
<Box
|
||||
id="editorToolbar"
|
||||
|
||||
@@ -64,6 +64,7 @@ type TipTapProps = {
|
||||
onContentChange?: () => void;
|
||||
onInsertAttachment?: (type: AttachmentType) => void;
|
||||
onDownloadAttachment?: (attachment: Attachment) => void;
|
||||
onPreviewAttachment?: (attachment: Attachment) => void;
|
||||
onAttachFile?: (file: File) => void;
|
||||
onFocus?: () => void;
|
||||
content?: string;
|
||||
@@ -109,6 +110,7 @@ function TipTap(props: TipTapProps) {
|
||||
onChange,
|
||||
onInsertAttachment,
|
||||
onDownloadAttachment,
|
||||
onPreviewAttachment,
|
||||
onAttachFile,
|
||||
onContentChange,
|
||||
onFocus = () => {},
|
||||
@@ -253,6 +255,10 @@ function TipTap(props: TipTapProps) {
|
||||
onDownloadAttachment?.(attachment);
|
||||
return true;
|
||||
},
|
||||
onPreviewAttachment(_editor, attachment) {
|
||||
onPreviewAttachment?.(attachment);
|
||||
return true;
|
||||
},
|
||||
onOpenLink: (url) => {
|
||||
window.open(url, "_blank");
|
||||
return true;
|
||||
|
||||
@@ -197,7 +197,11 @@ import {
|
||||
mdiFileVideoOutline,
|
||||
mdiWeb,
|
||||
mdiUploadOutline,
|
||||
mdiLinkOff
|
||||
mdiLinkOff,
|
||||
mdiMagnifyPlusOutline,
|
||||
mdiMagnifyMinusOutline,
|
||||
mdiRotateRight,
|
||||
mdiRotateLeft
|
||||
} from "@mdi/js";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Theme } from "@notesnook/theme";
|
||||
@@ -490,3 +494,8 @@ export const FileVideo = createIcon(mdiFileVideoOutline);
|
||||
export const FileGeneral = createIcon(mdiFileOutline);
|
||||
export const FileWebClip = createIcon(mdiWeb);
|
||||
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