mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-28 16:06:41 +01:00
migrate field types based on TextEditor
This commit is contained in:
77
src/components/Table/EditorCellTextField.tsx
Normal file
77
src/components/Table/EditorCellTextField.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { IEditorCellProps } from "@src/components/fields/types";
|
||||
import { useSaveOnUnmount } from "@src/hooks/useSaveOnUnmount";
|
||||
|
||||
import { InputBase, InputBaseProps } from "@mui/material";
|
||||
|
||||
import { spreadSx } from "@src/utils/ui";
|
||||
|
||||
export interface IEditorCellTextFieldProps extends IEditorCellProps<string> {
|
||||
InputProps?: Partial<InputBaseProps>;
|
||||
}
|
||||
|
||||
export default function EditorCellTextField({
|
||||
column,
|
||||
value,
|
||||
onSubmit,
|
||||
setFocusInsideCell,
|
||||
InputProps = {},
|
||||
}: IEditorCellTextFieldProps) {
|
||||
const [localValue, setLocalValue] = useSaveOnUnmount(value, onSubmit);
|
||||
const maxLength = column.config?.maxLength;
|
||||
|
||||
return (
|
||||
<InputBase
|
||||
value={localValue}
|
||||
onChange={(e) => setLocalValue(e.target.value)}
|
||||
fullWidth
|
||||
autoFocus
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
e.key === "ArrowLeft" ||
|
||||
e.key === "ArrowRight" ||
|
||||
e.key === "ArrowUp" ||
|
||||
e.key === "ArrowDown"
|
||||
) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
// Escape removes focus inside cell, this runs before save on unmount
|
||||
setLocalValue(value);
|
||||
}
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
// Removes focus from inside cell, triggering save on unmount
|
||||
setFocusInsideCell(false);
|
||||
}
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onDoubleClick={(e) => e.stopPropagation()}
|
||||
{...InputProps}
|
||||
inputProps={{ maxLength, ...InputProps.inputProps }}
|
||||
sx={[
|
||||
{
|
||||
width: "100%",
|
||||
height: "calc(100% - 1px)",
|
||||
marginTop: "1px",
|
||||
paddingBottom: "1px",
|
||||
|
||||
backgroundColor: "var(--cell-background-color)",
|
||||
outline: "inherit",
|
||||
outlineOffset: "inherit",
|
||||
|
||||
font: "inherit", // Prevent text jumping
|
||||
letterSpacing: "inherit", // Prevent text jumping
|
||||
|
||||
"& .MuiInputBase-input": { p: "var(--cell-padding)" },
|
||||
|
||||
"& textarea.MuiInputBase-input": {
|
||||
lineHeight: (theme) => theme.typography.body2.lineHeight,
|
||||
maxHeight: "100%",
|
||||
boxSizing: "border-box",
|
||||
py: 3 / 8,
|
||||
},
|
||||
},
|
||||
...spreadSx(InputProps.sx),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,9 @@ export const StyledCell = styled("div")(({ theme }) => ({
|
||||
height: "100%",
|
||||
contain: "strict",
|
||||
overflow: "hidden",
|
||||
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
|
||||
backgroundColor: "var(--cell-background-color)",
|
||||
|
||||
@@ -446,7 +446,11 @@ export default function Table({
|
||||
cell.column.columnDef.meta?.config?.required
|
||||
)}
|
||||
aria-selected={isSelectedCell}
|
||||
aria-describedby="rowy-table-cell-description"
|
||||
aria-describedby={
|
||||
canEditCell
|
||||
? "rowy-table-editable-cell-description"
|
||||
: undefined
|
||||
}
|
||||
style={{
|
||||
width: cell.column.getSize(),
|
||||
height: tableSchema.rowHeight,
|
||||
@@ -537,7 +541,10 @@ export default function Table({
|
||||
</StyledTable>
|
||||
|
||||
<Portal>
|
||||
<div id="rowy-table-cell-description" style={{ display: "none" }}>
|
||||
<div
|
||||
id="rowy-table-editable-cell-description"
|
||||
style={{ display: "none" }}
|
||||
>
|
||||
Press Enter to edit.
|
||||
</div>
|
||||
</Portal>
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import { useRef, useLayoutEffect } from "react";
|
||||
import { EditorProps } from "react-data-grid";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { get } from "lodash-es";
|
||||
|
||||
import { TextField } from "@mui/material";
|
||||
|
||||
import { tableScope, updateFieldAtom } from "@src/atoms/tableScope";
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
import { getFieldType } from "@src/components/fields";
|
||||
|
||||
/** WARNING: THIS DOES NOT WORK IN REACT 18 STRICT MODE */
|
||||
export default function TextEditor({ row, column }: EditorProps<any>) {
|
||||
const updateField = useSetAtom(updateFieldAtom, tableScope);
|
||||
|
||||
const type = getFieldType(column as any);
|
||||
|
||||
const cellValue = get(row, column.key);
|
||||
const defaultValue =
|
||||
type === FieldType.percentage && typeof cellValue === "number"
|
||||
? cellValue * 100
|
||||
: cellValue;
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// WARNING: THIS DOES NOT WORK IN REACT 18 STRICT MODE
|
||||
useLayoutEffect(() => {
|
||||
const inputElement = inputRef.current;
|
||||
return () => {
|
||||
const newValue = inputElement?.value;
|
||||
let formattedValue: any = newValue;
|
||||
if (newValue !== undefined) {
|
||||
if (type === FieldType.number) {
|
||||
formattedValue = Number(newValue);
|
||||
} else if (type === FieldType.percentage) {
|
||||
formattedValue = Number(newValue) / 100;
|
||||
}
|
||||
|
||||
updateField({
|
||||
path: row._rowy_ref.path,
|
||||
fieldName: column.key,
|
||||
value: formattedValue,
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [column.key, row._rowy_ref.path, type, updateField]);
|
||||
|
||||
let inputType = "text";
|
||||
switch (type) {
|
||||
case FieldType.email:
|
||||
inputType = "email";
|
||||
break;
|
||||
case FieldType.phone:
|
||||
inputType = "tel";
|
||||
break;
|
||||
case FieldType.url:
|
||||
inputType = "url";
|
||||
break;
|
||||
case FieldType.number:
|
||||
case FieldType.percentage:
|
||||
inputType = "number";
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const { maxLength } = (column as any).config;
|
||||
|
||||
return (
|
||||
<TextField
|
||||
defaultValue={defaultValue}
|
||||
type={inputType}
|
||||
fullWidth
|
||||
multiline={type === FieldType.longText}
|
||||
variant="standard"
|
||||
inputProps={{
|
||||
ref: inputRef,
|
||||
maxLength: maxLength,
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "var(--cell-background-color)",
|
||||
|
||||
"& .MuiInputBase-root": {
|
||||
height: "100%",
|
||||
font: "inherit", // Prevent text jumping
|
||||
letterSpacing: "inherit", // Prevent text jumping
|
||||
p: 0,
|
||||
},
|
||||
"& .MuiInputBase-input": {
|
||||
height: "100%",
|
||||
font: "inherit", // Prevent text jumping
|
||||
letterSpacing: "inherit", // Prevent text jumping
|
||||
p: "var(--cell-padding)",
|
||||
pb: 1 / 8,
|
||||
},
|
||||
"& textarea.MuiInputBase-input": {
|
||||
lineHeight: (theme) => theme.typography.body2.lineHeight,
|
||||
maxHeight: "100%",
|
||||
boxSizing: "border-box",
|
||||
py: 3 / 8,
|
||||
},
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
(column as any).type === FieldType.percentage ? "%" : undefined,
|
||||
}}
|
||||
autoFocus
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (e.key === "Escape") {
|
||||
(e.target as any).value = defaultValue;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ export interface ICellOptions {
|
||||
*/
|
||||
export default function withTableCell(
|
||||
DisplayCellComponent: React.ComponentType<IDisplayCellProps>,
|
||||
EditorCellComponent: React.ComponentType<IEditorCellProps>,
|
||||
EditorCellComponent: React.ComponentType<IEditorCellProps> | null,
|
||||
editorMode: "focus" | "inline" | "popover" = "focus",
|
||||
options: ICellOptions = {}
|
||||
) {
|
||||
@@ -115,13 +115,16 @@ export default function withTableCell(
|
||||
});
|
||||
};
|
||||
|
||||
const editorCell = (
|
||||
// Show displayCell as a fallback if intentionally null
|
||||
const editorCell = EditorCellComponent ? (
|
||||
<EditorCellComponent
|
||||
{...basicCellProps}
|
||||
tabIndex={focusInsideCell ? 0 : -1}
|
||||
onSubmit={handleSubmit}
|
||||
parentRef={parentRef}
|
||||
/>
|
||||
) : (
|
||||
displayCell
|
||||
);
|
||||
|
||||
if (editorMode === "focus" && focusInsideCell) {
|
||||
|
||||
6
src/components/fields/Email/EditorCell.tsx
Normal file
6
src/components/fields/Email/EditorCell.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { IEditorCellProps } from "@src/components/fields/types";
|
||||
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
|
||||
|
||||
export default function Email(props: IEditorCellProps<string>) {
|
||||
return <EditorCellTextField {...props} InputProps={{ type: "email" }} />;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "@src/components/fields/types";
|
||||
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
|
||||
import withTableCell from "@src/components/Table/withTableCell";
|
||||
|
||||
import EmailIcon from "@mui/icons-material/MailOutlined";
|
||||
import BasicCell from "@src/components/fields/_BasicCell/BasicCellValue";
|
||||
import TextEditor from "@src/components/Table/editors/TextEditor";
|
||||
import DisplayCell from "@src/components/fields/_BasicCell/BasicCellValue";
|
||||
import EditorCell from "./EditorCell";
|
||||
import { filterOperators } from "@src/components/fields/ShortText/Filter";
|
||||
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
|
||||
|
||||
@@ -23,8 +23,7 @@ export const config: IFieldConfig = {
|
||||
icon: <EmailIcon />,
|
||||
description: "Email address. Not validated.",
|
||||
contextMenuActions: BasicContextMenuActions,
|
||||
TableCell: withBasicCell(BasicCell),
|
||||
TableEditor: TextEditor,
|
||||
TableCell: withTableCell(DisplayCell, EditorCell),
|
||||
SideDrawerField,
|
||||
filter: {
|
||||
operators: filterOperators,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { IHeavyCellProps } from "@src/components/fields/types";
|
||||
import { IDisplayCellProps } from "@src/components/fields/types";
|
||||
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
export default function Id({ docRef }: IHeavyCellProps) {
|
||||
export default function Id({ docRef }: IDisplayCellProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
@@ -1,17 +1,9 @@
|
||||
import { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "@src/components/fields/types";
|
||||
import withHeavyCell from "@src/components/fields/_withTableCell/withHeavyCell";
|
||||
import withTableCell from "@src/components/Table/withTableCell";
|
||||
|
||||
import DisplayCell from "./DisplayCell";
|
||||
import SideDrawerField from "./SideDrawerField";
|
||||
import { Id as IdIcon } from "@src/assets/icons";
|
||||
import BasicCell from "@src/components/fields/_BasicCell/BasicCellValue";
|
||||
import withSideDrawerEditor from "@src/components/Table/editors/withSideDrawerEditor";
|
||||
|
||||
const TableCell = lazy(
|
||||
() => import("./TableCell" /* webpackChunkName: "TableCell-Id" */)
|
||||
);
|
||||
const SideDrawerField = lazy(
|
||||
() => import("./SideDrawerField" /* webpackChunkName: "SideDrawerField-Id" */)
|
||||
);
|
||||
|
||||
export const config: IFieldConfig = {
|
||||
type: FieldType.id,
|
||||
@@ -21,8 +13,7 @@ export const config: IFieldConfig = {
|
||||
initialValue: "",
|
||||
icon: <IdIcon />,
|
||||
description: "Displays the row’s ID. Read-only. Cannot be sorted.",
|
||||
TableCell: withHeavyCell(BasicCell, TableCell),
|
||||
TableEditor: withSideDrawerEditor(TableCell),
|
||||
TableCell: withTableCell(DisplayCell, null),
|
||||
SideDrawerField,
|
||||
};
|
||||
export default config;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { IBasicCellProps } from "@src/components/fields/types";
|
||||
import { IDisplayCellProps } from "@src/components/fields/types";
|
||||
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
export default function LongText({ value }: IBasicCellProps) {
|
||||
export default function LongText({ value }: IDisplayCellProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
maxHeight: "100%",
|
||||
padding: theme.spacing(3 / 8, 0),
|
||||
padding: theme.spacing(1, 0),
|
||||
|
||||
whiteSpace: "pre-line",
|
||||
lineHeight: theme.typography.body2.lineHeight,
|
||||
6
src/components/fields/LongText/EditorCell.tsx
Normal file
6
src/components/fields/LongText/EditorCell.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { IEditorCellProps } from "@src/components/fields/types";
|
||||
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
|
||||
|
||||
export default function LongText(props: IEditorCellProps<string>) {
|
||||
return <EditorCellTextField {...props} InputProps={{ multiline: true }} />;
|
||||
}
|
||||
@@ -1,21 +1,15 @@
|
||||
import { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "@src/components/fields/types";
|
||||
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
|
||||
import withTableCell from "@src/components/Table/withTableCell";
|
||||
|
||||
import LongTextIcon from "@mui/icons-material/Notes";
|
||||
import BasicCell from "./BasicCell";
|
||||
import TextEditor from "@src/components/Table/editors/TextEditor";
|
||||
import DisplayCell from "./DisplayCell";
|
||||
import EditorCell from "./EditorCell";
|
||||
import SideDrawerField from "./SideDrawerField";
|
||||
|
||||
import { filterOperators } from "./Filter";
|
||||
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
|
||||
|
||||
const SideDrawerField = lazy(
|
||||
() =>
|
||||
import(
|
||||
"./SideDrawerField" /* webpackChunkName: "SideDrawerField-LongText" */
|
||||
)
|
||||
);
|
||||
|
||||
const Settings = lazy(
|
||||
() => import("./Settings" /* webpackChunkName: "Settings-LongText" */)
|
||||
);
|
||||
@@ -30,8 +24,7 @@ export const config: IFieldConfig = {
|
||||
icon: <LongTextIcon />,
|
||||
description: "Text displayed on multiple lines.",
|
||||
contextMenuActions: BasicContextMenuActions,
|
||||
TableCell: withBasicCell(BasicCell),
|
||||
TableEditor: TextEditor,
|
||||
TableCell: withTableCell(DisplayCell, EditorCell),
|
||||
SideDrawerField,
|
||||
settings: Settings,
|
||||
filter: {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { IBasicCellProps } from "@src/components/fields/types";
|
||||
|
||||
export default function Number_({ value }: IBasicCellProps) {
|
||||
return <>{`${value ?? ""}`}</>;
|
||||
}
|
||||
5
src/components/fields/Number/DisplayCell.tsx
Normal file
5
src/components/fields/Number/DisplayCell.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { IDisplayCellProps } from "@src/components/fields/types";
|
||||
|
||||
export default function Number_({ value }: IDisplayCellProps) {
|
||||
return <>{`${value ?? ""}`}</>;
|
||||
}
|
||||
12
src/components/fields/Number/EditorCell.tsx
Normal file
12
src/components/fields/Number/EditorCell.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { IEditorCellProps } from "@src/components/fields/types";
|
||||
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
|
||||
|
||||
export default function Number_(props: IEditorCellProps<number>) {
|
||||
return (
|
||||
<EditorCellTextField
|
||||
{...(props as any)}
|
||||
InputProps={{ type: "number" }}
|
||||
onSubmit={(v) => props.onSubmit(Number(v))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "@src/components/fields/types";
|
||||
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
|
||||
import withTableCell from "@src/components/Table/withTableCell";
|
||||
|
||||
import { Number as NumberIcon } from "@src/assets/icons";
|
||||
import BasicCell from "./BasicCell";
|
||||
import TextEditor from "@src/components/Table/editors/TextEditor";
|
||||
import DisplayCell from "./DisplayCell";
|
||||
import EditorCell from "./EditorCell";
|
||||
import { filterOperators } from "./Filter";
|
||||
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
|
||||
const SideDrawerField = lazy(
|
||||
@@ -22,8 +22,7 @@ export const config: IFieldConfig = {
|
||||
icon: <NumberIcon />,
|
||||
description: "Numeric value.",
|
||||
contextMenuActions: BasicContextMenuActions,
|
||||
TableCell: withBasicCell(BasicCell),
|
||||
TableEditor: TextEditor,
|
||||
TableCell: withTableCell(DisplayCell, EditorCell),
|
||||
SideDrawerField,
|
||||
filter: {
|
||||
operators: filterOperators,
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { IBasicCellProps } from "@src/components/fields/types";
|
||||
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
export default function Percentage({ value }: IBasicCellProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
if (value === null || value === undefined) return null;
|
||||
|
||||
const percentage = typeof value === "number" ? value : 0;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
textAlign: "right",
|
||||
color: theme.palette.text.primary,
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{Math.round(percentage * 100)}%
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { IHeavyCellProps } from "@src/components/fields/types";
|
||||
import { IDisplayCellProps } from "@src/components/fields/types";
|
||||
|
||||
import { useTheme } from "@mui/material";
|
||||
import { resultColorsScale } from "@src/utils/color";
|
||||
|
||||
export default function Percentage({ column, value }: IHeavyCellProps) {
|
||||
export default function Percentage({ column, value }: IDisplayCellProps) {
|
||||
const theme = useTheme();
|
||||
const { colors } = (column as any).config;
|
||||
|
||||
13
src/components/fields/Percentage/EditorCell.tsx
Normal file
13
src/components/fields/Percentage/EditorCell.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { IEditorCellProps } from "@src/components/fields/types";
|
||||
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
|
||||
|
||||
export default function Percentage(props: IEditorCellProps<number>) {
|
||||
return (
|
||||
<EditorCellTextField
|
||||
{...(props as any)}
|
||||
InputProps={{ type: "number", endAdornment: "%" }}
|
||||
value={typeof props.value === "number" ? props.value * 100 : props.value}
|
||||
onSubmit={(v) => props.onSubmit(Number(v) / 100)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,13 @@
|
||||
import { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "@src/components/fields/types";
|
||||
import withHeavyCell from "@src/components/fields/_withTableCell/withHeavyCell";
|
||||
import withTableCell from "@src/components/Table/withTableCell";
|
||||
|
||||
import { Percentage as PercentageIcon } from "@src/assets/icons";
|
||||
import TextEditor from "@src/components/Table/editors/TextEditor";
|
||||
import DisplayCell from "./DisplayCell";
|
||||
import EditorCell from "./EditorCell";
|
||||
import { filterOperators } from "@src/components/fields/Number/Filter";
|
||||
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
|
||||
|
||||
const BasicCell = lazy(
|
||||
() => import("./BasicCell" /* webpackChunkName: "BasicCell-Percentage" */)
|
||||
);
|
||||
|
||||
const TableCell = lazy(
|
||||
() => import("./TableCell" /* webpackChunkName: "TableCell-Percentage" */)
|
||||
);
|
||||
|
||||
const SideDrawerField = lazy(
|
||||
() =>
|
||||
import(
|
||||
@@ -37,8 +30,7 @@ export const config: IFieldConfig = {
|
||||
requireConfiguration: true,
|
||||
description: "Percentage stored as a number between 0 and 1.",
|
||||
contextMenuActions: BasicContextMenuActions,
|
||||
TableCell: withHeavyCell(BasicCell, TableCell),
|
||||
TableEditor: TextEditor,
|
||||
TableCell: withTableCell(DisplayCell, EditorCell),
|
||||
SideDrawerField,
|
||||
settings: Settings,
|
||||
filter: {
|
||||
|
||||
6
src/components/fields/Phone/EditorCell.tsx
Normal file
6
src/components/fields/Phone/EditorCell.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { IEditorCellProps } from "@src/components/fields/types";
|
||||
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
|
||||
|
||||
export default function Phone(props: IEditorCellProps<string>) {
|
||||
return <EditorCellTextField {...props} InputProps={{ type: "tel" }} />;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "@src/components/fields/types";
|
||||
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
|
||||
import withTableCell from "@src/components/Table/withTableCell";
|
||||
|
||||
import PhoneIcon from "@mui/icons-material/PhoneOutlined";
|
||||
import BasicCell from "@src/components/fields/_BasicCell/BasicCellValue";
|
||||
import TextEditor from "@src/components/Table/editors/TextEditor";
|
||||
import DisplayCell from "@src/components/fields/_BasicCell/BasicCellValue";
|
||||
import EditorCell from "./EditorCell";
|
||||
import { filterOperators } from "@src/components/fields/ShortText/Filter";
|
||||
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
|
||||
|
||||
@@ -23,8 +23,7 @@ export const config: IFieldConfig = {
|
||||
icon: <PhoneIcon />,
|
||||
description: "Phone number stored as text. Not validated.",
|
||||
contextMenuActions: BasicContextMenuActions,
|
||||
TableCell: withBasicCell(BasicCell),
|
||||
TableEditor: TextEditor,
|
||||
TableCell: withTableCell(DisplayCell, EditorCell),
|
||||
SideDrawerField,
|
||||
filter: {
|
||||
operators: filterOperators,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useAtom } from "jotai";
|
||||
import { IBasicCellProps } from "@src/components/fields/types";
|
||||
import { IDisplayCellProps } from "@src/components/fields/types";
|
||||
|
||||
import { Stack, IconButton } from "@mui/material";
|
||||
import LaunchIcon from "@mui/icons-material/Launch";
|
||||
|
||||
import { projectScope, projectIdAtom } from "@src/atoms/projectScope";
|
||||
|
||||
export default function Reference({ value }: IBasicCellProps) {
|
||||
export default function Reference({ value }: IDisplayCellProps) {
|
||||
const [projectId] = useAtom(projectIdAtom, projectScope);
|
||||
|
||||
const path = value?.path ?? "";
|
||||
@@ -17,8 +17,7 @@ export default function Reference({ value }: IBasicCellProps) {
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
className="cell-collapse-padding"
|
||||
sx={{ p: "var(--cell-padding)", pr: 0.5 }}
|
||||
sx={{ p: "var(--cell-padding)", pr: 0.5, width: "100%" }}
|
||||
>
|
||||
<div style={{ flexGrow: 1, overflow: "hidden" }}>{path}</div>
|
||||
|
||||
@@ -1,100 +1,33 @@
|
||||
import { useRef, useLayoutEffect } from "react";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { EditorProps } from "react-data-grid";
|
||||
import { get } from "lodash-es";
|
||||
import { useSnackbar } from "notistack";
|
||||
import type { IEditorCellProps } from "@src/components/fields/types";
|
||||
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
|
||||
|
||||
import { TextField } from "@mui/material";
|
||||
import { useAtom } from "jotai";
|
||||
import { doc, deleteField } from "firebase/firestore";
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
import { projectScope } from "@src/atoms/projectScope";
|
||||
import { firebaseDbAtom } from "@src/sources/ProjectSourceFirebase";
|
||||
import { tableScope, updateFieldAtom } from "@src/atoms/tableScope";
|
||||
import { doc, deleteField } from "firebase/firestore";
|
||||
|
||||
/** WARNING: THIS DOES NOT WORK IN REACT 18 STRICT MODE */
|
||||
export default function TextEditor({ row, column }: EditorProps<any>) {
|
||||
const [firebaseDb] = useAtom(firebaseDbAtom, projectScope);
|
||||
const updateField = useSetAtom(updateFieldAtom, tableScope);
|
||||
export default function Reference(
|
||||
props: IEditorCellProps<ReturnType<typeof doc>>
|
||||
) {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// WARNING: THIS DOES NOT WORK IN REACT 18 STRICT MODE
|
||||
useLayoutEffect(() => {
|
||||
const inputElement = inputRef.current;
|
||||
return () => {
|
||||
const newValue = inputElement?.value;
|
||||
if (newValue !== undefined && newValue !== "") {
|
||||
try {
|
||||
const refValue = doc(firebaseDb, newValue);
|
||||
|
||||
updateField({
|
||||
path: row._rowy_ref.path,
|
||||
fieldName: column.key,
|
||||
value: refValue,
|
||||
});
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(`Invalid path: ${e.message}`, { variant: "error" });
|
||||
}
|
||||
} else {
|
||||
updateField({
|
||||
path: row._rowy_ref.path,
|
||||
fieldName: column.key,
|
||||
value: deleteField(),
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [column.key, row._rowy_ref.path, updateField]);
|
||||
|
||||
const defaultValue = get(row, column.key)?.path ?? "";
|
||||
const { maxLength } = (column as any).config;
|
||||
const [firebaseDb] = useAtom(firebaseDbAtom, projectScope);
|
||||
|
||||
return (
|
||||
<TextField
|
||||
defaultValue={defaultValue}
|
||||
fullWidth
|
||||
variant="standard"
|
||||
inputProps={{
|
||||
ref: inputRef,
|
||||
maxLength: maxLength,
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "var(--cell-background-color)",
|
||||
|
||||
"& .MuiInputBase-root": {
|
||||
height: "100%",
|
||||
font: "inherit", // Prevent text jumping
|
||||
letterSpacing: "inherit", // Prevent text jumping
|
||||
p: 0,
|
||||
},
|
||||
"& .MuiInputBase-input": {
|
||||
height: "100%",
|
||||
font: "inherit", // Prevent text jumping
|
||||
letterSpacing: "inherit", // Prevent text jumping
|
||||
p: "var(--cell-padding)",
|
||||
pb: 1 / 8,
|
||||
},
|
||||
"& textarea.MuiInputBase-input": {
|
||||
lineHeight: (theme) => theme.typography.body2.lineHeight,
|
||||
maxHeight: "100%",
|
||||
boxSizing: "border-box",
|
||||
py: 3 / 8,
|
||||
},
|
||||
}}
|
||||
// InputProps={{
|
||||
// endAdornment:
|
||||
// (column as any).type === FieldType.percentage ? "%" : undefined,
|
||||
// }}
|
||||
autoFocus
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (e.key === "Escape") {
|
||||
(e.target as any).value = defaultValue;
|
||||
<EditorCellTextField
|
||||
{...(props as any)}
|
||||
value={props.value?.path ?? ""}
|
||||
onSubmit={(newValue) => {
|
||||
if (newValue !== undefined && newValue !== "") {
|
||||
try {
|
||||
const refValue = doc(firebaseDb, newValue);
|
||||
props.onSubmit(refValue);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(`Invalid path: ${e.message}`, { variant: "error" });
|
||||
}
|
||||
} else {
|
||||
props.onSubmit(deleteField() as any);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "@src/components/fields/types";
|
||||
import withTableCell from "@src/components/Table/withTableCell";
|
||||
|
||||
import { Reference } from "@src/assets/icons";
|
||||
//import InlineCell from "./InlineCell";
|
||||
import BasicCell from "./BasicCell";
|
||||
import DisplayCell from "./DisplayCell";
|
||||
import EditorCell from "./EditorCell";
|
||||
import { filterOperators } from "@src/components/fields/ShortText/Filter";
|
||||
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
|
||||
|
||||
const EditorCell = lazy(
|
||||
() => import("./EditorCell" /* webpackChunkName: "EditorCell-Reference" */)
|
||||
);
|
||||
const SideDrawerField = lazy(
|
||||
() =>
|
||||
import(
|
||||
"./SideDrawerField" /* webpackChunkName: "SideDrawerField-Reference" */
|
||||
)
|
||||
);
|
||||
// const Settings = lazy(
|
||||
// () => import("./Settings" /* webpackChunkName: "Settings-Reference" */)
|
||||
// );
|
||||
|
||||
export const config: IFieldConfig = {
|
||||
type: FieldType.reference,
|
||||
@@ -29,10 +23,10 @@ export const config: IFieldConfig = {
|
||||
initializable: true,
|
||||
icon: <Reference />,
|
||||
description: "Firestore document reference",
|
||||
TableCell: withBasicCell(BasicCell),
|
||||
TableEditor: EditorCell,
|
||||
TableCell: withTableCell(DisplayCell, EditorCell, "focus", {
|
||||
disablePadding: true,
|
||||
}),
|
||||
SideDrawerField,
|
||||
//settings: Settings,
|
||||
filter: { operators: filterOperators },
|
||||
};
|
||||
export default config;
|
||||
|
||||
@@ -1,61 +1,6 @@
|
||||
import type { IEditorCellProps } from "@src/components/fields/types";
|
||||
import { useSaveOnUnmount } from "@src/hooks/useSaveOnUnmount";
|
||||
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
|
||||
|
||||
import { InputBase } from "@mui/material";
|
||||
|
||||
export default function ShortText({
|
||||
column,
|
||||
value,
|
||||
onSubmit,
|
||||
setFocusInsideCell,
|
||||
}: IEditorCellProps<string>) {
|
||||
const [localValue, setLocalValue] = useSaveOnUnmount(value, onSubmit);
|
||||
const maxLength = column.config?.maxLength;
|
||||
|
||||
return (
|
||||
<InputBase
|
||||
value={localValue}
|
||||
onChange={(e) => setLocalValue(e.target.value)}
|
||||
fullWidth
|
||||
inputProps={{ maxLength }}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "calc(100% - 1px)",
|
||||
marginTop: "1px",
|
||||
paddingBottom: "1px",
|
||||
|
||||
backgroundColor: "var(--cell-background-color)",
|
||||
outline: "inherit",
|
||||
outlineOffset: "inherit",
|
||||
|
||||
font: "inherit", // Prevent text jumping
|
||||
letterSpacing: "inherit", // Prevent text jumping
|
||||
|
||||
"& .MuiInputBase-input": { p: "var(--cell-padding)" },
|
||||
|
||||
"& textarea.MuiInputBase-input": {
|
||||
lineHeight: (theme) => theme.typography.body2.lineHeight,
|
||||
maxHeight: "100%",
|
||||
boxSizing: "border-box",
|
||||
py: 3 / 8,
|
||||
},
|
||||
}}
|
||||
autoFocus
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
|
||||
e.stopPropagation();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
// Escape removes focus inside cell, this runs before save on unmount
|
||||
setLocalValue(value);
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
// Removes focus from inside cell, triggering save on unmount
|
||||
setFocusInsideCell(false);
|
||||
}
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onDoubleClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
);
|
||||
export default function ShortText(props: IEditorCellProps<string>) {
|
||||
return <EditorCellTextField {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { IBasicCellProps } from "@src/components/fields/types";
|
||||
import { IDisplayCellProps } from "@src/components/fields/types";
|
||||
|
||||
import { Stack, IconButton } from "@mui/material";
|
||||
import LaunchIcon from "@mui/icons-material/Launch";
|
||||
|
||||
export default function Url({ value }: IBasicCellProps) {
|
||||
export default function Url({ value }: IDisplayCellProps) {
|
||||
if (!value || typeof value !== "string") return null;
|
||||
|
||||
const href = value.includes("http") ? value : `https://${value}`;
|
||||
@@ -13,8 +13,7 @@ export default function Url({ value }: IBasicCellProps) {
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
className="cell-collapse-padding"
|
||||
sx={{ p: "var(--cell-padding)", pr: 0.5 }}
|
||||
sx={{ p: "var(--cell-padding)", pr: 0.5, width: "100%" }}
|
||||
>
|
||||
<div style={{ flexGrow: 1, overflow: "hidden" }}>{value}</div>
|
||||
|
||||
6
src/components/fields/Url/EditorCell.tsx
Normal file
6
src/components/fields/Url/EditorCell.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { IEditorCellProps } from "@src/components/fields/types";
|
||||
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
|
||||
|
||||
export default function Url(props: IEditorCellProps<string>) {
|
||||
return <EditorCellTextField {...props} InputProps={{ type: "url" }} />;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "@src/components/fields/types";
|
||||
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
|
||||
import withTableCell from "@src/components/Table/withTableCell";
|
||||
|
||||
import UrlIcon from "@mui/icons-material/Link";
|
||||
import TableCell from "./TableCell";
|
||||
import TextEditor from "@src/components/Table/editors/TextEditor";
|
||||
import DisplayCell from "./DisplayCell";
|
||||
import EditorCell from "./EditorCell";
|
||||
import { filterOperators } from "@src/components/fields/ShortText/Filter";
|
||||
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
|
||||
|
||||
@@ -23,8 +23,9 @@ export const config: IFieldConfig = {
|
||||
icon: <UrlIcon />,
|
||||
description: "Web address. Not validated.",
|
||||
contextMenuActions: BasicContextMenuActions,
|
||||
TableCell: withBasicCell(TableCell),
|
||||
TableEditor: TextEditor,
|
||||
TableCell: withTableCell(DisplayCell, EditorCell, "focus", {
|
||||
disablePadding: true,
|
||||
}),
|
||||
SideDrawerField,
|
||||
filter: {
|
||||
operators: filterOperators,
|
||||
|
||||
Reference in New Issue
Block a user