Added new filed ARRAY

This commit is contained in:
iamanishroy
2023-02-11 19:02:11 +05:30
parent 72dab1fa8c
commit d621afca6d
8 changed files with 518 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
import { useTheme } from "@mui/material";
import { IDisplayCellProps } from "@src/components/fields/types";
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>
);
}

View File

@@ -0,0 +1,92 @@
import { useRef, useState } from "react";
import {
Button,
ButtonGroup,
ListItemText,
MenuItem,
Select,
} from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import { ChevronDown as ArrowDropDownIcon } from "@src/assets/icons";
import { FieldType } from "@src/components/fields/types";
import { getFieldProp } from "@src/components/fields";
import {
ArraySupportedFields,
ArraySupportedFiledTypes,
} from "./SupportedTypes";
function AddButton({ handleAddNew }: { handleAddNew: Function }) {
const anchorEl = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(false);
const [fieldType, setFieldType] = useState<ArraySupportedFiledTypes>(
FieldType.shortText
);
return (
<>
<ButtonGroup
variant="contained"
color="primary"
aria-label="Split button"
sx={{ width: "fit-content" }}
ref={anchorEl}
>
<Button
variant="contained"
color="primary"
onClick={() => handleAddNew(fieldType)}
startIcon={<AddIcon />}
>
Add {getFieldProp("name", fieldType)}
</Button>
<Button
variant="contained"
color="primary"
aria-label="Select add element"
aria-haspopup="menu"
style={{ padding: 0 }}
onClick={() => setOpen(true)}
id="add-row-menu-button"
aria-controls={open ? "add-new-element" : undefined}
aria-expanded={open ? "true" : "false"}
>
<ArrowDropDownIcon />
</Button>
</ButtonGroup>
<Select
id="add-new-element"
open={open}
onClose={() => setOpen(false)}
label="Add new element"
style={{ display: "none" }}
value={fieldType}
onChange={(e) => setFieldType(e.target.value as typeof fieldType)}
MenuProps={{
anchorEl: anchorEl.current,
MenuListProps: { "aria-labelledby": "add-row-menu-button" },
anchorOrigin: { horizontal: "left", vertical: "bottom" },
transformOrigin: { horizontal: "left", vertical: "top" },
}}
>
{ArraySupportedFields.map((fieldType, i) => (
<MenuItem value={fieldType} disabled={false} key={i + ""}>
<ListItemText
primary={getFieldProp("name", fieldType)}
secondary={getFieldProp("description", fieldType)}
secondaryTypographyProps={{
variant: "caption",
whiteSpace: "pre-line",
}}
/>
</MenuItem>
))}
</Select>
</>
);
}
export default AddButton;

View File

@@ -0,0 +1,105 @@
import { DocumentReference, GeoPoint, Timestamp } from "firebase/firestore";
import { FieldType } from "@src/components/fields/types";
import NumberValueSidebar from "@src/components/fields/Number/SideDrawerField";
import ShortTextValueSidebar from "@src/components/fields/ShortText/SideDrawerField";
import JsonValueSidebar from "@src/components/fields/Json/SideDrawerField";
import CheckBoxValueSidebar from "@src/components/fields/Checkbox/SideDrawerField";
import GeoPointValueSidebar from "@src/components/fields/GeoPoint/SideDrawerField";
import DateTimeValueSidebar from "@src/components/fields/DateTime/SideDrawerField";
import ReferenceValueSidebar from "@src/components/fields/Reference/SideDrawerField";
export const ArraySupportedFields = [
FieldType.number,
FieldType.shortText,
FieldType.json,
FieldType.checkbox,
FieldType.geoPoint,
FieldType.dateTime,
FieldType.reference,
] as const;
export type ArraySupportedFiledTypes = typeof ArraySupportedFields[number];
export const SupportedTypes = {
[FieldType.number]: {
Sidebar: NumberValueSidebar,
initialValue: 0,
dataType: "common",
instance: Object,
},
[FieldType.shortText]: {
Sidebar: ShortTextValueSidebar,
initialValue: "",
dataType: "common",
instance: Object,
},
[FieldType.checkbox]: {
Sidebar: CheckBoxValueSidebar,
initialValue: false,
dataType: "common",
instance: Object,
},
[FieldType.json]: {
Sidebar: JsonValueSidebar,
initialValue: {},
sx: [
{
marginTop: "24px",
},
],
dataType: "common",
instance: Object,
},
[FieldType.geoPoint]: {
Sidebar: GeoPointValueSidebar,
initialValue: new GeoPoint(0, 0),
dataType: "firestore-type",
instance: GeoPoint,
},
[FieldType.dateTime]: {
Sidebar: DateTimeValueSidebar,
initialValue: Timestamp.now(),
dataType: "firestore-type",
instance: Timestamp,
},
[FieldType.reference]: {
Sidebar: ReferenceValueSidebar,
initialValue: null,
dataType: "firestore-type",
instance: DocumentReference,
},
};
export function detectType(value: any): ArraySupportedFiledTypes {
if (value === null) {
return FieldType.reference;
}
for (const supportedField of ArraySupportedFields) {
if (SupportedTypes[supportedField].dataType === "firestore-type") {
if (value instanceof SupportedTypes[supportedField].instance) {
return supportedField;
}
}
}
switch (typeof value) {
case "bigint":
case "number": {
return FieldType.number;
}
case "string": {
return FieldType.shortText;
}
case "boolean": {
return FieldType.checkbox;
}
case "object": {
return FieldType.json;
}
default: {
return FieldType.shortText;
}
}
}

View File

@@ -0,0 +1,205 @@
import {
DragDropContext,
Droppable,
Draggable,
DropResult,
} from "react-beautiful-dnd";
import { Stack, Box, Button, ListItem, List } from "@mui/material";
import ClearIcon from "@mui/icons-material/Clear";
import DragIndicatorOutlinedIcon from "@mui/icons-material/DragIndicatorOutlined";
import DeleteIcon from "@mui/icons-material/DeleteOutline";
import { FieldType, ISideDrawerFieldProps } from "@src/components/fields/types";
import { TableRowRef } from "@src/types/table";
import AddButton from "./AddButton";
import { getPseudoColumn } from "./utils";
import {
ArraySupportedFiledTypes,
detectType,
SupportedTypes,
} from "./SupportedTypes";
function ArrayFieldInput({
onChange,
value,
_rowy_ref,
index,
onRemove,
onSubmit,
id,
}: {
index: number;
onRemove: (index: number) => void;
onChange: (value: any) => void;
value: any;
onSubmit: () => void;
_rowy_ref: TableRowRef;
id: string;
}) {
const typeDetected = detectType(value);
const Sidebar = SupportedTypes[typeDetected].Sidebar;
return (
<Draggable draggableId={id} index={index} isDragDisabled={false}>
{(provided) => (
<ListItem
sx={[{ padding: 0, marginBottom: "12px" }]}
ref={provided.innerRef}
{...provided.draggableProps}
>
<Box
sx={[{ position: "relative", height: "1.5rem" }]}
{...provided.dragHandleProps}
>
<DragIndicatorOutlinedIcon
color="disabled"
sx={[
{
marginRight: "6px",
opacity: (theme) =>
false ? theme.palette.action.disabledOpacity : 1,
},
]}
/>
</Box>
<Stack
width={"100%"}
sx={
typeDetected === FieldType.json
? SupportedTypes[typeDetected].sx
: null
}
>
<Sidebar
disabled={false}
onDirty={onChange}
onChange={onChange}
onSubmit={onSubmit}
column={getPseudoColumn(typeDetected, index, value)}
value={value}
_rowy_ref={_rowy_ref}
/>
</Stack>
<Box
sx={[{ position: "relative", height: "1.5rem" }]}
onClick={() => onRemove(index)}
>
<DeleteIcon
color="disabled"
sx={[
{
marginLeft: "6px",
":hover": {
cursor: "pointer",
color: "error.main",
},
},
]}
/>
</Box>
</ListItem>
)}
</Draggable>
);
}
export default function ArraySideDrawerField({
column,
value,
onChange,
onSubmit,
disabled,
_rowy_ref,
onDirty,
...props
}: ISideDrawerFieldProps) {
const handleAddNew = (fieldType: ArraySupportedFiledTypes) => {
onChange([...(value || []), SupportedTypes[fieldType].initialValue]);
onDirty(true);
};
const handleChange = (newValue_: any, indexUpdated: number) => {
onChange(
[...(value || [])].map((v: any, i) => {
if (i === indexUpdated) {
return newValue_;
}
return v;
})
);
};
const handleRemove = (index: number) => {
value.splice(index, 1);
onChange([...value]);
onDirty(true);
onSubmit();
};
const handleClearField = () => {
onChange([]);
onSubmit();
};
function handleOnDragEnd(result: DropResult) {
if (
!result.destination ||
result.destination.index === result.source.index
) {
return;
}
const list = Array.from(value);
const [removed] = list.splice(result.source.index, 1);
list.splice(result.destination.index, 0, removed);
onChange(list);
onSubmit();
}
if (value === undefined || Array.isArray(value)) {
return (
<>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="columns_manager" direction="vertical">
{(provided) => (
<List {...provided.droppableProps} ref={provided.innerRef}>
{(value || []).map((v: any, index: number) => (
<ArrayFieldInput
key={`index-${index}-value`}
id={`index-${index}-value`}
_rowy_ref={_rowy_ref}
value={v}
onChange={(newValue) => handleChange(newValue, index)}
onRemove={handleRemove}
index={index}
onSubmit={onSubmit}
/>
))}
{provided.placeholder}
</List>
)}
</Droppable>
</DragDropContext>
<AddButton handleAddNew={handleAddNew} />
</>
);
}
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>
);
}

View File

@@ -0,0 +1,59 @@
import { ColumnConfig } from "@src/types/table";
import { FieldType } from "@src/constants/fields";
import { ArraySupportedFiledTypes } from "./SupportedTypes";
import { GeoPoint, DocumentReference } from "firebase/firestore";
export function getPseudoColumn(
fieldType: FieldType,
index: number,
value: any
): ColumnConfig {
return {
fieldName: (+new Date()).toString(),
index: index,
key: (+new Date()).toString(),
name: value + "",
type: fieldType,
};
}
// archive: detectType / TODO: remove
export function detectType(value: any): ArraySupportedFiledTypes {
if (value === null) {
return FieldType.reference;
}
console.log(typeof GeoPoint);
console.log(value instanceof DocumentReference, value);
if (typeof value === "object") {
const keys = Object.keys(value);
// console.log({ keys, value }, typeof value);
if (keys.length === 2) {
if (keys.includes("_lat") && keys.includes("_long")) {
return FieldType.geoPoint;
}
if (keys.includes("nanoseconds") && keys.includes("seconds")) {
return FieldType.dateTime;
}
}
if (+new Date(value)) {
return FieldType.dateTime;
}
return FieldType.json;
}
switch (typeof value) {
case "bigint":
case "number": {
return FieldType.number;
}
case "string": {
return FieldType.shortText;
}
case "boolean": {
return FieldType.checkbox;
}
default: {
return FieldType.shortText;
}
}
}

View File

@@ -0,0 +1,30 @@
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" */)
);
export const config: IFieldConfig = {
type: FieldType.array,
name: "Array",
group: "Code",
dataType: "object",
initialValue: [],
initializable: true,
icon: <DataArrayIcon />,
description:
"Connects to a sub-table in the current row. Also displays number of rows inside the sub-table. Max sub-table depth: 100.",
TableCell: withRenderTableCell(DisplayCell, SideDrawerField, "popover", {
popoverProps: { PaperProps: { sx: { p: 1, minWidth: "200px" } } },
}),
SideDrawerField,
requireConfiguration: true,
};
export default config;

View File

@@ -31,6 +31,7 @@ import ConnectTable from "./ConnectTable";
import ConnectService from "./ConnectService";
import Json from "./Json";
import Code from "./Code";
import Array from "./Array";
import Action from "./Action";
import Derivative from "./Derivative";
import Formula from "./Formula";
@@ -82,6 +83,7 @@ export const FIELDS: IFieldConfig[] = [
Json,
Code,
Markdown,
Array,
/** CLOUD FUNCTION */
Action,
Derivative,

View File

@@ -35,6 +35,7 @@ export enum FieldType {
json = "JSON",
code = "CODE",
markdown = "MARKDOWN",
array = "ARRAY",
// CLOUD FUNCTION
action = "ACTION",
derivative = "DERIVATIVE",