diff --git a/src/app/calculators/prebarrage/fr.json b/src/app/calculators/prebarrage/fr.json index e10e659961bb80b9c634f61af2f2f38c414541fa..b770ea7b83b373ae2ae18e3d6779897309bc1a68 100644 --- a/src/app/calculators/prebarrage/fr.json +++ b/src/app/calculators/prebarrage/fr.json @@ -5,6 +5,6 @@ "Xinit": "Valeur initiale du paramètre recherché", "X": "Valeur du paramètre recherché", - "select_upstream": "Bassin / cloison amont", - "select_downstream": "Bassin / cloison aval" + "select_upstream": "Bassin amont", + "select_downstream": "Bassin aval" } \ No newline at end of file diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts index a377bd9398cda1ff9239ba8a06b6664db2774673..951751efd94c279ccfdd3a254b79b3496673ffb8 100644 --- a/src/app/components/generic-calculator/calculator.component.ts +++ b/src/app/components/generic-calculator/calculator.component.ts @@ -48,6 +48,7 @@ import { MultiDimensionResults } from "../../results/multidimension-results"; import { NgParameter } from "../../formulaire/elements/ngparam"; import { FormulaireFixedVar } from "../../formulaire/definition/form-fixedvar"; import { PbSchema } from "../../formulaire/elements/pb-schema"; +import { PbSchemaComponent } from "../pb-schema/pb-schema.component"; import { HotkeysService, Hotkey } from "angular2-hotkeys"; @@ -77,6 +78,12 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe @ViewChild(PabTableComponent) private _pabTableComponent: PabTableComponent; + /** + * PbSchemaComponent if any + */ + @ViewChild(PbSchemaComponent) + private _pbSchemaComponent: PbSchemaComponent; + /** * composant d'affichage des résultats */ @@ -503,6 +510,10 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe this._isUIValid = this._isUIValid && this._pabTableComponent.isValid; } } + + if (this._pbSchemaComponent !== undefined) { + this._isUIValid = this._isUIValid && this._pbSchemaComponent.isValid; + } } public getElementStyleDisplay(id: string) { diff --git a/src/app/components/pb-schema/pb-schema.component.html b/src/app/components/pb-schema/pb-schema.component.html index 0c35fbbc4b40a13759e109003a1374cbcbd59d44..d87b406bc687360e1e4c87bdeebee77030ca807d 100644 --- a/src/app/components/pb-schema/pb-schema.component.html +++ b/src/app/components/pb-schema/pb-schema.component.html @@ -40,4 +40,6 @@ <div id="schema" #schema></div> + <pre id="debug">{{ graphDef }} </pre> + </mat-card-content> diff --git a/src/app/components/pb-schema/pb-schema.component.scss b/src/app/components/pb-schema/pb-schema.component.scss index eccdfe35b464b52ef685a490a05e48478e83a489..7c3c8cb0bec3dffac0c0d1c00b1e9d9fd8fc28e0 100644 --- a/src/app/components/pb-schema/pb-schema.component.scss +++ b/src/app/components/pb-schema/pb-schema.component.scss @@ -49,3 +49,7 @@ mat-card-content { margin-bottom: .5em; text-align: center; } + +#debug { + /* display: none; */ +} diff --git a/src/app/components/pb-schema/pb-schema.component.ts b/src/app/components/pb-schema/pb-schema.component.ts index a5556947341f19b025cd9cf35af3d0193f50f367..7b4a8af0c797f353a832262dad5fa612c7288bf0 100644 --- a/src/app/components/pb-schema/pb-schema.component.ts +++ b/src/app/components/pb-schema/pb-schema.component.ts @@ -1,7 +1,7 @@ import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit, ViewChild } from "@angular/core"; import { - PreBarrage, PbBassin, PbBassinParams, PbCloison + PreBarrage, PbBassin, PbBassinParams, PbCloison, CalculatorType } from "jalhyd"; import * as mermaid from "mermaid"; @@ -32,7 +32,7 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { /** handle on SVG container */ private nativeElement: any; - /** flag de validité des FieldSet enfants */ + /** flag de validité du composant */ private _isValid = false; private upstreamId = "amont"; @@ -51,7 +51,13 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { private model: PreBarrage; /** Latest clicked item: a PbCloison, a PbBassin or undefined if river "Upstream" or "Downstream" was clicked */ - private _selectedItem: any; + private _selectedItem: PbCloison | PbBassin; + + /** Records existing walls as they are built, to detect if multiple walls connect the same pair of basins */ + private existingWalls: { [key: string]: number }; + + /** Stores appropriate number suffix for a given wall uid (related to existingWalls above) */ + private wallsSuffixes: { [key: string]: number }; public constructor( private i18nService: I18nService, @@ -91,7 +97,6 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { * Builds the interactive schema from the PreBarrage model */ private refresh() { - console.log("riz fraîche"); this.render(); this.refreshEventListeners(); this.updateValidity(); @@ -107,26 +112,29 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { this.nativeElement.querySelectorAll("g.node").forEach(item => { item.style.cursor = "pointer"; item.addEventListener("click", () => { - this.selectBasin(item.id); - }); - }); - this.nativeElement.querySelectorAll("g.edgeLabel").forEach(item => { - item.style.cursor = "pointer"; - item.addEventListener("click", () => { - this.selectWall(item); + this.selectNode(item); }); }); } /** - * Builds a Mermaid graph text definition + * Builds a Mermaid graph text definition, using Nodes + * to represent basins as well as walls */ private graphDefinition() { + this.existingWalls = {}; + this.wallsSuffixes = {}; const def: string[] = [ "graph TB" ]; + // river upstream / downstream def.push(`${this.upstreamId}("${this.i18nService.localizeText("INFO_LIB_AMONT")}")`); def.push(`${this.downstreamId}("${this.i18nService.localizeText("INFO_LIB_AVAL")}")`); + // styles + def.push("classDef wall fill:#e8e8e8,stroke-width:0;"); + def.push("classDef node-highlighted fill:orange;"); + // def.push("classDef basin fill:black;"); + // debug if (this.model.children.length === 0) { const b1 = new PbBassin(new PbBassinParams(0.1, 42)); @@ -139,92 +147,58 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { this.model.addChild(new PbCloison(b1, undefined)); } - for (const b of this.model.bassins) { - // basin - def.push(`${b.uid}("${this.itemDesription(b)}")`); - // upstream walls - for (const uw of b.cloisonsAmont) { - const upstreamBasinId = uw.bassinAmont === undefined ? this.upstreamId : uw.bassinAmont.uid; - // upstream wall unique identifier - const uwString = `${upstreamBasinId}-->|${this.itemDesription(uw)}|${b.uid}`; - if (! def.includes(uwString)) { - def.push(uwString); + for (const c of this.model.children) { + if (c instanceof PbBassin) { + def.push(`${c.uid}("${this.itemDesription(c)}")`); // rounded edges + // def.push(`class ${c.uid} basin;`); + } else if (c instanceof PbCloison) { + const upstreamBasinId = c.bassinAmont === undefined ? this.upstreamId : c.bassinAmont.uid; + const downstreamBasinId = c.bassinAval === undefined ? this.downstreamId : c.bassinAval.uid; + // record this wall + const basinsPair = upstreamBasinId + "-" + downstreamBasinId; + if (! (basinsPair in this.existingWalls)) { + this.existingWalls[basinsPair] = 0; } - } - // downstream walls - for (const dw of b.cloisonsAval) { - const downstreamBasinId = dw.bassinAval === undefined ? this.downstreamId : dw.bassinAval.uid; - // downstream wall unique identifier - const dwString = `${b.uid}-->|${this.itemDesription(dw)}|${downstreamBasinId}`; - if (! def.includes(dwString)) { - def.push(dwString); + // affect suffix if needed + if (this.existingWalls[basinsPair] > 0) { + this.wallsSuffixes[c.uid] = this.existingWalls[basinsPair]; } + this.existingWalls[basinsPair]++; + // draw wall Node + def.push(`${c.uid}["${this.itemDesription(c)}"]`); // square edges + def.push(`class ${c.uid} wall;`); + // draw "arrow" with 2 lines + def.push(`${upstreamBasinId}---${c.uid}`); // up line + def.push(`${c.uid}-->${downstreamBasinId}`); // down arrow } } return def.join("\n"); } - private selectBasin(id: string) { - if ([ this.upstreamId, this.downstreamId ].includes(id)) { - console.log("YOU CLICKED EITHER UPSTREAM OR DOWNSTREAM"); + private selectNode(item: any) { + // highlight clicked element + this.clearHighlightedItems(); + item.classList.add("node-highlighted"); + // find what was clicked + if ([ this.upstreamId, this.downstreamId ].includes(item.id)) { + console.log("YOU CLICKED EITHER UPSTREAM OR DOWNSTREAM", item.id); this._selectedItem = undefined; } else { - let basin: PbBassin; - for (const b of this.model.bassins) { - if (b.uid === id) { - basin = b; + for (const b of this.model.children) { + if (b.uid === item.id) { + this._selectedItem = b; } } - this._selectedItem = basin; - // @TODO highlight node in schema - console.log("BASIN FOUND !", basin); - } - } - - private selectWall(item: SVGGElement) { - // Mermaid does not allow to assign IDs to connectors and labels… - const text: string = item.querySelector("span.edgeLabel").textContent; - if (text) { - const [ uBs, dBs ] = text.split("-"); - let wall: PbCloison; - // clodo test: is there an upstream basin or is it upstream river ? - if (uBs === this.i18nService.localizeText("INFO_LIB_AMONT")) { - // find wall from downstream basin - const dBi = Number(dBs.substring(1)); - const dB = this.model.bassins[dBi - 1]; - for (const w of dB.cloisonsAmont) { - // find the one that is connected to upstream river - if (w.bassinAmont === undefined) { - wall = w; - } - } + if (this._selectedItem !== undefined) { + console.log(`${this._selectedItem.calcType === CalculatorType.PbBassin ? "BASIN" : "WALL"} FOUND !`, this._selectedItem); } else { - // find wall from upstream basin - const uBi = Number(uBs.substring(1)); - const uB = this.model.bassins[uBi - 1]; - // clodo test again - let dB: PbBassin; - if (dBs !== this.i18nService.localizeText("INFO_LIB_AVAL")) { - const dBi = Number(dBs.substring(1)); - dB = this.model.bassins[dBi - 1]; - } - for (const w of uB.cloisonsAval) { - // find the one that is connected to dB (either a basin or downstream river) - if (w.bassinAval === dB) { - wall = w; - } - } + console.log("watt ze fyook ?"); } - if (wall === undefined) { - throw new Error(`PbSchemaComponent.selectWall(): cannot find wall for label "${text}"`); - } - this._selectedItem = wall; - // @TODO highlight label and edge in schema - console.log("WALL FOUND !", wall); } } + // for debug only public get graphDef(): string { return this.graphDefinition(); } @@ -274,7 +248,10 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { ? this.i18nService.localizeText("INFO_LIB_AVAL") : "B" + (this.findBasinPosition(item.bassinAval) + 1); desc = upstreamBasinName + "-" + downstreamBasinName; - + // if a similar wall already exists, suffix ! + if (item.uid in this.wallsSuffixes) { + desc += " (" + this.wallsSuffixes[item.uid] + ")"; + } } else if (item instanceof PbBassin) { desc = this.i18nService.localizeText("INFO_PB_BASSIN_N") + (this.findBasinPosition(item) + 1); } // else undefined @@ -302,6 +279,7 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { /** Removes a basin or wall, and all related items */ public onRemoveClick() { this.model.deleteChild(this._selectedItem.findPositionInParent()); + this.unselect(); this.refresh(); } @@ -309,9 +287,10 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { return this.i18nService.localizeText("INFO_FIELDSET_REMOVE"); } + /** Adds a new lone basin */ public onAddBasinClick() { - console.log("Ajoute un bassin, coquin !"); this.model.addChild(new PbBassin(new PbBassinParams(20, 99))); + this.unselect(); this.refresh(); } @@ -323,36 +302,22 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { * Computes the global Pab validity : validity of every cell of every row */ private updateValidity() { - this._isValid = true; - /* for (const r of this.rows) { - for (const c of r.cells) { - this._isValid = this._isValid && ! this.isInvalid(c); - } - } */ + // check that at least 1 basin is present and a route from river + // upstream to river downstream exists (2nd check includes 1st) + this._isValid = this.model.hasUpDownConnection(); this.validChange.emit(); } - /* public exportAsSpreadsheet() { - const elem: any = document.getElementById("geometry"); - const elemCopy = (elem as HTMLElement).cloneNode(true) as HTMLElement; - // enrich element copy: replace inputs by their values, so that it appears in the exported spreadsheet - const tables: any = elemCopy.getElementsByTagName("table"); - for (const table of tables) { - const tds: any = table.getElementsByTagName("td"); - for (const td of tds) { - // if it contains an input, replace it with the input value - const inputs = td.getElementsByTagName("input"); - if (inputs.length > 0) { - const input = inputs[0]; - td.innerHTML = input.value; - } - } - } - // export the enriched element copy - AppComponent.exportAsSpreadsheet(elemCopy as any); + private clearHighlightedItems() { + this.nativeElement.querySelectorAll("g.node").forEach(item => { + console.log("found an item !"); + item.classList.remove("node-highlighted"); + }); + } + + private unselect() { + this._selectedItem = undefined; + this.clearHighlightedItems(); } - public get uitextExportAsSpreadsheet() { - return this.i18nService.localizeText("INFO_RESULTS_EXPORT_AS_SPREADSHEET"); - } */ }