mirror of
https://github.com/rowyio/rowy.git
synced 2026-07-12 13:28:48 +02:00
Merge branch 'fields-refactor' of https://github.com/AntlerVC/firetable into fields-refactor
This commit is contained in:
@@ -73,13 +73,14 @@ export default function withPopoverCell(
|
||||
|
||||
const basicCell = (
|
||||
<BasicCell
|
||||
{...props}
|
||||
column={props.column}
|
||||
value={localValue}
|
||||
name={(props.column as any).name}
|
||||
type={(props.column as any).type as FieldType}
|
||||
onSubmit={handleSubmit}
|
||||
disabled={props.column.editable === false}
|
||||
docRef={props.row.ref}
|
||||
setShowComplexCell={setShowComplexCell}
|
||||
ref={basicCellRef}
|
||||
disabled={props.column.editable === false}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
151
www/src/components/fields/ConnectTable/ConnectTableSelect.tsx
Normal file
151
www/src/components/fields/ConnectTable/ConnectTableSelect.tsx
Normal file
@@ -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<string, any>;
|
||||
};
|
||||
|
||||
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<ConnectTableValue[]>["TextFieldProps"];
|
||||
onClose?: MultiSelectProps<ConnectTableValue[]>["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 (
|
||||
<MultiSelect
|
||||
value={config.multiple === false ? sanitisedValue[0] : sanitisedValue}
|
||||
onChange={handleChange}
|
||||
onOpen={() => 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: <Loading />,
|
||||
inputValue: search,
|
||||
onInputChange: (_, value, reason) => {
|
||||
if (reason === "input") setSearch(value);
|
||||
},
|
||||
filterOptions: () => options,
|
||||
}}
|
||||
countText={`${localValue.length} of ${
|
||||
algoliaState.response?.nbHits ?? "?"
|
||||
}`}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
89
www/src/components/fields/ConnectTable/PopoverBasicCell.tsx
Normal file
89
www/src/components/fields/ConnectTable/PopoverBasicCell.tsx
Normal file
@@ -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<any>
|
||||
) {
|
||||
const classes = useStyles();
|
||||
const config = column.config ?? {};
|
||||
|
||||
return (
|
||||
<ButtonBase
|
||||
className={clsx("cell-collapse-padding", classes.root)}
|
||||
onClick={() => setShowComplexCell(true)}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
wrap="nowrap"
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
className={classes.value}
|
||||
>
|
||||
{Array.isArray(value) &&
|
||||
value.map((doc: any) => (
|
||||
<Grid item key={doc.docPath}>
|
||||
<Chip
|
||||
label={config.primaryKeys
|
||||
.map((key: string) => doc.snapshot[key])
|
||||
.join(" ")}
|
||||
className={classes.chip}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<ArrowDropDownIcon
|
||||
className={clsx(classes.icon, disabled && classes.disabled)}
|
||||
/>
|
||||
</ButtonBase>
|
||||
);
|
||||
});
|
||||
export default ConnectTable;
|
||||
40
www/src/components/fields/ConnectTable/PopoverCell.tsx
Normal file
40
www/src/components/fields/ConnectTable/PopoverCell.tsx
Normal file
@@ -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 (
|
||||
<ConnectTableSelect
|
||||
column={column}
|
||||
value={value}
|
||||
onChange={onSubmit}
|
||||
config={(config as any) ?? {}}
|
||||
disabled={disabled}
|
||||
TextFieldProps={{
|
||||
style: { display: "none" },
|
||||
SelectProps: {
|
||||
open: true,
|
||||
MenuProps: {
|
||||
anchorEl: parentRef,
|
||||
anchorOrigin: { vertical: "bottom", horizontal: "left" },
|
||||
transformOrigin: { vertical: "top", horizontal: "left" },
|
||||
},
|
||||
},
|
||||
}}
|
||||
onClose={() => setShowComplexCell(false)}
|
||||
loadBeforeOpen
|
||||
/>
|
||||
);
|
||||
}
|
||||
74
www/src/components/fields/ConnectTable/SideDrawerField.tsx
Normal file
74
www/src/components/fields/ConnectTable/SideDrawerField.tsx
Normal file
@@ -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 (
|
||||
<Controller
|
||||
control={control}
|
||||
name={column.key}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
const handleDelete = (hit: any) => () => {
|
||||
// if (multiple)
|
||||
onChange(value.filter((v) => v.snapshot.objectID !== hit.objectID));
|
||||
// else form.setFieldValue(field.name, []);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!disabled && (
|
||||
<ConnectTableSelect
|
||||
column={column}
|
||||
config={(config as any) ?? {}}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
TextFieldProps={{
|
||||
label: "",
|
||||
hiddenLabel: true,
|
||||
fullWidth: true,
|
||||
onBlur,
|
||||
SelectProps: {
|
||||
renderValue: () => `${value?.length ?? 0} selected`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{Array.isArray(value) && (
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
style={{ marginTop: theme.spacing(1) }}
|
||||
>
|
||||
{value.map(({ snapshot }) => (
|
||||
<Grid item key={snapshot.objectID}>
|
||||
<Chip
|
||||
component="li"
|
||||
size="medium"
|
||||
label={column.config?.primaryKeys
|
||||
?.map((key: string) => snapshot[key])
|
||||
.join(" ")}
|
||||
onDelete={disabled ? undefined : handleDelete(snapshot)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
35
www/src/components/fields/ConnectTable/index.tsx
Normal file
35
www/src/components/fields/ConnectTable/index.tsx
Normal file
@@ -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<string, any>; }",
|
||||
initialValue: [],
|
||||
icon: <ConnectTableIcon />,
|
||||
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;
|
||||
4
www/src/components/fields/ConnectTable/utils.ts
Normal file
4
www/src/components/fields/ConnectTable/utils.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const sanitiseValue = (value: any) => {
|
||||
if (value === undefined || value === null || value === "") return [];
|
||||
else return value as string[];
|
||||
};
|
||||
@@ -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<any>
|
||||
) {
|
||||
@@ -86,4 +86,4 @@ export const SingleSelect = React.forwardRef(function SingleSelect(
|
||||
</ButtonBase>
|
||||
);
|
||||
});
|
||||
export default SingleSelect;
|
||||
export default MultiSelect;
|
||||
|
||||
@@ -6,7 +6,7 @@ import MultiSelect_ from "@antlerengineering/multiselect";
|
||||
|
||||
import { sanitiseValue } from "./utils";
|
||||
|
||||
export default function SingleSelect({
|
||||
export default function MultiSelect({
|
||||
value,
|
||||
onSubmit,
|
||||
column,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -19,7 +19,7 @@ const SideDrawerField = lazy(
|
||||
|
||||
export const config: IFieldConfig = {
|
||||
type: FieldType.multiSelect,
|
||||
name: "MultiSelect",
|
||||
name: "Multi Select",
|
||||
dataType: "string[]",
|
||||
initialValue: [],
|
||||
icon: <MultiSelectIcon />,
|
||||
|
||||
@@ -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: <SingleSelectIcon />,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -39,10 +39,8 @@ export interface IPopoverCellProps extends ICustomCellProps {
|
||||
setShowComplexCell: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
parentRef: PopoverProps["anchorEl"];
|
||||
}
|
||||
export interface IPopoverBasicCellProps extends IBasicCellProps {
|
||||
export interface IPopoverBasicCellProps extends ICustomCellProps {
|
||||
setShowComplexCell: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
disabled: boolean;
|
||||
onSubmit: (value: any) => void;
|
||||
}
|
||||
|
||||
export interface ISideDrawerFieldProps {
|
||||
|
||||
Reference in New Issue
Block a user