Counterparts section design update, touch dnd support, marks scrolling #182
79
package-lock.json
generated
79
package-lock.json
generated
@ -34,9 +34,10 @@
|
|||||||
"nostr-tools": "2.7.0",
|
"nostr-tools": "2.7.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^4.4.168",
|
"pdfjs-dist": "^4.4.168",
|
||||||
|
"rdndmb-html5-to-touch": "^8.0.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dnd": "16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "16.0.1",
|
"react-dnd-multi-backend": "^8.0.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-redux": "9.1.0",
|
"react-redux": "9.1.0",
|
||||||
@ -3267,6 +3268,19 @@
|
|||||||
"@babel/runtime": "^7.9.2"
|
"@babel/runtime": "^7.9.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dnd-multi-backend": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dnd-multi-backend/-/dnd-multi-backend-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-yFFARotr+OEJk787Fsj+V52pi6j7+Pt/CRp3IR2Ai3fnxA/z6J54T7+gxkXzXu4cvxTNE7NiBzzAaJ2f7JjFTw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/LouisBrunner"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"dnd-core": "^16.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/doctrine": {
|
"node_modules/doctrine": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||||
@ -5687,6 +5701,21 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/rdndmb-html5-to-touch": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/rdndmb-html5-to-touch/-/rdndmb-html5-to-touch-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-VfIbLjlL9NAnZzc2M5fGPCNkDyK12+ahgILGO5RjS7jkgUlxwB0c/XvxVQNfY/2ocg7isTY/G7tqxJk5fSTZAA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dnd-multi-backend": "^8.0.3",
|
||||||
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
|
"react-dnd-touch-backend": "^16.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/LouisBrunner"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
@ -5702,6 +5731,7 @@
|
|||||||
"version": "16.0.1",
|
"version": "16.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
|
||||||
"integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
|
"integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-dnd/invariant": "^4.0.1",
|
"@react-dnd/invariant": "^4.0.1",
|
||||||
"@react-dnd/shallowequal": "^4.0.1",
|
"@react-dnd/shallowequal": "^4.0.1",
|
||||||
@ -5731,10 +5761,55 @@
|
|||||||
"version": "16.0.1",
|
"version": "16.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
|
||||||
"integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
|
"integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dnd-core": "^16.0.1"
|
"dnd-core": "^16.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-dnd-multi-backend": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dnd-multi-backend/-/react-dnd-multi-backend-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-IwH7Mf6R05KIFohX0hHMTluoAvuUD8SO15KCD+9fY0nJ4nc1FGCMCSyMZw8R1XNStKp+JnNg3ZMtiaf5DebSUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dnd-multi-backend": "^8.0.3",
|
||||||
|
"react-dnd-preview": "^8.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/LouisBrunner"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"dnd-core": "^16.0.1",
|
||||||
|
"react": "^16.14.0 || ^17.0.2 || ^18.0.0",
|
||||||
|
"react-dnd": "^16.0.1",
|
||||||
|
"react-dom": "^16.14.0 || ^17.0.2 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dnd-preview": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dnd-preview/-/react-dnd-preview-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-s69Ro47QYDthDhj73iQ0VioMCjtlZ1AytKBDkQaHKm5DTjA8D2bIaFKCBQd330QEW0SIzqLJrZGCSlIY2xraJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/LouisBrunner"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.14.0 || ^17.0.2 || ^18.0.0",
|
||||||
|
"react-dnd": "^16.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dnd-touch-backend": {
|
||||||
|
"version": "16.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dnd-touch-backend/-/react-dnd-touch-backend-16.0.1.tgz",
|
||||||
|
"integrity": "sha512-NonoCABzzjyWGZuDxSG77dbgMZ2Wad7eQiCd/ECtsR2/NBLTjGksPUx9UPezZ1nQ/L7iD130Tz3RUshL/ClKLA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-dnd/invariant": "^4.0.1",
|
||||||
|
"dnd-core": "^16.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||||
|
@ -44,9 +44,10 @@
|
|||||||
"nostr-tools": "2.7.0",
|
"nostr-tools": "2.7.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^4.4.168",
|
"pdfjs-dist": "^4.4.168",
|
||||||
|
"rdndmb-html5-to-touch": "^8.0.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dnd": "16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "16.0.1",
|
"react-dnd-multi-backend": "^8.0.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-redux": "9.1.0",
|
"react-redux": "9.1.0",
|
||||||
|
@ -141,6 +141,8 @@ li {
|
|||||||
color: black;
|
color: black;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
scroll-margin-top: $header-height + $body-vertical-padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-dev='true'] {
|
[data-dev='true'] {
|
||||||
|
@ -2,6 +2,8 @@ import { CurrentUserMark } from '../../types/mark.ts'
|
|||||||
import styles from '../DrawPDFFields/style.module.scss'
|
import styles from '../DrawPDFFields/style.module.scss'
|
||||||
import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf.ts'
|
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 { npubToHex } from '../../utils/nostr.ts'
|
||||||
|
|
||||||
interface PdfMarkItemProps {
|
interface PdfMarkItemProps {
|
||||||
userMark: CurrentUserMark
|
userMark: CurrentUserMark
|
||||||
@ -14,13 +16,11 @@ interface PdfMarkItemProps {
|
|||||||
/**
|
/**
|
||||||
* Responsible for display an individual Pdf Mark.
|
* Responsible for display an individual Pdf Mark.
|
||||||
*/
|
*/
|
||||||
const PdfMarkItem = ({
|
const PdfMarkItem = forwardRef<HTMLDivElement, PdfMarkItemProps>(
|
||||||
selectedMark,
|
(
|
||||||
handleMarkClick,
|
{ selectedMark, handleMarkClick, selectedMarkValue, userMark, pageWidth },
|
||||||
selectedMarkValue,
|
ref
|
||||||
userMark,
|
) => {
|
||||||
pageWidth
|
|
||||||
}: PdfMarkItemProps) => {
|
|
||||||
const { location } = userMark.mark
|
const { location } = userMark.mark
|
||||||
const handleClick = () => handleMarkClick(userMark.mark.id)
|
const handleClick = () => handleMarkClick(userMark.mark.id)
|
||||||
const isEdited = () => selectedMark?.mark.id === userMark.mark.id
|
const isEdited = () => selectedMark?.mark.id === userMark.mark.id
|
||||||
@ -29,9 +29,16 @@ const PdfMarkItem = ({
|
|||||||
const { from } = useScale()
|
const { from } = useScale()
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={ref}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className={`file-mark ${styles.drawingRectangle} ${isEdited() && styles.edited}`}
|
className={`file-mark ${styles.drawingRectangle} ${isEdited() && styles.edited}`}
|
||||||
style={{
|
style={{
|
||||||
|
backgroundColor: selectedMark?.mark.npub
|
||||||
|
? `#${npubToHex(selectedMark?.mark.npub)?.substring(0, 6)}4b`
|
||||||
|
: undefined,
|
||||||
|
borderColor: selectedMark?.mark.npub
|
||||||
|
? `#${npubToHex(selectedMark?.mark.npub)?.substring(0, 6)}`
|
||||||
|
: undefined,
|
||||||
left: inPx(from(pageWidth, location.left)),
|
left: inPx(from(pageWidth, location.left)),
|
||||||
top: inPx(from(pageWidth, location.top)),
|
top: inPx(from(pageWidth, location.top)),
|
||||||
width: inPx(from(pageWidth, location.width)),
|
width: inPx(from(pageWidth, location.width)),
|
||||||
@ -43,6 +50,7 @@ const PdfMarkItem = ({
|
|||||||
{getMarkValue()}
|
{getMarkValue()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export default PdfMarkItem
|
export default PdfMarkItem
|
||||||
|
@ -48,16 +48,15 @@ const PdfPageItem = ({
|
|||||||
alt={`page ${pageIndex + 1} of ${fileName}`}
|
alt={`page ${pageIndex + 1} of ${fileName}`}
|
||||||
/>
|
/>
|
||||||
{currentUserMarks.map((m, i) => (
|
{currentUserMarks.map((m, i) => (
|
||||||
<div key={i} ref={(el) => (markRefs.current[m.id] = el)}>
|
|
||||||
<PdfMarkItem
|
<PdfMarkItem
|
||||||
key={i}
|
key={i}
|
||||||
|
ref={(el) => (markRefs.current[m.id] = el)}
|
||||||
handleMarkClick={handleMarkClick}
|
handleMarkClick={handleMarkClick}
|
||||||
selectedMarkValue={selectedMarkValue}
|
selectedMarkValue={selectedMarkValue}
|
||||||
userMark={m}
|
userMark={m}
|
||||||
selectedMark={selectedMark}
|
selectedMark={selectedMark}
|
||||||
pageWidth={page.width}
|
pageWidth={page.width}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
{otherUserMarks.map((m, i) => {
|
{otherUserMarks.map((m, i) => {
|
||||||
return (
|
return (
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
import {
|
import styles from './style.module.scss'
|
||||||
Button,
|
import { Button, FormHelperText, TextField, Tooltip } from '@mui/material'
|
||||||
FormHelperText,
|
|
||||||
ListItemIcon,
|
|
||||||
ListItemText,
|
|
||||||
MenuItem,
|
|
||||||
Select,
|
|
||||||
TextField,
|
|
||||||
Tooltip
|
|
||||||
} from '@mui/material'
|
|
||||||
import type { Identifier, XYCoord } from 'dnd-core'
|
import type { Identifier, XYCoord } from 'dnd-core'
|
||||||
import saveAs from 'file-saver'
|
import saveAs from 'file-saver'
|
||||||
import JSZip from 'jszip'
|
import JSZip from 'jszip'
|
||||||
import { Event, kinds } from 'nostr-tools'
|
import { Event, kinds } from 'nostr-tools'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { DndProvider, useDrag, useDrop } from 'react-dnd'
|
import { DndProvider, useDrag, useDrop } from 'react-dnd'
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend'
|
import { MultiBackend } from 'react-dnd-multi-backend'
|
||||||
|
import { HTML5toTouch } from 'rdndmb-html5-to-touch'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
@ -49,7 +42,6 @@ import {
|
|||||||
uploadToFileStorage
|
uploadToFileStorage
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import styles from './style.module.scss'
|
|
||||||
import fileListStyles from '../../components/FileList/style.module.scss'
|
import fileListStyles from '../../components/FileList/style.module.scss'
|
||||||
import { DrawTool, MarkType } from '../../types/drawing'
|
import { DrawTool, MarkType } from '../../types/drawing'
|
||||||
import { DrawPDFFields } from '../../components/DrawPDFFields'
|
import { DrawPDFFields } from '../../components/DrawPDFFields'
|
||||||
@ -874,21 +866,12 @@ export const CreatePage = () => {
|
|||||||
<div className={styles.flexWrap}>
|
<div className={styles.flexWrap}>
|
||||||
<div className={styles.inputWrapper}>
|
<div className={styles.inputWrapper}>
|
||||||
<TextField
|
<TextField
|
||||||
|
fullWidth
|
||||||
placeholder="Title"
|
placeholder="Title"
|
||||||
size="small"
|
size="small"
|
||||||
type="text"
|
type="text"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
fontSize: '16px',
|
|
||||||
'& .MuiInputBase-input': {
|
|
||||||
padding: '7px 14px'
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-notchedOutline': {
|
|
||||||
display: 'none'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ol className={`${styles.paperGroup} ${styles.orderedFilesList}`}>
|
<ol className={`${styles.paperGroup} ${styles.orderedFilesList}`}>
|
||||||
@ -907,9 +890,6 @@ export const CreatePage = () => {
|
|||||||
aria-label={`delete ${file.name}`}
|
aria-label={`delete ${file.name}`}
|
||||||
variant="text"
|
variant="text"
|
||||||
onClick={(event) => handleRemoveFile(event, file)}
|
onClick={(event) => handleRemoveFile(event, file)}
|
||||||
sx={{
|
|
||||||
minWidth: '44px'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faTrash} />
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
</Button>
|
</Button>
|
||||||
@ -932,76 +912,6 @@ export const CreatePage = () => {
|
|||||||
}
|
}
|
||||||
right={
|
right={
|
||||||
<div className={styles.flexWrap}>
|
<div className={styles.flexWrap}>
|
||||||
<div className={styles.inputWrapper}>
|
|
||||||
<TextField
|
|
||||||
placeholder="Add user"
|
|
||||||
value={userInput}
|
|
||||||
onChange={(e) => setUserInput(e.target.value)}
|
|
||||||
onKeyDown={handleInputKeyDown}
|
|
||||||
error={!!error}
|
|
||||||
fullWidth
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px',
|
|
||||||
'& .MuiInputBase-input': {
|
|
||||||
padding: '7px 14px'
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-notchedOutline': {
|
|
||||||
display: 'none'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
name="add-user-role"
|
|
||||||
aria-label="role"
|
|
||||||
value={userRole}
|
|
||||||
variant="filled"
|
|
||||||
// Hide arrow for dropdown
|
|
||||||
IconComponent={() => null}
|
|
||||||
renderValue={(value) => (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
color="var(--primary-main)"
|
|
||||||
icon={value === UserRole.signer ? faPen : faEye}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
onChange={(e) => setUserRole(e.target.value as UserRole)}
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px',
|
|
||||||
minWidth: '44px',
|
|
||||||
'& .MuiInputBase-input': {
|
|
||||||
padding: '7px 14px!important',
|
|
||||||
textOverflow: 'unset!important'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MenuItem value={UserRole.signer}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<FontAwesomeIcon icon={faPen} />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>{UserRole.signer}</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem value={UserRole.viewer} sx={{}}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<FontAwesomeIcon icon={faEye} />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>{UserRole.viewer}</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
</Select>
|
|
||||||
<Button
|
|
||||||
disabled={!userInput}
|
|
||||||
onClick={handleAddUser}
|
|
||||||
variant="contained"
|
|
||||||
aria-label="Add"
|
|
||||||
sx={{
|
|
||||||
minWidth: '44px',
|
|
||||||
padding: '11.5px 12px',
|
|
||||||
borderTopLeftRadius: 0,
|
|
||||||
borderBottomLeftRadius: 0
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faPlus} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={`${styles.paperGroup} ${styles.users}`}>
|
<div className={`${styles.paperGroup} ${styles.users}`}>
|
||||||
<DisplayUser
|
<DisplayUser
|
||||||
metadata={metadata}
|
metadata={metadata}
|
||||||
@ -1011,7 +921,43 @@ export const CreatePage = () => {
|
|||||||
moveSigner={moveSigner}
|
moveSigner={moveSigner}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.addCounterpart}>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
placeholder="Add counterpart"
|
||||||
|
value={userInput}
|
||||||
|
onChange={(e) => setUserInput(e.target.value)}
|
||||||
|
onKeyDown={handleInputKeyDown}
|
||||||
|
error={!!error}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
setUserRole(
|
||||||
|
userRole === UserRole.signer
|
||||||
|
? UserRole.viewer
|
||||||
|
: UserRole.signer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
variant="contained"
|
||||||
|
aria-label="Toggle User Role"
|
||||||
|
className={styles.counterpartToggleButton}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={userRole === UserRole.signer ? faPen : faEye}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={!userInput}
|
||||||
|
onClick={handleAddUser}
|
||||||
|
variant="contained"
|
||||||
|
aria-label="Add"
|
||||||
|
className={styles.counterpartToggleButton}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPlus} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<Button onClick={handleCreate} variant="contained">
|
<Button onClick={handleCreate} variant="contained">
|
||||||
Publish
|
Publish
|
||||||
</Button>
|
</Button>
|
||||||
@ -1032,11 +978,7 @@ export const CreatePage = () => {
|
|||||||
{drawTool.active ? (
|
{drawTool.active ? (
|
||||||
<FontAwesomeIcon fontSize={'15px'} icon={faEllipsis} />
|
<FontAwesomeIcon fontSize={'15px'} icon={faEllipsis} />
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span className={styles.comingSoonPlaceholder}>
|
||||||
style={{
|
|
||||||
fontSize: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Coming soon
|
Coming soon
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -1084,12 +1026,12 @@ const DisplayUser = ({
|
|||||||
}: DisplayUsersProps) => {
|
}: DisplayUsersProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider backend={MultiBackend} options={HTML5toTouch}>
|
||||||
{users
|
{users
|
||||||
.filter((user) => user.role === UserRole.signer)
|
.filter((user) => user.role === UserRole.signer)
|
||||||
.map((user, index) => (
|
.map((user, index) => (
|
||||||
<SignerRow
|
<SignerCounterpart
|
||||||
key={`signer-${index}`}
|
key={`signer-${user.pubkey}`}
|
||||||
userMeta={metadata[user.pubkey]}
|
userMeta={metadata[user.pubkey]}
|
||||||
user={user}
|
user={user}
|
||||||
index={index}
|
index={index}
|
||||||
@ -1101,72 +1043,16 @@ const DisplayUser = ({
|
|||||||
</DndProvider>
|
</DndProvider>
|
||||||
{users
|
{users
|
||||||
.filter((user) => user.role === UserRole.viewer)
|
.filter((user) => user.role === UserRole.viewer)
|
||||||
.map((user, index) => {
|
.map((user) => {
|
||||||
const userMeta = metadata[user.pubkey]
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.user} key={index}>
|
<div className={styles.user} key={`viewer-${user.pubkey}`}>
|
||||||
<div className={styles.avatar}>
|
<Counterpart
|
||||||
<UserAvatar
|
userMeta={metadata[user.pubkey]}
|
||||||
pubkey={user.pubkey}
|
user={user}
|
||||||
name={
|
handleUserRoleChange={handleUserRoleChange}
|
||||||
userMeta?.display_name ||
|
handleRemoveUser={handleRemoveUser}
|
||||||
userMeta?.name ||
|
|
||||||
shorten(hexToNpub(user.pubkey))
|
|
||||||
}
|
|
||||||
image={userMeta?.picture}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Select
|
|
||||||
name={`change-user-role-${user.pubkey}`}
|
|
||||||
aria-label="role"
|
|
||||||
value={user.role}
|
|
||||||
variant="outlined"
|
|
||||||
IconComponent={() => null}
|
|
||||||
renderValue={(value) => (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
fontSize={'14px'}
|
|
||||||
color="var(--primary-main)"
|
|
||||||
icon={value === UserRole.signer ? faPen : faEye}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleUserRoleChange(e.target.value as UserRole, user.pubkey)
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px',
|
|
||||||
minWidth: '34px',
|
|
||||||
maxWidth: '34px',
|
|
||||||
minHeight: '34px',
|
|
||||||
maxHeight: '34px',
|
|
||||||
'& .MuiInputBase-input': {
|
|
||||||
padding: '10px !important',
|
|
||||||
textOverflow: 'unset!important'
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-notchedOutline': {
|
|
||||||
display: 'none'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MenuItem value={UserRole.signer}>{UserRole.signer}</MenuItem>
|
|
||||||
<MenuItem value={UserRole.viewer}>{UserRole.viewer}</MenuItem>
|
|
||||||
</Select>
|
|
||||||
<Tooltip title="Remove User" arrow>
|
|
||||||
<Button
|
|
||||||
onClick={() => handleRemoveUser(user.pubkey)}
|
|
||||||
sx={{
|
|
||||||
minWidth: '34px',
|
|
||||||
height: '34px',
|
|
||||||
padding: 0,
|
|
||||||
color: 'rgba(0, 0, 0, 0.35)',
|
|
||||||
'&:hover': {
|
|
||||||
color: 'white'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon fontSize={'14px'} icon={faTrash} />
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
@ -1179,23 +1065,26 @@ interface DragItem {
|
|||||||
type: string
|
type: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignerRowProps = {
|
type CounterpartProps = {
|
||||||
userMeta: ProfileMetadata
|
userMeta: ProfileMetadata
|
||||||
user: User
|
user: User
|
||||||
index: number
|
|
||||||
moveSigner: (dragIndex: number, hoverIndex: number) => void
|
|
||||||
handleUserRoleChange: (role: UserRole, pubkey: string) => void
|
handleUserRoleChange: (role: UserRole, pubkey: string) => void
|
||||||
handleRemoveUser: (pubkey: string) => void
|
handleRemoveUser: (pubkey: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SignerRow = ({
|
type SignerCounterpartProps = CounterpartProps & {
|
||||||
|
index: number
|
||||||
|
moveSigner: (dragIndex: number, hoverIndex: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const SignerCounterpart = ({
|
||||||
userMeta,
|
userMeta,
|
||||||
user,
|
user,
|
||||||
index,
|
index,
|
||||||
moveSigner,
|
moveSigner,
|
||||||
handleUserRoleChange,
|
handleUserRoleChange,
|
||||||
handleRemoveUser
|
handleRemoveUser
|
||||||
}: SignerRowProps) => {
|
}: SignerCounterpartProps) => {
|
||||||
const ref = useRef<HTMLTableRowElement>(null)
|
const ref = useRef<HTMLTableRowElement>(null)
|
||||||
|
|
||||||
const [{ handlerId }, drop] = useDrop<
|
const [{ handlerId }, drop] = useDrop<
|
||||||
@ -1269,7 +1158,7 @@ const SignerRow = ({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const opacity = isDragging ? 0 : 1
|
const opacity = isDragging ? 0.2 : 1
|
||||||
drag(drop(ref))
|
drag(drop(ref))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -1280,6 +1169,24 @@ const SignerRow = ({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon width={'14px'} fontSize={'14px'} icon={faGripLines} />
|
<FontAwesomeIcon width={'14px'} fontSize={'14px'} icon={faGripLines} />
|
||||||
|
<Counterpart
|
||||||
|
user={user}
|
||||||
|
userMeta={userMeta}
|
||||||
|
handleRemoveUser={handleRemoveUser}
|
||||||
|
handleUserRoleChange={handleUserRoleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Counterpart = ({
|
||||||
|
userMeta,
|
||||||
|
user,
|
||||||
|
handleUserRoleChange,
|
||||||
|
handleRemoveUser
|
||||||
|
}: CounterpartProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<div className={styles.avatar}>
|
<div className={styles.avatar}>
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
pubkey={user.pubkey}
|
pubkey={user.pubkey}
|
||||||
@ -1291,56 +1198,31 @@ const SignerRow = ({
|
|||||||
image={userMeta?.picture}
|
image={userMeta?.picture}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Select
|
<Tooltip title="Toggle User Role" arrow disableInteractive>
|
||||||
name={`change-user-role-${user.pubkey}`}
|
|
||||||
aria-label="role"
|
|
||||||
value={user.role}
|
|
||||||
variant="outlined"
|
|
||||||
IconComponent={() => null}
|
|
||||||
renderValue={(value) => (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
fontSize={'14px'}
|
|
||||||
color="var(--primary-main)"
|
|
||||||
icon={value === UserRole.signer ? faPen : faEye}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleUserRoleChange(e.target.value as UserRole, user.pubkey)
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
fontSize: '16px',
|
|
||||||
minWidth: '34px',
|
|
||||||
maxWidth: '34px',
|
|
||||||
minHeight: '34px',
|
|
||||||
maxHeight: '34px',
|
|
||||||
'& .MuiInputBase-input': {
|
|
||||||
padding: '10px !important',
|
|
||||||
textOverflow: 'unset!important'
|
|
||||||
},
|
|
||||||
'& .MuiOutlinedInput-notchedOutline': {
|
|
||||||
display: 'none'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MenuItem value={UserRole.signer}>{UserRole.signer}</MenuItem>
|
|
||||||
<MenuItem value={UserRole.viewer}>{UserRole.viewer}</MenuItem>
|
|
||||||
</Select>
|
|
||||||
<Tooltip title="Remove User" arrow>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleRemoveUser(user.pubkey)}
|
onClick={() =>
|
||||||
sx={{
|
handleUserRoleChange(
|
||||||
minWidth: '34px',
|
user.role === UserRole.signer ? UserRole.viewer : UserRole.signer,
|
||||||
height: '34px',
|
user.pubkey
|
||||||
padding: 0,
|
)
|
||||||
color: 'rgba(0, 0, 0, 0.35)',
|
|
||||||
'&:hover': {
|
|
||||||
color: 'white'
|
|
||||||
}
|
}
|
||||||
}}
|
className={styles.counterpartRowToggleButton}
|
||||||
|
data-variant="primary"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon fontSize={'14px'} icon={faTrash} />
|
<FontAwesomeIcon
|
||||||
|
icon={user.role === UserRole.signer ? faPen : faEye}
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
<Tooltip title="Remove User" arrow disableInteractive>
|
||||||
|
<Button
|
||||||
|
onClick={() => handleRemoveUser(user.pubkey)}
|
||||||
|
className={styles.counterpartRowToggleButton}
|
||||||
|
data-variant="secondary"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
min-width: 44px;
|
||||||
color: $primary-main;
|
color: $primary-main;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +79,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
height: 34px;
|
height: 36px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
outline: solid 1px #dddddd;
|
outline: solid 1px #dddddd;
|
||||||
@ -89,11 +90,43 @@
|
|||||||
&:focus-within {
|
&:focus-within {
|
||||||
outline-color: $primary-main;
|
outline-color: $primary-main;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override default MUI input styles only inside inputWrapepr
|
||||||
|
:global {
|
||||||
|
.MuiInputBase-input {
|
||||||
|
padding: 7px 14px;
|
||||||
|
}
|
||||||
|
.MuiOutlinedInput-notchedOutline {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.addCounterpart {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: start;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
> .inputWrapper {
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
min-width: 44px;
|
||||||
|
padding: 11px 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.users {
|
.users {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
max-height: 33vh;
|
max-height: 33vh;
|
||||||
|
|
||||||
|
.counterpartToggleButton {
|
||||||
|
min-width: 44px;
|
||||||
|
padding: 11px 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
@ -108,6 +141,22 @@
|
|||||||
a:hover {
|
a:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Higher specificify to override default button styles
|
||||||
|
.counterpartRowToggleButton {
|
||||||
|
min-width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.counterpartRowToggleButton {
|
||||||
|
&[data-variant='primary'] {
|
||||||
|
color: $primary-main;
|
||||||
|
}
|
||||||
|
&[data-variant='secondary'] {
|
||||||
|
color: rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
@ -187,3 +236,7 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comingSoonPlaceholder {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Function will replace the middle of the string with 3 dots if length greater then
|
* Function will replace the middle of the string with ellipsis if length greater then
|
||||||
* offset value
|
* offset value
|
||||||
* @param str string to shorten
|
* @param str string to shorten
|
||||||
* @param offset of how many chars to keep in the beginning and the end
|
* @param offset of how many chars to keep in the beginning and the end
|
||||||
@ -9,10 +9,7 @@ export const shorten = (str: string, offset = 9) => {
|
|||||||
// return original string if it is not long enough
|
// return original string if it is not long enough
|
||||||
if (str.length < offset * 2 + 4) return str
|
if (str.length < offset * 2 + 4) return str
|
||||||
|
|
||||||
return `${str.slice(0, offset)}...${str.slice(
|
return `${str.slice(0, offset)}…${str.slice(str.length - offset, str.length)}`
|
||||||
str.length - offset,
|
|
||||||
str.length
|
|
||||||
)}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const stringToHex = (str: string) => {
|
export const stringToHex = (str: string) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user