From 6b063dba3d9edc15622819da0539052b2e531cdb Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Mon, 29 Nov 2021 14:37:36 +1100 Subject: [PATCH] fix webhooks, extensions styling --- package.json | 1 - .../Extensions/AddExtensionButton.tsx | 83 ++++ .../TableHeader/Extensions/ExtensionList.tsx | 323 ++++++--------- .../TableHeader/Extensions/index.tsx | 60 ++- .../TableHeader/Webhooks/AddWebhookButton.tsx | 83 ++++ .../TableHeader/Webhooks/WebhookList.tsx | 375 +++++++----------- src/components/TableHeader/Webhooks/index.tsx | 59 +-- src/contexts/ProjectContext.tsx | 11 +- 8 files changed, 483 insertions(+), 512 deletions(-) create mode 100644 src/components/TableHeader/Extensions/AddExtensionButton.tsx create mode 100644 src/components/TableHeader/Webhooks/AddWebhookButton.tsx diff --git a/package.json b/package.json index 7c61dd52..7161e2e9 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "react-router-hash-link": "^2.4.3", "react-scripts": "^4.0.3", "react-usestateref": "^1.0.5", - "semver": "^7.3.5", "serve": "^11.3.2", "swr": "^1.0.1", "tinymce": "^5.9.2", diff --git a/src/components/TableHeader/Extensions/AddExtensionButton.tsx b/src/components/TableHeader/Extensions/AddExtensionButton.tsx new file mode 100644 index 00000000..6d4f43e7 --- /dev/null +++ b/src/components/TableHeader/Extensions/AddExtensionButton.tsx @@ -0,0 +1,83 @@ +import { useRef, useState } from "react"; + +import { + Button, + ButtonProps, + Menu, + MenuItem, + Divider, + ListItemIcon, +} from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import EmailIcon from "@mui/icons-material/EmailOutlined"; + +import { extensionTypes, extensionNames, ExtensionType } from "./utils"; +import { EMAIL_REQUEST } from "@src/constants/externalLinks"; + +export interface IAddExtensionButtonProps extends Partial { + handleAddExtension: (type: ExtensionType) => void; +} + +export default function AddExtensionButton({ + handleAddExtension, + ...props +}: IAddExtensionButtonProps) { + const addButtonRef = useRef(null); + const [open, setOpen] = useState(false); + + const handleChooseAddType = (type: ExtensionType) => { + setOpen(false); + handleAddExtension(type); + }; + + return ( + <> + + + setOpen(false)} + anchorOrigin={{ vertical: "bottom", horizontal: "right" }} + transformOrigin={{ vertical: "top", horizontal: "right" }} + > + {extensionTypes.map((type) => ( + handleChooseAddType(type)}> + {extensionNames[type]} + + ))} + + + + + + + + Request new Extension… + + + + ); +} diff --git a/src/components/TableHeader/Extensions/ExtensionList.tsx b/src/components/TableHeader/Extensions/ExtensionList.tsx index e85c04fd..8dd858b2 100644 --- a/src/components/TableHeader/Extensions/ExtensionList.tsx +++ b/src/components/TableHeader/Extensions/ExtensionList.tsx @@ -1,43 +1,27 @@ -import { useState, useRef } from "react"; import { format, formatRelative } from "date-fns"; import { Stack, - ButtonBase, List, ListItem, ListItemText, Avatar, - Button, IconButton, - Menu, - MenuItem, - Divider, - ListItemIcon, Switch, Tooltip, Typography, } from "@mui/material"; -import AddIcon from "@mui/icons-material/Add"; import ExtensionIcon from "@src/assets/icons/Extension"; import DuplicateIcon from "@src/assets/icons/Copy"; import EditIcon from "@mui/icons-material/EditOutlined"; import DeleteIcon from "@mui/icons-material/DeleteOutlined"; -import EmailIcon from "@mui/icons-material/EmailOutlined"; import EmptyState from "@src/components/EmptyState"; -import { - extensionTypes, - extensionNames, - IExtension, - ExtensionType, -} from "./utils"; +import { extensionNames, IExtension } from "./utils"; import { DATE_TIME_FORMAT } from "@src/constants/dates"; -import { EMAIL_REQUEST } from "@src/constants/externalLinks"; export interface IExtensionListProps { extensions: IExtension[]; - handleAddExtension: (type: ExtensionType) => void; handleUpdateActive: (index: number, active: boolean) => void; handleDuplicate: (index: number) => void; handleEdit: (index: number) => void; @@ -46,204 +30,127 @@ export interface IExtensionListProps { export default function ExtensionList({ extensions, - handleAddExtension, handleUpdateActive, handleDuplicate, handleEdit, handleDelete, }: IExtensionListProps) { - const [anchorEl, setAnchorEl] = useState(null); - const addButtonRef = useRef(null); - - const activeExtensionCount = extensions.filter( - (extension) => extension.active - ).length; - - const handleAddButton = () => { - setAnchorEl(addButtonRef.current); - }; - - const handleChooseAddType = (type: ExtensionType) => { - handleClose(); - handleAddExtension(type); - }; - - const handleClose = () => { - setAnchorEl(null); - }; + if (extensions.length === 0) + return ( + + ); return ( - <> - - - Extensions ({activeExtensionCount} / {extensions.length}) - - - - - {extensionTypes.map((type) => ( - handleChooseAddType(type)}> - {extensionNames[type]} - - ))} - - - - - - - - Request new Extension… - - - - - {extensions.length === 0 ? ( - - - - ) : ( - - {extensions.map((extensionObject, index) => ( - - } - secondaryAction={ - - - - - handleUpdateActive(index, !extensionObject.active) - } - inputProps={{ "aria-label": "Activate" }} - sx={{ mr: 1 }} - /> - - - - handleDuplicate(index)} - > - - - - - handleEdit(index)} - > - - - - - handleDelete(index)} - sx={{ "&&": { mr: -1.5 } }} - > - - - - - - - Last updated -
- by {extensionObject.lastEditor.displayName} -
- at{" "} - {format( - extensionObject.lastEditor.lastUpdate, - DATE_TIME_FORMAT - )} - - } - > - - - {formatRelative( - extensionObject.lastEditor.lastUpdate, - new Date() - )} - - - -
-
- } + + {extensions.map((extensionObject, index) => ( + - ))} - - )} - + } + secondaryAction={ + + + + + handleUpdateActive(index, !extensionObject.active) + } + inputProps={{ "aria-label": "Activate" }} + sx={{ mr: 1 }} + /> + + + + handleDuplicate(index)} + > + + + + + handleEdit(index)} + > + + + + + handleDelete(index)} + sx={{ "&&": { mr: -1.5 } }} + > + + + + + + + Last updated +
+ by {extensionObject.lastEditor.displayName} +
+ at{" "} + {format( + extensionObject.lastEditor.lastUpdate, + DATE_TIME_FORMAT + )} + + } + > + + + {formatRelative( + extensionObject.lastEditor.lastUpdate, + new Date() + )} + + + +
+
+ } + sx={{ + flexWrap: { xs: "wrap", sm: "nowrap" }, + "& .MuiListItemSecondaryAction-root": { + position: { xs: "static", sm: "absolute" }, + width: { xs: "100%", sm: "auto" }, + transform: { xs: "none", sm: "translateY(-50%)" }, + }, + pr: { xs: 0, sm: 216 / 8 }, + }} + /> + ))} +
); } diff --git a/src/components/TableHeader/Extensions/index.tsx b/src/components/TableHeader/Extensions/index.tsx index 91349884..86a9cb6d 100644 --- a/src/components/TableHeader/Extensions/index.tsx +++ b/src/components/TableHeader/Extensions/index.tsx @@ -1,11 +1,10 @@ import { useState } from "react"; import _isEqual from "lodash/isEqual"; -import { Breadcrumbs } from "@mui/material"; - import TableHeaderButton from "../TableHeaderButton"; import ExtensionIcon from "@src/assets/icons/Extension"; import Modal from "@src/components/Modal"; +import AddExtensionButton from "./AddExtensionButton"; import ExtensionList from "./ExtensionList"; import ExtensionModal from "./ExtensionModal"; import ExtensionMigration from "./ExtensionMigration"; @@ -40,12 +39,6 @@ export default function Extensions() { const snackLogContext = useSnackLogContext(); const edited = !_isEqual(currentExtensionObjects, localExtensionsObjects); - const tablePathTokens = - tableState?.tablePath?.split("/").filter(function (_, i) { - // replace IDs with dash that appears at even indexes - return i % 2 === 0; - }) ?? []; - const handleOpen = () => { if (tableState?.config.sparks) { // migration is required @@ -180,6 +173,10 @@ export default function Extensions() { lastUpdate: Date.now(), }); + const activeExtensionCount = localExtensionsObjects.filter( + (extension) => extension.active + ).length; + return ( <> { + setExtensionModal({ + mode: "add", + extensionObject: emptyExtensionObject(type, currentEditor()), + }); + }} + variant={ + localExtensionsObjects.length === 0 ? "contained" : "outlined" + } + /> + } children={ - <> - - {tablePathTokens.map((pathToken) => ( - {pathToken} - ))} - - { - setExtensionModal({ - mode: "add", - extensionObject: emptyExtensionObject( - type, - currentEditor() - ), - }); - }} - handleUpdateActive={handleUpdateActive} - handleEdit={handleEdit} - handleDuplicate={handleDuplicate} - handleDelete={handleDelete} - /> - + } actions={{ primary: { diff --git a/src/components/TableHeader/Webhooks/AddWebhookButton.tsx b/src/components/TableHeader/Webhooks/AddWebhookButton.tsx new file mode 100644 index 00000000..234d9264 --- /dev/null +++ b/src/components/TableHeader/Webhooks/AddWebhookButton.tsx @@ -0,0 +1,83 @@ +import { useRef, useState } from "react"; + +import { + Button, + ButtonProps, + Menu, + MenuItem, + Divider, + ListItemIcon, +} from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import EmailIcon from "@mui/icons-material/EmailOutlined"; + +import { webhookTypes, webhookNames, WebhookType } from "./utils"; +import { EMAIL_REQUEST } from "@src/constants/externalLinks"; + +export interface IAddWebhookButtonProps extends Partial { + handleAddWebhook: (type: WebhookType) => void; +} + +export default function AddWebhookButton({ + handleAddWebhook, + ...props +}: IAddWebhookButtonProps) { + const addButtonRef = useRef(null); + const [open, setOpen] = useState(false); + + const handleChooseAddType = (type: WebhookType) => { + setOpen(false); + handleAddWebhook(type); + }; + + return ( + <> + + + setOpen(false)} + anchorOrigin={{ vertical: "bottom", horizontal: "right" }} + transformOrigin={{ vertical: "top", horizontal: "right" }} + > + {webhookTypes.map((type) => ( + handleChooseAddType(type)}> + {webhookNames[type]} + + ))} + + + + + + + + Request new webhook… + + + + ); +} diff --git a/src/components/TableHeader/Webhooks/WebhookList.tsx b/src/components/TableHeader/Webhooks/WebhookList.tsx index 20ee6af0..0954fb8b 100644 --- a/src/components/TableHeader/Webhooks/WebhookList.tsx +++ b/src/components/TableHeader/Webhooks/WebhookList.tsx @@ -1,37 +1,27 @@ -import { useState, useRef } from "react"; import { useAtom } from "jotai"; import { format, formatRelative } from "date-fns"; import { Stack, - ButtonBase, List, ListItem, ListItemText, Avatar, - Button, IconButton, - Menu, - MenuItem, - Divider, - ListItemIcon, Switch, Tooltip, Typography, } from "@mui/material"; -import AddIcon from "@mui/icons-material/Add"; import WebhookIcon from "@src/assets/icons/Webhook"; import LogsIcon from "@src/assets/icons/CloudLogs"; import EditIcon from "@mui/icons-material/EditOutlined"; import DeleteIcon from "@mui/icons-material/DeleteOutlined"; -import CopyIcon from "@src/assets/icons/Copy"; -import EmailIcon from "@mui/icons-material/EmailOutlined"; +import LinkIcon from "@mui/icons-material/Link"; import EmptyState from "@src/components/EmptyState"; -import { webhookTypes, webhookNames, IWebhook, WebhookType } from "./utils"; +import { webhookNames, IWebhook } from "./utils"; import { DATE_TIME_FORMAT } from "@src/constants/dates"; import { useProjectContext } from "@src/contexts/ProjectContext"; -import { EMAIL_REQUEST } from "@src/constants/externalLinks"; import { modalAtom, cloudLogFiltersAtom, @@ -39,7 +29,6 @@ import { export interface IWebhookListProps { webhooks: IWebhook[]; - handleAddWebhook: (type: WebhookType) => void; handleUpdateActive: (index: number, active: boolean) => void; handleEdit: (index: number) => void; handleDelete: (index: number) => void; @@ -47,243 +36,157 @@ export interface IWebhookListProps { export default function WebhookList({ webhooks, - handleAddWebhook, handleUpdateActive, handleEdit, handleDelete, }: IWebhookListProps) { const { settings, tableState } = useProjectContext(); - const [anchorEl, setAnchorEl] = useState(null); - const addButtonRef = useRef(null); const [, setModal] = useAtom(modalAtom); const [, setCloudLogFilters] = useAtom(cloudLogFiltersAtom); - const activeWebhookCount = webhooks.filter( - (webhook) => webhook.active - ).length; - const handleAddButton = () => { - setAnchorEl(addButtonRef.current); - }; - - const handleChooseAddType = (type: WebhookType) => { - handleClose(); - handleAddWebhook(type); - }; - - const handleClose = () => { - setAnchorEl(null); - }; const baseUrl = `${settings?.services?.hooks}/wh/${tableState?.tablePath}/`; + + if (webhooks.length === 0) + return ( + + ); + return ( - <> - - - Webhooks ({activeWebhookCount} / {webhooks.length}) - - - - - {webhookTypes.map((type) => ( - handleChooseAddType(type)}> - {webhookNames[type]} - - ))} - - - - - - - - Request new webhook… - - - - - {webhooks.length === 0 ? ( - - - - ) : ( - - {webhooks.map((webhook, index) => ( - - {webhook.name} {webhookNames[webhook.type]} - - } - secondary={ -
-
- - - {baseUrl} - {webhook.endpoint} - - -
- - - navigator.clipboard.writeText( - `${baseUrl}${webhook.endpoint}` - ) - } - > - - - -
- } - primaryTypographyProps={{ - style: { - minHeight: 40, - display: "flex", - alignItems: "center", - }, - }} - /> - } - secondaryAction={ - - - - - handleUpdateActive(index, !webhook.active) - } - inputProps={{ "aria-label": "Activate" }} - sx={{ mr: 1 }} - /> - - - - { - setModal("cloudLogs"); - setCloudLogFilters({ - type: "webhook", - timeRange: { type: "days", value: 7 }, - webhook: [webhook.endpoint], - }); - }} - > - - - - - handleEdit(index)} - > - - - - - handleDelete(index)} - sx={{ "&&": { mr: -1.5 } }} - > - - - - - - - Last updated -
- by {webhook.lastEditor.displayName} -
- at{" "} - {format( - webhook.lastEditor.lastUpdate, - DATE_TIME_FORMAT - )} - - } + + {webhooks.map((webhook, index) => ( + + {webhookNames[webhook.type]}{" "} + - - + {webhook.endpoint} +
+ + + navigator.clipboard.writeText( + baseUrl + webhook.endpoint + ) + } + size="small" + color="secondary" + sx={{ my: (20 - 32) / 2 / 8 }} > - {formatRelative( - webhook.lastEditor.lastUpdate, - new Date() - )} - - -
- - + + + + + } + primaryTypographyProps={{ + style: { + minHeight: 40, + display: "flex", + alignItems: "center", + }, + }} /> - ))} -
- )} - + } + secondaryAction={ + + + + handleUpdateActive(index, !webhook.active)} + inputProps={{ "aria-label": "Activate" }} + sx={{ mr: 1 }} + /> + + + + { + setModal("cloudLogs"); + setCloudLogFilters({ + type: "webhook", + timeRange: { type: "days", value: 7 }, + webhook: [webhook.endpoint], + }); + }} + > + + + + + handleEdit(index)} + > + + + + + handleDelete(index)} + sx={{ "&&": { mr: -1.5 } }} + > + + + + + + + Last updated +
+ by {webhook.lastEditor.displayName} +
+ at {format(webhook.lastEditor.lastUpdate, DATE_TIME_FORMAT)} + + } + > + + + {formatRelative(webhook.lastEditor.lastUpdate, new Date())} + + + +
+
+ } + sx={{ + flexWrap: { xs: "wrap", sm: "nowrap" }, + "& .MuiListItemSecondaryAction-root": { + position: { xs: "static", sm: "absolute" }, + width: { xs: "100%", sm: "auto" }, + transform: { xs: "none", sm: "translateY(-50%)" }, + }, + pr: { xs: 0, sm: 216 / 8 }, + }} + /> + ))} + ); } diff --git a/src/components/TableHeader/Webhooks/index.tsx b/src/components/TableHeader/Webhooks/index.tsx index 5c23de41..f5ec9530 100644 --- a/src/components/TableHeader/Webhooks/index.tsx +++ b/src/components/TableHeader/Webhooks/index.tsx @@ -1,11 +1,10 @@ import { useState } from "react"; import _isEqual from "lodash/isEqual"; -import { Breadcrumbs } from "@mui/material"; - import TableHeaderButton from "../TableHeaderButton"; import WebhookIcon from "@src/assets/icons/Webhook"; import Modal from "@src/components/Modal"; +import AddWebhookButton from "./AddWebhookButton"; import WebhookList from "./WebhookList"; import WebhookModal from "./WebhookModal"; @@ -34,14 +33,10 @@ export default function Webhooks() { webhookObject: IWebhook; index?: number; } | null>(null); - if (!compatibleRowyRunVersion?.({ minVersion: "1.2.0" })) return <>; - const edited = !_isEqual(currentWebhooks, localWebhooksObjects); - const tablePathTokens = - tableState?.tablePath?.split("/").filter(function (_, i) { - // replace IDs with dash that appears at even indexes - return i % 2 === 0; - }) ?? []; + if (!compatibleRowyRunVersion?.({ minVersion: "1.2.0" })) return null; + + const edited = !_isEqual(currentWebhooks, localWebhooksObjects); const handleOpen = () => { setOpenWebhookList(true); @@ -159,6 +154,10 @@ export default function Webhooks() { lastUpdate: Date.now(), }); + const activeWebhookCount = localWebhooksObjects.filter( + (webhook) => webhook.active + ).length; + return ( <> { + setWebhookModal({ + mode: "add", + webhookObject: emptyWebhookObject(type, currentEditor()), + }); + }} + variant={ + localWebhooksObjects.length === 0 ? "contained" : "outlined" + } + /> + } children={ - <> - - {tablePathTokens.map((pathToken) => ( - {pathToken} - ))} - - { - setWebhookModal({ - mode: "add", - webhookObject: emptyWebhookObject(type, currentEditor()), - }); - }} - handleUpdateActive={handleUpdateActive} - handleEdit={handleEdit} - handleDelete={handleDelete} - /> - + } actions={{ primary: { diff --git a/src/contexts/ProjectContext.tsx b/src/contexts/ProjectContext.tsx index e025ccdf..ec00eeac 100644 --- a/src/contexts/ProjectContext.tsx +++ b/src/contexts/ProjectContext.tsx @@ -4,6 +4,7 @@ import { DataGridHandle } from "react-data-grid"; import _sortBy from "lodash/sortBy"; 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"; @@ -19,7 +20,6 @@ import { rowyRun, IRowyRunRequestProps } from "@src/utils/rowyRun"; import { rowyUser } from "@src/utils/fns"; import { WIKI_LINKS } from "@src/constants/externalLinks"; import { runRoutes } from "@src/constants/runRoutes"; -import semver from "semver"; export type Table = { id: string; @@ -375,13 +375,12 @@ export const ProjectContextProvider: React.FC = ({ children }) => { minVersion?: string; maxVersion?: string; }) => { - // example: "1.0.0", "1.0.0-beta.1", "1.0.0-rc.1+1" - const version = rowyRunVersion.split("-")[0]; - if (!version) return false; - if (minVersion && semver.lt(version, minVersion)) return false; - if (maxVersion && semver.gt(version, maxVersion)) return false; + if (!rowyRunVersion) return false; + if (minVersion && compare(rowyRunVersion, minVersion, "<")) return false; + if (maxVersion && compare(rowyRunVersion, maxVersion, ">")) return false; return true; }; + // A ref to the data grid. Contains data grid functions const dataGridRef = useRef(null); const sideDrawerRef = useRef();