mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-23 19:49:56 +01:00
web: add option to preview attachments in attachment manager (#9186)
* web: add option to preview attachments in attachment manager Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * web: open pdf preview dialog in full width&height Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * web: minor ui adjustment for pdf preview dialog * web: fix pdf text selection not visible --------- Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
This commit is contained in:
@@ -115,13 +115,13 @@ textarea,
|
||||
}
|
||||
|
||||
.rpv-core__text-layer-text::selection {
|
||||
background-color: var(--background-selected) !important;
|
||||
background-color: var(--accent-selected) !important;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.rpv-core__text-layer-text::-moz-selection {
|
||||
/* Code for Firefox */
|
||||
background-color: var(--background-selected) !important;
|
||||
background-color: var(--accent-selected) !important;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ import { logger } from "../utils/logger";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { db } from "./db";
|
||||
import { checkUpload, decryptFile, saveFile } from "../interfaces/fs";
|
||||
import { ScopedThemeProvider } from "../components/theme-provider";
|
||||
import { Lightbox } from "../components/lightbox";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Attachment } from "@notesnook/core";
|
||||
|
||||
async function download(hash: string, groupId?: string) {
|
||||
const attachment = await db.attachments.attachment(hash);
|
||||
@@ -127,3 +131,29 @@ export async function checkAttachment(hash: string) {
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
export async function previewImageAttachment(attachment: Attachment) {
|
||||
const container = document.getElementById("dialogContainer");
|
||||
if (!(container instanceof HTMLElement)) return;
|
||||
|
||||
const dataurl = await downloadAttachment(
|
||||
attachment.hash,
|
||||
"base64",
|
||||
attachment.id
|
||||
);
|
||||
if (!dataurl) {
|
||||
return showToast("error", strings.imagePreviewFailed());
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<ScopedThemeProvider>
|
||||
<Lightbox
|
||||
image={dataurl}
|
||||
onClose={() => {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
}}
|
||||
/>
|
||||
</ScopedThemeProvider>,
|
||||
container
|
||||
);
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
FileWebClip,
|
||||
Icon,
|
||||
Loading,
|
||||
PasswordInvisible,
|
||||
References,
|
||||
Rename,
|
||||
Reupload,
|
||||
@@ -40,7 +41,11 @@ import {
|
||||
} from "../icons";
|
||||
import { store, useStore } from "../../stores/attachment-store";
|
||||
import { db } from "../../common/db";
|
||||
import { saveAttachment } from "../../common/attachments";
|
||||
import {
|
||||
downloadAttachment,
|
||||
previewImageAttachment,
|
||||
saveAttachment
|
||||
} from "../../common/attachments";
|
||||
import { reuploadAttachment } from "../editor/picker";
|
||||
import { Multiselect } from "../../common/multi-select";
|
||||
import { Menu } from "../../hooks/use-menu";
|
||||
@@ -59,6 +64,8 @@ import { PromptDialog } from "../../dialogs/prompt";
|
||||
import { DialogManager } from "../../common/dialog-manager";
|
||||
import { useStore as useSelectionStore } from "../../stores/selection-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { showToast } from "../../utils/toast";
|
||||
import { PdfPreviewDialog } from "../../dialogs/pdf-preview-dialog";
|
||||
|
||||
const FILE_ICONS: Record<string, Icon> = {
|
||||
"image/": FileImage,
|
||||
@@ -249,6 +256,34 @@ const AttachmentMenuItems: (
|
||||
status?: AttachmentProgressStatus
|
||||
) => MenuItem[] = (attachment, status) => {
|
||||
return [
|
||||
{
|
||||
key: "preview-attachment",
|
||||
type: "button",
|
||||
title: strings.previewAttachment(),
|
||||
icon: PasswordInvisible.path,
|
||||
isHidden:
|
||||
!attachment.mimeType.startsWith("image/") &&
|
||||
attachment.mimeType !== PDFMimeType,
|
||||
onClick: async () => {
|
||||
if (attachment.mimeType.startsWith("image")) {
|
||||
await previewImageAttachment(attachment);
|
||||
} else if (attachment.mimeType === PDFMimeType) {
|
||||
const blob = await downloadAttachment(
|
||||
attachment.hash,
|
||||
"blob",
|
||||
attachment.id
|
||||
);
|
||||
if (!blob) {
|
||||
return showToast("error", strings.attachmentPreviewFailed());
|
||||
}
|
||||
|
||||
PdfPreviewDialog.show({
|
||||
url: URL.createObjectURL(blob),
|
||||
hash: attachment.hash
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "notes",
|
||||
type: "button",
|
||||
|
||||
@@ -44,6 +44,7 @@ type DialogProps = SxProp & {
|
||||
) => void;
|
||||
onOpen?: () => void;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
showCloseButton?: boolean;
|
||||
textAlignment?: "left" | "right" | "center";
|
||||
buttonsAlignment?: "start" | "center" | "end";
|
||||
@@ -98,7 +99,7 @@ function BaseDialog(props: React.PropsWithChildren<DialogProps>) {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: ["100%", "90%", props.width || "380px"],
|
||||
maxHeight: ["100%", "80%", "70%"],
|
||||
maxHeight: ["100%", "80%", props.height || "70%"],
|
||||
height: ["100%", "auto", "auto"],
|
||||
bg: "background",
|
||||
alignSelf: "center",
|
||||
|
||||
@@ -26,7 +26,6 @@ import React, {
|
||||
useLayoutEffect,
|
||||
useCallback
|
||||
} from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Box, Button, Flex, Progress, Text } from "@theme-ui/components";
|
||||
import Properties from "../properties";
|
||||
import {
|
||||
@@ -53,13 +52,16 @@ import Header from "./header";
|
||||
import { Attachment } from "../icons";
|
||||
import { attachFiles, AttachmentProgress, insertAttachments } from "./picker";
|
||||
import { useEditorManager } from "./manager";
|
||||
import { saveAttachment, downloadAttachment } from "../../common/attachments";
|
||||
import {
|
||||
saveAttachment,
|
||||
downloadAttachment,
|
||||
previewImageAttachment
|
||||
} from "../../common/attachments";
|
||||
import { EV, EVENTS } from "@notesnook/core";
|
||||
import { db } from "../../common/db";
|
||||
import Titlebox, { resizeTextarea } from "./title-box";
|
||||
import Config from "../../utils/config";
|
||||
import { ScopedThemeProvider } from "../theme-provider";
|
||||
import { Lightbox } from "../lightbox";
|
||||
import { showToast } from "../../utils/toast";
|
||||
import { Item, MaybeDeletedItem, isDeleted } from "@notesnook/core";
|
||||
import { debounce, debounceWithId } from "@notesnook/common";
|
||||
@@ -571,24 +573,7 @@ export function Editor(props: EditorProps) {
|
||||
const { hash, type } = data;
|
||||
const attachment = await db.attachments.attachment(hash);
|
||||
if (attachment && type === "image") {
|
||||
const container = document.getElementById("dialogContainer");
|
||||
if (!(container instanceof HTMLElement)) return;
|
||||
|
||||
const dataurl = await downloadAttachment(hash, "base64", id);
|
||||
if (!dataurl)
|
||||
return showToast("error", strings.imagePreviewFailed());
|
||||
|
||||
ReactDOM.render(
|
||||
<ScopedThemeProvider>
|
||||
<Lightbox
|
||||
image={dataurl}
|
||||
onClose={() => {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
}}
|
||||
/>
|
||||
</ScopedThemeProvider>,
|
||||
container
|
||||
);
|
||||
await previewImageAttachment(attachment);
|
||||
} else if (
|
||||
attachment &&
|
||||
onPreviewDocument &&
|
||||
|
||||
55
apps/web/src/dialogs/pdf-preview-dialog.tsx
Normal file
55
apps/web/src/dialogs/pdf-preview-dialog.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
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 React, { Suspense } from "react";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import Dialog from "../components/dialog";
|
||||
import { Loading } from "../components/icons";
|
||||
|
||||
const PdfPreview = React.lazy(() => import("../components/pdf-preview"));
|
||||
|
||||
type PdfPreviewDialogProps = BaseDialogProps<boolean> & {
|
||||
url: string;
|
||||
hash: string;
|
||||
};
|
||||
|
||||
export const PdfPreviewDialog = DialogManager.register(
|
||||
function PdfPreviewDialog({ onClose, url, hash }: PdfPreviewDialogProps) {
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
width="100%"
|
||||
height="100%"
|
||||
noScroll
|
||||
sx={{
|
||||
py: 1
|
||||
}}
|
||||
onClose={() => onClose(false)}
|
||||
>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<PdfPreview
|
||||
fileUrl={url}
|
||||
hash={hash}
|
||||
onClose={() => onClose(false)}
|
||||
/>
|
||||
</Suspense>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user