From 7161b874d408de55871c6bdcd9f92629db5324b7 Mon Sep 17 00:00:00 2001 From: Eugene Date: Thu, 18 Jul 2024 15:38:07 +0300 Subject: [PATCH] updates Mark type adds Pdf View components --- .../DrawPDFFields/style.module.scss | 8 +- src/components/PDFView/PdfItem.tsx | 16 +- src/components/PDFView/PdfMarkItem.tsx | 20 +- src/components/PDFView/PdfPageItem.tsx | 40 +++- src/components/PDFView/index.tsx | 32 +-- src/components/PDFView/style.module.scss | 16 ++ src/pages/create/index.tsx | 39 ++-- src/pages/sign/MarkFormField.tsx | 32 +++ src/pages/sign/index.tsx | 211 +++++++++++++----- src/pages/sign/style.module.scss | 5 +- src/types/core.ts | 4 +- src/types/mark.ts | 28 ++- 12 files changed, 318 insertions(+), 133 deletions(-) create mode 100644 src/components/PDFView/style.module.scss create mode 100644 src/pages/sign/MarkFormField.tsx diff --git a/src/components/DrawPDFFields/style.module.scss b/src/components/DrawPDFFields/style.module.scss index 4793f44..8f14fd1 100644 --- a/src/components/DrawPDFFields/style.module.scss +++ b/src/components/DrawPDFFields/style.module.scss @@ -59,14 +59,18 @@ .drawingRectangle { position: absolute; border: 1px solid #01aaad; - width: 40px; - height: 40px; + //width: 40px; + //height: 40px; z-index: 50; background-color: #01aaad4b; cursor: pointer; display: flex; justify-content: center; + &.nonEditable { + cursor: default; + } + .resizeHandle { position: absolute; right: -5px; diff --git a/src/components/PDFView/PdfItem.tsx b/src/components/PDFView/PdfItem.tsx index 7ffd000..d463ea5 100644 --- a/src/components/PDFView/PdfItem.tsx +++ b/src/components/PDFView/PdfItem.tsx @@ -1,25 +1,25 @@ import { PdfFile } from '../../types/drawing.ts' -import { MarkConfigDetails} from '../../types/mark.ts' +import { Mark, MarkConfigDetails } from '../../types/mark.ts' import PdfPageItem from './PdfPageItem.tsx'; interface PdfItemProps { pdfFile: PdfFile - markConfigDetails: MarkConfigDetails[] + marks: Mark[] + handleMarkClick: (id: number) => void } -const PdfItem = ({ pdfFile, markConfigDetails }: PdfItemProps) => { - const filterMarkConfigDetails = (i: number) => { - return markConfigDetails.filter( - (details) => details.markLocation.page === i); +const PdfItem = ({ pdfFile, marks, handleMarkClick }: PdfItemProps) => { + const filterByPage = (marks: Mark[], page: number): Mark[] => { + return marks.filter((mark) => mark.location.page === page); } return ( pdfFile.pages.map((page, i) => { - console.log('page: ', page); return ( ) })) diff --git a/src/components/PDFView/PdfMarkItem.tsx b/src/components/PDFView/PdfMarkItem.tsx index a170c45..60774e0 100644 --- a/src/components/PDFView/PdfMarkItem.tsx +++ b/src/components/PDFView/PdfMarkItem.tsx @@ -1,20 +1,24 @@ -import { MarkLocation } from '../../types/mark.ts' +import { Mark, MarkLocation } from '../../types/mark.ts' import styles from '../DrawPDFFields/style.module.scss' import { inPx } from '../../utils/pdf.ts' interface PdfMarkItemProps { - markLocation: MarkLocation + mark: Mark + handleMarkClick: (id: number) => void + isEditable: boolean } -const PdfMarkItem = ({ markLocation }: PdfMarkItemProps) => { +const PdfMarkItem = ({ mark, handleMarkClick, isEditable }: PdfMarkItemProps) => { + const handleClick = () => isEditable && handleMarkClick(mark.id); return (
) diff --git a/src/components/PDFView/PdfPageItem.tsx b/src/components/PDFView/PdfPageItem.tsx index 5176fe9..051ac44 100644 --- a/src/components/PDFView/PdfPageItem.tsx +++ b/src/components/PDFView/PdfPageItem.tsx @@ -1,27 +1,45 @@ import styles from '../DrawPDFFields/style.module.scss' import { PdfPage } from '../../types/drawing.ts' -import { MarkConfigDetails, MarkLocation } from '../../types/mark.ts' +import { Mark, MarkConfigDetails } from '../../types/mark.ts' import PdfMarkItem from './PdfMarkItem.tsx' -import { useState } from 'react'; +import { useSelector } from 'react-redux' +import { State } from '../../store/rootReducer.ts' +import { hexToNpub } from '../../utils' interface PdfPageProps { page: PdfPage - markConfigDetails: MarkConfigDetails[] + marks: Mark[] + handleMarkClick: (id: number) => void } -const PdfPageItem = ({ page, markConfigDetails }: PdfPageProps) => { - const [currentMark, setCurrentMark] = useState(null); - +const PdfPageItem = ({ page, marks, handleMarkClick }: PdfPageProps) => { + const usersPubkey = useSelector((state: State) => state.auth.usersPubkey) + const isEditable = (mark: Mark): boolean => { + if (!usersPubkey) return false; + return mark.npub === hexToNpub(usersPubkey); + } return (
- - {markConfigDetails.map((detail, i) => ( - + + { + marks.map((mark, i) => ( + ))}
) diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index 6c6113c..1f72275 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -1,41 +1,29 @@ import { PdfFile } from '../../types/drawing.ts' import { Box } from '@mui/material' import PdfItem from './PdfItem.tsx' -import { MarkConfig, MarkConfigDetails } from '../../types/mark.ts' -import { State } from '../../store/rootReducer' -import { useSelector } from 'react-redux'; -import { hexToNpub, npubToHex } from '../../utils' +import { Mark, MarkConfigDetails } from '../../types/mark.ts' interface PdfViewProps { files: { [filename: string]: PdfFile }, fileHashes: { [key: string]: string | null }, - markConfig: MarkConfig, + marks: Mark[], + handleMarkClick: (id: number) => void } -const PdfView = (props: PdfViewProps) => { - console.log('current file hashes: ', props.fileHashes) - const usersPubkey = useSelector((state: State) => state.auth.usersPubkey); - if (!usersPubkey) return; - console.log(props.markConfig[hexToNpub(usersPubkey)]); - - console.log('users pubkey: ', usersPubkey); - console.log('mark config: ', props.markConfig); - - const getMarkConfigDetails = (fileName: string): MarkConfigDetails[] | undefined => { - const fileHash = props.fileHashes[fileName]; - if (!fileHash) return; - return props.markConfig[hexToNpub(usersPubkey)][fileHash]; +const PdfView = ({ files, fileHashes, marks, handleMarkClick }: PdfViewProps) => { + const filterByFile = (marks: Mark[], fileHash: string): Mark[] => { + return marks.filter((mark) => mark.pdfFileHash === fileHash); } - const { files } = props; return ( - + {Object.entries(files) - .filter(([name]) => !!getMarkConfigDetails(name)) .map(([name, file], i) => ( + marks={filterByFile(marks, fileHashes[name] ?? "")} + handleMarkClick={handleMarkClick} + /> ))} ) diff --git a/src/components/PDFView/style.module.scss b/src/components/PDFView/style.module.scss new file mode 100644 index 0000000..2e6e519 --- /dev/null +++ b/src/components/PDFView/style.module.scss @@ -0,0 +1,16 @@ +.imageWrapper { + display: flex; + justify-content: center; + align-items: center; + width: 100%; /* Adjust as needed */ + height: 100%; /* Adjust as needed */ + overflow: hidden; /* Ensure no overflow */ + border: 1px solid #ccc; /* Optional: for visual debugging */ + background-color: #e0f7fa; /* Optional: for visual debugging */ +} + +.image { + max-width: 100%; + max-height: 100%; + object-fit: contain; /* Ensure the image fits within the container */ +} \ No newline at end of file diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 215ca83..f509fa6 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -62,6 +62,8 @@ import { import styles from './style.module.scss' import { PdfFile } from '../../types/drawing' import { DrawPDFFields } from '../../components/DrawPDFFields' +import { Mark } from '../../types/mark.ts' +import { v4 as uuidv4 } from 'uuid'; export const CreatePage = () => { const navigate = useNavigate() @@ -339,34 +341,29 @@ export const CreatePage = () => { return fileHashes } - const createMarkConfig = (fileHashes: { [key: string]: string }) => { - const markConfig: any = {} - - drawnPdfs.forEach(drawnPdf => { - const fileHash = fileHashes[drawnPdf.file.name] - - drawnPdf.pages.forEach((page, pageIndex) => { - page.drawnFields.forEach(drawnField => { - if (!markConfig[drawnField.counterpart]) markConfig[drawnField.counterpart] = {} - if (!markConfig[drawnField.counterpart][fileHash]) markConfig[drawnField.counterpart][fileHash] = [] - - console.log('drawn field: ', drawnField); - - markConfig[drawnField.counterpart][fileHash].push({ - markType: drawnField.type, - markLocation: { - page: pageIndex, + const createMarkConfig = (fileHashes: { [key: string]: string }) : Mark[] => { + return drawnPdfs.flatMap((drawnPdf) => { + const fileHash = fileHashes[drawnPdf.file.name]; + return drawnPdf.pages.flatMap((page, index) => { + return page.drawnFields.map((drawnField) => { + return { + type: drawnField.type, + location: { + page: index, top: drawnField.top, left: drawnField.left, height: drawnField.height, width: drawnField.width, - } - }) + }, + npub: drawnField.counterpart, + pdfFileHash: fileHash + } }) }) }) - - return markConfig + .map((mark, index) => { + return {...mark, id: index } + }); } // Handle errors during zip file generation diff --git a/src/pages/sign/MarkFormField.tsx b/src/pages/sign/MarkFormField.tsx new file mode 100644 index 0000000..2e63f38 --- /dev/null +++ b/src/pages/sign/MarkFormField.tsx @@ -0,0 +1,32 @@ +import { CurrentUserMark, Mark } from '../../types/mark.ts' +import styles from './style.module.scss' +import { Box, Button, TextField } from '@mui/material' + +interface MarkFormFieldProps { + handleSubmit: (event: any) => void + handleChange: (event: any) => void + currentMark: CurrentUserMark + currentMarkValue: string +} + +const MarkFormField = (props: MarkFormFieldProps) => { + const { handleSubmit, handleChange, currentMark, currentMarkValue } = props; + const getSubmitButton = () => currentMark.isLast ? 'Complete' : 'Next'; + return ( +
+ + + + +
+ ) +} + +export default MarkFormField; \ No newline at end of file diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index c7f2866..e8caa7c 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -1,8 +1,8 @@ -import { Box, Button, Typography } from '@mui/material' +import { Box, Button, FormControl, InputLabel, TextField, Typography } from '@mui/material' import axios from 'axios' import saveAs from 'file-saver' import JSZip from 'jszip' -import _ from 'lodash' +import _, { set } from 'lodash' import { MuiFileInput } from 'mui-file-input' import { Event, verifyEvent } from 'nostr-tools' import { useEffect, useState } from 'react' @@ -36,7 +36,8 @@ import styles from './style.module.scss' import { PdfFile } from '../../types/drawing.ts' import { toFile, toPdfFile } from '../../utils/pdf.ts' import PdfView from '../../components/PDFView' -import { MarkConfig } from '../../types/mark.ts' +import { CurrentUserMark, Mark, MarkConfig, MarkConfigDetails, User } from '../../types/mark.ts' +import MarkFormField from './MarkFormField.tsx' enum SignedStatus { Fully_Signed, User_Is_Next_Signer, @@ -74,7 +75,7 @@ export const SignPage = () => { const [signers, setSigners] = useState<`npub1${string}`[]>([]) const [viewers, setViewers] = useState<`npub1${string}`[]>([]) - const [markConfig, setMarkConfig] = useState(null) + const [marks, setMarks] = useState([]) const [creatorFileHashes, setCreatorFileHashes] = useState<{ [key: string]: string }>({}) @@ -93,6 +94,11 @@ export const SignPage = () => { const [authUrl, setAuthUrl] = useState() const nostrController = NostrController.getInstance() + const [currentUserMark, setCurrentUserMark] = useState(null); + const [currentUserMarks, setCurrentUserMarks] = useState([]); + const [currentMarkValue, setCurrentMarkValue] = useState(''); + const [isMarksCompleted, setIsMarksCompleted] = useState(false); + const [isLastUserMark, setIsLastUserMark] = useState(false); useEffect(() => { if (signers.length > 0) { @@ -183,9 +189,25 @@ export const SignPage = () => { setViewers(createSignatureContent.viewers) setCreatorFileHashes(createSignatureContent.fileHashes) setSubmittedBy(createSignatureEvent.pubkey) - setMarkConfig(createSignatureContent.markConfig); + setMarks(createSignatureContent.markConfig); - console.log('createSignatureContent', createSignatureContent) + console.log('createSignatureContent markConfig', createSignatureContent); + if (usersPubkey) { + console.log('this runs behind users pubkey'); + const curMarks = getCurrentUserMarks(createSignatureContent.markConfig, usersPubkey) + if (curMarks.length === 0) { + setIsMarksCompleted(true) + } else { + const nextMark = findNextIncompleteMark(curMarks) + if (!nextMark) { + setIsMarksCompleted(true) + } else { + setCurrentUserMark(nextMark) + setIsMarksCompleted(false) + } + setCurrentUserMarks(curMarks) + } + } setSignedBy(Object.keys(meta.docSignatures) as `npub1${string}`[]) } @@ -514,6 +536,57 @@ export const SignPage = () => { ) } + 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 = { + ...currentUserMark.mark, + value: currentMarkValue + }; + + const indexToUpdate = marks.findIndex(mark => mark.id === curMark.id); + + const updatedMarks: Mark[] = [ + ...marks.slice(0, indexToUpdate), + curMark, + ...marks.slice(indexToUpdate + 1) + ]; + + setMarks(updatedMarks) + setCurrentMarkValue("") + + const updatedCurUserMarks = getCurrentUserMarks(updatedMarks, usersPubkey!) + console.log('updatedCurUserMarks: ', updatedCurUserMarks) + setCurrentUserMarks(updatedCurUserMarks) + const nextMark = findNextIncompleteMark(updatedCurUserMarks) + console.log('next mark: ', nextMark) + if (!nextMark) { + setCurrentUserMark(null) + setIsMarksCompleted(true) + } else { + setCurrentUserMark(nextMark) + setIsMarksCompleted(false) + } + } + + const findNextIncompleteMark = (usersMarks: CurrentUserMark[]): CurrentUserMark | undefined => { + return usersMarks.find(mark => !mark.isCompleted); + } + // Update the meta signatures const updateMetaSignatures = (meta: Meta, signedEvent: SignedEvent): Meta => { const metaCopy = _.cloneDeep(meta) @@ -735,6 +808,25 @@ export const SignPage = () => { navigate(appPublicRoutes.verify) } + const getCurrentUserMarks = (marks: Mark[], pubkey: string): CurrentUserMark[] => { + console.log('marks: ', marks); + + const filteredMarks = marks + .filter(mark => mark.npub === hexToNpub(pubkey)) + const currentMarks = filteredMarks + .map((mark, index, arr) => { + return { + mark, + isLast: isLast(index, arr), + isCompleted: !!mark.value + } + }) + console.log('current marks: ', currentMarks) + return currentMarks; + } + + const isLast = (index: number, arr: any[]) => (index === (arr.length -1)) + const handleExportSigit = async () => { if (Object.entries(files).length === 0 || !meta) return @@ -852,9 +944,12 @@ export const SignPage = () => { ) } + if (isLoading) { + return + } + return ( <> - {isLoading && } {displayInput && ( <> @@ -881,56 +976,68 @@ export const SignPage = () => { )} - {submittedBy && Object.entries(files).length > 0 && meta && ( - <> - + {/*{submittedBy && Object.entries(files).length > 0 && meta && (*/} + {/* <>*/} + {/* */} - {signedStatus === SignedStatus.Fully_Signed && ( - - - - )} + {/* {signedStatus === SignedStatus.Fully_Signed && (*/} + {/* */} + {/* */} + {/* */} + {/* )}*/} - {signedStatus === SignedStatus.User_Is_Next_Signer && ( - - - - )} + {/* {signedStatus === SignedStatus.User_Is_Next_Signer && (*/} + {/* */} + {/* */} + {/* */} + {/* )}*/} - {isSignerOrCreator && ( - - - - )} - - )} - {markConfig && (*/} + {/* */} + {/* */} + {/* )}*/} + {/* */} + {/*)}*/} + { + !isMarksCompleted && marks.length > 0 && ( + )} + marks={marks} + fileHashes={currentFileHashes} + handleMarkClick={handleMarkClick} + /> + ) + } + + { + !isMarksCompleted && currentUserMark !== null && + } + + { isMarksCompleted &&

Ready to Sign!

} -
- - -
diff --git a/src/pages/sign/style.module.scss b/src/pages/sign/style.module.scss index d3b1086..aa3377b 100644 --- a/src/pages/sign/style.module.scss +++ b/src/pages/sign/style.module.scss @@ -51,7 +51,10 @@ .fixedBottomForm { position: fixed; bottom: 0; - width: 50%; + left: 50%; + transform: translateX(-50%); + width: 100%; + max-width: 500px; border-top: 1px solid #ccc; box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1); padding: 10px 20px; diff --git a/src/types/core.ts b/src/types/core.ts index f4fb8b4..99a8cfa 100644 --- a/src/types/core.ts +++ b/src/types/core.ts @@ -1,4 +1,4 @@ -import { MarkConfig } from "./mark" +import { Mark } from './mark' import { Keys } from '../store/auth/types' export enum UserRole { @@ -23,7 +23,7 @@ export interface CreateSignatureEventContent { signers: `npub1${string}`[] viewers: `npub1${string}`[] fileHashes: { [key: string]: string } - markConfig: MarkConfig + markConfig: Mark[] title: string zipUrl: string } diff --git a/src/types/mark.ts b/src/types/mark.ts index 57e1512..3bc38f8 100644 --- a/src/types/mark.ts +++ b/src/types/mark.ts @@ -1,10 +1,25 @@ import { MarkType } from "./drawing"; +// export interface Mark { +// /** +// * @key png (pdf page) file hash +// */ +// [key: string]: MarkConfigDetails[] +// } + +export interface CurrentUserMark { + mark: Mark + isLast: boolean + isCompleted: boolean +} + export interface Mark { - /** - * @key png (pdf page) file hash - */ - [key: string]: MarkConfigDetails[] + id: number; + npub: string; + pdfFileHash: string; + type: MarkType; + location: MarkLocation; + value?: string; } export interface MarkConfig { @@ -33,11 +48,12 @@ export interface MarkValue { } export interface MarkConfigDetails { - markType: MarkType; + type: MarkType; /** * Coordinates in format: X:10;Y:50 */ - markLocation: MarkLocation; + location: MarkLocation; + value?: MarkValue } export interface MarkLocation {