From f5d9855a2402bb28b53c70803b25a8f9caf4e4c8 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 30 Sep 2021 16:41:04 +0200 Subject: [PATCH] Add extension points for source tree (#1816) This change will add an extension point which allows to wrap the source tree. This is required in order to use a context provider e.g. to capture a selected file. Another extension point allows to add a row between the row of a file. In order to implement the extension points ui-extensions has now a wrapper property and passes the children of an extension point to implementing extension. --- docs/en/development/ui-extensions.md | 74 ++++++++++++++++- gradle/changelog/ep_history_download.yaml | 2 + .../ui-extensions/src/ExtensionPoint.test.tsx | 81 +++++++++++++++++-- scm-ui/ui-extensions/src/ExtensionPoint.tsx | 38 ++++++--- scm-ui/ui-extensions/src/extensionPoints.ts | 26 ++++++ .../src/repos/sources/components/FileTree.tsx | 68 +++++++++------- .../repos/sources/components/FileTreeLeaf.tsx | 59 ++++++++------ .../src/repos/sources/containers/Sources.tsx | 1 + 8 files changed, 279 insertions(+), 70 deletions(-) create mode 100644 gradle/changelog/ep_history_download.yaml diff --git a/docs/en/development/ui-extensions.md b/docs/en/development/ui-extensions.md index 7f0821550f..b1490a7126 100644 --- a/docs/en/development/ui-extensions.md +++ b/docs/en/development/ui-extensions.md @@ -105,7 +105,7 @@ binder.bind("repo.avatar", GitAvatar, (props) => props.type === "git"); ``` ```javascript - + ``` ### Typings @@ -141,3 +141,75 @@ Negative Example: This code for example, would lead to a compile time type error because we made a typo in the `name` of the extension when binding it. If we had used the `bind` method without the type parameter, we would not have gotten an error but run into problems at runtime. + +### Children + +If an extension point defines children those children are propagated to the extensions as children prop e.g: + +```tsx +const MyExtension:FC = ({children}) => ( +
{children}
+) +const App = () => { + binder.bind("box", MyExtension); + return ( + +

Box Content

+
+ ); +} +``` + +The example above renders the following html code: + +```html +
+

Box Content

+
+``` + +An exception is when the extension already has a children property, this could be the case if jsx is directly bind. +This exception applies not only to the children property it applies to every property. +The example below renders `Ahoi`, because the property of the jsx overwrites the one from the extension point. + +```tsx +type Props = { + greeting: string; +} + +const GreetingExtension:FC = ({greeting}) => ( + <>{greeting} +); + +const App = () => { + binder.bind("greet", ); + return ; +}; +``` + +### Wrapper + +Sometimes it can be useful to allow plugin developers to wrap an existing component. +The `wrapper` property is exactly for this case, it allows to wrap an existing component with multiple extensions e.g.: + +```tsx +const Outer: FC = ({ children }) => ( + <>Outer -> {children} +); + +const Inner: FC = ({ children }) => ( + <>Outer -> {children} +); + +const App = () => { + binder.bind("wrapped", Outer); + binder.bind("wrapped", Inner); + return ( + + Children + + ); +} +``` + +The example above renders `Outer -> Inner -> Children`, because each extension is passed as children to the parent extension. diff --git a/gradle/changelog/ep_history_download.yaml b/gradle/changelog/ep_history_download.yaml new file mode 100644 index 0000000000..c35635baf6 --- /dev/null +++ b/gradle/changelog/ep_history_download.yaml @@ -0,0 +1,2 @@ +- type: Added + description: Extension points for source tree ([#1816](https://github.com/scm-manager/scm-manager/pull/1816)) diff --git a/scm-ui/ui-extensions/src/ExtensionPoint.test.tsx b/scm-ui/ui-extensions/src/ExtensionPoint.test.tsx index 0f627374bb..0794322c39 100644 --- a/scm-ui/ui-extensions/src/ExtensionPoint.test.tsx +++ b/scm-ui/ui-extensions/src/ExtensionPoint.test.tsx @@ -21,9 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React from "react"; +import React, { FC } from "react"; import ExtensionPoint from "./ExtensionPoint"; import { shallow, mount } from "enzyme"; +// eslint-disable-next-line no-restricted-imports import "@scm-manager/ui-tests/enzyme"; import binder from "./binder"; @@ -89,7 +90,7 @@ describe("ExtensionPoint test", () => { ); @@ -126,7 +127,7 @@ describe("ExtensionPoint test", () => { it("should pass the context of the parent component", () => { const UserContext = React.createContext({ - name: "anonymous" + name: "anonymous", }); type HelloProps = { @@ -148,7 +149,7 @@ describe("ExtensionPoint test", () => { return ( @@ -187,7 +188,7 @@ describe("ExtensionPoint test", () => { }; mockedBinder.hasExtension.mockReturnValue(true); - mockedBinder.getExtension.mockReturnValue(