diff --git a/deploy/app.js b/deploy/app.js new file mode 100644 index 0000000000000000000000000000000000000000..f2fb025c607568088c25a7de6a01339bdeaf45a4 --- /dev/null +++ b/deploy/app.js @@ -0,0 +1,64 @@ +const path = require('path') +const express = require('express') +const app = express() +const session = require('express-session') +const MemoryStore = require('memorystore')(session) + +app.disable('x-powered-by') + +if (process.env.NODE_ENV !== 'production') { + app.use(require('cors')()) +} + +/** + * load env first, then load other modules + */ + +const configureAuth = require('./auth') + +const store = new MemoryStore({ + checkPeriod: 86400000 +}) + +const SESSIONSECRET = process.env.SESSIONSECRET || 'this is not really a random session secret' + +/** + * passport application of oidc requires session + */ +app.use(session({ + secret: SESSIONSECRET, + resave: true, + saveUninitialized: false, + store +})) + +/** + * configure Auth + * async function, but can start server without + */ +configureAuth(app) + +const catchError = require('./catchError') +app.use(catchError) + +const publicPath = process.env.NODE_ENV === 'production' + ? path.join(__dirname, 'public') + : path.join(__dirname, '..', 'dist', 'aot') +app.use(express.static(publicPath)) + +app.use((req, res, next) => { + res.set('Content-Type', 'application/json') + next() +}) + +const templateRouter = require('./templates') +const nehubaConfigRouter = require('./nehubaConfig') +const datasetRouter = require('./datasets') +const pluginRouter = require('./plugins') + +app.use('/templates', templateRouter) +app.use('/nehubaConfig', nehubaConfigRouter) +app.use('/datasets', datasetRouter) +app.use('/plugins', pluginRouter) + +module.exports = app \ No newline at end of file diff --git a/deploy/plugins/index.js b/deploy/plugins/index.js new file mode 100644 index 0000000000000000000000000000000000000000..9f15b4687a85be1f07b76284694132fd0fb799eb --- /dev/null +++ b/deploy/plugins/index.js @@ -0,0 +1,19 @@ +/** + * TODO + * how to discover plugins? + */ + +const express = require('express') +const router = express.Router() +const PLUGIN_URLS = process.env.PLUGIN_URLS && JSON.stringify(process.env.PLUGIN_URLS.split(';')) + +router.get('', (_req, res) => { + + if (PLUGIN_URLS) { + return res.status(200).send(PLUGIN_URLS) + } else { + return res.status(200).send('[]') + } +}) + +module.exports = router \ No newline at end of file diff --git a/deploy/server.js b/deploy/server.js index cd6a1da4c2e72ac70be1157b88de454e2e993d7f..85596178cccb7433a77195c447e3efa665cd2583 100644 --- a/deploy/server.js +++ b/deploy/server.js @@ -1,73 +1,11 @@ -const path = require('path') -const express = require('express') -const app = express() -const session = require('express-session') -const MemoryStore = require('memorystore')(session) - -app.disable('x-powered-by') - if (process.env.NODE_ENV !== 'production') { require('dotenv').config() - app.use(require('cors')()) process.on('unhandledRejection', (err, p) => { console.log({err, p}) }) } -/** - * load env first, then load other modules - */ - -const configureAuth = require('./auth') - -const store = new MemoryStore({ - checkPeriod: 86400000 -}) - -const SESSIONSECRET = process.env.SESSIONSECRET || 'this is not really a random session secret' - -/** - * passport application of oidc requires session - */ -app.use(session({ - secret: SESSIONSECRET, - resave: true, - saveUninitialized: false, - store -})) - -const startServer = async (app) => { - try{ - await configureAuth(app) - }catch (e) { - console.log('error during configureAuth', e) - } - - const catchError = require('./catchError') - app.use(catchError) - - const publicPath = process.env.NODE_ENV === 'production' - ? path.join(__dirname, 'public') - : path.join(__dirname, '..', 'dist', 'aot') - app.use(express.static(publicPath)) - - app.use((req, res, next) => { - res.set('Content-Type', 'application/json') - next() - }) - - const templateRouter = require('./templates') - const nehubaConfigRouter = require('./nehubaConfig') - const datasetRouter = require('./datasets') - - - app.use('/templates', templateRouter) - app.use('/nehubaConfig', nehubaConfigRouter) - app.use('/datasets', datasetRouter) - - const PORT = process.env.PORT || 3000 - - app.listen(PORT, () => console.log(`listening on port ${PORT}`)) -} +const app = require('./app') +const PORT = process.env.PORT || 3000 -startServer(app) \ No newline at end of file +app.listen(PORT, () => console.log(`listening on port ${PORT}`)) \ No newline at end of file diff --git a/src/atlasViewer/atlasViewer.dataService.service.ts b/src/atlasViewer/atlasViewer.dataService.service.ts index 182bd824b534c62d1744263c4f25d9a97d9f695b..ee42e02befd2533f36d80be12359691001f95490 100644 --- a/src/atlasViewer/atlasViewer.dataService.service.ts +++ b/src/atlasViewer/atlasViewer.dataService.service.ts @@ -20,6 +20,17 @@ export class AtlasViewerDataService implements OnDestroy{ PLUGINDEV ? fetch(PLUGINDEV).then(res => res.json()) : Promise.resolve([]), + new Promise(resolve => { + fetch(`${this.constantService.backendUrl}plugins`) + .then(res => res.json()) + .then(arr => Promise.all( + arr.map(url => fetch(url).then(res => res.json())) + )) + .then(resolve) + .catch(e => { + resolve([]) + }) + }), Promise.all( BUNDLEDPLUGINS .filter(v => typeof v === 'string') diff --git a/src/atlasViewer/atlasViewer.pluginService.service.ts b/src/atlasViewer/atlasViewer.pluginService.service.ts index 8bf27a5f3a7dee3cbcda9be7ba4a62b1acee0ee2..6a3d590f639535b9f3fb80e1298ccc599307ac54 100644 --- a/src/atlasViewer/atlasViewer.pluginService.service.ts +++ b/src/atlasViewer/atlasViewer.pluginService.service.ts @@ -53,7 +53,7 @@ export class PluginServices{ fetch(plugin.scriptURL) .then(res=>res.text()) .then(script=>plugin.script = script) : - Promise.reject('both template and templateURL are not defined') + Promise.reject('both script and scriptURL are not defined') ]) } @@ -108,6 +108,7 @@ export class PluginServices{ const widgetCompRef = this.widgetService.addNewWidget(pluginUnit,{ state : 'floating', exitable : true, + persistency: plugin.persistency, title : plugin.displayName || plugin.name }) @@ -179,4 +180,5 @@ export interface PluginManifest{ script? : string initState? : any initStateUrl? : string + persistency? : boolean } \ No newline at end of file diff --git a/src/components/toast/toast.component.ts b/src/components/toast/toast.component.ts index 93b9233ba7b4048b8923cb282b4647530591650a..c0ca259a5e4bbf2da6d61f4027a81a15ad44bd93 100644 --- a/src/components/toast/toast.component.ts +++ b/src/components/toast/toast.component.ts @@ -12,6 +12,7 @@ import { toastAnimation } from "./toast.animation"; export class ToastComponent{ @Input() message : string + @Input() htmlMessage: string @Input() timeout : number = 0 @Input() dismissable : boolean = true diff --git a/src/components/toast/toast.template.html b/src/components/toast/toast.template.html index 7cd477e5aee8a9afc1793982f94cf3b51037725d..6d1450269366772b1c96930e0178436bbad5d81e 100644 --- a/src/components/toast/toast.template.html +++ b/src/components/toast/toast.template.html @@ -4,12 +4,25 @@ </ng-template> </div> - <div message *ngIf = "message"> + <div message + [innerHTML]="htmlMessage" + *ngIf = "htmlMessage"> + </div> + <div + message + *ngIf="message && !htmlMessage"> {{ message }} </div> - <div (click) = "dismiss($event)" *ngIf = "dismissable" close> - <i class = "fas fa-remove"></i> + <div + (click)="dismiss($event)" + *ngIf="dismissable" close> + <i class="fas fa-remove"></i> </div> - <timer-component (timerEnd)="dismissed.emit(false)" [pause] = "hover" [timeout] = "timeout" timer> + <timer-component + *ngIf="timeout > 0" + (timerEnd)="dismissed.emit(false)" + [pause]="hover" + [timeout]="timeout" + timer> </timer-component> </div> \ No newline at end of file diff --git a/src/ui/databrowserModule/databrowser.service.ts b/src/ui/databrowserModule/databrowser.service.ts index 24b811e80dc8983d10183d068677123e2a8c3473..2ebc06a8ecbb7797d53e5bb9315876b45d5371a4 100644 --- a/src/ui/databrowserModule/databrowser.service.ts +++ b/src/ui/databrowserModule/databrowser.service.ts @@ -93,7 +93,6 @@ export class DatabrowserService implements OnDestroy{ this.subscriptions.push( this.selectedRegions$.subscribe(r => { this.selectedRegions = r - console.log(r) this.workerService.worker.postMessage({ type: 'BUILD_REGION_SELECTION_TREE', selectedRegions: r, diff --git a/src/ui/menuicons/menuicons.component.ts b/src/ui/menuicons/menuicons.component.ts index 45a8a671c96b7fae1508c0dba3a38881503418f2..f6e80ca7c12885050621d1fdc3ad696c28486eb9 100644 --- a/src/ui/menuicons/menuicons.component.ts +++ b/src/ui/menuicons/menuicons.component.ts @@ -4,6 +4,7 @@ import { WidgetServices } from "src/atlasViewer/widgetUnit/widgetService.service import { WidgetUnit } from "src/atlasViewer/widgetUnit/widgetUnit.component"; import { LayerBrowser } from "src/ui/layerbrowser/layerbrowser.component"; import { DataBrowser } from "src/ui/databrowserModule/databrowser/databrowser.component"; +import { PluginBannerUI } from "../pluginBanner/pluginBanner.component"; @Component({ selector: 'menu-icons', @@ -22,7 +23,6 @@ export class MenuIconsBar{ dataBrowser: ComponentRef<DataBrowser> = null dbWidget: ComponentRef<WidgetUnit> = null - /** * layerBrowser */ @@ -30,6 +30,13 @@ export class MenuIconsBar{ layerBrowser: ComponentRef<LayerBrowser> = null lbWidget: ComponentRef<WidgetUnit> = null + /** + * pluginBrowser + */ + pbcf: ComponentFactory<PluginBannerUI> + pluginBanner: ComponentRef<PluginBannerUI> = null + pbWidget: ComponentRef<WidgetUnit> = null + constructor( private widgetServices:WidgetServices, private injector:Injector, @@ -37,6 +44,7 @@ export class MenuIconsBar{ ){ this.dbcf = cfr.resolveComponentFactory(DataBrowser) this.lbcf = cfr.resolveComponentFactory(LayerBrowser) + this.pbcf = cfr.resolveComponentFactory(PluginBannerUI) } public clickSearch(event: MouseEvent){ if (this.dbWidget) { @@ -92,6 +100,32 @@ export class MenuIconsBar{ this.lbWidget.instance.position = [left, top] } + public clickPlugins(event: MouseEvent){ + if(this.pbWidget) { + this.pbWidget.destroy() + this.pbWidget = null + return + } + this.pluginBanner = this.pbcf.create(this.injector) + this.pbWidget = this.widgetServices.addNewWidget(this.pluginBanner, { + exitable: true, + persistency: true, + state: 'floating', + title: 'Plugin Browser', + titleHTML: '<i class="fas fa-tools"></i> Plugin Browser' + }) + + this.pbWidget.onDestroy(() => { + this.pbWidget = null + this.pluginBanner = null + }) + + const el = event.currentTarget as HTMLElement + const top = el.offsetTop + const left = el.offsetLeft + 50 + this.pbWidget.instance.position = [left, top] + } + get databrowserIsShowing() { return this.dataBrowser !== null } @@ -100,6 +134,10 @@ export class MenuIconsBar{ return this.layerBrowser !== null } + get pluginbrowserIsShowing() { + return this.pluginBanner !== null + } + get dataBrowserTitle() { return `Browse` } diff --git a/src/ui/menuicons/menuicons.template.html b/src/ui/menuicons/menuicons.template.html index 8f40b4e913de40da340d571b4e0e587afa09f110..1b6ac141f3180b05a63a433693bf2be048be5fd1 100644 --- a/src/ui/menuicons/menuicons.template.html +++ b/src/ui/menuicons/menuicons.template.html @@ -27,8 +27,10 @@ </div> <div tooltip="Plugins" + (click)="clickPlugins($event)" placement="right" - class="btn btn-sm btn-secondary rounded-circle"> + [ngClass]="pluginbrowserIsShowing ? 'btn-primary' : 'btn-secondary'" + class="btn btn-sm rounded-circle"> <i class="fas fa-tools"> </i> diff --git a/src/ui/pluginBanner/pluginBanner.component.ts b/src/ui/pluginBanner/pluginBanner.component.ts index 11c044191da1e0374d728b27e6b6ed3af2143b3e..0b4fe404a110a3d82cfdade361e894e0a2aeb92e 100644 --- a/src/ui/pluginBanner/pluginBanner.component.ts +++ b/src/ui/pluginBanner/pluginBanner.component.ts @@ -24,7 +24,7 @@ export class PluginBannerUI{ } get pluginEnabledFlag(){ - return PLUGINDEV || BUNDLEDPLUGINS.length > 0 + return true || PLUGINDEV || BUNDLEDPLUGINS.length > 0 ? true : false } diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index e24e6ac310d315cadea1dc1234b3bfab5b45e826..407d38c7562406c11487bd2aa6f0eb0638967338 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -95,6 +95,7 @@ import { FilterNgLayer } from "src/util/pipes/filterNgLayer.pipe"; /* dynamically created components needs to be declared here */ NehubaViewerUnit, LayerBrowser, + PluginBannerUI ], exports : [ SubjectViewer, diff --git a/src/util/directives/toastContainer.directive.ts b/src/util/directives/toastContainer.directive.ts index befacad355e8a3e83e8e5cd44621dfdeec67a83b..162e3c6d776fa64dea6dff58ab6e3cf9e7e0c4fc 100644 --- a/src/util/directives/toastContainer.directive.ts +++ b/src/util/directives/toastContainer.directive.ts @@ -51,6 +51,7 @@ export class ToastContainerDirective{ toastComponent.instance.dismissable = handler.dismissable toastComponent.instance.message = handler.message + toastComponent.instance.htmlMessage = handler.htmlMessage toastComponent.instance.timeout = handler.timeout const _subscription = toastComponent.instance.dismissed.subscribe(userInitiated => { diff --git a/src/util/pluginHandlerClasses/toastHandler.ts b/src/util/pluginHandlerClasses/toastHandler.ts index deae6df7ca4eb39412cee1e5d0398a7ea95d4198..80d696980b907641d384600bcfe3643163037fe8 100644 --- a/src/util/pluginHandlerClasses/toastHandler.ts +++ b/src/util/pluginHandlerClasses/toastHandler.ts @@ -4,4 +4,5 @@ export class ToastHandler{ dismissable : boolean = true show : () => void hide : () => void + htmlMessage: string } \ No newline at end of file