diff --git a/package.json b/package.json index 447fdda..dc13581 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 25", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 2", "lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint:staged": "eslint --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", diff --git a/src/components/DrawPDFFields/index.tsx b/src/components/DrawPDFFields/index.tsx index 5318361..d0c4b61 100644 --- a/src/components/DrawPDFFields/index.tsx +++ b/src/components/DrawPDFFields/index.tsx @@ -1,40 +1,27 @@ -import { - AccessTime, - CalendarMonth, - ExpandMore, - Gesture, - PictureAsPdf, - Badge, - Work, - Close -} from '@mui/icons-material' +import { Close } from '@mui/icons-material' import { Box, - Typography, - Accordion, - AccordionDetails, - AccordionSummary, CircularProgress, + Divider, FormControl, InputLabel, MenuItem, Select } from '@mui/material' import styles from './style.module.scss' -import { useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' import * as PDFJS from 'pdfjs-dist' -import { ProfileMetadata, User } from '../../types' +import { ProfileMetadata, User, UserRole } from '../../types' import { PdfFile, - DrawTool, MouseState, PdfPage, DrawnField, - MarkType + DrawTool } from '../../types/drawing' import { truncate } from 'lodash' -import { hexToNpub } from '../../utils' +import { extractFileExtension, hexToNpub } from '../../utils' import { toPdfFiles } from '../../utils/pdf.ts' PDFJS.GlobalWorkerOptions.workerSrc = new URL( 'pdfjs-dist/build/pdf.worker.min.mjs', @@ -46,64 +33,31 @@ interface Props { users: User[] metadata: { [key: string]: ProfileMetadata } onDrawFieldsChange: (pdfFiles: PdfFile[]) => void + selectedTool?: DrawTool } export const DrawPDFFields = (props: Props) => { - const { selectedFiles, onDrawFieldsChange, users } = props + const { selectedFiles, selectedTool, onDrawFieldsChange, users } = props const [pdfFiles, setPdfFiles] = useState([]) const [parsingPdf, setParsingPdf] = useState(false) - const [showDrawToolBox, setShowDrawToolBox] = useState(false) - - const [selectedTool, setSelectedTool] = useState() - const [toolbox] = useState([ - { - identifier: MarkType.SIGNATURE, - icon: , - label: 'Signature', - active: false - }, - { - identifier: MarkType.FULLNAME, - icon: , - label: 'Full Name', - active: true - }, - { - identifier: MarkType.JOBTITLE, - icon: , - label: 'Job Title', - active: false - }, - { - identifier: MarkType.DATE, - icon: , - label: 'Date', - active: false - }, - { - identifier: MarkType.DATETIME, - icon: , - label: 'Datetime', - active: false - } - ]) const [mouseState, setMouseState] = useState({ clicked: false }) useEffect(() => { - /** - * Reads the pdf binary files and converts it's pages to images - * creates the pdfFiles object and sets to a state - */ - const parsePdfPages = async () => { - const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles) - - setPdfFiles(pdfFiles) - } if (selectedFiles) { + /** + * Reads the pdf binary files and converts it's pages to images + * creates the pdfFiles object and sets to a state + */ + const parsePdfPages = async () => { + const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles) + + setPdfFiles(pdfFiles) + } + setParsingPdf(true) parsePdfPages().finally(() => { @@ -148,11 +102,7 @@ export const DrawPDFFields = (props: Props) => { // Proceed only if left click if (event.button !== 0) return - // Only allow drawing if mouse is not over other drawn element - const target = event.target as HTMLElement - const isOverPdfImageWrapper = target.tagName === 'IMG' - - if (!selectedTool || !isOverPdfImageWrapper) { + if (!selectedTool) { return } @@ -204,8 +154,14 @@ export const DrawPDFFields = (props: Props) => { ) => { if (mouseState.clicked && selectedTool) { const lastElementIndex = page.drawnFields.length - 1 + const lastDrawnField = page.drawnFields[lastElementIndex] + // Return early if we don't have lastDrawnField + // Issue noticed in the console when dragging out of bounds + // to the page below (without releaseing mouse click) + if (!lastDrawnField) return + const { mouseX, mouseY } = getMouseCoordinates(event) const width = mouseX - lastDrawnField.left @@ -255,15 +211,14 @@ export const DrawPDFFields = (props: Props) => { * @param event Mouse event * @param drawnField which we are moving */ - const onDranwFieldMouseMove = ( + const onDrawnFieldMouseMove = ( event: React.MouseEvent, drawnField: DrawnField ) => { - const target = event.target as HTMLElement | null if (mouseState.dragging) { const { mouseX, mouseY, rect } = getMouseCoordinates( event, - target?.parentNode as HTMLElement + event.currentTarget.parentElement ) const coordsOffset = mouseState.coordsInWrapper @@ -292,7 +247,9 @@ export const DrawPDFFields = (props: Props) => { * @param event Mouse event * @param drawnField which we are resizing */ - const onResizeHandleMouseDown = (event: React.MouseEvent) => { + const onResizeHandleMouseDown = ( + event: React.MouseEvent + ) => { // Proceed only if left click if (event.button !== 0) return @@ -309,14 +266,16 @@ export const DrawPDFFields = (props: Props) => { * @param drawnField which we are resizing */ const onResizeHandleMouseMove = ( - event: React.MouseEvent, + event: React.MouseEvent, drawnField: DrawnField ) => { - const target = event.target as HTMLElement | null if (mouseState.resizing) { const { mouseX, mouseY } = getMouseCoordinates( event, - target?.parentNode?.parentNode as HTMLElement + // currentTarget = span handle + // 1st parent = drawnField + // 2nd parent = img + event.currentTarget.parentElement?.parentElement ) const width = mouseX - drawnField.left @@ -337,7 +296,7 @@ export const DrawPDFFields = (props: Props) => { * @param drawnFileIndex drawn file index */ const onRemoveHandleMouseDown = ( - event: React.MouseEvent, + event: React.MouseEvent, pdfFileIndex: number, pdfPageIndex: number, drawnFileIndex: number @@ -368,10 +327,10 @@ export const DrawPDFFields = (props: Props) => { * event.target will be used */ const getMouseCoordinates = ( - event: React.MouseEvent, - customTarget?: HTMLElement + event: React.MouseEvent, + customTarget?: HTMLElement | null ) => { - const target = (customTarget ? customTarget : event.target) as HTMLElement + const target = customTarget ? customTarget : event.currentTarget const rect = target.getBoundingClientRect() const mouseX = event.clientX - rect.left //x position within the element. const mouseY = event.clientY - rect.top //y position within the element. @@ -383,64 +342,26 @@ export const DrawPDFFields = (props: Props) => { } } - /** - * - * @returns if expanded pdf accordion is present - */ - const hasExpandedPdf = () => { - return !!pdfFiles.filter((pdfFile) => !!pdfFile.expanded).length - } - - const handleAccordionExpandChange = (expanded: boolean, pdfFile: PdfFile) => { - pdfFile.expanded = expanded - - refreshPdfFiles() - setShowDrawToolBox(hasExpandedPdf()) - } - - /** - * Changes the drawing tool - * @param drawTool to draw with - */ - const handleToolSelect = (drawTool: DrawTool) => { - // If clicked on the same tool, unselect - if (drawTool.identifier === selectedTool?.identifier) { - setSelectedTool(null) - return - } - - setSelectedTool(drawTool) - } - /** * Renders the pdf pages and drawing elements */ const getPdfPages = (pdfFile: PdfFile, pdfFileIndex: number) => { return ( - + <> {pdfFile.pages.map((page, pdfPageIndex: number) => { return (
{ - onMouseMove(event, page) - }} - onMouseDown={(event) => { - onMouseDown(event, page) - }} > { + onMouseMove(event, page) + }} + onMouseDown={(event) => { + onMouseDown(event, page) + }} draggable="false" - style={{ width: '100%' }} src={page.image} /> @@ -450,7 +371,7 @@ export const DrawPDFFields = (props: Props) => { key={drawnFieldIndex} onMouseDown={onDrawnFieldMouseDown} onMouseMove={(event) => { - onDranwFieldMouseMove(event, drawnField) + onDrawnFieldMouseMove(event, drawnField) }} className={styles.drawingRectangle} style={{ @@ -496,36 +417,38 @@ export const DrawPDFFields = (props: Props) => { labelId="counterparts" label="Counterparts" > - {users.map((user, index) => { - let displayValue = truncate( - hexToNpub(user.pubkey), - { - length: 16 - } - ) - - const metadata = props.metadata[user.pubkey] - - if (metadata) { - displayValue = truncate( - metadata.name || - metadata.display_name || - metadata.username, + {users + .filter((u) => u.role === UserRole.signer) + .map((user, index) => { + let displayValue = truncate( + hexToNpub(user.pubkey), { length: 16 } ) - } - return ( - - {displayValue} - - ) - })} + const metadata = props.metadata[user.pubkey] + + if (metadata) { + displayValue = truncate( + metadata.name || + metadata.display_name || + metadata.username, + { + length: 16 + } + ) + } + + return ( + + {displayValue} + + ) + })}
@@ -535,7 +458,7 @@ export const DrawPDFFields = (props: Props) => { ) })} -
+ ) } @@ -552,57 +475,38 @@ export const DrawPDFFields = (props: Props) => { } return ( - - - Draw fields on the PDFs: - - {pdfFiles.map((pdfFile, pdfFileIndex: number) => { - return ( - { - handleAccordionExpandChange(expanded, pdfFile) - }} +
+ {selectedFiles.map((file, i) => { + const name = file.name + const extension = extractFileExtension(name) + const pdfFile = pdfFiles.find((pdf) => pdf.file.name === name) + return ( + +
- } - aria-controls={`panel${pdfFileIndex}-content`} - id={`panel${pdfFileIndex}header`} + {pdfFile ? ( + getPdfPages(pdfFile, i) + ) : ( +
+ This is a {extension} file +
+ )} +
+ {i < selectedFiles.length - 1 && ( + - - {pdfFile.file.name} - - - {getPdfPages(pdfFile, pdfFileIndex)} - - - ) - })} - - - {showDrawToolBox && ( - - - {toolbox - .filter((drawTool) => drawTool.active) - .map((drawTool: DrawTool, index: number) => { - return ( - { - handleToolSelect(drawTool) - }} - className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`} - > - {drawTool.icon} - {drawTool.label} - - ) - })} - - - )} - + File Separator + + )} +
+ ) + })} +
) } diff --git a/src/components/DrawPDFFields/style.module.scss b/src/components/DrawPDFFields/style.module.scss index 7490d1f..142f88a 100644 --- a/src/components/DrawPDFFields/style.module.scss +++ b/src/components/DrawPDFFields/style.module.scss @@ -1,3 +1,5 @@ +@import '../../styles/sizes.scss'; + .pdfFieldItem { background: white; padding: 10px; @@ -5,53 +7,10 @@ cursor: pointer; } -.drawToolBoxContainer { - position: fixed; - bottom: 0; - left: 0; - right: 0; - display: flex; - justify-content: center; - z-index: 50; - - .drawToolBox { - display: flex; - gap: 10px; - min-width: 100px; - background-color: white; - padding: 15px; - box-shadow: 0 0 10px 1px #0000003b; - border-radius: 4px; - - .toolItem { - display: flex; - flex-direction: column; - align-items: center; - border: 1px solid rgba(0, 0, 0, 0.137); - padding: 5px; - cursor: pointer; - -webkit-user-select: none; - user-select: none; - - &.selected { - border-color: #01aaad; - color: #01aaad; - } - - &:not(.selected) { - &:hover { - border-color: #01aaad79; - } - } - } - } -} - .pdfImageWrapper { position: relative; -webkit-user-select: none; user-select: none; - margin-bottom: 10px; > img { display: block; @@ -81,7 +40,7 @@ } &.edited { - border: 1px dotted #01aaad + border: 1px dotted #01aaad; } .resizeHandle { @@ -93,7 +52,14 @@ background-color: #fff; border: 1px solid rgb(160, 160, 160); border-radius: 50%; - cursor: pointer; + cursor: nwse-resize; + + // Increase the area a bit so it's easier to click + &::after { + content: ''; + position: absolute; + inset: -14px; + } } .removeHandle { @@ -124,3 +90,29 @@ padding: 5px 0; } } + +.fileWrapper { + display: flex; + flex-direction: column; + gap: 15px; + position: relative; + scroll-margin-top: $header-height + $body-vertical-padding; +} + +.view { + display: flex; + flex-direction: column; + gap: 25px; +} + +.otherFile { + border-radius: 4px; + background: rgba(255, 255, 255, 0.5); + height: 100px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: rgba(0, 0, 0, 0.25); + font-size: 14px; +} diff --git a/src/components/FileList/style.module.scss b/src/components/FileList/style.module.scss index 6f7b64a..22d8515 100644 --- a/src/components/FileList/style.module.scss +++ b/src/components/FileList/style.module.scss @@ -17,8 +17,8 @@ ul { list-style-type: none; /* Removes bullet points */ - margin: 0; /* Removes default margin */ - padding: 0; /* Removes default padding */ + margin: 0; /* Removes default margin */ + padding: 0; /* Removes default padding */ } li { @@ -27,7 +27,6 @@ li { padding: 0; /* Removes any default padding */ } - .wrap { display: flex; flex-direction: column; @@ -50,7 +49,7 @@ li { } .files::-webkit-scrollbar-track { - background-color: rgba(0,0,0,0.15); + background-color: rgba(0, 0, 0, 0.15); } .files::-webkit-scrollbar-thumb { @@ -70,12 +69,12 @@ li { background: #ffffff; padding: 5px 10px; align-items: center; - color: rgba(0,0,0,0.5); + color: rgba(0, 0, 0, 0.5); cursor: pointer; flex-grow: 1; font-size: 16px; font-weight: 500; - + min-height: 45px; &.active { background: #4c82a3; @@ -84,7 +83,6 @@ li { } .fileItem:hover { - transition: ease 0.2s; background: #4c82a3; color: white; } @@ -100,6 +98,7 @@ li { -webkit-box-orient: vertical; overflow: hidden; -webkit-line-clamp: 1; + line-clamp: 1; } .fileNumber { @@ -120,4 +119,4 @@ li { align-items: center; height: 25px; width: 25px; -} \ No newline at end of file +} diff --git a/src/components/MarkFormField/style.module.scss b/src/components/MarkFormField/style.module.scss index b5c6bb9..ef80df0 100644 --- a/src/components/MarkFormField/style.module.scss +++ b/src/components/MarkFormField/style.module.scss @@ -8,6 +8,39 @@ left: 0; align-items: center; z-index: 1000; + + button { + transition: ease 0.2s; + width: auto; + border-radius: 4px; + outline: unset; + border: unset; + background: unset; + color: #ffffff; + background: #4c82a3; + font-weight: 500; + font-size: 14px; + padding: 8px 15px; + white-space: nowrap; + display: flex; + flex-direction: row; + grid-gap: 12px; + justify-content: center; + align-items: center; + text-decoration: unset; + position: relative; + cursor: pointer; + } + + button:hover { + background: #5e8eab; + color: white; + } + + button:active { + background: #447592; + color: white; + } } .actions { @@ -19,7 +52,7 @@ flex-direction: column; align-items: center; grid-gap: 15px; - box-shadow: 0 -2px 4px 0 rgb(0,0,0,0.1); + box-shadow: 0 -2px 4px 0 rgb(0, 0, 0, 0.1); max-width: 750px; &.expanded { @@ -73,7 +106,7 @@ .textInput { height: 100px; - background: rgba(0,0,0,0.1); + background: rgba(0, 0, 0, 0.1); border-radius: 4px; border: solid 2px #4c82a3; display: flex; @@ -84,17 +117,19 @@ .input { border-radius: 4px; - border: solid 1px rgba(0,0,0,0.15); + border: solid 1px rgba(0, 0, 0, 0.15); padding: 5px 10px; font-size: 16px; width: 100%; - background: linear-gradient(rgba(0,0,0,0.00), rgba(0,0,0,0.00) 100%), linear-gradient(white, white); + background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 100%), + linear-gradient(white, white); } .input:focus { - border: solid 1px rgba(0,0,0,0.15); + border: solid 1px rgba(0, 0, 0, 0.15); outline: none; - background: linear-gradient(rgba(0,0,0,0.05), rgba(0,0,0,0.05) 100%), linear-gradient(white, white); + background: linear-gradient(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.05) 100%), + linear-gradient(white, white); } .actionsBottom { @@ -105,41 +140,6 @@ align-items: center; } -button { - transition: ease 0.2s; - width: auto; - border-radius: 4px; - outline: unset; - border: unset; - background: unset; - color: #ffffff; - background: #4c82a3; - font-weight: 500; - font-size: 14px; - padding: 8px 15px; - white-space: nowrap; - display: flex; - flex-direction: row; - grid-gap: 12px; - justify-content: center; - align-items: center; - text-decoration: unset; - position: relative; - cursor: pointer; -} - -button:hover { - transition: ease 0.2s; - background: #5e8eab; - color: white; -} - -button:active { - transition: ease 0.2s; - background: #447592; - color: white; -} - .submitButton { width: 100%; max-width: 300px; @@ -172,18 +172,18 @@ button:active { font-size: 12px; padding: 5px 10px; border-radius: 3px; - background: rgba(0,0,0,0.1); - color: rgba(0,0,0,0.5); + background: rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.5); } .paginationButton:hover { background: #447592; - color: rgba(255,255,255,0.5); + color: rgba(255, 255, 255, 0.5); } .paginationButtonDone { background: #5e8eab; - color: rgb(255,255,255); + color: rgb(255, 255, 255); } .paginationButtonCurrent { @@ -204,7 +204,7 @@ button:active { background: white; color: #434343; padding: 5px 30px; - box-shadow: 0px -3px 4px 0 rgb(0,0,0,0.1); + box-shadow: 0px -3px 4px 0 rgb(0, 0, 0, 0.1); position: absolute; top: -25px; } diff --git a/src/components/UserAvatar/index.tsx b/src/components/UserAvatar/index.tsx index 9ae60ce..6049a07 100644 --- a/src/components/UserAvatar/index.tsx +++ b/src/components/UserAvatar/index.tsx @@ -30,7 +30,7 @@ export const UserAvatar = ({ pubkey, name, image }: UserAvatarProps) => { padding: 0 }} /> - {name ? : null} + {name ? {name} : null} ) } diff --git a/src/components/UserAvatar/styles.module.scss b/src/components/UserAvatar/styles.module.scss index fbe8cf5..d57cdf1 100644 --- a/src/components/UserAvatar/styles.module.scss +++ b/src/components/UserAvatar/styles.module.scss @@ -10,4 +10,8 @@ font-weight: 500; font-size: 14px; color: var(--text-color); + + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } diff --git a/src/layouts/StickySideColumns.module.scss b/src/layouts/StickySideColumns.module.scss index 7690822..7495cad 100644 --- a/src/layouts/StickySideColumns.module.scss +++ b/src/layouts/StickySideColumns.module.scss @@ -10,6 +10,9 @@ .sidesWrap { position: relative; + + // HACK: Stop grid column from growing + min-width: 0; } .sides { diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index fe4ac7f..7da2b79 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -1,27 +1,16 @@ -import { Clear, DragHandle } from '@mui/icons-material' import { - Box, Button, - FormControl, - IconButton, - InputLabel, + FormHelperText, + ListItemIcon, + ListItemText, MenuItem, - Paper, Select, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, TextField, - Tooltip, - Typography + Tooltip } from '@mui/material' import type { Identifier, XYCoord } from 'dnd-core' import saveAs from 'file-saver' import JSZip from 'jszip' -import { MuiFileInput } from 'mui-file-input' import { Event, kinds } from 'nostr-tools' import { useEffect, useRef, useState } from 'react' import { DndProvider, useDrag, useDrop } from 'react-dnd' @@ -61,14 +50,46 @@ import { } from '../../utils' import { Container } from '../../components/Container' import styles from './style.module.scss' -import { PdfFile } from '../../types/drawing' +import fileListStyles from '../../components/FileList/style.module.scss' +import { DrawTool, MarkType, PdfFile } from '../../types/drawing' import { DrawPDFFields } from '../../components/DrawPDFFields' import { Mark } from '../../types/mark.ts' +import { StickySideColumns } from '../../layouts/StickySideColumns.tsx' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { + fa1, + faBriefcase, + faCalendarDays, + faCheckDouble, + faCircleDot, + faClock, + faCreditCard, + faEllipsis, + faEye, + faGripLines, + faHeading, + faIdCard, + faImage, + faPaperclip, + faPen, + faPhone, + faPlus, + faSignature, + faSquareCaretDown, + faSquareCheck, + faStamp, + faT, + faTableCellsLarge, + faTrash, + faUpload +} from '@fortawesome/free-solid-svg-icons' export const CreatePage = () => { const navigate = useNavigate() const location = useLocation() const { uploadedFiles } = location.state || {} + const [currentFile, setCurrentFile] = useState() + const isActive = (file: File) => file.name === currentFile?.name const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') @@ -76,9 +97,22 @@ export const CreatePage = () => { const [authUrl, setAuthUrl] = useState() const [title, setTitle] = useState(`sigit_${formatTimestamp(Date.now())}`) + const [selectedFiles, setSelectedFiles] = useState([]) + const fileInputRef = useRef(null) + const handleUploadButtonClick = () => { + if (fileInputRef.current) { + fileInputRef.current.click() + } + } const [userInput, setUserInput] = useState('') + const handleInputKeyDown = (event: React.KeyboardEvent) => { + if (event.code === 'Enter' || event.code === 'NumpadEnter') { + event.preventDefault() + handleAddUser() + } + } const [userRole, setUserRole] = useState(UserRole.signer) const [error, setError] = useState() @@ -93,6 +127,132 @@ export const CreatePage = () => { ) const [drawnPdfs, setDrawnPdfs] = useState([]) + const [selectedTool, setSelectedTool] = useState() + const [toolbox] = useState([ + { + identifier: MarkType.TEXT, + icon: , + label: 'Text', + active: false + }, + { + identifier: MarkType.SIGNATURE, + icon: , + label: 'Signature', + active: false + }, + { + identifier: MarkType.JOBTITLE, + icon: , + label: 'Job Title', + active: false + }, + { + identifier: MarkType.FULLNAME, + icon: , + label: 'Full Name', + active: true + }, + { + identifier: MarkType.INITIALS, + icon: , + label: 'Initials', + active: false + }, + { + identifier: MarkType.DATETIME, + icon: , + label: 'Date Time', + active: false + }, + { + identifier: MarkType.DATE, + icon: , + label: 'Date', + active: false + }, + { + identifier: MarkType.NUMBER, + icon: , + label: 'Number', + active: false + }, + { + identifier: MarkType.IMAGES, + icon: , + label: 'Images', + active: false + }, + { + identifier: MarkType.CHECKBOX, + icon: , + label: 'Checkbox', + active: false + }, + { + identifier: MarkType.MULTIPLE, + icon: , + label: 'Multiple', + active: false + }, + { + identifier: MarkType.FILE, + icon: , + label: 'File', + active: false + }, + { + identifier: MarkType.RADIO, + icon: , + label: 'Radio', + active: false + }, + { + identifier: MarkType.SELECT, + icon: , + label: 'Select', + active: false + }, + { + identifier: MarkType.CELLS, + icon: , + label: 'Cells', + active: false + }, + { + identifier: MarkType.STAMP, + icon: , + label: 'Stamp', + active: false + }, + { + identifier: MarkType.PAYMENT, + icon: , + label: 'Payment', + active: false + }, + { + identifier: MarkType.PHONE, + icon: , + label: 'Phone', + active: false + } + ]) + + /** + * Changes the drawing tool + * @param drawTool to draw with + */ + const handleToolSelect = (drawTool: DrawTool) => { + // If clicked on the same tool, unselect + if (drawTool.identifier === selectedTool?.identifier) { + setSelectedTool(undefined) + return + } + + setSelectedTool(drawTool) + } + useEffect(() => { users.forEach((user) => { if (!(user.pubkey in metadata)) { @@ -268,19 +428,22 @@ export const CreatePage = () => { }) } - const handleSelectFiles = (files: File[]) => { - setSelectedFiles((prev) => { - const prevFileNames = prev.map((file) => file.name) - - const newFiles = files.filter( - (file) => !prevFileNames.includes(file.name) - ) - - return [...prev, ...newFiles] - }) + const handleSelectFiles = (event: React.ChangeEvent) => { + if (event.target.files) { + setSelectedFiles(Array.from(event.target.files)) + } } - const handleRemoveFile = (fileToRemove: File) => { + const handleFileClick = (id: string) => { + document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' }) + } + + const handleRemoveFile = ( + event: React.MouseEvent, + fileToRemove: File + ) => { + event.stopPropagation() + setSelectedFiles((prevFiles) => prevFiles.filter((file) => file.name !== fileToRemove.name) ) @@ -702,94 +865,200 @@ export const CreatePage = () => { <> {isLoading && } - setTitle(e.target.value)} - variant="outlined" - /> - - - handleSelectFiles(value)} - /> - - {selectedFiles.length > 0 && ( -
    - {selectedFiles.map((file, index) => ( -
  • - {file.name} - handleRemoveFile(file)}> - {' '} - -
  • - ))} -
- )} -
- - - Add Counterparts - - - - setUserInput(e.target.value)} - helperText={error} - error={!!error} - /> - - Role - - - - - + + + ))} + + - - - + + } + right={ +
+
+ setUserInput(e.target.value)} + onKeyDown={handleInputKeyDown} + error={!!error} + fullWidth + sx={{ + fontSize: '16px', + '& .MuiInputBase-input': { + padding: '7px 14px' + }, + '& .MuiOutlinedInput-notchedOutline': { + display: 'none' + } + }} + /> + + +
- +
+ +
- + - - - +
+ {toolbox.map((drawTool: DrawTool, index: number) => { + return ( +
{ + handleToolSelect(drawTool) + } + : () => null + } + className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''} ${!drawTool.active ? styles.comingSoon : ''} + `} + > + {drawTool.icon} + {drawTool.label} + {drawTool.active ? ( + + ) : ( + + Coming soon + + )} +
+ ) + })} +
+ + {!!error && ( + {error} + )} +
+ } + > + +
) @@ -811,80 +1080,93 @@ const DisplayUser = ({ moveSigner }: DisplayUsersProps) => { return ( - - - - - User - Role - Action - - - - - {users - .filter((user) => user.role === UserRole.signer) - .map((user, index) => ( - + + {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] + return ( +
+
+ - ))} - - {users - .filter((user) => user.role === UserRole.viewer) - .map((user, index) => { - const userMeta = metadata[user.pubkey] - return ( - - - - - - - - - - handleRemoveUser(user.pubkey)}> - - - - - - ) - })} - -
-
+ + + + + + + ) + })} + ) } @@ -988,16 +1270,14 @@ const SignerRow = ({ drag(drop(ref)) return ( - - - + +
- - - null} + renderValue={(value) => ( + + )} + 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' } + }} + > + {UserRole.signer} + {UserRole.viewer} + + + + +
) } diff --git a/src/pages/create/style.module.scss b/src/pages/create/style.module.scss index 395936b..2a1a1d8 100644 --- a/src/pages/create/style.module.scss +++ b/src/pages/create/style.module.scss @@ -1,41 +1,176 @@ @import '../../styles/colors.scss'; -.container { +.flexWrap { display: flex; flex-direction: column; - color: $text-color; - margin-top: 10px; - gap: 10px; - width: 550px; - max-width: 550px; - - .inputBlock { - display: flex; - flex-direction: column; - gap: 25px; - } + gap: 15px; } -.subHeader { - border-bottom: 0.5px solid; -} +.orderedFilesList { + counter-reset: item; + list-style-type: none; + margin: 0; -.tableHeaderCell { - border-right: 1px solid rgba(224, 224, 224, 1); -} - -.tableCell { - border-right: 1px solid rgba(224, 224, 224, 1); - height: 56px; - - .user { + li { display: flex; align-items: center; + + transition: ease 0.4s; + border-radius: 4px; + background: #ffffff; + padding: 7px 10px; + color: rgba(0, 0, 0, 0.5); + min-height: 45px; + cursor: pointer; gap: 10px; - .name { - text-align: center; - cursor: pointer; + &::before { + content: counter(item) ' '; + counter-increment: item; + font-size: 14px; + } + :nth-child(1) { + flex-grow: 1; + + font-size: 16px; + + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + + button { + color: $primary-main; + } + + &:hover, + &.active, + &:focus-within { + background: $primary-main; + color: white; + + button { + color: white; + } } } -} \ No newline at end of file +} + +.uploadFileText { + margin-left: 12px; +} + +.paperGroup { + border-radius: 4px; + background: white; + padding: 15px; + display: flex; + flex-direction: column; + gap: 15px; + + // Automatic scrolling if paper-group gets large enough + // used for files on the left and users on the right + max-height: 350px; + overflow-x: hidden; + overflow-y: auto; +} + +.inputWrapper { + display: flex; + align-items: center; + + height: 34px; + overflow: hidden; + border-radius: 4px; + outline: solid 1px #dddddd; + background: white; + + width: 100%; + + &:focus-within { + outline-color: $primary-main; + } +} + +.user { + display: flex; + gap: 10px; + + font-size: 14px; + text-align: start; + justify-content: center; + align-items: center; + + a:hover { + text-decoration: none; + } +} + +.avatar { + flex-grow: 1; + min-width: 0; + + &:first-child { + margin-left: 24px; + } + + img { + // Override the default avatar size + width: 30px; + height: 30px; + } +} + +.fileName { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + flex-grow: 1; +} + +.toolbox { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 15px; + + max-height: 450px; + overflow-x: hidden; + overflow-y: auto; +} + +.toolItem { + width: 90px; + height: 90px; + + transition: ease 0.2s; + display: inline-flex; + flex-direction: column; + gap: 5px; + border-radius: 4px; + padding: 10px 5px 5px 5px; + background: rgba(0, 0, 0, 0.05); + color: rgba(0, 0, 0, 0.5); + align-items: center; + justify-content: center; + font-size: 14px; + cursor: pointer; + -webkit-user-select: none; + user-select: none; + + &.selected { + background: $primary-main; + color: white; + } + + &:not(.selected) { + &:hover { + background: $primary-light; + color: white; + } + } + + &.comingSoon { + opacity: 0.5; + cursor: not-allowed; + } +} diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 0f0329b..ddc777e 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -225,11 +225,11 @@ export const HomePage = () => { type="button" aria-label="upload files" > - + {isDragActive ? ( -

Drop the files here ...

+ ) : ( -

Click or drag files to upload!

+ )}
diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index a762292..be755f4 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -236,9 +236,11 @@ export const SignPage = () => { if (!arrayBuffer) return const blob = new Blob([arrayBuffer]) saveAs(blob, `exported-${unixNow()}.sigit.zip`) - } catch (error: any) { + } catch (error) { console.log('error in zip:>> ', error) - toast.error(error.message || 'Error occurred in generating zip file') + if (error instanceof Error) { + toast.error(error.message || 'Error occurred in generating zip file') + } } } diff --git a/src/pages/sign/internal/displayMeta.tsx b/src/pages/sign/internal/displayMeta.tsx index fbc9264..03ba364 100644 --- a/src/pages/sign/internal/displayMeta.tsx +++ b/src/pages/sign/internal/displayMeta.tsx @@ -141,7 +141,7 @@ export const DisplayMeta = ({ }) } }) - }, [users, submittedBy]) + }, [users, submittedBy, metadata]) const downloadFile = async (filename: string) => { const arrayBuffer = await files[filename].file.arrayBuffer() diff --git a/src/types/drawing.ts b/src/types/drawing.ts index f14dbea..1e65038 100644 --- a/src/types/drawing.ts +++ b/src/types/drawing.ts @@ -41,9 +41,22 @@ export interface DrawTool { } export enum MarkType { + TEXT = 'TEXT', SIGNATURE = 'SIGNATURE', JOBTITLE = 'JOBTITLE', FULLNAME = 'FULLNAME', + INITIALS = 'INITIALS', + DATETIME = 'DATETIME', DATE = 'DATE', - DATETIME = 'DATETIME' + NUMBER = 'NUMBER', + IMAGES = 'IMAGES', + CHECKBOX = 'CHECKBOX', + MULTIPLE = 'MULTIPLE', + FILE = 'FILE', + RADIO = 'RADIO', + SELECT = 'SELECT', + CELLS = 'CELLS', + STAMP = 'STAMP', + PAYMENT = 'PAYMENT', + PHONE = 'PHONE' } diff --git a/src/types/errors/DecryptionError.ts b/src/types/errors/DecryptionError.ts index 6a174b8..7014ab8 100644 --- a/src/types/errors/DecryptionError.ts +++ b/src/types/errors/DecryptionError.ts @@ -1,18 +1,26 @@ export class DecryptionError extends Error { public message: string = '' - constructor(public inputError: any) { + constructor(public inputError: unknown) { super() - if (inputError.message.toLowerCase().includes('expected')) { - this.message = `The decryption key length or format is invalid.` - } else if ( - inputError.message.includes('The JWK "alg" member was inconsistent') - ) { - this.message = `The decryption key is invalid.` + // Make sure inputError has access to the .message + if (inputError instanceof Error) { + if (inputError.message.toLowerCase().includes('expected')) { + this.message = `The decryption key length or format is invalid.` + } else if ( + inputError.message.includes('The JWK "alg" member was inconsistent') + ) { + this.message = `The decryption key is invalid.` + } else { + this.message = + inputError.message || 'An error occurred while decrypting file.' + } } else { + // We don't have message on the inputError + // Stringify whole error and set that as a message this.message = - inputError.message || 'An error occurred while decrypting file.' + JSON.stringify(inputError) || 'An error occurred while decrypting file.' } this.name = 'DecryptionError' diff --git a/src/utils/meta.ts b/src/utils/meta.ts index 276f049..0bee969 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -190,3 +190,11 @@ export const extractFileExtensions = (fileNames: string[]) => { return { extensions, isSame } } + +/** + * @param fileName - Filename to check + * @returns Extension string + */ +export const extractFileExtension = (fileName: string) => { + return fileName.split('.').pop() +} diff --git a/src/utils/zip.ts b/src/utils/zip.ts index 7a7a49f..577dd86 100644 --- a/src/utils/zip.ts +++ b/src/utils/zip.ts @@ -37,9 +37,11 @@ const readContentOfZipEntry = async ( const loadZip = async (data: InputFileFormat): Promise => { try { return await JSZip.loadAsync(data) - } catch (err: any) { + } catch (err) { console.log('err in loading zip file :>> ', err) - toast.error(err.message || 'An error occurred in loading zip file.') + if (err instanceof Error) { + toast.error(err.message || 'An error occurred in loading zip file.') + } return null } }