From 2c586f3c13f15010b08324557bbd89ba35fd00cb Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 14 Aug 2024 18:59:00 +0200 Subject: [PATCH 01/10] feat(verify-page): add files view and content images --- src/components/UsersDetails.tsx/index.tsx | 8 +- src/layouts/StickySideColumns.module.scss | 8 +- src/pages/verify/index.tsx | 151 ++++++++++++++-------- src/pages/verify/style.module.scss | 24 ++++ src/utils/meta.ts | 10 +- 5 files changed, 142 insertions(+), 59 deletions(-) diff --git a/src/components/UsersDetails.tsx/index.tsx b/src/components/UsersDetails.tsx/index.tsx index fc7d43d..021918c 100644 --- a/src/components/UsersDetails.tsx/index.tsx +++ b/src/components/UsersDetails.tsx/index.tsx @@ -56,7 +56,7 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => { typeof usersPubkey !== 'undefined' && signers.includes(hexToNpub(usersPubkey)) - const ext = extractFileExtensions(Object.keys(fileHashes)) + const { extensions, isSame } = extractFileExtensions(Object.keys(fileHashes)) return submittedBy ? (
@@ -196,14 +196,14 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => { {signedStatus} - {ext.length > 0 ? ( + {extensions.length > 0 ? ( - {ext.length > 1 ? ( + {!isSame ? ( <> Multiple File Types ) : ( - getExtensionIconLabel(ext[0]) + getExtensionIconLabel(extensions[0]) )} ) : ( diff --git a/src/layouts/StickySideColumns.module.scss b/src/layouts/StickySideColumns.module.scss index fd99964..7690822 100644 --- a/src/layouts/StickySideColumns.module.scss +++ b/src/layouts/StickySideColumns.module.scss @@ -23,7 +23,11 @@ grid-gap: 15px; } .content { - max-width: 550px; - width: 550px; + padding: 10px; + border: 10px solid $overlay-background-color; + border-radius: 4px; + + max-width: 590px; + width: 590px; margin: 0 auto; } diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index 69f7f0b..017b9c5 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -1,8 +1,8 @@ -import { Box, Button, Tooltip, Typography, useTheme } from '@mui/material' +import { Box, Button, Divider, Tooltip, Typography } from '@mui/material' import JSZip from 'jszip' import { MuiFileInput } from 'mui-file-input' import { Event, verifyEvent } from 'nostr-tools' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { toast } from 'react-toastify' import { LoadingSpinner } from '../../components/LoadingSpinner' import { NostrController } from '../../controllers' @@ -16,10 +16,10 @@ import { parseJson, readContentOfZipEntry, signEventForMetaFile, - shorten + shorten, + getCurrentUserFiles } from '../../utils' import styles from './style.module.scss' -import { Cancel, CheckCircle } from '@mui/icons-material' import { useLocation } from 'react-router-dom' import axios from 'axios' import { PdfFile } from '../../types/drawing.ts' @@ -27,7 +27,8 @@ import { addMarks, convertToPdfBlob, convertToPdfFile, - groupMarksByPage + groupMarksByPage, + inPx } from '../../utils/pdf.ts' import { State } from '../../store/rootReducer.ts' import { useSelector } from 'react-redux' @@ -40,13 +41,77 @@ import { UsersDetails } from '../../components/UsersDetails.tsx/index.tsx' import { UserAvatar } from '../../components/UserAvatar/index.tsx' import { useSigitProfiles } from '../../hooks/useSigitProfiles.tsx' import { TooltipChild } from '../../components/TooltipChild.tsx' +import FileList from '../../components/FileList' +import { CurrentUserFile } from '../../types/file.ts' + +interface PdfViewProps { + files: CurrentUserFile[] + currentFile: CurrentUserFile | null +} + +const SlimPdfView = ({ files, currentFile }: PdfViewProps) => { + const pdfRefs = useRef<(HTMLDivElement | null)[]>([]) + useEffect(() => { + if (currentFile !== null && !!pdfRefs.current[currentFile.id]) { + pdfRefs.current[currentFile.id]?.scrollIntoView({ + behavior: 'smooth', + block: 'end' + }) + } + }, [currentFile]) + return ( +
+ {files.map((currentUserFile, i) => { + const { hash, filename, pdfFile, id } = currentUserFile + if (!hash) return + return ( + <> +
(pdfRefs.current[id] = el)} + key={filename} + className={styles.fileWrapper} + > + {pdfFile.pages.map((page, i) => { + return ( +
+ + {page.drawnFields.map((f, i) => ( +
+ ))} +
+ ) + })} +
+ + {i < files.length - 1 && ( + + File Separator + + )} + + ) + })} +
+ ) +} export const VerifyPage = () => { - const theme = useTheme() - const textColor = theme.palette.getContrastText( - theme.palette.background.paper - ) - const location = useLocation() /** * uploadedZip will be received from home page when a user uploads a sigit zip wrapper that contains meta.json @@ -57,6 +122,8 @@ export const VerifyPage = () => { const { submittedBy, zipUrl, encryptionKey, signers, viewers, fileHashes } = useSigitMeta(meta) + console.log('----------', meta) + const profiles = useSigitProfiles([ ...(submittedBy ? [submittedBy] : []), ...signers, @@ -72,6 +139,15 @@ export const VerifyPage = () => { [key: string]: string | null }>(fileHashes) const [files, setFiles] = useState<{ [filename: string]: PdfFile }>({}) + const [currentFile, setCurrentFile] = useState(null) + + useEffect(() => { + if (Object.entries(files).length > 0) { + const tmp = getCurrentUserFiles(files, fileHashes) + + setCurrentFile(tmp[0]) + } + }, [fileHashes, files]) const usersPubkey = useSelector((state: State) => state.auth.usersPubkey) const nostrController = NostrController.getInstance() @@ -414,51 +490,24 @@ export const VerifyPage = () => { - - {Object.entries(currentFileHashes).map( - ([filename, hash], index) => { - const isValidHash = fileHashes[filename] === hash - - return ( - - - {filename} - - {isValidHash && ( - - - - )} - {!isValidHash && ( - - - - )} - - ) - } - )} - + {currentFile !== null && ( + + )} {displayExportedBy()} - - - } right={} - /> + > + + )} diff --git a/src/pages/verify/style.module.scss b/src/pages/verify/style.module.scss index ea6408f..c368748 100644 --- a/src/pages/verify/style.module.scss +++ b/src/pages/verify/style.module.scss @@ -50,3 +50,27 @@ } } } + +.view { + width: 550px; + max-width: 550px; + + display: flex; + flex-direction: column; + gap: 25px; +} + +.imageWrapper { + position: relative; + + img { + width: 100%; + display: block; + } +} + +.fileWrapper { + display: flex; + flex-direction: column; + gap: 15px; +} diff --git a/src/utils/meta.ts b/src/utils/meta.ts index 4915f19..98ec127 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -142,7 +142,7 @@ export const extractSigitCardDisplayInfo = async (meta: Meta) => { ) const files = Object.keys(createSignatureContent.fileHashes) - const extensions = extractFileExtensions(files) + const { extensions } = extractFileExtensions(files) const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[] const isCompletelySigned = createSignatureContent.signers.every((signer) => @@ -169,6 +169,10 @@ export const extractSigitCardDisplayInfo = async (meta: Meta) => { } } +/** + * @param fileNames - List of filenames to check + * @returns List of extensions and if all are same + */ export const extractFileExtensions = (fileNames: string[]) => { const extensions = fileNames.reduce((result: string[], file: string) => { const extension = file.split('.').pop() @@ -178,5 +182,7 @@ export const extractFileExtensions = (fileNames: string[]) => { return result }, []) - return extensions + const isSame = extensions.every((ext) => ext === extensions[0]) + + return { extensions, isSame } } From f88e2ad6804424755dacfc8c89da4fb8c2b90fcc Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 15 Aug 2024 13:47:23 +0200 Subject: [PATCH 02/10] fix(verify-page): parse and show mark values --- src/hooks/useSigitMeta.tsx | 23 +++++++++--- src/pages/verify/index.tsx | 74 +++++++++++++++++++++++++++----------- src/types/core.ts | 5 +++ 3 files changed, 77 insertions(+), 25 deletions(-) diff --git a/src/hooks/useSigitMeta.tsx b/src/hooks/useSigitMeta.tsx index a393824..fea5154 100644 --- a/src/hooks/useSigitMeta.tsx +++ b/src/hooks/useSigitMeta.tsx @@ -1,5 +1,10 @@ import { useEffect, useState } from 'react' -import { CreateSignatureEventContent, Meta, SignedEventContent } from '../types' +import { + CreateSignatureEventContent, + DocSignatureEvent, + Meta, + SignedEventContent +} from '../types' import { Mark } from '../types/mark' import { fromUnixTimestamp, @@ -38,7 +43,9 @@ export interface FlatMeta encryptionKey: string | null // Parsed Document Signatures - parsedSignatureEvents: { [signer: `npub1${string}`]: Event } + parsedSignatureEvents: { + [signer: `npub1${string}`]: DocSignatureEvent + } // Calculated completion time completedAt?: number @@ -74,7 +81,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => { const [zipUrl, setZipUrl] = useState('') const [parsedSignatureEvents, setParsedSignatureEvents] = useState<{ - [signer: `npub1${string}`]: Event + [signer: `npub1${string}`]: DocSignatureEvent }>({}) const [completedAt, setCompletedAt] = useState() @@ -141,7 +148,10 @@ export const useSigitMeta = (meta: Meta): FlatMeta => { } // Temp. map to hold events and signers - const parsedSignatureEventsMap = new Map<`npub1${string}`, Event>() + const parsedSignatureEventsMap = new Map< + `npub1${string}`, + DocSignatureEvent + >() const signerStatusMap = new Map<`npub1${string}`, SignStatus>() const getPrevSignerSig = (npub: `npub1${string}`) => { @@ -183,9 +193,12 @@ export const useSigitMeta = (meta: Meta): FlatMeta => { if (isValidSignature) { // get the signature of prev signer from the content of current signers signedEvent const prevSignersSig = getPrevSignerSig(npub) - try { const obj: SignedEventContent = JSON.parse(event.content) + parsedSignatureEventsMap.set(npub, { + ...event, + parsedContent: obj + }) if ( obj.prevSig && prevSignersSig && diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index 017b9c5..b5628db 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -6,7 +6,11 @@ import { useEffect, useRef, useState } from 'react' import { toast } from 'react-toastify' import { LoadingSpinner } from '../../components/LoadingSpinner' import { NostrController } from '../../controllers' -import { CreateSignatureEventContent, Meta } from '../../types' +import { + CreateSignatureEventContent, + DocSignatureEvent, + Meta +} from '../../types' import { decryptArrayBuffer, extractMarksFromSignedMeta, @@ -43,13 +47,21 @@ import { useSigitProfiles } from '../../hooks/useSigitProfiles.tsx' import { TooltipChild } from '../../components/TooltipChild.tsx' import FileList from '../../components/FileList' import { CurrentUserFile } from '../../types/file.ts' +import { Mark } from '../../types/mark.ts' interface PdfViewProps { files: CurrentUserFile[] currentFile: CurrentUserFile | null + parsedSignatureEvents: { + [signer: `npub1${string}`]: DocSignatureEvent + } } -const SlimPdfView = ({ files, currentFile }: PdfViewProps) => { +const SlimPdfView = ({ + files, + currentFile, + parsedSignatureEvents +}: PdfViewProps) => { const pdfRefs = useRef<(HTMLDivElement | null)[]>([]) useEffect(() => { if (currentFile !== null && !!pdfRefs.current[currentFile.id]) { @@ -63,6 +75,7 @@ const SlimPdfView = ({ files, currentFile }: PdfViewProps) => {
{files.map((currentUserFile, i) => { const { hash, filename, pdfFile, id } = currentUserFile + const signatureEvents = Object.keys(parsedSignatureEvents) if (!hash) return return ( <> @@ -73,22 +86,38 @@ const SlimPdfView = ({ files, currentFile }: PdfViewProps) => { className={styles.fileWrapper} > {pdfFile.pages.map((page, i) => { + const marks: Mark[] = [] + + signatureEvents.forEach((e) => { + const m = parsedSignatureEvents[ + e as `npub1${string}` + ].parsedContent?.marks.filter( + (m) => m.pdfFileHash == hash && m.location.page == i + ) + if (m) { + marks.push(...m) + } + }) return (
- {page.drawnFields.map((f, i) => ( -
- ))} + {marks.map((m) => { + return ( +
+ {m.value} +
+ ) + })}
) })} @@ -119,10 +148,15 @@ export const VerifyPage = () => { */ const { uploadedZip, meta } = location.state || {} - const { submittedBy, zipUrl, encryptionKey, signers, viewers, fileHashes } = - useSigitMeta(meta) - - console.log('----------', meta) + const { + submittedBy, + zipUrl, + encryptionKey, + signers, + viewers, + fileHashes, + parsedSignatureEvents + } = useSigitMeta(meta) const profiles = useSigitProfiles([ ...(submittedBy ? [submittedBy] : []), @@ -279,7 +313,6 @@ export const VerifyPage = () => { } } - console.log('fileHashes :>> ', fileHashes) setCurrentFileHashes(fileHashes) setLoadingSpinnerDesc('Parsing meta.json') @@ -506,6 +539,7 @@ export const VerifyPage = () => { )} diff --git a/src/types/core.ts b/src/types/core.ts index 609837f..8583d4a 100644 --- a/src/types/core.ts +++ b/src/types/core.ts @@ -1,5 +1,6 @@ import { Mark } from './mark' import { Keys } from '../store/auth/types' +import { Event } from 'nostr-tools' export enum UserRole { signer = 'Signer', @@ -44,3 +45,7 @@ export interface UserAppData { keyPair?: Keys // this key pair is used for blossom requests authentication blossomUrls: string[] // array for storing Urls for the files that stores all the sigits and processedGiftWraps on blossom } + +export interface DocSignatureEvent extends Event { + parsedContent?: SignedEventContent +} From 78060fa15fb55c597918042559d544ff4528dc24 Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 15 Aug 2024 16:43:10 +0200 Subject: [PATCH 03/10] fix(marks): assign selectedMarkValue to currentValue and mark.value --- src/utils/mark.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/mark.ts b/src/utils/mark.ts index 18cc3e8..f0cd47e 100644 --- a/src/utils/mark.ts +++ b/src/utils/mark.ts @@ -119,7 +119,11 @@ const getUpdatedMark = ( return { ...selectedMark, currentValue: selectedMarkValue, - isCompleted: !!selectedMarkValue + isCompleted: !!selectedMarkValue, + mark: { + ...selectedMark.mark, + value: selectedMarkValue + } } } From 7278485b76c6e4a6319d1f3a7941d4985dc93cad Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 15 Aug 2024 16:44:53 +0200 Subject: [PATCH 04/10] fix(verify-page): export (download) files now includes files The issue was noticed on the windows machine, removing forward slash made it work --- src/utils/file.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/file.ts b/src/utils/file.ts index 94308d5..619ecff 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -15,7 +15,7 @@ const getZipWithFiles = async ( for (const [fileName, pdf] of Object.entries(files)) { const pages = await addMarks(pdf.file, marksByPage) const blob = await convertToPdfBlob(pages) - zip.file(`/files/${fileName}`, blob) + zip.file(`files/${fileName}`, blob) } return zip From 268a4db3ff211566af3e8cf77838c54d3e9c861e Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 15 Aug 2024 17:02:30 +0200 Subject: [PATCH 05/10] revert: "feat(pdf-marking): adds file validity check" Refs: ed7acd6cb4c73ee2907fb5062a10dbb8d369f7c9 --- src/components/FileList/index.tsx | 12 ++---------- src/components/FileList/style.module.scss | 23 ----------------------- src/pages/sign/index.tsx | 2 +- src/types/file.ts | 1 - src/utils/utils.ts | 7 ++----- 5 files changed, 5 insertions(+), 40 deletions(-) diff --git a/src/components/FileList/index.tsx b/src/components/FileList/index.tsx index 7cd30eb..f1fdcd8 100644 --- a/src/components/FileList/index.tsx +++ b/src/components/FileList/index.tsx @@ -1,8 +1,6 @@ import { CurrentUserFile } from '../../types/file.ts' import styles from './style.module.scss' import { Button } from '@mui/material' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faCheck } from '@fortawesome/free-solid-svg-icons' interface FileListProps { files: CurrentUserFile[] @@ -28,14 +26,8 @@ const FileList = ({ className={`${styles.fileItem} ${isActive(file) && styles.active}`} onClick={() => setCurrentFile(file)} > -
{file.id}
-
-
{file.filename}
-
- -
- {file.isHashValid && } -
+ {file.id} + {file.filename} ))} diff --git a/src/components/FileList/style.module.scss b/src/components/FileList/style.module.scss index 6f7b64a..40b83a3 100644 --- a/src/components/FileList/style.module.scss +++ b/src/components/FileList/style.module.scss @@ -21,12 +21,6 @@ ul { padding: 0; /* Removes default padding */ } -li { - list-style-type: none; /* Removes the bullets */ - margin: 0; /* Removes any default margin */ - padding: 0; /* Removes any default padding */ -} - .wrap { display: flex; @@ -89,12 +83,6 @@ li { color: white; } -.fileInfo { - flex-grow: 1; - font-size: 16px; - font-weight: 500; -} - .fileName { display: -webkit-box; -webkit-box-orient: vertical; @@ -109,15 +97,4 @@ li { flex-direction: column; justify-content: center; align-items: center; - width: 10px; -} - -.fileVisual { - display: flex; - flex-shrink: 0; - flex-direction: column; - justify-content: center; - align-items: center; - height: 25px; - width: 25px; } \ No newline at end of file diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index a762292..9f2ab7c 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -952,7 +952,7 @@ export const SignPage = () => { return ( { * including its name, hash, and content * @param files * @param fileHashes - * @param creatorFileHashes */ export const getCurrentUserFiles = ( files: { [filename: string]: PdfFile }, - fileHashes: { [key: string]: string | null }, - creatorFileHashes: { [key: string]: string } + fileHashes: { [key: string]: string | null } ): CurrentUserFile[] => { return Object.entries(files).map(([filename, pdfFile], index) => { return { pdfFile, filename, id: index + 1, - ...(!!fileHashes[filename] && { hash: fileHashes[filename]! }), - isHashValid: creatorFileHashes[filename] === fileHashes[filename] + ...(!!fileHashes[filename] && { hash: fileHashes[filename]! }) } }) } From 423b6b6792feca3ff5891bfc1d9f2181d8a00195 Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 15 Aug 2024 17:34:11 +0200 Subject: [PATCH 06/10] fix(verify-page): add mark styling --- src/pages/verify/index.tsx | 3 +-- src/pages/verify/style.module.scss | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index b5628db..aa79b73 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -104,10 +104,9 @@ const SlimPdfView = ({ {marks.map((m) => { return (
Date: Thu, 15 Aug 2024 17:48:05 +0200 Subject: [PATCH 07/10] fix(sigit): add to submittedBy avatar badge for verified sigit creation --- src/components/DisplaySigit/index.tsx | 13 +++++++++---- src/components/UsersDetails.tsx/index.tsx | 11 ++++++++--- src/utils/meta.ts | 8 ++++++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx index 0d7407f..92dc01d 100644 --- a/src/components/DisplaySigit/index.tsx +++ b/src/components/DisplaySigit/index.tsx @@ -1,5 +1,5 @@ import { Meta } from '../../types' -import { SigitCardDisplayInfo, SigitStatus } from '../../utils' +import { SigitCardDisplayInfo, SigitStatus, SignStatus } from '../../utils' import { Link } from 'react-router-dom' import { formatTimestamp, hexToNpub, npubToHex, shorten } from '../../utils' import { appPublicRoutes, appPrivateRoutes } from '../../routes' @@ -13,7 +13,6 @@ import { faFile } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { UserAvatar } from '../UserAvatar' import { UserAvatarGroup } from '../UserAvatarGroup' import styles from './style.module.scss' @@ -34,7 +33,8 @@ export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => { submittedBy, signers, signedStatus, - fileExtensions + fileExtensions, + isValid } = parsedMeta const { signersStatus } = useSigitMeta(meta) @@ -62,6 +62,7 @@ export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => { const profile = profiles[submittedBy] return ( { disableInteractive > - + ) diff --git a/src/components/UsersDetails.tsx/index.tsx b/src/components/UsersDetails.tsx/index.tsx index 021918c..3681cfd 100644 --- a/src/components/UsersDetails.tsx/index.tsx +++ b/src/components/UsersDetails.tsx/index.tsx @@ -9,7 +9,6 @@ import { shorten, SignStatus } from '../../utils' -import { UserAvatar } from '../UserAvatar' import { useSigitMeta } from '../../hooks/useSigitMeta' import { UserAvatarGroup } from '../UserAvatarGroup' @@ -44,7 +43,8 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => { createdAt, completedAt, parsedSignatureEvents, - signedStatus + signedStatus, + isValid } = useSigitMeta(meta) const { usersPubkey } = useSelector((state: State) => state.auth) const profiles = useSigitProfiles([ @@ -68,6 +68,7 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => { const profile = profiles[submittedBy] return ( { disableInteractive > - + ) diff --git a/src/utils/meta.ts b/src/utils/meta.ts index 98ec127..276f049 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -1,6 +1,6 @@ import { CreateSignatureEventContent, Meta } from '../types' import { fromUnixTimestamp, parseJson } from '.' -import { Event } from 'nostr-tools' +import { Event, verifyEvent } from 'nostr-tools' import { toast } from 'react-toastify' export enum SignStatus { @@ -75,6 +75,7 @@ export interface SigitCardDisplayInfo { signers: `npub1${string}`[] fileExtensions: string[] signedStatus: SigitStatus + isValid: boolean } /** @@ -128,12 +129,15 @@ export const extractSigitCardDisplayInfo = async (meta: Meta) => { const sigitInfo: SigitCardDisplayInfo = { signers: [], fileExtensions: [], - signedStatus: SigitStatus.Partial + signedStatus: SigitStatus.Partial, + isValid: false } try { const createSignatureEvent = await parseNostrEvent(meta.createSignature) + sigitInfo.isValid = verifyEvent(createSignatureEvent) + // created_at in nostr events are stored in seconds sigitInfo.createdAt = fromUnixTimestamp(createSignatureEvent.created_at) From b6479db2665ef500d20831dcd941d5ce728b79d3 Mon Sep 17 00:00:00 2001 From: enes Date: Fri, 16 Aug 2024 12:01:41 +0200 Subject: [PATCH 08/10] fix(marks): add file grouping for marks, fix read pdf types --- src/pages/verify/index.tsx | 6 ++--- src/utils/file.ts | 6 ++--- src/utils/pdf.ts | 47 ++++++++++++++++++++++++++------------ 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index aa79b73..865310e 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -31,7 +31,7 @@ import { addMarks, convertToPdfBlob, convertToPdfFile, - groupMarksByPage, + groupMarksByFileNamePage, inPx } from '../../utils/pdf.ts' import { State } from '../../store/rootReducer.ts' @@ -415,10 +415,10 @@ export const VerifyPage = () => { zip.file('meta.json', stringifiedMeta) const marks = extractMarksFromSignedMeta(updatedMeta) - const marksByPage = groupMarksByPage(marks) + const marksByPage = groupMarksByFileNamePage(marks) for (const [fileName, pdf] of Object.entries(files)) { - const pages = await addMarks(pdf.file, marksByPage) + const pages = await addMarks(pdf.file, marksByPage[fileName]) const blob = await convertToPdfBlob(pages) zip.file(`files/${fileName}`, blob) } diff --git a/src/utils/file.ts b/src/utils/file.ts index 619ecff..401d3c4 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -1,6 +1,6 @@ import { Meta } from '../types' import { extractMarksFromSignedMeta } from './mark.ts' -import { addMarks, convertToPdfBlob, groupMarksByPage } from './pdf.ts' +import { addMarks, convertToPdfBlob, groupMarksByFileNamePage } from './pdf.ts' import JSZip from 'jszip' import { PdfFile } from '../types/drawing.ts' @@ -10,10 +10,10 @@ const getZipWithFiles = async ( ): Promise => { const zip = new JSZip() const marks = extractMarksFromSignedMeta(meta) - const marksByPage = groupMarksByPage(marks) + const marksByFileNamePage = groupMarksByFileNamePage(marks) for (const [fileName, pdf] of Object.entries(files)) { - const pages = await addMarks(pdf.file, marksByPage) + const pages = await addMarks(pdf.file, marksByFileNamePage[fileName]) const blob = await convertToPdfBlob(pages) zip.file(`files/${fileName}`, blob) } diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts index cef12c2..ce2f132 100644 --- a/src/utils/pdf.ts +++ b/src/utils/pdf.ts @@ -71,14 +71,19 @@ const isPdf = (file: File) => file.type.toLowerCase().includes('pdf') /** * Reads the pdf file binaries */ -const readPdf = (file: File): Promise => { +const readPdf = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader() - reader.onload = (e: any) => { - const data = e.target.result - - resolve(data) + reader.onload = (e) => { + const data = e.target?.result + // Make sure we only resolve for string or ArrayBuffer type + // They are accepted by PDFJS.getDocument function + if (data && typeof data !== 'undefined') { + resolve(data) + } else { + reject(new Error('File is null or undefined')) + } } reader.onerror = (err) => { @@ -94,7 +99,7 @@ const readPdf = (file: File): Promise => { * Converts pdf to the images * @param data pdf file bytes */ -const pdfToImages = async (data: any): Promise => { +const pdfToImages = async (data: string | ArrayBuffer): Promise => { const images: string[] = [] const pdf = await PDFJS.getDocument(data).promise const canvas = document.createElement('canvas') @@ -142,7 +147,8 @@ const addMarks = async ( canvas.width = viewport.width await page.render({ canvasContext: context!, viewport: viewport }).promise - marksPerPage[i]?.forEach((mark) => draw(mark, context!)) + if (marksPerPage && Object.hasOwn(marksPerPage, i)) + marksPerPage[i]?.forEach((mark) => draw(mark, context!)) images.push(canvas.toDataURL()) } @@ -230,11 +236,11 @@ const convertToPdfFile = async ( * @function scaleMark scales remaining marks in line with SCALE * @function byPage groups remaining Marks by their page marks.location.page */ -const groupMarksByPage = (marks: Mark[]) => { +const groupMarksByFileNamePage = (marks: Mark[]) => { return marks .filter(hasValue) .map(scaleMark) - .reduce<{ [key: number]: Mark[] }>(byPage, {}) + .reduce<{ [filename: string]: { [page: number]: Mark[] } }>(byPage, {}) } /** @@ -245,10 +251,21 @@ const groupMarksByPage = (marks: Mark[]) => { * @param obj - accumulator in the reducer callback * @param mark - current value, i.e. Mark being examined */ -const byPage = (obj: { [key: number]: Mark[] }, mark: Mark) => { - const key = mark.location.page - const curGroup = obj[key] ?? [] - return { ...obj, [key]: [...curGroup, mark] } +const byPage = ( + obj: { [filename: string]: { [page: number]: Mark[] } }, + mark: Mark +) => { + const filename = mark.fileName + const pageNumber = mark.location.page + const pages = obj[filename] ?? {} + const marks = pages[pageNumber] ?? [] + return { + ...obj, + [filename]: { + ...pages, + [pageNumber]: [...marks, mark] + } + } } export { @@ -259,5 +276,5 @@ export { convertToPdfFile, addMarks, convertToPdfBlob, - groupMarksByPage -} \ No newline at end of file + groupMarksByFileNamePage +} From eca31cea4f68730ab5d70428902a04514d268764 Mon Sep 17 00:00:00 2001 From: Eugene Date: Thu, 15 Aug 2024 15:35:37 +0300 Subject: [PATCH 09/10] feat(pdf-marking): adds file validity check --- src/components/FileList/index.tsx | 12 ++++++++++-- src/components/FileList/style.module.scss | 23 +++++++++++++++++++++++ src/pages/sign/index.tsx | 2 +- src/types/file.ts | 1 + src/utils/utils.ts | 7 +++++-- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/components/FileList/index.tsx b/src/components/FileList/index.tsx index f1fdcd8..7cd30eb 100644 --- a/src/components/FileList/index.tsx +++ b/src/components/FileList/index.tsx @@ -1,6 +1,8 @@ import { CurrentUserFile } from '../../types/file.ts' import styles from './style.module.scss' import { Button } from '@mui/material' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCheck } from '@fortawesome/free-solid-svg-icons' interface FileListProps { files: CurrentUserFile[] @@ -26,8 +28,14 @@ const FileList = ({ className={`${styles.fileItem} ${isActive(file) && styles.active}`} onClick={() => setCurrentFile(file)} > - {file.id} - {file.filename} +
{file.id}
+
+
{file.filename}
+
+ +
+ {file.isHashValid && } +
))} diff --git a/src/components/FileList/style.module.scss b/src/components/FileList/style.module.scss index 40b83a3..6f7b64a 100644 --- a/src/components/FileList/style.module.scss +++ b/src/components/FileList/style.module.scss @@ -21,6 +21,12 @@ ul { padding: 0; /* Removes default padding */ } +li { + list-style-type: none; /* Removes the bullets */ + margin: 0; /* Removes any default margin */ + padding: 0; /* Removes any default padding */ +} + .wrap { display: flex; @@ -83,6 +89,12 @@ ul { color: white; } +.fileInfo { + flex-grow: 1; + font-size: 16px; + font-weight: 500; +} + .fileName { display: -webkit-box; -webkit-box-orient: vertical; @@ -97,4 +109,15 @@ ul { flex-direction: column; justify-content: center; align-items: center; + width: 10px; +} + +.fileVisual { + display: flex; + flex-shrink: 0; + flex-direction: column; + justify-content: center; + align-items: center; + height: 25px; + width: 25px; } \ No newline at end of file diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 9f2ab7c..a762292 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -952,7 +952,7 @@ export const SignPage = () => { return ( { * including its name, hash, and content * @param files * @param fileHashes + * @param creatorFileHashes */ export const getCurrentUserFiles = ( files: { [filename: string]: PdfFile }, - fileHashes: { [key: string]: string | null } + fileHashes: { [key: string]: string | null }, + creatorFileHashes: { [key: string]: string } ): CurrentUserFile[] => { return Object.entries(files).map(([filename, pdfFile], index) => { return { pdfFile, filename, id: index + 1, - ...(!!fileHashes[filename] && { hash: fileHashes[filename]! }) + ...(!!fileHashes[filename] && { hash: fileHashes[filename]! }), + isHashValid: creatorFileHashes[filename] === fileHashes[filename] } }) } From 18637bbbc193f970c03c9b19d522fff29390273f Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 20 Aug 2024 11:44:34 +0200 Subject: [PATCH 10/10] fix: update verify to use file signature check --- src/pages/verify/index.tsx | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index 865310e..ceba0eb 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -170,17 +170,23 @@ export const VerifyPage = () => { const [currentFileHashes, setCurrentFileHashes] = useState<{ [key: string]: string | null - }>(fileHashes) + }>({}) const [files, setFiles] = useState<{ [filename: string]: PdfFile }>({}) const [currentFile, setCurrentFile] = useState(null) + const [signatureFileHashes, setSignatureFileHashes] = useState<{ + [key: string]: string + }>(fileHashes) + + useEffect(() => { + setSignatureFileHashes(fileHashes) + }, [fileHashes]) useEffect(() => { if (Object.entries(files).length > 0) { - const tmp = getCurrentUserFiles(files, fileHashes) - + const tmp = getCurrentUserFiles(files, fileHashes, signatureFileHashes) setCurrentFile(tmp[0]) } - }, [fileHashes, files]) + }, [signatureFileHashes, fileHashes, files]) const usersPubkey = useSelector((state: State) => state.auth.usersPubkey) const nostrController = NostrController.getInstance() @@ -524,7 +530,11 @@ export const VerifyPage = () => { <> {currentFile !== null && ( { >