diff --git a/e2e/calculator.po.ts b/e2e/calculator.po.ts index da84adc10448a18c56db41bd2c3e63646ea1a4c1..cb46ead62327610f042b9b62afa357e44d36fb81 100644 --- a/e2e/calculator.po.ts +++ b/e2e/calculator.po.ts @@ -64,11 +64,11 @@ export class CalculatorPage { } getAddStructureButton() { - return element(by.css("fieldset-container .hyd-window-btns button.add-structure")); + return element(by.css("structure-fieldset-container .hyd-window-btns button.add-structure")); } getCopyStructureButton() { - return element(by.css("fieldset-container .hyd-window-btns button.copy-structure")); + return element(by.css("structure-fieldset-container .hyd-window-btns button.copy-structure")); } getAllLinkButtons() { diff --git a/jalhyd_branch b/jalhyd_branch index ba5ab733debaf6478f0d6b583eb80b41848804b7..626e97d71d9e3364f9abe16f7e3c8d70dea5a7fa 100644 --- a/jalhyd_branch +++ b/jalhyd_branch @@ -1 +1 @@ -324-pab-ajouter-la-charge-et-l-ennoiement-dans-le-tableau-de-resultat-et-l-export +devel \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7c57cdce8f41945363e18863eb2a1122c4a7b940..c39a646e4ea262213bbe5d71352749971cef5120 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -120,6 +120,9 @@ import { ImmediateErrorStateMatcher } from "./formulaire/immediate-error-state-m import { LoadSessionURLComponent } from "./components/load-session-url/load-session-url.component"; import { DialogShowMessageComponent } from "./components/dialog-show-message/dialog-show-message.component"; import { DialogConfirmLoadSessionURLComponent } from "./components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component"; +import { StructureFieldsetContainerComponent } from "./components/structure-fieldset-container/structure-fieldset-container.component"; +import { BasinFieldsetContainerComponent } from "./components/basin-fieldset-container/basin-fieldset-container.component"; +import { PrebarrageService } from "./services/prebarrage.service"; const appRoutes: Routes = [ { path: "list/search", component: CalculatorListComponent }, @@ -209,6 +212,8 @@ const appRoutes: Routes = [ DialogConfirmLoadSessionURLComponent, FieldSetComponent, FieldsetContainerComponent, + StructureFieldsetContainerComponent, + BasinFieldsetContainerComponent, FixedResultsComponent, FixedVarResultsComponent, FlexGtXxsShowHideDirective, @@ -261,6 +266,7 @@ const appRoutes: Routes = [ HttpService, I18nService, NotificationsService, + PrebarrageService, { provide: ErrorStateMatcher, useClass: ImmediateErrorStateMatcher diff --git a/src/app/calculators/pbbassin/en.json b/src/app/calculators/pbbassin/en.json index 6566e3fd828cb3b988499fd1881afb6acdbe7f6b..a0c025b65802fd3c6a7c419c30116897cf99ff7e 100644 --- a/src/app/calculators/pbbassin/en.json +++ b/src/app/calculators/pbbassin/en.json @@ -1,5 +1,6 @@ { - "fs_basin_params": "Basin parameters", + "basin_container": "Basins", + "fs_basin": "Basin parameters", "S": "Surface", "ZF": "Bottom elevation" diff --git a/src/app/calculators/pbbassin/fr.json b/src/app/calculators/pbbassin/fr.json index 71d54fd771d5d737f4d6e7267777f36fc1876358..29fe43624e0c3c1f9275e1f96ab56e55ed9c7b9f 100644 --- a/src/app/calculators/pbbassin/fr.json +++ b/src/app/calculators/pbbassin/fr.json @@ -1,5 +1,6 @@ { - "fs_basin_params": "Paramètres du bassin", + "basin_container": "Bassins", + "fs_basin": "Paramètres du bassin", "S": "Surface", "ZF": "Cote de fond" diff --git a/src/app/calculators/prebarrage/config.json b/src/app/calculators/prebarrage/config.json index 3474300cdb19deeb219ba7d619b6be49817c019c..7dada6df94c09621a3c55d9ba3475b3e2298a9f6 100644 --- a/src/app/calculators/prebarrage/config.json +++ b/src/app/calculators/prebarrage/config.json @@ -27,13 +27,21 @@ "type": "subform", "config": [ { - "id": "fs_basin_params", - "type": "fieldset", + "id": "fs_basin", + "type": "fieldset_template", + "calcType": "PbBassin", "fields": [ "S", "ZF" ] }, + { + "id": "basin_container", + "type": "template_container", + "templates": [ + "fs_basin" + ] + }, { "type": "options", "selectIds": [ ] diff --git a/src/app/components/basin-fieldset-container/basin-fieldset-container.component.ts b/src/app/components/basin-fieldset-container/basin-fieldset-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd89b05dd27120941acf22ecc18e7476cde56bf9 --- /dev/null +++ b/src/app/components/basin-fieldset-container/basin-fieldset-container.component.ts @@ -0,0 +1,42 @@ +import { Component } from "@angular/core"; + +import { I18nService } from "../../services/internationalisation.service"; +import { ApplicationSetupService } from "../../services/app-setup.service"; +import { FieldsetContainerComponent } from "../fieldset-container/fieldset-container.component"; +import { PrebarrageService } from "app/services/prebarrage.service"; +import { ServiceFactory } from "app/services/service-factory"; +import { FieldSet } from "app/formulaire/elements/fieldset"; + +@Component({ + selector: "basin-fieldset-container", + templateUrl: "../fieldset-container/fieldset-container.component.html", + styleUrls: [ + "../fieldset-container/fieldset-container.component.scss" + ] +}) +export class BasinFieldsetContainerComponent extends FieldsetContainerComponent { + + constructor(i18nService: I18nService, appSetupService: ApplicationSetupService, private predamService: PrebarrageService) { + super(i18nService, appSetupService); + } + + protected onFieldsetListChange() { + // disable "add" button (and "how many children" select) + setTimeout(() => // setTimeout to avoid ExpressionChangedAfterItHasBeenCheckedError + this._fieldsetComponents.forEach(fs => { + fs.showAddChildren = false; + fs.showMoveArrows = false; + })); + } + + protected addSubNub(after: FieldSet, clone?: boolean): void { + const fsIndex = this._container.kidIndex(after); + this.predamService.copyBasinByIndex(fsIndex, ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit); + } + + public onRemoveFieldset(fs: FieldSet) { + const fsIndex = this._container.kidIndex(fs); + super.onRemoveFieldset(fs); + this.predamService.deleteBasinByIndex(fsIndex, ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit); + } +} diff --git a/src/app/components/calculator-list/calculator-list.component.ts b/src/app/components/calculator-list/calculator-list.component.ts index 3316be022c5f1bed65596a9451b452c8b8a03400..ec0ec059a9df1cea2998e70ecbdbe9f0112580fa 100644 --- a/src/app/components/calculator-list/calculator-list.component.ts +++ b/src/app/components/calculator-list/calculator-list.component.ts @@ -149,7 +149,7 @@ export class CalculatorListComponent implements OnInit { if (f instanceof FormulaireParallelStructure) { for (const e of f.allFormElements) { if (e instanceof FieldsetContainer) { - e.addFromTemplate(0); + e.addFromTemplate(); break; } } @@ -158,7 +158,7 @@ export class CalculatorListComponent implements OnInit { if (f instanceof FormulairePab) { for (const e of f.allFormElements) { if (e instanceof FieldsetContainer) { - e.addFromTemplate(0); + e.addFromTemplate(); break; } } @@ -167,7 +167,7 @@ export class CalculatorListComponent implements OnInit { if (f instanceof FormulaireMacrorugoCompound) { for (const e of f.allFormElements) { if (e instanceof FieldsetContainer) { - e.addFromTemplate(0, 0, f.currentNub.getChildren()[0]); + e.addFromTemplate(0, f.currentNub.getChildren()[0]); break; } } @@ -176,7 +176,7 @@ export class CalculatorListComponent implements OnInit { if (f instanceof FormulaireSPP) { for (const e of f.allFormElements) { if (e instanceof FieldsetContainer) { - e.addFromTemplate(0); + e.addFromTemplate(); break; } } diff --git a/src/app/components/field-set/field-set.component.html b/src/app/components/field-set/field-set.component.html index 808d883ec6ebc96030ee985ddf107647ff5c820f..88654611e535f4cf3834616084282c15d310ea6b 100644 --- a/src/app/components/field-set/field-set.component.html +++ b/src/app/components/field-set/field-set.component.html @@ -4,12 +4,12 @@ </mat-card-title> <div class="hyd-window-btns"> <span *ngIf="showButtons"> - <mat-select id="add-many-children" [(value)]="childrenToAdd"> + <mat-select *ngIf="showAddChildren" id="add-many-children" [(value)]="childrenToAdd"> <mat-option *ngFor="let i of addManyOptionsList" [value]="i"> {{ i }} </mat-option> </mat-select> - <button type="button" mat-icon-button (click)="onAddClick()" class="add-structure" + <button *ngIf="showAddChildren" type="button" mat-icon-button (click)="onAddClick()" class="add-structure" [title]="uitextAddStructure"> <mat-icon>add_box</mat-icon> </button> @@ -22,11 +22,11 @@ [title]="uitextRemoveStructure"> <mat-icon>delete</mat-icon> </button> - <button type="button" mat-icon-button [disabled]="! enableUpButton" (click)="onMoveUpClick()" + <button *ngIf="showMoveArrows" type="button" mat-icon-button [disabled]="! enableUpButton" (click)="onMoveUpClick()" [title]="uitextMoveStructureUp"> <mat-icon>arrow_upward</mat-icon> </button> - <button type="button" mat-icon-button [disabled]="! enableDownButton" (click)="onMoveDownClick()" + <button *ngIf="showMoveArrows" type="button" mat-icon-button [disabled]="! enableDownButton" (click)="onMoveDownClick()" [title]="uitextMoveStructureDown"> <mat-icon>arrow_downward</mat-icon> </button> @@ -48,4 +48,4 @@ <select-field-line *ngIf="isSelectField(p)" [_select]=p> </select-field-line> </ng-template> -</mat-card-content> \ No newline at end of file +</mat-card-content> diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts index fa61d21cef01af81516a33cc19f3863aa7fc938a..293fce6e21ae0b137e9a6035b2d20e163739e269 100644 --- a/src/app/components/field-set/field-set.component.ts +++ b/src/app/components/field-set/field-set.component.ts @@ -28,6 +28,12 @@ export class FieldSetComponent implements DoCheck { /** number of children to add when clicking "add" or "clone" button */ public childrenToAdd = 1; + /** flag to show/hide "add" button (and "how many children" select */ + public showAddChildren: boolean = true; + + /** flag to show/hide "move up" and "move down" buttons */ + public showMoveArrows: boolean = true; + @Input() public set fieldSet(fs: FieldSet) { this._fieldSet = fs; diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts index 9d5664cf7bf0817ad0a61a60a960a24a2d383fae..aa9c6639ddf3c27535e57db4832d5a894f6531d7 100644 --- a/src/app/components/fieldset-container/fieldset-container.component.ts +++ b/src/app/components/fieldset-container/fieldset-container.component.ts @@ -32,13 +32,13 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit { return this._isValid.value; } @Input() - private _container: FieldsetContainer; + protected _container: FieldsetContainer; /** * liste des composants FieldSet enfants */ @ViewChildren(FieldSetComponent) - private _fieldsetComponents: QueryList<FieldSetComponent>; + protected _fieldsetComponents: QueryList<FieldSetComponent>; /** * flag de validité des FieldSet enfants @@ -83,8 +83,8 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit { * Ajoute un nouveau sous-nub (Structure, PabCloisons, YAXN… selon le cas) * dans un nouveau fieldset */ - private addSubNub(after: FieldSet, clone: boolean = false) { - const newFs = this._container.addFromTemplate(0, after.indexAsKid()); + protected addSubNub(after: FieldSet, clone: boolean = false) { + const newFs = this._container.addFromTemplate(after.indexAsKid()); if (clone) { const prms = after.backupParameters(); // replace in-place to change properties (overkill) @@ -106,7 +106,7 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit { } } - private onFieldsetListChange() { + protected onFieldsetListChange() { } public ngAfterViewInit() { @@ -204,7 +204,7 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit { * clic sur le bouton "ajouter une structure" */ public onAddStructureClick() { - this._container.addFromTemplate(0, 0); + this._container.addFromTemplate(0); this.validChange.emit(); } diff --git a/src/app/components/fixedvar-results/fixed-results.component.ts b/src/app/components/fixedvar-results/fixed-results.component.ts index 7fb67ea7f92d6c3ca87749fdad152aaff2fe77b3..ae76c3e1989e993a78305a906f1a5d91c776ca21 100644 --- a/src/app/components/fixedvar-results/fixed-results.component.ts +++ b/src/app/components/fixedvar-results/fixed-results.component.ts @@ -95,7 +95,7 @@ export class FixedResultsComponent extends ResultsComponentDirective { let label = this.formattedLabel(fp); const nub = fp.paramDefinition.parentNub; // add child type and position before label - if (nub && nub.parent && nub.parent.childrenType) { + if (nub && nub.parent && nub.intlType) { const pos = nub.findPositionInParent(); // label = this.intlService.localizeText("INFO_OUVRAGE") + " n°" + (pos + 1) + ": " + label; const cn = capitalize(this.intlService.childName(nub)); @@ -198,7 +198,7 @@ export class FixedResultsComponent extends ResultsComponentDirective { let label = this.formattedLabel(fp); const nub = fp.paramDefinition.parentNub; // add child type and position before label - if (nub && nub.parent && nub.parent.childrenType) { + if (nub && nub.parent && nub.intlType) { const pos = nub.findPositionInParent(); const cn = capitalize(this.intlService.childName(nub)); label = sprintf(this.intlService.localizeText("INFO_STUFF_N"), cn) diff --git a/src/app/components/generic-calculator/calculator.component.html b/src/app/components/generic-calculator/calculator.component.html index a560138f592c5d44b3a3b8263fda1e3632cb05ab..a61455db06fc8271a318e1955d57b18ba30bcfee 100644 --- a/src/app/components/generic-calculator/calculator.component.html +++ b/src/app/components/generic-calculator/calculator.component.html @@ -95,7 +95,8 @@ <div *ngIf="isPB" id="pb-form-container" [hidden]="! showPBInputData" fxFlex.gt-sm="1 0 400px" fxFlex.lt-md="1 0 500px" - fxFlex.lt-sm="1 0 300px"> + fxFlex.lt-sm="1 0 300px" + fxLayout="column"> <ng-template ngFor let-fe [ngForOf]="formElements"> <field-set *ngIf="isFieldset(fe)" @@ -104,11 +105,18 @@ (tabPressed)="onTabPressed($event)"> </field-set> - <fieldset-container *ngIf="isFieldsetContainer(fe)" + <structure-fieldset-container *ngIf="isStructureFieldsetContainer(fe)" [style.display]="getElementStyleDisplay(fe.id)" [_container]=fe (radio)=onRadioClick($event) (validChange)=onElementValid() (inputChange)=onInputChange($event) (tabPressed)="onTabPressed($event)"> - </fieldset-container> + </structure-fieldset-container> + + <basin-fieldset-container *ngIf="isBasinFieldsetContainer(fe)" + [style.display]="getElementStyleDisplay(fe.id)" [_container]=fe + (radio)=onRadioClick($event) (validChange)=onElementValid() + (inputChange)=onInputChange($event) (tabPressed)="onTabPressed($event)" + fxFlex="1 0 auto"> + </basin-fieldset-container> </ng-template> </div> @@ -130,6 +138,13 @@ fxFlex="1 0 auto"> </fieldset-container> + <structure-fieldset-container *ngIf="isStructureFieldsetContainer(fe)" + [style.display]="getElementStyleDisplay(fe.id)" [_container]=fe + (radio)=onRadioClick($event) (validChange)=onElementValid() (inputChange)=onInputChange($event) + (tabPressed)="onTabPressed($event)" + fxFlex="1 0 auto"> + </structure-fieldset-container> + <pab-table *ngIf="isPabTable(fe)" [pabTable]=fe (radio)=onRadioClick($event) (validChange)=onElementValid() (inputChange)=onInputChange($event) fxFlex="1 0 auto"> @@ -206,4 +221,4 @@ </form> -</mat-card> \ No newline at end of file +</mat-card> diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts index 06c2735f6bd9098e41525317badc6a6388942691..901fa13436d5ae5e5bf4f864a65dafa7a1c0e1e0 100644 --- a/src/app/components/generic-calculator/calculator.component.ts +++ b/src/app/components/generic-calculator/calculator.component.ts @@ -199,9 +199,35 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe return fe instanceof FieldSet; } - /** détermine si un FormulaireElement est du type FieldsetContainer */ + /** détermine si un FormulaireElement est du type FieldsetContainer et générique */ public isFieldsetContainer(fe: any): boolean { - return fe instanceof FieldsetContainer; + if (fe instanceof FieldsetContainer) { + switch (fe.template.calcTypeFromConfig) { + case CalculatorType.Structure: + case CalculatorType.PbBassin: + return false; + + default: + return true; + } + } + return false; + } + + /** détermine si un FormulaireElement est du type FieldsetContainer contenant des structures */ + public isStructureFieldsetContainer(fe: any): boolean { + if (fe instanceof FieldsetContainer) { + return fe.template.calcTypeFromConfig === CalculatorType.Structure; + } + return false; + } + + /** détermine si un FormulaireElement est du type FieldsetContainer contenant des bassins */ + public isBasinFieldsetContainer(fe: any): boolean { + if( fe instanceof FieldsetContainer){ + return fe.template.calcTypeFromConfig === CalculatorType.PbBassin; + } + return false; } /** détermine si un FormulaireElement est du type PabTable */ @@ -480,7 +506,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe if (!this._formulaire.calculateDisabled) { // all fieldsets must be valid res = true; - if (this._fieldsetComponents !== undefined) { + if (this._fieldsetComponents?.length > 0) { res = res && this._fieldsetComponents.reduce( // callback ( @@ -496,10 +522,10 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe return acc && fieldset.isValid; } // valeur initiale - , this._fieldsetComponents.length > 0); + , true); } // all fieldset containers must be valid - if (this._fieldsetContainerComponents !== undefined) { + if (this._fieldsetContainerComponents?.length > 0) { res = res && this._fieldsetContainerComponents.reduce<boolean>( // callback ( diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts index 3154a308fbdf1b9a83750ca2668e046f5bf5f578..d95168143cb67edb5fff3ffe00586e65fe60ad4f 100644 --- a/src/app/components/generic-input/generic-input.component.ts +++ b/src/app/components/generic-input/generic-input.component.ts @@ -36,7 +36,7 @@ export abstract class GenericInputComponentDirective implements OnChanges { id = param.symbol; // if inside a child Nub, prefix with child position to disambiguate const nub = param.paramDefinition.parentNub; - if (nub && nub.parent && nub.parent.childrenType) { + if (nub && nub.parent && nub.intlType) { id = nub.findPositionInParent() + "_" + id; } } diff --git a/src/app/components/param-computed/param-computed.component.ts b/src/app/components/param-computed/param-computed.component.ts index 5525dcf7ab3cc4aa308f25491f006f7e755e4b87..85d6f4070e3d55302ecb29f89e3b812c587b79ec 100644 --- a/src/app/components/param-computed/param-computed.component.ts +++ b/src/app/components/param-computed/param-computed.component.ts @@ -32,7 +32,7 @@ export class ParamComputedComponent { let id = "calc_" + this.param.symbol; // if inside a child Nub, prefix with child position to disambiguate const nub = this.param.paramDefinition.parentNub; - if (nub && nub.parent && nub.parent.childrenType) { + if (nub && nub.parent && nub.intlType) { id = nub.findPositionInParent() + "_" + id; } return id; diff --git a/src/app/components/param-link/param-link.component.ts b/src/app/components/param-link/param-link.component.ts index 2571d9ec7cd0fab288fc147345c7e5ced4352a59..8e03ffeb2f39f59a2654949b2b702cf184f3deff 100644 --- a/src/app/components/param-link/param-link.component.ts +++ b/src/app/components/param-link/param-link.component.ts @@ -40,7 +40,7 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy { let id = "linked_" + this.param.symbol; // if inside a child Nub, prefix with child position to disambiguate const nub = this.param.paramDefinition.parentNub; - if (nub && nub.parent && nub.parent.childrenType) { + if (nub && nub.parent && nub.intlType) { id = nub.findPositionInParent() + "_" + id; } return id; diff --git a/src/app/components/param-values/param-values.component.ts b/src/app/components/param-values/param-values.component.ts index eeb36a21d27a6d06cca3f76a449ee6976d181f00..8f3959026a6006caa0f09cf6fcd0778d123f8988 100644 --- a/src/app/components/param-values/param-values.component.ts +++ b/src/app/components/param-values/param-values.component.ts @@ -47,7 +47,7 @@ export class ParamValuesComponent implements AfterViewInit, Observer { let id = "var_" + this.param.symbol; // if inside a child Nub, prefix with child position to disambiguate const nub = this.param.paramDefinition.parentNub; - if (nub && nub.parent && nub.parent.childrenType) { + if (nub && nub.parent && nub.intlType) { id = nub.findPositionInParent() + "_" + id; } return id; diff --git a/src/app/components/pb-schema/pb-schema.component.ts b/src/app/components/pb-schema/pb-schema.component.ts index f294c65e1c3933f926c54bbad53077be75cde7df..7ac6b72a7bc7a7ac2c2d6414e1bd11deccce26f7 100644 --- a/src/app/components/pb-schema/pb-schema.component.ts +++ b/src/app/components/pb-schema/pb-schema.component.ts @@ -4,8 +4,8 @@ import { MatDialog } from "@angular/material/dialog"; import screenfull from "screenfull"; import { - PreBarrage, PbBassin, PbBassinParams, PbCloison, Observer, IObservable, MermaidUtil - } from "jalhyd"; + PbBassin, PbCloison, Observer, IObservable, MermaidUtil +} from "jalhyd"; import mermaid from "mermaid"; @@ -19,9 +19,9 @@ import { FormulairePrebarrage } from "../../formulaire/definition/form-prebarrag import { AppComponent } from "../../app.component"; import { fv } from "app/util"; -import { FormulaireNode } from "app/formulaire/elements/formulaire-node"; import { ServiceFactory } from "app/services/service-factory"; import { DefinedBoolean } from "app/definedvalue/definedboolean"; +import { PrebarrageService, PrebarrageServiceEvents } from "app/services/prebarrage.service"; /** * The interactive schema for calculator type "PreBarrage" (component) @@ -47,10 +47,6 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni /** flag de validité du composant */ private _isValid: DefinedBoolean; - private upstreamId = "amont"; - - private downstreamId = "aval"; - /** événément de changement de validité */ @Output() private validChange = new EventEmitter(); @@ -59,9 +55,6 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni @Output() private nodeSelected = new EventEmitter(); - /** underlying PB */ - private model: PreBarrage; - /** Latest clicked item: a PbCloison, a PbBassin or undefined if river "Upstream" or "Downstream" was clicked */ private _selectedItem: PbCloison | PbBassin; @@ -72,10 +65,12 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni @Inject(forwardRef(() => GenericCalculatorComponent)) private calculatorComponent: GenericCalculatorComponent, private i18nService: I18nService, private hotkeysService: HotkeysService, - private newPbCloisonDialog: MatDialog + private newPbCloisonDialog: MatDialog, + private predamService: PrebarrageService ) { this.hotkeysService.add(new Hotkey("del", AppComponent.onHotkey(this.removeOnHotkey, this))); this._isValid = new DefinedBoolean(); + this.predamService.changeEventEmitter.subscribe(event => this.onPredamServiceEvent(event)); } /** tracks the fullscreen state */ @@ -102,10 +97,6 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni /** called when fullscreen state changes */ public fullscreenChange(isFullscreen: boolean) { } - public get selectedItem(): any { - return this._selectedItem; - } - public ngAfterContentInit(): void { mermaid.initialize({ flowchart: { @@ -116,7 +107,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni this.nativeElement = this.schema.nativeElement; this.render(); // restore previously selected item - this._selectedItem = this.pbSchema.form.selectedItem; + this._selectedItem = this.predamService.setSelectedNub(this.pbSchema.form.selectedItem); if (this._selectedItem !== undefined) { // @WARNING clodo timeout to prevent ExpressionChangedAfterItHasBeenCheckedError // and select schema node after schema is refreshed by ngAfterViewInit() @@ -185,36 +176,38 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni this.pbSchema.wallsSuffixes = {}; const def: string[] = [ "graph TB" ]; + const pbModel = this.predamService.model; + // river upstream / downstream let upstreamLabel = this.i18nService.localizeText("INFO_LIB_AMONT"); let downstreamLabel = this.i18nService.localizeText("INFO_LIB_AVAL"); // add result data Z and Q, if any if ( - this.model?.result?.resultElements - && this.model.result.resultElements[0]?.ok + pbModel.result?.resultElements + && pbModel.result.resultElements[0]?.ok ) { // when a parameter is variating, index of the variating parameter // values to build the data from const form = this.calculatorComponent.formulaire as FormulairePrebarrage; const idx = form.pbResults.variableIndex; - const qValue = this.model.prms.Q.isCalculated - ? this.model.result.resultElements[idx].vCalc + const qValue = pbModel.prms.Q.isCalculated + ? pbModel.result.resultElements[idx].vCalc : ( - this.model.prms.Q.hasMultipleValues - ? this.model.prms.Q.getInferredValuesList(this.model.variatingLength())[idx] - : this.model.prms.Q.singleValue + pbModel.prms.Q.hasMultipleValues + ? pbModel.prms.Q.getInferredValuesList(pbModel.variatingLength())[idx] + : pbModel.prms.Q.singleValue ); // upstream upstreamLabel += "<br>"; upstreamLabel += "Q = " + fv(qValue); upstreamLabel += "<br>"; upstreamLabel += "Z = " + fv( - this.model.prms.Z1.isCalculated - ? this.model.result.resultElements[idx].vCalc + pbModel.prms.Z1.isCalculated + ? pbModel.result.resultElements[idx].vCalc : ( - this.model.prms.Z1.hasMultipleValues - ? this.model.prms.Z1.getInferredValuesList(this.model.variatingLength())[idx] - : this.model.prms.Z1.singleValue + pbModel.prms.Z1.hasMultipleValues + ? pbModel.prms.Z1.getInferredValuesList(pbModel.variatingLength())[idx] + : pbModel.prms.Z1.singleValue ) ); // downstream @@ -222,14 +215,14 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni downstreamLabel += "Q = " + fv(qValue); downstreamLabel += "<br>"; downstreamLabel += "Z = " + fv( - this.model.prms.Z2.hasMultipleValues - ? this.model.prms.Z2.getInferredValuesList(this.model.variatingLength())[idx] - : this.model.prms.Z2.singleValue - ); + pbModel.prms.Z2.hasMultipleValues + ? pbModel.prms.Z2.getInferredValuesList(pbModel.variatingLength())[idx] + : pbModel.prms.Z2.singleValue + ); } // add to graph definition - def.push(`${this.upstreamId}("${upstreamLabel}")`); - def.push(`${this.downstreamId}("${downstreamLabel}")`); + def.push(`${this.predamService.upstreamId}("${upstreamLabel}")`); + def.push(`${this.predamService.downstreamId}("${downstreamLabel}")`); // styles def.push("classDef wall fill:#e8e8e8,stroke-width:0;"); @@ -240,7 +233,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni def.push("classDef node-highlighted-error fill:#d92f03;"); // irstea-rouille (material "accent"), 900 const sortedWalls: PbCloison[] = []; - for (const c of this.model.children) { + for (const c of pbModel.children) { if (c instanceof PbBassin) { def.push(`${c.uid}("${this.itemDescriptionWithResultData(c)}")`); // rounded edges def.push(`class ${c.uid} basin;`); @@ -253,8 +246,8 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni // sort then draw walls sortedWalls.sort(this.triCloisonsGaucheDroite); for (const c of sortedWalls) { - const upstreamBasinId = c.bassinAmont === undefined ? this.upstreamId : c.bassinAmont.uid; - const downstreamBasinId = c.bassinAval === undefined ? this.downstreamId : c.bassinAval.uid; + const upstreamBasinId = c.bassinAmont === undefined ? this.predamService.upstreamId : c.bassinAmont.uid; + const downstreamBasinId = c.bassinAval === undefined ? this.predamService.downstreamId : c.bassinAval.uid; // record this wall const basinsPair = upstreamBasinId + "-" + downstreamBasinId; if (! (basinsPair in this.existingWalls)) { @@ -307,22 +300,6 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni return (sommeA <= sommeB ? -1 : 1); } - /** - * check if a list of (Mermaid transformed) ids matches a given id - * @param ids ids to transform the Mermaid way - * @param itemId id to find - */ - private matchMermaidIds(ids: string[], itemId: string): boolean { - return ids.find(id => MermaidUtil.isMermaidEqualIds(id, itemId)) !== undefined; - } - - /** - * return upstream (and downstream) basin objet - */ - private get upstreamBassin(): PbBassin { - return (this.model as unknown) as PbBassin; - } - /** * @param item DOM element */ @@ -332,15 +309,11 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni this.clearHighlightedItems(); item.classList.add("node-highlighted"); // find what was clicked - if (this.matchMermaidIds([this.upstreamId, this.downstreamId], item.id)) { - this._selectedItem = this.upstreamBassin; - } else { - this._selectedItem = this.model.findChild(item.id); - } + this._selectedItem = this.predamService.setSelectedNub(this.predamService.findFromItemId(item.id)); this.highlightErrorItems(item.id); // show proper form and hide results this.nodeSelected.emit({ - node: this._selectedItem === this.upstreamBassin ? undefined : this._selectedItem + node: this._selectedItem === this.predamService.upstreamBassin ? undefined : this._selectedItem }); // exit fullscreen this.exitFullscreen(); @@ -445,7 +418,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni if (!done) { if (MermaidUtil.isMermaidEqualIds("amont", item.id)) { this.selectNode(item); - this._selectedItem = this.upstreamBassin; + this._selectedItem = this.predamService.setSelectedNub(this.predamService.upstreamBassin); done = true; } } @@ -454,7 +427,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni // at this time @Input data is supposed to be already populated public ngOnInit() { - this.model = this.pbSchema.pb; + this.predamService.model = this.pbSchema.pb; } public get enableAddItems(): boolean { @@ -462,7 +435,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni } public get enableRemoveButton() { - if (this._selectedItem === this.upstreamBassin) { + if (this._selectedItem === this.predamService.upstreamBassin) { return false; } // if deleting a PbCloison would replace it by a new one at @@ -485,24 +458,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni /** Removes a basin or wall, and all related items */ public onRemoveClick() { - this.model.deleteChild(this._selectedItem.findPositionInParent()); - // never let an unconnected basin ! (not done in model to prevent unwanted - // automatic child addition when clearing children) - if (this._selectedItem instanceof PbCloison) { - const emptyFields: boolean = ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit; - // if no downstream connections remain, connect to river downstream - if (this._selectedItem.bassinAmont?.cloisonsAval.length === 0) { - this.model.addChild(new PbCloison(this._selectedItem.bassinAmont, undefined, undefined, emptyFields)); - } - // if no upstream connections remain, connect to river upstream - if (this._selectedItem.bassinAval?.cloisonsAmont.length === 0) { - this.model.addChild(new PbCloison(undefined, this._selectedItem.bassinAval, undefined, emptyFields)); - } - } - this.clearResults(); - this.unselect(); - this.refreshWithSelection(); - this.calculatorComponent.showPBInputData = true; + this.predamService.deleteSelected(ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit); } public get uitextRemove() { @@ -517,18 +473,18 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni } public get enableCopyButton() { - return (this._selectedItem !== undefined && this._selectedItem instanceof PbCloison); + // disable copy for upstream/downstream basins + return this._selectedItem !== this.predamService.upstreamBassin; } - /** Copies a wall */ + /** Copies a wall or a basin */ public onCopyClick() { - const wall = this._selectedItem as PbCloison; - const wallCopy = new PbCloison(wall.bassinAmont, wall.bassinAval, undefined, ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit); - wallCopy.loadObjectRepresentation(wall.objectRepresentation()); - this.model.addChild(wallCopy); - this.clearResults(); - this.refreshWithSelection(wallCopy.uid); - this.calculatorComponent.showPBInputData = true; + if (this._selectedItem instanceof PbCloison) { + this.predamService.copySelectedWall(ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit); + } + else { + this.predamService.copySelectedBasin(ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit); + } } public get uitextCopy() { @@ -537,11 +493,30 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni /** Adds a new lone basin */ public onAddBasinClick() { - const newBasin = new PbBassin(new PbBassinParams(20, 99, ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit)); - this.model.addChild(newBasin); - this.clearResults(); - this.refreshWithSelection(newBasin.uid); - this.calculatorComponent.showPBInputData = true; + this.predamService.addBasin(ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit); + } + + /** + * process events from PrebarrageService + */ + private onPredamServiceEvent(event: any) { + const nub = event.data; + switch (event.id) { + case PrebarrageServiceEvents.BASIN_WALL_ADDED: + case PrebarrageServiceEvents.BASIN_WALL_COPIED: + case PrebarrageServiceEvents.MOVE_BASIN: + this.clearResults(); + this.refreshWithSelection(nub.uid); + this.calculatorComponent.showPBInputData = true; + break; + + case PrebarrageServiceEvents.BASIN_WALL_REMOVED: + this.clearResults(); + this.unselect(); + this.refreshWithSelection(); + this.calculatorComponent.showPBInputData = true; + break; + } } public get uitextAddBasin() { @@ -549,7 +524,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni } public get enableAddWallButton(): boolean { - return (this.model.bassins.length > 0); + return this.predamService.hasBasins; } /** Adds a new lone wall, opening a modal to choose connected basins */ @@ -559,7 +534,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni DialogNewPbCloisonComponent, { data: { - basins: this.model.bassins + basins: this.predamService.bassins }, disableClose: true } @@ -567,16 +542,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni // apply modifications dialogRef.afterClosed().subscribe(result => { if (result.up !== undefined && result.down !== undefined) { - const wall = new PbCloison( - result.up === 0 ? undefined : this.model.bassins[result.up - 1], - result.down === 0 ? undefined : this.model.bassins[result.down - 1], - undefined, - ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit - ); - this.model.addChild(wall); - this.clearResults(); - this.refreshWithSelection(wall.uid); - this.calculatorComponent.showPBInputData = true; + this.predamService.addWall(result.up, result.down, ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit); } }); } @@ -588,19 +554,15 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni public get enableUpButton() { return ( this._selectedItem instanceof PbBassin - && this.model.findBasinPosition(this._selectedItem.uid) !== 0 - && this.isStandaloneBasin(this._selectedItem) + && this.predamService.findBasinPosition(this._selectedItem.uid) !== 0 + && this.predamService.isStandaloneBasin(this._selectedItem) ); } public onMoveBasinUpClick() { if (this._selectedItem instanceof PbBassin) { - this.model.moveBasin(this._selectedItem.uid, this.model.findBasinPosition(this._selectedItem.uid) - 1); + this.predamService.moveSelectedBasinUp(); } - const basin = this._selectedItem; // utilité ? - this.clearResults(); - this.refreshWithSelection(this._selectedItem.uid); - this.calculatorComponent.showPBInputData = true; } public get uitextMoveBasinUp() { @@ -610,19 +572,15 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni public get enableDownButton() { return ( this._selectedItem instanceof PbBassin - && this.model.findBasinPosition(this._selectedItem.uid) !== this.model.bassins.length - 1 - && this.isStandaloneBasin(this._selectedItem) + && !this.predamService.isLastBasin(this._selectedItem.uid) + && this.predamService.isStandaloneBasin(this._selectedItem) ); } public onMoveBasinDownClick() { if (this._selectedItem instanceof PbBassin) { - this.model.moveBasin(this._selectedItem.uid, this.model.findBasinPosition(this._selectedItem.uid) + 1); + this.predamService.moveSelectedBasinDown(); } - const basin = this._selectedItem; - this.clearResults(); - this.refreshWithSelection(this._selectedItem.uid); - this.calculatorComponent.showPBInputData = true; } public get uitextMoveBasinDown() { @@ -665,29 +623,13 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni image.src = blobURL; } - /** - * Returns true if given basin is either connected to nothing, or only to - * river upstream or downstream - */ - private isStandaloneBasin(basin: PbBassin) { - return ( - ( - basin.cloisonsAmont.length === 0 - || basin.cloisonsAmont.map(c => c.bassinAmont).every(e => e === undefined) - ) && ( - basin.cloisonsAval.length === 0 - || basin.cloisonsAval.map(c => c.bassinAval).every(e => e === undefined) - ) - ); - } - /** * Computes the global Pab validity : validity of every cell of every row */ private updateValidity() { // check that at least 1 basin is present and a route from river // upstream to river downstream exists (2nd check includes 1st) - this._isValid.value = this.model.hasUpDownConnection() && !this.model.hasBasinNotConnected(); + this._isValid.value = this.predamService.isValid(); if (this._isValid.changed) { this.validChange.emit(); @@ -707,32 +649,17 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni }); } - /** - * turn 'aval', 'amont' and Mermaid ids to nub real uid - */ - private toNubUid(id: string): string { - if (id !== undefined && id !== null) { - if (this.matchMermaidIds([this.upstreamId, this.downstreamId], id)) { - return this.model.uid; - } - if (id.startsWith("flowchart-")) { // Mermaid style id (ie. flowchart-id-xx) - return id.split('-')[1]; - } - } - return id; - } - private highlightErrorItems(selectedUid: string) { this.nativeElement.querySelectorAll("g.node").forEach(item => { item.classList.remove("node-error"); item.classList.remove("node-highlighted-error"); }); const invalidUids: string[] = this.pbSchema.form.checkParameters(); - selectedUid = this.toNubUid(selectedUid); + selectedUid = this.predamService.toNubUid(selectedUid); if (invalidUids.length > 0) { this.nativeElement.querySelectorAll("g.node").forEach(item => { // in this case, item is a HTML node of the SVG schema which id is a nub uid - const itemId = this.toNubUid(item.id); + const itemId = this.predamService.toNubUid(item.id); if (invalidUids.includes(itemId)) { // if current item is among invalid ones if (selectedUid === itemId) { // if current item is the selected item @@ -747,7 +674,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni private unselect() { // console.debug(`PbSchemaComponent.unselect()`); - this._selectedItem = undefined; + this._selectedItem = this.predamService.setSelectedNub(undefined); this.clearHighlightedItems(); this.nodeSelected.emit({}); // nothing selected } @@ -761,17 +688,17 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni * Refreshes the schema; if uid is given, selects the node having this * nub uid, else keeps previous selection */ - private refreshWithSelection(uid ?: string) { + private refreshWithSelection(uid?: string) { // console.debug(`PbSchemaComponent.refreshWithSelection(${uid})`); // remember previously selected node const selectedNodeUID = this._selectedItem?.uid; this.refresh(); // select a specific node on the schema if (uid !== undefined) { - this.selectNodeOnSchema(this.model.findChild(uid)); + this.selectNodeOnSchema(this.predamService.findChild(uid)); } else if (selectedNodeUID !== undefined) { // re-select previously selected node - this.selectNodeOnSchema(this.model.findChild(selectedNodeUID)); + this.selectNodeOnSchema(this.predamService.findChild(selectedNodeUID)); } } diff --git a/src/app/components/structure-fieldset-container/structure-fieldset-container.component.ts b/src/app/components/structure-fieldset-container/structure-fieldset-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..34b9a3bbabc1b14e4ef87fcffa44b47e48d4efd6 --- /dev/null +++ b/src/app/components/structure-fieldset-container/structure-fieldset-container.component.ts @@ -0,0 +1,20 @@ +import { Component } from "@angular/core"; + +import { FieldSet } from "../../formulaire/elements/fieldset"; +import { I18nService } from "../../services/internationalisation.service"; +import { ApplicationSetupService } from "../../services/app-setup.service"; +import { FieldsetContainerComponent } from "../fieldset-container/fieldset-container.component"; + +@Component({ + selector: "structure-fieldset-container", + templateUrl: "../fieldset-container/fieldset-container.component.html", + styleUrls: [ + "../fieldset-container/fieldset-container.component.scss" + ] +}) +export class StructureFieldsetContainerComponent extends FieldsetContainerComponent { + + constructor(i18nService: I18nService, appSetupService: ApplicationSetupService) { + super(i18nService, appSetupService); + } +} diff --git a/src/app/formulaire/definition/form-macrorugo-compound.ts b/src/app/formulaire/definition/form-macrorugo-compound.ts index b28b59b7d21e335b8605e9d7b84ce1851eb4836d..ec470e645ca81406ae5e353736467e77427508ba 100644 --- a/src/app/formulaire/definition/form-macrorugo-compound.ts +++ b/src/app/formulaire/definition/form-macrorugo-compound.ts @@ -98,7 +98,7 @@ export class FormulaireMacrorugoCompound extends FormulaireRepeatableFieldset { if (elt instanceof FieldsetContainer) { elt.clearKids(); for (const c of this.currentNub.getChildren()) { - elt.addFromTemplate(0, undefined, c); + elt.addFromTemplate(undefined, c); } } } diff --git a/src/app/formulaire/definition/form-prebarrage.ts b/src/app/formulaire/definition/form-prebarrage.ts index 8f317caba59b6b5218a157daccf1b6cc6ffd2884..95e5f65498b3ad929c00730266a3fa0ccb33d221 100644 --- a/src/app/formulaire/definition/form-prebarrage.ts +++ b/src/app/formulaire/definition/form-prebarrage.ts @@ -147,6 +147,24 @@ export class FormulairePrebarrage extends FormulaireFixedVar { this.basinForm.preparseConfig(this.basinFormConfig); this.basinForm.parseConfig(this.basinFormConfig); ServiceFactory.formulaireService.updateFormulaireLocalisation(this.basinForm); + // add fieldsets for existing basins + if (node.parent.bassins.length > 0) { + for (const bassin of node.parent.bassins) { + for (const e of this.basinForm.allFormElements) { + if (e instanceof FieldsetContainer) { // @TODO manage many containers one day ? + e.addFromTemplate(undefined, bassin, { resetResults: false }); + } + } + } + } else { + // if there was no existing basin, add a default one + for (const e of this.basinForm.allFormElements) { + if (e instanceof FieldsetContainer) { + e.addFromTemplate(undefined, undefined, { resetResults: false }); + break; + } + } + } this.showFormElements(this.basinForm); } else if (node instanceof PbCloison) { @@ -163,7 +181,7 @@ export class FormulairePrebarrage extends FormulaireFixedVar { for (const struct of node.structures) { for (const e of this.wallForm.allFormElements) { if (e instanceof FieldsetContainer) { // @TODO manage many containers one day ? - e.addFromTemplate(0, undefined, struct, { resetResults: false }); + e.addFromTemplate(undefined, struct, { resetResults: false }); } } } @@ -171,7 +189,7 @@ export class FormulairePrebarrage extends FormulaireFixedVar { // if there was no existing structure, add a default one for (const e of this.wallForm.allFormElements) { if (e instanceof FieldsetContainer) { - e.addFromTemplate(0, undefined, undefined, { resetResults: false }); + e.addFromTemplate(undefined, undefined, { resetResults: false }); break; } } diff --git a/src/app/formulaire/elements/fieldset-container.ts b/src/app/formulaire/elements/fieldset-container.ts index a8c1ecbe60f98fabcfd8ad96e80465e1f23389c6..23b5488953e9c72500de7ecd8babd9bbacf86948 100644 --- a/src/app/formulaire/elements/fieldset-container.ts +++ b/src/app/formulaire/elements/fieldset-container.ts @@ -5,21 +5,23 @@ import { FormulaireNode } from "./formulaire-node"; import { Nub } from "jalhyd"; export class FieldsetContainer extends FormulaireElement { - private _templates: FieldsetTemplate[]; + private _template: FieldsetTemplate; public title: string; constructor(parent: FormulaireNode) { super(parent); - this._templates = []; } - private addTemplate(fst: FieldsetTemplate) { - this._templates.push(new FieldsetTemplate(fst)); + public get template(): FieldsetTemplate { + return this._template; } - public getTemplate(index: number): FieldsetTemplate { - return this._templates[index]; + public set template(fst: FieldsetTemplate) { + if (this._template !== undefined) { + throw new Error("template already set!"); + } + this._template = new FieldsetTemplate(fst); } public addFieldset(fs: FieldSet) { @@ -74,10 +76,8 @@ export class FieldsetContainer extends FormulaireElement { * @param nub attaches the given Nub to the new FieldSet * @param extra extra key-value pairs to add to the "newFieldset" event */ - public addFromTemplate(templateIndex: number, after?: number, nub?: Nub, extra?: any): FieldSet { - const templ: FieldsetTemplate = this._templates[templateIndex]; - - const inst: FieldSet = templ.instantiateTemplate(this, after, nub, extra); + public addFromTemplate(after?: number, nub?: Nub, extra?: any): FieldSet { + const inst: FieldSet = this._template.instantiateTemplate(this, after, nub, extra); this.updateLocalisation(); @@ -105,7 +105,7 @@ export class FieldsetContainer extends FormulaireElement { for (const t of templateNames) { for (const d of templates) { if (d.id === t) { - this.addTemplate(d); + this.template = d; } } } diff --git a/src/app/formulaire/elements/formulaire-node.ts b/src/app/formulaire/elements/formulaire-node.ts index b57bec770d2d128fc76e2fcd53a43370f0efd667..ebefb5e58d26bf1dc33a1144407a3ccb03250f54 100644 --- a/src/app/formulaire/elements/formulaire-node.ts +++ b/src/app/formulaire/elements/formulaire-node.ts @@ -95,7 +95,7 @@ export abstract class FormulaireNode implements IObservable { * @param kid FormulaireNode enfant dont on cherche l'indice * @return -1 si non trouvé, indice commençant à 0 sinon */ - private kidIndex(kid: FormulaireNode): number { + public kidIndex(kid: FormulaireNode): number { let n = 0; for (const k of this._kids) { if (k._uid === kid._uid) { diff --git a/src/app/formulaire/elements/ngparam.ts b/src/app/formulaire/elements/ngparam.ts index 188f87fd4b451d41b3ff11b9ffe5eb52a98f5579..e4dc902cd4034702c761634d145c30781db189b2 100644 --- a/src/app/formulaire/elements/ngparam.ts +++ b/src/app/formulaire/elements/ngparam.ts @@ -212,7 +212,7 @@ export class NgParameter extends InputField implements Observer { } public set radioConfig(rc: ParamRadioConfig) { - if (this._radioConfig === undefined) { + if (rc === undefined) { throw new Error("cannot set radio config to undefined!"); } this._radioConfig = rc; diff --git a/src/app/services/formulaire.service.ts b/src/app/services/formulaire.service.ts index 775e6ae0e1cc45f5c442857705dbc88d0f3b3c89..4c9e42baa9e5dc138d02427ff9d4d4ad1520bad8 100644 --- a/src/app/services/formulaire.service.ts +++ b/src/app/services/formulaire.service.ts @@ -388,7 +388,7 @@ export class FormulaireService extends Observable { for (const struct of f.currentNub.structures) { for (const e of f.allFormElements) { if (e instanceof FieldsetContainer) { // @TODO manage many containers one day ? - e.addFromTemplate(0, undefined, struct); + e.addFromTemplate(undefined, struct); } } } @@ -400,7 +400,7 @@ export class FormulaireService extends Observable { for (const c of f.currentNub.getChildren()) { for (const e of f.allFormElements) { if (e instanceof FieldsetContainer) { // @TODO manage many containers one day ? - e.addFromTemplate(0, undefined, c); + e.addFromTemplate(undefined, c); } } } diff --git a/src/app/services/internationalisation.service.ts b/src/app/services/internationalisation.service.ts index 4b8f545f65dd4ebcdf9b9e7592c50920df7b95db..7dddda2a95c63a280013f2f72e74bdb618e055fb 100644 --- a/src/app/services/internationalisation.service.ts +++ b/src/app/services/internationalisation.service.ts @@ -334,8 +334,7 @@ export class I18nService extends Observable implements Observer { * @param short if true, will return short name */ public childName(nub: Nub, plural: boolean = false, short: boolean = false) { - const type: string = nub.parent.childrenType; - let k = "INFO_CHILD_TYPE_" + type.toUpperCase(); + let k = "INFO_CHILD_TYPE_" + nub.intlType.toUpperCase(); if (short) { k += "_SHORT"; } else if (plural) { diff --git a/src/app/services/prebarrage.service.ts b/src/app/services/prebarrage.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..915cc1c61c22714f1f34eb07a7bd678f5ea2e203 --- /dev/null +++ b/src/app/services/prebarrage.service.ts @@ -0,0 +1,305 @@ +import { EventEmitter, Injectable } from "@angular/core"; +import { MermaidUtil, PbBassin, PbBassinParams, PbCloison, PreBarrage } from "jalhyd"; + +export enum PrebarrageServiceEvents { + BASIN_WALL_ADDED, // a wall has been added + BASIN_WALL_COPIED, // a basin or wall has been copied + BASIN_WALL_REMOVED, // a basin or a wall has been removed + MOVE_BASIN, // a basin has been moved up or down +} + +/** + * service relatif au schéma de prébarrage + */ +@Injectable() +export class PrebarrageService { + /** predam data */ + private _model: PreBarrage; + + public readonly upstreamId = "amont"; + + public readonly downstreamId = "aval"; + + /** Latest clicked item: a PbCloison, a PbBassin or undefined if river "Upstream" or "Downstream" was clicked */ + private _selectedNub: PbCloison | PbBassin; + + private _changeEventEmitter = new EventEmitter(); + + public get model(): PreBarrage { + return this._model; + } + + public set model(pb: PreBarrage) { + this._model = pb; + } + + public get changeEventEmitter() { + return this._changeEventEmitter; + } + + /** + * return upstream (and downstream) basin object + */ + public get upstreamBassin(): PbBassin { + return (this._model as unknown) as PbBassin; + } + + public get selectedNub(): PbBassin | PbCloison { + return this._selectedNub; + } + + /** + * set selected nub in schema + */ + public setSelectedNub(n: PbBassin | PbCloison): PbBassin | PbCloison { + this._selectedNub = n; + return n; + } + + public deleteSelected(emptyFields: boolean) { + this.deleteByIndex(this._selectedNub.findPositionInParent(), emptyFields); + } + + /** + * remove nth basin + * @param bi basin index + */ + public deleteBasinByIndex(bi: number, emptyFields: boolean) { + const i = this.nthBasinIndex(bi); + this.deleteByIndex(i, emptyFields); + } + + private deleteByIndex(i: number, emptyFields: boolean) { + this._model.deleteChild(i); + // never let an unconnected basin ! (not done in model to prevent unwanted + // automatic child addition when clearing children) + if (this._selectedNub instanceof PbCloison) { + // if no downstream connections remain, connect to river downstream + if (this._selectedNub.bassinAmont?.cloisonsAval.length === 0) { + this._model.addChild(new PbCloison(this._selectedNub.bassinAmont, undefined, undefined, emptyFields)); + } + // if no upstream connections remain, connect to river upstream + if (this._selectedNub.bassinAval?.cloisonsAmont.length === 0) { + this._model.addChild(new PbCloison(undefined, this._selectedNub.bassinAval, undefined, emptyFields)); + } + } + this._changeEventEmitter.emit( + { + id: PrebarrageServiceEvents.BASIN_WALL_REMOVED, + } + ); + } + + /** + * compute index of nth basin + * @param bi nth basin + * @returns index in child list + */ + private nthBasinIndex(bi): number { + let nth: number = 0; + let res: number = 0; + for (const kid of this._model.children) { + if (kid instanceof PbBassin) { + if (nth == bi) { + return res; + } + nth++; + } + res++; + } + return -1; + } + + private copyWall(wall: PbCloison, emptyFields: boolean): PbCloison { + const wallCopy = new PbCloison(wall.bassinAmont, wall.bassinAval, undefined, emptyFields); + wallCopy.loadObjectRepresentation(wall.objectRepresentation()); + this._model.addChild(wallCopy); + return wallCopy; + } + + public copySelectedWall(emptyFields: boolean) { + const wall = this._selectedNub as PbCloison; + const wallCopy = new PbCloison(wall.bassinAmont, wall.bassinAval, undefined, emptyFields); + wallCopy.loadObjectRepresentation(wall.objectRepresentation()); + this._model.addChild(wallCopy); + this._changeEventEmitter.emit( + { + id: PrebarrageServiceEvents.BASIN_WALL_COPIED, + data: wallCopy + } + ); + } + + public addBasin(emptyFields: boolean) { + const newBasin = new PbBassin(new PbBassinParams(20, 99, emptyFields)); + this._model.addChild(newBasin); + this._changeEventEmitter.emit( + { + id: PrebarrageServiceEvents.BASIN_WALL_ADDED, + data: newBasin + } + ); + } + + public copySelectedBasin(emptyFields: boolean) { + this.copyBasin(this._selectedNub as PbBassin, emptyFields); + } + + /** + * copy nth basin + * @param bi basin index + */ + public copyBasinByIndex(bi: number, emptyFields: boolean) { + const i = this.nthBasinIndex(bi); + this.copyBasin(this._model.children[i] as PbBassin, emptyFields); + } + + private copyBasin(basin: PbBassin, emptyFields: boolean) { + const basinCopy = new PbBassin(new PbBassinParams(20, 99, emptyFields)); + basinCopy.loadObjectRepresentation(basin.objectRepresentation()); + this._model.addChild(basinCopy); + this._changeEventEmitter.emit( + { + id: PrebarrageServiceEvents.BASIN_WALL_COPIED, + data: basinCopy + } + ); + } + + public get hasBasins(): boolean { + return this._model.bassins.length > 0; + } + + public get bassins(): PbBassin[] { + return this._model.bassins; + } + + public addWall(upstreamIndex: number, downstreamIndex: number, emptyFields: boolean) { + const wall = new PbCloison( + upstreamIndex === 0 ? undefined : this._model.bassins[upstreamIndex - 1], + downstreamIndex === 0 ? undefined : this._model.bassins[downstreamIndex - 1], + undefined, + emptyFields + ); + this._model.addChild(wall); + this._changeEventEmitter.emit( + { + id: PrebarrageServiceEvents.BASIN_WALL_ADDED, + data: wall + } + ); + } + + /** + * Returns true if given basin is either connected to nothing, or only to + * river upstream or downstream + */ + public isStandaloneBasin(basin: PbBassin) { + return ( + ( + basin.cloisonsAmont.length === 0 + || basin.cloisonsAmont.map(c => c.bassinAmont).every(e => e === undefined) + ) && ( + basin.cloisonsAval.length === 0 + || basin.cloisonsAval.map(c => c.bassinAval).every(e => e === undefined) + ) + ); + } + + /** + * @param uid nub uid + */ + public findBasinPosition(uid: string): number { + return this._model.findBasinPosition(uid); + } + + /** + * @param uid nub uid + */ + private moveBasinUp(uid: string) { + this._model.moveBasin(uid, this._model.findBasinPosition(uid) - 1); + } + + public moveSelectedBasinUp() { + const uid = this._selectedNub.uid; + this.moveBasinUp(uid); + this._changeEventEmitter.emit( + { + id: PrebarrageServiceEvents.MOVE_BASIN, + data: this._selectedNub + } + ); + } + + /** + * @param uid nub uid + */ + private moveBasinDown(uid: string) { + this._model.moveBasin(uid, this._model.findBasinPosition(uid) + 1); + } + + public moveSelectedBasinDown() { + const uid = this._selectedNub.uid; + this.moveBasinDown(uid); + this._changeEventEmitter.emit( + { + id: PrebarrageServiceEvents.MOVE_BASIN, + data: this._selectedNub + } + ); + } + + /** + * @param uid nub uid + */ + public isLastBasin(uid: string): boolean { + return this._model.findBasinPosition(uid) === this._model.bassins.length - 1 + } + + public isValid(): boolean { + return this._model.hasUpDownConnection() && !this._model.hasBasinNotConnected(); + } + + /** + * @param uid nub uid + */ + public findChild(uid: string): PbBassin | PbCloison { + return this._model.findChild(uid); + } + + /** + * turn 'aval', 'amont' and Mermaid ids to nub real uid + */ + public toNubUid(itemId: string): string { + if (itemId !== undefined && itemId !== null) { + if (this.matchMermaidIds([this.upstreamId, this.downstreamId], itemId)) { + return this.model.uid; + } + if (itemId.startsWith("flowchart-")) { // Mermaid style id (ie. flowchart-id-xx) + return itemId.split('-')[1]; + } + } + return itemId; + } + + /** + * check if a list of (Mermaid transformed) ids matches a given id + * @param ids ids to transform the Mermaid way + * @param itemId id to find + */ + private matchMermaidIds(ids: string[], itemId: string): boolean { + return ids.find(id => MermaidUtil.isMermaidEqualIds(id, itemId)) !== undefined; + } + + /** + * @param itemId Mermaid id to find + * @returns + */ + public findFromItemId(itemId: string) { + if (this.matchMermaidIds([this.upstreamId, this.downstreamId], itemId)) { + return this.upstreamBassin; + } else { + return this.model.findChild(itemId); + } + } +} diff --git a/src/app/services/service-factory.ts b/src/app/services/service-factory.ts index a566f3aabb97fc5b2db06dcc5388def593fac7b9..e3ea55cae3c7ce504924688dac8ae1517f3268ba 100644 --- a/src/app/services/service-factory.ts +++ b/src/app/services/service-factory.ts @@ -3,6 +3,7 @@ import { FormulaireService } from "./formulaire.service"; import { I18nService } from "./internationalisation.service"; import { HttpService } from "./http.service"; import { NotificationsService } from "./notifications.service"; +import { PrebarrageService } from "./prebarrage.service"; /** * A "Singleton" the TS way, that holds pointers to all services, to be accessed @@ -15,10 +16,12 @@ export const ServiceFactory: { i18nService: I18nService; httpService: HttpService; notificationsService: NotificationsService; + prebarrageService: PrebarrageService; } = { applicationSetupService: undefined, formulaireService: undefined, i18nService: undefined, httpService: undefined, - notificationsService: undefined + notificationsService: undefined, + prebarrageService: undefined };