diff --git a/www/src/components/SideDrawer/Form/Autosave.tsx b/www/src/components/SideDrawer/Form/Autosave.tsx index 99170454..7e32c69f 100644 --- a/www/src/components/SideDrawer/Form/Autosave.tsx +++ b/www/src/components/SideDrawer/Form/Autosave.tsx @@ -7,7 +7,7 @@ import _isUndefined from "lodash/isUndefined"; import _reduce from "lodash/reduce"; import { Control, useWatch } from "react-hook-form"; -import { Values } from "."; +import { Values } from "./utils"; import { useAppContext } from "contexts/appContext"; import { useFiretableContext, firetableUser } from "contexts/firetableContext"; diff --git a/www/src/components/SideDrawer/Form/Fields/Action.tsx b/www/src/components/SideDrawer/Form/Fields/Action.tsx index eb799056..b099ee0b 100644 --- a/www/src/components/SideDrawer/Form/Fields/Action.tsx +++ b/www/src/components/SideDrawer/Form/Fields/Action.tsx @@ -1,5 +1,6 @@ import React, { useContext, useState } from "react"; -import { Control, Controller, useWatch } from "react-hook-form"; +import { Controller, useWatch } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { createStyles, @@ -40,12 +41,8 @@ const useStyles = makeStyles(theme => }) ); -export interface IActionProps { - control: Control; - name: string; - docRef: firebase.firestore.DocumentReference; +export interface IActionProps extends IFieldProps { config: { callableName: string }; - editable?: boolean; } function Action({ control, name, docRef, editable, config }: IActionProps) { diff --git a/www/src/components/SideDrawer/Form/Fields/Checkbox.tsx b/www/src/components/SideDrawer/Form/Fields/Checkbox.tsx index 60413511..ffee1758 100644 --- a/www/src/components/SideDrawer/Form/Fields/Checkbox.tsx +++ b/www/src/components/SideDrawer/Form/Fields/Checkbox.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { makeStyles, @@ -38,15 +39,16 @@ const useStyles = makeStyles(theme => }) ); -export interface ICheckboxProps extends MuiSwitchProps { - control: Control; - name: string; +export interface ICheckboxProps + extends IFieldProps, + Omit { label?: React.ReactNode; editable?: boolean; } export default function Checkbox({ control, + docRef, label, name, editable, diff --git a/www/src/components/SideDrawer/Form/Fields/Code.tsx b/www/src/components/SideDrawer/Form/Fields/Code.tsx index c2533dfd..dced6179 100644 --- a/www/src/components/SideDrawer/Form/Fields/Code.tsx +++ b/www/src/components/SideDrawer/Form/Fields/Code.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef } from "react"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import AceEditor from "react-ace"; import "ace-builds/src-noconflict/mode-javascript"; @@ -101,12 +102,7 @@ function ControlledCode({ onChange, onBlur, value }: IControlledCodeProps) { ); } -export interface ICodeProps { - control: Control; - name: string; -} - -export default function Code({ control, name, ...props }: ICodeProps) { +export default function Code({ control, docRef, name, ...props }: IFieldProps) { return ( }) ); -export interface IColorProps { - control: Control; - name: string; -} - -export default function Color({ control, name }: IColorProps) { +export default function Color({ control, name }: IFieldProps) { const classes = useStyles(); const [showPicker, setShowPicker] = useState(false); diff --git a/www/src/components/SideDrawer/Form/Fields/ConnectTable.tsx b/www/src/components/SideDrawer/Form/Fields/ConnectTable.tsx index b383b957..39a848fb 100644 --- a/www/src/components/SideDrawer/Form/Fields/ConnectTable.tsx +++ b/www/src/components/SideDrawer/Form/Fields/ConnectTable.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Control, Controller } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { useTheme, Grid, Chip } from "@material-ui/core"; @@ -7,13 +8,13 @@ import ConnectTableSelect, { IConnectTableSelectProps, } from "components/ConnectTableSelect"; -export interface IConnectTableProps extends Partial { - control: Control; - name: string; -} +export interface IConnectTableProps + extends IFieldProps, + Partial {} export default function ConnectTable({ control, + docRef, name, editable, ...props diff --git a/www/src/components/SideDrawer/Form/Fields/DatePicker.tsx b/www/src/components/SideDrawer/Form/Fields/DatePicker.tsx index 2cf36fd0..ac9a8170 100644 --- a/www/src/components/SideDrawer/Form/Fields/DatePicker.tsx +++ b/www/src/components/SideDrawer/Form/Fields/DatePicker.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { useTheme } from "@material-ui/core"; import { @@ -12,13 +13,12 @@ import { MuiPickersUtilsProvider } from "@material-ui/pickers"; import DateFnsUtils from "@date-io/date-fns"; export interface IDatePickerProps - extends Omit { - control: Control; - name: string; -} + extends IFieldProps, + Omit {} export default function DatePicker({ control, + docRef, name, ...props }: IDatePickerProps) { diff --git a/www/src/components/SideDrawer/Form/Fields/DateTimePicker.tsx b/www/src/components/SideDrawer/Form/Fields/DateTimePicker.tsx index f52ed273..3d8247f4 100644 --- a/www/src/components/SideDrawer/Form/Fields/DateTimePicker.tsx +++ b/www/src/components/SideDrawer/Form/Fields/DateTimePicker.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { useTheme } from "@material-ui/core"; import { @@ -14,13 +15,12 @@ import { MuiPickersUtilsProvider } from "@material-ui/pickers"; import DateFnsUtils from "@date-io/date-fns"; export interface IDateTimePickerProps - extends Omit { - control: Control; - name: string; -} + extends IFieldProps, + Omit {} export default function DateTimePicker({ control, + docRef, name, ...props }: IDateTimePickerProps) { diff --git a/www/src/components/SideDrawer/Form/Fields/FileUploader.tsx b/www/src/components/SideDrawer/Form/Fields/FileUploader.tsx index fcd43519..f3581734 100644 --- a/www/src/components/SideDrawer/Form/Fields/FileUploader.tsx +++ b/www/src/components/SideDrawer/Form/Fields/FileUploader.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useState } from "react"; import clsx from "clsx"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { useDropzone } from "react-dropzone"; import useUploader, { FileValue } from "hooks/useFiretable/useUploader"; @@ -55,7 +56,7 @@ const useStyles = makeStyles(theme => ); export interface IControlledFileUploaderProps - extends Pick { + extends Pick { onChange: (...event: any[]) => void; onBlur: () => void; value: any; @@ -173,19 +174,7 @@ export function ControlledFileUploader({ ); } -export interface IFileUploaderProps { - control: Control; - name: string; - - editable?: boolean; - docRef?: firebase.firestore.DocumentReference; -} - -export default function FileUploader({ - control, - name, - ...props -}: IFileUploaderProps) { +export default function FileUploader({ control, name, ...props }: IFieldProps) { return ( ); export interface IControlledImageUploaderProps - extends Pick { + extends Pick { onChange: (...event: any[]) => void; onBlur: () => void; value: any; @@ -253,19 +253,11 @@ export function ControlledImageUploader({ ); } -export interface IImageUploaderProps { - control: Control; - name: string; - - docRef?: firebase.firestore.DocumentReference; - editable?: boolean; -} - export default function ImageUploader({ control, name, ...props -}: IImageUploaderProps) { +}: IFieldProps) { return ( { return true; }; -export interface IJsonEditorProps { - control: Control; - name: string; -} - -export default function JsonEditor({ control, name }: IJsonEditorProps) { +export default function JsonEditor({ control, name }: IFieldProps) { const classes = useStyles(); const theme = useTheme(); diff --git a/www/src/components/SideDrawer/Form/Fields/MultiSelect.tsx b/www/src/components/SideDrawer/Form/Fields/MultiSelect.tsx index 02b5989d..431426c5 100644 --- a/www/src/components/SideDrawer/Form/Fields/MultiSelect.tsx +++ b/www/src/components/SideDrawer/Form/Fields/MultiSelect.tsx @@ -1,25 +1,24 @@ import React from "react"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { useTheme, Grid } from "@material-ui/core"; import MultiSelect_, { MultiSelectProps } from "@antlerengineering/multiselect"; import FormattedChip from "components/FormattedChip"; -export type IMultiSelectProps = Omit< - MultiSelectProps, - "multiple" | "value" | "onChange" | "options" -> & { - control: Control; - name: string; - - config?: { options: string[] }; - editable?: boolean; -}; +export type IMultiSelectProps = IFieldProps & + Omit< + MultiSelectProps, + "name" | "multiple" | "value" | "onChange" | "options" + > & { + config?: { options: string[] }; + }; export default function MultiSelect({ control, name, + docRef, editable, config, ...props diff --git a/www/src/components/SideDrawer/Form/Fields/Percentage.tsx b/www/src/components/SideDrawer/Form/Fields/Percentage.tsx index 5c84c2a6..64248978 100644 --- a/www/src/components/SideDrawer/Form/Fields/Percentage.tsx +++ b/www/src/components/SideDrawer/Form/Fields/Percentage.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { makeStyles, createStyles, Typography } from "@material-ui/core"; import { resultColorsScale } from "util/color"; @@ -41,15 +42,7 @@ const useStyles = makeStyles(theme => }) ); -export interface IPercentageProps { - control: Control; - name: string; -} - -/** - * TODO: Fix cell not updating properly when switching between rows - */ -export default function Percentage({ control, name }: IPercentageProps) { +export default function Percentage({ control, name }: IFieldProps) { const classes = useStyles(); return ( diff --git a/www/src/components/SideDrawer/Form/Fields/Rating.tsx b/www/src/components/SideDrawer/Form/Fields/Rating.tsx index f092e952..8e3c73a3 100644 --- a/www/src/components/SideDrawer/Form/Fields/Rating.tsx +++ b/www/src/components/SideDrawer/Form/Fields/Rating.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { makeStyles, createStyles, Grid } from "@material-ui/core"; import { Rating as MuiRating } from "@material-ui/lab"; @@ -25,13 +26,7 @@ const useStyles = makeStyles(theme => }) ); -export interface IRatingProps { - control: Control; - name: string; - editable?: boolean; -} - -export default function Rating({ control, name, editable }: IRatingProps) { +export default function Rating({ control, name, editable }: IFieldProps) { const classes = useStyles(); return ( diff --git a/www/src/components/SideDrawer/Form/Fields/RichText.tsx b/www/src/components/SideDrawer/Form/Fields/RichText.tsx index 696380b4..7c16c1dc 100644 --- a/www/src/components/SideDrawer/Form/Fields/RichText.tsx +++ b/www/src/components/SideDrawer/Form/Fields/RichText.tsx @@ -1,14 +1,10 @@ import React from "react"; -import { Control, Controller } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import _RichText from "components/RichText"; -export interface IRichTextProps { - control: Control; - name: string; -} - -export default function RichText({ control, name }: IRichTextProps) { +export default function RichText({ control, name }: IFieldProps) { return ( , - "multiple" | "value" | "onChange" | "options" -> & { - control: Control; - name: string; - - config?: { options: string[] }; - editable?: boolean; -}; +export type ISingleSelectProps = IFieldProps & + Omit< + MultiSelectProps, + "name" | "multiple" | "value" | "onChange" | "options" + > & { + config?: { options: string[] }; + }; /** * Uses the MultiSelect UI, but writes values as a string, @@ -23,6 +21,7 @@ export type ISingleSelectProps = Omit< */ export default function SingleSelect({ control, + docRef, name, editable, config, diff --git a/www/src/components/SideDrawer/Form/Fields/Slider.tsx b/www/src/components/SideDrawer/Form/Fields/Slider.tsx index 139403db..ae450822 100644 --- a/www/src/components/SideDrawer/Form/Fields/Slider.tsx +++ b/www/src/components/SideDrawer/Form/Fields/Slider.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { makeStyles, @@ -43,9 +44,7 @@ const useStyles = makeStyles(theme => }) ); -export interface ISliderProps extends SliderProps { - control: Control; - name: string; +export interface ISliderProps extends IFieldProps, Omit { units?: string; minLabel?: React.ReactNode; maxLabel?: React.ReactNode; @@ -53,6 +52,7 @@ export interface ISliderProps extends SliderProps { export default function Slider({ control, + docRef, name, units, minLabel, diff --git a/www/src/components/SideDrawer/Form/Fields/SubTable.tsx b/www/src/components/SideDrawer/Form/Fields/SubTable.tsx index 1303db65..d83e6591 100644 --- a/www/src/components/SideDrawer/Form/Fields/SubTable.tsx +++ b/www/src/components/SideDrawer/Form/Fields/SubTable.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Control, useWatch } from "react-hook-form"; +import { Controller, useWatch } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { Link } from "react-router-dom"; import queryString from "query-string"; @@ -31,10 +32,7 @@ const useStyles = makeStyles(theme => }) ); -export interface ISubTableProps { - control: Control; - name: string; - docRef: firebase.firestore.DocumentReference; +export interface ISubTableProps extends IFieldProps { config: { parentLabel?: string[] }; label: string; } diff --git a/www/src/components/SideDrawer/Form/Fields/Text.tsx b/www/src/components/SideDrawer/Form/Fields/Text.tsx index 242a47b6..25fd4ab8 100644 --- a/www/src/components/SideDrawer/Form/Fields/Text.tsx +++ b/www/src/components/SideDrawer/Form/Fields/Text.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { makeStyles, @@ -14,13 +15,6 @@ const useStyles = makeStyles(theme => }) ); -export interface IFieldProps { - control: Control; - name: string; - docRef: firebase.firestore.DocumentReference; - editable?: boolean; -} - export interface ITextProps extends IFieldProps, Omit { @@ -60,7 +54,7 @@ export default function Text({ break; case "number": - variantProps = { inputmode: "numeric", pattern: "[0-9]*" }; + variantProps = { inputMode: "numeric", pattern: "[0-9]*" }; break; case "short": diff --git a/www/src/components/SideDrawer/Form/Fields/Url.tsx b/www/src/components/SideDrawer/Form/Fields/Url.tsx index f7bb23fa..ae7015f5 100644 --- a/www/src/components/SideDrawer/Form/Fields/Url.tsx +++ b/www/src/components/SideDrawer/Form/Fields/Url.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Controller, Control } from "react-hook-form"; +import { Controller } from "react-hook-form"; +import { IFieldProps } from "../utils"; import { Grid, @@ -9,12 +10,11 @@ import { } from "@material-ui/core"; import LaunchIcon from "@material-ui/icons/Launch"; -export interface IUrlProps extends Omit { - control: Control; - name: string; -} +export interface IUrlProps + extends IFieldProps, + Omit {} -export default function Url({ control, name, ...props }: IUrlProps) { +export default function Url({ control, name, docRef, ...props }: IUrlProps) { return ( import("./Fields/Url" /* webpackChunkName: "SideDrawer-Url" */) ); @@ -80,60 +77,6 @@ const Action = lazy(() => import("./Fields/Action" /* webpackChunkName: "SideDrawer-Action" */) ); -export type Values = { [key: string]: any }; -export type Field = { - type?: FieldType; - name: string; - label?: string; - [key: string]: any; -}; -export type Fields = (Field | ((values: Values) => Field))[]; - -const initializeValue = type => { - switch (type) { - case FieldType.singleSelect: - case FieldType.multiSelect: - case FieldType.image: - case FieldType.file: - return []; - case FieldType.date: - case FieldType.dateTime: - return null; - - case FieldType.checkbox: - return false; - - case FieldType.number: - return 0; - - case FieldType.json: - return {}; - - case FieldType.shortText: - case FieldType.longText: - case FieldType.email: - case FieldType.phone: - case FieldType.url: - case FieldType.code: - case FieldType.richText: - default: - break; - } -}; - -const getInitialValues = (fields: Fields): Values => - fields.reduce((acc, _field) => { - const field = _isFunction(_field) ? _field({}) : _field; - if (!field.name) return acc; - let _type = field.type; - if (field.config && field.config.renderFieldType) { - _type = field.config.renderFieldType; - } - const value = initializeValue(_type); - - return { ...acc, [field.name]: value }; - }, {}); - export interface IFormProps { fields: Fields; values: Values; @@ -188,7 +131,7 @@ export default function Form({ fields, values }: IFormProps) { _type = field.config.renderFieldType; } - let renderedField: React.ReactNode = null; + let fieldComponent: React.ComponentType | null = null; switch (_type) { case FieldType.shortText: @@ -196,111 +139,79 @@ export default function Form({ fields, values }: IFormProps) { case FieldType.email: case FieldType.phone: case FieldType.number: - renderedField = ( - - ); + fieldComponent = Text; break; case FieldType.url: - renderedField = ; + fieldComponent = Url; break; case FieldType.singleSelect: - renderedField = ( - - ); + fieldComponent = SingleSelect; break; case FieldType.multiSelect: - renderedField = ; + fieldComponent = MultiSelect; break; case FieldType.date: - renderedField = ; + fieldComponent = DatePicker; break; case FieldType.dateTime: - renderedField = ( - - ); + fieldComponent = DateTimePicker; break; case FieldType.checkbox: - renderedField = ; + fieldComponent = Checkbox; break; case FieldType.color: - renderedField = ; + fieldComponent = Color; break; case FieldType.slider: - renderedField = ; + fieldComponent = Slider; break; case FieldType.richText: - renderedField = ; + fieldComponent = RichText; break; case FieldType.image: - renderedField = ( - - ); + fieldComponent = ImageUploader; break; case FieldType.file: - renderedField = ( - - ); + fieldComponent = FileUploader; break; case FieldType.rating: - renderedField = ; + fieldComponent = Rating; break; case FieldType.percentage: - renderedField = ; + fieldComponent = Percentage; break; case FieldType.connectTable: - renderedField = ( - - ); + fieldComponent = ConnectTable; break; case FieldType.subTable: - renderedField = ( - - ); + fieldComponent = SubTable; break; case FieldType.action: - renderedField = ( - - ); + fieldComponent = Action; break; case FieldType.json: - renderedField = ; + fieldComponent = JsonEditor; break; case FieldType.code: - renderedField = ; + fieldComponent = Code; break; case undefined: @@ -311,6 +222,12 @@ export default function Form({ fields, values }: IFormProps) { break; } + // Should not reach this state + if (fieldComponent === null) { + console.error("`fieldComponent` is null"); + return null; + } + return ( - {renderedField} + {React.createElement(fieldComponent, { + ...fieldProps, + control, + docRef, + })} ); })} diff --git a/www/src/components/SideDrawer/Form/utils.ts b/www/src/components/SideDrawer/Form/utils.ts new file mode 100644 index 00000000..66ff555c --- /dev/null +++ b/www/src/components/SideDrawer/Form/utils.ts @@ -0,0 +1,65 @@ +import { Control } from "react-hook-form"; +import _isFunction from "lodash/isFunction"; + +import { FieldType } from "constants/fields"; + +export interface IFieldProps { + control: Control; + name: string; + docRef: firebase.firestore.DocumentReference; + editable?: boolean; +} + +export type Values = { [key: string]: any }; +export type Field = { + type?: FieldType; + name: string; + label?: string; + [key: string]: any; +}; +export type Fields = (Field | ((values: Values) => Field))[]; + +export const initializeValue = type => { + switch (type) { + case FieldType.singleSelect: + case FieldType.multiSelect: + case FieldType.image: + case FieldType.file: + return []; + case FieldType.date: + case FieldType.dateTime: + return null; + + case FieldType.checkbox: + return false; + + case FieldType.number: + return 0; + + case FieldType.json: + return {}; + + case FieldType.shortText: + case FieldType.longText: + case FieldType.email: + case FieldType.phone: + case FieldType.url: + case FieldType.code: + case FieldType.richText: + default: + break; + } +}; + +export const getInitialValues = (fields: Fields): Values => + fields.reduce((acc, _field) => { + const field = _isFunction(_field) ? _field({}) : _field; + if (!field.name) return acc; + let _type = field.type; + if (field.config && field.config.renderFieldType) { + _type = field.config.renderFieldType; + } + const value = initializeValue(_type); + + return { ...acc, [field.name]: value }; + }, {}); diff --git a/www/src/components/SideDrawer/index.tsx b/www/src/components/SideDrawer/index.tsx index 4a9a0d67..77fff889 100644 --- a/www/src/components/SideDrawer/index.tsx +++ b/www/src/components/SideDrawer/index.tsx @@ -8,7 +8,8 @@ import ChevronIcon from "@material-ui/icons/KeyboardArrowLeft"; import ChevronUpIcon from "@material-ui/icons/KeyboardArrowUp"; import ChevronDownIcon from "@material-ui/icons/KeyboardArrowDown"; -import Form, { Field } from "./Form"; +import Form from "./Form"; +import { Field } from "./Form/utils"; import ErrorBoundary from "components/ErrorBoundary"; import { useStyles } from "./useStyles";