refactor(create): optimize the events and fix responsiveness of the drawn fields with proper re-renders
This commit is contained in:
parent
4cde0796a2
commit
abea8dcd15
@ -8,19 +8,20 @@ import {
|
||||
Select
|
||||
} from '@mui/material'
|
||||
import styles from './style.module.scss'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { ProfileMetadata, User, UserRole, KeyboardCode } from '../../types'
|
||||
import { MouseState, PdfPage, DrawnField, DrawTool } from '../../types/drawing'
|
||||
import { MouseState, DrawnField, DrawTool } from '../../types/drawing'
|
||||
import { hexToNpub, npubToHex, getProfileUsername } from '../../utils'
|
||||
import { SigitFile } from '../../utils/file'
|
||||
import { getToolboxLabelByMarkType } from '../../utils/mark'
|
||||
import { FileDivider } from '../FileDivider'
|
||||
import { ExtensionFileBox } from '../ExtensionFileBox'
|
||||
import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf'
|
||||
import { useScale } from '../../hooks/useScale'
|
||||
import { AvatarIconButton } from '../UserAvatarIconButton'
|
||||
import { UserAvatar } from '../UserAvatar'
|
||||
import _ from 'lodash'
|
||||
import { Updater } from 'use-immer'
|
||||
import { FileItem } from './internal/FileItem'
|
||||
import { FileDivider } from '../FileDivider'
|
||||
import { Counterpart } from './internal/Counterpart'
|
||||
|
||||
const MINIMUM_RECT_SIZE = {
|
||||
width: 21,
|
||||
@ -36,16 +37,25 @@ interface HideSignersForDrawnField {
|
||||
[key: number]: boolean
|
||||
}
|
||||
|
||||
interface Props {
|
||||
type PageIndexer = [file: number, page: number]
|
||||
type FieldIndexer = [...PageIndexer, field: number]
|
||||
|
||||
interface DrawPdfFieldsProps {
|
||||
users: User[]
|
||||
metadata: { [key: string]: ProfileMetadata }
|
||||
sigitFiles: SigitFile[]
|
||||
setSigitFiles: React.Dispatch<React.SetStateAction<SigitFile[]>>
|
||||
updateSigitFiles: Updater<SigitFile[]>
|
||||
selectedTool?: DrawTool
|
||||
}
|
||||
|
||||
export const DrawPDFFields = (props: Props) => {
|
||||
const { selectedTool, sigitFiles, setSigitFiles, users } = props
|
||||
export const DrawPDFFields = ({
|
||||
selectedTool,
|
||||
metadata,
|
||||
sigitFiles,
|
||||
updateSigitFiles,
|
||||
users
|
||||
}: DrawPdfFieldsProps) => {
|
||||
const { to, from } = useScale()
|
||||
|
||||
const signers = users.filter((u) => u.role === UserRole.signer)
|
||||
const defaultSignerNpub = signers.length ? hexToNpub(signers[0].pubkey) : ''
|
||||
@ -58,158 +68,124 @@ export const DrawPDFFields = (props: Props) => {
|
||||
* @param pubkeys
|
||||
* @returns available pubkey or empty string
|
||||
*/
|
||||
const getAvailableSigner = (...pubkeys: string[]) => {
|
||||
const availableSigner: string | undefined = pubkeys.find((pubkey) =>
|
||||
signers.some((s) => s.pubkey === npubToHex(pubkey))
|
||||
)
|
||||
return availableSigner || ''
|
||||
}
|
||||
const getAvailableSigner = useCallback(
|
||||
(...pubkeys: string[]) => {
|
||||
const availableSigner: string | undefined = pubkeys.find((pubkey) =>
|
||||
signers.some((s) => s.pubkey === npubToHex(pubkey))
|
||||
)
|
||||
return availableSigner || ''
|
||||
},
|
||||
[signers]
|
||||
)
|
||||
|
||||
const { to, from } = useScale()
|
||||
|
||||
const [mouseState, setMouseState] = useState<MouseState>({
|
||||
clicked: false
|
||||
})
|
||||
|
||||
const [activeDrawnField, setActiveDrawnField] = useState<{
|
||||
fileIndex: number
|
||||
pageIndex: number
|
||||
drawnFieldIndex: number
|
||||
}>()
|
||||
const [mouseState, setMouseState] = useState<MouseState>({})
|
||||
const [indexer, setIndexer] = useState<PageIndexer | FieldIndexer>()
|
||||
const [field, setField] = useState<
|
||||
DrawnField & {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
>()
|
||||
const [lastIndexer, setLastIndexer] = useState<FieldIndexer>()
|
||||
const isActiveDrawnField = (
|
||||
fileIndex: number,
|
||||
pageIndex: number,
|
||||
drawnFieldIndex: number
|
||||
) =>
|
||||
activeDrawnField?.fileIndex === fileIndex &&
|
||||
activeDrawnField?.pageIndex === pageIndex &&
|
||||
activeDrawnField?.drawnFieldIndex === drawnFieldIndex
|
||||
lastIndexer &&
|
||||
lastIndexer[0] === fileIndex &&
|
||||
lastIndexer[1] === pageIndex &&
|
||||
lastIndexer[2] === drawnFieldIndex
|
||||
|
||||
/**
|
||||
* Drawing events
|
||||
* Gets the pointer coordinates relative to a element in the `event` param
|
||||
* @param event PointerEvent
|
||||
* @param customTarget coordinates relative to this element, if not provided
|
||||
* event.target will be used
|
||||
*/
|
||||
useEffect(() => {
|
||||
window.addEventListener('pointerup', handlePointerUp)
|
||||
window.addEventListener('pointercancel', handlePointerUp)
|
||||
const getPointerCoordinates = (
|
||||
event: React.PointerEvent,
|
||||
customTarget?: HTMLElement | null
|
||||
) => {
|
||||
const target = customTarget ? customTarget : event.currentTarget
|
||||
const rect = target.getBoundingClientRect()
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('pointerup', handlePointerUp)
|
||||
window.removeEventListener('pointercancel', handlePointerUp)
|
||||
// Clamp X Y within the target
|
||||
const x = Math.max(0, Math.min(event.clientX, rect.right) - rect.left) //x position within the element.
|
||||
const y = Math.max(0, Math.min(event.clientY, rect.bottom) - rect.top) //y position within the element.
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
rect
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const refreshPdfFiles = () => {
|
||||
setSigitFiles([...sigitFiles])
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired only on when left (primary pointer interaction) clicking page image
|
||||
* Creates new drawnElement and pushes in the array
|
||||
* It is re rendered and visible right away
|
||||
* Creates new drawnElement
|
||||
*
|
||||
* @param event Pointer event
|
||||
* @param page PdfPage where press happened
|
||||
* @param pageIndexer File and page index
|
||||
* @param pageWidth pdf value used to scale pointer coordinates
|
||||
*/
|
||||
const handlePointerDown = (
|
||||
event: React.PointerEvent,
|
||||
page: PdfPage,
|
||||
fileIndex: number,
|
||||
pageIndex: number
|
||||
) => {
|
||||
// Proceed only if left click
|
||||
if (event.button !== 0) return
|
||||
|
||||
if (!selectedTool) {
|
||||
return
|
||||
}
|
||||
|
||||
const { x, y } = getPointerCoordinates(event)
|
||||
|
||||
const newField: DrawnField = {
|
||||
left: to(page.width, x),
|
||||
top: to(page.width, y),
|
||||
width: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.width,
|
||||
height: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.height,
|
||||
counterpart: getAvailableSigner(lastSigner, defaultSignerNpub),
|
||||
type: selectedTool.identifier
|
||||
}
|
||||
|
||||
page.drawnFields.push(newField)
|
||||
|
||||
setActiveDrawnField({
|
||||
fileIndex,
|
||||
pageIndex,
|
||||
drawnFieldIndex: page.drawnFields.length - 1
|
||||
})
|
||||
setMouseState((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
clicked: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Drawing is finished, resets all the variables used to draw
|
||||
* @param event Pointer event
|
||||
*/
|
||||
const handlePointerUp = () => {
|
||||
sigitFiles.forEach((s) => {
|
||||
s.pages?.forEach((p) => {
|
||||
// Remove drawn fields below the MINIMUM_RECT_SIZE threshhold
|
||||
p.drawnFields = p.drawnFields.filter(
|
||||
(f) =>
|
||||
!(
|
||||
f.width < MINIMUM_RECT_SIZE.width ||
|
||||
f.height < MINIMUM_RECT_SIZE.height
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
setMouseState((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
clicked: false,
|
||||
dragging: false,
|
||||
resizing: false
|
||||
}
|
||||
})
|
||||
refreshPdfFiles()
|
||||
}
|
||||
|
||||
/**
|
||||
* After {@link handlePointerDown} create an drawing element, this function gets called on every pixel moved
|
||||
* which alters the newly created drawing element, resizing it while pointer moves
|
||||
* @param event Pointer event
|
||||
* @param page PdfPage where moving is happening
|
||||
*/
|
||||
const handlePointerMove = (event: React.PointerEvent, page: PdfPage) => {
|
||||
if (mouseState.clicked && selectedTool && event.pointerType === 'mouse') {
|
||||
const lastElementIndex = page.drawnFields.length - 1
|
||||
|
||||
const lastDrawnField = page.drawnFields[lastElementIndex]
|
||||
|
||||
// Return early if we don't have lastDrawnField
|
||||
// Issue noticed in the console when dragging out of bounds
|
||||
// to the page below (without releaseing mouse click)
|
||||
if (!lastDrawnField) return
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(
|
||||
event: React.PointerEvent,
|
||||
pageIndexer: PageIndexer,
|
||||
pageWidth: number
|
||||
) => {
|
||||
// Proceed only if left click
|
||||
if (event.button !== 0) return
|
||||
if (!selectedTool) return
|
||||
event.currentTarget.setPointerCapture(event.pointerId)
|
||||
const counterpart = getAvailableSigner(lastSigner, defaultSignerNpub)
|
||||
const { x, y } = getPointerCoordinates(event)
|
||||
|
||||
const width = to(page.width, x) - lastDrawnField.left
|
||||
const height = to(page.width, y) - lastDrawnField.top
|
||||
setIndexer(pageIndexer)
|
||||
setField({
|
||||
x: to(pageWidth, x),
|
||||
y: to(pageWidth, y),
|
||||
left: to(pageWidth, x),
|
||||
top: to(pageWidth, y),
|
||||
width: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.width,
|
||||
height: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.height,
|
||||
type: selectedTool.identifier,
|
||||
counterpart
|
||||
})
|
||||
setMouseState({
|
||||
clicked: true
|
||||
})
|
||||
},
|
||||
[defaultSignerNpub, getAvailableSigner, lastSigner, selectedTool, to]
|
||||
)
|
||||
|
||||
lastDrawnField.width = width
|
||||
lastDrawnField.height = height
|
||||
/**
|
||||
* After {@link handlePointerDown} creates an drawing element, this function
|
||||
* alters the newly created drawing element, resizing it while pointer moves
|
||||
* @param event Pointer event
|
||||
* @param pageWidth pdf value used to scale pointer coordinates
|
||||
*/
|
||||
const handlePointerMove = useCallback(
|
||||
(event: React.PointerEvent, pageWidth: number) => {
|
||||
if (mouseState.clicked && selectedTool && event.pointerType === 'mouse') {
|
||||
const { x, y } = getPointerCoordinates(event)
|
||||
const pageX = to(pageWidth, x)
|
||||
const pageY = to(pageWidth, y)
|
||||
|
||||
const currentDrawnFields = page.drawnFields
|
||||
// Calculate left, top, width, and height based on direction
|
||||
setField((prev) => {
|
||||
const left = pageX < prev!.x ? pageX : prev!.x
|
||||
const top = pageY < prev!.y ? pageY : prev!.y
|
||||
|
||||
currentDrawnFields[lastElementIndex] = lastDrawnField
|
||||
|
||||
refreshPdfFiles()
|
||||
}
|
||||
}
|
||||
const width = Math.abs(pageX - prev!.x)
|
||||
const height = Math.abs(pageY - prev!.y)
|
||||
return { ...prev!, left, top, width, height }
|
||||
})
|
||||
}
|
||||
},
|
||||
[mouseState.clicked, selectedTool, to]
|
||||
)
|
||||
|
||||
/**
|
||||
* Fired when event happens on the drawn element which will be moved
|
||||
@ -219,22 +195,30 @@ export const DrawPDFFields = (props: Props) => {
|
||||
* y - offsetY
|
||||
*
|
||||
* @param event Pointer event
|
||||
* @param drawnFieldIndex Which we are moving
|
||||
* @param fieldIndexer Which field we are moving
|
||||
*/
|
||||
const handleDrawnFieldPointerDown = (
|
||||
event: React.PointerEvent,
|
||||
fileIndex: number,
|
||||
pageIndex: number,
|
||||
drawnFieldIndex: number
|
||||
fieldIndexer: FieldIndexer
|
||||
) => {
|
||||
event.stopPropagation()
|
||||
|
||||
// Proceed only if left click
|
||||
if (event.button !== 0) return
|
||||
|
||||
event.currentTarget.setPointerCapture(event.pointerId)
|
||||
const drawingRectangleCoords = getPointerCoordinates(event)
|
||||
const [fileIndex, pageIndex, drawnFieldIndex] = fieldIndexer
|
||||
const page = sigitFiles[fileIndex].pages![pageIndex]
|
||||
const drawnField = page.drawnFields[drawnFieldIndex]
|
||||
|
||||
setActiveDrawnField({ fileIndex, pageIndex, drawnFieldIndex })
|
||||
setIndexer(fieldIndexer)
|
||||
setField({
|
||||
...drawnField,
|
||||
x: to(page.width, drawingRectangleCoords.x),
|
||||
y: to(page.width, drawingRectangleCoords.y)
|
||||
})
|
||||
setLastIndexer(fieldIndexer)
|
||||
setMouseState({
|
||||
dragging: true,
|
||||
clicked: false,
|
||||
@ -244,7 +228,7 @@ export const DrawPDFFields = (props: Props) => {
|
||||
}
|
||||
})
|
||||
|
||||
// make signers dropdown visible
|
||||
// Make signers dropdown visible
|
||||
setHideSignersForDrawnField((prev) => ({
|
||||
...prev,
|
||||
[drawnFieldIndex]: false
|
||||
@ -254,12 +238,10 @@ export const DrawPDFFields = (props: Props) => {
|
||||
/**
|
||||
* Moves the drawnElement by the pointer position (pointer can grab anywhere on the drawn element)
|
||||
* @param event Pointer event
|
||||
* @param drawnField which we are moving
|
||||
* @param pageWidth pdf value which is used to calculate scaled offset
|
||||
* @param pageWidth pdf value used to scale pointer coordinates
|
||||
*/
|
||||
const handleDrawnFieldPointerMove = (
|
||||
event: React.PointerEvent,
|
||||
drawnField: DrawnField,
|
||||
pageWidth: number
|
||||
) => {
|
||||
if (mouseState.dragging) {
|
||||
@ -273,18 +255,21 @@ export const DrawPDFFields = (props: Props) => {
|
||||
let left = to(pageWidth, x - coordsOffset.x)
|
||||
let top = to(pageWidth, y - coordsOffset.y)
|
||||
|
||||
const rightLimit = to(pageWidth, rect.width) - drawnField.width
|
||||
const bottomLimit = to(pageWidth, rect.height) - drawnField.height
|
||||
setField((prev) => {
|
||||
const rightLimit = to(pageWidth, rect.width) - prev!.width
|
||||
const bottomLimit = to(pageWidth, rect.height) - prev!.height
|
||||
|
||||
if (left < 0) left = 0
|
||||
if (top < 0) top = 0
|
||||
if (left > rightLimit) left = rightLimit
|
||||
if (top > bottomLimit) top = bottomLimit
|
||||
if (left < 0) left = 0
|
||||
if (top < 0) top = 0
|
||||
if (left > rightLimit) left = rightLimit
|
||||
if (top > bottomLimit) top = bottomLimit
|
||||
|
||||
drawnField.left = left
|
||||
drawnField.top = top
|
||||
|
||||
refreshPdfFiles()
|
||||
return {
|
||||
...prev!,
|
||||
left,
|
||||
top
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -292,73 +277,85 @@ export const DrawPDFFields = (props: Props) => {
|
||||
/**
|
||||
* Fired when clicked on the resize handle, sets the state for a resize action
|
||||
* @param event Pointer event
|
||||
* @param drawnFieldIndex which we are resizing
|
||||
* @param fieldIndexer which field we are resizing
|
||||
*/
|
||||
const handleResizePointerDown = (
|
||||
event: React.PointerEvent,
|
||||
fileIndex: number,
|
||||
pageIndex: number,
|
||||
drawnFieldIndex: number
|
||||
) => {
|
||||
// Proceed only if left click
|
||||
if (event.button !== 0) return
|
||||
event.stopPropagation()
|
||||
const handleResizePointerDown = useCallback(
|
||||
(event: React.PointerEvent, fieldIndexer: FieldIndexer) => {
|
||||
// Proceed only if left click
|
||||
if (event.button !== 0) return
|
||||
event.stopPropagation()
|
||||
|
||||
setActiveDrawnField({ fileIndex, pageIndex, drawnFieldIndex })
|
||||
setMouseState({
|
||||
resizing: true
|
||||
})
|
||||
}
|
||||
event.currentTarget.setPointerCapture(event.pointerId)
|
||||
const [fileIndex, pageIndex, drawnFieldIndex] = fieldIndexer
|
||||
const page = sigitFiles[fileIndex].pages![pageIndex]
|
||||
const drawnField = page.drawnFields[drawnFieldIndex]
|
||||
setIndexer(fieldIndexer)
|
||||
setField({
|
||||
...drawnField,
|
||||
x: drawnField.left,
|
||||
y: drawnField.top
|
||||
})
|
||||
setLastIndexer(fieldIndexer)
|
||||
setMouseState({
|
||||
resizing: true
|
||||
})
|
||||
},
|
||||
[sigitFiles]
|
||||
)
|
||||
|
||||
/**
|
||||
* Resizes the drawn element by the mouse position
|
||||
* @param event Pointer event
|
||||
* @param drawnField which we are resizing
|
||||
* @param pageWidth pdf value which is used to calculate scaled offset
|
||||
* @param pageWidth pdf value used to scale pointer coordinates
|
||||
*/
|
||||
const handleResizePointerMove = (
|
||||
event: React.PointerEvent,
|
||||
drawnField: DrawnField,
|
||||
pageWidth: number
|
||||
) => {
|
||||
if (mouseState.resizing) {
|
||||
const { x, y } = getPointerCoordinates(
|
||||
event,
|
||||
const handleResizePointerMove = useCallback(
|
||||
(event: React.PointerEvent, pageWidth: number) => {
|
||||
if (mouseState.resizing) {
|
||||
// currentTarget = span handle
|
||||
// 1st parent = drawnField
|
||||
// 2nd parent = img
|
||||
event.currentTarget.parentElement?.parentElement
|
||||
)
|
||||
const { x, y } = getPointerCoordinates(
|
||||
event,
|
||||
event.currentTarget.parentElement?.parentElement
|
||||
)
|
||||
|
||||
const width = to(pageWidth, x) - drawnField.left
|
||||
const height = to(pageWidth, y) - drawnField.top
|
||||
const pageX = to(pageWidth, x)
|
||||
const pageY = to(pageWidth, y)
|
||||
|
||||
drawnField.width = width
|
||||
drawnField.height = height
|
||||
setField((prev) => {
|
||||
const left = pageX < prev!.x ? pageX : prev!.x
|
||||
const top = pageY < prev!.y ? pageY : prev!.y
|
||||
|
||||
refreshPdfFiles()
|
||||
}
|
||||
}
|
||||
const width = Math.abs(pageX - prev!.x)
|
||||
const height = Math.abs(pageY - prev!.y)
|
||||
return { ...prev!, left, top, width, height }
|
||||
})
|
||||
}
|
||||
},
|
||||
[mouseState.resizing, to]
|
||||
)
|
||||
|
||||
const handlePointerUpReleaseCapture = useCallback(
|
||||
(event: React.PointerEvent) => {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
/**
|
||||
* Removes the drawn element using the indexes in the params
|
||||
* @param event Pointer event
|
||||
* @param pdfFileIndex pdf file index
|
||||
* @param pdfPageIndex pdf page index
|
||||
* @param drawnFileIndex drawn file index
|
||||
* @param fieldIIndexer [file index, page index, field index]
|
||||
*/
|
||||
const handleRemovePointerDown = (
|
||||
event: React.PointerEvent,
|
||||
pdfFileIndex: number,
|
||||
pdfPageIndex: number,
|
||||
drawnFileIndex: number
|
||||
[fileIndex, pageIndex, fieldIndex]: FieldIndexer
|
||||
) => {
|
||||
event.stopPropagation()
|
||||
|
||||
const pages = sigitFiles[pdfFileIndex]?.pages
|
||||
if (pages) {
|
||||
pages[pdfPageIndex]?.drawnFields?.splice(drawnFileIndex, 1)
|
||||
}
|
||||
updateSigitFiles((draft) => {
|
||||
draft[fileIndex]?.pages![pageIndex]?.drawnFields?.splice(fieldIndex, 1)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -397,28 +394,72 @@ export const DrawPDFFields = (props: Props) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pointer coordinates relative to a element in the `event` param
|
||||
* @param event PointerEvent
|
||||
* @param customTarget coordinates relative to this element, if not provided
|
||||
* event.target will be used
|
||||
* Drawing is finished, resets all the variables used to draw
|
||||
*/
|
||||
const getPointerCoordinates = (
|
||||
event: React.PointerEvent,
|
||||
customTarget?: HTMLElement | null
|
||||
) => {
|
||||
const target = customTarget ? customTarget : event.currentTarget
|
||||
const rect = target.getBoundingClientRect()
|
||||
const handlePointerUp = useCallback(() => {
|
||||
// Proceed if we have selected something
|
||||
if (indexer) {
|
||||
// Check if we have the "preview" field state
|
||||
if (field) {
|
||||
// Cancel update if preview field is below the MINIMUM_RECT_SIZE threshhold
|
||||
if (
|
||||
field.width < MINIMUM_RECT_SIZE.width ||
|
||||
field.height < MINIMUM_RECT_SIZE.height
|
||||
) {
|
||||
setIndexer(undefined)
|
||||
setMouseState({})
|
||||
return
|
||||
}
|
||||
|
||||
// Clamp X Y within the target
|
||||
const x = Math.min(event.clientX, rect.right) - rect.left //x position within the element.
|
||||
const y = Math.min(event.clientY, rect.bottom) - rect.top //y position within the element.
|
||||
const [fileIndex, pageIndex, fieldIndex] = indexer
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
rect
|
||||
// Add new drawn field to the files
|
||||
if (mouseState.clicked) {
|
||||
updateSigitFiles((draft) => {
|
||||
draft[fileIndex].pages![pageIndex].drawnFields.push(field)
|
||||
})
|
||||
}
|
||||
|
||||
// Move
|
||||
if (mouseState.dragging) {
|
||||
updateSigitFiles((draft) => {
|
||||
draft[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
|
||||
})
|
||||
}
|
||||
|
||||
// Resize
|
||||
if (mouseState.resizing) {
|
||||
updateSigitFiles((draft) => {
|
||||
draft[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
|
||||
})
|
||||
}
|
||||
|
||||
// Clear indexer after applying the update
|
||||
setIndexer(undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
setMouseState({})
|
||||
}, [
|
||||
field,
|
||||
indexer,
|
||||
mouseState.clicked,
|
||||
mouseState.dragging,
|
||||
mouseState.resizing,
|
||||
updateSigitFiles
|
||||
])
|
||||
|
||||
/**
|
||||
* Drawing events
|
||||
*/
|
||||
useEffect(() => {
|
||||
window.addEventListener('pointerup', handlePointerUp)
|
||||
window.addEventListener('pointercancel', handlePointerUp)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('pointerup', handlePointerUp)
|
||||
window.removeEventListener('pointercancel', handlePointerUp)
|
||||
}
|
||||
}, [handlePointerUp])
|
||||
|
||||
/**
|
||||
* Renders the pdf pages and drawing elements
|
||||
@ -430,6 +471,13 @@ export const DrawPDFFields = (props: Props) => {
|
||||
return (
|
||||
<>
|
||||
{file.pages?.map((page, pageIndex: number) => {
|
||||
let isPageIndexerActive = false
|
||||
if (indexer) {
|
||||
const [fi, pi, di] = indexer
|
||||
isPageIndexerActive =
|
||||
fi === fileIndex && pi === pageIndex && typeof di === 'undefined'
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={pageIndex}
|
||||
@ -438,32 +486,68 @@ export const DrawPDFFields = (props: Props) => {
|
||||
onKeyDown={(event) => handleEscapeButtonDown(event)}
|
||||
>
|
||||
<img
|
||||
onPointerMove={(event) => {
|
||||
handlePointerMove(event, page)
|
||||
}}
|
||||
onPointerDown={(event) => {
|
||||
handlePointerDown(event, page, fileIndex, pageIndex)
|
||||
handlePointerDown(event, [fileIndex, pageIndex], page.width)
|
||||
}}
|
||||
onPointerMove={(event) => {
|
||||
handlePointerMove(event, page.width)
|
||||
}}
|
||||
onPointerUp={handlePointerUpReleaseCapture}
|
||||
draggable="false"
|
||||
src={page.image}
|
||||
alt={`page ${pageIndex + 1} of ${file.name}`}
|
||||
/>
|
||||
|
||||
{isPageIndexerActive && field && (
|
||||
<div
|
||||
className={styles.drawingRectanglePreview}
|
||||
style={{
|
||||
backgroundColor: field.counterpart
|
||||
? `#${npubToHex(field.counterpart)?.substring(0, 6)}4b`
|
||||
: undefined,
|
||||
outlineColor: field.counterpart
|
||||
? `#${npubToHex(field.counterpart)?.substring(0, 6)}`
|
||||
: undefined,
|
||||
left: inPx(from(page.width, field.left)),
|
||||
top: inPx(from(page.width, field.top)),
|
||||
width: inPx(from(page.width, field.width)),
|
||||
height: inPx(from(page.width, field.height))
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`file-mark ${styles.placeholder}`}
|
||||
style={{
|
||||
fontFamily: FONT_TYPE,
|
||||
fontSize: inPx(from(page.width, FONT_SIZE))
|
||||
}}
|
||||
>
|
||||
{getToolboxLabelByMarkType(field.type) || 'placeholder'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{page.drawnFields.map((drawnField, drawnFieldIndex: number) => {
|
||||
let isFieldIndexerActive = false
|
||||
if (indexer) {
|
||||
const [fi, pi, di] = indexer
|
||||
isFieldIndexerActive =
|
||||
fi === fileIndex &&
|
||||
pi === pageIndex &&
|
||||
di === drawnFieldIndex
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={drawnFieldIndex}
|
||||
onPointerDown={(event) =>
|
||||
handleDrawnFieldPointerDown(
|
||||
event,
|
||||
handleDrawnFieldPointerDown(event, [
|
||||
fileIndex,
|
||||
pageIndex,
|
||||
drawnFieldIndex
|
||||
)
|
||||
])
|
||||
}
|
||||
onPointerMove={(event) => {
|
||||
handleDrawnFieldPointerMove(event, drawnField, page.width)
|
||||
handleDrawnFieldPointerMove(event, page.width)
|
||||
}}
|
||||
onPointerUp={handlePointerUpReleaseCapture}
|
||||
className={styles.drawingRectangle}
|
||||
style={{
|
||||
backgroundColor: drawnField.counterpart
|
||||
@ -472,12 +556,29 @@ export const DrawPDFFields = (props: Props) => {
|
||||
outlineColor: drawnField.counterpart
|
||||
? `#${npubToHex(drawnField.counterpart)?.substring(0, 6)}`
|
||||
: undefined,
|
||||
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)),
|
||||
...(isFieldIndexerActive && field
|
||||
? {
|
||||
left: inPx(from(page.width, field.left)),
|
||||
top: inPx(from(page.width, field.top)),
|
||||
width: inPx(from(page.width, field.width)),
|
||||
height: inPx(from(page.width, field.height))
|
||||
}
|
||||
: {
|
||||
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',
|
||||
touchAction: 'none',
|
||||
zIndex: isActiveDrawnField(
|
||||
fileIndex,
|
||||
pageIndex,
|
||||
drawnFieldIndex
|
||||
)
|
||||
? 60
|
||||
: undefined,
|
||||
opacity:
|
||||
mouseState.dragging &&
|
||||
isActiveDrawnField(
|
||||
@ -501,37 +602,36 @@ export const DrawPDFFields = (props: Props) => {
|
||||
</div>
|
||||
<span
|
||||
onPointerDown={(event) =>
|
||||
handleResizePointerDown(
|
||||
event,
|
||||
handleResizePointerDown(event, [
|
||||
fileIndex,
|
||||
pageIndex,
|
||||
drawnFieldIndex
|
||||
)
|
||||
])
|
||||
}
|
||||
onPointerMove={(event) => {
|
||||
handleResizePointerMove(event, drawnField, page.width)
|
||||
handleResizePointerMove(event, page.width)
|
||||
}}
|
||||
onPointerUp={handlePointerUpReleaseCapture}
|
||||
className={styles.resizeHandle}
|
||||
style={{
|
||||
background:
|
||||
mouseState.resizing &&
|
||||
...(mouseState.resizing &&
|
||||
isActiveDrawnField(
|
||||
fileIndex,
|
||||
pageIndex,
|
||||
drawnFieldIndex
|
||||
)
|
||||
? 'var(--primary-main)'
|
||||
: undefined
|
||||
) && {
|
||||
cursor: 'grabbing',
|
||||
opacity: 0.1
|
||||
})
|
||||
}}
|
||||
></span>
|
||||
<span
|
||||
onPointerDown={(event) => {
|
||||
handleRemovePointerDown(
|
||||
event,
|
||||
handleRemovePointerDown(event, [
|
||||
fileIndex,
|
||||
pageIndex,
|
||||
drawnFieldIndex
|
||||
)
|
||||
])
|
||||
}}
|
||||
className={styles.removeHandle}
|
||||
>
|
||||
@ -569,23 +669,26 @@ export const DrawPDFFields = (props: Props) => {
|
||||
onChange={(event) => {
|
||||
drawnField.counterpart = event.target.value
|
||||
setLastSigner(event.target.value)
|
||||
refreshPdfFiles()
|
||||
}}
|
||||
labelId="counterparts"
|
||||
label="Counterparts"
|
||||
sx={{
|
||||
background: 'white'
|
||||
}}
|
||||
renderValue={(value) =>
|
||||
renderCounterpartValue(value)
|
||||
}
|
||||
renderValue={(value) => (
|
||||
<Counterpart
|
||||
npub={value}
|
||||
metadata={metadata}
|
||||
signers={signers}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{signers.map((signer, index) => {
|
||||
const npub = hexToNpub(signer.pubkey)
|
||||
const metadata = props.metadata[signer.pubkey]
|
||||
const profileMetadata = metadata[signer.pubkey]
|
||||
const displayValue = getProfileUsername(
|
||||
npub,
|
||||
metadata
|
||||
profileMetadata
|
||||
)
|
||||
// make current signers dropdown visible
|
||||
if (
|
||||
@ -604,7 +707,7 @@ export const DrawPDFFields = (props: Props) => {
|
||||
<MenuItem key={index} value={npub}>
|
||||
<ListItemIcon>
|
||||
<AvatarIconButton
|
||||
src={metadata?.picture}
|
||||
src={profileMetadata?.picture}
|
||||
hexKey={signer.pubkey}
|
||||
aria-label={`account of user ${displayValue}`}
|
||||
color="inherit"
|
||||
@ -635,58 +738,24 @@ export const DrawPDFFields = (props: Props) => {
|
||||
)
|
||||
}
|
||||
|
||||
const renderCounterpartValue = (npub: string) => {
|
||||
let displayValue = _.truncate(npub, { length: 16 })
|
||||
|
||||
const signer = signers.find((u) => u.pubkey === npubToHex(npub))
|
||||
if (signer) {
|
||||
const metadata = props.metadata[signer.pubkey]
|
||||
displayValue = getProfileUsername(npub, metadata)
|
||||
|
||||
return (
|
||||
<div className={styles.counterpartSelectValue}>
|
||||
<AvatarIconButton
|
||||
src={props.metadata[signer.pubkey]?.picture}
|
||||
hexKey={signer.pubkey || undefined}
|
||||
sx={{
|
||||
padding: 0,
|
||||
marginRight: '6px',
|
||||
'> img': {
|
||||
width: '21px',
|
||||
height: '21px'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{displayValue}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return displayValue
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="files-wrapper">
|
||||
{sigitFiles.map((file, i) => {
|
||||
return (
|
||||
<React.Fragment key={file.name}>
|
||||
<div className="file-wrapper" id={`file-${file.name}`}>
|
||||
{file.isPdf && getPdfPages(file, i)}
|
||||
{file.isImage && (
|
||||
<img
|
||||
className="file-image"
|
||||
src={file.objectUrl}
|
||||
alt={file.name}
|
||||
/>
|
||||
)}
|
||||
{!(file.isPdf || file.isImage) && (
|
||||
<ExtensionFileBox extension={file.extension} />
|
||||
)}
|
||||
</div>
|
||||
{i < sigitFiles.length - 1 && <FileDivider />}
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
{sigitFiles.length > 0 &&
|
||||
sigitFiles
|
||||
.map<React.ReactNode>((file, i) =>
|
||||
file.isPdf ? (
|
||||
<React.Fragment key={file.name}>
|
||||
{getPdfPages(file, i)}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<FileItem key={file.name} file={file} />
|
||||
)
|
||||
)
|
||||
.reduce((prev, curr, i) => [
|
||||
prev,
|
||||
<FileDivider key={`separator-${i}`} />,
|
||||
curr
|
||||
])}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -47,7 +47,7 @@
|
||||
background-color: #fff;
|
||||
border: 1px solid rgb(160, 160, 160);
|
||||
border-radius: 50%;
|
||||
cursor: nwse-resize;
|
||||
cursor: grab;
|
||||
|
||||
// Increase the area a bit so it's easier to click
|
||||
&::after {
|
||||
@ -85,10 +85,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.counterpartSelectValue {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.counterpartAvatar {
|
||||
img {
|
||||
width: 21px;
|
||||
@ -107,3 +103,16 @@
|
||||
outline: 1px dotted #01aaad;
|
||||
}
|
||||
}
|
||||
|
||||
.drawingRectanglePreview {
|
||||
position: absolute;
|
||||
outline: 1px solid;
|
||||
z-index: 50;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
touch-action: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user