Create page - draft feature, save progress to local storage #320
17
package-lock.json
generated
17
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sigit",
|
"name": "sigit",
|
||||||
"version": "0.0.0-beta",
|
"version": "1.0.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sigit",
|
"name": "sigit",
|
||||||
"version": "0.0.0-beta",
|
"version": "1.0.3",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0-or-later ",
|
"license": "AGPL-3.0-or-later ",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -51,8 +51,7 @@
|
|||||||
"react-toastify": "10.0.4",
|
"react-toastify": "10.0.4",
|
||||||
"redux": "5.0.1",
|
"redux": "5.0.1",
|
||||||
"signature_pad": "^5.0.4",
|
"signature_pad": "^5.0.4",
|
||||||
"tseep": "1.2.1",
|
"tseep": "1.2.1"
|
||||||
"use-immer": "^0.11.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||||
@ -17265,16 +17264,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/use-immer": {
|
|
||||||
"version": "0.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.11.0.tgz",
|
|
||||||
"integrity": "sha512-RNAqi3GqsWJ4bcCd4LMBgdzvPmTABam24DUaFiKfX9s3MSorNRz9RDZYJkllJoMHUxVLMDetwAuCDeyWNrp1yA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"immer": ">=8.0.0",
|
|
||||||
"react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/use-sync-external-store": {
|
"node_modules/use-sync-external-store": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||||
|
@ -62,8 +62,7 @@
|
|||||||
"react-toastify": "10.0.4",
|
"react-toastify": "10.0.4",
|
||||||
"redux": "5.0.1",
|
"redux": "5.0.1",
|
||||||
"signature_pad": "^5.0.4",
|
"signature_pad": "^5.0.4",
|
||||||
"tseep": "1.2.1",
|
"tseep": "1.2.1"
|
||||||
"use-immer": "^0.11.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||||
|
117
src/components/DisplaySigit/LocalDraftSigit.tsx
Normal file
117
src/components/DisplaySigit/LocalDraftSigit.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Tooltip, Button, Divider } from '@mui/material'
|
||||||
|
import {
|
||||||
|
faCalendar,
|
||||||
|
faFile,
|
||||||
|
faFileCircleExclamation,
|
||||||
|
faPen,
|
||||||
|
faTrash
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { SigitDraft, UserRole } from '../../types'
|
||||||
|
import { appPrivateRoutes } from '../../routes'
|
||||||
|
import {
|
||||||
|
formatTimestamp,
|
||||||
|
getSigitDraft,
|
||||||
|
npubToHex,
|
||||||
|
SigitStatus,
|
||||||
|
SignStatus
|
||||||
|
} from '../../utils'
|
||||||
|
import { DisplaySigner } from '../DisplaySigner'
|
||||||
|
import { UserAvatarGroup } from '../UserAvatarGroup'
|
||||||
|
import { getExtensionIconLabel } from '../getExtensionIconLabel'
|
||||||
|
import { useAppSelector, useDidMount } from '../../hooks'
|
||||||
|
import styles from './style.module.scss'
|
||||||
|
|
||||||
|
interface LocalDraftSigitProps {
|
||||||
|
handleDraftDelete: () => void
|
||||||
|
}
|
||||||
|
export const LocalDraftSigit = ({
|
||||||
|
handleDraftDelete
|
||||||
|
}: LocalDraftSigitProps) => {
|
||||||
|
const [draft, setDraft] = useState<SigitDraft>()
|
||||||
|
useDidMount(async () => {
|
||||||
|
// Check if draft exists and add link to direct
|
||||||
|
const draft = await getSigitDraft()
|
||||||
|
if (draft) {
|
||||||
|
setDraft(draft)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const submittedBy = useAppSelector((state) => state.auth.usersPubkey)
|
||||||
|
|
||||||
|
if (!draft) return null
|
||||||
|
|
||||||
|
const extensions = draft.files.map((f) => f.extension)
|
||||||
|
const isSame = extensions.every((e) => extensions[0] === e)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.itemWrapper}>
|
||||||
|
<Link className={styles.insetLink} to={appPrivateRoutes.create}></Link>
|
||||||
|
<p className={`line-clamp-2 ${styles.title}`}>{draft.title}</p>
|
||||||
|
<div className={styles.users}>
|
||||||
|
{submittedBy && (
|
||||||
|
<DisplaySigner status={SignStatus.Pending} pubkey={submittedBy} />
|
||||||
|
)}
|
||||||
|
{submittedBy && draft.users.length ? (
|
||||||
|
<Divider orientation="vertical" flexItem />
|
||||||
|
) : null}
|
||||||
|
<UserAvatarGroup max={7}>
|
||||||
|
{draft.users.map((user) => {
|
||||||
|
const pubkey = npubToHex(user.pubkey)!
|
||||||
|
return (
|
||||||
|
<DisplaySigner
|
||||||
|
key={pubkey}
|
||||||
|
status={
|
||||||
|
user.role === UserRole.signer
|
||||||
|
? SignStatus.Pending
|
||||||
|
: SignStatus.Viewer
|
||||||
|
}
|
||||||
|
pubkey={pubkey}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</UserAvatarGroup>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.details} ${styles.iconLabel}`}>
|
||||||
|
<FontAwesomeIcon icon={faCalendar} />
|
||||||
|
{formatTimestamp(draft.lastUpdated)}
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.details} ${styles.status}`}>
|
||||||
|
<span className={styles.iconLabel}>
|
||||||
|
<FontAwesomeIcon icon={faPen} /> {SigitStatus.LocalDraft}
|
||||||
|
</span>
|
||||||
|
{extensions.length > 0 ? (
|
||||||
|
<span className={styles.iconLabel}>
|
||||||
|
{!isSame ? (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faFile} /> Multiple File Types
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
getExtensionIconLabel(extensions[0])
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faFileCircleExclamation} /> —
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.itemActions}>
|
||||||
|
<Tooltip title="Delete" arrow placement="top" disableInteractive>
|
||||||
|
<Button
|
||||||
|
onClick={handleDraftDelete}
|
||||||
|
sx={{
|
||||||
|
color: 'var(--primary-main)',
|
||||||
|
minWidth: '34px',
|
||||||
|
padding: '10px'
|
||||||
|
}}
|
||||||
|
variant={'text'}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -18,7 +18,6 @@ import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf'
|
|||||||
import { useScale } from '../../hooks/useScale'
|
import { useScale } from '../../hooks/useScale'
|
||||||
import { AvatarIconButton } from '../UserAvatarIconButton'
|
import { AvatarIconButton } from '../UserAvatarIconButton'
|
||||||
import { UserAvatar } from '../UserAvatar'
|
import { UserAvatar } from '../UserAvatar'
|
||||||
import { Updater } from 'use-immer'
|
|
||||||
import { FileItem } from './internal/FileItem'
|
import { FileItem } from './internal/FileItem'
|
||||||
import { FileDivider } from '../FileDivider'
|
import { FileDivider } from '../FileDivider'
|
||||||
import { Counterpart } from './internal/Counterpart'
|
import { Counterpart } from './internal/Counterpart'
|
||||||
@ -28,6 +27,7 @@ const MINIMUM_RECT_SIZE = {
|
|||||||
height: 10
|
height: 10
|
||||||
} as const
|
} as const
|
||||||
import { NDKUserProfile } from '@nostr-dev-kit/ndk'
|
import { NDKUserProfile } from '@nostr-dev-kit/ndk'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
const DEFAULT_START_SIZE = {
|
const DEFAULT_START_SIZE = {
|
||||||
width: 140,
|
width: 140,
|
||||||
@ -45,7 +45,7 @@ interface DrawPdfFieldsProps {
|
|||||||
users: User[]
|
users: User[]
|
||||||
userProfiles: { [key: string]: NDKUserProfile }
|
userProfiles: { [key: string]: NDKUserProfile }
|
||||||
sigitFiles: SigitFile[]
|
sigitFiles: SigitFile[]
|
||||||
updateSigitFiles: Updater<SigitFile[]>
|
setSigitFiles: React.Dispatch<React.SetStateAction<SigitFile[]>>
|
||||||
selectedTool?: DrawTool
|
selectedTool?: DrawTool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,11 +53,10 @@ export const DrawPDFFields = ({
|
|||||||
selectedTool,
|
selectedTool,
|
||||||
userProfiles,
|
userProfiles,
|
||||||
sigitFiles,
|
sigitFiles,
|
||||||
updateSigitFiles,
|
setSigitFiles,
|
||||||
users
|
users
|
||||||
}: DrawPdfFieldsProps) => {
|
}: DrawPdfFieldsProps) => {
|
||||||
const { to, from } = useScale()
|
const { to, from } = useScale()
|
||||||
|
|
||||||
const signers = users.filter((u) => u.role === UserRole.signer)
|
const signers = users.filter((u) => u.role === UserRole.signer)
|
||||||
const defaultSignerNpub = signers.length ? hexToNpub(signers[0].pubkey) : ''
|
const defaultSignerNpub = signers.length ? hexToNpub(signers[0].pubkey) : ''
|
||||||
const [lastSigner, setLastSigner] = useState(defaultSignerNpub)
|
const [lastSigner, setLastSigner] = useState(defaultSignerNpub)
|
||||||
@ -354,8 +353,10 @@ export const DrawPDFFields = ({
|
|||||||
) => {
|
) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
updateSigitFiles((draft) => {
|
setSigitFiles((prev) => {
|
||||||
draft[fileIndex]?.pages![pageIndex]?.drawnFields?.splice(fieldIndex, 1)
|
const clone = _.cloneDeep(prev)
|
||||||
|
clone[fileIndex]?.pages![pageIndex]?.drawnFields?.splice(fieldIndex, 1)
|
||||||
|
return clone
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,22 +417,28 @@ export const DrawPDFFields = ({
|
|||||||
|
|
||||||
// Add new drawn field to the files
|
// Add new drawn field to the files
|
||||||
if (mouseState.clicked) {
|
if (mouseState.clicked) {
|
||||||
updateSigitFiles((draft) => {
|
setSigitFiles((prev) => {
|
||||||
draft[fileIndex].pages![pageIndex].drawnFields.push(field)
|
const clone = _.cloneDeep(prev)
|
||||||
|
clone[fileIndex].pages![pageIndex].drawnFields.push(field)
|
||||||
|
return clone
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move
|
// Move
|
||||||
if (mouseState.dragging) {
|
if (mouseState.dragging) {
|
||||||
updateSigitFiles((draft) => {
|
setSigitFiles((prev) => {
|
||||||
draft[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
|
const clone = _.cloneDeep(prev)
|
||||||
|
clone[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
|
||||||
|
return clone
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize
|
// Resize
|
||||||
if (mouseState.resizing) {
|
if (mouseState.resizing) {
|
||||||
updateSigitFiles((draft) => {
|
setSigitFiles((prev) => {
|
||||||
draft[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
|
const clone = _.cloneDeep(prev)
|
||||||
|
clone[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
|
||||||
|
return clone
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,7 +453,7 @@ export const DrawPDFFields = ({
|
|||||||
mouseState.clicked,
|
mouseState.clicked,
|
||||||
mouseState.dragging,
|
mouseState.dragging,
|
||||||
mouseState.resizing,
|
mouseState.resizing,
|
||||||
updateSigitFiles
|
setSigitFiles
|
||||||
])
|
])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +45,10 @@ import {
|
|||||||
uploadToFileStorage,
|
uploadToFileStorage,
|
||||||
DEFAULT_TOOLBOX,
|
DEFAULT_TOOLBOX,
|
||||||
settleAllFullfilfedPromises,
|
settleAllFullfilfedPromises,
|
||||||
uploadMetaToFileStorage
|
uploadMetaToFileStorage,
|
||||||
|
clearSigitDraft,
|
||||||
|
saveSigitDraft,
|
||||||
|
getSigitDraft
|
||||||
} 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'
|
||||||
@ -77,7 +80,6 @@ import { AvatarIconButton } from '../../components/UserAvatarIconButton'
|
|||||||
import { NDKUserProfile, NostrEvent } from '@nostr-dev-kit/ndk'
|
import { NDKUserProfile, NostrEvent } from '@nostr-dev-kit/ndk'
|
||||||
import { useNDKContext } from '../../hooks/useNDKContext.ts'
|
import { useNDKContext } from '../../hooks/useNDKContext.ts'
|
||||||
import { useNDK } from '../../hooks/useNDK.ts'
|
import { useNDK } from '../../hooks/useNDK.ts'
|
||||||
import { useImmer } from 'use-immer'
|
|
||||||
import { ButtonUnderline } from '../../components/ButtonUnderline/index.tsx'
|
import { ButtonUnderline } from '../../components/ButtonUnderline/index.tsx'
|
||||||
|
|
||||||
type FoundUser = NostrEvent & { npub: string }
|
type FoundUser = NostrEvent & { npub: string }
|
||||||
@ -97,7 +99,9 @@ export const CreatePage = () => {
|
|||||||
|
|
||||||
const [title, setTitle] = useState(`sigit_${formatTimestamp(Date.now())}`)
|
const [title, setTitle] = useState(`sigit_${formatTimestamp(Date.now())}`)
|
||||||
|
|
||||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([...uploadedFiles])
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([
|
||||||
|
...(uploadedFiles || [])
|
||||||
|
])
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
const handleUploadButtonClick = () => {
|
const handleUploadButtonClick = () => {
|
||||||
if (fileInputRef.current) {
|
if (fileInputRef.current) {
|
||||||
@ -123,7 +127,7 @@ export const CreatePage = () => {
|
|||||||
[key: string]: NDKUserProfile
|
[key: string]: NDKUserProfile
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
const [drawnFiles, updateDrawnFiles] = useImmer<SigitFile[]>([])
|
const [drawnFiles, setDrawnFiles] = useState<SigitFile[]>([])
|
||||||
const [parsingPdf, setIsParsing] = useState<boolean>(false)
|
const [parsingPdf, setIsParsing] = useState<boolean>(false)
|
||||||
|
|
||||||
const searchFieldRef = useRef<HTMLInputElement>(null)
|
const searchFieldRef = useRef<HTMLInputElement>(null)
|
||||||
@ -283,27 +287,29 @@ export const CreatePage = () => {
|
|||||||
selectedFiles,
|
selectedFiles,
|
||||||
getSigitFile
|
getSigitFile
|
||||||
)
|
)
|
||||||
updateDrawnFiles((draft) => {
|
setDrawnFiles((prev) => {
|
||||||
|
const clone = _.cloneDeep(prev)
|
||||||
// Existing files are untouched
|
// Existing files are untouched
|
||||||
|
|
||||||
// Handle removed files
|
// Handle removed files
|
||||||
// Remove in reverse to avoid index issues
|
// Remove in reverse to avoid index issues
|
||||||
for (let i = draft.length - 1; i >= 0; i--) {
|
for (let i = clone.length - 1; i >= 0; i--) {
|
||||||
if (
|
if (
|
||||||
!files.some(
|
!files.some(
|
||||||
(f) => f.name === draft[i].name && f.size === draft[i].size
|
(f) => f.name === clone[i].name && f.size === clone[i].size
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
draft.splice(i, 1)
|
clone.splice(i, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new files
|
// Add new files
|
||||||
files.forEach((f) => {
|
files.forEach((f) => {
|
||||||
if (!draft.some((d) => d.name === f.name && d.size === f.size)) {
|
if (!clone.some((d) => d.name === f.name && d.size === f.size)) {
|
||||||
draft.push(f)
|
clone.push(f)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return clone
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +319,52 @@ export const CreatePage = () => {
|
|||||||
setIsParsing(false)
|
setIsParsing(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [selectedFiles, updateDrawnFiles])
|
}, [selectedFiles])
|
||||||
|
|
||||||
|
const [draftEnabled, setDraftEnabled] = useState(true)
|
||||||
|
useEffect(() => {
|
||||||
|
// Only proceed if we have no uploaded files
|
||||||
|
if (uploadedFiles?.length ?? 0) return
|
||||||
|
|
||||||
|
getSigitDraft().then((draft) => {
|
||||||
|
if (draft) {
|
||||||
|
setSelectedFiles(draft.files)
|
||||||
|
setDrawnFiles((prev) => {
|
||||||
|
const clone = _.cloneDeep(prev)
|
||||||
|
clone.splice(0, clone.length, ...draft.files)
|
||||||
|
return clone
|
||||||
|
})
|
||||||
|
setUsers(draft.users)
|
||||||
|
setTitle(draft.title)
|
||||||
|
|
||||||
|
// After loading draft clear it
|
||||||
|
clearSigitDraft()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [uploadedFiles])
|
||||||
|
useEffect(() => {
|
||||||
|
if (draftEnabled) {
|
||||||
|
saveSigitDraft({
|
||||||
|
title,
|
||||||
|
users,
|
||||||
|
lastUpdated: Date.now(),
|
||||||
|
files: drawnFiles
|
||||||
|
}).catch((error) => {
|
||||||
|
if (
|
||||||
|
error instanceof DOMException &&
|
||||||
|
error.name === 'QuotaExceededError'
|
||||||
|
) {
|
||||||
|
// Disable draft if we hit size error
|
||||||
|
setDraftEnabled(false)
|
||||||
|
console.warn(
|
||||||
|
'Draft functionality disabled temporarily. File size exceeds local storage limit.'
|
||||||
|
)
|
||||||
|
clearSigitDraft()
|
||||||
|
}
|
||||||
|
// Ignore other errors
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [draftEnabled, drawnFiles, title, users])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the drawing tool
|
* Changes the drawing tool
|
||||||
@ -504,7 +555,7 @@ export const CreatePage = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
updateDrawnFiles(drawnFilesCopy)
|
setDrawnFiles(drawnFilesCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -940,6 +991,7 @@ export const CreatePage = () => {
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
clearSigitDraft()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1017,6 +1069,7 @@ export const CreatePage = () => {
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
clearSigitDraft()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1285,7 +1338,7 @@ export const CreatePage = () => {
|
|||||||
userProfiles={userProfiles}
|
userProfiles={userProfiles}
|
||||||
selectedTool={selectedTool}
|
selectedTool={selectedTool}
|
||||||
sigitFiles={drawnFiles}
|
sigitFiles={drawnFiles}
|
||||||
updateSigitFiles={updateDrawnFiles}
|
setSigitFiles={setDrawnFiles}
|
||||||
/>
|
/>
|
||||||
{parsingPdf && <LoadingSpinner variant="small" />}
|
{parsingPdf && <LoadingSpinner variant="small" />}
|
||||||
</StickySideColumns>
|
</StickySideColumns>
|
||||||
|
@ -2,7 +2,7 @@ import { Button, TextField } from '@mui/material'
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { useAppSelector } from '../../hooks'
|
import { useAppSelector, useDidMount } from '../../hooks'
|
||||||
import { appPrivateRoutes } from '../../routes'
|
import { appPrivateRoutes } from '../../routes'
|
||||||
import { Meta } from '../../types'
|
import { Meta } from '../../types'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
@ -13,12 +13,15 @@ import { useDropzone } from 'react-dropzone'
|
|||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import {
|
import {
|
||||||
|
clearSigitDraft,
|
||||||
extractSigitCardDisplayInfo,
|
extractSigitCardDisplayInfo,
|
||||||
|
hasSigitDraft,
|
||||||
navigateFromZip,
|
navigateFromZip,
|
||||||
SigitCardDisplayInfo,
|
SigitCardDisplayInfo,
|
||||||
SigitStatus
|
SigitStatus
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import { Footer } from '../../components/Footer/Footer'
|
import { Footer } from '../../components/Footer/Footer'
|
||||||
|
import { LocalDraftSigit } from '../../components/DisplaySigit/LocalDraftSigit'
|
||||||
|
|
||||||
// Unsupported Filter options are commented
|
// Unsupported Filter options are commented
|
||||||
const FILTERS = [
|
const FILTERS = [
|
||||||
@ -44,6 +47,12 @@ export const HomePage = () => {
|
|||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
const q = searchParams.get('q') ?? ''
|
const q = searchParams.get('q') ?? ''
|
||||||
|
|
||||||
|
const [showDraft, setShowDraft] = useState<boolean>(false)
|
||||||
|
useDidMount(async () => {
|
||||||
|
// Check if draft exists and add link to direct
|
||||||
|
setShowDraft(hasSigitDraft())
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const searchInput = document.getElementById('q') as HTMLInputElement | null
|
const searchInput = document.getElementById('q') as HTMLInputElement | null
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
@ -152,7 +161,7 @@ export const HomePage = () => {
|
|||||||
meta={sigits[key]}
|
meta={sigits[key]}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
} else {
|
} else if (!showDraft) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.noResults}>
|
<div className={styles.noResults}>
|
||||||
<p>No results</p>
|
<p>No results</p>
|
||||||
@ -260,7 +269,17 @@ export const HomePage = () => {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className={styles.submissions}>{renderSubmissions()}</div>
|
<div className={styles.submissions}>
|
||||||
|
{showDraft && (
|
||||||
|
<LocalDraftSigit
|
||||||
|
handleDraftDelete={() => {
|
||||||
|
clearSigitDraft()
|
||||||
|
setShowDraft(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{renderSubmissions()}
|
||||||
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,9 +11,11 @@ export interface SigitDraft {
|
|||||||
title: string
|
title: string
|
||||||
users: User[]
|
users: User[]
|
||||||
files: SigitFile[]
|
files: SigitFile[]
|
||||||
|
lastUpdated: number
|
||||||
}
|
}
|
||||||
export interface SerializedSigitDraft {
|
export interface SerializedSigitDraft {
|
||||||
title: string
|
title: string
|
||||||
|
lastUpdated: number
|
||||||
users: User[]
|
users: User[]
|
||||||
files: SigitFileDraft[]
|
files: SigitFileDraft[]
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
toFile,
|
toFile,
|
||||||
getSigitFile
|
getSigitFile
|
||||||
} from './file'
|
} from './file'
|
||||||
|
const DRAFT_KEY = 'sigitDraft'
|
||||||
let saveSigitDraftTimeout: number | null = null
|
let saveSigitDraftTimeout: number | null = null
|
||||||
const serializeSigitDraft = async (
|
const serializeSigitDraft = async (
|
||||||
draft: SigitDraft
|
draft: SigitDraft
|
||||||
@ -48,6 +48,7 @@ const serializeSigitDraft = async (
|
|||||||
const serializedFileDraft = await Promise.all(serializedFiles)
|
const serializedFileDraft = await Promise.all(serializedFiles)
|
||||||
return {
|
return {
|
||||||
title: draft.title,
|
title: draft.title,
|
||||||
|
lastUpdated: draft.lastUpdated,
|
||||||
users: [...draft.users],
|
users: [...draft.users],
|
||||||
files: serializedFileDraft
|
files: serializedFileDraft
|
||||||
}
|
}
|
||||||
@ -79,24 +80,31 @@ const deserializeSigitDraft = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveSigitDraft = (draft: SigitDraft) => {
|
export const saveSigitDraft = (draft: SigitDraft): Promise<void> => {
|
||||||
if (saveSigitDraftTimeout) {
|
if (saveSigitDraftTimeout) {
|
||||||
clearTimeout(saveSigitDraftTimeout)
|
clearTimeout(saveSigitDraftTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveSigitDraftTimeout = window.setTimeout(() => {
|
return new Promise((resolve, reject) => {
|
||||||
serializeSigitDraft(draft)
|
saveSigitDraftTimeout = window.setTimeout(() => {
|
||||||
.then((draftToSave) => {
|
serializeSigitDraft(draft)
|
||||||
localStorage.setItem('sigitDraft', JSON.stringify(draftToSave))
|
.then((draftToSave) => {
|
||||||
})
|
localStorage.setItem(DRAFT_KEY, JSON.stringify(draftToSave))
|
||||||
.catch((error) => {
|
resolve()
|
||||||
console.log(`Error while saving sigit draft. Error: `, error)
|
})
|
||||||
})
|
.catch((error) => {
|
||||||
}, 1000)
|
reject(error)
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasSigitDraft = () => {
|
||||||
|
return DRAFT_KEY in localStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSigitDraft = async () => {
|
export const getSigitDraft = async () => {
|
||||||
const sigitDraft = localStorage.getItem('sigitDraft')
|
const sigitDraft = localStorage.getItem(DRAFT_KEY)
|
||||||
if (!sigitDraft) return null
|
if (!sigitDraft) return null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -108,5 +116,5 @@ export const getSigitDraft = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const clearSigitDraft = () => {
|
export const clearSigitDraft = () => {
|
||||||
localStorage.removeItem('sigitDraft')
|
localStorage.removeItem(DRAFT_KEY)
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,8 @@ export enum SignStatus {
|
|||||||
|
|
||||||
export enum SigitStatus {
|
export enum SigitStatus {
|
||||||
Partial = 'In-Progress',
|
Partial = 'In-Progress',
|
||||||
Complete = 'Completed'
|
Complete = 'Completed',
|
||||||
|
LocalDraft = 'Draft'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SigitCardDisplayInfo {
|
export interface SigitCardDisplayInfo {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user