feat(pdf-marking): binds text to marks and saves with signatures

This commit is contained in:
eugene 2024-07-23 12:22:53 +03:00
parent 296b135c06
commit 4a932ffe03
10 changed files with 122 additions and 73 deletions

View File

@ -66,9 +66,11 @@
cursor: pointer; cursor: pointer;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center;
&.nonEditable { &.nonEditable {
cursor: default; cursor: default;
visibility: hidden;
} }
.resizeHandle { .resizeHandle {

View File

@ -1,14 +1,16 @@
import { PdfFile } from '../../types/drawing.ts' import { PdfFile } from '../../types/drawing.ts'
import { Mark, MarkConfigDetails } from '../../types/mark.ts' import { CurrentUserMark, Mark, MarkConfigDetails } from '../../types/mark.ts'
import PdfPageItem from './PdfPageItem.tsx'; import PdfPageItem from './PdfPageItem.tsx';
interface PdfItemProps { interface PdfItemProps {
pdfFile: PdfFile pdfFile: PdfFile
marks: Mark[] marks: Mark[]
handleMarkClick: (id: number) => void handleMarkClick: (id: number) => void
currentMarkValue: string
currentUserMark: CurrentUserMark | null
} }
const PdfItem = ({ pdfFile, marks, handleMarkClick }: PdfItemProps) => { const PdfItem = ({ pdfFile, marks, handleMarkClick, currentMarkValue, currentUserMark }: PdfItemProps) => {
const filterByPage = (marks: Mark[], page: number): Mark[] => { const filterByPage = (marks: Mark[], page: number): Mark[] => {
return marks.filter((mark) => mark.location.page === page); return marks.filter((mark) => mark.location.page === page);
} }
@ -20,6 +22,8 @@ const PdfItem = ({ pdfFile, marks, handleMarkClick }: PdfItemProps) => {
key={i} key={i}
marks={filterByPage(marks, i)} marks={filterByPage(marks, i)}
handleMarkClick={handleMarkClick} handleMarkClick={handleMarkClick}
currentMarkValue={currentMarkValue}
currentUserMark={currentUserMark}
/> />
) )
})) }))

View File

@ -1,4 +1,4 @@
import { Mark, MarkLocation } from '../../types/mark.ts' import { CurrentUserMark, Mark, MarkLocation } from '../../types/mark.ts'
import styles from '../DrawPDFFields/style.module.scss' import styles from '../DrawPDFFields/style.module.scss'
import { inPx } from '../../utils/pdf.ts' import { inPx } from '../../utils/pdf.ts'
@ -6,10 +6,17 @@ interface PdfMarkItemProps {
mark: Mark mark: Mark
handleMarkClick: (id: number) => void handleMarkClick: (id: number) => void
isEditable: boolean isEditable: boolean
currentMarkValue: string
currentUserMark: CurrentUserMark | null
} }
const PdfMarkItem = ({ mark, handleMarkClick, isEditable }: PdfMarkItemProps) => { const PdfMarkItem = ({ mark, handleMarkClick, isEditable, currentMarkValue, currentUserMark }: PdfMarkItemProps) => {
const handleClick = () => isEditable && handleMarkClick(mark.id); const handleClick = () => isEditable && handleMarkClick(mark.id);
const getMarkValue = () => (
currentUserMark?.mark.id === mark.id
? currentMarkValue
: mark.value
)
return ( return (
<div <div
onClick={handleClick} onClick={handleClick}
@ -20,7 +27,7 @@ const PdfMarkItem = ({ mark, handleMarkClick, isEditable }: PdfMarkItemProps) =>
width: inPx(mark.location.width), width: inPx(mark.location.width),
height: inPx(mark.location.height) height: inPx(mark.location.height)
}} }}
/> >{getMarkValue()}</div>
) )
} }

View File

@ -1,6 +1,6 @@
import styles from '../DrawPDFFields/style.module.scss' import styles from '../DrawPDFFields/style.module.scss'
import { PdfPage } from '../../types/drawing.ts' import { PdfPage } from '../../types/drawing.ts'
import { Mark, MarkConfigDetails } from '../../types/mark.ts' import { CurrentUserMark, Mark, MarkConfigDetails } from '../../types/mark.ts'
import PdfMarkItem from './PdfMarkItem.tsx' import PdfMarkItem from './PdfMarkItem.tsx'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { State } from '../../store/rootReducer.ts' import { State } from '../../store/rootReducer.ts'
@ -9,9 +9,11 @@ interface PdfPageProps {
page: PdfPage page: PdfPage
marks: Mark[] marks: Mark[]
handleMarkClick: (id: number) => void handleMarkClick: (id: number) => void
currentMarkValue: string
currentUserMark: CurrentUserMark | null
} }
const PdfPageItem = ({ page, marks, handleMarkClick }: PdfPageProps) => { const PdfPageItem = ({ page, marks, handleMarkClick, currentMarkValue, currentUserMark }: PdfPageProps) => {
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey) const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
const isEditable = (mark: Mark): boolean => { const isEditable = (mark: Mark): boolean => {
if (!usersPubkey) return false; if (!usersPubkey) return false;
@ -39,6 +41,8 @@ const PdfPageItem = ({ page, marks, handleMarkClick }: PdfPageProps) => {
mark={mark} mark={mark}
isEditable={isEditable(mark)} isEditable={isEditable(mark)}
handleMarkClick={handleMarkClick} handleMarkClick={handleMarkClick}
currentMarkValue={currentMarkValue}
currentUserMark={currentUserMark}
/> />
))} ))}
</div> </div>

View File

@ -1,16 +1,18 @@
import { PdfFile } from '../../types/drawing.ts' import { PdfFile } from '../../types/drawing.ts'
import { Box } from '@mui/material' import { Box } from '@mui/material'
import PdfItem from './PdfItem.tsx' import PdfItem from './PdfItem.tsx'
import { Mark, MarkConfigDetails } from '../../types/mark.ts' import { CurrentUserMark, Mark, MarkConfigDetails } from '../../types/mark.ts'
interface PdfViewProps { interface PdfViewProps {
files: { [filename: string]: PdfFile }, files: { [filename: string]: PdfFile },
fileHashes: { [key: string]: string | null }, fileHashes: { [key: string]: string | null },
marks: Mark[], marks: Mark[],
handleMarkClick: (id: number) => void handleMarkClick: (id: number) => void
currentMarkValue: string
currentUserMark: CurrentUserMark | null
} }
const PdfView = ({ files, fileHashes, marks, handleMarkClick }: PdfViewProps) => { const PdfView = ({ files, fileHashes, marks, handleMarkClick, currentMarkValue, currentUserMark }: PdfViewProps) => {
const filterByFile = (marks: Mark[], fileHash: string): Mark[] => { const filterByFile = (marks: Mark[], fileHash: string): Mark[] => {
return marks.filter((mark) => mark.pdfFileHash === fileHash); return marks.filter((mark) => mark.pdfFileHash === fileHash);
} }
@ -23,6 +25,8 @@ const PdfView = ({ files, fileHashes, marks, handleMarkClick }: PdfViewProps) =>
key={i} key={i}
marks={filterByFile(marks, fileHashes[name] ?? "")} marks={filterByFile(marks, fileHashes[name] ?? "")}
handleMarkClick={handleMarkClick} handleMarkClick={handleMarkClick}
currentMarkValue={currentMarkValue}
currentUserMark={currentUserMark}
/> />
))} ))}
</Box> </Box>

View File

@ -1,6 +1,7 @@
import { CurrentUserMark, Mark } from '../../types/mark.ts' import { CurrentUserMark, Mark } from '../../types/mark.ts'
import styles from './style.module.scss' import styles from './style.module.scss'
import { Box, Button, TextField } from '@mui/material' import { Box, Button, TextField } from '@mui/material'
import { MarkTypeTranslation } from './const.ts'
interface MarkFormFieldProps { interface MarkFormFieldProps {
handleSubmit: (event: any) => void handleSubmit: (event: any) => void
@ -17,7 +18,7 @@ const MarkFormField = (props: MarkFormFieldProps) => {
<Box component="form" onSubmit={handleSubmit}> <Box component="form" onSubmit={handleSubmit}>
<TextField <TextField
id="mark-value" id="mark-value"
label={currentMark.mark.type} label={MarkTypeTranslation[currentMark.mark.type.valueOf()]}
value={currentMarkValue} value={currentMarkValue}
onChange={handleChange} onChange={handleChange}
/> />

5
src/pages/sign/const.ts Normal file
View File

@ -0,0 +1,5 @@
import { MarkType } from '../../types/drawing.ts'
export const MarkTypeTranslation: { [key: string]: string } = {
[MarkType.FULLNAME.valueOf()]: "Full Name"
}

View File

@ -13,7 +13,7 @@ import { LoadingSpinner } from '../../components/LoadingSpinner'
import { NostrController } from '../../controllers' import { NostrController } from '../../controllers'
import { appPublicRoutes } from '../../routes' import { appPublicRoutes } from '../../routes'
import { State } from '../../store/rootReducer' import { State } from '../../store/rootReducer'
import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types' import { CreateSignatureEventContent, Meta, SignedEvent, UserRole } from '../../types'
import { import {
decryptArrayBuffer, decryptArrayBuffer,
encryptArrayBuffer, encryptArrayBuffer,
@ -239,6 +239,7 @@ export const SignPage = () => {
.then((res) => { .then((res) => {
handleArrayBufferFromBlossom(res.data, encryptionKey) handleArrayBufferFromBlossom(res.data, encryptionKey)
setMeta(metaInNavState) setMeta(metaInNavState)
console.log('meta in nav state: ', metaInNavState)
}) })
.catch((err) => { .catch((err) => {
console.error(`error occurred in getting file from ${zipUrl}`, err) console.error(`error occurred in getting file from ${zipUrl}`, err)
@ -490,6 +491,8 @@ export const SignPage = () => {
} }
) )
console.log('parsed meta: ', parsedMetaJson)
setMeta(parsedMetaJson) setMeta(parsedMetaJson)
} }
@ -514,7 +517,10 @@ export const SignPage = () => {
const prevSig = getPrevSignersSig(hexToNpub(usersPubkey!)) const prevSig = getPrevSignersSig(hexToNpub(usersPubkey!))
if (!prevSig) return if (!prevSig) return
const signedEvent = await signEventForMeta(prevSig) const marks = getSignerMarksForMeta()
if (!marks) return
const signedEvent = await signEventForMeta({ prevSig, marks })
if (!signedEvent) return if (!signedEvent) return
const updatedMeta = updateMetaSignatures(meta, signedEvent) const updatedMeta = updateMetaSignatures(meta, signedEvent)
@ -528,14 +534,19 @@ export const SignPage = () => {
} }
// Sign the event for the meta file // Sign the event for the meta file
const signEventForMeta = async (prevSig: string) => { const signEventForMeta = async (signerContent: { prevSig: string, marks: Mark[] }) => {
return await signEventForMetaFile( return await signEventForMetaFile(
JSON.stringify({ prevSig }), JSON.stringify(signerContent),
nostrController, nostrController,
setIsLoading setIsLoading
) )
} }
const getSignerMarksForMeta = (): Mark[] | undefined => {
if (currentUserMarks.length === 0) return;
return currentUserMarks.map(( { mark }: CurrentUserMark) => mark);
}
const handleMarkClick = (id: number) => { const handleMarkClick = (id: number) => {
const nextMark = currentUserMarks.find(mark => mark.mark.id === id) const nextMark = currentUserMarks.find(mark => mark.mark.id === id)
setCurrentUserMark(nextMark!) setCurrentUserMark(nextMark!)
@ -595,6 +606,7 @@ export const SignPage = () => {
[hexToNpub(signedEvent.pubkey)]: JSON.stringify(signedEvent, null, 2) [hexToNpub(signedEvent.pubkey)]: JSON.stringify(signedEvent, null, 2)
} }
metaCopy.modifiedAt = now() metaCopy.modifiedAt = now()
console.log('meta copy: ', metaCopy);
return metaCopy return metaCopy
} }
@ -719,6 +731,7 @@ export const SignPage = () => {
await Promise.all(promises) await Promise.all(promises)
.then(() => { .then(() => {
toast.success('Notifications sent successfully') toast.success('Notifications sent successfully')
console.log('meta: ', meta);
setMeta(meta) setMeta(meta)
}) })
.catch(() => { .catch(() => {
@ -948,6 +961,34 @@ export const SignPage = () => {
return <LoadingSpinner desc={loadingSpinnerDesc} /> return <LoadingSpinner desc={loadingSpinnerDesc} />
} }
if (!isMarksCompleted) {
return (
<>
<Box className={styles.container}>
{
marks.length > 0 && (
<PdfView
files={files}
marks={marks}
fileHashes={currentFileHashes}
handleMarkClick={handleMarkClick}
currentMarkValue={currentMarkValue}
currentUserMark={currentUserMark}
/>)}
{
currentUserMark !== null && (
<MarkFormField
handleSubmit={handleSubmit}
handleChange={handleChange}
currentMark={currentUserMark}
currentMarkValue={currentMarkValue}
/>
)}
</Box>
</>
)}
return ( return (
<> <>
<Box className={styles.container}> <Box className={styles.container}>
@ -976,67 +1017,46 @@ export const SignPage = () => {
</> </>
)} )}
{/*{submittedBy && Object.entries(files).length > 0 && meta && (*/} {submittedBy && Object.entries(files).length > 0 && meta && (
{/* <>*/} <>
{/* <DisplayMeta*/} <DisplayMeta
{/* meta={meta}*/} meta={meta}
{/* files={files}*/}
{/* submittedBy={submittedBy}*/}
{/* signers={signers}*/}
{/* viewers={viewers}*/}
{/* creatorFileHashes={creatorFileHashes}*/}
{/* currentFileHashes={currentFileHashes}*/}
{/* signedBy={signedBy}*/}
{/* nextSigner={nextSinger}*/}
{/* getPrevSignersSig={getPrevSignersSig}*/}
{/* />*/}
{/* {signedStatus === SignedStatus.Fully_Signed && (*/}
{/* <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>*/}
{/* <Button onClick={handleExport} variant="contained">*/}
{/* Export*/}
{/* </Button>*/}
{/* </Box>*/}
{/* )}*/}
{/* {signedStatus === SignedStatus.User_Is_Next_Signer && (*/}
{/* <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>*/}
{/* <Button onClick={handleSign} variant="contained">*/}
{/* Sign*/}
{/* </Button>*/}
{/* </Box>*/}
{/* )}*/}
{/* {isSignerOrCreator && (*/}
{/* <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>*/}
{/* <Button onClick={handleExportSigit} variant="contained">*/}
{/* Export Sigit*/}
{/* </Button>*/}
{/* </Box>*/}
{/* )}*/}
{/* </>*/}
{/*)}*/}
{
!isMarksCompleted && marks.length > 0 && (
<PdfView
files={files} files={files}
marks={marks} submittedBy={submittedBy}
fileHashes={currentFileHashes} signers={signers}
handleMarkClick={handleMarkClick} viewers={viewers}
creatorFileHashes={creatorFileHashes}
currentFileHashes={currentFileHashes}
signedBy={signedBy}
nextSigner={nextSinger}
getPrevSignersSig={getPrevSignersSig}
/> />
)
}
{ {signedStatus === SignedStatus.Fully_Signed && (
!isMarksCompleted && currentUserMark !== null && <MarkFormField <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
handleSubmit={handleSubmit} <Button onClick={handleExport} variant="contained">
handleChange={handleChange} Export
currentMark={currentUserMark} </Button>
currentMarkValue={currentMarkValue} </Box>
/> )}
}
{ isMarksCompleted && <p>Ready to Sign!</p>} {signedStatus === SignedStatus.User_Is_Next_Signer && (
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
<Button onClick={handleSign} variant="contained">
Sign
</Button>
</Box>
)}
{isSignerOrCreator && (
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
<Button onClick={handleExportSigit} variant="contained">
Export Sigit
</Button>
</Box>
)}
</>
)}
</Box> </Box>

View File

@ -55,13 +55,14 @@
transform: translateX(-50%); transform: translateX(-50%);
width: 100%; width: 100%;
max-width: 500px; max-width: 500px;
height: 100px;
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1); box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
padding: 10px 20px; padding: 10px 20px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 1000; //z-index: 200;
} }
.fixedBottomForm input[type="text"] { .fixedBottomForm input[type="text"] {

View File

@ -30,6 +30,7 @@ export interface CreateSignatureEventContent {
export interface SignedEventContent { export interface SignedEventContent {
prevSig: string prevSig: string
markConfig: Mark[]
} }
export interface Sigit { export interface Sigit {