From 645d36630d970040f9dbfed318da92ec6015a8af Mon Sep 17 00:00:00 2001 From: Bobby Wang Date: Sun, 29 Jan 2023 00:14:51 +0700 Subject: [PATCH 01/41] implement auto typing for await import --- package.json | 2 +- src/components/CodeEditor/CodeEditor.tsx | 19 ++++++++-- .../CodeEditor/useMonacoCustomizations.ts | 37 ++++++++----------- yarn.lock | 2 +- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 4fac6672..e31ea62e 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "match-sorter": "^6.3.1", "material-ui-popup-state": "^4.0.1", "mdi-material-ui": "^7.3.0", - "monaco-editor-auto-typings": "^0.4.0", + "monaco-editor-auto-typings": "^0.4.3", "notistack": "^2.0.4", "path-browserify": "^1.0.1", "pb-util": "^1.0.3", diff --git a/src/components/CodeEditor/CodeEditor.tsx b/src/components/CodeEditor/CodeEditor.tsx index 2abfd45b..638759ed 100644 --- a/src/components/CodeEditor/CodeEditor.tsx +++ b/src/components/CodeEditor/CodeEditor.tsx @@ -14,6 +14,7 @@ import FullScreenButton from "@src/components/FullScreenButton"; import { spreadSx } from "@src/utils/ui"; import githubLightTheme from "@src/components/CodeEditor/github-light-default.json"; import githubDarkTheme from "@src/components/CodeEditor/github-dark-default.json"; +import { AutoTypings, LocalStorageCache } from "monaco-editor-auto-typings"; export interface ICodeEditorProps extends Partial, @@ -47,7 +48,7 @@ export default function CodeEditor({ extraLibs, diagnosticsOptions, onUnmount, - defaultLanguage = "javascript", + defaultLanguage = "typescript", ...props }: ICodeEditorProps) { const theme = useTheme(); @@ -95,11 +96,22 @@ export default function CodeEditor({ monaco.editor.defineTheme("github-light", githubLightTheme as any); monaco.editor.defineTheme("github-dark", githubDarkTheme as any); }} - onMount={(editor) => { + {...props} + onMount={async (editor, monaco) => { + if (props.onMount) { + props.onMount(editor, monaco); + } if (onFocus) editor.onDidFocusEditorWidget(onFocus); if (onBlur) editor.onDidBlurEditorWidget(onBlur); + const autoTypings = await AutoTypings.create(editor, { + monaco: monaco, + sourceCache: new LocalStorageCache(), + debounceDuration: 500, // ms + onError: (e) => { + console.log("Auto typing error", e); + }, + }); }} - {...props} onValidate={onValidate_} theme={`github-${theme.palette.mode}`} options={{ @@ -113,6 +125,7 @@ export default function CodeEditor({ fixedOverflowWidgets: true, tabSize: 2, ...props.options, + language: "typescript", }} /> diff --git a/src/components/CodeEditor/useMonacoCustomizations.ts b/src/components/CodeEditor/useMonacoCustomizations.ts index e6d55533..c73fba31 100644 --- a/src/components/CodeEditor/useMonacoCustomizations.ts +++ b/src/components/CodeEditor/useMonacoCustomizations.ts @@ -13,15 +13,9 @@ 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"; @@ -77,24 +71,26 @@ export default function useMonacoCustomizations({ if (!monaco) return; try { - monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ + monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs, + module: monaco.languages.typescript.ModuleKind.CommonJS, target: monaco.languages.typescript.ScriptTarget.ES2020, allowNonTsExtensions: true, + typeRoots: ["node_modules/@types"], }); - monaco.languages.typescript.javascriptDefaults.addExtraLib(firestoreDefs); - monaco.languages.typescript.javascriptDefaults.addExtraLib( + monaco.languages.typescript.typescriptDefaults.addExtraLib(firestoreDefs); + monaco.languages.typescript.typescriptDefaults.addExtraLib( firebaseAuthDefs ); - monaco.languages.typescript.javascriptDefaults.addExtraLib( + monaco.languages.typescript.typescriptDefaults.addExtraLib( firebaseStorageDefs ); - monaco.languages.typescript.javascriptDefaults.addExtraLib( + monaco.languages.typescript.typescriptDefaults.addExtraLib( utilsDefs, "ts:filename/utils.d.ts" ); - monaco.languages.typescript.javascriptDefaults.addExtraLib(rowyUtilsDefs); + monaco.languages.typescript.typescriptDefaults.addExtraLib(rowyUtilsDefs); } catch (error) { console.error( "An error occurred during initialization of Monaco: ", @@ -108,7 +104,7 @@ export default function useMonacoCustomizations({ if (!monaco) return; if (!extraLibs) return; try { - monaco.languages.typescript.javascriptDefaults.addExtraLib( + monaco.languages.typescript.typescriptDefaults.addExtraLib( extraLibs.join("\n"), "ts:filename/extraLibs.d.ts" ); @@ -123,7 +119,7 @@ export default function useMonacoCustomizations({ if (!monaco) return; try { - monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({ + monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ ...JSON.parse(stringifiedDiagnosticsOptions), diagnosticCodesToIgnore: [ 1323, // remove dynamic import error @@ -143,11 +139,11 @@ export default function useMonacoCustomizations({ .map((row) => row[columnKey]) .filter((entry) => entry !== undefined) .map((entry) => JSON.stringify(entry)); - monaco?.languages.typescript.javascriptDefaults.addExtraLib( + monaco?.languages.typescript.typescriptDefaults.addExtraLib( `type ${interfaceName} = any;` ); // if (!samples || samples.length === 0) { - // monaco?.languages.typescript.javascriptDefaults.addExtraLib( + // monaco?.languages.typescript.typescriptDefaults.addExtraLib( // `type ${interfaceName} = any;` // ); // return; @@ -165,7 +161,7 @@ export default function useMonacoCustomizations({ // rendererOptions: { "just-types": "true" }, // }); // const newLib = result.lines.join("\n").replaceAll("export ", ""); - // monaco?.languages.typescript.javascriptDefaults.addExtraLib(newLib); + // monaco?.languages.typescript.typescriptDefaults.addExtraLib(newLib); //} }; @@ -184,12 +180,11 @@ export default function useMonacoCustomizations({ .join("\n")} } `; - monaco?.languages.typescript.javascriptDefaults.addExtraLib(secretsDef); + monaco?.languages.typescript.typescriptDefaults.addExtraLib(secretsDef); } catch (error) { console.error("Could not set secret definitions: ", error); } }; - //TODO: types const setBaseDefinitions = () => { const rowDefinition = tableColumnsOrdered @@ -208,13 +203,13 @@ export default function useMonacoCustomizations({ .map((key) => `"${key}"`) .join("|\n"); - monaco?.languages.typescript.javascriptDefaults.addExtraLib( + monaco?.languages.typescript.typescriptDefaults.addExtraLib( ["/**", " * extensions type configuration", " */", extensionsDefs].join( "\n" ), "ts:filename/extensions.d.ts" ); - monaco?.languages.typescript.javascriptDefaults.addExtraLib( + monaco?.languages.typescript.typescriptDefaults.addExtraLib( [ "// basic types that are used in all places", "declare var require: any;", diff --git a/yarn.lock b/yarn.lock index 8ff8ab68..2fd3cb4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8961,7 +8961,7 @@ mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "^1.2.6" -monaco-editor-auto-typings@^0.4.0: +monaco-editor-auto-typings@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/monaco-editor-auto-typings/-/monaco-editor-auto-typings-0.4.3.tgz#24498064ff876c641467815e65d54ae75f31cd7f" integrity sha512-7lpiWHkg8eX2DRJGApaFq1wGn95Ute3Xv4PLz36qFyTytzz86irjxwMl00JnOOGX/R0eUbfuudbEtkQh2VK9Gg== From 48194920f9f2e9d62dafd3947f83b13c1507b2aa Mon Sep 17 00:00:00 2001 From: il3ven Date: Sun, 18 Jun 2023 13:09:01 +0000 Subject: [PATCH 02/41] minimize `BuildLogsSnack` upon clicking fullscreen --- .../TableModals/CloudLogsModal/BuildLogs/BuildLogsSnack.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TableModals/CloudLogsModal/BuildLogs/BuildLogsSnack.tsx b/src/components/TableModals/CloudLogsModal/BuildLogs/BuildLogsSnack.tsx index ffc2f2e8..d1c4e176 100644 --- a/src/components/TableModals/CloudLogsModal/BuildLogs/BuildLogsSnack.tsx +++ b/src/components/TableModals/CloudLogsModal/BuildLogs/BuildLogsSnack.tsx @@ -150,6 +150,7 @@ export default function BuildLogsSnack({ timeRange: { type: "days", value: 7 }, buildLogExpanded: 0, }); + setExpanded(false); }} style={{ color: "white" }} > From dedda07b34f58e41be296ac7aa9708489b0584b4 Mon Sep 17 00:00:00 2001 From: Bobby Wang Date: Tue, 20 Jun 2023 00:53:33 +0800 Subject: [PATCH 03/41] add code editor quick fix to rowy.secrets.get --- .../CodeEditor/useMonacoCustomizations.ts | 67 +++++++++++++++++-- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/src/components/CodeEditor/useMonacoCustomizations.ts b/src/components/CodeEditor/useMonacoCustomizations.ts index 053b5f90..f4d3d123 100644 --- a/src/components/CodeEditor/useMonacoCustomizations.ts +++ b/src/components/CodeEditor/useMonacoCustomizations.ts @@ -1,5 +1,6 @@ import { useEffect } from "react"; import { useAtom } from "jotai"; +import { matchSorter } from "match-sorter"; import { tableScope, @@ -82,8 +83,6 @@ 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: ", @@ -124,13 +123,13 @@ export default function useMonacoCustomizations({ } }, [monaco, stringifiedDiagnosticsOptions]); - const setLoggingReplacementActions = () => { + const setReplacementActions = () => { if (!monaco) return; const { dispose } = monaco.languages.registerCodeActionProvider( "javascript", { provideCodeActions: (model, range, context, token) => { - const actions = context.markers + const consoleLogReplacements = context.markers .filter((error) => { return error.message.includes("Rowy Cloud Logging"); }) @@ -156,8 +155,63 @@ export default function useMonacoCustomizations({ isPreferred: true, }; }); + const secretNameReplacements = context.markers + .filter((error) => { + return error.message.includes( + "is not assignable to parameter of type 'SecretNames'" + ); + }) + .map((error) => { + const typoSecretName = model + .getLineContent(error.startLineNumber) + .slice(error.startColumn, error.endColumn - 2); + const similarSecretNames = + matchSorter(secretNames.secretNames ?? [], typoSecretName) ?? + []; + const otherSecretNames = + secretNames.secretNames?.filter( + (secretName) => !similarSecretNames.includes(secretName) + ) ?? []; + return [ + ...similarSecretNames.map((secretName) => ({ + title: `Replace with "${secretName}"`, + diagnostics: [error], + kind: "quickfix", + edit: { + edits: [ + { + resource: model.uri, + edit: { + range: error, + text: `"${secretName}"`, + }, + }, + ], + }, + isPreferred: true, + })), + ...otherSecretNames.map((secretName) => ({ + title: `Replace with "${secretName}"`, + diagnostics: [error], + kind: "quickfix", + edit: { + edits: [ + { + resource: model.uri, + edit: { + range: error, + text: `"${secretName}"`, + }, + }, + ], + }, + isPreferred: false, + })), + ]; + }) + .flat(); return { - actions: actions, + actions: [...consoleLogReplacements, ...secretNameReplacements], dispose: () => {}, }; }, @@ -169,7 +223,6 @@ export default function useMonacoCustomizations({ dispose(); }); }; - const addJsonFieldDefinition = async ( columnKey: string, interfaceName: string @@ -269,6 +322,8 @@ export default function useMonacoCustomizations({ } `; monaco?.languages.typescript.javascriptDefaults.addExtraLib(secretsDef); + + setReplacementActions(); }, [monaco, secretNames]); let boxSx: SystemStyleObject = { From aa8a52c4787cb18c92ca18d1b1a5d6760ecd48cc Mon Sep 17 00:00:00 2001 From: Nithin <78612244+nithinrdy@users.noreply.github.com> Date: Sun, 6 Aug 2023 13:36:44 +0530 Subject: [PATCH 04/41] Create atom to track active table's headers --- src/atoms/tableScope/table.ts | 3 +++ src/components/Table/Table.tsx | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/atoms/tableScope/table.ts b/src/atoms/tableScope/table.ts index f8d519c4..0f8b89f8 100644 --- a/src/atoms/tableScope/table.ts +++ b/src/atoms/tableScope/table.ts @@ -16,6 +16,7 @@ import { BulkWriteFunction, } from "@src/types/table"; import { updateRowData } from "@src/utils/table"; +import { Header } from "@tanstack/react-table"; /** Root atom from which others are derived */ export const tableIdAtom = atom(""); @@ -47,6 +48,8 @@ export const tableColumnsOrderedAtom = atom((get) => { ["desc", "asc"] ); }); +/** Store the headers of the table as an array. */ +export const tableHeadersAtom = atom[]>([]); /** Reducer function to convert from array of columns to columns object */ export const tableColumnsReducer = ( a: Record, diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 892eabf9..f2f7d551 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -9,6 +9,7 @@ import { } from "@tanstack/react-table"; import type { ColumnPinningState, + Header, VisibilityState, } from "@tanstack/react-table"; import { DropResult } from "react-beautiful-dnd"; @@ -25,6 +26,7 @@ import EmptyState from "@src/components/EmptyState"; import { tableScope, tableSchemaAtom, + tableHeadersAtom, tableColumnsOrderedAtom, tableRowsAtom, tableNextPageAtom, @@ -96,6 +98,7 @@ export default function Table({ }: ITableProps) { const [tableSchema] = useAtom(tableSchemaAtom, tableScope); const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope); + const setTableHeaders = useSetAtom(tableHeadersAtom, tableScope); const [tableRows] = useAtom(tableRowsAtom, tableScope); const [tableNextPage] = useAtom(tableNextPageAtom, tableScope); const [tablePage, setTablePage] = useAtom(tablePageAtom, tableScope); @@ -185,6 +188,17 @@ export default function Table({ state: { ...prev.state, columnVisibility, columnPinning, columnSizing }, onColumnSizingChange: setColumnSizing, })); + // Store the headers of the current table as an array. + // Initial value is an empty array, which progressively grows as headerGroups are cycled through + // and their headers are added to the array. + setTableHeaders( + table.getHeaderGroups().reduce((currentHeadersList, headerGroup) => { + return [ + ...currentHeadersList, + ...headerGroup.headers.map((header) => header), + ]; + }, [] as Header[]) + ); // Get rows and columns for virtualization const { rows } = table.getRowModel(); const leafColumns = table.getVisibleLeafColumns(); From 7deb40039dde5d4534780bbc61570e6ee59588b5 Mon Sep 17 00:00:00 2001 From: Nithin <78612244+nithinrdy@users.noreply.github.com> Date: Sun, 6 Aug 2023 13:38:14 +0530 Subject: [PATCH 05/41] Add modal to set column width --- src/atoms/tableScope/ui.ts | 2 +- src/components/ColumnMenu/ColumnMenu.tsx | 11 +++ src/components/ColumnModals/ColumnModals.tsx | 4 ++ .../ColumnModals/SetColumnWidthModal.tsx | 71 +++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/components/ColumnModals/SetColumnWidthModal.tsx diff --git a/src/atoms/tableScope/ui.ts b/src/atoms/tableScope/ui.ts index b170d250..b5639790 100644 --- a/src/atoms/tableScope/ui.ts +++ b/src/atoms/tableScope/ui.ts @@ -41,7 +41,7 @@ export const columnMenuAtom = atom<{ * ``` */ export const columnModalAtom = atomWithHash<{ - type: "new" | "name" | "type" | "config"; + type: "new" | "name" | "type" | "config" | "setColumnWidth"; columnKey?: string; index?: number; } | null>("columnModal", null, { replaceState: true }); diff --git a/src/components/ColumnMenu/ColumnMenu.tsx b/src/components/ColumnMenu/ColumnMenu.tsx index b1944703..1a4d3246 100644 --- a/src/components/ColumnMenu/ColumnMenu.tsx +++ b/src/components/ColumnMenu/ColumnMenu.tsx @@ -24,6 +24,7 @@ import { } from "@src/assets/icons"; import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"; import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward"; +import StraightenIcon from "@mui/icons-material/Straighten"; import EditIcon from "@mui/icons-material/EditOutlined"; import SettingsIcon from "@mui/icons-material/SettingsOutlined"; import EvalIcon from "@mui/icons-material/PlayCircleOutline"; @@ -263,6 +264,16 @@ export default function ColumnMenu({ : column.type ), }, + { + key: "setColumnWidth", + label: "Set Column Width", + icon: , + onClick: () => { + openColumnModal({ type: "setColumnWidth", columnKey: column.key }); + handleClose(); + }, + disabled: !column.resizable, + }, ]; const configActions: IMenuContentsProps["menuItems"] = [ diff --git a/src/components/ColumnModals/ColumnModals.tsx b/src/components/ColumnModals/ColumnModals.tsx index 2dd37f20..99429f4c 100644 --- a/src/components/ColumnModals/ColumnModals.tsx +++ b/src/components/ColumnModals/ColumnModals.tsx @@ -5,6 +5,7 @@ import NewColumnModal from "./NewColumnModal"; import NameChangeModal from "./NameChangeModal"; import TypeChangeModal from "./TypeChangeModal"; import ColumnConfigModal from "./ColumnConfigModal"; +import SetColumnWidthModal from "./SetColumnWidthModal"; import { tableScope, @@ -40,5 +41,8 @@ export default function ColumnModals() { if (columnModal.type === "config") return ; + if (columnModal.type === "setColumnWidth") + return ; + return null; } diff --git a/src/components/ColumnModals/SetColumnWidthModal.tsx b/src/components/ColumnModals/SetColumnWidthModal.tsx new file mode 100644 index 00000000..4bdb3496 --- /dev/null +++ b/src/components/ColumnModals/SetColumnWidthModal.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from "react"; +import { IColumnModalProps } from "."; +import { selectedCellAtom, tableHeadersAtom } from "@src/atoms/tableScope"; +import { tableScope } from "@src/atoms/tableScope"; +import { useAtom } from "jotai"; + +import { TextField } from "@mui/material"; +import Modal from "@src/components/Modal"; +import { TableRow } from "@src/types/table"; +import { Header } from "@tanstack/react-table"; + +export default function ResizeColumnModal({ + onClose, + column, +}: IColumnModalProps) { + const [newWidth, setWidth] = useState(0); + const [selectedCell] = useAtom(selectedCellAtom, tableScope); + const [tableHeaders] = useAtom(tableHeadersAtom, tableScope); + const [selectedHeader, setSelectedHeader] = useState | null>(null); + + useEffect(() => { + if (selectedCell && tableHeaders) { + setSelectedHeader( + tableHeaders.find((h) => h.id === selectedCell?.columnKey) ?? null + ); + } + }, [selectedCell, tableHeaders]); + + useEffect(() => { + selectedHeader && setWidth(selectedHeader.getSize()); + }, [selectedHeader]); + + return ( + setWidth(Number(e.target.value))} + /> + } + actions={{ + primary: { + onMouseDown: (e) => { + selectedHeader && + selectedHeader.getResizeHandler()({ + clientX: e.clientX - (newWidth - selectedHeader.getSize()), + } as any); + onClose(); + }, + children: "Update", + }, + secondary: { + onClick: onClose, + children: "Cancel", + }, + }} + /> + ); +} From 693f1fb91770e320f9c2fbb6089248b5e1c9f024 Mon Sep 17 00:00:00 2001 From: Nithin <78612244+nithinrdy@users.noreply.github.com> Date: Sun, 6 Aug 2023 18:41:32 +0530 Subject: [PATCH 06/41] Update on Mouse up instead Performing the update on mouse-down caused this issue where dragging the mouse around after clicking would result in the edge of the column to move along with it. Updating on mouse-up instead avoids this issue. --- src/components/ColumnModals/SetColumnWidthModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ColumnModals/SetColumnWidthModal.tsx b/src/components/ColumnModals/SetColumnWidthModal.tsx index 4bdb3496..4a8a9d00 100644 --- a/src/components/ColumnModals/SetColumnWidthModal.tsx +++ b/src/components/ColumnModals/SetColumnWidthModal.tsx @@ -52,7 +52,7 @@ export default function ResizeColumnModal({ } actions={{ primary: { - onMouseDown: (e) => { + onMouseUp: (e) => { selectedHeader && selectedHeader.getResizeHandler()({ clientX: e.clientX - (newWidth - selectedHeader.getSize()), From 3589c568b45c330b04fbf0bd847eb75748fbb088 Mon Sep 17 00:00:00 2001 From: Nithin <78612244+nithinrdy@users.noreply.github.com> Date: Mon, 7 Aug 2023 04:13:32 +0530 Subject: [PATCH 07/41] Minor changes - Better component name - Remove unnecessary map() call --- src/components/ColumnModals/SetColumnWidthModal.tsx | 2 +- src/components/Table/Table.tsx | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/ColumnModals/SetColumnWidthModal.tsx b/src/components/ColumnModals/SetColumnWidthModal.tsx index 4a8a9d00..1574064b 100644 --- a/src/components/ColumnModals/SetColumnWidthModal.tsx +++ b/src/components/ColumnModals/SetColumnWidthModal.tsx @@ -9,7 +9,7 @@ import Modal from "@src/components/Modal"; import { TableRow } from "@src/types/table"; import { Header } from "@tanstack/react-table"; -export default function ResizeColumnModal({ +export default function SetColumnWidthModal({ onClose, column, }: IColumnModalProps) { diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index f2f7d551..d5615b53 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -193,10 +193,7 @@ export default function Table({ // and their headers are added to the array. setTableHeaders( table.getHeaderGroups().reduce((currentHeadersList, headerGroup) => { - return [ - ...currentHeadersList, - ...headerGroup.headers.map((header) => header), - ]; + return [...currentHeadersList, ...headerGroup.headers]; }, [] as Header[]) ); // Get rows and columns for virtualization From aece2f6c84ae0428f8ad91a7e102c0b1ffb0749e Mon Sep 17 00:00:00 2001 From: il3ven Date: Sat, 19 Aug 2023 17:54:11 +0000 Subject: [PATCH 08/41] update secretsDef in useMonacoCustomizations The enum generated by the previous template string would have had syntax errors if the secret contained hyphens. This commit fixes it. --- src/components/CodeEditor/useMonacoCustomizations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/CodeEditor/useMonacoCustomizations.ts b/src/components/CodeEditor/useMonacoCustomizations.ts index f4d3d123..3fd77bcf 100644 --- a/src/components/CodeEditor/useMonacoCustomizations.ts +++ b/src/components/CodeEditor/useMonacoCustomizations.ts @@ -314,10 +314,10 @@ export default function useMonacoCustomizations({ if (!secretNames.secretNames) return; const secretsDef = `type SecretNames = ${secretNames.secretNames .map((secret: string) => `"${secret}"`) - .join(" | ")} + .join(" | ")} \n enum secrets { ${secretNames.secretNames - .map((secret: string) => `${secret} = "${secret}"`) + .map((secret: string) => `"${secret}" = "${secret}"`) .join("\n")} } `; From e8e9d09894732ce276592d1fa8f00fbbd937fb20 Mon Sep 17 00:00:00 2001 From: Vishnu Nithin Reddy <78612244+nithinrdy@users.noreply.github.com> Date: Sun, 20 Aug 2023 13:00:25 +0530 Subject: [PATCH 09/41] Use the setColumnSizing method instead; store the entire table in an atom, not just the table headers. --- src/atoms/tableScope/table.ts | 4 +- .../ColumnModals/SetColumnWidthModal.tsx | 41 ++++++++----------- src/components/Table/Table.tsx | 16 +++----- 3 files changed, 23 insertions(+), 38 deletions(-) diff --git a/src/atoms/tableScope/table.ts b/src/atoms/tableScope/table.ts index 0f8b89f8..31792826 100644 --- a/src/atoms/tableScope/table.ts +++ b/src/atoms/tableScope/table.ts @@ -16,7 +16,7 @@ import { BulkWriteFunction, } from "@src/types/table"; import { updateRowData } from "@src/utils/table"; -import { Header } from "@tanstack/react-table"; +import { Table } from "@tanstack/react-table"; /** Root atom from which others are derived */ export const tableIdAtom = atom(""); @@ -49,7 +49,7 @@ export const tableColumnsOrderedAtom = atom((get) => { ); }); /** Store the headers of the table as an array. */ -export const tableHeadersAtom = atom[]>([]); +export const reactTableAtom = atom | null>(null); /** Reducer function to convert from array of columns to columns object */ export const tableColumnsReducer = ( a: Record, diff --git a/src/components/ColumnModals/SetColumnWidthModal.tsx b/src/components/ColumnModals/SetColumnWidthModal.tsx index 1574064b..5c5e17fa 100644 --- a/src/components/ColumnModals/SetColumnWidthModal.tsx +++ b/src/components/ColumnModals/SetColumnWidthModal.tsx @@ -1,37 +1,31 @@ import { useEffect, useState } from "react"; import { IColumnModalProps } from "."; -import { selectedCellAtom, tableHeadersAtom } from "@src/atoms/tableScope"; +import { reactTableAtom } from "@src/atoms/tableScope"; import { tableScope } from "@src/atoms/tableScope"; import { useAtom } from "jotai"; import { TextField } from "@mui/material"; import Modal from "@src/components/Modal"; -import { TableRow } from "@src/types/table"; -import { Header } from "@tanstack/react-table"; export default function SetColumnWidthModal({ onClose, column, }: IColumnModalProps) { + const [reactTable] = useAtom(reactTableAtom, tableScope); const [newWidth, setWidth] = useState(0); - const [selectedCell] = useAtom(selectedCellAtom, tableScope); - const [tableHeaders] = useAtom(tableHeadersAtom, tableScope); - const [selectedHeader, setSelectedHeader] = useState | null>(null); useEffect(() => { - if (selectedCell && tableHeaders) { - setSelectedHeader( - tableHeaders.find((h) => h.id === selectedCell?.columnKey) ?? null - ); - } - }, [selectedCell, tableHeaders]); + setWidth(reactTable?.getAllColumns()[column.index].getSize() || 0); + }, [reactTable, column]); - useEffect(() => { - selectedHeader && setWidth(selectedHeader.getSize()); - }, [selectedHeader]); + const handleUpdate = () => { + reactTable?.setColumnSizing((old) => { + const newSizing = { ...old }; + newSizing[column.name] = newWidth; + return newSizing; + }); + onClose(); + }; return ( setWidth(Number(e.target.value))} + onKeyDown={(e) => { + e.key === "Enter" && handleUpdate(); + }} /> } actions={{ primary: { - onMouseUp: (e) => { - selectedHeader && - selectedHeader.getResizeHandler()({ - clientX: e.clientX - (newWidth - selectedHeader.getSize()), - } as any); - onClose(); - }, + onClick: handleUpdate, children: "Update", }, secondary: { diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index d5615b53..b0c46ae6 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -9,7 +9,6 @@ import { } from "@tanstack/react-table"; import type { ColumnPinningState, - Header, VisibilityState, } from "@tanstack/react-table"; import { DropResult } from "react-beautiful-dnd"; @@ -26,7 +25,7 @@ import EmptyState from "@src/components/EmptyState"; import { tableScope, tableSchemaAtom, - tableHeadersAtom, + reactTableAtom, tableColumnsOrderedAtom, tableRowsAtom, tableNextPageAtom, @@ -98,7 +97,6 @@ export default function Table({ }: ITableProps) { const [tableSchema] = useAtom(tableSchemaAtom, tableScope); const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope); - const setTableHeaders = useSetAtom(tableHeadersAtom, tableScope); const [tableRows] = useAtom(tableRowsAtom, tableScope); const [tableNextPage] = useAtom(tableNextPageAtom, tableScope); const [tablePage, setTablePage] = useAtom(tablePageAtom, tableScope); @@ -188,14 +186,10 @@ export default function Table({ state: { ...prev.state, columnVisibility, columnPinning, columnSizing }, onColumnSizingChange: setColumnSizing, })); - // Store the headers of the current table as an array. - // Initial value is an empty array, which progressively grows as headerGroups are cycled through - // and their headers are added to the array. - setTableHeaders( - table.getHeaderGroups().reduce((currentHeadersList, headerGroup) => { - return [...currentHeadersList, ...headerGroup.headers]; - }, [] as Header[]) - ); + const setReactTable = useSetAtom(reactTableAtom, tableScope); + useMemo(() => { + setReactTable(table); + }, [table, setReactTable]); // Get rows and columns for virtualization const { rows } = table.getRowModel(); const leafColumns = table.getVisibleLeafColumns(); From 8316e6ac6a22259e143cbdfe32f7b8c0f75f366d Mon Sep 17 00:00:00 2001 From: Vishnu Nithin Reddy <78612244+nithinrdy@users.noreply.github.com> Date: Sun, 20 Aug 2023 13:11:00 +0530 Subject: [PATCH 10/41] Add comments --- src/atoms/tableScope/table.ts | 2 +- src/components/ColumnModals/SetColumnWidthModal.tsx | 2 ++ src/components/Table/Table.tsx | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/atoms/tableScope/table.ts b/src/atoms/tableScope/table.ts index 31792826..a95995a0 100644 --- a/src/atoms/tableScope/table.ts +++ b/src/atoms/tableScope/table.ts @@ -48,7 +48,7 @@ export const tableColumnsOrderedAtom = atom((get) => { ["desc", "asc"] ); }); -/** Store the headers of the table as an array. */ +/** Store the table */ export const reactTableAtom = atom | null>(null); /** Reducer function to convert from array of columns to columns object */ export const tableColumnsReducer = ( diff --git a/src/components/ColumnModals/SetColumnWidthModal.tsx b/src/components/ColumnModals/SetColumnWidthModal.tsx index 5c5e17fa..bdfde0d3 100644 --- a/src/components/ColumnModals/SetColumnWidthModal.tsx +++ b/src/components/ColumnModals/SetColumnWidthModal.tsx @@ -15,12 +15,14 @@ export default function SetColumnWidthModal({ const [newWidth, setWidth] = useState(0); useEffect(() => { + // Set the initial width to the current column width once the table is fetched. setWidth(reactTable?.getAllColumns()[column.index].getSize() || 0); }, [reactTable, column]); const handleUpdate = () => { reactTable?.setColumnSizing((old) => { const newSizing = { ...old }; + // Set the new width for the column. newSizing[column.name] = newWidth; return newSizing; }); diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index b0c46ae6..a7e00871 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -100,6 +100,7 @@ export default function Table({ const [tableRows] = useAtom(tableRowsAtom, tableScope); const [tableNextPage] = useAtom(tableNextPageAtom, tableScope); const [tablePage, setTablePage] = useAtom(tablePageAtom, tableScope); + const setReactTable = useSetAtom(reactTableAtom, tableScope); const updateColumn = useSetAtom(updateColumnAtom, tableScope); @@ -186,7 +187,7 @@ export default function Table({ state: { ...prev.state, columnVisibility, columnPinning, columnSizing }, onColumnSizingChange: setColumnSizing, })); - const setReactTable = useSetAtom(reactTableAtom, tableScope); + // Update the reactTable atom when table state changes. useMemo(() => { setReactTable(table); }, [table, setReactTable]); From ef58d37ed6ace4e107550d31c5898ef0f8405bc5 Mon Sep 17 00:00:00 2001 From: il3ven Date: Sun, 20 Aug 2023 10:49:39 +0000 Subject: [PATCH 11/41] show set column width option even if column.resizable is undefined --- src/components/ColumnMenu/ColumnMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ColumnMenu/ColumnMenu.tsx b/src/components/ColumnMenu/ColumnMenu.tsx index 1a4d3246..d676f024 100644 --- a/src/components/ColumnMenu/ColumnMenu.tsx +++ b/src/components/ColumnMenu/ColumnMenu.tsx @@ -272,7 +272,7 @@ export default function ColumnMenu({ openColumnModal({ type: "setColumnWidth", columnKey: column.key }); handleClose(); }, - disabled: !column.resizable, + disabled: column.resizable === false, }, ]; From 7ca6fe3be6fdfab7354ec2cead7c5e9ae69ac46d Mon Sep 17 00:00:00 2001 From: il3ven Date: Sun, 20 Aug 2023 10:50:23 +0000 Subject: [PATCH 12/41] add
tag for handling submit and fix an error --- .../ColumnModals/SetColumnWidthModal.tsx | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/components/ColumnModals/SetColumnWidthModal.tsx b/src/components/ColumnModals/SetColumnWidthModal.tsx index bdfde0d3..a5071d64 100644 --- a/src/components/ColumnModals/SetColumnWidthModal.tsx +++ b/src/components/ColumnModals/SetColumnWidthModal.tsx @@ -23,7 +23,7 @@ export default function SetColumnWidthModal({ reactTable?.setColumnSizing((old) => { const newSizing = { ...old }; // Set the new width for the column. - newSizing[column.name] = newWidth; + newSizing[column.fieldName] = newWidth; return newSizing; }); onClose(); @@ -35,24 +35,30 @@ export default function SetColumnWidthModal({ title="Set Column Width" maxWidth="xs" children={ - setWidth(Number(e.target.value))} - onKeyDown={(e) => { - e.key === "Enter" && handleUpdate(); + { + e.preventDefault(); + handleUpdate(); }} - /> + > + setWidth(Number(e.target.value))} + /> + } actions={{ primary: { - onClick: handleUpdate, children: "Update", + type: "submit", + form: "column-width-modal", }, secondary: { onClick: onClose, From 67a4172b7ff8dbfde59c33c942d1dc190322fac0 Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Mon, 21 Aug 2023 01:46:05 +0530 Subject: [PATCH 13/41] Create Filter.tsx --- src/components/fields/Array/Filter.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/components/fields/Array/Filter.tsx diff --git a/src/components/fields/Array/Filter.tsx b/src/components/fields/Array/Filter.tsx new file mode 100644 index 00000000..d30a60f2 --- /dev/null +++ b/src/components/fields/Array/Filter.tsx @@ -0,0 +1,20 @@ +import { IFilterOperator } from "@src/components/fields/types"; + +export const operators: IFilterOperator[] = [ + { + label: "equals", + value: "==", + }, + { + label: "not equals", + value: "!=", + }, + { + label: "contains the following", + value: "array-contains", + }, + { + label: "contains atleast one of the following", + value: "array-contains-any", + }, +]; From df6ed6958ffc00e3464f3cc884d063a8375f8127 Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Mon, 21 Aug 2023 01:46:14 +0530 Subject: [PATCH 14/41] Update index.tsx --- src/components/fields/Array/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/fields/Array/index.tsx b/src/components/fields/Array/index.tsx index fe00851e..4acde5d9 100644 --- a/src/components/fields/Array/index.tsx +++ b/src/components/fields/Array/index.tsx @@ -5,6 +5,7 @@ import { IFieldConfig, FieldType } from "@src/components/fields/types"; import withRenderTableCell from "@src/components/Table/TableCell/withRenderTableCell"; import DisplayCell from "./DisplayCell"; +import { operators } from "./Filter"; import BasicContextMenuActions from "@src/components/Table/ContextMenu/BasicCellContextMenuActions"; @@ -27,6 +28,7 @@ export const config: IFieldConfig = { popoverProps: { PaperProps: { sx: { p: 1, minWidth: "200px" } } }, }), SideDrawerField, + filter: { operators }, requireConfiguration: false, contextMenuActions: BasicContextMenuActions, }; From bf9ae7bd0e0d61cfed141cad34c1201bc43e0731 Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Mon, 21 Aug 2023 01:46:28 +0530 Subject: [PATCH 15/41] Update index.tsx --- src/components/fields/Array/SideDrawerField/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/fields/Array/SideDrawerField/index.tsx b/src/components/fields/Array/SideDrawerField/index.tsx index 4f6efd09..91831ca6 100644 --- a/src/components/fields/Array/SideDrawerField/index.tsx +++ b/src/components/fields/Array/SideDrawerField/index.tsx @@ -120,7 +120,7 @@ export default function ArraySideDrawerField({ }: ISideDrawerFieldProps) { const handleAddNew = (fieldType: ArraySupportedFiledTypes) => { onChange([...(value || []), SupportedTypes[fieldType].initialValue]); - onDirty(true); + if (onDirty) onDirty(true); }; const handleChange = (newValue_: any, indexUpdated: number) => { onChange( @@ -137,13 +137,13 @@ export default function ArraySideDrawerField({ const handleRemove = (index: number) => { value.splice(index, 1); onChange([...value]); - onDirty(true); + if (onDirty) onDirty(true); onSubmit(); }; const handleClearField = () => { onChange([]); - onSubmit(); + if (onSubmit) onSubmit(); }; function handleOnDragEnd(result: DropResult) { @@ -157,7 +157,7 @@ export default function ArraySideDrawerField({ const [removed] = list.splice(result.source.index, 1); list.splice(result.destination.index, 0, removed); onChange(list); - onSubmit(); + if (onSubmit) onSubmit(); } if (value === undefined || Array.isArray(value)) { From cca3e0afc22c3bc7be8b8c2b7d3f5142c2bfeb08 Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Mon, 21 Aug 2023 01:46:57 +0530 Subject: [PATCH 16/41] Update useFirestoreCollectionWithAtom.ts --- src/hooks/useFirestoreCollectionWithAtom.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/hooks/useFirestoreCollectionWithAtom.ts b/src/hooks/useFirestoreCollectionWithAtom.ts index 16a7fb92..0681d283 100644 --- a/src/hooks/useFirestoreCollectionWithAtom.ts +++ b/src/hooks/useFirestoreCollectionWithAtom.ts @@ -405,6 +405,13 @@ export const tableFiltersToFirestoreFilters = (filters: TableFilter[]) => { where(filter.key.concat(".hex"), "!=", filter.value.hex.toString()) ); continue; + } else if (filter.operator === "array-contains") { + if (!filter.value || !filter.value.length) continue; + // make the value as a singular string + firestoreFilters.push( + where(filter.key, filter.operator as WhereFilterOp, filter.value[0]) + ); + continue; } firestoreFilters.push( where(filter.key, filter.operator as WhereFilterOp, filter.value) From e94daab98541f10912ac3f35e1b8734ac3065cbc Mon Sep 17 00:00:00 2001 From: il3ven Date: Mon, 21 Aug 2023 09:40:57 +0000 Subject: [PATCH 17/41] Revert "Merge pull request #1256 from staticGuru/filter_url" This reverts commit 6819bf1f5f40161ae78fe19e4b24e31e5ad72eaa, reversing changes made to 70498bcc0544d2d17e4cb1586645dd6a5f87ae6d. --- .../TableToolbar/Filters/Filters.tsx | 70 ++----------------- src/components/TableToolbar/Filters/utils.tsx | 27 ------- src/types/table.d.ts | 3 +- 3 files changed, 6 insertions(+), 94 deletions(-) delete mode 100644 src/components/TableToolbar/Filters/utils.tsx diff --git a/src/components/TableToolbar/Filters/Filters.tsx b/src/components/TableToolbar/Filters/Filters.tsx index 868d6f86..8ebc32ff 100644 --- a/src/components/TableToolbar/Filters/Filters.tsx +++ b/src/components/TableToolbar/Filters/Filters.tsx @@ -1,10 +1,7 @@ -/* eslint-disable react-hooks/exhaustive-deps */ import { useState, useEffect } from "react"; import { useAtom } from "jotai"; import useMemoValue from "use-memo-value"; import { isEmpty, isDate } from "lodash-es"; -import { useSearchParams } from "react-router-dom"; -import { useSnackbar } from "notistack"; import { Tab, @@ -22,7 +19,6 @@ import TabPanel from "@mui/lab/TabPanel"; import FiltersPopover from "./FiltersPopover"; import FilterInputs from "./FilterInputs"; -import { changePageUrl, separateOperands } from "./utils"; import { projectScope, @@ -66,17 +62,12 @@ export default function Filters() { const [, setTableSorts] = useAtom(tableSortsAtom, tableScope); const [updateTableSchema] = useAtom(updateTableSchemaAtom, tableScope); const [{ defaultQuery }] = useAtom(tableFiltersPopoverAtom, tableScope); + const tableFilterInputs = useFilterInputs(tableColumnsOrdered); const setTableQuery = tableFilterInputs.setQuery; const userFilterInputs = useFilterInputs(tableColumnsOrdered, defaultQuery); const setUserQuery = userFilterInputs.setQuery; - const { availableFilters, filterColumns } = userFilterInputs; - const [searchParams] = useSearchParams(); - const { enqueueSnackbar } = useSnackbar(); - useEffect(() => { - let isFiltered = searchParams.get("filter"); - if (isFiltered) updateUserFilter(isFiltered); - }, [searchParams]); + const { availableFilters } = userFilterInputs; // Get table filters & user filters from config documents const tableFilters = useMemoValue( @@ -91,44 +82,6 @@ export default function Filters() { const hasTableFilters = Array.isArray(tableFilters) && tableFilters.length > 0; const hasUserFilters = Array.isArray(userFilters) && userFilters.length > 0; - function updateUserFilter(str: string) { - let { operators, operands = [] } = separateOperands(str); - if (!operators.length) return; - if (operators.length) { - let appliedFilter: TableFilter[] = []; - appliedFilter = [ - { - key: operands[0], - operator: operators[0], - value: Number(operands[1]), - }, - ]; - let isValidFilter = checkFilterValidation(appliedFilter[0]); - if (isValidFilter) { - setOverrideTableFilters(true); - setUserFilters(appliedFilter); - } else { - enqueueSnackbar("Oops, Invalid filter!!!", { variant: "error" }); - setUserFilters([]); - setOverrideTableFilters(false); - userFilterInputs.resetQuery(); - } - } - } - function checkFilterValidation(filter: TableFilter): boolean { - let isFilterableColumn = filterColumns?.filter( - (item) => - item.key === filter.key || - item.label === filter.key || - item.type === filter.key - ); - if (!isFilterableColumn?.length) return false; - filter.key = isFilterableColumn?.[0]?.value; - filter.operator = filter.operator === "-is-" ? "id-equal" : filter.operator; - filter.value = - filter.operator === "id-equal" ? filter.value.toString() : filter.value; - return true; - } // Set the local table filter useEffect(() => { @@ -156,7 +109,7 @@ export default function Filters() { } else if (hasUserFilters) { filtersToApply = userFilters; } - updatePageURL(filtersToApply); + setLocalFilters(filtersToApply); // Reset order so we don’t have to make a new index if (filtersToApply.length) { @@ -167,6 +120,7 @@ export default function Filters() { hasUserFilters, setLocalFilters, setTableSorts, + setTableQuery, tableFilters, tableFiltersOverridable, setUserQuery, @@ -219,21 +173,7 @@ export default function Filters() { if (updateUserSettings && filters) updateUserSettings({ tables: { [`${tableId}`]: { filters } } }); }; - function updatePageURL(filters: TableFilter[]) { - if (!filters.length) { - changePageUrl(); - } else { - const [filter] = filters; - const fieldName = filter.key === "_rowy_ref.id" ? "ID" : filter.key; - const operator = - filter.operator === "id-equal" ? "-is-" : filter.operator; - const formattedValue = availableFilters?.valueFormatter - ? availableFilters.valueFormatter(filter.value, filter.operator) - : filter.value.toString(); - const queryParams = `?filter=${fieldName}${operator}${formattedValue}`; - changePageUrl(queryParams); - } - } + return ( `\\${op}`).join("|"), "g") - ); - return { operators, operands }; -} -export function changePageUrl(newURL: string | undefined = URL) { - if (newURL !== URL) { - newURL = URL + newURL; - } - window.history.pushState({ path: newURL }, "", newURL); -} - -function findOperators(str: string) { - const operators = [">=", "<=", ">", "<", "==", "!=", "=", "-is-"]; - const regex = new RegExp(operators.map((op) => `\\${op}`).join("|"), "g"); - return str.match(regex) || []; -} diff --git a/src/types/table.d.ts b/src/types/table.d.ts index 5b33e6d7..3ed4b7c3 100644 --- a/src/types/table.d.ts +++ b/src/types/table.d.ts @@ -193,8 +193,7 @@ export type TableFilter = { | "time-minute-equal" | "id-equal" | "color-equal" - | "color-not-equal" - | "-is-"; + | "color-not-equal"; value: any; }; From f7fdf57c36cb52110c2f0480b74b3796cf948524 Mon Sep 17 00:00:00 2001 From: Manmeet Date: Tue, 13 Jun 2023 22:27:08 +0530 Subject: [PATCH 18/41] add multi filters support --- src/atoms/tableScope/table.ts | 2 + .../TableToolbar/Filters/FilterInputs.tsx | 114 ++- .../Filters/FilterInputsCollection.tsx | 123 ++++ .../TableToolbar/Filters/Filters.tsx | 168 +++-- .../TableToolbar/Filters/FiltersPopover.tsx | 103 +-- .../TableToolbar/Filters/useFilterInputs.ts | 89 ++- src/hooks/useFirestoreCollectionWithAtom.ts | 55 +- .../TableSourceFirestore.tsx | 7 + src/types/settings.d.ts | 1 + src/types/table.d.ts | 5 +- yarn.lock | 669 +++++++++--------- 11 files changed, 856 insertions(+), 480 deletions(-) create mode 100644 src/components/TableToolbar/Filters/FilterInputsCollection.tsx diff --git a/src/atoms/tableScope/table.ts b/src/atoms/tableScope/table.ts index f8d519c4..b88e34c4 100644 --- a/src/atoms/tableScope/table.ts +++ b/src/atoms/tableScope/table.ts @@ -59,6 +59,8 @@ export const tableColumnsReducer = ( /** Filters applied to the local view */ export const tableFiltersAtom = atom([]); +/** Join operator applied to mulitple filters */ +export const tableFiltersJoinAtom = atom<"AND" | "OR">("AND"); /** Sorts applied to the local view */ export const tableSortsAtom = atom([]); diff --git a/src/components/TableToolbar/Filters/FilterInputs.tsx b/src/components/TableToolbar/Filters/FilterInputs.tsx index 24fa1b55..d8930dc3 100644 --- a/src/components/TableToolbar/Filters/FilterInputs.tsx +++ b/src/components/TableToolbar/Filters/FilterInputs.tsx @@ -10,6 +10,7 @@ import { Typography, TextField, InputLabel, + IconButton, } from "@mui/material"; import ColumnSelect from "@src/components/Table/ColumnSelect"; @@ -17,11 +18,28 @@ import FieldSkeleton from "@src/components/SideDrawer/FieldSkeleton"; import IdFilterInput from "./IdFilterInput"; import { InlineErrorFallback } from "@src/components/ErrorFallback"; -import type { useFilterInputs } from "./useFilterInputs"; import { getFieldType, getFieldProp } from "@src/components/fields"; +import type { IFieldConfig } from "@src/components/fields/types"; -export interface IFilterInputsProps extends ReturnType { +import { TableFilter } from "@src/types/table"; + +import DeleteIcon from "@mui/icons-material/Delete"; +import DragIndicatorOutlinedIcon from "@mui/icons-material/DragIndicatorOutlined"; +import { useFilterInputs } from "./useFilterInputs"; + +export interface IFilterInputsProps { + filterColumns: ReturnType["filterColumns"]; + selectedColumn: ReturnType["filterColumns"][0]; + availableFilters: IFieldConfig["filter"]; disabled?: boolean; + query: TableFilter; + setQuery: (query: TableFilter) => void; + handleDelete: () => void; + index: number; + noOfQueries: number; + handleChangeColumn: (column: string) => void; + joinOperator: "AND" | "OR"; + setJoinOperator: (operator: "AND" | "OR") => void; } export default function FilterInputs({ @@ -32,6 +50,11 @@ export default function FilterInputs({ query, setQuery, disabled, + joinOperator, + setJoinOperator, + handleDelete, + index, + noOfQueries, }: IFilterInputsProps) { const columnType = selectedColumn ? getFieldType(selectedColumn) : null; @@ -76,18 +99,18 @@ export default function FilterInputs({ return ( - + handleChangeColumn(newKey ?? "")} disabled={disabled} /> - + { - setQuery((query) => ({ + const newQuery = { ...query, - operator: e.target.value as string, - })); + operator: e.target.value as TableFilter["operator"], + }; + + setQuery(newQuery); }} SelectProps={{ displayEmpty: true }} - sx={{ "& .MuiSelect-select": { display: "flex" } }} + sx={{ + "& .MuiSelect-select": { display: "flex" }, + }} > Select operator @@ -113,7 +140,7 @@ export default function FilterInputs({ - + {query.key && query.operator && ( { - setQuery((query) => ({ ...query, value })); + const newQuery = { + ...query, + value, + }; + setQuery(newQuery); }, disabled, operator: query.operator, @@ -146,6 +177,67 @@ export default function FilterInputs({ )} + + + + {} + + + + + {noOfQueries > 1 && + index !== noOfQueries - 1 && + (index === 0 ? ( + + + + + setJoinOperator(e.target.value === "AND" ? "AND" : "OR") + } + sx={{ + "& .MuiSelect-select": { + display: "flex", + }, + }} + > + And + Or + + + + + ) : ( + + + {joinOperator === "AND" ? "And" : "Or"} + + + ))} ); } diff --git a/src/components/TableToolbar/Filters/FilterInputsCollection.tsx b/src/components/TableToolbar/Filters/FilterInputsCollection.tsx new file mode 100644 index 00000000..adbc9215 --- /dev/null +++ b/src/components/TableToolbar/Filters/FilterInputsCollection.tsx @@ -0,0 +1,123 @@ +import FilterInputs from "./FilterInputs"; + +import { Button } from "@mui/material"; + +import type { useFilterInputs } from "./useFilterInputs"; + +import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd"; +import AddIcon from "@mui/icons-material/Add"; +import { find } from "lodash-es"; + +export interface IFilterInputsCollectionProps + extends ReturnType { + disabled?: boolean; +} + +export default function FilterInputsCollection({ + filterColumns, + selectedColumns, + handleColumnChange, + availableFiltersForEachSelectedColumn, + queries, + setQueries, + disabled, + joinOperator, + setJoinOperator, +}: IFilterInputsCollectionProps) { + const onDragEnd = (result: any) => { + if (!result.destination) return; + + setQueries((prevQueries) => { + const newQueries = [...prevQueries]; + const [reorderedItem] = newQueries.splice(result.source.index, 1); + newQueries.splice(result.destination.index, 0, reorderedItem); + return newQueries; + }); + }; + + return ( + <> + + + {(provided) => ( +
+ {queries.map((query, index) => { + return ( + + {(provided) => ( +
+ { + handleColumnChange(query.key, key); + }} + availableFilters={ + availableFiltersForEachSelectedColumn[index] + } + query={query} + setQuery={(newQuery: any) => { + setQueries((prevQueries) => { + const newQueries = [...prevQueries]; + newQueries[index] = newQuery; + return newQueries; + }); + }} + disabled={disabled} + joinOperator={joinOperator} + setJoinOperator={setJoinOperator} + handleDelete={() => { + setQueries((prevQueries) => { + const newQueries = [...prevQueries]; + newQueries.splice(index, 1); + return newQueries; + }); + }} + index={index} + noOfQueries={queries.length} + /> +
+ )} +
+ ); + })} + {provided.placeholder} +
+ )} +
+
+ + + + ); +} diff --git a/src/components/TableToolbar/Filters/Filters.tsx b/src/components/TableToolbar/Filters/Filters.tsx index 8ebc32ff..f2fbdf05 100644 --- a/src/components/TableToolbar/Filters/Filters.tsx +++ b/src/components/TableToolbar/Filters/Filters.tsx @@ -18,7 +18,7 @@ import TabList from "@mui/lab/TabList"; import TabPanel from "@mui/lab/TabPanel"; import FiltersPopover from "./FiltersPopover"; -import FilterInputs from "./FilterInputs"; +import FilterInputsCollection from "./FilterInputsCollection"; import { projectScope, @@ -35,16 +35,26 @@ import { tableSortsAtom, updateTableSchemaAtom, tableFiltersPopoverAtom, + tableFiltersJoinAtom, } from "@src/atoms/tableScope"; import { useFilterInputs, INITIAL_QUERY } from "./useFilterInputs"; import { analytics, logEvent } from "@src/analytics"; import type { TableFilter } from "@src/types/table"; -const shouldDisableApplyButton = (value: any) => - isEmpty(value) && - !isDate(value) && - typeof value !== "boolean" && - typeof value !== "number"; +const shouldDisableApplyButton = (queries: any) => { + let disable = queries.length === 0; + + for (let query of queries) { + disable = + disable || + (isEmpty(query.value) && + !isDate(query.value) && + typeof query.value !== "boolean" && + typeof query.value !== "number"); + } + + return disable; +}; enum FilterType { yourFilter = "local_filter", @@ -64,10 +74,17 @@ export default function Filters() { const [{ defaultQuery }] = useAtom(tableFiltersPopoverAtom, tableScope); const tableFilterInputs = useFilterInputs(tableColumnsOrdered); - const setTableQuery = tableFilterInputs.setQuery; + const setTableQueries = tableFilterInputs.setQueries; const userFilterInputs = useFilterInputs(tableColumnsOrdered, defaultQuery); - const setUserQuery = userFilterInputs.setQuery; - const { availableFilters } = userFilterInputs; + const setUserQueries = userFilterInputs.setQueries; + const { availableFiltersForEachSelectedColumn } = userFilterInputs; + const availableFiltersForFirstColumn = + availableFiltersForEachSelectedColumn[0]; + + const [tableFiltersJoin, setTableFiltersJoin] = useAtom( + tableFiltersJoinAtom, + tableScope + ); // Get table filters & user filters from config documents const tableFilters = useMemoValue( @@ -86,14 +103,14 @@ export default function Filters() { // Set the local table filter useEffect(() => { // Set local state for UI - setTableQuery( - Array.isArray(tableFilters) && tableFilters[0] - ? tableFilters[0] + setTableQueries( + Array.isArray(tableFilters) && tableFilters && tableFilters.length > 0 + ? tableFilters : INITIAL_QUERY ); - setUserQuery( - Array.isArray(userFilters) && userFilters[0] - ? userFilters[0] + setUserQueries( + Array.isArray(userFilters) && userFilters && userFilters.length > 0 + ? userFilters : INITIAL_QUERY ); setCanOverrideCheckbox(tableFiltersOverridable); @@ -120,10 +137,10 @@ export default function Filters() { hasUserFilters, setLocalFilters, setTableSorts, - setTableQuery, + setTableQueries, tableFilters, tableFiltersOverridable, - setUserQuery, + setUserQueries, userFilters, userRoles, ]); @@ -151,27 +168,74 @@ export default function Filters() { // When defaultQuery (from atom) is updated, update the UI useEffect(() => { if (defaultQuery) { - setUserQuery(defaultQuery); + setUserQueries([defaultQuery]); setTab("user"); } - }, [setUserQuery, defaultQuery]); + }, [setUserQueries, defaultQuery]); const [overrideTableFilters, setOverrideTableFilters] = useState( tableFiltersOverridden ); + useEffect(() => { + if (userSettings.tables?.[tableId]?.joinOperator) { + userFilterInputs.setJoinOperator( + userSettings.tables?.[tableId]?.joinOperator === "AND" ? "AND" : "OR" + ); + } + + if (tableSchema.joinOperator) { + tableFilterInputs.setJoinOperator( + tableSchema.joinOperator === "AND" ? "AND" : "OR" + ); + } + }, [userSettings.tables?.[tableId]?.joinOperator]); + + useEffect(() => { + if (tableFiltersOverridable && (hasUserFilters || userFilters === null)) { + setTableFiltersJoin( + userSettings.tables?.[tableId]?.joinOperator === "AND" ? "AND" : "OR" + ); + } else if (hasTableFilters) { + setTableFiltersJoin(tableSchema.joinOperator === "AND" ? "AND" : "OR"); + } else if (hasUserFilters) { + setTableFiltersJoin( + userSettings.tables?.[tableId]?.joinOperator === "AND" ? "AND" : "OR" + ); + } + }, [ + tableFiltersOverridable, + hasUserFilters, + hasTableFilters, + userFilters, + tableSchema.joinOperator, + userSettings.tables?.[tableId]?.joinOperator, + ]); + // Save table filters to table schema document - const setTableFilters = (filters: TableFilter[]) => { + const setTableFilters = ( + filters: TableFilter[], + op: "AND" | "OR" = "AND" + ) => { logEvent(analytics, FilterType.tableFilter); if (updateTableSchema) - updateTableSchema({ filters, filtersOverridable: canOverrideCheckbox }); + updateTableSchema({ + filters, + filtersOverridable: canOverrideCheckbox, + joinOperator: op, + }); }; // Save user filters to user document // null overrides table filters - const setUserFilters = (filters: TableFilter[] | null) => { + const setUserFilters = ( + filters: TableFilter[] | null, + op: "AND" | "OR" = "AND" + ) => { logEvent(analytics, FilterType.yourFilter); if (updateUserSettings && filters) - updateUserSettings({ tables: { [`${tableId}`]: { filters } } }); + updateUserSettings({ + tables: { [`${tableId}`]: { filters, joinOperator: op } }, + }); }; return ( @@ -180,7 +244,7 @@ export default function Filters() { hasAppliedFilters={hasAppliedFilters} hasTableFilters={hasTableFilters} tableFiltersOverridden={tableFiltersOverridden} - availableFilters={availableFilters} + availableFilters={availableFiltersForFirstColumn} setUserFilters={setUserFilters} > {({ handleClose }) => { @@ -245,7 +309,7 @@ export default function Filters() { - + {hasTableFilters && ( { setUserFilters(overrideTableFilters ? null : []); userFilterInputs.resetQuery(); }} > - Clear + Clear All {hasTableFilters && (overrideTableFilters ? " (ignore table filter)" @@ -289,12 +353,15 @@ export default function Filters() {