CI - Add git hooks and staging checks #128

Merged
enes merged 13 commits from issue-38-90-pipeline-check into staging 2024-08-07 13:54:35 +00:00
20 changed files with 523 additions and 374 deletions
Showing only changes of commit e3eb1f37c1 - Show all commits

View File

@ -1,15 +1,43 @@
import { AccessTime, CalendarMonth, ExpandMore, Gesture, PictureAsPdf, Badge, Work, Close } from '@mui/icons-material' import {
import { Box, Typography, Accordion, AccordionDetails, AccordionSummary, CircularProgress, FormControl, InputLabel, MenuItem, Select } from '@mui/material' AccessTime,
CalendarMonth,
ExpandMore,
Gesture,
PictureAsPdf,
Badge,
Work,
Close
} from '@mui/icons-material'
import {
Box,
Typography,
Accordion,
AccordionDetails,
AccordionSummary,
CircularProgress,
FormControl,
InputLabel,
MenuItem,
Select
} from '@mui/material'
import styles from './style.module.scss' import styles from './style.module.scss'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import * as PDFJS from "pdfjs-dist"; import * as PDFJS from 'pdfjs-dist'
import { ProfileMetadata, User } from '../../types'; import { ProfileMetadata, User } from '../../types'
import { PdfFile, DrawTool, MouseState, PdfPage, DrawnField, MarkType } from '../../types/drawing'; import {
import { truncate } from 'lodash'; PdfFile,
import { hexToNpub } from '../../utils'; DrawTool,
MouseState,
PdfPage,
DrawnField,
MarkType
} from '../../types/drawing'
import { truncate } from 'lodash'
import { hexToNpub } from '../../utils'
import { toPdfFiles } from '../../utils/pdf.ts' import { toPdfFiles } from '../../utils/pdf.ts'
PDFJS.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.mjs'; PDFJS.GlobalWorkerOptions.workerSrc =
'node_modules/pdfjs-dist/build/pdf.worker.mjs'
interface Props { interface Props {
selectedFiles: File[] selectedFiles: File[]
@ -20,44 +48,43 @@ interface Props {
export const DrawPDFFields = (props: Props) => { export const DrawPDFFields = (props: Props) => {
const { selectedFiles } = props const { selectedFiles } = props
const [pdfFiles, setPdfFiles] = useState<PdfFile[]>([]) const [pdfFiles, setPdfFiles] = useState<PdfFile[]>([])
const [parsingPdf, setParsingPdf] = useState<boolean>(false) const [parsingPdf, setParsingPdf] = useState<boolean>(false)
const [showDrawToolBox, setShowDrawToolBox] = useState<boolean>(false) const [showDrawToolBox, setShowDrawToolBox] = useState<boolean>(false)
const [selectedTool, setSelectedTool] = useState<DrawTool | null>() const [selectedTool, setSelectedTool] = useState<DrawTool | null>()
const [toolbox] = useState<DrawTool[]>([ const [toolbox] = useState<DrawTool[]>([
{ {
identifier: MarkType.SIGNATURE, identifier: MarkType.SIGNATURE,
icon: <Gesture/>, icon: <Gesture />,
label: 'Signature', label: 'Signature',
active: false active: false
}, },
{ {
identifier: MarkType.FULLNAME, identifier: MarkType.FULLNAME,
icon: <Badge/>, icon: <Badge />,
label: 'Full Name', label: 'Full Name',
active: true active: true
}, },
{ {
identifier: MarkType.JOBTITLE, identifier: MarkType.JOBTITLE,
icon: <Work/>, icon: <Work />,
label: 'Job Title', label: 'Job Title',
active: false active: false
}, },
{ {
identifier: MarkType.DATE, identifier: MarkType.DATE,
icon: <CalendarMonth/>, icon: <CalendarMonth />,
label: 'Date', label: 'Date',
active: false active: false
}, },
{ {
identifier: MarkType.DATETIME, identifier: MarkType.DATETIME,
icon: <AccessTime/>, icon: <AccessTime />,
label: 'Datetime', label: 'Datetime',
active: false active: false
}, }
]) ])
const [mouseState, setMouseState] = useState<MouseState>({ const [mouseState, setMouseState] = useState<MouseState>({
@ -67,7 +94,7 @@ export const DrawPDFFields = (props: Props) => {
useEffect(() => { useEffect(() => {
if (selectedFiles) { if (selectedFiles) {
setParsingPdf(true) setParsingPdf(true)
parsePdfPages().finally(() => { parsePdfPages().finally(() => {
setParsingPdf(false) setParsingPdf(false)
}) })
@ -81,13 +108,13 @@ export const DrawPDFFields = (props: Props) => {
/** /**
* Drawing events * Drawing events
*/ */
useEffect(() => { useEffect(() => {
// window.addEventListener('mousedown', onMouseDown); // window.addEventListener('mousedown', onMouseDown);
window.addEventListener('mouseup', onMouseUp); window.addEventListener('mouseup', onMouseUp)
return () => { return () => {
// window.removeEventListener('mousedown', onMouseDown); // window.removeEventListener('mousedown', onMouseDown);
window.removeEventListener('mouseup', onMouseUp); window.removeEventListener('mouseup', onMouseUp)
} }
}, []) }, [])
@ -106,7 +133,7 @@ export const DrawPDFFields = (props: Props) => {
const onMouseDown = (event: any, page: PdfPage) => { const onMouseDown = (event: any, page: PdfPage) => {
// Proceed only if left click // Proceed only if left click
if (event.button !== 0) return if (event.button !== 0) return
// Only allow drawing if mouse is not over other drawn element // Only allow drawing if mouse is not over other drawn element
const isOverPdfImageWrapper = event.target.tagName === 'IMG' const isOverPdfImageWrapper = event.target.tagName === 'IMG'
@ -158,11 +185,11 @@ export const DrawPDFFields = (props: Props) => {
*/ */
const onMouseMove = (event: any, page: PdfPage) => { const onMouseMove = (event: any, page: PdfPage) => {
if (mouseState.clicked && selectedTool) { if (mouseState.clicked && selectedTool) {
const lastElementIndex = page.drawnFields.length -1 const lastElementIndex = page.drawnFields.length - 1
const lastDrawnField = page.drawnFields[lastElementIndex] const lastDrawnField = page.drawnFields[lastElementIndex]
const { mouseX, mouseY } = getMouseCoordinates(event) const { mouseX, mouseY } = getMouseCoordinates(event)
const width = mouseX - lastDrawnField.left const width = mouseX - lastDrawnField.left
const height = mouseY - lastDrawnField.top const height = mouseY - lastDrawnField.top
@ -172,10 +199,10 @@ export const DrawPDFFields = (props: Props) => {
const currentDrawnFields = page.drawnFields const currentDrawnFields = page.drawnFields
currentDrawnFields[lastElementIndex] = lastDrawnField currentDrawnFields[lastElementIndex] = lastDrawnField
refreshPdfFiles() refreshPdfFiles()
} }
} }
/** /**
* Fired when event happens on the drawn element which will be moved * Fired when event happens on the drawn element which will be moved
@ -189,7 +216,7 @@ export const DrawPDFFields = (props: Props) => {
*/ */
const onDrawnFieldMouseDown = (event: any) => { const onDrawnFieldMouseDown = (event: any) => {
event.stopPropagation() event.stopPropagation()
// Proceed only if left click // Proceed only if left click
if (event.button !== 0) return if (event.button !== 0) return
@ -212,7 +239,10 @@ export const DrawPDFFields = (props: Props) => {
*/ */
const onDranwFieldMouseMove = (event: any, drawnField: DrawnField) => { const onDranwFieldMouseMove = (event: any, drawnField: DrawnField) => {
if (mouseState.dragging) { if (mouseState.dragging) {
const { mouseX, mouseY, rect } = getMouseCoordinates(event, event.target.parentNode) const { mouseX, mouseY, rect } = getMouseCoordinates(
event,
event.target.parentNode
)
const coordsOffset = mouseState.coordsInWrapper const coordsOffset = mouseState.coordsInWrapper
if (coordsOffset) { if (coordsOffset) {
@ -258,8 +288,11 @@ export const DrawPDFFields = (props: Props) => {
*/ */
const onResizeHandleMouseMove = (event: any, drawnField: DrawnField) => { const onResizeHandleMouseMove = (event: any, drawnField: DrawnField) => {
if (mouseState.resizing) { if (mouseState.resizing) {
const { mouseX, mouseY } = getMouseCoordinates(event, event.target.parentNode.parentNode) const { mouseX, mouseY } = getMouseCoordinates(
event,
event.target.parentNode.parentNode
)
const width = mouseX - drawnField.left const width = mouseX - drawnField.left
const height = mouseY - drawnField.top const height = mouseY - drawnField.top
@ -277,10 +310,18 @@ export const DrawPDFFields = (props: Props) => {
* @param pdfPageIndex pdf page index * @param pdfPageIndex pdf page index
* @param drawnFileIndex drawn file index * @param drawnFileIndex drawn file index
*/ */
const onRemoveHandleMouseDown = (event: any, pdfFileIndex: number, pdfPageIndex: number, drawnFileIndex: number) => { const onRemoveHandleMouseDown = (
event: any,
pdfFileIndex: number,
pdfPageIndex: number,
drawnFileIndex: number
) => {
event.stopPropagation() event.stopPropagation()
pdfFiles[pdfFileIndex].pages[pdfPageIndex].drawnFields.splice(drawnFileIndex, 1) pdfFiles[pdfFileIndex].pages[pdfPageIndex].drawnFields.splice(
drawnFileIndex,
1
)
} }
/** /**
@ -300,9 +341,9 @@ export const DrawPDFFields = (props: Props) => {
*/ */
const getMouseCoordinates = (event: any, customTarget?: any) => { const getMouseCoordinates = (event: any, customTarget?: any) => {
const target = customTarget ? customTarget : event.target const target = customTarget ? customTarget : event.target
const rect = target.getBoundingClientRect(); const rect = target.getBoundingClientRect()
const mouseX = event.clientX - rect.left; //x position within the element. const mouseX = event.clientX - rect.left //x position within the element.
const mouseY = event.clientY - rect.top; //y position within the element. const mouseY = event.clientY - rect.top //y position within the element.
return { return {
mouseX, mouseX,
@ -316,8 +357,8 @@ export const DrawPDFFields = (props: Props) => {
* creates the pdfFiles object and sets to a state * creates the pdfFiles object and sets to a state
*/ */
const parsePdfPages = async () => { const parsePdfPages = async () => {
const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles); const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles)
setPdfFiles(pdfFiles) setPdfFiles(pdfFiles)
} }
@ -326,7 +367,7 @@ export const DrawPDFFields = (props: Props) => {
* @returns if expanded pdf accordion is present * @returns if expanded pdf accordion is present
*/ */
const hasExpandedPdf = () => { const hasExpandedPdf = () => {
return !!pdfFiles.filter(pdfFile => !!pdfFile.expanded).length return !!pdfFiles.filter((pdfFile) => !!pdfFile.expanded).length
} }
const handleAccordionExpandChange = (expanded: boolean, pdfFile: PdfFile) => { const handleAccordionExpandChange = (expanded: boolean, pdfFile: PdfFile) => {
@ -355,9 +396,11 @@ export const DrawPDFFields = (props: Props) => {
*/ */
const getPdfPages = (pdfFile: PdfFile, pdfFileIndex: number) => { const getPdfPages = (pdfFile: PdfFile, pdfFileIndex: number) => {
return ( return (
<Box sx={{ <Box
width: '100%' sx={{
}}> width: '100%'
}}
>
{pdfFile.pages.map((page, pdfPageIndex: number) => { {pdfFile.pages.map((page, pdfPageIndex: number) => {
return ( return (
<div <div
@ -367,17 +410,27 @@ export const DrawPDFFields = (props: Props) => {
marginBottom: '10px' marginBottom: '10px'
}} }}
className={`${styles.pdfImageWrapper} ${selectedTool ? styles.drawing : ''}`} className={`${styles.pdfImageWrapper} ${selectedTool ? styles.drawing : ''}`}
onMouseMove={(event) => {onMouseMove(event, page)}} onMouseMove={(event) => {
onMouseDown={(event) => {onMouseDown(event, page)}} onMouseMove(event, page)
}}
onMouseDown={(event) => {
onMouseDown(event, page)
}}
> >
<img draggable="false" style={{ width: '100%' }} src={page.image}/> <img
draggable="false"
style={{ width: '100%' }}
src={page.image}
/>
{page.drawnFields.map((drawnField, drawnFieldIndex: number) => { {page.drawnFields.map((drawnField, drawnFieldIndex: number) => {
return ( return (
<div <div
key={drawnFieldIndex} key={drawnFieldIndex}
onMouseDown={onDrawnFieldMouseDown} onMouseDown={onDrawnFieldMouseDown}
onMouseMove={(event) => { onDranwFieldMouseMove(event, drawnField)}} onMouseMove={(event) => {
onDranwFieldMouseMove(event, drawnField)
}}
className={styles.drawingRectangle} className={styles.drawingRectangle}
style={{ style={{
left: `${drawnField.left}px`, left: `${drawnField.left}px`,
@ -389,41 +442,68 @@ export const DrawPDFFields = (props: Props) => {
> >
<span <span
onMouseDown={onResizeHandleMouseDown} onMouseDown={onResizeHandleMouseDown}
onMouseMove={(event) => {onResizeHandleMouseMove(event, drawnField)}} onMouseMove={(event) => {
onResizeHandleMouseMove(event, drawnField)
}}
className={styles.resizeHandle} className={styles.resizeHandle}
></span> ></span>
<span <span
onMouseDown={(event) => {onRemoveHandleMouseDown(event, pdfFileIndex, pdfPageIndex, drawnFieldIndex)}} onMouseDown={(event) => {
onRemoveHandleMouseDown(
event,
pdfFileIndex,
pdfPageIndex,
drawnFieldIndex
)
}}
className={styles.removeHandle} className={styles.removeHandle}
> >
<Close fontSize='small'/> <Close fontSize="small" />
</span> </span>
<div <div
onMouseDown={onUserSelectHandleMouseDown} onMouseDown={onUserSelectHandleMouseDown}
className={styles.userSelect} className={styles.userSelect}
> >
<FormControl fullWidth size='small'> <FormControl fullWidth size="small">
<InputLabel id="counterparts">Counterpart</InputLabel> <InputLabel id="counterparts">Counterpart</InputLabel>
<Select <Select
value={drawnField.counterpart} value={drawnField.counterpart}
onChange={(event) => { drawnField.counterpart = event.target.value; refreshPdfFiles() }} onChange={(event) => {
drawnField.counterpart = event.target.value
refreshPdfFiles()
}}
labelId="counterparts" labelId="counterparts"
label="Counterparts" label="Counterparts"
> >
{props.users.map((user, index) => { {props.users.map((user, index) => {
let displayValue = truncate(hexToNpub(user.pubkey), { let displayValue = truncate(
length: 16 hexToNpub(user.pubkey),
}) {
length: 16
}
)
const metadata = props.metadata[user.pubkey] const metadata = props.metadata[user.pubkey]
if (metadata) { if (metadata) {
displayValue = truncate(metadata.name || metadata.display_name || metadata.username, { displayValue = truncate(
length: 16 metadata.name ||
}) metadata.display_name ||
metadata.username,
{
length: 16
}
)
} }
return <MenuItem key={index} value={hexToNpub(user.pubkey)}>{displayValue}</MenuItem> return (
<MenuItem
key={index}
value={hexToNpub(user.pubkey)}
>
{displayValue}
</MenuItem>
)
})} })}
</Select> </Select>
</FormControl> </FormControl>
@ -435,13 +515,13 @@ export const DrawPDFFields = (props: Props) => {
) )
})} })}
</Box> </Box>
) )
} }
if (parsingPdf) { if (parsingPdf) {
return ( return (
<Box sx={{ width: '100%', textAlign: 'center' }}> <Box sx={{ width: '100%', textAlign: 'center' }}>
<CircularProgress/> <CircularProgress />
</Box> </Box>
) )
} }
@ -454,22 +534,28 @@ export const DrawPDFFields = (props: Props) => {
<Box> <Box>
<Box sx={{ mt: 1 }}> <Box sx={{ mt: 1 }}>
<Typography sx={{ mb: 1 }}>Draw fields on the PDFs:</Typography> <Typography sx={{ mb: 1 }}>Draw fields on the PDFs:</Typography>
{pdfFiles.map((pdfFile, pdfFileIndex: number) => { {pdfFiles.map((pdfFile, pdfFileIndex: number) => {
return ( return (
<Accordion key={pdfFileIndex} expanded={pdfFile.expanded} onChange={(_event, expanded) => {handleAccordionExpandChange(expanded, pdfFile)}}> <Accordion
key={pdfFileIndex}
expanded={pdfFile.expanded}
onChange={(_event, expanded) => {
handleAccordionExpandChange(expanded, pdfFile)
}}
>
<AccordionSummary <AccordionSummary
expandIcon={<ExpandMore />} expandIcon={<ExpandMore />}
aria-controls={`panel${pdfFileIndex}-content`} aria-controls={`panel${pdfFileIndex}-content`}
id={`panel${pdfFileIndex}header`} id={`panel${pdfFileIndex}header`}
> >
<PictureAsPdf sx={{ mr: 1 }}/> <PictureAsPdf sx={{ mr: 1 }} />
{pdfFile.file.name} {pdfFile.file.name}
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
{getPdfPages(pdfFile, pdfFileIndex)} {getPdfPages(pdfFile, pdfFileIndex)}
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
) )
})} })}
</Box> </Box>
@ -477,16 +563,22 @@ export const DrawPDFFields = (props: Props) => {
{showDrawToolBox && ( {showDrawToolBox && (
<Box className={styles.drawToolBoxContainer}> <Box className={styles.drawToolBoxContainer}>
<Box className={styles.drawToolBox}> <Box className={styles.drawToolBox}>
{toolbox.filter(drawTool => drawTool.active).map((drawTool: DrawTool, index: number) => { {toolbox
return ( .filter((drawTool) => drawTool.active)
<Box .map((drawTool: DrawTool, index: number) => {
key={index} return (
onClick={() => {handleToolSelect(drawTool)}} className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`}> <Box
{ drawTool.icon } key={index}
{ drawTool.label } onClick={() => {
</Box> handleToolSelect(drawTool)
) }}
})} className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`}
>
{drawTool.icon}
{drawTool.label}
</Box>
)
})}
</Box> </Box>
</Box> </Box>
)} )}

View File

@ -1,6 +1,6 @@
import { PdfFile } from '../../types/drawing.ts' import { PdfFile } from '../../types/drawing.ts'
import { CurrentUserMark } from '../../types/mark.ts' import { CurrentUserMark } from '../../types/mark.ts'
import PdfPageItem from './PdfPageItem.tsx'; import PdfPageItem from './PdfPageItem.tsx'
interface PdfItemProps { interface PdfItemProps {
pdfFile: PdfFile pdfFile: PdfFile
@ -13,23 +13,31 @@ interface PdfItemProps {
/** /**
* Responsible for displaying pages of a single Pdf File. * Responsible for displaying pages of a single Pdf File.
*/ */
const PdfItem = ({ pdfFile, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfItemProps) => { const PdfItem = ({
const filterByPage = (marks: CurrentUserMark[], page: number): CurrentUserMark[] => { pdfFile,
return marks.filter((m) => m.mark.location.page === page); currentUserMarks,
handleMarkClick,
selectedMarkValue,
selectedMark
}: PdfItemProps) => {
const filterByPage = (
marks: CurrentUserMark[],
page: number
): CurrentUserMark[] => {
return marks.filter((m) => m.mark.location.page === page)
} }
return ( return pdfFile.pages.map((page, i) => {
pdfFile.pages.map((page, i) => { return (
return ( <PdfPageItem
<PdfPageItem page={page}
page={page} key={i}
key={i} currentUserMarks={filterByPage(currentUserMarks, i)}
currentUserMarks={filterByPage(currentUserMarks, i)} handleMarkClick={handleMarkClick}
handleMarkClick={handleMarkClick} selectedMarkValue={selectedMarkValue}
selectedMarkValue={selectedMarkValue} selectedMark={selectedMark}
selectedMark={selectedMark} />
/> )
) })
}))
} }
export default PdfItem export default PdfItem

View File

@ -12,14 +12,18 @@ interface PdfMarkItemProps {
/** /**
* Responsible for display an individual Pdf Mark. * Responsible for display an individual Pdf Mark.
*/ */
const PdfMarkItem = ({ selectedMark, handleMarkClick, selectedMarkValue, userMark }: PdfMarkItemProps) => { const PdfMarkItem = ({
const { location } = userMark.mark; selectedMark,
const handleClick = () => handleMarkClick(userMark.mark.id); handleMarkClick,
const getMarkValue = () => ( selectedMarkValue,
userMark
}: PdfMarkItemProps) => {
const { location } = userMark.mark
const handleClick = () => handleMarkClick(userMark.mark.id)
const getMarkValue = () =>
selectedMark?.mark.id === userMark.mark.id selectedMark?.mark.id === userMark.mark.id
? selectedMarkValue ? selectedMarkValue
: userMark.mark.value : userMark.mark.value
)
return ( return (
<div <div
onClick={handleClick} onClick={handleClick}
@ -30,8 +34,10 @@ const PdfMarkItem = ({ selectedMark, handleMarkClick, selectedMarkValue, userMar
width: inPx(location.width), width: inPx(location.width),
height: inPx(location.height) height: inPx(location.height)
}} }}
>{getMarkValue()}</div> >
{getMarkValue()}
</div>
) )
} }
export default PdfMarkItem export default PdfMarkItem

View File

@ -6,17 +6,17 @@ import React, { useState, useEffect } from 'react'
import { import {
findNextCurrentUserMark, findNextCurrentUserMark,
isCurrentUserMarksComplete, isCurrentUserMarksComplete,
updateCurrentUserMarks, updateCurrentUserMarks
} from '../../utils' } from '../../utils'
import { EMPTY } from '../../utils/const.ts' import { EMPTY } from '../../utils/const.ts'
import { Container } from '../Container' import { Container } from '../Container'
import styles from '../../pages/sign/style.module.scss' import styles from '../../pages/sign/style.module.scss'
interface PdfMarkingProps { interface PdfMarkingProps {
files: { pdfFile: PdfFile, filename: string, hash: string | null }[], files: { pdfFile: PdfFile; filename: string; hash: string | null }[]
currentUserMarks: CurrentUserMark[], currentUserMarks: CurrentUserMark[]
setIsReadyToSign: (isReadyToSign: boolean) => void, setIsReadyToSign: (isReadyToSign: boolean) => void
setCurrentUserMarks: (currentUserMarks: CurrentUserMark[]) => void, setCurrentUserMarks: (currentUserMarks: CurrentUserMark[]) => void
setUpdatedMarks: (markToUpdate: Mark) => void setUpdatedMarks: (markToUpdate: Mark) => void
} }
@ -35,21 +35,21 @@ const PdfMarking = (props: PdfMarkingProps) => {
setUpdatedMarks setUpdatedMarks
} = props } = props
const [selectedMark, setSelectedMark] = useState<CurrentUserMark | null>(null) const [selectedMark, setSelectedMark] = useState<CurrentUserMark | null>(null)
const [selectedMarkValue, setSelectedMarkValue] = useState<string>("") const [selectedMarkValue, setSelectedMarkValue] = useState<string>('')
useEffect(() => { useEffect(() => {
setSelectedMark(findNextCurrentUserMark(currentUserMarks) || null) setSelectedMark(findNextCurrentUserMark(currentUserMarks) || null)
}, [currentUserMarks]) }, [currentUserMarks])
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)
setSelectedMark(nextMark!); setSelectedMark(nextMark!)
setSelectedMarkValue(nextMark?.mark.value ?? EMPTY); setSelectedMarkValue(nextMark?.mark.value ?? EMPTY)
} }
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault()
if (!selectedMarkValue || !selectedMark) return; if (!selectedMarkValue || !selectedMark) return
const updatedMark: CurrentUserMark = { const updatedMark: CurrentUserMark = {
...selectedMark, ...selectedMark,
@ -61,7 +61,10 @@ const PdfMarking = (props: PdfMarkingProps) => {
} }
setSelectedMarkValue(EMPTY) setSelectedMarkValue(EMPTY)
const updatedCurrentUserMarks = updateCurrentUserMarks(currentUserMarks, updatedMark); const updatedCurrentUserMarks = updateCurrentUserMarks(
currentUserMarks,
updatedMark
)
setCurrentUserMarks(updatedCurrentUserMarks) setCurrentUserMarks(updatedCurrentUserMarks)
setSelectedMark(findNextCurrentUserMark(updatedCurrentUserMarks) || null) setSelectedMark(findNextCurrentUserMark(updatedCurrentUserMarks) || null)
console.log(isCurrentUserMarksComplete(updatedCurrentUserMarks)) console.log(isCurrentUserMarksComplete(updatedCurrentUserMarks))
@ -69,32 +72,32 @@ const PdfMarking = (props: PdfMarkingProps) => {
setUpdatedMarks(updatedMark.mark) setUpdatedMarks(updatedMark.mark)
} }
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setSelectedMarkValue(event.target.value) const handleChange = (event: React.ChangeEvent<HTMLInputElement>) =>
setSelectedMarkValue(event.target.value)
return ( return (
<> <>
<Container className={styles.container}> <Container className={styles.container}>
{ {currentUserMarks?.length > 0 && (
currentUserMarks?.length > 0 && ( <PdfView
<PdfView files={files}
files={files} handleMarkClick={handleMarkClick}
handleMarkClick={handleMarkClick} selectedMarkValue={selectedMarkValue}
selectedMarkValue={selectedMarkValue} selectedMark={selectedMark}
selectedMark={selectedMark} currentUserMarks={currentUserMarks}
currentUserMarks={currentUserMarks} />
/>)} )}
{ {selectedMark !== null && (
selectedMark !== null && ( <MarkFormField
<MarkFormField handleSubmit={handleSubmit}
handleSubmit={handleSubmit} handleChange={handleChange}
handleChange={handleChange} selectedMark={selectedMark}
selectedMark={selectedMark} selectedMarkValue={selectedMarkValue}
selectedMarkValue={selectedMarkValue} />
/> )}
)}
</Container> </Container>
</> </>
) )
} }
export default PdfMarking export default PdfMarking

View File

@ -13,7 +13,13 @@ interface PdfPageProps {
/** /**
* Responsible for rendering a single Pdf Page and its Marks * 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
className={styles.pdfImageWrapper} className={styles.pdfImageWrapper}
@ -23,24 +29,18 @@ const PdfPageItem = ({ page, currentUserMarks, handleMarkClick, selectedMarkValu
marginTop: '10px' marginTop: '10px'
}} }}
> >
<img <img draggable="false" src={page.image} style={{ width: '100%' }} />
draggable="false" {currentUserMarks.map((m, i) => (
src={page.image} <PdfMarkItem
style={{ width: '100%'}} key={i}
handleMarkClick={handleMarkClick}
/> selectedMarkValue={selectedMarkValue}
{ userMark={m}
currentUserMarks.map((m, i) => ( selectedMark={selectedMark}
<PdfMarkItem
key={i}
handleMarkClick={handleMarkClick}
selectedMarkValue={selectedMarkValue}
userMark={m}
selectedMark={selectedMark}
/> />
))} ))}
</div> </div>
) )
} }
export default PdfPageItem export default PdfPageItem

View File

@ -4,7 +4,7 @@ import PdfItem from './PdfItem.tsx'
import { CurrentUserMark } from '../../types/mark.ts' import { CurrentUserMark } from '../../types/mark.ts'
interface PdfViewProps { interface PdfViewProps {
files: { pdfFile: PdfFile, filename: string, hash: string | null }[] files: { pdfFile: PdfFile; filename: string; hash: string | null }[]
currentUserMarks: CurrentUserMark[] currentUserMarks: CurrentUserMark[]
handleMarkClick: (id: number) => void handleMarkClick: (id: number) => void
selectedMarkValue: string selectedMarkValue: string
@ -14,29 +14,38 @@ interface PdfViewProps {
/** /**
* Responsible for rendering Pdf files. * Responsible for rendering Pdf files.
*/ */
const PdfView = ({ files, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfViewProps) => { const PdfView = ({
const filterByFile = (currentUserMarks: CurrentUserMark[], hash: string): CurrentUserMark[] => { files,
return currentUserMarks.filter((currentUserMark) => currentUserMark.mark.pdfFileHash === hash) currentUserMarks,
handleMarkClick,
selectedMarkValue,
selectedMark
}: PdfViewProps) => {
const filterByFile = (
currentUserMarks: CurrentUserMark[],
hash: string
): CurrentUserMark[] => {
return currentUserMarks.filter(
(currentUserMark) => currentUserMark.mark.pdfFileHash === hash
)
} }
return ( return (
<Box sx={{ width: '100%' }}> <Box sx={{ width: '100%' }}>
{ {files.map(({ pdfFile, hash }, i) => {
files.map(({ pdfFile, hash }, i) => { if (!hash) return
if (!hash) return return (
return ( <PdfItem
<PdfItem pdfFile={pdfFile}
pdfFile={pdfFile} key={i}
key={i} currentUserMarks={filterByFile(currentUserMarks, hash)}
currentUserMarks={filterByFile(currentUserMarks, hash)} selectedMark={selectedMark}
selectedMark={selectedMark} handleMarkClick={handleMarkClick}
handleMarkClick={handleMarkClick} selectedMarkValue={selectedMarkValue}
selectedMarkValue={selectedMarkValue}
/> />
) )
}) })}
}
</Box> </Box>
) )
} }
export default PdfView; export default PdfView

View File

@ -111,7 +111,9 @@ button:disabled {
/* Fonts */ /* Fonts */
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Roboto';
src: local('Roboto Medium'), local('Roboto-Medium'), src:
local('Roboto Medium'),
local('Roboto-Medium'),
url('assets/fonts/roboto-medium.woff2') format('woff2'), url('assets/fonts/roboto-medium.woff2') format('woff2'),
url('assets/fonts/roboto-medium.woff') format('woff'); url('assets/fonts/roboto-medium.woff') format('woff');
font-weight: 500; font-weight: 500;
@ -121,7 +123,9 @@ button:disabled {
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Roboto';
src: local('Roboto Light'), local('Roboto-Light'), src:
local('Roboto Light'),
local('Roboto-Light'),
url('assets/fonts/roboto-light.woff2') format('woff2'), url('assets/fonts/roboto-light.woff2') format('woff2'),
url('assets/fonts/roboto-light.woff') format('woff'); url('assets/fonts/roboto-light.woff') format('woff');
font-weight: 300; font-weight: 300;
@ -131,7 +135,9 @@ button:disabled {
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Roboto';
src: local('Roboto Bold'), local('Roboto-Bold'), src:
local('Roboto Bold'),
local('Roboto-Bold'),
url('assets/fonts/roboto-bold.woff2') format('woff2'), url('assets/fonts/roboto-bold.woff2') format('woff2'),
url('assets/fonts/roboto-bold.woff') format('woff'); url('assets/fonts/roboto-bold.woff') format('woff');
font-weight: bold; font-weight: bold;
@ -141,10 +147,12 @@ button:disabled {
@font-face { @font-face {
font-family: 'Roboto'; font-family: 'Roboto';
src: local('Roboto'), local('Roboto-Regular'), src:
local('Roboto'),
local('Roboto-Regular'),
url('assets/fonts/roboto-regular.woff2') format('woff2'), url('assets/fonts/roboto-regular.woff2') format('woff2'),
url('assets/fonts/roboto-regular.woff') format('woff'); url('assets/fonts/roboto-regular.woff') format('woff');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }

View File

@ -341,29 +341,30 @@ export const CreatePage = () => {
return fileHashes return fileHashes
} }
const createMarks = (fileHashes: { [key: string]: string }) : Mark[] => { const createMarks = (fileHashes: { [key: string]: string }): Mark[] => {
return drawnPdfs.flatMap((drawnPdf) => { return drawnPdfs
const fileHash = fileHashes[drawnPdf.file.name]; .flatMap((drawnPdf) => {
return drawnPdf.pages.flatMap((page, index) => { const fileHash = fileHashes[drawnPdf.file.name]
return page.drawnFields.map((drawnField) => { return drawnPdf.pages.flatMap((page, index) => {
return { return page.drawnFields.map((drawnField) => {
type: drawnField.type, return {
location: { type: drawnField.type,
page: index, location: {
top: drawnField.top, page: index,
left: drawnField.left, top: drawnField.top,
height: drawnField.height, left: drawnField.left,
width: drawnField.width, height: drawnField.height,
}, width: drawnField.width
npub: drawnField.counterpart, },
pdfFileHash: fileHash npub: drawnField.counterpart,
} pdfFileHash: fileHash
}
})
}) })
}) })
})
.map((mark, index) => { .map((mark, index) => {
return {...mark, id: index } return { ...mark, id: index }
}); })
} }
// Handle errors during zip file generation // Handle errors during zip file generation
@ -431,13 +432,9 @@ export const CreatePage = () => {
if (!arraybuffer) return null if (!arraybuffer) return null
return new File( return new File([new Blob([arraybuffer])], `${unixNow}.sigit.zip`, {
[new Blob([arraybuffer])], type: 'application/zip'
`${unixNow}.sigit.zip`, })
{
type: 'application/zip'
}
)
} }
// Handle errors during file upload // Handle errors during file upload
@ -545,9 +542,7 @@ 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) => return receivers.map((receiver) => sendNotification(receiver, meta))
sendNotification(receiver, meta)
)
} }
const handleCreate = async () => { const handleCreate = async () => {

View File

@ -15,8 +15,8 @@ interface MarkFormFieldProps {
* Responsible for rendering a form field connected to a mark and keeping track of its value. * 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')
return ( return (
<div className={styles.fixedBottomForm}> <div className={styles.fixedBottomForm}>
<Box component="form" onSubmit={handleSubmit}> <Box component="form" onSubmit={handleSubmit}>
@ -34,4 +34,4 @@ const MarkFormField = (props: MarkFormFieldProps) => {
) )
} }
export default MarkFormField; export default MarkFormField

View File

@ -16,13 +16,16 @@ import { State } from '../../store/rootReducer'
import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types' import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types'
import { import {
decryptArrayBuffer, decryptArrayBuffer,
encryptArrayBuffer, extractMarksFromSignedMeta, encryptArrayBuffer,
extractMarksFromSignedMeta,
extractZipUrlAndEncryptionKey, extractZipUrlAndEncryptionKey,
generateEncryptionKey, generateEncryptionKey,
generateKeysFile, getFilesWithHashes, generateKeysFile,
getFilesWithHashes,
getHash, getHash,
hexToNpub, hexToNpub,
isOnline, loadZip, isOnline,
loadZip,
now, now,
npubToHex, npubToHex,
parseJson, parseJson,
@ -41,7 +44,8 @@ import { getLastSignersSig } from '../../utils/sign.ts'
import { import {
filterMarksByPubkey, filterMarksByPubkey,
getCurrentUserMarks, getCurrentUserMarks,
isCurrentUserMarksComplete, updateMarks isCurrentUserMarksComplete,
updateMarks
} from '../../utils' } from '../../utils'
import PdfMarking from '../../components/PDFView/PdfMarking.tsx' import PdfMarking from '../../components/PDFView/PdfMarking.tsx'
enum SignedStatus { enum SignedStatus {
@ -81,7 +85,7 @@ export const SignPage = () => {
const [signers, setSigners] = useState<`npub1${string}`[]>([]) const [signers, setSigners] = useState<`npub1${string}`[]>([])
const [viewers, setViewers] = useState<`npub1${string}`[]>([]) const [viewers, setViewers] = useState<`npub1${string}`[]>([])
const [marks, setMarks] = useState<Mark[] >([]) const [marks, setMarks] = useState<Mark[]>([])
const [creatorFileHashes, setCreatorFileHashes] = useState<{ const [creatorFileHashes, setCreatorFileHashes] = useState<{
[key: string]: string [key: string]: string
}>({}) }>({})
@ -100,8 +104,10 @@ export const SignPage = () => {
const [authUrl, setAuthUrl] = useState<string>() const [authUrl, setAuthUrl] = useState<string>()
const nostrController = NostrController.getInstance() const nostrController = NostrController.getInstance()
const [currentUserMarks, setCurrentUserMarks] = useState<CurrentUserMark[]>([]); const [currentUserMarks, setCurrentUserMarks] = useState<CurrentUserMark[]>(
const [isReadyToSign, setIsReadyToSign] = useState(false); []
)
const [isReadyToSign, setIsReadyToSign] = useState(false)
useEffect(() => { useEffect(() => {
if (signers.length > 0) { if (signers.length > 0) {
@ -192,13 +198,16 @@ export const SignPage = () => {
setViewers(createSignatureContent.viewers) setViewers(createSignatureContent.viewers)
setCreatorFileHashes(createSignatureContent.fileHashes) setCreatorFileHashes(createSignatureContent.fileHashes)
setSubmittedBy(createSignatureEvent.pubkey) setSubmittedBy(createSignatureEvent.pubkey)
setMarks(createSignatureContent.markConfig); setMarks(createSignatureContent.markConfig)
if (usersPubkey) { if (usersPubkey) {
const metaMarks = filterMarksByPubkey(createSignatureContent.markConfig, usersPubkey!) const metaMarks = filterMarksByPubkey(
createSignatureContent.markConfig,
usersPubkey!
)
const signedMarks = extractMarksFromSignedMeta(meta) const signedMarks = extractMarksFromSignedMeta(meta)
const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks); const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks)
setCurrentUserMarks(currentUserMarks); setCurrentUserMarks(currentUserMarks)
// setCurrentUserMark(findNextCurrentUserMark(currentUserMarks) || null) // setCurrentUserMark(findNextCurrentUserMark(currentUserMarks) || null)
Review

We should remove the comment or explain why we keep it

We should remove the comment or explain why we keep it
Review

Removed, it's no longer needed.

Removed, it's no longer needed.
setIsReadyToSign(isCurrentUserMarksComplete(currentUserMarks)) setIsReadyToSign(isCurrentUserMarksComplete(currentUserMarks))
} }
@ -307,7 +316,7 @@ export const SignPage = () => {
) )
if (arrayBuffer) { if (arrayBuffer) {
files[fileName] = await convertToPdfFile(arrayBuffer, fileName); files[fileName] = await convertToPdfFile(arrayBuffer, fileName)
const hash = await getHash(arrayBuffer) const hash = await getHash(arrayBuffer)
if (hash) { if (hash) {
@ -348,7 +357,7 @@ export const SignPage = () => {
const decrypt = async (file: File) => { const decrypt = async (file: File) => {
setLoadingSpinnerDesc('Decrypting file') setLoadingSpinnerDesc('Decrypting file')
const zip = await loadZip(file); const zip = await loadZip(file)
if (!zip) return if (!zip) return
const parsedKeysJson = await parseKeysJson(zip) const parsedKeysJson = await parseKeysJson(zip)
@ -439,7 +448,7 @@ export const SignPage = () => {
) )
if (arrayBuffer) { if (arrayBuffer) {
files[fileName] = await convertToPdfFile(arrayBuffer, fileName); files[fileName] = await convertToPdfFile(arrayBuffer, fileName)
const hash = await getHash(arrayBuffer) const hash = await getHash(arrayBuffer)
if (hash) { if (hash) {
@ -520,7 +529,10 @@ export const SignPage = () => {
} }
// Sign the event for the meta file // Sign the event for the meta file
const signEventForMeta = async (signerContent: { prevSig: string, marks: Mark[] }) => { const signEventForMeta = async (signerContent: {
prevSig: string
marks: Mark[]
}) => {
return await signEventForMetaFile( return await signEventForMetaFile(
JSON.stringify(signerContent), JSON.stringify(signerContent),
nostrController, nostrController,
@ -529,8 +541,8 @@ export const SignPage = () => {
} }
const getSignerMarksForMeta = (): Mark[] | undefined => { const getSignerMarksForMeta = (): Mark[] | undefined => {
if (currentUserMarks.length === 0) return; if (currentUserMarks.length === 0) return
return currentUserMarks.map(( { mark }: CurrentUserMark) => mark); return currentUserMarks.map(({ mark }: CurrentUserMark) => mark)
} }
// Update the meta signatures // Update the meta signatures
@ -600,13 +612,9 @@ export const SignPage = () => {
if (!arraybuffer) return null if (!arraybuffer) return null
return new File( return new File([new Blob([arraybuffer])], `${unixNow}.sigit.zip`, {
[new Blob([arraybuffer])], type: 'application/zip'
`${unixNow}.sigit.zip`, })
{
type: 'application/zip'
}
)
} }
// Handle errors during zip file generation // Handle errors during zip file generation
@ -694,7 +702,7 @@ export const SignPage = () => {
setIsLoading(true) setIsLoading(true)
setLoadingSpinnerDesc('Signing nostr event') setLoadingSpinnerDesc('Signing nostr event')
if (!meta) return; if (!meta) return
const prevSig = getLastSignersSig(meta, signers) const prevSig = getLastSignersSig(meta, signers)
if (!prevSig) return if (!prevSig) return
@ -918,11 +926,13 @@ export const SignPage = () => {
) )
} }
return <PdfMarking return (
files={getFilesWithHashes(files, currentFileHashes)} <PdfMarking
currentUserMarks={currentUserMarks} files={getFilesWithHashes(files, currentFileHashes)}
setIsReadyToSign={setIsReadyToSign} currentUserMarks={currentUserMarks}
setCurrentUserMarks={setCurrentUserMarks} setIsReadyToSign={setIsReadyToSign}
setUpdatedMarks={setUpdatedMarks} setCurrentUserMarks={setCurrentUserMarks}
/> setUpdatedMarks={setUpdatedMarks}
/>
)
} }

View File

@ -23,14 +23,17 @@ import {
SignedEventContent SignedEventContent
} from '../../types' } from '../../types'
import { import {
decryptArrayBuffer, extractMarksFromSignedMeta, decryptArrayBuffer,
extractMarksFromSignedMeta,
extractZipUrlAndEncryptionKey, extractZipUrlAndEncryptionKey,
getHash, getHash,
hexToNpub, now, hexToNpub,
now,
npubToHex, npubToHex,
parseJson, parseJson,
readContentOfZipEntry, readContentOfZipEntry,
shorten, signEventForMetaFile shorten,
signEventForMetaFile
} from '../../utils' } from '../../utils'
import styles from './style.module.scss' import styles from './style.module.scss'
import { Cancel, CheckCircle } from '@mui/icons-material' import { Cancel, CheckCircle } from '@mui/icons-material'
@ -41,7 +44,7 @@ import {
addMarks, addMarks,
convertToPdfBlob, convertToPdfBlob,
convertToPdfFile, convertToPdfFile,
groupMarksByPage, groupMarksByPage
} 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'
@ -78,7 +81,7 @@ 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 [files, setFiles] = useState<{ [filename: string]: PdfFile }>({})
const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>( const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>(
{} {}
@ -155,7 +158,10 @@ export const VerifyPage = () => {
) )
if (arrayBuffer) { if (arrayBuffer) {
files[fileName] = await convertToPdfFile(arrayBuffer, fileName!) files[fileName] = await convertToPdfFile(
arrayBuffer,
fileName!
)
const hash = await getHash(arrayBuffer) const hash = await getHash(arrayBuffer)
if (hash) { if (hash) {
@ -169,7 +175,6 @@ export const VerifyPage = () => {
setCurrentFileHashes(fileHashes) setCurrentFileHashes(fileHashes)
setFiles(files) setFiles(files)
setSigners(createSignatureContent.signers) setSigners(createSignatureContent.signers)
setViewers(createSignatureContent.viewers) setViewers(createSignatureContent.viewers)
setCreatorFileHashes(createSignatureContent.fileHashes) setCreatorFileHashes(createSignatureContent.fileHashes)
@ -177,8 +182,6 @@ export const VerifyPage = () => {
setMeta(metaInNavState) setMeta(metaInNavState)
setIsLoading(false) setIsLoading(false)
} }
}) })
.catch((err) => { .catch((err) => {
@ -381,7 +384,7 @@ export const VerifyPage = () => {
} }
const handleExport = async () => { const handleExport = async () => {
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)
if ( if (
@ -395,10 +398,10 @@ export const VerifyPage = () => {
setIsLoading(true) setIsLoading(true)
setLoadingSpinnerDesc('Signing nostr event') setLoadingSpinnerDesc('Signing nostr event')
if (!meta) return; if (!meta) return
const prevSig = getLastSignersSig(meta, signers) const prevSig = getLastSignersSig(meta, signers)
if (!prevSig) return; if (!prevSig) return
const signedEvent = await signEventForMetaFile( const signedEvent = await signEventForMetaFile(
JSON.stringify({ prevSig }), JSON.stringify({ prevSig }),
@ -406,10 +409,10 @@ export const VerifyPage = () => {
setIsLoading setIsLoading
) )
if (!signedEvent) return; if (!signedEvent) return
const exportSignature = JSON.stringify(signedEvent, null, 2) const exportSignature = JSON.stringify(signedEvent, null, 2)
const updatedMeta = {...meta, exportSignature } const updatedMeta = { ...meta, exportSignature }
const stringifiedMeta = JSON.stringify(updatedMeta, null, 2) const stringifiedMeta = JSON.stringify(updatedMeta, null, 2)
const zip = new JSZip() const zip = new JSZip()

View File

@ -9,7 +9,7 @@ export interface MouseState {
} }
export interface PdfFile { export interface PdfFile {
file: File, file: File
pages: PdfPage[] pages: PdfPage[]
expanded?: boolean expanded?: boolean
} }
@ -34,7 +34,7 @@ export interface DrawnField {
export interface DrawTool { export interface DrawTool {
identifier: MarkType identifier: MarkType
label: string label: string
icon: JSX.Element, icon: JSX.Element
defaultValue?: string defaultValue?: string
selected?: boolean selected?: boolean
active?: boolean active?: boolean
@ -46,4 +46,4 @@ export enum MarkType {
FULLNAME = 'FULLNAME', FULLNAME = 'FULLNAME',
DATE = 'DATE', DATE = 'DATE',
DATETIME = 'DATETIME' DATETIME = 'DATETIME'
} }

View File

@ -1,4 +1,4 @@
import { MarkType } from "./drawing"; import { MarkType } from './drawing'
export interface CurrentUserMark { export interface CurrentUserMark {
mark: Mark mark: Mark
@ -7,18 +7,18 @@ export interface CurrentUserMark {
} }
export interface Mark { export interface Mark {
id: number; id: number
npub: string; npub: string
pdfFileHash: string; pdfFileHash: string
type: MarkType; type: MarkType
location: MarkLocation; location: MarkLocation
value?: string; value?: string
} }
export interface MarkLocation { export interface MarkLocation {
top: number; top: number
left: number; left: number
height: number; height: number
width: number; width: number
page: number; page: number
} }

View File

@ -1,4 +1,4 @@
export interface OutputByType { export interface OutputByType {
base64: string base64: string
string: string string: string
text: string text: string
@ -11,16 +11,18 @@
} }
interface InputByType { interface InputByType {
base64: string; base64: string
string: string; string: string
text: string; text: string
binarystring: string; binarystring: string
array: number[]; array: number[]
uint8array: Uint8Array; uint8array: Uint8Array
arraybuffer: ArrayBuffer; arraybuffer: ArrayBuffer
blob: Blob; blob: Blob
stream: NodeJS.ReadableStream; stream: NodeJS.ReadableStream
} }
export type OutputType = keyof OutputByType export type OutputType = keyof OutputByType
export type InputFileFormat = InputByType[keyof InputByType] | Promise<InputByType[keyof InputByType]>; export type InputFileFormat =
| InputByType[keyof InputByType]
| Promise<InputByType[keyof InputByType]>

View File

@ -3,4 +3,4 @@ import { MarkType } from '../types/drawing.ts'
export const EMPTY: string = '' export const EMPTY: string = ''
export const MARK_TYPE_TRANSLATION: { [key: string]: string } = { export const MARK_TYPE_TRANSLATION: { [key: string]: string } = {
[MarkType.FULLNAME.valueOf()]: 'Full Name' [MarkType.FULLNAME.valueOf()]: 'Full Name'
} }

View File

@ -9,9 +9,12 @@ import { Event } from 'nostr-tools'
* @param marks - default Marks extracted from Meta * @param marks - default Marks extracted from Meta
* @param signedMetaMarks - signed user Marks extracted from DocSignatures * @param signedMetaMarks - signed user Marks extracted from DocSignatures
*/ */
const getCurrentUserMarks = (marks: Mark[], signedMetaMarks: Mark[]): CurrentUserMark[] => { const getCurrentUserMarks = (
marks: Mark[],
signedMetaMarks: Mark[]
): CurrentUserMark[] => {
return marks.map((mark, index, arr) => { return marks.map((mark, index, arr) => {
const signedMark = signedMetaMarks.find((m) => m.id === mark.id); const signedMark = signedMetaMarks.find((m) => m.id === mark.id)
if (signedMark && !!signedMark.value) { if (signedMark && !!signedMark.value) {
mark.value = signedMark.value mark.value = signedMark.value
} }
@ -27,8 +30,10 @@ const getCurrentUserMarks = (marks: Mark[], signedMetaMarks: Mark[]): CurrentUse
* Returns next incomplete CurrentUserMark if there is one * Returns next incomplete CurrentUserMark if there is one
* @param usersMarks * @param usersMarks
*/ */
const findNextCurrentUserMark = (usersMarks: CurrentUserMark[]): CurrentUserMark | undefined => { const findNextCurrentUserMark = (
return usersMarks.find((mark) => !mark.isCompleted); usersMarks: CurrentUserMark[]
): CurrentUserMark | undefined => {
return usersMarks.find((mark) => !mark.isCompleted)
} }
/** /**
@ -37,7 +42,7 @@ const findNextCurrentUserMark = (usersMarks: CurrentUserMark[]): CurrentUserMark
* @param pubkey * @param pubkey
*/ */
const filterMarksByPubkey = (marks: Mark[], pubkey: string): Mark[] => { const filterMarksByPubkey = (marks: Mark[], pubkey: string): Mark[] => {
return marks.filter(mark => mark.npub === hexToNpub(pubkey)) return marks.filter((mark) => mark.npub === hexToNpub(pubkey))
} }
/** /**
@ -57,7 +62,9 @@ const extractMarksFromSignedMeta = (meta: Meta): Mark[] => {
* marked as complete. * marked as complete.
* @param currentUserMarks * @param currentUserMarks
*/ */
const isCurrentUserMarksComplete = (currentUserMarks: CurrentUserMark[]): boolean => { const isCurrentUserMarksComplete = (
currentUserMarks: CurrentUserMark[]
): boolean => {
return currentUserMarks.every((mark) => mark.isCompleted) return currentUserMarks.every((mark) => mark.isCompleted)
} }
@ -68,7 +75,7 @@ const isCurrentUserMarksComplete = (currentUserMarks: CurrentUserMark[]): boolea
* @param markToUpdate * @param markToUpdate
*/ */
const updateMarks = (marks: Mark[], markToUpdate: Mark): Mark[] => { const updateMarks = (marks: Mark[], markToUpdate: Mark): Mark[] => {
const indexToUpdate = marks.findIndex(mark => mark.id === markToUpdate.id); const indexToUpdate = marks.findIndex((mark) => mark.id === markToUpdate.id)
return [ return [
...marks.slice(0, indexToUpdate), ...marks.slice(0, indexToUpdate),
markToUpdate, markToUpdate,
@ -76,8 +83,13 @@ const updateMarks = (marks: Mark[], markToUpdate: Mark): Mark[] => {
] ]
} }
const updateCurrentUserMarks = (currentUserMarks: CurrentUserMark[], markToUpdate: CurrentUserMark): CurrentUserMark[] => { const updateCurrentUserMarks = (
const indexToUpdate = currentUserMarks.findIndex((m) => m.mark.id === markToUpdate.mark.id) currentUserMarks: CurrentUserMark[],
markToUpdate: CurrentUserMark
): CurrentUserMark[] => {
const indexToUpdate = currentUserMarks.findIndex(
(m) => m.mark.id === markToUpdate.mark.id
)
return [ return [
...currentUserMarks.slice(0, indexToUpdate), ...currentUserMarks.slice(0, indexToUpdate),
markToUpdate, markToUpdate,
@ -85,7 +97,7 @@ const updateCurrentUserMarks = (currentUserMarks: CurrentUserMark[], markToUpdat
] ]
} }
const isLast = <T>(index: number, arr: T[]) => (index === (arr.length -1)) const isLast = <T>(index: number, arr: T[]) => index === arr.length - 1
export { export {
getCurrentUserMarks, getCurrentUserMarks,
@ -94,5 +106,5 @@ export {
isCurrentUserMarksComplete, isCurrentUserMarksComplete,
findNextCurrentUserMark, findNextCurrentUserMark,
updateMarks, updateMarks,
updateCurrentUserMarks, updateCurrentUserMarks
} }

View File

@ -3,13 +3,14 @@ import * as PDFJS from 'pdfjs-dist'
import { PDFDocument } from 'pdf-lib' import { PDFDocument } from 'pdf-lib'
import { Mark } from '../types/mark.ts' import { Mark } from '../types/mark.ts'
PDFJS.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.mjs'; PDFJS.GlobalWorkerOptions.workerSrc =
'node_modules/pdfjs-dist/build/pdf.worker.mjs'
/** /**
* Scale between the PDF page's natural size and rendered size * Scale between the PDF page's natural size and rendered size
* @constant {number} * @constant {number}
*/ */
const SCALE: number = 3; const SCALE: number = 3
/** /**
* Defined font size used when generating a PDF. Currently it is difficult to fully * Defined font size used when generating a PDF. Currently it is difficult to fully
* correlate font size used at the time of filling in / drawing on the PDF * correlate font size used at the time of filling in / drawing on the PDF
@ -17,20 +18,20 @@ const SCALE: number = 3;
* This should be fixed going forward. * This should be fixed going forward.
* Switching to PDF-Lib will most likely make this problem redundant. * Switching to PDF-Lib will most likely make this problem redundant.
*/ */
const FONT_SIZE: number = 40; const FONT_SIZE: number = 40
/** /**
* Current font type used when generating a PDF. * Current font type used when generating a PDF.
*/ */
const FONT_TYPE: string = 'Arial'; const FONT_TYPE: string = 'Arial'
/** /**
* Converts a PDF ArrayBuffer to a generic PDF File * Converts a PDF ArrayBuffer to a generic PDF File
* @param arrayBuffer of a PDF * @param arrayBuffer of a PDF
* @param fileName identifier of the pdf file * @param fileName identifier of the pdf file
*/ */
const toFile = (arrayBuffer: ArrayBuffer, fileName: string) : File => { const toFile = (arrayBuffer: ArrayBuffer, fileName: string): File => {
const blob = new Blob([arrayBuffer], { type: "application/pdf" }); const blob = new Blob([arrayBuffer], { type: 'application/pdf' })
return new File([blob], fileName, { type: "application/pdf" }); return new File([blob], fileName, { type: 'application/pdf' })
} }
/** /**
@ -50,42 +51,40 @@ const toPdfFile = async (file: File): Promise<PdfFile> => {
* @return PdfFile[] - an array of Sigit's internal Pdf File type * @return PdfFile[] - an array of Sigit's internal Pdf File type
*/ */
const toPdfFiles = async (selectedFiles: File[]): Promise<PdfFile[]> => { const toPdfFiles = async (selectedFiles: File[]): Promise<PdfFile[]> => {
return Promise.all(selectedFiles return Promise.all(selectedFiles.filter(isPdf).map(toPdfFile))
.filter(isPdf)
.map(toPdfFile));
} }
/** /**
* A utility that transforms a drawing coordinate number into a CSS-compatible string * A utility that transforms a drawing coordinate number into a CSS-compatible string
* @param coordinate * @param coordinate
*/ */
const inPx = (coordinate: number): string => `${coordinate}px`; const inPx = (coordinate: number): string => `${coordinate}px`
/** /**
* A utility that checks if a given file is of the pdf type * A utility that checks if a given file is of the pdf type
* @param file * @param file
*/ */
const isPdf = (file: File) => file.type.toLowerCase().includes('pdf'); 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> => { 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: any) => { reader.onload = (e: any) => {
const data = e.target.result const data = e.target.result
resolve(data) resolve(data)
}; }
reader.onerror = (err) => { reader.onerror = (err) => {
console.error('err', err) console.error('err', err)
reject(err) reject(err)
}; }
reader.readAsDataURL(file); reader.readAsDataURL(file)
}) })
} }
@ -94,26 +93,28 @@ const readPdf = (file: File): Promise<string> => {
* @param data pdf file bytes * @param data pdf file bytes
*/ */
const pdfToImages = async (data: any): 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')
for (let i = 0; i < pdf.numPages; i++) { for (let i = 0; i < pdf.numPages; i++) {
const page = await pdf.getPage(i + 1); const page = await pdf.getPage(i + 1)
const viewport = page.getViewport({ scale: SCALE }); const viewport = page.getViewport({ scale: SCALE })
const context = canvas.getContext("2d"); const context = canvas.getContext('2d')
canvas.height = viewport.height; canvas.height = viewport.height
canvas.width = viewport.width; canvas.width = viewport.width
await page.render({ canvasContext: context!, viewport: viewport }).promise; await page.render({ canvasContext: context!, viewport: viewport }).promise
images.push(canvas.toDataURL()); images.push(canvas.toDataURL())
} }
return Promise.resolve(images.map((image) => { return Promise.resolve(
return { images.map((image) => {
image, return {
drawnFields: [] image,
} drawnFields: []
})) }
})
)
} }
/** /**
@ -121,34 +122,37 @@ const pdfToImages = async (data: any): Promise<PdfPage[]> => {
* Returns an array of encoded images where each image is a representation * Returns an array of encoded images where each image is a representation
* of a PDF page with completed and signed marks from all users * of a PDF page with completed and signed marks from all users
*/ */
const addMarks = async (file: File, marksPerPage: {[key: string]: Mark[]}) => { const addMarks = async (
const p = await readPdf(file); file: File,
const pdf = await PDFJS.getDocument(p).promise; marksPerPage: { [key: string]: Mark[] }
const canvas = document.createElement("canvas"); ) => {
const p = await readPdf(file)
const pdf = await PDFJS.getDocument(p).promise
const canvas = document.createElement('canvas')
const images: string[] = []; const images: string[] = []
for (let i = 0; i< pdf.numPages; i++) { for (let i = 0; i < pdf.numPages; i++) {
const page = await pdf.getPage(i+1) const page = await pdf.getPage(i + 1)
const viewport = page.getViewport({ scale: SCALE }); const viewport = page.getViewport({ scale: SCALE })
const context = canvas.getContext("2d"); const context = canvas.getContext('2d')
canvas.height = viewport.height; canvas.height = viewport.height
canvas.width = viewport.width; canvas.width = viewport.width
await page.render({ canvasContext: context!, viewport: viewport }).promise; await page.render({ canvasContext: context!, viewport: viewport }).promise
marksPerPage[i].forEach(mark => draw(mark, context!)) marksPerPage[i].forEach((mark) => draw(mark, context!))
images.push(canvas.toDataURL()); images.push(canvas.toDataURL())
} }
return Promise.resolve(images); return Promise.resolve(images)
} }
/** /**
* Utility to scale mark in line with the PDF-to-PNG scale * Utility to scale mark in line with the PDF-to-PNG scale
*/ */
const scaleMark = (mark: Mark): Mark => { const scaleMark = (mark: Mark): Mark => {
const { location } = mark; const { location } = mark
return { return {
...mark, ...mark,
location: { location: {
@ -165,7 +169,7 @@ const scaleMark = (mark: Mark): Mark => {
* Utility to check if a Mark has value * Utility to check if a Mark has value
* @param mark * @param mark
*/ */
const hasValue = (mark: Mark): boolean => !!mark.value; const hasValue = (mark: Mark): boolean => !!mark.value
/** /**
* Draws a Mark on a Canvas representation of a PDF Page * Draws a Mark on a Canvas representation of a PDF Page
@ -173,14 +177,14 @@ const hasValue = (mark: Mark): boolean => !!mark.value;
* @param ctx a Canvas representation of a specific PDF Page * @param ctx a Canvas representation of a specific PDF Page
*/ */
const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => { const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => {
const { location } = mark; const { location } = mark
ctx!.font = FONT_SIZE + 'px ' + FONT_TYPE; ctx!.font = FONT_SIZE + 'px ' + FONT_TYPE
ctx!.fillStyle = 'black'; ctx!.fillStyle = 'black'
const textMetrics = ctx!.measureText(mark.value!); const textMetrics = ctx!.measureText(mark.value!)
const textX = location.left + (location.width - textMetrics.width) / 2; const textX = location.left + (location.width - textMetrics.width) / 2
const textY = location.top + (location.height + parseInt(ctx!.font)) / 2; const textY = location.top + (location.height + parseInt(ctx!.font)) / 2
ctx!.fillText(mark.value!, textX, textY); ctx!.fillText(mark.value!, textX, textY)
} }
/** /**
@ -188,7 +192,7 @@ const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => {
* @param markedPdfPages * @param markedPdfPages
*/ */
const convertToPdfBlob = async (markedPdfPages: string[]): Promise<Blob> => { const convertToPdfBlob = async (markedPdfPages: string[]): Promise<Blob> => {
const pdfDoc = await PDFDocument.create(); const pdfDoc = await PDFDocument.create()
for (const page of markedPdfPages) { for (const page of markedPdfPages) {
const pngImage = await pdfDoc.embedPng(page) const pngImage = await pdfDoc.embedPng(page)
@ -203,7 +207,6 @@ const convertToPdfBlob = async (markedPdfPages: string[]): Promise<Blob> => {
const pdfBytes = await pdfDoc.save() const pdfBytes = await pdfDoc.save()
return new Blob([pdfBytes], { type: 'application/pdf' }) return new Blob([pdfBytes], { type: 'application/pdf' })
} }
/** /**
@ -211,9 +214,12 @@ const convertToPdfBlob = async (markedPdfPages: string[]): Promise<Blob> => {
* @param arrayBuffer * @param arrayBuffer
* @param fileName * @param fileName
*/ */
const convertToPdfFile = async (arrayBuffer: ArrayBuffer, fileName: string): Promise<PdfFile> => { const convertToPdfFile = async (
const file = toFile(arrayBuffer, fileName); arrayBuffer: ArrayBuffer,
return toPdfFile(file); fileName: string
): Promise<PdfFile> => {
const file = toFile(arrayBuffer, fileName)
return toPdfFile(file)
} }
/** /**
@ -226,7 +232,7 @@ const groupMarksByPage = (marks: Mark[]) => {
return marks return marks
.filter(hasValue) .filter(hasValue)
.map(scaleMark) .map(scaleMark)
.reduce<{[key: number]: Mark[]}>(byPage, {}) .reduce<{ [key: number]: Mark[] }>(byPage, {})
} }
/** /**
@ -237,11 +243,10 @@ const groupMarksByPage = (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 = (obj: { [key: number]: Mark[]}, mark: Mark) => { const byPage = (obj: { [key: number]: Mark[] }, mark: Mark) => {
const key = mark.location.page; const key = mark.location.page
const curGroup = obj[key] ?? []; const curGroup = obj[key] ?? []
return { ...obj, [key]: [...curGroup, mark] return { ...obj, [key]: [...curGroup, mark] }
}
} }
export { export {
@ -252,5 +257,5 @@ export {
convertToPdfFile, convertToPdfFile,
addMarks, addMarks,
convertToPdfBlob, convertToPdfBlob,
groupMarksByPage, groupMarksByPage
} }

View File

@ -5,7 +5,10 @@ import { Meta } from '../types'
* This function returns the signature of last signer * This function returns the signature of last signer
* It will be used in the content of export signature's signedEvent * It will be used in the content of export signature's signedEvent
*/ */
const getLastSignersSig = (meta: Meta, signers: `npub1${string}`[]): string | null => { const getLastSignersSig = (
meta: Meta,
signers: `npub1${string}`[]
): string | null => {
// if there're no signers then use creator's signature // if there're no signers then use creator's signature
if (signers.length === 0) { if (signers.length === 0) {
try { try {
@ -21,13 +24,11 @@ const getLastSignersSig = (meta: Meta, signers: `npub1${string}`[]): string | nu
// get the signature of last signer // get the signature of last signer
try { try {
const lastSignatureEvent: Event = JSON.parse( const lastSignatureEvent: Event = JSON.parse(meta.docSignatures[lastSigner])
meta.docSignatures[lastSigner]
)
return lastSignatureEvent.sig return lastSignatureEvent.sig
} catch (error) { } catch (error) {
return null return null
} }
} }
export { getLastSignersSig } export { getLastSignersSig }

View File

@ -73,9 +73,9 @@ export const timeout = (ms: number = 60000) => {
* @param fileHashes * @param fileHashes
*/ */
export const getFilesWithHashes = ( export const getFilesWithHashes = (
files: { [filename: string ]: PdfFile }, files: { [filename: string]: PdfFile },
fileHashes: { [key: string]: string | null } fileHashes: { [key: string]: string | null }
) => { ) => {
return Object.entries(files).map(([filename, pdfFile]) => { return Object.entries(files).map(([filename, pdfFile]) => {
return { pdfFile, filename, hash: fileHashes[filename] } return { pdfFile, filename, hash: fileHashes[filename] }
}) })

View File

@ -36,17 +36,12 @@ const readContentOfZipEntry = async <T extends OutputType>(
const loadZip = async (data: InputFileFormat): Promise<JSZip | null> => { const loadZip = async (data: InputFileFormat): Promise<JSZip | null> => {
try { try {
return await JSZip.loadAsync(data); return await JSZip.loadAsync(data)
} catch (err: any) { } catch (err: any) {
console.log('err in loading zip file :>> ', err) console.log('err in loading zip file :>> ', err)
toast.error(err.message || 'An error occurred in loading zip file.') toast.error(err.message || 'An error occurred in loading zip file.')
return null; return null
} }
} }
export { export { readContentOfZipEntry, loadZip }
readContentOfZipEntry,
loadZip
}