diff --git a/src/components/fields/Formula/DisplayCell.tsx b/src/components/fields/Formula/DisplayCell.tsx index ddea15d3..79760e32 100644 --- a/src/components/fields/Formula/DisplayCell.tsx +++ b/src/components/fields/Formula/DisplayCell.tsx @@ -5,11 +5,16 @@ import { getDisplayCell } from "./util"; export default function Formula(props: IDisplayCellProps) { const { result, error } = useFormula({ row: props.row, + listenerFields: props.column.config?.listenerFields || [], formulaFn: props.column.config?.formulaFn, }); const type = props.column.config?.renderFieldType; const DisplayCell = getDisplayCell(type); + if (error) { + return <>Error; + } + return ; } diff --git a/src/components/fields/Formula/Settings.tsx b/src/components/fields/Formula/Settings.tsx index 36d21e91..cb907a8c 100644 --- a/src/components/fields/Formula/Settings.tsx +++ b/src/components/fields/Formula/Settings.tsx @@ -7,19 +7,19 @@ import { Typography, Stack, FormHelperText, + Tooltip, } from "@mui/material"; import FieldSkeleton from "@src/components/SideDrawer/FieldSkeleton"; import { useAtom } from "jotai"; -import { - tableColumnsOrderedAtom, - tableRowsAtom, - tableScope, -} from "@src/atoms/tableScope"; -import { useFormula } from "./useFormula"; -import { listenerFieldTypes, outputFieldTypes, typeDefs } from "./util"; +import { tableColumnsOrderedAtom, tableScope } from "@src/atoms/tableScope"; +import { defaultFn, listenerFieldTypes, outputFieldTypes } from "./util"; import MultiSelect from "@rowy/multiselect"; import FieldsDropdown from "@src/components/ColumnModals/FieldsDropdown"; +import { getFieldProp } from ".."; + +/* eslint-disable import/no-webpack-loader-syntax */ +import formulaDefs from "!!raw-loader!./formula.d.ts"; const CodeEditor = lazy( () => @@ -28,39 +28,13 @@ const CodeEditor = lazy( export default function Settings({ config, - fieldName, onChange, onBlur, errors, }: ISettingsProps) { const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope); - const [tableRows] = useAtom(tableRowsAtom, tableScope); - const { error, result, loading } = useFormula({ - row: tableRows[0], - formulaFn: config.formulaFn, - }); - - // if (error && !config?.error) { - // onChange("error")(error.message); - // } - // if (!error && config?.error) { - // onChange("error")(undefined); - // } - - // if (loading) { - // console.log("loading"); - // } - - // if (error) { - // console.log(error); - // } - - const formulaFn = - config?.formulaFn ?? - `// Write your formula code here -// for example: -// return column1 + column2; -// checkout the documentation for more info: https://docs.rowy.io/field-types/formula`; + const returnType = getFieldProp("dataType", config.renderFieldType) ?? "any"; + const formulaFn = config?.formulaFn ? config.formulaFn : defaultFn; return ( @@ -70,7 +44,7 @@ export default function Settings({ label="Listener fields" options={tableColumnsOrdered .filter((c) => listenerFieldTypes.includes(c.type)) - .map((c) => c.name)} + .map((c) => ({ label: c.name, value: c.key }))} value={config.listenerFields ?? []} onChange={onChange("listenerFields")} TextFieldProps={{ @@ -130,52 +104,26 @@ export default function Settings({ spacing={1} style={{ flexGrow: 1, marginTop: -8, marginLeft: 0 }} > - {/* {Object.values(previewColumns).map((column) => ( - - - {column.fieldName} - - - ))} */} + + + row + + }> ` + ), + ]} onChange={useDebouncedCallback(onChange("formulaFn"), 300)} /> - {/* - Preview: - - - - - - {Object.entries(previewColumns).map( - ([field, { name, type, key }]) => ( - - - - - ) - )} - - */} ); } diff --git a/src/components/fields/Formula/formula.d.ts b/src/components/fields/Formula/formula.d.ts new file mode 100644 index 00000000..90f9351d --- /dev/null +++ b/src/components/fields/Formula/formula.d.ts @@ -0,0 +1,8 @@ +type FormulaContext = { + row: Row; + // ref: FirebaseFirestore.DocumentReference; + // storage: firebasestorage.Storage; + // db: FirebaseFirestore.Firestore; +}; + +type Formula = (context: FormulaContext) => "PLACEHOLDER_OUTPUT_TYPE"; diff --git a/src/components/fields/Formula/useFormula.tsx b/src/components/fields/Formula/useFormula.tsx index 48a2d9e8..f33bab43 100644 --- a/src/components/fields/Formula/useFormula.tsx +++ b/src/components/fields/Formula/useFormula.tsx @@ -3,13 +3,15 @@ import { TableRow } from "@src/types/table"; import { useAtom } from "jotai"; import { pick, zipObject } from "lodash-es"; import { useEffect, useMemo, useState } from "react"; -import { useDeepCompareMemoize } from "./util"; +import { listenerFieldTypes, useDeepCompareMemoize } from "./util"; export const useFormula = ({ row, + listenerFields, formulaFn, }: { row: TableRow; + listenerFields: string[]; formulaFn: string; }) => { const [result, setResult] = useState(null); @@ -17,7 +19,9 @@ export const useFormula = ({ const [loading, setLoading] = useState(false); const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope); - const availableColumns = tableColumnsOrdered.map((c) => c.key); + const availableColumns = tableColumnsOrdered + .filter((c) => listenerFieldTypes.includes(c.type)) + .map((c) => c.key); const availableFields = useMemo( () => ({ @@ -30,17 +34,22 @@ export const useFormula = ({ [row, availableColumns] ); + const listeners = useMemo( + () => pick(availableFields, listenerFields), + [availableFields, listenerFields] + ); + useEffect(() => { setLoading(true); const worker = new Worker(new URL("./worker.ts", import.meta.url), { - type: "classic", + type: "module", }); const timeout = setTimeout(() => { setError(new Error("Timeout")); setLoading(false); worker.terminate(); - }, 1000); + }, 10000); worker.onmessage = ({ data: { result, error } }: any) => { worker.terminate(); @@ -53,8 +62,9 @@ export const useFormula = ({ clearInterval(timeout); }; + const functionBody = formulaFn.replace(/^.*=>\s*{/, "").replace(/}$/, ""); worker.postMessage({ - formulaFn: formulaFn, + formulaFn: functionBody, row: availableFields, }); @@ -63,7 +73,7 @@ export const useFormula = ({ clearInterval(timeout); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [useDeepCompareMemoize(availableFields), formulaFn]); + }, [useDeepCompareMemoize(listeners), formulaFn]); return { result, error, loading }; }; diff --git a/src/components/fields/Formula/util.tsx b/src/components/fields/Formula/util.tsx index 6dd04db4..8fda4b07 100644 --- a/src/components/fields/Formula/util.tsx +++ b/src/components/fields/Formula/util.tsx @@ -47,7 +47,6 @@ export const listenerFieldTypes = Object.values(FieldType).filter( FieldType.connectTable, FieldType.connector, FieldType.subTable, - FieldType.reference, FieldType.last, ].includes(type) ); @@ -74,32 +73,13 @@ export const outputFieldTypes = Object.values(FieldType).filter( ].includes(type) ); -export const typeDefs = (type: FieldType) => { - switch (type) { - case FieldType.shortText: - case FieldType.longText: - case FieldType.richText: - case FieldType.email: - case FieldType.phone: - case FieldType.url: - case FieldType.singleSelect: - return "string | undefined"; - case FieldType.multiSelect: - return "Array | undefined"; - case FieldType.number: - case FieldType.rating: - case FieldType.slider: - case FieldType.percentage: - return "number | undefined"; - case FieldType.checkbox: - return "boolean | undefined"; - case FieldType.date: - case FieldType.dateTime: - case FieldType.duration: - return "{ seconds: number; nanoseconds: number; }"; - } - return "any"; -}; +export const defaultFn = `const formula:Formula = async ({ row })=> { + // Write your formula code here + // for example: + // return row.a + row.b; + // checkout the documentation for more info: https://docs.rowy.io/field-types/formula +} +`; export const getDisplayCell = (type: FieldType) => { switch (type) { diff --git a/src/components/fields/Formula/worker.ts b/src/components/fields/Formula/worker.ts index 1dab169c..57c03265 100644 --- a/src/components/fields/Formula/worker.ts +++ b/src/components/fields/Formula/worker.ts @@ -1,19 +1,20 @@ -import { ContentSaveCogOutline } from "mdi-material-ui"; - onmessage = async ({ data }) => { try { const { formulaFn, row } = data; - console.dir(row); - const AsyncFunction = (async (x: any) => x).constructor as any; - // eslint-disable-next-line no-new-func - // const result = await new AsyncFunction("row", formulaFn)(row); + + const AsyncFunction = async function () {}.constructor as any; const fn = new AsyncFunction("row", formulaFn); const result = await fn(row); + await new Promise((resolve) => setTimeout(() => resolve(50), 3000)).then( + (res) => console.log(res) + ); console.log(result); postMessage({ result }); } catch (error: any) { console.error("error: ", error); - postMessage({ error: new Error(error) }); + postMessage({ + error: new Error("Something went wrong. Check console logs."), + }); } finally { // eslint-disable-next-line no-restricted-globals self.close();