@ -28,9 +28,9 @@ import {
|
|||||||
} from '../../routes'
|
} from '../../routes'
|
||||||
import {
|
import {
|
||||||
clearAuthToken,
|
clearAuthToken,
|
||||||
|
getProfileUsername,
|
||||||
hexToNpub,
|
hexToNpub,
|
||||||
saveNsecBunkerDelegatedKey,
|
saveNsecBunkerDelegatedKey
|
||||||
shorten
|
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { setUserRobotImage } from '../../store/userRobotImage/action'
|
import { setUserRobotImage } from '../../store/userRobotImage/action'
|
||||||
@ -58,9 +58,8 @@ export const AppBar = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (metadataState) {
|
if (metadataState) {
|
||||||
if (metadataState.content) {
|
if (metadataState.content) {
|
||||||
const { picture, display_name, name } = JSON.parse(
|
const profileMetadata = JSON.parse(metadataState.content)
|
||||||
metadataState.content
|
const { picture } = profileMetadata
|
||||||
)
|
|
||||||
|
|
||||||
if (picture || userRobotImage) {
|
if (picture || userRobotImage) {
|
||||||
setUserAvatar(picture || userRobotImage)
|
setUserAvatar(picture || userRobotImage)
|
||||||
@ -70,7 +69,7 @@ export const AppBar = () => {
|
|||||||
? hexToNpub(authState.usersPubkey)
|
? hexToNpub(authState.usersPubkey)
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
setUsername(shorten(display_name || name || npub, 7))
|
setUsername(getProfileUsername(npub, profileMetadata))
|
||||||
} else {
|
} else {
|
||||||
setUserAvatar(userRobotImage || '')
|
setUserAvatar(userRobotImage || '')
|
||||||
setUsername('')
|
setUsername('')
|
||||||
@ -133,8 +132,10 @@ export const AppBar = () => {
|
|||||||
<div className={styles.banner}>
|
<div className={styles.banner}>
|
||||||
<Container>
|
<Container>
|
||||||
<div className={styles.bannerInner}>
|
<div className={styles.bannerInner}>
|
||||||
SIGit is currently Alpha software (available for internal
|
<p className={styles.bannerText}>
|
||||||
testing), use at your own risk!
|
SIGit is currently Alpha software (available for internal
|
||||||
|
testing), use at your own risk!
|
||||||
|
</p>
|
||||||
<Button
|
<Button
|
||||||
aria-label={`close banner`}
|
aria-label={`close banner`}
|
||||||
variant="text"
|
variant="text"
|
||||||
|
@ -67,3 +67,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bannerText {
|
||||||
|
margin-left: 54px;
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@ -11,16 +11,16 @@ import styles from './style.module.scss'
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { ProfileMetadata, User, UserRole } from '../../types'
|
import { ProfileMetadata, User, UserRole } from '../../types'
|
||||||
import { MouseState, PdfPage, DrawnField, DrawTool } from '../../types/drawing'
|
import { MouseState, PdfPage, DrawnField, DrawTool } from '../../types/drawing'
|
||||||
import { truncate } from 'lodash'
|
import { hexToNpub, npubToHex, getProfileUsername } from '../../utils'
|
||||||
import { settleAllFullfilfedPromises, hexToNpub, npubToHex } from '../../utils'
|
import { SigitFile } from '../../utils/file'
|
||||||
import { getSigitFile, SigitFile } from '../../utils/file'
|
|
||||||
import { getToolboxLabelByMarkType } from '../../utils/mark'
|
import { getToolboxLabelByMarkType } from '../../utils/mark'
|
||||||
import { FileDivider } from '../FileDivider'
|
import { FileDivider } from '../FileDivider'
|
||||||
import { ExtensionFileBox } from '../ExtensionFileBox'
|
import { ExtensionFileBox } from '../ExtensionFileBox'
|
||||||
import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf'
|
import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf'
|
||||||
import { useScale } from '../../hooks/useScale'
|
import { useScale } from '../../hooks/useScale'
|
||||||
import { AvatarIconButton } from '../UserAvatarIconButton'
|
import { AvatarIconButton } from '../UserAvatarIconButton'
|
||||||
import { LoadingSpinner } from '../LoadingSpinner'
|
import { UserAvatar } from '../UserAvatar'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
const DEFAULT_START_SIZE = {
|
const DEFAULT_START_SIZE = {
|
||||||
width: 140,
|
width: 140,
|
||||||
@ -28,52 +28,50 @@ const DEFAULT_START_SIZE = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
selectedFiles: File[]
|
|
||||||
users: User[]
|
users: User[]
|
||||||
metadata: { [key: string]: ProfileMetadata }
|
metadata: { [key: string]: ProfileMetadata }
|
||||||
onDrawFieldsChange: (sigitFiles: SigitFile[]) => void
|
sigitFiles: SigitFile[]
|
||||||
|
setSigitFiles: React.Dispatch<React.SetStateAction<SigitFile[]>>
|
||||||
selectedTool?: DrawTool
|
selectedTool?: DrawTool
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DrawPDFFields = (props: Props) => {
|
export const DrawPDFFields = (props: Props) => {
|
||||||
const { selectedFiles, selectedTool, onDrawFieldsChange, users } = props
|
const { selectedTool, sigitFiles, setSigitFiles, users } = props
|
||||||
const { to, from } = useScale()
|
|
||||||
|
|
||||||
const [sigitFiles, setSigitFiles] = useState<SigitFile[]>([])
|
const signers = users.filter((u) => u.role === UserRole.signer)
|
||||||
const [parsingPdf, setIsParsing] = useState<boolean>(false)
|
const defaultSignerNpub = signers.length ? hexToNpub(signers[0].pubkey) : ''
|
||||||
|
const [lastSigner, setLastSigner] = useState(defaultSignerNpub)
|
||||||
|
/**
|
||||||
|
* Return first pubkey that is present in the signers list
|
||||||
|
* @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 { to, from } = useScale()
|
||||||
|
|
||||||
const [mouseState, setMouseState] = useState<MouseState>({
|
const [mouseState, setMouseState] = useState<MouseState>({
|
||||||
clicked: false
|
clicked: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const [activeDrawField, setActiveDrawField] = useState<number>()
|
const [activeDrawnField, setActiveDrawnField] = useState<{
|
||||||
|
fileIndex: number
|
||||||
useEffect(() => {
|
pageIndex: number
|
||||||
if (selectedFiles) {
|
drawnFieldIndex: number
|
||||||
/**
|
}>()
|
||||||
* Reads the binary files and converts to internal file type
|
const isActiveDrawnField = (
|
||||||
* and sets to a state (adds images if it's a PDF)
|
fileIndex: number,
|
||||||
*/
|
pageIndex: number,
|
||||||
const parsePages = async () => {
|
drawnFieldIndex: number
|
||||||
const files = await settleAllFullfilfedPromises(
|
) =>
|
||||||
selectedFiles,
|
activeDrawnField?.fileIndex === fileIndex &&
|
||||||
getSigitFile
|
activeDrawnField?.pageIndex === pageIndex &&
|
||||||
)
|
activeDrawnField?.drawnFieldIndex === drawnFieldIndex
|
||||||
|
|
||||||
setSigitFiles(files)
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsParsing(true)
|
|
||||||
|
|
||||||
parsePages().finally(() => {
|
|
||||||
setIsParsing(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [selectedFiles])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (sigitFiles) onDrawFieldsChange(sigitFiles)
|
|
||||||
}, [onDrawFieldsChange, sigitFiles])
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drawing events
|
* Drawing events
|
||||||
@ -100,7 +98,12 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
* @param event Pointer event
|
* @param event Pointer event
|
||||||
* @param page PdfPage where press happened
|
* @param page PdfPage where press happened
|
||||||
*/
|
*/
|
||||||
const handlePointerDown = (event: React.PointerEvent, page: PdfPage) => {
|
const handlePointerDown = (
|
||||||
|
event: React.PointerEvent,
|
||||||
|
page: PdfPage,
|
||||||
|
fileIndex: number,
|
||||||
|
pageIndex: number
|
||||||
|
) => {
|
||||||
// Proceed only if left click
|
// Proceed only if left click
|
||||||
if (event.button !== 0) return
|
if (event.button !== 0) return
|
||||||
|
|
||||||
@ -115,12 +118,17 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
top: to(page.width, y),
|
top: to(page.width, y),
|
||||||
width: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.width,
|
width: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.width,
|
||||||
height: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.height,
|
height: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.height,
|
||||||
counterpart: '',
|
counterpart: getAvailableSigner(lastSigner, defaultSignerNpub),
|
||||||
type: selectedTool.identifier
|
type: selectedTool.identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
page.drawnFields.push(newField)
|
page.drawnFields.push(newField)
|
||||||
|
|
||||||
|
setActiveDrawnField({
|
||||||
|
fileIndex,
|
||||||
|
pageIndex,
|
||||||
|
drawnFieldIndex: page.drawnFields.length - 1
|
||||||
|
})
|
||||||
setMouseState((prev) => {
|
setMouseState((prev) => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
@ -189,6 +197,8 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
*/
|
*/
|
||||||
const handleDrawnFieldPointerDown = (
|
const handleDrawnFieldPointerDown = (
|
||||||
event: React.PointerEvent,
|
event: React.PointerEvent,
|
||||||
|
fileIndex: number,
|
||||||
|
pageIndex: number,
|
||||||
drawnFieldIndex: number
|
drawnFieldIndex: number
|
||||||
) => {
|
) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
@ -198,7 +208,7 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
|
|
||||||
const drawingRectangleCoords = getPointerCoordinates(event)
|
const drawingRectangleCoords = getPointerCoordinates(event)
|
||||||
|
|
||||||
setActiveDrawField(drawnFieldIndex)
|
setActiveDrawnField({ fileIndex, pageIndex, drawnFieldIndex })
|
||||||
setMouseState({
|
setMouseState({
|
||||||
dragging: true,
|
dragging: true,
|
||||||
clicked: false,
|
clicked: false,
|
||||||
@ -254,13 +264,15 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
*/
|
*/
|
||||||
const handleResizePointerDown = (
|
const handleResizePointerDown = (
|
||||||
event: React.PointerEvent,
|
event: React.PointerEvent,
|
||||||
|
fileIndex: number,
|
||||||
|
pageIndex: number,
|
||||||
drawnFieldIndex: number
|
drawnFieldIndex: number
|
||||||
) => {
|
) => {
|
||||||
// Proceed only if left click
|
// Proceed only if left click
|
||||||
if (event.button !== 0) return
|
if (event.button !== 0) return
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
setActiveDrawField(drawnFieldIndex)
|
setActiveDrawnField({ fileIndex, pageIndex, drawnFieldIndex })
|
||||||
setMouseState({
|
setMouseState({
|
||||||
resizing: true
|
resizing: true
|
||||||
})
|
})
|
||||||
@ -369,7 +381,7 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
handlePointerMove(event, page)
|
handlePointerMove(event, page)
|
||||||
}}
|
}}
|
||||||
onPointerDown={(event) => {
|
onPointerDown={(event) => {
|
||||||
handlePointerDown(event, page)
|
handlePointerDown(event, page, fileIndex, pageIndex)
|
||||||
}}
|
}}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
src={page.image}
|
src={page.image}
|
||||||
@ -381,7 +393,12 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
<div
|
<div
|
||||||
key={drawnFieldIndex}
|
key={drawnFieldIndex}
|
||||||
onPointerDown={(event) =>
|
onPointerDown={(event) =>
|
||||||
handleDrawnFieldPointerDown(event, drawnFieldIndex)
|
handleDrawnFieldPointerDown(
|
||||||
|
event,
|
||||||
|
fileIndex,
|
||||||
|
pageIndex,
|
||||||
|
drawnFieldIndex
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onPointerMove={(event) => {
|
onPointerMove={(event) => {
|
||||||
handleDrawnFieldPointerMove(event, drawnField, page.width)
|
handleDrawnFieldPointerMove(event, drawnField, page.width)
|
||||||
@ -402,7 +419,11 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
touchAction: 'none',
|
touchAction: 'none',
|
||||||
opacity:
|
opacity:
|
||||||
mouseState.dragging &&
|
mouseState.dragging &&
|
||||||
activeDrawField === drawnFieldIndex
|
isActiveDrawnField(
|
||||||
|
fileIndex,
|
||||||
|
pageIndex,
|
||||||
|
drawnFieldIndex
|
||||||
|
)
|
||||||
? 0.8
|
? 0.8
|
||||||
: undefined
|
: undefined
|
||||||
}}
|
}}
|
||||||
@ -419,7 +440,12 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
onPointerDown={(event) =>
|
onPointerDown={(event) =>
|
||||||
handleResizePointerDown(event, drawnFieldIndex)
|
handleResizePointerDown(
|
||||||
|
event,
|
||||||
|
fileIndex,
|
||||||
|
pageIndex,
|
||||||
|
drawnFieldIndex
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onPointerMove={(event) => {
|
onPointerMove={(event) => {
|
||||||
handleResizePointerMove(event, drawnField, page.width)
|
handleResizePointerMove(event, drawnField, page.width)
|
||||||
@ -428,7 +454,11 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
style={{
|
style={{
|
||||||
background:
|
background:
|
||||||
mouseState.resizing &&
|
mouseState.resizing &&
|
||||||
activeDrawField === drawnFieldIndex
|
isActiveDrawnField(
|
||||||
|
fileIndex,
|
||||||
|
pageIndex,
|
||||||
|
drawnFieldIndex
|
||||||
|
)
|
||||||
? 'var(--primary-main)'
|
? 'var(--primary-main)'
|
||||||
: undefined
|
: undefined
|
||||||
}}
|
}}
|
||||||
@ -446,56 +476,59 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
>
|
>
|
||||||
<Close fontSize="small" />
|
<Close fontSize="small" />
|
||||||
</span>
|
</span>
|
||||||
<div
|
{!isActiveDrawnField(
|
||||||
onPointerDown={handleUserSelectPointerDown}
|
fileIndex,
|
||||||
className={styles.userSelect}
|
pageIndex,
|
||||||
>
|
drawnFieldIndex
|
||||||
<FormControl fullWidth size="small">
|
) &&
|
||||||
<InputLabel id="counterparts">Counterpart</InputLabel>
|
!!drawnField.counterpart && (
|
||||||
<Select
|
<div className={styles.counterpartAvatar}>
|
||||||
value={drawnField.counterpart}
|
<UserAvatar
|
||||||
onChange={(event) => {
|
pubkey={npubToHex(drawnField.counterpart)!}
|
||||||
drawnField.counterpart = event.target.value
|
/>
|
||||||
refreshPdfFiles()
|
</div>
|
||||||
}}
|
)}
|
||||||
labelId="counterparts"
|
{isActiveDrawnField(
|
||||||
label="Counterparts"
|
fileIndex,
|
||||||
sx={{
|
pageIndex,
|
||||||
background: 'white'
|
drawnFieldIndex
|
||||||
}}
|
) && (
|
||||||
renderValue={(value) => renderCounterpartValue(value)}
|
<div
|
||||||
>
|
onPointerDown={handleUserSelectPointerDown}
|
||||||
{users
|
className={styles.userSelect}
|
||||||
.filter((u) => u.role === UserRole.signer)
|
>
|
||||||
.map((user, index) => {
|
<FormControl fullWidth size="small">
|
||||||
const npub = hexToNpub(user.pubkey)
|
<InputLabel id="counterparts">Counterpart</InputLabel>
|
||||||
let displayValue = truncate(npub, {
|
<Select
|
||||||
length: 16
|
value={getAvailableSigner(drawnField.counterpart)}
|
||||||
})
|
onChange={(event) => {
|
||||||
|
drawnField.counterpart = event.target.value
|
||||||
const metadata = props.metadata[user.pubkey]
|
setLastSigner(event.target.value)
|
||||||
|
refreshPdfFiles()
|
||||||
if (metadata) {
|
}}
|
||||||
displayValue = truncate(
|
labelId="counterparts"
|
||||||
metadata.name ||
|
label="Counterparts"
|
||||||
metadata.display_name ||
|
sx={{
|
||||||
metadata.username ||
|
background: 'white'
|
||||||
npub,
|
}}
|
||||||
{
|
renderValue={(value) =>
|
||||||
length: 16
|
renderCounterpartValue(value)
|
||||||
}
|
}
|
||||||
)
|
>
|
||||||
}
|
{signers.map((signer, index) => {
|
||||||
|
const npub = hexToNpub(signer.pubkey)
|
||||||
|
const metadata = props.metadata[signer.pubkey]
|
||||||
|
const displayValue = getProfileUsername(
|
||||||
|
npub,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem key={index} value={npub}>
|
||||||
key={index}
|
|
||||||
value={hexToNpub(user.pubkey)}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<AvatarIconButton
|
<AvatarIconButton
|
||||||
src={metadata?.picture}
|
src={metadata?.picture}
|
||||||
hexKey={user.pubkey}
|
hexKey={signer.pubkey}
|
||||||
aria-label={`account of user ${displayValue}`}
|
aria-label={`account of user ${displayValue}`}
|
||||||
color="inherit"
|
color="inherit"
|
||||||
sx={{
|
sx={{
|
||||||
@ -511,9 +544,10 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -524,28 +558,19 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderCounterpartValue = (value: string) => {
|
const renderCounterpartValue = (npub: string) => {
|
||||||
const user = users.find((u) => u.pubkey === npubToHex(value))
|
let displayValue = _.truncate(npub, { length: 16 })
|
||||||
if (user) {
|
|
||||||
let displayValue = truncate(value, {
|
|
||||||
length: 16
|
|
||||||
})
|
|
||||||
|
|
||||||
const metadata = props.metadata[user.pubkey]
|
const signer = signers.find((u) => u.pubkey === npubToHex(npub))
|
||||||
|
if (signer) {
|
||||||
|
const metadata = props.metadata[signer.pubkey]
|
||||||
|
displayValue = getProfileUsername(npub, metadata)
|
||||||
|
|
||||||
if (metadata) {
|
|
||||||
displayValue = truncate(
|
|
||||||
metadata.name || metadata.display_name || metadata.username || value,
|
|
||||||
{
|
|
||||||
length: 16
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={styles.counterpartSelectValue}>
|
||||||
<AvatarIconButton
|
<AvatarIconButton
|
||||||
src={props.metadata[user.pubkey]?.picture}
|
src={props.metadata[signer.pubkey]?.picture}
|
||||||
hexKey={npubToHex(user.pubkey) || undefined}
|
hexKey={signer.pubkey || undefined}
|
||||||
sx={{
|
sx={{
|
||||||
padding: 0,
|
padding: 0,
|
||||||
marginRight: '6px',
|
marginRight: '6px',
|
||||||
@ -556,19 +581,11 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{displayValue}
|
{displayValue}
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return displayValue
|
||||||
}
|
|
||||||
|
|
||||||
if (parsingPdf) {
|
|
||||||
return <LoadingSpinner variant="small" />
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sigitFiles.length) {
|
|
||||||
return ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -589,7 +606,7 @@ export const DrawPDFFields = (props: Props) => {
|
|||||||
<ExtensionFileBox extension={file.extension} />
|
<ExtensionFileBox extension={file.extension} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{i < selectedFiles.length - 1 && <FileDivider />}
|
{i < sigitFiles.length - 1 && <FileDivider />}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
@ -84,3 +84,14 @@
|
|||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.counterpartSelectValue {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counterpartAvatar {
|
||||||
|
img {
|
||||||
|
width: 21px;
|
||||||
|
height: 21px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { AvatarIconButton } from '../UserAvatarIconButton'
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { useProfileMetadata } from '../../hooks/useProfileMetadata'
|
import { useProfileMetadata } from '../../hooks/useProfileMetadata'
|
||||||
import { Tooltip } from '@mui/material'
|
import { Tooltip } from '@mui/material'
|
||||||
import { shorten } from '../../utils'
|
import { getProfileUsername } from '../../utils'
|
||||||
import { TooltipChild } from '../TooltipChild'
|
import { TooltipChild } from '../TooltipChild'
|
||||||
|
|
||||||
interface UserAvatarProps {
|
interface UserAvatarProps {
|
||||||
@ -22,7 +22,7 @@ export const UserAvatar = ({
|
|||||||
isNameVisible = false
|
isNameVisible = false
|
||||||
}: UserAvatarProps) => {
|
}: UserAvatarProps) => {
|
||||||
const profile = useProfileMetadata(pubkey)
|
const profile = useProfileMetadata(pubkey)
|
||||||
const name = profile?.display_name || profile?.name || shorten(pubkey)
|
const name = getProfileUsername(pubkey, profile)
|
||||||
const image = profile?.picture
|
const image = profile?.picture
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -39,7 +39,8 @@ import {
|
|||||||
signEventForMetaFile,
|
signEventForMetaFile,
|
||||||
updateUsersAppData,
|
updateUsersAppData,
|
||||||
uploadToFileStorage,
|
uploadToFileStorage,
|
||||||
DEFAULT_TOOLBOX
|
DEFAULT_TOOLBOX,
|
||||||
|
settleAllFullfilfedPromises
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import fileListStyles from '../../components/FileList/style.module.scss'
|
import fileListStyles from '../../components/FileList/style.module.scss'
|
||||||
@ -60,7 +61,8 @@ import {
|
|||||||
faTrash,
|
faTrash,
|
||||||
faUpload
|
faUpload
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { SigitFile } from '../../utils/file.ts'
|
import { getSigitFile, SigitFile } from '../../utils/file.ts'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
export const CreatePage = () => {
|
export const CreatePage = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -106,9 +108,31 @@ export const CreatePage = () => {
|
|||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
const [drawnFiles, setDrawnFiles] = useState<SigitFile[]>([])
|
const [drawnFiles, setDrawnFiles] = useState<SigitFile[]>([])
|
||||||
|
const [parsingPdf, setIsParsing] = useState<boolean>(false)
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedFiles) {
|
||||||
|
/**
|
||||||
|
* Reads the binary files and converts to internal file type
|
||||||
|
* and sets to a state (adds images if it's a PDF)
|
||||||
|
*/
|
||||||
|
const parsePages = async () => {
|
||||||
|
const files = await settleAllFullfilfedPromises(
|
||||||
|
selectedFiles,
|
||||||
|
getSigitFile
|
||||||
|
)
|
||||||
|
|
||||||
|
setDrawnFiles(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsParsing(true)
|
||||||
|
|
||||||
|
parsePages().finally(() => {
|
||||||
|
setIsParsing(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [selectedFiles])
|
||||||
|
|
||||||
const [selectedTool, setSelectedTool] = useState<DrawTool>()
|
const [selectedTool, setSelectedTool] = useState<DrawTool>()
|
||||||
const [toolbox] = useState<DrawTool[]>(DEFAULT_TOOLBOX)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the drawing tool
|
* Changes the drawing tool
|
||||||
@ -282,6 +306,19 @@ export const CreatePage = () => {
|
|||||||
|
|
||||||
const handleRemoveUser = (pubkey: string) => {
|
const handleRemoveUser = (pubkey: string) => {
|
||||||
setUsers((prev) => prev.filter((user) => user.pubkey !== pubkey))
|
setUsers((prev) => prev.filter((user) => user.pubkey !== pubkey))
|
||||||
|
|
||||||
|
// Set counterpart to ''
|
||||||
|
const drawnFilesCopy = _.cloneDeep(drawnFiles)
|
||||||
|
drawnFilesCopy.forEach((s) => {
|
||||||
|
s.pages?.forEach((p) => {
|
||||||
|
p.drawnFields.forEach((d) => {
|
||||||
|
if (d.counterpart === hexToNpub(pubkey)) {
|
||||||
|
d.counterpart = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setDrawnFiles(drawnFilesCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -724,10 +761,6 @@ export const CreatePage = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDrawFieldsChange = (sigitFiles: SigitFile[]) => {
|
|
||||||
setDrawnFiles(sigitFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authUrl) {
|
if (authUrl) {
|
||||||
return (
|
return (
|
||||||
<iframe
|
<iframe
|
||||||
@ -841,28 +874,36 @@ export const CreatePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`${styles.paperGroup} ${styles.toolbox}`}>
|
<div className={`${styles.paperGroup} ${styles.toolbox}`}>
|
||||||
{toolbox.map((drawTool: DrawTool, index: number) => {
|
{DEFAULT_TOOLBOX.filter((drawTool) => !drawTool.isHidden).map(
|
||||||
return (
|
(drawTool: DrawTool, index: number) => {
|
||||||
<div
|
return (
|
||||||
key={index}
|
<div
|
||||||
{...(drawTool.active && {
|
key={index}
|
||||||
onClick: () => handleToolSelect(drawTool)
|
{...(!drawTool.isComingSoon && {
|
||||||
})}
|
onClick: () => handleToolSelect(drawTool)
|
||||||
className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''} ${!drawTool.active ? styles.comingSoon : ''}
|
})}
|
||||||
|
className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''} ${drawTool.isComingSoon ? styles.comingSoon : ''}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon fontSize={'15px'} icon={drawTool.icon} />
|
<FontAwesomeIcon
|
||||||
{drawTool.label}
|
fontSize={'15px'}
|
||||||
{drawTool.active ? (
|
icon={drawTool.icon}
|
||||||
<FontAwesomeIcon fontSize={'15px'} icon={faEllipsis} />
|
/>
|
||||||
) : (
|
{drawTool.label}
|
||||||
<span className={styles.comingSoonPlaceholder}>
|
{!drawTool.isComingSoon ? (
|
||||||
Coming soon
|
<FontAwesomeIcon
|
||||||
</span>
|
fontSize={'15px'}
|
||||||
)}
|
icon={faEllipsis}
|
||||||
</div>
|
/>
|
||||||
)
|
) : (
|
||||||
})}
|
<span className={styles.comingSoonPlaceholder}>
|
||||||
|
Coming soon
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button onClick={handleCreate} variant="contained">
|
<Button onClick={handleCreate} variant="contained">
|
||||||
@ -878,13 +919,17 @@ export const CreatePage = () => {
|
|||||||
centerIcon={faFile}
|
centerIcon={faFile}
|
||||||
rightIcon={faToolbox}
|
rightIcon={faToolbox}
|
||||||
>
|
>
|
||||||
<DrawPDFFields
|
{parsingPdf ? (
|
||||||
metadata={metadata}
|
<LoadingSpinner variant="small" />
|
||||||
users={users}
|
) : (
|
||||||
selectedFiles={selectedFiles}
|
<DrawPDFFields
|
||||||
onDrawFieldsChange={onDrawFieldsChange}
|
users={users}
|
||||||
selectedTool={selectedTool}
|
metadata={metadata}
|
||||||
/>
|
selectedTool={selectedTool}
|
||||||
|
sigitFiles={drawnFiles}
|
||||||
|
setSigitFiles={setDrawnFiles}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</StickySideColumns>
|
</StickySideColumns>
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
||||||
import EditIcon from '@mui/icons-material/Edit'
|
import EditIcon from '@mui/icons-material/Edit'
|
||||||
import { Box, IconButton, SxProps, Theme, Typography } from '@mui/material'
|
import { Box, IconButton, SxProps, Theme, Typography } from '@mui/material'
|
||||||
import { truncate } from 'lodash'
|
|
||||||
import { Event, VerifiedEvent, kinds, nip19 } from 'nostr-tools'
|
import { Event, VerifiedEvent, kinds, nip19 } from 'nostr-tools'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
@ -14,6 +13,7 @@ import { State } from '../../store/rootReducer'
|
|||||||
import { NostrJoiningBlock, ProfileMetadata } from '../../types'
|
import { NostrJoiningBlock, ProfileMetadata } from '../../types'
|
||||||
import {
|
import {
|
||||||
getNostrJoiningBlockNumber,
|
getNostrJoiningBlockNumber,
|
||||||
|
getProfileUsername,
|
||||||
getRoboHashPicture,
|
getRoboHashPicture,
|
||||||
hexToNpub,
|
hexToNpub,
|
||||||
shorten
|
shorten
|
||||||
@ -42,15 +42,7 @@ export const ProfilePage = () => {
|
|||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [loadingSpinnerDesc] = useState('Fetching metadata')
|
const [loadingSpinnerDesc] = useState('Fetching metadata')
|
||||||
|
|
||||||
const profileName =
|
const profileName = pubkey && getProfileUsername(pubkey, profileMetadata)
|
||||||
pubkey &&
|
|
||||||
profileMetadata &&
|
|
||||||
truncate(
|
|
||||||
profileMetadata.display_name || profileMetadata.name || hexToNpub(pubkey),
|
|
||||||
{
|
|
||||||
length: 16
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (npub) {
|
if (npub) {
|
||||||
|
@ -31,7 +31,10 @@ export interface DrawTool {
|
|||||||
icon: IconDefinition
|
icon: IconDefinition
|
||||||
defaultValue?: string
|
defaultValue?: string
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
active?: boolean
|
/** show or hide the toolbox item */
|
||||||
|
isHidden?: boolean
|
||||||
|
/** show or hide "coming soon" message on the toolbox item */
|
||||||
|
isComingSoon?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MarkType {
|
export enum MarkType {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export interface ProfileMetadata {
|
export interface ProfileMetadata {
|
||||||
name?: string
|
name?: string
|
||||||
display_name?: string
|
display_name?: string
|
||||||
|
/** @deprecated use name instead */
|
||||||
username?: string
|
username?: string
|
||||||
picture?: string
|
picture?: string
|
||||||
banner?: string
|
banner?: string
|
||||||
|
@ -3,26 +3,26 @@ import { hexToNpub } from './nostr.ts'
|
|||||||
import { Meta, SignedEventContent } from '../types'
|
import { Meta, SignedEventContent } from '../types'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { EMPTY } from './const.ts'
|
import { EMPTY } from './const.ts'
|
||||||
import { MarkType } from '../types/drawing.ts'
|
import { DrawTool, MarkType } from '../types/drawing.ts'
|
||||||
import {
|
import {
|
||||||
faT,
|
faT,
|
||||||
faSignature,
|
faSignature,
|
||||||
faBriefcase,
|
faBriefcase,
|
||||||
faIdCard,
|
faIdCard,
|
||||||
faHeading,
|
|
||||||
faClock,
|
faClock,
|
||||||
faCalendarDays,
|
|
||||||
fa1,
|
fa1,
|
||||||
faImage,
|
faCalendarDays,
|
||||||
faSquareCheck,
|
|
||||||
faCheckDouble,
|
faCheckDouble,
|
||||||
faPaperclip,
|
|
||||||
faCircleDot,
|
faCircleDot,
|
||||||
faSquareCaretDown,
|
|
||||||
faTableCellsLarge,
|
|
||||||
faStamp,
|
|
||||||
faCreditCard,
|
faCreditCard,
|
||||||
faPhone
|
faHeading,
|
||||||
|
faImage,
|
||||||
|
faPaperclip,
|
||||||
|
faPhone,
|
||||||
|
faSquareCaretDown,
|
||||||
|
faSquareCheck,
|
||||||
|
faStamp,
|
||||||
|
faTableCellsLarge
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,114 +152,113 @@ const findOtherUserMarks = (marks: Mark[], pubkey: string): Mark[] => {
|
|||||||
return marks.filter((mark) => mark.npub !== hexToNpub(pubkey))
|
return marks.filter((mark) => mark.npub !== hexToNpub(pubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_TOOLBOX = [
|
export const DEFAULT_TOOLBOX: DrawTool[] = [
|
||||||
{
|
{
|
||||||
identifier: MarkType.TEXT,
|
identifier: MarkType.FULLNAME,
|
||||||
icon: faT,
|
icon: faIdCard,
|
||||||
label: 'Text',
|
label: 'Full Name',
|
||||||
active: true
|
isComingSoon: true
|
||||||
},
|
|
||||||
{
|
|
||||||
identifier: MarkType.SIGNATURE,
|
|
||||||
icon: faSignature,
|
|
||||||
label: 'Signature',
|
|
||||||
active: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.JOBTITLE,
|
identifier: MarkType.JOBTITLE,
|
||||||
icon: faBriefcase,
|
icon: faBriefcase,
|
||||||
label: 'Job Title',
|
label: 'Job Title',
|
||||||
active: false
|
isComingSoon: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.FULLNAME,
|
identifier: MarkType.SIGNATURE,
|
||||||
icon: faIdCard,
|
icon: faSignature,
|
||||||
label: 'Full Name',
|
label: 'Signature',
|
||||||
active: false
|
isComingSoon: true
|
||||||
},
|
|
||||||
{
|
|
||||||
identifier: MarkType.INITIALS,
|
|
||||||
icon: faHeading,
|
|
||||||
label: 'Initials',
|
|
||||||
active: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.DATETIME,
|
identifier: MarkType.DATETIME,
|
||||||
icon: faClock,
|
icon: faClock,
|
||||||
label: 'Date Time',
|
label: 'Date Time',
|
||||||
active: false
|
isComingSoon: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
identifier: MarkType.DATE,
|
identifier: MarkType.TEXT,
|
||||||
icon: faCalendarDays,
|
icon: faT,
|
||||||
label: 'Date',
|
label: 'Text'
|
||||||
active: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.NUMBER,
|
identifier: MarkType.NUMBER,
|
||||||
icon: fa1,
|
icon: fa1,
|
||||||
label: 'Number',
|
label: 'Number',
|
||||||
active: false
|
isComingSoon: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
identifier: MarkType.INITIALS,
|
||||||
|
icon: faHeading,
|
||||||
|
label: 'Initials',
|
||||||
|
isHidden: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
identifier: MarkType.DATE,
|
||||||
|
icon: faCalendarDays,
|
||||||
|
label: 'Date',
|
||||||
|
isHidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.IMAGES,
|
identifier: MarkType.IMAGES,
|
||||||
icon: faImage,
|
icon: faImage,
|
||||||
label: 'Images',
|
label: 'Images',
|
||||||
active: false
|
isHidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.CHECKBOX,
|
identifier: MarkType.CHECKBOX,
|
||||||
icon: faSquareCheck,
|
icon: faSquareCheck,
|
||||||
label: 'Checkbox',
|
label: 'Checkbox',
|
||||||
active: false
|
isHidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.MULTIPLE,
|
identifier: MarkType.MULTIPLE,
|
||||||
icon: faCheckDouble,
|
icon: faCheckDouble,
|
||||||
label: 'Multiple',
|
label: 'Multiple',
|
||||||
active: false
|
isHidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.FILE,
|
identifier: MarkType.FILE,
|
||||||
icon: faPaperclip,
|
icon: faPaperclip,
|
||||||
label: 'File',
|
label: 'File',
|
||||||
active: false
|
isHidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.RADIO,
|
identifier: MarkType.RADIO,
|
||||||
icon: faCircleDot,
|
icon: faCircleDot,
|
||||||
label: 'Radio',
|
label: 'Radio',
|
||||||
active: false
|
isHidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.SELECT,
|
identifier: MarkType.SELECT,
|
||||||
icon: faSquareCaretDown,
|
icon: faSquareCaretDown,
|
||||||
label: 'Select',
|
label: 'Select',
|
||||||
active: false
|
isHidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.CELLS,
|
identifier: MarkType.CELLS,
|
||||||
icon: faTableCellsLarge,
|
icon: faTableCellsLarge,
|
||||||
label: 'Cells',
|
label: 'Cells',
|
||||||
active: false
|
isHidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.STAMP,
|
identifier: MarkType.STAMP,
|
||||||
icon: faStamp,
|
icon: faStamp,
|
||||||
label: 'Stamp',
|
label: 'Stamp',
|
||||||
active: false
|
isHidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.PAYMENT,
|
identifier: MarkType.PAYMENT,
|
||||||
icon: faCreditCard,
|
icon: faCreditCard,
|
||||||
label: 'Payment',
|
label: 'Payment',
|
||||||
active: false
|
isHidden: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.PHONE,
|
identifier: MarkType.PHONE,
|
||||||
icon: faPhone,
|
icon: faPhone,
|
||||||
label: 'Phone',
|
label: 'Phone',
|
||||||
active: false
|
isHidden: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import _ from 'lodash'
|
import _, { truncate } from 'lodash'
|
||||||
import {
|
import {
|
||||||
Event,
|
Event,
|
||||||
EventTemplate,
|
EventTemplate,
|
||||||
@ -30,7 +30,7 @@ import {
|
|||||||
import { AuthState, Keys } from '../store/auth/types'
|
import { AuthState, Keys } from '../store/auth/types'
|
||||||
import { RelaysState } from '../store/relays/types'
|
import { RelaysState } from '../store/relays/types'
|
||||||
import store from '../store/store'
|
import store from '../store/store'
|
||||||
import { Meta, SignedEvent, UserAppData } from '../types'
|
import { Meta, ProfileMetadata, SignedEvent, UserAppData } from '../types'
|
||||||
import { getDefaultRelayMap } from './relays'
|
import { getDefaultRelayMap } from './relays'
|
||||||
import { parseJson, removeLeadingSlash } from './string'
|
import { parseJson, removeLeadingSlash } from './string'
|
||||||
import { timeout } from './utils'
|
import { timeout } from './utils'
|
||||||
@ -974,3 +974,16 @@ export const sendNotification = async (receiver: string, meta: Meta) => {
|
|||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show user's name, first available in order: display_name, name, or npub as fallback
|
||||||
|
* @param npub User identifier, it can be either pubkey or npub1 (we only show npub)
|
||||||
|
* @param profile User profile
|
||||||
|
*/
|
||||||
|
export const getProfileUsername = (
|
||||||
|
npub: `npub1${string}` | string,
|
||||||
|
profile?: ProfileMetadata
|
||||||
|
) =>
|
||||||
|
truncate(profile?.display_name || profile?.name || hexToNpub(npub), {
|
||||||
|
length: 16
|
||||||
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user
Can we rely on the
active
flag rather than commenting out?The active flag is for
coming soon
currently, and instead of adding a new flag to differentiate between coming soon and visually hidden, I opted to comment out.Can you please add a comment to capture the difference in the codebase?
Okay, made it clearer with new property names,
isHidden
andisComingSoon
so there is no confusion as withactive
(also removed comments and usedisHidden
instead).