From 6b136b0d0eb0208142c4385ebd8e99ac57e92b4c Mon Sep 17 00:00:00 2001 From: Bobby Wang Date: Mon, 27 Feb 2023 07:59:23 +0700 Subject: [PATCH 1/4] add code action: replace console.log with logging.log --- src/components/CodeEditor/CodeEditor.tsx | 38 +++++++++++- .../CodeEditor/useMonacoCustomizations.ts | 59 +++++++++++++++---- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/src/components/CodeEditor/CodeEditor.tsx b/src/components/CodeEditor/CodeEditor.tsx index 2abfd45b..6c503085 100644 --- a/src/components/CodeEditor/CodeEditor.tsx +++ b/src/components/CodeEditor/CodeEditor.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import Editor, { EditorProps } from "@monaco-editor/react"; +import Editor, { EditorProps, Monaco } from "@monaco-editor/react"; import type { editor } from "monaco-editor/esm/vs/editor/editor.api"; import { useTheme, Box, BoxProps, AppBar, Toolbar } from "@mui/material"; @@ -72,6 +72,36 @@ export default function CodeEditor({ onValidate?.(markers); }; + const validate = (monaco: Monaco, model: editor.ITextModel) => { + const markers = []; + for (let i = 1; i < model.getLineCount() + 1; i++) { + const range = { + startLineNumber: i, + startColumn: 1, + endLineNumber: i, + endColumn: model.getLineLength(i) + 1, + }; + const line = model.getValueInRange(range); + for (const keyword of ["console.log", "console.warn", "console.error"]) { + const consoleLogIndex = line.indexOf(keyword); + if (consoleLogIndex >= 0) { + markers.push({ + message: `Replace with ${keyword.replace( + "console", + "logging" + )}: Rowy Cloud Logging provides a better experience to view logs. Simply replace 'console' with 'logging'. \n\nhttps://docs.rowy.io/cloud-logs`, + severity: monaco.MarkerSeverity.Warning, + startLineNumber: range.startLineNumber, + endLineNumber: range.endLineNumber, + startColumn: consoleLogIndex + 1, + endColumn: consoleLogIndex + keyword.length + 1, + }); + } + } + } + monaco.editor.setModelMarkers(model, "owner", markers); + }; + return ( { monaco.editor.defineTheme("github-light", githubLightTheme as any); monaco.editor.defineTheme("github-dark", githubDarkTheme as any); + monaco.editor.onDidCreateModel((model) => { + validate(monaco, model); + model.onDidChangeContent(() => { + validate(monaco, model); + }); + }); }} onMount={(editor) => { if (onFocus) editor.onDidFocusEditorWidget(onFocus); diff --git a/src/components/CodeEditor/useMonacoCustomizations.ts b/src/components/CodeEditor/useMonacoCustomizations.ts index e6d55533..d145d72e 100644 --- a/src/components/CodeEditor/useMonacoCustomizations.ts +++ b/src/components/CodeEditor/useMonacoCustomizations.ts @@ -1,9 +1,4 @@ import { useEffect } from "react"; -// import { -// quicktype, -// InputData, -// jsonInputForTargetLanguage, -// } from "quicktype-core"; import { useAtom } from "jotai"; import { @@ -13,15 +8,10 @@ import { } from "@src/atoms/tableScope"; import { useMonaco } from "@monaco-editor/react"; import type { languages } from "monaco-editor/esm/vs/editor/editor.api"; -import githubLightTheme from "./github-light-default.json"; -import githubDarkTheme from "./github-dark-default.json"; import { useTheme } from "@mui/material"; import type { SystemStyleObject, Theme } from "@mui/system"; -// TODO: -// import { getFieldType, getFieldProp } from "@src/components/fields"; - /* eslint-disable import/no-webpack-loader-syntax */ import firestoreDefs from "!!raw-loader!./firestore.d.ts"; import firebaseAuthDefs from "!!raw-loader!./firebaseAuth.d.ts"; @@ -72,7 +62,6 @@ export default function useMonacoCustomizations({ }; }, []); - // Initialize external libs & TypeScript compiler options useEffect(() => { if (!monaco) return; @@ -95,6 +84,8 @@ export default function useMonacoCustomizations({ "ts:filename/utils.d.ts" ); monaco.languages.typescript.javascriptDefaults.addExtraLib(rowyUtilsDefs); + + setLoggingReplacementActions(); } catch (error) { console.error( "An error occurred during initialization of Monaco: ", @@ -135,6 +126,52 @@ export default function useMonacoCustomizations({ } }, [monaco, stringifiedDiagnosticsOptions]); + const setLoggingReplacementActions = () => { + if (!monaco) return; + const { dispose } = monaco.languages.registerCodeActionProvider( + "javascript", + { + provideCodeActions: (model, range, context, token) => { + const actions = context.markers + .filter((error) => { + return error.message.includes("Rowy Cloud Logging"); + }) + .map((error) => { + // first sentence of the message is "Replace with logging.[log/warn/error]" + const firstSentence = error.message.split(":")[0]; + const replacement = firstSentence.split("with ")[1]; + return { + title: firstSentence, + diagnostics: [error], + kind: "quickfix", + edit: { + edits: [ + { + resource: model.uri, + edit: { + range: error, + text: replacement, + }, + }, + ], + }, + isPreferred: true, + }; + }); + return { + actions: actions, + dispose: () => {}, + }; + }, + } + ); + monaco.editor.onWillDisposeModel((model) => { + // dispose code action provider when model is disposed + // this makes sure code actions are not displayed multiple times + dispose(); + }); + }; + const addJsonFieldDefinition = async ( columnKey: string, interfaceName: string From f36adf296c36ba41c0e16d6fedc1a27397de84bc Mon Sep 17 00:00:00 2001 From: Han Tuerker Date: Wed, 1 Mar 2023 22:23:16 +0100 Subject: [PATCH 2/4] fix(preview-table): fix table sort bug --- src/components/Table/ColumnHeader/useSaveTableSorts.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Table/ColumnHeader/useSaveTableSorts.tsx b/src/components/Table/ColumnHeader/useSaveTableSorts.tsx index 8ad053d5..c0c239ed 100644 --- a/src/components/Table/ColumnHeader/useSaveTableSorts.tsx +++ b/src/components/Table/ColumnHeader/useSaveTableSorts.tsx @@ -18,13 +18,13 @@ function useSaveTableSorts(canEditColumns: boolean) { const [updateTableSchema] = useAtom(updateTableSchemaAtom, tableScope); const [updateUserSettings] = useAtom(updateUserSettingsAtom, projectScope); const [tableId] = useAtom(tableIdAtom, tableScope); - if (!updateTableSchema) throw new Error("Cannot update table schema"); const { enqueueSnackbar, closeSnackbar } = useSnackbar(); const [snackbarId, setSnackbarId] = useState(null); // Offer to save when table sorts changes const trigger = useCallback( (sorts: TableSort[]) => { + if (!updateTableSchema) throw new Error("Cannot update table schema"); if (updateUserSettings) { updateUserSettings({ tables: { From d02d72f3ea8a4d56ffc7abd136c6edd46d29b158 Mon Sep 17 00:00:00 2001 From: Han Tuerker Date: Thu, 2 Mar 2023 10:10:37 +0100 Subject: [PATCH 3/4] feat(preview-table): add serialized ref including parent recursively --- .../fields/Formula/PreviewTable.tsx | 9 +---- .../fields/Formula/TableSourcePreview.ts | 37 ++++++------------- src/components/fields/Formula/formula.d.ts | 7 ++-- src/components/fields/Formula/useFormula.tsx | 18 ++++++--- src/components/fields/Formula/util.tsx | 24 +++++++++++- src/components/fields/Formula/worker.ts | 4 +- 6 files changed, 52 insertions(+), 47 deletions(-) diff --git a/src/components/fields/Formula/PreviewTable.tsx b/src/components/fields/Formula/PreviewTable.tsx index 8b2107fc..6cf85c22 100644 --- a/src/components/fields/Formula/PreviewTable.tsx +++ b/src/components/fields/Formula/PreviewTable.tsx @@ -23,14 +23,7 @@ const PreviewTable = ({ tableSchema }: { tableSchema: TableSchema }) => { scope={tableScope} initialValues={[ [currentUserAtom, currentUser], - [ - tableSettingsAtom, - { - ...tableSettings, - id: "preview-table", - collection: "preview-collection", - }, - ], + [tableSettingsAtom, tableSettings], [tableRowsDbAtom, []], ]} > diff --git a/src/components/fields/Formula/TableSourcePreview.ts b/src/components/fields/Formula/TableSourcePreview.ts index c4536c01..07712f87 100644 --- a/src/components/fields/Formula/TableSourcePreview.ts +++ b/src/components/fields/Formula/TableSourcePreview.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect } from "react"; -import { useSetAtom } from "jotai"; +import { useAtom, useSetAtom } from "jotai"; import { useAtomCallback } from "jotai/utils"; import { cloneDeep, findIndex, sortBy } from "lodash-es"; @@ -10,39 +10,24 @@ import { tableRowsDbAtom, tableSchemaAtom, tableScope, + tableSettingsAtom, } from "@src/atoms/tableScope"; import { TableRow, TableSchema } from "@src/types/table"; import { updateRowData } from "@src/utils/table"; - -const initialRows = [ - { - _rowy_ref: { - id: "zzzzzzzzzzzzzzzzzzzw", - path: "preview-collection/zzzzzzzzzzzzzzzzzzzw", - }, - }, - { - _rowy_ref: { - id: "zzzzzzzzzzzzzzzzzzzx", - path: "preview-collection/zzzzzzzzzzzzzzzzzzzx", - }, - }, - { - _rowy_ref: { - id: "zzzzzzzzzzzzzzzzzzzy", - path: "preview-collection/zzzzzzzzzzzzzzzzzzzy", - }, - }, -]; +import { serializeRef } from "./util"; const TableSourcePreview = ({ tableSchema }: { tableSchema: TableSchema }) => { + const [tableSettings] = useAtom(tableSettingsAtom, tableScope); const setTableSchemaAtom = useSetAtom(tableSchemaAtom, tableScope); const setRows = useSetAtom(tableRowsDbAtom, tableScope); - useEffect(() => { - setRows(initialRows); - }, [setRows]); + setRows( + ["preview-doc-1", "preview-doc-2", "preview-doc-3"].map((docId) => ({ + _rowy_ref: serializeRef(`${tableSettings.collection}/${docId}`), + })) + ); + }, [setRows, tableSettings.collection]); useEffect(() => { setTableSchemaAtom(() => ({ @@ -52,7 +37,7 @@ const TableSourcePreview = ({ tableSchema }: { tableSchema: TableSchema }) => { }, [tableSchema, setTableSchemaAtom]); const readRowsDb = useAtomCallback( - useCallback((get) => get(tableRowsDbAtom) || initialRows, []), + useCallback((get) => get(tableRowsDbAtom) || [], []), tableScope ); diff --git a/src/components/fields/Formula/formula.d.ts b/src/components/fields/Formula/formula.d.ts index 69fcde0d..8c6210c8 100644 --- a/src/components/fields/Formula/formula.d.ts +++ b/src/components/fields/Formula/formula.d.ts @@ -1,10 +1,9 @@ -type RowRef = Pick; +type RowRef = { id: string; path: string; parent: T }; +interface Ref extends RowRef {} type FormulaContext = { row: Row; - ref: RowRef; - // storage: firebasestorage.Storage; - // db: FirebaseFirestore.Firestore; + ref: Ref; }; type Formula = (context: FormulaContext) => "PLACEHOLDER_OUTPUT_TYPE"; diff --git a/src/components/fields/Formula/useFormula.tsx b/src/components/fields/Formula/useFormula.tsx index 64d0fa6a..f6c44b75 100644 --- a/src/components/fields/Formula/useFormula.tsx +++ b/src/components/fields/Formula/useFormula.tsx @@ -5,7 +5,11 @@ import { useAtom } from "jotai"; import { TableRow, TableRowRef } from "@src/types/table"; import { tableColumnsOrderedAtom, tableScope } from "@src/atoms/tableScope"; -import { listenerFieldTypes, useDeepCompareMemoize } from "./util"; +import { + listenerFieldTypes, + serializeRef, + useDeepCompareMemoize, +} from "./util"; export const useFormula = ({ row, @@ -60,11 +64,13 @@ export const useFormula = ({ setLoading(false); }; - worker.postMessage({ - formulaFn, - row: JSON.stringify(availableFields), - ref: { id: ref.id, path: ref.path }, - }); + worker.postMessage( + JSON.stringify({ + formulaFn, + row: availableFields, + ref: serializeRef(ref.path), + }) + ); return () => { worker.terminate(); diff --git a/src/components/fields/Formula/util.tsx b/src/components/fields/Formula/util.tsx index 46d8cbee..71973a2e 100644 --- a/src/components/fields/Formula/util.tsx +++ b/src/components/fields/Formula/util.tsx @@ -24,6 +24,8 @@ import JsonDisplayCell from "@src/components/fields/Json/DisplayCell"; import CodeDisplayCell from "@src/components/fields/Code/DisplayCell"; import MarkdownDisplayCell from "@src/components/fields/Markdown/DisplayCell"; import CreatedByDisplayCell from "@src/components/fields/CreatedBy/DisplayCell"; +import { TableRowRef } from "@src/types/table"; +import { DocumentData, DocumentReference } from "firebase/firestore"; export function useDeepCompareMemoize(value: T) { const ref = useRef(value); @@ -65,7 +67,7 @@ export const outputFieldTypes = Object.values(FieldType).filter( ].includes(type) ); -export const defaultFn = `const formula:Formula = async ({ row })=> { +export const defaultFn = `const formula:Formula = async ({ row, ref })=> { // WRITE YOUR CODE ONLY BELOW THIS LINE. DO NOT WRITE CODE/COMMENTS OUTSIDE THE FUNCTION BODY // Example: @@ -120,3 +122,23 @@ export const getDisplayCell = (type: FieldType) => { return ShortTextDisplayCell; } }; + +export const serializeRef = (path: string, maxDepth = 20) => { + const pathArr = path.split("/"); + const serializedRef = { + path: pathArr.join("/"), + id: pathArr.pop(), + } as any; + let curr: TableRowRef | Partial> = + serializedRef; + let depth = 0; + while (pathArr.length > 0 && curr && depth < maxDepth) { + (curr.parent as any) = { + path: pathArr.join("/"), + id: pathArr.pop(), + } as Partial>; + curr = curr.parent as any; + maxDepth++; + } + return serializedRef; +}; diff --git a/src/components/fields/Formula/worker.ts b/src/components/fields/Formula/worker.ts index a0d85fd3..2b0af366 100644 --- a/src/components/fields/Formula/worker.ts +++ b/src/components/fields/Formula/worker.ts @@ -1,6 +1,6 @@ onmessage = async ({ data }) => { try { - const { formulaFn, row, ref } = data; + const { formulaFn, row, ref } = JSON.parse(data); const AsyncFunction = async function () {}.constructor as any; const [_, fnBody] = formulaFn.match(/=>\s*({?[\s\S]*}?)$/); if (!fnBody) return; @@ -9,7 +9,7 @@ onmessage = async ({ data }) => { "ref", `const fn = async () => \n${fnBody}\n return fn();` ); - const result = await fn(JSON.parse(row), ref); + const result = await fn(row, ref); postMessage({ result }); } catch (error: any) { console.error("Error: ", error); From efce39a11798abb6baf439d68c8e85b44e9ee5d4 Mon Sep 17 00:00:00 2001 From: iamanishroy <6275anishroy@gmail.com> Date: Tue, 7 Mar 2023 12:52:13 +0530 Subject: [PATCH 4/4] added custom hook [to be tested] --- src/components/Table/Table.tsx | 12 +++++++--- src/components/Table/useStateWithRef.ts | 29 +++++++++++++++++++++++++ src/components/Table/useTraceUpdates.ts | 22 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/components/Table/useStateWithRef.ts create mode 100644 src/components/Table/useTraceUpdates.ts diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 03285ac4..b8138583 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -1,5 +1,5 @@ import { useMemo, useRef, useState, useEffect, useCallback } from "react"; -import useStateRef from "react-usestateref"; +// import useStateRef from "react-usestateref"; // testing with useStateWithRef import { useAtom, useSetAtom } from "jotai"; import { useThrottledCallback } from "use-debounce"; import { @@ -41,6 +41,7 @@ import { useMenuAction } from "./useMenuAction"; import { useSaveColumnSizing } from "./useSaveColumnSizing"; import useHotKeys from "./useHotKey"; import type { TableRow, ColumnConfig } from "@src/types/table"; +import useStateWithRef from "./useStateWithRef"; // testing with useStateWithRef export const DEFAULT_ROW_HEIGHT = 41; export const DEFAULT_COL_WIDTH = 150; @@ -110,7 +111,9 @@ export default function Table({ // so the state can re-render `TableBody`, preventing virtualization // not detecting scroll if the container element was initially `null` const [containerEl, setContainerEl, containerRef] = - useStateRef(null); + // useStateRef(null); // <-- older approach with useStateRef + useStateWithRef(null); // <-- newer approach with custom hook + const gridRef = useRef(null); // Get column defs from table schema @@ -255,7 +258,10 @@ export default function Table({ return (
setContainerEl(el)} + ref={(el) => { + if (!el) return; + setContainerEl(el); + }} onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)} style={{ overflow: "auto", width: "100%", height: "100%" }} > diff --git a/src/components/Table/useStateWithRef.ts b/src/components/Table/useStateWithRef.ts new file mode 100644 index 00000000..be630977 --- /dev/null +++ b/src/components/Table/useStateWithRef.ts @@ -0,0 +1,29 @@ +import { + MutableRefObject, + useCallback, + useRef, + useSyncExternalStore, +} from "react"; + +// NOTE: This is not the final solution. But is a potential solution for this problem. +export default function useStateWithRef( + initialState: T +): [T, (newValue: T) => void, MutableRefObject] { + const value = useRef(initialState); + const get = useCallback(() => value.current, []); + const subscribers = useRef(new Set<() => void>()); + + const set = useCallback((newValue: T) => { + value.current = newValue; + subscribers.current.forEach((callback) => callback()); + }, []); + + const subscribe = useCallback((callback: () => void) => { + subscribers.current.add(callback); + return () => subscribers.current.delete(callback); + }, []); + + const state = useSyncExternalStore(subscribe, get); + + return [state, set, value]; +} diff --git a/src/components/Table/useTraceUpdates.ts b/src/components/Table/useTraceUpdates.ts new file mode 100644 index 00000000..9485c28a --- /dev/null +++ b/src/components/Table/useTraceUpdates.ts @@ -0,0 +1,22 @@ +import { useEffect, useRef } from "react"; + +// This hook is used to log changes to props in a component. +export default function useTraceUpdates( + props: { [key: string]: any }, + printMessage: string = "Changed props:" +) { + const prev = useRef(props); + useEffect(() => { + const changedProps = Object.entries(props).reduce((ps, [k, v]) => { + if (prev.current[k] !== v) { + // @ts-ignore + ps[k] = [prev.current[k], v]; + } + return ps; + }, {}); + if (Object.keys(changedProps).length > 0) { + console.log(printMessage, changedProps); + } + prev.current = props; + }); +}