Compare commits
No commits in common. "b6e739bc143ab831186c33d54073b0377479454b" and "f162baadbbe3e71b773175492f7465188730ed8c" have entirely different histories.
b6e739bc14
...
f162baadbb
@ -1,5 +1,5 @@
|
|||||||
import { Meta } from '../../types'
|
import { Meta } from '../../types'
|
||||||
import { SigitCardDisplayInfo, SigitStatus, SignStatus } from '../../utils'
|
import { SigitCardDisplayInfo, SigitStatus } from '../../utils'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { formatTimestamp, hexToNpub, npubToHex, shorten } from '../../utils'
|
import { formatTimestamp, hexToNpub, npubToHex, shorten } from '../../utils'
|
||||||
import { appPublicRoutes, appPrivateRoutes } from '../../routes'
|
import { appPublicRoutes, appPrivateRoutes } from '../../routes'
|
||||||
@ -13,6 +13,7 @@ import {
|
|||||||
faFile
|
faFile
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { UserAvatar } from '../UserAvatar'
|
||||||
import { UserAvatarGroup } from '../UserAvatarGroup'
|
import { UserAvatarGroup } from '../UserAvatarGroup'
|
||||||
|
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
@ -33,8 +34,7 @@ export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => {
|
|||||||
submittedBy,
|
submittedBy,
|
||||||
signers,
|
signers,
|
||||||
signedStatus,
|
signedStatus,
|
||||||
fileExtensions,
|
fileExtensions
|
||||||
isValid
|
|
||||||
} = parsedMeta
|
} = parsedMeta
|
||||||
|
|
||||||
const { signersStatus } = useSigitMeta(meta)
|
const { signersStatus } = useSigitMeta(meta)
|
||||||
@ -62,7 +62,6 @@ export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => {
|
|||||||
const profile = profiles[submittedBy]
|
const profile = profiles[submittedBy]
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key={submittedBy}
|
|
||||||
title={
|
title={
|
||||||
profile?.display_name ||
|
profile?.display_name ||
|
||||||
profile?.name ||
|
profile?.name ||
|
||||||
@ -73,11 +72,7 @@ export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => {
|
|||||||
disableInteractive
|
disableInteractive
|
||||||
>
|
>
|
||||||
<TooltipChild>
|
<TooltipChild>
|
||||||
<DisplaySigner
|
<UserAvatar pubkey={submittedBy} image={profile?.picture} />
|
||||||
status={isValid ? SignStatus.Signed : SignStatus.Invalid}
|
|
||||||
profile={profile}
|
|
||||||
pubkey={submittedBy}
|
|
||||||
/>
|
|
||||||
</TooltipChild>
|
</TooltipChild>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
shorten,
|
shorten,
|
||||||
SignStatus
|
SignStatus
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
|
import { UserAvatar } from '../UserAvatar'
|
||||||
import { useSigitMeta } from '../../hooks/useSigitMeta'
|
import { useSigitMeta } from '../../hooks/useSigitMeta'
|
||||||
import { UserAvatarGroup } from '../UserAvatarGroup'
|
import { UserAvatarGroup } from '../UserAvatarGroup'
|
||||||
|
|
||||||
@ -43,8 +44,7 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
createdAt,
|
createdAt,
|
||||||
completedAt,
|
completedAt,
|
||||||
parsedSignatureEvents,
|
parsedSignatureEvents,
|
||||||
signedStatus,
|
signedStatus
|
||||||
isValid
|
|
||||||
} = useSigitMeta(meta)
|
} = useSigitMeta(meta)
|
||||||
const { usersPubkey } = useSelector((state: State) => state.auth)
|
const { usersPubkey } = useSelector((state: State) => state.auth)
|
||||||
const profiles = useSigitProfiles([
|
const profiles = useSigitProfiles([
|
||||||
@ -56,7 +56,7 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
typeof usersPubkey !== 'undefined' &&
|
typeof usersPubkey !== 'undefined' &&
|
||||||
signers.includes(hexToNpub(usersPubkey))
|
signers.includes(hexToNpub(usersPubkey))
|
||||||
|
|
||||||
const { extensions, isSame } = extractFileExtensions(Object.keys(fileHashes))
|
const ext = extractFileExtensions(Object.keys(fileHashes))
|
||||||
|
|
||||||
return submittedBy ? (
|
return submittedBy ? (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
@ -68,7 +68,6 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
const profile = profiles[submittedBy]
|
const profile = profiles[submittedBy]
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key={submittedBy}
|
|
||||||
title={
|
title={
|
||||||
profile?.display_name ||
|
profile?.display_name ||
|
||||||
profile?.name ||
|
profile?.name ||
|
||||||
@ -79,11 +78,7 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
disableInteractive
|
disableInteractive
|
||||||
>
|
>
|
||||||
<TooltipChild>
|
<TooltipChild>
|
||||||
<DisplaySigner
|
<UserAvatar pubkey={submittedBy} image={profile?.picture} />
|
||||||
status={isValid ? SignStatus.Signed : SignStatus.Invalid}
|
|
||||||
profile={profile}
|
|
||||||
pubkey={submittedBy}
|
|
||||||
/>
|
|
||||||
</TooltipChild>
|
</TooltipChild>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
@ -201,14 +196,14 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
<span className={styles.detailsItem}>
|
<span className={styles.detailsItem}>
|
||||||
<FontAwesomeIcon icon={faEye} /> {signedStatus}
|
<FontAwesomeIcon icon={faEye} /> {signedStatus}
|
||||||
</span>
|
</span>
|
||||||
{extensions.length > 0 ? (
|
{ext.length > 0 ? (
|
||||||
<span className={styles.detailsItem}>
|
<span className={styles.detailsItem}>
|
||||||
{!isSame ? (
|
{ext.length > 1 ? (
|
||||||
<>
|
<>
|
||||||
<FontAwesomeIcon icon={faFile} /> Multiple File Types
|
<FontAwesomeIcon icon={faFile} /> Multiple File Types
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
getExtensionIconLabel(extensions[0])
|
getExtensionIconLabel(ext[0])
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import {
|
import { CreateSignatureEventContent, Meta, SignedEventContent } from '../types'
|
||||||
CreateSignatureEventContent,
|
|
||||||
DocSignatureEvent,
|
|
||||||
Meta,
|
|
||||||
SignedEventContent
|
|
||||||
} from '../types'
|
|
||||||
import { Mark } from '../types/mark'
|
import { Mark } from '../types/mark'
|
||||||
import {
|
import {
|
||||||
fromUnixTimestamp,
|
fromUnixTimestamp,
|
||||||
@ -43,9 +38,7 @@ export interface FlatMeta
|
|||||||
encryptionKey: string | null
|
encryptionKey: string | null
|
||||||
|
|
||||||
// Parsed Document Signatures
|
// Parsed Document Signatures
|
||||||
parsedSignatureEvents: {
|
parsedSignatureEvents: { [signer: `npub1${string}`]: Event }
|
||||||
[signer: `npub1${string}`]: DocSignatureEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculated completion time
|
// Calculated completion time
|
||||||
completedAt?: number
|
completedAt?: number
|
||||||
@ -81,7 +74,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
|||||||
const [zipUrl, setZipUrl] = useState<string>('')
|
const [zipUrl, setZipUrl] = useState<string>('')
|
||||||
|
|
||||||
const [parsedSignatureEvents, setParsedSignatureEvents] = useState<{
|
const [parsedSignatureEvents, setParsedSignatureEvents] = useState<{
|
||||||
[signer: `npub1${string}`]: DocSignatureEvent
|
[signer: `npub1${string}`]: Event
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
const [completedAt, setCompletedAt] = useState<number>()
|
const [completedAt, setCompletedAt] = useState<number>()
|
||||||
@ -148,10 +141,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Temp. map to hold events and signers
|
// Temp. map to hold events and signers
|
||||||
const parsedSignatureEventsMap = new Map<
|
const parsedSignatureEventsMap = new Map<`npub1${string}`, Event>()
|
||||||
`npub1${string}`,
|
|
||||||
DocSignatureEvent
|
|
||||||
>()
|
|
||||||
const signerStatusMap = new Map<`npub1${string}`, SignStatus>()
|
const signerStatusMap = new Map<`npub1${string}`, SignStatus>()
|
||||||
|
|
||||||
const getPrevSignerSig = (npub: `npub1${string}`) => {
|
const getPrevSignerSig = (npub: `npub1${string}`) => {
|
||||||
@ -193,12 +183,9 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
|||||||
if (isValidSignature) {
|
if (isValidSignature) {
|
||||||
// get the signature of prev signer from the content of current signers signedEvent
|
// get the signature of prev signer from the content of current signers signedEvent
|
||||||
const prevSignersSig = getPrevSignerSig(npub)
|
const prevSignersSig = getPrevSignerSig(npub)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const obj: SignedEventContent = JSON.parse(event.content)
|
const obj: SignedEventContent = JSON.parse(event.content)
|
||||||
parsedSignatureEventsMap.set(npub, {
|
|
||||||
...event,
|
|
||||||
parsedContent: obj
|
|
||||||
})
|
|
||||||
if (
|
if (
|
||||||
obj.prevSig &&
|
obj.prevSig &&
|
||||||
prevSignersSig &&
|
prevSignersSig &&
|
||||||
|
@ -23,11 +23,7 @@
|
|||||||
grid-gap: 15px;
|
grid-gap: 15px;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
padding: 10px;
|
max-width: 550px;
|
||||||
border: 10px solid $overlay-background-color;
|
width: 550px;
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
max-width: 590px;
|
|
||||||
width: 590px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import { Box, Button, Divider, Tooltip, Typography } from '@mui/material'
|
import { Box, Button, Tooltip, Typography, useTheme } from '@mui/material'
|
||||||
import JSZip from 'jszip'
|
import JSZip from 'jszip'
|
||||||
import { MuiFileInput } from 'mui-file-input'
|
import { MuiFileInput } from 'mui-file-input'
|
||||||
import { Event, verifyEvent } from 'nostr-tools'
|
import { Event, verifyEvent } from 'nostr-tools'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { NostrController } from '../../controllers'
|
import { NostrController } from '../../controllers'
|
||||||
import {
|
import { CreateSignatureEventContent, Meta } from '../../types'
|
||||||
CreateSignatureEventContent,
|
|
||||||
DocSignatureEvent,
|
|
||||||
Meta
|
|
||||||
} from '../../types'
|
|
||||||
import {
|
import {
|
||||||
decryptArrayBuffer,
|
decryptArrayBuffer,
|
||||||
extractMarksFromSignedMeta,
|
extractMarksFromSignedMeta,
|
||||||
@ -20,10 +16,10 @@ import {
|
|||||||
parseJson,
|
parseJson,
|
||||||
readContentOfZipEntry,
|
readContentOfZipEntry,
|
||||||
signEventForMetaFile,
|
signEventForMetaFile,
|
||||||
shorten,
|
shorten
|
||||||
getCurrentUserFiles
|
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
|
import { Cancel, CheckCircle } from '@mui/icons-material'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { PdfFile } from '../../types/drawing.ts'
|
import { PdfFile } from '../../types/drawing.ts'
|
||||||
@ -31,8 +27,7 @@ import {
|
|||||||
addMarks,
|
addMarks,
|
||||||
convertToPdfBlob,
|
convertToPdfBlob,
|
||||||
convertToPdfFile,
|
convertToPdfFile,
|
||||||
groupMarksByFileNamePage,
|
groupMarksByPage
|
||||||
inPx
|
|
||||||
} from '../../utils/pdf.ts'
|
} from '../../utils/pdf.ts'
|
||||||
import { State } from '../../store/rootReducer.ts'
|
import { State } from '../../store/rootReducer.ts'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
@ -45,101 +40,13 @@ import { UsersDetails } from '../../components/UsersDetails.tsx/index.tsx'
|
|||||||
import { UserAvatar } from '../../components/UserAvatar/index.tsx'
|
import { UserAvatar } from '../../components/UserAvatar/index.tsx'
|
||||||
import { useSigitProfiles } from '../../hooks/useSigitProfiles.tsx'
|
import { useSigitProfiles } from '../../hooks/useSigitProfiles.tsx'
|
||||||
import { TooltipChild } from '../../components/TooltipChild.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,
|
|
||||||
parsedSignatureEvents
|
|
||||||
}: 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 (
|
|
||||||
<div className={styles.view}>
|
|
||||||
{files.map((currentUserFile, i) => {
|
|
||||||
const { hash, filename, pdfFile, id } = currentUserFile
|
|
||||||
const signatureEvents = Object.keys(parsedSignatureEvents)
|
|
||||||
if (!hash) return
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
id={filename}
|
|
||||||
ref={(el) => (pdfRefs.current[id] = el)}
|
|
||||||
key={filename}
|
|
||||||
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 (
|
|
||||||
<div className={styles.imageWrapper} key={i}>
|
|
||||||
<img draggable="false" src={page.image} />
|
|
||||||
{marks.map((m) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={styles.mark}
|
|
||||||
key={m.id}
|
|
||||||
style={{
|
|
||||||
left: inPx(m.location.left),
|
|
||||||
top: inPx(m.location.top),
|
|
||||||
width: inPx(m.location.width),
|
|
||||||
height: inPx(m.location.height)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{m.value}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{i < files.length - 1 && (
|
|
||||||
<Divider
|
|
||||||
sx={{
|
|
||||||
fontSize: '12px',
|
|
||||||
color: 'rgba(0,0,0,0.15)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
File Separator
|
|
||||||
</Divider>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const VerifyPage = () => {
|
export const VerifyPage = () => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const textColor = theme.palette.getContrastText(
|
||||||
|
theme.palette.background.paper
|
||||||
|
)
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
/**
|
/**
|
||||||
* uploadedZip will be received from home page when a user uploads a sigit zip wrapper that contains meta.json
|
* uploadedZip will be received from home page when a user uploads a sigit zip wrapper that contains meta.json
|
||||||
@ -147,15 +54,8 @@ export const VerifyPage = () => {
|
|||||||
*/
|
*/
|
||||||
const { uploadedZip, meta } = location.state || {}
|
const { uploadedZip, meta } = location.state || {}
|
||||||
|
|
||||||
const {
|
const { submittedBy, zipUrl, encryptionKey, signers, viewers, fileHashes } =
|
||||||
submittedBy,
|
useSigitMeta(meta)
|
||||||
zipUrl,
|
|
||||||
encryptionKey,
|
|
||||||
signers,
|
|
||||||
viewers,
|
|
||||||
fileHashes,
|
|
||||||
parsedSignatureEvents
|
|
||||||
} = useSigitMeta(meta)
|
|
||||||
|
|
||||||
const profiles = useSigitProfiles([
|
const profiles = useSigitProfiles([
|
||||||
...(submittedBy ? [submittedBy] : []),
|
...(submittedBy ? [submittedBy] : []),
|
||||||
@ -170,23 +70,8 @@ export const VerifyPage = () => {
|
|||||||
|
|
||||||
const [currentFileHashes, setCurrentFileHashes] = useState<{
|
const [currentFileHashes, setCurrentFileHashes] = useState<{
|
||||||
[key: string]: string | null
|
[key: string]: string | null
|
||||||
}>({})
|
|
||||||
const [files, setFiles] = useState<{ [filename: string]: PdfFile }>({})
|
|
||||||
const [currentFile, setCurrentFile] = useState<CurrentUserFile | null>(null)
|
|
||||||
const [signatureFileHashes, setSignatureFileHashes] = useState<{
|
|
||||||
[key: string]: string
|
|
||||||
}>(fileHashes)
|
}>(fileHashes)
|
||||||
|
const [files, setFiles] = useState<{ [filename: string]: PdfFile }>({})
|
||||||
useEffect(() => {
|
|
||||||
setSignatureFileHashes(fileHashes)
|
|
||||||
}, [fileHashes])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (Object.entries(files).length > 0) {
|
|
||||||
const tmp = getCurrentUserFiles(files, fileHashes, signatureFileHashes)
|
|
||||||
setCurrentFile(tmp[0])
|
|
||||||
}
|
|
||||||
}, [signatureFileHashes, fileHashes, files])
|
|
||||||
|
|
||||||
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
|
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
@ -318,6 +203,7 @@ export const VerifyPage = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('fileHashes :>> ', fileHashes)
|
||||||
setCurrentFileHashes(fileHashes)
|
setCurrentFileHashes(fileHashes)
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Parsing meta.json')
|
setLoadingSpinnerDesc('Parsing meta.json')
|
||||||
@ -421,10 +307,10 @@ export const VerifyPage = () => {
|
|||||||
zip.file('meta.json', stringifiedMeta)
|
zip.file('meta.json', stringifiedMeta)
|
||||||
|
|
||||||
const marks = extractMarksFromSignedMeta(updatedMeta)
|
const marks = extractMarksFromSignedMeta(updatedMeta)
|
||||||
const marksByPage = groupMarksByFileNamePage(marks)
|
const marksByPage = groupMarksByPage(marks)
|
||||||
|
|
||||||
for (const [fileName, pdf] of Object.entries(files)) {
|
for (const [fileName, pdf] of Object.entries(files)) {
|
||||||
const pages = await addMarks(pdf.file, marksByPage[fileName])
|
const pages = await addMarks(pdf.file, marksByPage)
|
||||||
const blob = await convertToPdfBlob(pages)
|
const blob = await convertToPdfBlob(pages)
|
||||||
zip.file(`files/${fileName}`, blob)
|
zip.file(`files/${fileName}`, blob)
|
||||||
}
|
}
|
||||||
@ -528,33 +414,51 @@ export const VerifyPage = () => {
|
|||||||
<StickySideColumns
|
<StickySideColumns
|
||||||
left={
|
left={
|
||||||
<>
|
<>
|
||||||
{currentFile !== null && (
|
<Box className={styles.filesWrapper}>
|
||||||
<FileList
|
{Object.entries(currentFileHashes).map(
|
||||||
files={getCurrentUserFiles(
|
([filename, hash], index) => {
|
||||||
files,
|
const isValidHash = fileHashes[filename] === hash
|
||||||
currentFileHashes,
|
|
||||||
signatureFileHashes
|
return (
|
||||||
)}
|
<Box key={`file-${index}`} className={styles.file}>
|
||||||
currentFile={currentFile}
|
<Typography
|
||||||
setCurrentFile={setCurrentFile}
|
component="label"
|
||||||
handleDownload={handleExport}
|
sx={{
|
||||||
/>
|
color: textColor,
|
||||||
)}
|
flexGrow: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{filename}
|
||||||
|
</Typography>
|
||||||
|
{isValidHash && (
|
||||||
|
<Tooltip title="File integrity check passed" arrow>
|
||||||
|
<CheckCircle
|
||||||
|
sx={{ color: theme.palette.success.light }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{!isValidHash && (
|
||||||
|
<Tooltip title="File integrity check failed" arrow>
|
||||||
|
<Cancel
|
||||||
|
sx={{ color: theme.palette.error.main }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
{displayExportedBy()}
|
{displayExportedBy()}
|
||||||
|
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<Button onClick={handleExport} variant="contained">
|
||||||
|
Export Sigit
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
right={<UsersDetails meta={meta} />}
|
right={<UsersDetails meta={meta} />}
|
||||||
>
|
/>
|
||||||
<SlimPdfView
|
|
||||||
currentFile={currentFile}
|
|
||||||
files={getCurrentUserFiles(
|
|
||||||
files,
|
|
||||||
currentFileHashes,
|
|
||||||
signatureFileHashes
|
|
||||||
)}
|
|
||||||
parsedSignatureEvents={parsedSignatureEvents}
|
|
||||||
/>
|
|
||||||
</StickySideColumns>
|
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
|
@ -50,36 +50,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mark {
|
|
||||||
position: absolute;
|
|
||||||
border: 1px dotted black;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Mark } from './mark'
|
import { Mark } from './mark'
|
||||||
import { Keys } from '../store/auth/types'
|
import { Keys } from '../store/auth/types'
|
||||||
import { Event } from 'nostr-tools'
|
|
||||||
|
|
||||||
export enum UserRole {
|
export enum UserRole {
|
||||||
signer = 'Signer',
|
signer = 'Signer',
|
||||||
@ -45,7 +44,3 @@ export interface UserAppData {
|
|||||||
keyPair?: Keys // this key pair is used for blossom requests authentication
|
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
|
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
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Meta } from '../types'
|
import { Meta } from '../types'
|
||||||
import { extractMarksFromSignedMeta } from './mark.ts'
|
import { extractMarksFromSignedMeta } from './mark.ts'
|
||||||
import { addMarks, convertToPdfBlob, groupMarksByFileNamePage } from './pdf.ts'
|
import { addMarks, convertToPdfBlob, groupMarksByPage } from './pdf.ts'
|
||||||
import JSZip from 'jszip'
|
import JSZip from 'jszip'
|
||||||
import { PdfFile } from '../types/drawing.ts'
|
import { PdfFile } from '../types/drawing.ts'
|
||||||
|
|
||||||
@ -10,12 +10,12 @@ const getZipWithFiles = async (
|
|||||||
): Promise<JSZip> => {
|
): Promise<JSZip> => {
|
||||||
const zip = new JSZip()
|
const zip = new JSZip()
|
||||||
const marks = extractMarksFromSignedMeta(meta)
|
const marks = extractMarksFromSignedMeta(meta)
|
||||||
const marksByFileNamePage = groupMarksByFileNamePage(marks)
|
const marksByPage = groupMarksByPage(marks)
|
||||||
|
|
||||||
for (const [fileName, pdf] of Object.entries(files)) {
|
for (const [fileName, pdf] of Object.entries(files)) {
|
||||||
const pages = await addMarks(pdf.file, marksByFileNamePage[fileName])
|
const pages = await addMarks(pdf.file, marksByPage)
|
||||||
const blob = await convertToPdfBlob(pages)
|
const blob = await convertToPdfBlob(pages)
|
||||||
zip.file(`files/${fileName}`, blob)
|
zip.file(`/files/${fileName}`, blob)
|
||||||
}
|
}
|
||||||
|
|
||||||
return zip
|
return zip
|
||||||
|
@ -119,11 +119,7 @@ const getUpdatedMark = (
|
|||||||
return {
|
return {
|
||||||
...selectedMark,
|
...selectedMark,
|
||||||
currentValue: selectedMarkValue,
|
currentValue: selectedMarkValue,
|
||||||
isCompleted: !!selectedMarkValue,
|
isCompleted: !!selectedMarkValue
|
||||||
mark: {
|
|
||||||
...selectedMark.mark,
|
|
||||||
value: selectedMarkValue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { CreateSignatureEventContent, Meta } from '../types'
|
import { CreateSignatureEventContent, Meta } from '../types'
|
||||||
import { fromUnixTimestamp, parseJson } from '.'
|
import { fromUnixTimestamp, parseJson } from '.'
|
||||||
import { Event, verifyEvent } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
export enum SignStatus {
|
export enum SignStatus {
|
||||||
@ -75,7 +75,6 @@ export interface SigitCardDisplayInfo {
|
|||||||
signers: `npub1${string}`[]
|
signers: `npub1${string}`[]
|
||||||
fileExtensions: string[]
|
fileExtensions: string[]
|
||||||
signedStatus: SigitStatus
|
signedStatus: SigitStatus
|
||||||
isValid: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,15 +128,12 @@ export const extractSigitCardDisplayInfo = async (meta: Meta) => {
|
|||||||
const sigitInfo: SigitCardDisplayInfo = {
|
const sigitInfo: SigitCardDisplayInfo = {
|
||||||
signers: [],
|
signers: [],
|
||||||
fileExtensions: [],
|
fileExtensions: [],
|
||||||
signedStatus: SigitStatus.Partial,
|
signedStatus: SigitStatus.Partial
|
||||||
isValid: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const createSignatureEvent = await parseNostrEvent(meta.createSignature)
|
const createSignatureEvent = await parseNostrEvent(meta.createSignature)
|
||||||
|
|
||||||
sigitInfo.isValid = verifyEvent(createSignatureEvent)
|
|
||||||
|
|
||||||
// created_at in nostr events are stored in seconds
|
// created_at in nostr events are stored in seconds
|
||||||
sigitInfo.createdAt = fromUnixTimestamp(createSignatureEvent.created_at)
|
sigitInfo.createdAt = fromUnixTimestamp(createSignatureEvent.created_at)
|
||||||
|
|
||||||
@ -146,7 +142,7 @@ export const extractSigitCardDisplayInfo = async (meta: Meta) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const files = Object.keys(createSignatureContent.fileHashes)
|
const files = Object.keys(createSignatureContent.fileHashes)
|
||||||
const { extensions } = extractFileExtensions(files)
|
const extensions = extractFileExtensions(files)
|
||||||
|
|
||||||
const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
|
const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[]
|
||||||
const isCompletelySigned = createSignatureContent.signers.every((signer) =>
|
const isCompletelySigned = createSignatureContent.signers.every((signer) =>
|
||||||
@ -173,10 +169,6 @@ 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[]) => {
|
export const extractFileExtensions = (fileNames: string[]) => {
|
||||||
const extensions = fileNames.reduce((result: string[], file: string) => {
|
const extensions = fileNames.reduce((result: string[], file: string) => {
|
||||||
const extension = file.split('.').pop()
|
const extension = file.split('.').pop()
|
||||||
@ -186,7 +178,5 @@ export const extractFileExtensions = (fileNames: string[]) => {
|
|||||||
return result
|
return result
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const isSame = extensions.every((ext) => ext === extensions[0])
|
return extensions
|
||||||
|
|
||||||
return { extensions, isSame }
|
|
||||||
}
|
}
|
||||||
|
@ -71,19 +71,14 @@ const isPdf = (file: File) => file.type.toLowerCase().includes('pdf')
|
|||||||
/**
|
/**
|
||||||
* Reads the pdf file binaries
|
* Reads the pdf file binaries
|
||||||
*/
|
*/
|
||||||
const readPdf = (file: File): Promise<string | ArrayBuffer> => {
|
const readPdf = (file: File): Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
|
|
||||||
reader.onload = (e) => {
|
reader.onload = (e: any) => {
|
||||||
const data = e.target?.result
|
const data = e.target.result
|
||||||
// Make sure we only resolve for string or ArrayBuffer type
|
|
||||||
// They are accepted by PDFJS.getDocument function
|
resolve(data)
|
||||||
if (data && typeof data !== 'undefined') {
|
|
||||||
resolve(data)
|
|
||||||
} else {
|
|
||||||
reject(new Error('File is null or undefined'))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.onerror = (err) => {
|
reader.onerror = (err) => {
|
||||||
@ -99,7 +94,7 @@ const readPdf = (file: File): Promise<string | ArrayBuffer> => {
|
|||||||
* Converts pdf to the images
|
* Converts pdf to the images
|
||||||
* @param data pdf file bytes
|
* @param data pdf file bytes
|
||||||
*/
|
*/
|
||||||
const pdfToImages = async (data: string | ArrayBuffer): Promise<PdfPage[]> => {
|
const pdfToImages = async (data: any): Promise<PdfPage[]> => {
|
||||||
const images: string[] = []
|
const images: string[] = []
|
||||||
const pdf = await PDFJS.getDocument(data).promise
|
const pdf = await PDFJS.getDocument(data).promise
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
@ -147,8 +142,7 @@ const addMarks = async (
|
|||||||
canvas.width = viewport.width
|
canvas.width = viewport.width
|
||||||
await page.render({ canvasContext: context!, viewport: viewport }).promise
|
await page.render({ canvasContext: context!, viewport: viewport }).promise
|
||||||
|
|
||||||
if (marksPerPage && Object.hasOwn(marksPerPage, i))
|
marksPerPage[i]?.forEach((mark) => draw(mark, context!))
|
||||||
marksPerPage[i]?.forEach((mark) => draw(mark, context!))
|
|
||||||
|
|
||||||
images.push(canvas.toDataURL())
|
images.push(canvas.toDataURL())
|
||||||
}
|
}
|
||||||
@ -236,11 +230,11 @@ const convertToPdfFile = async (
|
|||||||
* @function scaleMark scales remaining marks in line with SCALE
|
* @function scaleMark scales remaining marks in line with SCALE
|
||||||
* @function byPage groups remaining Marks by their page marks.location.page
|
* @function byPage groups remaining Marks by their page marks.location.page
|
||||||
*/
|
*/
|
||||||
const groupMarksByFileNamePage = (marks: Mark[]) => {
|
const groupMarksByPage = (marks: Mark[]) => {
|
||||||
return marks
|
return marks
|
||||||
.filter(hasValue)
|
.filter(hasValue)
|
||||||
.map(scaleMark)
|
.map(scaleMark)
|
||||||
.reduce<{ [filename: string]: { [page: number]: Mark[] } }>(byPage, {})
|
.reduce<{ [key: number]: Mark[] }>(byPage, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -251,21 +245,10 @@ const groupMarksByFileNamePage = (marks: Mark[]) => {
|
|||||||
* @param obj - accumulator in the reducer callback
|
* @param obj - accumulator in the reducer callback
|
||||||
* @param mark - current value, i.e. Mark being examined
|
* @param mark - current value, i.e. Mark being examined
|
||||||
*/
|
*/
|
||||||
const byPage = (
|
const byPage = (obj: { [key: number]: Mark[] }, mark: Mark) => {
|
||||||
obj: { [filename: string]: { [page: number]: Mark[] } },
|
const key = mark.location.page
|
||||||
mark: Mark
|
const curGroup = obj[key] ?? []
|
||||||
) => {
|
return { ...obj, [key]: [...curGroup, 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 {
|
export {
|
||||||
@ -276,5 +259,5 @@ export {
|
|||||||
convertToPdfFile,
|
convertToPdfFile,
|
||||||
addMarks,
|
addMarks,
|
||||||
convertToPdfBlob,
|
convertToPdfBlob,
|
||||||
groupMarksByFileNamePage
|
groupMarksByPage
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user