mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-03-02 10:20:51 +01:00
Add action bar menu (#2015)
Create action bar overflow menu to handle increasing number of file actions.
This commit is contained in:
2
gradle/changelog/overflow_menu.yaml
Normal file
2
gradle/changelog/overflow_menu.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: Extension point to render file actions in overflow menu ([#2015](https://github.com/scm-manager/scm-manager/pull/2015))
|
||||
@@ -22,7 +22,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import React, { ComponentType, ReactNode } from "react";
|
||||
import {
|
||||
Branch,
|
||||
Changeset,
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
import { ExtensionPointDefinition } from "./binder";
|
||||
import { RenderableExtensionPointDefinition, SimpleRenderableDynamicExtensionPointDefinition } from "./ExtensionPoint";
|
||||
import ExtractProps from "./extractProps";
|
||||
import { ContentType } from "@scm-manager/ui-api";
|
||||
|
||||
type RepositoryCreatorSubFormProps = {
|
||||
repository: RepositoryCreation;
|
||||
@@ -62,7 +63,8 @@ export type RepositoryCreatorComponentProps = ExtractProps<RepositoryCreator["ty
|
||||
* @deprecated use {@link RepositoryCreator}`["type"]` instead
|
||||
*/
|
||||
export type RepositoryCreatorExtension = RepositoryCreator["type"];
|
||||
export type RepositoryCreator = ExtensionPointDefinition<"repos.creator",
|
||||
export type RepositoryCreator = ExtensionPointDefinition<
|
||||
"repos.creator",
|
||||
{
|
||||
subtitle: string;
|
||||
path: string;
|
||||
@@ -76,10 +78,13 @@ export type RepositoryCreator = ExtensionPointDefinition<"repos.creator",
|
||||
nameForm: React.ComponentType<RepositoryCreatorSubFormProps>;
|
||||
informationForm: React.ComponentType<RepositoryCreatorSubFormProps>;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type RepositoryFlags = RenderableExtensionPointDefinition<"repository.flags",
|
||||
{ repository: Repository; tooltipLocation?: "bottom" | "right" | "top" | "left" }>;
|
||||
export type RepositoryFlags = RenderableExtensionPointDefinition<
|
||||
"repository.flags",
|
||||
{ repository: Repository; tooltipLocation?: "bottom" | "right" | "top" | "left" }
|
||||
>;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link ReposSourcesActionbar}`["props"]` instead
|
||||
@@ -89,7 +94,8 @@ export type ReposSourcesActionbarExtensionProps = ReposSourcesActionbar["props"]
|
||||
* @deprecated use {@link ReposSourcesActionbar} instead
|
||||
*/
|
||||
export type ReposSourcesActionbarExtension = ReposSourcesActionbar;
|
||||
export type ReposSourcesActionbar = RenderableExtensionPointDefinition<"repos.sources.actionbar",
|
||||
export type ReposSourcesActionbar = RenderableExtensionPointDefinition<
|
||||
"repos.sources.actionbar",
|
||||
{
|
||||
baseUrl: string;
|
||||
revision: string;
|
||||
@@ -97,7 +103,8 @@ export type ReposSourcesActionbar = RenderableExtensionPointDefinition<"repos.so
|
||||
path: string;
|
||||
sources: File;
|
||||
repository: Repository;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link ReposSourcesEmptyActionbar}`["props"]` instead
|
||||
@@ -107,11 +114,13 @@ export type ReposSourcesEmptyActionbarExtensionProps = ReposSourcesEmptyActionba
|
||||
* @deprecated use {@link ReposSourcesEmptyActionbar} instead
|
||||
*/
|
||||
export type ReposSourcesEmptyActionbarExtension = ReposSourcesEmptyActionbar;
|
||||
export type ReposSourcesEmptyActionbar = RenderableExtensionPointDefinition<"repos.sources.empty.actionbar",
|
||||
export type ReposSourcesEmptyActionbar = RenderableExtensionPointDefinition<
|
||||
"repos.sources.empty.actionbar",
|
||||
{
|
||||
sources: File;
|
||||
repository: Repository;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link ReposSourcesTreeWrapper}`["props"]` instead
|
||||
@@ -122,13 +131,15 @@ export type ReposSourcesTreeWrapperProps = ReposSourcesTreeWrapper["props"];
|
||||
* @deprecated use {@link ReposSourcesTreeWrapper} instead
|
||||
*/
|
||||
export type ReposSourcesTreeWrapperExtension = ReposSourcesTreeWrapper;
|
||||
export type ReposSourcesTreeWrapper = RenderableExtensionPointDefinition<"repos.source.tree.wrapper",
|
||||
export type ReposSourcesTreeWrapper = RenderableExtensionPointDefinition<
|
||||
"repos.source.tree.wrapper",
|
||||
{
|
||||
repository: Repository;
|
||||
directory: File;
|
||||
baseUrl: string;
|
||||
revision: string;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ReposSourcesTreeRowProps = {
|
||||
repository: Repository;
|
||||
@@ -139,15 +150,19 @@ export type ReposSourcesTreeRowProps = {
|
||||
* @deprecated use {@link ReposSourcesTreeRowRight} instead
|
||||
*/
|
||||
export type ReposSourcesTreeRowRightExtension = ReposSourcesTreeRowRight;
|
||||
export type ReposSourcesTreeRowRight = RenderableExtensionPointDefinition<"repos.sources.tree.row.right",
|
||||
ReposSourcesTreeRowProps>;
|
||||
export type ReposSourcesTreeRowRight = RenderableExtensionPointDefinition<
|
||||
"repos.sources.tree.row.right",
|
||||
ReposSourcesTreeRowProps
|
||||
>;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link ReposSourcesTreeRowAfter} instead
|
||||
*/
|
||||
export type ReposSourcesTreeRowAfterExtension = ReposSourcesTreeRowAfter;
|
||||
export type ReposSourcesTreeRowAfter = RenderableExtensionPointDefinition<"repos.sources.tree.row.after",
|
||||
ReposSourcesTreeRowProps>;
|
||||
export type ReposSourcesTreeRowAfter = RenderableExtensionPointDefinition<
|
||||
"repos.sources.tree.row.after",
|
||||
ReposSourcesTreeRowProps
|
||||
>;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link PrimaryNavigationLoginButton}`["props"]` instead
|
||||
@@ -158,7 +173,8 @@ export type PrimaryNavigationLoginButtonProps = PrimaryNavigationLoginButton["pr
|
||||
* use {@link PrimaryNavigationLoginButton} instead
|
||||
*/
|
||||
export type PrimaryNavigationLoginButtonExtension = PrimaryNavigationLoginButton;
|
||||
export type PrimaryNavigationLoginButton = RenderableExtensionPointDefinition<"primary-navigation.login",
|
||||
export type PrimaryNavigationLoginButton = RenderableExtensionPointDefinition<
|
||||
"primary-navigation.login",
|
||||
{
|
||||
links: Links;
|
||||
label: string;
|
||||
@@ -167,7 +183,8 @@ export type PrimaryNavigationLoginButton = RenderableExtensionPointDefinition<"p
|
||||
to: string;
|
||||
className: string;
|
||||
content: React.ReactNode;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link PrimaryNavigationLogoutButtonExtension}`["props"]` instead
|
||||
@@ -178,19 +195,22 @@ export type PrimaryNavigationLogoutButtonProps = PrimaryNavigationLogoutButton["
|
||||
* @deprecated use {@link PrimaryNavigationLogoutButton} instead
|
||||
*/
|
||||
export type PrimaryNavigationLogoutButtonExtension = PrimaryNavigationLogoutButton;
|
||||
export type PrimaryNavigationLogoutButton = RenderableExtensionPointDefinition<"primary-navigation.logout",
|
||||
export type PrimaryNavigationLogoutButton = RenderableExtensionPointDefinition<
|
||||
"primary-navigation.logout",
|
||||
{
|
||||
links: Links;
|
||||
label: string;
|
||||
className: string;
|
||||
content: React.ReactNode;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link SourceExtension}`["props"]` instead
|
||||
*/
|
||||
export type SourceExtensionProps = SourceExtension["props"];
|
||||
export type SourceExtension = RenderableExtensionPointDefinition<"repos.sources.extensions",
|
||||
export type SourceExtension = RenderableExtensionPointDefinition<
|
||||
"repos.sources.extensions",
|
||||
{
|
||||
repository: Repository;
|
||||
baseUrl: string;
|
||||
@@ -198,7 +218,8 @@ export type SourceExtension = RenderableExtensionPointDefinition<"repos.sources.
|
||||
extension: string;
|
||||
sources: File | undefined;
|
||||
path: string;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link RepositoryOverviewTop}`["props"]` instead
|
||||
@@ -209,12 +230,14 @@ export type RepositoryOverviewTopExtensionProps = RepositoryOverviewTop["props"]
|
||||
* @deprecated use {@link RepositoryOverviewTop} instead
|
||||
*/
|
||||
export type RepositoryOverviewTopExtension = RepositoryOverviewTop;
|
||||
export type RepositoryOverviewTop = RenderableExtensionPointDefinition<"repository.overview.top",
|
||||
export type RepositoryOverviewTop = RenderableExtensionPointDefinition<
|
||||
"repository.overview.top",
|
||||
{
|
||||
page: number;
|
||||
search: string;
|
||||
namespace?: string;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link RepositoryOverviewLeft} instead
|
||||
@@ -247,8 +270,10 @@ export type AdminSetting = RenderableExtensionPointDefinition<"admin.setting", {
|
||||
*
|
||||
* @deprecated Use `changeset.description.tokens` instead
|
||||
*/
|
||||
export type ChangesetDescription = RenderableExtensionPointDefinition<"changeset.description",
|
||||
{ changeset: Changeset; value: string }>;
|
||||
export type ChangesetDescription = RenderableExtensionPointDefinition<
|
||||
"changeset.description",
|
||||
{ changeset: Changeset; value: string }
|
||||
>;
|
||||
|
||||
/**
|
||||
* - Can be used to replace parts of a changeset description with components
|
||||
@@ -257,19 +282,28 @@ export type ChangesetDescription = RenderableExtensionPointDefinition<"changeset
|
||||
* - replacement: The component to take instead of the text to replace
|
||||
* - replaceAll: Optional boolean; if set to `true`, all occurances of the text will be replaced (default: `false`)
|
||||
*/
|
||||
export type ChangesetDescriptionTokens = ExtensionPointDefinition<"changeset.description.tokens",
|
||||
(changeset: Changeset, value: string) => Array<{
|
||||
export type ChangesetDescriptionTokens = ExtensionPointDefinition<
|
||||
"changeset.description.tokens",
|
||||
(
|
||||
changeset: Changeset,
|
||||
value: string
|
||||
) => Array<{
|
||||
textToReplace: string;
|
||||
replacement: ReactNode;
|
||||
replaceAll?: boolean;
|
||||
}>,
|
||||
{ changeset: Changeset; value: string }>;
|
||||
{ changeset: Changeset; value: string }
|
||||
>;
|
||||
|
||||
export type ChangesetRight = RenderableExtensionPointDefinition<"changeset.right",
|
||||
{ repository: Repository; changeset: Changeset }>;
|
||||
export type ChangesetRight = RenderableExtensionPointDefinition<
|
||||
"changeset.right",
|
||||
{ repository: Repository; changeset: Changeset }
|
||||
>;
|
||||
|
||||
export type ChangesetsAuthorSuffix = RenderableExtensionPointDefinition<"changesets.author.suffix",
|
||||
{ changeset: Changeset }>;
|
||||
export type ChangesetsAuthorSuffix = RenderableExtensionPointDefinition<
|
||||
"changesets.author.suffix",
|
||||
{ changeset: Changeset }
|
||||
>;
|
||||
|
||||
export type GroupNavigation = RenderableExtensionPointDefinition<"group.navigation", { group: Group; url: string }>;
|
||||
|
||||
@@ -280,16 +314,20 @@ export type GroupSetting = RenderableExtensionPointDefinition<"group.setting", {
|
||||
* - Add a new Route to the main Route (scm/)
|
||||
* - Props: authenticated?: boolean, links: Links
|
||||
*/
|
||||
export type MainRoute = RenderableExtensionPointDefinition<"main.route",
|
||||
export type MainRoute = RenderableExtensionPointDefinition<
|
||||
"main.route",
|
||||
{
|
||||
me: Me;
|
||||
authenticated?: boolean;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type PluginAvatar = RenderableExtensionPointDefinition<"plugins.plugin-avatar",
|
||||
export type PluginAvatar = RenderableExtensionPointDefinition<
|
||||
"plugins.plugin-avatar",
|
||||
{
|
||||
plugin: Plugin;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type PrimaryNavigation = RenderableExtensionPointDefinition<"primary-navigation", { links: Links }>;
|
||||
|
||||
@@ -298,31 +336,44 @@ export type PrimaryNavigation = RenderableExtensionPointDefinition<"primary-navi
|
||||
* - A PrimaryNavigationLink Component can be used here
|
||||
* - Actually this Extension Point is used from the Activity Plugin to display the activities at the first Main Navigation menu.
|
||||
*/
|
||||
export type PrimaryNavigationFirstMenu = RenderableExtensionPointDefinition<"primary-navigation.first-menu",
|
||||
{ links: Links; label: string }>;
|
||||
export type PrimaryNavigationFirstMenu = RenderableExtensionPointDefinition<
|
||||
"primary-navigation.first-menu",
|
||||
{ links: Links; label: string }
|
||||
>;
|
||||
|
||||
export type ProfileRoute = RenderableExtensionPointDefinition<"profile.route", { me: Me; url: string }>;
|
||||
export type ProfileSetting = RenderableExtensionPointDefinition<"profile.setting",
|
||||
{ me?: Me; url: string; links: Links }>;
|
||||
export type ProfileSetting = RenderableExtensionPointDefinition<
|
||||
"profile.setting",
|
||||
{ me?: Me; url: string; links: Links }
|
||||
>;
|
||||
|
||||
export type RepoConfigRoute = RenderableExtensionPointDefinition<"repo-config.route",
|
||||
{ repository: Repository; url: string }>;
|
||||
export type RepoConfigRoute = RenderableExtensionPointDefinition<
|
||||
"repo-config.route",
|
||||
{ repository: Repository; url: string }
|
||||
>;
|
||||
|
||||
export type RepoConfigDetails = RenderableExtensionPointDefinition<"repo-config.details",
|
||||
{ repository: Repository; url: string }>;
|
||||
export type RepoConfigDetails = RenderableExtensionPointDefinition<
|
||||
"repo-config.details",
|
||||
{ repository: Repository; url: string }
|
||||
>;
|
||||
|
||||
export type ReposBranchDetailsInformation = RenderableExtensionPointDefinition<"repos.branch-details.information",
|
||||
{ repository: Repository; branch: Branch }>;
|
||||
export type ReposBranchDetailsInformation = RenderableExtensionPointDefinition<
|
||||
"repos.branch-details.information",
|
||||
{ repository: Repository; branch: Branch }
|
||||
>;
|
||||
|
||||
/**
|
||||
* - Location: At meta data view for file
|
||||
* - can be used to render additional meta data line
|
||||
* - Props: file: string, repository: Repository, revision: string
|
||||
*/
|
||||
export type ReposContentMetaData = RenderableExtensionPointDefinition<"repos.content.metadata",
|
||||
{ file: File; repository: Repository; revision: string }>;
|
||||
export type ReposContentMetaData = RenderableExtensionPointDefinition<
|
||||
"repos.content.metadata",
|
||||
{ file: File; repository: Repository; revision: string }
|
||||
>;
|
||||
|
||||
export type ReposCreateNamespace = RenderableExtensionPointDefinition<"repos.create.namespace",
|
||||
export type ReposCreateNamespace = RenderableExtensionPointDefinition<
|
||||
"repos.create.namespace",
|
||||
{
|
||||
label: string;
|
||||
helpText: string;
|
||||
@@ -330,54 +381,75 @@ export type ReposCreateNamespace = RenderableExtensionPointDefinition<"repos.cre
|
||||
onChange: (namespace: string) => void;
|
||||
errorMessage: string;
|
||||
validationError?: boolean;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ReposSourcesContentActionBar = RenderableExtensionPointDefinition<"repos.sources.content.actionbar",
|
||||
export type ReposSourcesContentActionBar = RenderableExtensionPointDefinition<
|
||||
"repos.sources.content.actionbar",
|
||||
{
|
||||
repository: Repository;
|
||||
file: File;
|
||||
revision: string;
|
||||
handleExtensionError: React.Dispatch<React.SetStateAction<Error | undefined>>;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type RepositoryNavigation = RenderableExtensionPointDefinition<"repository.navigation",
|
||||
{ repository: Repository; url: string; indexLinks: Links }>;
|
||||
export type RepositoryNavigation = RenderableExtensionPointDefinition<
|
||||
"repository.navigation",
|
||||
{ repository: Repository; url: string; indexLinks: Links }
|
||||
>;
|
||||
|
||||
export type RepositoryNavigationTopLevel = RenderableExtensionPointDefinition<"repository.navigation.topLevel",
|
||||
{ repository: Repository; url: string; indexLinks: Links }>;
|
||||
export type RepositoryNavigationTopLevel = RenderableExtensionPointDefinition<
|
||||
"repository.navigation.topLevel",
|
||||
{ repository: Repository; url: string; indexLinks: Links }
|
||||
>;
|
||||
|
||||
export type RepositoryRoleDetailsInformation = RenderableExtensionPointDefinition<"repositoryRole.role-details.information",
|
||||
{ role: RepositoryRole }>;
|
||||
export type RepositoryRoleDetailsInformation = RenderableExtensionPointDefinition<
|
||||
"repositoryRole.role-details.information",
|
||||
{ role: RepositoryRole }
|
||||
>;
|
||||
|
||||
export type RepositorySetting = RenderableExtensionPointDefinition<"repository.setting",
|
||||
{ repository: Repository; url: string; indexLinks: Links }>;
|
||||
export type RepositorySetting = RenderableExtensionPointDefinition<
|
||||
"repository.setting",
|
||||
{ repository: Repository; url: string; indexLinks: Links }
|
||||
>;
|
||||
|
||||
export type RepositoryAvatar = RenderableExtensionPointDefinition<"repos.repository-avatar",
|
||||
{ repository: Repository }>;
|
||||
export type RepositoryAvatar = RenderableExtensionPointDefinition<
|
||||
"repos.repository-avatar",
|
||||
{ repository: Repository }
|
||||
>;
|
||||
|
||||
/**
|
||||
* - Location: At each repository in repository overview
|
||||
* - can be used to add avatar for each repository (e.g., to mark repository type)
|
||||
*/
|
||||
export type PrimaryRepositoryAvatar = RenderableExtensionPointDefinition<"repos.repository-avatar.primary",
|
||||
{ repository: Repository }>;
|
||||
export type PrimaryRepositoryAvatar = RenderableExtensionPointDefinition<
|
||||
"repos.repository-avatar.primary",
|
||||
{ repository: Repository }
|
||||
>;
|
||||
|
||||
/**
|
||||
* - Location: At bottom of a single repository view
|
||||
* - can be used to show detailed information about the repository (how to clone, e.g.)
|
||||
*/
|
||||
export type RepositoryDetailsInformation = RenderableExtensionPointDefinition<"repos.repository-details.information",
|
||||
{ repository: Repository }>;
|
||||
export type RepositoryDetailsInformation = RenderableExtensionPointDefinition<
|
||||
"repos.repository-details.information",
|
||||
{ repository: Repository }
|
||||
>;
|
||||
|
||||
/**
|
||||
* - Location: At sources viewer
|
||||
* - can be used to render a special source that is not an image or a source code
|
||||
*/
|
||||
export type RepositorySourcesView = RenderableExtensionPointDefinition<"repos.sources.view",
|
||||
{ file: File; contentType: string; revision: string; basePath: string }>;
|
||||
export type RepositorySourcesView = RenderableExtensionPointDefinition<
|
||||
"repos.sources.view",
|
||||
{ file: File; contentType: string; revision: string; basePath: string }
|
||||
>;
|
||||
|
||||
export type RolesRoute = RenderableExtensionPointDefinition<"roles.route",
|
||||
{ role: HalRepresentation & RepositoryRoleBase & { creationDate?: string; lastModified?: string }; url: string }>;
|
||||
export type RolesRoute = RenderableExtensionPointDefinition<
|
||||
"roles.route",
|
||||
{ role: HalRepresentation & RepositoryRoleBase & { creationDate?: string; lastModified?: string }; url: string }
|
||||
>;
|
||||
|
||||
export type UserRoute = RenderableExtensionPointDefinition<"user.route", { user: User; url: string }>;
|
||||
export type UserSetting = RenderableExtensionPointDefinition<"user.setting", { user: User; url: string }>;
|
||||
@@ -388,13 +460,15 @@ export type UserSetting = RenderableExtensionPointDefinition<"user.setting", { u
|
||||
* - Used by the Markdown Plantuml Plugin
|
||||
*/
|
||||
export type MarkdownCodeRenderer<Language extends string | undefined = undefined> =
|
||||
SimpleRenderableDynamicExtensionPointDefinition<"markdown-renderer.code.",
|
||||
SimpleRenderableDynamicExtensionPointDefinition<
|
||||
"markdown-renderer.code.",
|
||||
Language,
|
||||
{
|
||||
language?: Language extends string ? Language : string;
|
||||
value: string;
|
||||
indexLinks: Links;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* - Define custom protocols and their renderers for links in markdown
|
||||
@@ -408,14 +482,16 @@ export type MarkdownCodeRenderer<Language extends string | undefined = undefined
|
||||
* binder.bind<extensionPoints.MarkdownLinkProtocolRenderer<"myprotocol">>("markdown-renderer.link.protocol", { protocol: "myprotocol", renderer: MyProtocolRenderer })
|
||||
* ```
|
||||
*/
|
||||
export type MarkdownLinkProtocolRenderer<Protocol extends string | undefined = undefined> = ExtensionPointDefinition<"markdown-renderer.link.protocol",
|
||||
export type MarkdownLinkProtocolRenderer<Protocol extends string | undefined = undefined> = ExtensionPointDefinition<
|
||||
"markdown-renderer.link.protocol",
|
||||
{
|
||||
protocol: Protocol extends string ? Protocol : string;
|
||||
renderer: React.ComponentType<{
|
||||
protocol: Protocol extends string ? Protocol : string;
|
||||
href: string;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* Used to determine an avatar image url from a given {@link Person}.
|
||||
@@ -430,8 +506,10 @@ export type AvatarFactory = ExtensionPointDefinition<"avatar.factory", (person:
|
||||
*
|
||||
* @deprecated Has no effect, use {@link AvatarFactory} instead
|
||||
*/
|
||||
export type ChangesetAvatarFactory = ExtensionPointDefinition<"changeset.avatar-factory",
|
||||
(changeset: Changeset) => void>;
|
||||
export type ChangesetAvatarFactory = ExtensionPointDefinition<
|
||||
"changeset.avatar-factory",
|
||||
(changeset: Changeset) => void
|
||||
>;
|
||||
|
||||
type MainRedirectProps = {
|
||||
me: Me;
|
||||
@@ -442,9 +520,11 @@ type MainRedirectProps = {
|
||||
* - Extension Point for a link factory that provide the Redirect Link
|
||||
* - Actually used from the activity plugin: binder.bind("main.redirect", () => "/activity");
|
||||
*/
|
||||
export type MainRedirect = ExtensionPointDefinition<"main.redirect",
|
||||
export type MainRedirect = ExtensionPointDefinition<
|
||||
"main.redirect",
|
||||
(props: MainRedirectProps) => string,
|
||||
MainRedirectProps>;
|
||||
MainRedirectProps
|
||||
>;
|
||||
|
||||
/**
|
||||
* - A Factory function to create markdown [renderer](https://github.com/rexxars/react-markdown#node-types)
|
||||
@@ -452,13 +532,18 @@ export type MainRedirect = ExtensionPointDefinition<"main.redirect",
|
||||
*
|
||||
* @deprecated Use {@link MarkdownCodeRenderer} or {@link MarkdownLinkProtocolRenderer} instead
|
||||
*/
|
||||
export type MarkdownRendererFactory = ExtensionPointDefinition<"markdown-renderer-factory",
|
||||
(renderContext: unknown) => Record<string, React.ComponentType<any>>>;
|
||||
export type MarkdownRendererFactory = ExtensionPointDefinition<
|
||||
"markdown-renderer-factory",
|
||||
(renderContext: unknown) => Record<string, React.ComponentType<any>>
|
||||
>;
|
||||
|
||||
export type RepositoryCardBeforeTitle = RenderableExtensionPointDefinition<"repository.card.beforeTitle",
|
||||
{ repository: Repository }>;
|
||||
export type RepositoryCardBeforeTitle = RenderableExtensionPointDefinition<
|
||||
"repository.card.beforeTitle",
|
||||
{ repository: Repository }
|
||||
>;
|
||||
|
||||
export type RepositoryCreationInitialization = RenderableExtensionPointDefinition<"repos.create.initialize",
|
||||
export type RepositoryCreationInitialization = RenderableExtensionPointDefinition<
|
||||
"repos.create.initialize",
|
||||
{
|
||||
repository: Repository;
|
||||
setCreationContextEntry: (key: string, value: any) => void;
|
||||
@@ -467,28 +552,43 @@ export type RepositoryCreationInitialization = RenderableExtensionPointDefinitio
|
||||
version?: string;
|
||||
initialization?: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type NamespaceTopLevelNavigation = RenderableExtensionPointDefinition<"namespace.navigation.topLevel",
|
||||
{ namespace: Namespace; url: string }>;
|
||||
export type NamespaceTopLevelNavigation = RenderableExtensionPointDefinition<
|
||||
"namespace.navigation.topLevel",
|
||||
{ namespace: Namespace; url: string }
|
||||
>;
|
||||
|
||||
export type NamespaceRoute = RenderableExtensionPointDefinition<"namespace.route",
|
||||
{ namespace: Namespace; url: string }>;
|
||||
export type NamespaceRoute = RenderableExtensionPointDefinition<
|
||||
"namespace.route",
|
||||
{ namespace: Namespace; url: string }
|
||||
>;
|
||||
|
||||
export type NamespaceSetting = RenderableExtensionPointDefinition<"namespace.setting",
|
||||
{ namespace: Namespace; url: string }>;
|
||||
export type NamespaceSetting = RenderableExtensionPointDefinition<
|
||||
"namespace.setting",
|
||||
{ namespace: Namespace; url: string }
|
||||
>;
|
||||
|
||||
export type RepositoryTagDetailsInformation = RenderableExtensionPointDefinition<"repos.tag-details.information",
|
||||
{ repository: Repository; tag: Tag }>;
|
||||
export type RepositoryTagDetailsInformation = RenderableExtensionPointDefinition<
|
||||
"repos.tag-details.information",
|
||||
{ repository: Repository; tag: Tag }
|
||||
>;
|
||||
|
||||
export type SearchHitRenderer<Type extends string | undefined = undefined> = RenderableExtensionPointDefinition<Type extends string ? `search.hit.${Type}.renderer` : `search.hit.${string}.renderer`,
|
||||
{ hit: Hit }>;
|
||||
export type SearchHitRenderer<Type extends string | undefined = undefined> = RenderableExtensionPointDefinition<
|
||||
Type extends string ? `search.hit.${Type}.renderer` : `search.hit.${string}.renderer`,
|
||||
{ hit: Hit }
|
||||
>;
|
||||
|
||||
export type RepositorySourcesContentDownloadButton = RenderableExtensionPointDefinition<"repos.sources.content.downloadButton",
|
||||
{ repository: Repository; file: File }>;
|
||||
export type RepositorySourcesContentDownloadButton = RenderableExtensionPointDefinition<
|
||||
"repos.sources.content.downloadButton",
|
||||
{ repository: Repository; file: File }
|
||||
>;
|
||||
|
||||
export type RepositoryRoute = RenderableExtensionPointDefinition<"repository.route",
|
||||
{ repository: Repository; url: string; indexLinks: Links }>;
|
||||
export type RepositoryRoute = RenderableExtensionPointDefinition<
|
||||
"repository.route",
|
||||
{ repository: Repository; url: string; indexLinks: Links }
|
||||
>;
|
||||
|
||||
type RepositoryRedirectProps = {
|
||||
namespace: string;
|
||||
@@ -509,13 +609,44 @@ type RepositoryRedirectProps = {
|
||||
};
|
||||
};
|
||||
|
||||
export type RepositoryRedirect = ExtensionPointDefinition<"repository.redirect",
|
||||
export type RepositoryRedirect = ExtensionPointDefinition<
|
||||
"repository.redirect",
|
||||
(props: RepositoryRedirectProps) => string,
|
||||
RepositoryRedirectProps>;
|
||||
RepositoryRedirectProps
|
||||
>;
|
||||
|
||||
export type InitializationStep<Step extends string | undefined = undefined> =
|
||||
SimpleRenderableDynamicExtensionPointDefinition<"initialization.step.",
|
||||
SimpleRenderableDynamicExtensionPointDefinition<
|
||||
"initialization.step.",
|
||||
Step,
|
||||
{
|
||||
data: HalRepresentation;
|
||||
}>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ContentActionExtensionProps = {
|
||||
repository: Repository;
|
||||
file: File;
|
||||
revision: string;
|
||||
handleExtensionError: React.Dispatch<React.SetStateAction<Error | undefined>>;
|
||||
contentType?: ContentType;
|
||||
};
|
||||
|
||||
type BaseActionBarOverflowMenuProps = {
|
||||
category: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
props?: unknown;
|
||||
};
|
||||
|
||||
export type ActionMenuProps = BaseActionBarOverflowMenuProps & { action: (props: ContentActionExtensionProps) => void };
|
||||
export type ModalMenuProps = BaseActionBarOverflowMenuProps & {
|
||||
modalElement: ComponentType<ContentActionExtensionProps & { close: () => void }>;
|
||||
};
|
||||
export type LinkMenuProps = BaseActionBarOverflowMenuProps & { link: (props: ContentActionExtensionProps) => string };
|
||||
|
||||
export type FileViewActionBarOverflowMenu = ExtensionPointDefinition<
|
||||
"repos.sources.content.actionbar.menu",
|
||||
ActionMenuProps | ModalMenuProps | LinkMenuProps,
|
||||
ContentActionExtensionProps
|
||||
>;
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"redux": "^4.0.0",
|
||||
"string_score": "^0.1.22",
|
||||
"styled-components": "^5.3.5",
|
||||
"systemjs": "0.21.6"
|
||||
"systemjs": "0.21.6",
|
||||
"@headlessui/react": "^1.4.3"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import { Icon } from "@scm-manager/ui-components";
|
||||
import { extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import { MenuItemContainer } from "./ContentActionMenu";
|
||||
|
||||
const ActionMenuItem: FC<
|
||||
extensionPoints.ActionMenuProps & {
|
||||
active: boolean;
|
||||
onClick: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
extensionProps: extensionPoints.ContentActionExtensionProps;
|
||||
}
|
||||
> = ({ action, active, label, icon, props, extensionProps, ...rest }) => {
|
||||
const [t] = useTranslation("plugins");
|
||||
|
||||
return (
|
||||
<MenuItemContainer
|
||||
className={classNames("is-clickable", "is-flex", "is-align-items-centered", {
|
||||
"has-background-info has-text-white": active,
|
||||
})}
|
||||
title={t(label)}
|
||||
{...props}
|
||||
{...rest}
|
||||
onClick={(event) => {
|
||||
rest.onClick(event);
|
||||
action(extensionProps);
|
||||
}}
|
||||
>
|
||||
<Icon name={icon} color="inherit" className="pr-5" />
|
||||
<span>{t(label)}</span>
|
||||
</MenuItemContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionMenuItem;
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { binder, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import React, { FC, ReactElement, useState } from "react";
|
||||
import { Icon } from "@scm-manager/ui-components";
|
||||
import styled from "styled-components";
|
||||
import { Menu } from "@headlessui/react";
|
||||
import FallbackMenuButton from "./FallbackMenuButton";
|
||||
import MenuItem from "./MenuItem";
|
||||
|
||||
const MenuButton = styled(Menu.Button)`
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
height: 2.5rem;
|
||||
width: 50px;
|
||||
margin-bottom: 0.5rem;
|
||||
`;
|
||||
|
||||
const MenuItems = styled(Menu.Items)`
|
||||
padding: 0.5rem;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
width: max-content;
|
||||
border: var(--scm-border);
|
||||
border-radius: 5px;
|
||||
background-color: var(--scm-secondary-background);
|
||||
box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02);
|
||||
`;
|
||||
|
||||
export const MenuItemContainer = styled.div`
|
||||
border-radius: 5px;
|
||||
padding: 0.5rem;
|
||||
`;
|
||||
|
||||
const HR = styled.hr`
|
||||
margin: 0.25rem;
|
||||
background: var(--scm-border-color);
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
extensionProps: extensionPoints.ContentActionExtensionProps;
|
||||
};
|
||||
|
||||
const ContentActionMenu: FC<Props> = ({ extensionProps }) => {
|
||||
const [selectedModal, setSelectedModal] = useState<ReactElement | undefined>();
|
||||
const extensions = binder.getExtensions<extensionPoints.FileViewActionBarOverflowMenu>(
|
||||
"repos.sources.content.actionbar.menu",
|
||||
extensionProps
|
||||
);
|
||||
const categories = extensions.reduce<Record<string, extensionPoints.FileViewActionBarOverflowMenu["type"][]>>(
|
||||
(result, extension) => {
|
||||
if (!(extension.category in result)) {
|
||||
result[extension.category] = [];
|
||||
}
|
||||
result[extension.category].push(extension);
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const renderMenu = () => (
|
||||
<>
|
||||
<Menu as="div" className="is-relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<MenuButton>
|
||||
<Icon name="ellipsis-v" className="has-text-default" />
|
||||
</MenuButton>
|
||||
{open && (
|
||||
<div className="has-background-secondary-least">
|
||||
<MenuItems>
|
||||
{Object.entries(categories).map(([_category, extensionSet], index) => (
|
||||
<>
|
||||
{extensionSet.map((extension) => (
|
||||
<Menu.Item as={React.Fragment} key={extension.label}>
|
||||
{({ active }) => {
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore onClick prop required but gets provided implicit by the Menu.Item from headless ui
|
||||
<MenuItem
|
||||
extensionProps={extensionProps}
|
||||
active={active}
|
||||
setSelectedModal={setSelectedModal}
|
||||
{...extension}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Menu.Item>
|
||||
))}
|
||||
{Object.keys(categories).length > index + 1 ? <HR /> : null}
|
||||
</>
|
||||
))}
|
||||
</MenuItems>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
|
||||
if (extensions.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{extensions.length === 1 ? (
|
||||
<FallbackMenuButton
|
||||
extension={extensions[0]}
|
||||
extensionProps={extensionProps}
|
||||
setSelectedModal={setSelectedModal}
|
||||
/>
|
||||
) : (
|
||||
renderMenu()
|
||||
)}
|
||||
{selectedModal || null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContentActionMenu;
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC, ReactElement } from "react";
|
||||
import { Button, Icon } from "@scm-manager/ui-components";
|
||||
import styled from "styled-components";
|
||||
import { Link } from "react-router-dom";
|
||||
import { extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const FallbackButton = styled(Button)`
|
||||
height: 2.5rem;
|
||||
width: 50px;
|
||||
margin-bottom: 0.5rem;
|
||||
> i {
|
||||
padding: 0 !important;
|
||||
}
|
||||
&:hover {
|
||||
color: var(--scm-link-color);
|
||||
}
|
||||
`;
|
||||
|
||||
const FallbackLink = styled(Link)`
|
||||
width: 50px;
|
||||
&:hover {
|
||||
color: var(--scm-link-color);
|
||||
}
|
||||
`;
|
||||
|
||||
const FallbackMenuButton: FC<{
|
||||
extension: extensionPoints.FileViewActionBarOverflowMenu["type"];
|
||||
extensionProps: extensionPoints.ContentActionExtensionProps;
|
||||
setSelectedModal: (element: ReactElement | undefined) => void;
|
||||
}> = ({ extension, extensionProps, setSelectedModal }) => {
|
||||
const [t] = useTranslation("plugins");
|
||||
if ("action" in extension) {
|
||||
return (
|
||||
<FallbackButton
|
||||
icon={extension.icon}
|
||||
title={t(extension.label)}
|
||||
action={() => extension.action(extensionProps)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if ("link" in extension) {
|
||||
return (
|
||||
<FallbackLink to={extension.link(extensionProps)} className="button" title={t(extension.label)}>
|
||||
<Icon name={extension.icon} color="inherit" />
|
||||
</FallbackLink>
|
||||
);
|
||||
}
|
||||
if ("modalElement" in extension) {
|
||||
return (
|
||||
<FallbackButton
|
||||
icon={extension.icon}
|
||||
title={t(extension.label)}
|
||||
action={() =>
|
||||
setSelectedModal(
|
||||
React.createElement(extension.modalElement, {
|
||||
...extensionProps,
|
||||
close: () => setSelectedModal(undefined),
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
export default FallbackMenuButton;
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import { Icon } from "@scm-manager/ui-components";
|
||||
import styled from "styled-components";
|
||||
import { Link } from "react-router-dom";
|
||||
import { extensionPoints } from "@scm-manager/ui-extensions";
|
||||
|
||||
const MenuItemLinkContainer = styled(Link)<{ active: boolean }>`
|
||||
border-radius: 5px;
|
||||
padding: 0.5rem;
|
||||
color: ${(props) => (props.active ? "var(--scm-white-color)" : "inherit")};
|
||||
:hover {
|
||||
color: var(--scm-white-color);
|
||||
}
|
||||
`;
|
||||
|
||||
const LinkMenuItem: FC<
|
||||
extensionPoints.LinkMenuProps & { active: boolean; extensionProps: extensionPoints.ContentActionExtensionProps }
|
||||
> = ({ link, active, label, icon, props, extensionProps, ...rest }) => {
|
||||
const [t] = useTranslation("plugins");
|
||||
|
||||
return (
|
||||
<MenuItemLinkContainer
|
||||
className={classNames("is-clickable", "is-flex", "is-align-items-centered", {
|
||||
"has-background-info": active,
|
||||
})}
|
||||
to={link(extensionProps)}
|
||||
title={t(label)}
|
||||
active={active}
|
||||
{...props}
|
||||
{...rest}
|
||||
>
|
||||
<Icon name={icon} color="inherit" className="pr-5" />
|
||||
<span>{t(label)}</span>
|
||||
</MenuItemLinkContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default LinkMenuItem;
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC, ReactElement } from "react";
|
||||
import ActionMenuItem from "./ActionMenuItem";
|
||||
import LinkMenuItem from "./LinkMenuItem";
|
||||
import ModalMenuItem from "./ModalMenuItem";
|
||||
import { extensionPoints } from "@scm-manager/ui-extensions";
|
||||
|
||||
const MenuItem: FC<
|
||||
extensionPoints.FileViewActionBarOverflowMenu["type"] & {
|
||||
active: boolean;
|
||||
onClick: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
setSelectedModal: (element: ReactElement | undefined) => void;
|
||||
extensionProps: extensionPoints.ContentActionExtensionProps;
|
||||
}
|
||||
> = ({ extensionProps, label, icon, props, category, active, onClick, setSelectedModal, ...rest }) => {
|
||||
if ("action" in rest) {
|
||||
return (
|
||||
<ActionMenuItem
|
||||
label={label}
|
||||
icon={icon}
|
||||
category={category}
|
||||
extensionProps={extensionProps}
|
||||
active={active}
|
||||
onClick={onClick}
|
||||
props={props}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if ("link" in rest) {
|
||||
return (
|
||||
<LinkMenuItem
|
||||
category={category}
|
||||
label={label}
|
||||
icon={icon}
|
||||
active={active}
|
||||
extensionProps={extensionProps}
|
||||
props={props}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if ("modalElement" in rest) {
|
||||
return (
|
||||
<ModalMenuItem
|
||||
category={category}
|
||||
label={label}
|
||||
icon={icon}
|
||||
extensionProps={extensionProps}
|
||||
active={active}
|
||||
onClick={onClick}
|
||||
setSelectedModal={setSelectedModal}
|
||||
props={props}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default MenuItem;
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC, ReactElement } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import { Icon } from "@scm-manager/ui-components";
|
||||
import { MenuItemContainer } from "./ContentActionMenu";
|
||||
import { extensionPoints } from "@scm-manager/ui-extensions";
|
||||
|
||||
const ModalMenuItem: FC<
|
||||
extensionPoints.ModalMenuProps & {
|
||||
active: boolean;
|
||||
onClick: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
setSelectedModal: (element: ReactElement | undefined) => void;
|
||||
extensionProps: extensionPoints.ContentActionExtensionProps;
|
||||
}
|
||||
> = ({ modalElement, active, label, icon, props, extensionProps, setSelectedModal, ...rest }) => {
|
||||
const [t] = useTranslation("plugins");
|
||||
|
||||
return (
|
||||
<MenuItemContainer
|
||||
className={classNames("is-clickable", "is-flex", "is-align-items-centered", {
|
||||
"has-background-info has-text-white": active,
|
||||
})}
|
||||
title={t(label)}
|
||||
{...props}
|
||||
{...rest}
|
||||
onClick={(event) => {
|
||||
setSelectedModal(
|
||||
React.createElement(modalElement, { ...extensionProps, close: () => setSelectedModal(undefined) })
|
||||
);
|
||||
rest.onClick(event);
|
||||
}}
|
||||
>
|
||||
<Icon name={icon} color="inherit" className="pr-5" />
|
||||
<span>{t(label)}</span>
|
||||
</MenuItemContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalMenuItem;
|
||||
@@ -26,12 +26,14 @@ import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import { File, Repository } from "@scm-manager/ui-types";
|
||||
import { File, Link, Repository } from "@scm-manager/ui-types";
|
||||
import { DateFromNow, ErrorNotification, FileSize, Icon, OpenInFullscreenButton } from "@scm-manager/ui-components";
|
||||
import FileButtonAddons from "../components/content/FileButtonAddons";
|
||||
import SourcesView from "./SourcesView";
|
||||
import HistoryView from "./HistoryView";
|
||||
import AnnotateView from "./AnnotateView";
|
||||
import ContentActionMenu from "../components/content/overflowMenu/ContentActionMenu";
|
||||
import { useContentType } from "@scm-manager/ui-api";
|
||||
|
||||
type Props = {
|
||||
file: File;
|
||||
@@ -70,6 +72,7 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) =
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const [selected, setSelected] = useState<SourceViewSelection>("source");
|
||||
const [errorFromExtension, setErrorFromExtension] = useState<Error>();
|
||||
const { data: contentType } = useContentType((file._links.self as Link).href);
|
||||
|
||||
const wrapContent = (content: ReactNode) => {
|
||||
return (
|
||||
@@ -119,6 +122,14 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) =
|
||||
/>
|
||||
) : null;
|
||||
|
||||
const extensionProps: extensionPoints.ContentActionExtensionProps = {
|
||||
repository,
|
||||
file,
|
||||
revision: revision ? encodeURIComponent(revision) : "",
|
||||
handleExtensionError: setErrorFromExtension,
|
||||
contentType,
|
||||
};
|
||||
|
||||
return (
|
||||
<HeaderWrapper>
|
||||
<div className={classNames("level", "is-flex-wrap-wrap")}>
|
||||
@@ -138,14 +149,10 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) =
|
||||
/>
|
||||
<ExtensionPoint<extensionPoints.ReposSourcesContentActionBar>
|
||||
name="repos.sources.content.actionbar"
|
||||
props={{
|
||||
repository,
|
||||
file,
|
||||
revision: revision ? encodeURIComponent(revision) : "",
|
||||
handleExtensionError: setErrorFromExtension
|
||||
}}
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
<ContentActionMenu extensionProps={extensionProps} />
|
||||
</div>
|
||||
</div>
|
||||
</HeaderWrapper>
|
||||
@@ -201,7 +208,7 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) =
|
||||
props={{
|
||||
file,
|
||||
repository,
|
||||
revision
|
||||
revision,
|
||||
}}
|
||||
/>
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user