pab-table.component.ts 27.45 KiB
import { Component, Input, Output, EventEmitter, OnInit } from "@angular/core";
import {
    Pab,
    Session,
    PabCloisons,
    Props,
    CalculatorType,
    ParallelStructure,
    LoiDebit,
    Cloisons,
    Nub,
    Structure
 } from "jalhyd";
import { I18nService } from "../../services/internationalisation/internationalisation.service";
import { PabTable } from "../../formulaire/pab-table";
/**
 * The big editable data grid for calculator type "Pab" (component)
@Component({
    selector: "pab-table",
    templateUrl: "./pab-table.component.html",
    styleUrls: [
        "./pab-table.component.scss"
export class PabTableComponent implements /* DoCheck, AfterViewInit, */ OnInit {
    @Input()
    private pabTable: PabTable;
    /** flag de validité des FieldSet enfants */
    private _isValid = false;
    /** événément de changement de validité */
    @Output()
    private validChange = new EventEmitter();
    /** événément de changement de valeur d'un input */
    @Output()
    private inputChange = new EventEmitter();
    /** underlying Pab, binded to the rows */
    private model: Pab;
    /** general headers above the columns */
    public headers: any[];
    /** columns headers description */
    public cols: any[];
    /** data binded to the table */
    public rows: any[];
    /** number of children to add when clicking "add" or "clone" button */
    public childrenToAdd = 1;
    /** items currently selected */
    private selectedItems: any[];
    /** used for shift+click implementation */
    private latestClickedCell: any;
    public constructor(
        private i18nService: I18nService
    ) {
        this.selectedItems = [];
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
public get title(): string { return this.i18nService.localizeText("INFO_PAB_TABLE"); } public get isValid() { return this._isValid; } /** returns true if the cell has an underlying model (ie. is editable) */ public hasModel(cell: any): boolean { return (cell !== undefined && cell.model !== undefined); } /** returns true if the cell is an editable number */ public isNumberInput(cell: any): boolean { return this.hasModel(cell) && ! this.isSelect(cell); } /** returns true if the cell is a select box @TODO rename */ public isSelect(cell: any): boolean { return this.hasModel(cell) && (cell.options !== undefined); } /** value to display in a cell, if it is not editable */ public cellValue(cell: any) { if (cell === undefined) { return ""; } else { if (this.hasModel(cell)) { return cell.model; } else { return cell.value; } } } public rowSpan(cell: any) { if (cell !== undefined && cell.rowspan) { return cell.rowspan; } return undefined; } public colSpan(cell: any) { if (cell !== undefined && cell.colspan) { return cell.colspan; } return undefined; } /** returns true if the cell / row has a selectable item */ public isSelectable(cellOrRow: any): boolean { return ( cellOrRow !== undefined && cellOrRow.selectable ); } /** returns true if the cell / row has a selectableColumn item */ public isSelectableByColumn(cellOrRow: any): boolean { return ( cellOrRow !== undefined && cellOrRow.selectableColumn ); } /** * - checks if the cell / row has a selectable item, that is currently * selected
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
* - if cell / row has a selectableColumn attribute, also checks if * this column is selected * * returns true if at least one criterion is met */ public isSelected(cellOrRow: any): boolean { let cellSelected = false; let columnSelected = false; // cell if (this.isSelectable(cellOrRow)) { cellSelected = true; if (Array.isArray(cellOrRow.selectable)) { for (const elt of cellOrRow.selectable) { cellSelected = cellSelected && this.selectedItems.includes(elt); } } else { cellSelected = this.selectedItems.includes(cellOrRow.selectable); } } // column if (this.isSelectableByColumn(cellOrRow)) { columnSelected = this.isDeviceColumnSelected(cellOrRow.selectableColumn); } // done return (cellSelected || columnSelected); } /** * returns true if every wall (including downwall) has its nth device * selected (or has no nth device) */ public isDeviceColumnSelected(n: number): boolean { let ok = true; for (const c of this.model.children) { const nthChild = c.getChildren()[n]; if (nthChild) { ok = ok && this.selectedItems.includes(nthChild); } } const nthChildDW = this.model.downWall.getChildren()[n]; if (nthChildDW) { ok = ok && this.selectedItems.includes(nthChildDW); } return ok; } /** * selects or unselects the clicked cell, depending on its current state * and the modifier key held if any */ public toggleSelection(cell: any, $event: any) { if ( this.isSelectable(cell) && ! this.hasModel(cell) // editable cells listen to the click event for edition only ) { if ($event.shiftKey && cell !== this.latestClickedCell) { // shift + click // @TODO interpopolate from this.latestClickedCell to this one console.log("shift + click"); } else if ( $event.ctrlKey // ctrl + click || ($event.shiftKey && cell === this.latestClickedCell) // shift on same cell => equiv. of ctrl ) { if (this.isSelected(cell)) { // unselect this cell / these cells if (Array.isArray(cell.selectable)) { this.selectedItems = this.selectedItems.filter(e => ! cell.selectable.includes(e)); } else { this.selectedItems = this.selectedItems.filter(e => e !== cell.selectable); }
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
} else { // add this cell / these cells to selection if (Array.isArray(cell.selectable)) { this.selectedItems = this.selectedItems.concat(cell.selectable); this.selectedItems = this.selectedItems.filter( (item, index) => this.selectedItems.indexOf(item) === index // deduplicate ); } else { this.selectedItems.push(cell.selectable); } } } else { // just a click if (this.isSelected(cell)) { // select nothing this.selectedItems = []; } else { // select this cell / thses cells only if (Array.isArray(cell.selectable)) { this.selectedItems = cell.selectable.slice(); // array copy } else { this.selectedItems = [ cell.selectable ]; } } } this.latestClickedCell = cell; // clean list this.selectedItems = this.selectedItems.filter(e => e !== undefined); // @TODO useful ? $event.preventDefault(); $event.stopPropagation(); return false; } } // prevents Firefox to display weird cell border when ctrl+clicking public preventCtrlClickBorder($event) { if ($event.ctrlKey) { $event.preventDefault(); } } public get addManyOptionsList() { return Array(20).fill(0).map((value, index) => index + 1); } // DEBUG private dumpParams(n: Nub) { console.log(`---- PARAMETERS FOR NUB "${n.uid}" (${n.constructor.name}) ----`); for (const p of n.parameterIterator) { if (p.visible) { console.log(`> ${p.symbol} : ${p.singleValue}, ${p.currentValue}, ${p.v}`); } } } public zpouet() { console.log("!!!------------ zpoueeeeeeeeet ------------!!!"); for (const c of this.model.children) { this.dumpParams(c); } this.dumpParams(this.model.downWall); } // at this time @Input data is supposed to be already populated public ngOnInit() { // get model this.model = this.pabTable.pab;
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
// ------------- TEST ------------------------ // empty PAB const chLen = this.model.children.length; for (let i = 0; i < chLen; i++) { this.model.deleteChild(0); } // cloison à 1 ouvrage const cl1 = Session.getInstance().createNub( new Props({ calcType: CalculatorType.Cloisons }) ) as Cloisons; cl1.addChild(Session.getInstance().createNub( new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.RectangularOrificeFree }), cl1 )); // PabCloison associé const pc1 = Session.getInstance().createNub( new Props({ calcType: CalculatorType.PabCloisons }), this.model ) as PabCloisons; pc1.initModelCloisons(cl1); pc1.prms.setCurrentValuesFromModel(); this.model.addChild(pc1); this.dumpParams(pc1); // cloison à 3 ouvrages const cl2 = Session.getInstance().createNub( new Props({ calcType: CalculatorType.Cloisons }) ) as Cloisons; cl2.addChild(Session.getInstance().createNub( new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.GateCem88v }), cl2 )); cl2.addChild(Session.getInstance().createNub( new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.RectangularOrificeFree }), cl2 )); cl2.addChild(Session.getInstance().createNub( new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.RectangularOrificeSubmerged }), cl2 )); // PabCloison associé const pc2 = Session.getInstance().createNub( new Props({ calcType: CalculatorType.PabCloisons }), this.model ) as PabCloisons; pc2.initModelCloisons(cl2); pc2.prms.setCurrentValuesFromModel(); this.model.addChild(pc2); this.dumpParams(pc2); // cloison à 2 ouvrages const cl3 = Session.getInstance().createNub( new Props({ calcType: CalculatorType.Cloisons }) ) as Cloisons; cl3.addChild(Session.getInstance().createNub( new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.GateCem88v }), cl3 )); cl3.addChild(Session.getInstance().createNub( new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.RectangularOrificeFree }), cl3 )); // PabCloison associé const pc3 = Session.getInstance().createNub( new Props({ calcType: CalculatorType.PabCloisons }), this.model ) as PabCloisons; pc3.initModelCloisons(cl3); pc3.prms.setCurrentValuesFromModel(); this.model.addChild(pc3); this.dumpParams(pc3); // cloison aval à 1 ouvrage const cloisonAval: ParallelStructure = Session.getInstance().createSessionNub( new Props({ calcType: CalculatorType.ParallelStructure }) ) as ParallelStructure;
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
cloisonAval.addChild(Session.getInstance().createNub( new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.KIVI }), cloisonAval )); this.model.setDownWall(cloisonAval.uid); this.dumpParams(this.model.downWall); // debug console.log("Model in ngOnInit:", this.model); // ------------- FIN TEST -------------------- this.buildTable(); } /** * Builds the editable data grid from the Pab model */ private buildTable() { const maxNbDevices = this.findMaxNumberOfDevices(); // 0. build spanned headers over real columns this.headers = []; // 1 header for basin let bs: any[] = this.model.children; bs = bs.concat(this.model.downWall); this.headers.push({ title: "Bassin", colspan: 6, selectable: bs }); // 1 header for each device of the wall having the most devices (including downwall) for (let i = 0; i < maxNbDevices; i++) { this.headers.push({ title: "Cloison : ouvrage n°" + (i + 1), colspan: 3, selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]), selectableColumn: i }); } // A. build columns set this.cols = []; // 6 cols for basin this.cols.push({ title: "N° de bassin", selectable: bs }); this.cols.push({ title: "Longueur", selectable: bs }); this.cols.push({ title: "Largeur", selectable: bs }); this.cols.push({ title: "Débit d'attrait", selectable: bs }); this.cols.push({ title: "Cote radier mi-bassin", selectable: bs }); this.cols.push({ title: "Cote radier amont paroi", selectable: bs }); // no col for wall type (defined by rowspan-2 header above) // 3 cols for each device of the wall having the most devices (including downwall) for (let i = 0; i < maxNbDevices; i++) {
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
this.cols.push({ title: "Type", selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]), selectableColumn: i }); this.cols.push({ title: "Paramètres", selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]), selectableColumn: i }); this.cols.push({ title: "Valeurs", selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]), selectableColumn: i }); } // B. Build rows set this.rows = []; // B.1 many rows for each wall let childIndex = 0; for (const cloison of this.model.children) { // as much rows as the greatest number of parameters among its devices const maxNbParams = this.findMaxNumberOfDeviceParameters(cloison); // console.log(">>> max nb params: ", maxNbParams); for (let i = 0; i < maxNbParams; i++) { // build device params row const deviceParamRow = { selectable: cloison, cells: [] }; // basin number if (i === 0) { deviceParamRow.cells.push({ value: childIndex + 1, rowspan: maxNbParams + 1, class: "basin_number", selectable: cloison }); } // 5 empty cells if (i === 0) { deviceParamRow.cells.push({ colspan: 5, rowspan: maxNbParams, selectable: cloison }); } // device param cells : 3 cells for each device for (const ouvrage of cloison.structures) { const nvParam = ouvrage.getNthVisibleParam(i); // cell 1 : device type if (i === 0) { // 1st row deviceParamRow.cells.push({ model: ouvrage.properties.getPropValue("loiDebit"), // options: StructureProperties.findCompatibleLoiDebit(ouvrage) options: [ "salut", "coucou", "pouet" ], selectable: ouvrage }); } // fill space if (i === 1) { deviceParamRow.cells.push({ rowspan: (maxNbParams - 1), selectable: ouvrage }); } // cell 2 : param name if (nvParam) { deviceParamRow.cells.push({ value: nvParam.symbol, selectable: ouvrage });
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
} else { deviceParamRow.cells.push({ selectable: ouvrage }); } // cell 3 : param value if (nvParam) { deviceParamRow.cells.push({ model: nvParam, selectable: ouvrage }); } else { deviceParamRow.cells.push({ selectable: ouvrage }); } } // fill horizontal space const devDiff = (maxNbDevices - cloison.structures.length); if (i === 0) { for (let j = 0; j < devDiff; j++) { deviceParamRow.cells.push({ colspan: 3, rowspan: maxNbParams, selectable: cloison, selectableColumn: cloison.structures.length + j }); } } // done ! this.rows.push(deviceParamRow); } // 1 row for the basin after the wall const basinRow: { selectable: any, cells: any[] } = { selectable: cloison, cells: [ // no cell for basin number (defined by rowspan-n cell above) { model: cloison.prms.LB }, { model: cloison.prms.BB }, { model: cloison.prms.QA }, { model: cloison.prms.ZRMB }, { model: cloison.prms.ZRAM } ] }; // fill horizontal space for (let i = 0; i < maxNbDevices; i++) { basinRow.cells.push({ colspan: 3 }); } // done ! this.rows.push(basinRow); childIndex ++; } // B.2 many rows for downwall // as much rows as the greatest number of parameters among its devices const maxNbParamsDW = this.findMaxNumberOfDeviceParameters(this.model.downWall); for (let i = 0; i < maxNbParamsDW; i++) { // build device params row
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
const deviceParamRowDW = { selectable: this.model.downWall, cells: [] }; // basin number if (i === 0) { deviceParamRowDW.cells.push({ value: "Aval", rowspan: maxNbParamsDW, class: "basin_number", selectable: this.model.downWall }); } // 5 empty cells if (i === 0) { deviceParamRowDW.cells.push({ colspan: 5, rowspan: maxNbParamsDW, selectable: this.model.downWall }); } // downwall device param cells : 3 cells for each device for (const ouvrage of this.model.downWall.structures) { const nvParam = ouvrage.getNthVisibleParam(i); // cell 1 : device type if (i === 0) { // 1st row deviceParamRowDW.cells.push({ model: ouvrage.properties.getPropValue("loiDebit"), // options: StructureProperties.findCompatibleLoiDebit(ouvrage) options: [ "salut", "coucou", "pouet" ] }); } // fill space if (i === 1) { deviceParamRowDW.cells.push({ rowspan: (maxNbParamsDW - 1), selectable: ouvrage }); } // cell 2 : param name if (nvParam) { deviceParamRowDW.cells.push({ value: nvParam.symbol, selectable: ouvrage }); } else { deviceParamRowDW.cells.push({ selectable: ouvrage }); } // cell 3 : param value if (nvParam) { deviceParamRowDW.cells.push({ model: nvParam, selectable: ouvrage }); } else { deviceParamRowDW.cells.push({ selectable: ouvrage }); } } // fill horizontal space const devDiff = (maxNbDevices - this.model.downWall.structures.length); if (i === 0) { for (let j = 0; j < devDiff; j++) { deviceParamRowDW.cells.push({ colspan: 3, rowspan: maxNbParamsDW, selectable: this.model.downWall, selectableColumn: this.model.downWall.structures.length + j }); }
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
} // done ! this.rows.push(deviceParamRowDW); } } private findMaxNumberOfDevices(): number { let maxNbDevices = 1; for (const w of this.model.children) { maxNbDevices = Math.max(maxNbDevices, w.getChildren().length); } maxNbDevices = Math.max(maxNbDevices, this.model.downWall.getChildren().length); return maxNbDevices; } private findMaxNumberOfDeviceParameters(struct: ParallelStructure): number { let maxNbParams = 1; for (const d of struct.getChildren()) { let nbParams = 0; for (const p of d.parameterIterator) { if (p.visible) { // console.log("(counting)", p.symbol); nbParams ++; } } // console.log(">>> child params: ", nbParams); maxNbParams = Math.max(maxNbParams, nbParams); } return maxNbParams; } /** returns true if exactly one device is selected, and nothing else */ public get selectionIsOneDevice() { return ( this.selectedItems.length === 1 && this.selectedItems[0] instanceof Structure ); } /** * Returns true if there is at least one selected item, * and all selected items are devices */ private onlyDevicesAreSelected() { let ok = false; if (this.selectedItems.length > 0) { ok = true; for (const s of this.selectedItems) { ok = ok && (s instanceof Structure); } } return ok; } /** * Returns true if there is at least one selected item, * and all selected items are walls */ private onlyWallsAreSelected() { let ok = false; if (this.selectedItems.length > 0) { ok = true; for (const s of this.selectedItems) { ok = ok && (s instanceof ParallelStructure); } } return ok; } public get relatedEntityTitle() {
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
let title = ""; if (this.onlyDevicesAreSelected()) { title = "Ouvrages"; } else if (this.onlyWallsAreSelected()) { title = "Cloisons"; } if (title !== "") { title += " :"; } return title; } public get enableAddButton() { return this.selectedItems.length === 1; } public get enableCopyButton() { return this.selectedItems.length === 1; } public get enableUpButton() { return this.selectedItems.length === 1; } public get enableDownButton() { return this.selectedItems.length === 1; } public get enableRemoveButton() { return this.selectedItems.length === 1; // return this.selectedItems.length > 0; // too dangerous ? or @TODO ask confirmation ? } /** * returns true if "many" objects are selected: either more than one object, * or a wall that contains more than one device */ public get enableEditPabButton() { return ( this.selectedItems.length > 0 // too restrictive ? /* this.selectedItems.length > 1 || ( this.selectedItems.length > 0 && this.selectedItems[0] instanceof ParallelStructure && this.selectedItems[0].getChildren().length > 1 ) */ ); } public onAddClick() { console.log("Add !!!"); } public onCopyClick() { console.log("Copy !!!"); } public onMoveUpClick() { console.log("Move up !!!"); } public onMoveDownClick() { console.log("Move down !!!"); } public onRemoveClick() { console.log("Remove !!!"); }
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
public showEditPab() { console.log(">> Edit PAB !!"); } /* public ngAfterViewInit() { this.onFieldsetListChange(); this._fieldsetComponents.changes.subscribe(_ => this.onFieldsetListChange()); } public ngDoCheck() { this.updateValidity(); } */ /** * @TODO Calcul de la validité de la Pab */ private updateValidity() { this._isValid = false; /* if (this._fieldsetComponents !== undefined) { this._isValid = this._fieldsetComponents.reduce( // callback ( // accumulator (valeur précédente du résultat) acc, // currentValue (élément courant dans le tableau) fieldset, // currentIndex (indice courant dans le tableau) currIndex, // array (tableau parcouru) array ) => { return acc && fieldset.isValid; } // valeur initiale , this._fieldsetComponents.length > 0); } */ this.validChange.emit(); } /** * réception d'un événement de changement de valeur d'un input */ private onInputChange($event) { this.inputChange.emit($event); } /** * Renvoie l'événement au composant du dessus */ public onTabPressed(event) { console.log("tab pressed dans le tablo !"); } public get uitextEditPabTable() { return "Modifier les valeurs"; } }