diff --git a/.gitignore b/.gitignore index 3e26ea5..57fc171 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ Thumbs.db .scannerwork/ out/ package-lock.json +pnpm-lock.yaml ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 diff --git a/package.json b/package.json index c9d6e33..47b2e68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@netgrif/application-builder", - "version": "2.1.0-RC.2", + "version": "2.1.0-RC.3", "description": "Netgrif Application Builder for building, configuring and modeling applications for Application Engine.", "homepage": "https://builder.netgrif.com", "license": "SEE LICENSE IN LICENSE FILE", diff --git a/src/app/app.component.html b/src/app/app.component.html index 586c862..8618d58 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,105 +1,98 @@
- - -
- -
- - - app_registration - - -
- - @for (process of applicationService.models; track process[0]) { - - - {{ process[1].icon }} - - } - - add - - -
-
- - - - play_arrow - - - - - - device_hub - - - device_hub - - - - - - bug_report - - - help - - -
-
-
+ + + + + + play_arrow + + + + + + device_hub + + + device_hub + + + + + bug_report + + + help + + + + + + + + + @for (process of applicationService.models; track process[0]) { + + + @if (process[1].icon) { + {{ process[1].icon }} + } + {{ process[1].title.value }} [{{ process[1].id }}] + + + } + + + + + +
diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 715971a..56f2083 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -16,7 +16,7 @@ .logo { display: flex; justify-content: center; - padding-top: 0; + padding-top: 1px; } mat-action-list { @@ -86,12 +86,12 @@ padding-top: 0; .mdc-list-item { - //padding-left: 9px; - //padding-right: 9px; + padding-left: 9px; + padding-right: 9px; height: 42px; } } -.active-process { - background-color: map-get(netgif-theme.$netgrif-blue, 900); +.tab-icon { + margin-right: 8px; } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b0a9340..e20679f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,25 +1,23 @@ -import {AfterViewInit, Component, HostListener} from '@angular/core'; +import {AfterViewInit, Component, HostListener, OnInit} from '@angular/core'; import {MatDialog} from '@angular/material/dialog'; -import {Router} from '@angular/router'; import {NetgrifApplicationEngine} from '@netgrif/components-core/'; import {JoyrideService} from 'ngx-joyride'; import {AppBuilderConfigurationService} from './app-builder-configuration.service'; import {DialogApplicationEditComponent} from './dialogs/dialog-application-edit/dialog-application-edit.component'; -import {DialogConfirmComponent} from './dialogs/dialog-confirm/dialog-confirm.component'; import {DialogIntroComponent} from './dialogs/dialog-intro/dialog-intro.component'; -import {ModelImportService} from './modeler/model-import-service'; import {MortgageService} from './modeler/mortgage.service'; import {ModelService} from './modeler/services/model/model.service'; import {ApplicationService} from './project-builder/application.service'; import {DatabaseStorageService} from './project-builder/database-storage.service'; import {TutorialService} from './tutorial/tutorial-service'; +import {MatTabChangeEvent} from '@angular/material/tabs'; @Component({ selector: 'nab-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) -export class AppComponent implements AfterViewInit { +export class AppComponent implements OnInit, AfterViewInit { title = 'Netgrif Application Builder'; config: NetgrifApplicationEngine; @@ -30,12 +28,10 @@ export class AppComponent implements AfterViewInit { constructor( config: AppBuilderConfigurationService, - private router: Router, private matDialog: MatDialog, private readonly joyrideService: JoyrideService, private _mortgageService: MortgageService, private tutorialService: TutorialService, - private importService: ModelImportService, private db: DatabaseStorageService, public modelService: ModelService, public applicationService: ApplicationService, @@ -43,33 +39,22 @@ export class AppComponent implements AfterViewInit { this.config = config.get(); } + ngOnInit(): void { + this.applicationService.createApplication(); + this.applicationService.switchToFirst(); + } + ngAfterViewInit(): void { // TODO: NAB-326 https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API - this.matDialog.open(DialogIntroComponent, { - width: '40%', - panelClass: 'dialog-width-40', - disableClose: true, - data: this.db.getAllApplications(), - }); - - /*const oldModel = localStorage.getItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.KEY); - if (!oldModel) { - return; + const apps = this.db.getAllApplications(); + if (apps && apps.length > 0) { + this.matDialog.open(DialogIntroComponent, { + width: '40%', + panelClass: 'dialog-width-40', + disableClose: true, + data: apps, + }); } - const dialogRef = this.matDialog.open(DialogLocalStorageModelComponent, { - data: { - id: localStorage.getItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.ID), - timestamp: localStorage.getItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.TIMESTAMP), - title: localStorage.getItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.TITLE), - }, - }); - dialogRef.afterClosed().subscribe(result => { - if (result === true) { - this.importService.importFromXml(oldModel); - } else if (result === false) { - localStorage.clear(); - } - });*/ } openApplicationDialog() { @@ -80,14 +65,7 @@ export class AppComponent implements AfterViewInit { } addMortgage() { - const dialogRef = this.matDialog.open(DialogConfirmComponent); - - dialogRef.afterClosed().subscribe(result => { - if (result === true) { - this._mortgageService.loadModel(); - this.router.navigate(['/modeler']); - } - }); + this._mortgageService.loadModel(); } help() { @@ -105,8 +83,20 @@ export class AppComponent implements AfterViewInit { window.open(url, '_blank'); } - switchToProcess(processId: string) { + switchToProcess(processId: string): void { this.applicationService.switchActiveModel(processId); } + changeTab(tabEvent: MatTabChangeEvent): void { + this.switchToProcess(tabEvent.tab.textLabel); + } + + addNewEmptyModel(): void { + const model = this.applicationService.addNewEmptyModel(); + this.applicationService.switchActiveModel(model.id); + } + + activeProcessIndex(): number { + return this.applicationService.modelList.indexOf(this.applicationService.getActiveModel()) + 1; + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 62f36d0..a64ef76 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -16,7 +16,6 @@ import {RoleModeComponent} from './modeler/role-mode/role-mode.component'; import {ActionsModeComponent} from './modeler/actions-mode/actions-mode.component'; import {MatIconRegistry} from '@angular/material/icon'; import {environment} from '../environments/environment'; -import {DialogConfirmComponent} from './dialogs/dialog-confirm/dialog-confirm.component'; import {DialogRefactorComponent} from './dialogs/dialog-refactor/dialog-refactor.component'; import {DialogErrorsComponent} from './dialogs/dialog-errors/dialog-errors.component'; import {ExportService, ExportUtils, ImportService} from '@netgrif/petriflow'; @@ -30,9 +29,6 @@ import {DialogDeleteModelComponent} from './dialogs/dialog-delete-model/dialog-d import {DialogArcEditComponent} from './dialogs/dialog-arc-edit/dialog-arc-edit.component'; import {DialogTransitionEditComponent} from './dialogs/dialog-transition-edit/dialog-transition-edit.component'; import {SimulationModeComponent} from './modeler/simulation-mode/simulation-mode.component'; -import { - DialogLocalStorageModelComponent -} from './dialogs/dialog-local-storage-model/dialog-local-storage-model.component'; import {HistoryModeComponent} from './modeler/history-mode/history-mode.component'; import {DialogChangeDataComponent} from './dialogs/dialog-change-data/dialog-change-data.component'; import {DialogModelEditComponent} from './dialogs/dialog-model-edit/dialog-model-edit.component'; @@ -67,7 +63,6 @@ const appRoutes: Routes = [ @NgModule({ declarations: [ AppComponent, - DialogConfirmComponent, DialogRefactorComponent, DialogErrorsComponent, DialogDeadNetComponent, @@ -79,7 +74,6 @@ const appRoutes: Routes = [ DialogChangeDataComponent, DialogModelEditComponent, MaterialIconPickerComponent, - DialogLocalStorageModelComponent, DialogMarkingChangeComponent, DialogApplicationEditComponent, DialogIntroComponent, diff --git a/src/app/dialogs/dialog-application-edit/dialog-application-edit.component.html b/src/app/dialogs/dialog-application-edit/dialog-application-edit.component.html index cae21f2..55414ce 100644 --- a/src/app/dialogs/dialog-application-edit/dialog-application-edit.component.html +++ b/src/app/dialogs/dialog-application-edit/dialog-application-edit.component.html @@ -4,24 +4,28 @@

Application

Id - + + Id is required
Version - + + Version is required
Name - + + Name is required Description - + Author @@ -48,20 +52,20 @@

Application

Processes

- @for (process of applicationService.models; track process[0]) { - - {{ process[1].icon }} -

{{ process[1].title.value }}

+ @for (process of applicationService.modelList; track process) { + + {{ process.icon }} +

{{ process.title.value }}

- Id: {{ process[0] }},   - Initials: {{ process[1].initials }},   - Version: {{ process[1].version }} + Id: {{ process.id }},   + Initials: {{ process.initials }},   + Version: {{ process.version }}

- -
@@ -79,7 +83,7 @@

{{ process[1].title.value }}

@if (!fileInputLoading) { - } @if (!exportLoading) { - @@ -102,7 +107,8 @@

{{ process[1].title.value }}

} - diff --git a/src/app/dialogs/dialog-application-edit/dialog-application-edit.component.ts b/src/app/dialogs/dialog-application-edit/dialog-application-edit.component.ts index 6c8886a..9279065 100644 --- a/src/app/dialogs/dialog-application-edit/dialog-application-edit.component.ts +++ b/src/app/dialogs/dialog-application-edit/dialog-application-edit.component.ts @@ -1,5 +1,5 @@ import {COMMA, ENTER} from '@angular/cdk/keycodes'; -import {Component, ElementRef, OnInit, ViewChild} from '@angular/core'; +import {Component, ElementRef, ViewChild} from '@angular/core'; import {FormControl, Validators} from '@angular/forms'; import {MatChipInputEvent} from '@angular/material/chips'; import {MatDialog} from '@angular/material/dialog'; @@ -14,27 +14,26 @@ import {ApplicationPackageExport} from "../../project-builder/application-packag import {ModelExportService} from "../../modeler/services/model/model-export.service"; import {DialogErrorsComponent} from "../dialog-errors/dialog-errors.component"; import {ModelService} from "../../modeler/services/model/model.service"; -import { - SnackBarHorizontalPosition, - SnackBarService, - SnackBarVerticalPosition -} from "@netgrif/components-core"; +import {SnackBarHorizontalPosition, SnackBarService, SnackBarVerticalPosition} from "@netgrif/components-core"; +import {DialogDeleteModelComponent} from '../dialog-delete-model/dialog-delete-model.component'; @Component({ selector: 'nab-dialog-application-edit', templateUrl: './dialog-application-edit.component.html', styleUrl: './dialog-application-edit.component.scss', }) -export class DialogApplicationEditComponent implements OnInit { +export class DialogApplicationEditComponent { readonly chipSeparators = [ENTER, COMMA] as const; @ViewChild('appPkgFileInput') fileInput: ElementRef; - public form: FormControl; public fileInputLoading: boolean; public exportLoading: boolean; private packageImporter: ApplicationPackageImport; private packageExporter: ApplicationPackageExport; + public idCtrl: FormControl; + public nameCtrl: FormControl; + public versionCtrl: FormControl; constructor( public applicationService: ApplicationService, @@ -46,7 +45,13 @@ export class DialogApplicationEditComponent implements OnInit { private snackBarService: SnackBarService, private modelService: ModelService ) { - this.form = new FormControl('', [ + this.idCtrl = new FormControl('', [ + Validators.required, + ]); + this.nameCtrl = new FormControl('', [ + Validators.required, + ]); + this.versionCtrl = new FormControl('', [ Validators.required, ]); this.fileInputLoading = false; @@ -55,18 +60,17 @@ export class DialogApplicationEditComponent implements OnInit { this.packageExporter = new ApplicationPackageExport(this.exportUtils, this.exportService); } - ngOnInit(): void { - } exportApplication($event: Event) { $event.stopPropagation(); this.exportLoading = true; + // TODO: NAB-380 - export tags? this.packageExporter.generatePackageFile(this.applicationService.application, this.applicationService.models) .catch(err => { console.error(err); }).finally(() => { - this.exportLoading = false; - }); + this.exportLoading = false; + }); } importApplication($event: Event) { @@ -130,10 +134,23 @@ export class DialogApplicationEditComponent implements OnInit { } this.applicationService.models.set(changedModel.model.id, changedModel.model); if (changedModel) { - this.historyService.save(`Model has been changed.`); // TODO sprav historiu pre všetky procesy + this.historyService.save(`Model has been changed.`, changedModel.model); } } }); } + removeModel(processId: string) { + const dialogRef = this.dialog.open(DialogDeleteModelComponent); + dialogRef.afterClosed().subscribe(result => { + if (result === true) { + this.applicationService.removeModel(processId); + } + }); + } + + public hasErrors(): boolean { + return this.idCtrl.invalid || this.nameCtrl.invalid || this.versionCtrl.invalid; + } + } diff --git a/src/app/dialogs/dialog-confirm/dialog-confirm.component.html b/src/app/dialogs/dialog-confirm/dialog-confirm.component.html deleted file mode 100644 index 9b7f496..0000000 --- a/src/app/dialogs/dialog-confirm/dialog-confirm.component.html +++ /dev/null @@ -1,8 +0,0 @@ -

New Model Confirmation

- - Are you sure you want to open example model ? You loose all data from actual model. - - - - - diff --git a/src/app/dialogs/dialog-confirm/dialog-confirm.component.scss b/src/app/dialogs/dialog-confirm/dialog-confirm.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/dialogs/dialog-confirm/dialog-confirm.component.spec.ts b/src/app/dialogs/dialog-confirm/dialog-confirm.component.spec.ts deleted file mode 100644 index 7b84374..0000000 --- a/src/app/dialogs/dialog-confirm/dialog-confirm.component.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; -import {MaterialImportModule} from '../../material-import/material-import.module'; - -import {DialogConfirmComponent} from './dialog-confirm.component'; - -describe('DialogConfirmComponent', () => { - let component: DialogConfirmComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [DialogConfirmComponent], - imports: [MaterialImportModule], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(DialogConfirmComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/dialogs/dialog-confirm/dialog-confirm.component.ts b/src/app/dialogs/dialog-confirm/dialog-confirm.component.ts deleted file mode 100644 index 18c26d3..0000000 --- a/src/app/dialogs/dialog-confirm/dialog-confirm.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {Component} from '@angular/core'; - -@Component({ - selector: 'nab-dialog-confirm', - templateUrl: './dialog-confirm.component.html', - styleUrls: ['./dialog-confirm.component.scss'], -}) -export class DialogConfirmComponent { -} diff --git a/src/app/dialogs/dialog-intro/dialog-intro.component.html b/src/app/dialogs/dialog-intro/dialog-intro.component.html index de7aff0..7935689 100644 --- a/src/app/dialogs/dialog-intro/dialog-intro.component.html +++ b/src/app/dialogs/dialog-intro/dialog-intro.component.html @@ -1,13 +1,13 @@

Welcome to Netgrif Application Builder

- Netgrif Application Builder (NAB) is the tool for building process driven applications using Petriflow language. + Netgrif Application Builder (NAB) is a tool for building process driven applications using Petriflow language. NAB is composed of several modules that help you in different stages of application development.

You can start where you left of or start with a fresh new application.

- @if (apps && apps.length != 0) { + @if (apps && apps.length !== 0) {
@for (app of apps; track app.name) { diff --git a/src/app/dialogs/dialog-intro/dialog-intro.component.ts b/src/app/dialogs/dialog-intro/dialog-intro.component.ts index 1635306..0436b00 100644 --- a/src/app/dialogs/dialog-intro/dialog-intro.component.ts +++ b/src/app/dialogs/dialog-intro/dialog-intro.component.ts @@ -38,7 +38,6 @@ export class DialogIntroComponent { } createNewApplication() { - this.applicationService.createApplication(); this.dialog.open(DialogApplicationEditComponent, { width: '50%', panelClass: 'dialog-width-50', diff --git a/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.html b/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.html deleted file mode 100644 index 21e2693..0000000 --- a/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.html +++ /dev/null @@ -1,14 +0,0 @@ -

Continue previous work

- - Do you wish to continue work on "{{data.title}}" [{{data.id}}] from {{data.timestamp}} - - - - - diff --git a/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.scss b/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.spec.ts b/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.spec.ts deleted file mode 100644 index c5dc7bd..0000000 --- a/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DialogLocalStorageModelComponent } from './dialog-local-storage-model.component'; - -describe('DialogLocalStorageModelComponent', () => { - let component: DialogLocalStorageModelComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ DialogLocalStorageModelComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(DialogLocalStorageModelComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.ts b/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.ts deleted file mode 100644 index 093bf7f..0000000 --- a/src/app/dialogs/dialog-local-storage-model/dialog-local-storage-model.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {Component, Inject, OnInit} from '@angular/core'; -import {MAT_DIALOG_DATA} from '@angular/material/dialog'; - -export interface DialogData { - id: string; - timestamp: string; - title: string; -} - -@Component({ - selector: 'nab-dialog-local-storage-model', - templateUrl: './dialog-local-storage-model.component.html', - styleUrls: ['./dialog-local-storage-model.component.scss'] -}) -export class DialogLocalStorageModelComponent { - - constructor( - @Inject(MAT_DIALOG_DATA) public data: DialogData - ) { - } - -} diff --git a/src/app/dialogs/dialog-model-edit/dialog-model-edit.component.html b/src/app/dialogs/dialog-model-edit/dialog-model-edit.component.html index 4611105..176f4a4 100644 --- a/src/app/dialogs/dialog-model-edit/dialog-model-edit.component.html +++ b/src/app/dialogs/dialog-model-edit/dialog-model-edit.component.html @@ -1,9 +1,10 @@ -

Edit model

+

Edit process

Id Id is required + Process with given id already exists in application Version diff --git a/src/app/dialogs/dialog-model-edit/dialog-model-edit.component.ts b/src/app/dialogs/dialog-model-edit/dialog-model-edit.component.ts index 7c13d1f..868db47 100644 --- a/src/app/dialogs/dialog-model-edit/dialog-model-edit.component.ts +++ b/src/app/dialogs/dialog-model-edit/dialog-model-edit.component.ts @@ -32,9 +32,11 @@ export class DialogModelEditComponent { private _processTool: ProcessActionsTool ) { this.model = data; - this.idCtrl = new FormControl('', [Validators.required]); + this.idCtrl = new FormControl('', [ + Validators.required, + this.validUnique() + ]); this.versionCtrl = new FormControl('', [ - // Validators.required, this.validVersion() ]); this.titleCtrl = new FormControl('', [Validators.required]); @@ -73,6 +75,16 @@ export class DialogModelEditComponent { }; } + private validUnique(): ValidatorFn { + return (fc: FormControl): { [key: string]: any } | null => { + if (this.modelService.appService.getModel(fc.value) !== undefined && fc.value !== this.model.originalElement.id) { + return ({validUnique: true}); + } else { + return null; + } + }; + } + getTags() { return Array.from(this.model.model.tags, ([key, value]) => ({ key, value })); } diff --git a/src/app/modeler/control-panel/modes/redo-tool.ts b/src/app/modeler/control-panel/modes/redo-tool.ts index b0177ca..c230fd9 100644 --- a/src/app/modeler/control-panel/modes/redo-tool.ts +++ b/src/app/modeler/control-panel/modes/redo-tool.ts @@ -22,7 +22,8 @@ export class RedoTool extends Tool { ) ); this.disabled.next(true); - history.historyChange.subscribe(change => { + // TODO: NAB-380 + history.historyChange().subscribe(change => { this.disabled.next(change.size === 0 || change.head === change.size - 1); }); } diff --git a/src/app/modeler/control-panel/modes/undo-tool.ts b/src/app/modeler/control-panel/modes/undo-tool.ts index 5b0c7ba..9b0fc9c 100644 --- a/src/app/modeler/control-panel/modes/undo-tool.ts +++ b/src/app/modeler/control-panel/modes/undo-tool.ts @@ -22,7 +22,7 @@ export class UndoTool extends Tool { ) ); this.disabled.next(true); - history.historyChange.subscribe(change => { + history.historyChange().subscribe(change => { this.disabled.next(change.size === 0 || change.head === 0); }); } diff --git a/src/app/modeler/control-panel/trees/trigger-tree/trigger-tree.component.ts b/src/app/modeler/control-panel/trees/trigger-tree/trigger-tree.component.ts index 1b10df7..bf20bc5 100644 --- a/src/app/modeler/control-panel/trees/trigger-tree/trigger-tree.component.ts +++ b/src/app/modeler/control-panel/trees/trigger-tree/trigger-tree.component.ts @@ -22,7 +22,7 @@ interface TriggerNode { {provide: NGX_MAT_DATE_FORMATS, useValue: DATE_TIME_FORMAT} ] }) -export class TriggerTreeComponent { +export class TriggerTreeComponent implements OnInit { @Input() triggers: Array; typeOptions = [{key: 'auto', value: 'Auto'}, {key: 'user', value: 'User'}, {key: 'time', value: 'Time'}]; @@ -41,6 +41,13 @@ export class TriggerTreeComponent { this.counter = 0; } + ngOnInit() { + if (this.triggers && this.triggers.length > 0) { + this.import(); + this.import(); + } + } + import(): void { this.counter = 0; const tree = [{ diff --git a/src/app/modeler/data-mode/data-detail/data-detail.component.ts b/src/app/modeler/data-mode/data-detail/data-detail.component.ts index 4c3f60e..7127c40 100644 --- a/src/app/modeler/data-mode/data-detail/data-detail.component.ts +++ b/src/app/modeler/data-mode/data-detail/data-detail.component.ts @@ -187,6 +187,15 @@ export class DataDetailComponent implements OnDestroy { this.item.placeholder.value = $event.target.value; break; } + case 'init': { + const value = $event.target.value; + if (this.item.init === undefined) { + this.item.init = new I18nWithDynamic(value); + } else { + this.item.init.value = value; + } + break; + } case 'dynamic-init': { const value = $event.source.checked; if (this.item.init === undefined) { diff --git a/src/app/modeler/data-mode/data-master-detail.service.ts b/src/app/modeler/data-mode/data-master-detail.service.ts index 2d684c0..828dec7 100644 --- a/src/app/modeler/data-mode/data-master-detail.service.ts +++ b/src/app/modeler/data-mode/data-master-detail.service.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {DataType, DataVariable} from '@netgrif/petriflow'; +import {DataType, DataVariable, I18nWithDynamic} from '@netgrif/petriflow'; import {ModelService} from '../services/model/model.service'; import {AbstractMasterDetailService} from '../components/master-detail/abstract-master-detail.service'; import {Sort} from '@angular/material/sort'; @@ -22,6 +22,7 @@ export class DataMasterDetailService extends AbstractMasterDetailService> { - return this._historyService.history.memory; + return this._historyService.history().memory; } public create(): HistoryChange { diff --git a/src/app/modeler/model-import-service.ts b/src/app/modeler/model-import-service.ts index faeed78..9816cac 100644 --- a/src/app/modeler/model-import-service.ts +++ b/src/app/modeler/model-import-service.ts @@ -4,9 +4,8 @@ import {ImportSuccessfulComponent} from './control-panel/import-successful/impor import {ImportService} from '@netgrif/petriflow'; import {MatDialog} from '@angular/material/dialog'; import {MatSnackBar} from '@angular/material/snack-bar'; +import {ApplicationService} from '../project-builder/application.service'; import {Router} from '@angular/router'; -import {ModelService} from './services/model/model.service'; -import {HistoryService} from './services/history/history.service'; @Injectable({ providedIn: 'root' @@ -15,11 +14,10 @@ export class ModelImportService { constructor( private importService: ImportService, - private modelService: ModelService, private snackBar: MatSnackBar, private dialog: MatDialog, + private _appService: ApplicationService, private router: Router, - private historyService: HistoryService ) { } @@ -42,9 +40,9 @@ export class ModelImportService { } if (petriNetResult.model !== undefined) { - this.modelService.model = petriNetResult.model; - this.historyService.save(`Model ${this.modelService.model.id} has been imported.`) + this._appService.addModel(petriNetResult.model); + this._appService.switchActiveModel(petriNetResult.model.id); + this.router.navigate(['/modeler']); } - this.router.navigate(['/modeler']); } } diff --git a/src/app/modeler/mortgage.service.ts b/src/app/modeler/mortgage.service.ts index 07696aa..2f20c7b 100644 --- a/src/app/modeler/mortgage.service.ts +++ b/src/app/modeler/mortgage.service.ts @@ -1,6 +1,5 @@ import {Injectable} from '@angular/core'; import {HttpClient} from '@angular/common/http'; -import {ModelerTabsService} from './services/modeler-tabs.service'; import {ModelImportService} from './model-import-service'; @Injectable({ diff --git a/src/app/modeler/role-mode/role-detail/role-detail.component.html b/src/app/modeler/role-mode/role-detail/role-detail.component.html index 55ab2c1..efe170b 100644 --- a/src/app/modeler/role-mode/role-detail/role-detail.component.html +++ b/src/app/modeler/role-mode/role-detail/role-detail.component.html @@ -4,9 +4,9 @@ Id - - Id is required - + + Id is required + Transition with given id already exists @@ -23,9 +23,11 @@
- - Is Global - +
+ + Is Global + +
diff --git a/src/app/modeler/role-mode/role-detail/role-detail.component.ts b/src/app/modeler/role-mode/role-detail/role-detail.component.ts index ef18267..4487718 100644 --- a/src/app/modeler/role-mode/role-detail/role-detail.component.ts +++ b/src/app/modeler/role-mode/role-detail/role-detail.component.ts @@ -13,13 +13,13 @@ import {ModelerUtils} from '../../modeler-utils'; @Component({ selector: 'nab-role-detail', templateUrl: './role-detail.component.html', - styleUrl: './role-detail.component.scss' + styleUrl: './role-detail.component.scss', }) export class RoleDetailComponent implements OnDestroy { public role: ChangedRole; public shouldSave: boolean = false; - public form: FormControl; + public roleIdForm: FormControl; public constructor( private _masterService: RoleMasterDetailService, @@ -27,7 +27,7 @@ export class RoleDetailComponent implements OnDestroy { private _router: Router, private _actionMode: ActionsModeService, private _actionsMasterDetail: ActionsMasterDetailService, - protected _historyService: HistoryService + protected _historyService: HistoryService, ) { this._masterService.getSelected$().subscribe(item => { this.saveChange(); @@ -36,9 +36,9 @@ export class RoleDetailComponent implements OnDestroy { } this.role = new ChangedRole(item.clone()); }); - this.form = new FormControl('', [ + this.roleIdForm = new FormControl('', [ Validators.required, - this.validUnique() + this.validUnique(), ]); } @@ -76,7 +76,7 @@ export class RoleDetailComponent implements OnDestroy { } changeGlobalFlag($event): void { - this.role.role.global = $event.target.checked; + this.role.role.global = $event.checked; this.shouldSave = true; } diff --git a/src/app/modeler/services/history/history.service.ts b/src/app/modeler/services/history/history.service.ts index b77fbfc..41cfda8 100644 --- a/src/app/modeler/services/history/history.service.ts +++ b/src/app/modeler/services/history/history.service.ts @@ -13,15 +13,13 @@ import {RedoTool} from '../../control-panel/modes/redo-tool'; }) export class HistoryService { - private readonly _history: History; - private readonly _historyChange: Subject>; + private readonly _histories: Map>; constructor( private modelService: ModelService, private exportService: ExportService, ) { - this._history = new History(); - this._historyChange = new Subject(); + this._histories = new Map>(); } public save(message: string, model?: PetriNet): void { @@ -30,39 +28,59 @@ export class HistoryService { this.push(model.clone(), message); } - public undo(): void { - this.reloadModel(this._history.undo(), UndoTool.ID); + public undo(id = this.getId()): void { + const history = this.findById(id); + this.reloadModel(history.undo(), UndoTool.ID); } - public redo(): void { - this.reloadModel(this._history.redo(), RedoTool.ID); + public redo(id = this.getId()): void { + const history = this.findById(id); + this.reloadModel(history.redo(), RedoTool.ID); } public reload(model: PetriNet): void { - this._history.head = this._history.memory.findIndex(value => value.record === model); + this.history(model.id).head = this.history(model.id).memory.findIndex(value => value.record === model); this.reloadModel(model, ''); } + public changeId(oldId: string, newId: string): void { + this._histories.set(newId, this._histories.get(oldId)); + this._histories.delete(oldId); + } + + private getId(): string { + return this.modelService.model.id; + } + private reloadModel(model: PetriNet, message: string): PetriNet { if (model === undefined) { return undefined; } - this.historyChange.next(HistoryChange.of(this._history, message)); - this.modelService.model = model.clone(); + this.historyChange(model.id).next(HistoryChange.of(this.history(model.id), message)); + const newModel = model.clone(); + this.modelService.appService.updateModel(this.modelService.model.id, newModel); + this.modelService.model = newModel; return model; } private push(model: PetriNet, message: string): void { - if (!model || model.lastChanged === this.currentModel?.lastChanged || this.history.memory.find(change => + if (!model || model.lastChanged === this.currentModel(model.id)?.lastChanged || this.history(model.id).memory.find(change => change.record.lastChanged === model.lastChanged) ) { return; } - const update = this._history.push(model, message); - this.historyChange.next(update); + const update = this.findById(model.id).push(model, message); + this.historyChange(model.id).next(update); this.saveToLocalStorage(model).then(); } + private findById(id: string): History { + if (!this._histories.has(id)) { + this._histories.set(id, new History()) + } + return this._histories.get(id); + } + async saveToLocalStorage(model: PetriNet): Promise { localStorage.setItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.KEY, this.exportService.exportXml(model)); localStorage.setItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.TIMESTAMP, new Date().toLocaleString()); @@ -70,15 +88,15 @@ export class HistoryService { localStorage.setItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.TITLE, `${model.title.value}`); } - get historyChange(): Subject> { - return this._historyChange; + public historyChange(id = this.getId()): Subject> { + return this.findById(id).change; } - get currentModel(): PetriNet { - return this._history.record; + public currentModel(id = this.getId()): PetriNet { + return this.findById(id).record; } - get history(): History { - return this._history; + public history(id = this.getId()): History { + return this.findById(id); } } diff --git a/src/app/modeler/services/history/history.ts b/src/app/modeler/services/history/history.ts index ba756a0..aa16ff5 100644 --- a/src/app/modeler/services/history/history.ts +++ b/src/app/modeler/services/history/history.ts @@ -1,14 +1,18 @@ import {HistoryChange} from './history-change'; +import {Subject} from 'rxjs'; export class History { + private _memory: Array>; private _head: number; private readonly limit: number; + private readonly _change: Subject>; constructor(limit: number = 100) { this._memory = new Array>(); - this.limit = limit; this._head = -1; + this.limit = limit; + this._change = new Subject(); } public get record(): T { @@ -68,4 +72,8 @@ export class History { get memory(): Array> { return this._memory; } + + get change(): Subject> { + return this._change; + } } diff --git a/src/app/modeler/services/model/model.service.ts b/src/app/modeler/services/model/model.service.ts index 0a5f19d..45f3637 100644 --- a/src/app/modeler/services/model/model.service.ts +++ b/src/app/modeler/services/model/model.service.ts @@ -6,6 +6,7 @@ import { DataType, DataVariable, I18nString, + ImportUtils, NodeElement, PetriNet, Place, @@ -98,7 +99,7 @@ export class ModelService { public newModel(): PetriNet { const model = new PetriNet(); - model.id = ModelConfig.IDENTIFIER + '-' + this.appService.getAndIncrementModelSequence(); + model.id = this.appService.nextModelId(); model.version = ModelConfig.VERSION; model.title = new I18nString(ModelConfig.TITLE); model.initials = ModelConfig.INITIALS; @@ -379,6 +380,7 @@ export class ModelService { const role = this.model.getRole(newRole.id); role.id = newRole.role.id; role.title = newRole.role.title; + role.global = newRole.role.global; this.model.removeRole(newRole.id); this.model.addRole(role); @@ -497,8 +499,10 @@ export class ModelService { const referencedData = model.getData(id); if (referencedData) { if (referencedData.init.value) { - return Number(referencedData.init.value); - // TODO: NAB-326 check if isFinite and >= 0 + if (ImportUtils.isInitValueNumber(referencedData.init)) { + return Number(referencedData.init.value); + } + return 0; } return 0; } diff --git a/src/app/modeler/simulation-mode/simulation-mode.service.ts b/src/app/modeler/simulation-mode/simulation-mode.service.ts index 3a351ff..8d84604 100644 --- a/src/app/modeler/simulation-mode/simulation-mode.service.ts +++ b/src/app/modeler/simulation-mode/simulation-mode.service.ts @@ -1,6 +1,6 @@ import {Injectable, Injector} from '@angular/core'; import {BehaviorSubject} from 'rxjs'; -import {Arc, BasicSimulation, PetriNet, Place, Transition} from '@netgrif/petriflow'; +import {Arc, BasicSimulation, ImportUtils, PetriNet, Place, Transition} from '@netgrif/petriflow'; import {TutorialService} from '../../tutorial/tutorial-service'; import {ModelService} from '../services/model/model.service'; import {EventSimulationTool} from './tool/event-simulation.tool'; @@ -88,7 +88,13 @@ export class SimulationModeService extends CanvasModeService { this.originalModel.subscribe(model => { if (!model) return; this.data = new Map(model.getArcs().filter(a => !!a.reference && !!model.getData(a.reference)) - .map(a => [a.reference, Number.parseInt(model.getData(a.reference).init?.value, 10) || 0])); + .map(a => { + const data = model.getData(a.reference); + if (ImportUtils.isInitValueNumber(data.init)) { + return [a.reference, Number.parseInt(data.init.value, 10)]; + } + return [a.reference, 0]; + })); this.simulation = new BasicSimulation(model, this.data); this.renderModel(model); }); diff --git a/src/app/project-builder/application.service.ts b/src/app/project-builder/application.service.ts index b287e20..9f302df 100644 --- a/src/app/project-builder/application.service.ts +++ b/src/app/project-builder/application.service.ts @@ -1,13 +1,15 @@ import {Injectable, OnDestroy} from '@angular/core'; -import {MatDialog} from '@angular/material/dialog'; import {PetriNet} from '@netgrif/petriflow'; import {Subscription} from 'rxjs'; import {filter} from 'rxjs/operators'; -import {DialogDeleteModelComponent} from '../dialogs/dialog-delete-model/dialog-delete-model.component'; import {HistoryService} from '../modeler/services/history/history.service'; import {ModelService} from '../modeler/services/model/model.service'; import Application from './application'; import {SimulationModeService} from "../modeler/simulation-mode/simulation-mode.service"; +import {SequenceGenerator} from '../modeler/services/model/sequence-generator'; +import {ModelConfig} from '../modeler/services/model/model-config'; +import {Router} from '@angular/router'; +import {EditModeService} from '../modeler/edit-mode/edit-mode.service'; @Injectable({ providedIn: 'root', @@ -16,15 +18,15 @@ export class ApplicationService implements OnDestroy { private readonly _models: Map; private _application: Application; - private _modelIdSequence = 0; - + private _modelIdSequence = new SequenceGenerator(`${ModelConfig.IDENTIFIER}_`); private _modelSubscription: Subscription; constructor( private modelService: ModelService, private historyService: HistoryService, - private dialog: MatDialog, - private simulationModeService: SimulationModeService + private simulationModeService: SimulationModeService, + private editModeService: EditModeService, + private router: Router, ) { this._models = new Map(); this._modelSubscription = modelService.modelSubject.pipe( @@ -58,8 +60,16 @@ export class ApplicationService implements OnDestroy { return this._models; } - getAndIncrementModelSequence(): number { - return this._modelIdSequence++; + get modelList(): Array { + return Array.from(this._models.values()); + } + + public nextModelId(): string { + const id = this._modelIdSequence.next(); + if (this.models.has(id)) { + return this.nextModelId(); + } + return id; } getModel(id: string): PetriNet { @@ -74,36 +84,26 @@ export class ApplicationService implements OnDestroy { this._application = Application.getEmpty(); this.addNewEmptyModel(); console.log('New application created', this._application); + this._modelIdSequence.reset([]); return this._application; } private deleteModel(processId: string) { if (this.modelService.model.id === processId) { - if (this._models.size > 1) { - this.switchActiveModel(this._models.keys().next().value); - } else { + if (this._models.size <= 1) { this.addNewEmptyModel(); // nemôže byť aplikácie bez procesu - this.switchActiveModel(this._models.keys().next().value); } + this.switchActiveModel(this._models.keys().next().value); } this._models.delete(processId); this.updateProcesses(); console.log('Process removed', processId); } - removeModel(processId: string, confirmationDialog = true) { // TODO remove cez app edit dialog nefunguje, vymaze iný prvok - if (!confirmationDialog) { - this.deleteModel(processId); - } else { - const dialogRef = this.dialog.open(DialogDeleteModelComponent); - dialogRef.afterClosed().subscribe(result => { - if (result === true) { - const oldId = this.modelService.model.id; - this.deleteModel(oldId); - this.historyService.save(`Model ${oldId} has been deleted.`, this.modelService.model); - } - }); - } + removeModel(processId: string) { + const model = this.getModel(processId); + this.deleteModel(processId); + this.historyService.save(`Model ${processId} has been deleted.`, model); } addModel(net: PetriNet): void { @@ -113,34 +113,52 @@ export class ApplicationService implements OnDestroy { console.log('New process added', net.id); } - addNewEmptyModel() { - const newModel = this.modelService.newModel(); - this._models.set(newModel.id, newModel); + updateModel(oldId: string, model: PetriNet): void { + // TODO: NAB-380 reload model undo/redo + const oldModel = this._models.get(oldId); + if (!oldModel) { + return + } + if (oldId === model.id) { + this._models.set(oldId, model); + } else { + this._models.delete(oldId); + this._models.set(oldId, model); + } this.updateProcesses(); - // this.modelService.model = this.modelService.newModel(); - this.historyService.save(`New model has been created.`, newModel); - console.log('New process added', newModel.id); + } + + addNewEmptyModel(): PetriNet { + const newModel = this.modelService.newModel() + this.addModel(newModel); + return newModel; } updateModelId(oldId: string, newId: string) { - if (!this._models.get(oldId)) return; + if (!this._models.get(oldId)) { + return; + } this._models.set(newId, this._models.get(oldId)); this._models.delete(oldId); this.updateProcesses(); + this.historyService.changeId(oldId, newId); console.log('Process id updated', oldId, '->', newId); } switchActiveModel(processId: string) { - if (!this._models.get(processId)) return; + if (!this._models.get(processId)) { + return; + } this.modelService.model = this._models.get(processId); this.simulationModeService.originalModel.next(this._models.get(processId)); - this.historyService.save(`Model ${this.modelService.model.id} has been changed.`, this._models.get(processId)); + this.router.navigate(['/modeler']); + this.editModeService.renderModel(); console.log('Current process switched', processId); } switchToFirst() { if (this._application.processes.length > 0) { - this.modelService.model = this._models.get(this._application.processes[0]); + this.switchActiveModel(this._application.processes[0]); } } diff --git a/src/app/project-builder/application.ts b/src/app/project-builder/application.ts index 6c0ca1b..03654bb 100644 --- a/src/app/project-builder/application.ts +++ b/src/app/project-builder/application.ts @@ -1,22 +1,22 @@ export default class Application { // TODO validácie public appMeta: string = 'application'; - public id: string; // TODO doplniť o identifikátor a tak aby prešiel cez sanitáciu + public id: string; public name: string; - public description: string = ''; - public version: string = '1.0.0'; + public description: string; + public version: string; public author: string; public tags: Array = []; public processes: Array = []; - constructor(name: string, description: string = '', version: string = '1.0.0') { + constructor(id: string, name: string, description: string, version: string) { + this.id = id; this.name = name; this.description = description; this.version = version; } public static getEmpty(): Application { - return new Application('New Application'); + return new Application('new_application', 'New Application', '', '1.0.0'); } - } diff --git a/src/app/tutorial/tutorial-service.ts b/src/app/tutorial/tutorial-service.ts index 6515f1d..f0df6fc 100644 --- a/src/app/tutorial/tutorial-service.ts +++ b/src/app/tutorial/tutorial-service.ts @@ -1,8 +1,6 @@ import {Injectable} from '@angular/core'; -import {MortgageService} from '../modeler/mortgage.service'; import {Router} from '@angular/router'; import {TutorialStep} from './tutorial-step'; -import {ModelService} from '../modeler/services/model/model.service'; @Injectable({ providedIn: 'root' @@ -27,24 +25,16 @@ export class TutorialService { bug: TutorialStep; steps: Array; onClose: () => void; - mortgageLoaded: boolean; constructor( - private mortgageService: MortgageService, private router: Router, - private modelService: ModelService ) { this.welcome = TutorialStep.of( 'welcome', 'Welcome to the Netgrif Application Builder', 'Netgrif Application Builder (NAB) is the tool for building process driven applications using Petriflow language. NAB is composed of several modules that help you in different stages of application development.', () => { - this.mortgageLoaded = false; - if (modelService.model.getTransitions().length === 0 && modelService.model.getPlaces().length === 0 && modelService.model.getArcs().length === 0 && - modelService.model.getDataSet().length === 0 && modelService.model.getTransactions().length === 0 && modelService.model.getRoles().length === 0) { - this.mortgageService.loadModel(); - this.mortgageLoaded = true; - } + // TODO: release/3.0 - load mortgage - cyclic dependency through MortgageService this.router.navigate(['/modeler']); }, () => { @@ -235,9 +225,6 @@ export class TutorialService { ]; this.onClose = () => { this.router.navigate(['/modeler']); - if (this.mortgageLoaded) { - this.modelService.model = this.modelService.newModel(); // TODO toto vytvorí nový model ktorý asi nie je treba - } }; } } diff --git a/src/assets/mortgage_simple.xml b/src/assets/mortgage_simple.xml index 1a4a2fb..224f6d6 100644 --- a/src/assets/mortgage_simple.xml +++ b/src/assets/mortgage_simple.xml @@ -3,6 +3,7 @@ 1.0.0 MTG Mortgage + house true false false diff --git a/src/styles.scss b/src/styles.scss index 854ae26..97e95e1 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -91,6 +91,7 @@ body { color: black; fill: black; } + nab-tool, nab-control-panel-mode, nab-import-tool-button { .mat-mdc-icon-button .mat-mdc-button-persistent-ripple { border-radius: 5px; @@ -110,24 +111,24 @@ nab-tool, nab-control-panel-mode, nab-import-tool-button { } .checkbox-background-warn { - .mdc-checkbox .mdc-checkbox__native-control:enabled:checked~.mdc-checkbox__background, .mdc-checkbox .mdc-checkbox__native-control:enabled:indeterminate~.mdc-checkbox__background, .mdc-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled~.mdc-checkbox__background { + .mdc-checkbox .mdc-checkbox__native-control:enabled:checked ~ .mdc-checkbox__background, .mdc-checkbox .mdc-checkbox__native-control:enabled:indeterminate ~ .mdc-checkbox__background, .mdc-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled ~ .mdc-checkbox__background { border-color: map-get(netgif-theme.$netgrif-warn, 500) !important; background-color: map-get(netgif-theme.$netgrif-warn, 500) !important; } - .mdc-checkbox .mdc-checkbox__native-control:enabled~.mdc-checkbox__background .mdc-checkbox__mixedmark { + .mdc-checkbox .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background .mdc-checkbox__mixedmark { border-color: white; color: white; } } .checkbox-background-green { - .mdc-checkbox .mdc-checkbox__native-control:enabled:checked~.mdc-checkbox__background, .mdc-checkbox .mdc-checkbox__native-control:enabled:indeterminate~.mdc-checkbox__background, .mdc-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled~.mdc-checkbox__background { - border-color:#008800 !important; + .mdc-checkbox .mdc-checkbox__native-control:enabled:checked ~ .mdc-checkbox__background, .mdc-checkbox .mdc-checkbox__native-control:enabled:indeterminate ~ .mdc-checkbox__background, .mdc-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled ~ .mdc-checkbox__background { + border-color: #008800 !important; background-color: #008800 !important; } - .mdc-checkbox .mdc-checkbox__native-control:enabled~.mdc-checkbox__background .mdc-checkbox__checkmark { + .mdc-checkbox .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background .mdc-checkbox__checkmark { color: white; } } @@ -171,7 +172,7 @@ nab-tool, nab-control-panel-mode, nab-import-tool-button { width: 60% !important; } -.mat-mdc-dialog-container .mat-mdc-dialog-title+.mat-mdc-dialog-content { +.mat-mdc-dialog-container .mat-mdc-dialog-title + .mat-mdc-dialog-content { padding-top: 8px !important; } @@ -185,6 +186,7 @@ nab-simulation-mode { .builder-field-input { margin-top: 6px; + .mat-mdc-form-field-subscript-wrapper { position: absolute; width: auto; @@ -217,14 +219,17 @@ nab-simulation-mode { padding-left: 8px; padding-right: 4px; } + .mat-expansion-panel-body { padding-left: 8px; padding-right: 4px; } + .mat-expanded { margin-bottom: 0; margin-top: 0; } + .mat-expansion-panel-header.mat-expanded { height: 40px; } @@ -257,3 +262,20 @@ nab-simulation-mode { background-size: contain; } } + +.app-tabs { + .mat-mdc-tab.mdc-tab { + height: 40px !important; + padding-left: 8px !important; + padding-right: 8px !important; + } + + .mdc-tab__content { + pointer-events: all !important; + } + + .mat-mdc-tab.mat-mdc-tab-disabled { + opacity: 1 !important; + } +} +