diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea78..44f18f78 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -23,6 +23,9 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. +**Rowy Run version** +If applicable, share the Rowy Run version from your project settings. + **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] diff --git a/package.json b/package.json index 85695216..f86fd68a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rowy", - "version": "2.4.0", + "version": "2.5.0", "homepage": "https://rowy.io", "repository": { "type": "git", @@ -15,10 +15,10 @@ "@hookform/resolvers": "^2.8.5", "@mdi/js": "^6.5.95", "@monaco-editor/react": "^4.3.1", - "@mui/icons-material": "^5.4.4", - "@mui/lab": "^5.0.0-alpha.71", - "@mui/material": "^5.4.4", - "@mui/styles": "^5.4.4", + "@mui/icons-material": "^5.5.1", + "@mui/lab": "^5.0.0-alpha.73", + "@mui/material": "^5.5.1", + "@mui/styles": "^5.5.1", "@rowy/form-builder": "^0.5.3", "@rowy/multiselect": "^0.2.3", "@tinymce/tinymce-react": "^3.12.6", diff --git a/src/components/CodeEditor/index.tsx b/src/components/CodeEditor/index.tsx index 742c9347..2d35f70b 100644 --- a/src/components/CodeEditor/index.tsx +++ b/src/components/CodeEditor/index.tsx @@ -38,7 +38,7 @@ export default function CodeEditor({ extraLibs, diagnosticsOptions, onUnmount, - + defaultLanguage = "javascript", ...props }: ICodeEditorProps) { const theme = useTheme(); @@ -77,7 +77,7 @@ export default function CodeEditor({ style={fullScreen ? { height: "100%" } : {}} > } className="editor" diff --git a/src/components/CodeEditor/rowy.d.ts b/src/components/CodeEditor/rowy.d.ts index 86d7b144..f6852cce 100644 --- a/src/components/CodeEditor/rowy.d.ts +++ b/src/components/CodeEditor/rowy.d.ts @@ -48,7 +48,10 @@ interface Rowy { /** * Get an existing secret from the secret manager. */ - get: (name: SecretNames, version?: string) => Promise; + get: ( + name: SecretNames, + version?: string + ) => Promise; }; /** * Gives access to the Cloud Storage. diff --git a/src/components/ConfirmationDialog/Dialog.tsx b/src/components/ConfirmationDialog/Dialog.tsx index 0e06760c..64ac30ac 100644 --- a/src/components/ConfirmationDialog/Dialog.tsx +++ b/src/components/ConfirmationDialog/Dialog.tsx @@ -21,6 +21,7 @@ export default function Confirmation({ confirm, confirmationCommand, handleConfirm, + handleCancel, confirmColor, open, handleClose, @@ -60,7 +61,14 @@ export default function Confirmation({ {!hideCancel && ( - + )} - {!userClaims?.roles.includes("ADMIN") && ( + {!userRoles.includes("ADMIN") && ( () => { if (!tableState?.rows) return; - let row = cell!.row; if (direction === "up" && row > 0) row -= 1; if (direction === "down" && row < tableState.rows.length - 1) row += 1; - setCell!((cell) => ({ column: cell!.column, row })); - const idx = tableState?.columns[cell!.column]?.index; dataGridRef?.current?.selectCell({ rowIdx: row, idx }, false); }; @@ -68,13 +65,6 @@ export default function SideDrawer() { useEffect(() => { if (cell && tableState?.rows[cell.row]) { - window.history.pushState( - "", - `${tableState?.config.id}`, - `${window.location.pathname}?rowRef=${encodeURIComponent( - tableState?.rows[cell.row].ref.path - )}` - ); if (urlDocState.doc) { urlDocState.unsubscribe(); dispatchUrlDoc({ path: "", doc: null }); diff --git a/src/components/Table/ContextMenu/index.tsx b/src/components/Table/ContextMenu/index.tsx index 757fdc12..1475f4b6 100644 --- a/src/components/Table/ContextMenu/index.tsx +++ b/src/components/Table/ContextMenu/index.tsx @@ -4,6 +4,7 @@ import { getFieldProp } from "@src/components/fields"; import MenuContents from "./MenuContent"; import DuplicateIcon from "@src/assets/icons/CopyCells"; import DeleteIcon from "@mui/icons-material/DeleteOutlined"; +import LinkIcon from "@mui/icons-material/Link"; import { useProjectContext } from "@src/contexts/ProjectContext"; import { useContextMenuAtom } from "@src/atoms/ContextMenu"; @@ -47,8 +48,18 @@ export default function ContextMenu() { } const row = tableState?.rows[selectedCell!.rowIndex]; - if (userRoles.includes("ADMIN") && row) { + if (row) { const rowActions = [ + { + label: "Copy link to row", + icon: , + onClick: () => { + const rowRef = encodeURIComponent(row.ref.path); + navigator.clipboard.writeText( + window.location.href + `?rowRef=${rowRef}` + ); + }, + }, { label: "Duplicate row", icon: , diff --git a/src/components/TableSettings/DeleteMenu.tsx b/src/components/TableSettings/DeleteMenu.tsx index f12f45bf..d52d38ea 100644 --- a/src/components/TableSettings/DeleteMenu.tsx +++ b/src/components/TableSettings/DeleteMenu.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import { useHistory } from "react-router-dom"; +import { useSnackbar } from "notistack"; import { IconButton, Menu, MenuItem, DialogContentText } from "@mui/material"; import DeleteIcon from "@mui/icons-material/DeleteOutlined"; @@ -27,14 +28,21 @@ export default function DeleteMenu({ clearDialog, data }: IDeleteMenuProps) { const handleClose = () => setAnchorEl(null); const history = useHistory(); + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); const handleResetStructure = async () => { + const snack = enqueueSnackbar("Resetting columns…", { persist: true }); + const schemaDocRef = db.doc(`${TABLE_SCHEMAS}/${data!.id}`); await schemaDocRef.update({ columns: {} }); + clearDialog(); + closeSnackbar(snack); }; const handleDelete = async () => { + const snack = enqueueSnackbar("Deleting table…", { persist: true }); + const tablesDocRef = db.doc(SETTINGS); const tableData = (await tablesDocRef.get()).data(); const updatedTables = tableData?.tables.filter( @@ -49,8 +57,10 @@ export default function DeleteMenu({ clearDialog, data }: IDeleteMenuProps) { ) .doc(data?.id) .delete(); + await analytics.logEvent("delete_table"); clearDialog(); + closeSnackbar(snack); history.push(routes.home); }; diff --git a/src/components/TableSettings/TableId.tsx b/src/components/TableSettings/TableId.tsx index 4da7d20c..febf7380 100644 --- a/src/components/TableSettings/TableId.tsx +++ b/src/components/TableSettings/TableId.tsx @@ -1,33 +1,22 @@ import { useEffect } from "react"; import { useWatch } from "react-hook-form"; import _camelCase from "lodash/camelCase"; +import { + ShortTextComponent, + IShortTextComponentProps, +} from "@rowy/form-builder"; -import { TextField, TextFieldProps } from "@mui/material"; -import { IFieldComponentProps, FieldAssistiveText } from "@rowy/form-builder"; - -export interface ICamelCaseIdProps - extends IFieldComponentProps, - Omit< - TextFieldProps, - "variant" | "name" | "label" | "onBlur" | "onChange" | "value" | "ref" - > { +export interface ITableIdProps extends IShortTextComponentProps { watchedField?: string; } -export default function CamelCaseId({ - field: { onChange, onBlur, value, ref }, +export default function TableId({ watchedField, ...props }: ITableIdProps) { + const { + field: { onChange }, + useFormMethods: { control }, + disabled, + } = props; - name, - useFormMethods: { control }, - - errorMessage, - assistiveText, - - disabled, - - watchedField, - ...props -}: ICamelCaseIdProps) { const watchedValue = useWatch({ control, name: watchedField } as any); useEffect(() => { if (!disabled && typeof watchedValue === "string" && !!watchedValue) @@ -35,37 +24,9 @@ export default function CamelCaseId({ }, [watchedValue, disabled]); return ( - - {errorMessage} - - - {assistiveText} - - - ) - } - FormHelperTextProps={{ component: "div" } as any} - name={name} - id={`field-${name}`} - sx={{ "& .MuiInputBase-input": { fontFamily: "mono" } }} + ); } diff --git a/src/components/TableSettings/TableName.tsx b/src/components/TableSettings/TableName.tsx new file mode 100644 index 00000000..2a96654f --- /dev/null +++ b/src/components/TableSettings/TableName.tsx @@ -0,0 +1,27 @@ +import { useEffect } from "react"; +import { useWatch } from "react-hook-form"; +import _startCase from "lodash/startCase"; +import { + ShortTextComponent, + IShortTextComponentProps, +} from "@rowy/form-builder"; + +export interface ITableNameProps extends IShortTextComponentProps { + watchedField?: string; +} + +export default function TableName({ watchedField, ...props }: ITableNameProps) { + const { + field: { onChange }, + useFormMethods: { control }, + disabled, + } = props; + + const watchedValue = useWatch({ control, name: watchedField } as any); + useEffect(() => { + if (!disabled && typeof watchedValue === "string" && !!watchedValue) + onChange(_startCase(watchedValue)); + }, [watchedValue, disabled]); + + return ; +} diff --git a/src/components/TableSettings/form.tsx b/src/components/TableSettings/form.tsx index 8db56e25..09b4ef19 100644 --- a/src/components/TableSettings/form.tsx +++ b/src/components/TableSettings/form.tsx @@ -194,10 +194,11 @@ export const tableSettings = ( // Step 2: Display { step: "display", - type: FieldType.shortText, + type: "tableName", name: "name", label: "Table name", required: true, + watchedField: "collection", assistiveText: "User-facing name for this table", autoFocus: true, gridCols: { xs: 12, sm: 6 }, diff --git a/src/components/TableSettings/index.tsx b/src/components/TableSettings/index.tsx index 8f11b2f8..619705fd 100644 --- a/src/components/TableSettings/index.tsx +++ b/src/components/TableSettings/index.tsx @@ -2,12 +2,14 @@ import useSWR from "swr"; import _find from "lodash/find"; import _sortBy from "lodash/sortBy"; import _get from "lodash/get"; +import _isEmpty from "lodash/isEmpty"; import { useSnackbar } from "notistack"; import { DialogContentText, Stack, Typography } from "@mui/material"; import { FormDialog, FormFields } from "@rowy/form-builder"; import { tableSettings } from "./form"; +import TableName from "./TableName"; import TableId from "./TableId"; import SuggestedRules from "./SuggestedRules"; import SteppedAccordion from "@src/components/SteppedAccordion"; @@ -25,12 +27,31 @@ import { TABLE_GROUP_SCHEMAS, TABLE_SCHEMAS, } from "@src/config/dbPaths"; +import { Controller } from "react-hook-form"; export enum TableSettingsDialogModes { create, update, } -export interface ICreateTableDialogProps { +const customComponents = { + tableName: { + component: TableName, + defaultValue: "", + validation: [["string"]], + }, + tableId: { + component: TableId, + defaultValue: "", + validation: [["string"]], + }, + suggestedRules: { + component: SuggestedRules, + defaultValue: "", + validation: [["string"]], + }, +}; + +export interface ITableSettingsProps { mode: TableSettingsDialogModes | null; clearDialog: () => void; data: Table | null; @@ -40,7 +61,7 @@ export default function TableSettings({ mode, clearDialog, data, -}: ICreateTableDialogProps) { +}: ITableSettingsProps) { const { settingsActions, roles, tables, rowyRun } = useProjectContext(); const sectionNames = Array.from( new Set((tables ?? []).map((t) => t.section)) @@ -49,12 +70,17 @@ export default function TableSettings({ const router = useRouter(); const { requestConfirmation } = useConfirmation(); const snackLogContext = useSnackLogContext(); - const { enqueueSnackbar } = useSnackbar(); + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); const { data: collections } = useSWR( "firebaseCollections", () => rowyRun?.({ route: runRoutes.listCollections }), - { revalidateIfStale: false, dedupingInterval: 60_000 } + { + revalidateOnMount: true, + revalidateOnFocus: false, + revalidateOnReconnect: false, + dedupingInterval: 60_000 * 60, + } ); const open = mode !== null; @@ -64,11 +90,12 @@ export default function TableSettings({ const handleSubmit = async (v) => { const { _suggestedRules, ...values } = v; const data = { ...values }; + if (values.schemaSource) data.schemaSource = _find(tables, { id: values.schemaSource }); - const hasExtensions = Boolean(_get(data, "_schema.extensionObjects")); - const hasWebhooks = Boolean(_get(data, "_schema.webhooks")); + const hasExtensions = !_isEmpty(_get(data, "_schema.extensionObjects")); + const hasWebhooks = !_isEmpty(_get(data, "_schema.webhooks")); const deployExtensionsWebhooks = (onComplete?: () => void) => { if (rowyRun && (hasExtensions || hasWebhooks)) { requestConfirmation({ @@ -124,6 +151,31 @@ export default function TableSettings({ if (onComplete) onComplete(); }, + handleCancel: async () => { + let _schema: Record = {}; + if (hasExtensions) { + _schema.extensionObjects = _get( + data, + "_schema.extensionObjects" + )!.map((x) => ({ + ...x, + active: false, + })); + } + if (hasWebhooks) { + _schema.webhooks = _get(data, "_schema.webhooks")!.map((x) => ({ + ...x, + active: false, + })); + } + + await settingsActions?.updateTable({ + id: data.id, + tableType: data.tableType, + _schema, + }); + if (onComplete) onComplete(); + }, }); } else { if (onComplete) onComplete(); @@ -135,7 +187,11 @@ export default function TableSettings({ deployExtensionsWebhooks(); clearDialog(); analytics.logEvent("update_table", { type: values.tableType }); + enqueueSnackbar("Updated table"); } else { + const creatingSnackbar = enqueueSnackbar("Creating table…", { + persist: true, + }); await settingsActions?.createTable(data); await analytics.logEvent("create_table", { type: values.tableType }); deployExtensionsWebhooks(() => { @@ -149,6 +205,7 @@ export default function TableSettings({ router.history.push(values.id); } clearDialog(); + closeSnackbar(creatingSnackbar); }); } }; @@ -168,18 +225,6 @@ export default function TableSettings({ ), Array.isArray(collections) ? collections.filter((x) => x !== CONFIG) : null ); - const customComponents = { - tableId: { - component: TableId, - defaultValue: "", - validation: [["string"]], - }, - suggestedRules: { - component: SuggestedRules, - defaultValue: "", - validation: [["string"]], - }, - }; return ( + <>} + /> + (m: string, key: string) => { - const objKey = key.split(":")[0]; - const defaultValue = key.split(":")[1] || ""; - return _get(data, objKey, defaultValue); -}; +import { replacer } from "@src/utils/fns"; const getStateIcon = (actionState, config) => { switch (actionState) { diff --git a/src/components/fields/Action/Settings.tsx b/src/components/fields/Action/Settings.tsx index 36a59ae1..7f6eec8a 100644 --- a/src/components/fields/Action/Settings.tsx +++ b/src/components/fields/Action/Settings.tsx @@ -76,7 +76,7 @@ const Settings = ({ config, onChange }) => { const scriptExtraLibs = [ [ - "declare class ActionParams {", + "declare interface actionParams {", " /**", " * actionParams are provided by dialog popup form", " */", diff --git a/src/components/fields/Action/action.d.ts b/src/components/fields/Action/action.d.ts index 82ecf5e9..a4339978 100644 --- a/src/components/fields/Action/action.d.ts +++ b/src/components/fields/Action/action.d.ts @@ -13,7 +13,7 @@ type ActionContext = { storage: firebasestorage.Storage; db: FirebaseFirestore.Firestore; auth: firebaseauth.BaseAuth; - actionParams: ActionParams; + actionParams: actionParams; user: ActionUser; }; diff --git a/src/components/fields/Code/Settings.tsx b/src/components/fields/Code/Settings.tsx new file mode 100644 index 00000000..60adcee4 --- /dev/null +++ b/src/components/fields/Code/Settings.tsx @@ -0,0 +1,38 @@ +import MultiSelect from "@rowy/multiselect"; + +const languages = [ + "javascript", + "typescript", + "json", + "html", + "css", + "scss", + "shell", + "yaml", + "xml", + "ruby", + "python", + "php", + "markdown", + "rust", + "csharp", + "cpp", + "c", + "java", + "go", + "plaintext", +]; +export default function Settings({ config, onChange }) { + return ( + { + onChange("language")(value); + }} + label="Language" + /> + ); +} diff --git a/src/components/fields/Code/SideDrawerField.tsx b/src/components/fields/Code/SideDrawerField.tsx index 2549f3f4..6e10fce3 100644 --- a/src/components/fields/Code/SideDrawerField.tsx +++ b/src/components/fields/Code/SideDrawerField.tsx @@ -13,7 +13,12 @@ export default function Code({ control={control} name={column.key} render={({ field: { onChange, value } }) => ( - + )} /> ); diff --git a/src/components/fields/Code/index.tsx b/src/components/fields/Code/index.tsx index 7c2e13d1..e541631f 100644 --- a/src/components/fields/Code/index.tsx +++ b/src/components/fields/Code/index.tsx @@ -6,6 +6,10 @@ import CodeIcon from "@mui/icons-material/Code"; import BasicCell from "./BasicCell"; import withSideDrawerEditor from "@src/components/Table/editors/withSideDrawerEditor"; +const Settings = lazy( + () => import("./Settings" /* webpackChunkName: "Settings-ConnectService" */) +); + const SideDrawerField = lazy( () => import("./SideDrawerField" /* webpackChunkName: "SideDrawerField-Code" */) @@ -23,5 +27,6 @@ export const config: IFieldConfig = { TableCell: withBasicCell(BasicCell), TableEditor: withSideDrawerEditor(BasicCell), SideDrawerField, + settings: Settings, }; export default config; diff --git a/src/components/fields/Color/PopoverCell.tsx b/src/components/fields/Color/PopoverCell.tsx index 0e6e90e6..6607b0b4 100644 --- a/src/components/fields/Color/PopoverCell.tsx +++ b/src/components/fields/Color/PopoverCell.tsx @@ -1,16 +1,23 @@ import { IPopoverCellProps } from "../types"; import { ColorPicker, toColor } from "react-color-palette"; +import { useDebouncedCallback } from "use-debounce"; import "react-color-palette/lib/css/styles.css"; +import { useEffect, useState } from "react"; export default function Color({ value, onSubmit }: IPopoverCellProps) { - const handleChangeComplete = (color: any) => onSubmit(color); - + const [localValue, setLocalValue] = useState(value); + const [handleChangeComplete] = useDebouncedCallback((color) => { + onSubmit(color); + }, 400); + useEffect(() => { + handleChangeComplete(localValue); + }, [localValue]); return ( ); diff --git a/src/components/fields/Connector/InlineCell.tsx b/src/components/fields/Connector/InlineCell.tsx new file mode 100644 index 00000000..bdd05840 --- /dev/null +++ b/src/components/fields/Connector/InlineCell.tsx @@ -0,0 +1,57 @@ +import { forwardRef } from "react"; +import { IPopoverInlineCellProps } from "../types"; + +import { ButtonBase, Grid, Chip } from "@mui/material"; +import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; + +import ChipList from "@src/components/Table/formatters/ChipList"; +import { get } from "lodash"; +import { getLabel } from "./utils"; + +export const ConnectService = forwardRef(function ConnectService( + { value, showPopoverCell, disabled, column }: IPopoverInlineCellProps, + ref: React.Ref +) { + const config = column.config ?? {}; + const displayKey = config.titleKey ?? config.primaryKey; + return ( + showPopoverCell(true)} + ref={ref} + disabled={disabled} + className="cell-collapse-padding" + sx={{ + height: "100%", + font: "inherit", + color: "inherit !important", + letterSpacing: "inherit", + textAlign: "inherit", + justifyContent: "flex-start", + }} + > + + {Array.isArray(value) && + value.map((item) => ( + + + + ))} + + + {!disabled && ( + + )} + + ); +}); + +export default ConnectService; diff --git a/src/components/fields/Connector/PopoverCell.tsx b/src/components/fields/Connector/PopoverCell.tsx new file mode 100644 index 00000000..e2d4beb5 --- /dev/null +++ b/src/components/fields/Connector/PopoverCell.tsx @@ -0,0 +1,35 @@ +import { IPopoverCellProps } from "../types"; + +import ConnectServiceSelect from "./Select"; + +export default function ConnectService({ + value, + onSubmit, + column, + parentRef, + showPopoverCell, + disabled, + docRef, +}: IPopoverCellProps) { + return ( + showPopoverCell(false), + }, + }} + /> + ); +} diff --git a/src/components/fields/Connector/Select/PopupContents.tsx b/src/components/fields/Connector/Select/PopupContents.tsx new file mode 100644 index 00000000..1d4c79d5 --- /dev/null +++ b/src/components/fields/Connector/Select/PopupContents.tsx @@ -0,0 +1,213 @@ +import React, { useEffect, useState } from "react"; +import clsx from "clsx"; +import { useDebouncedCallback } from "use-debounce"; +import _get from "lodash/get"; + +import { + Button, + Checkbox, + Divider, + Grid, + InputAdornment, + List, + ListItemIcon, + ListItemText, + MenuItem, + TextField, + Typography, + Radio, +} from "@mui/material"; +import SearchIcon from "@mui/icons-material/Search"; + +import { IConnectorSelectProps } from "."; +import useStyles from "./styles"; +import Loading from "@src/components/Loading"; +import { useProjectContext } from "@src/contexts/ProjectContext"; +import { replacer } from "@src/utils/fns"; +import { getLabel } from "../utils"; +import { useSnackbar } from "notistack"; + +export interface IPopupContentsProps + extends Omit {} + +// TODO: Implement infinite scroll here +export default function PopupContents({ + value = [], + onChange, + column, + docRef, +}: IPopupContentsProps) { + const { rowyRun, tableState } = useProjectContext(); + + const { enqueueSnackbar } = useSnackbar(); + // const url = config.url ; + const { config } = column; + const elementId = config.elementId; + const multiple = Boolean(config.multiple); + + const classes = useStyles(); + + // Webservice search query + const [query, setQuery] = useState(""); + // Webservice response + const [response, setResponse] = useState(null); + const [hits, setHits] = useState([]); + + useEffect(() => { + console.log(response); + if (response?.success === false) { + enqueueSnackbar(response.message, { variant: "error" }); + } else if (Array.isArray(response?.hits)) { + setHits(response.hits); + } else { + setHits([]); + //enqueueSnackbar("response is not any array", { variant: "error" }); + } + }, [response]); + const [search] = useDebouncedCallback( + async (query: string) => { + const resp = await rowyRun!({ + route: { method: "POST", path: "/connector" }, + body: { + columnKey: column.key, + query: query, + schemaDocPath: tableState?.config.tableConfig.path, + rowDocPath: docRef.path, + }, + }); + setResponse(resp); + }, + 1000, + { leading: true } + ); + + useEffect(() => { + search(query); + }, [query]); + + if (!response) return ; + + const select = (hit: any) => () => { + if (multiple) onChange([...value, hit]); + else onChange([hit]); + }; + const deselect = (hit: any) => () => { + if (multiple) + onChange(value.filter((v) => v[elementId] !== hit[elementId])); + else onChange([]); + }; + + const selectedValues = value?.map((item) => _get(item, elementId)); + + const clearSelection = () => onChange([]); + + return ( + + + setQuery(e.target.value)} + fullWidth + variant="filled" + margin="dense" + label="Search items" + className={classes.noMargins} + InputProps={{ + endAdornment: ( + + + + ), + }} + onClick={(e) => e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} + /> + + + + + {hits.map((hit) => { + const isSelected = selectedValues.some((v) => v === hit[elementId]); + console.log({ + isSelected, + selectedValues, + elementId, + }); + return ( + + = config.max + } + > + + {multiple ? ( + + ) : ( + + )} + + + + + + ); + })} + + + + {multiple && ( + + + + {value?.length} of {hits?.length} + + + + + + )} + + ); +} diff --git a/src/components/fields/Connector/Select/index.tsx b/src/components/fields/Connector/Select/index.tsx new file mode 100644 index 00000000..7508b7cf --- /dev/null +++ b/src/components/fields/Connector/Select/index.tsx @@ -0,0 +1,72 @@ +import { Suspense } from "react"; +import clsx from "clsx"; + +import { TextField, TextFieldProps } from "@mui/material"; + +import useStyles from "./styles"; +import Loading from "@src/components/Loading"; +import ErrorBoundary from "@src/components/ErrorBoundary"; +import PopupContents from "./PopupContents"; + +export type ServiceValue = { + value: string; + [prop: string]: any; +}; + +export interface IConnectorSelectProps { + value: ServiceValue[]; + onChange: (value: ServiceValue[]) => void; + column: any; + editable?: boolean; + /** Optional style overrides for root MUI `TextField` component */ + className?: string; + /** Override any props of the root MUI `TextField` component */ + TextFieldProps?: Partial; + docRef: firebase.default.firestore.DocumentReference; + disabled?: boolean; +} + +export default function ConnectorSelect({ + value = [], + className, + TextFieldProps = {}, + disabled, + ...props +}: IConnectorSelectProps) { + const classes = useStyles(); + + const sanitisedValue = Array.isArray(value) ? value : []; + return ( + `${(value as any[]).length} selected`, + displayEmpty: true, + classes: { select: classes.selectRoot }, + ...TextFieldProps.SelectProps, + // Must have this set to prevent MUI transforming `value` + // prop for this component to a comma-separated string + MenuProps: { + classes: { paper: classes.paper, list: classes.menuChild }, + MenuListProps: { disablePadding: true }, + anchorOrigin: { vertical: "bottom", horizontal: "center" }, + transformOrigin: { vertical: "top", horizontal: "center" }, + ...TextFieldProps.SelectProps?.MenuProps, + }, + }} + disabled={disabled} + > + + }> + + + + + ); +} diff --git a/src/components/fields/Connector/Select/styles.ts b/src/components/fields/Connector/Select/styles.ts new file mode 100644 index 00000000..8398b468 --- /dev/null +++ b/src/components/fields/Connector/Select/styles.ts @@ -0,0 +1,83 @@ +import { makeStyles, createStyles } from "@mui/styles"; + +export const useStyles = makeStyles((theme) => + createStyles({ + root: { minWidth: 200 }, + selectRoot: { paddingRight: theme.spacing(4) }, + + paper: { overflow: "hidden", maxHeight: "calc(100% - 48px)" }, + menuChild: { + padding: `0 ${theme.spacing(2)}`, + minWidth: 340, + // Need to set fixed height here so popup is positioned correctly + height: 340, + }, + + grid: { outline: 0 }, + + noMargins: { margin: 0 }, + + searchRow: { marginTop: theme.spacing(2) }, + + listRow: { + background: `${theme.palette.background.paper} no-repeat`, + position: "relative", + margin: theme.spacing(0, -2), + maxWidth: `calc(100% + ${theme.spacing(4)})`, + + "&::before, &::after": { + content: '""', + position: "absolute", + top: 0, + left: 0, + right: 0, + zIndex: 9, + + display: "block", + height: 16, + + background: `linear-gradient(to bottom, ${theme.palette.background.paper}, rgba(255, 255, 255, 0))`, + }, + + "&::after": { + top: "auto", + bottom: 0, + background: `linear-gradient(to top, ${theme.palette.background.paper}, rgba(255, 255, 255, 0))`, + }, + }, + list: () => { + let maxHeightDeductions = 0; + maxHeightDeductions -= 64; // search box + maxHeightDeductions -= 48; // multiple + maxHeightDeductions += 8; // footer padding + + return { + padding: theme.spacing(2, 0), + overflowY: "auto" as "auto", + // height: `calc(340px - ${-maxHeightDeductions}px)`, + height: 340 + maxHeightDeductions, + }; + }, + + checkboxContainer: { minWidth: theme.spacing(36 / 8) }, + checkbox: { + padding: theme.spacing(6 / 8, 9 / 8), + "&:hover": { background: "transparent" }, + }, + + divider: { margin: theme.spacing(0, 2, 0, 6.5) }, + + footerRow: { marginBottom: theme.spacing(2) }, + selectedRow: { + "$listRow + &": { marginTop: -theme.spacing(1) }, + "$footerRow + &": { marginTop: -theme.spacing(2) }, + + marginBottom: 0, + "& > div": { height: 48 }, + }, + selectAllButton: { marginRight: -theme.spacing(1) }, + selectedNum: { fontFeatureSettings: '"tnum"' }, + }) +); + +export default useStyles; diff --git a/src/components/fields/Connector/Settings.tsx b/src/components/fields/Connector/Settings.tsx new file mode 100644 index 00000000..7f240064 --- /dev/null +++ b/src/components/fields/Connector/Settings.tsx @@ -0,0 +1,164 @@ +import { lazy, Suspense, useState } from "react"; +import _get from "lodash/get"; +import stringify from "json-stable-stringify-without-jsonify"; + +import { + Stepper, + Step, + StepButton, + StepContent, + Stack, + Grid, + Switch, + TextField, + FormControl, + FormLabel, + FormControlLabel, + RadioGroup, + Radio, + Typography, + InputLabel, + Link, + Checkbox, + FormHelperText, + Fab, +} from "@mui/material"; + +import SteppedAccordion from "@src/components/SteppedAccordion"; +import FieldSkeleton from "@src/components/SideDrawer/Form/FieldSkeleton"; +import CodeEditorHelper from "@src/components/CodeEditor/CodeEditorHelper"; +import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; +/* eslint-disable import/no-webpack-loader-syntax */ +import connectorDefs from "!!raw-loader!./connector.d.ts"; + +import { useProjectContext } from "@src/contexts/ProjectContext"; +import { WIKI_LINKS } from "@src/constants/externalLinks"; +import { useAppContext } from "@src/contexts/AppContext"; +import { baseFunction } from "./utils"; + +//import typeDefs from "!!raw-loader!./types.d.ts"; +const CodeEditor = lazy( + () => + import("@src/components/CodeEditor" /* webpackChunkName: "CodeEditor" */) +); + +// external service requirement +// Web service URL : url +// Results key path :resultsKey +// Primary Key : primaryKey +// Title Key : titleKey + +// rowy managed service +// function that takes query & user then returns a list of objects with an array of objects +// Primary Key : primaryKey +// label Key : labelKey +// single select or multiselect +const diagnosticsOptions = { + noSemanticValidation: false, + noSyntaxValidation: false, + noSuggestionDiagnostics: true, +}; + +export default function Settings({ config, onChange }) { + const { projectId } = useAppContext(); + return ( + <> +
+ Connector Function + }> + + + +
+ + {/* Primary Key */} + onChange("elementId")(e.target.value)} + helperText={ + <> + The key that will be used to uniquely identify the selected + option.{" "} + + Learn more + + + + } + /> + + + {/* Title Key */} + onChange("labelFormatter")(e.target.value)} + helperText={ + <> + The field key or template that will be used to display the + selected option.{" "} + + Learn more + + + + } + /> + + + + + Allow for multiple item selection + + onChange("multiple")(e.target.checked)} + /> + + + + {config.multiple && ( + <> + onChange("max")(e.target.value)} + helperText="The maximum number of items that can be selected, or 0 for no limit." + /> + + )} + + + ); +} diff --git a/src/components/fields/Connector/SideDrawerField.tsx b/src/components/fields/Connector/SideDrawerField.tsx new file mode 100644 index 00000000..32e0c077 --- /dev/null +++ b/src/components/fields/Connector/SideDrawerField.tsx @@ -0,0 +1,74 @@ +import { Controller } from "react-hook-form"; +import { ISideDrawerFieldProps } from "../types"; +import { get } from "lodash"; + +import { useTheme, Grid, Chip } from "@mui/material"; + +import ConnectServiceSelect from "./Select"; +import { getLabel } from "./utils"; + +export default function ConnectService({ + column, + control, + disabled, + docRef, +}: ISideDrawerFieldProps) { + const theme = useTheme(); + + const config = column.config ?? {}; + const displayKey = config.titleKey ?? config.primaryKey; + + return ( + { + const handleDelete = (id: any) => () => { + // if (multiple) + onChange(value.filter((v) => get(v, config.elementId) !== id)); + // else form.setFieldValue(field.name, []); + }; + + return ( + <> + {!disabled && ( + `${value?.length ?? 0} selected`, + }, + }} + /> + )} + + {Array.isArray(value) && ( + + {value.map((item) => { + const key = get(item, config.elementId); + console.log(key, item); + return ( + + + + ); + })} + + )} + + ); + }} + /> + ); +} diff --git a/src/components/fields/Connector/connector.d.ts b/src/components/fields/Connector/connector.d.ts new file mode 100644 index 00000000..d031d64b --- /dev/null +++ b/src/components/fields/Connector/connector.d.ts @@ -0,0 +1,22 @@ +type ConnectorUser = { + timestamp: Date; + displayName: string; + email: string; + uid: string; + emailVerified: boolean; + photoURL: string; + roles: string[]; +}; +type ConnectorContext = { + row: Row; + ref: FirebaseFirestore.DocumentReference; + storage: firebasestorage.Storage; + db: FirebaseFirestore.Firestore; + auth: firebaseauth.BaseAuth; + query: string; + user: ConnectorUser; +}; +type ConnectorResult = any[]; +type Connector = ( + context: ConnectorContext +) => Promise | ActionResult; diff --git a/src/components/fields/Connector/index.tsx b/src/components/fields/Connector/index.tsx new file mode 100644 index 00000000..32507581 --- /dev/null +++ b/src/components/fields/Connector/index.tsx @@ -0,0 +1,42 @@ +import { lazy } from "react"; +import { IFieldConfig, FieldType } from "@src/components/fields/types"; +import withPopoverCell from "../_withTableCell/withPopoverCell"; +import ConnectorIcon from "@mui/icons-material/Cable"; +import BasicCell from "../_BasicCell/BasicCellNull"; +import InlineCell from "./InlineCell"; +import NullEditor from "@src/components/Table/editors/NullEditor"; + +const PopoverCell = lazy( + () => + import("./PopoverCell" /* webpackChunkName: "PopoverCell-ConnectService" */) +); +const SideDrawerField = lazy( + () => + import( + "./SideDrawerField" /* webpackChunkName: "SideDrawerField-ConnectService" */ + ) +); +const Settings = lazy( + () => import("./Settings" /* webpackChunkName: "Settings-ConnectService" */) +); + +export const config: IFieldConfig = { + type: FieldType.connector, + name: "Connector", + group: "Connection", + dataType: "any", + initialValue: "", + initializable: true, + icon: , + description: + "Connects to any table or API to fetch a list of results based on a text query or row data.", + TableCell: withPopoverCell(BasicCell, InlineCell, PopoverCell, { + anchorOrigin: { horizontal: "left", vertical: "bottom" }, + transparent: true, + }), + TableEditor: NullEditor as any, + SideDrawerField, + requireConfiguration: true, + settings: Settings, +}; +export default config; diff --git a/src/components/fields/Connector/types.d.ts b/src/components/fields/Connector/types.d.ts new file mode 100644 index 00000000..9017df1f --- /dev/null +++ b/src/components/fields/Connector/types.d.ts @@ -0,0 +1,5 @@ +type ConnectService = (request: { + query: string; + row: any; + user: any; +}) => Promise; diff --git a/src/components/fields/Connector/utils.ts b/src/components/fields/Connector/utils.ts new file mode 100644 index 00000000..e201d3f0 --- /dev/null +++ b/src/components/fields/Connector/utils.ts @@ -0,0 +1,21 @@ +import { replacer } from "@src/utils/fns"; +import _get from "lodash/get"; +export const sanitiseValue = (value: any) => { + if (value === undefined || value === null || value === "") return []; + else return value as string[]; +}; + +export const baseFunction = `const connectorFn: Connector = async ({query, row, user}) => { + // TODO: Implement your service function here + return []; +};`; + +export const getLabel = (config, row) => { + if (!config.labelFormatter) { + return `⚠️ needs configuration`; + } else if (config.labelFormatter.includes("{{")) { + return config.labelFormatter.replace(/\{\{(.*?)\}\}/g, replacer(row)); + } else { + return _get(row, config.labelFormatter); + } +}; diff --git a/src/components/fields/Image/TableCell.tsx b/src/components/fields/Image/TableCell.tsx index 3c6fd68b..3125601d 100644 --- a/src/components/fields/Image/TableCell.tsx +++ b/src/components/fields/Image/TableCell.tsx @@ -30,6 +30,7 @@ const useStyles = makeStyles((theme) => root: { padding: theme.spacing(0, 0.5, 0, 1), outline: "none", + height: "100%", }, dragActive: { backgroundColor: alpha( diff --git a/src/components/fields/index.tsx b/src/components/fields/index.tsx index d4340295..85b893a3 100644 --- a/src/components/fields/index.tsx +++ b/src/components/fields/index.tsx @@ -39,6 +39,7 @@ import UpdatedAt from "./UpdatedAt"; import User from "./User"; import Id from "./Id"; import Status from "./Status"; +import Connector from "./Connector"; import { TableColumn } from "../Table"; // Export field configs in order for FieldsDropdown @@ -68,6 +69,7 @@ export const FIELDS: IFieldConfig[] = [ Image_, File_, // CONNECTION + Connector, SubTable, ConnectTable, ConnectService, diff --git a/src/constants/externalLinks.ts b/src/constants/externalLinks.ts index 8e069285..18de531c 100644 --- a/src/constants/externalLinks.ts +++ b/src/constants/externalLinks.ts @@ -36,6 +36,7 @@ const WIKI_PATHS = { fieldTypesSupportedFields: "/field-types/supported-fields", fieldTypesDerivative: "/field-types/derivative", fieldTypesConnectTable: "/field-types/connect-table", + fieldTypesConnector: "/field-types/connector", fieldTypesConnectService: "/field-types/connect-service", fieldTypesAction: "/field-types/action", fieldTypesAdd: "/field-types/add", diff --git a/src/constants/fields.ts b/src/constants/fields.ts index 991a34f0..71a80666 100644 --- a/src/constants/fields.ts +++ b/src/constants/fields.ts @@ -26,6 +26,7 @@ export enum FieldType { file = "FILE", // CONNECTION subTable = "SUB_TABLE", + connector = "CONNECTOR", connectTable = "DOCUMENT_SELECT", connectService = "SERVICE_SELECT", // CODE diff --git a/src/contexts/AppContext.tsx b/src/contexts/AppContext.tsx index bb24caec..881ce5ed 100644 --- a/src/contexts/AppContext.tsx +++ b/src/contexts/AppContext.tsx @@ -81,15 +81,18 @@ export const AppProvider: React.FC = ({ children }) => { }; useEffect(() => { auth.onAuthStateChanged((auth) => { - setCurrentUser(auth); - if (auth) + if (auth) { auth.getIdTokenResult(true).then((results) => { + setCurrentUser(auth); setAuthToken(results.token); setUserRoles( Array.isArray(results.claims.roles) ? results.claims.roles : [] ); setUserClaims(results.claims); }); + } else { + setCurrentUser(auth); + } }); }, []); diff --git a/src/contexts/ProjectContext.tsx b/src/contexts/ProjectContext.tsx index 7951d5ce..053672eb 100644 --- a/src/contexts/ProjectContext.tsx +++ b/src/contexts/ProjectContext.tsx @@ -6,9 +6,6 @@ import _find from "lodash/find"; import firebase from "firebase/app"; import { compare } from "compare-versions"; -import { Button } from "@mui/material"; -import InlineOpenInNewIcon from "@src/components/InlineOpenInNewIcon"; - import useTable, { TableActions, TableState } from "@src/hooks/useTable"; import useSettings from "@src/hooks/useSettings"; import { useAppContext } from "./AppContext"; @@ -123,6 +120,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => { const { enqueueSnackbar } = useSnackbar(); const { tableState, tableActions } = useTable(); const [tables, setTables] = useState(); + const [settings, settingsActions] = useSettings(); const table = _find(tables, (table) => table.id === tableState.config.id); @@ -166,7 +164,12 @@ export const ProjectContextProvider: React.FC = ({ children }) => { () => Array.isArray(tables) ? Array.from( - new Set(tables.reduce((a, c) => [...a, ...c.roles], ["ADMIN"])) + new Set( + tables.reduce( + (a, c) => [...a, ...c.roles], + ["ADMIN", "EDITOR", "VIEWER"] + ) + ) ) : [], [tables] @@ -373,8 +376,8 @@ export const ProjectContextProvider: React.FC = ({ children }) => { const { service, ...rest } = args; const authToken = await getAuthToken(); const serviceUrl = service - ? settings.doc.services[service] - : settings.doc.rowyRunUrl; + ? settings.doc?.services[service] + : settings.doc?.rowyRunUrl; if (serviceUrl) { return rowyRun({ diff --git a/src/hooks/useTable/useTableData.tsx b/src/hooks/useTable/useTableData.tsx index 96b3aec9..5f72a892 100644 --- a/src/hooks/useTable/useTableData.tsx +++ b/src/hooks/useTable/useTableData.tsx @@ -310,14 +310,19 @@ const useTableData = () => { let ref = db.collection(path).doc(); if (typeof id === "string") ref = db.collection(path).doc(id); - else if (id?.type === "smaller") - ref = db - .collection(path) - .doc( - decrementId( - rows.find((r) => !r._rowy_outOfOrder)?.id ?? "zzzzzzzzzzzzzzzzzzzz" - ) - ); + else if (id?.type === "smaller") { + let prevId = + rows.find((r) => !r._rowy_outOfOrder)?.id ?? "zzzzzzzzzzzzzzzzzzzz"; + if ( + tableState.orderBy?.length !== 0 || + tableState.filters?.length !== 0 + ) { + const query = await db.collection(tableState.path).limit(1).get(); + prevId = query.empty ? "zzzzzzzzzzzzzzzzzzzz" : query.docs[0].id; + } + ref = db.collection(path).doc(decrementId(prevId)); + } + const newId = ref.id; const missingRequiredFields = requiredFields diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index c9a21af7..dae0f598 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -78,10 +78,10 @@ export default function HomePage() { data: null, }); - const handleCreateTable = () => + const handleCreateTable = (data?: null | (Table & { tableType: string })) => setSettingsDialogState({ mode: TableSettingsDialogModes.create, - data: null, + data: data || null, }); const [settingsDocState] = useDoc( @@ -107,7 +107,7 @@ export default function HomePage() { handleCreateTable()} sx={{ zIndex: "speedDial", position: "fixed", diff --git a/src/utils/fns.ts b/src/utils/fns.ts index 3f20d77e..2e404262 100644 --- a/src/utils/fns.ts +++ b/src/utils/fns.ts @@ -195,3 +195,9 @@ export const isTargetInsideBox = (target, box) => { const boxRect = box.getBoundingClientRect(); return targetRect.y < boxRect.y + boxRect.height; }; + +export const replacer = (data: any) => (m: string, key: string) => { + const objKey = key.split(":")[0]; + const defaultValue = key.split(":")[1] || ""; + return _get(data, objKey, defaultValue); +}; diff --git a/yarn.lock b/yarn.lock index aa5bd707..8f3dc39e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2429,38 +2429,38 @@ "@monaco-editor/loader" "^1.2.0" prop-types "^15.7.2" -"@mui/base@5.0.0-alpha.70": - version "5.0.0-alpha.70" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.70.tgz#e280ee3b69d86034f2cff445161747940129d576" - integrity sha512-8UZWhz1JYuQnPkAbC37cl4aL1JyNWZ08wDXlp57W7fYQp5xFpBP/7p56AcWg2qG9CNJP0IlFg2Wp4md1v2l4iA== +"@mui/base@5.0.0-alpha.72": + version "5.0.0-alpha.72" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.72.tgz#551d64402ee5065cf81fd1388a3e7ab8c426fe3e" + integrity sha512-WCAooa9eqbsC68LhyKtDBRumH4hV1eRZ0A3SDKFHSwYG9fCOdsFv/H1dIYRJM0rwD45bMnuDiG3Qmx7YsTiptw== dependencies: "@babel/runtime" "^7.17.2" "@emotion/is-prop-valid" "^1.1.2" "@mui/utils" "^5.4.4" - "@popperjs/core" "^2.4.4" + "@popperjs/core" "^2.11.3" clsx "^1.1.1" prop-types "^15.7.2" react-is "^17.0.2" -"@mui/icons-material@^5.4.4": - version "5.4.4" - resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.4.4.tgz#0dc7b4e68cbbdfc675f09f0763be1100aad910af" - integrity sha512-7zoRpjO8vsd+bPvXq6rtXu0V8Saj70X09dtTQogZmxQKabrYW3g7+Yym7SCRA7IYVF3ysz2AvdQrGD1P/sGepg== +"@mui/icons-material@^5.5.1": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.5.1.tgz#848a57972617411370775980cbc6990588d4aafb" + integrity sha512-40f68p5+Yhq3dCn3QYHqQt5RETPyR3AkDw+fma8PtcjqvZ+d+jF84kFmT6NqwA3he7TlwluEtkyAmPzUE4uPdA== dependencies: "@babel/runtime" "^7.17.2" -"@mui/lab@^5.0.0-alpha.71": - version "5.0.0-alpha.71" - resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.71.tgz#6be1e648b63316dd628c0179b418adfd4c0a5c22" - integrity sha512-ScGfSsiYa2XZl+TYEgDFoCv1DoXoWNQwyJBbDlapacEw10wGmY6sgMkCjsPhpuabgC5FVOaV5k30OxG7cZKXJQ== +"@mui/lab@^5.0.0-alpha.73": + version "5.0.0-alpha.73" + resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.73.tgz#f1b81682be155d87d492f64202ada8b7fe83be08" + integrity sha512-10Uj0Atc7gBTXKX4VV38P6RdqTQrJZxcl3HeEcytIO1S3NAGfc7gZ3Hdpnhtj5U8kcRJZZPH9LtrBbMZzxU/1A== dependencies: "@babel/runtime" "^7.17.2" "@date-io/date-fns" "^2.13.1" "@date-io/dayjs" "^2.13.1" "@date-io/luxon" "^2.13.1" "@date-io/moment" "^2.13.1" - "@mui/base" "5.0.0-alpha.70" - "@mui/system" "^5.4.4" + "@mui/base" "5.0.0-alpha.72" + "@mui/system" "^5.5.1" "@mui/utils" "^5.4.4" clsx "^1.1.1" prop-types "^15.7.2" @@ -2468,19 +2468,19 @@ react-transition-group "^4.4.2" rifm "^0.12.1" -"@mui/material@^5.4.4": - version "5.4.4" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.4.4.tgz#2652a07085bf107da590007286336640f605055e" - integrity sha512-VDJC7GzO1HTFqfMe2zwvaW/sRhABBJXFkKEv5gO3uXx7x9fdwJHQr4udU7NWZCUdOcx9Y0h3BsAILLefYq+WPw== +"@mui/material@^5.5.1": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.5.1.tgz#9ca89a8b32afd59c843a5bc0332b0786cf9bf1d0" + integrity sha512-bJSYgymgSZ7btPTNnWFrr2EmGoVQc4A/0WLfP/ESY2dxnhnbFDwt7twiOKmJp3u84YXriEDt5v9EZQLf7A+y0Q== dependencies: "@babel/runtime" "^7.17.2" - "@mui/base" "5.0.0-alpha.70" - "@mui/system" "^5.4.4" - "@mui/types" "^7.1.2" + "@mui/base" "5.0.0-alpha.72" + "@mui/system" "^5.5.1" + "@mui/types" "^7.1.3" "@mui/utils" "^5.4.4" "@types/react-transition-group" "^4.4.4" clsx "^1.1.1" - csstype "^3.0.10" + csstype "^3.0.11" hoist-non-react-statics "^3.3.2" prop-types "^15.7.2" react-is "^17.0.2" @@ -2504,18 +2504,18 @@ "@emotion/cache" "^11.7.1" prop-types "^15.7.2" -"@mui/styles@^5.4.4": - version "5.4.4" - resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-5.4.4.tgz#e6a18f3528a494d782fd80e7feb01bd75a3570dd" - integrity sha512-w+9VIC1+JiPfF7osomX1j+aX7yyNNw8BnMYo6niK+zbwIxSYX/wcq4Jh7rlt6FSiaKL4Qi1uf7MPlNAhIxXq3g== +"@mui/styles@^5.5.1": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-5.5.1.tgz#cfd2b6dbdb4b2cb0e989568bb9cc45f5d7346d2a" + integrity sha512-mxwfjwTwPE+r7/U4Nn/QKPzJ2cIqmRuK3xu44Us613D5jqPeB/ftOsAy0OpCYAwpkUxmQIrRQiilQ8zE+f4rBQ== dependencies: "@babel/runtime" "^7.17.2" "@emotion/hash" "^0.8.0" "@mui/private-theming" "^5.4.4" - "@mui/types" "^7.1.2" + "@mui/types" "^7.1.3" "@mui/utils" "^5.4.4" clsx "^1.1.1" - csstype "^3.0.10" + csstype "^3.0.11" hoist-non-react-statics "^3.3.2" jss "^10.8.2" jss-plugin-camel-case "^10.8.2" @@ -2527,24 +2527,24 @@ jss-plugin-vendor-prefixer "^10.8.2" prop-types "^15.7.2" -"@mui/system@^5.4.4": - version "5.4.4" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.4.4.tgz#cd5d3d35c75594abd88708e715b5e39a8874ff51" - integrity sha512-Zjbztq2o/VRuRRCWjG44juRrPKYLQMqtQpMHmMttGu5BnvK6PAPW3WOY0r1JCAwLhbd8Kug9nyhGQYKETjo+tQ== +"@mui/system@^5.5.1": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.5.1.tgz#1f2b2a8c5542db6176e3b5a8ed12aea602cdeb81" + integrity sha512-2hynI4hN8304hOCT8sc4knJviwUUYJ7XK3mXwQ0nagVGOPnWSOad/nYADm7K0vdlCeUXLIbDbe7oNN3Kaiu2kA== dependencies: "@babel/runtime" "^7.17.2" "@mui/private-theming" "^5.4.4" "@mui/styled-engine" "^5.4.4" - "@mui/types" "^7.1.2" + "@mui/types" "^7.1.3" "@mui/utils" "^5.4.4" clsx "^1.1.1" - csstype "^3.0.10" + csstype "^3.0.11" prop-types "^15.7.2" -"@mui/types@^7.1.2": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.2.tgz#4f3678ae77a7a3efab73b6e040469cc6df2144ac" - integrity sha512-SD7O1nVzqG+ckQpFjDhXPZjRceB8HQFHEvdLLrPhlJy4lLbwEBbxK74Tj4t6Jgk0fTvLJisuwOutrtYe9P/xBQ== +"@mui/types@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.3.tgz#d7636f3046110bcccc63e6acfd100e2ad9ca712a" + integrity sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA== "@mui/utils@^5.4.4": version "5.4.4" @@ -2620,10 +2620,10 @@ schema-utils "^2.6.5" source-map "^0.7.3" -"@popperjs/core@^2.4.4": - version "2.10.2" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590" - integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ== +"@popperjs/core@^2.11.3": + version "2.11.4" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.4.tgz#d8c7b8db9226d2d7664553a0741ad7d0397ee503" + integrity sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg== "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" @@ -6252,10 +6252,10 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^3.0.10: - version "3.0.10" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" - integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== +csstype@^3.0.11: + version "3.0.11" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" + integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== csstype@^3.0.2: version "3.0.9" @@ -11864,9 +11864,9 @@ minimatch@3.0.4, minimatch@^3.0.4: brace-expansion "^1.1.7" minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== minipass-collect@^1.0.2: version "1.0.2"