From ce50f43fe9ade692ace66cef496184216e784d6d Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Tue, 12 Jan 2021 20:40:47 +1100 Subject: [PATCH] add ConnectTable --- www/src/components/Table/withPopoverCell.tsx | 9 +- .../fields/Color/PopoverBasicCell.tsx | 12 +- .../ConnectTable/ConnectTableSelect.tsx | 151 ++++++++++++++++++ .../fields/ConnectTable/PopoverBasicCell.tsx | 89 +++++++++++ .../fields/ConnectTable/PopoverCell.tsx | 40 +++++ .../fields/ConnectTable/SideDrawerField.tsx | 74 +++++++++ .../components/fields/ConnectTable/index.tsx | 35 ++++ .../components/fields/ConnectTable/utils.ts | 4 + .../fields/MultiSelect/PopoverBasicCell.tsx | 4 +- .../fields/MultiSelect/PopoverCell.tsx | 2 +- .../fields/MultiSelect/SideDrawerField.tsx | 2 +- .../components/fields/MultiSelect/index.tsx | 2 +- .../components/fields/SingleSelect/index.tsx | 2 +- www/src/components/fields/index.tsx | 3 +- www/src/components/fields/types.ts | 4 +- 15 files changed, 411 insertions(+), 22 deletions(-) create mode 100644 www/src/components/fields/ConnectTable/ConnectTableSelect.tsx create mode 100644 www/src/components/fields/ConnectTable/PopoverBasicCell.tsx create mode 100644 www/src/components/fields/ConnectTable/PopoverCell.tsx create mode 100644 www/src/components/fields/ConnectTable/SideDrawerField.tsx create mode 100644 www/src/components/fields/ConnectTable/index.tsx create mode 100644 www/src/components/fields/ConnectTable/utils.ts diff --git a/www/src/components/Table/withPopoverCell.tsx b/www/src/components/Table/withPopoverCell.tsx index e53493d6..ca3d173c 100644 --- a/www/src/components/Table/withPopoverCell.tsx +++ b/www/src/components/Table/withPopoverCell.tsx @@ -73,13 +73,14 @@ export default function withPopoverCell( const basicCell = ( ); diff --git a/www/src/components/fields/Color/PopoverBasicCell.tsx b/www/src/components/fields/Color/PopoverBasicCell.tsx index 131aaf3e..f3fb75c2 100644 --- a/www/src/components/fields/Color/PopoverBasicCell.tsx +++ b/www/src/components/fields/Color/PopoverBasicCell.tsx @@ -1,4 +1,5 @@ import React from "react"; +import clsx from "clsx"; import { IPopoverBasicCellProps } from "../types"; import { makeStyles, createStyles, Grid, ButtonBase } from "@material-ui/core"; @@ -6,17 +7,12 @@ import { makeStyles, createStyles, Grid, ButtonBase } from "@material-ui/core"; const useStyles = makeStyles((theme) => createStyles({ root: { - position: "absolute", - top: 0, - right: 0, - bottom: 0, - left: 0, - paddingLeft: theme.spacing(1.5), - font: "inherit", color: "inherit !important", letterSpacing: "inherit", textAlign: "inherit", + + padding: theme.spacing(0, 1), }, colorIndicator: { @@ -40,7 +36,7 @@ export const Color = React.forwardRef(function Color( container alignItems="center" spacing={1} - className={classes.root} + className={clsx("cell-collapse-padding", classes.root)} component={ButtonBase} onClick={() => setShowComplexCell(true)} ref={ref} diff --git a/www/src/components/fields/ConnectTable/ConnectTableSelect.tsx b/www/src/components/fields/ConnectTable/ConnectTableSelect.tsx new file mode 100644 index 00000000..d462b4dc --- /dev/null +++ b/www/src/components/fields/ConnectTable/ConnectTableSelect.tsx @@ -0,0 +1,151 @@ +import React, { useState, useEffect } from "react"; +import useAlgolia from "use-algolia"; +import _find from "lodash/find"; +import { useDebounce } from "use-debounce"; + +import MultiSelect, { MultiSelectProps } from "@antlerengineering/multiselect"; +import Loading from "components/Loading"; + +export type ConnectTableValue = { + docPath: string; + snapshot: Record; +}; + +export interface IConnectTableSelectProps { + value: ConnectTableValue[]; + onChange: (value: ConnectTableValue[]) => void; + column: any; + config: { + filters: string; + primaryKeys: string[]; + secondaryKeys?: string[]; + multiple?: boolean; + searchLabel?: string; + [key: string]: any; + }; + disabled?: boolean; + /** Optional style overrides for root MUI `TextField` component */ + className?: string; + /** Override any props of the root MUI `TextField` component */ + TextFieldProps?: MultiSelectProps["TextFieldProps"]; + onClose?: MultiSelectProps["onClose"]; + /** Load the Algolia index before the MultiSelect onOpen function is triggered */ + loadBeforeOpen?: boolean; +} + +export default function ConnectTableSelect({ + value = [], + onChange, + column, + + config, + disabled, + className, + + TextFieldProps = {}, + onClose, + loadBeforeOpen, +}: IConnectTableSelectProps) { + // Store a local copy of the value so the dropdown doesn’t automatically close + // when the user selects a new item and we allow for multiple selections + const [localValue, setLocalValue] = useState( + Array.isArray(value) ? value : [] + ); + + const algoliaIndex = config.index; + const [algoliaState, requestDispatch, , setAlgoliaConfig] = useAlgolia( + process.env.REACT_APP_ALGOLIA_APP_ID!, + process.env.REACT_APP_ALGOLIA_SEARCH_API_KEY!, + // Don’t choose the index until the user opens the dropdown if !loadBeforeOpen + loadBeforeOpen ? algoliaIndex : "" + ); + const options = algoliaState.hits.map((hit) => ({ + label: config.primaryKeys?.map((key: string) => hit[key]).join(" "), + value: hit.objectID, + })); + + // Pass a list of objectIDs to MultiSelect + const sanitisedValue = localValue.map( + (item) => item.docPath.split("/")[item.docPath.split("/").length - 1] + ); + + const handleChange = (_newValue) => { + // Ensure we return an array + const newValue = Array.isArray(_newValue) + ? _newValue + : _newValue !== null + ? [_newValue] + : []; + + // Calculate new value + const newLocalValue = newValue.map((objectID) => { + // If this objectID is already in the previous value, use that previous + // value’s snapshot (in case it points to an object not in the current + // Algolia query) + const existingMatch = _find(localValue, { + docPath: `${algoliaIndex}/${objectID}`, + }); + if (existingMatch) return existingMatch; + + // If this is a completely new selection, grab the snapshot from the + // current Algolia query + const match = _find(algoliaState.hits, { objectID }); + const { _highlightResult, ...snapshot } = match; + return { + snapshot, + docPath: `${algoliaIndex}/${snapshot.objectID}`, + }; + }); + + // If !multiple, we MUST change the value (bypassing localValue), + // otherwise `setLocalValue` won’t be called in time for the new + // `localValue` to be read by `handleSave` + if (config.multiple === false) onChange(newLocalValue); + // Otherwise, `setLocalValue` until user closes dropdown + else setLocalValue(newLocalValue); + }; + + // Save when user closes dropdown + const handleSave = () => { + if (config.multiple !== false) onChange(localValue); + if (onClose) onClose(); + }; + + // Change MultiSelect input field to search Algolia directly + const [search, setSearch] = useState(""); + const [debouncedSearch] = useDebounce(search, 1000); + useEffect(() => { + requestDispatch({ query: debouncedSearch }); + }, [debouncedSearch]); + + return ( + setAlgoliaConfig({ indexName: algoliaIndex })} + onClose={handleSave} + options={options} + TextFieldProps={{ + className, + hiddenLabel: true, + ...TextFieldProps, + }} + label={column?.name} + labelPlural={config.searchLabel} + multiple={(config.multiple ?? true) as any} + AutocompleteProps={{ + loading: algoliaState.loading, + loadingText: , + inputValue: search, + onInputChange: (_, value, reason) => { + if (reason === "input") setSearch(value); + }, + filterOptions: () => options, + }} + countText={`${localValue.length} of ${ + algoliaState.response?.nbHits ?? "?" + }`} + disabled={disabled} + /> + ); +} diff --git a/www/src/components/fields/ConnectTable/PopoverBasicCell.tsx b/www/src/components/fields/ConnectTable/PopoverBasicCell.tsx new file mode 100644 index 00000000..f894fab2 --- /dev/null +++ b/www/src/components/fields/ConnectTable/PopoverBasicCell.tsx @@ -0,0 +1,89 @@ +import React from "react"; +import clsx from "clsx"; +import { IPopoverBasicCellProps } from "../types"; + +import { + makeStyles, + createStyles, + ButtonBase, + Grid, + Chip, +} from "@material-ui/core"; +import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown"; + +const useStyles = makeStyles((theme) => + createStyles({ + root: { + height: "100%", + padding: theme.spacing(0, 1, 0, 1.5), + + font: "inherit", + color: "inherit !important", + letterSpacing: "inherit", + textAlign: "inherit", + justifyContent: "flex-start", + }, + + value: { + flex: 1, + maxWidth: `calc(100% - 24px + 4px)`, + overflow: "hidden", + marginRight: 0, + }, + chip: { + display: "flex", + cursor: "inherit", + }, + chipLabel: { whiteSpace: "nowrap" }, + + icon: { + display: "block", + color: theme.palette.action.active, + }, + disabled: { + color: theme.palette.action.disabled, + }, + }) +); + +export const ConnectTable = React.forwardRef(function ConnectTable( + { value, setShowComplexCell, disabled, column }: IPopoverBasicCellProps, + ref: React.Ref +) { + const classes = useStyles(); + const config = column.config ?? {}; + + return ( + setShowComplexCell(true)} + ref={ref} + disabled={disabled} + > + + {Array.isArray(value) && + value.map((doc: any) => ( + + doc.snapshot[key]) + .join(" ")} + className={classes.chip} + /> + + ))} + + + + + ); +}); +export default ConnectTable; diff --git a/www/src/components/fields/ConnectTable/PopoverCell.tsx b/www/src/components/fields/ConnectTable/PopoverCell.tsx new file mode 100644 index 00000000..2af5a4ed --- /dev/null +++ b/www/src/components/fields/ConnectTable/PopoverCell.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { IPopoverCellProps } from "../types"; +import _get from "lodash/get"; + +import ConnectTableSelect from "./ConnectTableSelect"; + +export default function ConnectTable({ + value, + onSubmit, + column, + parentRef, + setShowComplexCell, + disabled, +}: IPopoverCellProps) { + const config = column.config ?? {}; + if (!config || !config.primaryKeys) return null; + + return ( + setShowComplexCell(false)} + loadBeforeOpen + /> + ); +} diff --git a/www/src/components/fields/ConnectTable/SideDrawerField.tsx b/www/src/components/fields/ConnectTable/SideDrawerField.tsx new file mode 100644 index 00000000..217654cf --- /dev/null +++ b/www/src/components/fields/ConnectTable/SideDrawerField.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { Controller } from "react-hook-form"; +import { ISideDrawerFieldProps } from "../types"; + +import { useTheme, Grid, Chip } from "@material-ui/core"; + +import ConnectTableSelect from "./ConnectTableSelect"; + +export default function ConnectTable({ + column, + control, + disabled, +}: ISideDrawerFieldProps) { + const theme = useTheme(); + + const config = column.config ?? {}; + + return ( + { + const handleDelete = (hit: any) => () => { + // if (multiple) + onChange(value.filter((v) => v.snapshot.objectID !== hit.objectID)); + // else form.setFieldValue(field.name, []); + }; + + return ( + <> + {!disabled && ( + `${value?.length ?? 0} selected`, + }, + }} + /> + )} + + {Array.isArray(value) && ( + + {value.map(({ snapshot }) => ( + + snapshot[key]) + .join(" ")} + onDelete={disabled ? undefined : handleDelete(snapshot)} + /> + + ))} + + )} + + ); + }} + /> + ); +} diff --git a/www/src/components/fields/ConnectTable/index.tsx b/www/src/components/fields/ConnectTable/index.tsx new file mode 100644 index 00000000..e17165c5 --- /dev/null +++ b/www/src/components/fields/ConnectTable/index.tsx @@ -0,0 +1,35 @@ +import React, { lazy } from "react"; +import { IFieldConfig, FieldType } from "components/fields/types"; +import withPopoverCell from "components/Table/withPopoverCell"; + +import ConnectTableIcon from "assets/icons/ConnectTable"; +import PopoverBasicCell from "./PopoverBasicCell"; +import NullEditor from "components/Table/editors/NullEditor"; + +const PopoverCell = lazy( + () => + import("./PopoverCell" /* webpackChunkName: "PopoverCell-ConnectTable" */) +); +const SideDrawerField = lazy( + () => + import( + "./SideDrawerField" /* webpackChunkName: "SideDrawerField-ConnectTable" */ + ) +); + +export const config: IFieldConfig = { + type: FieldType.connectTable, + name: "Connect Table", + dataType: "{ docPath: string; snapshot: Record; }", + initialValue: [], + icon: , + description: + "Connects to an existing table to fetch a snapshot of values from a row. Requires Algolia integration.", + TableCell: withPopoverCell(PopoverCell, PopoverBasicCell, { + anchorOrigin: { horizontal: "left", vertical: "bottom" }, + transparent: true, + }), + TableEditor: NullEditor, + SideDrawerField, +}; +export default config; diff --git a/www/src/components/fields/ConnectTable/utils.ts b/www/src/components/fields/ConnectTable/utils.ts new file mode 100644 index 00000000..d2feec47 --- /dev/null +++ b/www/src/components/fields/ConnectTable/utils.ts @@ -0,0 +1,4 @@ +export const sanitiseValue = (value: any) => { + if (value === undefined || value === null || value === "") return []; + else return value as string[]; +}; diff --git a/www/src/components/fields/MultiSelect/PopoverBasicCell.tsx b/www/src/components/fields/MultiSelect/PopoverBasicCell.tsx index ff8a9686..2dce0a49 100644 --- a/www/src/components/fields/MultiSelect/PopoverBasicCell.tsx +++ b/www/src/components/fields/MultiSelect/PopoverBasicCell.tsx @@ -44,7 +44,7 @@ const useStyles = makeStyles((theme) => }) ); -export const SingleSelect = React.forwardRef(function SingleSelect( +export const MultiSelect = React.forwardRef(function MultiSelect( { value, setShowComplexCell, disabled, onSubmit }: IPopoverBasicCellProps, ref: React.Ref ) { @@ -86,4 +86,4 @@ export const SingleSelect = React.forwardRef(function SingleSelect( ); }); -export default SingleSelect; +export default MultiSelect; diff --git a/www/src/components/fields/MultiSelect/PopoverCell.tsx b/www/src/components/fields/MultiSelect/PopoverCell.tsx index 6f7efec2..16115e95 100644 --- a/www/src/components/fields/MultiSelect/PopoverCell.tsx +++ b/www/src/components/fields/MultiSelect/PopoverCell.tsx @@ -6,7 +6,7 @@ import MultiSelect_ from "@antlerengineering/multiselect"; import { sanitiseValue } from "./utils"; -export default function SingleSelect({ +export default function MultiSelect({ value, onSubmit, column, diff --git a/www/src/components/fields/MultiSelect/SideDrawerField.tsx b/www/src/components/fields/MultiSelect/SideDrawerField.tsx index beecd21c..593d6a15 100644 --- a/www/src/components/fields/MultiSelect/SideDrawerField.tsx +++ b/www/src/components/fields/MultiSelect/SideDrawerField.tsx @@ -9,7 +9,7 @@ import FormattedChip from "components/FormattedChip"; import { sanitiseValue } from "./utils"; import { ConvertStringToArray } from "./ConvertStringToArray"; -export default function SingleSelect({ +export default function MultiSelect({ column, control, disabled, diff --git a/www/src/components/fields/MultiSelect/index.tsx b/www/src/components/fields/MultiSelect/index.tsx index 0db14658..7ee68e4b 100644 --- a/www/src/components/fields/MultiSelect/index.tsx +++ b/www/src/components/fields/MultiSelect/index.tsx @@ -19,7 +19,7 @@ const SideDrawerField = lazy( export const config: IFieldConfig = { type: FieldType.multiSelect, - name: "MultiSelect", + name: "Multi Select", dataType: "string[]", initialValue: [], icon: , diff --git a/www/src/components/fields/SingleSelect/index.tsx b/www/src/components/fields/SingleSelect/index.tsx index eee9eaa5..a861cb45 100644 --- a/www/src/components/fields/SingleSelect/index.tsx +++ b/www/src/components/fields/SingleSelect/index.tsx @@ -19,7 +19,7 @@ const SideDrawerField = lazy( export const config: IFieldConfig = { type: FieldType.singleSelect, - name: "SingleSelect", + name: "Single Select", dataType: "string | null", initialValue: null, icon: , diff --git a/www/src/components/fields/index.tsx b/www/src/components/fields/index.tsx index cc34bd41..978301a2 100644 --- a/www/src/components/fields/index.tsx +++ b/www/src/components/fields/index.tsx @@ -24,6 +24,7 @@ import File_ from "./File"; import SingleSelect from "./SingleSelect"; import MultiSelect from "./MultiSelect"; import SubTable from "./SubTable"; +import ConnectTable from "./ConnectTable"; import Json from "./Json"; import Code from "./Code"; import RichText from "./RichText"; @@ -60,7 +61,7 @@ export const FIELDS: IFieldConfig[] = [ MultiSelect, // CONNECTION SubTable, - // TODO: connectTable, + ConnectTable, // TODO: connectService, // CODE Json, diff --git a/www/src/components/fields/types.ts b/www/src/components/fields/types.ts index 54c3a1e6..d907ee6e 100644 --- a/www/src/components/fields/types.ts +++ b/www/src/components/fields/types.ts @@ -39,10 +39,8 @@ export interface IPopoverCellProps extends ICustomCellProps { setShowComplexCell: React.Dispatch>; parentRef: PopoverProps["anchorEl"]; } -export interface IPopoverBasicCellProps extends IBasicCellProps { +export interface IPopoverBasicCellProps extends ICustomCellProps { setShowComplexCell: React.Dispatch>; - disabled: boolean; - onSubmit: (value: any) => void; } export interface ISideDrawerFieldProps {