-
Notifications
You must be signed in to change notification settings - Fork 542
feat: add array column type #1045
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { IDisplayCellProps } from "@src/components/fields/types"; | ||
| import { Link } from "react-router-dom"; | ||
|
|
||
| import { IconButton, Stack, useTheme } from "@mui/material"; | ||
| import OpenIcon from "@mui/icons-material/OpenInBrowser"; | ||
|
|
||
| export default function Array({ value }: IDisplayCellProps) { | ||
| const theme = useTheme(); | ||
|
|
||
| if (!value) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <div | ||
| style={{ | ||
| width: "100%", | ||
| maxHeight: "100%", | ||
| whiteSpace: "pre-wrap", | ||
| lineHeight: theme.typography.body2.lineHeight, | ||
| fontFamily: theme.typography.fontFamilyMono, | ||
| }} | ||
| > | ||
| {JSON.stringify(value, null, 4)} | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { ISettingsProps } from "@src/components/fields/types"; | ||
|
|
||
| const Settings = (props: ISettingsProps) => { | ||
| return null; | ||
| }; | ||
|
|
||
| export default Settings; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,233 @@ | ||
| import { ISideDrawerFieldProps } from "@src/components/fields/types"; | ||
|
|
||
| import { | ||
| Stack, | ||
| Box, | ||
| Button, | ||
| Select, | ||
| MenuItem, | ||
| FormControlLabel, | ||
| TextField, | ||
| Switch, | ||
| Chip, | ||
| Badge, | ||
| } from "@mui/material"; | ||
| import AddIcon from "@mui/icons-material/Add"; | ||
| import ClearIcon from "@mui/icons-material/Clear"; | ||
|
|
||
| import JSONField from "@src/components/fields/Json/SideDrawerField"; | ||
| import CodeEditor from "@src/components/CodeEditor"; | ||
|
|
||
| function ArrayFieldInput({ | ||
| onChange, | ||
| value, | ||
| disabled, | ||
| id, | ||
| }: { | ||
| onChange: (value: any) => void; | ||
| value: any; | ||
| disabled: boolean; | ||
| id: string; | ||
| }) { | ||
| switch (typeof value) { | ||
| case "bigint": | ||
| case "number": { | ||
| return ( | ||
| <TextField | ||
| onChange={(e) => onChange(+e.target.value)} | ||
| type="number" | ||
| placeholder="Enter value" | ||
| disabled={disabled} | ||
| id={id} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| case "string": { | ||
| return ( | ||
| <TextField | ||
| onChange={(e) => onChange(e.target.value)} | ||
| type="text" | ||
| placeholder="Enter value" | ||
| disabled={disabled} | ||
| id={id} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| case "boolean": { | ||
| return ( | ||
| <Switch | ||
| checked={!!value} | ||
| onChange={(e) => onChange(e.target.checked)} | ||
| disabled={disabled} | ||
| id={id} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| case "object": { | ||
| return ( | ||
| <Box sx={{ overflow: "auto", width: "100%" }}> | ||
| <CodeEditor | ||
| defaultLanguage="json" | ||
| value={JSON.stringify(value) || "{\n \n}"} | ||
| onChange={(v) => { | ||
| try { | ||
| if (v) onChange(JSON.parse(v)); | ||
| } catch (e) { | ||
| console.log(`Failed to parse JSON: ${e}`); | ||
| } | ||
| }} | ||
| /> | ||
| </Box> | ||
| ); | ||
| } | ||
|
|
||
| default: | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| export default function ArraySideDrawerField({ | ||
| column, | ||
| value, | ||
| onChange, | ||
| onSubmit, | ||
| disabled, | ||
| ...props | ||
| }: ISideDrawerFieldProps) { | ||
| const handleNewField = () => { | ||
| onChange([...(value || []), ""]); | ||
| onSubmit(); | ||
| }; | ||
|
|
||
| const handleChange = (newValue: any, indexUpdated: number) => { | ||
| onChange( | ||
| [...(value || [])].map((v: any, i) => { | ||
| if (i === indexUpdated) { | ||
| return newValue; | ||
| } | ||
|
|
||
| return v; | ||
| }) | ||
| ); | ||
|
|
||
| onSubmit(); | ||
| }; | ||
|
|
||
| const handleChangeType = (newType: any, indexUpdated: number) => { | ||
| console.log(newType, indexUpdated); | ||
|
|
||
| onChange( | ||
| [...(value || [])].map((v: any, i) => { | ||
| if (i === indexUpdated) { | ||
| switch (newType) { | ||
| case "boolean": { | ||
| return true; | ||
| } | ||
| case "number": { | ||
| return 0; | ||
| } | ||
| case "string": { | ||
| return ""; | ||
| } | ||
| case "object": { | ||
| return {}; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return v; | ||
| }) | ||
| ); | ||
|
|
||
| onSubmit(); | ||
| }; | ||
|
|
||
| const handleClearField = () => { | ||
| onChange([]); | ||
| onSubmit(); | ||
| }; | ||
|
|
||
| if (!value || Array.isArray(value)) { | ||
| return ( | ||
| <Stack spacing={2}> | ||
| {(value || []).map((v: any, index: number) => { | ||
| return ( | ||
| <Stack | ||
| key={`array-index-${index}-field`} | ||
| spacing={1} | ||
| alignItems="start" | ||
| direction="row" | ||
| > | ||
| <FormControlLabel | ||
| sx={{ ml: 1 }} | ||
| control={ | ||
| <Select | ||
| size="small" | ||
| labelId={`index-${index}-type`} | ||
| id={`index-${index}-type`} | ||
| value={typeof v} | ||
| disabled={disabled} | ||
| sx={{ width: 100 }} | ||
| onChange={(e) => handleChangeType(e.target.value, index)} | ||
| label="Type" | ||
| > | ||
| <MenuItem value="string">String</MenuItem> | ||
| <MenuItem value="number">Number</MenuItem> | ||
| <MenuItem value="boolean">Boolean</MenuItem> | ||
| <MenuItem value="object">JSON</MenuItem> | ||
| </Select> | ||
| } | ||
| label={index === 0 ? "Type" : null} | ||
| labelPlacement="top" | ||
| /> | ||
|
|
||
| <FormControlLabel | ||
| sx={{ width: "100%", overflow: "hidden" }} | ||
| control={ | ||
| <ArrayFieldInput | ||
| value={v} | ||
| id={`index-${index}-value`} | ||
| disabled={disabled} | ||
| onChange={(newValue) => handleChange(newValue, index)} | ||
| /> | ||
| } | ||
| label={index === 0 ? "Value" : null} | ||
| labelPlacement="top" | ||
| /> | ||
| </Stack> | ||
| ); | ||
| })} | ||
|
|
||
| <Button | ||
| sx={{ mt: 1, width: "fit-content" }} | ||
| onClick={handleNewField} | ||
| variant="contained" | ||
| color="primary" | ||
| startIcon={<AddIcon />} | ||
| > | ||
| Add field | ||
| </Button> | ||
| </Stack> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <Stack> | ||
| <Box component="pre" my="0"> | ||
| {JSON.stringify(value, null, 4)} | ||
| </Box> | ||
| <Button | ||
| sx={{ mt: 1, width: "fit-content" }} | ||
| onClick={handleClearField} | ||
| variant="text" | ||
| color="warning" | ||
| startIcon={<ClearIcon />} | ||
| > | ||
| Clear field | ||
| </Button> | ||
| </Stack> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { lazy } from "react"; | ||
| import DataArrayIcon from "@mui/icons-material/DataArray"; | ||
|
|
||
| import { IFieldConfig, FieldType } from "@src/components/fields/types"; | ||
| import withRenderTableCell from "@src/components/Table/TableCell/withRenderTableCell"; | ||
|
|
||
| import DisplayCell from "./DisplayCell"; | ||
|
|
||
| const SideDrawerField = lazy( | ||
| () => | ||
| import("./SideDrawerField" /* webpackChunkName: "SideDrawerField-Array" */) | ||
| ); | ||
| const Settings = lazy( | ||
| () => import("./Settings" /* webpackChunkName: "Settings-Array" */) | ||
| ); | ||
| export const config: IFieldConfig = { | ||
| type: FieldType.array, | ||
| name: "Array", | ||
| group: "Code", | ||
| dataType: "object", | ||
| initialValue: undefined, | ||
| initializable: true, | ||
| icon: <DataArrayIcon />, | ||
| settings: Settings, | ||
| description: | ||
| "Connects to a sub-table in the current row. Also displays number of rows inside the sub-table. Max sub-table depth: 100.", | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need description |
||
| TableCell: withRenderTableCell(DisplayCell, SideDrawerField, "popover", { | ||
| popoverProps: { PaperProps: { sx: { p: 1, minWidth: "200px" } } }, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went with the popover approach because the sub-table approach opens a Modal from the bottom, and thus the user loses the context of the row since he has to close the modal to see the row and the Modal breaks the flow, IMO Popover approach seems to be simple steps to go with. Let me know I can use modal in case this isn't what we want
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can try embedding the table component inside the popover, however providing a way to configure the column schema might be more challenging |
||
| }), | ||
| SideDrawerField, | ||
| requireConfiguration: true, | ||
| }; | ||
| export default config; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be used to add array of objects