Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: more UI-displayable file types for drop box + experiment results #307

Merged
merged 28 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
756a1fc
refact: factor out FileDisplay component from drop box to be shared
davidlougheed Oct 4, 2023
e609de9
refact: cleaner code for detecting fiel format in FileDisplay
davidlougheed Oct 4, 2023
42eea02
chore: add size prop support to DownloadButton
davidlougheed Oct 4, 2023
9aeb86a
refact: move display components to new subfolder
davidlougheed Oct 4, 2023
c903552
feat(manager): allow viewing CSV/TSV/image files in
davidlougheed Oct 4, 2023
015f246
lint
davidlougheed Oct 4, 2023
37cd3ba
lint: rm console log
davidlougheed Oct 4, 2023
d005bdc
chore: csv-display memoization
davidlougheed Oct 4, 2023
4da41cf
fix: bad language highlighter selection in file display
davidlougheed Oct 4, 2023
734f427
style(manager): widen drop box modal width
davidlougheed Oct 4, 2023
0e63dca
add todo
davidlougheed Oct 4, 2023
08c0b12
feat(explorer): add view file ability for experiment results
davidlougheed Oct 4, 2023
9e3679c
Merge remote-tracking branch 'origin/master' into feat/exp-res-display
davidlougheed Oct 4, 2023
b8e2567
perf(manager): upmount modal child components on close to avoid mem l…
davidlougheed Oct 12, 2023
33f5934
lint: rm debug log
davidlougheed Oct 12, 2023
6410ba2
feat: add audio and video support to file displayer
davidlougheed Oct 12, 2023
eaed37e
chore: update dep
davidlougheed Oct 12, 2023
cb76b41
style: paginate csv display
davidlougheed Oct 12, 2023
c5f622c
feat: add xls/xlsx file display capabilities
davidlougheed Oct 12, 2023
f8b1795
fix: case sensitivity with file extensions
davidlougheed Oct 13, 2023
0e033f8
chore: add webm as viewable file
davidlougheed Oct 13, 2023
ae8654e
lint
davidlougheed Oct 13, 2023
433eab5
feat: add markdown file display
davidlougheed Oct 13, 2023
f457663
lint
davidlougheed Oct 13, 2023
bceb587
Merge remote-tracking branch 'origin/master' into feat/exp-res-display
davidlougheed Oct 14, 2023
0b325ea
feat: add docx rendering to file display
davidlougheed Oct 16, 2023
2779098
chore: improved loading behaviour/styling for file display
davidlougheed Oct 16, 2023
c9e0205
lint: rm done TODO
davidlougheed Oct 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/bento_web.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1,541 changes: 1,463 additions & 78 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,26 @@
"jose": "^4.14.6",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
"mammoth": "^1.6.0",
"papaparse": "^5.4.1",
"prop-types": "^15.7.2",
"query-string": "^6.13.7",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-dropzone": "^14.2.3",
"react-json-view": "^1.21.3",
"react-markdown": "^8.0.7",
"react-pdf": "^6.2.2",
"react-redux": "^7.2.9",
"react-router-dom": "^5.3.4",
"react-router-prop-types": "^1.0.5",
"react-syntax-highlighter": "^13.5.3",
"redux": "^4.2.0",
"redux-thunk": "^2.4.2",
"remark-gfm": "^3.0.1",
"socket.io-client": "^4.7.2",
"victory": "^35.3.1"
"victory": "^35.3.1",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz"
},
"devDependencies": {
"@babel/core": "^7.23.0",
Expand Down
6 changes: 4 additions & 2 deletions src/components/DownloadButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useCallback } from "react";
import { Button } from "antd";
import PropTypes from "prop-types";

const DownloadButton = ({ disabled, uri, children, type }) => {
const DownloadButton = ({ disabled, uri, children, size, type }) => {
const { accessToken } = useSelector((state) => state.auth);

const onClick = useCallback(() => {
Expand All @@ -23,21 +23,23 @@ const DownloadButton = ({ disabled, uri, children, type }) => {
}, [uri, accessToken]);

return (
<Button key="download" icon="download" type={type} disabled={disabled} onClick={onClick}>
<Button key="download" icon="download" size={size} type={type} disabled={disabled} onClick={onClick}>
{children === undefined ? "Download" : children}
</Button>
);
};

DownloadButton.defaultProps = {
disabled: false,
size: "default",
type: "default",
};

DownloadButton.propTypes = {
disabled: PropTypes.bool,
uri: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
size: PropTypes.oneOf(["small", "default", "large"]),
type: PropTypes.oneOf(["primary", "ghost", "dashed", "danger", "link", "default"]),
};

Expand Down
19 changes: 19 additions & 0 deletions src/components/display/AudioDisplay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { useEffect, useRef } from "react";
import PropTypes from "prop-types";

const AudioDisplay = ({blob}) => {
const audioRef = useRef(null);

useEffect(() => {
if (audioRef.current && blob) {
audioRef.current.src = URL.createObjectURL(blob);
}
}, [audioRef, blob]);

return <audio style={{ width: "100%" }} ref={audioRef} controls={true} />;
};
AudioDisplay.propTypes = {
blob: PropTypes.instanceOf(Blob),
};

export default AudioDisplay;
66 changes: 66 additions & 0 deletions src/components/display/CsvDisplay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import Papa from "papaparse";
import { Alert, Table } from "antd";

const TABLE_PAGINATION = { pageSize: 25 };
const TABLE_SCROLL = { x: true };
const DEFAULT_COLUMN = { key: "col" };

const rowKey = (_, i) => `row${i}`;

const CsvDisplay = ({ contents, loading }) => {
const [parsedData, setParsedData] = useState([]);
const [parseError, setParseError] = useState("");
const [isParsing, setIsParsing] = useState(true); // Start in parsing state
const [columns, setColumns] = useState([DEFAULT_COLUMN]);

useEffect(() => {
if (contents === undefined || contents === null) return;

setIsParsing(true);
const rows = [];
Papa.parse(contents, {
worker: true,
step: (res) => {
if (res.errors?.length) {
setParseError(res.errors[0].message);
}
rows.push(Object.fromEntries(res.data.map((v, i) => [`col${i}`, v])));
},
complete() {
setIsParsing(false);
if (!parseError) {
setColumns(rows[0] ? Object.entries(rows[0]).map((_, i) => ({
dataIndex: `col${i}`,
})) : [DEFAULT_COLUMN]);
setParsedData(rows);
}
},
});
}, [contents]);

if (parseError) {
return <Alert message="Parsing error" description={parseError} type="error" showIcon={true} />;
}

return (
<Table
size="small"
bordered={true}
showHeader={false}
pagination={TABLE_PAGINATION}
scroll={TABLE_SCROLL}
loading={loading || isParsing}
columns={columns}
dataSource={parsedData}
rowKey={rowKey}
/>
);
};
CsvDisplay.propTypes = {
contents: PropTypes.string,
loading: PropTypes.bool,
};

export default CsvDisplay;
59 changes: 59 additions & 0 deletions src/components/display/DocxDisplay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useEffect, useMemo, useState } from "react";
import mammoth from "mammoth/mammoth.browser";
import { Alert, Skeleton, Spin } from "antd";
import PropTypes from "prop-types";

const MAMMOTH_OPTIONS = {
convertImage: mammoth.images.imgElement((image) =>
image.read("base64").then((buffer) => ({
src: `data:${image.contentType};base64,${buffer}`,
style: "max-width: 90%; height: auto; margin: 0.5em 5%;",
})),
),
};

const DocxDisplay = ({ contents, loading }) => {
const [parsing, setParsing] = useState(false);
const [error, setError] = useState(null);
const [docHTML, setDocHTML] = useState(null);

useEffect(() => {
if (!contents) return;

(async () => {
setDocHTML(null); // reset HTML contents if array buffer contents changes
setError(null); // reset error if array buffer contents changes
setParsing(true);

try {
const res = await mammoth.convertToHtml({ arrayBuffer: contents }, MAMMOTH_OPTIONS);
res.messages.forEach((msg) => console.info("Received message while parsing .docx:", msg));
setDocHTML(res.value);
} catch (err) {
console.error("Received error while parsing .docx:", err);
setError(err);
} finally {
setParsing(false);
}
})();
}, [contents]);

const innerHTML = useMemo(() => ({ __html: docHTML ?? "<div />" }), [docHTML]);

const waiting = loading || parsing;

// noinspection JSValidateTypes
return <Spin spinning={waiting}>
{waiting && <Skeleton loading={true} />}
{error && (
<Alert showIcon={true} message="Parsing error" description={error} />
)}
<div dangerouslySetInnerHTML={innerHTML} />
</Spin>;
};
DocxDisplay.propTypes = {
contents: PropTypes.instanceOf(ArrayBuffer),
loading: PropTypes.bool,
};

export default DocxDisplay;
Loading