Skip to content
This repository has been archived by the owner on Nov 29, 2023. It is now read-only.

feat: Import and export annotations #127

Merged
merged 15 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 6 additions & 2 deletions client/src/Components/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import SelectionView from "../SelectionView/SelectionView";

export default function App(): JSX.Element {
const [pythonPackage, setPythonPackage] = useState<PythonPackage>(parsePythonPackageJson(pythonPackageJson as PythonPackageJson));
const [annotationStore, setAnnotationStore] = useState(new AnnotationStore());
const initialJSON = JSON.parse('{"renamings":{},"enums":{}}');
initialJSON["renamings"] = new Map();
initialJSON["enums"] = new Map();
const [annotationStore, setAnnotationStore] = useState(AnnotationStore.fromJson(initialJSON));

return (
<HashRouter>
<div className={AppCSS.app}>
<div className={AppCSS.menu}>
<Menu setPythonPackage={setPythonPackage}/>
<Menu setPythonPackage={setPythonPackage} annotationStore={annotationStore}
setAnnotationStore={setAnnotationStore}/>
</div>
<div className={AppCSS.leftPane}>
<TreeView pythonPackage={pythonPackage}/>
Expand Down
77 changes: 47 additions & 30 deletions client/src/Components/Dialog/ImportAnnotationFileDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,62 @@
import React, {useState} from "react";
import React, {FormEvent, useState} from "react";
import {Button, Form, Modal} from "react-bootstrap";
import Dropzone from 'react-dropzone';
import {Setter} from "../../util/types";
import {isValidJsonFile} from "../../util/validation";
import "../SelectionView/SelectionView.css";
import Dropzone from 'react-dropzone';
import {isValidJsonFile} from "../../util/validation";
import DialogCSS from "./dialog.module.css";
import AnnotationStore from "../../model/annotation/AnnotationStore";

interface ImportAnnotationFileDialogProps {
isVisible: boolean
setIsVisible: Setter<boolean>
setAnnotationStore: Setter<AnnotationStore>
}

export default function ImportAnnotationFileDialog(props: ImportAnnotationFileDialogProps): JSX.Element {

const [fileName, setFileName] = useState("");
/* not yet needed, but important for storing the file later
const [file, setFile] = useState<File[]>([]); */
const [newAnnotationStore, setNewAnnotationStore] = useState(new AnnotationStore());

const close = () => {
props.setIsVisible(false);
};

const submit = () => {
console.log("TODO");
const submit = (event: FormEvent) => {
event.preventDefault();
if (fileName) {
props.setAnnotationStore(newAnnotationStore);
}
props.setIsVisible(false);
};

const onDrop = (acceptedFiles: File[]) => {
if (isValidJsonFile(acceptedFiles[acceptedFiles.length - 1].name)) {
if (acceptedFiles.length > 1) {
acceptedFiles = [acceptedFiles[acceptedFiles.length - 1]];
}
setFileName(acceptedFiles[0].name);
const reader = new FileReader();
reader.onload = () => {
if (typeof reader.result === 'string') {
const readAnnotationJson = JSON.parse(reader.result);
readAnnotationJson["renamings"] = new Map(Object.entries(readAnnotationJson["renamings"]));
readAnnotationJson["enums"] = new Map(Object.entries(readAnnotationJson["enums"]));
setNewAnnotationStore(AnnotationStore.fromJson(readAnnotationJson));
}
};
reader.readAsText(acceptedFiles[0]);
}
};

return (
<Modal
onHide={close}
show={props.isVisible}
size={"lg"}
className={DialogCSS.modalDialog}
>
<Modal onHide={close}
show={props.isVisible}
size={"lg"}
className={DialogCSS.modalDialog}>
<Modal.Header closeButton>
<Modal.Title>Import annotation file</Modal.Title>
</Modal.Header>

<Modal.Body>
<Form noValidate>
<Modal.Body>
Expand All @@ -44,23 +65,16 @@ export default function ImportAnnotationFileDialog(props: ImportAnnotationFileDi
Select an annotation file to upload.
</Form.Label>
<div className={DialogCSS.dropzone}>
<Dropzone onDrop={acceptedFiles => {
if (isValidJsonFile(acceptedFiles[acceptedFiles.length - 1].name)) {
if (acceptedFiles.length > 1) {
acceptedFiles = [acceptedFiles[acceptedFiles.length - 1]];
}
setFileName(acceptedFiles[0].name);
/* not yet needed, but important for storing the file later
setFile([acceptedFiles[0]]);*/
}
}}>
<Dropzone onDrop={onDrop}>
{({getRootProps, getInputProps}) => (
<section>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p className={DialogCSS.dropzoneText}>Drag and drop an annotation files
here, or click to select the
file<br/>(only *.json will be accepted)</p>
<input {...getInputProps()}/>
<p className={DialogCSS.dropzoneText}>
Drag and drop an annotation file here or click to select the
file.<br/>
(only *.json will be accepted)
</p>
</div>
</section>
)}
Expand All @@ -70,10 +84,13 @@ export default function ImportAnnotationFileDialog(props: ImportAnnotationFileDi
</Form.Group>
</Modal.Body>
<Modal.Footer>
<Button variant="danger" onClick={close}>
<Button variant="danger"
onClick={close}>
Cancel
</Button>
<Button variant="primary" type="button" onSubmit={submit}>
<Button variant="primary"
type="submit"
onClick={submit}>
Submit
</Button>
</Modal.Footer>
Expand Down
67 changes: 36 additions & 31 deletions client/src/Components/Dialog/ImportPythonPackageDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, {FormEvent, useState} from "react";
import {Button, Form, Modal} from "react-bootstrap";
import Dropzone from 'react-dropzone';
import PythonPackage from "../../model/python/PythonPackage";
import {parsePythonPackageJson} from "../../model/python/PythonPackageBuilder";
import {Setter} from "../../util/types";
import {isValidJsonFile} from "../../util/validation";
import "../SelectionView/SelectionView.css";
import Dropzone from 'react-dropzone';
import {isValidJsonFile} from "../../util/validation";
import DialogCSS from "./dialog.module.css";
import PythonPackage from "../../model/python/PythonPackage";
import {parsePythonPackageJson} from "../../model/python/PythonPackageBuilder";

interface ImportPythonPackageDialogProps {
isVisible: boolean
Expand All @@ -29,17 +29,32 @@ export default function ImportPythonPackageDialog(props: ImportPythonPackageDial
if (newPythonPackage) props.setPythonPackage(newPythonPackage);
};

const slurpAndParse = (acceptedFiles: File[]) => {
if (isValidJsonFile(acceptedFiles[acceptedFiles.length - 1].name)) {
if (acceptedFiles.length > 1) {
acceptedFiles = [acceptedFiles[acceptedFiles.length - 1]];
}
setFileName(acceptedFiles[0].name);
const reader = new FileReader();
reader.onload = () => {
if (typeof reader.result === 'string') {
setNewPythonPackage(parsePythonPackageJson(JSON.parse(reader.result)));
}
};
reader.readAsText(acceptedFiles[0]);
}
};

return (
<Modal
onHide={close}
show={props.isVisible}
size={"lg"}
className={DialogCSS.modalDialog}
>
<Modal onHide={close}
show={props.isVisible}
size={"lg"}
className={DialogCSS.modalDialog}>
<Modal.Header closeButton>
<Modal.Title>Import Python package</Modal.Title>
<Modal.Title>
Import Python package
</Modal.Title>
</Modal.Header>

<Modal.Body>
<Form noValidate>
<Modal.Body>
Expand All @@ -48,28 +63,16 @@ export default function ImportPythonPackageDialog(props: ImportPythonPackageDial
Select a Python package to upload.
</Form.Label>
<div className={DialogCSS.dropzone}>
<Dropzone onDrop={acceptedFiles => {
if (isValidJsonFile(acceptedFiles[acceptedFiles.length - 1].name)) {
if (acceptedFiles.length > 1) {
acceptedFiles = [acceptedFiles[acceptedFiles.length - 1]];
}
setFileName(acceptedFiles[0].name);
const reader = new FileReader();
reader.onload = () => {
if (typeof reader.result === 'string') {
setNewPythonPackage(parsePythonPackageJson(JSON.parse(reader.result)));
}
};
reader.readAsText(acceptedFiles[0]);
}
}}>
<Dropzone onDrop={slurpAndParse}>
{({getRootProps, getInputProps}) => (
<section>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p className={DialogCSS.dropzoneText}>Drag and drop a Python package
here, or click to select the
file <br/>(only *.json will be accepted)</p>
<p className={DialogCSS.dropzoneText}>
Drag and drop a Python package here, or click to select the
file.<br/>
(Only *.json will be accepted.)
</p>
</div>
</section>
)}
Expand All @@ -82,7 +85,9 @@ export default function ImportPythonPackageDialog(props: ImportPythonPackageDial
<Button variant="danger" onClick={close}>
Cancel
</Button>
<Button variant="primary" type="submit" onClick={submit}>
<Button variant="primary"
type="submit"
onClick={submit}>
Submit
</Button>
</Modal.Footer>
Expand Down
30 changes: 21 additions & 9 deletions client/src/Components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import classNames from "classnames";
import React, {useState} from 'react';
import {Nav, Navbar, NavDropdown} from "react-bootstrap";
import {useLocation} from "react-router";
import {NavLink} from "react-router-dom";
import PythonPackage from "../../model/python/PythonPackage";
import {Setter} from "../../util/types";
import {useLocation} from "react-router";
import MenuCSS from "./Menu.module.css";
import classNames from "classnames";
import ImportAnnotationFileDialog from "../Dialog/ImportAnnotationFileDialog";
import ImportPythonPackageDialog from "../Dialog/ImportPythonPackageDialog";
import MenuCSS from "./Menu.module.css";
import {Setter} from "../../util/types";
import PythonPackage from "../../model/python/PythonPackage";
import AnnotationStore from "../../model/annotation/AnnotationStore";

interface MenuProps {
setPythonPackage: Setter<PythonPackage>
annotationStore: AnnotationStore
setAnnotationStore: Setter<AnnotationStore>
}

export default function Menu(props: MenuProps): JSX.Element {
Expand All @@ -24,6 +27,10 @@ export default function Menu(props: MenuProps): JSX.Element {
const pathname = useLocation().pathname.split("/").slice(1);
const cssClasses = classNames(MenuCSS.menu, "justify-content-between");

const exportAnnotations = () => {
props.annotationStore.downloadAnnotations(props.annotationStore.toJson());
};

return (
<Navbar className={cssClasses} bg="light" expand="lg">
<Navbar.Text>{
Expand All @@ -39,13 +46,18 @@ export default function Menu(props: MenuProps): JSX.Element {
</Navbar.Text>
<Nav>
<NavDropdown title="Import" id="import-dropdown" align="end">
<NavDropdown.Item onClick={openImportPythonPackageDialog}>Python Package</NavDropdown.Item>
<NavDropdown.Item onClick={openImportAnnotationFileDialog}>Annotation File</NavDropdown.Item>
<NavDropdown.Item onClick={openImportPythonPackageDialog} href="#">Python Package</NavDropdown.Item>
<NavDropdown.Item onClick={openImportAnnotationFileDialog} href="#">Annotation
File</NavDropdown.Item>
</NavDropdown>
<Navbar.Text>Export</Navbar.Text>

<Nav.Link onClick={exportAnnotations} href="#">
Export
</Nav.Link>
</Nav>
{showImportAnnotationFileDialog && <ImportAnnotationFileDialog isVisible={showImportAnnotationFileDialog}
setIsVisible={setShowImportAnnotationFileDialog}/>}
setIsVisible={setShowImportAnnotationFileDialog}
setAnnotationStore={props.setAnnotationStore}/>}
{showImportPythonPackageDialog && <ImportPythonPackageDialog isVisible={showImportPythonPackageDialog}
setIsVisible={setShowImportPythonPackageDialog}
setPythonPackage={props.setPythonPackage}/>}
Expand Down
34 changes: 18 additions & 16 deletions client/src/Components/SelectionView/FunctionView.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from "react";
import AnnotationStore from "../../model/annotation/AnnotationStore";
import PythonFunction from "../../model/python/PythonFunction";
import {isEmptyList} from "../../util/listOperations";
import {Setter} from "../../util/types";
import DocumentationText from "./DocumentationText";
import {isEmptyList} from "../../util/listOperations";
import ParameterNode from "./ParameterNode";
import AnnotationStore from "../../model/annotation/AnnotationStore";
import {Setter} from "../../util/types";

interface FunctionViewProps {
pythonFunction: PythonFunction,
Expand All @@ -18,19 +18,21 @@ export default function FunctionView(props: FunctionViewProps): JSX.Element {
<h1>{props.pythonFunction.name}</h1>
<DocumentationText inputText={props.pythonFunction.description}/>
<h2 className={"function-title"}>Parameters</h2>
{
!isEmptyList(props.pythonFunction.parameters) ?
props.pythonFunction.parameters.map(parameters => (
<ParameterNode
key={parameters.name}
pythonParameter={parameters}
annotationStore={props.annotationStore}
setAnnotationStore={props.setAnnotationStore}
isTitle={false}
/>
)) :
<span className={"text-muted pl-1rem"}>There are no parameters.</span>
}
<div className={"pl-1rem"}>
{
!isEmptyList(props.pythonFunction.parameters) ?
props.pythonFunction.parameters.map(parameters => (
<ParameterNode
key={parameters.name}
pythonParameter={parameters}
annotationStore={props.annotationStore}
setAnnotationStore={props.setAnnotationStore}
isTitle={false}
/>
)) :
<span className={"text-muted pl-1rem"}>There are no parameters.</span>
}
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion client/src/Components/SelectionView/ParameterNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default function ParameterNode(props: ParameterNodeProps): JSX.Element {
</div>

{(newName || newEnumDefinition) &&
<h4>Annotations</h4>
<h5 className={"pl-1rem"}>Annotations</h5>
}
<RenameAnnotationView
newName={newName}
Expand Down
2 changes: 1 addition & 1 deletion client/src/Components/SelectionView/SelectionView.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,5 @@ h1 {

.annotation-list {
padding-bottom: 20px;
padding-left: 1rem;
padding-left: 2rem;
}
Loading