diff --git a/www/src/Theme.ts b/www/src/Theme.tsx
similarity index 90%
rename from www/src/Theme.ts
rename to www/src/Theme.tsx
index e424cb3a..1169fe0c 100644
--- a/www/src/Theme.ts
+++ b/www/src/Theme.tsx
@@ -1,4 +1,7 @@
+import React from "react";
+
import createMuiTheme from "@material-ui/core/styles/createMuiTheme";
+import ClearIcon from "@material-ui/icons/Clear";
const HEADING_TEXT = "Europa, sans-serif";
const BODY_TEXT = '"Open Sans", sans-serif';
@@ -88,6 +91,11 @@ const Theme = createMuiTheme({
root: {
borderRadius: 4,
},
+ outlined: {
+ backgroundColor: "rgba(0, 0, 0, 0.08)",
+ borderColor: "rgba(0, 0, 0, 0.08)",
+ color: "rgba(0, 0, 0, 0.6)",
+ },
label: {
// overline style
fontFamily: HEADING_TEXT,
@@ -96,12 +104,14 @@ const Theme = createMuiTheme({
fontStyle: "normal",
lineHeight: 1.2,
letterSpacing: 1.125,
- color: "rgba(0, 0, 0, 0.6)",
},
labelSmall: {
paddingLeft: 12,
paddingRight: 11,
},
+ deleteIcon: {
+ color: "inherit",
+ },
},
MuiPaper: {
rounded: {
@@ -151,7 +161,11 @@ const Theme = createMuiTheme({
transformOrigin: { vertical: "top", horizontal: "center" },
},
},
- MuiChip: { size: "small" },
+ MuiChip: {
+ size: "small",
+ variant: "outlined",
+ deleteIcon: ,
+ },
MuiButton: { color: "primary" },
},
});
diff --git a/www/src/assets/icons/Action.tsx b/www/src/assets/icons/Action.tsx
index f055780e..6c9252b9 100644
--- a/www/src/assets/icons/Action.tsx
+++ b/www/src/assets/icons/Action.tsx
@@ -2,7 +2,7 @@ import React from "react";
import SvgIcon, { SvgIconProps } from "@material-ui/core/SvgIcon";
import { mdiGestureTap } from "@mdi/js";
-export default function SubTable(props: SvgIconProps) {
+export default function Action(props: SvgIconProps) {
return (
diff --git a/www/src/assets/icons/Upload.tsx b/www/src/assets/icons/Upload.tsx
new file mode 100644
index 00000000..59945325
--- /dev/null
+++ b/www/src/assets/icons/Upload.tsx
@@ -0,0 +1,11 @@
+import * as React from "react";
+import SvgIcon, { SvgIconProps } from "@material-ui/core/SvgIcon";
+import { mdiUpload } from "@mdi/js";
+
+export default function Upload(props: SvgIconProps) {
+ return (
+
+
+
+ );
+}
diff --git a/www/src/components/Confirmation.tsx b/www/src/components/Confirmation.tsx
index 99ae4f6a..65eb0949 100644
--- a/www/src/components/Confirmation.tsx
+++ b/www/src/components/Confirmation.tsx
@@ -20,7 +20,7 @@ const useStyles = makeStyles(theme =>
})
);
-interface Props {
+export interface IConfirmationProps {
children: JSX.Element;
message?: {
title?: string;
@@ -30,9 +30,15 @@ interface Props {
confirm?: string | JSX.Element;
};
confirmationCommand?: string;
+ functionName?: string;
}
-const Confirmation = (props: Props) => {
- const { children, message, confirmationCommand } = props;
+
+export default function Confirmation({
+ children,
+ message,
+ confirmationCommand,
+ functionName = "onClick",
+}: IConfirmationProps) {
const classes = useStyles();
const [showDialog, setShowDialog] = useState(false);
const [dryText, setDryText] = useState("");
@@ -40,20 +46,16 @@ const Confirmation = (props: Props) => {
const handleClose = () => {
setShowDialog(false);
};
- //Gets the function of the wrapped button to use for execution on conformation
- const confirmHandler = children.props.onChange || children.props.onClick;
+
+ const confirmHandler = children.props[functionName];
const button = React.cloneElement(children, {
- onClick: () => {
- setShowDialog(true);
- },
- onChange: () => {
- setShowDialog(true);
- },
+ [functionName]: () => setShowDialog(true),
});
return (
<>
{button}
+
>
);
-};
-
-export default Confirmation;
+}
diff --git a/www/src/components/Fields/CheckBox.tsx b/www/src/components/Fields/CheckBox.tsx
index 34dec7b1..56199465 100644
--- a/www/src/components/Fields/CheckBox.tsx
+++ b/www/src/components/Fields/CheckBox.tsx
@@ -26,6 +26,7 @@ const CheckBox = (props: Props) => {
row.firstName
),
}}
+ functionName="onChange"
>
chipList: { overflow: "hidden" },
chipGridItem: { maxWidth: "100%" },
- chip: { cursor: "pointer", width: "100%" },
+ chip: { width: "100%" },
uploadButton: { marginLeft: "auto" },
})
);
@@ -87,17 +87,16 @@ const File = (props: Props) => {
}
label={file.name}
- component="a"
- target="_blank"
- rel="noopener noreferrer"
- href={file.downloadURL}
+ onClick={e => {
+ window.open(file.downloadURL);
+ e.stopPropagation();
+ }}
onDelete={
config && config.isLocked
? undefined
: () => handleDelete(file.downloadURL)
}
className={classes.chip}
- onClick={e => e.stopPropagation()}
/>
))
diff --git a/www/src/components/SideDrawer/Form/Fields/FileUploader.tsx b/www/src/components/SideDrawer/Form/Fields/FileUploader.tsx
new file mode 100644
index 00000000..b2fa5ba5
--- /dev/null
+++ b/www/src/components/SideDrawer/Form/Fields/FileUploader.tsx
@@ -0,0 +1,151 @@
+import React, { useCallback, useState } from "react";
+import clsx from "clsx";
+import { FieldProps } from "formik";
+
+import { useDropzone } from "react-dropzone";
+import useUploader, { FileValue } from "hooks/useFiretable/useUploader";
+
+import {
+ makeStyles,
+ createStyles,
+ ButtonBase,
+ Typography,
+ Grid,
+ Chip,
+ CircularProgress,
+} from "@material-ui/core";
+
+import UploadIcon from "assets/icons/Upload";
+import { FileIcon } from "constants/fields";
+
+import ErrorMessage from "../ErrorMessage";
+import Confirmation from "components/Confirmation";
+
+const useStyles = makeStyles(theme =>
+ createStyles({
+ dropzoneButton: {
+ backgroundColor:
+ theme.palette.type === "light"
+ ? "rgba(0, 0, 0, 0.09)"
+ : "rgba(255, 255, 255, 0.09)",
+ borderRadius: theme.shape.borderRadius,
+ padding: theme.spacing(0, 2),
+ justifyContent: "flex-start",
+
+ margin: 0,
+ width: "100%",
+ height: 56,
+
+ color: theme.palette.text.secondary,
+
+ "& svg": { marginRight: theme.spacing(2) },
+ },
+
+ chipList: { marginTop: theme.spacing(1) },
+ chipGridItem: { maxWidth: "100%" },
+ chip: { width: "100%" },
+ })
+);
+
+export interface IFileUploaderProps extends FieldProps {
+ docRef?: firebase.firestore.DocumentReference;
+}
+
+export default function FileUploader({
+ form,
+ field,
+ docRef,
+}: IFileUploaderProps) {
+ const classes = useStyles();
+
+ const [uploaderState, upload] = useUploader();
+ const { progress } = uploaderState;
+
+ // Store a preview image locally while uploading
+ const [localFile, setLocalFile] = useState("");
+
+ const onDrop = useCallback(
+ (acceptedFiles: File[]) => {
+ const file = acceptedFiles[0];
+
+ if (docRef && file) {
+ upload({
+ docRef,
+ fieldName: field.name,
+ files: [file],
+ previousValue: field.value ?? [],
+ onComplete: newValue => {
+ form.setFieldValue(field.name, newValue);
+ setLocalFile("");
+ },
+ });
+ setLocalFile(file.name);
+ }
+ },
+ [docRef]
+ );
+
+ const handleDelete = (index: number) => {
+ const newValue = [...field.value];
+ newValue.splice(index, 1);
+ form.setFieldValue(field.name, newValue);
+ };
+
+ const { getRootProps, getInputProps } = useDropzone({
+ onDrop,
+ multiple: false,
+ });
+
+ return (
+ <>
+
+
+
+
+ Upload file
+
+
+
+
+ {field.value.map((file: FileValue, i) => (
+
+
+ }
+ label={file.name}
+ onClick={() => window.open(file.downloadURL)}
+ onDelete={() => handleDelete(i)}
+ className={classes.chip}
+ />
+
+
+ ))}
+
+ {localFile && (
+
+ }
+ label={localFile}
+ className={classes.chip}
+ onDelete={() => {}}
+ deleteIcon={
+
+ }
+ />
+
+ )}
+
+
+
+ >
+ );
+}
diff --git a/www/src/components/SideDrawer/Form/Fields/ImageUploader.tsx b/www/src/components/SideDrawer/Form/Fields/ImageUploader.tsx
index 48152209..61c0b9f8 100644
--- a/www/src/components/SideDrawer/Form/Fields/ImageUploader.tsx
+++ b/www/src/components/SideDrawer/Form/Fields/ImageUploader.tsx
@@ -82,7 +82,6 @@ const useStyles = makeStyles(theme =>
);
export interface IImageUploaderProps extends FieldProps {
- label: React.ReactNode;
docRef?: firebase.firestore.DocumentReference;
}
diff --git a/www/src/components/SideDrawer/Form/index.tsx b/www/src/components/SideDrawer/Form/index.tsx
index 4d3aa1ac..080d7f6e 100644
--- a/www/src/components/SideDrawer/Form/index.tsx
+++ b/www/src/components/SideDrawer/Form/index.tsx
@@ -21,6 +21,7 @@ import Color from "./Fields/Color";
import Slider from "./Fields/Slider";
// import TextMulti from "./Fields/TextMulti";
import ImageUploader from "./Fields/ImageUploader";
+import FileUploader from "./Fields/FileUploader";
import { FieldType } from "constants/fields";
// import Heading from "./Heading";
@@ -200,13 +201,22 @@ export default function Form({ fields, values }: IFormProps) {
);
break;
+ case FieldType.file:
+ renderedField = (
+
+ );
+ break;
+
case FieldType.rating:
renderedField = (
);
break;
- // case FieldType.file:
// case FieldType.connectTable:
// case FieldType.subTable:
// case FieldType.action: