Skip to content
Snippets Groups Projects
Commit 5c908beb authored by Mathias Chouet's avatar Mathias Chouet
Browse files

Prebarrage: GUI

grey out Calculate button if basins structure is invalid
use Mermaid Nodes for walls
suffix multiple walls on same basins pair
base Mermaid Nodes on children UIDs
display Mermaid code (debug)
unselect element after deletion
highlight selected element
parent 874fb7a7
No related branches found
No related tags found
1 merge request!96WIP: Resolve "Simplifier les composants de résultats"
...@@ -5,6 +5,6 @@ ...@@ -5,6 +5,6 @@
"Xinit": "Valeur initiale du paramètre recherché", "Xinit": "Valeur initiale du paramètre recherché",
"X": "Valeur du paramètre recherché", "X": "Valeur du paramètre recherché",
"select_upstream": "Bassin / cloison amont", "select_upstream": "Bassin amont",
"select_downstream": "Bassin / cloison aval" "select_downstream": "Bassin aval"
} }
\ No newline at end of file
...@@ -45,6 +45,7 @@ import { PabTable } from "../../formulaire/elements/pab-table"; ...@@ -45,6 +45,7 @@ import { PabTable } from "../../formulaire/elements/pab-table";
import { MultiDimensionResults } from "../../results/multidimension-results"; import { MultiDimensionResults } from "../../results/multidimension-results";
import { NgParameter } from "../../formulaire/elements/ngparam"; import { NgParameter } from "../../formulaire/elements/ngparam";
import { PbSchema } from "../../formulaire/elements/pb-schema"; import { PbSchema } from "../../formulaire/elements/pb-schema";
import { PbSchemaComponent } from "../pb-schema/pb-schema.component";
import { HotkeysService, Hotkey } from "angular2-hotkeys"; import { HotkeysService, Hotkey } from "angular2-hotkeys";
...@@ -74,6 +75,12 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe ...@@ -74,6 +75,12 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
@ViewChild(PabTableComponent) @ViewChild(PabTableComponent)
private _pabTableComponent: PabTableComponent; private _pabTableComponent: PabTableComponent;
/**
* PbSchemaComponent if any
*/
@ViewChild(PbSchemaComponent)
private _pbSchemaComponent: PbSchemaComponent;
/** /**
* composant d'affichage des résultats * composant d'affichage des résultats
*/ */
...@@ -484,6 +491,10 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe ...@@ -484,6 +491,10 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
if (this._pabTableComponent !== undefined) { if (this._pabTableComponent !== undefined) {
this._isUIValid = this._isUIValid && this._pabTableComponent.isValid; this._isUIValid = this._isUIValid && this._pabTableComponent.isValid;
} }
if (this._pbSchemaComponent !== undefined) {
this._isUIValid = this._isUIValid && this._pbSchemaComponent.isValid;
}
} }
public getElementStyleDisplay(id: string) { public getElementStyleDisplay(id: string) {
......
...@@ -40,4 +40,6 @@ ...@@ -40,4 +40,6 @@
<div id="schema" #schema></div> <div id="schema" #schema></div>
<pre id="debug">{{ graphDef }} </pre>
</mat-card-content> </mat-card-content>
...@@ -49,3 +49,7 @@ mat-card-content { ...@@ -49,3 +49,7 @@ mat-card-content {
margin-bottom: .5em; margin-bottom: .5em;
text-align: center; text-align: center;
} }
#debug {
/* display: none; */
}
import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit, ViewChild } from "@angular/core"; import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit, ViewChild } from "@angular/core";
import { import {
PreBarrage, PbBassin, PbBassinParams, PbCloison PreBarrage, PbBassin, PbBassinParams, PbCloison, CalculatorType
} from "jalhyd"; } from "jalhyd";
import * as mermaid from "mermaid"; import * as mermaid from "mermaid";
...@@ -32,7 +32,7 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { ...@@ -32,7 +32,7 @@ export class PbSchemaComponent implements AfterViewInit, OnInit {
/** handle on SVG container */ /** handle on SVG container */
private nativeElement: any; private nativeElement: any;
/** flag de validité des FieldSet enfants */ /** flag de validité du composant */
private _isValid = false; private _isValid = false;
private upstreamId = "amont"; private upstreamId = "amont";
...@@ -51,7 +51,13 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { ...@@ -51,7 +51,13 @@ export class PbSchemaComponent implements AfterViewInit, OnInit {
private model: PreBarrage; private model: PreBarrage;
/** Latest clicked item: a PbCloison, a PbBassin or undefined if river "Upstream" or "Downstream" was clicked */ /** 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( public constructor(
private i18nService: I18nService, private i18nService: I18nService,
...@@ -91,7 +97,6 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { ...@@ -91,7 +97,6 @@ export class PbSchemaComponent implements AfterViewInit, OnInit {
* Builds the interactive schema from the PreBarrage model * Builds the interactive schema from the PreBarrage model
*/ */
private refresh() { private refresh() {
console.log("riz fraîche");
this.render(); this.render();
this.refreshEventListeners(); this.refreshEventListeners();
this.updateValidity(); this.updateValidity();
...@@ -107,26 +112,29 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { ...@@ -107,26 +112,29 @@ export class PbSchemaComponent implements AfterViewInit, OnInit {
this.nativeElement.querySelectorAll("g.node").forEach(item => { this.nativeElement.querySelectorAll("g.node").forEach(item => {
item.style.cursor = "pointer"; item.style.cursor = "pointer";
item.addEventListener("click", () => { item.addEventListener("click", () => {
this.selectBasin(item.id); this.selectNode(item);
});
});
this.nativeElement.querySelectorAll("g.edgeLabel").forEach(item => {
item.style.cursor = "pointer";
item.addEventListener("click", () => {
this.selectWall(item);
}); });
}); });
} }
/** /**
* Builds a Mermaid graph text definition * Builds a Mermaid graph text definition, using Nodes
* to represent basins as well as walls
*/ */
private graphDefinition() { private graphDefinition() {
this.existingWalls = {};
this.wallsSuffixes = {};
const def: string[] = [ "graph TB" ]; const def: string[] = [ "graph TB" ];
// river upstream / downstream
def.push(`${this.upstreamId}("${this.i18nService.localizeText("INFO_LIB_AMONT")}")`); def.push(`${this.upstreamId}("${this.i18nService.localizeText("INFO_LIB_AMONT")}")`);
def.push(`${this.downstreamId}("${this.i18nService.localizeText("INFO_LIB_AVAL")}")`); 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 // debug
if (this.model.children.length === 0) { if (this.model.children.length === 0) {
const b1 = new PbBassin(new PbBassinParams(0.1, 42)); const b1 = new PbBassin(new PbBassinParams(0.1, 42));
...@@ -139,92 +147,58 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { ...@@ -139,92 +147,58 @@ export class PbSchemaComponent implements AfterViewInit, OnInit {
this.model.addChild(new PbCloison(b1, undefined)); this.model.addChild(new PbCloison(b1, undefined));
} }
for (const b of this.model.bassins) { for (const c of this.model.children) {
// basin if (c instanceof PbBassin) {
def.push(`${b.uid}("${this.itemDesription(b)}")`); def.push(`${c.uid}("${this.itemDesription(c)}")`); // rounded edges
// upstream walls // def.push(`class ${c.uid} basin;`);
for (const uw of b.cloisonsAmont) { } else if (c instanceof PbCloison) {
const upstreamBasinId = uw.bassinAmont === undefined ? this.upstreamId : uw.bassinAmont.uid; const upstreamBasinId = c.bassinAmont === undefined ? this.upstreamId : c.bassinAmont.uid;
// upstream wall unique identifier const downstreamBasinId = c.bassinAval === undefined ? this.downstreamId : c.bassinAval.uid;
const uwString = `${upstreamBasinId}-->|${this.itemDesription(uw)}|${b.uid}`; // record this wall
if (! def.includes(uwString)) { const basinsPair = upstreamBasinId + "-" + downstreamBasinId;
def.push(uwString); if (! (basinsPair in this.existingWalls)) {
this.existingWalls[basinsPair] = 0;
} }
} // affect suffix if needed
// downstream walls if (this.existingWalls[basinsPair] > 0) {
for (const dw of b.cloisonsAval) { this.wallsSuffixes[c.uid] = this.existingWalls[basinsPair];
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);
} }
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"); return def.join("\n");
} }
private selectBasin(id: string) { private selectNode(item: any) {
if ([ this.upstreamId, this.downstreamId ].includes(id)) { // highlight clicked element
console.log("YOU CLICKED EITHER UPSTREAM OR DOWNSTREAM"); 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; this._selectedItem = undefined;
} else { } else {
let basin: PbBassin; for (const b of this.model.children) {
for (const b of this.model.bassins) { if (b.uid === item.id) {
if (b.uid === id) { this._selectedItem = b;
basin = b;
} }
} }
this._selectedItem = basin; if (this._selectedItem !== undefined) {
// @TODO highlight node in schema console.log(`${this._selectedItem.calcType === CalculatorType.PbBassin ? "BASIN" : "WALL"} FOUND !`, this._selectedItem);
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;
}
}
} else { } else {
// find wall from upstream basin console.log("watt ze fyook ?");
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;
}
}
} }
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 { public get graphDef(): string {
return this.graphDefinition(); return this.graphDefinition();
} }
...@@ -274,7 +248,10 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { ...@@ -274,7 +248,10 @@ export class PbSchemaComponent implements AfterViewInit, OnInit {
? this.i18nService.localizeText("INFO_LIB_AVAL") ? this.i18nService.localizeText("INFO_LIB_AVAL")
: "B" + (this.findBasinPosition(item.bassinAval) + 1); : "B" + (this.findBasinPosition(item.bassinAval) + 1);
desc = upstreamBasinName + "-" + downstreamBasinName; 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) { } else if (item instanceof PbBassin) {
desc = this.i18nService.localizeText("INFO_PB_BASSIN_N") + (this.findBasinPosition(item) + 1); desc = this.i18nService.localizeText("INFO_PB_BASSIN_N") + (this.findBasinPosition(item) + 1);
} // else undefined } // else undefined
...@@ -302,6 +279,7 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { ...@@ -302,6 +279,7 @@ export class PbSchemaComponent implements AfterViewInit, OnInit {
/** Removes a basin or wall, and all related items */ /** Removes a basin or wall, and all related items */
public onRemoveClick() { public onRemoveClick() {
this.model.deleteChild(this._selectedItem.findPositionInParent()); this.model.deleteChild(this._selectedItem.findPositionInParent());
this.unselect();
this.refresh(); this.refresh();
} }
...@@ -309,9 +287,10 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { ...@@ -309,9 +287,10 @@ export class PbSchemaComponent implements AfterViewInit, OnInit {
return this.i18nService.localizeText("INFO_FIELDSET_REMOVE"); return this.i18nService.localizeText("INFO_FIELDSET_REMOVE");
} }
/** Adds a new lone basin */
public onAddBasinClick() { public onAddBasinClick() {
console.log("Ajoute un bassin, coquin !");
this.model.addChild(new PbBassin(new PbBassinParams(20, 99))); this.model.addChild(new PbBassin(new PbBassinParams(20, 99)));
this.unselect();
this.refresh(); this.refresh();
} }
...@@ -323,36 +302,22 @@ export class PbSchemaComponent implements AfterViewInit, OnInit { ...@@ -323,36 +302,22 @@ export class PbSchemaComponent implements AfterViewInit, OnInit {
* Computes the global Pab validity : validity of every cell of every row * Computes the global Pab validity : validity of every cell of every row
*/ */
private updateValidity() { private updateValidity() {
this._isValid = true; // check that at least 1 basin is present and a route from river
/* for (const r of this.rows) { // upstream to river downstream exists (2nd check includes 1st)
for (const c of r.cells) { this._isValid = this.model.hasUpDownConnection();
this._isValid = this._isValid && ! this.isInvalid(c);
}
} */
this.validChange.emit(); this.validChange.emit();
} }
/* public exportAsSpreadsheet() { private clearHighlightedItems() {
const elem: any = document.getElementById("geometry"); this.nativeElement.querySelectorAll("g.node").forEach(item => {
const elemCopy = (elem as HTMLElement).cloneNode(true) as HTMLElement; console.log("found an item !");
// enrich element copy: replace inputs by their values, so that it appears in the exported spreadsheet item.classList.remove("node-highlighted");
const tables: any = elemCopy.getElementsByTagName("table"); });
for (const table of tables) { }
const tds: any = table.getElementsByTagName("td");
for (const td of tds) { private unselect() {
// if it contains an input, replace it with the input value this._selectedItem = undefined;
const inputs = td.getElementsByTagName("input"); this.clearHighlightedItems();
if (inputs.length > 0) {
const input = inputs[0];
td.innerHTML = input.value;
}
}
}
// export the enriched element copy
AppComponent.exportAsSpreadsheet(elemCopy as any);
} }
public get uitextExportAsSpreadsheet() {
return this.i18nService.localizeText("INFO_RESULTS_EXPORT_AS_SPREADSHEET");
} */
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment