fix: #201, #145, #205 and additional fixes #206

Merged
enes merged 14 commits from 201-toolbox-update into staging 2024-09-30 14:20:07 +00:00
11 changed files with 318 additions and 230 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,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}>
<p className={styles.bannerText}>
SIGit is currently Alpha software (available for internal SIGit is currently Alpha software (available for internal
testing), use at your own risk! testing), use at your own risk!
</p>
<Button <Button
aria-label={`close banner`} aria-label={`close banner`}
variant="text" variant="text"

View File

@ -67,3 +67,9 @@
} }
} }
} }
.bannerText {
margin-left: 54px;
flex-grow: 1;
text-align: center;
}

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 { 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,6 +476,23 @@ 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}
@ -453,9 +500,10 @@ 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={drawnField.counterpart} value={getAvailableSigner(drawnField.counterpart)}
onChange={(event) => { onChange={(event) => {
drawnField.counterpart = event.target.value drawnField.counterpart = event.target.value
setLastSigner(event.target.value)
refreshPdfFiles() refreshPdfFiles()
}} }}
labelId="counterparts" labelId="counterparts"
@ -463,39 +511,24 @@ export const DrawPDFFields = (props: Props) => {
sx={{ sx={{
background: 'white' background: 'white'
}} }}
renderValue={(value) => renderCounterpartValue(value)} renderValue={(value) =>
renderCounterpartValue(value)
}
> >
{users {signers.map((signer, index) => {
.filter((u) => u.role === UserRole.signer) const npub = hexToNpub(signer.pubkey)
.map((user, index) => { const metadata = props.metadata[signer.pubkey]
const npub = hexToNpub(user.pubkey) const displayValue = getProfileUsername(
let displayValue = truncate(npub, {
length: 16
})
const metadata = props.metadata[user.pubkey]
if (metadata) {
displayValue = truncate(
metadata.name ||
metadata.display_name ||
metadata.username ||
npub, npub,
{ metadata
length: 16
}
) )
}
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={{
@ -514,6 +547,7 @@ export const DrawPDFFields = (props: Props) => {
</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>
) )
})} })}

View File

@ -84,3 +84,14 @@
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 { 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 (

View File

@ -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,20 +874,27 @@ 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(
(drawTool: DrawTool, index: number) => {
return ( return (
<div <div
key={index} key={index}
{...(drawTool.active && { {...(!drawTool.isComingSoon && {
onClick: () => handleToolSelect(drawTool) 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
fontSize={'15px'}
icon={drawTool.icon}
/>
{drawTool.label} {drawTool.label}
{drawTool.active ? ( {!drawTool.isComingSoon ? (
<FontAwesomeIcon fontSize={'15px'} icon={faEllipsis} /> <FontAwesomeIcon
fontSize={'15px'}
icon={faEllipsis}
/>
) : ( ) : (
<span className={styles.comingSoonPlaceholder}> <span className={styles.comingSoonPlaceholder}>
Coming soon Coming soon
@ -862,7 +902,8 @@ export const CreatePage = () => {
)} )}
</div> </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}
> >
{parsingPdf ? (
<LoadingSpinner variant="small" />
) : (
<DrawPDFFields <DrawPDFFields
metadata={metadata}
users={users} users={users}
selectedFiles={selectedFiles} metadata={metadata}
onDrawFieldsChange={onDrawFieldsChange}
selectedTool={selectedTool} selectedTool={selectedTool}
sigitFiles={drawnFiles}
setSigitFiles={setDrawnFiles}
/> />
)}
</StickySideColumns> </StickySideColumns>
</Container> </Container>
</> </>

View File

@ -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) {

View File

@ -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 {

View File

@ -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

View File

@ -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
}, },
{ {

Can we rely on the active flag rather than commenting out?

Can we rely on the `active` flag rather than commenting out?
Outdated
Review

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.

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?

Can you please add a comment to capture the difference in the codebase?
Outdated
Review

Okay, made it clearer with new property names, isHidden and isComingSoon so there is no confusion as with active (also removed comments and used isHidden instead).

Okay, made it clearer with new property names, `isHidden` and `isComingSoon` so there is no confusion as with `active` (also removed comments and used `isHidden` instead).
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
} }
] ]

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 _ 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
})