From babb06ad7efcdeab6ec9fcdfda5225ccb846a738 Mon Sep 17 00:00:00 2001 From: janh Date: Sat, 24 Sep 2022 21:01:43 +0200 Subject: [PATCH] added readme.md --- README.md | 412 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 389 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 2ad454e..9b13cba 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,399 @@ -# Cumulocity widget plugin +# Summit Demo -This is the Cumulocity module federation plugin. Plugins can be developed like any Cumulocity application, but can be used at runtime by other applications. Therefore, they export an Angular module which can then be imported by any other application. The exports are defined in `package.json`: +## Prepare + - node 14 + - npm i -g @c8y/cli@next + - clone this repository + - switch to summit-ui-demo -``` -"exports": [ - { - "name": "Example widget plugin", - "module": "WidgetPluginModule", - "path": "./widget/widget-plugin.module.ts", - "description": "Adds custom widget" - } -] -``` +## 1. Step +Add your tenant and shell to npm start command. Run `npm start` and checkout the two application started. -**How to start** -Run the command below to scaffold a `widget` plugin. + - shows exports/remotes in package.json + - shows debugging with `--shell` + +
+ Diff +``` +diff --git a/package.json b/package.json +index b100dfe..a2dfd14 100644 +--- a/package.json ++++ b/package.json +@@ -3,7 +3,7 @@ + "version": "1.0.0", + "description": "This is the Cumulocity module federation plugin. Plugins can be developed like any Cumulocity application, but can be used at runtime by other applications. Therefore, they export an Angular module which can then be imported by any other application. The exports are defined in `package.json`:", + "scripts": { +- "start": "c8ycli server", ++ "start": "c8ycli server -u http://iot-summit-berlin.dev.c8y.io --shell cockpit", + "build": "c8ycli build", + "deploy": "c8ycli deploy", + "postinstall": "ngcc" ``` -c8ycli new widget-plugin -``` +
-As the app.module is a typical Cumuloctiy application, any new plugin can be tested via the CLI: + > If not working -> checkout `step1` branch + +## 2. Step +Add a new plugin adding a navigator node -``` -npm start -- --shell cockpit -``` + - shows how simple a new plugin can be added + - shows how to add a navigator node with useClass + +
+ Diff +``` +diff --git a/bookmarks/bookmarks.module.ts b/bookmarks/bookmarks.module.ts +new file mode 100644 +index 0000000..9907e4a +--- /dev/null ++++ b/bookmarks/bookmarks.module.ts +@@ -0,0 +1,15 @@ ++import { NgModule } from "@angular/core"; ++import { CommonModule } from "@angular/common"; ++import { BookmarksService } from "./bookmarks.service"; ++import { HOOK_NAVIGATOR_NODES } from "@c8y/ngx-components"; ++ ++@NgModule({ ++ declarations: [], ++ imports: [CommonModule], ++ exports: [], ++ providers: [ ++ BookmarksService, ++ { provide: HOOK_NAVIGATOR_NODES, useClass: BookmarksService, multi: true }, ++ ], ++}) ++export class BookmarksModule {} +diff --git a/bookmarks/bookmarks.service.ts b/bookmarks/bookmarks.service.ts +new file mode 100644 +index 0000000..a3e0765 +--- /dev/null ++++ b/bookmarks/bookmarks.service.ts +@@ -0,0 +1,15 @@ ++import { Injectable } from '@angular/core'; ++import { NavigatorNode } from '@c8y/ngx-components'; ++ ++@Injectable({ ++ providedIn: 'root' ++}) ++export class BookmarksService { ++ get() { ++ return new NavigatorNode({ ++ label: 'Bookmarks', ++ icon: 'bookmark', ++ priority: -1000 ++ }); ++ } ++} +\ No newline at end of file +diff --git a/package.json b/package.json +index a2dfd14..bd81906 100644 +--- a/package.json ++++ b/package.json +@@ -62,11 +62,17 @@ + "module": "WidgetPluginModule", + "path": "./widget/widget-plugin.module.ts", + "description": "Adds a custom widget to the shell application" ++ }, ++ { ++ "name": "Device bookmarks", ++ "module": "BookmarksModule", ++ "path": "./bookmarks/bookmarks.module.ts", ++ "description": "Allows you to bookmark your favorite device" + } + ], + "remotes": { + "summit-ui-demo": [ +- "WidgetPluginModule" ++ "WidgetPluginModule", "BookmarksModule" + ] + } + } + +``` +
+ + > If not working -> checkout `step2` branch + + + ## 3. Step +Add logic and injection to the HOOK + + - injection can be tricky. Here it's quite simple as all is on the root injector. + +
+ Diff +diff --git a/bookmarks/bookmarks.service.ts b/bookmarks/bookmarks.service.ts +index a3e0765..00c23fc 100644 +--- a/bookmarks/bookmarks.service.ts ++++ b/bookmarks/bookmarks.service.ts +@@ -1,15 +1,30 @@ +-import { Injectable } from '@angular/core'; +-import { NavigatorNode } from '@c8y/ngx-components'; ++import { Injectable } from "@angular/core"; ++import { InventoryService } from "@c8y/client"; ++import { NavigatorNode } from "@c8y/ngx-components"; ++import { AssetNode, AssetNodeService } from "@c8y/ngx-components/assets-navigator"; + + @Injectable({ +- providedIn: 'root' ++ providedIn: "root", + }) + export class BookmarksService { +- get() { +- return new NavigatorNode({ +- label: 'Bookmarks', +- icon: 'bookmark', +- priority: -1000 ++ constructor(private inventory: InventoryService, private assetService: AssetNodeService) {} ++ ++ async get() { ++ const { data: bookmarkedDevices } = await this.inventory.list({ ++ fragmentType: "c8y_IsBookmarked", ++ pageSize: 2000 ++ }); ++ ++ const children = bookmarkedDevices.map((mo) => new AssetNode(this.assetService, { mo })); ++ ++ const rootNode = new NavigatorNode({ ++ label: "Bookmarks", ++ icon: "bookmark", ++ priority: -1000, + }); ++ ++ rootNode.children = children; ++ ++ return rootNode; + } +-} +\ No newline at end of file ++} + +
+ + > If not working -> checkout `step3` branch + +## 3. Step +Add a action bar for all devices + + - use routing to check if on a device + - use content projection to avoid root element + +
+ Diff + diff --git a/bookmarks/add-bookmark.component.ts b/bookmarks/add-bookmark.component.ts +new file mode 100644 +index 0000000..8adaff1 +--- /dev/null ++++ b/bookmarks/add-bookmark.component.ts +@@ -0,0 +1,24 @@ ++import { Component, ViewChild, ViewContainerRef } from "@angular/core"; ++ ++@Component({ ++ selector: "[c8y-add-bookmark]", ++ template: ` ++ ++
  • ++ ++
  • ++
    ++ `, ++}) ++export class AddBookmarkComponent { ++ @ViewChild("template", { static: true }) template; ++ ++ constructor(private viewContainerRef: ViewContainerRef) {} ++ ++ ngOnInit() { ++ this.viewContainerRef.createEmbeddedView(this.template); ++ } ++} +diff --git a/bookmarks/add-bookmark.service.ts b/bookmarks/add-bookmark.service.ts +new file mode 100644 +index 0000000..242e9e5 +--- /dev/null ++++ b/bookmarks/add-bookmark.service.ts +@@ -0,0 +1,22 @@ ++import { Injectable } from "@angular/core"; ++import { Router } from "@angular/router"; ++import { ActionBarItem } from "@c8y/ngx-components"; ++import { AddBookmarkComponent } from "./add-bookmark.component"; ++ ++@Injectable({ ++ providedIn: "root", ++}) ++export class AddBookmarkService { ++ constructor(private router: Router) {} ++ ++ get() { ++ if (/device\/\w+/.test(this.router.url)) { ++ return { ++ template: AddBookmarkComponent, ++ priority: 0, ++ placement: "more", ++ } as ActionBarItem; ++ } ++ return []; ++ } ++} +diff --git a/bookmarks/bookmarks.module.ts b/bookmarks/bookmarks.module.ts +index 9907e4a..c370178 100644 +--- a/bookmarks/bookmarks.module.ts ++++ b/bookmarks/bookmarks.module.ts +@@ -1,15 +1,18 @@ + import { NgModule } from "@angular/core"; +-import { CommonModule } from "@angular/common"; ++import { CommonModule } from "@c8y/ngx-components"; + import { BookmarksService } from "./bookmarks.service"; +-import { HOOK_NAVIGATOR_NODES } from "@c8y/ngx-components"; ++import { AddBookmarkService } from "./add-bookmark.service"; ++import { HOOK_ACTION_BAR, HOOK_NAVIGATOR_NODES } from "@c8y/ngx-components"; ++import { AddBookmarkComponent } from "./add-bookmark.component"; + + @NgModule({ +- declarations: [], ++ declarations: [AddBookmarkComponent], + imports: [CommonModule], + exports: [], + providers: [ + BookmarksService, + { provide: HOOK_NAVIGATOR_NODES, useClass: BookmarksService, multi: true }, ++ { provide: HOOK_ACTION_BAR, useClass: AddBookmarkService, multi: true } + ], + }) + export class BookmarksModule {} + +
    -In the Module Federation terminology, `widget` plugin is called `remote` and the `cokpit` is called `shell`. Modules provided by this `widget` will be loaded by the `cockpit` application at the runtime. This plugin provides a basic custom widget that can be accessed through the `Add widget` menu. +## 4. Step +Add the logic to bookmark a device -> Note that the `--shell` flag creates a proxy to the cockpit application and provides` WidgetPluginModule` as an `remote` via URL options. + - get the context + - update the device + +
    + Diff +diff --git a/bookmarks/add-bookmark.component.ts b/bookmarks/add-bookmark.component.ts +new file mode 100644 +index 0000000..bc69b7d +--- /dev/null ++++ b/bookmarks/add-bookmark.component.ts +@@ -0,0 +1,59 @@ ++import { Component, ViewChild, ViewContainerRef } from "@angular/core"; ++import { Router } from "@angular/router"; ++import { InventoryService } from "@c8y/client"; ++import { ++ ContextRouteService, ++ getActivatedRoute, ++ NavigatorService, ++} from "@c8y/ngx-components"; ++ ++@Component({ ++ selector: "[c8y-add-bookmark]", ++ template: ` ++ ++
  • ++ ++
  • ++
    ++ `, ++}) ++export class AddBookmarkComponent { ++ isBookmarked = true; ++ @ViewChild("template", { static: true }) template; ++ ++ constructor( ++ private viewContainerRef: ViewContainerRef, ++ private router: Router, ++ private inventory: InventoryService, ++ private contextRoute: ContextRouteService, ++ private navigator: NavigatorService ++ ) {} ++ ++ ngOnInit() { ++ this.viewContainerRef.createEmbeddedView(this.template); ++ this.isBookmarked = !!this.contextRoute.getContextData( ++ getActivatedRoute(this.router) ++ )?.contextData?.c8y_IsBookmarked; ++ } ++ ++ async bookmark() { ++ const currentDevice = this.contextRoute.getContextData( ++ getActivatedRoute(this.router) ++ )?.contextData; ++ if (currentDevice) { ++ await this.inventory.update({ ++ id: currentDevice.id as string, ++ c8y_IsBookmarked: this.isBookmarked ? null : {}, ++ }); ++ this.isBookmarked = !this.isBookmarked; ++ } ++ this.navigator.refresh(); ++ } ++} +diff --git a/bookmarks/add-bookmark.service.ts b/bookmarks/add-bookmark.service.ts +new file mode 100644 +index 0000000..242e9e5 +--- /dev/null ++++ b/bookmarks/add-bookmark.service.ts +@@ -0,0 +1,22 @@ ++import { Injectable } from "@angular/core"; ++import { Router } from "@angular/router"; ++import { ActionBarItem } from "@c8y/ngx-components"; ++import { AddBookmarkComponent } from "./add-bookmark.component"; ++ ++@Injectable({ ++ providedIn: "root", ++}) ++export class AddBookmarkService { ++ constructor(private router: Router) {} ++ ++ get() { ++ if (/device\/\w+/.test(this.router.url)) { ++ return { ++ template: AddBookmarkComponent, ++ priority: 0, ++ placement: "more", ++ } as ActionBarItem; ++ } ++ return []; ++ } ++} +diff --git a/bookmarks/bookmarks.module.ts b/bookmarks/bookmarks.module.ts +index 9907e4a..c370178 100644 +--- a/bookmarks/bookmarks.module.ts ++++ b/bookmarks/bookmarks.module.ts +@@ -1,15 +1,18 @@ + import { NgModule } from "@angular/core"; +-import { CommonModule } from "@angular/common"; ++import { CommonModule } from "@c8y/ngx-components"; + import { BookmarksService } from "./bookmarks.service"; +-import { HOOK_NAVIGATOR_NODES } from "@c8y/ngx-components"; ++import { AddBookmarkService } from "./add-bookmark.service"; ++import { HOOK_ACTION_BAR, HOOK_NAVIGATOR_NODES } from "@c8y/ngx-components"; ++import { AddBookmarkComponent } from "./add-bookmark.component"; + + @NgModule({ +- declarations: [], ++ declarations: [AddBookmarkComponent], + imports: [CommonModule], + exports: [], + providers: [ + BookmarksService, + { provide: HOOK_NAVIGATOR_NODES, useClass: BookmarksService, multi: true }, ++ { provide: HOOK_ACTION_BAR, useClass: AddBookmarkService, multi: true } + ], + }) + export class BookmarksModule {} -Also deploying needs no special handling and can be simply done via `npm run deploy`. As soon as the application has exports it will be uploaded as a plugin. +