Compare commits

..

No commits in common. "37b0ae21e0248ae6f75d123e5439a035fb6164ec" and "d8395ef8b39ed91f2fc3dbab33333e0dd06a0195" have entirely different histories.

17 changed files with 174 additions and 47 deletions

View File

@ -139,7 +139,7 @@ export const DrawPDFFields = (props: Props) => {
* Drawing is finished, resets all the variables used to draw * Drawing is finished, resets all the variables used to draw
* @param event Mouse event * @param event Mouse event
*/ */
const onMouseUp = () => { const onMouseUp = (event: MouseEvent) => {
setMouseState((prev) => { setMouseState((prev) => {
return { return {
...prev, ...prev,
@ -187,7 +187,7 @@ export const DrawPDFFields = (props: Props) => {
* @param event Mouse event * @param event Mouse event
* @param drawnField Which we are moving * @param drawnField Which we are moving
*/ */
const onDrawnFieldMouseDown = (event: any) => { const onDrawnFieldMouseDown = (event: any, drawnField: DrawnField) => {
event.stopPropagation() event.stopPropagation()
// Proceed only if left click // Proceed only if left click
@ -240,7 +240,7 @@ export const DrawPDFFields = (props: Props) => {
* @param event Mouse event * @param event Mouse event
* @param drawnField which we are resizing * @param drawnField which we are resizing
*/ */
const onResizeHandleMouseDown = (event: any) => { const onResizeHandleMouseDown = (event: any, drawnField: DrawnField) => {
// Proceed only if left click // Proceed only if left click
if (event.button !== 0) return if (event.button !== 0) return
@ -377,7 +377,7 @@ export const DrawPDFFields = (props: Props) => {
return ( return (
<div <div
key={drawnFieldIndex} key={drawnFieldIndex}
onMouseDown={onDrawnFieldMouseDown} onMouseDown={(event) => { onDrawnFieldMouseDown(event, drawnField) }}
onMouseMove={(event) => { onDranwFieldMouseMove(event, drawnField)}} onMouseMove={(event) => { onDranwFieldMouseMove(event, drawnField)}}
className={styles.drawingRectangle} className={styles.drawingRectangle}
style={{ style={{
@ -389,7 +389,7 @@ export const DrawPDFFields = (props: Props) => {
}} }}
> >
<span <span
onMouseDown={onResizeHandleMouseDown} onMouseDown={(event) => {onResizeHandleMouseDown(event, drawnField)}}
onMouseMove={(event) => {onResizeHandleMouseMove(event, drawnField)}} onMouseMove={(event) => {onResizeHandleMouseMove(event, drawnField)}}
className={styles.resizeHandle} className={styles.resizeHandle}
></span> ></span>
@ -400,7 +400,7 @@ export const DrawPDFFields = (props: Props) => {
<Close fontSize='small'/> <Close fontSize='small'/>
</span> </span>
<div <div
onMouseDown={onUserSelectHandleMouseDown} onMouseDown={(event) => {onUserSelectHandleMouseDown(event)}}
className={styles.userSelect} className={styles.userSelect}
> >
<FormControl fullWidth size='small'> <FormControl fullWidth size='small'>

View File

@ -59,6 +59,8 @@
.drawingRectangle { .drawingRectangle {
position: absolute; position: absolute;
border: 1px solid #01aaad; border: 1px solid #01aaad;
//width: 40px;
//height: 40px;
z-index: 50; z-index: 50;
background-color: #01aaad4b; background-color: #01aaad4b;
cursor: pointer; cursor: pointer;

View File

View File

@ -10,9 +10,6 @@ interface PdfItemProps {
selectedMark: CurrentUserMark | null selectedMark: CurrentUserMark | null
} }
/**
* Responsible for displaying pages of a single Pdf File.
*/
const PdfItem = ({ pdfFile, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfItemProps) => { const PdfItem = ({ pdfFile, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfItemProps) => {
const filterByPage = (marks: CurrentUserMark[], page: number): CurrentUserMark[] => { const filterByPage = (marks: CurrentUserMark[], page: number): CurrentUserMark[] => {
return marks.filter((m) => m.mark.location.page === page); return marks.filter((m) => m.mark.location.page === page);

View File

@ -9,9 +9,10 @@ interface PdfMarkItemProps {
selectedMark: CurrentUserMark | null selectedMark: CurrentUserMark | null
} }
/** //selectedMark represents the mark that the user is actively editing
* Responsible for display an individual Pdf Mark. // selectedMarkValue representsnthe edited value
*/ // userMark is part of the overall currentUserMark array
const PdfMarkItem = ({ selectedMark, handleMarkClick, selectedMarkValue, userMark }: PdfMarkItemProps) => { const PdfMarkItem = ({ selectedMark, handleMarkClick, selectedMarkValue, userMark }: PdfMarkItemProps) => {
const { location } = userMark.mark; const { location } = userMark.mark;
const handleClick = () => handleMarkClick(userMark.mark.id); const handleClick = () => handleMarkClick(userMark.mark.id);

View File

@ -4,7 +4,7 @@ import PdfView from './index.tsx'
import MarkFormField from '../../pages/sign/MarkFormField.tsx' import MarkFormField from '../../pages/sign/MarkFormField.tsx'
import { PdfFile } from '../../types/drawing.ts' import { PdfFile } from '../../types/drawing.ts'
import { CurrentUserMark, Mark } from '../../types/mark.ts' import { CurrentUserMark, Mark } from '../../types/mark.ts'
import React, { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { import {
findNextCurrentUserMark, findNextCurrentUserMark,
isCurrentUserMarksComplete, isCurrentUserMarksComplete,
@ -21,12 +21,6 @@ interface PdfMarkingProps {
setUpdatedMarks: (markToUpdate: Mark) => void setUpdatedMarks: (markToUpdate: Mark) => void
} }
/**
* Top-level component responsible for displaying Pdfs, Pages, and Marks,
* as well as tracking if the document is ready to be signed.
* @param props
* @constructor
*/
const PdfMarking = (props: PdfMarkingProps) => { const PdfMarking = (props: PdfMarkingProps) => {
const { const {
files, files,
@ -48,7 +42,7 @@ const PdfMarking = (props: PdfMarkingProps) => {
setSelectedMarkValue(nextMark?.mark.value ?? EMPTY); setSelectedMarkValue(nextMark?.mark.value ?? EMPTY);
} }
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (event: any) => {
event.preventDefault(); event.preventDefault();
if (!selectedMarkValue || !selectedMark) return; if (!selectedMarkValue || !selectedMark) return;
@ -70,7 +64,7 @@ const PdfMarking = (props: PdfMarkingProps) => {
setUpdatedMarks(updatedMark.mark) setUpdatedMarks(updatedMark.mark)
} }
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setSelectedMarkValue(event.target.value) const handleChange = (event: any) => setSelectedMarkValue(event.target.value)
return ( return (
<> <>

View File

@ -10,9 +10,6 @@ interface PdfPageProps {
selectedMark: CurrentUserMark | null selectedMark: CurrentUserMark | null
} }
/**
* Responsible for rendering a single Pdf Page and its Marks
*/
const PdfPageItem = ({ page, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfPageProps) => { const PdfPageItem = ({ page, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfPageProps) => {
return ( return (
<div <div

View File

@ -11,9 +11,6 @@ interface PdfViewProps {
selectedMark: CurrentUserMark | null selectedMark: CurrentUserMark | null
} }
/**
* Responsible for rendering Pdf files.
*/
const PdfView = ({ files, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfViewProps) => { const PdfView = ({ files, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfViewProps) => {
const filterByFile = (currentUserMarks: CurrentUserMark[], hash: string): CurrentUserMark[] => { const filterByFile = (currentUserMarks: CurrentUserMark[], hash: string): CurrentUserMark[] => {
return currentUserMarks.filter((currentUserMark) => currentUserMark.mark.pdfFileHash === hash) return currentUserMarks.filter((currentUserMark) => currentUserMark.mark.pdfFileHash === hash)

View File

@ -63,6 +63,7 @@ import styles from './style.module.scss'
import { PdfFile } from '../../types/drawing' import { PdfFile } from '../../types/drawing'
import { DrawPDFFields } from '../../components/DrawPDFFields' import { DrawPDFFields } from '../../components/DrawPDFFields'
import { Mark } from '../../types/mark.ts' import { Mark } from '../../types/mark.ts'
import { v4 as uuidv4 } from 'uuid';
export const CreatePage = () => { export const CreatePage = () => {
const navigate = useNavigate() const navigate = useNavigate()
@ -340,7 +341,7 @@ export const CreatePage = () => {
return fileHashes return fileHashes
} }
const createMarks = (fileHashes: { [key: string]: string }) : Mark[] => { const createMarkConfig = (fileHashes: { [key: string]: string }) : Mark[] => {
return drawnPdfs.flatMap((drawnPdf) => { return drawnPdfs.flatMap((drawnPdf) => {
const fileHash = fileHashes[drawnPdf.file.name]; const fileHash = fileHashes[drawnPdf.file.name];
return drawnPdf.pages.flatMap((page, index) => { return drawnPdf.pages.flatMap((page, index) => {
@ -377,13 +378,15 @@ export const CreatePage = () => {
const generateZipFile = async (zip: JSZip): Promise<ArrayBuffer | null> => { const generateZipFile = async (zip: JSZip): Promise<ArrayBuffer | null> => {
setLoadingSpinnerDesc('Generating zip file') setLoadingSpinnerDesc('Generating zip file')
return await zip const arraybuffer = await zip
.generateAsync({ .generateAsync({
type: 'arraybuffer', type: 'arraybuffer',
compression: 'DEFLATE', compression: 'DEFLATE',
compressionOptions: { level: 6 } compressionOptions: { level: 6 }
}) })
.catch(handleZipError) .catch(handleZipError)
return arraybuffer
} }
// Encrypt the zip file with the generated encryption key // Encrypt the zip file with the generated encryption key
@ -430,13 +433,15 @@ export const CreatePage = () => {
if (!arraybuffer) return null if (!arraybuffer) return null
return new File( const finalZipFile = new File(
[new Blob([arraybuffer])], [new Blob([arraybuffer])],
`${unixNow}.sigit.zip`, `${unixNow}.sigit.zip`,
{ {
type: 'application/zip' type: 'application/zip'
} }
) )
return finalZipFile
} }
// Handle errors during file upload // Handle errors during file upload
@ -458,12 +463,14 @@ export const CreatePage = () => {
type: 'application/sigit' type: 'application/sigit'
}) })
return await uploadToFileStorage(file) const fileUrl = await uploadToFileStorage(file)
.then((url) => { .then((url) => {
toast.success('files.zip uploaded to file storage') toast.success('files.zip uploaded to file storage')
return url return url
}) })
.catch(handleUploadError) .catch(handleUploadError)
return fileUrl
} }
// Manage offline scenarios for signing or viewing the file // Manage offline scenarios for signing or viewing the file
@ -487,13 +494,15 @@ export const CreatePage = () => {
zip.file(file.name, file) zip.file(file.name, file)
}) })
return await zip const arraybuffer = await zip
.generateAsync({ .generateAsync({
type: 'arraybuffer', type: 'arraybuffer',
compression: 'DEFLATE', compression: 'DEFLATE',
compressionOptions: { level: 6 } compressionOptions: { level: 6 }
}) })
.catch(handleZipError) .catch(handleZipError)
return arraybuffer
} }
const generateCreateSignature = async ( const generateCreateSignature = async (
@ -504,7 +513,9 @@ export const CreatePage = () => {
) => { ) => {
const signers = users.filter((user) => user.role === UserRole.signer) const signers = users.filter((user) => user.role === UserRole.signer)
const viewers = users.filter((user) => user.role === UserRole.viewer) const viewers = users.filter((user) => user.role === UserRole.viewer)
const markConfig = createMarks(fileHashes) const markConfig = createMarkConfig(fileHashes)
console.log('mark config: ', markConfig)
const content: CreateSignatureEventContent = { const content: CreateSignatureEventContent = {
signers: signers.map((signer) => hexToNpub(signer.pubkey)), signers: signers.map((signer) => hexToNpub(signer.pubkey)),
@ -515,6 +526,8 @@ export const CreatePage = () => {
title title
} }
console.log('content: ', content)
setLoadingSpinnerDesc('Signing nostr event for create signature') setLoadingSpinnerDesc('Signing nostr event for create signature')
const createSignature = await signEventForMetaFile( const createSignature = await signEventForMetaFile(
@ -544,9 +557,11 @@ export const CreatePage = () => {
: viewers.map((viewer) => viewer.pubkey) : viewers.map((viewer) => viewer.pubkey)
).filter((receiver) => receiver !== usersPubkey) ).filter((receiver) => receiver !== usersPubkey)
return receivers.map((receiver) => const promises = receivers.map((receiver) =>
sendNotification(receiver, meta) sendNotification(receiver, meta)
) )
return promises
} }
const handleCreate = async () => { const handleCreate = async () => {

View File

@ -1,4 +1,4 @@
import { CurrentUserMark } 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'
@ -11,9 +11,6 @@ interface MarkFormFieldProps {
selectedMarkValue: string selectedMarkValue: string
} }
/**
* Responsible for rendering a form field connected to a mark and keeping track of its value.
*/
const MarkFormField = (props: MarkFormFieldProps) => { const MarkFormField = (props: MarkFormFieldProps) => {
const { handleSubmit, handleChange, selectedMark, selectedMarkValue } = props; const { handleSubmit, handleChange, selectedMark, selectedMarkValue } = props;
const getSubmitButton = () => selectedMark.isLast ? 'Complete' : 'Next'; const getSubmitButton = () => selectedMark.isLast ? 'Complete' : 'Next';

View File

@ -319,6 +319,8 @@ export const SignPage = () => {
} }
} }
console.log('processed files: ', files);
setFiles(files) setFiles(files)
setCurrentFileHashes(fileHashes) setCurrentFileHashes(fileHashes)
} }
@ -337,13 +339,15 @@ export const SignPage = () => {
if (!keysFileContent) return null if (!keysFileContent) return null
return await parseJson<{ sender: string; keys: string[] }>( const parsedJSON = await parseJson<{ sender: string; keys: string[] }>(
keysFileContent keysFileContent
).catch((err) => { ).catch((err) => {
console.log(`Error parsing content of keys.json:`, err) console.log(`Error parsing content of keys.json:`, err)
toast.error(err.message || `Error parsing content of keys.json`) toast.error(err.message || `Error parsing content of keys.json`)
return null return null
}) })
return parsedJSON
} }
const decrypt = async (file: File) => { const decrypt = async (file: File) => {
@ -451,6 +455,8 @@ export const SignPage = () => {
} }
} }
console.log('processed files: ', files);
setFiles(files) setFiles(files)
setCurrentFileHashes(fileHashes) setCurrentFileHashes(fileHashes)
@ -534,6 +540,42 @@ export const SignPage = () => {
return currentUserMarks.map(( { mark }: CurrentUserMark) => mark); return currentUserMarks.map(( { mark }: CurrentUserMark) => mark);
} }
// const handleMarkClick = (id: number) => {
// const nextMark = currentUserMarks.find(mark => mark.mark.id === id)
// setCurrentUserMark(nextMark!)
// setCurrentMarkValue(nextMark?.mark.value || "")
// }
// const getMarkConfigPerUser = (markConfig: MarkConfig) => {
// if (!usersPubkey) return;
// return markConfig[hexToNpub(usersPubkey)];
// }
// const handleChange = (event: any) => setCurrentMarkValue(event.target.value);
//
// const handleSubmit = (event: any) => {
// event.preventDefault();
// if (!currentMarkValue || !currentUserMark) return;
//
// const curMark: Mark = {
// ...currentUserMark.mark,
// value: currentMarkValue
// };
//
// const updatedMarks: Mark[] = updateMarks(marks, curMark)
//
// setMarks(updatedMarks)
// setCurrentMarkValue("")
//
// // do the similar thing to the thing above
// const metaMarks = filterMarksByPubkey(updatedMarks, usersPubkey!)
// const signedMarks = extractMarksFromSignedMeta(meta!)
// const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks)
// setCurrentUserMarks(currentUserMarks)
// setCurrentUserMark(findNextCurrentUserMark(currentUserMarks) || null)
// setIsReadyToSign(isCurrentUserMarksComplete(currentUserMarks))
// }
// Update the meta signatures // Update the meta signatures
const updateMetaSignatures = (meta: Meta, signedEvent: SignedEvent): Meta => { const updateMetaSignatures = (meta: Meta, signedEvent: SignedEvent): Meta => {
const metaCopy = _.cloneDeep(meta) const metaCopy = _.cloneDeep(meta)
@ -601,13 +643,15 @@ export const SignPage = () => {
if (!arraybuffer) return null if (!arraybuffer) return null
return new File( const finalZipFile = new File(
[new Blob([arraybuffer])], [new Blob([arraybuffer])],
`${unixNow}.sigit.zip`, `${unixNow}.sigit.zip`,
{ {
type: 'application/zip' type: 'application/zip'
} }
) )
return finalZipFile
} }
// Handle errors during zip file generation // Handle errors during zip file generation
@ -926,4 +970,30 @@ export const SignPage = () => {
setCurrentUserMarks={setCurrentUserMarks} setCurrentUserMarks={setCurrentUserMarks}
setUpdatedMarks={setUpdatedMarks} setUpdatedMarks={setUpdatedMarks}
/> />
// 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>
// </>
// )
} }

View File

@ -380,6 +380,10 @@ export const VerifyPage = () => {
} }
const handleExport = async () => { const handleExport = async () => {
// const proverbialUrls = await addMarksV2(Object.values(files)[0].file, meta!)
// await convertToFinalPdf(proverbialUrls)
if (Object.entries(files).length === 0 ||!meta ||!usersPubkey) return; if (Object.entries(files).length === 0 ||!meta ||!usersPubkey) return;
const usersNpub = hexToNpub(usersPubkey) const usersNpub = hexToNpub(usersPubkey)

View File

@ -1,5 +1,12 @@
import { MarkType } from "./drawing"; import { MarkType } from "./drawing";
// export interface Mark {
// /**
// * @key png (pdf page) file hash
// */
// [key: string]: MarkConfigDetails[]
// }
export interface CurrentUserMark { export interface CurrentUserMark {
mark: Mark mark: Mark
isLast: boolean isLast: boolean
@ -15,6 +22,40 @@ export interface Mark {
value?: string; value?: string;
} }
export interface MarkConfig {
/**
* @key user npub
*/
[key: string]: User
}
export interface User {
/**
* @key png (pdf page) file hash
*/
[key: string]: MarkConfigDetails[]
}
export interface MarkDetails {
/**
* @key coords in format X:10;Y:50
*/
[key: string]: MarkValue
}
export interface MarkValue {
value: string
}
export interface MarkConfigDetails {
type: MarkType;
/**
* Coordinates in format: X:10;Y:50
*/
location: MarkLocation;
value?: MarkValue
}
export interface MarkLocation { export interface MarkLocation {
top: number; top: number;
left: number; left: number;

View File

@ -6,4 +6,4 @@ export * from './nostr'
export * from './string' export * from './string'
export * from './zip' export * from './zip'
export * from './utils' export * from './utils'
export * from './mark' export { extractMarksFromSignedMeta } from './mark.ts'

View File

@ -85,7 +85,7 @@ const updateCurrentUserMarks = (currentUserMarks: CurrentUserMark[], markToUpdat
] ]
} }
const isLast = <T>(index: number, arr: T[]) => (index === (arr.length -1)) const isLast = (index: number, arr: any[]) => (index === (arr.length -1))
export { export {
getCurrentUserMarks, getCurrentUserMarks,

View File

@ -12,10 +12,11 @@ import { toast } from 'react-toastify'
import { NostrController } from '../controllers' import { NostrController } from '../controllers'
import { AuthState } from '../store/auth/types' import { AuthState } from '../store/auth/types'
import store from '../store/store' import store from '../store/store'
import { CreateSignatureEventContent, Meta } from '../types' import { CreateSignatureEventContent, Meta, SignedEventContent } from '../types'
import { hexToNpub, now } from './nostr' import { hexToNpub, now } from './nostr'
import { parseJson } from './string' import { parseJson } from './string'
import { hexToBytes } from '@noble/hashes/utils' import { hexToBytes } from '@noble/hashes/utils'
import { Mark } from '../types/mark.ts'
/** /**
* Uploads a file to a file storage service. * Uploads a file to a file storage service.
@ -83,12 +84,14 @@ export const signEventForMetaFile = async (
} }
// Sign the event // Sign the event
return await nostrController.signEvent(event).catch((err) => { const signedEvent = await nostrController.signEvent(event).catch((err) => {
console.error(err) console.error(err)
toast.error(err.message || 'Error occurred in signing nostr event') toast.error(err.message || 'Error occurred in signing nostr event')
setIsLoading(false) // Set loading state to false setIsLoading(false) // Set loading state to false
return null return null
}) })
return signedEvent // Return the signed event
} }
/** /**
@ -262,3 +265,10 @@ export const extractZipUrlAndEncryptionKey = async (meta: Meta) => {
return null return null
} }
export const extractMarksFromSignedMeta = (meta: Meta): Mark[] => {
return Object.values(meta.docSignatures)
.map((val: string) => JSON.parse(val as string))
.map((val: Event) => JSON.parse(val.content))
.flatMap((val: SignedEventContent) => val.marks);
}

View File

@ -22,9 +22,8 @@ const readContentOfZipEntry = async <T extends OutputType>(
return null return null
} }
// Read and return the content of the zip entry asynchronously // Read the content of the zip entry asynchronously
// or null if an error has occurred const fileContent = await zipEntry.async(outputType).catch((err) => {
return await zipEntry.async(outputType).catch((err) => {
// Handle any errors that occur during the read operation // Handle any errors that occur during the read operation
console.log(`Error reading content of ${filePath}:`, err) console.log(`Error reading content of ${filePath}:`, err)
toast.error( toast.error(
@ -32,6 +31,9 @@ const readContentOfZipEntry = async <T extends OutputType>(
) )
return null return null
}) })
// Return the file content or null if an error occurred
return fileContent
} }
const loadZip = async (data: InputFileFormat): Promise<JSZip | null> => { const loadZip = async (data: InputFileFormat): Promise<JSZip | null> => {