From 1bf3c2d4e980c530060e98a09ebe3dff614f1572 Mon Sep 17 00:00:00 2001 From: Ralf Aron Date: Sun, 15 Oct 2023 16:00:29 +0200 Subject: [PATCH 1/2] implement toolbar using ng-template --- projects/aas-lib/src/lib/aas-lib.module.ts | 3 - projects/aas-lib/src/lib/download.service.ts | 31 +- .../src/lib/toolbar/toolbar.component.html | 109 ---- .../src/lib/toolbar/toolbar.component.scss | 7 - .../src/lib/toolbar/toolbar.component.ts | 164 ------ .../src/lib/toolbar/toolbar.service.ts | 470 ------------------ projects/aas-lib/src/lib/toolbar/toolbar.ts | 128 ----- projects/aas-lib/src/public-api.ts | 4 - .../test/aas-tree/aas-tree.component.spec.ts | 2 +- .../aas-lib/src/test/download.service.spec.ts | 11 +- .../test/toolbar/toolbar.component.spec.ts | 404 --------------- .../src/test/toolbar/toolbar.service.spec.ts | 48 -- .../aas-portal/src/app/aas/aas.component.html | 68 ++- .../aas-portal/src/app/aas/aas.component.ts | 141 ++---- .../src/app/about/about.component.html | 2 + .../src/app/about/about.component.ts | 23 +- projects/aas-portal/src/app/app.module.ts | 7 +- .../src/app/dashboard/dashboard.actions.ts | 7 +- .../app/dashboard/dashboard.component.html | 53 +- .../src/app/dashboard/dashboard.component.ts | 193 +++---- .../src/app/dashboard/dashboard.reducer.ts | 8 +- .../src/app/main/main.component.html | 8 +- .../aas-portal/src/app/main/main.component.ts | 23 +- .../src/app/start/start.component.html | 74 ++- .../src/app/start/start.component.ts | 129 ++--- .../aas-portal/src/app/toolbar.service.ts | 31 ++ .../src/test/aas/aas.component.spec.ts | 9 +- .../dashboard/dashboard.component.spec.ts | 2 +- .../src/test/toolbar.service.spec.ts | 42 ++ 29 files changed, 534 insertions(+), 1667 deletions(-) delete mode 100644 projects/aas-lib/src/lib/toolbar/toolbar.component.html delete mode 100644 projects/aas-lib/src/lib/toolbar/toolbar.component.scss delete mode 100644 projects/aas-lib/src/lib/toolbar/toolbar.component.ts delete mode 100644 projects/aas-lib/src/lib/toolbar/toolbar.service.ts delete mode 100644 projects/aas-lib/src/lib/toolbar/toolbar.ts delete mode 100644 projects/aas-lib/src/test/toolbar/toolbar.component.spec.ts delete mode 100644 projects/aas-lib/src/test/toolbar/toolbar.service.spec.ts create mode 100644 projects/aas-portal/src/app/toolbar.service.ts create mode 100644 projects/aas-portal/src/test/toolbar.service.spec.ts diff --git a/projects/aas-lib/src/lib/aas-lib.module.ts b/projects/aas-lib/src/lib/aas-lib.module.ts index af384d76..c2489814 100644 --- a/projects/aas-lib/src/lib/aas-lib.module.ts +++ b/projects/aas-lib/src/lib/aas-lib.module.ts @@ -13,7 +13,6 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { StoreModule } from '@ngrx/store'; import { FormsModule } from '@angular/forms'; -import { ToolbarComponent } from './toolbar/toolbar.component'; import { NotifyComponent } from './notify/notify.component'; import { LocalizeComponent } from './localize/localize.component'; import { LoginFormComponent } from './auth/login-form/login-form.component'; @@ -42,7 +41,6 @@ import { SecuredImageComponent } from './secured-image/secured-image.component'; @NgModule({ declarations: [ NotifyComponent, - ToolbarComponent, LocalizeComponent, LoginFormComponent, RegisterFormComponent, @@ -75,7 +73,6 @@ import { SecuredImageComponent } from './secured-image/secured-image.component'; ], exports: [ NotifyComponent, - ToolbarComponent, LocalizeComponent, LoginFormComponent, RegisterFormComponent, diff --git a/projects/aas-lib/src/lib/download.service.ts b/projects/aas-lib/src/lib/download.service.ts index ef2735e7..52972f40 100644 --- a/projects/aas-lib/src/lib/download.service.ts +++ b/projects/aas-lib/src/lib/download.service.ts @@ -50,25 +50,20 @@ export class DownloadService { * @param id The AAS identifier. * @param name The file name. */ - public downloadDocumentAsync(url: string, id: string, name: string): Promise { - return new Promise((resolve, reject) => { - this.http.get( - `/api/v1/containers/${encodeBase64Url(url)}/documents/${encodeBase64Url(id)}`, - { - responseType: 'blob' - }).pipe(map(blob => { - const a = document.createElement('a'); - a.href = URL.createObjectURL(blob); - a.setAttribute('download', name); - a.click(); - URL.revokeObjectURL(a.href); - })).subscribe({ - error: (error) => reject(error), - complete: () => resolve() - }); - }); + public downloadDocument(url: string, id: string, name: string): Observable { + return this.http.get( + `/api/v1/containers/${encodeBase64Url(url)}/documents/${encodeBase64Url(id)}`, + { + responseType: 'blob' + }).pipe(map(blob => { + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.setAttribute('download', name); + a.click(); + URL.revokeObjectURL(a.href); + })); } - + /** * Uploads the specified aasx file. * @param file A file. diff --git a/projects/aas-lib/src/lib/toolbar/toolbar.component.html b/projects/aas-lib/src/lib/toolbar/toolbar.component.html deleted file mode 100644 index ee4e528e..00000000 --- a/projects/aas-lib/src/lib/toolbar/toolbar.component.html +++ /dev/null @@ -1,109 +0,0 @@ - - - \ No newline at end of file diff --git a/projects/aas-lib/src/lib/toolbar/toolbar.component.scss b/projects/aas-lib/src/lib/toolbar/toolbar.component.scss deleted file mode 100644 index c57d4b61..00000000 --- a/projects/aas-lib/src/lib/toolbar/toolbar.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ diff --git a/projects/aas-lib/src/lib/toolbar/toolbar.component.ts b/projects/aas-lib/src/lib/toolbar/toolbar.component.ts deleted file mode 100644 index 95bd5e30..00000000 --- a/projects/aas-lib/src/lib/toolbar/toolbar.component.ts +++ /dev/null @@ -1,164 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2023 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; -import { Subscription } from 'rxjs'; -import { ToolbarService } from './toolbar.service'; -import { - ToolbarDropDown, - ToolbarItem, - ToolbarItemGroup, - ToolbarItemType, - ToolbarRadio, - ToolbarTextInput, - ToolbarColorPicker, - ToolbarOption, - ToolbarSelect, - ToolbarCommandItem, - ToolbarCheckedItem as ToolbarCheckedItem -} from './toolbar'; - -@Component({ - selector: 'fhg-toolbar', - templateUrl: './toolbar.component.html', - styleUrls: ['./toolbar.component.scss'] -}) -export class ToolbarComponent implements OnInit, OnDestroy { - private readonly subscription = new Subscription(); - - constructor( - private translate: TranslateService, - private toolbar: ToolbarService) { } - - public groups: ToolbarItemGroup[] = []; - - public ngOnInit(): void { - this.subscription.add(this.toolbar.groups.subscribe( - (values) => this.groups = values - )); - } - - public ngOnDestroy() { - this.subscription.unsubscribe(); - } - - public getName(item: ToolbarItem): string | undefined { - if (typeof item.name === 'string') { - return this.translate.instant(item.name); - } - - return ''; - } - - public getValue(item: ToolbarItem): any { - if (this.isRadio(item)) { - return item.value; - } - - if (this.isColorPicker(item)) { - return item.value; - } - - if (this.isSelect(item)) { - return item.value; - } - - if (this.isTextInput(item)) { - return item.value; - } - - return null; - } - - public isChecked(item: ToolbarItem): boolean { - return this.isCheckedItem(item) ? item.checked : false; - } - - public getPlaceholder(item: ToolbarItem): string { - if (this.isTextInput(item)) { - return this.translate.instant(item.placeholder); - } - - return ''; - } - - public getDropdownItems(item: ToolbarItem): ToolbarItem[] { - return this.isDropdown(item) ? item.items : []; - } - - public getOptions(item: ToolbarItem): ToolbarOption[] { - return this.isSelect(item) ? item.options : []; - } - - public isSelected(item: ToolbarItem, option: ToolbarOption): boolean { - return this.isSelect(item) ? item.value === option.value : false; - } - - public canExecute(item: ToolbarItem, argument?: any): boolean { - if (this.isSelect(item)) { - const element = argument as HTMLSelectElement; - return element && element.selectedIndex >= 0 && - item.command.canExecute(element.options[element.selectedIndex].value); - } - - if (this.isCommandItem(item)) { - return item.command.canExecute(argument); - } - - return false; - } - - public execute(item: ToolbarItem, argument?: any): void { - if (this.isSelect(item)) { - const element = argument as HTMLSelectElement; - if (element && element.selectedIndex >= 0) { - item.command.execute(element.options[element.selectedIndex].value); - } - } - else if (this.isCommandItem(item)) { - item.command.execute(argument); - } - } - - public isButtonGroup(group: ToolbarItemGroup): boolean { - return group.items.every( - item => item.type === ToolbarItemType.Button || - item.type === ToolbarItemType.Dropdown || - item.type === ToolbarItemType.Radio || - item.type === ToolbarItemType.Checkbox); - } - - private isCommandItem(item: ToolbarItem): item is ToolbarCommandItem { - return 'command' in item; - } - - private isRadio(item: ToolbarItem): item is ToolbarRadio { - return item.type === ToolbarItemType.Radio; - } - - private isCheckedItem(item: ToolbarItem): item is ToolbarCheckedItem { - return 'checked' in item; - } - - private isTextInput(item: ToolbarItem): item is ToolbarTextInput { - return item.type === ToolbarItemType.TextInput; - } - - private isDropdown(item: ToolbarItem): item is ToolbarDropDown { - return item.type === ToolbarItemType.Dropdown; - } - - private isColorPicker(item: ToolbarItem): item is ToolbarColorPicker { - return item.type === ToolbarItemType.ColorPicker; - } - - private isSelect(item: ToolbarItem): item is ToolbarSelect { - return item.type === ToolbarItemType.Select; - } -} \ No newline at end of file diff --git a/projects/aas-lib/src/lib/toolbar/toolbar.service.ts b/projects/aas-lib/src/lib/toolbar/toolbar.service.ts deleted file mode 100644 index d1c1f29c..00000000 --- a/projects/aas-lib/src/lib/toolbar/toolbar.service.ts +++ /dev/null @@ -1,470 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2023 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -import { Injectable } from '@angular/core'; -import { BehaviorSubject, Observable, Subscription } from 'rxjs'; -import { - Toolbar, - ToolbarCommandCanExecute, - ToolbarCommandExecute as ToolbarCommandExecute, - ToolbarDivider, - ToolbarItem, - ToolbarButton, - ToolbarMenuItem, - ToolbarDropDown, - ToolbarItemGroup, - ToolbarItemType, - ToolbarItemStyle, - ToolbarRadio, - ToolbarTextInput, - ToolbarSwitch, - ToolbarFileInput, - ToolbarColorPicker, - ToolbarCheckbox, - ToolbarSelect, - ToolbarOption, - ToolbarCheckedItem -} from './toolbar'; - -@Injectable({ - providedIn: 'root' -}) -export class ToolbarService { - private readonly toolbarGroups = new BehaviorSubject([]); - private subscription = new Subscription(); - private ready = false; - private nextId = 1; - - constructor() { - this.groups = this.toolbarGroups.asObservable(); - } - - public groups: Observable; - - public setToolbar(toolbar?: Toolbar | null): void { - if (toolbar != null) { - const inputTextGroups = toolbar.groups.filter(g => g.items.some(item => item.type === ToolbarItemType.TextInput)); - if (inputTextGroups.length === 1) { - inputTextGroups[0].fill = true; - } - - this.ready = true; - this.toolbarGroups.next(toolbar.groups); - } - else { - this.toolbarGroups.next([]); - this.nextId = 1; - this.subscription.unsubscribe(); - this.subscription = new Subscription(); - this.ready = false; - } - } - - public createGroup(items: ToolbarItem[], visible?: boolean | Observable): ToolbarItemGroup { - const group: ToolbarItemGroup = { - id: this.createId(), - visible: typeof visible === 'boolean' ? visible : true, - fill: false, - items: items - } - - if (visible instanceof Observable) { - this.subscription.add(visible.subscribe( - value => this.ready ? this.updateGroupVisible(group.id, value) : group.visible = value)); - } - - return group; - } - - public createButton(classname: string, - execute: ToolbarCommandExecute, - canExecute?: ToolbarCommandCanExecute): ToolbarButton { - const item: ToolbarButton = { - id: this.createId(), - type: ToolbarItemType.Button, - style: ToolbarItemStyle.Icon, - classname: classname, - command: { execute: execute, canExecute: canExecute ?? (() => true) } - }; - - return item; - } - - public createRadio( - classname: string, - value: string | boolean | number, - checked: boolean | Observable, - execute: ToolbarCommandExecute, - canExecute?: ToolbarCommandCanExecute): ToolbarRadio { - const radio: ToolbarRadio = { - id: this.createId(), - type: ToolbarItemType.Radio, - style: ToolbarItemStyle.Icon, - classname: classname, - value: value, - checked: typeof checked === 'boolean' ? checked : false, - command: { - execute: execute, - canExecute: canExecute ?? (() => true) - } - }; - - if (checked instanceof Observable) { - this.subscription.add(checked.subscribe( - state => this.ready ? this.updateChecked(radio.id, state) : radio.checked = state)); - } - - return radio; - } - - public createCheckbox( - classname: string, - checked: boolean | Observable, - execute: ToolbarCommandExecute, - canExecute?: ToolbarCommandCanExecute): ToolbarCheckbox { - const checkbox: ToolbarCheckbox = { - id: this.createId(), - type: ToolbarItemType.Checkbox, - style: ToolbarItemStyle.Icon, - classname: classname, - checked: typeof checked === 'boolean' ? checked : false, - command: { - execute: execute, - canExecute: canExecute ?? (() => true) - } - }; - - if (checked instanceof Observable) { - this.subscription.add(checked.subscribe( - value => this.ready ? this.updateChecked(checkbox.id, value) : checkbox.checked = value)); - } - - return checkbox; - } - - public createSwitch( - name: string, - checked: boolean | Observable, - execute: ToolbarCommandExecute, - canExecute?: ToolbarCommandCanExecute): ToolbarSwitch { - const item: ToolbarSwitch = { - id: this.createId(), - type: ToolbarItemType.Switch, - style: ToolbarItemStyle.Icon, - name: name, - checked: typeof checked === 'boolean' ? checked : false, - command: { - execute: execute, - canExecute: canExecute ?? (() => true) - } - }; - - if (checked instanceof Observable) { - this.subscription.add(checked.subscribe( - value => this.ready ? this.updateChecked(item.id, value) : item.checked = value)); - } - - return item; - } - - public createTextInput(classname: string, value: string | Observable, placeholder: string, execute: ToolbarCommandExecute): ToolbarTextInput { - const textInput: ToolbarTextInput = { - id: this.createId(), - type: ToolbarItemType.TextInput, - style: ToolbarItemStyle.Icon, - classname: classname, - value: typeof value === 'string' ? value : '', - placeholder: placeholder, - command: { execute: execute, canExecute: () => true } - }; - - if (value instanceof Observable) { - this.subscription.add( - value.subscribe(text => this.ready ? this.updateInputValue(textInput.id, text) : textInput.value = text)); - } - - return textInput; - } - - public createFileInput(name: string, execute: ToolbarCommandExecute): ToolbarFileInput { - const fileInput: ToolbarFileInput = { - id: this.createId(), - type: ToolbarItemType.FileInput, - style: ToolbarItemStyle.Icon, - name: name, - command: { execute: execute, canExecute: () => true } - }; - - return fileInput; - } - - public createDivider(): ToolbarItem { - return ({ - id: this.createId(), - type: ToolbarItemType.Divider, - style: ToolbarItemStyle.Text, - } as ToolbarDivider) as ToolbarItem; - } - - public createColorPicker( - value: string | Observable, - execute: ToolbarCommandExecute, - canExecute?: ToolbarCommandCanExecute): ToolbarColorPicker { - const item: ToolbarColorPicker = { - id: this.createId(), - value: typeof value === 'string' ? value : '', - type: ToolbarItemType.ColorPicker, - style: ToolbarItemStyle.Icon, - command: { execute: execute, canExecute: canExecute ?? (() => true) } - }; - - if (value instanceof Observable) { - this.subscription.add(value.subscribe( - color => this.ready ? this.updateColorPickerValue(item.id, color) : item.value = color)); - } - - return item; - } - - public createDropDown( - classname: string, - items: ToolbarItem[] | Observable, - name?: string | Observable, - enabled?: boolean | Observable): ToolbarDropDown { - const dropDown: ToolbarDropDown = { - id: this.createId(), - type: ToolbarItemType.Dropdown, - style: name ? ToolbarItemStyle.IconText : ToolbarItemStyle.Icon, - classname: classname, - name: typeof name === 'string' ? name : '', - items: Array.isArray(items) ? items : [], - enabled: typeof enabled === 'boolean' ? enabled : true - }; - - if (name instanceof Observable) { - this.subscription.add(name.subscribe( - value => this.ready ? this.updateDropDownName(dropDown.id, value) : dropDown.name = value)); - } - - if (items instanceof Observable) { - this.subscription.add(items.subscribe( - value => this.ready ? this.updateDropDownItems(dropDown.id, value) : dropDown.items = value)); - } - - if (enabled instanceof Observable) { - this.subscription.add(enabled.subscribe( - value => this.ready ? this.updateDropDownEnabled(dropDown.id, value) : dropDown.enabled = value)); - } - - return dropDown; - } - - public createMenuItem( - name: string, - execute: ToolbarCommandExecute, - canExecute?: ToolbarCommandCanExecute): ToolbarMenuItem { - const menuItem: ToolbarMenuItem = { - id: this.createId(), - type: ToolbarItemType.MenuItem, - style: ToolbarItemStyle.Text, - name: name, - command: { - execute: execute, - canExecute: canExecute ?? (() => true) - } - }; - - return menuItem; - } - - public createSelect( - options: ToolbarOption[] | Observable, - value: Observable, - execute: ToolbarCommandExecute, - canExecute?: ToolbarCommandCanExecute): ToolbarSelect { - const select: ToolbarSelect = { - id: this.createId(), - value: null, - type: ToolbarItemType.Select, - style: ToolbarItemStyle.Text, - options: Array.isArray(options) ? options : [], - command: { execute: execute, canExecute: canExecute ?? (() => true) } - }; - - if (value instanceof Observable) { - this.subscription.add(value.subscribe(v => this.ready ? this.updateSelectValue(select.id, v) : select.value = v)) - } - - if (options instanceof Observable) { - this.subscription.add(options.subscribe( - items => this.ready ? this.updateSelectOptions(select.id, items) : select.options = items)); - } - - return select; - } - - public createOption(name: string, value: T): ToolbarOption { - const option: ToolbarOption = { - id: this.createId(), - name: name, - value: value - }; - - return option; - } - - private updateGroupVisible(id: string, visible: boolean): void { - const toolbarGroups = [...this.toolbarGroups.getValue()]; - const index = toolbarGroups.findIndex(item => item.id === id); - if (index >= 0) { - const group = toolbarGroups[index]; - toolbarGroups[index] = { ...group, visible }; - this.toolbarGroups.next(toolbarGroups); - } - } - - private updateChecked(id: string, checked: boolean): void { - const toolbarGroups = [...this.toolbarGroups.getValue()]; - for (let i = 0; i < toolbarGroups.length; i++) { - const group = toolbarGroups[i]; - const items = group.items; - for (let j = 0; j < items.length; j++) { - if (items[j].id === id) { - const checkedItem = items[j] as ToolbarCheckedItem; - toolbarGroups[i] = { ...group, items: [...items] }; - toolbarGroups[i].items[j] = { ...checkedItem, checked } as ToolbarCheckedItem; - this.toolbarGroups.next(toolbarGroups); - return; - } - } - } - } - - private updateColorPickerValue(id: string, value: string): void { - const toolbarGroups = [...this.toolbarGroups.getValue()]; - for (let i = 0; i < toolbarGroups.length; i++) { - const group = toolbarGroups[i]; - const items = group.items; - for (let j = 0; j < items.length; j++) { - if (items[j].id === id) { - const colorPicker = items[j] as ToolbarColorPicker; - toolbarGroups[i] = { ...group, items: [...items] }; - toolbarGroups[i].items[j] = { ...colorPicker, value } as ToolbarColorPicker; - this.toolbarGroups.next(toolbarGroups); - return; - } - } - } - } - - private updateDropDownName(id: string, name: string): void { - const toolbarGroups = [...this.toolbarGroups.getValue()]; - for (let i = 0; i < toolbarGroups.length; i++) { - const group = toolbarGroups[i]; - const items = group.items; - for (let j = 0; j < items.length; j++) { - if (items[j].id === id) { - const dropDown = items[j] as ToolbarDropDown; - toolbarGroups[i] = { ...group, items: [...items] }; - toolbarGroups[i].items[j] = { ...dropDown, name } as ToolbarDropDown; - this.toolbarGroups.next(toolbarGroups); - return; - } - } - } - } - - private updateDropDownItems(id: string, items: ToolbarItem[]): void { - const toolbarGroups = [...this.toolbarGroups.getValue()]; - for (let i = 0; i < toolbarGroups.length; i++) { - const group = toolbarGroups[i]; - const groupItems = group.items; - for (let j = 0; j < groupItems.length; j++) { - if (groupItems[j].id === id) { - const dropDown = groupItems[j] as ToolbarDropDown; - toolbarGroups[i] = { ...group, items: [...groupItems] }; - toolbarGroups[i].items[j] = { ...dropDown, items } as ToolbarDropDown; - this.toolbarGroups.next(toolbarGroups); - return; - } - } - } - } - - private updateDropDownEnabled(id: string, enabled: boolean): void { - const toolbarGroups = [...this.toolbarGroups.getValue()]; - for (let i = 0; i < toolbarGroups.length; i++) { - const group = toolbarGroups[i]; - const items = group.items; - for (let j = 0; j < items.length; j++) { - if (items[j].id === id) { - const dropDown = items[j] as ToolbarDropDown; - toolbarGroups[i] = { ...group, items: [...items] }; - toolbarGroups[i].items[j] = { ...dropDown, enabled } as ToolbarDropDown; - this.toolbarGroups.next(toolbarGroups); - return; - } - } - } - } - - private updateSelectOptions(id: string, options: ToolbarOption[]): void { - const toolbarGroups = [...this.toolbarGroups.getValue()]; - for (let i = 0; i < toolbarGroups.length; i++) { - const group = toolbarGroups[i]; - const items = group.items; - for (let j = 0; j < items.length; j++) { - if (items[j].id === id) { - const select = items[j] as ToolbarSelect; - toolbarGroups[i] = { ...group, items: [...items] }; - toolbarGroups[i].items[j] = { ...select, options } as ToolbarSelect; - this.toolbarGroups.next(toolbarGroups); - return; - } - } - } - } - - private updateSelectValue(id: string, value: any): void { - const toolbarGroups = [...this.toolbarGroups.getValue()]; - for (let i = 0; i < toolbarGroups.length; i++) { - const group = toolbarGroups[i]; - const items = group.items; - for (let j = 0; j < items.length; j++) { - if (items[j].id === id) { - const select = items[j] as ToolbarSelect; - toolbarGroups[i] = { ...group, items: [...items] }; - toolbarGroups[i].items[j] = { ...select, value } as ToolbarSelect; - this.toolbarGroups.next(toolbarGroups); - return; - } - } - } - } - - private updateInputValue(id: string, value: string): void { - const toolbarGroups = [...this.toolbarGroups.getValue()]; - for (let i = 0; i < toolbarGroups.length; i++) { - const group = toolbarGroups[i]; - const items = group.items; - for (let j = 0; j < items.length; j++) { - if (items[j].id === id) { - const textInput = items[j] as ToolbarTextInput; - toolbarGroups[i] = { ...group, items: [...items] }; - toolbarGroups[i].items[j] = { ...textInput, value } as ToolbarTextInput; - this.toolbarGroups.next(toolbarGroups); - return; - } - } - } - } - - private createId(): string { - return 'fhg-ti-' + this.nextId++; - } -} \ No newline at end of file diff --git a/projects/aas-lib/src/lib/toolbar/toolbar.ts b/projects/aas-lib/src/lib/toolbar/toolbar.ts deleted file mode 100644 index 4ce48eaa..00000000 --- a/projects/aas-lib/src/lib/toolbar/toolbar.ts +++ /dev/null @@ -1,128 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2023 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -import { Observable } from "rxjs"; - -export type ToolbarCommandExecute = (argument?: any) => void; -export type ToolbarCommandCanExecute = (argument?: any) => boolean; - -export interface ToolbarCommand { - execute(argument?: any): void; - canExecute(argument?: any): boolean; -} - -export enum ToolbarItemType { - Button = 'Button', - TextInput = 'TextInput', - FileInput = 'FileInput', - Radio = 'Radio', - Switch = 'Switch', - Dropdown = 'Dropdown', - MenuItem = 'MenuItem', - Divider = 'Divider', - ColorPicker = 'ColorPicker', - Select = 'Select', - Checkbox = "Checkbox" -} - -export enum ToolbarItemStyle { - Text = 'Text', - Icon = 'Icon', - IconText = 'IconText' -} - -export interface ToolbarObject { - id: string; -} - -export interface ToolbarItem extends ToolbarObject { - type: ToolbarItemType; - style: ToolbarItemStyle; - name?: string; - classname?: string; - enabled?: boolean; -} - -export interface ToolbarCommandItem extends ToolbarItem { - command: ToolbarCommand; -} - -export interface ToolbarButton extends ToolbarCommandItem { - type: ToolbarItemType.Button; -} - -export interface ToolbarColorPicker extends ToolbarCommandItem { - type: ToolbarItemType.ColorPicker; - value: string; -} - -export interface ToolbarCheckedItem extends ToolbarCommandItem { - checked: boolean; -} - -export interface ToolbarRadio extends ToolbarCheckedItem { - type: ToolbarItemType.Radio; - value: string | boolean | number; -} - -export interface ToolbarCheckbox extends ToolbarCheckedItem { - type: ToolbarItemType.Checkbox; -} - -export interface ToolbarSwitch extends ToolbarCheckedItem { - type: ToolbarItemType.Switch; -} - -export interface ToolbarTextInput extends ToolbarCommandItem { - type: ToolbarItemType.TextInput; - value: string | Observable; - placeholder: string; -} - -export interface ToolbarFileInput extends ToolbarCommandItem { - type: ToolbarItemType.FileInput; -} - -export interface ToolbarDivider extends ToolbarItem { - type: ToolbarItemType.Divider; -} - -export interface ToolbarMenuItem extends ToolbarCommandItem { - type: ToolbarItemType.MenuItem; -} - -export interface ToolbarDropDown extends ToolbarItem { - type: ToolbarItemType.Dropdown; - items: ToolbarItem[]; -} - -export interface ToolbarOption extends ToolbarObject { - value: any; - name: string; -} - -export interface ToolbarSelect extends ToolbarCommandItem { - type: ToolbarItemType.Select; - value: any; - options: ToolbarOption[]; -} - -export interface ToolbarItemGroup extends ToolbarObject { - name?: string; - visible: boolean; - fill: boolean; - items: ToolbarItem[]; -} - -export interface Toolbar { - groups: ToolbarItemGroup[]; -} - -export function isToolbarSelect(item: ToolbarItem): item is ToolbarSelect { - return item.type === ToolbarItemType.Select; -} diff --git a/projects/aas-lib/src/public-api.ts b/projects/aas-lib/src/public-api.ts index e797a381..5b4e7f34 100644 --- a/projects/aas-lib/src/public-api.ts +++ b/projects/aas-lib/src/public-api.ts @@ -20,10 +20,6 @@ export * from './lib/convert'; export * from './lib/notify/notify.component'; export * from './lib/notify/notify.service'; -export * from './lib/toolbar/toolbar.component'; -export * from './lib/toolbar/toolbar.service'; -export * from './lib/toolbar/toolbar'; - export * from './lib/localize/localize.component'; export * from './lib/localize/culture-info'; diff --git a/projects/aas-lib/src/test/aas-tree/aas-tree.component.spec.ts b/projects/aas-lib/src/test/aas-tree/aas-tree.component.spec.ts index 87197480..2a352719 100644 --- a/projects/aas-lib/src/test/aas-tree/aas-tree.component.spec.ts +++ b/projects/aas-lib/src/test/aas-tree/aas-tree.component.spec.ts @@ -49,7 +49,7 @@ describe('AASTreeComponent', () => { }, { provide: DownloadService, - useValue: jasmine.createSpyObj(['downloadFileAsync', 'downloadDocumentAsync', 'uploadDocuments']) + useValue: jasmine.createSpyObj(['downloadFileAsync', 'downloadDocument', 'uploadDocuments']) }, { provide: WindowService, diff --git a/projects/aas-lib/src/test/download.service.spec.ts b/projects/aas-lib/src/test/download.service.spec.ts index 310820ce..73232c8f 100644 --- a/projects/aas-lib/src/test/download.service.spec.ts +++ b/projects/aas-lib/src/test/download.service.spec.ts @@ -10,7 +10,7 @@ import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { EMPTY } from 'rxjs'; +import { EMPTY, first, of } from 'rxjs'; import { DownloadService } from '../lib/download.service'; @@ -59,15 +59,14 @@ describe('DownloadService', () => { }); }); - describe('downloadDocumentAsync', function () { - it('downloads an AASX package file', async function () { + describe('downloadDocument', function () { + it('downloads an AASX package file', function () { const spy = spyOn(httpClient, 'get').and.returnValue(EMPTY); - await expectAsync(service.downloadDocumentAsync( + service.downloadDocument( 'http://localhost:1234', 'https://iosb-ina.fraunhofer.de/ids/aas/5174_7001_0122_9237', 'Test.aasx' - )).toBeResolved(); - + ); expect(spy).toHaveBeenCalled(); }); }); diff --git a/projects/aas-lib/src/test/toolbar/toolbar.component.spec.ts b/projects/aas-lib/src/test/toolbar/toolbar.component.spec.ts deleted file mode 100644 index a4c6c0fb..00000000 --- a/projects/aas-lib/src/test/toolbar/toolbar.component.spec.ts +++ /dev/null @@ -1,404 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2023 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { BehaviorSubject, first } from 'rxjs'; -import { - Toolbar, - ToolbarButton, - ToolbarCheckbox, - ToolbarColorPicker, - ToolbarCommandCanExecute, - ToolbarCommandExecute, - ToolbarDropDown, - ToolbarFileInput, - ToolbarMenuItem, - ToolbarOption, - ToolbarRadio, - ToolbarSelect, - ToolbarSwitch, - ToolbarTextInput -} from '../../lib/toolbar/toolbar'; - -import { ToolbarService } from '../../lib/toolbar/toolbar.service'; -import { ToolbarComponent } from '../../lib/toolbar/toolbar.component'; - -describe('ToolbarComponent', () => { - let component: ToolbarComponent; - let service: ToolbarService; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ToolbarComponent], - providers: [], - imports: [ - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateFakeLoader - } - }) - - ] - }); - - fixture = TestBed.createComponent(ToolbarComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - - service = TestBed.inject(ToolbarService); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('toolbar button', function () { - let toolbar: Toolbar; - let button: ToolbarButton; - let execute: jasmine.Spy; - let canExecute: jasmine.Spy; - - beforeEach(function () { - execute = jasmine.createSpy('execute'); - canExecute = jasmine.createSpy('canExecute').and.returnValue(true); - button = service.createButton('bi bi-gear', execute, canExecute); - toolbar = { groups: [service.createGroup([button])] }; - service.setToolbar(toolbar); - }); - - it('should provide a button', function (done: DoneFn) { - service.groups.pipe(first()).subscribe(() => { - fixture.detectChanges(); - const toolbarElement: HTMLElement = fixture.debugElement.nativeElement; - const buttonElement: HTMLButtonElement = toolbarElement.querySelector('button')!; - buttonElement.dispatchEvent(new Event('click')); - expect(execute).toHaveBeenCalled(); - done(); - }); - }); - }); - - describe('radio button', function () { - let toolbar: Toolbar; - let radio1: ToolbarRadio; - let radio2: ToolbarRadio; - let checked1: BehaviorSubject; - let checked2: BehaviorSubject; - let execute1: ToolbarCommandExecute; - let execute2: ToolbarCommandExecute; - - beforeEach(function () { - checked1 = new BehaviorSubject(true); - checked2 = new BehaviorSubject(false); - - execute1 = () => { - const value = checked1.getValue(); - checked1.next(!value); - checked2.next(value); - }; - - execute2 = () => () => { - const value = checked2.getValue(); - checked2.next(!value); - checked1.next(value); - };; - - radio1 = service.createRadio('bi bi-1-circle', '1', checked1.asObservable(), execute1); - radio2 = service.createRadio('bi bi-2-circle', '2', checked2.asObservable(), execute2); - toolbar = { groups: [service.createGroup([radio1, radio2])] }; - service.setToolbar(toolbar); - }); - - it('should provide radio buttons', function (done: DoneFn) { - service.groups.pipe(first()).subscribe(() => { - fixture.detectChanges(); - const toolbarElement: HTMLElement = fixture.debugElement.nativeElement; - const radio1Element: HTMLInputElement = toolbarElement.querySelector('#' + radio1.id)!; - const radio2Element: HTMLInputElement = toolbarElement.querySelector('#' + radio2.id)!; - expect(radio1Element.checked).toBeTrue(); - expect(radio2Element.checked).toBeFalse(); - - radio2Element.checked = true; - checked2.asObservable().pipe(first()).subscribe(() => { - fixture.detectChanges(); - expect(radio1Element.checked).toBeFalse(); - expect(radio2Element.checked).toBeTrue(); - }); - - radio1Element.checked = true; - checked1.asObservable().pipe(first()).subscribe(() => { - fixture.detectChanges(); - expect(radio1Element.checked).toBeTrue(); - expect(radio2Element.checked).toBeFalse(); - done(); - }); - }); - }); - }); - - describe('checkbox', function () { - let toolbar: Toolbar; - let checkbox: ToolbarCheckbox; - let execute: ToolbarCommandExecute; - let canExecute: jasmine.Spy; - let checked: BehaviorSubject; - - beforeEach(function () { - checked = new BehaviorSubject(false); - - execute = () => { - checked.next(!checked.getValue()); - }; - - canExecute = jasmine.createSpy('canExecute').and.returnValue(true); - checkbox = service.createCheckbox('bi bi-check', checked.asObservable(), execute, canExecute); - toolbar = { groups: [service.createGroup([checkbox])] }; - service.setToolbar(toolbar); - }); - - it('should provide a checkbox', function (done: DoneFn) { - service.groups.pipe(first()).subscribe(() => { - fixture.detectChanges(); - const toolbarElement: HTMLElement = fixture.debugElement.nativeElement; - const checkboxElement: HTMLInputElement = toolbarElement.querySelector('#' + checkbox.id)!; - expect(checkboxElement.checked).toBeFalse(); - - checkboxElement.checked = true; - checked.asObservable().pipe(first()).subscribe(() => { - fixture.detectChanges(); - expect(checkboxElement.checked).toBeTrue(); - }); - - checkboxElement.checked = false; - checked.asObservable().pipe(first()).subscribe(() => { - fixture.detectChanges(); - expect(checkboxElement.checked).toBeFalse(); - done(); - }); - }); - }); - }); - - describe('switch', function () { - let toolbar: Toolbar; - let toolbarSwitch: ToolbarSwitch; - let execute: ToolbarCommandExecute; - let canExecute: jasmine.Spy; - let checked: BehaviorSubject; - - beforeEach(function () { - checked = new BehaviorSubject(false); - - execute = () => { - checked.next(!checked.getValue()); - }; - - canExecute = jasmine.createSpy('canExecute').and.returnValue(true); - toolbarSwitch = service.createSwitch('switch', checked.asObservable(), execute, canExecute); - toolbar = { groups: [service.createGroup([toolbarSwitch])] }; - service.setToolbar(toolbar); - }); - - it('should provide a switch', function (done: DoneFn) { - service.groups.pipe(first()).subscribe(() => { - fixture.detectChanges(); - const toolbarElement: HTMLElement = fixture.debugElement.nativeElement; - const switchElement: HTMLInputElement = toolbarElement.querySelector('#' + toolbarSwitch.id)!; - expect(switchElement.checked).toBeFalse(); - - switchElement.checked = true; - checked.asObservable().pipe(first()).subscribe(() => { - fixture.detectChanges(); - expect(switchElement.checked).toBeTrue(); - }); - - switchElement.checked = false; - checked.asObservable().pipe(first()).subscribe(() => { - fixture.detectChanges(); - expect(switchElement.checked).toBeFalse(); - done(); - }); - }); - }); - }); - - describe('dropdown menu', function () { - let toolbar: Toolbar; - let dropdown: ToolbarDropDown; - let menuItem: ToolbarMenuItem; - let execute: jasmine.Spy; - - beforeEach(function () { - execute = jasmine.createSpy('execute'); - menuItem = service.createMenuItem('command', execute); - dropdown = service.createDropDown('', [menuItem]); - - toolbar = { groups: [service.createGroup([dropdown])] }; - service.setToolbar(toolbar); - }); - - it('provides a dropdown menu', function (done: DoneFn) { - service.groups.pipe(first()).subscribe(() => { - fixture.detectChanges(); - const toolbarElement: HTMLElement = fixture.debugElement.nativeElement; - const dropdownElement: HTMLDivElement = toolbarElement.querySelector('#' + dropdown.id)!; - dropdownElement.dispatchEvent(new Event('click')); - fixture.detectChanges(); - const menuItemElement: HTMLButtonElement = toolbarElement.querySelector('#' + menuItem.id)! - menuItemElement.dispatchEvent(new Event('click')); - expect(execute).toHaveBeenCalled(); - done(); - }); - }); - }); - - describe('text input', function () { - let toolbar: Toolbar; - let textInput: ToolbarTextInput; - let execute: ToolbarCommandExecute; - let canExecute: jasmine.Spy; - let value: BehaviorSubject; - - beforeEach(function () { - value = new BehaviorSubject(''); - - execute = (argument: string) => { - value.next(argument); - }; - - canExecute = jasmine.createSpy('canExecute').and.returnValue(true); - textInput = service.createTextInput('bi bi-pen', '', 'text input', execute); - toolbar = { groups: [service.createGroup([textInput])] }; - service.setToolbar(toolbar); - }); - - it('should provide a text input', function (done: DoneFn) { - service.groups.pipe(first()).subscribe(() => { - fixture.detectChanges(); - const toolbarElement: HTMLElement = fixture.debugElement.nativeElement; - const inputElement: HTMLInputElement = toolbarElement.querySelector('#' + textInput.id)!; - expect(inputElement.value).toEqual(''); - - inputElement.value = 'Hello World!'; - inputElement.dispatchEvent(new Event('input')); - value.asObservable().pipe(first()).subscribe(() => { - fixture.detectChanges(); - expect(inputElement.value).toEqual('Hello World!'); - done(); - }); - }); - }); - }); - - describe('color picker', function () { - let toolbar: Toolbar; - let colorPicker: ToolbarColorPicker; - let execute: ToolbarCommandExecute; - let color: BehaviorSubject; - - beforeEach(function () { - color = new BehaviorSubject('#000000'); - - execute = (argument: string) => { - color.next(argument); - }; - - colorPicker = service.createColorPicker(color, execute); - toolbar = { groups: [service.createGroup([colorPicker])] }; - service.setToolbar(toolbar); - }); - - it('should provide a color picker', function (done: DoneFn) { - service.groups.pipe(first()).subscribe(() => { - fixture.detectChanges(); - const toolbarElement: HTMLElement = fixture.debugElement.nativeElement; - const colorElement: HTMLInputElement = toolbarElement.querySelector('#' + colorPicker.id)!; - - colorElement.value = '#123456'; - colorElement.dispatchEvent(new Event('change')); - color.asObservable().pipe(first()).subscribe(() => { - fixture.detectChanges(); - expect(colorElement.value).toEqual('#123456'); - done(); - }); - }); - }); - }); - - describe('file input', function () { - let toolbar: Toolbar; - let fileInput: ToolbarFileInput; - let execute: ToolbarCommandExecute; - let canExecute: jasmine.Spy; - - beforeEach(function () { - execute = (argument: FileList) => { - }; - - canExecute = jasmine.createSpy('canExecute').and.returnValue(true); - fileInput = service.createFileInput('select file', execute); - toolbar = { groups: [service.createGroup([fileInput])] }; - service.setToolbar(toolbar); - }); - - it('should provide a file select', function (done: DoneFn) { - service.groups.pipe(first()).subscribe(() => { - fixture.detectChanges(); - const toolbarElement: HTMLElement = fixture.debugElement.nativeElement; - const inputElement: HTMLInputElement = toolbarElement.querySelector('#' + fileInput.id)!; - const buttonElement: HTMLButtonElement = toolbarElement.querySelector('#' + fileInput.id + 'Addon')!; - expect(inputElement).toBeTruthy(); - expect(buttonElement).toBeTruthy(); - done(); - }); - }); - }); - - describe('select', function () { - let toolbar: Toolbar; - let select: ToolbarSelect; - let option1: ToolbarOption; - let option2: ToolbarOption; - let execute: ToolbarCommandExecute; - let value: BehaviorSubject; - - beforeEach(function () { - value = new BehaviorSubject('1'); - execute = (argument: string) => { - value.next(argument); - }; - - option1 = service.createOption('1', '1'); - option2 = service.createOption('2', '2'); - select = service.createSelect([option1, option2], value.asObservable(), execute); - toolbar = { groups: [service.createGroup([select])] }; - service.setToolbar(toolbar); - }); - - it('provides a select', function (done: DoneFn) { - service.groups.pipe(first()).subscribe(() => { - fixture.detectChanges(); - const toolbarElement: HTMLElement = fixture.debugElement.nativeElement; - const selectElement: HTMLSelectElement = toolbarElement.querySelector('#' + select.id)!; - expect(selectElement.value).toEqual(option1.value); - - selectElement.value = option2.value; - selectElement.dispatchEvent(new Event('change')); - }); - - value.asObservable().pipe(first()).subscribe(() => { - fixture.detectChanges(); - expect(value.getValue()).toEqual(option2.value); - done(); - }); - }); - }); -}); \ No newline at end of file diff --git a/projects/aas-lib/src/test/toolbar/toolbar.service.spec.ts b/projects/aas-lib/src/test/toolbar/toolbar.service.spec.ts deleted file mode 100644 index 63d73577..00000000 --- a/projects/aas-lib/src/test/toolbar/toolbar.service.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2023 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -import { TestBed } from '@angular/core/testing'; -import { first } from 'rxjs'; -import { Toolbar } from '../../lib/toolbar/toolbar'; -import { ToolbarService } from '../../lib/toolbar/toolbar.service'; - -describe('ToolbarService', () => { - let service: ToolbarService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(ToolbarService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); - - it('allows set and reset a specific toolbar', function (done: DoneFn) { - const toolbar: Toolbar = { - groups: [ - service.createGroup( - [ - service.createButton('bi bi-gear', () => {}, () => true), - ] - ) - ] - } - - service.setToolbar(toolbar); - service.groups.pipe(first()).subscribe(values => { - expect(values).toEqual(toolbar.groups); - }); - - service.setToolbar(); - service.groups.pipe(first()).subscribe(values => { - expect(values.length).toEqual(0); - done(); - }); - }); -}); \ No newline at end of file diff --git a/projects/aas-portal/src/app/aas/aas.component.html b/projects/aas-portal/src/app/aas/aas.component.html index 1078a109..4ce77348 100644 --- a/projects/aas-portal/src/app/aas/aas.component.html +++ b/projects/aas-portal/src/app/aas/aas.component.html @@ -10,7 +10,7 @@
- +
@@ -34,4 +34,68 @@
-
\ No newline at end of file +
+ + +
+ + +
+
+ + + +
+
+ +
+
+ +
+
+ + +
+
+ + + +
+
+
+ + + +
+
\ No newline at end of file diff --git a/projects/aas-portal/src/app/aas/aas.component.ts b/projects/aas-portal/src/app/aas/aas.component.ts index dadea7b6..208bbd87 100644 --- a/projects/aas-portal/src/app/aas/aas.component.ts +++ b/projects/aas-portal/src/app/aas/aas.component.ts @@ -7,7 +7,7 @@ *****************************************************************************/ import { head } from 'lodash-es'; -import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { BehaviorSubject, EMPTY, map, mergeMap, Observable, Subscription, first, from } from 'rxjs'; import * as lib from 'projects/aas-lib/src/public-api'; @@ -29,6 +29,7 @@ import { State } from './aas.state'; import { DashboardChartType } from '../dashboard/dashboard.state'; import { DashboardQuery } from '../types/dashboard-query-params'; import { getEndpointType } from '../configuration'; +import { ToolbarService } from '../toolbar.service'; import { AASDocument, equalDocument, @@ -48,8 +49,8 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { private readonly store: Store; private readonly $state = new BehaviorSubject('offline'); private readonly subscription = new Subscription(); + private _dashboardPage = ''; private templates: TemplateDescriptor[] = []; - private dashboardName = ''; private selectedElements: aas.Referable[] = []; constructor( @@ -63,18 +64,24 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { private readonly api: AASApiService, private readonly download: lib.DownloadService, private readonly commandHandler: CommandHandlerService, - private readonly toolbar: lib.ToolbarService, + private readonly toolbar: ToolbarService, private readonly auth: lib.AuthService, private readonly clipboard: lib.ClipboardService ) { this.store = store as Store; this.state = this.$state.asObservable(); this.search = this.store.select(AASSelectors.selectSearch); + + this.dashboardPages = this.dashboard.pages.pipe((map(pages => pages.map(page => page.name)))); + this.editable = this.store.select(AASSelectors.selectEditable); } @ViewChild('aasTree') public aasTree: lib.AASTree | null = null; + @ViewChild('aasToolbar', { read: TemplateRef }) + public aasToolbar!: TemplateRef; + public document: AASDocument | null = null; public readonly state: Observable; @@ -119,6 +126,18 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { return this.document?.readonly ?? true; } + public readonly dashboardPages: Observable; + + public get dashboardPage(): string { + return this._dashboardPage; + } + + public set dashboardPage(value: string) { + this.dashboard.setPageName(value); + } + + public readonly editable: Observable; + public ngOnInit(): void { const params = this.route.snapshot.queryParams as lib.AASQueryParams; let query: lib.AASQuery | undefined; @@ -134,8 +153,6 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { this.store.dispatch(AASActions.setSearch({ search: query.search })); } - this.toolbar.setToolbar(this.createToolbar()); - if (query) { let document: Observable | undefined; if (query.url) { @@ -176,7 +193,7 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { })); this.subscription.add(this.dashboard.name.subscribe(name => { - this.dashboardName = name; + this._dashboardPage = name; })); } @@ -184,10 +201,12 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { if (this.aasTree) { this.subscription.add(this.aasTree.selectedElements.subscribe(values => this.selectedElements = values)); } + + this.toolbar.set(this.aasToolbar); } public ngOnDestroy(): void { - this.toolbar.setToolbar(); + this.toolbar.clear(); this.subscription.unsubscribe(); } @@ -203,16 +222,21 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { public canAddToDashboard(): boolean { const selectedElements = this.selectedElements; - return this.dashboardName != null && this.document != null && + return this.dashboardPage != null && this.document != null && selectedElements.length > 0 && selectedElements.every(element => this.isNumberProperty(element) || this.isTimeSeries(element)); } - public async addToDashboard(chartType: DashboardChartType): Promise { + public async addToDashboard(chartType: string): Promise { if (this.document) { try { - this.dashboard.add(this.dashboardName, this.document, this.selectedElements, chartType); - this.clipboard.set('DashboardQuery', { page: this.dashboardName } as DashboardQuery); + this.dashboard.add( + this.dashboardPage, + this.document, + this.selectedElements, + chartType as DashboardChartType); + + this.clipboard.set('DashboardQuery', { page: this.dashboardPage } as DashboardQuery); return await this.router.navigateByUrl('/dashboard?format=DashboardQuery'); } catch (error) { this.notify.error(error); @@ -336,15 +360,23 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { return type === 'AasxDirectory' || type === 'AasxServer'; } - public async downloadDocument(): Promise { - try { - await this.download.downloadDocumentAsync( - this.document!.idShort + '.aasx', - this.document!.id, - this.document!.container); - } catch (error) { - this.notify.error(error); - } + public downloadDocument(): void { + this.download.downloadDocument( + this.document!.idShort + '.aasx', + this.document!.id, + this.document!.container).subscribe({ error: (error) => this.notify.error(error) }); + } + + public findNext(): void { + this.aasTree?.findNext() + } + + public findPrevious(): void { + this.aasTree?.findPrevious() + } + + public applySearch(text: string): void { + this.store.dispatch(AASActions.setSearch({ search: text })); } private versionToString(administration?: aas.AdministrativeInformation): string { @@ -380,73 +412,4 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { element.idShort === 'TimeSeriesHistory' && element.contentType === 'application/json'; } - - private createToolbar(): lib.Toolbar { - return { - groups: [ - this.toolbar.createGroup( - [ - this.toolbar.createButton( - 'bi bi-play-fill', - () => this.play(), - () => this.$state.getValue() === 'offline'), - this.toolbar.createButton( - 'bi bi-stop-fill', - () => this.stop(), - () => this.$state.getValue() === 'online') - ], - this.store.select(AASSelectors.selectOnlineReady)), - this.toolbar.createGroup( - [ - this.toolbar.createSelect( - this.dashboard.pages.pipe(map(pages => pages.map(page => this.toolbar.createOption( - page.name, - page.name)))), - this.dashboard.name, - (page) => this.dashboard.setPageName(page)), - this.toolbar.createButton( - 'bi bi-graph-up', - () => this.addToDashboard(DashboardChartType.Line), - () => this.canAddToDashboard()), - this.toolbar.createButton( - 'bi bi-bar-chart-line-fill', - () => this.addToDashboard(DashboardChartType.BarVertical), - () => this.canAddToDashboard()) - - ]), - this.toolbar.createGroup( - [ - this.toolbar.createButton('bi bi-arrow-repeat', () => this.synchronize(), () => this.canSynchronize()), - ], - this.store.select(AASSelectors.selectEditable)), - this.toolbar.createGroup( - [ - this.toolbar.createButton('bi bi-download', () => this.downloadDocument(), () => this.canDownloadDocument()) - ]), - this.toolbar.createGroup( - [ - this.toolbar.createButton('bi bi-arrow-90deg-left', () => this.undo(), () => this.canUndo()), - this.toolbar.createButton('bi bi-arrow-90deg-right', () => this.redo(), () => this.canRedo()), - ], - this.store.select(AASSelectors.selectEditable)), - this.toolbar.createGroup( - [ - this.toolbar.createButton('bi bi-plus-lg', () => this.newElement(), () => this.canNewElement()), - this.toolbar.createButton('bi bi-pencil', () => this.editElement(), () => this.canEditElement()), - this.toolbar.createButton('bi bi-trash', () => this.deleteElement(), () => this.canDeleteElement()), - ], - this.store.select(AASSelectors.selectEditable)), - this.toolbar.createGroup( - [ - this.toolbar.createTextInput( - 'bi bi-search', - this.search.pipe(first()), - 'PLACEHOLDER_SEARCH', - (text: string) => this.store.dispatch(AASActions.setSearch({ search: text }))), - this.toolbar.createButton('bi bi-chevron-down', () => this.aasTree?.findNext()), - this.toolbar.createButton('bi bi-chevron-up', () => this.aasTree?.findPrevious()), - ]) - ] - } - } } \ No newline at end of file diff --git a/projects/aas-portal/src/app/about/about.component.html b/projects/aas-portal/src/app/about/about.component.html index bb512379..6604e333 100644 --- a/projects/aas-portal/src/app/about/about.component.html +++ b/projects/aas-portal/src/app/about/about.component.html @@ -26,3 +26,5 @@

Third-party Libraries

Server Messages

+ + diff --git a/projects/aas-portal/src/app/about/about.component.ts b/projects/aas-portal/src/app/about/about.component.ts index b2f3dc16..c7d7724d 100644 --- a/projects/aas-portal/src/app/about/about.component.ts +++ b/projects/aas-portal/src/app/about/about.component.ts @@ -6,24 +6,31 @@ * *****************************************************************************/ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit, TemplateRef, ViewChild, AfterViewInit } from '@angular/core'; import { Library, Message } from 'common'; import { ServerApiService } from './server-api.service'; import pkg from '../../../../../package.json'; import { TranslateService } from '@ngx-translate/core'; +import { ToolbarService } from '../toolbar.service'; @Component({ - selector: 'about', + selector: 'fhg-about', templateUrl: './about.component.html', styleUrls: ['./about.component.scss'] }) -export class AboutComponent implements OnInit { - constructor(private serverApi: ServerApiService, private translate: TranslateService) { +export class AboutComponent implements OnInit, OnDestroy, AfterViewInit { + constructor( + private serverApi: ServerApiService, + private translate: TranslateService, + private toolbar: ToolbarService) { this.author = pkg.author; this.version = pkg.version; this.homepage = pkg.homepage; } + @ViewChild('aasToolbar', { read: TemplateRef }) + public aboutToolbar!: TemplateRef; + public version = ''; public serverVersion = '' @@ -45,4 +52,12 @@ export class AboutComponent implements OnInit { this.serverApi.getMessages().subscribe(messages => this.messages = messages); } + + public ngAfterViewInit(): void { + this.toolbar.set(this.aboutToolbar); + } + + public ngOnDestroy(): void { + this.toolbar.clear(); + } } \ No newline at end of file diff --git a/projects/aas-portal/src/app/app.module.ts b/projects/aas-portal/src/app/app.module.ts index fb09b443..fb050ba1 100644 --- a/projects/aas-portal/src/app/app.module.ts +++ b/projects/aas-portal/src/app/app.module.ts @@ -8,8 +8,9 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { StoreModule } from '@ngrx/store'; -import { NgbModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap' +import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { HttpClientModule, HttpClient } from '@angular/common/http'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { AASLibModule } from 'projects/aas-lib/src/public-api'; @@ -55,9 +56,11 @@ import { httpInterceptorProviders } from './index'; ], imports: [ BrowserModule, + BrowserAnimationsModule, AppRoutingModule, HttpClientModule, FormsModule, + NgbModule, StoreModule.forRoot( { start: startReducer, @@ -67,8 +70,6 @@ import { httpInterceptorProviders } from './index'; project: projectReducer }), EffectsModule.forRoot([ProjectEffects]), - NgbModule, - NgbTypeaheadModule, TranslateModule.forRoot({ defaultLanguage: 'en-us', loader: { diff --git a/projects/aas-portal/src/app/dashboard/dashboard.actions.ts b/projects/aas-portal/src/app/dashboard/dashboard.actions.ts index 8a570ef3..b6fe7d33 100644 --- a/projects/aas-portal/src/app/dashboard/dashboard.actions.ts +++ b/projects/aas-portal/src/app/dashboard/dashboard.actions.ts @@ -11,7 +11,7 @@ import { DashboardPage, DashboardRow, DashboardState } from "./dashboard.state"; export enum DashboardActionType { UPDATE_ROWS = '[Dashboard] update rows', - TOGGLE_EDIT_MODE = '[Dashboard] toggle edit mode', + SET_EDIT_MODE = '[Dashboard] toggle edit mode', SET_PAGE_NAME = '[Dashboard] set page name', SET_PAGES = '[Dashboard] set pages', ADD_NEW_PAGE = '[Dashboard] add new page', @@ -25,8 +25,9 @@ export const updateRows = createAction( DashboardActionType.UPDATE_ROWS, props<{ rows: DashboardRow[] }>()); -export const toggleEditMode = createAction( - DashboardActionType.TOGGLE_EDIT_MODE); +export const setEditMode = createAction( + DashboardActionType.SET_EDIT_MODE, + props<{ editMode: boolean }>()); export const setPageName = createAction( DashboardActionType.SET_PAGE_NAME, diff --git a/projects/aas-portal/src/app/dashboard/dashboard.component.html b/projects/aas-portal/src/app/dashboard/dashboard.component.html index 2f404956..c8e206f6 100644 --- a/projects/aas-portal/src/app/dashboard/dashboard.component.html +++ b/projects/aas-portal/src/app/dashboard/dashboard.component.html @@ -47,14 +47,16 @@
- +
- +
@@ -64,4 +66,49 @@
- \ No newline at end of file + + + +
+ + + + +
+
+
+ + +
+
+
+ + +
+
+ + + + +
+
\ No newline at end of file diff --git a/projects/aas-portal/src/app/dashboard/dashboard.component.ts b/projects/aas-portal/src/app/dashboard/dashboard.component.ts index 39fc55c1..292b01a3 100644 --- a/projects/aas-portal/src/app/dashboard/dashboard.component.ts +++ b/projects/aas-portal/src/app/dashboard/dashboard.component.ts @@ -7,13 +7,13 @@ *****************************************************************************/ import "chart.js/auto"; -import { AfterViewChecked, Component, ElementRef, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { AfterViewInit, AfterViewChecked, Component, ElementRef, OnDestroy, OnInit, QueryList, TemplateRef, ViewChild, ViewChildren } from '@angular/core'; import { isNumber } from 'lodash-es'; import { Chart, ChartConfiguration, ChartDataset, ChartType } from 'chart.js'; import { aas, convertToString, LiveNode, LiveRequest, parseNumber, WebSocketData } from 'common'; import * as lib from 'projects/aas-lib/src/public-api'; import { WebSocketSubject } from 'rxjs/webSocket'; -import { map, Subscription } from "rxjs"; +import { Observable, Subscription } from "rxjs"; import { ActivatedRoute } from "@angular/router"; import { Store } from "@ngrx/store"; @@ -31,10 +31,12 @@ import { RenamePageCommand } from "./commands/rename-page-command"; import { AddNewPageCommand } from "./commands/add-new-page-command"; import { DeleteItemCommand } from "./commands/delete-item-command"; import { SetChartTypeCommand } from "./commands/set-chart-type-command"; -import { selectEditMode, selectName, selectPage, selectPages, selectRows, selectSelectionMode } from "./dashboard.selectors"; +import { selectEditMode, selectPage, selectPages, selectRows, selectSelectionMode } from "./dashboard.selectors"; import { SetMinMaxCommand } from "./commands/set-min-max-command"; import { DashboardQuery, DashboardQueryParams } from "../types/dashboard-query-params"; import * as DashboardActions from './dashboard.actions'; +import { DashboardApiService } from "./dashboard-api.service"; +import { ToolbarService } from '../toolbar.service'; import { DashboardItem, DashboardItemType, @@ -45,7 +47,6 @@ import { DashboardColumn, State } from './dashboard.state'; -import { DashboardApiService } from "./dashboard-api.service"; interface UpdateTuple { item: DashboardChart; @@ -67,7 +68,7 @@ interface TimeSeries { templateUrl: './dashboard.component.html', styleUrls: ['./dashboard.component.scss'] }) -export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { +export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit, AfterViewChecked { private readonly store: Store; private readonly map = new Map(); private readonly charts = new Map(); @@ -76,6 +77,8 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { private reset = false; private selections = new Set(); private selectedSources = new Map(); + private _page: DashboardPage; + private _editMode = false; constructor( store: Store, @@ -86,13 +89,14 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { private readonly webServiceFactory: lib.WebSocketFactoryService, private readonly dashboard: DashboardService, private readonly notify: lib.NotifyService, - private readonly toolbar: lib.ToolbarService, + private readonly toolbar: ToolbarService, private readonly commandHandler: CommandHandlerService, private readonly window: lib.WindowService, private readonly clipboard: lib.ClipboardService ) { this.store = store as Store; - this.page = this.dashboard.defaultPage ?? { name: '-', items: [], requests: [] }; + this._page = this.dashboard.defaultPage ?? { name: '-', items: [], requests: [] }; + this.pages = this.store.select(selectPages); this.subscription.add(this.store.select(selectPage).pipe() .subscribe(value => this.updatePage(value))); @@ -110,9 +114,26 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { @ViewChildren('chart') public chartContainers: QueryList> | null = null; - public page: DashboardPage; + @ViewChild('dashboardToolbar', { read: TemplateRef }) + public dashboardToolbar!: TemplateRef; + + public get page(): DashboardPage { + return this._page; + } + + public set page(value: DashboardPage) { + this.store.dispatch(DashboardActions.setPageName({ name: value.name })); + } - public editMode = false; + public pages: Observable; + + public get editMode(): boolean { + return this._editMode; + } + + public set editMode(value: boolean) { + this.store.dispatch(DashboardActions.setEditMode({ editMode: value })); + } public rows: DashboardRow[] = [] @@ -139,7 +160,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { public selectionMode: SelectionMode = SelectionMode.Single; public ngOnInit(): void { - this.toolbar.setToolbar(this.createToolbar()); this.commandHandler.clear(); let query: DashboardQuery | undefined; @@ -150,25 +170,32 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { const name = query ? query.page : this.auth.getCookie('.DashboardName'); if (name) { - this.setPage(name); + const page = this.dashboard.find(name); + if (page) { + this._page = page; + } } } public ngOnDestroy(): void { - if (this.page) { - this.auth.setCookie('.DashboardName', this.page.name); + if (this._page) { + this.auth.setCookie('.DashboardName', this._page.name); } else if (this.auth.checkCookie('.DashboardName')) { this.auth.deleteCookie('.DashboardName'); } this.dashboard.save(); - this.toolbar.setToolbar(); + this.toolbar.clear(); this.closeWebSocket(); this.charts.forEach(item => item.chart.destroy()); this.subscription.unsubscribe(); } + public ngAfterViewInit(): void { + this.toolbar.set(this.dashboardToolbar); + } + public ngAfterViewChecked(): void { if (this.reset) { try { @@ -238,7 +265,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { try { const name = this.window.prompt(this.translate.instant('PROMPT_DASHBOARD_NAME')); if (name) { - this.commandHandler.execute(new RenamePageCommand(this.store, this.dashboard, this.page, name)); + this.commandHandler.execute(new RenamePageCommand(this.store, this.dashboard, this._page, name)); } } catch (error) { this.notify.error(error); @@ -249,14 +276,14 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { try { if (this.selectedItems.length > 0) { this.commandHandler.execute( - new DeleteItemCommand(this.store, this.dashboard, this.page, this.selectedItems)); + new DeleteItemCommand(this.store, this.dashboard, this._page, this.selectedItems)); this.selectedItems.forEach(item => { this.selections.delete(item.id); this.selectedSources.delete(item.id); }); } else { - this.commandHandler.execute(new DeletePageCommand(this.store, this.dashboard, this.page)); + this.commandHandler.execute(new DeletePageCommand(this.store, this.dashboard, this._page)); this.selections.clear(); this.selectedSources.clear(); } @@ -265,18 +292,14 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { } } - public toggleEditMode(): void { - this.store.dispatch(DashboardActions.toggleEditMode()); - } - public canMoveLeft(): boolean { const selectedItem = this.selectedItem; - return this.editMode && selectedItem != null && this.dashboard.canMoveLeft(this.page, selectedItem); + return this.editMode && selectedItem != null && this.dashboard.canMoveLeft(this._page, selectedItem); } public moveLeft(): void { try { - this.commandHandler.execute(new MoveLeftCommand(this.store, this.dashboard, this.page, this.selectedItem!)); + this.commandHandler.execute(new MoveLeftCommand(this.store, this.dashboard, this._page, this.selectedItem!)); } catch (error) { this.notify.error(error); } @@ -284,12 +307,12 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { public canMoveRight(): boolean { const selectedItem = this.selectedItem; - return this.editMode && selectedItem != null && this.dashboard.canMoveRight(this.page, selectedItem); + return this.editMode && selectedItem != null && this.dashboard.canMoveRight(this._page, selectedItem); } public moveRight(): void { try { - this.commandHandler.execute(new MoveRightCommand(this.store, this.dashboard, this.page, this.selectedItem!)); + this.commandHandler.execute(new MoveRightCommand(this.store, this.dashboard, this._page, this.selectedItem!)); } catch (error) { this.notify.error(error); } @@ -297,12 +320,12 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { public canMoveUp(): boolean { const selectedItem = this.selectedItem; - return this.editMode && selectedItem != null && this.dashboard.canMoveUp(this.page, selectedItem); + return this.editMode && selectedItem != null && this.dashboard.canMoveUp(this._page, selectedItem); } public moveUp(): void { try { - this.commandHandler.execute(new MoveUpCommand(this.store, this.dashboard, this.page, this.selectedItem!)); + this.commandHandler.execute(new MoveUpCommand(this.store, this.dashboard, this._page, this.selectedItem!)); } catch (error) { this.notify.error(error); } @@ -310,12 +333,12 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { public canMoveDown(): boolean { const selectedItem = this.selectedItem; - return this.editMode && selectedItem != null && this.dashboard.canMoveDown(this.page, selectedItem); + return this.editMode && selectedItem != null && this.dashboard.canMoveDown(this._page, selectedItem); } public moveDown(): void { try { - this.commandHandler.execute(new MoveDownCommand(this.store, this.dashboard, this.page, this.selectedItem!)); + this.commandHandler.execute(new MoveDownCommand(this.store, this.dashboard, this._page, this.selectedItem!)); } catch (error) { this.notify.error(error); } @@ -344,7 +367,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { this.commandHandler.execute(new SetColorCommand( this.store, this.dashboard, - this.page, + this._page, column.item, this.selectedSources.get(column.id) ?? 0, color)); @@ -358,7 +381,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { this.commandHandler.execute(new SetChartTypeCommand( this.store, this.dashboard, - this.page, + this._page, column.item, value as DashboardChartType)); } catch (error) { @@ -382,7 +405,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { this.commandHandler.execute(new SetMinMaxCommand( this.store, this.dashboard, - this.page, + this._page, column.item as DashboardChart, Number(value), undefined)); @@ -407,7 +430,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { this.commandHandler.execute(new SetMinMaxCommand( this.store, this.dashboard, - this.page, + this._page, column.item as DashboardChart, undefined, Number(value))); @@ -437,7 +460,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { } private findItem(id: string): DashboardItem | undefined { - return this.page.items.find(item => item.id === id); + return this._page.items.find(item => item.id === id); } private updatePage(page?: DashboardPage): void { @@ -454,20 +477,14 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { this.selectedSources.clear(); if (page) { - this.page = page; - this.store.dispatch(DashboardActions.updateRows({ rows: this.dashboard.getRows(this.page) })); - } - } - - private setPage(name: string): void { - if (this.page.name !== name && this.dashboard.find(name)) { - this.store.dispatch(DashboardActions.setPageName({ name })); + this._page = page; + this.store.dispatch(DashboardActions.updateRows({ rows: this.dashboard.getRows(this._page) })); } } private updateEditMode(value: boolean): void { - this.editMode = value; - if (this.editMode) { + this._editMode = value; + if (this._editMode) { this.closeWebSocket(); this.map.clear(); this.charts.forEach(item => item.chart.destroy()); @@ -477,7 +494,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { } private openWebSocket(): void { - if (this.page && this.page.requests && this.page.requests.length > 0) { + if (this._page && this._page.requests && this._page.requests.length > 0) { this.webSocketSubject = this.webServiceFactory.create(); this.webSocketSubject.subscribe({ next: this.socketOnMessage, @@ -495,7 +512,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { private createCharts(query: QueryList>): void { this.charts.clear(); - this.page.items.forEach(item => { + this._page.items.forEach(item => { if (this.isChart(item)) { const canvas = query.find(element => element.nativeElement.id === item.id); if (canvas) { @@ -507,7 +524,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { private play(): void { if (this.webSocketSubject) { - for (const request of this.page.requests) { + for (const request of this._page.requests) { this.webSocketSubject.next(this.createMessage(request)); } } @@ -821,45 +838,45 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { return value?.type === DashboardItemType.Chart; } - private createToolbar(): lib.Toolbar { - const $editMode = this.store.select(selectEditMode); - return { - groups: [ - this.toolbar.createGroup( - [ - this.toolbar.createSelect( - this.store.select(selectPages) - .pipe(map(pages => pages.map(page => this.toolbar.createOption( - page.name, - page.name)))), - this.store.select(selectName), - (name) => this.setPage(name)), - this.toolbar.createButton('bi bi-plus', () => this.addNew(), () => this.editMode), - this.toolbar.createButton('bi bi-pen', () => this.rename(), () => this.editMode), - this.toolbar.createButton('bi bi-trash', () => this.delete(), () => this.editMode) - ]), - this.toolbar.createGroup( - [ - this.toolbar.createSwitch( - 'Edit mode', - $editMode, - () => this.toggleEditMode()) - ]), - this.toolbar.createGroup( - [ - this.toolbar.createButton('bi bi-arrow-90deg-left', () => this.undo(), () => this.canUndo()), - this.toolbar.createButton('bi bi-arrow-90deg-right', () => this.redo(), () => this.canRedo()), - ], - $editMode), - this.toolbar.createGroup( - [ - this.toolbar.createButton('bi bi-box-arrow-left', () => this.moveLeft(), () => this.canMoveLeft()), - this.toolbar.createButton('bi bi-box-arrow-up', () => this.moveUp(), () => this.canMoveUp()), - this.toolbar.createButton('bi bi-box-arrow-down', () => this.moveDown(), () => this.canMoveDown()), - this.toolbar.createButton('bi bi-box-arrow-right', () => this.moveRight(), () => this.canMoveRight()) - ], - $editMode), - ] - }; - } + // private createToolbar(): lib.Toolbar { + // const $editMode = this.store.select(selectEditMode); + // return { + // groups: [ + // this.toolbar.createGroup( + // [ + // this.toolbar.createSelect( + // this.store.select(selectPages) + // .pipe(map(pages => pages.map(page => this.toolbar.createOption( + // page.name, + // page.name)))), + // this.store.select(selectName), + // (name) => this.setPage(name)), + // this.toolbar.createButton('bi bi-plus', () => this.addNew(), () => this.editMode), + // this.toolbar.createButton('bi bi-pen', () => this.rename(), () => this.editMode), + // this.toolbar.createButton('bi bi-trash', () => this.delete(), () => this.editMode) + // ]), + // this.toolbar.createGroup( + // [ + // this.toolbar.createSwitch( + // 'Edit mode', + // $editMode, + // () => this.toggleEditMode()) + // ]), + // this.toolbar.createGroup( + // [ + // this.toolbar.createButton('bi bi-arrow-90deg-left', () => this.undo(), () => this.canUndo()), + // this.toolbar.createButton('bi bi-arrow-90deg-right', () => this.redo(), () => this.canRedo()), + // ], + // $editMode), + // this.toolbar.createGroup( + // [ + // this.toolbar.createButton('bi bi-box-arrow-left', () => this.moveLeft(), () => this.canMoveLeft()), + // this.toolbar.createButton('bi bi-box-arrow-up', () => this.moveUp(), () => this.canMoveUp()), + // this.toolbar.createButton('bi bi-box-arrow-down', () => this.moveDown(), () => this.canMoveDown()), + // this.toolbar.createButton('bi bi-box-arrow-right', () => this.moveRight(), () => this.canMoveRight()) + // ], + // $editMode), + // ] + // }; + // } } diff --git a/projects/aas-portal/src/app/dashboard/dashboard.reducer.ts b/projects/aas-portal/src/app/dashboard/dashboard.reducer.ts index 2a352de4..2f373d53 100644 --- a/projects/aas-portal/src/app/dashboard/dashboard.reducer.ts +++ b/projects/aas-portal/src/app/dashboard/dashboard.reducer.ts @@ -57,8 +57,8 @@ export const dashboardReducer = createReducer(initialState, (_, { state }) => setState(state) ), on( - DashboardActions.toggleEditMode, - (state) => toggleEditMode(state) + DashboardActions.setEditMode, + (state, { editMode }) => setEditMode(state, editMode) ), on( DashboardActions.updatePage, @@ -73,8 +73,8 @@ function updateRows(state: DashboardState, rows: DashboardRow[]): DashboardState return { ...state, rows }; } -function toggleEditMode(state: DashboardState): DashboardState { - return { ...state, editMode: !state.editMode }; +function setEditMode(state: DashboardState, editMode: boolean): DashboardState { + return { ...state, editMode: editMode }; } function setPageName(state: DashboardState, name: string): DashboardState { diff --git a/projects/aas-portal/src/app/main/main.component.html b/projects/aas-portal/src/app/main/main.component.html index 4fc7a926..c9d7fca9 100644 --- a/projects/aas-portal/src/app/main/main.component.html +++ b/projects/aas-portal/src/app/main/main.component.html @@ -30,8 +30,12 @@
- +
- \ No newline at end of file + + + \ No newline at end of file diff --git a/projects/aas-portal/src/app/main/main.component.ts b/projects/aas-portal/src/app/main/main.component.ts index 4abf03e6..0d4a900c 100644 --- a/projects/aas-portal/src/app/main/main.component.ts +++ b/projects/aas-portal/src/app/main/main.component.ts @@ -6,11 +6,12 @@ * *****************************************************************************/ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { Router } from '@angular/router'; -import { Subscription, first } from 'rxjs'; +import { Observable, Subscription, first, map } from 'rxjs'; import { ClipboardService, WindowService, AASQuery } from 'projects/aas-lib/src/public-api'; import { ProjectService } from '../project/project.service'; +import { ToolbarService } from '../toolbar.service'; export enum LinkId { START = 0, @@ -55,9 +56,18 @@ export class MainComponent implements OnInit, OnDestroy { private readonly router: Router, private readonly window: WindowService, private readonly project: ProjectService, - private readonly clipboard: ClipboardService) { + private readonly clipboard: ClipboardService, + private readonly viewContainer: ViewContainerRef, + private readonly toolbar: ToolbarService) { + + this.toolbarTemplate = this.toolbar.toolbarTemplate.pipe(map(value => this.nextToolbar(value))) } + @ViewChild('emptyToolbar', { read: TemplateRef }) + public emptyToolbar!: TemplateRef; + + public toolbarTemplate: Observable | null>; + public activeId = LinkId.START; public get links(): LinkDescriptor[] { @@ -74,7 +84,8 @@ export class MainComponent implements OnInit, OnDestroy { this.router.navigateByUrl('/aas?format=AASQuery'); } else { this.router.navigateByUrl('/start'); - }}); + } + }); } else { this.router.navigateByUrl('/start'); } @@ -83,4 +94,8 @@ export class MainComponent implements OnInit, OnDestroy { public ngOnDestroy(): void { this.subscription.unsubscribe(); } + + private nextToolbar(value: TemplateRef | null): TemplateRef { + return value ?? this.emptyToolbar; + } } \ No newline at end of file diff --git a/projects/aas-portal/src/app/start/start.component.html b/projects/aas-portal/src/app/start/start.component.html index 016b0ccb..78c07c7d 100644 --- a/projects/aas-portal/src/app/start/start.component.html +++ b/projects/aas-portal/src/app/start/start.component.html @@ -7,5 +7,75 @@ !---------------------------------------------------------------------------->
- -
\ No newline at end of file + + + + + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+ + + + +
+
+
+ + +
+
+
+
+ +
+
\ No newline at end of file diff --git a/projects/aas-portal/src/app/start/start.component.ts b/projects/aas-portal/src/app/start/start.component.ts index 1405c803..5aa64296 100644 --- a/projects/aas-portal/src/app/start/start.component.ts +++ b/projects/aas-portal/src/app/start/start.component.ts @@ -6,7 +6,7 @@ * *****************************************************************************/ -import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { Router } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Store } from '@ngrx/store'; @@ -22,7 +22,8 @@ import * as StartActions from './start.actions'; import { State } from './start.state'; import { UploadFormComponent } from './upload-form/upload-form.component'; import { getEndpointType } from '../configuration'; -import { selectFilter, selectIsViewModeList, selectIsViewModeTree, selectShowAll, selectViewMode } from './start.selectors'; +import { selectFilter, selectShowAll, selectViewMode } from './start.selectors'; +import { ToolbarService } from '../toolbar.service'; @Component({ selector: 'fhg-start', @@ -43,7 +44,7 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { private readonly window: lib.WindowService, private readonly project: ProjectService, private readonly notify: lib.NotifyService, - private readonly toolbar: lib.ToolbarService, + private readonly toolbar: ToolbarService, private readonly auth: lib.AuthService, private readonly download: lib.DownloadService, private readonly clipboard: lib.ClipboardService @@ -56,6 +57,9 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { @ViewChild('aasTable') public aasTable: lib.AASTable | null = null; + @ViewChild('startToolbar', { read: TemplateRef }) + public startToolbar!: TemplateRef; + public viewMode: lib.ViewMode = lib.ViewMode.List; public showAll = false; @@ -70,9 +74,11 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { public allAvailable = true; - public ngOnInit(): void { - this.toolbar.setToolbar(this.createToolbar()); + public readonly endpoint = this.project.workspace.pipe(map(item => item?.name ?? '-')); + public readonly endpoints = this.project.workspaces; + + public ngOnInit(): void { this.subscription.add( this.store .select(selectShowAll).pipe() @@ -105,14 +111,7 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { this.project.documents.subscribe( (documents) => (this.allAvailable = documents.every((item) => item.content))) ); - } - public ngOnDestroy(): void { - this.toolbar.setToolbar(); - this.subscription.unsubscribe(); - } - - public ngAfterViewInit(): void { this.subscription.add(this.aasTable?.selectedDocuments.subscribe( selectedDocuments => { this.selectedDocuments = selectedDocuments; @@ -120,6 +119,15 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { })); } + public ngAfterViewInit(): void { + this.toolbar.set(this.startToolbar); + } + + public ngOnDestroy(): void { + this.toolbar.clear(); + this.subscription.unsubscribe(); + } + public setViewMode(viewMode: string | lib.ViewMode): void { this.store.dispatch(StartActions.setViewMode({ viewMode: viewMode as lib.ViewMode })); } @@ -128,7 +136,7 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { this.store.dispatch(StartActions.setShowAll({ showAll })); } - public async addEndpoint(): Promise { + public addEndpoint(): void { this.auth.ensureAuthorized('editor').pipe( map(() => this.modal.open(AddEndpointFormComponent, { backdrop: 'static' })), mergeMap((modalRef) => { @@ -208,18 +216,14 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { return this.aasTable && this.selectedDocuments.length > 0 ? true : false; } - public async downloadDocument(): Promise { - if (this.aasTable) { - for (const document of this.selectedDocuments) { - try { - await this.download.downloadDocumentAsync( - document.idShort + '.aasx', - document.id, - document.container); - } catch (error) { - this.notify.error(error); - } - } + public downloadDocument(): void { + if (!this.aasTable) return; + + for (const document of this.selectedDocuments) { + this.download.downloadDocument( + document.idShort + '.aasx', + document.id, + document.container).subscribe({ error: (error) => this.notify.error(error) }); } } @@ -295,6 +299,10 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { } } + public applyFilter(filter: string): void { + this.store.dispatch(StartActions.setFilter({ filter })); + } + private selectSubmodels(document: AASDocument, semanticId: string): aas.Submodel[] { return document.content?.submodels.filter(submodel => lib.resolveSemanticId(submodel) === semanticId) ?? []; } @@ -305,75 +313,4 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { return type === 'AasxDirectory' || type === 'AasxServer'; }) ?? []; } - - private createToolbar(): lib.Toolbar { - return { - groups: [ - this.toolbar.createGroup( - [ - this.toolbar.createDropDown( - 'bi bi-stack-overflow', - this.createMenu(), - this.project.workspace.pipe(map(item => item?.name ?? '-')) - ), - this.toolbar.createDropDown( - 'bi bi-gear', - [ - this.toolbar.createMenuItem('CMD_ADD_ENDPOINT', () => this.addEndpoint()), - this.toolbar.createMenuItem('CMD_REMOVE_ENDPOINT', () => this.removeEndpoint()), - this.toolbar.createMenuItem('CMD_RESET_CONFIGURATION', () => this.reset()), - ]) - ]), - this.toolbar.createGroup( - [ - this.toolbar.createButton('bi bi-upload', () => this.uploadDocument(), () => this.canUploadDocument()), - this.toolbar.createButton('bi bi-download', () => this.downloadDocument(), () => this.canDownloadDocument()), - this.toolbar.createButton('bi bi-trash', () => this.deleteDocument(), () => this.canDeleteDocument()) - ] - ), - this.toolbar.createGroup( - [ - this.toolbar.createRadio( - 'bi bi-list', - 'list', - this.store.select(selectIsViewModeList), - () => this.setViewMode('list')), - this.toolbar.createRadio( - 'bi bi-diagram-3', - 'tree', - this.store.select(selectIsViewModeTree), - () => this.setViewMode('tree')) - ]), - this.toolbar.createGroup( - [ - this.toolbar.createDropDown( - '', - [ - this.toolbar.createMenuItem( - 'CMD_VIEW_USER_FEEDBACK', - () => this.viewUserFeedback(), - () => this.canViewUserFeedback()), - this.toolbar.createMenuItem( - 'CMD_VIEW_NAMEPLATE', - () => this.viewNameplate(), - () => this.canViewNameplate()) - ], - 'LABEL_VIEWS', - this.someSelectedDocuments.asObservable()) - ]), - this.toolbar.createGroup( - [ - this.toolbar.createTextInput( - 'bi bi-filter', - this.store.select(selectFilter).pipe(first()), - 'PLACEHOLDER_FILTER', - (filter: string) => this.store.dispatch(StartActions.setFilter({ filter }))) - ])] - } as lib.Toolbar; - } - - private createMenu(): Observable { - return this.project.workspaces.pipe(map(items => items.map( - item => this.toolbar.createMenuItem(item.name, () => this.setWorkspace(item.name))))); - } } \ No newline at end of file diff --git a/projects/aas-portal/src/app/toolbar.service.ts b/projects/aas-portal/src/app/toolbar.service.ts new file mode 100644 index 00000000..96b08ff9 --- /dev/null +++ b/projects/aas-portal/src/app/toolbar.service.ts @@ -0,0 +1,31 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2023 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ + +import { Injectable, TemplateRef } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ToolbarService { + private readonly toolbarTemplate$ = new BehaviorSubject | null>(null); + + constructor() { + this.toolbarTemplate = this.toolbarTemplate$.asObservable(); + } + + public readonly toolbarTemplate: Observable | null>; + + public set(value: TemplateRef): void { + Promise.resolve().then(() => this.toolbarTemplate$.next(value)); + } + + public clear(): void { + Promise.resolve().then(() => this.toolbarTemplate$.next(null)); + } +} \ No newline at end of file diff --git a/projects/aas-portal/src/test/aas/aas.component.spec.ts b/projects/aas-portal/src/test/aas/aas.component.spec.ts index 74b18438..a2e1e744 100644 --- a/projects/aas-portal/src/test/aas/aas.component.spec.ts +++ b/projects/aas-portal/src/test/aas/aas.component.spec.ts @@ -11,7 +11,7 @@ import { Store, StoreModule } from '@ngrx/store'; import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { AASDocument, aas } from 'common'; -import { Observable, noop, of } from 'rxjs'; +import { Observable, first, noop, of } from 'rxjs'; import { AASTree, DownloadService, NotifyService, OnlineState } from 'projects/aas-lib/src/public-api'; import { CommonModule } from '@angular/common'; @@ -175,9 +175,10 @@ describe('AASComponent', () => { expect(router.navigateByUrl).toHaveBeenCalled(); }); - it('can download an AASX file', async function () { - spyOn(download, 'downloadDocumentAsync').and.returnValue(Promise.resolve()); + it('can download an AASX file', function () { + spyOn(download, 'downloadDocument').and.returnValue(of(void 0)); expect(component.canDownloadDocument()).toBeTrue(); - await expectAsync(component.downloadDocument()).toBeResolved(); + component.downloadDocument(); + expect(download.downloadDocument).toHaveBeenCalled(); }); }); \ No newline at end of file diff --git a/projects/aas-portal/src/test/dashboard/dashboard.component.spec.ts b/projects/aas-portal/src/test/dashboard/dashboard.component.spec.ts index c46fcf0f..b109a6a7 100644 --- a/projects/aas-portal/src/test/dashboard/dashboard.component.spec.ts +++ b/projects/aas-portal/src/test/dashboard/dashboard.component.spec.ts @@ -141,7 +141,7 @@ describe('DashboardComponent', () => { describe('edit mode', function () { beforeEach(function () { - component.toggleEditMode(); + component.editMode = true; }); it('has an edit mode', function () { diff --git a/projects/aas-portal/src/test/toolbar.service.spec.ts b/projects/aas-portal/src/test/toolbar.service.spec.ts new file mode 100644 index 00000000..4aeef7dd --- /dev/null +++ b/projects/aas-portal/src/test/toolbar.service.spec.ts @@ -0,0 +1,42 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2023 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ + +import { TestBed } from '@angular/core/testing'; +import { ToolbarService } from '../app/toolbar.service'; +import { TemplateRef } from '@angular/core'; +import { first } from 'rxjs'; + +describe('ToolbarService', () => { + let service: ToolbarService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ToolbarService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('sets a new toolbar', function(done: DoneFn) { + const template = jasmine.createSpyObj>(['createEmbeddedView']); + service.set(template); + service.toolbarTemplate.pipe(first(value => value !== null)).subscribe(value => { + expect(value).toEqual(template); + done(); + }) + }); + + it('removes a toolbar', function(done: DoneFn) { + service.clear(); + service.toolbarTemplate.pipe(first()).subscribe(value => { + expect(value).toBeNull(); + done(); + }) + }); +}); \ No newline at end of file From 8cee2a5a77917a3966a87288376d5d3d45c85b12 Mon Sep 17 00:00:00 2001 From: Ralf Aron Date: Sun, 15 Oct 2023 18:55:28 +0200 Subject: [PATCH 2/2] cleanup toolbar implementation --- .../aas-lib/src/test/download.service.spec.ts | 2 +- .../aas-portal/src/app/aas/aas.component.ts | 6 ++- .../src/app/about/about.component.ts | 10 ++-- .../src/app/dashboard/dashboard.component.ts | 48 ++----------------- .../src/app/start/start.component.ts | 6 ++- .../src/app/view/view.component.html | 4 +- .../aas-portal/src/app/view/view.component.ts | 18 +++++-- .../src/test/aas/aas.component.spec.ts | 2 +- 8 files changed, 38 insertions(+), 58 deletions(-) diff --git a/projects/aas-lib/src/test/download.service.spec.ts b/projects/aas-lib/src/test/download.service.spec.ts index 73232c8f..d42f844c 100644 --- a/projects/aas-lib/src/test/download.service.spec.ts +++ b/projects/aas-lib/src/test/download.service.spec.ts @@ -10,7 +10,7 @@ import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { EMPTY, first, of } from 'rxjs'; +import { EMPTY } from 'rxjs'; import { DownloadService } from '../lib/download.service'; diff --git a/projects/aas-portal/src/app/aas/aas.component.ts b/projects/aas-portal/src/app/aas/aas.component.ts index 208bbd87..94d7ddaf 100644 --- a/projects/aas-portal/src/app/aas/aas.component.ts +++ b/projects/aas-portal/src/app/aas/aas.component.ts @@ -80,7 +80,7 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { public aasTree: lib.AASTree | null = null; @ViewChild('aasToolbar', { read: TemplateRef }) - public aasToolbar!: TemplateRef; + public aasToolbar: TemplateRef | null = null; public document: AASDocument | null = null; @@ -202,7 +202,9 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { this.subscription.add(this.aasTree.selectedElements.subscribe(values => this.selectedElements = values)); } - this.toolbar.set(this.aasToolbar); + if (this.aasToolbar) { + this.toolbar.set(this.aasToolbar); + } } public ngOnDestroy(): void { diff --git a/projects/aas-portal/src/app/about/about.component.ts b/projects/aas-portal/src/app/about/about.component.ts index c7d7724d..fb4f4290 100644 --- a/projects/aas-portal/src/app/about/about.component.ts +++ b/projects/aas-portal/src/app/about/about.component.ts @@ -20,7 +20,7 @@ import { ToolbarService } from '../toolbar.service'; }) export class AboutComponent implements OnInit, OnDestroy, AfterViewInit { constructor( - private serverApi: ServerApiService, + private serverApi: ServerApiService, private translate: TranslateService, private toolbar: ToolbarService) { this.author = pkg.author; @@ -29,7 +29,7 @@ export class AboutComponent implements OnInit, OnDestroy, AfterViewInit { } @ViewChild('aasToolbar', { read: TemplateRef }) - public aboutToolbar!: TemplateRef; + public aboutToolbar: TemplateRef | null = null; public version = ''; @@ -46,7 +46,7 @@ export class AboutComponent implements OnInit, OnDestroy, AfterViewInit { public ngOnInit(): void { this.serverApi.getInfo().subscribe(info => { - this.serverVersion = info.version; + this.serverVersion = info.version; this.libraries = info.libraries ?? []; }); @@ -54,7 +54,9 @@ export class AboutComponent implements OnInit, OnDestroy, AfterViewInit { } public ngAfterViewInit(): void { - this.toolbar.set(this.aboutToolbar); + if (this.aboutToolbar) { + this.toolbar.set(this.aboutToolbar); + } } public ngOnDestroy(): void { diff --git a/projects/aas-portal/src/app/dashboard/dashboard.component.ts b/projects/aas-portal/src/app/dashboard/dashboard.component.ts index 292b01a3..b76dec4e 100644 --- a/projects/aas-portal/src/app/dashboard/dashboard.component.ts +++ b/projects/aas-portal/src/app/dashboard/dashboard.component.ts @@ -115,7 +115,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit, Aft public chartContainers: QueryList> | null = null; @ViewChild('dashboardToolbar', { read: TemplateRef }) - public dashboardToolbar!: TemplateRef; + public dashboardToolbar: TemplateRef | null = null; public get page(): DashboardPage { return this._page; @@ -193,7 +193,9 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit, Aft } public ngAfterViewInit(): void { - this.toolbar.set(this.dashboardToolbar); + if (this.dashboardToolbar) { + this.toolbar.set(this.dashboardToolbar); + } } public ngAfterViewChecked(): void { @@ -837,46 +839,4 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit, Aft private isChart(value?: DashboardItem | null): value is DashboardChart { return value?.type === DashboardItemType.Chart; } - - // private createToolbar(): lib.Toolbar { - // const $editMode = this.store.select(selectEditMode); - // return { - // groups: [ - // this.toolbar.createGroup( - // [ - // this.toolbar.createSelect( - // this.store.select(selectPages) - // .pipe(map(pages => pages.map(page => this.toolbar.createOption( - // page.name, - // page.name)))), - // this.store.select(selectName), - // (name) => this.setPage(name)), - // this.toolbar.createButton('bi bi-plus', () => this.addNew(), () => this.editMode), - // this.toolbar.createButton('bi bi-pen', () => this.rename(), () => this.editMode), - // this.toolbar.createButton('bi bi-trash', () => this.delete(), () => this.editMode) - // ]), - // this.toolbar.createGroup( - // [ - // this.toolbar.createSwitch( - // 'Edit mode', - // $editMode, - // () => this.toggleEditMode()) - // ]), - // this.toolbar.createGroup( - // [ - // this.toolbar.createButton('bi bi-arrow-90deg-left', () => this.undo(), () => this.canUndo()), - // this.toolbar.createButton('bi bi-arrow-90deg-right', () => this.redo(), () => this.canRedo()), - // ], - // $editMode), - // this.toolbar.createGroup( - // [ - // this.toolbar.createButton('bi bi-box-arrow-left', () => this.moveLeft(), () => this.canMoveLeft()), - // this.toolbar.createButton('bi bi-box-arrow-up', () => this.moveUp(), () => this.canMoveUp()), - // this.toolbar.createButton('bi bi-box-arrow-down', () => this.moveDown(), () => this.canMoveDown()), - // this.toolbar.createButton('bi bi-box-arrow-right', () => this.moveRight(), () => this.canMoveRight()) - // ], - // $editMode), - // ] - // }; - // } } diff --git a/projects/aas-portal/src/app/start/start.component.ts b/projects/aas-portal/src/app/start/start.component.ts index 5aa64296..4c6f229e 100644 --- a/projects/aas-portal/src/app/start/start.component.ts +++ b/projects/aas-portal/src/app/start/start.component.ts @@ -58,7 +58,7 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { public aasTable: lib.AASTable | null = null; @ViewChild('startToolbar', { read: TemplateRef }) - public startToolbar!: TemplateRef; + public startToolbar: TemplateRef | null = null; public viewMode: lib.ViewMode = lib.ViewMode.List; @@ -120,7 +120,9 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { } public ngAfterViewInit(): void { - this.toolbar.set(this.startToolbar); + if (this.startToolbar) { + this.toolbar.set(this.startToolbar); + } } public ngOnDestroy(): void { diff --git a/projects/aas-portal/src/app/view/view.component.html b/projects/aas-portal/src/app/view/view.component.html index b05f2a92..74c5d26c 100644 --- a/projects/aas-portal/src/app/view/view.component.html +++ b/projects/aas-portal/src/app/view/view.component.html @@ -8,4 +8,6 @@ - \ No newline at end of file + + + diff --git a/projects/aas-portal/src/app/view/view.component.ts b/projects/aas-portal/src/app/view/view.component.ts index 0615492b..ae233c32 100644 --- a/projects/aas-portal/src/app/view/view.component.ts +++ b/projects/aas-portal/src/app/view/view.component.ts @@ -6,7 +6,7 @@ * *****************************************************************************/ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { AfterViewInit, Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { EMPTY, from, mergeMap, of, Subscription, toArray, zip } from 'rxjs'; @@ -16,13 +16,14 @@ import { State } from './view.state'; import * as ViewActions from './view.actions'; import * as ViewSelectors from './view.selectors'; import { ProjectService } from '../project/project.service'; +import { ToolbarService } from '../toolbar.service'; @Component({ selector: 'fhg-view', templateUrl: './view.component.html', styleUrls: ['./view.component.scss'] }) -export class ViewComponent implements OnInit, OnDestroy { +export class ViewComponent implements OnInit, AfterViewInit, OnDestroy { private readonly store: Store; private readonly subscription = new Subscription(); @@ -30,7 +31,8 @@ export class ViewComponent implements OnInit, OnDestroy { store: Store, private readonly route: ActivatedRoute, private readonly project: ProjectService, - private readonly clipboard: lib.ClipboardService + private readonly clipboard: lib.ClipboardService, + private readonly toolbar: ToolbarService ) { this.store = store as Store; this.subscription.add(this.store.select(ViewSelectors.selectSubmodels) @@ -44,6 +46,9 @@ export class ViewComponent implements OnInit, OnDestroy { })); } + @ViewChild('viewToolbar', { read: TemplateRef }) + public viewToolbar: TemplateRef | null = null; + public template?: string; public submodels: lib.DocumentSubmodelPair[] = []; @@ -73,7 +78,14 @@ export class ViewComponent implements OnInit, OnDestroy { } } + public ngAfterViewInit(): void { + if (this.viewToolbar) { + this.toolbar.set(this.viewToolbar); + } + } + public ngOnDestroy(): void { this.subscription.unsubscribe(); + this.toolbar.clear(); } } \ No newline at end of file diff --git a/projects/aas-portal/src/test/aas/aas.component.spec.ts b/projects/aas-portal/src/test/aas/aas.component.spec.ts index a2e1e744..5b3690e0 100644 --- a/projects/aas-portal/src/test/aas/aas.component.spec.ts +++ b/projects/aas-portal/src/test/aas/aas.component.spec.ts @@ -11,7 +11,7 @@ import { Store, StoreModule } from '@ngrx/store'; import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { AASDocument, aas } from 'common'; -import { Observable, first, noop, of } from 'rxjs'; +import { Observable, noop, of } from 'rxjs'; import { AASTree, DownloadService, NotifyService, OnlineState } from 'projects/aas-lib/src/public-api'; import { CommonModule } from '@angular/common';