Releasing new design #161
@ -101,7 +101,7 @@ export const DrawPDFFields = (props: Props) => {
|
||||
* It is re rendered and visible right away
|
||||
*
|
||||
* @param event Mouse event
|
||||
* @param page PdfPage where press happened
|
||||
* @param page PdfItem where press happened
|
||||
*/
|
||||
const onMouseDown = (event: any, page: PdfPage) => {
|
||||
// Proceed only if left click
|
||||
@ -154,7 +154,7 @@ export const DrawPDFFields = (props: Props) => {
|
||||
* After {@link onMouseDown} create an drawing element, this function gets called on every pixel moved
|
||||
* which alters the newly created drawing element, resizing it while mouse move
|
||||
* @param event Mouse event
|
||||
* @param page PdfPage where moving is happening
|
||||
* @param page PdfItem where moving is happening
|
||||
*/
|
||||
const onMouseMove = (event: any, page: PdfPage) => {
|
||||
if (mouseState.clicked && selectedTool) {
|
||||
|
0
src/components/PDFView/Mark.tsx
Normal file
0
src/components/PDFView/Mark.tsx
Normal file
28
src/components/PDFView/PdfItem.tsx
Normal file
28
src/components/PDFView/PdfItem.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { PdfFile } from '../../types/drawing.ts'
|
||||
import { MarkConfigDetails} from '../../types/mark.ts'
|
||||
import PdfPageItem from './PdfPageItem.tsx';
|
||||
|
||||
interface PdfItemProps {
|
||||
pdfFile: PdfFile
|
||||
markConfigDetails: MarkConfigDetails[]
|
||||
}
|
||||
|
||||
const PdfItem = ({ pdfFile, markConfigDetails }: PdfItemProps) => {
|
||||
const filterMarkConfigDetails = (i: number) => {
|
||||
return markConfigDetails.filter(
|
||||
(details) => details.markLocation.page === i);
|
||||
}
|
||||
return (
|
||||
pdfFile.pages.map((page, i) => {
|
||||
console.log('page: ', page);
|
||||
return (
|
||||
<PdfPageItem
|
||||
page={page}
|
||||
key={i}
|
||||
markConfigDetails={filterMarkConfigDetails(i)}
|
||||
/>
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
export default PdfItem
|
23
src/components/PDFView/PdfMarkItem.tsx
Normal file
23
src/components/PDFView/PdfMarkItem.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { MarkLocation } from '../../types/mark.ts'
|
||||
import styles from '../DrawPDFFields/style.module.scss'
|
||||
import { inPx } from '../../utils/pdf.ts'
|
||||
|
||||
interface PdfMarkItemProps {
|
||||
markLocation: MarkLocation
|
||||
}
|
||||
|
||||
const PdfMarkItem = ({ markLocation }: PdfMarkItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className={styles.drawingRectangle}
|
||||
style={{
|
||||
left: inPx(markLocation.left),
|
||||
top: inPx(markLocation.top),
|
||||
width: inPx(markLocation.width),
|
||||
height: inPx(markLocation.height)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default PdfMarkItem
|
30
src/components/PDFView/PdfPageItem.tsx
Normal file
30
src/components/PDFView/PdfPageItem.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import styles from '../DrawPDFFields/style.module.scss'
|
||||
import { PdfPage } from '../../types/drawing.ts'
|
||||
import { MarkConfigDetails, MarkLocation } from '../../types/mark.ts'
|
||||
import PdfMarkItem from './PdfMarkItem.tsx'
|
||||
import { useState } from 'react';
|
||||
interface PdfPageProps {
|
||||
page: PdfPage
|
||||
markConfigDetails: MarkConfigDetails[]
|
||||
}
|
||||
|
||||
const PdfPageItem = ({ page, markConfigDetails }: PdfPageProps) => {
|
||||
const [currentMark, setCurrentMark] = useState<MarkLocation | null>(null);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid #c4c4c4',
|
||||
marginBottom: '10px'
|
||||
}}
|
||||
className={styles.pdfImageWrapper}
|
||||
>
|
||||
<img draggable="false" style={{width: '100%'}} src={page.image} />
|
||||
{markConfigDetails.map((detail, i) => (
|
||||
<PdfMarkItem key={i} markLocation={detail.markLocation} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PdfPageItem
|
45
src/components/PDFView/index.tsx
Normal file
45
src/components/PDFView/index.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
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'
|
||||
|
||||
interface PdfViewProps {
|
||||
files: { [filename: string]: PdfFile },
|
||||
fileHashes: { [key: string]: string | null },
|
||||
markConfig: MarkConfig,
|
||||
}
|
||||
|
||||
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 { files } = props;
|
||||
return (
|
||||
<Box>
|
||||
{Object.entries(files)
|
||||
.filter(([name]) => !!getMarkConfigDetails(name))
|
||||
.map(([name, file], i) => (
|
||||
<PdfItem
|
||||
pdfFile={file}
|
||||
key={i}
|
||||
markConfigDetails={getMarkConfigDetails(name) as MarkConfigDetails[]} />
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default PdfView;
|
@ -350,9 +350,17 @@ export const CreatePage = () => {
|
||||
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: `P:${pageIndex};X:${drawnField.left};Y:${drawnField.top}`
|
||||
markLocation: {
|
||||
page: pageIndex,
|
||||
top: drawnField.top,
|
||||
left: drawnField.left,
|
||||
height: drawnField.height,
|
||||
width: drawnField.width,
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -510,6 +518,8 @@ export const CreatePage = () => {
|
||||
const viewers = users.filter((user) => user.role === UserRole.viewer)
|
||||
const markConfig = createMarkConfig(fileHashes)
|
||||
|
||||
console.log('mark config: ', markConfig)
|
||||
|
||||
const content: CreateSignatureEventContent = {
|
||||
signers: signers.map((signer) => hexToNpub(signer.pubkey)),
|
||||
viewers: viewers.map((viewer) => hexToNpub(viewer.pubkey)),
|
||||
@ -519,6 +529,8 @@ export const CreatePage = () => {
|
||||
title
|
||||
}
|
||||
|
||||
console.log('content: ', content)
|
||||
|
||||
setLoadingSpinnerDesc('Signing nostr event for create signature')
|
||||
|
||||
const createSignature = await signEventForMetaFile(
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
generateKeysFile,
|
||||
getHash,
|
||||
hexToNpub,
|
||||
isOnline,
|
||||
isOnline, loadZip,
|
||||
now,
|
||||
npubToHex,
|
||||
parseJson,
|
||||
@ -33,6 +33,10 @@ import {
|
||||
} from '../../utils'
|
||||
import { DisplayMeta } from './internal/displayMeta'
|
||||
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'
|
||||
enum SignedStatus {
|
||||
Fully_Signed,
|
||||
User_Is_Next_Signer,
|
||||
@ -58,7 +62,7 @@ export const SignPage = () => {
|
||||
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||
|
||||
const [files, setFiles] = useState<{ [filename: string]: ArrayBuffer }>({})
|
||||
const [files, setFiles] = useState<{ [filename: string]: PdfFile }>({})
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||
@ -70,6 +74,7 @@ export const SignPage = () => {
|
||||
|
||||
const [signers, setSigners] = useState<`npub1${string}`[]>([])
|
||||
const [viewers, setViewers] = useState<`npub1${string}`[]>([])
|
||||
const [markConfig, setMarkConfig] = useState<MarkConfig | null>(null)
|
||||
const [creatorFileHashes, setCreatorFileHashes] = useState<{
|
||||
[key: string]: string
|
||||
}>({})
|
||||
@ -178,6 +183,9 @@ export const SignPage = () => {
|
||||
setViewers(createSignatureContent.viewers)
|
||||
setCreatorFileHashes(createSignatureContent.fileHashes)
|
||||
setSubmittedBy(createSignatureEvent.pubkey)
|
||||
setMarkConfig(createSignatureContent.markConfig);
|
||||
|
||||
console.log('createSignatureContent', createSignatureContent)
|
||||
|
||||
setSignedBy(Object.keys(meta.docSignatures) as `npub1${string}`[])
|
||||
}
|
||||
@ -262,16 +270,13 @@ export const SignPage = () => {
|
||||
|
||||
if (!decrypted) return
|
||||
|
||||
const zip = await JSZip.loadAsync(decrypted).catch((err) => {
|
||||
console.log('err in loading zip file :>> ', err)
|
||||
toast.error(err.message || 'An error occurred in loading zip file.')
|
||||
const zip = await loadZip(decrypted)
|
||||
if (!zip) {
|
||||
setIsLoading(false)
|
||||
return null
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!zip) return
|
||||
|
||||
const files: { [filename: string]: ArrayBuffer } = {}
|
||||
const files: { [filename: string]: PdfFile } = {}
|
||||
const fileHashes: { [key: string]: string | null } = {}
|
||||
const fileNames = Object.values(zip.files).map((entry) => entry.name)
|
||||
|
||||
@ -285,7 +290,7 @@ export const SignPage = () => {
|
||||
)
|
||||
|
||||
if (arrayBuffer) {
|
||||
files[fileName] = arrayBuffer
|
||||
files[fileName] = await convertToPdfFile(arrayBuffer, fileName);
|
||||
|
||||
const hash = await getHash(arrayBuffer)
|
||||
if (hash) {
|
||||
@ -296,10 +301,17 @@ export const SignPage = () => {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('processed files: ', files);
|
||||
|
||||
setFiles(files)
|
||||
setCurrentFileHashes(fileHashes)
|
||||
}
|
||||
|
||||
const convertToPdfFile = async (arrayBuffer: ArrayBuffer, fileName: string) => {
|
||||
const file = toFile(arrayBuffer, fileName);
|
||||
return toPdfFile(file);
|
||||
}
|
||||
|
||||
const parseKeysJson = async (zip: JSZip) => {
|
||||
const keysFileContent = await readContentOfZipEntry(
|
||||
zip,
|
||||
@ -323,11 +335,7 @@ export const SignPage = () => {
|
||||
const decrypt = async (file: File) => {
|
||||
setLoadingSpinnerDesc('Decrypting file')
|
||||
|
||||
const zip = await JSZip.loadAsync(file).catch((err) => {
|
||||
console.log('err in loading zip file :>> ', err)
|
||||
toast.error(err.message || 'An error occurred in loading zip file.')
|
||||
return null
|
||||
})
|
||||
const zip = await loadZip(file);
|
||||
if (!zip) return
|
||||
|
||||
const parsedKeysJson = await parseKeysJson(zip)
|
||||
@ -398,32 +406,27 @@ export const SignPage = () => {
|
||||
|
||||
setLoadingSpinnerDesc('Parsing zip file')
|
||||
|
||||
const zip = await JSZip.loadAsync(decryptedZipFile).catch((err) => {
|
||||
console.log('err in loading zip file :>> ', err)
|
||||
toast.error(err.message || 'An error occurred in loading zip file.')
|
||||
return null
|
||||
})
|
||||
|
||||
const zip = await loadZip(decryptedZipFile)
|
||||
if (!zip) return
|
||||
|
||||
const files: { [filename: string]: ArrayBuffer } = {}
|
||||
const files: { [filename: string]: PdfFile } = {}
|
||||
const fileHashes: { [key: string]: string | null } = {}
|
||||
const fileNames = Object.values(zip.files)
|
||||
.filter((entry) => entry.name.startsWith('files/') && !entry.dir)
|
||||
.map((entry) => entry.name)
|
||||
.map((entry) => entry.replace(/^files\//, ''))
|
||||
|
||||
// generate hashes for all entries in files folder of zipArchive
|
||||
// these hashes can be used to verify the originality of files
|
||||
for (let fileName of fileNames) {
|
||||
for (const fileName of fileNames) {
|
||||
const arrayBuffer = await readContentOfZipEntry(
|
||||
zip,
|
||||
fileName,
|
||||
'arraybuffer'
|
||||
)
|
||||
|
||||
fileName = fileName.replace(/^files\//, '')
|
||||
if (arrayBuffer) {
|
||||
files[fileName] = arrayBuffer
|
||||
files[fileName] = await convertToPdfFile(arrayBuffer, fileName);
|
||||
|
||||
const hash = await getHash(arrayBuffer)
|
||||
if (hash) {
|
||||
@ -434,6 +437,8 @@ export const SignPage = () => {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('processed files: ', files);
|
||||
|
||||
setFiles(files)
|
||||
setCurrentFileHashes(fileHashes)
|
||||
|
||||
@ -916,6 +921,17 @@ export const SignPage = () => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{markConfig && (<PdfView
|
||||
files={files}
|
||||
markConfig={markConfig}
|
||||
fileHashes={currentFileHashes}/>)}
|
||||
|
||||
<div
|
||||
className={styles.fixedBottomForm}>
|
||||
<input type="text" placeholder="type here..." />
|
||||
<button>Next</button>
|
||||
</div>
|
||||
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
|
@ -34,10 +34,11 @@ import { UserComponent } from '../../../components/username'
|
||||
import { MetadataController } from '../../../controllers'
|
||||
import { npubToHex, shorten, hexToNpub, parseJson } from '../../../utils'
|
||||
import styles from '../style.module.scss'
|
||||
import { PdfFile } from '../../../types/drawing.ts'
|
||||
|
||||
type DisplayMetaProps = {
|
||||
meta: Meta
|
||||
files: { [filename: string]: ArrayBuffer }
|
||||
files: { [filename: string]: PdfFile }
|
||||
submittedBy: string
|
||||
signers: `npub1${string}`[]
|
||||
viewers: `npub1${string}`[]
|
||||
@ -143,7 +144,7 @@ export const DisplayMeta = ({
|
||||
}, [users, submittedBy])
|
||||
|
||||
const downloadFile = async (filename: string) => {
|
||||
const arrayBuffer = files[filename]
|
||||
const arrayBuffer = await files[filename].file.arrayBuffer()
|
||||
if (!arrayBuffer) return
|
||||
|
||||
const blob = new Blob([arrayBuffer])
|
||||
|
@ -47,4 +47,36 @@
|
||||
@extend .user;
|
||||
}
|
||||
}
|
||||
|
||||
.fixedBottomForm {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 50%;
|
||||
border-top: 1px solid #ccc;
|
||||
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.fixedBottomForm input[type="text"] {
|
||||
width: 80%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.fixedBottomForm button {
|
||||
background-color: #3f3d56;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
margin-left: 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,15 @@ export interface MarkConfigDetails {
|
||||
/**
|
||||
* Coordinates in format: X:10;Y:50
|
||||
*/
|
||||
markLocation: string;
|
||||
markLocation: MarkLocation;
|
||||
}
|
||||
|
||||
export interface MarkLocation {
|
||||
top: number;
|
||||
left: number;
|
||||
height: number;
|
||||
width: number;
|
||||
page: number;
|
||||
}
|
||||
|
||||
// Creator Meta Object Example
|
||||
|
@ -10,4 +10,17 @@ export interface OutputByType {
|
||||
nodebuffer: Buffer
|
||||
}
|
||||
|
||||
interface InputByType {
|
||||
base64: string;
|
||||
string: string;
|
||||
text: string;
|
||||
binarystring: string;
|
||||
array: number[];
|
||||
uint8array: Uint8Array;
|
||||
arraybuffer: ArrayBuffer;
|
||||
blob: Blob;
|
||||
stream: NodeJS.ReadableStream;
|
||||
}
|
||||
|
||||
export type OutputType = keyof OutputByType
|
||||
export type InputFileFormat = InputByType[keyof InputByType] | Promise<InputByType[keyof InputByType]>;
|
@ -20,6 +20,8 @@ const toPdfFiles = async (selectedFiles: File[]): Promise<PdfFile[]> => {
|
||||
.map(toPdfFile));
|
||||
}
|
||||
|
||||
const inPx = (coordinate: number): string => `${coordinate}px`;
|
||||
|
||||
const isPdf = (file: File) => file.type.toLowerCase().includes('pdf');
|
||||
|
||||
/**
|
||||
@ -74,5 +76,6 @@ const pdfToImages = async (data: any): Promise<PdfPage[]> => {
|
||||
export {
|
||||
toFile,
|
||||
toPdfFile,
|
||||
toPdfFiles
|
||||
toPdfFiles,
|
||||
inPx
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import JSZip from 'jszip'
|
||||
import { toast } from 'react-toastify'
|
||||
import { OutputByType, OutputType } from '../types'
|
||||
import { InputFileFormat, OutputByType, OutputType } from '../types'
|
||||
|
||||
/**
|
||||
* Read the content of a file within a zip archive.
|
||||
@ -9,7 +9,7 @@ import { OutputByType, OutputType } from '../types'
|
||||
* @param outputType The type of output to return (e.g., 'string', 'arraybuffer', 'uint8array', etc.).
|
||||
* @returns A Promise resolving to the content of the file, or null if an error occurs.
|
||||
*/
|
||||
export const readContentOfZipEntry = async <T extends OutputType>(
|
||||
const readContentOfZipEntry = async <T extends OutputType>(
|
||||
zip: JSZip,
|
||||
filePath: string,
|
||||
outputType: T
|
||||
@ -35,3 +35,20 @@ export const readContentOfZipEntry = async <T extends OutputType>(
|
||||
// Return the file content or null if an error occurred
|
||||
return fileContent
|
||||
}
|
||||
|
||||
const loadZip = async (data: InputFileFormat): Promise<JSZip | null> => {
|
||||
try {
|
||||
return await JSZip.loadAsync(data);
|
||||
} catch (err: any) {
|
||||
console.log('err in loading zip file :>> ', err)
|
||||
toast.error(err.message || 'An error occurred in loading zip file.')
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
readContentOfZipEntry,
|
||||
loadZip
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user