diff --git a/package.json b/package.json index 4f26023588e22a926d837e55298974cfa15599d4..f612aaad6bbfd547dbbfb8103327938c54a4243e 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "angular-bootstrap-md": "^5.0.5", "angular2-chartjs": "^0.4.1", "core-js": "^2.4.1", + "file-saver": "^1.3.8", "he": "^1.1.1", "jalhyd": "file:../jalhyd/jalhyd-1.0.0.tgz", "ngx-md": "^3.1.1", @@ -36,6 +37,7 @@ "@angular/cli": "1.5.4", "@angular/compiler-cli": "^5.0.0", "@angular/language-service": "^5.0.0", + "@types/file-saver": "^1.3.0", "@types/jasmine": "~2.5.53", "@types/jasminewd2": "~2.0.2", "@types/node": "~6.0.60", diff --git a/src/app/app.component.html b/src/app/app.component.html index 77966eddc04dc1fea960c0e8dfe228f04a40d6e4..53236b7a7b1386ea284e415f6e8314292264fef1 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -34,11 +34,18 @@ <!-- ATTENTION ! pas de href="#" sous peine de rechargement de la page et réinitialisation de l'appli --> <a class="closebtn" (click)="closeNav()">×</a> <a (click)="newCalc()">{{uitextSidenavNewCalc}}</a> + <a (click)="loadSession()">{{uitextSidenavLoadSession}}</a> <a (click)="params()">{{uitextSidenavParams}}</a> </div> </div> </div> + <!-- chargement des calculettes --> + <div loadCalcDialogAnchor></div> + + <!-- sauvegarde des calculettes --> + <div saveCalcDialogAnchor></div> + <!-- règle gradée des colonnes Bootstrap --> <div *ngIf="ruler" class="container-fluid"> <div class="row"> diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 8f89ea11909c54281672d5c7456741564d0cb393..95e2860536191fdf34dd7513b0b79781c52a92aa 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, ApplicationRef, OnInit, OnDestroy } from '@angular/core'; +import { Component, ApplicationRef, OnInit, OnDestroy, ViewChild, ComponentRef } from '@angular/core'; //import { MdDialog } from '@angular/material'; import { Router, ActivatedRoute } from '@angular/router'; @@ -16,6 +16,10 @@ import { ApplicationSetupService } from './services/app-setup/app-setup.service' import { HttpService } from './services/http/http.service'; import { HelpService } from './services/help/help.service'; import { HelpComponent } from './components/help/help.component'; +import { LoadCalcDialogAnchorDirective } from './components/load-calculator/load-calculator-anchor.directive'; +import { LoadCalculatorComponent } from './components/load-calculator/load-calculator.component'; +import { SaveCalcDialogAnchorDirective } from './components/save-calculator/save-calculator-anchor.directive'; +import { SaveCalculatorComponent } from './components/save-calculator/save-calculator.component'; @Component({ @@ -26,6 +30,11 @@ import { HelpComponent } from './components/help/help.component'; export class AppComponent implements OnInit, OnDestroy, Observer { private _displayErrorDialog: boolean = false; + /** + * liste des calculettes. Forme des objets : + * "title": nom de la calculette + * "uid": id unique du formulaire + */ private _calculators: any[] = []; /** @@ -34,6 +43,12 @@ export class AppComponent implements OnInit, OnDestroy, Observer { */ private _currentFormId: number; + @ViewChild(LoadCalcDialogAnchorDirective) + private loadCalcDialogAnchor: LoadCalcDialogAnchorDirective; + + @ViewChild(SaveCalcDialogAnchorDirective) + private saveCalcDialogAnchor: SaveCalcDialogAnchorDirective; + /** * composant actuellement affiché par l'élément <router-outlet> */ @@ -84,6 +99,10 @@ export class AppComponent implements OnInit, OnDestroy, Observer { return this.intlService.localizeText("INFO_SETUP_TITLE"); } + private get uitextSidenavLoadSession() { + return "Charger une session"; + } + /** * abonnement au service d'erreurs */ @@ -147,6 +166,10 @@ export class AppComponent implements OnInit, OnDestroy, Observer { }, 1); break; + case "saveForm": + this.saveForm(data["form"]); + break; + case "closeForm": const form: FormulaireDefinition = data["form"]; this.closeCalculator(form); @@ -183,6 +206,52 @@ export class AppComponent implements OnInit, OnDestroy, Observer { this._calculators[formIndex]["title"] = title; } + /** + * sauvegarde du/des formulaires + * @param form formulaire à sélectionner par défaut dans la liste + */ + private saveForm(form: FormulaireDefinition) { + // création du dialogue de sélection des formulaires à sauver + const compRef: ComponentRef<SaveCalculatorComponent> = this.saveCalcDialogAnchor.createDialog(); + + // création de la liste des formulaires + let list = []; + for (const c of this._calculators) { + const uid = +c["uid"]; + list.push({ + "selected": uid === form.uid, + "title": c["title"], + "uid": uid + }) + } + + // passage de la liste, récupération d'une Promise pour traiter le résultat + const prom: Promise<any[]> = compRef.instance.run(list); + prom.then(list => { + let name = compRef.instance.filename; + + // ajout extension ".json" + const re = /.+\.json/; + const match = re.exec(name.toLowerCase()); + if (match === null) + name = name + ".json"; + + this.saveSession(list, name) + }); + } + + private saveSession(calcList: any[], filename) { + let elems = []; + for (const c of calcList) + if (c.selected) { + console.log(c.title); + const form: FormulaireDefinition = this.formulaireService.getFormulaireFromId(c.uid); + elems.push(form.JSONserialise()); + } + let session = { "session": { "elements": elems } }; + this.formulaireService.saveSession(session, filename); + } + private closeCalculator(form: FormulaireDefinition) { const formId: number = form.uid; @@ -274,6 +343,19 @@ export class AppComponent implements OnInit, OnDestroy, Observer { this.toList(); } + private loadSession() { + this.closeNav(); + + // création du dialogue de sélection des formulaires à sauver + const compRef: ComponentRef<LoadCalculatorComponent> = this.loadCalcDialogAnchor.createDialog(); + + const prom: Promise<any[]> = compRef.instance.run(); + prom.then(list => { + this.formulaireService.loadSession(compRef.instance.selectedFile, compRef.instance.calculators); + compRef.destroy(); + }); + } + private params() { this.closeNav(); this.router.navigate(['/setup']); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 56f5e83b5d9644fb82341936df0e3f39ea183943..e084fe592034151af1d011caaf7dd1d531a7abc8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -49,6 +49,10 @@ import { VerticalResultElementComponent } from './components/result-element/vert import { LogEntryComponent } from './components/log-entry/log-entry.component'; import { HelpService } from './services/help/help.service'; import { HelpComponent } from './components/help/help.component'; +import { LoadCalculatorComponent } from './components/load-calculator/load-calculator.component'; +import { LoadCalcDialogAnchorDirective } from './components/load-calculator/load-calculator-anchor.directive'; +import { SaveCalculatorComponent } from './components/save-calculator/save-calculator.component'; +import { SaveCalcDialogAnchorDirective } from './components/save-calculator/save-calculator-anchor.directive'; const appRoutes: Routes = [ { path: 'list', component: CalculatorListComponent }, @@ -94,9 +98,12 @@ const appRoutes: Routes = [ CalcCanvasComponent, SectionCanvasComponent, HorizontalResultElementComponent, VerticalResultElementComponent, FixedResultsComponent, VarResultsComponent, - HelpComponent + HelpComponent, + LoadCalculatorComponent, LoadCalcDialogAnchorDirective, + SaveCalculatorComponent, SaveCalcDialogAnchorDirective ], // entryComponents: [AlertDialog], + entryComponents: [LoadCalculatorComponent, SaveCalculatorComponent], providers: [ // services ParamService, InternationalisationService, HttpService, FormulaireService, ApplicationSetupService, HelpService ], diff --git a/src/app/calculators/regime-uniforme/regime-uniforme.config.json b/src/app/calculators/regime-uniforme/regime-uniforme.config.json index 5710ce3e1b1c62bce4c8402e11a398c034d1274c..7286a71d854c26253d79f644a1ee23f176901b13 100644 --- a/src/app/calculators/regime-uniforme/regime-uniforme.config.json +++ b/src/app/calculators/regime-uniforme/regime-uniforme.config.json @@ -2,7 +2,6 @@ { "id": "fs_section", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "cal", "fields": [ { @@ -104,7 +103,6 @@ { "id": "fs_bief", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "cal", "fields": [ { @@ -127,7 +125,6 @@ { "id": "fs_hydraulique", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "cal", "fields": [ { @@ -145,7 +142,6 @@ { "id": "fs_param_calc", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "fix", "fields": [ { diff --git a/src/app/calculators/remous/remous.config.json b/src/app/calculators/remous/remous.config.json index f530722a3210a51e2f269a6a41748b85e3314ebf..289a274e1025d7b6fcda32c5556734efe79fba3c 100644 --- a/src/app/calculators/remous/remous.config.json +++ b/src/app/calculators/remous/remous.config.json @@ -2,7 +2,6 @@ { "id": "fs_section", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "fix", "fields": [ { @@ -104,7 +103,6 @@ { "id": "fs_bief", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "fix", "fields": [ { @@ -132,7 +130,6 @@ { "id": "fs_condlim", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "fix", "fields": [ { @@ -155,7 +152,6 @@ { "id": "fs_param_calc", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "fix", "fields": [ { diff --git a/src/app/calculators/section-param/section-param.config.json b/src/app/calculators/section-param/section-param.config.json index 88aac4d949f05449c57ae25044a5931fe9b8565f..56ea1dd3cb265ab2e03c10c0fa9cd8689157168e 100644 --- a/src/app/calculators/section-param/section-param.config.json +++ b/src/app/calculators/section-param/section-param.config.json @@ -2,7 +2,6 @@ { "id": "fs_section", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "var", "fields": [ { @@ -104,7 +103,6 @@ { "id": "fs_bief", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "var", "fields": [ { @@ -127,7 +125,6 @@ { "id": "fs_hydraulique", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "var", "fields": [ { @@ -145,7 +142,6 @@ { "id": "fs_param_calc", "type": "fieldset", - "defaultNodeType": "SectionTrapeze", "option": "fix", "fields": [ { diff --git a/src/app/components/calculator-list/calculator-list.component.ts b/src/app/components/calculator-list/calculator-list.component.ts index 91b239baac3176ea758b8a6f94dcbc995e64013f..1a14c08d0c33fe7d7176354f881dcd9e8d6a92f9 100644 --- a/src/app/components/calculator-list/calculator-list.component.ts +++ b/src/app/components/calculator-list/calculator-list.component.ts @@ -6,6 +6,8 @@ import { CalculatorType, EnumEx } from "jalhyd"; import { FormulaireDefinition } from "../../formulaire/definition/form-definition"; import { ServiceFactory } from "../../services/service-factory"; import { InternationalisationService } from '../../services/internationalisation/internationalisation.service'; +import { FormulaireParallelStructure } from "../../formulaire/definition/concrete/form-parallel-structures"; +import { FieldsetContainer } from "../../formulaire/fieldset-container"; class ListElement { private _label: string; @@ -41,6 +43,15 @@ export class CalculatorListComponent implements OnInit { const p: Promise<FormulaireDefinition> = ServiceFactory.instance.formulaireService.createFormulaire(t); p.then(f => { this.router.navigate(['/calculator', f.uid]); + return f; + }).then(f => { + // on ajoute un ouvrage après l'ouverture de la calculette "ouvrages parallèles" + if (f instanceof FormulaireParallelStructure) + for (const e of f.allFormElements) + if (e instanceof FieldsetContainer) { + e.addFromTemplate(0); + break; + } }); } diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts index baa3ce41893bea13e3479e6365f2cebdf03cddb8..240f408b9337099a717173d56df4b9632e1e30ca 100644 --- a/src/app/components/fieldset-container/fieldset-container.component.ts +++ b/src/app/components/fieldset-container/fieldset-container.component.ts @@ -73,8 +73,8 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit { } public ngAfterViewInit() { + this.onFielsetListChange(); this._fieldsetComponents.changes.subscribe(_ => this.onFielsetListChange()); - this.addStructure(); } /* diff --git a/src/app/components/generic-calculator/calculator.component.html b/src/app/components/generic-calculator/calculator.component.html index 72737bb6f2863c6d65b70550b0dbbedd46bdc785..3eda7ef8cde3bea94aeb91ec360181840c986497 100644 --- a/src/app/components/generic-calculator/calculator.component.html +++ b/src/app/components/generic-calculator/calculator.component.html @@ -10,6 +10,11 @@ <i *ngIf="enableHelpButton" class="fa fa-question-circle fa-3x" style="color:blue" (click)="openHelp()"></i> </div> + <div class="col-1 px-0 mx-0"> + <!-- bouton de sauvegarde --> + <i class="fa fa-save fa-3x" (click)="saveCalculator()"></i> + </div> + <div class="col-1 px-0 mx-0"> <!-- bouton de fermeture --> <!-- <button type="button" id="close-button" class="btn btn-primary float-right black" (click)="confirmModal.show()">×</button> --> diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts index 70abce73479b75d8b8c8ae41db8334ffc6866606..b7b8a07fc9487b2738c8179cf7ee0d1090eaf1bc 100644 --- a/src/app/components/generic-calculator/calculator.component.ts +++ b/src/app/components/generic-calculator/calculator.component.ts @@ -371,4 +371,8 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, private openHelp() { this._formulaire.requestHelp(); } + + private saveCalculator() { + this.formulaireService.saveForm(this._formulaire); + } } diff --git a/src/app/components/load-calculator/load-calculator-anchor.directive.ts b/src/app/components/load-calculator/load-calculator-anchor.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..f270813f12348a6a9b894f9ba5180d47b73f375f --- /dev/null +++ b/src/app/components/load-calculator/load-calculator-anchor.directive.ts @@ -0,0 +1,27 @@ +import { Directive, ComponentFactoryResolver, ComponentFactory, ComponentRef } from '@angular/core'; + +import { ViewContainerRef } from '@angular/core'; +import { LoadCalculatorComponent } from './load-calculator.component'; + +@Directive({ + selector: '[loadCalcDialogAnchor]' +}) +export class LoadCalcDialogAnchorDirective { + constructor( + private viewContainer: ViewContainerRef, + private componentFactoryResolver: ComponentFactoryResolver + ) { } + + public createDialog(): ComponentRef<LoadCalculatorComponent> { + this.viewContainer.clear(); + + let compFactory: ComponentFactory<LoadCalculatorComponent> = this.componentFactoryResolver.resolveComponentFactory(LoadCalculatorComponent); + let compRef: ComponentRef<LoadCalculatorComponent> = this.viewContainer.createComponent(compFactory); + + // compRef.instance.confirmResult.subscribe(() => { + // compRef.destroy(); + // }); + + return compRef; + } +} diff --git a/src/app/components/load-calculator/load-calculator.component.html b/src/app/components/load-calculator/load-calculator.component.html new file mode 100644 index 0000000000000000000000000000000000000000..b596cadbf55f04ffd5ee4cf376c706549d00654d --- /dev/null +++ b/src/app/components/load-calculator/load-calculator.component.html @@ -0,0 +1,24 @@ +<div mdbModal #loadDialog="mdb-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" [config]="{backdrop: false, ignoreBackdropClick: true,show:true}"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title w-100" id="myModalLabel">{{uitextDialogTitle}}</h4> + </div> + <div class="modal-body"> + <div> + <input type="file" #fileSelector multiple="false" accept="*.json" (change)="onFileSelect()"> + </div> + <div *ngFor="let c of _calculators"> + <input type="checkbox" value={{c.uid}} checked={{isSelected(c)}} (change)="onCheckCalc($event)">{{c.title}} + </div> + <button *ngIf="showSelectButtons" type="button" class="btn btn-mdb-color waves-light" (click)="selectAll()" mdbRippleRadius>{{uitextSelectAll}}</button> + <button *ngIf="showSelectButtons" type="button" class="btn btn-mdb-color waves-light" (click)="deselectAll()" mdbRippleRadius>{{uitextDeselectAll}}</button> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-danger relative waves-light" (click)="loadDialog.hide();cancelLoad()" mdbRippleRadius>{{uitextCancel}}</button> + <button type="button" class="btn btn-success waves-light" [disabled]="disableLoadButton" (click)="loadDialog.hide();confirmLoad()" + mdbRippleRadius>{{uitextLoad}}</button> + </div> + </div> + </div> +</div> \ No newline at end of file diff --git a/src/app/components/load-calculator/load-calculator.component.ts b/src/app/components/load-calculator/load-calculator.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1a3f8b710f1e50228323e7a7c9b8457f1f8a0c7 --- /dev/null +++ b/src/app/components/load-calculator/load-calculator.component.ts @@ -0,0 +1,161 @@ +import { Component, EventEmitter, ViewChild } from "@angular/core"; + +import { ServiceFactory } from "../../services/service-factory"; +import { InternationalisationService } from "../../services/internationalisation/internationalisation.service"; + +@Component({ + selector: 'load-calc', + templateUrl: "./load-calculator.component.html" +}) +export class LoadCalculatorComponent { + @ViewChild('fileSelector') fileSelector; + + private _selectFile: File; + + /** + * liste des calculettes affichées. Forme des objets : + * "title": nom de la calculette + * "selected": flag de sélection pour la sauvegarde + */ + private _calculators: any[]; + + /** + * événement émis lors du clic sur "annuler"/"charger" + * utilisé par la promise de gestion de la confirmation/annulation de la sauvegarde + */ + private _confirmResult = new EventEmitter(); + + // services + private intlService: InternationalisationService; + + constructor() { + this.intlService = ServiceFactory.instance.internationalisationService; + } + + private get uitextDialogTitle() { + return "Charger des calculettes"; + } + + private get uitextCancel() { + // return this.intlService.localizeText("INFO_OPTION_NO"); + return "Annuler"; + } + + private get uitextLoad() { + // return this.intlService.localizeText("INFO_OPTION_NO"); + return "Charger"; + } + + private get uitextSelectAll() { + return "Toutes"; + } + + private get uitextDeselectAll() { + return "Aucune"; + } + + /** + * calcule l'état du bouton charger + */ + private get disableLoadButton() { + // pas de fichier sélectionné -> bouton grisé + if (this._selectFile === undefined) + return true; + + // au moins une calculette sélectionnée -> dégrisé + if (this._calculators !== undefined) + for (const c of this._calculators) + if (c.selected) + return false; + + // grisé sinon + return true; + } + + public run(): Promise<any[]> { + // promise de gestion de la confirmation/annulation de la sauvegarde + return new Promise((resolve, reject) => { + this._confirmResult.subscribe((confirm) => { + if (confirm) { + resolve(this._calculators); + } else { + reject(); + } + }); + }); + } + + private get showSelectButtons(): boolean { + return this._calculators && this._calculators.length != 0; + } + + private getSelectedFile(): File { + const files: { [key: string]: File } = this.fileSelector.nativeElement.files; + for (const key in files) + if (!isNaN(parseInt(key))) + return files[key]; + return undefined; + } + + private onFileSelect() { + const formService = ServiceFactory.instance.formulaireService; + this._selectFile = this.getSelectedFile(); + if (this._selectFile !== undefined) + formService.calculatorInfosFromSessionFile(this._selectFile).then( + calcInfos => { + this._calculators = calcInfos; + for (const n of this._calculators) + n["selected"] = true; + }); + } + + public get selectedFile(): File { + return this._selectFile; + } + + public get calculators(): any[] { + return this._calculators; + } + + private isSelected(c: any) { + return c.selected ? "checked" : undefined; + } + + private onCheckCalc(event: any) { + for (const c of this._calculators) + if (c.uid == +event.target.value) { + c.selected = event.target.checked; + break; + } + } + + private selectAll() { + for (const c of this._calculators) + c.selected = true; + } + + private deselectAll() { + for (const c of this._calculators) + c.selected = false; + } + + private set confirmed(b: boolean) { + setTimeout(() => { + this._confirmResult.next(b); + }, 0); + } + + /** + * appelé quand on clique sur annuler + */ + private cancelLoad() { + this.confirmed = false; + } + + /** + * appelé quand on clique sur charger + */ + private confirmLoad() { + this.confirmed = true; + } +} diff --git a/src/app/components/remous-results/remous-results.component.ts b/src/app/components/remous-results/remous-results.component.ts index 5f6f261c0fb4c717f29c249e98ef0bde9326b023..07bbca115f0eb052f226c231adda47df5729d129 100644 --- a/src/app/components/remous-results/remous-results.component.ts +++ b/src/app/components/remous-results/remous-results.component.ts @@ -225,7 +225,8 @@ export class RemousResultsComponent { // le dernier dataset de la liste datasets est dessiné en 1er this._remousResults.update(); - this.varResultsComponent.results = this._remousResults.varResults; + if (this.varResultsComponent) + this.varResultsComponent.results = this._remousResults.varResults; const penteFond: number = this._remousResults.penteFond; diff --git a/src/app/components/save-calculator/save-calculator-anchor.directive.ts b/src/app/components/save-calculator/save-calculator-anchor.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..4317ff3ac192d721ba1bbdc97ff3a719f1384848 --- /dev/null +++ b/src/app/components/save-calculator/save-calculator-anchor.directive.ts @@ -0,0 +1,27 @@ +import { Directive, ComponentFactoryResolver, ComponentFactory, ComponentRef } from '@angular/core'; + +import { ViewContainerRef } from '@angular/core'; +import { SaveCalculatorComponent } from './save-calculator.component'; + +@Directive({ + selector: '[saveCalcDialogAnchor]' +}) +export class SaveCalcDialogAnchorDirective { + constructor( + private viewContainer: ViewContainerRef, + private componentFactoryResolver: ComponentFactoryResolver + ) { } + + public createDialog(): ComponentRef<SaveCalculatorComponent> { + this.viewContainer.clear(); + + let compFactory: ComponentFactory<SaveCalculatorComponent> = this.componentFactoryResolver.resolveComponentFactory(SaveCalculatorComponent); + let compRef: ComponentRef<SaveCalculatorComponent> = this.viewContainer.createComponent(compFactory); + + // dialogComponentRef.instance.close.subscribe(() => { + // dialogComponentRef.destroy(); + // }); + + return compRef; + } +} diff --git a/src/app/components/save-calculator/save-calculator.component.html b/src/app/components/save-calculator/save-calculator.component.html new file mode 100644 index 0000000000000000000000000000000000000000..cab54103edd0c849cfc77b83bb377ec6a90d3bbd --- /dev/null +++ b/src/app/components/save-calculator/save-calculator.component.html @@ -0,0 +1,32 @@ +<div mdbModal #saveDialog="mdb-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" [config]="{backdrop: false, ignoreBackdropClick: true,show:true}"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title w-100" id="myModalLabel">{{uitextDialogTitle}}</h4> + </div> + <div class="modal-body"> + <!-- liste de calculettes avec check --> + <div *ngFor="let c of _calculators"> + <input type="checkbox" value={{c.uid}} checked={{isSelected(c)}} (change)="onCheckCalc($event)">{{c.title}} + </div> + + <!-- bouton "tout sélectionnner" --> + <button type="button" class="btn btn-mdb-color waves-light" (click)="selectAll()" mdbRippleRadius>{{uitextSelectAll}}</button> + + <!-- bouton "tout désélectionnner" --> + <button type="button" class="btn btn-mdb-color waves-light py-10" (click)="deselectAll()" mdbRippleRadius>{{uitextDeselectAll}}</button> + + <!-- nom du fichier --> + <div class="md-form form-sm mt-4"> + <input mdbActive type="text" id="form1" class="form-control" [(ngModel)]="_filename"> + <!-- on utilise [innerHTML] pour que les codes HTML comme soient interprétés correctement --> + <label for="form1" [innerHTML]="_filenameTitle"></label> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-danger relative waves-light" (click)="saveDialog.hide();cancelSave()" mdbRippleRadius>{{uitextCloseDialogNo}}</button> + <button type="button" class="btn btn-success waves-light" aria-label="Close " (click)="saveDialog.hide();confirmSave()" mdbRippleRadius>{{uitextCloseDialogYes}}</button> + </div> + </div> + </div> +</div> \ No newline at end of file diff --git a/src/app/components/save-calculator/save-calculator.component.ts b/src/app/components/save-calculator/save-calculator.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e30cd2db078144cdd70ca127f3b5c29fc5861b4 --- /dev/null +++ b/src/app/components/save-calculator/save-calculator.component.ts @@ -0,0 +1,118 @@ +import { Component, EventEmitter } from "@angular/core"; +import { ServiceFactory } from "../../services/service-factory"; +import { InternationalisationService } from "../../services/internationalisation/internationalisation.service"; + +@Component({ + selector: 'save-calc', + templateUrl: "./save-calculator.component.html" +}) +export class SaveCalculatorComponent { + /** + * liste des calculettes affichées. Forme des objets : + * "title": nom de la calculette + * "selected": flag de sélection pour la sauvegarde + * "uid": id unique du formulaire + */ + private _calculators: any[]; + + /** + * nom du fichier + */ + private _filename: string = "session"; + + private _filenameTitle = "Nom de fichier"; + + /** + * événement émis lors du clic sur "annuler"/"enregister" + * utilisé par la promise de gestion de la confirmation/annulation de la sauvegarde + */ + private confirmResult = new EventEmitter(); + + // services + private intlService: InternationalisationService; + + constructor() { + this.intlService = ServiceFactory.instance.internationalisationService; + } + + private get uitextDialogTitle() { + return "Enregister les calculettes"; + } + + private get uitextCloseDialogYes() { + //return this.intlService.localizeText("INFO_OPTION_YES"); + return "Sauver"; + } + + private get uitextCloseDialogNo() { + // return this.intlService.localizeText("INFO_OPTION_NO"); + return "Annuler"; + } + + private get uitextSelectAll() { + return "Toutes"; + } + + private get uitextDeselectAll() { + return "Aucune"; + } + + public get filename(): string { + return this._filename; + } + + public run(calcList: any[]): Promise<any[]> { + this._calculators = calcList; + + // promise de gestion de la confirmation/annulation de la sauvegarde + return new Promise((resolve, reject) => { + this.confirmResult.subscribe((confirm) => { + if (confirm) { + resolve(this._calculators); + } else { + reject(); + } + }); + }); + } + + private isSelected(c: any) { + return c.selected ? "checked" : undefined; + } + + private onCheckCalc(event: any) { + for (const c of this._calculators) + if (c.uid == +event.target.value) + c.selected = event.target.checked; + } + + private selectAll() { + for (const c of this._calculators) + c.selected = true; + } + + private deselectAll() { + for (const c of this._calculators) + c.selected = false; + } + + private set confirmed(b: boolean) { + setTimeout(() => { + this.confirmResult.next(b); + }, 0); + } + + /** + * appelé quand on clique sur annuler + */ + private cancelSave() { + this.confirmed = false; + } + + /** + * appelé quand on clique sur sauver + */ + private confirmSave() { + this.confirmed = true; + } +} diff --git a/src/app/formulaire/definition/concrete/form-cond-distri.ts b/src/app/formulaire/definition/concrete/form-cond-distri.ts index b79c5d50c422c29eb3ed00096c9e2bee525c0774..c9e70be83fe041ed100de2532b4b64ff14f2303e 100644 --- a/src/app/formulaire/definition/concrete/form-cond-distri.ts +++ b/src/app/formulaire/definition/concrete/form-cond-distri.ts @@ -16,13 +16,17 @@ export class FormulaireConduiteDistributrice extends FormulaireDefinition { private _formResult: FormResultFixedVar; constructor() { - super(CalculatorType.ConduiteDistributrice); + super(); this._formFixedVar = new FormDefFixedVar(this); this._formParamCalc = new FormDefParamToCalculate(this); this._formResult = new FormResultFixedVar(this, false); this._formCompute = new FormComputeFixedVar(this, this._formResult); } + protected get defaultProperties(): {} { + return { "calcType": CalculatorType.ConduiteDistributrice, "nodeType": ComputeNodeType.None }; + } + protected initParse() { this._formParamCalc.initParse(); } diff --git a/src/app/formulaire/definition/concrete/form-courbe-remous.ts b/src/app/formulaire/definition/concrete/form-courbe-remous.ts index 0f60d8a59e1c38e7ff3b7aedc5a99d803e67a7a9..d67e9bf24054149d46a542a767382dd6d4ede2ab 100644 --- a/src/app/formulaire/definition/concrete/form-courbe-remous.ts +++ b/src/app/formulaire/definition/concrete/form-courbe-remous.ts @@ -21,12 +21,16 @@ export class FormulaireCourbeRemous extends FormulaireDefinition { private _resolveMethSelectId: string; constructor() { - super(CalculatorType.CourbeRemous) + super(); this._formSection = new FormDefSection(this); this._formResult = new FormResultRemous(this); this._formCompute = new FormComputeCourbeRemous(this, this._formSection, this._formResult); } + protected get defaultProperties(): {} { + return { "calcType": CalculatorType.CourbeRemous, "nodeType": ComputeNodeType.SectionPuissance }; + } + protected parseOptions(json: {}) { this._formSection.parseOptions(json); diff --git a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts index baf385278a86ef377612817a839b6e59f92f5ffb..96d6fad59683bc4d6cc15cd03e1ce2d7eeac1c0d 100644 --- a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts +++ b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts @@ -18,13 +18,17 @@ export class FormulaireLechaptCalmon extends FormulaireDefinition implements Obs private _formResult: FormResultFixedVar; constructor() { - super(CalculatorType.LechaptCalmon) + super(); this._formFixedVar = new FormDefFixedVar(this); this._formParamCalc = new FormDefParamToCalculate(this); this._formResult = new FormResultFixedVar(this, false); this._formCompute = new FormComputeFixedVar(this, this._formResult); } + protected get defaultProperties(): {} { + return { "calcType": CalculatorType.LechaptCalmon, "nodeType": ComputeNodeType.None }; + } + protected initParse() { this._formParamCalc.initParse(); } diff --git a/src/app/formulaire/definition/concrete/form-parallel-structures.ts b/src/app/formulaire/definition/concrete/form-parallel-structures.ts index 66838edba6c3e52e30279889cec8c0936ebf6fe4..64b83a279ce560209b0e082d6d5ec8400af48637 100644 --- a/src/app/formulaire/definition/concrete/form-parallel-structures.ts +++ b/src/app/formulaire/definition/concrete/form-parallel-structures.ts @@ -1,9 +1,7 @@ -import { CalculatorType, ComputeNodeType, Structure, ParallelStructure, SessionNub } from "jalhyd"; +import { CalculatorType, ComputeNodeType, Structure, ParallelStructure, SessionNub, StructureType, LoiDebit, StructureProperties, Props } from "jalhyd"; import { FormulaireDefinition } from "../form-definition"; import { CalculatorResults } from "../../../results/calculator-results"; -import { ServiceFactory } from "../../../services/service-factory"; -import { ParamService } from "../../../services/param/param.service"; import { FormDefParamToCalculate } from "../form-def-paramcalc"; import { FormDefFixedVar } from "../form-def-fixedvar"; import { FormDefParallelStructures } from "../form-def-parallel-structures"; @@ -33,7 +31,7 @@ export class FormulaireParallelStructure extends FormulaireDefinition { private __ouvrageSelectId: string; constructor() { - super(CalculatorType.ParallelStructure) + super(); this._formFixedVar = new FormDefFixedVar(this); this._formParamCalc = new FormDefParamToCalculate(this); this._formResult = new FormResultFixedVar(this, false); @@ -41,15 +39,20 @@ export class FormulaireParallelStructure extends FormulaireDefinition { this._formCompute = new FormComputeParallelStructures(this, this._formParallelStruct, this._formResult); } - private createStructNub(templ: FieldsetTemplate): SessionNub { - const paramService: ParamService = ServiceFactory.instance.paramService; + protected get defaultProperties(): {} { + return { + "calcType": CalculatorType.ParallelStructure, "nodeType": ComputeNodeType.None, + "structureType": StructureType.SeuilRectangulaire, "loiDebit": LoiDebit.Cem88v + }; + } + private createStructNub(templ: FieldsetTemplate): SessionNub { // valeurs par défaut de CalculatorType, ComputeNodeType, StructureType, LoiDebit // !!! attention !!! pour l'instant, il doit y avoir cohérence entre ces valeurs et celles du fichier de conf // cad valeur par défaut du 1er select (type d'ouvrage), du 2ème (loi de débit). // A terme, il faudrait analyser le fichier de conf (dépendances d'existence) pour déterminer automatiquement ces valeurs const params = FieldSet.makeDefaultProps(templ.calcTypeFromConfig, templ.defaultNodeTypeFromConfig); - return paramService.createSessionNub(params); + return this.createSessionNub(params); } /** @@ -62,11 +65,7 @@ export class FormulaireParallelStructure extends FormulaireDefinition { } private get parallelStructureNub(): ParallelStructure { - const params = { - "calcType": CalculatorType.ParallelStructure, - "nodeType": ComputeNodeType.None - }; - return this.getSessionNub(params).nub as ParallelStructure; + return this.currentSessionNub.nub as ParallelStructure; } public createFieldset(parent: FormulaireNode, json: {}, data?: {}): FieldSet { @@ -192,6 +191,28 @@ export class FormulaireParallelStructure extends FormulaireDefinition { return n as FieldsetContainer; } + /** + * après une modification, détermine si les propriétés sont compatibles entre elles et les ajuste au besoin + * @param props propriétés à vérifier + * @param name nom de la propriété qui vient de changer + * @param val nouvelle valeur de la propriété + */ + private adjustProperties(props: Props, name: string, val: any) { + const res: Props = props.clone(); + + // si prop=type d'ouvrage, on prend une loi de débit compatible avec (spécifique aux ouvrages //) comme valeur par défaut + if (name === "structureType") { + if (!StructureProperties.isCompatibleValues(val, res.getPropValue("loiDebit"))) + res.setPropValue("loiDebit", StructureProperties.findCompatibleLoiDebit(val)); + } + // si prop=loi débit, on prend un type d'ouvrage compatible + else if (name === "loiDebit") + if (!StructureProperties.isCompatibleValues(res.getPropValue("structureType"), val)) + res.setPropValue("structureType", StructureProperties.findCompatibleStructure(val)); + + return res; + } + /** * abonnement en tant qu'observateur du FieldsetContainer */ @@ -229,8 +250,8 @@ export class FormulaireParallelStructure extends FormulaireDefinition { else if (sender instanceof FieldSet && data.action == "propertyChange") { switch (sender.id) { case "fs_ouvrage": - // this.replaceCurrentSessionNub(sender.properties); - const newNub = this.replaceSessionNub(sender.sessionNub, sender.properties); + const props = this.adjustProperties(sender.properties, data["name"], data["value"]); + const newNub = this.replaceSessionNub(sender.sessionNub, props); sender.setSessionNub(newNub); this.reset(); break; diff --git a/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts b/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts index bc8d202d131162619850b424d6227acc8e3c5b70..64a5141cdb1703b8f874265783afcd5b9dfff25c 100644 --- a/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts +++ b/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts @@ -17,13 +17,17 @@ export class FormulairePasseBassinDimensions extends FormulaireDefinition { private _formResult: FormResultFixedVar; constructor() { - super(CalculatorType.PabDimensions); + super(); this._formFixedVar = new FormDefFixedVar(this); this._formParamCalc = new FormDefParamToCalculate(this); this._formResult = new FormResultFixedVar(this, false); this._formCompute = new FormComputeFixedVar(this, this._formResult); } + protected get defaultProperties(): {} { + return { "calcType": CalculatorType.PabDimensions, "nodeType": ComputeNodeType.None }; + } + protected initParse() { this._formParamCalc.initParse(); } diff --git a/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts b/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts index 02ed7376af7c340c8337b49a367c75bf8f8e13e3..31d2a86390c4ffa51a2822bfbabee50653ad66d5 100644 --- a/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts +++ b/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts @@ -17,13 +17,17 @@ export class FormulairePasseBassinPuissance extends FormulaireDefinition { private _formResult: FormResultFixedVar; constructor() { - super(CalculatorType.PabPuissance); + super(); this._formFixedVar = new FormDefFixedVar(this); this._formParamCalc = new FormDefParamToCalculate(this); this._formResult = new FormResultFixedVar(this, false); this._formCompute = new FormComputeFixedVar(this, this._formResult); } + protected get defaultProperties(): {} { + return { "calcType": CalculatorType.PabPuissance, "nodeType": ComputeNodeType.None }; + } + protected initParse() { this._formParamCalc.initParse(); } diff --git a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts index b4f322431ebc8cb74cd06e96855f68aa19b11555..50e7aeff7b5e5cc3f8f0eca81db18a7c88969f78 100644 --- a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts +++ b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts @@ -20,7 +20,7 @@ export class FormulaireRegimeUniforme extends FormulaireDefinition implements Ob private _formResult: FormResultFixedVar; constructor() { - super(CalculatorType.RegimeUniforme) + super(); this._formFixedVar = new FormDefFixedVar(this); this._formParamCalc = new FormDefParamToCalculate(this); this._formSection = new FormDefSection(this); @@ -28,6 +28,10 @@ export class FormulaireRegimeUniforme extends FormulaireDefinition implements Ob this._formCompute = new FormComputeFixedVar(this, this._formResult); } + protected get defaultProperties(): {} { + return { "calcType": CalculatorType.RegimeUniforme, "nodeType": ComputeNodeType.SectionCercle }; + } + protected initParse() { this._formParamCalc.initParse(); } diff --git a/src/app/formulaire/definition/concrete/form-section-parametree.ts b/src/app/formulaire/definition/concrete/form-section-parametree.ts index 63f636138d3f22505f32dbfd08574c8b8e252ddc..1f14c0f662e254bc5e3d81823e5e943f4362827d 100644 --- a/src/app/formulaire/definition/concrete/form-section-parametree.ts +++ b/src/app/formulaire/definition/concrete/form-section-parametree.ts @@ -22,13 +22,17 @@ export class FormulaireSectionParametree extends FormulaireDefinition { private _formSectionResult: FormResultSection; constructor() { - super(CalculatorType.SectionParametree); + super(); this._formFixedVar = new FormDefFixedVar(this); this._formSection = new FormDefSection(this); this._formSectionResult = new FormResultSection(this, this._formSection); this._formCompute = new FormComputeSectionParametree(this, this._formSection, this._formSectionResult); } + protected get defaultProperties(): {} { + return { "calcType": CalculatorType.SectionParametree, "nodeType": ComputeNodeType.SectionCercle }; + } + protected parseOptions(json: {}) { this._formSection.parseOptions(json); } diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts index 37fbddfd41332f178a88efcf12efc6e7b8cc5fef..1a43403007d74910984da7d1ad9fdb08868d2e56 100644 --- a/src/app/formulaire/definition/form-definition.ts +++ b/src/app/formulaire/definition/form-definition.ts @@ -20,11 +20,6 @@ import { FieldsetTemplate } from "../fieldset-template"; * classe de base pour tous les formulaires */ export abstract class FormulaireDefinition extends FormulaireNode implements Observer { - /** - * type de calculette - */ - private _calcType: CalculatorType; - /** * nom de la calculette */ @@ -47,15 +42,19 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs protected _paramService: ParamService; - constructor(calcType: CalculatorType) { + constructor() { super(undefined); - this._calcType = calcType; this._paramService = ServiceFactory.instance.paramService; - this.initSessionNub(); } public get calculatorType(): CalculatorType { - return this._calcType; + const props = this._currentSessionNub === undefined ? this.defaultProperties : this._currentSessionNub.properties.props; + return props["calcType"]; + } + + public get nodeType(): ComputeNodeType { + const props = this._currentSessionNub === undefined ? this.defaultProperties : this._currentSessionNub.properties.props; + return props["nodeType"]; } public get calculatorName() { @@ -70,15 +69,17 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs return this._jsonConfig; } - protected initSessionNub() { - this._currentSessionNub = this.createSessionNub({ "calcType": this._calcType, "nodeType": ComputeNodeType.None }); + protected abstract get defaultProperties(): {}; + + public initSessionNub(props?: {}) { + this._currentSessionNub = this.createSessionNub(props === undefined ? this.defaultProperties : props); } public get currentSessionNub(): SessionNub { return this._currentSessionNub; } - protected findNub(params: Props | {}) { + private findNub(params: Props | {}) { return this._paramService.findSessionNub(params); } @@ -160,7 +161,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs private parse_fieldset(json: {}) { const fs = this.createFieldset(this, json); - fs.parseConfig(json, { "parentForm": this }); + fs.parseConfig(json); this.afterParseFieldset(fs); } @@ -435,6 +436,74 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs return new TopFormulaireElementIterator(this); } + /** + * sérialisation en JSON + */ + public JSONserialise(): {} { + let res = {}; + res["id"] = this.calculatorName; + res["uid"] = this.uid; + res["props"] = this._currentSessionNub.properties.props; + res["elements"] = this.serialiseKids(); + return { "form": res }; + } + + private deserialiseFieldset(elements: {}): FieldSet { + const res: FieldSet = this.getFormulaireNodeById(elements["id"]) as FieldSet; + res.deserialiseJSON(elements); + return res; + } + + private deserialiseFieldsetContainer(elements: {}): FieldsetContainer { + const res: FieldsetContainer = this.getFormulaireNodeById(elements["id"]) as FieldsetContainer; + res.deserialiseJSON(elements); + return res; + } + + private deserialiseElement(element: {}) { + const keys = Object.keys(element); + if (keys.length !== 1) + throw new Error(`session file : invalid form object '${element}'`); + + switch (keys[0]) { + case "fieldset": + this.deserialiseFieldset(element[keys[0]]); + break; + + case "fieldset_container": + this.deserialiseFieldsetContainer(element[keys[0]]); + break; + + default: + throw new Error(`session file : invalid key '${keys[0]}' in form object`); + } + } + + /** + * désérialisation depuis JSON + */ + public deserialiseJSON(elements: {}) { + for (const k in elements) { + switch (k) { + case "id": + this._calculatorName = elements[k]; + break; + + case "uid": + case "props": + break; + + case "elements": + for (const e of elements[k]) + this.deserialiseElement(e); + break; + + default: + throw new Error(`session file : invalid key '${k}' in form object`); + } + } + } + // interface Observer public update(sender: any, data: any) { diff --git a/src/app/formulaire/fieldset-container.ts b/src/app/formulaire/fieldset-container.ts index a354d2a7d9ca17355f08fd16d03ae576c932386a..1f384a140395d38231d64f8a7bb3e4a439e4a615 100644 --- a/src/app/formulaire/fieldset-container.ts +++ b/src/app/formulaire/fieldset-container.ts @@ -28,6 +28,16 @@ export class FieldsetContainer extends FormulaireElement { return this._templates[index]; } + private getTemplateIndex(id: string): number { + let i = 0; + for (const t of this._templates) { + if (t.config["id"] == id) + return i; + i++; + } + throw new Error(`template ${id} non trouvé`); + } + public addFieldset(fs: FieldSet) { this.fieldsets.push(fs); } @@ -68,7 +78,7 @@ export class FieldsetContainer extends FormulaireElement { * @param templateIndex indice du template dans la liste * @param after insère le nouveau FieldSet après cette position, à la fin sinon */ - public addFromTemplate(templateIndex: number, after?: number) { + public addFromTemplate(templateIndex: number, after?: number): FieldSet { const templ: FieldsetTemplate = this._templates[templateIndex]; const inst: FieldSet = templ.instantiateTemplate(this, after); @@ -80,6 +90,8 @@ export class FieldsetContainer extends FormulaireElement { "action": "newFieldset", "fieldset": inst }, this); + + return inst; } public get fieldsets(): FieldSet[] { @@ -105,4 +117,61 @@ export class FieldsetContainer extends FormulaireElement { super.updateLocalisation(loc); } + + /** + * sérialisation en JSON + */ + public JSONserialise(): {} { + let res = {}; + res["id"] = this.id; + res["elements"] = this.serialiseKids(); + return { "fieldset_container": res }; + } + + private deserialiseFieldset(elements: {}): FieldSet { + const ind = this.getTemplateIndex(elements["id"]); + const res: FieldSet = this.addFromTemplate(ind); + const props = elements["props"]; + for (const k in props) + if (k !== "calcType" && k !== "nodeType") + res.setPropValue(k, props[k]); + res.deserialiseJSON(elements); + return res; + } + + private deserialiseElement(element: {}) { + const keys = Object.keys(element); + if (keys.length !== 1) + throw new Error(`session file : invalid fieldset object '${element}'`); + + switch (keys[0]) { + case "fieldset": + this.deserialiseFieldset(element[keys[0]]); + break; + + default: + throw new Error(`session file : invalid key '${keys[0]}' in fieldset object`); + } + } + + /** + * désérialisation depuis JSON + */ + public deserialiseJSON(elements: {}) { + for (const k in elements) { + switch (k) { + case "id": + this._confId = elements[k]; + break; + + case "elements": + for (const e of elements[k]) + this.deserialiseElement(e); + break; + + default: + throw new Error(`session file : invalid key '${k}' in form object`); + } + } + } } diff --git a/src/app/formulaire/fieldset-template.ts b/src/app/formulaire/fieldset-template.ts index a5d61834921dcdeb5408047af79db76c29386f53..ce3fcf353158e5e5afb82accafa73bfeda2b1d08 100644 --- a/src/app/formulaire/fieldset-template.ts +++ b/src/app/formulaire/fieldset-template.ts @@ -46,7 +46,7 @@ export class FieldsetTemplate { public instantiateTemplate(cont: FieldsetContainer, after: number): FieldSet { const parentForm = cont.parent as FormulaireDefinition; const res = parentForm.createFieldset(cont, this._jsonConfig, { "template": this, "after": after }); - res.parseConfig(this._jsonConfig, { "parentForm": parentForm }); + res.parseConfig(this._jsonConfig); parentForm.afterParseFieldset(res); return res; } diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts index 434cf111595689ac695eeaf9b219e070910f9c87..fefcb06c40b79cb44a24b99752aab5f3e4cf97a7 100644 --- a/src/app/formulaire/fieldset.ts +++ b/src/app/formulaire/fieldset.ts @@ -1,4 +1,4 @@ -import { CalculatorType, ComputeNodeType, ParamDefinition, LoiDebit, StructureType, Props, SessionNub, StructureProperties, Observer } from "jalhyd"; +import { CalculatorType, ComputeNodeType, ParamDefinition, LoiDebit, StructureType, Props, SessionNub, Observer } from "jalhyd"; import { FormulaireElement } from "./formulaire-element"; import { Dependency } from "./dependency/dependency"; @@ -15,11 +15,6 @@ import { FieldsetContainer } from "./fieldset-container"; import { FormulaireNode } from "./formulaire-node"; export class FieldSet extends FormulaireElement implements Observer { - /** - * formulaire parent - */ - private _parentForm: FormulaireDefinition; - /** * SessionNub associé */ @@ -45,12 +40,23 @@ export class FieldSet extends FormulaireElement implements Observer { this._props = new Props(); } + /** + * formulaire parent + */ + private get parentForm(): FormulaireDefinition { + let res = this.parent; + while (!(res instanceof FormulaireDefinition)) + res = res.parent; + return res as FormulaireDefinition; + + } public get sessionNub(): SessionNub { return this._sessionNub; } public setSessionNub(sn: SessionNub, update: boolean = true) { this._sessionNub = sn; + this._props.setProps(sn.properties, this); if (update) this.updateFields(); } @@ -107,22 +113,12 @@ export class FieldSet extends FormulaireElement implements Observer { return this._props; } - public getPropValue(key: string): any { + private getPropValue(key: string): any { return this._props.getPropValue(key); } - public setPropValue(key: string, val: any) { - if (this._props.setPropValue(key, val, this)) { - // si prop=type d'ouvrage, on prend une loi de débit compatible avec (spécifique aux ouvrages //) comme valeur par défaut - if (key === "structureType") { - if (!StructureProperties.isCompatibleValues(val, this.getPropValue("loiDebit"))) - this.setPropValue("loiDebit", StructureProperties.findCompatibleLoiDebit(val)); - } - // si prop=loi débit, on prend un type d'ouvrage compatible - else if (key === "loiDebit") - if (!StructureProperties.isCompatibleValues(this.getPropValue("structureType"), val)) - this.setPropValue("structureType", StructureProperties.findCompatibleStructure(val)); - } + public setPropValue(key: string, val: any): boolean { + return this._props.setPropValue(key, val, this); } /** @@ -279,31 +275,21 @@ export class FieldSet extends FormulaireElement implements Observer { // fin MAJ selects - this.applyDependencies(this._parentForm); + this.applyDependencies(this.parentForm); } public parseConfig(json: {}, data?: {}) { this._jsonConfig = json; - this._parentForm = data["parentForm"]; this._confId = json["id"]; const ct: string = json["calcType"]; - const calc_type: CalculatorType = ct == undefined ? this._parentForm.calculatorType : CalculatorType[ct]; + const calc_type: CalculatorType = ct == undefined ? this.parentForm.calculatorType : CalculatorType[ct]; this.setPropValue("calcType", calc_type); - const nt: string = json["nodeType"]; - const node_type: ComputeNodeType = nt == undefined ? ComputeNodeType.None : ComputeNodeType[nt]; - const dnt: string = json["defaultNodeType"]; - const default_node_type: ComputeNodeType = dnt == undefined ? ComputeNodeType.None : ComputeNodeType[dnt]; - - if (nt !== undefined && dnt !== undefined) - throw new Error("les champs 'nodeType' et 'defaultNodeType' ne doivent pas être définis en même temps") - - const ntype = dnt !== undefined ? default_node_type : node_type; - - this.setPropValue("nodeType", ntype); + const node_type: ComputeNodeType = dnt == undefined ? this.parentForm.nodeType : ComputeNodeType[dnt]; + this.setPropValue("nodeType", node_type); const st: string = json["defaultStructType"]; if (st) @@ -383,19 +369,85 @@ export class FieldSet extends FormulaireElement implements Observer { if (data.action) switch (data.action) { case "select": - if (data.value.id.indexOf("select_section_") != -1) // sections paramétrées + if (sender.id === "select_section") // sections paramétrées this.setPropValue("nodeType", data.value.value); - if (data.value.id.indexOf("select_ouvrage") != -1) // ouvrages parallèles + if (sender.id === "select_ouvrage") // ouvrages parallèles this.setPropValue("structureType", data.value.value); - else if (data.value.id.indexOf("select_loidebit1") != -1) // ouvrages parallèles + else if (sender.id === "select_loidebit1") // ouvrages parallèles this.setPropValue("loiDebit", data.value.value); - else if (data.value.id.indexOf("select_loidebit2") != -1) // ouvrages parallèles + else if (sender.id === "select_loidebit2") // ouvrages parallèles this.setPropValue("loiDebit", data.value.value); - else if (data.value.id.indexOf("select_resolution") != -1) // courbes de remous, méthode de résolution + else if (sender.id === "select_resolution") // courbes de remous, méthode de résolution this.setPropValue("methodeResolution", data.value.value); - else if (data.value.id.indexOf("select_target") != -1) // courbes de remous, variable à calculer + else if (sender.id === "select_target") // courbes de remous, variable à calculer this.setPropValue("varCalc", data.value.value); break; } } + + /** + * sérialisation en JSON + */ + public JSONserialise(): {} { + let res = {}; + res["id"] = this.id; + res["props"] = this._props.props; + res["elements"] = this.serialiseKids(); + return { "fieldset": res }; + } + + private deserialiseParam(elements: {}): NgParameter { + const res: NgParameter = this.getFormulaireNodeById(elements["id"]) as NgParameter; + res.deserialiseJSON(elements); + return res; + } + + private deserialiseSelect(elements: {}): SelectField { + const res: SelectField = this.getFormulaireNodeById(elements["id"]) as SelectField; + res.deserialiseJSON(elements); + return res; + } + + private deserialiseElement(element: {}) { + const keys = Object.keys(element); + if (keys.length !== 1) + throw new Error(`session file : invalid fieldset object '${element}'`); + + switch (keys[0]) { + case "param": + this.deserialiseParam(element[keys[0]]); + break; + + case "select": + this.deserialiseSelect(element[keys[0]]); + break; + + default: + throw new Error(`session file : invalid key '${keys[0]}' in fieldset object`); + } + } + + /** + * désérialisation depuis JSON + */ + public deserialiseJSON(elements: {}) { + for (const k in elements) { + switch (k) { + case "id": + break; + + case "props": + this._props = new Props(elements[k]); + break; + + case "elements": + for (const e of elements[k]) + this.deserialiseElement(e); + break; + + default: + throw new Error(`session file : invalid key '${k}' in form object`); + } + } + } } diff --git a/src/app/formulaire/formulaire-node.ts b/src/app/formulaire/formulaire-node.ts index 814d47c88f0734d4a1940a527ba12d81ece7b1f7..aca8ea0c153d692e0330f4f3f614fde0de569a62 100644 --- a/src/app/formulaire/formulaire-node.ts +++ b/src/app/formulaire/formulaire-node.ts @@ -143,4 +143,18 @@ export abstract class FormulaireNode implements IObservable { notifyObservers(data: any, sender?: any) { this._observable.notifyObservers(data, sender); } + + protected serialiseKids() { + const elems = []; + for (const k of this.kids) + elems.push(k.JSONserialise()); + return elems; + } + + /** + * sérialisation en JSON + */ + public JSONserialise(): {} { + return {}; + } } diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts index 671730429dd4413c273548c9832142561144c8f1..552594bc42c3db2db734a7d87c08b1c3fa49f523 100644 --- a/src/app/formulaire/ngparam.ts +++ b/src/app/formulaire/ngparam.ts @@ -320,4 +320,78 @@ export class NgParameter extends InputField { super.updateLocalisation(loc, key); } } + + private paramValuesJSON(): any { + let res = {}; + res["mode"] = ParamValueMode[this._paramValues.valueMode]; + switch (this._paramValues.valueMode) { + case ParamValueMode.SINGLE: + res["value"] = this._paramValues.singleValue; + break; + + case ParamValueMode.MINMAX: + res["min"] = this._paramValues.min; + res["max"] = this._paramValues.max; + res["step"] = this._paramValues.step; + break; + + case ParamValueMode.LISTE: + res["values"] = this._paramValues.valueList; + break; + } + return res; + } + + /** + * sérialisation en JSON + */ + public JSONserialise(): {} { + let res = {}; + res["id"] = this.id; + res["values"] = this.paramValuesJSON(); + return { "param": res }; + } + + private deserialiseValues(json: {}) { + const m: ParamValueMode = (<any>ParamValueMode)[json["mode"]]; + switch (m) { + case ParamValueMode.SINGLE: + this._paramValues.setValues(+json["value"]); + break; + + case ParamValueMode.MINMAX: + this._paramValues.setValues(+json["min"], +json["max"], +json["step"]); + break; + + case ParamValueMode.LISTE: + this._paramValues.setValues(json["values"]); + break; + + case ParamValueMode.CALCUL: + this._paramValues.valueMode = ParamValueMode.CALCUL; + break; + + default: + throw new Error(`session file : invalid value mode '${json["mode"]}' in param object`); + } + } + + /** + * désérialisation depuis JSON + */ + public deserialiseJSON(elements: {}) { + for (const k in elements) { + switch (k) { + case "id": + break; + + case "values": + this.deserialiseValues(elements[k]); + break; + + default: + throw new Error(`session file : invalid key '${k}' in param object`); + } + } + } } diff --git a/src/app/formulaire/select-entry.ts b/src/app/formulaire/select-entry.ts index 8da49428e9fa9e06ee76f4ceae143cb5137208a3..dc552ff9f6a52b09ba4f0625fa0f15f6781cc0bc 100644 --- a/src/app/formulaire/select-entry.ts +++ b/src/app/formulaire/select-entry.ts @@ -27,4 +27,14 @@ export class SelectEntry { get value(): any { return this._value; } + + /** + * sérialisation en JSON + */ + public JSONserialise() { + let res = {}; + res["id"] = this.id; + res["value"] = this._value; + return res; + } } diff --git a/src/app/formulaire/select-field.ts b/src/app/formulaire/select-field.ts index d6366564c38290fa786ff2951a43ce229bf7f254..916fee39c058dff6a2c0a6723517f2a470a0f7d7 100644 --- a/src/app/formulaire/select-field.ts +++ b/src/app/formulaire/select-field.ts @@ -117,4 +117,39 @@ export class SelectField extends Field { this.addEntry(e); } } + + /** + * sérialisation en JSON + */ + public JSONserialise(): {} { + let res = {}; + res["id"] = this.id; + res["selected_id"] = this._selectedEntry.id; + return { "select": res }; + } + + /** + * désérialisation depuis JSON + */ + public deserialiseJSON(elements: {}) { + for (const k in elements) { + switch (k) { + case "id": + this._confId = elements[k]; + break; + + case "selected_id": + const sel = elements[k]; + for (const e of this._entries) + if (e.id === sel) { + this._selectedEntry = e; + break; + } + break; + + default: + throw new Error(`session file : invalid key '${k}' in select object`); + } + } + } } diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts index 50e6ec6f71dc0ceb6b03282e8c46cdf64e5eabb3..5984c3f9d0e22f1188115f91d312d87721ae8e9b 100644 --- a/src/app/services/formulaire/formulaire.service.ts +++ b/src/app/services/formulaire/formulaire.service.ts @@ -3,6 +3,7 @@ import { Response } from "@angular/http"; import { Observable as rxObservable } from "rxjs/Observable"; import "rxjs/add/operator/toPromise"; import { decode } from "he"; +import { saveAs } from "file-saver" import { CalculatorType, EnumEx, Observable } from "jalhyd"; @@ -146,7 +147,7 @@ export class FormulaireService extends Observable { return this._httpService.httpGetRequest(undefined, undefined, undefined, f, processData); } - public createFormulaire(ct: CalculatorType): Promise<FormulaireDefinition> { + private newFormulaire(ct: CalculatorType, jsonState?: {}): FormulaireDefinition { let f: FormulaireDefinition; switch (ct) { case CalculatorType.ConduiteDistributrice: @@ -185,10 +186,31 @@ export class FormulaireService extends Observable { throw new Error(`FormulaireService.createFormulaire() : type de calculette ${ct} non pris en charge`) } - f.calculatorName = decode(this.getLocalisedTitleFromCalculatorType(ct) + " (" + f.uid + ")"); + if (jsonState !== undefined) { + const props = jsonState["props"]; + f.initSessionNub(props); + } + else + f.initSessionNub(); + + return f; + } + + /** + * crée un formulaire d'un type donné + * @param ct type de formulaire + * @param jsonState + */ + public createFormulaire(ct: CalculatorType, jsonState?: {}): Promise<FormulaireDefinition> { + const f: FormulaireDefinition = this.newFormulaire(ct, jsonState); + + if (jsonState === undefined) + f.calculatorName = decode(this.getLocalisedTitleFromCalculatorType(ct) + " (" + f.uid + ")"); this._formulaires.push(f); let prom: Promise<Response> = this.loadConfig(f, ct); return prom.then(_ => { + if (jsonState !== undefined) + f.deserialiseJSON(jsonState); return f; }).then(f => { this.loadUpdateFormulaireLocalisation(f); @@ -327,4 +349,123 @@ export class FormulaireService extends Observable { return false; } + + private readSingleFile(file: File): Promise<any> { + return new Promise<any>((resolve, reject) => { + var fr = new FileReader(); + + fr.onload = () => { + resolve(fr.result); + }; + + fr.onerror = () => { + fr.abort(); + reject(new Error(`Erreur de lecture du fichier ${file.name}`)); + }; + + fr.readAsText(file); + }); + } + + /** + * charge une session en tenant compte des calculettes sélectionnées + * @param f fichier session + * @param formInfos infos sur les calculettes @see LoadCalculatorComponent._calculators + */ + public loadSession(f: File, formInfos: any[]) { + this.readSingleFile(f).then(s => { + const session = JSON.parse(s); + for (const k in session) + switch (k) { + case "session": + this.deserialiseSession(session[k], formInfos); + break; + + default: + throw new Error(`session file : invalid key '${k}'`); + } + }).catch(err => { + throw err; + }); + } + + public saveSession(session: {}, filename?: string) { + const blob = new Blob([JSON.stringify(session)], { type: "text/plain;charset=utf-8" }); + saveAs(blob, filename); + } + + /** + * obtient des infos (nom, uid des calculettes) d'un fichier session + * @param f fichier session + */ + public calculatorInfosFromSessionFile(f: File): Promise<any[]> { + return this.readSingleFile(f).then(s => { + const res: any[] = []; + const session = JSON.parse(s); + + // liste des noms de calculettes + for (const k in session) + switch (k) { + case "session": + const sess = session[k]; + const elems = sess["elements"]; + for (const e of elems) + for (const k in e) + if (k === "form") { + const form = e[k]; + res.push({ "uid": form["uid"], "title": form["id"] }); + } + break; + + default: + throw new Error(`session file : invalid key '${k}'`); + } + return res; + }); + } + + public saveForm(f: FormulaireDefinition) { + this.notifyObservers({ + "action": "saveForm", + "form": f + }); + } + + private deserialiseForm(elements: {}) { + const props = elements["props"]; + const ct: CalculatorType = props["calcType"]; + this.createFormulaire(ct, elements); + } + + private deserialiseSessionElement(element: {}, formInfos: any[]) { + const keys = Object.keys(element); + if (keys.length !== 1) + throw new Error(`session file : invalid session object '${element}'`); + + switch (keys[0]) { + case "form": + const form = element[keys[0]]; + + for (const i of formInfos) + if (i["uid"] == form["uid"] && i["selected"]) + this.deserialiseForm(form); + break; + + default: + throw new Error(`session file : invalid key '${keys[0]}' in session object`); + } + } + + private deserialiseSession(elements: {}, formInfos: any[]) { + for (const ks in elements) + switch (ks) { + case "elements": + for (const e of elements[ks]) + this.deserialiseSessionElement(e, formInfos); + break; + + default: + throw new Error(`session file : invalid key '${ks}' in session object`); + } + } } diff --git a/src/app/services/internationalisation/internationalisation.service.ts b/src/app/services/internationalisation/internationalisation.service.ts index f418a88998bd01789e1106247a3002eeebe225da..d5e3486b82e5b5517b97ff7e00e6f4c738895b36 100644 --- a/src/app/services/internationalisation/internationalisation.service.ts +++ b/src/app/services/internationalisation/internationalisation.service.ts @@ -192,24 +192,27 @@ export class InternationalisationService extends Observable { default: const match = this.parseLabel(o); - if (match) + if (match) { if (match[1] === "ouvrage") res = this.localizeText("INFO_OUVRAGE") + " n°" + (+match[2] + 1); - const p = match[3]; - switch (p) { - case "Q": - res += " : " + this.localizeText("INFO_GRANDEUR_" + p); - break; + const p = match[3]; + switch (p) { + case "Q": + res += " : " + this.localizeText("INFO_GRANDEUR_" + p); + break; - case "Q_Mode": - res += " : " + this.localizeText("INFO_TYPE_ECOULEMENT"); - break; + case "Q_Mode": + res += " : " + this.localizeText("INFO_TYPE_ECOULEMENT"); + break; - case "Q_Regime": - res += " : " + this.localizeText("INFO_REGIME"); - break; + case "Q_Regime": + res += " : " + this.localizeText("INFO_REGIME"); + break; + } } + else + res = o; break; } }