mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
restructure and consolidate TableModals
This commit is contained in:
@@ -3,7 +3,7 @@ import { atomWithStorage, atomWithHash } from "jotai/utils";
|
||||
|
||||
import type { PopoverProps } from "@mui/material";
|
||||
import type { ColumnConfig, TableFilter } from "@src/types/table";
|
||||
import { SEVERITY_LEVELS } from "@src/components/TableToolbar/CloudLogs/CloudLogSeverityIcon";
|
||||
import { SEVERITY_LEVELS } from "@src/components/TableModals/CloudLogsModal/CloudLogSeverityIcon";
|
||||
|
||||
/**
|
||||
* Open table column menu. Set to `null` to close.
|
||||
@@ -80,7 +80,7 @@ export const sideDrawerShowHiddenFieldsAtom = atomWithStorage(
|
||||
|
||||
/**
|
||||
* Opens a table modal. Set to `null` to close.
|
||||
* Modals: cloud logs, extensions, webhooks, export.
|
||||
* Modals: cloud logs, extensions, webhooks, export, import, import CSV.
|
||||
*
|
||||
* @example Basic usage:
|
||||
* ```
|
||||
@@ -94,9 +94,22 @@ export const sideDrawerShowHiddenFieldsAtom = atomWithStorage(
|
||||
* ```
|
||||
*/
|
||||
export const tableModalAtom = atomWithHash<
|
||||
"cloudLogs" | "extensions" | "webhooks" | "export" | null
|
||||
| "cloudLogs"
|
||||
| "extensions"
|
||||
| "webhooks"
|
||||
| "export"
|
||||
| "import"
|
||||
| "importCsv"
|
||||
| null
|
||||
>("tableModal", null, { replaceState: true });
|
||||
|
||||
export type ImportCsvData = { columns: string[]; rows: Record<string, any>[] };
|
||||
/** Store import CSV popover and wizard state */
|
||||
export const importCsvAtom = atom<{
|
||||
importType: "csv" | "tsv";
|
||||
csvData: ImportCsvData | null;
|
||||
}>({ importType: "csv", csvData: null });
|
||||
|
||||
/** Store side drawer open state */
|
||||
export const sideDrawerOpenAtom = atom(false);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import { useSnackbar } from "notistack";
|
||||
import { getTableSchemaPath } from "@src/utils/table";
|
||||
|
||||
export default function ColumnConfigModal({
|
||||
handleClose,
|
||||
onClose,
|
||||
column,
|
||||
}: IColumnModalProps) {
|
||||
const [rowyRun] = useAtom(rowyRunAtom, globalScope);
|
||||
@@ -82,7 +82,7 @@ export default function ColumnConfigModal({
|
||||
return (
|
||||
<Modal
|
||||
maxWidth="md"
|
||||
onClose={handleClose}
|
||||
onClose={onClose}
|
||||
title={`${column.name}: Config`}
|
||||
disableBackdropClick
|
||||
disableEscapeKeyDown
|
||||
@@ -203,13 +203,13 @@ export default function ColumnConfigModal({
|
||||
closeSnackbar(savingSnack);
|
||||
enqueueSnackbar("Changes saved");
|
||||
|
||||
handleClose();
|
||||
onClose();
|
||||
setShowRebuildPrompt(false);
|
||||
},
|
||||
children: "Update",
|
||||
},
|
||||
secondary: {
|
||||
onClick: handleClose,
|
||||
onClick: onClose,
|
||||
children: "Cancel",
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { ColumnConfig } from "@src/types/table";
|
||||
|
||||
export interface IColumnModalProps {
|
||||
handleClose: () => void;
|
||||
onClose: () => void;
|
||||
column: ColumnConfig;
|
||||
}
|
||||
|
||||
@@ -24,22 +24,21 @@ export default function ColumnModals() {
|
||||
|
||||
if (!columnModal) return null;
|
||||
|
||||
const handleClose = () => setColumnModal(RESET);
|
||||
const onClose = () => setColumnModal(RESET);
|
||||
|
||||
if (columnModal.type === "new")
|
||||
return <NewColumnModal handleClose={handleClose} />;
|
||||
if (columnModal.type === "new") return <NewColumnModal onClose={onClose} />;
|
||||
|
||||
const column = tableSchema.columns?.[columnModal.columnKey ?? ""];
|
||||
if (!column) return null;
|
||||
|
||||
if (columnModal.type === "name")
|
||||
return <NameChangeModal handleClose={handleClose} column={column} />;
|
||||
return <NameChangeModal onClose={onClose} column={column} />;
|
||||
|
||||
if (columnModal.type === "type")
|
||||
return <TypeChangeModal handleClose={handleClose} column={column} />;
|
||||
return <TypeChangeModal onClose={onClose} column={column} />;
|
||||
|
||||
if (columnModal.type === "config")
|
||||
return <ColumnConfigModal handleClose={handleClose} column={column} />;
|
||||
return <ColumnConfigModal onClose={onClose} column={column} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import Modal from "@src/components/Modal";
|
||||
import { tableScope, updateColumnAtom } from "@src/atoms/tableScope";
|
||||
|
||||
export default function NameChangeModal({
|
||||
handleClose,
|
||||
onClose,
|
||||
column,
|
||||
}: IColumnModalProps) {
|
||||
const updateColumn = useSetAtom(updateColumnAtom, tableScope);
|
||||
@@ -16,7 +16,7 @@ export default function NameChangeModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={handleClose}
|
||||
onClose={onClose}
|
||||
title="Rename column"
|
||||
maxWidth="xs"
|
||||
children={
|
||||
@@ -35,12 +35,12 @@ export default function NameChangeModal({
|
||||
primary: {
|
||||
onClick: () => {
|
||||
updateColumn({ key: column.key, config: { name: newName } });
|
||||
handleClose();
|
||||
onClose();
|
||||
},
|
||||
children: "Update",
|
||||
},
|
||||
secondary: {
|
||||
onClick: handleClose,
|
||||
onClick: onClose,
|
||||
children: "Cancel",
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -27,8 +27,8 @@ const AUDIT_FIELD_TYPES = [
|
||||
];
|
||||
|
||||
export default function NewColumnModal({
|
||||
handleClose,
|
||||
}: Pick<IColumnModalProps, "handleClose">) {
|
||||
onClose,
|
||||
}: Pick<IColumnModalProps, "onClose">) {
|
||||
const [updateTable] = useAtom(updateTableAtom, globalScope);
|
||||
const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
|
||||
const addColumn = useSetAtom(addColumnAtom, tableScope);
|
||||
@@ -73,7 +73,7 @@ export default function NewColumnModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={handleClose}
|
||||
onClose={onClose}
|
||||
title="Add new column"
|
||||
fullWidth
|
||||
maxWidth="xs"
|
||||
@@ -159,7 +159,7 @@ export default function NewColumnModal({
|
||||
if (requireConfiguration) {
|
||||
setColumnModal({ type: "config", columnKey: fieldKey });
|
||||
} else {
|
||||
handleClose();
|
||||
onClose();
|
||||
}
|
||||
logEvent(analytics, "create_column", { type });
|
||||
},
|
||||
@@ -171,7 +171,7 @@ export default function NewColumnModal({
|
||||
children: requireConfiguration ? "Next" : "Add",
|
||||
},
|
||||
secondary: {
|
||||
onClick: handleClose,
|
||||
onClick: onClose,
|
||||
children: "Cancel",
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { getFieldProp } from "@src/components/fields";
|
||||
import { analytics, logEvent } from "analytics";
|
||||
|
||||
export default function TypeChangeModal({
|
||||
handleClose,
|
||||
onClose,
|
||||
column,
|
||||
}: IColumnModalProps) {
|
||||
const updateColumn = useSetAtom(updateColumnAtom, tableScope);
|
||||
@@ -20,7 +20,7 @@ export default function TypeChangeModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={handleClose}
|
||||
onClose={onClose}
|
||||
title="Change column type"
|
||||
children={
|
||||
<>
|
||||
@@ -41,7 +41,7 @@ export default function TypeChangeModal({
|
||||
onClick: () => {
|
||||
const prevType = column.type;
|
||||
updateColumn({ key: column.key, config: { type: newType } });
|
||||
handleClose();
|
||||
onClose();
|
||||
logEvent(analytics, "change_column_type", { newType, prevType });
|
||||
},
|
||||
children: "Update",
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function ConfirmDialog() {
|
||||
}}
|
||||
maxWidth={maxWidth}
|
||||
TransitionComponent={SlideTransitionMui}
|
||||
style={{ cursor: "default" }}
|
||||
sx={{ cursor: "default", zIndex: (theme) => theme.zIndex.modal + 50 }}
|
||||
>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { APP_BAR_HEIGHT } from "@src/layouts/Navigation";
|
||||
|
||||
// FIXME:
|
||||
// import ImportWizard from "@src/components/TableWizards/ImportWizard";
|
||||
// import ImportWizard from "@src/components/TableModals/ImportWizard";
|
||||
// import ImportCSV from "@src/components/TableToolbar/ImportCsv";
|
||||
|
||||
export default function EmptyTable() {
|
||||
|
||||
@@ -19,7 +19,7 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
|
||||
import EmptyState from "@src/components/EmptyState";
|
||||
import BuildLogList from "./BuildLogList";
|
||||
import CloudLogSubheader from "@src/components/TableToolbar/CloudLogs/CloudLogSubheader";
|
||||
import CloudLogSubheader from "@src/components/TableModals/CloudLogsModal/CloudLogSubheader";
|
||||
|
||||
import { DATE_TIME_FORMAT } from "@src/constants/dates";
|
||||
import useBuildLogs from "./useBuildLogs";
|
||||
@@ -1,6 +1,7 @@
|
||||
import useSWR from "swr";
|
||||
import { useAtom } from "jotai";
|
||||
import { startCase } from "lodash-es";
|
||||
import { ITableModalProps } from "@src/components/TableModals";
|
||||
|
||||
import {
|
||||
LinearProgress,
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import { CloudLogs as LogsIcon } from "@src/assets/icons";
|
||||
|
||||
import Modal, { IModalProps } from "@src/components/Modal";
|
||||
import Modal from "@src/components/Modal";
|
||||
import TableToolbarButton from "@src/components/TableToolbar/TableToolbarButton";
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
import TimeRangeSelect from "./TimeRangeSelect";
|
||||
@@ -38,7 +39,7 @@ import {
|
||||
} from "@src/atoms/tableScope";
|
||||
import { cloudLogFetcher } from "./utils";
|
||||
|
||||
export default function CloudLogsModal(props: IModalProps) {
|
||||
export default function CloudLogsModal({ onClose }: ITableModalProps) {
|
||||
const [projectId] = useAtom(projectIdAtom, globalScope);
|
||||
const [rowyRun] = useAtom(rowyRunAtom, globalScope);
|
||||
const [compatibleRowyRunVersion] = useAtom(
|
||||
@@ -74,7 +75,8 @@ export default function CloudLogsModal(props: IModalProps) {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
title="Cloud logs"
|
||||
onClose={onClose}
|
||||
maxWidth="xl"
|
||||
fullWidth
|
||||
fullHeight
|
||||
2
src/components/TableModals/CloudLogsModal/index.ts
Normal file
2
src/components/TableModals/CloudLogsModal/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./CloudLogsModal";
|
||||
export { default } from "./CloudLogsModal";
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { RESET } from "jotai/utils";
|
||||
import { isEqual } from "lodash-es";
|
||||
import { ITableModalProps } from "@src/components/TableModals";
|
||||
|
||||
import TableToolbarButton from "@src/components/TableToolbar/TableToolbarButton";
|
||||
import { Extension as ExtensionIcon } from "@src/assets/icons";
|
||||
import Modal from "@src/components/Modal";
|
||||
import AddExtensionButton from "./AddExtensionButton";
|
||||
import ExtensionList from "./ExtensionList";
|
||||
@@ -14,9 +12,7 @@ import ExtensionMigration from "./ExtensionMigration";
|
||||
import {
|
||||
globalScope,
|
||||
currentUserAtom,
|
||||
projectSettingsAtom,
|
||||
rowyRunAtom,
|
||||
rowyRunModalAtom,
|
||||
confirmDialogAtom,
|
||||
} from "@src/atoms/globalScope";
|
||||
import {
|
||||
@@ -24,7 +20,6 @@ import {
|
||||
tableSettingsAtom,
|
||||
tableSchemaAtom,
|
||||
updateTableSchemaAtom,
|
||||
tableModalAtom,
|
||||
} from "@src/atoms/tableScope";
|
||||
import { useSnackLogContext } from "@src/contexts/SnackLogContext";
|
||||
|
||||
@@ -33,16 +28,13 @@ import { runRoutes } from "@src/constants/runRoutes";
|
||||
import { analytics, logEvent } from "@src/analytics";
|
||||
import { getTableSchemaPath } from "@src/utils/table";
|
||||
|
||||
export default function Extensions() {
|
||||
export default function ExtensionsModal({ onClose }: ITableModalProps) {
|
||||
const [currentUser] = useAtom(currentUserAtom, globalScope);
|
||||
const [projectSettings] = useAtom(projectSettingsAtom, globalScope);
|
||||
const [rowyRun] = useAtom(rowyRunAtom, globalScope);
|
||||
const openRowyRunModal = useSetAtom(rowyRunModalAtom, globalScope);
|
||||
const confirm = useSetAtom(confirmDialogAtom, globalScope);
|
||||
const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
|
||||
const [tableSchema] = useAtom(tableSchemaAtom, tableScope);
|
||||
const [updateTableSchema] = useAtom(updateTableSchemaAtom, tableScope);
|
||||
const [modal, setModal] = useAtom(tableModalAtom, tableScope);
|
||||
|
||||
const currentExtensionObjects = (tableSchema.extensionObjects ??
|
||||
[]) as IExtension[];
|
||||
@@ -50,10 +42,11 @@ export default function Extensions() {
|
||||
currentExtensionObjects
|
||||
);
|
||||
|
||||
const open = modal === "extensions";
|
||||
const setOpen = (open: boolean) => setModal(open ? "extensions" : RESET);
|
||||
|
||||
const [openMigrationGuide, setOpenMigrationGuide] = useState(false);
|
||||
useEffect(() => {
|
||||
if (tableSchema.sparks) setOpenMigrationGuide(true);
|
||||
}, [tableSchema.sparks]);
|
||||
|
||||
const [extensionModal, setExtensionModal] = useState<{
|
||||
mode: "add" | "update";
|
||||
extensionObject: IExtension;
|
||||
@@ -63,25 +56,6 @@ export default function Extensions() {
|
||||
const snackLogContext = useSnackLogContext();
|
||||
const edited = !isEqual(currentExtensionObjects, localExtensionsObjects);
|
||||
|
||||
if (!projectSettings.rowyRunUrl)
|
||||
return (
|
||||
<TableToolbarButton
|
||||
title="Extensions"
|
||||
onClick={() => openRowyRunModal({ feature: "Extensions" })}
|
||||
icon={<ExtensionIcon />}
|
||||
/>
|
||||
);
|
||||
|
||||
const handleOpen = () => {
|
||||
if (tableSchema.sparks) {
|
||||
// migration is required
|
||||
console.log("Extension migration required.");
|
||||
setOpenMigrationGuide(true);
|
||||
} else {
|
||||
setOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = (
|
||||
_setOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||
) => {
|
||||
@@ -90,14 +64,15 @@ export default function Extensions() {
|
||||
confirm({
|
||||
title: "Discard changes?",
|
||||
confirm: "Discard",
|
||||
cancel: "Keep",
|
||||
handleConfirm: () => {
|
||||
_setOpen(false);
|
||||
setLocalExtensionsObjects(currentExtensionObjects);
|
||||
setOpen(false);
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setOpen(false);
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,7 +80,7 @@ export default function Extensions() {
|
||||
if (updateTableSchema)
|
||||
await updateTableSchema({ extensionObjects: localExtensionsObjects });
|
||||
if (callback) callback();
|
||||
setOpen(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleSaveDeploy = async () => {
|
||||
@@ -216,55 +191,49 @@ export default function Extensions() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableToolbarButton
|
||||
title="Extensions"
|
||||
onClick={handleOpen}
|
||||
icon={<ExtensionIcon />}
|
||||
<Modal
|
||||
onClose={handleClose}
|
||||
disableBackdropClick={edited}
|
||||
disableEscapeKeyDown={edited}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
title={`Extensions (${activeExtensionCount}\u2009/\u2009${localExtensionsObjects.length})`}
|
||||
header={
|
||||
<AddExtensionButton
|
||||
handleAddExtension={(type: ExtensionType) => {
|
||||
setExtensionModal({
|
||||
mode: "add",
|
||||
extensionObject: emptyExtensionObject(type, currentEditor()),
|
||||
});
|
||||
}}
|
||||
variant={
|
||||
localExtensionsObjects.length === 0 ? "contained" : "outlined"
|
||||
}
|
||||
/>
|
||||
}
|
||||
children={
|
||||
<ExtensionList
|
||||
extensions={localExtensionsObjects}
|
||||
handleUpdateActive={handleUpdateActive}
|
||||
handleEdit={handleEdit}
|
||||
handleDuplicate={handleDuplicate}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
}
|
||||
actions={{
|
||||
primary: {
|
||||
children: "Save & Deploy",
|
||||
onClick: handleSaveDeploy,
|
||||
disabled: !edited,
|
||||
},
|
||||
secondary: {
|
||||
children: "Save",
|
||||
onClick: () => handleSaveExtensions(),
|
||||
disabled: !edited,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{open && (
|
||||
<Modal
|
||||
onClose={handleClose}
|
||||
disableBackdropClick={edited}
|
||||
disableEscapeKeyDown={edited}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
title={`Extensions (${activeExtensionCount}\u2009/\u2009${localExtensionsObjects.length})`}
|
||||
header={
|
||||
<AddExtensionButton
|
||||
handleAddExtension={(type: ExtensionType) => {
|
||||
setExtensionModal({
|
||||
mode: "add",
|
||||
extensionObject: emptyExtensionObject(type, currentEditor()),
|
||||
});
|
||||
}}
|
||||
variant={
|
||||
localExtensionsObjects.length === 0 ? "contained" : "outlined"
|
||||
}
|
||||
/>
|
||||
}
|
||||
children={
|
||||
<ExtensionList
|
||||
extensions={localExtensionsObjects}
|
||||
handleUpdateActive={handleUpdateActive}
|
||||
handleEdit={handleEdit}
|
||||
handleDuplicate={handleDuplicate}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
}
|
||||
actions={{
|
||||
primary: {
|
||||
children: "Save & Deploy",
|
||||
onClick: handleSaveDeploy,
|
||||
disabled: !edited,
|
||||
},
|
||||
secondary: {
|
||||
children: "Save",
|
||||
onClick: () => handleSaveExtensions(),
|
||||
disabled: !edited,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{extensionModal && (
|
||||
<ExtensionModal
|
||||
handleClose={() => setExtensionModal(null)}
|
||||
@@ -279,7 +248,6 @@ export default function Extensions() {
|
||||
handleClose={() => setOpenMigrationGuide(false)}
|
||||
handleUpgradeComplete={() => {
|
||||
setOpenMigrationGuide(false);
|
||||
setOpen(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
Checkbox,
|
||||
} from "@mui/material";
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
import ColumnSelect from "@src/components/TableToolbar/ColumnSelect";
|
||||
import ColumnSelect from "@src/components/Table/ColumnSelect";
|
||||
|
||||
import {
|
||||
globalScope,
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IExtensionModalStepProps } from "./ExtensionModal";
|
||||
|
||||
import { Typography } from "@mui/material";
|
||||
import ColumnSelect from "@src/components/TableToolbar/ColumnSelect";
|
||||
import ColumnSelect from "@src/components/Table/ColumnSelect";
|
||||
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
|
||||
2
src/components/TableModals/ExtensionsModal/index.ts
Normal file
2
src/components/TableModals/ExtensionsModal/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./ExtensionsModal";
|
||||
export { default } from "./ExtensionsModal";
|
||||
@@ -3,6 +3,7 @@ import useMemoValue from "use-memo-value";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { mergeWith, find, isEqual } from "lodash-es";
|
||||
import { ITableModalProps } from "@src/components/TableModals";
|
||||
|
||||
import {
|
||||
useTheme,
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
AlertTitle,
|
||||
} from "@mui/material";
|
||||
|
||||
import WizardDialog from "@src/components/TableWizards/WizardDialog";
|
||||
import WizardDialog from "@src/components/TableModals/WizardDialog";
|
||||
import Step1Columns from "./Step1Columns";
|
||||
import Step2NewColumns from "./Step2NewColumns";
|
||||
import Step3Preview from "./Step3Preview";
|
||||
@@ -23,11 +24,12 @@ import {
|
||||
tableSchemaAtom,
|
||||
addColumnAtom,
|
||||
addRowAtom,
|
||||
importCsvAtom,
|
||||
ImportCsvData,
|
||||
} from "@src/atoms/tableScope";
|
||||
import { ColumnConfig } from "@src/types/table";
|
||||
import { getFieldProp } from "@src/components/fields";
|
||||
import { analytics, logEvent } from "@src/analytics";
|
||||
import { ImportType } from "@src/components/TableToolbar/ImportCsv";
|
||||
|
||||
export type CsvConfig = {
|
||||
pairs: { csvKey: string; columnKey: string }[];
|
||||
@@ -35,35 +37,22 @@ export type CsvConfig = {
|
||||
};
|
||||
|
||||
export interface IStepProps {
|
||||
csvData: NonNullable<IImportCsvWizardProps["csvData"]>;
|
||||
csvData: NonNullable<ImportCsvData>;
|
||||
config: CsvConfig;
|
||||
setConfig: React.Dispatch<React.SetStateAction<CsvConfig>>;
|
||||
updateConfig: (value: Partial<CsvConfig>) => void;
|
||||
isXs: boolean;
|
||||
}
|
||||
|
||||
export interface IImportCsvWizardProps {
|
||||
importType: ImportType;
|
||||
handleClose: () => void;
|
||||
csvData: {
|
||||
columns: string[];
|
||||
rows: Record<string, any>[];
|
||||
} | null;
|
||||
}
|
||||
|
||||
export default function ImportCsvWizard({
|
||||
importType,
|
||||
handleClose,
|
||||
csvData,
|
||||
}: IImportCsvWizardProps) {
|
||||
export default function ImportCsvWizard({ onClose }: ITableModalProps) {
|
||||
const [tableSchema] = useAtom(tableSchemaAtom, tableScope);
|
||||
const addColumn = useSetAtom(addColumnAtom, tableScope);
|
||||
// const addRow = useSetAtom(addRowAtom, tableScope);
|
||||
const [{ importType, csvData }] = useAtom(importCsvAtom, tableScope);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const theme = useTheme();
|
||||
const isXs = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
const [open, setOpen] = useState(true);
|
||||
const columns = useMemoValue(tableSchema.columns ?? {}, isEqual);
|
||||
|
||||
const [config, setConfig] = useState<CsvConfig>({
|
||||
@@ -109,19 +98,15 @@ export default function ImportCsvWizard({
|
||||
logEvent(analytics, "import_success", { type: importType });
|
||||
|
||||
// Close wizard
|
||||
setOpen(false);
|
||||
setTimeout(handleClose, 300);
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (!csvData) return null;
|
||||
|
||||
return (
|
||||
<WizardDialog
|
||||
open={open}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
setTimeout(handleClose, 300);
|
||||
}}
|
||||
open
|
||||
onClose={onClose}
|
||||
title="Import CSV or TSV"
|
||||
steps={
|
||||
[
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import ArrowIcon from "@mui/icons-material/ArrowForward";
|
||||
|
||||
import { IStepProps } from ".";
|
||||
import FadeList from "@src/components/TableWizards/ScrollableList";
|
||||
import FadeList from "@src/components/TableModals/ScrollableList";
|
||||
import Column from "@src/components/Table/Column";
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
tableColumnsOrderedAtom,
|
||||
} from "@src/atoms/tableScope";
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
import { suggestType } from "@src/components/TableWizards/ImportWizard/utils";
|
||||
import { suggestType } from "@src/components/TableModals/ImportWizard/utils";
|
||||
|
||||
export default function Step1Columns({
|
||||
csvData,
|
||||
@@ -6,13 +6,13 @@ import { Grid, Typography, Divider, ButtonBase } from "@mui/material";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
|
||||
import { IStepProps } from ".";
|
||||
import ScrollableList from "@src/components/TableWizards/ScrollableList";
|
||||
import ScrollableList from "@src/components/TableModals/ScrollableList";
|
||||
import Column from "@src/components/Table/Column";
|
||||
import Cell from "@src/components/TableWizards/Cell";
|
||||
import Cell from "@src/components/Table/Cell";
|
||||
import FieldsDropdown from "@src/components/ColumnModals/FieldsDropdown";
|
||||
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
import { SELECTABLE_TYPES } from "@src/components/TableWizards/ImportWizard/utils";
|
||||
import { SELECTABLE_TYPES } from "@src/components/TableModals/ImportWizard/utils";
|
||||
|
||||
export default function Step2NewColumns({
|
||||
csvData,
|
||||
@@ -4,7 +4,7 @@ import { parseJSON } from "date-fns";
|
||||
|
||||
import { styled, Grid } from "@mui/material";
|
||||
import Column from "@src/components/Table/Column";
|
||||
import Cell from "@src/components/TableWizards/Cell";
|
||||
import Cell from "@src/components/Table/Cell";
|
||||
|
||||
import { IStepProps } from ".";
|
||||
import { tableScope, tableSchemaAtom } from "@src/atoms/tableScope";
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { merge } from "lodash-es";
|
||||
import { ITableModalProps } from "@src/components/TableModals";
|
||||
|
||||
import { useTheme, useMediaQuery, Typography } from "@mui/material";
|
||||
|
||||
import WizardDialog from "@src/components/TableWizards/WizardDialog";
|
||||
import WizardDialog from "@src/components/TableModals/WizardDialog";
|
||||
import Step1Columns from "./Step1Columns";
|
||||
import Step2Rename from "./Step2Rename";
|
||||
import Step3Types from "./Step3Types";
|
||||
@@ -32,7 +33,7 @@ export interface IStepProps {
|
||||
isXs: boolean;
|
||||
}
|
||||
|
||||
export default function ImportWizard() {
|
||||
export default function ImportWizard({ onClose }: ITableModalProps) {
|
||||
const setTableFilters = useSetAtom(tableFiltersAtom, tableScope);
|
||||
const setTableOrders = useSetAtom(tableOrdersAtom, tableScope);
|
||||
const [tableRows] = useAtom(tableRowsAtom, tableScope);
|
||||
@@ -40,9 +41,6 @@ export default function ImportWizard() {
|
||||
const theme = useTheme();
|
||||
const isXs = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
// if (importWizardRef) importWizardRef.current = { open, setOpen };
|
||||
|
||||
const [config, setConfig] = useState<TableColumnsConfig>({});
|
||||
const updateConfig: IStepProps["updateConfig"] = useCallback((value) => {
|
||||
setConfig((prev) => ({ ...merge(prev, value) }));
|
||||
@@ -50,23 +48,22 @@ export default function ImportWizard() {
|
||||
|
||||
// Reset table filters and orders on open
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setTableFilters([]);
|
||||
setTableOrders([]);
|
||||
}, [open, setTableFilters, setTableOrders]);
|
||||
}, [setTableFilters, setTableOrders]);
|
||||
|
||||
if (tableRows.length === 0) return null;
|
||||
|
||||
const handleFinish = () => {
|
||||
// FIXME: Investigate if this overwrites
|
||||
// tableActions?.table.updateConfig("columns", config);
|
||||
setOpen(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<WizardDialog
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
open
|
||||
onClose={onClose}
|
||||
title="Import"
|
||||
steps={[
|
||||
{
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import DragHandleIcon from "@mui/icons-material/DragHandle";
|
||||
import { AddColumn as AddColumnIcon } from "@src/assets/icons";
|
||||
|
||||
import ScrollableList from "@src/components/TableWizards/ScrollableList";
|
||||
import ScrollableList from "@src/components/TableModals/ScrollableList";
|
||||
import Column from "@src/components/Table/Column";
|
||||
import EmptyState from "@src/components/EmptyState";
|
||||
|
||||
@@ -13,7 +13,7 @@ import EditIcon from "@mui/icons-material/Edit";
|
||||
import DoneIcon from "@mui/icons-material/Done";
|
||||
|
||||
import { IStepProps } from ".";
|
||||
import ScrollableList from "@src/components/TableWizards/ScrollableList";
|
||||
import ScrollableList from "@src/components/TableModals/ScrollableList";
|
||||
import Column from "@src/components/Table/Column";
|
||||
|
||||
export default function Step2Rename({
|
||||
@@ -5,9 +5,9 @@ import { Grid, Typography, Divider, ButtonBase } from "@mui/material";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
|
||||
import { IStepProps } from ".";
|
||||
import ScrollableList from "@src/components/TableWizards/ScrollableList";
|
||||
import ScrollableList from "@src/components/TableModals/ScrollableList";
|
||||
import Column from "@src/components/Table/Column";
|
||||
import Cell from "@src/components/TableWizards/Cell";
|
||||
import Cell from "@src/components/Table/Cell";
|
||||
import FieldsDropdown from "@src/components/ColumnModals/FieldsDropdown";
|
||||
|
||||
import { tableScope, tableRowsAtom } from "@src/atoms/tableScope";
|
||||
@@ -3,7 +3,7 @@ import { IStepProps } from ".";
|
||||
|
||||
import { styled, Grid } from "@mui/material";
|
||||
import Column from "@src/components/Table/Column";
|
||||
import Cell from "@src/components/TableWizards/Cell";
|
||||
import Cell from "@src/components/Table/Cell";
|
||||
|
||||
import { tableScope, tableRowsAtom } from "@src/atoms/tableScope";
|
||||
|
||||
35
src/components/TableModals/TableModals.tsx
Normal file
35
src/components/TableModals/TableModals.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { lazy } from "react";
|
||||
import { useAtom } from "jotai";
|
||||
import { RESET } from "jotai/utils";
|
||||
import { tableScope, tableModalAtom } from "@src/atoms/tableScope";
|
||||
|
||||
// prettier-ignore
|
||||
const CloudLogsModal = lazy(() => import("./CloudLogsModal" /* webpackChunkName: "TableModals-CloudLogsModal" */));
|
||||
// prettier-ignore
|
||||
const ExtensionsModal = lazy(() => import("./ExtensionsModal" /* webpackChunkName: "TableModals-ExtensionsModal" */));
|
||||
// prettier-ignore
|
||||
const WebhooksModal = lazy(() => import("./WebhooksModal" /* webpackChunkName: "TableModals-WebhooksModal" */));
|
||||
// prettier-ignore
|
||||
const ImportWizard = lazy(() => import("./ImportWizard" /* webpackChunkName: "TableModals-ImportWizard" */));
|
||||
// prettier-ignore
|
||||
const ImportCsvWizard = lazy(() => import("./ImportCsvWizard" /* webpackChunkName: "TableModals-ImportCsvWizard" */));
|
||||
|
||||
export interface ITableModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function TableModals() {
|
||||
const [tableModal, setTableModal] = useAtom(tableModalAtom, tableScope);
|
||||
|
||||
if (!tableModal) return null;
|
||||
|
||||
const onClose = () => setTableModal(RESET);
|
||||
|
||||
if (tableModal === "cloudLogs") return <CloudLogsModal onClose={onClose} />;
|
||||
if (tableModal === "extensions") return <ExtensionsModal onClose={onClose} />;
|
||||
if (tableModal === "webhooks") return <WebhooksModal onClose={onClose} />;
|
||||
if (tableModal === "import") return <ImportWizard onClose={onClose} />;
|
||||
if (tableModal === "importCsv") return <ImportCsvWizard onClose={onClose} />;
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Typography } from "@mui/material";
|
||||
import WarningIcon from "@mui/icons-material/WarningAmber";
|
||||
import { TableSettings } from "@src/types/table";
|
||||
import { IWebhook } from "@src/components/TableToolbar/Webhooks/utils";
|
||||
import { IWebhook } from "@src/components/TableModals/WebhooksModal/utils";
|
||||
|
||||
export const webhookTypes = [
|
||||
"basic",
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Typography, Link, TextField } from "@mui/material";
|
||||
import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
|
||||
import { TableSettings } from "@src/types/table";
|
||||
import { IWebhook } from "@src/components/TableToolbar/Webhooks/utils";
|
||||
import { IWebhook } from "@src/components/TableModals/WebhooksModal/utils";
|
||||
|
||||
export const webhookSendgrid = {
|
||||
name: "SendGrid",
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Typography, Link, TextField } from "@mui/material";
|
||||
import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
|
||||
import { TableSettings } from "@src/types/table";
|
||||
import { IWebhook } from "@src/components/TableToolbar/Webhooks/utils";
|
||||
import { IWebhook } from "@src/components/TableModals/WebhooksModal/utils";
|
||||
|
||||
export const webhookTypeform = {
|
||||
name: "Typeform",
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Typography, Link, TextField } from "@mui/material";
|
||||
import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon";
|
||||
import { TableSettings } from "@src/types/table";
|
||||
import { IWebhook } from "@src/components/TableToolbar/Webhooks/utils";
|
||||
import { IWebhook } from "@src/components/TableModals/WebhooksModal/utils";
|
||||
|
||||
export const webhook = {
|
||||
name: "Web Form",
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { RESET } from "jotai/utils";
|
||||
import { isEqual } from "lodash-es";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { ITableModalProps } from "@src/components/TableModals";
|
||||
|
||||
import TableToolbarButton from "@src/components/TableToolbar/TableToolbarButton";
|
||||
import WebhookIcon from "@mui/icons-material/Webhook";
|
||||
@@ -31,7 +31,7 @@ import { runRoutes } from "@src/constants/runRoutes";
|
||||
import { analytics, logEvent } from "@src/analytics";
|
||||
import { getTableSchemaPath } from "@src/utils/table";
|
||||
|
||||
export default function Webhooks() {
|
||||
export default function WebhooksModal({ onClose }: ITableModalProps) {
|
||||
const [currentUser] = useAtom(currentUserAtom, globalScope);
|
||||
const [rowyRun] = useAtom(rowyRunAtom, globalScope);
|
||||
const [compatibleRowyRunVersion] = useAtom(
|
||||
@@ -43,16 +43,12 @@ export default function Webhooks() {
|
||||
const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
|
||||
const [tableSchema] = useAtom(tableSchemaAtom, tableScope);
|
||||
const [updateTableSchema] = useAtom(updateTableSchemaAtom, tableScope);
|
||||
const [modal, setModal] = useAtom(tableModalAtom, tableScope);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const currentWebhooks = (tableSchema.webhooks ?? []) as IWebhook[];
|
||||
const [localWebhooksObjects, setLocalWebhooksObjects] =
|
||||
useState(currentWebhooks);
|
||||
|
||||
const open = modal === "webhooks";
|
||||
const setOpen = (open: boolean) => setModal(open ? "webhooks" : RESET);
|
||||
|
||||
const [webhookModal, setWebhookModal] = useState<{
|
||||
mode: "add" | "update";
|
||||
webhookObject: IWebhook;
|
||||
@@ -72,8 +68,6 @@ export default function Webhooks() {
|
||||
|
||||
const edited = !isEqual(currentWebhooks, localWebhooksObjects);
|
||||
|
||||
const handleOpen = () => setOpen(true);
|
||||
|
||||
const handleClose = (
|
||||
_setOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||
) => {
|
||||
@@ -82,24 +76,24 @@ export default function Webhooks() {
|
||||
confirm({
|
||||
title: "Discard changes?",
|
||||
confirm: "Discard",
|
||||
cancel: "Keep",
|
||||
handleConfirm: () => {
|
||||
_setOpen(false);
|
||||
setLocalWebhooksObjects(currentWebhooks);
|
||||
setOpen(false);
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setOpen(false);
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveWebhooks = async (callback?: Function) => {
|
||||
if (updateTableSchema)
|
||||
if (updateTableSchema) {
|
||||
await updateTableSchema({ webhooks: localWebhooksObjects });
|
||||
}
|
||||
if (callback) callback();
|
||||
setOpen(false);
|
||||
// TODO: convert to async function that awaits for the document write to complete
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleSaveDeploy = () =>
|
||||
@@ -196,64 +190,56 @@ export default function Webhooks() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableToolbarButton
|
||||
title="Webhooks"
|
||||
onClick={handleOpen}
|
||||
icon={<WebhookIcon />}
|
||||
<Modal
|
||||
onClose={handleClose}
|
||||
disableBackdropClick={edited}
|
||||
disableEscapeKeyDown={edited}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
title={`Webhooks (${activeWebhookCount}\u2009/\u2009${localWebhooksObjects.length})`}
|
||||
header={
|
||||
<AddWebhookButton
|
||||
handleAddWebhook={(type: WebhookType) => {
|
||||
setWebhookModal({
|
||||
mode: "add",
|
||||
webhookObject: emptyWebhookObject(
|
||||
type,
|
||||
currentEditor(),
|
||||
tableSettings
|
||||
),
|
||||
});
|
||||
}}
|
||||
variant={
|
||||
localWebhooksObjects.length === 0 ? "contained" : "outlined"
|
||||
}
|
||||
/>
|
||||
}
|
||||
children={
|
||||
<WebhookList
|
||||
webhooks={localWebhooksObjects}
|
||||
handleUpdateActive={handleUpdateActive}
|
||||
handleEdit={handleEdit}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
}
|
||||
actions={{
|
||||
primary: {
|
||||
children: "Save & Deploy",
|
||||
onClick: () => {
|
||||
handleSaveDeploy();
|
||||
},
|
||||
disabled: !edited,
|
||||
},
|
||||
secondary: {
|
||||
children: "Save",
|
||||
onClick: () => {
|
||||
handleSaveWebhooks();
|
||||
},
|
||||
disabled: !edited,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{open && (
|
||||
<Modal
|
||||
onClose={handleClose}
|
||||
disableBackdropClick={edited}
|
||||
disableEscapeKeyDown={edited}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
title={`Webhooks (${activeWebhookCount}\u2009/\u2009${localWebhooksObjects.length})`}
|
||||
header={
|
||||
<AddWebhookButton
|
||||
handleAddWebhook={(type: WebhookType) => {
|
||||
setWebhookModal({
|
||||
mode: "add",
|
||||
webhookObject: emptyWebhookObject(
|
||||
type,
|
||||
currentEditor(),
|
||||
tableSettings
|
||||
),
|
||||
});
|
||||
}}
|
||||
variant={
|
||||
localWebhooksObjects.length === 0 ? "contained" : "outlined"
|
||||
}
|
||||
/>
|
||||
}
|
||||
children={
|
||||
<WebhookList
|
||||
webhooks={localWebhooksObjects}
|
||||
handleUpdateActive={handleUpdateActive}
|
||||
handleEdit={handleEdit}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
}
|
||||
actions={{
|
||||
primary: {
|
||||
children: "Save & Deploy",
|
||||
onClick: () => {
|
||||
handleSaveDeploy();
|
||||
},
|
||||
disabled: !edited,
|
||||
},
|
||||
secondary: {
|
||||
children: "Save",
|
||||
onClick: () => {
|
||||
handleSaveWebhooks();
|
||||
},
|
||||
disabled: !edited,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{webhookModal && (
|
||||
<WebhookModal
|
||||
handleClose={() => setWebhookModal(null)}
|
||||
2
src/components/TableModals/WebhooksModal/index.ts
Normal file
2
src/components/TableModals/WebhooksModal/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./WebhooksModal";
|
||||
export { default } from "./WebhooksModal";
|
||||
2
src/components/TableModals/index.ts
Normal file
2
src/components/TableModals/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./TableModals";
|
||||
export { default } from "./TableModals";
|
||||
@@ -1,40 +0,0 @@
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { RESET } from "jotai/utils";
|
||||
|
||||
import TableToolbarButton from "@src/components/TableToolbar/TableToolbarButton";
|
||||
import { CloudLogs as LogsIcon } from "@src/assets/icons";
|
||||
import CloudLogsModal from "./CloudLogsModal";
|
||||
|
||||
import {
|
||||
globalScope,
|
||||
projectSettingsAtom,
|
||||
rowyRunModalAtom,
|
||||
} from "@src/atoms/globalScope";
|
||||
import { tableScope, tableModalAtom } from "@src/atoms/tableScope";
|
||||
|
||||
export default function CloudLogs() {
|
||||
const [projectSettings] = useAtom(projectSettingsAtom, globalScope);
|
||||
const openRowyRunModal = useSetAtom(rowyRunModalAtom, globalScope);
|
||||
const [modal, setModal] = useAtom(tableModalAtom, tableScope);
|
||||
|
||||
const open = modal === "cloudLogs";
|
||||
const setOpen = (open: boolean) => setModal(open ? "cloudLogs" : RESET);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableToolbarButton
|
||||
title="Cloud logs"
|
||||
icon={<LogsIcon />}
|
||||
onClick={
|
||||
projectSettings.rowyRunUrl
|
||||
? () => setOpen(true)
|
||||
: () => openRowyRunModal({ feature: "Cloud logs" })
|
||||
}
|
||||
/>
|
||||
|
||||
{open && (
|
||||
<CloudLogsModal onClose={() => setOpen(false)} title="Cloud logs" />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./CloudLogs";
|
||||
export { default } from "./CloudLogs";
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
} from "@mui/material";
|
||||
import ColumnSelect from "@src/components/TableToolbar/ColumnSelect";
|
||||
import ColumnSelect from "@src/components/Table/ColumnSelect";
|
||||
|
||||
import {
|
||||
tableScope,
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
Radio,
|
||||
FormHelperText,
|
||||
} from "@mui/material";
|
||||
import ColumnSelect from "@src/components/TableToolbar/ColumnSelect";
|
||||
import ColumnSelect from "@src/components/Table/ColumnSelect";
|
||||
|
||||
import {
|
||||
tableScope,
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./Extensions";
|
||||
export { default } from "./Extensions";
|
||||
@@ -4,7 +4,7 @@ import { useForm } from "react-hook-form";
|
||||
import { Grid, MenuItem, TextField, InputLabel } from "@mui/material";
|
||||
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
import ColumnSelect from "@src/components/TableToolbar/ColumnSelect";
|
||||
import ColumnSelect from "@src/components/Table/ColumnSelect";
|
||||
import FormAutosave from "@src/components/ColumnModals/ColumnConfigModal/FormAutosave";
|
||||
import FieldSkeleton from "@src/components/SideDrawer/FieldSkeleton";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import VisibilityIcon from "@mui/icons-material/VisibilityOutlined";
|
||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOffOutlined";
|
||||
import IconSlash from "@src/components/IconSlash";
|
||||
|
||||
import ColumnSelect, { ColumnItem } from "./ColumnSelect";
|
||||
import ColumnSelect, { ColumnItem } from "@src/components/Table/ColumnSelect";
|
||||
import ButtonWithStatus from "@src/components/ButtonWithStatus";
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useCallback, useRef } from "react";
|
||||
import { useAtom } from "jotai";
|
||||
import { useState, useCallback, useRef } from "react";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { parse } from "csv-parse/browser/esm";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
@@ -26,17 +26,13 @@ import { Upload as FileUploadIcon } from "@src/assets/icons";
|
||||
import CheckIcon from "@mui/icons-material/CheckCircle";
|
||||
|
||||
import { globalScope, userRolesAtom } from "@src/atoms/globalScope";
|
||||
import { tableScope, tableSettingsAtom } from "@src/atoms/tableScope";
|
||||
import {
|
||||
tableScope,
|
||||
tableSettingsAtom,
|
||||
tableModalAtom,
|
||||
importCsvAtom,
|
||||
} from "@src/atoms/tableScope";
|
||||
import { analytics, logEvent } from "@src/analytics";
|
||||
// FIXME:
|
||||
// import ImportCsvWizard, {
|
||||
// IImportCsvWizardProps,
|
||||
// } from "@src/components/TableWizards/ImportCsvWizard";
|
||||
|
||||
export enum ImportType {
|
||||
csv = "csv",
|
||||
tsv = "tsv",
|
||||
}
|
||||
|
||||
export enum ImportMethod {
|
||||
paste = "paste",
|
||||
@@ -45,23 +41,24 @@ export enum ImportMethod {
|
||||
}
|
||||
|
||||
export interface IImportCsvProps {
|
||||
render?: (
|
||||
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void
|
||||
) => React.ReactNode;
|
||||
PopoverProps?: Partial<MuiPopoverProps>;
|
||||
}
|
||||
|
||||
export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
export default function ImportCsv({ PopoverProps }: IImportCsvProps) {
|
||||
const [userRoles] = useAtom(userRolesAtom, globalScope);
|
||||
const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
|
||||
const [{ importType, csvData }, setImportCsv] = useAtom(
|
||||
importCsvAtom,
|
||||
tableScope
|
||||
);
|
||||
const openTableModal = useSetAtom(tableModalAtom, tableScope);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const importTypeRef = useRef(ImportType.csv);
|
||||
const importTypeRef = useRef(importType);
|
||||
const importMethodRef = useRef(ImportMethod.upload);
|
||||
const [open, setOpen] = useState<HTMLButtonElement | null>(null);
|
||||
const [tab, setTab] = useState("upload");
|
||||
const [csvData, setCsvData] =
|
||||
useState</* IImportCsvWizardProps["csvData"] */ any>(null);
|
||||
|
||||
const [error, setError] = useState("");
|
||||
const validCsv =
|
||||
csvData !== null && csvData?.columns.length > 0 && csvData?.rows.length > 0;
|
||||
@@ -70,32 +67,38 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
setOpen(event.currentTarget);
|
||||
const handleClose = () => {
|
||||
setOpen(null);
|
||||
setCsvData(null);
|
||||
setImportCsv({ importType: "csv", csvData: null });
|
||||
setTab("upload");
|
||||
setError("");
|
||||
};
|
||||
const popoverId = open ? "csv-popover" : undefined;
|
||||
|
||||
const parseCsv = (csvString: string) =>
|
||||
parse(csvString, { delimiter: [",", "\t"] }, (err, rows) => {
|
||||
if (err) {
|
||||
setError(err.message);
|
||||
} else {
|
||||
const columns = rows.shift() ?? [];
|
||||
if (columns.length === 0) {
|
||||
setError("No columns detected");
|
||||
const parseCsv = useCallback(
|
||||
(csvString: string) =>
|
||||
parse(csvString, { delimiter: [",", "\t"] }, (err, rows) => {
|
||||
if (err) {
|
||||
setError(err.message);
|
||||
} else {
|
||||
const mappedRows = rows.map((row: any) =>
|
||||
row.reduce(
|
||||
(a: any, c: any, i: number) => ({ ...a, [columns[i]]: c }),
|
||||
{}
|
||||
)
|
||||
);
|
||||
setCsvData({ columns, rows: mappedRows });
|
||||
setError("");
|
||||
const columns = rows.shift() ?? [];
|
||||
if (columns.length === 0) {
|
||||
setError("No columns detected");
|
||||
} else {
|
||||
const mappedRows = rows.map((row: any) =>
|
||||
row.reduce(
|
||||
(a: any, c: any, i: number) => ({ ...a, [columns[i]]: c }),
|
||||
{}
|
||||
)
|
||||
);
|
||||
setImportCsv({
|
||||
importType: importTypeRef.current,
|
||||
csvData: { columns, rows: mappedRows },
|
||||
});
|
||||
setError("");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}),
|
||||
[setImportCsv]
|
||||
);
|
||||
|
||||
const onDrop = useCallback(
|
||||
async (acceptedFiles: File[]) => {
|
||||
@@ -105,9 +108,7 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
reader.onload = (event: any) => parseCsv(event.target.result);
|
||||
reader.readAsText(file);
|
||||
importTypeRef.current =
|
||||
file.type === "text/tab-separated-values"
|
||||
? ImportType.tsv
|
||||
: ImportType.csv;
|
||||
file.type === "text/tab-separated-values" ? "tsv" : "csv";
|
||||
} catch (error) {
|
||||
enqueueSnackbar(`Please import a .tsv or .csv file`, {
|
||||
variant: "error",
|
||||
@@ -118,7 +119,7 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
});
|
||||
}
|
||||
},
|
||||
[enqueueSnackbar]
|
||||
[enqueueSnackbar, parseCsv]
|
||||
);
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
@@ -141,8 +142,8 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
getFirstLine?.replace(strInQuotes, tabsWithSpace) ?? "";
|
||||
const tabPattern = /\t/;
|
||||
return tabPattern.test(formatString)
|
||||
? (importTypeRef.current = ImportType.tsv)
|
||||
: (importTypeRef.current = ImportType.csv);
|
||||
? (importTypeRef.current = "tsv")
|
||||
: (importTypeRef.current = "csv");
|
||||
}
|
||||
const handlePaste = useDebouncedCallback((value: string) => {
|
||||
parseCsv(value);
|
||||
@@ -166,21 +167,15 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const [openWizard, setOpenWizard] = useState(false);
|
||||
|
||||
if (tableSettings.readOnly && !userRoles.includes("ADMIN")) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{render ? (
|
||||
render(handleOpen)
|
||||
) : (
|
||||
<TableToolbarButton
|
||||
title="Import CSV or TSV"
|
||||
onClick={handleOpen}
|
||||
icon={<ImportIcon />}
|
||||
/>
|
||||
)}
|
||||
<TableToolbarButton
|
||||
title="Import CSV or TSV"
|
||||
onClick={handleOpen}
|
||||
icon={<ImportIcon />}
|
||||
/>
|
||||
|
||||
<Popover
|
||||
id={popoverId}
|
||||
@@ -204,7 +199,10 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
<TabList
|
||||
onChange={(_, v) => {
|
||||
setTab(v);
|
||||
setCsvData(null);
|
||||
setImportCsv({
|
||||
importType: importTypeRef.current,
|
||||
csvData: null,
|
||||
});
|
||||
setError("");
|
||||
}}
|
||||
aria-label="Import CSV method tabs"
|
||||
@@ -299,7 +297,11 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
label="Paste CSV or TSV text"
|
||||
placeholder="column, column, …"
|
||||
onChange={(e) => {
|
||||
if (csvData !== null) setCsvData(null);
|
||||
if (csvData !== null)
|
||||
setImportCsv({
|
||||
importType: importTypeRef.current,
|
||||
csvData: null,
|
||||
});
|
||||
handlePaste(e.target.value);
|
||||
}}
|
||||
sx={{
|
||||
@@ -325,7 +327,11 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
label="Paste URL to CSV or TSV file"
|
||||
placeholder="https://"
|
||||
onChange={(e) => {
|
||||
if (csvData !== null) setCsvData(null);
|
||||
if (csvData !== null)
|
||||
setImportCsv({
|
||||
importType: importTypeRef.current,
|
||||
csvData: null,
|
||||
});
|
||||
handleUrl(e.target.value);
|
||||
}}
|
||||
helperText={loading ? "Fetching…" : error}
|
||||
@@ -346,7 +352,7 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
minWidth: 100,
|
||||
}}
|
||||
onClick={() => {
|
||||
setOpenWizard(true);
|
||||
openTableModal("importCsv");
|
||||
logEvent(analytics, `import_${importMethodRef.current}`, {
|
||||
type: importTypeRef.current,
|
||||
});
|
||||
@@ -355,14 +361,6 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
Continue
|
||||
</Button>
|
||||
</Popover>
|
||||
|
||||
{/* {openWizard && csvData && (
|
||||
<ImportCsvWizard
|
||||
importType={importTypeRef.current}
|
||||
handleClose={() => setOpenWizard(false)}
|
||||
csvData={csvData}
|
||||
/>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { lazy, Suspense } from "react";
|
||||
import { useAtom } from "jotai";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
|
||||
import { Stack } from "@mui/material";
|
||||
import WebhookIcon from "@mui/icons-material/Webhook";
|
||||
import {
|
||||
Extension as ExtensionIcon,
|
||||
CloudLogs as CloudLogsIcon,
|
||||
} from "@src/assets/icons";
|
||||
import TableToolbarButton from "./TableToolbarButton";
|
||||
import { ButtonSkeleton } from "./TableToolbarSkeleton";
|
||||
|
||||
import AddRow from "./AddRow";
|
||||
@@ -9,16 +15,21 @@ import LoadedRowsStatus from "./LoadedRowsStatus";
|
||||
import TableSettings from "./TableSettings";
|
||||
import HiddenFields from "./HiddenFields";
|
||||
import RowHeight from "./RowHeight";
|
||||
import BuildLogsSnack from "./CloudLogs/BuildLogs/BuildLogsSnack";
|
||||
|
||||
import { globalScope, userRolesAtom } from "@src/atoms/globalScope";
|
||||
import {
|
||||
globalScope,
|
||||
projectSettingsAtom,
|
||||
userRolesAtom,
|
||||
compatibleRowyRunVersionAtom,
|
||||
rowyRunModalAtom,
|
||||
} from "@src/atoms/globalScope";
|
||||
import {
|
||||
tableScope,
|
||||
tableSettingsAtom,
|
||||
tableSchemaAtom,
|
||||
tableModalAtom,
|
||||
} from "@src/atoms/tableScope";
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
import { useSnackLogContext } from "@src/contexts/SnackLogContext";
|
||||
|
||||
// prettier-ignore
|
||||
const Filters = lazy(() => import("./Filters" /* webpackChunkName: "Filters" */));
|
||||
@@ -27,21 +38,21 @@ const Export = lazy(() => import("./Export" /* webpackChunkName: "Export" */));
|
||||
// prettier-ignore
|
||||
const ImportCsv = lazy(() => import("./ImportCsv" /* webpackChunkName: "ImportCsv" */));
|
||||
// prettier-ignore
|
||||
const CloudLogs = lazy(() => import("./CloudLogs" /* webpackChunkName: "CloudLogs" */));
|
||||
// prettier-ignore
|
||||
const Extensions = lazy(() => import("./Extensions" /* webpackChunkName: "Extensions" */));
|
||||
// prettier-ignore
|
||||
const Webhooks = lazy(() => import("./Webhooks" /* webpackChunkName: "Webhooks" */));
|
||||
// prettier-ignore
|
||||
const ReExecute = lazy(() => import("./ReExecute" /* webpackChunkName: "ReExecute" */));
|
||||
|
||||
export const TABLE_TOOLBAR_HEIGHT = 44;
|
||||
|
||||
export default function TableToolbar() {
|
||||
const [projectSettings] = useAtom(projectSettingsAtom, globalScope);
|
||||
const [userRoles] = useAtom(userRolesAtom, globalScope);
|
||||
const [compatibleRowyRunVersion] = useAtom(
|
||||
compatibleRowyRunVersionAtom,
|
||||
globalScope
|
||||
);
|
||||
const openRowyRunModal = useSetAtom(rowyRunModalAtom, globalScope);
|
||||
const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
|
||||
const [tableSchema] = useAtom(tableSchemaAtom, tableScope);
|
||||
const snackLogContext = useSnackLogContext();
|
||||
const openTableModal = useSetAtom(tableModalAtom, tableScope);
|
||||
|
||||
const hasDerivatives =
|
||||
Object.values(tableSchema.columns ?? {}).filter(
|
||||
@@ -74,16 +85,16 @@ export default function TableToolbar() {
|
||||
}}
|
||||
>
|
||||
<AddRow />
|
||||
{/* Spacer */} <div />
|
||||
<div /> {/* Spacer */}
|
||||
<HiddenFields />
|
||||
<Suspense fallback={<ButtonSkeleton />}>
|
||||
<Filters />
|
||||
</Suspense>
|
||||
{/* Spacer */} <div />
|
||||
<div /> {/* Spacer */}
|
||||
<LoadedRowsStatus />
|
||||
<div style={{ flexGrow: 1, minWidth: 64 }} />
|
||||
<RowHeight />
|
||||
{/* Spacer */} <div />
|
||||
<div /> {/* Spacer */}
|
||||
{tableSettings.tableType !== "collectionGroup" && (
|
||||
<Suspense fallback={<ButtonSkeleton />}>
|
||||
<ImportCsv />
|
||||
@@ -94,30 +105,40 @@ export default function TableToolbar() {
|
||||
</Suspense>
|
||||
{userRoles.includes("ADMIN") && (
|
||||
<>
|
||||
{/* Spacer */} <div />
|
||||
<Suspense fallback={<ButtonSkeleton />}>
|
||||
<Webhooks />
|
||||
</Suspense>
|
||||
<Suspense fallback={<ButtonSkeleton />}>
|
||||
<Extensions />
|
||||
</Suspense>
|
||||
<Suspense fallback={<ButtonSkeleton />}>
|
||||
<CloudLogs />
|
||||
</Suspense>
|
||||
{snackLogContext.isSnackLogOpen && (
|
||||
<Suspense fallback={null}>
|
||||
<BuildLogsSnack
|
||||
onClose={snackLogContext.closeSnackLog}
|
||||
onOpenPanel={alert}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
<div /> {/* Spacer */}
|
||||
<TableToolbarButton
|
||||
title="Webhooks"
|
||||
onClick={() => {
|
||||
if (compatibleRowyRunVersion({ minVersion: "1.2.0" })) {
|
||||
openTableModal("webhooks");
|
||||
} else {
|
||||
openRowyRunModal({ feature: "Webhooks", version: "1.2.0" });
|
||||
}
|
||||
}}
|
||||
icon={<WebhookIcon />}
|
||||
/>
|
||||
<TableToolbarButton
|
||||
title="Extensions"
|
||||
onClick={() => {
|
||||
if (projectSettings.rowyRunUrl) openTableModal("extensions");
|
||||
else openRowyRunModal({ feature: "Extensions" });
|
||||
}}
|
||||
icon={<ExtensionIcon />}
|
||||
/>
|
||||
<TableToolbarButton
|
||||
title="Cloud logs"
|
||||
icon={<CloudLogsIcon />}
|
||||
onClick={() => {
|
||||
if (projectSettings.rowyRunUrl) openTableModal("cloudLogs");
|
||||
else openRowyRunModal({ feature: "Cloud logs" });
|
||||
}}
|
||||
/>
|
||||
{(hasDerivatives || hasExtensions) && (
|
||||
<Suspense fallback={<ButtonSkeleton />}>
|
||||
<ReExecute />
|
||||
</Suspense>
|
||||
)}
|
||||
{/* Spacer */} <div />
|
||||
<div /> {/* Spacer */}
|
||||
<TableSettings />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./Webhooks";
|
||||
export { default } from "./Webhooks";
|
||||
@@ -1,7 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
export interface ITableWizardsProps {}
|
||||
|
||||
export default function TableWizards(props: ITableWizardsProps) {
|
||||
return <div></div>;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./TableWizards";
|
||||
export { default } from "./TableWizards";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRef, Suspense } from "react";
|
||||
import { useRef, Suspense, lazy } from "react";
|
||||
import { useAtom, Provider } from "jotai";
|
||||
import { DebugAtoms } from "@src/atoms/utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
@@ -15,6 +15,7 @@ import Table from "@src/components/Table";
|
||||
import SideDrawer from "@src/components/SideDrawer";
|
||||
import ColumnMenu from "@src/components/ColumnMenu";
|
||||
import ColumnModals from "@src/components/ColumnModals";
|
||||
import TableModals from "@src/components/TableModals";
|
||||
|
||||
import { currentUserAtom, globalScope } from "@src/atoms/globalScope";
|
||||
import TableSourceFirestore from "@src/sources/TableSourceFirestore";
|
||||
@@ -27,9 +28,14 @@ import {
|
||||
} from "@src/atoms/tableScope";
|
||||
import useBeforeUnload from "@src/hooks/useBeforeUnload";
|
||||
import ActionParamsProvider from "@src/components/fields/Action/FormDialog/Provider";
|
||||
import { useSnackLogContext } from "@src/contexts/SnackLogContext";
|
||||
|
||||
// prettier-ignore
|
||||
const BuildLogsSnack = lazy(() => import("@src/components/TableModals/CloudLogsModal/BuildLogs/BuildLogsSnack" /* webpackChunkName: "TableModals-BuildLogsSnack" */));
|
||||
|
||||
function TablePage() {
|
||||
const [tableSchema] = useAtom(tableSchemaAtom, tableScope);
|
||||
const snackLogContext = useSnackLogContext();
|
||||
|
||||
// Warn user about leaving when they have a table modal open
|
||||
useBeforeUnload(columnModalAtom, tableScope);
|
||||
@@ -71,6 +77,18 @@ function TablePage() {
|
||||
<ColumnMenu />
|
||||
<ColumnModals />
|
||||
</Suspense>
|
||||
|
||||
<Suspense fallback={null}>
|
||||
<TableModals />
|
||||
{snackLogContext.isSnackLogOpen && (
|
||||
<Suspense fallback={null}>
|
||||
<BuildLogsSnack
|
||||
onClose={snackLogContext.closeSnackLog}
|
||||
onOpenPanel={alert}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</Suspense>
|
||||
</ActionParamsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
4
src/types/table.d.ts
vendored
4
src/types/table.d.ts
vendored
@@ -4,8 +4,8 @@ import type {
|
||||
DocumentData,
|
||||
DocumentReference,
|
||||
} from "firebase/firestore";
|
||||
import { IExtension } from "@src/components/TableToolbar/Extensions/utils";
|
||||
import { IWebhook } from "@src/components/TableToolbar/Webhooks/utils";
|
||||
import { IExtension } from "@src/components/TableModals/ExtensionsModal/utils";
|
||||
import { IWebhook } from "@src/components/TableModals/WebhooksModal/utils";
|
||||
|
||||
/**
|
||||
* A standard function to update a doc in the database
|
||||
|
||||
Reference in New Issue
Block a user