From 316807a0ed37d65e23e9912dfc24d198b4cd1006 Mon Sep 17 00:00:00 2001 From: Manmeet Date: Tue, 23 May 2023 13:10:50 +0530 Subject: [PATCH 1/8] add sort button --- src/components/TableToolbar/Sort/Sort.tsx | 133 ++++++++++++++++++ .../TableToolbar/Sort/SortPopover.tsx | 67 +++++++++ src/components/TableToolbar/Sort/index.tsx | 2 + src/components/TableToolbar/TableToolbar.tsx | 26 ++-- 4 files changed, 219 insertions(+), 9 deletions(-) create mode 100644 src/components/TableToolbar/Sort/Sort.tsx create mode 100644 src/components/TableToolbar/Sort/SortPopover.tsx create mode 100644 src/components/TableToolbar/Sort/index.tsx diff --git a/src/components/TableToolbar/Sort/Sort.tsx b/src/components/TableToolbar/Sort/Sort.tsx new file mode 100644 index 00000000..1ab76a42 --- /dev/null +++ b/src/components/TableToolbar/Sort/Sort.tsx @@ -0,0 +1,133 @@ +import { useAtom } from "jotai"; + +import { + Grid, + IconButton, + MenuItem, + Stack, + TextField, + Typography, + alpha, +} from "@mui/material"; +import DeleteIcon from "@mui/icons-material/DeleteOutlined"; + +import { + tableColumnsOrderedAtom, + tableScope, + tableSettingsAtom, + tableSortsAtom, +} from "@src/atoms/tableScope"; +import SortPopover from "./SortPopover"; +import ColumnSelect from "@src/components/Table/ColumnSelect"; + +import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"; +import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward"; +import { projectScope, userRolesAtom } from "@src/atoms/projectScope"; +import useSaveTableSorts from "@src/components/Table/ColumnHeader/useSaveTableSorts"; + +export default function Sort() { + const [userRoles] = useAtom(userRolesAtom, projectScope); + const [tableSettings] = useAtom(tableSettingsAtom, tableScope); + + const canEditColumns = Boolean( + userRoles.includes("ADMIN") || + tableSettings.modifiableBy?.some((r) => userRoles.includes(r)) + ); + + const [tableSorts, setTableSorts] = useAtom(tableSortsAtom, tableScope); + const triggerSaveTableSorts = useSaveTableSorts(canEditColumns); + + const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope); + + const sortColumns = tableColumnsOrdered.map(({ key, name, type, index }) => ({ + value: key, + label: name, + type, + index, + })); + + return ( + + {({ handleClose }) => ( + + + { + if (value) { + setTableSorts([ + { key: value, direction: tableSorts[0].direction }, + ]); + + triggerSaveTableSorts([ + { key: value, direction: tableSorts[0].direction }, + ]); + } else { + setTableSorts([]); + } + }} + /> + + + + { + setTableSorts([ + { + key: tableSorts[0].key, + direction: e.target.value === "asc" ? "asc" : "desc", + }, + ]); + triggerSaveTableSorts([ + { + key: tableSorts[0].key, + direction: e.target.value === "asc" ? "asc" : "desc", + }, + ]); + }} + > + + + + Sort ascending + + + + + + Sort descending + + + + + + setTableSorts([])} + sx={{ + "&:hover, &:focus": { + color: "error.main", + backgroundColor: (theme) => + alpha( + theme.palette.error.main, + theme.palette.action.hoverOpacity * 2 + ), + }, + }} + > + + + + + )} + + ); +} diff --git a/src/components/TableToolbar/Sort/SortPopover.tsx b/src/components/TableToolbar/Sort/SortPopover.tsx new file mode 100644 index 00000000..11d86b44 --- /dev/null +++ b/src/components/TableToolbar/Sort/SortPopover.tsx @@ -0,0 +1,67 @@ +import { useRef, useState } from "react"; +import { useAtom } from "jotai"; + +import { Popover } from "@mui/material"; + +import ButtonWithStatus from "@src/components/ButtonWithStatus"; + +import { tableScope, tableSortsAtom } from "@src/atoms/tableScope"; + +import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"; + +export interface ISortPopoverProps { + children: (props: { handleClose: () => void }) => React.ReactNode; +} + +export default function SortPopover({ children }: ISortPopoverProps) { + const [tableSortPopoverState, setTableSortPopoverState] = useState(false); + + const anchorEl = useRef(null); + const popoverId = tableSortPopoverState ? "sort-popover" : undefined; + const handleClose = () => setTableSortPopoverState(false); + + const [tableSorts] = useAtom(tableSortsAtom, tableScope); + + return ( + <> + setTableSortPopoverState(true)} + active={true} + startIcon={ + + theme.transitions.create("transform", { + duration: theme.transitions.duration.short, + }), + + transform: + tableSorts[0].direction === "asc" ? "rotate(180deg)" : "none", + }} + /> + } + aria-describedby={popoverId} + > + Sorted: {tableSorts[0].key} + + + + {children({ handleClose })} + + + ); +} diff --git a/src/components/TableToolbar/Sort/index.tsx b/src/components/TableToolbar/Sort/index.tsx new file mode 100644 index 00000000..a67b91b2 --- /dev/null +++ b/src/components/TableToolbar/Sort/index.tsx @@ -0,0 +1,2 @@ +export * from "./Sort"; +export { default } from "./Sort"; diff --git a/src/components/TableToolbar/TableToolbar.tsx b/src/components/TableToolbar/TableToolbar.tsx index abd448bd..5845e185 100644 --- a/src/components/TableToolbar/TableToolbar.tsx +++ b/src/components/TableToolbar/TableToolbar.tsx @@ -32,11 +32,15 @@ import { tableSettingsAtom, tableSchemaAtom, tableModalAtom, + tableSortsAtom, } from "@src/atoms/tableScope"; import { FieldType } from "@src/constants/fields"; import { TableToolsType } from "@src/types/table"; import FilterIcon from "@mui/icons-material/FilterList"; +// prettier-ignore +const Sort = lazy(() => import("./Sort" /* webpackChunkName: "Filters" */)); + // prettier-ignore const Filters = lazy(() => import("./Filters" /* webpackChunkName: "Filters" */)); // prettier-ignore @@ -62,6 +66,7 @@ export default function TableToolbar({ const [tableSettings] = useAtom(tableSettingsAtom, tableScope); const [tableSchema] = useAtom(tableSchemaAtom, tableScope); const openTableModal = useSetAtom(tableModalAtom, tableScope); + const [tableSorts] = useAtom(tableSortsAtom, tableScope); const hasDerivatives = Object.values(tableSchema.columns ?? {}).filter( (column) => column.type === FieldType.derivative @@ -116,6 +121,11 @@ export default function TableToolbar({ )} + {tableSorts.length > 0 && tableSettings.isCollection !== false && ( + }> + + + )}
{/* Spacer */}
@@ -134,22 +144,20 @@ export default function TableToolbar({ ) )} - {(!projectSettings.exporterRoles || projectSettings.exporterRoles.length === 0 || userRoles.some((role) => projectSettings.exporterRoles?.includes(role) )) && ( - }> - openTableModal("export")} - icon={} - disabled={disabledTools.includes("export")} - /> + }> + openTableModal("export")} + icon={} + disabled={disabledTools.includes("export")} + /> )} - {userRoles.includes("ADMIN") && ( <>
{/* Spacer */} From b263c922d84cb4b9807b0c98b668b4817f01eb80 Mon Sep 17 00:00:00 2001 From: Rob Jackson Date: Wed, 10 May 2023 20:38:35 +0100 Subject: [PATCH 2/8] Add colour change customisation to Slider --- src/components/fields/Slider/DisplayCell.tsx | 13 +- src/components/fields/Slider/Settings.tsx | 167 ++++++++++++++++++- 2 files changed, 177 insertions(+), 3 deletions(-) diff --git a/src/components/fields/Slider/DisplayCell.tsx b/src/components/fields/Slider/DisplayCell.tsx index ae8cc736..de72a45a 100644 --- a/src/components/fields/Slider/DisplayCell.tsx +++ b/src/components/fields/Slider/DisplayCell.tsx @@ -1,18 +1,22 @@ import { IDisplayCellProps } from "@src/components/fields/types"; -import { Grid, Box } from "@mui/material"; +import { Grid, Box, useTheme } from "@mui/material"; import { resultColorsScale } from "@src/utils/color"; export default function Slider({ column, value }: IDisplayCellProps) { + const theme = useTheme(); + const { max, min, unit, + colors, }: { max: number; min: number; unit?: string; + colors: any; } = { max: 10, min: 0, @@ -24,6 +28,7 @@ export default function Slider({ column, value }: IDisplayCellProps) { ? 0 : ((value - min) / (max - min)) * 100; + const percentage = progress / 100; return ( @@ -48,7 +53,11 @@ export default function Slider({ column, value }: IDisplayCellProps) { maxWidth: "100%", width: `${progress}%`, - backgroundColor: resultColorsScale(progress / 100).toHex(), + backgroundColor: resultColorsScale( + percentage, + colors, + theme.palette.background.paper + ).toHex(), }} /> diff --git a/src/components/fields/Slider/Settings.tsx b/src/components/fields/Slider/Settings.tsx index c4bb268e..96009d04 100644 --- a/src/components/fields/Slider/Settings.tsx +++ b/src/components/fields/Slider/Settings.tsx @@ -1,7 +1,54 @@ +import { useState } from "react"; + +import { + Box, + TextField, + FormControlLabel, + Switch, + MenuItem, + Checkbox, + Grid, + InputLabel, + Typography, + useTheme, +} from "@mui/material"; +import ColorPickerInput from "@src/components/ColorPickerInput"; import { ISettingsProps } from "@src/components/fields/types"; -import { TextField, FormControlLabel, Switch } from "@mui/material"; + +import { Color, toColor } from "react-color-palette"; +import { fieldSx } from "@src/components/SideDrawer/utils"; +import { resultColorsScale, defaultColors } from "@src/utils/color"; + +const colorLabels: { [key: string]: string } = { + 0: "Start", + 1: "Middle", + 2: "End", +}; export default function Settings({ onChange, config }: ISettingsProps) { + const colors: string[] = config.colors ?? defaultColors; + + const [checkStates, setCheckStates] = useState( + colors.map(Boolean) + ); + + const onCheckboxChange = (index: number, checked: boolean) => { + onChange("colors")( + colors.map((value: any, idx: number) => + index === idx ? (checked ? value || defaultColors[idx] : null) : value + ) + ); + setCheckStates( + checkStates.map((value, idx) => (index === idx ? checked : value)) + ); + }; + + const handleColorChange = (index: number, color: Color): void => { + onChange("colors")( + colors.map((value, idx) => (index === idx ? color.hex : value)) + ); + }; + return ( <> + + + {checkStates.map((checked: boolean, index: number) => { + const colorHex = colors[index]; + return ( + + onCheckboxChange(index, !checked)} + /> + + + {checked && ( + + + `0 0 0 1px ${theme.palette.divider} inset`, + borderRadius: 0.5, + opacity: 0.5, + }} + /> + {colorHex} + + )} + + {colorHex && ( +
+ + handleColorChange(index, color) + } + disabled={!checkStates[index]} + /> +
+ )} +
+
+ ); + })} +
+ ); } + +const Preview = ({ colors }: { colors: any }) => { + const theme = useTheme(); + return ( + + Preview: + + {[0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1].map((value) => { + return ( + + + + {value * 100}% + + + ); + })} + + + ); +}; From 9b0ed580518c103dc2560fa4843ed94e24aaa41b Mon Sep 17 00:00:00 2001 From: Manmeet Date: Thu, 1 Jun 2023 10:38:08 +0530 Subject: [PATCH 3/8] save the sort in firebase --- src/components/ColumnMenu/ColumnMenu.tsx | 13 +++++----- .../Table/ColumnHeader/ColumnHeaderSort.tsx | 14 +++++----- src/components/TableToolbar/Sort/Sort.tsx | 26 ++++++++++--------- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/components/ColumnMenu/ColumnMenu.tsx b/src/components/ColumnMenu/ColumnMenu.tsx index dec3f6fe..d84bd16d 100644 --- a/src/components/ColumnMenu/ColumnMenu.tsx +++ b/src/components/ColumnMenu/ColumnMenu.tsx @@ -192,9 +192,10 @@ export default function ColumnMenu({ setTableSorts( isSorted && !isAsc ? [] : [{ key: sortKey, direction: "desc" }] ); - if (!isSorted || isAsc) { - triggerSaveTableSorts([{ key: sortKey, direction: "desc" }]); - } + + triggerSaveTableSorts( + isSorted && !isAsc ? [] : [{ key: sortKey, direction: "desc" }] + ); handleClose(); }, active: isSorted && !isAsc, @@ -209,9 +210,9 @@ export default function ColumnMenu({ setTableSorts( isSorted && isAsc ? [] : [{ key: sortKey, direction: "asc" }] ); - if (!isSorted || !isAsc) { - triggerSaveTableSorts([{ key: sortKey, direction: "asc" }]); - } + triggerSaveTableSorts( + isSorted && isAsc ? [] : [{ key: sortKey, direction: "asc" }] + ); handleClose(); }, active: isSorted && isAsc, diff --git a/src/components/Table/ColumnHeader/ColumnHeaderSort.tsx b/src/components/Table/ColumnHeader/ColumnHeaderSort.tsx index 45e924f0..2478ccbd 100644 --- a/src/components/Table/ColumnHeader/ColumnHeaderSort.tsx +++ b/src/components/Table/ColumnHeader/ColumnHeaderSort.tsx @@ -38,14 +38,12 @@ export const ColumnHeaderSort = memo(function ColumnHeaderSort({ const triggerSaveTableSorts = useSaveTableSorts(canEditColumns); const handleSortClick = () => { - if (nextSort === "none") setTableSorts([]); - else setTableSorts([{ key: sortKey, direction: nextSort }]); - triggerSaveTableSorts([ - { - key: sortKey, - direction: nextSort === "none" ? "asc" : nextSort, - }, - ]); + setTableSorts( + nextSort === "none" ? [] : [{ key: sortKey, direction: nextSort }] + ); + triggerSaveTableSorts( + nextSort === "none" ? [] : [{ key: sortKey, direction: nextSort }] + ); }; return ( diff --git a/src/components/TableToolbar/Sort/Sort.tsx b/src/components/TableToolbar/Sort/Sort.tsx index 1ab76a42..9d6d362e 100644 --- a/src/components/TableToolbar/Sort/Sort.tsx +++ b/src/components/TableToolbar/Sort/Sort.tsx @@ -57,17 +57,16 @@ export default function Sort() { options={sortColumns} value={tableSorts[0].key} onChange={(value: string | null) => { - if (value) { - setTableSorts([ - { key: value, direction: tableSorts[0].direction }, - ]); - - triggerSaveTableSorts([ - { key: value, direction: tableSorts[0].direction }, - ]); - } else { - setTableSorts([]); - } + setTableSorts( + value === null + ? [] + : [{ key: value, direction: tableSorts[0].direction }] + ); + triggerSaveTableSorts( + value === null + ? [] + : [{ key: value, direction: tableSorts[0].direction }] + ); }} />
@@ -111,7 +110,10 @@ export default function Sort() { setTableSorts([])} + onClick={() => { + setTableSorts([]); + triggerSaveTableSorts([]); + }} sx={{ "&:hover, &:focus": { color: "error.main", From 23e29ca9f7cf6aa8234abeac4a1c63be876fe113 Mon Sep 17 00:00:00 2001 From: mnmt7 <107614965+mnmt7@users.noreply.github.com> Date: Fri, 2 Jun 2023 13:17:01 +0530 Subject: [PATCH 4/8] "Filter by" option to filter by the value of the cell (#1251) * add filter-by option in right-click menu * enable filterby option on fields which are spread out versions of a JSON * remove "Filter value" option --- .../Table/ContextMenu/MenuContents.tsx | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/src/components/Table/ContextMenu/MenuContents.tsx b/src/components/Table/ContextMenu/MenuContents.tsx index 1e4cf425..6f934c26 100644 --- a/src/components/Table/ContextMenu/MenuContents.tsx +++ b/src/components/Table/ContextMenu/MenuContents.tsx @@ -22,6 +22,7 @@ import { userRolesAtom, altPressAtom, confirmDialogAtom, + updateUserSettingsAtom, } from "@src/atoms/projectScope"; import { tableScope, @@ -34,8 +35,10 @@ import { updateFieldAtom, tableFiltersPopoverAtom, _updateRowDbAtom, + tableIdAtom, } from "@src/atoms/tableScope"; import { FieldType } from "@src/constants/fields"; +import { TableRow } from "@src/types/table"; interface IMenuContentsProps { onClose: () => void; @@ -58,6 +61,8 @@ export default function MenuContents({ onClose }: IMenuContentsProps) { tableFiltersPopoverAtom, tableScope ); + const [updateUserSettings] = useAtom(updateUserSettingsAtom, projectScope); + const [tableId] = useAtom(tableIdAtom, tableScope); const addRowIdType = tableSchema.idType || "decrement"; @@ -241,7 +246,28 @@ export default function MenuContents({ onClose }: IMenuContentsProps) { // Cell actions // TODO: Add copy and paste here - const cellValue = row?.[selectedCell.columnKey]; + + const selectedColumnKey = selectedCell.columnKey; + const selectedColumnKeySplit = selectedColumnKey.split("."); + + const getNestedFieldValue = (object: TableRow, keys: string[]) => { + let value = object; + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + + if (value && typeof value === "object" && key in value) { + value = value[key]; + } else { + // Handle cases where the key does not exist in the nested structure + return undefined; + } + } + + return value; + }; + + const cellValue = getNestedFieldValue(row, selectedColumnKeySplit); const columnFilters = getFieldProp( "filter", @@ -249,14 +275,18 @@ export default function MenuContents({ onClose }: IMenuContentsProps) { ? selectedColumn.config?.renderFieldType : selectedColumn?.type ); - const handleFilterValue = () => { - openTableFiltersPopover({ - defaultQuery: { + const handleFilterBy = () => { + const filters = [ + { key: selectedColumn.fieldName, operator: columnFilters!.operators[0]?.value || "==", value: cellValue, }, - }); + ]; + + if (updateUserSettings) { + updateUserSettings({ tables: { [`${tableId}`]: { filters } } }); + } onClose(); }; const cellActions = [ @@ -272,10 +302,10 @@ export default function MenuContents({ onClose }: IMenuContentsProps) { onClick: handleClearValue, }, { - label: "Filter value", + label: "Filter by", icon: , disabled: !columnFilters || cellValue === undefined, - onClick: handleFilterValue, + onClick: handleFilterBy, }, ]; actionGroups.push(cellActions); From 7c12e3ac42c7e546c468c186e69b936faa4d8664 Mon Sep 17 00:00:00 2001 From: Vaibhav C Date: Tue, 6 Jun 2023 12:51:29 +0000 Subject: [PATCH 5/8] migrate to vite and vitest from cra and jest --- .github/workflows/deploy-preview.yml | 4 +- .gitignore | 1 + createDotEnv.js | 8 +- firebase.json | 2 +- public/index.html => index.html | 21 +- package.json | 24 +- .../CodeEditor/useMonacoCustomizations.ts | 13 +- .../ColumnConfigModal/DefaultValueInput.tsx | 3 +- src/components/RichTextEditor.tsx | 12 +- .../ExportModal/ModalContentsExport.tsx | 6 +- .../TableToolbar/ImportData/ImportFromCsv.tsx | 5 +- src/components/fields/Action/Settings.tsx | 3 +- src/components/fields/Connector/Settings.tsx | 5 +- src/components/fields/Derivative/Settings.tsx | 3 +- src/components/fields/Formula/Settings.tsx | 3 +- src/hooks/useDocumentTitle.ts | 6 +- src/react-app-env.d.ts | 1 - src/sources/ProjectSourceFirebase/init.ts | 16 +- src/vite-env.d.ts | 1 + tsconfig.json | 2 +- vite.config.ts | 37 + yarn.lock | 5654 +++-------------- 22 files changed, 1135 insertions(+), 4695 deletions(-) rename public/index.html => index.html (85%) delete mode 100644 src/react-app-env.d.ts create mode 100644 src/vite-env.d.ts create mode 100644 vite.config.ts diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 9ca97e44..5b13cc51 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -6,8 +6,8 @@ on: # paths: # - "website/**" env: - REACT_APP_FIREBASE_PROJECT_ID: rowyio - REACT_APP_FIREBASE_PROJECT_WEB_API_KEY: + VITE_APP_FIREBASE_PROJECT_ID: rowyio + VITE_APP_FIREBASE_PROJECT_WEB_API_KEY: "${{ secrets.FIREBASE_WEB_API_KEY_TRYROWY }}" CI: "" jobs: diff --git a/.gitignore b/.gitignore index af599611..4e723f64 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # production /build +/dist cloud_functions/functions/lib # firebase diff --git a/createDotEnv.js b/createDotEnv.js index 623ddb7f..2a0ca98a 100644 --- a/createDotEnv.js +++ b/createDotEnv.js @@ -8,10 +8,10 @@ const main = ( ) => { return fs.writeFileSync( ".env", - `REACT_APP_FIREBASE_PROJECT_ID = ${projectID} -REACT_APP_FIREBASE_PROJECT_WEB_API_KEY = ${firebaseWebApiKey} -REACT_APP_ALGOLIA_APP_ID = ${algoliaAppId} -REACT_APP_ALGOLIA_SEARCH_API_KEY = ${algoliaSearhApiKey}` + `VITE_APP_FIREBASE_PROJECT_ID = ${projectID} +VITE_APP_FIREBASE_PROJECT_WEB_API_KEY = ${firebaseWebApiKey} +VITE_APP_ALGOLIA_APP_ID = ${algoliaAppId} +VITE_APP_ALGOLIA_SEARCH_API_KEY = ${algoliaSearhApiKey}` ); }; diff --git a/firebase.json b/firebase.json index 26e83bd4..45bcffd3 100644 --- a/firebase.json +++ b/firebase.json @@ -1,6 +1,6 @@ { "hosting": { - "public": "build", + "public": "dist", "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], "rewrites": [ { diff --git a/public/index.html b/index.html similarity index 85% rename from public/index.html rename to index.html index 4e7116ce..43b61e5e 100644 --- a/public/index.html +++ b/index.html @@ -15,30 +15,30 @@ - + @@ -47,13 +47,13 @@ manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> - + @@ -87,7 +87,7 @@ property="og:description" content="Build on the Google Cloud Platform in minutes. Manage Firestore data in a spreadsheet-like UI, write Cloud Functions effortlessly in the browser, and connect to third-party apps. Rowy is open source!" /> - + @@ -96,11 +96,12 @@ property="twitter:description" content="Build on the Google Cloud Platform in minutes. Manage Firestore data in a spreadsheet-like UI, write Cloud Functions effortlessly in the browser, and connect to third-party apps. Rowy is open source!" /> - +
+