Compare commits

..

No commits in common. "2e1d48168a93471cae72db9c5c78287809b23f58" and "9bae5b9ba236ac1119ca6715ff04e8c346718a5d" have entirely different histories.

8 changed files with 169 additions and 236 deletions

View File

@ -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,8 +58,9 @@ export const AppBar = () => {
useEffect(() => { useEffect(() => {
if (metadataState) { if (metadataState) {
if (metadataState.content) { if (metadataState.content) {
const profileMetadata = JSON.parse(metadataState.content) const { picture, display_name, name } = JSON.parse(
const { picture } = profileMetadata metadataState.content
)
if (picture || userRobotImage) { if (picture || userRobotImage) {
setUserAvatar(picture || userRobotImage) setUserAvatar(picture || userRobotImage)
@ -69,7 +70,7 @@ export const AppBar = () => {
? hexToNpub(authState.usersPubkey) ? hexToNpub(authState.usersPubkey)
: '' : ''
setUsername(getProfileUsername(npub, profileMetadata)) setUsername(shorten(display_name || name || npub, 7))
} else { } else {
setUserAvatar(userRobotImage || '') setUserAvatar(userRobotImage || '')
setUsername('') setUsername('')

View File

@ -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 { hexToNpub, npubToHex, getProfileUsername } from '../../utils' import { truncate } from 'lodash'
import { SigitFile } from '../../utils/file' import { settleAllFullfilfedPromises, hexToNpub, npubToHex } from '../../utils'
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 { UserAvatar } from '../UserAvatar' import { LoadingSpinner } from '../LoadingSpinner'
import _ from 'lodash'
const DEFAULT_START_SIZE = { const DEFAULT_START_SIZE = {
width: 140, width: 140,
@ -28,50 +28,55 @@ 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 }
sigitFiles: SigitFile[] onDrawFieldsChange: (sigitFiles: SigitFile[]) => void
setSigitFiles: React.Dispatch<React.SetStateAction<SigitFile[]>>
selectedTool?: DrawTool selectedTool?: DrawTool
} }
export const DrawPDFFields = (props: Props) => { export const DrawPDFFields = (props: Props) => {
const { selectedTool, sigitFiles, setSigitFiles, users } = props const { selectedFiles, selectedTool, onDrawFieldsChange, users } = props
const signers = users.filter((u) => u.role === UserRole.signer) const signers = users.filter((u) => u.role === UserRole.signer)
const defaultSignerNpub = signers.length ? hexToNpub(signers[0].pubkey) : '' const defaultSignerNpub = signers.length ? hexToNpub(signers[0].pubkey) : ''
const [lastSigner, setLastSigner] = useState(defaultSignerNpub) 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 { to, from } = useScale()
const [sigitFiles, setSigitFiles] = useState<SigitFile[]>([])
const [parsingPdf, setIsParsing] = useState<boolean>(false)
const [mouseState, setMouseState] = useState<MouseState>({ const [mouseState, setMouseState] = useState<MouseState>({
clicked: false clicked: false
}) })
const [activeDrawnField, setActiveDrawnField] = useState<{ const [activeDrawField, setActiveDrawField] = useState<number>()
fileIndex: number
pageIndex: number useEffect(() => {
drawnFieldIndex: number if (selectedFiles) {
}>() /**
const isActiveDrawnField = ( * Reads the binary files and converts to internal file type
fileIndex: number, * and sets to a state (adds images if it's a PDF)
pageIndex: number, */
drawnFieldIndex: number const parsePages = async () => {
) => const files = await settleAllFullfilfedPromises(
activeDrawnField?.fileIndex === fileIndex && selectedFiles,
activeDrawnField?.pageIndex === pageIndex && getSigitFile
activeDrawnField?.drawnFieldIndex === drawnFieldIndex )
setSigitFiles(files)
}
setIsParsing(true)
parsePages().finally(() => {
setIsParsing(false)
})
}
}, [selectedFiles])
useEffect(() => {
if (sigitFiles) onDrawFieldsChange(sigitFiles)
}, [onDrawFieldsChange, sigitFiles])
/** /**
* Drawing events * Drawing events
@ -98,12 +103,7 @@ 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 = ( const handlePointerDown = (event: React.PointerEvent, page: PdfPage) => {
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
@ -118,17 +118,12 @@ 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: getAvailableSigner(lastSigner, defaultSignerNpub), counterpart: lastSigner,
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,
@ -197,8 +192,6 @@ 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()
@ -208,7 +201,7 @@ export const DrawPDFFields = (props: Props) => {
const drawingRectangleCoords = getPointerCoordinates(event) const drawingRectangleCoords = getPointerCoordinates(event)
setActiveDrawnField({ fileIndex, pageIndex, drawnFieldIndex }) setActiveDrawField(drawnFieldIndex)
setMouseState({ setMouseState({
dragging: true, dragging: true,
clicked: false, clicked: false,
@ -264,15 +257,13 @@ 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()
setActiveDrawnField({ fileIndex, pageIndex, drawnFieldIndex }) setActiveDrawField(drawnFieldIndex)
setMouseState({ setMouseState({
resizing: true resizing: true
}) })
@ -381,7 +372,7 @@ export const DrawPDFFields = (props: Props) => {
handlePointerMove(event, page) handlePointerMove(event, page)
}} }}
onPointerDown={(event) => { onPointerDown={(event) => {
handlePointerDown(event, page, fileIndex, pageIndex) handlePointerDown(event, page)
}} }}
draggable="false" draggable="false"
src={page.image} src={page.image}
@ -393,12 +384,7 @@ export const DrawPDFFields = (props: Props) => {
<div <div
key={drawnFieldIndex} key={drawnFieldIndex}
onPointerDown={(event) => onPointerDown={(event) =>
handleDrawnFieldPointerDown( handleDrawnFieldPointerDown(event, drawnFieldIndex)
event,
fileIndex,
pageIndex,
drawnFieldIndex
)
} }
onPointerMove={(event) => { onPointerMove={(event) => {
handleDrawnFieldPointerMove(event, drawnField, page.width) handleDrawnFieldPointerMove(event, drawnField, page.width)
@ -419,11 +405,7 @@ export const DrawPDFFields = (props: Props) => {
touchAction: 'none', touchAction: 'none',
opacity: opacity:
mouseState.dragging && mouseState.dragging &&
isActiveDrawnField( activeDrawField === drawnFieldIndex
fileIndex,
pageIndex,
drawnFieldIndex
)
? 0.8 ? 0.8
: undefined : undefined
}} }}
@ -440,12 +422,7 @@ export const DrawPDFFields = (props: Props) => {
</div> </div>
<span <span
onPointerDown={(event) => onPointerDown={(event) =>
handleResizePointerDown( handleResizePointerDown(event, drawnFieldIndex)
event,
fileIndex,
pageIndex,
drawnFieldIndex
)
} }
onPointerMove={(event) => { onPointerMove={(event) => {
handleResizePointerMove(event, drawnField, page.width) handleResizePointerMove(event, drawnField, page.width)
@ -454,11 +431,7 @@ export const DrawPDFFields = (props: Props) => {
style={{ style={{
background: background:
mouseState.resizing && mouseState.resizing &&
isActiveDrawnField( activeDrawField === drawnFieldIndex
fileIndex,
pageIndex,
drawnFieldIndex
)
? 'var(--primary-main)' ? 'var(--primary-main)'
: undefined : undefined
}} }}
@ -476,23 +449,6 @@ export const DrawPDFFields = (props: Props) => {
> >
<Close fontSize="small" /> <Close fontSize="small" />
</span> </span>
{!isActiveDrawnField(
fileIndex,
pageIndex,
drawnFieldIndex
) &&
!!drawnField.counterpart && (
<div className={styles.counterpartAvatar}>
<UserAvatar
pubkey={npubToHex(drawnField.counterpart)!}
/>
</div>
)}
{isActiveDrawnField(
fileIndex,
pageIndex,
drawnFieldIndex
) && (
<div <div
onPointerDown={handleUserSelectPointerDown} onPointerDown={handleUserSelectPointerDown}
className={styles.userSelect} className={styles.userSelect}
@ -500,7 +456,12 @@ export const DrawPDFFields = (props: Props) => {
<FormControl fullWidth size="small"> <FormControl fullWidth size="small">
<InputLabel id="counterparts">Counterpart</InputLabel> <InputLabel id="counterparts">Counterpart</InputLabel>
<Select <Select
value={getAvailableSigner(drawnField.counterpart)} value={
drawnField.counterpart ||
lastSigner ||
defaultSignerNpub ||
''
}
onChange={(event) => { onChange={(event) => {
drawnField.counterpart = event.target.value drawnField.counterpart = event.target.value
setLastSigner(event.target.value) setLastSigner(event.target.value)
@ -511,17 +472,27 @@ export const DrawPDFFields = (props: Props) => {
sx={{ sx={{
background: 'white' background: 'white'
}} }}
renderValue={(value) => renderValue={(value) => renderCounterpartValue(value)}
renderCounterpartValue(value)
}
> >
{signers.map((signer, index) => { {signers.map((signer, index) => {
const npub = hexToNpub(signer.pubkey) const npub = hexToNpub(signer.pubkey)
let displayValue = truncate(npub, {
length: 16
})
const metadata = props.metadata[signer.pubkey] const metadata = props.metadata[signer.pubkey]
const displayValue = getProfileUsername(
if (metadata) {
displayValue = truncate(
metadata.name ||
metadata.display_name ||
metadata.username ||
npub, npub,
metadata {
length: 16
}
) )
}
return ( return (
<MenuItem key={index} value={npub}> <MenuItem key={index} value={npub}>
@ -547,7 +518,6 @@ export const DrawPDFFields = (props: Props) => {
</Select> </Select>
</FormControl> </FormControl>
</div> </div>
)}
</div> </div>
) )
})} })}
@ -559,15 +529,24 @@ export const DrawPDFFields = (props: Props) => {
} }
const renderCounterpartValue = (npub: string) => { const renderCounterpartValue = (npub: string) => {
let displayValue = _.truncate(npub, { length: 16 }) let displayValue = truncate(npub, {
length: 16
})
const signer = signers.find((u) => u.pubkey === npubToHex(npub)) const signer = signers.find((u) => u.pubkey === npubToHex(npub))
if (signer) { if (signer) {
const metadata = props.metadata[signer.pubkey] const metadata = props.metadata[signer.pubkey]
displayValue = getProfileUsername(npub, metadata) if (metadata) {
displayValue = truncate(
metadata.display_name || metadata.name || npub,
{
length: 16
}
)
}
return ( return (
<div className={styles.counterpartSelectValue}> <>
<AvatarIconButton <AvatarIconButton
src={props.metadata[signer.pubkey]?.picture} src={props.metadata[signer.pubkey]?.picture}
hexKey={signer.pubkey || undefined} hexKey={signer.pubkey || undefined}
@ -581,13 +560,21 @@ export const DrawPDFFields = (props: Props) => {
}} }}
/> />
{displayValue} {displayValue}
</div> </>
) )
} }
return displayValue return displayValue
} }
if (parsingPdf) {
return <LoadingSpinner variant="small" />
}
if (!sigitFiles.length) {
return ''
}
return ( return (
<div className="files-wrapper"> <div className="files-wrapper">
{sigitFiles.map((file, i) => { {sigitFiles.map((file, i) => {
@ -606,7 +593,7 @@ export const DrawPDFFields = (props: Props) => {
<ExtensionFileBox extension={file.extension} /> <ExtensionFileBox extension={file.extension} />
)} )}
</div> </div>
{i < sigitFiles.length - 1 && <FileDivider />} {i < selectedFiles.length - 1 && <FileDivider />}
</React.Fragment> </React.Fragment>
) )
})} })}

View File

@ -84,14 +84,3 @@
padding: 5px 0; padding: 5px 0;
} }
} }
.counterpartSelectValue {
display: flex;
}
.counterpartAvatar {
img {
width: 21px;
height: 21px;
}
}

View File

@ -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 { getProfileUsername } from '../../utils' import { shorten } 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 = getProfileUsername(pubkey, profile) const name = profile?.display_name || profile?.name || shorten(pubkey)
const image = profile?.picture const image = profile?.picture
return ( return (

View File

@ -39,8 +39,7 @@ 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'
@ -61,8 +60,7 @@ import {
faTrash, faTrash,
faUpload faUpload
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { getSigitFile, SigitFile } from '../../utils/file.ts' import { SigitFile } from '../../utils/file.ts'
import _ from 'lodash'
export const CreatePage = () => { export const CreatePage = () => {
const navigate = useNavigate() const navigate = useNavigate()
@ -108,29 +106,6 @@ 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>()
@ -306,19 +281,6 @@ 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)
} }
/** /**
@ -761,6 +723,10 @@ export const CreatePage = () => {
} }
} }
const onDrawFieldsChange = (sigitFiles: SigitFile[]) => {
setDrawnFiles(sigitFiles)
}
if (authUrl) { if (authUrl) {
return ( return (
<iframe <iframe
@ -919,17 +885,13 @@ export const CreatePage = () => {
centerIcon={faFile} centerIcon={faFile}
rightIcon={faToolbox} rightIcon={faToolbox}
> >
{parsingPdf ? (
<LoadingSpinner variant="small" />
) : (
<DrawPDFFields <DrawPDFFields
users={users}
metadata={metadata} metadata={metadata}
users={users}
selectedFiles={selectedFiles}
onDrawFieldsChange={onDrawFieldsChange}
selectedTool={selectedTool} selectedTool={selectedTool}
sigitFiles={drawnFiles}
setSigitFiles={setDrawnFiles}
/> />
)}
</StickySideColumns> </StickySideColumns>
</Container> </Container>
</> </>

View File

@ -1,6 +1,7 @@
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'
@ -13,7 +14,6 @@ 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,7 +42,15 @@ 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 = pubkey && getProfileUsername(pubkey, profileMetadata) const profileName =
pubkey &&
profileMetadata &&
truncate(
profileMetadata.display_name || profileMetadata.name || hexToNpub(pubkey),
{
length: 16
}
)
useEffect(() => { useEffect(() => {
if (npub) { if (npub) {

View File

@ -1,7 +1,6 @@
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

View File

@ -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 _, { truncate } from 'lodash' import _ 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, ProfileMetadata, SignedEvent, UserAppData } from '../types' import { Meta, 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,16 +974,3 @@ 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
})