From aa767ec945373f4dd1f5c7e0f80fe76758069e4c Mon Sep 17 00:00:00 2001 From: Thomas Zerr Date: Mon, 18 Sep 2023 10:04:03 +0200 Subject: [PATCH] Add Loading Spinner API to content action overflow menu extension point --- .../changed_content_action_menu_api.yaml | 2 ++ scm-ui/ui-extensions/src/extensionPoints.tsx | 1 + .../overflowMenu/ContentActionMenu.tsx | 24 ++++++++++++++++--- .../content/overflowMenu/MenuItem.tsx | 4 +++- .../content/overflowMenu/ModalMenuItem.tsx | 5 ++-- 5 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 gradle/changelog/changed_content_action_menu_api.yaml diff --git a/gradle/changelog/changed_content_action_menu_api.yaml b/gradle/changelog/changed_content_action_menu_api.yaml new file mode 100644 index 0000000000..0c0cb93003 --- /dev/null +++ b/gradle/changelog/changed_content_action_menu_api.yaml @@ -0,0 +1,2 @@ +- type: changed + description: The internal API for content action menus were changed, to handle loading states of extensions diff --git a/scm-ui/ui-extensions/src/extensionPoints.tsx b/scm-ui/ui-extensions/src/extensionPoints.tsx index 7b1535cfaa..d0a1da1182 100644 --- a/scm-ui/ui-extensions/src/extensionPoints.tsx +++ b/scm-ui/ui-extensions/src/extensionPoints.tsx @@ -640,6 +640,7 @@ export type ContentActionExtensionProps = { revision: string; handleExtensionError: React.Dispatch>; contentType?: ContentType; + setLoading?: (isLoading: boolean) => void; }; type BaseActionBarOverflowMenuProps = { diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ContentActionMenu.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ContentActionMenu.tsx index 51ac029d42..25eba292f2 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ContentActionMenu.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ContentActionMenu.tsx @@ -23,19 +23,25 @@ */ import { binder, extensionPoints } from "@scm-manager/ui-extensions"; -import React, { FC, ReactElement, useState } from "react"; +import React, { FC, ReactElement, useCallback, useState } from "react"; import styled from "styled-components"; import { Menu } from "@scm-manager/ui-overlays"; import FallbackMenuButton from "./FallbackMenuButton"; import MenuItem from "./MenuItem"; -import { Icon } from "@scm-manager/ui-buttons"; +import { Button, Icon } from "@scm-manager/ui-buttons"; import { useTranslation } from "react-i18next"; +import { SmallLoadingSpinner } from "@scm-manager/ui-components"; const HR = styled.hr` margin: 0.25rem; background: var(--scm-border-color); `; +const StyledLoadingButton = styled(Button)` + padding-left: 1rem; + padding-right: 1rem; +`; + type Props = { extensionProps: extensionPoints.ContentActionExtensionProps; }; @@ -47,6 +53,13 @@ const ContentActionMenu: FC = ({ extensionProps }) => { "repos.sources.content.actionbar.menu", extensionProps ); + + const [loadingExtension, setLoadingExtensions] = useState>({}); + + const setLoading = useCallback((isLoading: boolean, extension: string) => { + setLoadingExtensions((prevState) => ({ ...prevState, [extension]: isLoading })); + }, []); + const categories = extensions.reduce>( (result, extension) => { if (!(extension.category in result)) { @@ -64,7 +77,11 @@ const ContentActionMenu: FC = ({ extensionProps }) => { return ( <> - {extensions.length === 1 ? ( + {Object.values(loadingExtension).some((isLoading) => isLoading) ? ( + + + + ) : extensions.length === 1 ? ( = ({ extensionProps }) => { key={extension.label} extensionProps={extensionProps} setSelectedModal={setSelectedModal} + setLoading={(isLoading: boolean) => setLoading(isLoading, extension.label)} {...extension} /> ))} diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/MenuItem.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/MenuItem.tsx index 2fb3895ff8..ff4931e21c 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/MenuItem.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/MenuItem.tsx @@ -32,8 +32,9 @@ const MenuItem: FC< extensionPoints.FileViewActionBarOverflowMenu["type"] & { setSelectedModal: (element: ReactElement | undefined) => void; extensionProps: extensionPoints.ContentActionExtensionProps; + setLoading?: (isLoading: boolean) => void; } -> = ({ extensionProps, label, icon, props, category, setSelectedModal, ...rest }) => { +> = ({ extensionProps, label, icon, props, category, setSelectedModal, setLoading, ...rest }) => { if ("action" in rest) { return ( diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ModalMenuItem.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ModalMenuItem.tsx index 67e36fe37b..67ef594d49 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ModalMenuItem.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ModalMenuItem.tsx @@ -32,15 +32,16 @@ const ModalMenuItem: FC< extensionPoints.ModalMenuProps & { setSelectedModal: (element: ReactElement | undefined) => void; extensionProps: extensionPoints.ContentActionExtensionProps; + setLoading?: (isLoading: boolean) => void; } -> = ({ modalElement, label, icon, props, extensionProps, setSelectedModal }) => { +> = ({ modalElement, label, icon, props, extensionProps, setSelectedModal, setLoading }) => { const [t] = useTranslation("plugins"); return ( setSelectedModal( - React.createElement(modalElement, { ...extensionProps, close: () => setSelectedModal(undefined) }) + React.createElement(modalElement, { ...extensionProps, close: () => setSelectedModal(undefined), setLoading }) ) } {...props}