new release #190
23
package-lock.json
generated
23
package-lock.json
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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<SigitFile[]>([])
|
||||
const [parsingPdf, setIsParsing] = useState<boolean>(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<HTMLDivElement>,
|
||||
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<HTMLSpanElement>,
|
||||
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'
|
||||
}}
|
||||
>
|
||||
<span
|
||||
onMouseDown={onResizeHandleMouseDown}
|
||||
onMouseMove={(event) => {
|
||||
onResizeHandleMouseMove(event, drawnField, page.scale)
|
||||
onResizeHandleMouseMove(event, drawnField, page.width)
|
||||
}}
|
||||
className={styles.resizeHandle}
|
||||
></span>
|
||||
@ -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}
|
||||
<ListItemIcon>
|
||||
<AvatarIconButton
|
||||
src={metadata?.picture}
|
||||
hexKey={user.pubkey}
|
||||
aria-label={`account of user ${displayValue}`}
|
||||
color="inherit"
|
||||
sx={{
|
||||
padding: 0,
|
||||
'> img': {
|
||||
width: '30px',
|
||||
height: '30px'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText>{displayValue}</ListItemText>
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
@ -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 (
|
||||
<>
|
||||
<AvatarIconButton
|
||||
src={props.metadata[drawnField.counterpart]?.picture}
|
||||
hexKey={npubToHex(drawnField.counterpart) || undefined}
|
||||
sx={{
|
||||
padding: 0,
|
||||
marginRight: '6px',
|
||||
'> img': {
|
||||
width: '21px',
|
||||
height: '21px'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{displayValue}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
if (parsingPdf) {
|
||||
return (
|
||||
<Box sx={{ width: '100%', textAlign: 'center' }}>
|
||||
|
@ -73,7 +73,7 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
bottom: -60px;
|
||||
min-width: 170px;
|
||||
min-width: 193px;
|
||||
min-height: 30px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
@ -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 (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className={`file-mark ${styles.drawingRectangle} ${isEdited() && styles.edited}`}
|
||||
style={{
|
||||
left: inPx(location.left * scale),
|
||||
top: inPx(location.top * scale),
|
||||
width: inPx(location.width * scale),
|
||||
height: inPx(location.height * scale),
|
||||
left: inPx(from(pageWidth, location.left)),
|
||||
top: inPx(from(pageWidth, location.top)),
|
||||
width: inPx(from(pageWidth, location.width)),
|
||||
height: inPx(from(pageWidth, location.height)),
|
||||
fontFamily: FONT_TYPE,
|
||||
fontSize: inPx(FONT_SIZE * scale)
|
||||
fontSize: inPx(from(pageWidth, FONT_SIZE))
|
||||
}}
|
||||
>
|
||||
{getMarkValue()}
|
||||
|
@ -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 (
|
||||
<div className={`image-wrapper ${styles.pdfImageWrapper}`}>
|
||||
<img draggable="false" src={page.image} />
|
||||
@ -44,7 +47,7 @@ const PdfPageItem = ({
|
||||
selectedMarkValue={selectedMarkValue}
|
||||
userMark={m}
|
||||
selectedMark={selectedMark}
|
||||
scale={page.scale}
|
||||
pageWidth={page.width}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
@ -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}
|
||||
|
52
src/hooks/useScale.tsx
Normal file
52
src/hooks/useScale.tsx
Normal file
@ -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
|
||||
})
|
@ -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}
|
||||
|
@ -12,7 +12,7 @@ export interface MouseState {
|
||||
|
||||
export interface PdfPage {
|
||||
image: string
|
||||
scale: number
|
||||
width: number
|
||||
drawnFields: DrawnField[]
|
||||
}
|
||||
|
||||
|
@ -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: []
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user