mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
Added new filed ARRAY
This commit is contained in:
24
src/components/fields/Array/DisplayCell.tsx
Normal file
24
src/components/fields/Array/DisplayCell.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
92
src/components/fields/Array/SideDrawerField/AddButton.tsx
Normal file
92
src/components/fields/Array/SideDrawerField/AddButton.tsx
Normal 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;
|
||||
105
src/components/fields/Array/SideDrawerField/SupportedTypes.ts
Normal file
105
src/components/fields/Array/SideDrawerField/SupportedTypes.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/components/fields/Array/SideDrawerField/index.tsx
Normal file
205
src/components/fields/Array/SideDrawerField/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
59
src/components/fields/Array/SideDrawerField/utils.ts
Normal file
59
src/components/fields/Array/SideDrawerField/utils.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/components/fields/Array/index.tsx
Normal file
30
src/components/fields/Array/index.tsx
Normal 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;
|
||||
@@ -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,
|
||||
|
||||
@@ -35,6 +35,7 @@ export enum FieldType {
|
||||
json = "JSON",
|
||||
code = "CODE",
|
||||
markdown = "MARKDOWN",
|
||||
array = "ARRAY",
|
||||
// CLOUD FUNCTION
|
||||
action = "ACTION",
|
||||
derivative = "DERIVATIVE",
|
||||
|
||||
Reference in New Issue
Block a user