diff --git a/package-lock.json b/package-lock.json index 479088b..2bcd952 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,9 +34,10 @@ "nostr-tools": "2.7.0", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.4.168", + "rdndmb-html5-to-touch": "^8.0.3", "react": "^18.2.0", - "react-dnd": "16.0.1", - "react-dnd-html5-backend": "16.0.1", + "react-dnd": "^16.0.1", + "react-dnd-multi-backend": "^8.0.3", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-redux": "9.1.0", @@ -3267,6 +3268,19 @@ "@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": { "version": "3.0.0", "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": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -5702,6 +5731,7 @@ "version": "16.0.1", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", + "license": "MIT", "dependencies": { "@react-dnd/invariant": "^4.0.1", "@react-dnd/shallowequal": "^4.0.1", @@ -5731,10 +5761,55 @@ "version": "16.0.1", "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==", + "license": "MIT", "dependencies": { "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": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", diff --git a/package.json b/package.json index a2f074d..cf7ae91 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,10 @@ "nostr-tools": "2.7.0", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.4.168", + "rdndmb-html5-to-touch": "^8.0.3", "react": "^18.2.0", - "react-dnd": "16.0.1", - "react-dnd-html5-backend": "16.0.1", + "react-dnd": "^16.0.1", + "react-dnd-multi-backend": "^8.0.3", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-redux": "9.1.0", diff --git a/src/App.scss b/src/App.scss index d3bff8a..4d95be6 100644 --- a/src/App.scss +++ b/src/App.scss @@ -141,6 +141,8 @@ li { color: black; letter-spacing: normal; border: 1px solid transparent; + + scroll-margin-top: $header-height + $body-vertical-padding; } [data-dev='true'] { diff --git a/src/components/PDFView/PdfMarkItem.tsx b/src/components/PDFView/PdfMarkItem.tsx index d5a7c78..db57800 100644 --- a/src/components/PDFView/PdfMarkItem.tsx +++ b/src/components/PDFView/PdfMarkItem.tsx @@ -2,6 +2,8 @@ import { CurrentUserMark } from '../../types/mark.ts' import styles from '../DrawPDFFields/style.module.scss' import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf.ts' import { useScale } from '../../hooks/useScale.tsx' +import { forwardRef } from 'react' +import { npubToHex } from '../../utils/nostr.ts' interface PdfMarkItemProps { userMark: CurrentUserMark @@ -14,35 +16,41 @@ interface PdfMarkItemProps { /** * Responsible for display an individual Pdf Mark. */ -const PdfMarkItem = ({ - selectedMark, - handleMarkClick, - selectedMarkValue, - userMark, - pageWidth -}: PdfMarkItemProps) => { - const { location } = userMark.mark - const handleClick = () => handleMarkClick(userMark.mark.id) - const isEdited = () => selectedMark?.mark.id === userMark.mark.id - const getMarkValue = () => - isEdited() ? selectedMarkValue : userMark.currentValue - const { from } = useScale() - return ( -
- {getMarkValue()} -
- ) -} +const PdfMarkItem = forwardRef( + ( + { selectedMark, handleMarkClick, selectedMarkValue, userMark, pageWidth }, + ref + ) => { + const { location } = userMark.mark + const handleClick = () => handleMarkClick(userMark.mark.id) + const isEdited = () => selectedMark?.mark.id === userMark.mark.id + const getMarkValue = () => + isEdited() ? selectedMarkValue : userMark.currentValue + const { from } = useScale() + return ( +
+ {getMarkValue()} +
+ ) + } +) export default PdfMarkItem diff --git a/src/components/PDFView/PdfPageItem.tsx b/src/components/PDFView/PdfPageItem.tsx index ec9034a..bbf6e4e 100644 --- a/src/components/PDFView/PdfPageItem.tsx +++ b/src/components/PDFView/PdfPageItem.tsx @@ -48,16 +48,15 @@ const PdfPageItem = ({ alt={`page ${pageIndex + 1} of ${fileName}`} /> {currentUserMarks.map((m, i) => ( -
(markRefs.current[m.id] = el)}> - -
+ (markRefs.current[m.id] = el)} + handleMarkClick={handleMarkClick} + selectedMarkValue={selectedMarkValue} + userMark={m} + selectedMark={selectedMark} + pageWidth={page.width} + /> ))} {otherUserMarks.map((m, i) => { return ( diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index f1cfbd0..f0bd6b9 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -1,20 +1,13 @@ -import { - Button, - FormHelperText, - ListItemIcon, - ListItemText, - MenuItem, - Select, - TextField, - Tooltip -} from '@mui/material' +import styles from './style.module.scss' +import { Button, FormHelperText, TextField, Tooltip } from '@mui/material' import type { Identifier, XYCoord } from 'dnd-core' import saveAs from 'file-saver' import JSZip from 'jszip' import { Event, kinds } from 'nostr-tools' import { useEffect, useRef, useState } from 'react' 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 { useLocation, useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' @@ -49,7 +42,6 @@ import { uploadToFileStorage } from '../../utils' import { Container } from '../../components/Container' -import styles from './style.module.scss' import fileListStyles from '../../components/FileList/style.module.scss' import { DrawTool, MarkType } from '../../types/drawing' import { DrawPDFFields } from '../../components/DrawPDFFields' @@ -874,21 +866,12 @@ export const CreatePage = () => {
setTitle(e.target.value)} - sx={{ - width: '100%', - fontSize: '16px', - '& .MuiInputBase-input': { - padding: '7px 14px' - }, - '& .MuiOutlinedInput-notchedOutline': { - display: 'none' - } - }} />
    @@ -907,9 +890,6 @@ export const CreatePage = () => { aria-label={`delete ${file.name}`} variant="text" onClick={(event) => handleRemoveFile(event, file)} - sx={{ - minWidth: '44px' - }} > @@ -932,76 +912,6 @@ export const CreatePage = () => { } right={
    -
    - setUserInput(e.target.value)} - onKeyDown={handleInputKeyDown} - error={!!error} - fullWidth - sx={{ - fontSize: '16px', - '& .MuiInputBase-input': { - padding: '7px 14px' - }, - '& .MuiOutlinedInput-notchedOutline': { - display: 'none' - } - }} - /> - - -
    -
    { moveSigner={moveSigner} />
    - +
    +
    + setUserInput(e.target.value)} + onKeyDown={handleInputKeyDown} + error={!!error} + /> +
    + + +
    @@ -1032,11 +978,7 @@ export const CreatePage = () => { {drawTool.active ? ( ) : ( - + Coming soon )} @@ -1084,12 +1026,12 @@ const DisplayUser = ({ }: DisplayUsersProps) => { return ( <> - + {users .filter((user) => user.role === UserRole.signer) .map((user, index) => ( - {users .filter((user) => user.role === UserRole.viewer) - .map((user, index) => { - const userMeta = metadata[user.pubkey] + .map((user) => { return ( -
    -
    - -
    - - - - +
    +
    ) })} @@ -1179,23 +1065,26 @@ interface DragItem { type: string } -type SignerRowProps = { +type CounterpartProps = { userMeta: ProfileMetadata user: User - index: number - moveSigner: (dragIndex: number, hoverIndex: number) => void handleUserRoleChange: (role: UserRole, pubkey: string) => void handleRemoveUser: (pubkey: string) => void } -const SignerRow = ({ +type SignerCounterpartProps = CounterpartProps & { + index: number + moveSigner: (dragIndex: number, hoverIndex: number) => void +} + +const SignerCounterpart = ({ userMeta, user, index, moveSigner, handleUserRoleChange, handleRemoveUser -}: SignerRowProps) => { +}: SignerCounterpartProps) => { const ref = useRef(null) const [{ handlerId }, drop] = useDrop< @@ -1269,7 +1158,7 @@ const SignerRow = ({ }) }) - const opacity = isDragging ? 0 : 1 + const opacity = isDragging ? 0.2 : 1 drag(drop(ref)) return ( @@ -1280,6 +1169,24 @@ const SignerRow = ({ ref={ref} > + +
    + ) +} + +const Counterpart = ({ + userMeta, + user, + handleUserRoleChange, + handleRemoveUser +}: CounterpartProps) => { + return ( + <>
    - - + -
    + + + + ) } diff --git a/src/pages/create/style.module.scss b/src/pages/create/style.module.scss index eddacfe..a7dd7f2 100644 --- a/src/pages/create/style.module.scss +++ b/src/pages/create/style.module.scss @@ -42,6 +42,7 @@ } button { + min-width: 44px; color: $primary-main; } @@ -78,7 +79,7 @@ align-items: center; flex-shrink: 0; - height: 34px; + height: 36px; overflow: hidden; border-radius: 4px; outline: solid 1px #dddddd; @@ -89,11 +90,43 @@ &:focus-within { 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 { flex-shrink: 0; max-height: 33vh; + + .counterpartToggleButton { + min-width: 44px; + padding: 11px 12px; + } } .user { @@ -108,6 +141,22 @@ a:hover { 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 { @@ -187,3 +236,7 @@ cursor: not-allowed; } } + +.comingSoonPlaceholder { + font-size: 10px; +} diff --git a/src/utils/string.ts b/src/utils/string.ts index 9ac0f05..14eee83 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -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 * @param str string to shorten * @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 if (str.length < offset * 2 + 4) return str - return `${str.slice(0, offset)}...${str.slice( - str.length - offset, - str.length - )}` + return `${str.slice(0, offset)}…${str.slice(str.length - offset, str.length)}` } export const stringToHex = (str: string) => {