-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #236 from pnnl/186-downloading-and-uploading-project
186 Project level document upload and download functionality
- Loading branch information
Showing
9 changed files
with
463 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import React from 'react' | ||
import JSONValue from '../types/json_value.type' | ||
import { Button } from 'react-bootstrap' | ||
import { TfiImport } from 'react-icons/tfi' | ||
import { EXPORT_FILE_TYPE } from '../utilities/paths_utils' | ||
|
||
interface ExportDocProps { | ||
sendData: JSONValue // The data to be downloaded | ||
fileName: string // The name of the file | ||
} | ||
|
||
/** | ||
* A component that triggers the download of a document as a file. | ||
* @param sendData - The data to be exported, json data from the DB. | ||
* @param fileName - The name of the file to be downloaded. (extension as *.qit) | ||
* @returns A React element that renders a button for downloading the document. | ||
*/ | ||
const ExportDoc: React.FC<ExportDocProps> = ({ | ||
sendData, | ||
fileName, | ||
}: any): JSX.Element => { | ||
const handleDownload = () => { | ||
try { | ||
//Create a Blob from the sendData | ||
const blob = new Blob([sendData as BlobPart], { | ||
type: 'application/json', | ||
}) | ||
const url = URL.createObjectURL(blob) | ||
|
||
// Create a temporary anchor element to trigger the download | ||
const link = document.createElement('a') | ||
link.href = url | ||
link.setAttribute('download', `${fileName + EXPORT_FILE_TYPE}`) | ||
|
||
// Append to the body, click and remove it | ||
document.body.appendChild(link) | ||
link.click() | ||
document.body.removeChild(link) | ||
|
||
// Clean up the URL object | ||
URL.revokeObjectURL(url) | ||
} catch (error) { | ||
console.log('Error in exporting the document', error) | ||
} | ||
} | ||
return ( | ||
<Button | ||
variant="light" | ||
onClick={event => { | ||
event.stopPropagation() | ||
event.preventDefault() | ||
handleDownload() | ||
}} | ||
> | ||
<TfiImport size={20} /> | ||
</Button> | ||
) | ||
} | ||
|
||
export default ExportDoc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { FC, useEffect, useState } from 'react' | ||
import ExportDoc from './export_document' | ||
import { exportDocumentAsJSONObject, useDB } from '../utilities/database_utils' | ||
import JSONValue from '../types/json_value.type' | ||
|
||
// Define the props interface for ExportDocWrapper | ||
interface ExportDocWrapperProps { | ||
docId: string | ||
docName: string | ||
includeChild: boolean | ||
} | ||
|
||
/** | ||
* A wrapper component for `ExportDoc` that exports a document from a PouchDB database as a JSON object. | ||
* @param {string} props.docId - The ID of the document to be exported. | ||
* @param {string} props.docName - The name of the document, used for naming the export file. | ||
* @param {boolean} props.includeChild - A flag indicating whether to include child documents in the export. | ||
* @returns {JSX.Element} A React element that renders the `ExportDoc` component with the exported data. | ||
*/ | ||
const ExportDocWrapper: FC<ExportDocWrapperProps> = ({ | ||
docId, | ||
docName, | ||
includeChild, | ||
}: ExportDocWrapperProps): JSX.Element => { | ||
const db = useDB() | ||
const [sendData, setSendData] = useState<JSONValue>({}) | ||
|
||
useEffect(() => { | ||
exportDocumentAsJSONObject(db, docId, includeChild).then(data => | ||
setSendData(data), | ||
) | ||
}, []) | ||
|
||
const timestamp = new Date(Date.now()).toUTCString() | ||
return ( | ||
<ExportDoc sendData={sendData} fileName={docName + ' ' + timestamp} /> | ||
) | ||
} | ||
|
||
export default ExportDocWrapper |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { useEffect, useRef, useState } from 'react' | ||
import type { ChangeEvent, FC, MouseEvent } from 'react' | ||
import { Button } from 'react-bootstrap' | ||
import { ImportDocumentIntoDB, useDB } from '../utilities/database_utils' | ||
import { EXPORT_FILE_TYPE } from '../utilities/paths_utils' | ||
|
||
interface ImportDocProps { | ||
id: string | ||
label: string | ||
} | ||
|
||
/** | ||
* ImportDoc component : Imports JSON documents into a PouchDB database. | ||
* | ||
* This component handles the importation of a project document from a JSON file into a PouchDB database. | ||
* It renders a button that, when clicked, triggers a hidden file input to open. When a file is selected, | ||
* the file's content is read, processed, and imported into the database. | ||
* | ||
* @param label - The label for the import operation. | ||
* @param file - The file to be imported. | ||
* | ||
* @returns The rendered button and hidden file input elements for importing a project. | ||
*/ | ||
const ImportDoc: FC<ImportDocProps> = ({ id, label }) => { | ||
// Create references to the hidden file inputs | ||
const hiddenFileUploadInputRef = useRef<HTMLInputElement>(null) | ||
const [projectNames, setProjectNames] = useState<string[]>([]) | ||
const [isFileProcessed, setIsFileProcessed] = useState<boolean>(false) | ||
const [error, setError] = useState<String>('') | ||
const db = useDB() | ||
|
||
const handleFileInputButtonClick = ( | ||
event: MouseEvent<HTMLButtonElement>, | ||
) => { | ||
hiddenFileUploadInputRef.current?.click() | ||
} | ||
|
||
/** | ||
* Fetches project names from the database and updates the state. | ||
* | ||
* @returns {Promise<void>} A promise that resolves when the fetch operation is complete. | ||
*/ | ||
const fetchProjectNames = async (): Promise<void> => { | ||
const result = await db.allDocs({ | ||
include_docs: true, | ||
}) | ||
const projectDocs = result.rows | ||
.map((row: any) => row.doc) | ||
.filter((doc: any) => doc.type === 'project') | ||
|
||
const projectNames = projectDocs.map( | ||
(doc: any) => doc.metadata_.doc_name, | ||
) | ||
setProjectNames(projectNames) | ||
} | ||
|
||
useEffect(() => { | ||
// Retrieve all project names from the database when each upload is processed | ||
fetchProjectNames() | ||
}, [projectNames, isFileProcessed]) | ||
|
||
const handleFileInputChange = async ( | ||
event: ChangeEvent<HTMLInputElement>, | ||
) => { | ||
if (event.target.files) { | ||
const file = event.target.files[0] | ||
if (file) { | ||
const isValid = file.name.endsWith(EXPORT_FILE_TYPE) | ||
if (!isValid) { | ||
setError( | ||
"Please select file with extension '" + | ||
EXPORT_FILE_TYPE + | ||
"'", | ||
) | ||
} else { | ||
setError('') | ||
processJsonData(file) // Processes JSON data from a file | ||
} | ||
} | ||
event.target.value = '' | ||
} | ||
} | ||
|
||
/** | ||
* Processes JSON data from a file and imports it into the database. | ||
* | ||
* @param {File} file - The JSON file containing documents to be imported. | ||
* @returns {Promise<void>} A promise that resolves when the processing is complete. | ||
*/ | ||
const processJsonData = async (file: File): Promise<void> => { | ||
// Reset state and local variables for every import | ||
setIsFileProcessed(false) | ||
|
||
const reader = new FileReader() | ||
reader.readAsText(file) | ||
reader.onload = async event => { | ||
const dataFromFile = (event.target as FileReader).result | ||
if (typeof dataFromFile !== 'string') { | ||
console.error('File content is not a string.') | ||
return | ||
} | ||
try { | ||
const jsonData = JSON.parse(dataFromFile) | ||
await ImportDocumentIntoDB(db, jsonData, projectNames) | ||
} catch (error) { | ||
console.error('Error parsing JSON from file:', error) | ||
} | ||
} | ||
} | ||
return ( | ||
<> | ||
{' '} | ||
| ||
<Button onClick={handleFileInputButtonClick}>{label}</Button> | ||
<input | ||
accept={'*' + EXPORT_FILE_TYPE} | ||
onChange={handleFileInputChange} | ||
ref={hiddenFileUploadInputRef} | ||
className="photo-upload-input" | ||
type="file" | ||
/> | ||
{error && <div className="error">{error}</div>} | ||
</> | ||
) | ||
} | ||
|
||
export default ImportDoc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { FC } from 'react' | ||
import ImportDoc from './import_document' | ||
|
||
// Define the props interface for ImportDocWrapper | ||
interface ImportDocWrapperProps { | ||
id: string | ||
label: string | ||
} | ||
|
||
/** | ||
* ImportDocWrapper component. | ||
* | ||
* This component wraps the `ImportDoc` component, retrieves attachments and job ID from the context, constructs | ||
* a reference ID, and passes the appropriate file blob and label to `ImportDoc`. | ||
* | ||
* @param {string} label - The label to be displayed for the import operation. | ||
* | ||
* @returns {JSX.Element} The rendered ImportDoc component with context-provided props. | ||
*/ | ||
const ImportDocWrapper: FC<ImportDocWrapperProps> = ({ | ||
id, | ||
label, | ||
}: ImportDocWrapperProps): JSX.Element => { | ||
return <ImportDoc id={id} label={label} /> | ||
} | ||
|
||
export default ImportDocWrapper |
Oops, something went wrong.