From 47120316152a7a3157f7cd089cb2184053ae25ec Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 27 Aug 2024 15:24:19 +0200 Subject: [PATCH] fix(pdf): scaling on resize, add avatars to counterpart select --- package-lock.json | 23 +++- package.json | 3 +- src/components/DrawPDFFields/index.tsx | 102 ++++++++++++++---- .../DrawPDFFields/style.module.scss | 2 +- src/components/PDFView/PdfMarkItem.tsx | 16 +-- src/components/PDFView/PdfPageItem.tsx | 15 +-- src/hooks/useScale.tsx | 52 +++++++++ src/pages/verify/index.tsx | 12 ++- src/types/drawing.ts | 2 +- src/utils/pdf.ts | 2 +- 10 files changed, 185 insertions(+), 44 deletions(-) create mode 100644 src/hooks/useScale.tsx diff --git a/package-lock.json b/package-lock.json index ef46577..479088b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,14 @@ { - "name": "web", + "name": "sigit", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "web", + "name": "sigit", "version": "0.0.0", "hasInstallScript": true, + "license": "AGPL-3.0-or-later ", "dependencies": { "@emotion/react": "11.11.4", "@emotion/styled": "11.11.0", @@ -40,6 +41,7 @@ "react-dropzone": "^14.2.3", "react-redux": "9.1.0", "react-router-dom": "6.22.1", + "react-singleton-hook": "^4.0.1", "react-toastify": "10.0.4", "redux": "5.0.1", "tseep": "1.2.1" @@ -5832,6 +5834,23 @@ "react-dom": ">=16.8" } }, + "node_modules/react-singleton-hook": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-singleton-hook/-/react-singleton-hook-4.0.1.tgz", + "integrity": "sha512-fWuk8VxcZPChrkQasDLM8pgd/7kyi+Cr/5FfCiD99FicjEru+JmtEZNnN4lJ8Z7KbqAST5CYPlpz6lmNsZFGNw==", + "license": "MIT", + "peerDependencies": { + "react": "18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-toastify": { "version": "10.0.4", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.4.tgz", diff --git a/package.json b/package.json index c835018..a2f074d 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "react-dropzone": "^14.2.3", "react-redux": "9.1.0", "react-router-dom": "6.22.1", + "react-singleton-hook": "^4.0.1", "react-toastify": "10.0.4", "redux": "5.0.1", "tseep": "1.2.1" @@ -82,4 +83,4 @@ ], "*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}": "npm run formatter:staged" } -} \ No newline at end of file +} diff --git a/src/components/DrawPDFFields/index.tsx b/src/components/DrawPDFFields/index.tsx index 35de444..b808c40 100644 --- a/src/components/DrawPDFFields/index.tsx +++ b/src/components/DrawPDFFields/index.tsx @@ -4,6 +4,8 @@ import { CircularProgress, FormControl, InputLabel, + ListItemIcon, + ListItemText, MenuItem, Select } from '@mui/material' @@ -13,11 +15,13 @@ import * as PDFJS from 'pdfjs-dist' import { ProfileMetadata, User, UserRole } from '../../types' import { MouseState, PdfPage, DrawnField, DrawTool } from '../../types/drawing' import { truncate } from 'lodash' -import { settleAllFullfilfedPromises, hexToNpub } from '../../utils' +import { settleAllFullfilfedPromises, hexToNpub, npubToHex } from '../../utils' import { getSigitFile, SigitFile } from '../../utils/file' import { FileDivider } from '../FileDivider' import { ExtensionFileBox } from '../ExtensionFileBox' import { inPx } from '../../utils/pdf' +import { useScale } from '../../hooks/useScale' +import { AvatarIconButton } from '../UserAvatarIconButton' PDFJS.GlobalWorkerOptions.workerSrc = new URL( 'pdfjs-dist/build/pdf.worker.min.mjs', @@ -34,6 +38,7 @@ interface Props { export const DrawPDFFields = (props: Props) => { const { selectedFiles, selectedTool, onDrawFieldsChange, users } = props + const { to, from } = useScale() const [sigitFiles, setSigitFiles] = useState([]) const [parsingPdf, setIsParsing] = useState(false) @@ -106,8 +111,8 @@ export const DrawPDFFields = (props: Props) => { const { mouseX, mouseY } = getMouseCoordinates(event) const newField: DrawnField = { - left: mouseX / page.scale, - top: mouseY / page.scale, + left: to(page.width, mouseX), + top: to(page.width, mouseY), width: 0, height: 0, counterpart: '', @@ -161,8 +166,8 @@ export const DrawPDFFields = (props: Props) => { const { mouseX, mouseY } = getMouseCoordinates(event) - const width = mouseX / page.scale - lastDrawnField.left - const height = mouseY / page.scale - lastDrawnField.top + const width = to(page.width, mouseX) - lastDrawnField.left + const height = to(page.width, mouseY) - lastDrawnField.top lastDrawnField.width = width lastDrawnField.height = height @@ -211,7 +216,7 @@ export const DrawPDFFields = (props: Props) => { const onDrawnFieldMouseMove = ( event: React.MouseEvent, drawnField: DrawnField, - scale: number + pageWidth: number ) => { if (mouseState.dragging) { const { mouseX, mouseY, rect } = getMouseCoordinates( @@ -221,11 +226,11 @@ export const DrawPDFFields = (props: Props) => { const coordsOffset = mouseState.coordsInWrapper if (coordsOffset) { - let left = (mouseX - coordsOffset.mouseX) / scale - let top = (mouseY - coordsOffset.mouseY) / scale + let left = to(pageWidth, mouseX - coordsOffset.mouseX) + let top = to(pageWidth, mouseY - coordsOffset.mouseY) - const rightLimit = rect.width / scale - drawnField.width - 3 - const bottomLimit = rect.height / scale - drawnField.height - 3 + const rightLimit = to(pageWidth, rect.width) - drawnField.width - 3 + const bottomLimit = to(pageWidth, rect.height) - drawnField.height - 3 if (left < 0) left = 0 if (top < 0) top = 0 @@ -266,7 +271,7 @@ export const DrawPDFFields = (props: Props) => { const onResizeHandleMouseMove = ( event: React.MouseEvent, drawnField: DrawnField, - scale: number + pageWidth: number ) => { if (mouseState.resizing) { const { mouseX, mouseY } = getMouseCoordinates( @@ -277,8 +282,8 @@ export const DrawPDFFields = (props: Props) => { event.currentTarget.parentElement?.parentElement ) - const width = mouseX / scale - drawnField.left - const height = mouseY / scale - drawnField.top + const width = to(pageWidth, mouseX) - drawnField.left + const height = to(pageWidth, mouseY) - drawnField.top drawnField.width = width drawnField.height = height @@ -375,21 +380,21 @@ export const DrawPDFFields = (props: Props) => { key={drawnFieldIndex} onMouseDown={onDrawnFieldMouseDown} onMouseMove={(event) => { - onDrawnFieldMouseMove(event, drawnField, page.scale) + onDrawnFieldMouseMove(event, drawnField, page.width) }} className={styles.drawingRectangle} style={{ - left: inPx(drawnField.left * page.scale), - top: inPx(drawnField.top * page.scale), - width: inPx(drawnField.width * page.scale), - height: inPx(drawnField.height * page.scale), + left: inPx(from(page.width, drawnField.left)), + top: inPx(from(page.width, drawnField.top)), + width: inPx(from(page.width, drawnField.width)), + height: inPx(from(page.width, drawnField.height)), pointerEvents: mouseState.clicked ? 'none' : 'all' }} > { - onResizeHandleMouseMove(event, drawnField, page.scale) + onResizeHandleMouseMove(event, drawnField, page.width) }} className={styles.resizeHandle} > @@ -423,6 +428,9 @@ export const DrawPDFFields = (props: Props) => { sx={{ background: 'white' }} + renderValue={(value) => + renderCounterpartValue(drawnField, value) + } > {users .filter((u) => u.role === UserRole.signer) @@ -451,7 +459,22 @@ export const DrawPDFFields = (props: Props) => { key={index} value={hexToNpub(user.pubkey)} > - {displayValue} + + img': { + width: '30px', + height: '30px' + } + }} + /> + + {displayValue} ) })} @@ -468,6 +491,45 @@ export const DrawPDFFields = (props: Props) => { ) } + const renderCounterpartValue = (drawnField: DrawnField, value: string) => { + const user = users.find((u) => u.pubkey === npubToHex(value)) + if (user) { + let displayValue = truncate(value, { + length: 16 + }) + + const metadata = props.metadata[value] + + if (metadata) { + displayValue = truncate( + metadata.name || metadata.display_name || metadata.username || value, + { + length: 16 + } + ) + } + return ( + <> + img': { + width: '21px', + height: '21px' + } + }} + /> + {displayValue} + + ) + } + + return value + } + if (parsingPdf) { return ( diff --git a/src/components/DrawPDFFields/style.module.scss b/src/components/DrawPDFFields/style.module.scss index b3150b3..62fa688 100644 --- a/src/components/DrawPDFFields/style.module.scss +++ b/src/components/DrawPDFFields/style.module.scss @@ -73,7 +73,7 @@ justify-content: center; align-items: center; bottom: -60px; - min-width: 170px; + min-width: 193px; min-height: 30px; padding: 5px 0; } diff --git a/src/components/PDFView/PdfMarkItem.tsx b/src/components/PDFView/PdfMarkItem.tsx index cc9c9b2..d5a7c78 100644 --- a/src/components/PDFView/PdfMarkItem.tsx +++ b/src/components/PDFView/PdfMarkItem.tsx @@ -1,13 +1,14 @@ import { CurrentUserMark } from '../../types/mark.ts' import styles from '../DrawPDFFields/style.module.scss' import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf.ts' +import { useScale } from '../../hooks/useScale.tsx' interface PdfMarkItemProps { userMark: CurrentUserMark handleMarkClick: (id: number) => void selectedMarkValue: string selectedMark: CurrentUserMark | null - scale: number + pageWidth: number } /** @@ -18,24 +19,25 @@ const PdfMarkItem = ({ handleMarkClick, selectedMarkValue, userMark, - scale + pageWidth }: PdfMarkItemProps) => { const { location } = userMark.mark const handleClick = () => handleMarkClick(userMark.mark.id) const isEdited = () => selectedMark?.mark.id === userMark.mark.id const getMarkValue = () => isEdited() ? selectedMarkValue : userMark.currentValue + const { from } = useScale() return (
{getMarkValue()} diff --git a/src/components/PDFView/PdfPageItem.tsx b/src/components/PDFView/PdfPageItem.tsx index f5f310d..518f06d 100644 --- a/src/components/PDFView/PdfPageItem.tsx +++ b/src/components/PDFView/PdfPageItem.tsx @@ -5,6 +5,7 @@ import PdfMarkItem from './PdfMarkItem.tsx' import { useEffect, useRef } from 'react' import pdfViewStyles from './style.module.scss' import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf.ts' +import { useScale } from '../../hooks/useScale.tsx' interface PdfPageProps { currentUserMarks: CurrentUserMark[] handleMarkClick: (id: number) => void @@ -33,6 +34,8 @@ const PdfPageItem = ({ } }, [selectedMark]) const markRefs = useRef<(HTMLDivElement | null)[]>([]) + const { from } = useScale() + return (
@@ -44,7 +47,7 @@ const PdfPageItem = ({ selectedMarkValue={selectedMarkValue} userMark={m} selectedMark={selectedMark} - scale={page.scale} + pageWidth={page.width} />
))} @@ -54,12 +57,12 @@ const PdfPageItem = ({ key={i} className={pdfViewStyles.otherUserMarksDisplay} style={{ - left: inPx(m.location.left * page.scale), - top: inPx(m.location.top * page.scale), - width: inPx(m.location.width * page.scale), - height: inPx(m.location.height * page.scale), + left: inPx(from(page.width, m.location.left)), + top: inPx(from(page.width, m.location.top)), + width: inPx(from(page.width, m.location.width)), + height: inPx(from(page.width, m.location.height)), fontFamily: FONT_TYPE, - fontSize: inPx(FONT_SIZE * page.scale) + fontSize: inPx(from(page.width, FONT_SIZE)) }} > {m.value} diff --git a/src/hooks/useScale.tsx b/src/hooks/useScale.tsx new file mode 100644 index 0000000..89eb8a5 --- /dev/null +++ b/src/hooks/useScale.tsx @@ -0,0 +1,52 @@ +import { useEffect, useState } from 'react' +import { singletonHook } from 'react-singleton-hook' + +const noScaleInit = { + to: (_: number, v: number) => v, + from: (_: number, v: number) => v +} + +const useScaleImpl = () => { + const [width, setWidth] = useState( + document.querySelector('#content-preview > *')?.clientWidth || 1 + ) + + // Get the scale based on the original width + const scale = (originalWidth: number) => { + return width / originalWidth + } + + // Get the original pixel value + const to = (originalWidth: number, value: number) => { + return value / scale(originalWidth) + } + + // Get the scaled pixel value + const from = (originalWidth: number, value: number) => { + return value * scale(originalWidth) + } + + useEffect(() => { + const resize = () => { + // Fetch the first container element we find + const element = document.querySelector('#content-preview > *') + + // Set the width state + if (element) { + setWidth(element.clientWidth) + } + } + resize() + + window.addEventListener('resize', resize) + return () => { + window.removeEventListener('resize', resize) + } + }, []) + + return { to, from } +} + +export const useScale = singletonHook(noScaleInit, useScaleImpl, { + unmountIfNoConsumers: true +}) diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index 6ff679a..8619806 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -52,6 +52,7 @@ import React from 'react' import { convertToSigitFile, SigitFile } from '../../utils/file.ts' import { FileDivider } from '../../components/FileDivider.tsx' import { ExtensionFileBox } from '../../components/ExtensionFileBox.tsx' +import { useScale } from '../../hooks/useScale.tsx' interface PdfViewProps { files: CurrentUserFile[] @@ -67,6 +68,7 @@ const SlimPdfView = ({ parsedSignatureEvents }: PdfViewProps) => { const pdfRefs = useRef<(HTMLDivElement | null)[]>([]) + const { from } = useScale() useEffect(() => { if (currentFile !== null && !!pdfRefs.current[currentFile.id]) { pdfRefs.current[currentFile.id]?.scrollIntoView({ @@ -110,12 +112,12 @@ const SlimPdfView = ({ className={`file-mark ${styles.mark}`} key={m.id} style={{ - left: inPx(m.location.left * page.scale), - top: inPx(m.location.top * page.scale), - width: inPx(m.location.width * page.scale), - height: inPx(m.location.height * page.scale), + left: inPx(from(page.width, m.location.left)), + top: inPx(from(page.width, m.location.top)), + width: inPx(from(page.width, m.location.width)), + height: inPx(from(page.width, m.location.height)), fontFamily: FONT_TYPE, - fontSize: inPx(FONT_SIZE * page.scale) + fontSize: inPx(from(page.width, FONT_SIZE)) }} > {m.value} diff --git a/src/types/drawing.ts b/src/types/drawing.ts index d8c5cc3..677b68c 100644 --- a/src/types/drawing.ts +++ b/src/types/drawing.ts @@ -12,7 +12,7 @@ export interface MouseState { export interface PdfPage { image: string - scale: number + width: number drawnFields: DrawnField[] } diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts index 8716ef5..b1e9847 100644 --- a/src/utils/pdf.ts +++ b/src/utils/pdf.ts @@ -85,7 +85,7 @@ export const pdfToImages = async ( await page.render({ canvasContext: context!, viewport: viewport }).promise pages.push({ image: canvas.toDataURL(), - scale, + width: originalViewport.width, drawnFields: [] }) }