mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
standardize ColumnSelect, wrap more TableToolbar children in lazy & Suspense
This commit is contained in:
@@ -9,8 +9,13 @@ import RowyRunModal from "@src/components/RowyRunModal";
|
||||
import NotFound from "@src/pages/NotFound";
|
||||
import RequireAuth from "@src/layouts/RequireAuth";
|
||||
|
||||
import { globalScope, currentUserAtom } from "@src/atoms/globalScope";
|
||||
import {
|
||||
globalScope,
|
||||
currentUserAtom,
|
||||
altPressAtom,
|
||||
} from "@src/atoms/globalScope";
|
||||
import { ROUTES } from "@src/constants/routes";
|
||||
import useKeyPressWithAtom from "@src/hooks/useKeyPressWithAtom";
|
||||
|
||||
import TableGroupRedirectPage from "./pages/TableGroupRedirect";
|
||||
import JotaiTestPage from "@src/pages/Test/JotaiTest";
|
||||
@@ -53,6 +58,7 @@ const ThemeTestPage = lazy(() => import("@src/pages/Test/ThemeTest" /* webpackCh
|
||||
|
||||
export default function App() {
|
||||
const [currentUser] = useAtom(currentUserAtom, globalScope);
|
||||
useKeyPressWithAtom("Alt", altPressAtom, globalScope);
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Loading fullScreen />}>
|
||||
|
||||
@@ -10,6 +10,12 @@ import type {
|
||||
} from "@src/types/table";
|
||||
import { getTableSchemaAtom } from "./project";
|
||||
|
||||
/**
|
||||
* Global state when the Alt key is pressed,
|
||||
* so we don’t set multiple event listeners
|
||||
*/
|
||||
export const altPressAtom = atom(false);
|
||||
|
||||
/** Nav open state stored in local storage. */
|
||||
export const navOpenAtom = atomWithStorage("__ROWY__NAV_OPEN", false);
|
||||
/** Nav pinned state stored in local storage. */
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
columnMenuAtom,
|
||||
columnModalAtom,
|
||||
tableFiltersPopoverAtom,
|
||||
altPressAtom,
|
||||
} from "@src/atoms/globalScope";
|
||||
import {
|
||||
tableScope,
|
||||
@@ -45,7 +46,6 @@ import {
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
import { getFieldProp } from "@src/components/fields";
|
||||
import { analytics, logEvent } from "@src/analytics";
|
||||
import useKeyPress from "@src/hooks/useKeyPress";
|
||||
import { formatSubTableName } from "@src/utils/table";
|
||||
|
||||
export interface IMenuModalProps {
|
||||
@@ -78,7 +78,7 @@ export default function ColumnMenu() {
|
||||
tableFiltersPopoverAtom,
|
||||
globalScope
|
||||
);
|
||||
const altPress = useKeyPress("Alt");
|
||||
const [altPress] = useAtom(altPressAtom, globalScope);
|
||||
|
||||
if (!columnMenu) return null;
|
||||
const { column, anchorEl } = columnMenu;
|
||||
|
||||
@@ -22,13 +22,13 @@ import {
|
||||
globalScope,
|
||||
userRolesAtom,
|
||||
columnMenuAtom,
|
||||
altPressAtom,
|
||||
} from "@src/atoms/globalScope";
|
||||
import { tableScope, updateColumnAtom } from "@src/atoms/tableScope";
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
import { getFieldProp } from "@src/components/fields";
|
||||
import { COLUMN_HEADER_HEIGHT } from "@src/components/Table/Column";
|
||||
import { ColumnConfig } from "@src/types/table";
|
||||
import useKeyPress from "@src/hooks/useKeyPress";
|
||||
|
||||
export { COLUMN_HEADER_HEIGHT };
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function DraggableHeaderRenderer({
|
||||
const [userRoles] = useAtom(userRolesAtom, globalScope);
|
||||
const updateColumn = useSetAtom(updateColumnAtom, tableScope);
|
||||
const openColumnMenu = useSetAtom(columnMenuAtom, globalScope);
|
||||
const altPress = useKeyPress("Alt");
|
||||
const [altPress] = useAtom(altPressAtom, globalScope);
|
||||
|
||||
const [{ isDragging }, dragRef] = useDrag({
|
||||
type: "COLUMN_DRAG",
|
||||
@@ -191,7 +191,13 @@ export default function DraggableHeaderRenderer({
|
||||
component="div"
|
||||
color="inherit"
|
||||
>
|
||||
{altPress ? `${column.index}: ${column.fieldName}` : column.name}
|
||||
{altPress ? (
|
||||
<>
|
||||
{column.index} <code>{column.fieldName}</code>
|
||||
</>
|
||||
) : (
|
||||
column.name
|
||||
)}
|
||||
</Typography>
|
||||
</LightTooltip>
|
||||
</Grid>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
globalScope,
|
||||
userRolesAtom,
|
||||
tableAddRowIdTypeAtom,
|
||||
altPressAtom,
|
||||
confirmDialogAtom,
|
||||
} from "@src/atoms/globalScope";
|
||||
import {
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
addRowAtom,
|
||||
deleteRowAtom,
|
||||
} from "@src/atoms/tableScope";
|
||||
import useKeyPress from "@src/hooks/useKeyPress";
|
||||
import { TableRow } from "@src/types/table";
|
||||
|
||||
export default function FinalColumn({ row }: FormatterProps<TableRow, any>) {
|
||||
@@ -29,7 +29,7 @@ export default function FinalColumn({ row }: FormatterProps<TableRow, any>) {
|
||||
const addRow = useSetAtom(addRowAtom, tableScope);
|
||||
const deleteRow = useSetAtom(deleteRowAtom, tableScope);
|
||||
|
||||
const altPress = useKeyPress("Alt");
|
||||
const [altPress] = useAtom(altPressAtom, globalScope);
|
||||
const handleDelete = () => deleteRow(row._rowy_ref.path);
|
||||
|
||||
if (!userRoles.includes("ADMIN") && tableSettings.readOnly === true)
|
||||
|
||||
80
src/components/TableToolbar/ColumnSelect.tsx
Normal file
80
src/components/TableToolbar/ColumnSelect.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
import MultiSelect, { MultiSelectProps } from "@rowy/multiselect";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
|
||||
import { globalScope, altPressAtom } from "@src/atoms/globalScope";
|
||||
import { tableScope, tableColumnsOrderedAtom } from "@src/atoms/tableScope";
|
||||
import { ColumnConfig } from "@src/types/table";
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
import { getFieldProp } from "@src/components/fields";
|
||||
|
||||
export type ColumnOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
type: FieldType;
|
||||
index: number;
|
||||
};
|
||||
|
||||
export interface IColumnSelectProps {
|
||||
filterColumns?: (column: ColumnConfig) => boolean;
|
||||
options?: ColumnOption[];
|
||||
}
|
||||
|
||||
export default function ColumnSelect({
|
||||
filterColumns,
|
||||
...props
|
||||
}: IColumnSelectProps & Omit<MultiSelectProps<string>, "options">) {
|
||||
const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope);
|
||||
const options = (
|
||||
filterColumns
|
||||
? tableColumnsOrdered.filter(filterColumns)
|
||||
: tableColumnsOrdered
|
||||
).map(({ key, name, type, index, fixed }) => ({
|
||||
value: key,
|
||||
label: name,
|
||||
type,
|
||||
index,
|
||||
}));
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
options={options}
|
||||
label="Column"
|
||||
labelPlural="columns"
|
||||
{...(props as any)}
|
||||
itemRenderer={(option: ColumnOption) => <ColumnItem option={option} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ColumnItem({
|
||||
option,
|
||||
children,
|
||||
}: React.PropsWithChildren<{ option: ColumnOption }>) {
|
||||
const [altPress] = useAtom(altPressAtom, globalScope);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
sx={{ color: "text.secondary", width: "100%" }}
|
||||
>
|
||||
{getFieldProp("icon", option.type)}
|
||||
<Typography color="text.primary" style={{ flexGrow: 1 }}>
|
||||
{altPress ? <code>{option.value}</code> : option.label}
|
||||
</Typography>
|
||||
{altPress && (
|
||||
<Typography
|
||||
color="text.disabled"
|
||||
variant="caption"
|
||||
style={{ fontVariantNumeric: "tabular-nums" }}
|
||||
>
|
||||
{option.index}
|
||||
</Typography>
|
||||
)}
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
} from "@mui/material";
|
||||
import ColumnSelect from "@src/components/TableToolbar/ColumnSelect";
|
||||
|
||||
import {
|
||||
tableScope,
|
||||
@@ -133,17 +134,10 @@ export default function Export({
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<MultiSelect
|
||||
<ColumnSelect
|
||||
value={columns.map((x) => x.key)}
|
||||
onChange={handleChange(setColumns)}
|
||||
options={tableColumnsOrdered
|
||||
.filter(
|
||||
(column) =>
|
||||
isString(column.name) &&
|
||||
isString(column.key) &&
|
||||
DOWNLOADABLE_COLUMNS.includes(column.type)
|
||||
)
|
||||
.map((column: any) => ({ label: column.name, value: column.key }))}
|
||||
filterColumns={(column) => DOWNLOADABLE_COLUMNS.includes(column.type)}
|
||||
label="Columns to export"
|
||||
labelPlural="columns"
|
||||
TextFieldProps={{
|
||||
|
||||
@@ -4,9 +4,7 @@ import { parse as json2csv } from "json2csv";
|
||||
import { saveAs } from "file-saver";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { getDocs } from "firebase/firestore";
|
||||
import { get, find, sortBy, isString } from "lodash-es";
|
||||
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
import { get, find } from "lodash-es";
|
||||
|
||||
import {
|
||||
Button,
|
||||
@@ -18,6 +16,7 @@ import {
|
||||
Radio,
|
||||
FormHelperText,
|
||||
} from "@mui/material";
|
||||
import ColumnSelect from "@src/components/TableToolbar/ColumnSelect";
|
||||
|
||||
import {
|
||||
tableScope,
|
||||
@@ -175,12 +174,9 @@ export default function Export({
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<MultiSelect
|
||||
<ColumnSelect
|
||||
value={columns.map((x) => x.key)}
|
||||
onChange={handleChange}
|
||||
options={tableColumnsOrdered
|
||||
.filter((column) => isString(column.name) && isString(column.key))
|
||||
.map((column) => ({ label: column.name, value: column.key }))}
|
||||
label="Columns to export"
|
||||
labelPlural="columns"
|
||||
TextFieldProps={{
|
||||
@@ -191,23 +187,25 @@ export default function Export({
|
||||
selectAll
|
||||
/>
|
||||
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Export type</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label="export type"
|
||||
name="export-type-radio-buttons-group"
|
||||
value={exportType}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
if (v) setExportType(v as "csv" | "tsv" | "json");
|
||||
}}
|
||||
>
|
||||
<FormControlLabel value="csv" control={<Radio />} label=".csv" />
|
||||
<FormControlLabel value="tsv" control={<Radio />} label=".tsv" />
|
||||
<FormControlLabel value="json" control={<Radio />} label=".json" />
|
||||
</RadioGroup>
|
||||
<FormHelperText>Encoding: UTF-8</FormHelperText>
|
||||
</FormControl>
|
||||
<div>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Export type</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label="export type"
|
||||
name="export-type-radio-buttons-group"
|
||||
value={exportType}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
if (v) setExportType(v as "csv" | "tsv" | "json");
|
||||
}}
|
||||
>
|
||||
<FormControlLabel value="csv" control={<Radio />} label=".csv" />
|
||||
<FormControlLabel value="tsv" control={<Radio />} label=".tsv" />
|
||||
<FormControlLabel value="json" control={<Radio />} label=".json" />
|
||||
</RadioGroup>
|
||||
<FormHelperText>Encoding: UTF-8</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div style={{ flexGrow: 1, marginTop: 0 }} />
|
||||
|
||||
|
||||
@@ -4,6 +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 FormAutosave from "@src/components/ColumnModals/ColumnConfigModal/FormAutosave";
|
||||
import FieldSkeleton from "@src/components/SideDrawer/Form/FieldSkeleton";
|
||||
|
||||
@@ -35,7 +36,7 @@ export default function FilterInputs({
|
||||
return (
|
||||
<Grid container spacing={2} sx={{ mb: 3 }}>
|
||||
<Grid item xs={4}>
|
||||
<MultiSelect
|
||||
<ColumnSelect
|
||||
multiple={false}
|
||||
label="Column"
|
||||
options={filterColumns}
|
||||
|
||||
@@ -14,7 +14,13 @@ export const useFilterInputs = (
|
||||
// Get list of columns that can be filtered
|
||||
const filterColumns = columns
|
||||
.filter((c) => getFieldProp("filter", getFieldType(c)))
|
||||
.map((c) => ({ value: c.key, label: c.name, ...c }));
|
||||
.map((c) => ({
|
||||
value: c.key,
|
||||
label: c.name,
|
||||
type: c.type,
|
||||
key: c.key,
|
||||
index: c.index,
|
||||
}));
|
||||
|
||||
// State for filter inputs
|
||||
const [query, setQuery] = useState<TableFilter | typeof INITIAL_QUERY>(
|
||||
|
||||
@@ -2,37 +2,28 @@ import { useEffect, useRef, useMemo, useState } from "react";
|
||||
import { useAtom } from "jotai";
|
||||
import { isEqual } from "lodash-es";
|
||||
|
||||
import { AutocompleteProps } from "@mui/material";
|
||||
import { Stack, Typography, Box, AutocompleteProps } from "@mui/material";
|
||||
import VisibilityIcon from "@mui/icons-material/VisibilityOutlined";
|
||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOffOutlined";
|
||||
import IconSlash, {
|
||||
ICON_SLASH_STROKE_DASHOFFSET,
|
||||
} from "@src/components/IconSlash";
|
||||
import IconSlash from "@src/components/IconSlash";
|
||||
|
||||
import MultiSelect from "@rowy/multiselect";
|
||||
import ColumnSelect, { ColumnItem } from "./ColumnSelect";
|
||||
import ButtonWithStatus from "@src/components/ButtonWithStatus";
|
||||
import Column from "@src/components/Table/Column";
|
||||
|
||||
import {
|
||||
globalScope,
|
||||
userSettingsAtom,
|
||||
updateUserSettingsAtom,
|
||||
} from "@src/atoms/globalScope";
|
||||
import {
|
||||
tableScope,
|
||||
tableIdAtom,
|
||||
tableSchemaAtom,
|
||||
tableColumnsOrderedAtom,
|
||||
} from "@src/atoms/tableScope";
|
||||
import { tableScope, tableIdAtom } from "@src/atoms/tableScope";
|
||||
import { formatSubTableName } from "@src/utils/table";
|
||||
import { getFieldProp } from "@src/components/fields";
|
||||
|
||||
export default function HiddenFields() {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const [userSettings] = useAtom(userSettingsAtom, globalScope);
|
||||
const [tableId] = useAtom(tableIdAtom, tableScope);
|
||||
const [tableSchema] = useAtom(tableSchemaAtom, tableScope);
|
||||
const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -50,10 +41,10 @@ export default function HiddenFields() {
|
||||
setHiddenFields(userDocHiddenFields);
|
||||
}, [userDocHiddenFields]);
|
||||
|
||||
const tableColumns = tableColumnsOrdered.map(({ key, name }) => ({
|
||||
value: key,
|
||||
label: name,
|
||||
}));
|
||||
// const tableColumns = tableColumnsOrdered.map(({ key, name }) => ({
|
||||
// value: key,
|
||||
// label: name,
|
||||
// }));
|
||||
|
||||
// Save when MultiSelect closes
|
||||
const [updateUserSettings] = useAtom(updateUserSettingsAtom, globalScope);
|
||||
@@ -73,20 +64,22 @@ export default function HiddenFields() {
|
||||
any
|
||||
>["renderOption"] = (props, option, { selected }) => (
|
||||
<li {...props}>
|
||||
<Column
|
||||
label={option.label}
|
||||
type={tableSchema.columns?.[option.value]?.type}
|
||||
secondaryItem={
|
||||
<div
|
||||
className="icon-container"
|
||||
style={selected ? { opacity: 1 } : {}}
|
||||
>
|
||||
<VisibilityIcon />
|
||||
<IconSlash style={selected ? { strokeDashoffset: 0 } : {}} />
|
||||
</div>
|
||||
}
|
||||
// active={selected}
|
||||
/>
|
||||
<ColumnItem option={option}>
|
||||
<Box
|
||||
sx={[
|
||||
{ position: "relative", height: "1.5rem" },
|
||||
selected
|
||||
? { color: "primary.main" }
|
||||
: {
|
||||
opacity: 0,
|
||||
".MuiAutocomplete-option.Mui-focused &": { opacity: 0.5 },
|
||||
},
|
||||
]}
|
||||
>
|
||||
<VisibilityIcon />
|
||||
<IconSlash style={selected ? { strokeDashoffset: 0 } : {}} />
|
||||
</Box>
|
||||
</ColumnItem>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -100,7 +93,7 @@ export default function HiddenFields() {
|
||||
>
|
||||
{hiddenFields.length > 0 ? `${hiddenFields.length} hidden` : "Hide"}
|
||||
</ButtonWithStatus>
|
||||
<MultiSelect
|
||||
<ColumnSelect
|
||||
TextFieldProps={{
|
||||
style: { display: "none" },
|
||||
SelectProps: {
|
||||
@@ -109,47 +102,12 @@ export default function HiddenFields() {
|
||||
anchorEl: buttonRef.current,
|
||||
anchorOrigin: { vertical: "bottom", horizontal: "left" },
|
||||
transformOrigin: { vertical: "top", horizontal: "left" },
|
||||
|
||||
sx: {
|
||||
"& .MuiAutocomplete-listbox .MuiAutocomplete-option": {
|
||||
padding: 0,
|
||||
paddingLeft: "0 !important",
|
||||
borderRadius: 0,
|
||||
marginBottom: "-1px",
|
||||
|
||||
"&::after": { content: "none" },
|
||||
"& .icon-container": { opacity: 0 },
|
||||
|
||||
"&:hover, &.Mui-focused, &.Mui-focusVisible": {
|
||||
position: "relative",
|
||||
zIndex: 2,
|
||||
|
||||
"& > div": {
|
||||
color: "text.primary",
|
||||
borderColor: "currentColor",
|
||||
boxShadow: (theme: any) =>
|
||||
`0 0 0 1px ${theme.palette.text.primary} inset`,
|
||||
},
|
||||
"& .icon-container": { opacity: 0.5 },
|
||||
},
|
||||
|
||||
// "&:hover .icon-container svg": { color: "primary.main" },
|
||||
"&:hover .icon-slash": { strokeDashoffset: 0 },
|
||||
'&[aria-selected="true"]:hover': {
|
||||
"& .icon-slash": {
|
||||
strokeDashoffset:
|
||||
ICON_SLASH_STROKE_DASHOFFSET + " !important",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
{...{ AutocompleteProps: { renderOption } }}
|
||||
label="Hidden fields"
|
||||
labelPlural="fields"
|
||||
options={tableColumns}
|
||||
value={hiddenFields ?? []}
|
||||
onChange={setHiddenFields}
|
||||
onClose={handleSave}
|
||||
|
||||
@@ -2,18 +2,13 @@ import { lazy, Suspense } from "react";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
import { Stack } from "@mui/material";
|
||||
import { ButtonSkeleton } from "./TableToolbarSkeleton";
|
||||
|
||||
import AddRow from "./AddRow";
|
||||
import Filters from "./Filters";
|
||||
import ImportCSV from "./ImportCsv";
|
||||
// import Export from "./Export";
|
||||
import LoadedRowsStatus from "./LoadedRowsStatus";
|
||||
import TableSettings from "./TableSettings";
|
||||
// import CloudLogs from "./CloudLogs";
|
||||
import HiddenFields from "./HiddenFields";
|
||||
import RowHeight from "./RowHeight";
|
||||
// import Extensions from "./Extensions";
|
||||
// import Webhooks from "./Webhooks";
|
||||
import ReExecute from "./ReExecute";
|
||||
// import BuildLogsSnack from "./CloudLogs/BuildLogs/BuildLogsSnack";
|
||||
|
||||
import { globalScope, userRolesAtom } from "@src/atoms/globalScope";
|
||||
@@ -25,7 +20,20 @@ import {
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
// import { useSnackLogContext } from "@src/contexts/SnackLogContext";
|
||||
|
||||
const Export = lazy(() => import("./Export"));
|
||||
// prettier-ignore
|
||||
const Filters = lazy(() => import("./Filters" /* webpackChunkName: "Filters" */));
|
||||
// prettier-ignore
|
||||
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;
|
||||
|
||||
@@ -68,29 +76,51 @@ export default function TableToolbar() {
|
||||
<AddRow />
|
||||
{/* Spacer */} <div />
|
||||
<HiddenFields />
|
||||
<Filters />
|
||||
<Suspense fallback={<ButtonSkeleton />}>
|
||||
<Filters />
|
||||
</Suspense>
|
||||
{/* Spacer */} <div />
|
||||
<LoadedRowsStatus />
|
||||
<div style={{ flexGrow: 1, minWidth: 64 }} />
|
||||
<RowHeight />
|
||||
{/* Spacer */} <div />
|
||||
{tableSettings.tableType !== "collectionGroup" && <ImportCSV />}
|
||||
<Suspense fallback={null}>
|
||||
{tableSettings.tableType !== "collectionGroup" && (
|
||||
<Suspense fallback={<ButtonSkeleton />}>
|
||||
<ImportCsv />
|
||||
</Suspense>
|
||||
)}
|
||||
<Suspense fallback={<ButtonSkeleton />}>
|
||||
<Export />
|
||||
</Suspense>
|
||||
{userRoles.includes("ADMIN") && (
|
||||
<>
|
||||
{/* Spacer */} <div />
|
||||
{/* <Webhooks /> */}
|
||||
{/* <Extensions /> */}
|
||||
{/* <CloudLogs /> */}
|
||||
{/*
|
||||
<Suspense fallback={<ButtonSkeleton/>}>
|
||||
<Webhooks />
|
||||
</Suspense>
|
||||
*/}
|
||||
{/*
|
||||
<Suspense fallback={<ButtonSkeleton/>}>
|
||||
<Extensions />
|
||||
</Suspense>
|
||||
*/}
|
||||
{/*
|
||||
<Suspense fallback={<ButtonSkeleton/>}>
|
||||
<CloudLogs />
|
||||
</Suspense>
|
||||
*/}
|
||||
{/* {snackLogContext.isSnackLogOpen && (
|
||||
<BuildLogsSnack
|
||||
onClose={snackLogContext.closeSnackLog}
|
||||
onOpenPanel={alert}
|
||||
/>
|
||||
)} */}
|
||||
{(hasDerivatives || hasExtensions) && <ReExecute />}
|
||||
{(hasDerivatives || hasExtensions) && (
|
||||
<Suspense fallback={<ButtonSkeleton />}>
|
||||
<ReExecute />
|
||||
</Suspense>
|
||||
)}
|
||||
{/* Spacer */} <div />
|
||||
<TableSettings />
|
||||
</>
|
||||
|
||||
@@ -3,11 +3,11 @@ import AddRowIcon from "@src/assets/icons/AddRow";
|
||||
|
||||
import { TABLE_TOOLBAR_HEIGHT } from "@src/components/TableToolbar";
|
||||
|
||||
const ButtonSkeleton = (props: Partial<SkeletonProps>) => (
|
||||
export const ButtonSkeleton = (props: Partial<SkeletonProps>) => (
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
{...props}
|
||||
sx={{ borderRadius: 1, ...props.sx }}
|
||||
sx={{ borderRadius: 1, width: 40, height: 32, ...props.sx }}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function TableToolbarSkeleton() {
|
||||
height: TABLE_TOOLBAR_HEIGHT,
|
||||
}}
|
||||
>
|
||||
<ButtonSkeleton>
|
||||
<ButtonSkeleton sx={{ width: undefined, height: undefined }}>
|
||||
<Button variant="contained" startIcon={<AddRowIcon />}>
|
||||
Add row
|
||||
</Button>
|
||||
@@ -35,12 +35,12 @@ export default function TableToolbarSkeleton() {
|
||||
|
||||
<div />
|
||||
|
||||
<ButtonSkeleton>
|
||||
<ButtonSkeleton sx={{ width: undefined, height: undefined }}>
|
||||
<Button variant="contained" startIcon={<AddRowIcon />}>
|
||||
Hide
|
||||
</Button>
|
||||
</ButtonSkeleton>
|
||||
<ButtonSkeleton>
|
||||
<ButtonSkeleton sx={{ width: undefined, height: undefined }}>
|
||||
<Button variant="contained" startIcon={<AddRowIcon />}>
|
||||
Filter
|
||||
</Button>
|
||||
@@ -48,10 +48,10 @@ export default function TableToolbarSkeleton() {
|
||||
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
|
||||
<ButtonSkeleton style={{ width: 40, height: 32 }} />
|
||||
<ButtonSkeleton />
|
||||
<div />
|
||||
<ButtonSkeleton style={{ width: 40, height: 32 }} />
|
||||
<ButtonSkeleton style={{ width: 40, height: 32 }} />
|
||||
<ButtonSkeleton />
|
||||
<ButtonSkeleton />
|
||||
</Stack>
|
||||
</Fade>
|
||||
);
|
||||
|
||||
@@ -131,7 +131,9 @@ export const hasDataTypes = (dataTypes: string[]) => {
|
||||
* @param column - The column to check
|
||||
* @returns FieldType
|
||||
*/
|
||||
export const getFieldType = (column: ColumnConfig) =>
|
||||
export const getFieldType = (
|
||||
column: Pick<ColumnConfig, "type" | "config"> & Partial<ColumnConfig>
|
||||
) =>
|
||||
column.type === FieldType.derivative
|
||||
? column.config?.renderFieldType
|
||||
: column.type;
|
||||
|
||||
41
src/hooks/useKeyPressWithAtom.ts
Normal file
41
src/hooks/useKeyPressWithAtom.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useEffect } from "react";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { PrimitiveAtom, Scope } from "jotai/core/atom";
|
||||
|
||||
/**
|
||||
* A hook that listens to when the target key is pressed
|
||||
* and updates an atom value.
|
||||
* @param targetKey - The key to listen to
|
||||
* @param atom - A function to update the atom
|
||||
* @param scope - The scope of the atom
|
||||
*/
|
||||
export default function useKeyPressWithAtom(
|
||||
targetKey: string,
|
||||
atom: PrimitiveAtom<boolean>,
|
||||
scope: Scope
|
||||
) {
|
||||
const setAtom = useSetAtom(atom, scope);
|
||||
|
||||
// Add event listeners
|
||||
useEffect(() => {
|
||||
if (!setAtom) return;
|
||||
|
||||
// If pressed key is our target key then set to true
|
||||
const downHandler = ({ key }: KeyboardEvent) => {
|
||||
if (key === targetKey) setAtom(true);
|
||||
};
|
||||
|
||||
// If released key is our target key then set to false
|
||||
const upHandler = ({ key }: KeyboardEvent) => {
|
||||
if (key === targetKey) setAtom(false);
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", downHandler);
|
||||
window.addEventListener("keyup", upHandler);
|
||||
// Remove event listeners on cleanup
|
||||
return () => {
|
||||
window.removeEventListener("keydown", downHandler);
|
||||
window.removeEventListener("keyup", upHandler);
|
||||
};
|
||||
}, [targetKey, setAtom]);
|
||||
}
|
||||
Reference in New Issue
Block a user