-
- {formatter ? (
- React.createElement(formatter, {
- value,
- rowIdx: 0,
- column: {
- type,
- key: field,
- name,
- config: { options: [] },
- editable: false,
- } as any,
- row: { [field]: value },
- isRowSelected: false,
- onRowSelectionChange: () => {},
- isSummaryRow: false,
- } as any)
- ) : typeof value === "string" ||
- typeof value === "number" ||
- value === undefined ||
- value === null ? (
- value
- ) : typeof value === "boolean" ? (
- value.toString()
- ) : (
-
- )}
-
-
- );
-}
diff --git a/src/components/Wizards/Column.tsx b/src/components/Wizards/Column.tsx
deleted file mode 100644
index 2a4603c31..000000000
--- a/src/components/Wizards/Column.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import clsx from "clsx";
-
-import { makeStyles, createStyles } from "@mui/styles";
-import { Grid, GridProps, Typography } from "@mui/material";
-import { alpha } from "@mui/material/styles";
-
-import { FieldType } from "@src/constants/fields";
-import { getFieldProp } from "@src/components/fields";
-
-const useStyles = makeStyles((theme) =>
- createStyles({
- root: {
- width: "100%",
- height: 42,
- border: `1px solid ${theme.palette.divider}`,
- backgroundColor: theme.palette.background.default,
-
- padding: theme.spacing(0, 1),
-
- color: theme.palette.text.secondary,
- "&:hover": { color: theme.palette.text.primary },
-
- "& svg": { display: "block" },
- },
-
- active: {
- backgroundColor: alpha(
- theme.palette.primary.main,
- theme.palette.action.selectedOpacity
- ),
- color:
- theme.palette.mode === "dark"
- ? theme.palette.text.primary
- : theme.palette.primary.dark,
- borderColor: alpha(
- theme.palette.primary.main,
- theme.palette.action.disabledOpacity
- ),
-
- "&:hover": {
- color:
- theme.palette.mode === "dark"
- ? theme.palette.text.primary
- : theme.palette.primary.dark,
- },
- },
-
- columnNameContainer: {
- flexShrink: 1,
- overflow: "hidden",
- },
- columnName: {
- fontWeight: theme.typography.fontWeightMedium,
- lineHeight: "42px",
- display: "block",
-
- userSelect: "none",
-
- marginLeft: theme.spacing(0.5),
- },
-
- secondaryItem: { marginLeft: theme.spacing(1) },
- })
-);
-
-export interface IColumnProps extends Partial {
- label: string;
- type?: FieldType;
- secondaryItem?: React.ReactNode;
-
- active?: boolean;
-}
-
-export default function Column({
- label,
- type,
- secondaryItem,
-
- active,
- ...props
-}: IColumnProps) {
- const classes = useStyles();
-
- return (
-
- {type && {getFieldProp("icon", type)}}
-
-
-
- {label}
-
-
-
- {secondaryItem && (
-
- {secondaryItem}
-
- )}
-
- );
-}
diff --git a/src/components/Wizards/ImportCsvWizard/Step1Columns.tsx b/src/components/Wizards/ImportCsvWizard/Step1Columns.tsx
deleted file mode 100644
index f7f87f8fb..000000000
--- a/src/components/Wizards/ImportCsvWizard/Step1Columns.tsx
+++ /dev/null
@@ -1,301 +0,0 @@
-import { useState } from "react";
-import MultiSelect from "@rowy/multiselect";
-import _find from "lodash/find";
-import _findIndex from "lodash/findIndex";
-import _camel from "lodash/camelCase";
-import _sortBy from "lodash/sortBy";
-import clsx from "clsx";
-
-import { makeStyles, createStyles } from "@mui/styles";
-import {
- Grid,
- Typography,
- Divider,
- FormControlLabel,
- Checkbox,
- Chip,
-} from "@mui/material";
-import ArrowIcon from "@mui/icons-material/ArrowForward";
-
-import { IStepProps } from ".";
-import FadeList from "../ScrollableList";
-import Column from "../Column";
-
-import { useProjectContext } from "@src/contexts/ProjectContext";
-import { FieldType } from "@src/constants/fields";
-import { suggestType } from "../ImportWizard/utils";
-
-const useStyles = makeStyles((theme) =>
- createStyles({
- csvListItem: { display: "flex" },
- csvColumn: {},
-
- formControlLabel: {
- marginRight: 0,
- flex: 1,
- },
- columnLabel: { flex: 1 },
-
- arrowGridItem: {
- width: theme.spacing(7),
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- },
- activeArrow: { color: theme.palette.secondary.main },
-
- multiSelectInput: {
- backgroundColor: theme.palette.background.default,
- border: `1px solid ${theme.palette.divider}`,
- borderRadius: 0,
- boxShadow: "none",
- height: 42,
-
- "& > *": {
- ...theme.typography.caption,
- fontWeight: theme.typography.fontWeightMedium,
- },
-
- color: theme.palette.text.secondary,
- "&:hover": {
- backgroundColor: theme.palette.background.default,
- color: theme.palette.text.primary,
- boxShadow: "none",
- },
-
- "&::before": { content: "none" },
- "&::after": { pointerEvents: "none" },
- },
- noneSelected: { color: theme.palette.text.disabled },
- multiSelectInputLabel: {
- display: "flex",
- alignItems: "center",
- },
- newColumnChip: {
- marginLeft: theme.spacing(1) + " !important",
- backgroundColor: theme.palette.action.focus,
- pointerEvents: "none",
- },
- })
-);
-
-export default function Step1Columns({
- csvData,
- config,
- updateConfig,
- setConfig,
- isXs,
-}: IStepProps) {
- const classes = useStyles();
-
- const { tableState } = useProjectContext();
- const tableColumns = _sortBy(Object.values(tableState?.columns ?? {}), [
- "index",
- ])
- .filter((column) => column.type !== FieldType.id)
- .map((column) => ({
- label: column.name as string,
- value: column.key as string,
- }));
-
- const [selectedFields, setSelectedFields] = useState(
- config.pairs.map((pair) => pair.csvKey)
- );
-
- const handleSelect = (field: string) => (e) => {
- const checked = e.target.checked;
-
- if (checked) {
- setSelectedFields((x) => [...x, field]);
-
- // Try to match the field to a column in the table
- const match =
- _find(tableColumns, (column) =>
- column.label.toLowerCase().includes(field.toLowerCase())
- )?.value ?? null;
- if (match) {
- setConfig((config) => ({
- ...config,
- pairs: [...config.pairs, { csvKey: field, columnKey: match }],
- }));
- }
- } else {
- const newValue = [...selectedFields];
- newValue.splice(newValue.indexOf(field), 1);
- setSelectedFields(newValue);
-
- // Check if this pair was already pushed to main config
- const configPair = _find(config.pairs, { csvKey: field });
- const configIndex = _findIndex(config.pairs, { csvKey: field });
-
- // Delete matching newColumn if it was created
- if (configPair) {
- const newColumnIndex = _findIndex(config.newColumns, {
- key: configPair.columnKey,
- });
- if (newColumnIndex > -1) {
- const newColumns = [...config.newColumns];
- newColumns.splice(newColumnIndex, 1);
- setConfig((config) => ({ ...config, newColumns }));
- }
- }
-
- // Delete pair from main config
- if (configIndex > -1) {
- const newConfig = [...config.pairs];
- newConfig.splice(configIndex, 1);
- setConfig((config) => ({ ...config, pairs: newConfig }));
- }
- }
- };
-
- const handleChange = (csvKey: string) => (value: string) => {
- const columnKey = !!tableState?.columns[value] ? value : _camel(value);
-
- // Check if this pair already exists in config
- const configIndex = _findIndex(config.pairs, { csvKey });
- if (configIndex > -1) {
- const pairs = [...config.pairs];
- pairs[configIndex].columnKey = columnKey;
- setConfig((config) => ({ ...config, pairs }));
- } else {
- updateConfig({
- pairs: [{ csvKey, columnKey }],
- });
- }
-
- if (!tableState?.columns[value]) {
- updateConfig({
- newColumns: [
- {
- name: value,
- fieldName: columnKey,
- key: columnKey,
- type: suggestType(csvData.rows, csvKey) || FieldType.shortText,
- index: -1,
- config: {},
- },
- ],
- });
- }
- };
-
- return (
-
-
- {!isXs && (
-
-
- Select columns ({config.pairs.length} of {csvData.columns.length})
-
-
- )}
-
-
- Table columns
-
-
-
-
-
-
-
- {csvData.columns.map((field) => {
- const selected = selectedFields.indexOf(field) > -1;
- const columnKey =
- _find(config.pairs, { csvKey: field })?.columnKey ?? null;
- const matchingColumn = columnKey
- ? tableState?.columns[columnKey] ??
- _find(config.newColumns, { key: columnKey }) ??
- null
- : null;
- const isNewColumn = !!_find(config.newColumns, { key: columnKey });
-
- return (
-
-
-
- }
- label={}
- classes={{
- root: classes.formControlLabel,
- label: classes.columnLabel,
- }}
- sx={{
- alignItems: "center",
- "& .MuiFormControlLabel-label": { mt: 0 },
- }}
- />
-
-
-
-
-
-
-
- {selected && (
- {
- if (!columnKey) return "Select or add column";
- else
- return (
- <>
- {matchingColumn?.name}
- {isNewColumn && (
-
- )}
- >
- );
- },
- },
- InputProps: {
- classes: {
- root: clsx(
- classes.multiSelectInput,
- !columnKey && classes.noneSelected
- ),
- inputHiddenLabel: classes.multiSelectInputLabel,
- },
- },
- }}
- clearable={false}
- displayEmpty
- labelPlural="columns"
- freeText
- AddButtonProps={{ children: "Add new column…" }}
- AddDialogProps={{
- title: "Add new column",
- textFieldLabel: "Column name",
- }}
- />
- )}
-
-
- );
- })}
-
-
- );
-}
diff --git a/src/components/Wizards/ImportCsvWizard/Step2NewColumns.tsx b/src/components/Wizards/ImportCsvWizard/Step2NewColumns.tsx
deleted file mode 100644
index ac97235bb..000000000
--- a/src/components/Wizards/ImportCsvWizard/Step2NewColumns.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-import { useState } from "react";
-import _find from "lodash/find";
-import { parseJSON } from "date-fns";
-
-import { makeStyles, createStyles } from "@mui/styles";
-import { Grid, Typography, Divider, ButtonBase } from "@mui/material";
-import ChevronRightIcon from "@mui/icons-material/ChevronRight";
-
-import { IStepProps } from ".";
-import FadeList from "../ScrollableList";
-import Column from "../Column";
-import Cell from "../Cell";
-import FieldsDropdown from "@src/components/Table/ColumnMenu/FieldsDropdown";
-
-import { FieldType } from "@src/constants/fields";
-import { SELECTABLE_TYPES } from "../ImportWizard/utils";
-
-const useStyles = makeStyles((theme) =>
- createStyles({
- typeSelectRow: { marginBottom: theme.spacing(3) },
-
- buttonBase: {
- width: "100%",
- textAlign: "left",
- },
-
- typeHeading: { margin: theme.spacing(52 / 8, 0, 1) },
-
- previewDivider: { marginBottom: theme.spacing(2) },
- previewSpacer: { width: theme.spacing(3) },
- cellContainer: { overflow: "hidden" },
- })
-);
-
-export default function Step2NewColumns({
- csvData,
- config,
- setConfig,
- isXs,
-}: IStepProps) {
- const classes = useStyles();
-
- const [fieldToEdit, setFieldToEdit] = useState(0);
-
- const handleChange = (v) => {
- const newColumns = [...config.newColumns];
- newColumns[fieldToEdit].type = v;
-
- setConfig((config) => ({ ...config, newColumns }));
- };
-
- const currentPair = _find(config.pairs, {
- columnKey: config.newColumns[fieldToEdit]?.key,
- });
- const rowData = csvData.rows.map((row) => row[currentPair?.csvKey ?? ""]);
-
- return (
- <>
-
-
-
-
- New table columns
-
-
-
-
- {config.newColumns.map(({ key, name, type }, i) => (
-
- setFieldToEdit(i)}
- aria-label={`Edit column ${key}`}
- focusRipple
- >
- }
- />
-
-
- ))}
-
-
-
-
- Column type: {config.newColumns[fieldToEdit].name}
-
-
-
-
-
-
-
-
-
- {!isXs && (
-
-
- Raw data
-
-
- )}
-
-
- Column preview
-
-
-
-
-
-
- {!isXs && (
-
-
-
- )}
-
-
-
-
-
- {rowData.slice(0, 20).map((cell, i) => (
-
- {!isXs && (
-
- |
-
- )}
-
- {!isXs && }
-
-
- |
-
-
- ))}
-
-
- >
- );
-}
diff --git a/src/components/Wizards/ImportCsvWizard/Step3Preview.tsx b/src/components/Wizards/ImportCsvWizard/Step3Preview.tsx
deleted file mode 100644
index 66faa4fe9..000000000
--- a/src/components/Wizards/ImportCsvWizard/Step3Preview.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import _find from "lodash/find";
-import { parseJSON } from "date-fns";
-
-import { makeStyles, createStyles } from "@mui/styles";
-import { Grid } from "@mui/material";
-
-import { IStepProps } from ".";
-import Column from "../Column";
-import Cell from "../Cell";
-
-import { useProjectContext } from "@src/contexts/ProjectContext";
-import { FieldType } from "@src/constants/fields";
-
-const useStyles = makeStyles((theme) =>
- createStyles({
- root: {
- minHeight: 300,
- height: "calc(100% - 80px)",
- },
-
- container: {
- height: "100%",
- display: "flex",
- flexDirection: "column",
- overflow: "scroll",
- },
-
- spacer: {
- width: theme.spacing(3),
- height: theme.spacing(3),
- flexShrink: 0,
- },
-
- header: {
- position: "sticky",
- top: 0,
- zIndex: 1,
- },
- data: {
- flexGrow: 1,
- },
-
- column: {
- width: 200,
- flexShrink: 0,
- marginLeft: -1,
-
- "&:first-of-type": { marginLeft: 0 },
- },
- })
-);
-
-export default function Step4Preview({ csvData, config }: IStepProps) {
- const classes = useStyles();
- const { tableState } = useProjectContext();
-
- if (!tableState) return null;
-
- const columns = config.pairs.map(({ csvKey, columnKey }) => ({
- csvKey,
- columnKey,
- ...(tableState!.columns[columnKey] ??
- _find(config.newColumns, { key: columnKey }) ??
- {}),
- }));
-
- return (
-
-
-
- {columns.map(({ key, name, type }) => (
-
-
-
- ))}
-
-
-
-
- {columns.map(({ csvKey, name, columnKey, type }) => (
-
- {csvData.rows.slice(0, 50).map((row, i) => (
- |
- ))}
-
-
- ))}
-
-
-
-
- );
-}
diff --git a/src/components/Wizards/ImportCsvWizard/index.tsx b/src/components/Wizards/ImportCsvWizard/index.tsx
deleted file mode 100644
index bf474f83c..000000000
--- a/src/components/Wizards/ImportCsvWizard/index.tsx
+++ /dev/null
@@ -1,191 +0,0 @@
-import { useState, useMemo } from "react";
-import { useSnackbar } from "notistack";
-import _mergeWith from "lodash/mergeWith";
-import _find from "lodash/find";
-
-import {
- useTheme,
- useMediaQuery,
- Typography,
- Link,
- Alert,
- AlertTitle,
-} from "@mui/material";
-
-import WizardDialog from "../WizardDialog";
-import Step1Columns from "./Step1Columns";
-import Step2NewColumns from "./Step2NewColumns";
-import Step3Preview from "./Step3Preview";
-
-import { ColumnConfig } from "@src/hooks/useTable/useTableConfig";
-import { useProjectContext } from "@src/contexts/ProjectContext";
-import { getFieldProp } from "@src/components/fields";
-import { analytics } from "@src/analytics";
-import { ImportType } from "@src/components/TableHeader/ImportCsv";
-
-export type CsvConfig = {
- pairs: { csvKey: string; columnKey: string }[];
- newColumns: ColumnConfig[];
-};
-
-export interface IStepProps {
- csvData: NonNullable;
- config: CsvConfig;
- setConfig: React.Dispatch>;
- updateConfig: (value: Partial) => void;
- isXs: boolean;
-}
-
-export interface IImportCsvWizardProps {
- importType: ImportType;
- handleClose: () => void;
- csvData: {
- columns: string[];
- rows: Record[];
- } | null;
-}
-
-export default function ImportCsvWizard({
- importType,
- handleClose,
- csvData,
-}: IImportCsvWizardProps) {
- const theme = useTheme();
- const isXs = useMediaQuery(theme.breakpoints.down("sm"));
-
- const [open, setOpen] = useState(true);
-
- const { tableState, tableActions, addRows } = useProjectContext();
- const { enqueueSnackbar } = useSnackbar();
-
- const [config, setConfig] = useState({
- pairs: [],
- newColumns: [],
- });
- const updateConfig: IStepProps["updateConfig"] = (value) => {
- setConfig((prev) => ({
- ..._mergeWith(prev, value, (objValue, srcValue) =>
- Array.isArray(objValue) ? objValue.concat(srcValue) : undefined
- ),
- }));
- };
-
- const parsedRows: any[] = useMemo(() => {
- if (!tableState || !csvData) return [];
- return csvData.rows.map((row) =>
- config.pairs.reduce((a, pair) => {
- const matchingColumn =
- tableState.columns[pair.columnKey] ??
- _find(config.newColumns, { key: pair.columnKey });
- const csvFieldParser = getFieldProp(
- "csvImportParser",
- matchingColumn.type
- );
- const value = csvFieldParser
- ? csvFieldParser(row[pair.csvKey], matchingColumn.config)
- : row[pair.csvKey];
- return { ...a, [pair.columnKey]: value };
- }, {})
- );
- }, [csvData, tableState, config]);
-
- const handleFinish = () => {
- if (!tableState || !tableActions || !addRows || !parsedRows) return;
- enqueueSnackbar("Importing data…");
- // Add all new rows — synchronous
- addRows(parsedRows.map((r) => ({ data: r })).reverse(), true);
-
- // Add any new columns to the end
- for (const col of config.newColumns) {
- tableActions.column.add(col.name, col.type, col);
- }
- analytics.logEvent("import_success", { type: importType }); //change this import_success
- // Close wizard
- setOpen(false);
- setTimeout(handleClose, 300);
- };
-
- if (!csvData) return null;
-
- return (
- {
- setOpen(false);
- setTimeout(handleClose, 300);
- }}
- title="Import CSV or TSV"
- steps={
- [
- {
- title: "Choose columns",
- description: (
- <>
-
- Select or add the columns to be imported to your table.
-
-
- Importing dates?
- Make sure they’re in UTC time and{" "}
-
- a supported format
-
- . If they’re not, you’ll need to re-import your CSV data.
-
- >
- ),
- content: (
-
- ),
- disableNext: config.pairs.length === 0,
- },
- config.newColumns.length > 0 && {
- title: "Set column types",
- description:
- "Set the type of each column to display your data correctly. Some column types have been suggested based on your data.",
- content: (
-
- ),
- disableNext: config.newColumns.reduce(
- (a, c) => a || (c.type as any) === "",
- false
- ),
- },
- {
- title: "Preview",
- description:
- "Preview your data with your configured columns. You can change column types by clicking “Edit type” from the column menu at any time.",
- content: (
-
- ),
- },
- ].filter((x) => x) as any
- }
- onFinish={handleFinish}
- />
- );
-}
diff --git a/src/components/Wizards/ImportWizard/Step1Columns.tsx b/src/components/Wizards/ImportWizard/Step1Columns.tsx
deleted file mode 100644
index 615f7bf33..000000000
--- a/src/components/Wizards/ImportWizard/Step1Columns.tsx
+++ /dev/null
@@ -1,213 +0,0 @@
-import { useMemo, useState, useEffect } from "react";
-import {
- DragDropContext,
- DropResult,
- Droppable,
- Draggable,
-} from "react-beautiful-dnd";
-import _sortBy from "lodash/sortBy";
-import _startCase from "lodash/startCase";
-
-import { makeStyles, createStyles } from "@mui/styles";
-import {
- Grid,
- Typography,
- Divider,
- FormControlLabel,
- Checkbox,
-} from "@mui/material";
-import DragHandleIcon from "@mui/icons-material/DragHandle";
-
-import { IStepProps } from ".";
-import FadeList from "../ScrollableList";
-import Column from "../Column";
-import EmptyState from "@src/components/EmptyState";
-import AddColumnIcon from "@src/assets/icons/AddColumn";
-
-import { useProjectContext } from "@src/contexts/ProjectContext";
-import { FieldType } from "@src/constants/fields";
-import { suggestType } from "./utils";
-
-const useStyles = makeStyles((theme) =>
- createStyles({
- spacer: { height: theme.spacing(1) },
- formControlLabel: { marginRight: 0 },
- columnLabel: { flex: 1 },
- })
-);
-
-export default function Step1Columns({ config, setConfig }: IStepProps) {
- const classes = useStyles();
-
- // Get a list of fields from first 50 documents
- const { tableState } = useProjectContext();
- const allFields = useMemo(() => {
- const sample = tableState!.rows.slice(0, 50);
- const fields_ = new Set();
- sample.forEach((doc) =>
- Object.keys(doc).forEach((key) => {
- if (key !== "ref") fields_.add(key);
- })
- );
- return Array.from(fields_).sort();
- }, [tableState?.rows]);
-
- // Store selected fields
- const [selectedFields, setSelectedFields] = useState(
- _sortBy(Object.keys(config), "index")
- );
-
- const handleSelect = (field: string) => (e) => {
- const checked = e.target.checked;
-
- if (checked) {
- setSelectedFields([...selectedFields, field]);
- } else {
- const newSelection = [...selectedFields];
- newSelection.splice(newSelection.indexOf(field), 1);
- setSelectedFields(newSelection);
- }
- };
-
- const handleSelectAll = () => {
- if (selectedFields.length !== allFields.length)
- setSelectedFields(allFields);
- else setSelectedFields([]);
- };
-
- const handleDragEnd = (result: DropResult) => {
- const newOrder = [...selectedFields];
- const [removed] = newOrder.splice(result.source.index, 1);
- newOrder.splice(result.destination!.index, 0, removed);
- setSelectedFields(newOrder);
- };
-
- useEffect(() => {
- setConfig(
- selectedFields.reduce(
- (a, c, i) => ({
- ...a,
- [c]: {
- fieldName: c,
- key: c,
- name: config[c]?.name || _startCase(c),
- type:
- config[c]?.type ||
- suggestType(tableState!.rows, c) ||
- FieldType.shortText,
- index: i,
- config: {},
- },
- }),
- {}
- )
- );
- }, [selectedFields]);
-
- return (
-
-
-
- Select columns ({selectedFields.length} of {allFields.length})
-
-
-
-
-
-
- }
- label="Select all"
- classes={{
- root: classes.formControlLabel,
- label: classes.columnLabel,
- }}
- style={{ height: 42 }}
- sx={{
- alignItems: "center",
- "& .MuiFormControlLabel-label": { mt: 0 },
- }}
- />
-
-
- {allFields.map((field) => (
-
- -1}
- aria-label={`Select column ${field}`}
- onChange={handleSelect(field)}
- color="default"
- />
- }
- label={}
- classes={{
- root: classes.formControlLabel,
- label: classes.columnLabel,
- }}
- sx={{
- alignItems: "center",
- "& .MuiFormControlLabel-label": { mt: 0 },
- }}
- />
-
- ))}
-
-
-
-
- Sort table columns
-
-
-
- {selectedFields.length === 0 ? (
-
-
-
- ) : (
-
-
-
- {(provided) => (
-
- {selectedFields.map((field, i) => (
-
-
- {(provided, snapshot) => (
-
- }
- />
-
- )}
-
-
- ))}
- {provided.placeholder}
-
- )}
-
-
-
- )}
-
-
- );
-}
diff --git a/src/components/Wizards/ImportWizard/Step2Rename.tsx b/src/components/Wizards/ImportWizard/Step2Rename.tsx
deleted file mode 100644
index efe48bd66..000000000
--- a/src/components/Wizards/ImportWizard/Step2Rename.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { useState } from "react";
-
-import { makeStyles, createStyles } from "@mui/styles";
-import {
- Grid,
- Typography,
- Divider,
- IconButton,
- ButtonBase,
- TextField,
- InputAdornment,
-} from "@mui/material";
-import EditIcon from "@mui/icons-material/Edit";
-import DoneIcon from "@mui/icons-material/Done";
-
-import { IStepProps } from ".";
-import FadeList from "../ScrollableList";
-import Column from "../Column";
-
-const useStyles = makeStyles((theme) =>
- createStyles({
- spacer: { width: theme.spacing(3) },
-
- buttonBase: {
- width: "100%",
- textAlign: "left",
- },
-
- doneButton: { padding: theme.spacing(1) },
-
- textField: { margin: 0 },
- inputBaseRoot: {
- paddingRight: 1,
- borderRadius: 0,
- boxShadow: `0 0 0 1px inset ${theme.palette.divider}`,
- backgroundColor: theme.palette.background.default + " !important",
-
- ...theme.typography.subtitle2,
- },
- inputHiddenLabel: {
- paddingTop: theme.spacing(15 / 8),
- paddingBottom: theme.spacing(14 / 8),
- paddingLeft: theme.spacing(17 / 8),
- },
- })
-);
-
-export default function Step2Rename({
- config,
- updateConfig,
- isXs,
-}: IStepProps) {
- const classes = useStyles();
-
- const [fieldToRename, setFieldToRename] = useState("");
- const [renameTextField, setRenameTextField] = useState("");
- const handleRename = () => {
- updateConfig({ [fieldToRename]: { name: renameTextField } });
- setFieldToRename("");
- setRenameTextField("");
- };
-
- return (
-
-
- {!isXs && (
-
-
- Field names
-
-
- )}
-
-
- Set column names
-
-
-
-
-
-
-
- {Object.entries(config).map(([field, { name }]) => (
-
- {!isXs && (
-
-
-
- )}
- {!isXs && }
-
- {fieldToRename === field ? (
- setRenameTextField(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === "Enter") handleRename();
- }}
- InputProps={{
- endAdornment: (
-
-
-
-
-
- ),
- classes: {
- root: classes.inputBaseRoot,
- inputHiddenLabel: classes.inputHiddenLabel,
- },
- }}
- hiddenLabel
- fullWidth
- autoFocus
- classes={{ root: classes.textField }}
- />
- ) : (
- {
- setFieldToRename(field);
- setRenameTextField(name);
- }}
- aria-label={`Rename column ${field}`}
- focusRipple
- >
- } />
-
- )}
-
-
- ))}
-
-
- );
-}
diff --git a/src/components/Wizards/ImportWizard/Step3Types.tsx b/src/components/Wizards/ImportWizard/Step3Types.tsx
deleted file mode 100644
index 547a161da..000000000
--- a/src/components/Wizards/ImportWizard/Step3Types.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import { useState } from "react";
-
-import { makeStyles, createStyles } from "@mui/styles";
-import { Grid, Typography, Divider, ButtonBase } from "@mui/material";
-import ChevronRightIcon from "@mui/icons-material/ChevronRight";
-
-import { IStepProps } from ".";
-import FadeList from "../ScrollableList";
-import Column from "../Column";
-import Cell from "../Cell";
-import FieldsDropdown from "@src/components/Table/ColumnMenu/FieldsDropdown";
-
-import { useProjectContext } from "@src/contexts/ProjectContext";
-import { FieldType } from "@src/constants/fields";
-import { SELECTABLE_TYPES } from "./utils";
-
-const useStyles = makeStyles((theme) =>
- createStyles({
- typeSelectRow: { marginBottom: theme.spacing(3) },
-
- buttonBase: {
- width: "100%",
- textAlign: "left",
- },
-
- typeHeading: { margin: theme.spacing(52 / 8, 0, 1) },
-
- previewSpacer: { width: theme.spacing(3) },
- cellContainer: { overflow: "hidden" },
- })
-);
-
-export default function Step3Types({ config, updateConfig, isXs }: IStepProps) {
- const classes = useStyles();
-
- const [fieldToEdit, setFieldToEdit] = useState(Object.keys(config)[0]);
-
- const handleChange = (v) => updateConfig({ [fieldToEdit]: { type: v } });
-
- const { tableState } = useProjectContext();
-
- return (
-
-
-
-
- Table columns
-
-
-
-
- {Object.entries(config).map(([field, { name, type }]) => (
-
- setFieldToEdit(field)}
- aria-label={`Edit column ${field}`}
- focusRipple
- >
-
- }
- />
-
-
- ))}
-
-
-
-
- Column type: {config[fieldToEdit].name}
-
-
-
-
-
-
-
- {!isXs && (
-
-
- Raw data
-
-
- )}
-
-
- Column preview
-
-
-
-
-
-
- {!isXs && (
-
-
-
- )}
-
-
-
-
-
- {tableState!.rows!.slice(0, 20).map((row) => (
-
- {!isXs && (
-
- |
-
- )}
-
- {!isXs && }
-
-
- |
-
-
- ))}
-
-
- );
-}
diff --git a/src/components/Wizards/ImportWizard/Step4Preview.tsx b/src/components/Wizards/ImportWizard/Step4Preview.tsx
deleted file mode 100644
index 96fe25030..000000000
--- a/src/components/Wizards/ImportWizard/Step4Preview.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { makeStyles, createStyles } from "@mui/styles";
-import { Grid } from "@mui/material";
-
-import { IStepProps } from ".";
-import Column from "../Column";
-import Cell from "../Cell";
-
-import { useProjectContext } from "@src/contexts/ProjectContext";
-
-const useStyles = makeStyles((theme) =>
- createStyles({
- root: {
- minHeight: 300,
- height: "calc(100% - 80px)",
- },
-
- container: {
- height: "100%",
- display: "flex",
- flexDirection: "column",
- overflow: "scroll",
- },
-
- spacer: {
- width: theme.spacing(3),
- height: theme.spacing(3),
- flexShrink: 0,
- },
-
- header: {
- position: "sticky",
- top: 0,
- zIndex: 1,
- },
- data: {
- flexGrow: 1,
- },
-
- column: {
- width: 200,
- flexShrink: 0,
- marginLeft: -1,
-
- "&:first-of-type": { marginLeft: 0 },
- },
- })
-);
-
-export default function Step4Preview({ config }: IStepProps) {
- const classes = useStyles();
- const { tableState } = useProjectContext();
-
- return (
-
-
-
- {Object.entries(config).map(([field, { name, type }]) => (
-
-
-
- ))}
-
-
-
-
- {Object.entries(config).map(([field, { name, type }]) => (
-
- {tableState!.rows!.slice(0, 20).map((row) => (
- |
- ))}
-
-
- ))}
-
-
-
-
- );
-}
diff --git a/src/components/Wizards/ImportWizard/index.tsx b/src/components/Wizards/ImportWizard/index.tsx
deleted file mode 100644
index 0b9d19363..000000000
--- a/src/components/Wizards/ImportWizard/index.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import React, { useState, useEffect } from "react";
-import _merge from "lodash/merge";
-
-import { useTheme, useMediaQuery, Typography } from "@mui/material";
-
-import WizardDialog from "../WizardDialog";
-import Step1Columns from "./Step1Columns";
-import Step2Rename from "./Step2Rename";
-import Step3Types from "./Step3Types";
-import Step4Preview from "./Step4Preview";
-
-import { ColumnConfig } from "@src/hooks/useTable/useTableConfig";
-import { useProjectContext } from "@src/contexts/ProjectContext";
-
-export type TableColumnsConfig = { [key: string]: ColumnConfig };
-
-export type ImportWizardRef = {
- open: boolean;
- setOpen: React.Dispatch>;
-};
-
-export interface IStepProps {
- config: TableColumnsConfig;
- setConfig: React.Dispatch>;
- updateConfig: (value: Partial) => void;
- isXs: boolean;
-}
-
-export default function ImportWizard() {
- const theme = useTheme();
- const isXs = useMediaQuery(theme.breakpoints.down("sm"));
-
- const { tableState, tableActions, importWizardRef } = useProjectContext();
-
- const [open, setOpen] = useState(false);
- if (importWizardRef) importWizardRef.current = { open, setOpen };
-
- const [config, setConfig] = useState({});
- const updateConfig: IStepProps["updateConfig"] = (value) => {
- setConfig((prev) => ({ ..._merge(prev, value) }));
- };
-
- useEffect(() => {
- if (!tableState || !open) return;
-
- if (Array.isArray(tableState.filters) && tableState.filters?.length > 0)
- tableActions!.table.filter([]);
-
- if (Array.isArray(tableState.orderBy) && tableState.orderBy?.length > 0)
- tableActions!.table.orderBy([]);
- }, [open, tableState]);
-
- if (tableState?.rows.length === 0) return null;
-
- const handleFinish = () => {
- tableActions?.table.updateConfig("columns", config);
- setOpen(false);
- };
-
- return (
- setOpen(false)}
- title="Import"
- steps={[
- {
- title: "Choose columns",
- description: (
- <>
-
- It looks like you already have data in this table. You can
- import and view the data by setting up columns for this table.
-
-
- Start by choosing which columns you want to display, then sort
- your columns.
-
- >
- ),
- content: (
-
- ),
- disableNext: Object.keys(config).length === 0,
- },
- {
- title: "Rename columns",
- description:
- "Rename your table columns with user-friendly names. These changes will not update the field names in your database.",
- content: (
-
- ),
- },
- {
- title: "Set column types",
- description:
- "Set the type of each column to display your data correctly. Some column types have been suggested based on your data.",
- content: (
-
- ),
- },
- {
- title: "Preview",
- description:
- "Preview your data with your configured columns. You can change column types by clicking “Edit type” from the column menu at any time.",
- content: (
-
- ),
- },
- ]}
- onFinish={handleFinish}
- />
- );
-}
diff --git a/src/components/Wizards/ImportWizard/utils.ts b/src/components/Wizards/ImportWizard/utils.ts
deleted file mode 100644
index 3ca577a8b..000000000
--- a/src/components/Wizards/ImportWizard/utils.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import _isDate from "lodash/isDate";
-import _sortBy from "lodash/sortBy";
-
-import { FieldType } from "@src/constants/fields";
-
-export const SELECTABLE_TYPES = [
- FieldType.shortText,
- FieldType.longText,
- FieldType.richText,
- FieldType.email,
- FieldType.phone,
-
- FieldType.checkbox,
- FieldType.number,
- FieldType.percentage,
-
- FieldType.date,
- FieldType.dateTime,
-
- FieldType.url,
- FieldType.rating,
-
- FieldType.singleSelect,
- FieldType.multiSelect,
-
- FieldType.json,
- FieldType.code,
-
- FieldType.color,
- FieldType.slider,
-];
-
-export const REGEX_EMAIL =
- /([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)/;
-export const REGEX_PHONE =
- /(([+][(]?[0-9]{1,3}[)]?)|([(]?[0-9]{4}[)]?))\s*[)]?[-\s\.]?[(]?[0-9]{1,3}[)]?([-\s\.]?[0-9]{3})([-\s\.]?[0-9]{3,4})/;
-export const REGEX_URL =
- /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
-export const REGEX_HTML = /<\/?[a-z][\s\S]*>/;
-
-const inferTypeFromValue = (value: any) => {
- if (!value || typeof value === "function") return;
-
- if (Array.isArray(value) && typeof value[0] === "string")
- return FieldType.multiSelect;
- if (typeof value === "boolean") return FieldType.checkbox;
- if (_isDate(value)) return FieldType.dateTime;
-
- if (typeof value === "object") {
- if ("hex" in value && "rgb" in value) return FieldType.color;
- if ("toDate" in value) return FieldType.dateTime;
- return FieldType.json;
- }
-
- if (typeof value === "number") {
- if (Math.abs(value) > 0 && Math.abs(value) < 1) return FieldType.percentage;
- return FieldType.number;
- }
-
- if (typeof value === "string") {
- if (REGEX_EMAIL.test(value)) return FieldType.email;
- if (REGEX_PHONE.test(value)) return FieldType.phone;
- if (REGEX_URL.test(value)) return FieldType.url;
- if (REGEX_HTML.test(value)) return FieldType.richText;
- if (value.length >= 50) return FieldType.longText;
- return FieldType.shortText;
- }
-
- return;
-};
-
-export const suggestType = (data: { [key: string]: any }[], field: string) => {
- const results: Record = {};
-
- data.forEach((row) => {
- const result = inferTypeFromValue(row[field]);
- if (!result) return;
- if (results[result] === undefined) results[result] = 1;
- else results[result] += 1;
- });
-
- const sortedResults = _sortBy(Object.entries(results), 1).reverse();
- if (!sortedResults || !sortedResults[0]) return FieldType.json;
- const bestMatch = sortedResults[0][0];
-
- if (bestMatch === FieldType.shortText) {
- const values = data.map((row) => row[field]);
- const uniqueValues = new Set(values);
- const hasDuplicates = values.length !== uniqueValues.size;
-
- if (hasDuplicates && uniqueValues.size < 30) return FieldType.singleSelect;
- }
-
- return bestMatch;
-};
diff --git a/src/components/Wizards/ScrollableList.tsx b/src/components/Wizards/ScrollableList.tsx
deleted file mode 100644
index 1edd84829..000000000
--- a/src/components/Wizards/ScrollableList.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { memo, ReactNode, ElementType } from "react";
-import useScrollInfo from "react-element-scroll-hook";
-
-import { styled, Divider, DividerProps } from "@mui/material";
-
-const MemoizedList = memo(
- styled("ul")(({ theme }) => ({
- listStyleType: "none",
- margin: 0,
- padding: theme.spacing(1.5, 0, 3),
-
- height: 400,
- overflowY: "auto",
-
- "& li": { margin: theme.spacing(0.5, 0) },
- }))
-);
-
-export interface IFadeListProps {
- children?: ReactNode | ElementType[];
- disableTopDivider?: boolean;
- disableBottomDivider?: boolean;
- dividerSx?: DividerProps["sx"];
- topDividerSx?: DividerProps["sx"];
- bottomDividerSx?: DividerProps["sx"];
- listSx?: DividerProps["sx"];
-}
-
-export default function FadeList({
- children,
- disableTopDivider = true,
- disableBottomDivider = false,
- dividerSx = [],
- topDividerSx = [],
- bottomDividerSx = [],
- listSx,
-}: IFadeListProps) {
- const [scrollInfo, setRef] = useScrollInfo();
-
- return (
- <>
- {!disableTopDivider &&
- scrollInfo.y.percentage !== null &&
- scrollInfo.y.percentage > 0 && (
-
- )}
-
-
- {children}
-
-
- {!disableBottomDivider &&
- scrollInfo.y.percentage !== null &&
- scrollInfo.y.percentage < 1 && (
-
- )}
- >
- );
-}
diff --git a/src/components/Wizards/WizardDialog.tsx b/src/components/Wizards/WizardDialog.tsx
deleted file mode 100644
index 65d4bbcde..000000000
--- a/src/components/Wizards/WizardDialog.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-import { ReactNode, useState } from "react";
-
-import {
- useTheme,
- useMediaQuery,
- Dialog,
- DialogProps,
- Stack,
- DialogTitle,
- Typography,
- IconButton,
- MobileStepper,
- DialogActions,
- Button,
- Slide,
-} from "@mui/material";
-import CloseIcon from "@mui/icons-material/Close";
-import ChevronRightIcon from "@mui/icons-material/ChevronRight";
-import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
-
-import { SlideTransitionMui } from "@src/components/Modal/SlideTransition";
-import ScrollableDialogContent from "@src/components/Modal/ScrollableDialogContent";
-
-export interface IWizardDialogProps extends DialogProps {
- title: string;
- steps: {
- title: string;
- description?: ReactNode;
- content: ReactNode;
- disableNext?: boolean;
- }[];
- onFinish: () => void;
- fullHeight?: boolean;
-}
-
-export default function WizardDialog({
- title,
- steps,
- onFinish,
- fullHeight = true,
- ...props
-}: IWizardDialogProps) {
- const theme = useTheme();
- const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
-
- const [step, setStep] = useState(0);
- const currentStep = steps[step];
-
- const handleNext = () =>
- step < steps.length - 1 ? setStep((s) => s + 1) : onFinish();
- const handleBack = () =>
- step > 0 ? setStep((s) => s - 1) : props.onClose?.({}, "escapeKeyDown");
-
- return (
-
- );
-}
diff --git a/src/components/fields/Action/ActionFab.tsx b/src/components/fields/Action/ActionFab.tsx
deleted file mode 100644
index 713d8288d..000000000
--- a/src/components/fields/Action/ActionFab.tsx
+++ /dev/null
@@ -1,179 +0,0 @@
-import { useState } from "react";
-import { useSnackbar } from "notistack";
-import _get from "lodash/get";
-
-import { Fab, FabProps } from "@mui/material";
-import RunIcon from "@mui/icons-material/PlayArrow";
-import RedoIcon from "@mui/icons-material/Refresh";
-import UndoIcon from "@mui/icons-material/Undo";
-import CircularProgressOptical from "@src/components/CircularProgressOptical";
-
-import { useProjectContext } from "@src/contexts/ProjectContext";
-import { functions } from "@src/firebase";
-import { useConfirmation } from "@src/components/ConfirmationDialog";
-import { useActionParams } from "./FormDialog/Context";
-import { runRoutes } from "@src/constants/runRoutes";
-
-import { replacer } from "@src/utils/fns";
-
-const getStateIcon = (actionState, config) => {
- switch (actionState) {
- case "undo":
- return _get(config, "customIcons.undo") || ;
- case "redo":
- return _get(config, "customIcons.redo") || ;
- default:
- return _get(config, "customIcons.run") || ;
- }
-};
-
-export interface IActionFabProps extends Partial {
- row: any;
- column: any;
- onSubmit: (value: any) => void;
- value: any;
- disabled: boolean;
-}
-
-export default function ActionFab({
- row,
- column,
- onSubmit,
- value,
- disabled,
- ...props
-}: IActionFabProps) {
- const { requestConfirmation } = useConfirmation();
- const { enqueueSnackbar } = useSnackbar();
- const { requestParams } = useActionParams();
- const { tableState, rowyRun } = useProjectContext();
- const { ref } = row;
- const { config } = column as any;
-
- const hasRan = value && value.status;
-
- const action: "run" | "undo" | "redo" = hasRan
- ? value.undo || config.undo?.enabled
- ? "undo"
- : "redo"
- : "run";
- const [isRunning, setIsRunning] = useState(false);
-
- const callableName: string =
- (column as any).callableName ?? config.callableName ?? "actionScript";
-
- const fnParams = (actionParams = null) => ({
- ref: { path: ref.path },
- column: { ...column, editor: undefined },
- action,
- schemaDocPath: tableState?.config.tableConfig.path,
- actionParams,
- });
-
- const handleActionScript = async (data) => {
- if (!rowyRun) return;
- const resp = await rowyRun({
- route: runRoutes.actionScript,
- body: data,
- });
- return resp;
- };
- const handleCallableAction = async (data) => {
- const resp: any = await functions.httpsCallable(callableName)(data);
- return resp.data;
- };
-
- const handleRun = async (actionParams = null) => {
- try {
- setIsRunning(true);
- const data = fnParams(actionParams);
- let result;
-
- if (callableName === "actionScript") {
- result = await handleActionScript(data);
- } else {
- result = await handleCallableAction(data);
- }
- const { message, success } = result ?? {};
- enqueueSnackbar(
- typeof message === "string" ? message : JSON.stringify(message),
- {
- variant: success ? "success" : "error",
- }
- );
- } catch (e) {
- console.log(e);
- enqueueSnackbar(`Failed to run action. Check the column settings.`, {
- variant: "error",
- });
- } finally {
- setIsRunning(false);
- }
- };
-
- const needsParams =
- config.friction === "params" &&
- Array.isArray(config.params) &&
- config.params.length > 0;
-
- const handleClick = async () => {
- if (needsParams) {
- return requestParams({
- column,
- row,
- handleRun,
- });
- } else if (action === "undo" && config.undo.confirmation) {
- return requestConfirmation({
- title: `${column.name} Confirmation`,
- body: config.undo.confirmation.replace(/\{\{(.*?)\}\}/g, replacer(row)),
- confirm: "Run",
- handleConfirm: () => handleRun(),
- });
- } else if (
- action !== "undo" &&
- config.friction === "confirmation" &&
- typeof config.confirmation === "string"
- ) {
- return requestConfirmation({
- title: `${column.name} Confirmation`,
- body: config.confirmation.replace(/\{\{(.*?)\}\}/g, replacer(row)),
- confirm: "Run",
- handleConfirm: () => handleRun(),
- });
- } else {
- handleRun();
- }
- };
- return (
-
- theme.palette.mode === "dark"
- ? undefined
- : theme.palette.background.default,
- },
- }}
- aria-label={action}
- {...props}
- >
- {isRunning ? (
-
- ) : (
- getStateIcon(action, config)
- )}
-
- );
-}
diff --git a/src/components/fields/Action/BasicCell.tsx b/src/components/fields/Action/BasicCell.tsx
deleted file mode 100644
index e5b57f6cf..000000000
--- a/src/components/fields/Action/BasicCell.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { IBasicCellProps } from "../types";
-
-export default function Action({ name, value }: IBasicCellProps) {
- return <>{value ? value.status : name}>;
-}
diff --git a/src/components/fields/Action/FormDialog/Context.ts b/src/components/fields/Action/FormDialog/Context.ts
deleted file mode 100644
index 061026a85..000000000
--- a/src/components/fields/Action/FormDialog/Context.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import React, { useContext } from "react";
-import { IActionParams, CONFIRMATION_EMPTY_STATE } from "./props";
-const ActionParamsContext = React.createContext(
- CONFIRMATION_EMPTY_STATE
-);
-export default ActionParamsContext;
-
-export const useActionParams = () => useContext(ActionParamsContext);
diff --git a/src/components/fields/Action/FormDialog/Dialog.tsx b/src/components/fields/Action/FormDialog/Dialog.tsx
deleted file mode 100644
index 7d2a54be4..000000000
--- a/src/components/fields/Action/FormDialog/Dialog.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { FormDialog } from "@rowy/form-builder";
-export default function ParamsDialog({
- column,
- handleRun,
- open,
- handleClose,
-}: any) {
- if (!open) return null;
- return (
-
- );
-}
diff --git a/src/components/fields/Action/FormDialog/Provider.tsx b/src/components/fields/Action/FormDialog/Provider.tsx
deleted file mode 100644
index f1cfecb9c..000000000
--- a/src/components/fields/Action/FormDialog/Provider.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React, { useState } from "react";
-
-import { paramsDialogProps } from "./props";
-import Dialog from "./Dialog";
-import ActionParamsContext from "./Context";
-interface IActionParamsProviderProps {
- children: React.ReactNode;
-}
-
-const ActionParamsProvider: React.FC = ({
- children,
-}) => {
- const [state, setState] = useState();
- const [open, setOpen] = useState(false);
- const handleClose = () => {
- setState(undefined);
- setOpen(false);
- };
- const requestParams = (props: paramsDialogProps) => {
- setState(props);
- setOpen(true);
- };
- return (
-
- {children}
-
- {state && }
-
- );
-};
-
-export default ActionParamsProvider;
diff --git a/src/components/fields/Action/FormDialog/index.ts b/src/components/fields/Action/FormDialog/index.ts
deleted file mode 100644
index 58a3921a3..000000000
--- a/src/components/fields/Action/FormDialog/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { useActionParams } from "./Context";
diff --git a/src/components/fields/Action/FormDialog/props.ts b/src/components/fields/Action/FormDialog/props.ts
deleted file mode 100644
index 61537b09e..000000000
--- a/src/components/fields/Action/FormDialog/props.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export type paramsDialogProps =
- | {
- column: any;
- row: any;
- handleRun: (actionParams: any) => void;
- }
- | undefined;
-export interface IActionParams {
- dialogProps?: paramsDialogProps;
- handleClose: () => void;
- open: boolean;
- requestParams: (props: paramsDialogProps) => void;
-}
-export const CONFIRMATION_EMPTY_STATE = {
- dialogProps: undefined,
- open: false,
- handleClose: () => {},
- requestParams: () => {},
-};
diff --git a/src/components/fields/Action/FormFieldSnippets.tsx b/src/components/fields/Action/FormFieldSnippets.tsx
deleted file mode 100644
index da21cab91..000000000
--- a/src/components/fields/Action/FormFieldSnippets.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import { useState } from "react";
-import { useSnackbar } from "notistack";
-import { FieldType } from "@rowy/form-builder";
-
-import { Button, Menu, MenuItem } from "@mui/material";
-import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
-
-const FORM_FIELD_SNIPPETS = [
- {
- label: "Text field",
- value: {
- type: FieldType.shortText,
- name: "username",
- label: "Username",
- placeholder: "foo_bar",
- required: true,
- maxCharacters: 15,
- validation: [
- ["min", 3, "Must be at least 3 characters"],
- {
- 0: "notOneOf",
- 1: ["admin", "administrator"],
- 2: "Reserved username",
- },
- ],
- },
- },
- {
- label: "Single select",
- value: {
- type: FieldType.singleSelect,
- name: "team",
- label: "Team",
- required: true,
- options: ["Blue", "Orange"],
- },
- },
- {
- label: "Multi select",
- value: {
- type: FieldType.multiSelect,
- name: "roles",
- label: "Roles",
- required: true,
- options: ["ADMIN", "EDITOR", "VIEWER"],
- },
- },
- {
- label: "Number field",
- value: {
- type: "shortText",
- InputProps: {
- type: "number",
- },
- defaultValue: 1,
- label: "Price",
- name: "price",
- },
- },
- {
- label: "Check Box",
- value: {
- type: "checkbox",
- label: "Breakfast included",
- name: "breakfast",
- },
- },
-];
-
-export default function FormFieldSnippets() {
- const { enqueueSnackbar } = useSnackbar();
- const [snippetMenuAnchor, setSnippetMenuAnchor] = useState(null);
-
- return (
- <>
- }
- onClick={(e) => setSnippetMenuAnchor(e.currentTarget)}
- id="snippet-button"
- aria-controls="snippet-menu"
- aria-haspopup="true"
- aria-expanded={!!snippetMenuAnchor ? "true" : "false"}
- >
- Copy snippet
-
-
- >
- );
-}
diff --git a/src/components/fields/Action/Settings.tsx b/src/components/fields/Action/Settings.tsx
deleted file mode 100644
index 7f6eec8a8..000000000
--- a/src/components/fields/Action/Settings.tsx
+++ /dev/null
@@ -1,596 +0,0 @@
-import { lazy, Suspense, useState } from "react";
-import _get from "lodash/get";
-import stringify from "json-stable-stringify-without-jsonify";
-import { Link } from "react-router-dom";
-
-import {
- Stack,
- Grid,
- TextField,
- FormControl,
- FormLabel,
- FormControlLabel,
- RadioGroup,
- Radio,
- Typography,
- InputLabel,
- Link as MuiLink,
- Checkbox,
- FormHelperText,
- Fab,
-} from "@mui/material";
-import RunIcon from "@mui/icons-material/PlayArrow";
-import RedoIcon from "@mui/icons-material/Refresh";
-import UndoIcon from "@mui/icons-material/Undo";
-
-import SteppedAccordion from "@src/components/SteppedAccordion";
-import MultiSelect from "@rowy/multiselect";
-import FieldSkeleton from "@src/components/SideDrawer/Form/FieldSkeleton";
-import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper";
-import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
-import FormFieldSnippets from "./FormFieldSnippets";
-
-import { useProjectContext } from "@src/contexts/ProjectContext";
-import { WIKI_LINKS } from "@src/constants/externalLinks";
-import { useAppContext } from "@src/contexts/AppContext";
-
-/* eslint-disable import/no-webpack-loader-syntax */
-import actionDefs from "!!raw-loader!./action.d.ts";
-import { RUN_ACTION_TEMPLATE, UNDO_ACTION_TEMPLATE } from "./templates";
-import { routes } from "constants/routes";
-
-const diagnosticsOptions = {
- noSemanticValidation: false,
- noSyntaxValidation: false,
- noSuggestionDiagnostics: true,
-};
-
-const CodeEditor = lazy(
- () =>
- import("@src/components/CodeEditor" /* webpackChunkName: "CodeEditor" */)
-);
-
-const Settings = ({ config, onChange }) => {
- const { tableState, roles, settings, compatibleRowyRunVersion } =
- useProjectContext();
- const { projectId } = useAppContext();
- const [activeStep, setActiveStep] = useState<
- "requirements" | "friction" | "action" | "undo" | "customization"
- >("requirements");
- const functionBodyOnly = compatibleRowyRunVersion!({ maxVersion: "1.3.10" });
- const steps =
- config.isActionScript && _get(config, "undo.enabled")
- ? ["requirements", "friction", "action", "undo", "customization"]
- : ["requirements", "friction", "action", "customization"];
-
- const columnOptions = Object.values(tableState?.columns ?? {}).map((c) => ({
- label: c.name,
- value: c.key,
- }));
-
- const formattedParamsJson = stringify(
- Array.isArray(config.params) ? config.params : [],
- { space: 2 }
- );
- const [codeValid, setCodeValid] = useState(true);
-
- const scriptExtraLibs = [
- [
- "declare interface actionParams {",
- " /**",
- " * actionParams are provided by dialog popup form",
- " */",
- (config.params ?? []).filter(Boolean).map((param) => {
- const validationKeys = Object.keys(param.validation ?? {});
- if (validationKeys.includes("string")) {
- return `static ${param.name}: string`;
- } else if (validationKeys.includes("array")) {
- return `static ${param.name}: any[]`;
- } else return `static ${param.name}: any`;
- }),
- "}",
- ].join("\n"),
- actionDefs,
- ];
-
- // Backwards-compatibility: previously user could set `confirmation` without
- // having to set `friction: confirmation`
- const showConfirmationField =
- config.friction === "confirmation" ||
- (!config.friction &&
- typeof config.confirmation === "string" &&
- config.confirmation !== "");
-
- const runFn = functionBodyOnly
- ? config?.script
- : config?.runFn
- ? config.runFn
- : config?.script
- ? `const action:Action = async ({row,ref,db,storage,auth,actionParams,user}) => {
- ${config.script.replace(/utilFns.getSecret/g, "rowy.secrets.get")}
- }`
- : RUN_ACTION_TEMPLATE;
-
- const undoFn = functionBodyOnly
- ? _get(config, "undo.script")
- : config.undoFn
- ? config.undoFn
- : _get(config, "undo.script")
- ? `const action : Action = async ({row,ref,db,storage,auth,actionParams,user}) => {
- ${_get(config, "undo.script")}
- }`
- : UNDO_ACTION_TEMPLATE;
- return (
-
-
-
-
-
-
-
-
- ),
- },
- {
- id: "confirmation",
- title: "Confirmation",
- content: (
-
-
-
- Clicking the action button will:
-
-
- onChange("friction")(e.target.value)}
- >
- }
- label="Run the action immediately"
- />
- }
- label="Ask the user for confirmation"
- />
- }
- label={
- <>
-
- Ask the user for input in a form (Alpha)
-
-
-
- This feature is currently undocumented and is subject
- to change in future minor versions
-
- >
- }
- />
-
-
-
- {showConfirmationField && (
- onChange("confirmation")(e.target.value)}
- fullWidth
- helperText="The action button will not ask for confirmation if this is left empty"
- />
- )}
-
- {config.friction === "params" && (
-
-
-
- Form fields
-
-
-
-
-
-
- }>
- {
- try {
- if (v) {
- const parsed = JSON.parse(v);
- onChange("params")(parsed);
- }
- } catch (e) {
- console.log(`Failed to parse JSON: ${e}`);
- setCodeValid(false);
- }
- }}
- onValidStatusUpdate={({ isValid }) =>
- setCodeValid(isValid)
- }
- error={!codeValid}
- />
-
-
- {!codeValid && (
-
- Invalid JSON
-
- )}
-
- )}
-
- ),
- },
- {
- id: "action",
- title: "Action",
- content: (
-
-
-
- Clicking the action button will run a:
-
-
- onChange("isActionScript")(
- e.target.value === "actionScript"
- )
- }
- >
- }
- label={
- <>
- Script
-
- Write JavaScript code below that will be executed by
- Rowy Run.{" "}
- {!settings?.rowyRunUrl && (
-
- Requires Rowy Run setup →
-
- )}
-
- >
- }
- disabled={!settings?.rowyRunUrl}
- />
- }
- label={
- <>
- Callable
-
- A{" "}
-
- callable function
-
- {" "}
- you’ve deployed on your Firestore or Google Cloud
- project
-
- >
- }
- />
-
-
-
- {!config.isActionScript ? (
- onChange("callableName")(e.target.value)}
- helperText={
- <>
- Write the name of the callable function you’ve deployed to
- your project.{" "}
-
- View your callable functions
-
-
-
- Your callable function must be compatible with Rowy Action
- columns.{" "}
-
- View requirements
-
-
- >
- }
- />
- ) : (
- <>
-
- Action script
- }>
-
-
-
-
-
-
-
-
- onChange("redo.enabled")(
- !Boolean(config.redo?.enabled)
- )
- }
- name="redo"
- />
- }
- label={
- <>
-
- User can redo
-
-
- Re-runs the script above
-
- >
- }
- style={{ marginLeft: -11 }}
- />
-
-
-
- onChange("undo.enabled")(
- !Boolean(config.undo?.enabled)
- )
- }
- name="undo"
- />
- }
- label={
- <>
-
- User can undo
-
-
- Runs a new script
-
- >
- }
- style={{ marginLeft: -11 }}
- />
-
-
- >
- )}
-
- ),
- },
- config.isActionScript &&
- _get(config, "undo.enabled") && {
- id: "undo",
- title: "Undo action",
- content: (
-
- {(showConfirmationField ||
- !config.friction ||
- config.friction === "none") && (
- {
- onChange("undo.confirmation")(e.target.value);
- }}
- fullWidth
- helperText={
- <>
- {showConfirmationField &&
- "Override the confirmation message above. "}
- The action button will not ask for confirmation if this
- is left empty{showConfirmationField && "."}
- >
- }
- />
- )}
-
-
- Undo script
- }>
-
-
-
-
-
- ),
- },
- {
- id: "customization",
- title: "Customization",
- content: (
- <>
-
- onChange("customIcons.enabled")(e.target.checked)
- }
- name="customIcons.enabled"
- />
- }
- label="Customize button icons with emoji"
- style={{ marginLeft: -11 }}
- />
-
- {config.customIcons?.enabled && (
-
-
-
-
- onChange("customIcons.run")(e.target.value)
- }
- label="Run:"
- className="labelHorizontal"
- inputProps={{ style: { width: "3ch" } }}
- />
-
- {_get(config, "customIcons.run") || }
-
-
-
-
-
-
-
- onChange("customIcons.redo")(e.target.value)
- }
- label="Redo:"
- className="labelHorizontal"
- inputProps={{ style: { width: "3ch" } }}
- />
-
- {_get(config, "customIcons.redo") || }
-
-
-
-
-
-
-
- onChange("customIcons.undo")(e.target.value)
- }
- label="Undo:"
- className="labelHorizontal"
- inputProps={{ style: { width: "3ch" } }}
- />
-
- {_get(config, "customIcons.undo") || }
-
-
-
-
- )}
- >
- ),
- },
- ].filter(Boolean)}
- />
- );
-};
-export default Settings;
diff --git a/src/components/fields/Action/SideDrawerField.tsx b/src/components/fields/Action/SideDrawerField.tsx
deleted file mode 100644
index bacffa5da..000000000
--- a/src/components/fields/Action/SideDrawerField.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { Controller, useWatch } from "react-hook-form";
-import { ISideDrawerFieldProps } from "../types";
-
-import { Stack, Link } from "@mui/material";
-
-import ActionFab from "./ActionFab";
-import { useFieldStyles } from "@src/components/SideDrawer/Form/utils";
-import { sanitiseCallableName, isUrl } from "@src/utils/fns";
-
-export default function Action({
- column,
- control,
- docRef,
- disabled,
-}: ISideDrawerFieldProps) {
- const fieldClasses = useFieldStyles();
-
- const row = useWatch({ control });
-
- return (
- {
- const hasRan = value && value.status;
-
- return (
-
-
- {hasRan && isUrl(value.status) ? (
-
- {value.status}
-
- ) : hasRan ? (
- value.status
- ) : (
- sanitiseCallableName(column.key)
- )}
-
-
-
-
- );
- }}
- />
- );
-}
diff --git a/src/components/fields/Action/TableCell.tsx b/src/components/fields/Action/TableCell.tsx
deleted file mode 100644
index 6a0496cb0..000000000
--- a/src/components/fields/Action/TableCell.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { IHeavyCellProps } from "../types";
-
-import { Stack } from "@mui/material";
-
-import ActionFab from "./ActionFab";
-import { sanitiseCallableName, isUrl } from "@src/utils/fns";
-
-export default function Action({
- column,
- row,
- value,
- onSubmit,
- disabled,
-}: IHeavyCellProps) {
- const hasRan = value && ![null, undefined].includes(value.status);
-
- return (
-
-
- {hasRan && isUrl(value.status) ? (
-
- {value.status}
-
- ) : hasRan ? (
- value.status
- ) : (
- sanitiseCallableName(column.key)
- )}
-
-
-
-
- );
-}
diff --git a/src/components/fields/Action/action.d.ts b/src/components/fields/Action/action.d.ts
deleted file mode 100644
index a43399782..000000000
--- a/src/components/fields/Action/action.d.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-type ActionUser = {
- timestamp: Date;
- displayName: string;
- email: string;
- uid: string;
- emailVerified: boolean;
- photoURL: string;
- roles: string[];
-};
-type ActionContext = {
- row: Row;
- ref: FirebaseFirestore.DocumentReference;
- storage: firebasestorage.Storage;
- db: FirebaseFirestore.Firestore;
- auth: firebaseauth.BaseAuth;
- actionParams: actionParams;
- user: ActionUser;
-};
-
-type ActionResult = {
- success: boolean;
- message?: any;
- status?: string | number | null | undefined;
-};
-
-type Action = (context: ActionContext) => Promise | ActionResult;
diff --git a/src/components/fields/Action/index.tsx b/src/components/fields/Action/index.tsx
deleted file mode 100644
index 7a538714b..000000000
--- a/src/components/fields/Action/index.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { lazy } from "react";
-import { IFieldConfig, FieldType } from "@src/components/fields/types";
-import withHeavyCell from "../_withTableCell/withHeavyCell";
-
-import ActionIcon from "@mui/icons-material/TouchAppOutlined";
-import BasicCell from "./BasicCell";
-import NullEditor from "@src/components/Table/editors/NullEditor";
-
-const TableCell = lazy(
- () => import("./TableCell" /* webpackChunkName: "TableCell-Action" */)
-);
-const SideDrawerField = lazy(
- () =>
- import("./SideDrawerField" /* webpackChunkName: "SideDrawerField-Action" */)
-);
-const Settings = lazy(
- () => import("./Settings" /* webpackChunkName: "Settings-Action" */)
-);
-export const config: IFieldConfig = {
- type: FieldType.action,
- name: "Action",
- group: "Cloud Function",
- dataType: "any",
- initialValue: {},
- icon: ,
- description:
- "Button with pre-defined action script or triggers a Cloud Function. Optionally supports Undo and Redo.",
- TableCell: withHeavyCell(BasicCell, TableCell),
- TableEditor: NullEditor as any,
- SideDrawerField,
- settings: Settings,
- requireConfiguration: true,
- sortKey: "status",
-};
-export default config;
diff --git a/src/components/fields/Action/templates.ts b/src/components/fields/Action/templates.ts
deleted file mode 100644
index 5701ef244..000000000
--- a/src/components/fields/Action/templates.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-export const RUN_ACTION_TEMPLATE = `const action:Action = async ({row,ref,db,storage,auth,actionParams,user}) => {
- // Write your action code here
- // for example:
- // const authToken = await rowy.secrets.get("service")
- // try {
- // const resp = await fetch('https://example.com/api/v1/users/'+ref.id,{
- // method: 'PUT',
- // headers: {
- // 'Content-Type': 'application/json',
- // 'Authorization': authToken
- // },
- // body: JSON.stringify(row)
- // })
- //
- // return {
- // success: true,
- // message: 'User updated successfully on example service',
- // status: "upto date"
- // }
- // } catch (error) {
- // return {
- // success: false,
- // message: 'User update failed on example service',
- // }
- // }
- // checkout the documentation for more info: https://docs.rowy.io/field-types/action#script
- }`;
-
-export const UNDO_ACTION_TEMPLATE = `const action : Action = async ({row,ref,db,storage,auth,actionParams,user}) => {
- // Write your undo code here
- // for example:
- // const authToken = await rowy.secrets.get("service")
- // try {
- // const resp = await fetch('https://example.com/api/v1/users/'+ref.id,{
- // method: 'DELETE',
- // headers: {
- // 'Content-Type': 'application/json',
- // 'Authorization': authToken
- // },
- // body: JSON.stringify(row)
- // })
- //
- // return {
- // success: true,
- // message: 'User deleted successfully on example service',
- // status: null
- // }
- // } catch (error) {
- // return {
- // success: false,
- // message: 'User delete failed on example service',
- // }
- // }
- }`;
diff --git a/src/components/fields/Aggregate/Settings.tsx b/src/components/fields/Aggregate/Settings.tsx
deleted file mode 100644
index e5dc832c2..000000000
--- a/src/components/fields/Aggregate/Settings.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { lazy, Suspense } from "react";
-import { Typography } from "@mui/material";
-import MultiSelect from "@rowy/multiselect";
-import FieldSkeleton from "@src/components/SideDrawer/Form/FieldSkeleton";
-import { FieldType } from "@src/constants/fields";
-import FieldsDropdown from "@src/components/Table/ColumnMenu/FieldsDropdown";
-import { useProjectContext } from "@src/contexts/ProjectContext";
-const CodeEditor = lazy(
- () =>
- import("@src/components/CodeEditor" /* webpackChunkName: "CodeEditor" */)
-);
-
-const Settings = ({ config, onChange }) => {
- const { tableState } = useProjectContext();
-
- const columnOptions = Object.values(tableState?.columns ?? {})
- .filter((column) => column.type === FieldType.subTable)
- .map((c) => ({ label: c.name, value: c.key }));
- return (
- <>
-
- Aggergate script
- }>
-
-
-
- Field type of the output
-
- ![
- FieldType.derivative,
- FieldType.aggregate,
- FieldType.subTable,
- FieldType.action,
- ].includes(f)
- )}
- onChange={(value) => {
- onChange("renderFieldType")(value);
- }}
- />
- >
- );
-};
-export default Settings;
diff --git a/src/components/fields/Aggregate/index.tsx b/src/components/fields/Aggregate/index.tsx
deleted file mode 100644
index a77802bff..000000000
--- a/src/components/fields/Aggregate/index.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { IFieldConfig, FieldType } from "@src/components/fields/types";
-import withBasicCell from "../_withTableCell/withBasicCell";
-
-import AggregateIcon from "@mui/icons-material/LayersOutlined";
-import BasicCell from "../_BasicCell/BasicCellNull";
-import NullEditor from "@src/components/Table/editors/NullEditor";
-
-export const config: IFieldConfig = {
- type: FieldType.aggregate,
- name: "Aggregate",
- group: "Cloud Function",
- dataType: "any",
- initialValue: "",
- initializable: false,
- icon: ,
- description:
- "Value aggregated from a specified sub-table of the row. Displayed using any other field type. Requires Cloud Function set up.",
- TableCell: withBasicCell(BasicCell),
- TableEditor: NullEditor as any,
- SideDrawerField: BasicCell as any,
-};
-export default config;
diff --git a/src/components/fields/Checkbox/SideDrawerField.tsx b/src/components/fields/Checkbox/SideDrawerField.tsx
deleted file mode 100644
index d0375b96c..000000000
--- a/src/components/fields/Checkbox/SideDrawerField.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Controller } from "react-hook-form";
-import { ISideDrawerFieldProps } from "../types";
-
-import { ButtonBase, FormControlLabel, Switch } from "@mui/material";
-
-import { useFieldStyles } from "@src/components/SideDrawer/Form/utils";
-
-export default function Checkbox({
- column,
- control,
- disabled,
-}: ISideDrawerFieldProps) {
- const fieldClasses = useFieldStyles();
-
- return (
- {
- const handleChange = (event: React.ChangeEvent) => {
- onChange(event.target.checked);
- };
-
- return (
-
-
- }
- label={column.name as string}
- labelPlacement="start"
- sx={{
- mx: 0,
- my: -0.25,
- width: "100%",
- alignItems: "center",
-
- "& .MuiFormControlLabel-label": {
- font: "inherit",
- letterSpacing: "inherit",
- flexGrow: 1,
- overflowX: "hidden",
- mt: 0,
- },
-
- "& .MuiSwitch-root": { mr: -1.25 },
- }}
- />
-
- );
- }}
- />
- );
-}
diff --git a/src/components/fields/Checkbox/TableCell.tsx b/src/components/fields/Checkbox/TableCell.tsx
deleted file mode 100644
index bf8f4d411..000000000
--- a/src/components/fields/Checkbox/TableCell.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { IHeavyCellProps } from "../types";
-import _get from "lodash/get";
-
-import { FormControlLabel, Switch } from "@mui/material";
-import Confirmation from "@src/components/Confirmation";
-
-const replacer = (data: any) => (m: string, key: string) => {
- const objKey = key.split(":")[0];
- const defaultValue = key.split(":")[1] || "";
- return _get(data, objKey, defaultValue);
-};
-
-export default function Checkbox({
- row,
- column,
- value,
- onSubmit,
- disabled,
-}: IHeavyCellProps) {
- let component = (
- onSubmit(!value)}
- disabled={disabled}
- color="success"
- />
- );
-
- if (column?.config?.confirmation)
- component = (
-
- {component}
-
- );
-
- return (
-
- );
-}
diff --git a/src/components/fields/Checkbox/index.tsx b/src/components/fields/Checkbox/index.tsx
deleted file mode 100644
index fbdc504be..000000000
--- a/src/components/fields/Checkbox/index.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { lazy } from "react";
-import { IFieldConfig, FieldType } from "@src/components/fields/types";
-import withHeavyCell from "../_withTableCell/withHeavyCell";
-
-import CheckboxIcon from "@mui/icons-material/ToggleOnOutlined";
-import BasicCell from "../_BasicCell/BasicCellName";
-import NullEditor from "@src/components/Table/editors/NullEditor";
-
-const TableCell = lazy(
- () => import("./TableCell" /* webpackChunkName: "TableCell-Checkbox" */)
-);
-const SideDrawerField = lazy(
- () =>
- import(
- "./SideDrawerField" /* webpackChunkName: "SideDrawerField-Checkbox" */
- )
-);
-
-export const config: IFieldConfig = {
- type: FieldType.checkbox,
- name: "Toggle",
- group: "Numeric",
- dataType: "boolean",
- initialValue: false,
- initializable: true,
- icon: ,
- description: "True/false value. Default: false.",
- TableCell: withHeavyCell(BasicCell, TableCell),
- TableEditor: NullEditor as any,
- csvImportParser: (value: string) => {
- if (["YES", "TRUE", "1"].includes(value.toUpperCase())) return true;
- else if (["NO", "FALSE", "0"].includes(value.toUpperCase())) return false;
- else return null;
- },
- filter: {
- operators: [
- {
- value: "==",
- label: "is",
- },
- ],
- defaultValue: false,
- },
- SideDrawerField,
-};
-export default config;
diff --git a/src/components/fields/Code/BasicCell.tsx b/src/components/fields/Code/BasicCell.tsx
deleted file mode 100644
index 38feca535..000000000
--- a/src/components/fields/Code/BasicCell.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { IBasicCellProps } from "../types";
-
-import { useTheme } from "@mui/material";
-
-export default function Code({ value }: IBasicCellProps) {
- const theme = useTheme();
-
- return (
-
- {value}
-
- );
-}
diff --git a/src/components/fields/Code/Settings.tsx b/src/components/fields/Code/Settings.tsx
deleted file mode 100644
index 60adcee4b..000000000
--- a/src/components/fields/Code/Settings.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import MultiSelect from "@rowy/multiselect";
-
-const languages = [
- "javascript",
- "typescript",
- "json",
- "html",
- "css",
- "scss",
- "shell",
- "yaml",
- "xml",
- "ruby",
- "python",
- "php",
- "markdown",
- "rust",
- "csharp",
- "cpp",
- "c",
- "java",
- "go",
- "plaintext",
-];
-export default function Settings({ config, onChange }) {
- return (
- {
- onChange("language")(value);
- }}
- label="Language"
- />
- );
-}
diff --git a/src/components/fields/Code/SideDrawerField.tsx b/src/components/fields/Code/SideDrawerField.tsx
deleted file mode 100644
index 6e10fce33..000000000
--- a/src/components/fields/Code/SideDrawerField.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Controller } from "react-hook-form";
-import { ISideDrawerFieldProps } from "../types";
-
-import CodeEditor from "@src/components/CodeEditor";
-
-export default function Code({
- control,
- column,
- disabled,
-}: ISideDrawerFieldProps) {
- return (
- (
-
- )}
- />
- );
-}
diff --git a/src/components/fields/Code/index.tsx b/src/components/fields/Code/index.tsx
deleted file mode 100644
index e541631fb..000000000
--- a/src/components/fields/Code/index.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { lazy } from "react";
-import { IFieldConfig, FieldType } from "@src/components/fields/types";
-import withBasicCell from "../_withTableCell/withBasicCell";
-
-import CodeIcon from "@mui/icons-material/Code";
-import BasicCell from "./BasicCell";
-import withSideDrawerEditor from "@src/components/Table/editors/withSideDrawerEditor";
-
-const Settings = lazy(
- () => import("./Settings" /* webpackChunkName: "Settings-ConnectService" */)
-);
-
-const SideDrawerField = lazy(
- () =>
- import("./SideDrawerField" /* webpackChunkName: "SideDrawerField-Code" */)
-);
-
-export const config: IFieldConfig = {
- type: FieldType.code,
- name: "Code",
- group: "Code",
- dataType: "string",
- initialValue: "",
- initializable: true,
- icon: ,
- description: "Raw code edited with the Monaco Editor.",
- TableCell: withBasicCell(BasicCell),
- TableEditor: withSideDrawerEditor(BasicCell),
- SideDrawerField,
- settings: Settings,
-};
-export default config;
diff --git a/src/components/fields/Color/InlineCell.tsx b/src/components/fields/Color/InlineCell.tsx
deleted file mode 100644
index 2fb6dce70..000000000
--- a/src/components/fields/Color/InlineCell.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { forwardRef } from "react";
-import { IPopoverInlineCellProps } from "../types";
-
-import { ButtonBase, Box } from "@mui/material";
-
-export const Color = forwardRef(function Color(
- { value, showPopoverCell, disabled }: IPopoverInlineCellProps,
- ref: React.Ref
-) {
- return (
- showPopoverCell(true)}
- ref={ref}
- disabled={disabled}
- className="cell-collapse-padding"
- sx={{
- font: "inherit",
- letterSpacing: "inherit",
- p: "var(--cell-padding)",
- justifyContent: "flex-start",
- height: "100%",
- }}
- >
- `0 0 0 1px ${theme.palette.divider} inset`,
- borderRadius: 0.5,
- }}
- />
-
- {value?.hex}
-
- );
-});
-
-export default Color;
diff --git a/src/components/fields/Color/PopoverCell.tsx b/src/components/fields/Color/PopoverCell.tsx
deleted file mode 100644
index 6607b0b41..000000000
--- a/src/components/fields/Color/PopoverCell.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { IPopoverCellProps } from "../types";
-import { ColorPicker, toColor } from "react-color-palette";
-import { useDebouncedCallback } from "use-debounce";
-import "react-color-palette/lib/css/styles.css";
-import { useEffect, useState } from "react";
-
-export default function Color({ value, onSubmit }: IPopoverCellProps) {
- const [localValue, setLocalValue] = useState(value);
- const [handleChangeComplete] = useDebouncedCallback((color) => {
- onSubmit(color);
- }, 400);
- useEffect(() => {
- handleChangeComplete(localValue);
- }, [localValue]);
- return (
-
- );
-}
diff --git a/src/components/fields/Color/SideDrawerField.tsx b/src/components/fields/Color/SideDrawerField.tsx
deleted file mode 100644
index 6aa5d2ed8..000000000
--- a/src/components/fields/Color/SideDrawerField.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { useState } from "react";
-import { Controller } from "react-hook-form";
-import { ISideDrawerFieldProps } from "../types";
-import { ColorPicker, toColor } from "react-color-palette";
-import "react-color-palette/lib/css/styles.css";
-
-import { ButtonBase, Box, Collapse } from "@mui/material";
-
-import { useFieldStyles } from "@src/components/SideDrawer/Form/utils";
-
-export default function Color({
- column,
- control,
- disabled,
-}: ISideDrawerFieldProps) {
- const fieldClasses = useFieldStyles();
-
- const [showPicker, setShowPicker] = useState(false);
- const toggleOpen = () => setShowPicker((s) => !s);
-
- return (
- {
- return (
- <>
- {
- toggleOpen();
- onBlur();
- }}
- component={ButtonBase}
- focusRipple
- disabled={disabled}
- sx={{
- justifyContent: "flex-start",
- "&&": { pl: 0.75 },
- color: value?.hex ? "textPrimary" : "textSecondary",
- }}
- >
-
- `0 0 0 1px ${theme.palette.divider} inset`,
- borderRadius: 0.5,
- }}
- />
-
- {value?.hex ?? "Choose a color…"}
-
-
- `0 0 0 1px ${theme.palette.divider}`,
- m: 1 / 8,
- },
- "& .rcp-saturation": {
- borderRadius: 1,
- borderBottomLeftRadius: 0,
- borderBottomRightRadius: 0,
- },
- }}
- >
-
-
- >
- );
- }}
- />
- );
-}
diff --git a/src/components/fields/Color/index.tsx b/src/components/fields/Color/index.tsx
deleted file mode 100644
index 6e98ccfa9..000000000
--- a/src/components/fields/Color/index.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { lazy } from "react";
-import { IFieldConfig, FieldType } from "@src/components/fields/types";
-import withPopoverCell from "../_withTableCell/withPopoverCell";
-
-import ColorIcon from "@mui/icons-material/Colorize";
-import BasicCell from "../_BasicCell/BasicCellNull";
-import InlineCell from "./InlineCell";
-import NullEditor from "@src/components/Table/editors/NullEditor";
-
-const PopoverCell = lazy(
- () => import("./PopoverCell" /* webpackChunkName: "PopoverCell-Color" */)
-);
-const SideDrawerField = lazy(
- () =>
- import("./SideDrawerField" /* webpackChunkName: "SideDrawerField-Color" */)
-);
-
-export const config: IFieldConfig = {
- type: FieldType.color,
- name: "Color",
- group: "Numeric",
- dataType: "Record",
- initialValue: {},
- initializable: true,
- icon: ,
- description:
- "Color stored as Hex, RGB, and HSV. Edited with a visual picker.",
- TableCell: withPopoverCell(BasicCell, InlineCell, PopoverCell, {
- anchorOrigin: { horizontal: "left", vertical: "bottom" },
- }),
- TableEditor: NullEditor as any,
- SideDrawerField,
-};
-export default config;
diff --git a/src/components/fields/ConnectService/ConnectServiceSelect/PopupContents.tsx b/src/components/fields/ConnectService/ConnectServiceSelect/PopupContents.tsx
deleted file mode 100644
index a2472d2e3..000000000
--- a/src/components/fields/ConnectService/ConnectServiceSelect/PopupContents.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import React, { useEffect, useState } from "react";
-import clsx from "clsx";
-import { useDebouncedCallback } from "use-debounce";
-import _get from "lodash/get";
-
-import {
- Button,
- Checkbox,
- Divider,
- Grid,
- InputAdornment,
- List,
- ListItemIcon,
- ListItemText,
- MenuItem,
- TextField,
- Typography,
- Radio,
-} from "@mui/material";
-import SearchIcon from "@mui/icons-material/Search";
-
-import { IConnectServiceSelectProps } from ".";
-import useStyles from "./styles";
-import Loading from "@src/components/Loading";
-
-export interface IPopupContentsProps
- extends Omit {}
-
-// TODO: Implement infinite scroll here
-export default function PopupContents({
- value = [],
- onChange,
- config,
- docRef,
-}: IPopupContentsProps) {
- const url = config.url;
- const titleKey = config.titleKey ?? config.primaryKey;
- const subtitleKey = config.subtitleKey;
- const resultsKey = config.resultsKey;
- const primaryKey = config.primaryKey;
- const multiple = Boolean(config.multiple);
-
- const classes = useStyles();
-
- // Webservice search query
- const [query, setQuery] = useState("");
- // Webservice response
- const [response, setResponse] = useState(null);
-
- const [docData, setDocData] = useState(null);
- useEffect(() => {
- docRef.get().then((d) => setDocData(d.data()));
- }, []);
-
- const hits: any["hits"] = _get(response, resultsKey) ?? [];
- const [search] = useDebouncedCallback(
- async (query: string) => {
- if (!docData) return;
- if (!url) return;
- const uri = new URL(url),
- params = { q: query };
- Object.keys(params).forEach((key) =>
- uri.searchParams.append(key, params[key])
- );
-
- const resp = await fetch(uri.toString(), {
- method: "POST",
- body: JSON.stringify(docData),
- headers: { "content-type": "application/json" },
- });
-
- const jsonBody = await resp.json();
- setResponse(jsonBody);
- },
- 1000,
- { leading: true }
- );
-
- useEffect(() => {
- search(query);
- }, [query, docData]);
-
- if (!response) return ;
-
- const select = (hit: any) => () => {
- if (multiple) onChange([...value, hit]);
- else onChange([hit]);
- };
- const deselect = (hit: any) => () => {
- if (multiple)
- onChange(value.filter((v) => v[primaryKey] !== hit[primaryKey]));
- else onChange([]);
- };
-
- const selectedValues = value?.map((item) => _get(item, primaryKey));
-
- const clearSelection = () => onChange([]);
-
- return (
-
-
- setQuery(e.target.value)}
- fullWidth
- variant="filled"
- margin="dense"
- label="Search items"
- className={classes.noMargins}
- InputProps={{
- endAdornment: (
-
-
-
- ),
- }}
- onClick={(e) => e.stopPropagation()}
- onKeyDown={(e) => e.stopPropagation()}
- />
-
-
-
-
- {hits.map((hit) => {
- const isSelected =
- selectedValues.indexOf(_get(hit, primaryKey)) !== -1;
- return (
-
-
-
-
- );
- })}
-
-
-
- {multiple && (
-
-
-
- {value?.length} of {hits?.length}
-
-
-
-
-
- )}
-
- );
-}
diff --git a/src/components/fields/ConnectService/ConnectServiceSelect/index.tsx b/src/components/fields/ConnectService/ConnectServiceSelect/index.tsx
deleted file mode 100644
index 90a66ddee..000000000
--- a/src/components/fields/ConnectService/ConnectServiceSelect/index.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { Suspense } from "react";
-import clsx from "clsx";
-
-import { TextField, TextFieldProps } from "@mui/material";
-
-import useStyles from "./styles";
-import Loading from "@src/components/Loading";
-import ErrorBoundary from "@src/components/ErrorBoundary";
-import PopupContents from "./PopupContents";
-
-export type ServiceValue = {
- value: string;
- [prop: string]: any;
-};
-
-export interface IConnectServiceSelectProps {
- value: ServiceValue[];
- onChange: (value: ServiceValue[]) => void;
- config: {
- displayKey: string;
- [key: string]: any;
- };
- editable?: boolean;
- /** Optional style overrides for root MUI `TextField` component */
- className?: string;
- /** Override any props of the root MUI `TextField` component */
- TextFieldProps?: Partial;
- docRef: firebase.default.firestore.DocumentReference;
- disabled?: boolean;
-}
-
-export default function ConnectServiceSelect({
- value = [],
- className,
- TextFieldProps = {},
- disabled,
- ...props
-}: IConnectServiceSelectProps) {
- const classes = useStyles();
-
- const sanitisedValue = Array.isArray(value) ? value : [];
-
- return (
- `${(value as any[]).length} selected`,
- displayEmpty: true,
- classes: { select: classes.selectRoot },
- ...TextFieldProps.SelectProps,
- // Must have this set to prevent MUI transforming `value`
- // prop for this component to a comma-separated string
- MenuProps: {
- classes: { paper: classes.paper, list: classes.menuChild },
- MenuListProps: { disablePadding: true },
- anchorOrigin: { vertical: "bottom", horizontal: "center" },
- transformOrigin: { vertical: "top", horizontal: "center" },
- ...TextFieldProps.SelectProps?.MenuProps,
- },
- }}
- disabled={disabled}
- >
-
- }>
-
-
-
-
- );
-}
diff --git a/src/components/fields/ConnectService/ConnectServiceSelect/styles.ts b/src/components/fields/ConnectService/ConnectServiceSelect/styles.ts
deleted file mode 100644
index 8398b4684..000000000
--- a/src/components/fields/ConnectService/ConnectServiceSelect/styles.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { makeStyles, createStyles } from "@mui/styles";
-
-export const useStyles = makeStyles((theme) =>
- createStyles({
- root: { minWidth: 200 },
- selectRoot: { paddingRight: theme.spacing(4) },
-
- paper: { overflow: "hidden", maxHeight: "calc(100% - 48px)" },
- menuChild: {
- padding: `0 ${theme.spacing(2)}`,
- minWidth: 340,
- // Need to set fixed height here so popup is positioned correctly
- height: 340,
- },
-
- grid: { outline: 0 },
-
- noMargins: { margin: 0 },
-
- searchRow: { marginTop: theme.spacing(2) },
-
- listRow: {
- background: `${theme.palette.background.paper} no-repeat`,
- position: "relative",
- margin: theme.spacing(0, -2),
- maxWidth: `calc(100% + ${theme.spacing(4)})`,
-
- "&::before, &::after": {
- content: '""',
- position: "absolute",
- top: 0,
- left: 0,
- right: 0,
- zIndex: 9,
-
- display: "block",
- height: 16,
-
- background: `linear-gradient(to bottom, ${theme.palette.background.paper}, rgba(255, 255, 255, 0))`,
- },
-
- "&::after": {
- top: "auto",
- bottom: 0,
- background: `linear-gradient(to top, ${theme.palette.background.paper}, rgba(255, 255, 255, 0))`,
- },
- },
- list: () => {
- let maxHeightDeductions = 0;
- maxHeightDeductions -= 64; // search box
- maxHeightDeductions -= 48; // multiple
- maxHeightDeductions += 8; // footer padding
-
- return {
- padding: theme.spacing(2, 0),
- overflowY: "auto" as "auto",
- // height: `calc(340px - ${-maxHeightDeductions}px)`,
- height: 340 + maxHeightDeductions,
- };
- },
-
- checkboxContainer: { minWidth: theme.spacing(36 / 8) },
- checkbox: {
- padding: theme.spacing(6 / 8, 9 / 8),
- "&:hover": { background: "transparent" },
- },
-
- divider: { margin: theme.spacing(0, 2, 0, 6.5) },
-
- footerRow: { marginBottom: theme.spacing(2) },
- selectedRow: {
- "$listRow + &": { marginTop: -theme.spacing(1) },
- "$footerRow + &": { marginTop: -theme.spacing(2) },
-
- marginBottom: 0,
- "& > div": { height: 48 },
- },
- selectAllButton: { marginRight: -theme.spacing(1) },
- selectedNum: { fontFeatureSettings: '"tnum"' },
- })
-);
-
-export default useStyles;
diff --git a/src/components/fields/ConnectService/InlineCell.tsx b/src/components/fields/ConnectService/InlineCell.tsx
deleted file mode 100644
index 95a93c34b..000000000
--- a/src/components/fields/ConnectService/InlineCell.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { forwardRef } from "react";
-import { IPopoverInlineCellProps } from "../types";
-
-import { ButtonBase, Grid, Chip } from "@mui/material";
-import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
-
-import ChipList from "@src/components/Table/formatters/ChipList";
-import { get } from "lodash";
-
-export const ConnectService = forwardRef(function ConnectService(
- { value, showPopoverCell, disabled, column }: IPopoverInlineCellProps,
- ref: React.Ref
-) {
- const config = column.config ?? {};
- const displayKey = config.titleKey ?? config.primaryKey;
- return (
- showPopoverCell(true)}
- ref={ref}
- disabled={disabled}
- className="cell-collapse-padding"
- sx={{
- height: "100%",
- font: "inherit",
- color: "inherit !important",
- letterSpacing: "inherit",
- textAlign: "inherit",
- justifyContent: "flex-start",
- }}
- >
-
- {Array.isArray(value) &&
- value.map((snapshot) => (
-
-
-
- ))}
-
-
- {!disabled && (
-
- )}
-
- );
-});
-
-export default ConnectService;
diff --git a/src/components/fields/ConnectService/PopoverCell.tsx b/src/components/fields/ConnectService/PopoverCell.tsx
deleted file mode 100644
index 229a2f083..000000000
--- a/src/components/fields/ConnectService/PopoverCell.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { IPopoverCellProps } from "../types";
-
-import ConnectServiceSelect from "./ConnectServiceSelect";
-
-export default function ConnectService({
- value,
- onSubmit,
- column,
- parentRef,
- showPopoverCell,
- disabled,
- docRef,
-}: IPopoverCellProps) {
- const config = column.config ?? {};
- if (!config) return null;
-
- return (
- showPopoverCell(false),
- },
- }}
- />
- );
-}
diff --git a/src/components/fields/ConnectService/Settings.tsx b/src/components/fields/ConnectService/Settings.tsx
deleted file mode 100644
index f0ed6d685..000000000
--- a/src/components/fields/ConnectService/Settings.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { TextField, FormControlLabel, Switch, Grid } from "@mui/material";
-
-export default function Settings({ config, onChange }) {
- return (
- <>
- {
- onChange("url")(e.target.value);
- }}
- />
- {
- onChange("resultsKey")(e.target.value);
- }}
- />
- {
- onChange("primaryKey")(e.target.value);
- }}
- />
-
-
- {
- onChange("titleKey")(e.target.value);
- }}
- />
-
-
- {
- onChange("subtitleKey")(e.target.value);
- }}
- />{" "}
- {" "}
-
- onChange("multiple")(!Boolean(config.multiple))}
- name="select-multiple"
- />
- }
- label="Enable multiple item selection"
- sx={{
- alignItems: "center",
- "& .MuiFormControlLabel-label": { mt: 0 },
- }}
- />
- >
- );
-}
diff --git a/src/components/fields/ConnectService/SideDrawerField.tsx b/src/components/fields/ConnectService/SideDrawerField.tsx
deleted file mode 100644
index 9237a0c36..000000000
--- a/src/components/fields/ConnectService/SideDrawerField.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { Controller } from "react-hook-form";
-import { ISideDrawerFieldProps } from "../types";
-import { get } from "lodash";
-
-import { useTheme, Grid, Chip } from "@mui/material";
-
-import ConnectServiceSelect from "./ConnectServiceSelect";
-
-export default function ConnectService({
- column,
- control,
- disabled,
- docRef,
-}: ISideDrawerFieldProps) {
- const theme = useTheme();
-
- const config = column.config ?? {};
- const displayKey = config.titleKey ?? config.primaryKey;
-
- return (
- {
- const handleDelete = (hit: any) => () => {
- // if (multiple)
- onChange(
- value.filter(
- (v) => get(v, config.primaryKey) !== get(hit, config.primaryKey)
- )
- );
- // else form.setFieldValue(field.name, []);
- };
-
- return (
- <>
- {!disabled && (
- `${value?.length ?? 0} selected`,
- // },
- }}
- />
- )}
-
- {Array.isArray(value) && (
-
- {value.map((snapshot) => (
-
-
-
- ))}
-
- )}
- >
- );
- }}
- />
- );
-}
diff --git a/src/components/fields/ConnectService/index.tsx b/src/components/fields/ConnectService/index.tsx
deleted file mode 100644
index 5efd260b4..000000000
--- a/src/components/fields/ConnectService/index.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { lazy } from "react";
-import { IFieldConfig, FieldType } from "@src/components/fields/types";
-import withPopoverCell from "../_withTableCell/withPopoverCell";
-
-import ConnectServiceIcon from "@mui/icons-material/Http";
-import BasicCell from "../_BasicCell/BasicCellNull";
-import InlineCell from "./InlineCell";
-import NullEditor from "@src/components/Table/editors/NullEditor";
-
-const PopoverCell = lazy(
- () =>
- import("./PopoverCell" /* webpackChunkName: "PopoverCell-ConnectService" */)
-);
-const SideDrawerField = lazy(
- () =>
- import(
- "./SideDrawerField" /* webpackChunkName: "SideDrawerField-ConnectService" */
- )
-);
-const Settings = lazy(
- () => import("./Settings" /* webpackChunkName: "Settings-ConnectService" */)
-);
-
-export const config: IFieldConfig = {
- type: FieldType.connectService,
- name: "Connect Service (Alpha)",
- group: "Connection",
- dataType: "{ docPath: string; snapshot: Record; }[]",
- initialValue: [],
- icon: ,
- description:
- "Connects to an external web service to fetch a list of results.",
- TableCell: withPopoverCell(BasicCell, InlineCell, PopoverCell, {
- anchorOrigin: { horizontal: "left", vertical: "bottom" },
- transparent: true,
- }),
- TableEditor: NullEditor as any,
- SideDrawerField,
- requireConfiguration: true,
- settings: Settings,
-};
-export default config;
diff --git a/src/components/fields/ConnectService/utils.ts b/src/components/fields/ConnectService/utils.ts
deleted file mode 100644
index d2feec47b..000000000
--- a/src/components/fields/ConnectService/utils.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export const sanitiseValue = (value: any) => {
- if (value === undefined || value === null || value === "") return [];
- else return value as string[];
-};
diff --git a/src/components/fields/ConnectTable/ConnectTableSelect.tsx b/src/components/fields/ConnectTable/ConnectTableSelect.tsx
deleted file mode 100644
index 2127a4127..000000000
--- a/src/components/fields/ConnectTable/ConnectTableSelect.tsx
+++ /dev/null
@@ -1,327 +0,0 @@
-import { useState, useEffect } from "react";
-import { useDebounce } from "use-debounce";
-import useAlgolia from "use-algolia";
-import _find from "lodash/find";
-import _get from "lodash/get";
-import _pick from "lodash/pick";
-import createPersistedState from "use-persisted-state";
-import { useSnackbar } from "notistack";
-
-import { Button } from "@mui/material";
-import MultiSelect, { MultiSelectProps } from "@rowy/multiselect";
-import Loading from "@src/components/Loading";
-import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
-
-import { useProjectContext } from "@src/contexts/ProjectContext";
-import { runRoutes } from "@src/constants/runRoutes";
-import { WIKI_LINKS } from "@src/constants/externalLinks";
-
-const useAlgoliaSearchKeys = createPersistedState(
- "__ROWY__ALGOLIA_SEARCH_KEYS"
-);
-const useAlgoliaAppId = createPersistedState("__ROWY__ALGOLIA_APP_ID");
-
-export type ConnectTableValue = {
- docPath: string;
- snapshot: Record;
-};
-
-const replacer = (data: any) => (m: string, key: string) => {
- const objKey = key.split(":")[0];
- const defaultValue = key.split(":")[1] || "";
- return _get(data, objKey, defaultValue);
-};
-
-export interface IConnectTableSelectProps {
- value: ConnectTableValue[] | ConnectTableValue | null;
- onChange: (value: ConnectTableValue[] | ConnectTableValue | null) => void;
- column: any;
- config: {
- filters: string;
- primaryKeys: string[];
- secondaryKeys?: string[];
- snapshotFields?: string[];
- trackedFields?: string[];
- multiple?: boolean;
- searchLabel?: string;
- [key: string]: any;
- };
- disabled?: boolean;
- /** Optional style overrides for root MUI `TextField` component */
- className?: string;
- row: any;
- /** 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,
- row,
- config,
- disabled,
- className,
- TextFieldProps = {},
- onClose,
- loadBeforeOpen,
-}: IConnectTableSelectProps) {
- const { enqueueSnackbar } = useSnackbar();
- const { rowyRun } = useProjectContext();
- const [algoliaAppId, setAlgoliaAppId] = useAlgoliaAppId(
- undefined
- );
- useEffect(() => {
- if (!algoliaAppId && rowyRun) {
- rowyRun({ route: runRoutes.algoliaAppId }).then(
- ({ success, appId, message }) => {
- if (success) setAlgoliaAppId(appId);
- else
- enqueueSnackbar(
- message.replace("not setup", "not set up") +
- ": Failed to get app ID",
- {
- variant: "error",
- action: (
-
- ),
- }
- );
- }
- );
- }
- }, []);
-
- const filters = config.filters
- ? config.filters.replace(/\{\{(.*?)\}\}/g, replacer(row))
- : "";
-
- const algoliaIndex = config.index;
- const [algoliaSearchKeys, setAlgoliaSearchKeys] = useAlgoliaSearchKeys(
- {}
- );
-
- const [algoliaState, requestDispatch, , setAlgoliaConfig] = useAlgolia(
- "",
- "",
- // Don’t choose the index until the user opens the dropdown if !loadBeforeOpen
- loadBeforeOpen ? algoliaIndex : "",
- { filters }
- );
-
- const setAlgoliaSearchKey = async (algoliaIndex: string) => {
- const requestedAt = Date.now() / 1000;
- if (
- algoliaSearchKeys &&
- (algoliaSearchKeys?.[algoliaIndex] as any)?.key &&
- requestedAt <
- (algoliaSearchKeys?.[algoliaIndex] as any).requestedAt + 3600
- ) {
- //'use existing key'
- setAlgoliaConfig({
- appId: algoliaAppId,
- indexName: algoliaIndex,
- searchKey: (algoliaSearchKeys?.[algoliaIndex] as any).key,
- });
- } else {
- //'get new key'
- if (rowyRun) {
- const resp = await rowyRun({
- route: runRoutes.algoliaSearchKey,
- params: [algoliaIndex as string],
- });
- const { key } = resp;
- if (key) {
- const newKey = {
- key,
- requestedAt,
- };
- setAlgoliaSearchKeys(
- algoliaSearchKeys
- ? { ...algoliaSearchKeys, [algoliaIndex]: newKey }
- : { [algoliaIndex]: newKey }
- );
- setAlgoliaConfig({ indexName: algoliaIndex, searchKey: key });
- }
- }
- }
- };
-
- useEffect(() => {
- setAlgoliaSearchKey(algoliaIndex);
- }, [algoliaIndex]);
-
- const options = algoliaState.hits.map((hit) => ({
- label: config.primaryKeys?.map((key: string) => hit[key]).join(" "),
- value: hit.objectID,
- }));
-
- // 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
- let initialLocalValue: any;
- if (config.multiple !== false) {
- initialLocalValue = Array.isArray(value)
- ? value
- : value?.docPath
- ? [value]
- : [];
- } else {
- initialLocalValue = Array.isArray(value)
- ? value[0]
- : value?.docPath
- ? value
- : null;
- }
- const [localValue, setLocalValue] = useState(initialLocalValue);
-
- // Pass objectID[] | objectID | null to MultiSelect
- const sanitisedValue =
- config.multiple !== false
- ? localValue.map((item) => item.docPath.split("/").pop())
- : localValue?.docPath?.split("/").pop() ?? null;
-
- const handleChange = (_newValue: string[] | string | null) => {
- let newLocalValue: any;
- if (config.multiple !== false && Array.isArray(_newValue)) {
- newLocalValue = (_newValue as string[])
- .map((objectID) => {
- const docPath = `${algoliaIndex}/${objectID}`;
-
- // Try to find the snapshot from the current Algolia query
- const match = _find(algoliaState.hits, { objectID });
-
- // If not found and this objectID is already in the previous value,
- // use that previous value’s snapshot
- // Else return null
- if (!match) {
- const existingMatch = _find(localValue, { docPath });
- if (existingMatch) return existingMatch;
- else return null;
- }
-
- const { _highlightResult, ...snapshot } = match;
-
- // Use snapshotFields to limit snapshots
- let partialSnapshot = snapshot;
- if (
- Array.isArray(config.snapshotFields) &&
- config.snapshotFields.length > 0
- )
- partialSnapshot = _pick(snapshot, config.snapshotFields);
-
- return { snapshot: partialSnapshot, docPath };
- })
- .filter((x) => x !== null);
- } else if (config.multiple === false && typeof _newValue === "string") {
- const docPath = `${algoliaIndex}/${_newValue}`;
-
- // Try to find the snapshot from the current Algolia query
- const match = _find(algoliaState.hits, { objectID: _newValue });
-
- // If not found and this objectID is the previous value, use that or null
- if (!match) {
- if (localValue?.docPath === docPath) newLocalValue = localValue;
- else newLocalValue = null;
- } else {
- const { _highlightResult, ...snapshot } = match;
-
- // Use snapshotFields to limit snapshots
- let partialSnapshot = snapshot;
- if (
- Array.isArray(config.snapshotFields) &&
- config.snapshotFields.length > 0
- )
- partialSnapshot = _pick(snapshot, config.snapshotFields);
-
- newLocalValue = { snapshot: partialSnapshot, docPath };
- }
- } else if (config.multiple === false && _newValue === null) {
- newLocalValue = null;
- }
-
- // Store in `localValue` until user closes dropdown and triggers `handleSave`
- setLocalValue(newLocalValue);
-
- // 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` because this component is
- // unmounted before `handleSave` is called
- if (config.multiple === false) onChange(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 });
- requestDispatch({ filters });
- }}
- onClose={handleSave}
- options={options}
- TextFieldProps={{
- className,
- hiddenLabel: true,
- SelectProps: {
- renderValue: () => {
- if (Array.isArray(localValue)) {
- if (localValue.length !== 1)
- return `${localValue.length} selected`;
- return config.primaryKeys
- ?.map((key: string) => localValue[0]?.snapshot?.[key])
- .join(" ");
- } else {
- if (!localValue?.snapshot) return "0 selected";
- return config.primaryKeys
- ?.map((key: string) => localValue?.snapshot?.[key])
- .join(" ");
- }
- },
- },
- ...TextFieldProps,
- }}
- label={column?.name}
- labelPlural={config.searchLabel}
- multiple={config.multiple !== false}
- {...({
- AutocompleteProps: {
- loading: algoliaState.loading,
- loadingText: ,
- inputValue: search,
- onInputChange: (_, value, reason) => {
- if (reason === "input") setSearch(value);
- },
- filterOptions: () => options,
- },
- } as any)}
- countText={
- Array.isArray(localValue)
- ? `${localValue.length} of ${algoliaState.response?.nbHits ?? "?"}`
- : undefined
- }
- disabled={disabled}
- />
- );
-}
diff --git a/src/components/fields/ConnectTable/InlineCell.tsx b/src/components/fields/ConnectTable/InlineCell.tsx
deleted file mode 100644
index 583404130..000000000
--- a/src/components/fields/ConnectTable/InlineCell.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { forwardRef } from "react";
-import { IPopoverInlineCellProps } from "../types";
-
-import { ButtonBase, Grid, Chip } from "@mui/material";
-import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
-
-import ChipList from "@src/components/Table/formatters/ChipList";
-
-export const ConnectTable = forwardRef(function ConnectTable(
- { value, showPopoverCell, disabled, column }: IPopoverInlineCellProps,
- ref: React.Ref
-) {
- const config = column.config ?? {};
-
- return (
- showPopoverCell(true)}
- ref={ref}
- disabled={disabled}
- className="cell-collapse-padding"
- sx={{
- height: "100%",
- font: "inherit",
- color: "inherit !important",
- letterSpacing: "inherit",
- textAlign: "inherit",
- justifyContent: "flex-start",
- }}
- >
-
- {Array.isArray(value) ? (
- value.map((item: any) => (
-
- item.snapshot[key])
- .join(" ")}
- />
-
- ))
- ) : value ? (
-
- value.snapshot[key])
- .join(" ")}
- />
-
- ) : null}
-
-
- {!disabled && (
-
- )}
-
- );
-});
-
-export default ConnectTable;
diff --git a/src/components/fields/ConnectTable/PopoverCell.tsx b/src/components/fields/ConnectTable/PopoverCell.tsx
deleted file mode 100644
index 619709695..000000000
--- a/src/components/fields/ConnectTable/PopoverCell.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { IPopoverCellProps } from "../types";
-
-import ConnectTableSelect from "./ConnectTableSelect";
-
-export default function ConnectTable({
- value,
- onSubmit,
- column,
- parentRef,
- showPopoverCell,
- row,
- disabled,
-}: IPopoverCellProps) {
- const config = column.config ?? {};
- if (!config || !config.primaryKeys) return null;
-
- return (
- showPopoverCell(false)}
- loadBeforeOpen
- />
- );
-}
diff --git a/src/components/fields/ConnectTable/Settings.tsx b/src/components/fields/ConnectTable/Settings.tsx
deleted file mode 100644
index c220f109a..000000000
--- a/src/components/fields/ConnectTable/Settings.tsx
+++ /dev/null
@@ -1,195 +0,0 @@
-import { useEffect, useState } from "react";
-import { ISettingsProps } from "../types";
-import _sortBy from "lodash/sortBy";
-
-import {
- Typography,
- Link,
- TextField,
- FormControlLabel,
- Checkbox,
- FormHelperText,
-} from "@mui/material";
-import MultiSelect from "@rowy/multiselect";
-import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
-import WarningIcon from "@mui/icons-material/WarningAmberOutlined";
-
-import { FieldType } from "@src/constants/fields";
-import { db } from "@src/firebase";
-import { useProjectContext } from "@src/contexts/ProjectContext";
-import { TABLE_SCHEMAS } from "@src/config/dbPaths";
-import { WIKI_LINKS } from "@src/constants/externalLinks";
-import { useRowyRunModal } from "@src/atoms/RowyRunModal";
-
-export default function Settings({ onChange, config }: ISettingsProps) {
- const { tables, settings } = useProjectContext();
-
- const openRowyRunModal = useRowyRunModal();
- useEffect(() => {
- if (!settings?.rowyRunUrl) openRowyRunModal("Connect Table fields");
- }, [settings?.rowyRunUrl]);
-
- const tableOptions = _sortBy(
- tables?.map((table) => ({
- label: table.name,
- value: table.id,
- section: table.section,
- collection: table.collection,
- })) ?? [],
- ["section", "label"]
- );
-
- const [columns, setColumns] = useState<
- { value: string; label: string; type: FieldType }[]
- >([]);
- const getColumns = async (table) => {
- const tableConfigDoc = await db.doc(`${TABLE_SCHEMAS}/${table}`).get();
- const tableConfig = tableConfigDoc.data();
- if (tableConfig && tableConfig.columns)
- setColumns(
- Object.values(tableConfig.columns).map((c: any) => ({
- label: c.name,
- value: c.key,
- type: c.type,
- }))
- );
- };
- useEffect(() => {
- if (config.index) {
- getColumns(config.index);
- }
- }, [config.index]);
-
- return (
- <>
-
- Connect Table requires additional setup.{" "}
-
- Instructions
-
-
-
-
- ) => (
- <>
- {option.section} > {option.label}{" "}
- {option.collection}
- >
- )}
- TextFieldProps={{
- helperText:
- "Make sure this table is being synced to an Algolia index",
- }}
- />
-
- onChange("multiple")(e.target.checked)}
- />
- }
- label={
- <>
- Multiple selection
-
- {config.multiple === false ? (
- <>
- Field values will be either{" "}
-