feat: signature squiggle #237

Merged
b merged 3 commits from feat/signature into staging 2024-10-28 16:23:29 +00:00
6 changed files with 72 additions and 17 deletions
Showing only changes of commit de44370a96 - Show all commits

View File

@ -7,13 +7,12 @@ import {
isCurrentValueLast isCurrentValueLast
} from '../../utils' } from '../../utils'
import React, { useState } from 'react' import React, { useState } from 'react'
import { MARK_TYPE_CONFIG } from '../getMarkComponents.tsx'
interface MarkFormFieldProps { interface MarkFormFieldProps {
currentUserMarks: CurrentUserMark[] currentUserMarks: CurrentUserMark[]
handleCurrentUserMarkChange: (mark: CurrentUserMark) => void handleCurrentUserMarkChange: (mark: CurrentUserMark) => void
handleSelectedMarkValueChange: ( handleSelectedMarkValueChange: (value: string) => void
event: React.ChangeEvent<HTMLInputElement>
) => void
handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void
selectedMark: CurrentUserMark selectedMark: CurrentUserMark
selectedMarkValue: string selectedMarkValue: string
@ -53,6 +52,8 @@ const MarkFormField = ({
} }
const toggleActions = () => setDisplayActions(!displayActions) const toggleActions = () => setDisplayActions(!displayActions)
const markLabel = getToolboxLabelByMarkType(selectedMark.mark.type) const markLabel = getToolboxLabelByMarkType(selectedMark.mark.type)
const { input: MarkInputComponent } =
MARK_TYPE_CONFIG[selectedMark.mark.type] || {}
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.trigger}> <div className={styles.trigger}>
@ -83,12 +84,14 @@ const MarkFormField = ({
</div> </div>
<div className={styles.inputWrapper}> <div className={styles.inputWrapper}>
<form onSubmit={(e) => handleFormSubmit(e)}> <form onSubmit={(e) => handleFormSubmit(e)}>
<input {typeof MarkInputComponent !== 'undefined' && (
className={styles.input} <MarkInputComponent
placeholder={markLabel}
onChange={handleSelectedMarkValueChange}
value={selectedMarkValue} value={selectedMarkValue}
placeholder={markLabel}
handler={handleSelectedMarkValueChange}
userMark={selectedMark}
/> />
)}
<div className={styles.actionsBottom}> <div className={styles.actionsBottom}>
<button type="submit" className={styles.submitButton}> <button type="submit" className={styles.submitButton}>
NEXT NEXT

View File

@ -0,0 +1,19 @@
import { MarkInputProps } from '../../types/mark'
import styles from '../MarkFormField/style.module.scss'
export const MarkInputText = ({
value,
handler,
placeholder
}: MarkInputProps) => {
return (
<input
className={styles.input}
placeholder={placeholder}
onChange={(e) => {
handler(e.currentTarget.value)
}}
value={value}
/>
)
}

View File

@ -4,6 +4,7 @@ import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf.ts'
import { useScale } from '../../hooks/useScale.tsx' import { useScale } from '../../hooks/useScale.tsx'
import { forwardRef } from 'react' import { forwardRef } from 'react'
import { npubToHex } from '../../utils/nostr.ts' import { npubToHex } from '../../utils/nostr.ts'
import { MARK_TYPE_CONFIG } from '../getMarkComponents.tsx'
interface PdfMarkItemProps { interface PdfMarkItemProps {
userMark: CurrentUserMark userMark: CurrentUserMark
@ -27,6 +28,8 @@ const PdfMarkItem = forwardRef<HTMLDivElement, PdfMarkItemProps>(
const getMarkValue = () => const getMarkValue = () =>
isEdited() ? selectedMarkValue : userMark.currentValue isEdited() ? selectedMarkValue : userMark.currentValue
const { from } = useScale() const { from } = useScale()
const { render: MarkRenderComponent } =
MARK_TYPE_CONFIG[userMark.mark.type] || {}
return ( return (
<div <div
ref={ref} ref={ref}
@ -47,7 +50,9 @@ const PdfMarkItem = forwardRef<HTMLDivElement, PdfMarkItemProps>(
fontSize: inPx(from(pageWidth, FONT_SIZE)) fontSize: inPx(from(pageWidth, FONT_SIZE))
}} }}
> >
{getMarkValue()} {typeof MarkRenderComponent !== 'undefined' && (
<MarkRenderComponent value={getMarkValue()} mark={userMark.mark} />
)}
</div> </div>
) )
} }

View File

@ -117,8 +117,7 @@ const PdfMarking = (props: PdfMarkingProps) => {
// setCurrentUserMarks(updatedCurrentUserMarks) // setCurrentUserMarks(updatedCurrentUserMarks)
// } // }
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => const handleChange = (value: string) => setSelectedMarkValue(value)
setSelectedMarkValue(event.target.value)
return ( return (
<> <>

View File

@ -55,6 +55,7 @@ import {
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { upgradeAndVerifyTimestamp } from '../../utils/opentimestamps.ts' import { upgradeAndVerifyTimestamp } from '../../utils/opentimestamps.ts'
import _ from 'lodash' import _ from 'lodash'
import { MARK_TYPE_CONFIG } from '../../components/getMarkComponents.tsx'
interface PdfViewProps { interface PdfViewProps {
files: CurrentUserFile[] files: CurrentUserFile[]
@ -114,6 +115,8 @@ const SlimPdfView = ({
alt={`page ${i} of ${file.name}`} alt={`page ${i} of ${file.name}`}
/> />
{marks.map((m) => { {marks.map((m) => {
const { render: MarkRenderComponent } =
MARK_TYPE_CONFIG[m.type] || {}
return ( return (
<div <div
className={`file-mark ${styles.mark}`} className={`file-mark ${styles.mark}`}
@ -129,7 +132,9 @@ const SlimPdfView = ({
fontSize: inPx(from(page.width, FONT_SIZE)) fontSize: inPx(from(page.width, FONT_SIZE))
}} }}
> >
{m.value} {typeof MarkRenderComponent !== 'undefined' && (
<MarkRenderComponent value={m.value} mark={m} />
)}
</div> </div>
) )
})} })}

View File

@ -1,4 +1,4 @@
import { PdfPage } from '../types/drawing.ts' import { MarkType, PdfPage } from '../types/drawing.ts'
import { PDFDocument, PDFFont, PDFPage, rgb } from 'pdf-lib' import { PDFDocument, PDFFont, PDFPage, rgb } from 'pdf-lib'
import { Mark } from '../types/mark.ts' import { Mark } from '../types/mark.ts'
import * as PDFJS from 'pdfjs-dist' import * as PDFJS from 'pdfjs-dist'
@ -132,9 +132,17 @@ export const addMarks = async (
for (let i = 0; i < pages.length; i++) { for (let i = 0; i < pages.length; i++) {
if (marksPerPage && Object.hasOwn(marksPerPage, i)) { if (marksPerPage && Object.hasOwn(marksPerPage, i)) {
marksPerPage[i]?.forEach((mark) => marksPerPage[i]?.forEach((mark) => {
switch (mark.type) {
case MarkType.SIGNATURE:
drawSignatureText(mark, pages[i])
break
default:
drawMarkText(mark, pages[i], robotoFont) drawMarkText(mark, pages[i], robotoFont)
) break
}
})
} }
} }
@ -245,3 +253,19 @@ async function embedFont(pdf: PDFDocument) {
const embeddedFont = await pdf.embedFont(fontBytes) const embeddedFont = await pdf.embedFont(fontBytes)
return embeddedFont return embeddedFont
} }
const drawSignatureText = (mark: Mark, page: PDFPage) => {
const { location } = mark
const { height } = page.getSize()
// Convert the mark location origin (top, left) to PDF origin (bottom, left)
const x = location.left
const y = height - location.top
if (hasValue(mark)) {
const paths = JSON.parse(mark.value!)
paths.forEach((d: string) => {
page.drawSvgPath(d, { x, y })
})
}
}