mirror of
https://github.com/rowyio/rowy.git
synced 2026-02-24 20:20:05 +01:00
add FileUploader
This commit is contained in:
@@ -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: <ClearIcon />,
|
||||
},
|
||||
MuiButton: { color: "primary" },
|
||||
},
|
||||
});
|
||||
@@ -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 (
|
||||
<SvgIcon viewBox="0 2 24 24" {...props}>
|
||||
<path d={mdiGestureTap} />
|
||||
|
||||
11
www/src/assets/icons/Upload.tsx
Normal file
11
www/src/assets/icons/Upload.tsx
Normal file
@@ -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 (
|
||||
<SvgIcon viewBox="0 0 24 24" {...props}>
|
||||
<path d={mdiUpload} />
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
<Dialog open={showDialog} onClose={handleClose}>
|
||||
<DialogTitle>
|
||||
{(message && message.title) || "Are you sure?"}
|
||||
@@ -108,6 +110,4 @@ const Confirmation = (props: Props) => {
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Confirmation;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const CheckBox = (props: Props) => {
|
||||
row.firstName
|
||||
),
|
||||
}}
|
||||
functionName="onChange"
|
||||
>
|
||||
<Grid container justify="center">
|
||||
<Switch
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
IconButton,
|
||||
CircularProgress,
|
||||
} from "@material-ui/core";
|
||||
import UploadIcon from "@material-ui/icons/Publish";
|
||||
import UploadIcon from "assets/icons/Upload";
|
||||
|
||||
import useUploader, { FileValue } from "hooks/useFiretable/useUploader";
|
||||
|
||||
@@ -34,7 +34,7 @@ const useStyles = makeStyles(theme =>
|
||||
|
||||
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) => {
|
||||
<Chip
|
||||
icon={<FileIcon />}
|
||||
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()}
|
||||
/>
|
||||
</Grid>
|
||||
))
|
||||
|
||||
151
www/src/components/SideDrawer/Form/Fields/FileUploader.tsx
Normal file
151
www/src/components/SideDrawer/Form/Fields/FileUploader.tsx
Normal file
@@ -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<string>("");
|
||||
|
||||
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 (
|
||||
<>
|
||||
<ButtonBase className={classes.dropzoneButton} {...getRootProps()}>
|
||||
<input id={`sidemodal-field-${field.name}`} {...getInputProps()} />
|
||||
<UploadIcon />
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
Upload file
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
|
||||
<Grid container spacing={1} className={classes.chipList}>
|
||||
{field.value.map((file: FileValue, i) => (
|
||||
<Grid item key={file.name} className={classes.chipGridItem}>
|
||||
<Confirmation
|
||||
message={{
|
||||
title: "Delete File",
|
||||
body: "Are you sure you want to delete this file?",
|
||||
confirm: "Delete",
|
||||
}}
|
||||
functionName="onDelete"
|
||||
>
|
||||
<Chip
|
||||
size="medium"
|
||||
icon={<FileIcon />}
|
||||
label={file.name}
|
||||
onClick={() => window.open(file.downloadURL)}
|
||||
onDelete={() => handleDelete(i)}
|
||||
className={classes.chip}
|
||||
/>
|
||||
</Confirmation>
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
{localFile && (
|
||||
<Grid item className={classes.chipGridItem}>
|
||||
<Chip
|
||||
size="medium"
|
||||
icon={<FileIcon />}
|
||||
label={localFile}
|
||||
className={classes.chip}
|
||||
onDelete={() => {}}
|
||||
deleteIcon={
|
||||
<CircularProgress size={20} thickness={4.5} color="inherit" />
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<ErrorMessage name={field.name} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -82,7 +82,6 @@ const useStyles = makeStyles(theme =>
|
||||
);
|
||||
|
||||
export interface IImageUploaderProps extends FieldProps {
|
||||
label: React.ReactNode;
|
||||
docRef?: firebase.firestore.DocumentReference;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = (
|
||||
<Field
|
||||
{...fieldProps}
|
||||
component={FileUploader}
|
||||
docRef={values.ref}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
case FieldType.rating:
|
||||
renderedField = (
|
||||
<Field {...fieldProps} component={Rating} />
|
||||
);
|
||||
break;
|
||||
|
||||
// case FieldType.file:
|
||||
// case FieldType.connectTable:
|
||||
// case FieldType.subTable:
|
||||
// case FieldType.action:
|
||||
|
||||
Reference in New Issue
Block a user