Files
rowy/src/components/ColumnModals/ColumnConfigModal/ColumnConfig.tsx

227 lines
7.1 KiB
TypeScript
Raw Normal View History

import { useState, Suspense, useMemo, createElement } from "react";
import { useAtom, useSetAtom } from "jotai";
import { set } from "lodash-es";
import { ErrorBoundary } from "react-error-boundary";
import { IColumnModalProps } from "@src/components/ColumnModals";
import { Typography, Stack } from "@mui/material";
import Modal from "@src/components/Modal";
import { getFieldProp } from "@src/components/fields";
import DefaultValueInput from "./DefaultValueInput";
import { InlineErrorFallback } from "@src/components/ErrorFallback";
import Loading from "@src/components/Loading";
import {
2022-07-18 14:40:46 +10:00
projectScope,
rowyRunAtom,
confirmDialogAtom,
2022-07-18 14:40:46 +10:00
} from "@src/atoms/projectScope";
import {
tableScope,
tableSettingsAtom,
updateColumnAtom,
} from "@src/atoms/tableScope";
import { useSnackLogContext } from "@src/contexts/SnackLogContext";
import { FieldType } from "@src/constants/fields";
import { runRoutes } from "@src/constants/runRoutes";
import { useSnackbar } from "notistack";
import {
getTableSchemaPath,
getTableBuildFunctionPathname,
} from "@src/utils/table";
export default function ColumnConfigModal({
onClose,
column,
}: IColumnModalProps) {
2022-07-18 14:40:46 +10:00
const [rowyRun] = useAtom(rowyRunAtom, projectScope);
const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
const updateColumn = useSetAtom(updateColumnAtom, tableScope);
2022-07-18 14:40:46 +10:00
const confirm = useSetAtom(confirmDialogAtom, projectScope);
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const snackLogContext = useSnackLogContext();
const [showRebuildPrompt, setShowRebuildPrompt] = useState(false);
const [newConfig, setNewConfig] = useState(column.config ?? {});
const customFieldSettings = getFieldProp("settings", column.type);
const settingsValidator = getFieldProp("settingsValidator", column.type);
const initializable = getFieldProp("initializable", column.type);
const rendedFieldSettings = useMemo(
() =>
2022-12-08 18:58:26 +08:00
[FieldType.derivative, FieldType.aggregate, FieldType.formula].includes(
column.type
) && newConfig.renderFieldType
? getFieldProp("settings", newConfig.renderFieldType)
: null,
[newConfig.renderFieldType, column.type]
);
const [errors, setErrors] = useState({});
const validateSettings = () => {
if (settingsValidator) {
const errors = settingsValidator(newConfig);
setErrors(errors);
return errors;
}
setErrors({});
return {};
};
const handleChange = (key: string) => (update: any) => {
if (
showRebuildPrompt === false &&
(key.includes("defaultValue") || column.type === FieldType.derivative) &&
column.config?.[key] !== update
) {
setShowRebuildPrompt(true);
}
const updatedConfig = set({ ...newConfig }, key, update);
setNewConfig(updatedConfig);
validateSettings();
};
return (
<Modal
maxWidth="md"
onClose={onClose}
title={`${column.name}: Config`}
disableBackdropClick
disableEscapeKeyDown
children={
<Suspense fallback={<Loading fullScreen={false} />}>
<>
{initializable && (
<>
<section style={{ marginTop: 1 }}>
{/* top margin fixes visual bug */}
<ErrorBoundary FallbackComponent={InlineErrorFallback}>
<DefaultValueInput
handleChange={handleChange}
column={{ ...column, config: newConfig }}
/>
</ErrorBoundary>
</section>
</>
)}
{customFieldSettings && (
<Stack
spacing={3}
sx={{ borderTop: 1, borderColor: "divider", pt: 3 }}
>
{createElement(customFieldSettings, {
config: newConfig,
onChange: handleChange,
fieldName: column.fieldName,
onBlur: validateSettings,
errors,
})}
</Stack>
)}
{rendedFieldSettings && (
<Stack
spacing={3}
sx={{ borderTop: 1, borderColor: "divider", pt: 3 }}
>
<Typography variant="subtitle1">
Rendered field config
</Typography>
{createElement(rendedFieldSettings, {
config: newConfig,
onChange: handleChange,
onBlur: validateSettings,
errors,
})}
</Stack>
)}
{/* {
<ConfigForm
type={type}
config={newConfig}
/>
} */}
</>
</Suspense>
}
actions={{
primary: {
onClick: async () => {
const errors = validateSettings();
if (Object.keys(errors).length > 0) {
confirm({
title: "Invalid settings",
body: (
<>
<Typography>Please fix the following settings:</Typography>
<ul style={{ paddingLeft: "1.5em" }}>
{Object.entries(errors).map(([key, message]) => (
<li key={key}>
<>
<code>{key}</code>: {message}
</>
</li>
))}
</ul>
</>
),
confirm: "Fix",
hideCancel: true,
handleConfirm: () => {},
});
return;
}
const savingSnack = enqueueSnackbar("Saving changes…");
await updateColumn({
key: column.key,
config: { config: newConfig },
});
if (showRebuildPrompt) {
confirm({
title: "Deploy changes?",
body: "You need to re-deploy this tables cloud function to apply the changes you made. You can also re-deploy later.",
confirm: "Deploy",
cancel: "Later",
handleConfirm: async () => {
if (!rowyRun) return;
snackLogContext.requestSnackLog();
rowyRun({
route: runRoutes.buildFunction,
body: {
tablePath: tableSettings.collection,
2022-06-15 14:26:52 +10:00
// pathname must match old URL format
pathname: getTableBuildFunctionPathname(
tableSettings.id,
tableSettings.tableType
),
2022-06-02 00:50:18 +10:00
tableConfigPath: getTableSchemaPath(tableSettings),
},
});
},
});
}
closeSnackbar(savingSnack);
enqueueSnackbar("Changes saved");
onClose();
setShowRebuildPrompt(false);
},
children: "Update",
},
secondary: {
onClick: onClose,
children: "Cancel",
},
}}
/>
);
}