diff --git a/src/components/fields/Array/DisplayCell.tsx b/src/components/fields/Array/DisplayCell.tsx new file mode 100644 index 00000000..e934ea5f --- /dev/null +++ b/src/components/fields/Array/DisplayCell.tsx @@ -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 ( +
+ {JSON.stringify(value, null, 4)} +
+ ); +} diff --git a/src/components/fields/Array/SideDrawerField/AddButton.tsx b/src/components/fields/Array/SideDrawerField/AddButton.tsx new file mode 100644 index 00000000..4719970e --- /dev/null +++ b/src/components/fields/Array/SideDrawerField/AddButton.tsx @@ -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(null); + const [open, setOpen] = useState(false); + const [fieldType, setFieldType] = useState( + FieldType.shortText + ); + + return ( + <> + + + + + + + + + ); +} + +export default AddButton; diff --git a/src/components/fields/Array/SideDrawerField/SupportedTypes.ts b/src/components/fields/Array/SideDrawerField/SupportedTypes.ts new file mode 100644 index 00000000..28acd30c --- /dev/null +++ b/src/components/fields/Array/SideDrawerField/SupportedTypes.ts @@ -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; + } + } +} diff --git a/src/components/fields/Array/SideDrawerField/index.tsx b/src/components/fields/Array/SideDrawerField/index.tsx new file mode 100644 index 00000000..b8415eb5 --- /dev/null +++ b/src/components/fields/Array/SideDrawerField/index.tsx @@ -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 ( + + {(provided) => ( + + + + false ? theme.palette.action.disabledOpacity : 1, + }, + ]} + /> + + + + + onRemove(index)} + > + + + + )} + + ); +} + +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 ( + <> + + + {(provided) => ( + + {(value || []).map((v: any, index: number) => ( + handleChange(newValue, index)} + onRemove={handleRemove} + index={index} + onSubmit={onSubmit} + /> + ))} + {provided.placeholder} + + )} + + + + + ); + } + + return ( + + + {JSON.stringify(value, null, 4)} + + + + ); +} diff --git a/src/components/fields/Array/SideDrawerField/utils.ts b/src/components/fields/Array/SideDrawerField/utils.ts new file mode 100644 index 00000000..599c4694 --- /dev/null +++ b/src/components/fields/Array/SideDrawerField/utils.ts @@ -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; + } + } +} diff --git a/src/components/fields/Array/index.tsx b/src/components/fields/Array/index.tsx new file mode 100644 index 00000000..9aad247c --- /dev/null +++ b/src/components/fields/Array/index.tsx @@ -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: , + 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; diff --git a/src/components/fields/index.ts b/src/components/fields/index.ts index 0d54b0a5..4b16d1f2 100644 --- a/src/components/fields/index.ts +++ b/src/components/fields/index.ts @@ -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, diff --git a/src/constants/fields.ts b/src/constants/fields.ts index 900b88db..b05c6b89 100644 --- a/src/constants/fields.ts +++ b/src/constants/fields.ts @@ -35,6 +35,7 @@ export enum FieldType { json = "JSON", code = "CODE", markdown = "MARKDOWN", + array = "ARRAY", // CLOUD FUNCTION action = "ACTION", derivative = "DERIVATIVE",