diff --git a/angular.json b/angular.json index e87d2b0a1a6f02297dd35553c7d5cd37e50f7094..5482a7544c7eb2f297d410be6b225f995c3b1de9 100644 --- a/angular.json +++ b/angular.json @@ -28,9 +28,13 @@ "src/styles.scss", "src/theme.scss", "node_modules/material-design-icons/iconfont/material-icons.css", - "node_modules/roboto-fontface/css/roboto/roboto-fontface.css" + "node_modules/roboto-fontface/css/roboto/roboto-fontface.css", + "node_modules/primeng/resources/primeng.min.css", + "node_modules/primeng/resources/themes/nova-light/theme.css" + ], + "scripts": [ + "node_modules/chartjs-plugin-zoom/chartjs-plugin-zoom.min.js" ], - "scripts": [], "showCircularDependencies": false }, "configurations": { diff --git a/e2e/calculator.po.ts b/e2e/calculator.po.ts index a3e578709bc86a9505ccb1158804f135d02faf5b..e94c27d19f31b79edbda1c4f62b61aaca39a9d98 100644 --- a/e2e/calculator.po.ts +++ b/e2e/calculator.po.ts @@ -91,9 +91,15 @@ export class CalculatorPage { await element(by.css("fixedvar-results fixed-results > .fixed-results-container")).isPresent() || await element(by.css("fixedvar-results results-graph > graph-results-container")).isPresent() + || + await element(by.css("pab-results pab-results-table")).isPresent() ); } + async hasLog() { + return (await element.all(by.css("log-entry")).count()) > 0; + } + async clickSaveCalcButton() { return await element(by.css("#save-calc")).click(); } diff --git a/e2e/clone-all-calc.e2e-spec.ts b/e2e/clone-all-calc.e2e-spec.ts index a6e46288c8198291e0bd055a3e0b62f8d25da2ad..1cf0a8d470e2ec2e4a1de4c20190408c165ebbe5 100644 --- a/e2e/clone-all-calc.e2e-spec.ts +++ b/e2e/clone-all-calc.e2e-spec.ts @@ -48,7 +48,9 @@ describe("ngHyd − clone all calculators with all possible <select> values", () // read all <input> values and compare them to stored ones const cloneValues = await calcPage.storeAllInputValues(); - expect(cloneValues).toEqual(sourceValues); + for (const k in cloneValues) { + expect(cloneValues[k]).toBeCloseTo(sourceValues[k]); + } }); }); } diff --git a/e2e/compute-reset-chained-links.e2e-spec.ts b/e2e/compute-reset-chained-links.e2e-spec.ts index ac42745193b90d02a7cbf23e952b16403e02c597..0ec8e5dfc0b25143672755dcc2248306e3b20b11 100644 --- a/e2e/compute-reset-chained-links.e2e-spec.ts +++ b/e2e/compute-reset-chained-links.e2e-spec.ts @@ -32,7 +32,7 @@ describe("ngHyd − compute then reset chained results", () => { await browser.sleep(200); await sidenav.clickLoadSessionButton(); await browser.sleep(200); - await sidenav.loadSessionFile("./session-cascade-params.json"); + await sidenav.loadSessionFile("./session/session-cascade-params.json"); await browser.sleep(500); expect(await navbar.getAllCalculatorTabs().count()).toBe(3); @@ -78,7 +78,7 @@ describe("ngHyd − compute then reset chained results", () => { await browser.sleep(200); await sidenav.clickLoadSessionButton(); await browser.sleep(200); - await sidenav.loadSessionFile("./session-cascade-results.json"); + await sidenav.loadSessionFile("./session/session-cascade-results.json"); await browser.sleep(500); expect(await navbar.getAllCalculatorTabs().count()).toBe(3); diff --git a/e2e/load-linked-params.e2e-spec.ts b/e2e/load-linked-params.e2e-spec.ts index 99f13e11f2f5b7ada8709420d001fd91fcc24b83..a26bb90c8f644c37d553185c6567a343798e088f 100644 --- a/e2e/load-linked-params.e2e-spec.ts +++ b/e2e/load-linked-params.e2e-spec.ts @@ -35,7 +35,7 @@ describe("ngHyd − load session with multiple linked parameters − ", () => { await sidenav.clickLoadSessionButton(); await browser.sleep(200); - await sidenav.loadSessionFile("./session-liens-spaghetti.json"); + await sidenav.loadSessionFile("./session/session-liens-spaghetti.json"); await browser.sleep(500); expect(await navbar.getAllCalculatorTabs().count()).toBe(5); diff --git a/e2e/load-save-session.e2e-spec.ts b/e2e/load-save-session.e2e-spec.ts index d18d7732b85ceac4c52827e813df8d9f7a0dd631..4badbd4568284fea07dfffde76e7b70e91ce6968 100644 --- a/e2e/load-save-session.e2e-spec.ts +++ b/e2e/load-save-session.e2e-spec.ts @@ -32,7 +32,7 @@ describe("ngHyd − save and load sessions", () => { await sidenav.clickLoadSessionButton(); await browser.sleep(200); - await sidenav.loadSessionFile("./session-6-calc.test.json"); + await sidenav.loadSessionFile("./session/session-6-calc.test.json"); await browser.sleep(200); expect(await navbar.getAllCalculatorTabs().count()).toBe(6); @@ -47,7 +47,7 @@ describe("ngHyd − save and load sessions", () => { await sidenav.clickLoadSessionButton(); await browser.sleep(200); - await sidenav.loadSessionFile("./session-optional-params.test.json"); + await sidenav.loadSessionFile("./session/session-optional-params.test.json"); await browser.sleep(200); expect(await navbar.getAllCalculatorTabs().count()).toBe(1); diff --git a/e2e/pab.e2e-spec.ts b/e2e/pab.e2e-spec.ts index 8d06db122cdd4a7acc2a0cfeecf3b063d81190a8..6d6ad1a5b21a6955f4e0d4a0157a998835e8a91d 100644 --- a/e2e/pab.e2e-spec.ts +++ b/e2e/pab.e2e-spec.ts @@ -25,49 +25,13 @@ describe("ngHyd − Passe à Bassins", () => { describe("create PAB - ", async () => { - it("when PAB is created after Cloisons & ParallelStructure", async() => { - // create Cloisons - await startPage.navigateTo(); - await listPage.clickMenuEntryForCalcType(10); - // create ParallelStructure - await navbar.clickNewCalculatorButton(); - await listPage.clickMenuEntryForCalcType(8); - // create PAB - await navbar.clickNewCalculatorButton(); - await listPage.clickMenuEntryForCalcType(15); - // check number of basins - const innerFieldsets = element.all(by.css(".fieldset-inner")); - expect(await innerFieldsets.count()).toBe(1); - // check <select> value - const smc = calcPage.getSelectById("select_modele_cloisons"); - const v = await calcPage.getSelectValueText(smc); - expect(v).toEqual("Cloisons"); - // check downstream wall - const smca = calcPage.getSelectById("select_modele_cloison_aval"); - expect(await calcPage.getSelectValueText(smca)).toEqual("Ouvrages"); - }); - - it("when PAB is created before Cloisons & ParallelStructure", async() => { + it("when PAB is created from scratch", async() => { // create PAB await startPage.navigateTo(); await listPage.clickMenuEntryForCalcType(15); - // create Cloisons - await navbar.clickNewCalculatorButton(); - await listPage.clickMenuEntryForCalcType(10); - // create ParallelStructure - await navbar.clickNewCalculatorButton(); - await listPage.clickMenuEntryForCalcType(8); - // check number of basins - await navbar.clickCalculatorTab(0); - const innerFieldsets = element.all(by.css(".fieldset-inner")); + // check that pab-table is present + const innerFieldsets = element.all(by.css(".pab-data-table")); expect(await innerFieldsets.count()).toBe(1); - // check <select> value - const smc = calcPage.getSelectById("select_modele_cloisons"); - const v = await calcPage.getSelectValueText(smc); - expect(v).toEqual("Cloisons"); - // check downstream wall - const smca = calcPage.getSelectById("select_modele_cloison_aval"); - expect(await calcPage.getSelectValueText(smca)).toEqual("Ouvrages"); }); }); @@ -93,12 +57,14 @@ describe("ngHyd − Passe à Bassins", () => { const DH = calcPage.getInputById("DH"); await DH.clear(); await DH.sendKeys("0.72"); + // create PAB from it, changing modal parameters const genButton = calcPage.getGeneratePabButton(); await genButton.click(); const debit = calcPage.getInputById("generatePabDebit"); expect(await debit.getAttribute("value")).toBe("1.5"); await debit.clear(); + await browser.sleep(300); // send "1.6" in 3 movements, because "." triggers an error and Angular can't cope with the subsequent keys await debit.sendKeys("1"); await browser.sleep(300); @@ -114,22 +80,22 @@ describe("ngHyd − Passe à Bassins", () => { expect(await nbBassins.getAttribute("value")).toBe("6"); await nbBassins.clear(); await nbBassins.sendKeys("5"); + // click "Generate" await element(by.css("dialog-generate-pab button#do-generate")).click(); await browser.sleep(1000); + // check parameters values const P_Q = calcPage.getInputById("Q"); expect(await P_Q.getAttribute("value")).toBe("1.6"); const P_Z2 = calcPage.getInputById("Z2"); expect(await P_Z2.getAttribute("value")).toBe("111.4"); + // check number of basins - const innerFieldsets = element.all(by.css(".fieldset-inner")); + const innerFieldsets = element.all(by.css("td.basin_number")); expect(await innerFieldsets.count()).toBe(5); - // check <select> values - innerFieldsets.each(async (inf) => { - const smc = inf.element(by.css("mat-select#select_modele_cloisons")); - expect(await calcPage.getSelectValueText(smc)).toEqual("Cloisons 1"); - }); + + // @TODO check more stuff }); }); @@ -143,7 +109,7 @@ describe("ngHyd − Passe à Bassins", () => { await browser.sleep(200); await sidenav.clickLoadSessionButton(); await browser.sleep(200); - await sidenav.loadSessionFile("./session-pab.json"); + await sidenav.loadSessionFile("./session/session-pab.json"); await browser.sleep(500); // check existence of the loaded modules expect(await navbar.getAllCalculatorTabs().count()).toBe(6); @@ -173,86 +139,32 @@ describe("ngHyd − Passe à Bassins", () => { expect(await calcPage.getSelectValueText(smca)).toEqual("Ouvrages 1"); }); - it("when PAB is loaded before Cloisons & ParallelStructure", async() => { - await startPage.navigateTo(); - // load - await navbar.clickMenuButton(); - await browser.sleep(200); - await sidenav.clickLoadSessionButton(); - await browser.sleep(200); - await sidenav.loadSessionFile("./session-pab-mauvais-ordre.json"); - await browser.sleep(500); - // check existence of the loaded modules - expect(await navbar.getAllCalculatorTabs().count()).toBe(6); - // check parameters values - await navbar.clickCalculatorTab(0); - await browser.sleep(200); - const P_Q = calcPage.getInputById("Q"); - expect(await P_Q.getAttribute("value")).toBe("1.533"); - const P_Z2 = calcPage.getInputById("Z2"); - expect(await P_Z2.getAttribute("value")).toBe("99.44"); - // check number of basins - const innerFieldsets = element.all(by.css(".fieldset-inner")); - expect(await innerFieldsets.count()).toBe(5); - // check <select> values - const expectedCloisons = [ "Cloisons 1", "Cloisons 2", "Cloisons", "Cloisons 2", "Cloisons 1" ]; - const expectedQA = [ 2, 1, 3, 5, 4 ]; - let i = 0; - for (let i = 0; i < innerFieldsets.length; i++) { - const inf = innerFieldsets[i]; - const smc = inf.element(by.css("mat-select#select_modele_cloisons")); - expect(await calcPage.getSelectValueText(smc)).toEqual(expectedCloisons[i]); - const QA = inf.element(by.css("input[id$=QA]")); - expect(Number(await QA.getAttribute("value"))).toBe(expectedQA[i]); - i++; - } - // check downstream wall - const smca = calcPage.getSelectById("select_modele_cloison_aval"); - expect(await calcPage.getSelectValueText(smca)).toEqual("Ouvrages 1"); - }); + }); - it("when PAB is loaded without Cloisons & ParallelStructure", async() => { + describe("load regulated variated PAB with calc errors - ", async () => { + + it("should display logs", async() => { await startPage.navigateTo(); // load await navbar.clickMenuButton(); await browser.sleep(200); await sidenav.clickLoadSessionButton(); await browser.sleep(200); - await sidenav.loadSessionFile("./session-pab-modeles-vides.json"); + await sidenav.loadSessionFile("./session/session-pab-regulee-variee.json"); await browser.sleep(500); - - // check existence of the loaded modules + // check existence of the loaded module expect(await navbar.getAllCalculatorTabs().count()).toBe(1); - // check number of basins await navbar.clickCalculatorTab(0); - await browser.sleep(200); - const innerFieldsets = element.all(by.css(".fieldset-inner")); - expect(await innerFieldsets.count()).toBe(3); - // check empty <select> - innerFieldsets.each(async (inf) => { - const smc = inf.element(by.css("mat-select#select_modele_cloisons")); - const smcEmpty = await calcPage.isSelectEmpty(smc); - console.log("SMC EMPTY ?", smcEmpty); - expect(smcEmpty).toBe(true); - }); - // check empty <select> for downstream wall - const smca = calcPage.getSelectById("select_modele_cloison_aval"); - expect(await calcPage.isSelectEmpty(smca)).toBe(true); - // create a Cloisons - await navbar.clickNewCalculatorButton(); - await listPage.clickMenuEntryForCalcType(10); - // create a ParallelStructure - await navbar.clickNewCalculatorButton(); - await listPage.clickMenuEntryForCalcType(8); - // check that they are selected - await navbar.clickCalculatorTab(0); - innerFieldsets.each(async (inf) => { - const smc = inf.element(by.css("mat-select#select_modele_cloisons")); - expect(await calcPage.getSelectValueText(smc)).toEqual("Cloisons"); - }); - const smca2 = calcPage.getSelectById("select_modele_cloison_aval"); - expect(await calcPage.getSelectValueText(smca2)).toEqual("Ouvrages"); + // calculate + const calcButton = calcPage.getCalculateButton(); + await calcButton.click(); + // check that result is not empty + const hasResults = await calcPage.hasResults(); + expect(hasResults).toBe(true); + + // check presence of logs + expect(await calcPage.hasLog()).toBe(true); }); }); diff --git a/e2e/session-pab-mauvais-ordre.json b/e2e/session-pab-mauvais-ordre.json deleted file mode 100644 index c60b52be8d02bf622573c496d7418831293068ec..0000000000000000000000000000000000000000 --- a/e2e/session-pab-mauvais-ordre.json +++ /dev/null @@ -1,458 +0,0 @@ -{ - "header": { - "source": "jalhyd", - "format_version": "1.0", - "created": "2019-05-10T13:09:33.792Z" - }, - "session": [ - { - "uid": "NXd0dH", - "props": { - "calcType": 15, - "nodeType": 0, - "modeleCloisonAval": "eWtnaj" - }, - "meta": { - "title": "PAB" - }, - "children": [ - { - "uid": "bWswdD", - "props": { - "calcType": 16, - "nodeType": 0, - "modeleCloisons": "bTkxem" - }, - "parameters": [ - { - "symbol": "QA", - "mode": "SINGLE", - "value": 2 - } - ] - }, - { - "uid": "am1oNz", - "props": { - "calcType": 16, - "nodeType": 0, - "modeleCloisons": "bnNhen" - }, - "parameters": [ - { - "symbol": "QA", - "mode": "SINGLE", - "value": 1 - } - ] - }, - { - "uid": "cWJrZm", - "props": { - "calcType": 16, - "nodeType": 0, - "modeleCloisons": "amp6N2" - }, - "parameters": [ - { - "symbol": "QA", - "mode": "SINGLE", - "value": 3 - } - ] - }, - { - "uid": "Y25nNn", - "props": { - "calcType": 16, - "nodeType": 0, - "modeleCloisons": "bnNhen" - }, - "parameters": [ - { - "symbol": "QA", - "mode": "SINGLE", - "value": 5 - } - ] - }, - { - "uid": "M2gxaX", - "props": { - "calcType": 16, - "nodeType": 0, - "modeleCloisons": "bTkxem" - }, - "parameters": [ - { - "symbol": "QA", - "mode": "SINGLE", - "value": 4 - } - ] - } - ], - "parameters": [ - { - "symbol": "Pr", - "mode": "SINGLE", - "value": 0.0001 - }, - { - "symbol": "Q", - "mode": "SINGLE", - "value": "1.533" - }, - { - "symbol": "Z1", - "mode": "CALCUL" - }, - { - "symbol": "Z2", - "mode": "SINGLE", - "value": 99.44 - } - ] - }, - { - "uid": "amp6N2", - "props": { - "calcType": 10, - "nodeType": 0 - }, - "meta": { - "title": "Cloisons" - }, - "children": [ - { - "uid": "YzNjam", - "props": { - "calcType": 7, - "nodeType": 5, - "structureType": 2, - "loiDebit": 12 - }, - "children": [], - "parameters": [ - { - "symbol": "S", - "mode": "SINGLE", - "value": 0.1 - }, - { - "symbol": "Cd", - "mode": "SINGLE", - "value": 0.7 - } - ] - } - ], - "parameters": [ - { - "symbol": "Pr", - "mode": "SINGLE", - "value": 0.0001 - }, - { - "symbol": "Q", - "mode": "CALCUL" - }, - { - "symbol": "Z1", - "mode": "SINGLE", - "value": 102 - }, - { - "symbol": "LB", - "mode": "SINGLE", - "value": 10 - }, - { - "symbol": "BB", - "mode": "SINGLE", - "value": 1 - }, - { - "symbol": "PB", - "mode": "SINGLE", - "value": 1 - }, - { - "symbol": "DH", - "mode": "SINGLE", - "value": 0.5 - } - ] - }, - { - "uid": "bTkxem", - "props": { - "calcType": 10, - "nodeType": 0 - }, - "meta": { - "title": "Cloisons 2" - }, - "children": [ - { - "uid": "Y3JjaW", - "props": { - "calcType": 7, - "nodeType": 5, - "structureType": 2, - "loiDebit": 12 - }, - "children": [], - "parameters": [ - { - "symbol": "S", - "mode": "SINGLE", - "value": 0.1 - }, - { - "symbol": "Cd", - "mode": "SINGLE", - "value": 0.7 - } - ] - } - ], - "parameters": [ - { - "symbol": "Pr", - "mode": "SINGLE", - "value": 0.0001 - }, - { - "symbol": "Q", - "mode": "CALCUL" - }, - { - "symbol": "Z1", - "mode": "SINGLE", - "value": 102 - }, - { - "symbol": "LB", - "mode": "SINGLE", - "value": 10 - }, - { - "symbol": "BB", - "mode": "SINGLE", - "value": 1 - }, - { - "symbol": "PB", - "mode": "SINGLE", - "value": 1 - }, - { - "symbol": "DH", - "mode": "SINGLE", - "value": 0.5 - } - ] - }, - { - "uid": "bnNhen", - "props": { - "calcType": 10, - "nodeType": 0 - }, - "meta": { - "title": "Cloisons 1" - }, - "children": [ - { - "uid": "amJzem", - "props": { - "calcType": 7, - "nodeType": 5, - "structureType": 2, - "loiDebit": 12 - }, - "children": [], - "parameters": [ - { - "symbol": "S", - "mode": "SINGLE", - "value": 0.1 - }, - { - "symbol": "Cd", - "mode": "SINGLE", - "value": 0.7 - } - ] - } - ], - "parameters": [ - { - "symbol": "Pr", - "mode": "SINGLE", - "value": 0.0001 - }, - { - "symbol": "Q", - "mode": "CALCUL" - }, - { - "symbol": "Z1", - "mode": "SINGLE", - "value": 102 - }, - { - "symbol": "LB", - "mode": "SINGLE", - "value": 10 - }, - { - "symbol": "BB", - "mode": "SINGLE", - "value": 1 - }, - { - "symbol": "PB", - "mode": "SINGLE", - "value": 1 - }, - { - "symbol": "DH", - "mode": "SINGLE", - "value": 0.5 - } - ] - }, - { - "uid": "M2Q0NX", - "props": { - "calcType": 8, - "nodeType": 0 - }, - "meta": { - "title": "Ouvrages" - }, - "children": [ - { - "uid": "MmJxen", - "props": { - "calcType": 7, - "nodeType": 5, - "structureType": 1, - "loiDebit": 1 - }, - "children": [], - "parameters": [ - { - "symbol": "ZDV", - "mode": "SINGLE", - "value": 100 - }, - { - "symbol": "W", - "mode": "SINGLE", - "value": 0.5 - }, - { - "symbol": "L", - "mode": "SINGLE", - "value": 2 - }, - { - "symbol": "Cd", - "mode": "SINGLE", - "value": 0.4 - } - ] - } - ], - "parameters": [ - { - "symbol": "Pr", - "mode": "SINGLE", - "value": 0.0001 - }, - { - "symbol": "Q", - "mode": "CALCUL" - }, - { - "symbol": "Z1", - "mode": "SINGLE", - "value": 102 - }, - { - "symbol": "Z2", - "mode": "SINGLE", - "value": 101.5 - } - ] - }, - { - "uid": "eWtnaj", - "props": { - "calcType": 8, - "nodeType": 0 - }, - "meta": { - "title": "Ouvrages 1" - }, - "children": [ - { - "uid": "ZmhpeG", - "props": { - "calcType": 7, - "nodeType": 5, - "structureType": 1, - "loiDebit": 1 - }, - "children": [], - "parameters": [ - { - "symbol": "ZDV", - "mode": "SINGLE", - "value": 100 - }, - { - "symbol": "W", - "mode": "SINGLE", - "value": 0.5 - }, - { - "symbol": "L", - "mode": "SINGLE", - "value": 2 - }, - { - "symbol": "Cd", - "mode": "SINGLE", - "value": 0.4 - } - ] - } - ], - "parameters": [ - { - "symbol": "Pr", - "mode": "SINGLE", - "value": 0.0001 - }, - { - "symbol": "Q", - "mode": "CALCUL" - }, - { - "symbol": "Z1", - "mode": "SINGLE", - "value": 102 - }, - { - "symbol": "Z2", - "mode": "SINGLE", - "value": 101.5 - } - ] - } - ] -} \ No newline at end of file diff --git a/e2e/session-pab-modeles-vides.json b/e2e/session-pab-modeles-vides.json deleted file mode 100644 index b67b85a2eaca8a0b7807eb2159558f2e0399b047..0000000000000000000000000000000000000000 --- a/e2e/session-pab-modeles-vides.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "header": { - "source": "jalhyd", - "format_version": "1.0", - "created": "2019-05-09T09:57:38.480Z" - }, - "session": [ - { - "uid": "b2xqMj", - "props": { - "calcType": 15, - "nodeType": 0 - }, - "meta": { - "title": "PAB" - }, - "children": [ - { - "uid": "czlkYn", - "props": { - "calcType": 16, - "nodeType": 0 - }, - "parameters": [ - { - "symbol": "QA", - "mode": "SINGLE", - "value": 0 - } - ] - }, - { - "uid": "eTlyYT", - "props": { - "calcType": 16, - "nodeType": 0 - }, - "parameters": [ - { - "symbol": "QA", - "mode": "SINGLE", - "value": 0 - } - ] - }, - { - "uid": "d3Q3an", - "props": { - "calcType": 16, - "nodeType": 0 - }, - "parameters": [ - { - "symbol": "QA", - "mode": "SINGLE", - "value": 0 - } - ] - } - ], - "parameters": [ - { - "symbol": "Pr", - "mode": "SINGLE", - "value": 0.0001 - }, - { - "symbol": "Q", - "mode": "SINGLE", - "value": 1.5 - }, - { - "symbol": "Z1", - "mode": "CALCUL" - }, - { - "symbol": "Z2", - "mode": "SINGLE", - "value": 99 - } - ] - } - ] -} diff --git a/e2e/session-pab.json b/e2e/session-pab.json index 2d00f2eecf74e85ef506b780ebd21131e5a7792e..11cc43baaa316f350fdf40075d0b0a96e8e555bd 100644 --- a/e2e/session-pab.json +++ b/e2e/session-pab.json @@ -349,8 +349,7 @@ "uid": "NXd0dH", "props": { "calcType": 15, - "nodeType": 0, - "modeleCloisonAval": "eWtnaj" + "nodeType": 0 }, "meta": { "title": "PAB" @@ -360,8 +359,7 @@ "uid": "bWswdD", "props": { "calcType": 16, - "nodeType": 0, - "modeleCloisons": "bTkxem" + "nodeType": 0 }, "parameters": [ { @@ -375,8 +373,7 @@ "uid": "am1oNz", "props": { "calcType": 16, - "nodeType": 0, - "modeleCloisons": "bnNhen" + "nodeType": 0 }, "parameters": [ { @@ -390,8 +387,7 @@ "uid": "cWJrZm", "props": { "calcType": 16, - "nodeType": 0, - "modeleCloisons": "amp6N2" + "nodeType": 0 }, "parameters": [ { @@ -405,8 +401,7 @@ "uid": "Y25nNn", "props": { "calcType": 16, - "nodeType": 0, - "modeleCloisons": "bnNhen" + "nodeType": 0 }, "parameters": [ { @@ -420,8 +415,7 @@ "uid": "M2gxaX", "props": { "calcType": 16, - "nodeType": 0, - "modeleCloisons": "bTkxem" + "nodeType": 0 }, "parameters": [ { diff --git a/e2e/session-6-calc.test.json b/e2e/sessions/session-6-calc.test.json similarity index 100% rename from e2e/session-6-calc.test.json rename to e2e/sessions/session-6-calc.test.json diff --git a/e2e/session-cascade-params.json b/e2e/sessions/session-cascade-params.json similarity index 100% rename from e2e/session-cascade-params.json rename to e2e/sessions/session-cascade-params.json diff --git a/e2e/session-cascade-results.json b/e2e/sessions/session-cascade-results.json similarity index 100% rename from e2e/session-cascade-results.json rename to e2e/sessions/session-cascade-results.json diff --git a/e2e/session-liens-spaghetti.json b/e2e/sessions/session-liens-spaghetti.json similarity index 100% rename from e2e/session-liens-spaghetti.json rename to e2e/sessions/session-liens-spaghetti.json diff --git a/e2e/session-optional-params.test.json b/e2e/sessions/session-optional-params.test.json similarity index 100% rename from e2e/session-optional-params.test.json rename to e2e/sessions/session-optional-params.test.json diff --git a/e2e/sessions/session-pab-regulee-variee.json b/e2e/sessions/session-pab-regulee-variee.json new file mode 100644 index 0000000000000000000000000000000000000000..3dcc919c8bab1d28352a13ee09360c1ba1419356 --- /dev/null +++ b/e2e/sessions/session-pab-regulee-variee.json @@ -0,0 +1 @@ +{"header":{"source":"jalhyd","format_version":"1.0","created":"2019-07-08T08:00:03.373Z"},"session":[{"uid":"dXhia3","props":{"calcType":15},"meta":{"title":"PAB régulée varié"},"children":[{"uid":"NnB5bm","props":{"calcType":10,"nodeType":0},"children":[{"uid":"cGsweT","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":76.67},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":76.53999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":76.65499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"amMxc2","props":{"calcType":10,"nodeType":0},"children":[{"uid":"MjRhNT","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":76.44},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":76.30999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":76.42499999999998},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"anZwOT","props":{"calcType":10,"nodeType":0},"children":[{"uid":"MDNnaj","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":76.21000000000001},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":76.08},{"symbol":"ZRAM","mode":"SINGLE","value":76.195},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"cDRwMD","props":{"calcType":10,"nodeType":0},"children":[{"uid":"aHQ2NT","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":75.98},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":75.85},{"symbol":"ZRAM","mode":"SINGLE","value":75.96499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"NnppdW","props":{"calcType":10,"nodeType":0},"children":[{"uid":"anYzZj","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":75.75},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":75.61999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":75.73499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"bGhzNn","props":{"calcType":10,"nodeType":0},"children":[{"uid":"MGM5bm","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":75.52},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":75.38999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":75.50499999999998},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"eXY0aT","props":{"calcType":10,"nodeType":0},"children":[{"uid":"NXp6cz","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":75.29},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":75.16},{"symbol":"ZRAM","mode":"SINGLE","value":75.27499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"bnJuZ2","props":{"calcType":10,"nodeType":0},"children":[{"uid":"bDR2YX","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":75.06},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":74.92999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":75.04499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"bmEzOG","props":{"calcType":10,"nodeType":0},"children":[{"uid":"Y2ZobT","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":74.83},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":74.69999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":74.81499999999998},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"ODg5Yj","props":{"calcType":10,"nodeType":0},"children":[{"uid":"eGpueG","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":74.6},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":74.47},{"symbol":"ZRAM","mode":"SINGLE","value":74.58499999999998},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"Mzl6aD","props":{"calcType":10,"nodeType":0},"children":[{"uid":"dno5Mj","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":74.37},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":74.24},{"symbol":"ZRAM","mode":"SINGLE","value":74.35499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"NnEyY2","props":{"calcType":10,"nodeType":0},"children":[{"uid":"OGlrMG","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":74.14},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":74.00999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":74.12499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"eGVvbz","props":{"calcType":10,"nodeType":0},"children":[{"uid":"d292cH","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":73.91},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":73.77999999999999},{"symbol":"ZRAM","mode":"SINGLE","value":73.89499999999998},{"symbol":"QA","mode":"SINGLE","value":0}]},{"uid":"Nzltdm","props":{"calcType":10,"nodeType":0},"children":[{"uid":"cm9zOT","props":{"calcType":7,"nodeType":5,"structureType":0,"loiDebit":11},"children":[],"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":73.68},{"symbol":"L","mode":"SINGLE","value":0.35},{"symbol":"Cd","mode":"SINGLE","value":0.65}]}],"parameters":[{"symbol":"LB","mode":"SINGLE","value":3.1},{"symbol":"BB","mode":"SINGLE","value":2.5},{"symbol":"ZRMB","mode":"SINGLE","value":73.55},{"symbol":"ZRAM","mode":"SINGLE","value":73.66499999999999},{"symbol":"QA","mode":"SINGLE","value":0}]}],"parameters":[{"symbol":"Q","mode":"SINGLE","value":0.773},{"symbol":"Z1","mode":"CALCUL"},{"symbol":"Z2","mode":"MINMAX","min":74.5,"max":75,"step":0.1}],"downWall":{"uid":"Y21pOG","props":{"calcType":16},"children":[{"uid":"c216YW","props":{"calcType":7,"loiDebit":15},"children":[],"parameters":[{"symbol":"L","mode":"SINGLE","value":0.6},{"symbol":"Cd","mode":"SINGLE","value":0.4},{"symbol":"minZDV","mode":"SINGLE","value":73.8},{"symbol":"maxZDV","mode":"SINGLE","value":74.3},{"symbol":"DH","mode":"SINGLE","value":0.217}]}],"parameters":[]}}]} \ No newline at end of file diff --git a/jalhyd_branch b/jalhyd_branch index 94f89b3b03cef0d6963c4d0d2b1be518876e8eb7..19c587308945308f3787a17aee4282c5c6de79df 100644 --- a/jalhyd_branch +++ b/jalhyd_branch @@ -1 +1 @@ -62-fonctionnalite-plusieurs-parametres-qui-varient-nghyd-126 \ No newline at end of file +33-ajout-du-calcul-d-une-passe-a-bassin diff --git a/package-lock.json b/package-lock.json index 265e3c560a30d324c6d0baf680706804d7c7a706..23fc51a4600763f6611275c96aae80776545573a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3417,6 +3417,14 @@ "color-name": "^1.0.0" } }, + "chartjs-plugin-zoom": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-0.7.2.tgz", + "integrity": "sha512-5AtMCjlBlRsA/vxlvcBsAYKbkk0tVYE6+XX9M9LE6aSqbjqGR5NUQwYH0YvvvpmJjTZfB+HDhHaaGxJ/8aGaaw==", + "requires": { + "hammerjs": "^2.0.8" + } + }, "cheerio": { "version": "1.0.0-rc.2", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", @@ -10859,6 +10867,11 @@ "meow": "^3.1.0" } }, + "primeng": { + "version": "8.0.0-rc.1", + "resolved": "https://registry.npmjs.org/primeng/-/primeng-8.0.0-rc.1.tgz", + "integrity": "sha512-xgynBphs4wtCbyyAVQD1ipQCVcSOlfZ697wVVIlZ76V9E1e6eYeNGXN/fLud3NngNyoFiJkP+VJL7kfcfWFnPQ==" + }, "printj": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", diff --git a/package.json b/package.json index bad7e6e723f7aecf7b0cef64641e728ad9467056..d3fda3c44d5c03a20718e9a468395292af30a4d4 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "e2e": "ng e2e", "electron": "electron .", "electron-builder": "electron-builder", - "start": "npm run preprocess && ng serve --host 0.0.0.0", + "start": "npm run preprocess && ng serve --host 0.0.0.0 --poll 5000", "prod": "npm run preprocess && ng serve --host 0.0.0.0 --prod", "build": "npm run preprocess && ng build --prod --build-optimizer=false", "build-cordova": "npm run preprocess && ng build --prod --build-optimizer=false && npm run add-cordova-js", @@ -22,7 +22,7 @@ "release-windows": "rd /s /q release & ng build --prod --build-optimizer=false && npm run electron-builder", "release-android": "npm run build-cordova && cordova platform add android; rm dist/assets/docs-fr/sitemap.xml.gz && cordova build android && mv platforms/android/app/build/outputs/apk/debug/app-debug.apk release/cassiopee.apk", "e2equick": "ng e2e --dev-server-target= --baseUrl=http://localhost:4200", - "jalhyd": "rm node_modules/jalhyd && cd ../jalhyd && npm run build && cd ../nghyd && npm install ../jalhyd", + "jalhyd": "rm -f node_modules/jalhyd && cd ../jalhyd && npm run build && cd ../nghyd && npm install ../jalhyd", "mathjax": "mkdir -p docs-fr/javascripts/mathjax && rsync -az --delete `cat rsync-mathjax-files` docs-fr/javascripts/mathjax", "mkdocs": "npm run mathjax && find docs-fr/javascripts/ -name '*.md' -type f -delete && python3 -m mkdocs build", "preprocess": "node preprocessors.js && npm run mkdocs", @@ -47,6 +47,7 @@ "@types/pako": "^1.0.1", "@types/sprintf-js": "^1.1.2", "angular2-chartjs": "^0.5.1", + "chartjs-plugin-zoom": "^0.7.2", "cordova-android": "^8.0.0", "cordova-plugin-device": "^2.0.2", "core-js": "^2.6.5", @@ -60,6 +61,7 @@ "ngx-md": "^7.0.0", "ngx-webstorage-service": "^4.0.1", "pako": "^1.0.10", + "primeng": "^8.0.0-rc.1", "roboto-fontface": "^0.10.0", "rxjs": "^6.3.3", "rxjs-compat": "^6.3.3", @@ -103,4 +105,4 @@ "android" ] } -} \ No newline at end of file +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 59d238cf1e0804dcf833355354bd55cc00271940..a4f44c3d2bdf8ccb859e03224da8940aaafc3aaa 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -577,8 +577,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer { /** * détection de la fermeture de la page/navigateur et demande de confirmation */ - @HostListener("window:beforeunload", ["$event"]) - confirmExit($event) { + @HostListener("window:beforeunload", [ "$event" ]) confirmExit($event) { if ( this.appSetupService.warnBeforeTabClose && environment.production // otherwise prevents dev server to reload app after recompiling @@ -587,4 +586,28 @@ export class AppComponent implements OnInit, OnDestroy, Observer { $event.returnValue = "Your data will be lost !"; } } + + /** + * Disable value modification on mouse wheel or up/down arrows, in input type="number" + */ + @HostListener("mousewheel", [ "$event" ]) onMouseWheelChrome(event: any) { + this.disableScroll(event); + } + @HostListener("DOMMouseScroll", [ "$event" ]) onMouseWheelFirefox(event: any) { + this.disableScroll(event); + } + @HostListener("onmousewheel", [ "$event" ]) onMouseWheelIE(event: any) { + this.disableScroll(event); + } + disableScroll(event: any) { + if (event.srcElement.type === "number") { + event.preventDefault(); + // @TODO how to send event to parent so that scrolling the page works ? + } + } + @HostListener("keydown", [ "$event" ]) onKeydown(event: any) { + if (event.which === 38 || event.which === 40) { // up / down arrow + event.preventDefault(); + } + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 69328bd5fe0b0e395432d09c6de258b8009581d0..c9ec24010f9eb43f0a83385406b6743c543b3043 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -18,6 +18,7 @@ import { MatListModule, MatCardModule, MatProgressBarModule, + MatRadioModule, MatTableModule, MatSnackBarModule, MatBadgeModule, @@ -26,6 +27,8 @@ import { } from "@angular/material"; import { MaterialFileInputModule } from "ngx-material-file-input"; +import {TableModule} from "primeng/components/table/table"; + import { FlexLayoutModule } from "@angular/flex-layout"; import { CustomBreakPointsProvider, @@ -76,9 +79,13 @@ import { VarResultsComponent } from "./components/fixedvar-results/var-results.c import { LogEntryComponent } from "./components/log-entry/log-entry.component"; import { ParamLinkComponent } from "./components/param-link/param-link.component"; import { SelectModelFieldLineComponent } from "./components/select-model-field-line/select-model-field-line.component"; +import { PabProfileGraphComponent } from "./components/pab-profile-graph/pab-profile-graph.component"; +import { PabTableComponent } from "./components/pab-table/pab-table.component"; +import { PabVariableResultsSelectorComponent } from "./components/pab-results/pab-variable-results-selector.component"; import { DialogConfirmEmptySessionComponent } from "./components/dialog-confirm-empty-session/dialog-confirm-empty-session.component"; import { DialogConfirmCloseCalcComponent } from "./components/dialog-confirm-close-calc/dialog-confirm-close-calc.component"; +import { DialogEditPabComponent } from "./components/dialog-edit-pab/dialog-edit-pab.component"; import { DialogEditParamComputedComponent } from "./components/dialog-edit-param-computed/dialog-edit-param-computed.component"; import { DialogEditParamValuesComponent } from "./components/dialog-edit-param-values/dialog-edit-param-values.component"; import { DialogGeneratePABComponent } from "./components/dialog-generate-pab/dialog-generate-pab.component"; @@ -124,6 +131,7 @@ const appRoutes: Routes = [ MatListModule, MatMenuModule, MatProgressBarModule, + MatRadioModule, MatSelectModule, MatSidenavModule, MatSnackBarModule, @@ -139,7 +147,8 @@ const appRoutes: Routes = [ enableTracing: false // debugging purposes only } ), - StorageServiceModule + StorageServiceModule, + TableModule ], declarations: [ // composants, pipes et directives AppComponent, @@ -151,6 +160,7 @@ const appRoutes: Routes = [ CalculatorResultsComponent, DialogConfirmCloseCalcComponent, DialogConfirmEmptySessionComponent, + DialogEditPabComponent, DialogEditParamComputedComponent, DialogEditParamValuesComponent, DialogGeneratePABComponent, @@ -174,8 +184,11 @@ const appRoutes: Routes = [ LogComponent, LogEntryComponent, NgParamInputComponent, + PabProfileGraphComponent, PabResultsComponent, PabResultsTableComponent, + PabTableComponent, + PabVariableResultsSelectorComponent, ParamComputedComponent, ParamFieldLineComponent, ParamLinkComponent, @@ -191,6 +204,7 @@ const appRoutes: Routes = [ entryComponents: [ DialogConfirmCloseCalcComponent, DialogConfirmEmptySessionComponent, + DialogEditPabComponent, DialogEditParamComputedComponent, DialogEditParamValuesComponent, DialogGeneratePABComponent, diff --git a/src/app/calculators/cloisons/cloisons.config.json b/src/app/calculators/cloisons/cloisons.config.json index 6ce6b17ba99df0ae7eeb462779fc6f7909d6cafc..ea679624bfed8ac817ef405a9543b1d328dca705 100644 --- a/src/app/calculators/cloisons/cloisons.config.json +++ b/src/app/calculators/cloisons/cloisons.config.json @@ -14,6 +14,7 @@ { "type": "input", "id": "Z1", + "option": "var", "unit": "m" }, { @@ -82,8 +83,8 @@ "enum": "LoiDebit.WeirSubmergedLarinier" }, { - "id": "select_loidebit1_kivi", - "enum": "LoiDebit.KIVI" + "id": "select_loidebit1_villemonte", + "enum": "LoiDebit.WeirVillemonte" } ], "dep_exist": [ @@ -143,7 +144,7 @@ }, { "type": "input", - "id": "ZDV", + "id": "h1", "unit": "m", "nodeType": "StructureRectangle", "dep_exist": [ @@ -191,31 +192,7 @@ } ] }, - { - "type": "input", - "id": "alpha", - "unit": "", - "nodeType": "StructureRectangle", - "dep_exist": [ - { - "refid": "select_loidebit1", - "refvalue": "select_loidebit1_kivi" - } - ] - }, - { - "type": "input", - "id": "beta", - "unit": "", - "nodeType": "StructureRectangle", - "dep_exist": [ - { - "refid": "select_loidebit1", - "refvalue": "select_loidebit1_kivi" - } - ] - }, - { + { "type": "input", "id": "alpha2", "unit": "", diff --git a/src/app/calculators/cloisons/cloisons.en.json b/src/app/calculators/cloisons/cloisons.en.json index c60b60eae765bc22efe6c5c2314d766c58fa0ad9..d95c9ebadbb1b04e6cc70eb2dae499428f3e6673 100644 --- a/src/app/calculators/cloisons/cloisons.en.json +++ b/src/app/calculators/cloisons/cloisons.en.json @@ -1,20 +1,13 @@ { - "Q": "Débit total", - "ZR": "Cote du lit amont", - "LB": "Longueur du bassin", - "BB": "Largeur du bassin", - "PB": "Profondeur moyenne du bassin", - "DH": "Chute", - "S": "Surface de l'orifice", - "ZRAM": "Bottom elevation", - "alpha2": "Demi-angle au sommet (°)", - "BT": "Demi-ouverture du triangle (m)", - "ZT": "Cote haute du triangle (m)", + "Q": "Total discharge", + "ZR": "Upstream bed elevation", + "PB": "Pool mean depth", + "h1": "Head", "select_ouvrage_seuil_triang": "Triangular weir", "select_ouvrage_seuil_triangtrunc": "Truncated triangular weir", "select_ouvrage_orifice": "Orifice", "select_ouvrage_seuil_rect": "Rectangular weir", - "select_loidebit1_kivi": "Kindsvater-Carter and Villemonte", + "select_loidebit1_villemonte": "Villemonte 1947", "select_loidebit1_fente": "Submerged slot (Larinier 1992)", "select_loidebit2_orifice": "Submerged orifice", "select_loidebit3_seuiltriang": "Free flow triangular weir", diff --git a/src/app/calculators/cloisons/cloisons.fr.json b/src/app/calculators/cloisons/cloisons.fr.json index fc6e0d91e9a17611cdd7137de74a552bec4e4ebf..7082d1cc1d82def88f048f55836b137fc91d3e3e 100644 --- a/src/app/calculators/cloisons/cloisons.fr.json +++ b/src/app/calculators/cloisons/cloisons.fr.json @@ -1,20 +1,10 @@ { "Q": "Débit total", - "ZR": "Cote du lit amont", - "LB": "Longueur du bassin", - "BB": "Largeur du bassin", - "PB": "Profondeur moyenne du bassin", - "DH": "Chute", - "S": "Surface de l'orifice", - "ZRAM": "Cote de radier amont de la cloison", - "alpha2": "Demi-angle au sommet (°)", - "BT": "Demi-ouverture du triangle (m)", - "ZT": "Cote haute du triangle (m)", "select_ouvrage_seuil_triang": "Seuil triangulaire", "select_ouvrage_seuil_triangtrunc": "Seuil triangulaire tronqué", "select_ouvrage_orifice": "Orifice", "select_ouvrage_seuil_rect": "Seuil rectangulaire", - "select_loidebit1_kivi": "Kindsvater-Carter et Villemonte", + "select_loidebit1_villemonte": "Villemonte 1947", "select_loidebit1_fente": "Fente noyée (Larinier 1992)", "select_loidebit2_orifice": "Orifice noyé", "select_loidebit3_seuiltriang": "Déversoir triangulaire dénoyé", diff --git a/src/app/calculators/pab/pab.config.json b/src/app/calculators/pab/pab.config.json index 443720b8d7f8eea4d5431f752b17ebe6afefaced..d477bc7af1a31bac68cacae5b905ff559617395e 100644 --- a/src/app/calculators/pab/pab.config.json +++ b/src/app/calculators/pab/pab.config.json @@ -8,7 +8,6 @@ { "type": "input", "id": "Q", - "symbol": "Q", "unit": "m³/s" }, { @@ -19,51 +18,13 @@ { "type": "input", "id": "Z2", - "unit": "m" - } - ] - }, - { - "id": "fs_bassin", - "type": "fieldset_template", - "calcType": "PabCloisons", - "option": "fix", - "fields": [ - { - "id": "select_modele_cloisons", - "type": "select_cloisons", - "select": [] - }, - { - "type": "input", - "id": "QA", - "unit": "m³/s" - } - ] - }, - { - "id": "bassin_container", - "type": "template_container", - "templates": [ - "fs_bassin" - ] - }, - { - "id": "fs_cloison_aval", - "type": "fieldset", - "calcType": "Pab", - "fields": [ - { - "id": "select_modele_cloison_aval", - "type": "select_cloison_aval", - "select": [] + "unit": "m", + "option": "var" } ] }, { - "type": "options", - "modeleCloisonsSelectId": "select_modele_cloisons", - "modeleCloisonAvalSelectId": "select_modele_cloison_aval", - "idCal": "Q" + "id": "tableau_entree_pab", + "type": "pab_table" } -] \ No newline at end of file +] diff --git a/src/app/calculators/pab/pab.en.json b/src/app/calculators/pab/pab.en.json index cf3cbb4ee03232538f17997af04dc4562a4aad4a..56e4550abe0114b66b06e0cbae4022825fc6912b 100644 --- a/src/app/calculators/pab/pab.en.json +++ b/src/app/calculators/pab/pab.en.json @@ -3,11 +3,20 @@ "Q": "Flow", "Z1": "Upstream elevation", "Z2": "Downstream elevation", + "LB": "Basin length", + "BB": "Basin width", + "PB": "Basin mean depth", + "W": "Gate opening", + "ZRAM": "Bottom elevation", + "ZRMB": "Mid-basin elevation", + "h1": "Head", "fs_bassin": "Basin", "fs_cloison_aval": "Downstream wall", "bassin_container": "Basins", "select_modele_cloisons": "Cross walls model", "select_modele_cloison_aval": "Downstream wall model", + "UNIT_LB": "m", + "UNIT_BB": "m", "UNIT_DH": "m", "UNIT_PV": "W/m³", "UNIT_Q": "m³/s", @@ -15,5 +24,6 @@ "UNIT_YMOY": "m", "UNIT_Z": "m", "UNIT_ZRAM": "m", - "UNIT_ZRB": "m" + "UNIT_ZRB": "m", + "UNIT_ZRMB": "m" } \ No newline at end of file diff --git a/src/app/calculators/pab/pab.fr.json b/src/app/calculators/pab/pab.fr.json index a372cb4e238b08103ebced6412687ba4ae64c45d..dd171f91acad2d5d1abf44c6b183d3e52901d8d7 100644 --- a/src/app/calculators/pab/pab.fr.json +++ b/src/app/calculators/pab/pab.fr.json @@ -1,13 +1,12 @@ { "fs_param_hydro": "Paramètres hydrauliques", - "Q": "Débit", - "Z1": "Cote amont", - "Z2": "Cote aval", "fs_bassin": "Bassin", "fs_cloison_aval": "Cloison aval", "bassin_container": "Bassins", "select_modele_cloisons": "Modèle de cloisons", "select_modele_cloison_aval": "Modèle de la cloison aval", + "UNIT_LB": "m", + "UNIT_BB": "m", "UNIT_DH": "m", "UNIT_PV": "W/m³", "UNIT_Q": "m³/s", @@ -15,5 +14,5 @@ "UNIT_YMOY": "m", "UNIT_Z": "m", "UNIT_ZRAM": "m", - "UNIT_ZRB": "m" + "UNIT_ZRMB": "m" } \ No newline at end of file diff --git a/src/app/calculators/parallel-structures/parallel-structures.config.json b/src/app/calculators/parallel-structures/parallel-structures.config.json index 966819227a3382cab9a093c7c111d58718ec8dba..2e61bc489dfa3ba373e85b617ad15065b82898f9 100644 --- a/src/app/calculators/parallel-structures/parallel-structures.config.json +++ b/src/app/calculators/parallel-structures/parallel-structures.config.json @@ -74,6 +74,10 @@ "id": "select_loidebit1_cunge80", "enum": "LoiDebit.Cunge80" }, + { + "id": "select_loidebit1_villemonte", + "enum": "LoiDebit.WeirVillemonte" + }, { "id": "select_loidebit1_kivi", "enum": "LoiDebit.KIVI" diff --git a/src/app/calculators/parallel-structures/parallel-structures.en.json b/src/app/calculators/parallel-structures/parallel-structures.en.json index ad6d5252f752b4b9e3e6150b8a5daba99e63ed46..52135d4610ddac92895e2258560413415516261c 100644 --- a/src/app/calculators/parallel-structures/parallel-structures.en.json +++ b/src/app/calculators/parallel-structures/parallel-structures.en.json @@ -14,6 +14,7 @@ "select_loidebit1_cem88v": "Weir/Undershot gate Cemagref 88", "select_loidebit1_fente": "Submerged slot (Larinier 1992)", "select_loidebit1_kivi": "Kindsvater-Carter and Villemonte", + "select_loidebit1_villemonte": "Villemonte 1957", "select_loidebit2_vannedenoye": "Free flow gate", "select_loidebit2_vannenoye": "Submerged gate", "select_loidebit2_cunge80": "Cunge 80", diff --git a/src/app/calculators/parallel-structures/parallel-structures.fr.json b/src/app/calculators/parallel-structures/parallel-structures.fr.json index 45e1910810eaf075737ac53a821291e7d2a29a4b..3b9d38763ea8196a0c472ce34554cf7808b68d3e 100644 --- a/src/app/calculators/parallel-structures/parallel-structures.fr.json +++ b/src/app/calculators/parallel-structures/parallel-structures.fr.json @@ -7,13 +7,13 @@ "select_ouvrage_seuil_triangtrunc": "Seuil triangulaire tronqué", "select_ouvrage_seuil_trap": "Seuil trapézoïdal", "select_ouvrage_vanne_trap": "Vanne trapézoïdale", - "W": "Ouverture de vanne", "select_loidebit1_seuildenoye": "Seuil dénoyé", "select_loidebit1_cunge80": "Cunge 80", "select_loidebit1_cem88d": "Déversoir/Orifice Cemagref 88", "select_loidebit1_cem88v": "Déversoir/Vanne de fond Cemagref 88", "select_loidebit1_fente": "Fente noyée (Larinier 1992)", "select_loidebit1_kivi": "Kindsvater-Carter et Villemonte", + "select_loidebit1_villemonte": "Villemonte 1957", "select_loidebit2_vannedenoye": "Vanne dénoyé", "select_loidebit2_vannenoye": "Vanne noyé", "select_loidebit2_cunge80": "Cunge 80", diff --git a/src/app/components/calculator-list/calculator-list.component.ts b/src/app/components/calculator-list/calculator-list.component.ts index f05e933f0188edb8091ab220aeb57a3826a355bd..c4acb05d66f1251e2ebc8a26f1fa66f927bd9a14 100644 --- a/src/app/components/calculator-list/calculator-list.component.ts +++ b/src/app/components/calculator-list/calculator-list.component.ts @@ -79,7 +79,7 @@ export class CalculatorListComponent implements OnInit { if ( // those sub-Nub types cannot be built outside a parent t !== CalculatorType.Structure && t !== CalculatorType.Section - && t !== CalculatorType.PabCloisons + && t !== CalculatorType.CloisonAval ) { unusedTheme.calculators.push({ type: t, diff --git a/src/app/components/dialog-edit-pab/dialog-edit-pab.component.html b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8674eb361f97173fe01467f1e8a70671864751e7 --- /dev/null +++ b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.html @@ -0,0 +1,74 @@ +<h1 mat-dialog-title [innerHTML]="uitextEditPabTitle"></h1> + +<form> + + <div mat-dialog-content> + + <!-- récap selection --> + <div id="selection-abstract"> + {{ selectionAbstract }} + </div> + + <!-- champ à modifier --> + <mat-form-field class="select-form-field"> + <mat-select [placeholder]="uitextVariable" [(value)]="variable"> + <mat-option *ngFor="let v of availableVariables" [value]="v.value"> + {{ v.label }} + </mat-option> + </mat-select> + </mat-form-field> + + <!-- ngDefaultControl : see https://github.com/angular/components/issues/8267 --> + <mat-radio-group ngDefaultControl [(ngModel)]="varAction" name="varAction"> + + <div class="radio-button-and-input-wrapper rbaiw-set-value"> + <mat-radio-button value="set-value"> + {{ uitextSetValue }} + </mat-radio-button> + <mat-form-field class="input-form-field"> + <input matInput [(ngModel)]="valueToSet" name="valueToSet" #valueToSetRef="ngModel" + required pattern="^-?([0-9]*\.)?([0-9]+[Ee]-?)?[0-9]+$"> + </mat-form-field> + <mat-error *ngIf="varAction === 'set-value' && valueToSetRef.invalid"> + {{ uitextMustBeANumber }} + </mat-error> + </div> + + <div class="radio-button-and-input-wrapper rbaiw-delta"> + <mat-radio-button value="delta"> + {{ uitextDelta }} + </mat-radio-button> + <mat-form-field class="input-form-field"> + <input matInput [(ngModel)]="delta" name="delta" #deltaRef="ngModel" + required pattern="^-?([0-9]*\.)?([0-9]+[Ee]-?)?[0-9]+$"> + </mat-form-field> + <mat-error *ngIf="varAction === 'delta' && deltaRef.invalid"> + {{ uitextMustBeANumber }} + </mat-error> + </div> + + <div class="radio-button-and-input-wrapper"> + <mat-radio-button value="interpolate" [disabled]="! interpolationEnabled"> + {{ uitextInterpolate }} + <span class="interpolation-bounds"> + {{ interpolationBounds }} + </span> + </mat-radio-button> + </div> + + </mat-radio-group> + + </div> + + <div mat-dialog-actions [attr.align]="'end'"> + <button mat-raised-button color="primary" [mat-dialog-close]="false" cdkFocusInitial> + {{ uitextCancel }} + </button> + <button mat-raised-button type="submit" color="warn" (click)="applyValues()" + [disabled]="buttonDisabled(valueToSetRef, deltaRef)"> + + {{ uitextValidate }} + </button> + </div> + +</form> diff --git a/src/app/components/dialog-edit-pab/dialog-edit-pab.component.scss b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..d8f214cbebe7f7aae7dffb37cece5d77e1bcfe54 --- /dev/null +++ b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.scss @@ -0,0 +1,54 @@ +.mat-dialog-content { + margin-bottom: 2em; + overflow: inherit; // wtf +} + +#selection-abstract { + font-size: .9em; + margin-bottom: 2em; +} + +mat-form-field.select-form-field { + width: 100%; + margin-bottom: 10px; +} + +.radio-button-and-input-wrapper { + margin-bottom: .5em; + + mat-radio-button { + width: 60%; + + &[value="interpolate"] { + width: 100%; + + ::ng-deep label.mat-radio-label { + width: 100%; + + > .mat-radio-label-content { + width: 100%; + + .interpolation-bounds { + float: right; + color: #707070; + } + } + } + } + } + + mat-form-field.input-form-field { + width: 35%; + margin-left: 5%; + } + + mat-error { + font-size: .8em; + margin-top: -12px; + margin-bottom: 15px; + } + + &.rbaiw-set-value, &.rbaiw-delta { + margin-top: -1em; + } +} diff --git a/src/app/components/dialog-edit-pab/dialog-edit-pab.component.ts b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ba3986a127aafa0f69422d61c870e66ea81bc9db --- /dev/null +++ b/src/app/components/dialog-edit-pab/dialog-edit-pab.component.ts @@ -0,0 +1,194 @@ +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material"; +import { Inject, Component } from "@angular/core"; + +import { I18nService } from "../../services/internationalisation/internationalisation.service"; + +import { sprintf } from "sprintf-js"; + +@Component({ + selector: "dialog-edit-pab", + templateUrl: "dialog-edit-pab.component.html", + styleUrls: [ "dialog-edit-pab.component.scss" ] +}) +export class DialogEditPabComponent { + + /** variables eligible to modification */ + public availableVariables: any[]; + + /** symbols of the variables eligible to modification */ + public availableVariableSymbols: string[]; + + /** action to apply to the variable */ + public varAction: string; + + /** value to set to all occurrences of the variable */ + public valueToSet: number; + + /** delta to apply (add) to all occurrences of the variable */ + public delta: number; + + /** true if selected devices are contained in one unique column */ + public vertical: boolean; + + /** variable to modify on every selected object */ + private _variable: string; + + /** number of selected walls and devices */ + private selectedItemsAbstract: { walls: number, wallsDevices: number, devices: number }; + + constructor( + public dialogRef: MatDialogRef<DialogEditPabComponent>, + private intlService: I18nService, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + this.selectedItemsAbstract = this.data.selectedItemsAbstract; + this.vertical = this.data.vertical; + this.availableVariables = this.data.availableVariables; + // console.log("AV VAR", this.availableVariables); + this.availableVariableSymbols = this.availableVariables.map(av => av.value); + this.variable = this.availableVariableSymbols[0]; + // default action + this.varAction = "set-value"; + // example values for validation / avoiding using a placeholder + this.valueToSet = this.availableVariables[0].first; + this.delta = 0; + } + + public get variable(): string { + return this._variable; + } + + // find available variable details from key + private findVariableDetails(variable: string): any { + let details: any; + for (const av of this.availableVariables) { + if (av.value === variable) { + details = av; + } + } + return details; + } + + // preloads valueToSet depending on variable chosen + public set variable(v: string) { + this._variable = v; + const av = this.findVariableDetails(v); + this.valueToSet = av.first; + } + + /** closes dialog and tells parent to apply modifications */ + public applyValues() { + this.dialogRef.close({ + action: this.varAction, + variable: this.variable, + value: Number(this.valueToSet), + delta: Number(this.delta), + variableDetails: this.findVariableDetails(this.variable) + }); + } + + public buttonDisabled(value: any, delta: any) { + return ( + (this.varAction === "set-value" && ! value.valid) + || (this.varAction === "delta" && ! delta.valid) + ); + } + + /** returns a short text summing up what is selected */ + public get selectionAbstract() { + let abstract = ""; + if (this.selectedItemsAbstract.walls > 0) { + // devices outside of complete walls ? + if (this.selectedItemsAbstract.devices > 0) { + abstract = sprintf( + this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_N_WALLS_P_DEVICES"), + this.selectedItemsAbstract.walls, + this.selectedItemsAbstract.devices, + this.selectedItemsAbstract.wallsDevices + this.selectedItemsAbstract.devices + ); + } else { + // only complete walls + abstract = sprintf( + this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_N_WALLS"), + this.selectedItemsAbstract.walls, + this.selectedItemsAbstract.wallsDevices + ); + } + } else { + // no complete wall, devices only + abstract = sprintf( + this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_N_DEVICES"), + this.selectedItemsAbstract.devices + ); + } + return abstract; + } + + public variableLabel(v: string) { + const av = this.findVariableDetails(v); + return av.label; + } + + public get interpolationEnabled() { + return ( + this.vertical + && (this.selectedItemsAbstract.devices + this.selectedItemsAbstract.wallsDevices) > 1 + ); + } + + public get interpolationBounds() { + let bounds = ""; + if (this.interpolationEnabled) { + const varDetails = this.findVariableDetails(this.variable); + if (varDetails) { + bounds = sprintf( + this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_INTERPOLATION_BOUNDS"), + varDetails.first, + varDetails.last + ); + } + } + return bounds; + } + + public get uitextEditPabTitle() { + return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_TITLE"); + } + + public get uitextVariable() { + return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_OPTION_VARIABLE"); + } + + public get uitextSetValue() { + return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_OPTION_SET_VALUE"); + } + + public get uitextDelta() { + return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_OPTION_DELTA"); + } + + public get uitextInterpolate() { + return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_OPTION_INTERPOLATE"); + } + + public get uitextSetValueInput() { + return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_SET_VALUE_INPUT"); + } + + public get uitextDeltaInput() { + return this.intlService.localizeText("INFO_DIALOG_EDIT_PAB_DELTA_INPUT"); + } + + public get uitextMustBeANumber(): string { + return this.intlService.localizeText("ERROR_PARAM_MUST_BE_A_NUMBER"); + } + + public get uitextValidate() { + return this.intlService.localizeText("INFO_OPTION_VALIDATE"); + } + + public get uitextCancel() { + return this.intlService.localizeText("INFO_OPTION_CANCEL"); + } + +} diff --git a/src/app/components/dialog-generate-pab/dialog-generate-pab.component.html b/src/app/components/dialog-generate-pab/dialog-generate-pab.component.html index 14202deecd17f1de7c5a8bc248dcdc84c93ddfc9..c1161f20c451d272622d87db4d7f3b5d795b9a02 100644 --- a/src/app/components/dialog-generate-pab/dialog-generate-pab.component.html +++ b/src/app/components/dialog-generate-pab/dialog-generate-pab.component.html @@ -25,12 +25,12 @@ </mat-error> <mat-form-field> - <input matInput required [placeholder]="uitextNBBassins" pattern="^[1-9][0-9]*$" + <input matInput required [placeholder]="uitextNBBassins" pattern="^([2-9]|[1-9][0-9]+)$" [(ngModel)]="nbBassins" name="nbBassins" #inputNbBassins="ngModel" id="generatePabNbBassins"> </mat-form-field> <mat-error *ngIf="inputNbBassins.invalid && (inputNbBassins.dirty || inputNbBassins.touched)"> <div *ngIf="inputNbBassins.errors.required || inputNbBassins.errors.pattern"> - {{ uitextMustBePositive }} + {{ uitextMustBeAtLeastTwo }} </div> </mat-error> diff --git a/src/app/components/dialog-generate-pab/dialog-generate-pab.component.ts b/src/app/components/dialog-generate-pab/dialog-generate-pab.component.ts index a0f55835b40c16228e8704fc973fda828f809267..bb81e269d9b3f8186ab10d25827336913ec0edb3 100644 --- a/src/app/components/dialog-generate-pab/dialog-generate-pab.component.ts +++ b/src/app/components/dialog-generate-pab/dialog-generate-pab.component.ts @@ -1,8 +1,10 @@ import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material"; import { Inject, Component } from "@angular/core"; +import { ApplicationSetupService } from "../../services/app-setup/app-setup.service"; import { I18nService } from "../../services/internationalisation/internationalisation.service"; -import { FormBuilder } from "@angular/forms"; + +import { sprintf } from "sprintf-js"; @Component({ selector: "dialog-generate-pab", @@ -22,12 +24,13 @@ export class DialogGeneratePABComponent { constructor( public dialogRef: MatDialogRef<DialogGeneratePABComponent>, private intlService: I18nService, - private fb: FormBuilder, + private appSetupService: ApplicationSetupService, @Inject(MAT_DIALOG_DATA) public data: any ) { - this.coteAmont = data.coteAmont; - this.debit = data.debit; - this.chute = data.chute; + const nDigits = this.appSetupService.displayDigits; + this.coteAmont = Number(data.coteAmont.toFixed(nDigits)); + this.debit = Number(data.debit.toFixed(nDigits)); + this.chute = Number(data.chute.toFixed(nDigits)); } public generatePAB() { @@ -67,6 +70,10 @@ export class DialogGeneratePABComponent { return this.intlService.localizeText("ERROR_PARAM_MUST_BE_POSITIVE"); } + public get uitextMustBeAtLeastTwo() { + return sprintf(this.intlService.localizeText("ERROR_PARAM_MUST_BE_AT_LEAST"), 2); + } + public get uitextGeneratePAB() { return this.intlService.localizeText("INFO_CALCULATOR_RESULTS_GENERATE_PAB"); } diff --git a/src/app/components/dialog-save-session/dialog-save-session.component.ts b/src/app/components/dialog-save-session/dialog-save-session.component.ts index eac698d910b693c0bda270014b765531b4506702..425c5f5ed09046dde490187c875df3adb51d85f3 100644 --- a/src/app/components/dialog-save-session/dialog-save-session.component.ts +++ b/src/app/components/dialog-save-session/dialog-save-session.component.ts @@ -47,7 +47,6 @@ export class DialogSaveSessionComponent { * - PabCloisons depend on their models */ public checkLinkedParamsAndModelsDependencies() { - console.log("CLPMD !", this.calculators); this.dependenciesProblems = []; // for all checked Nubs this.calculators.forEach((c) => { diff --git a/src/app/components/field-set/field-set.component.html b/src/app/components/field-set/field-set.component.html index 2471c8dcb4c729e487c16c8e1b9ed2c3f6de27d7..708b04ad6a322a05841b1f4e1e2199e88900fe41 100644 --- a/src/app/components/field-set/field-set.component.html +++ b/src/app/components/field-set/field-set.component.html @@ -35,8 +35,5 @@ <select-field-line *ngIf="isSelectField(p)" [_select]=p> </select-field-line> - - <select-model-field-line *ngIf="isSelectModelField(p)" [_select]=p> - </select-model-field-line> </ng-template> </mat-card-content> diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts index ee5e3de853b7a884b6f6b0f8f2b18963205f5f0b..d26f1e0048abe33f0f47a8eee5966ca15624b771 100644 --- a/src/app/components/field-set/field-set.component.ts +++ b/src/app/components/field-set/field-set.component.ts @@ -207,19 +207,6 @@ export class FieldSetComponent implements DoCheck { ); } - /** - * détermine si un Field est du type SelectFieldModel - */ - private isSelectModelField(f: Field): boolean { - return ( - f instanceof SelectFieldModel - && f.parentForm instanceof FormulairePab - && ( - f.id === (f.parentForm as FormulairePab).modeleCloisonsSelectId - || f.id === (f.parentForm as FormulairePab).modeleCloisonAvalSelectId - ) - ); - } /* * gestion des événements clic sur les radios : * réception d'un message du composant enfant (param-field) @@ -336,9 +323,9 @@ export class FieldSetComponent implements DoCheck { } let msg: string; if (this.childrenToAdd === 1) { - msg = this.i18nService.localizeText("INFO_FSC_FS_ADDED"); + msg = this.i18nService.localizeText("INFO_DEVICE_ADDED"); } else { - msg = sprintf(this.i18nService.localizeText("INFO_FSC_FS_ADDED_N_TIMES"), this.childrenToAdd); + msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_ADDED_N_TIMES"), this.childrenToAdd); } this.notifService.notify(msg); this.childrenToAdd = 1; // reinit to avoid confusion @@ -357,9 +344,9 @@ export class FieldSetComponent implements DoCheck { const pos = (this._fieldSet.parent as FieldsetContainer).getFieldsetPosition(this._fieldSet) + 1; let msg: string; if (this.childrenToAdd === 1) { - msg = sprintf(this.i18nService.localizeText("INFO_FSC_FS_COPIED"), pos); + msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_COPIED"), pos); } else { - msg = sprintf(this.i18nService.localizeText("INFO_FSC_FS_COPIED_N_TIMES"), pos, this.childrenToAdd); + msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_COPIED_N_TIMES"), pos, this.childrenToAdd); } this.notifService.notify(msg); this.childrenToAdd = 1; // reinit to avoid confusion @@ -372,7 +359,7 @@ export class FieldSetComponent implements DoCheck { const pos = (this._fieldSet.parent as FieldsetContainer).getFieldsetPosition(this._fieldSet) + 1; this.removeFieldset.emit(this._fieldSet); this.notifService.notify( - sprintf(this.i18nService.localizeText("INFO_FSC_FS_REMOVED"), pos) + sprintf(this.i18nService.localizeText("INFO_DEVICE_REMOVED"), pos) ); } @@ -383,7 +370,7 @@ export class FieldSetComponent implements DoCheck { const pos = (this._fieldSet.parent as FieldsetContainer).getFieldsetPosition(this._fieldSet) + 1; this.moveFieldsetUp.emit(this._fieldSet); this.notifService.notify( - sprintf(this.i18nService.localizeText("INFO_FSC_FS_MOVED"), pos) + sprintf(this.i18nService.localizeText("INFO_DEVICE_MOVED"), pos) ); } @@ -394,7 +381,7 @@ export class FieldSetComponent implements DoCheck { const pos = (this._fieldSet.parent as FieldsetContainer).getFieldsetPosition(this._fieldSet) + 1; this.moveFieldsetDown.emit(this._fieldSet); this.notifService.notify( - sprintf(this.i18nService.localizeText("INFO_FSC_FS_MOVED"), pos) + sprintf(this.i18nService.localizeText("INFO_DEVICE_MOVED"), pos) ); } } diff --git a/src/app/components/fixedvar-results/var-results.component.ts b/src/app/components/fixedvar-results/var-results.component.ts index 7a97e80c6028145b7443e1cb61c1b9ce9df197f2..424e434239f081897fa449828c155dfe2fe6698e 100644 --- a/src/app/components/fixedvar-results/var-results.component.ts +++ b/src/app/components/fixedvar-results/var-results.component.ts @@ -72,7 +72,7 @@ export class VarResultsComponent extends ResultsComponent { } this._headers = this._headers.concat(this._varResults.extraResultHeaders); - // C. pre-extract variable parameters valueslet longest = 0; + // C. pre-extract variable parameters values const varValues = []; // find longest list this.size = 0; diff --git a/src/app/components/generic-calculator/calculator.component.html b/src/app/components/generic-calculator/calculator.component.html index 4b518f5ab96f9dc279ed229bb9f4cd42c4c44842..f94d21d49c1b4d86244131ce3de674a7dcf9940f 100644 --- a/src/app/components/generic-calculator/calculator.component.html +++ b/src/app/components/generic-calculator/calculator.component.html @@ -28,19 +28,30 @@ <!-- nom du module de calcul --> <calc-name id="calculator-name" [title]="uitextCalculatorName"></calc-name> - <div id="calc-cards-container" class="container" fxLayout="row wrap" fxLayoutAlign="space-around start"> + <div id="calc-cards-container" class="container" + [fxLayout]="isPAB ? 'column' : 'row wrap'" + [fxLayoutAlign]="isPAB ? 'space-around stretch' : 'space-around start'"> + <!-- chapitres --> - <mat-card id="calc-card-field-sets" fxFlex.gt-xs="1 0 400px" fxFlex.lt-sm="1 0 300px"> + <mat-card id="calc-card-field-sets" + [class.pab-field-sets]="isPAB" + [fxFlex.gt-xs]="isPAB ? '1 0 auto' : '1 0 400px'" + [fxFlex.lt-sm]="isPAB ? '1 0 auto' : '1 0 300px'"> + <ng-template ngFor let-fe [ngForOf]="formElements"> <field-set *ngIf="isFieldset(fe)" [style.display]="getFieldsetStyleDisplay(fe.id)" [fieldSet]=fe - (radio)=onRadioClick($event) (validChange)=OnFieldsetValid() (inputChange)=onInputChange($event) + (radio)=onRadioClick($event) (validChange)=onElementValid() (inputChange)=onInputChange($event) (tabPressed)="onTabPressed($event)"> </field-set> <fieldset-container *ngIf="isFieldsetContainer(fe)" [_container]=fe (radio)=onRadioClick($event) - (validChange)=onFieldsetContainerValid() (inputChange)=onInputChange($event) + (validChange)=onElementValid() (inputChange)=onInputChange($event) (tabPressed)="onTabPressed($event)"> </fieldset-container> + + <pab-table *ngIf="isPabTable(fe)" [pabTable]=fe (radio)=onRadioClick($event) + (validChange)=onElementValid() (inputChange)=onInputChange($event)> + </pab-table> </ng-template> <mat-card-actions> @@ -52,14 +63,19 @@ </mat-card> <!-- résultats --> - <mat-card id="calc-card-results" fxFlex.gt-xs="1 0 400px" fxFlex.lt-sm="1 0 300px"> + <mat-card id="calc-card-results" + [class.pab-results]="isPAB" + [fxFlex.gt-xs]="isPAB ? '1 0 auto' : '1 0 400px'" + [fxFlex.lt-sm]="isPAB ? '1 0 auto' : '1 0 300px'"> + <div id="fake-results-anchor"></div> <mat-card-header> <mat-card-title> <h1 [innerHTML]="uitextResultsTitle"></h1> </mat-card-title> <div fxFlex></div> - <button mat-raised-button color="accent" id="generate-pab" *ngIf="isPABCloisons" (click)="generatePAB()"> + <button mat-raised-button color="accent" id="generate-pab" *ngIf="isPABCloisons" (click)="generatePAB()" + [disabled]="! generatePABEnabled" [title]="uitextGeneratePabTitle"> {{ uitextGeneratePAB }} </button> </mat-card-header> diff --git a/src/app/components/generic-calculator/calculator.component.scss b/src/app/components/generic-calculator/calculator.component.scss index c9cbd66b283bf1f3543a3101b9c9dcf91a80ce05..e1e07ee877c8930fded6cb3a3ce2e3f29f5d4330 100644 --- a/src/app/components/generic-calculator/calculator.component.scss +++ b/src/app/components/generic-calculator/calculator.component.scss @@ -38,6 +38,10 @@ mat-card { margin-left: 1em; margin-right: 1em; + &.pab-field-sets { + margin-right: -1em; + } + mat-card-actions { text-align: center; @@ -51,8 +55,12 @@ mat-card { margin-left: 1em; margin-right: 1em; + &.pab-results { + margin-right: -1em; + } + mat-card-header { - margin-bottom: 1em; + // margin-bottom: 1em; #generate-pab { height: min-content; diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts index 67b856e1f70cc84e07f725a449ba3d7a440b6ed5..48e622a72859780b76fe5cff84109301cdbcb6b2 100644 --- a/src/app/components/generic-calculator/calculator.component.ts +++ b/src/app/components/generic-calculator/calculator.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit, DoCheck, OnDestroy, ViewChild, ViewChildren, QueryList, AfterViewChecked, ElementRef } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { Observer, Session, Cloisons, Pab, PabCloisons, ParamValueMode, CalculatorType } from "jalhyd"; +import { Observer, Session, Cloisons, Pab, ParamValueMode, CalculatorType } from "jalhyd"; import { FormulaireService } from "../../services/formulaire/formulaire.service"; import { I18nService } from "../../services/internationalisation/internationalisation.service"; @@ -16,11 +16,12 @@ import { CalculatorNameComponent } from "./calc-name.component"; import { FormulaireElement } from "../../formulaire/formulaire-element"; import { FieldsetContainer } from "../../formulaire/fieldset-container"; import { FieldsetContainerComponent } from "../fieldset-container/fieldset-container.component"; +import { PabTableComponent } from "../pab-table/pab-table.component"; import { ServiceFactory } from "../../services/service-factory"; import { MatDialog } from "@angular/material"; import { DialogConfirmCloseCalcComponent } from "../dialog-confirm-close-calc/dialog-confirm-close-calc.component"; import { DialogGeneratePABComponent } from "../dialog-generate-pab/dialog-generate-pab.component"; -import { SelectFieldModel } from "../../formulaire/select-field-model"; +import { PabTable } from "../../formulaire/pab-table"; @Component({ selector: "hydrocalc", @@ -40,6 +41,12 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, @ViewChildren(FieldsetContainerComponent) private _fieldsetContainerComponents: QueryList<FieldsetContainerComponent>; + /** + * PabTableComponent if any + */ + @ViewChild(PabTableComponent) + private _pabTableComponent: PabTableComponent; + /** * composant d'affichage des résultats */ @@ -131,6 +138,13 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, return fe instanceof FieldsetContainer; } + /** + * détermine si un FormulaireElement est du type PabTable + */ + public isPabTable(fe: any): boolean { + return fe instanceof PabTable; + } + public get hasForm() { return this._formulaire !== undefined; } @@ -328,6 +342,7 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, // valeur initiale , this._fieldsetComponents.length > 0); } + if (this._fieldsetContainerComponents !== undefined) { this._isUIValid = this._isUIValid && this._fieldsetContainerComponents.reduce( // callback @@ -346,6 +361,10 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, // valeur initiale , true); } + + if (this._pabTableComponent !== undefined) { + this._isUIValid = this._isUIValid && this._pabTableComponent.isValid; + } } public getFieldsetStyleDisplay(id: string) { @@ -354,16 +373,9 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, } /** - * réception d'un événement de validité d'un FieldSetComponent - */ - public OnFieldsetValid() { - this.updateUIValidity(); - } - - /** - * réception d'un événement de validité d'un FieldsetContainerComponent + * réception d'un événement de validité d'un FormElement */ - public onFieldsetContainerValid() { + public onElementValid() { this.updateUIValidity(); } @@ -425,6 +437,16 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, return false; } + // for "one wide column" layout + public get isPAB() { + return ( + this._formulaire + && this._formulaire.currentNub + && this._formulaire.currentNub.calcType === CalculatorType.Pab + ); + } + + // for "generate PAB" button public get isPABCloisons() { return ( this._formulaire @@ -433,6 +455,22 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, ); } + // PAB generation is enabled only if no parameter is varying + public get generatePABEnabled(): boolean { + let ret = true; + for (const p of this._formulaire.currentNub.parameterIterator) { + ret = ret && (! p.hasMultipleValues); + } + return ret; + } + + public get uitextGeneratePabTitle() { + if (! this.generatePABEnabled) { + return this.intlService.localizeText("INFO_PAB_PARAMETRES_FIXES"); + } + return ""; + } + /** * Génère une passe à bassins à partir du modèle de cloisons en cours */ @@ -443,9 +481,9 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, DialogGeneratePABComponent, { data: { - chute: cloisons.prms.DH.singleValue, - debit: cloisons.prms.Q.singleValue, - coteAmont: cloisons.prms.Z1.singleValue + chute: cloisons.V(cloisons.prms.DH), + debit: cloisons.V(cloisons.prms.Q), + coteAmont: cloisons.V(cloisons.prms.Z1) }, disableClose: true } @@ -461,21 +499,8 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, params.Z1.singleValue = result.coteAmont; params.Z2.singleValue = result.coteAval; // création des bassins - for (let i = 0; i < result.nbBassins; i++) { - const pabCloisons = new PabCloisons(this._formulaire.currentNub as Cloisons); - pab.addChild(pabCloisons); // @TODO should be replace afterwards - for (const e of f.allFormElements) { - if (e instanceof FieldsetContainer) { - const newFieldset = e.addFromTemplate(0, undefined, pabCloisons); - // set selected value by ID; nub should be set by "select value changed" event listener - const modeleSelect = (newFieldset.getFormulaireNodeById("select_modele_cloisons") as SelectFieldModel); - modeleSelect.updateEntries(); - // ID of the Cloisons nub used by pabCloisons as a model - modeleSelect.setValueFromId(this._formulaire.currentNub.uid); - break; - } - } - } + pab.deleteChild(0); + pab.addCloisonsFromModel(this._formulaire.currentNub as Cloisons, result.nbBassins); // go to new PAB this.router.navigate(["/calculator", f.uid]); }); diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts index 4b23185d6d9738661fc67c44ce48c99f6e9742f5..19232853b2f118725cbeebfc2d25bbb18fadbe41 100644 --- a/src/app/components/generic-input/generic-input.component.ts +++ b/src/app/components/generic-input/generic-input.component.ts @@ -1,7 +1,7 @@ import { Input, Output, EventEmitter, ChangeDetectorRef, OnChanges, ViewChild } from "@angular/core"; import { NgModel } from "@angular/forms"; import { BaseComponent } from "../base/base.component"; -import { isNumeric, Structure, PabCloisons } from "jalhyd"; +import { isNumeric, Structure, Pab } from "jalhyd"; import { FormulaireDefinition } from "../../formulaire/definition/form-definition"; import { NgParameter } from "../../formulaire/ngparam"; import { I18nService } from "../../services/internationalisation/internationalisation.service"; @@ -46,7 +46,7 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC // if inside a nested Structure, prefix with Structure position // to disambiguate const nub = param.paramDefinition.parentNub; - if (nub && (nub instanceof Structure || nub instanceof PabCloisons)) { + if (nub && (nub instanceof Structure || nub.parent instanceof Pab)) { id = nub.findPositionInParent() + "_" + id; } } diff --git a/src/app/components/log/log.component.ts b/src/app/components/log/log.component.ts index 6ab7883505321926fb9209f9a00bfe8c9d5938f0..c84de036410d6effe1a9c82f088b12bc6e77579b 100644 --- a/src/app/components/log/log.component.ts +++ b/src/app/components/log/log.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { Component, Input } from "@angular/core"; import { cLog, Message } from "jalhyd"; @@ -17,12 +17,20 @@ export class LogComponent { */ private _log: cLog; + // title to display above the log + @Input() + public logTitle: string; + constructor( private intlService: I18nService, ) { } private get uitextTitreJournal() { - return this.intlService.localizeText("INFO_TITREJOURNAL"); + if (this.logTitle) { + return this.logTitle; + } else { + return this.intlService.localizeText("INFO_TITREJOURNAL"); + } } public get hasEntries(): boolean { diff --git a/src/app/components/pab-profile-graph/pab-profile-graph.component.html b/src/app/components/pab-profile-graph/pab-profile-graph.component.html new file mode 100644 index 0000000000000000000000000000000000000000..28b4288fd2bbf48bcedac4e7a4ed9375a2f91cc1 --- /dev/null +++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.html @@ -0,0 +1,21 @@ +<div class="graph-results-container" #graphProfile fxLayout="row wrap" fxLayoutAlign="center center"> + <div fxFlex="1 1 100%"> + <div class="graph-profile-buttons"> + <button mat-icon-button (click)="resetZoom()" [disabled]="! zoomWasChanged" [title]="uitextResetZoomTitle"> + <mat-icon color="primary">replay</mat-icon> + </button> + <button mat-icon-button (click)="exportAsImage(graphProfile)" [title]="uitextExportImageTitle"> + <mat-icon color="primary">image</mat-icon> + </button> + <button mat-icon-button *ngIf="! isFullscreen" (click)="setFullscreen(graphProfile)" [title]="uitextEnterFSTitle"> + <mat-icon color="primary" class="scaled12">fullscreen</mat-icon> + </button> + <button mat-icon-button *ngIf="isFullscreen" (click)="exitFullscreen()" [title]="uitextExitFSTitle"> + <mat-icon color="primary" class="scaled12">fullscreen_exit</mat-icon> + </button> + </div> + + <chart type="scatter" [data]="graph_data" [options]="graph_options" #graphChart> + </chart> + </div> +</div> \ No newline at end of file diff --git a/src/app/components/pab-profile-graph/pab-profile-graph.component.scss b/src/app/components/pab-profile-graph/pab-profile-graph.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..465866a6ce03ef7eaa710c1e4545563c44d2e9dd --- /dev/null +++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.scss @@ -0,0 +1,29 @@ +.graph-results-container{ + display: block; + background-color: white; +} + +.graph-profile-buttons { + padding-right: 10px; + padding-top: 4px; + margin-bottom: -30px; + text-align: right; + background-color: white; + + button { + margin-left: 3px; + width: auto; + + mat-icon { + &.scaled12 { + transform: scale(1.2); + } + } + + &:disabled { + mat-icon { + color: #bfbfbf; + } + } + } +} diff --git a/src/app/components/pab-profile-graph/pab-profile-graph.component.ts b/src/app/components/pab-profile-graph/pab-profile-graph.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..a6689110f4804d3782793e66a5707f2783fb7585 --- /dev/null +++ b/src/app/components/pab-profile-graph/pab-profile-graph.component.ts @@ -0,0 +1,369 @@ +import { Component, ViewChild, ChangeDetectorRef } from "@angular/core"; + +import { ChartComponent } from "angular2-chartjs"; + +import { ApplicationSetupService } from "../../services/app-setup/app-setup.service"; +import { I18nService } from "../../services/internationalisation/internationalisation.service"; +import { ResultsComponent } from "../fixedvar-results/results.component"; +import { PabResults } from "../../results/pab-results"; + +@Component({ + selector: "pab-profile-graph", + templateUrl: "./pab-profile-graph.component.html", + styleUrls: [ + "./pab-profile-graph.component.scss" + ] +}) +export class PabProfileGraphComponent extends ResultsComponent { + + @ViewChild(ChartComponent) + private chartComponent; + + private _results: PabResults; + + /** size of the longest variable value */ + private size = 0; + + /** inferred extended values list for each variating parameter */ + private varValues = []; + + private _zoomWasChanged = false; + + /* + * config du graphe + */ + public graph_data: { datasets: any[] }; + public graph_options = { + responsive: true, + maintainAspectRatio: true, + animation: { + duration: 0 + }, + legend: { + display: true, + position: "bottom", + reverse: false + }, + title: { + display: true, + text: this.intlService.localizeText("INFO_PAB_TITRE_PROFIL") + }, + elements: { + line: { + tension: 0 + } + } + }; + + public constructor( + private appSetupService: ApplicationSetupService, + private intlService: I18nService, + private cd: ChangeDetectorRef + ) { + super(); + const nDigits = this.appSetupService.displayDigits; + // do not move following block out of constructor or scale labels won't be rendered + this.graph_options["scales"] = { + xAxes: [{ + type: "linear", + position: "bottom", + ticks: { + precision: nDigits + }, + scaleLabel: { + display: true, + labelString: this.intlService.localizeText("INFO_LIB_DISTANCE_AMONT") + } + }], + yAxes: [{ + type: "linear", + position: "left", + ticks: { + precision: nDigits + }, + scaleLabel: { + display: true, + labelString: this.intlService.localizeText("INFO_LIB_COTE") + } + }] + }; + // enable zoom and pan (using "chartjs-plugin-zoom" package) + const that = this; + this.graph_options["plugins"] = { + zoom: { + pan: { + enabled: false, // conflicts with drag zoom + mode: "xy", + }, + zoom: { + enabled: true, + drag: { // conflicts with pan; set to false to enable mouse wheel zoom, + borderColor: "rgba(225,225,225,0.3)", + borderWidth: 1, + backgroundColor: "rgba(0,0,0,0.25)" + }, + mode: "xy", + // percentage of zoom on a wheel event + // speed: 0.1, + onZoomComplete: function(t: any) { return function() { t.zoomComplete(); }; }(that) + } + } + }; + } + + public set results(r: PabResults) { + this._results = r; + + // pre-extract variable parameters values + if (this._results) { + this.varValues = []; + const nDigits = this.appSetupService.displayDigits; + // find longest list + this.size = 0; + for (let i = 0; i < this._results.variatedParameters.length; i++) { + const vs = this._results.variatedParameters[i].valuesIterator.count(); + if (vs > this.size) { + this.size = vs; + } + } + // get extended values lists for each variable parameter + for (const v of this._results.variatedParameters) { + const vv = []; + const iter = v.getExtendedValuesIterator(this.size); + while (iter.hasNext) { + const nv = iter.next(); + vv.push(nv.value.toFixed(nDigits)); + } + this.varValues.push(vv); + } + } + } + + private zoomComplete() { + this._zoomWasChanged = true; + this.cd.detectChanges(); + } + + public get zoomWasChanged(): boolean { + return this._zoomWasChanged; + } + + public updateView() { + this.generateScatterGraph(); + } + + /** + * génère les données d'un graphe de type "scatter" + */ + private generateScatterGraph() { + const ySeries = this.getYSeries(); + + this.graph_data = { + datasets: [] + }; + + // build Y data series + for (const ys of ySeries) { + // push series config + this.graph_data.datasets.push({ + label: ys.label, + data: ys.data, + borderColor: ys.color, // couleur de la ligne + backgroundColor: "rgba(0,0,0,0)", // couleur de remplissage sous la courbe : transparent + showLine: "true" + }); + } + } + + public exportAsImage(element: HTMLDivElement) { + const canvas: HTMLCanvasElement = element.querySelector("canvas"); + canvas.toBlob((blob) => { + saveAs(blob, "chart.png"); + }); // defaults to image/png + } + + public resetZoom() { + this.chartComponent.chart.resetZoom(); + this._zoomWasChanged = false; + } + + public get uitextResetZoomTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_RESET_ZOOM"); + } + + public get uitextExportImageTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_EXPORT_IMAGE"); + } + + public get uitextEnterFSTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_ENTER_FS"); + } + + public get uitextExitFSTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_EXIT_FS"); + } + + private getXSeries(): string[] { + const data: string[] = []; + const nDigits = this.appSetupService.displayDigits; + // X is always wall abscissa + for (const cr of this._results.cloisonsResults) { + const x = cr.resultElement.getExtraResult("x"); // any resultElement will do + data.push(x.toFixed(nDigits)); + } + const xdw = this._results.cloisonAvalResults.resultElement.getExtraResult("x"); + data.push(xdw.toFixed(nDigits)); + return data; + } + + private getYSeries(): { data: { x: string, y: string }[], label: string, color: string }[] { + const ret: { data: { x: string, y: string }[], label: string, color: string }[] = []; + const xs = this.getXSeries(); // abscissae + const pabLength = Number(xs[xs.length - 1]) - Number(xs[0]); + const pabLength5Pct = (pabLength * 5) / 100; + + // 1. fond du machin + const dataF: { x: string, y: string }[] = []; + const nDigits = this.appSetupService.displayDigits; + // extend upstrem + dataF.push({ + x: (Number(xs[0]) - pabLength5Pct).toFixed(nDigits), + y: this._results.cloisonsResults[0].resultElement.getExtraResult("ZRAM").toFixed(nDigits) + }); + // regular walls + for (let i = 0; i < this._results.cloisonsResults.length; i++) { + const cr = this._results.cloisonsResults[i]; + const ZRAM = cr.resultElement.getExtraResult("ZRAM"); // any ResultElement will do + dataF.push({ + x: xs[i], + y: ZRAM.toFixed(nDigits) + }); + } + // downwall + const ZRAMdw = this._results.cloisonAvalResults.resultElement.getExtraResult("ZRAM"); + dataF.push({ + x: xs[ xs.length - 1 ], + y: ZRAMdw.toFixed(nDigits) + }); + // extend downstream + dataF.push({ + x: (Number(xs[xs.length - 1]) + pabLength5Pct).toFixed(nDigits), + y: ZRAMdw.toFixed(nDigits) + }); + // add series + ret.push({ + data: dataF, + label: this.intlService.localizeText("INFO_LIB_RADIER"), + color: "#808080" + }); + + // 2. séries + const nbSeries = this._results.cloisonsResults[0].resultElements.length; + const palette = this.distinctColors; + + seriesLoop: + for (let n = 0; n < nbSeries; n++) { + // --------- build nth series --------- + const dataN: { x: string, y: string }[] = []; + let i = 0; // abscissa index + + // if this iteration has errors, do not try to draw it + if (this._results.iterationHasError(n)) { + continue seriesLoop; + } + + // extend upstream + dataN.push({ + x: (Number(xs[0]) - pabLength5Pct).toFixed(nDigits), + y: this._results.cloisonsResults[0].resultElements[n].vCalc.toFixed(nDigits) + }); + + // walls + for (const x of xs) { + let Z1: number; + let nextZ1: number; + if (i < xs.length - 2) { + // regular walls + Z1 = this._results.cloisonsResults[i].resultElements[n].vCalc; + nextZ1 = this._results.cloisonsResults[i + 1].resultElements[n].vCalc; + } else if (i === xs.length - 2) { + // last regular wall + Z1 = this._results.cloisonsResults[i].resultElements[n].vCalc; + nextZ1 = this._results.cloisonAvalResults.resultElements[n].vCalc; + } else { + // downwall + Z1 = this._results.cloisonAvalResults.resultElements[n].vCalc; + nextZ1 = this._results.Z2[n]; + } + + // 2 points for each abscissa + dataN.push({ + x: x, + y: Z1.toFixed(nDigits) + }); + dataN.push({ + x: x, + y: nextZ1.toFixed(nDigits) + }); + + i++; + } + + // extend downstream + dataN.push({ + x: (Number(xs[xs.length - 1]) + pabLength5Pct).toFixed(nDigits), + y: this._results.Z2[n].toFixed(nDigits) + }); + + ret.push({ + data: dataN, + label: ( + this._results.variatedParameters.length > 0 ? + this.getLegendForSeries(n) : + this.intlService.localizeText("INFO_LIB_LIGNE_D_EAU") + ), + color: palette[ n % palette.length ] + }); + } + + return ret; + } + + /** + * Returns a label showing the boundary conditions values for + * the given iteration + * @param n index of the variating parameter(s) iteration + */ + private getLegendForSeries(n: number): string { + let i = 0; + return this.varValues.map((vv) => { + const vp = this._results.variatedParameters[i]; + i++; + let value = "0"; + value = vv[n]; + return `${vp.symbol} = ${value}`; + }).join(", "); + } + + /** + * 14 distinct colors @see https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors + */ + private get distinctColors(): string[] { + return [ + "#4363d8", // blue + "#f58231", // orange + "#3cb44b", // green + "#e6194B", // red + "#911eb4", // purple + "#ffe119", // yellow + "#f032e6", // magenta + "#9A6324", // brown + "#000075", // navy + "#808000", // olive + "#42d4f4", // cyan + "#a9a9a9", // grey + "#bfef45", // lime + "#469990", // teal + ]; + } +} diff --git a/src/app/components/pab-results/pab-results-table.component.html b/src/app/components/pab-results/pab-results-table.component.html index 4a86d0a987436e5a49b4ae273a74ba8de11c90fa..ce1029faef169b64fc08b8bb7215bf0ea99ec43a 100644 --- a/src/app/components/pab-results/pab-results-table.component.html +++ b/src/app/components/pab-results/pab-results-table.component.html @@ -1,5 +1,5 @@ <!-- @TODO copied from var-results.component.html > merge ?--> -<div class="pab-results-table-container" #pabResultsTable *ngIf="hasResults" fxLayout="row wrap" fxLayoutAlign="center center"> +<div class="pab-results-table-container" #pabResultsTable fxLayout="row wrap" fxLayoutAlign="center center"> <div fxFlex="1 1 100%"> <div class="pab-results-table-buttons"> <button mat-icon-button (click)="exportAsSpreadsheet()"> diff --git a/src/app/components/pab-results/pab-results-table.component.scss b/src/app/components/pab-results/pab-results-table.component.scss index 7e227e04eb8446b28b1a55995311e77330982856..ea1d83f80958f4d7b394b8706813e254fe895167 100644 --- a/src/app/components/pab-results/pab-results-table.component.scss +++ b/src/app/components/pab-results/pab-results-table.component.scss @@ -3,7 +3,6 @@ } .pab-results-table-container { - margin-top: 2em; background-color: white; } diff --git a/src/app/components/pab-results/pab-results-table.component.ts b/src/app/components/pab-results/pab-results-table.component.ts index af0ebe4df7a8dc6e4f06aa33fbc616650b69172d..3080acf1f7415f4c7734b20dc262fc7765ea1b55 100644 --- a/src/app/components/pab-results/pab-results-table.component.ts +++ b/src/app/components/pab-results/pab-results-table.component.ts @@ -1,8 +1,10 @@ import { Component, ViewChild, ElementRef } from "@angular/core"; -import { PabResults } from "../../results/pab-results"; +import { CloisonAval } from "jalhyd"; import * as XLSX from "xlsx"; + +import { PabResults } from "../../results/pab-results"; import { ApplicationSetupService } from "../../services/app-setup/app-setup.service"; import { I18nService } from "../../services/internationalisation/internationalisation.service"; import { ResultsComponent } from "../fixedvar-results/results.component"; @@ -42,89 +44,79 @@ export class PabResultsTableComponent extends ResultsComponent { this._pabResults && this._pabResults.cloisonsResults && this._pabResults.cloisonsResults.length > 0 - && ! this._pabResults.hasError() + && ! this._pabResults.hasOnlyErrors() ) { const pr = this._pabResults; const nDigits = this.appSetupService.displayDigits; + // when a parameter is variating, index of the variating parameter + // values to build the data from + const vi = pr.variableIndex; // refresh headers here if language changed this._headers = pr.headers; // line 1 - if (pr.cloisonsResults[0].vCalc) { // parfois le calcul des cloisons échoue + if (pr.cloisonsResults[0].resultElements[vi].vCalc) { // parfois le calcul des cloisons échoue this._dataSet.push([ - "", - pr.cloisonsResults[0] ? pr.cloisonsResults[0].vCalc.toFixed(nDigits) : "", + this.intlService.localizeText("INFO_LIB_AMONT"), + pr.cloisonsResults[0].resultElements[vi] ? pr.cloisonsResults[0].resultElements[vi].vCalc.toFixed(nDigits) : "", "", "", "", "", "", "", "" ]); } // lines 2 - n-1 - for (let i = 0; i < pr.cloisonsResults.length - 1; i++) { + for (let i = 0; i < pr.cloisonsResults.length; i++) { if ( - pr.cloisonsResults[i].vCalc - && pr.cloisonsResults[i + 1].vCalc + pr.cloisonsResults[i].resultElements[vi].vCalc ) { - const ZRAM = pr.cloisonsResults[i].getExtraResult("ZRAM"); - const PV = pr.cloisonsResults[i].getExtraResult("PV"); - const YMOY = pr.cloisonsResults[i].getExtraResult("YMOY"); - const ZRB = pr.cloisonsResults[i].getExtraResult("ZRB"); + const r2n = pr.cloisonsResults[i].resultElements[vi].extraResults; + let Z1: number; + if (i < pr.cloisonsResults.length - 1) { + Z1 = pr.cloisonsResults[i + 1].resultElements[vi].vCalc; + } else { + Z1 = pr.cloisonAvalResults.resultElements[vi].vCalc; + } this._dataSet.push([ i + 1, // n° cloison - pr.cloisonsResults[i + 1].vCalc.toFixed(nDigits), // Z - (ZRAM !== undefined) ? ZRAM.toFixed(nDigits) : "", // ZRAM - (pr.cloisonsResults[i].vCalc - pr.cloisonsResults[i + 1].vCalc).toFixed(nDigits), // DH - pr.cloisonsQ[i].toFixed(nDigits), // Q - (PV !== undefined) ? PV.toFixed(nDigits) : "", // PV - (YMOY !== undefined) ? YMOY.toFixed(nDigits) : "", // YMOY - (ZRB !== undefined) ? ZRB.toFixed(nDigits) : "", // ZRB - pr.bassinsQA[i] // QA + Z1.toFixed(nDigits), // Z + r2n.ZRAM.toFixed(nDigits), + r2n.DH.toFixed(nDigits), + r2n.Q.toFixed(nDigits), + r2n.PV.toFixed(nDigits), + r2n.YMOY.toFixed(nDigits), + r2n.ZRMB.toFixed(nDigits), + r2n.QA // QA ]); } } - // line n - const l = pr.cloisonsResults.length; - if ( - pr.cloisonsResults[l - 1].vCalc - && pr.cloisonAvalResults.vCalc - ) { - const nZRAM = pr.cloisonsResults[l - 1].getExtraResult("ZRAM"); - const nPV = pr.cloisonsResults[l - 1].getExtraResult("PV"); - const nY = pr.cloisonsResults[l - 1].getExtraResult("YMOY"); - const nZRB = pr.cloisonsResults[l - 1].getExtraResult("ZRB"); - this._dataSet.push([ - l, // n° cloison - pr.cloisonAvalResults.vCalc.toFixed(nDigits), // Z - (nZRAM !== undefined) ? nZRAM.toFixed(nDigits) : "", // ZRAM - (pr.cloisonsResults[l - 1].vCalc - pr.cloisonAvalResults.vCalc).toFixed(nDigits), // DH - pr.cloisonsQ[l - 1].toFixed(nDigits), // Q - (nPV !== undefined) ? nPV.toFixed(nDigits) : "", // PV - (nY !== undefined) ? nY.toFixed(nDigits) : "", // YMOY - (nZRB !== undefined) ? nZRB.toFixed(nDigits) : "", // ZRB - pr.bassinsQA[l - 1] // QA - ]); - } - // downstream line - if (pr.cloisonAvalResults.vCalc) { - const caZRAM = pr.cloisonAvalResults.getExtraResult("ZRAM"); + if (pr.cloisonAvalResults.resultElements[vi].vCalc) { + const rln = pr.cloisonAvalResults.resultElements[vi].extraResults; this._dataSet.push([ - this.intlService.localizeText("INFO_LIB_AVAL"), // n° cloison - pr.Z2.toFixed(nDigits), // Z - (caZRAM !== undefined) ? caZRAM.toFixed(nDigits) : "", // ZRAM - (pr.cloisonAvalResults.vCalc - pr.Z2).toFixed(nDigits), // DH - pr.cloisonAvalQ.toFixed(nDigits), // Q + this.intlService.localizeText("INFO_LIB_AVAL"), + pr.Z2[vi].toFixed(nDigits), + rln.ZRAM.toFixed(nDigits), + rln.DH.toFixed(nDigits), + rln.Q.toFixed(nDigits), "", "", "", "" ]); + // extra lift gate ? + const cloisonAval = (pr.cloisonAvalResults.sourceNub as CloisonAval); + if (cloisonAval && cloisonAval.hasVanneLevante()) { + const vanneZDV = cloisonAval.result.resultElements[vi].getExtraResult("ZDV"); + if (vanneZDV) { + this._dataSet.push([ + this.intlService.localizeText("INFO_LIB_COTE_VANNE_LEVANTE"), + vanneZDV.toFixed(nDigits), + "", "", "", "", "", "", "" + ]); + } + } } } } - public get hasResults(): boolean { - return this._pabResults && this._pabResults.hasResults; - } - public get headers() { return this._headers; } diff --git a/src/app/components/pab-results/pab-results.component.html b/src/app/components/pab-results/pab-results.component.html index 9fcc9927cd0a5b351b994c0585577205d89f36ce..fbb8e9c23fc4bee8ffaae0d19b1aaa82cf7ad5fb 100644 --- a/src/app/components/pab-results/pab-results.component.html +++ b/src/app/components/pab-results/pab-results.component.html @@ -1,11 +1,22 @@ <div class="container"> - <!-- journal --> - <log></log> - <results-graph *ngIf="hasResults"></results-graph> + <log #generalLog [logTitle]="uitextGeneralLogTitle">log général</log> + + <pab-variable-results-selector [results]="pabResults" (indexChange)="variableIndexChanged()"> + </pab-variable-results-selector> + + <log #iterationLog></log> <div> <!-- tableau de résultats --> - <pab-results-table [results]="pabResults"></pab-results-table> + <pab-results-table *ngIf="hasDisplayableResults" [results]="pabResults"></pab-results-table> </div> + + <div id="pab-graphs-container" class="container" fxLayout="row wrap" fxLayoutAlign="space-around start"> + <pab-profile-graph *ngIf="hasDisplayableResults" fxFlex.gt-xs="1 0 400px" fxFlex.lt-sm="1 0 300px"> + </pab-profile-graph> + <results-graph *ngIf="hasDisplayableResults" fxFlex.gt-xs="1 0 400px" fxFlex.lt-sm="1 0 300px"> + </results-graph> + </div> + </div> diff --git a/src/app/components/pab-results/pab-results.component.scss b/src/app/components/pab-results/pab-results.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..6f5eb10d525664ef64b09c7c5eda9a8a074218b1 --- /dev/null +++ b/src/app/components/pab-results/pab-results.component.scss @@ -0,0 +1,4 @@ +results-graph { + margin-left: 1em; + margin-right: 1em; +} diff --git a/src/app/components/pab-results/pab-results.component.ts b/src/app/components/pab-results/pab-results.component.ts index d17318d768e48b1b73eb202b9c32f6c61891eeaf..ff73e62cbe9c14ca7c584acf7be3e4a19566cb11 100644 --- a/src/app/components/pab-results/pab-results.component.ts +++ b/src/app/components/pab-results/pab-results.component.ts @@ -1,6 +1,6 @@ import { Component, ViewChild, DoCheck } from "@angular/core"; -import { Result, cLog } from "jalhyd"; +import { Result, cLog, Message, MessageCode, MessageSeverity } from "jalhyd"; import { LogComponent } from "../../components/log/log.component"; import { CalculatorResults } from "../../results/calculator-results"; @@ -8,15 +8,18 @@ import { NgParameter } from "../../formulaire/ngparam"; import { ApplicationSetupService } from "../../services/app-setup/app-setup.service"; import { PabResultsTableComponent } from "./pab-results-table.component"; import { PabResults } from "../../results/pab-results"; -import { ResultsGraphComponent } from "../results-graph/results-graph.component"; +import { PabVariableResultsSelectorComponent } from "./pab-variable-results-selector.component"; import { PlottableData } from "../../results/plottable-data"; import { PlottablePabResults } from "../../results/plottable-pab-results"; +import { ResultsGraphComponent } from "../results-graph/results-graph.component"; +import { I18nService } from "../../services/internationalisation/internationalisation.service"; +import { PabProfileGraphComponent } from "../pab-profile-graph/pab-profile-graph.component"; @Component({ selector: "pab-results", templateUrl: "./pab-results.component.html", styleUrls: [ - "../fixedvar-results/fixedvar-results.component.scss" + "./pab-results.component.scss" ] }) export class PabResultsComponent implements DoCheck { @@ -24,7 +27,7 @@ export class PabResultsComponent implements DoCheck { /** résultats non mis en forme */ private _pabResults: PabResults; - /** résultats mis en forme pour le graphique */ + /** résultats mis en forme pour le graphique de données (classique) */ private _plottableResults: PlottablePabResults; /** true si les résultats doiventt être remis à jour */ @@ -33,16 +36,24 @@ export class PabResultsComponent implements DoCheck { @ViewChild(PabResultsTableComponent) private pabResultsTableComponent: PabResultsTableComponent; - /** composant journal */ - @ViewChild(LogComponent) - private logComponent: LogComponent; + @ViewChild(PabVariableResultsSelectorComponent) + private pabVariableResultsSelectorComponent: PabVariableResultsSelectorComponent; + + @ViewChild("generalLog") + private generalLogComponent: LogComponent; + + @ViewChild("iterationLog") + private iterationLogComponent: LogComponent; - /** graphique dans le cas d'un paramètre à varier */ @ViewChild(ResultsGraphComponent) private resultsGraphComponent: ResultsGraphComponent; + @ViewChild(PabProfileGraphComponent) + private profileGraphComponent: PabProfileGraphComponent; + constructor( private appSetupService: ApplicationSetupService, + private i18nService: I18nService, ) { this._plottableResults = new PlottablePabResults(); } @@ -55,14 +66,30 @@ export class PabResultsComponent implements DoCheck { this.updateView(); } + /** + * update results table and chart when the variable index changed (event sent by + * PabVariableResultsSelectorComponent); variable index is already set in + * pabResults at this time + */ + public variableIndexChanged() { + this.updateView(); + } + public updateView() { - this.logComponent.log = undefined; + this.iterationLogComponent.log = undefined; + this.generalLogComponent.log = undefined; if (this.pabResultsTableComponent) { this.pabResultsTableComponent.results = undefined; } + if (this.pabVariableResultsSelectorComponent) { + this.pabVariableResultsSelectorComponent.results = undefined; + } if (this.resultsGraphComponent) { this.resultsGraphComponent.results = undefined; } + if (this.profileGraphComponent) { + this.profileGraphComponent.results = undefined; + } // set _doUpdate flag so that results are rebuilt on the next Angular display cycle this._doUpdate = false; if (this._pabResults !== undefined) { @@ -76,28 +103,164 @@ export class PabResultsComponent implements DoCheck { } } - private mergeLog(result: Result, log: cLog) { - if (result && result.hasLog) { + private mergeGlobalLog(result: Result, log: cLog) { + if (result) { if (result.hasGlobalLog) { log.addLog(result.globalLog); - } else { - log.addLog(result.log); + } + // if no parameter is varying, 1st element log is considered "global" + if (this.pabResults.variatedParameters.length === 0) { + if (result.hasResultElements && result.resultElement.hasLog) { + log.addLog(result.log); + } + } + } + } + + /** + * Returns the number of errors, warnings, infos among children logs + */ + private logStats(): any { + const ret = { + info: 0, + warning: 0, + error: 0 + }; + if (this._pabResults.result && this._pabResults.result.hasLog) { + for (const re of this._pabResults.result.resultElements) { + if (re.hasLog) { + for (const m of re.log.messages) { + const s = m.getSeverity(); + switch (s) { + case MessageSeverity.INFO: + ret.info ++; + break; + case MessageSeverity.WARNING: + ret.warning ++; + break; + case MessageSeverity.ERROR: + ret.error ++; + break; + } + } + } + } + } + for (const cr of this._pabResults.cloisonsResults) { + if (cr && cr.hasLog) { + for (const re of cr.resultElements) { + if (re.hasLog) { + for (const m of re.log.messages) { + const s = m.getSeverity(); + switch (s) { + case MessageSeverity.INFO: + ret.info ++; + break; + case MessageSeverity.WARNING: + ret.warning ++; + break; + case MessageSeverity.ERROR: + ret.error ++; + break; + } + } + } + } + } + } + if (this._pabResults.cloisonAvalResults && this._pabResults.cloisonAvalResults.hasLog) { + for (const re of this._pabResults.cloisonAvalResults.resultElements) { + if (re.hasLog) { + for (const m of re.log.messages) { + const s = m.getSeverity(); + switch (s) { + case MessageSeverity.INFO: + ret.info ++; + break; + case MessageSeverity.WARNING: + ret.warning ++; + break; + case MessageSeverity.ERROR: + ret.error ++; + break; + } + } + } } } + return ret; + } + + /* + * Retourne les logs à afficher dans le composant de log global, au dessus + * du sélecteur d'itération : messages globaux et / ou résumé des messages + * spécifiques à chaque ResultElement + */ + private get globalLog(): cLog { + const l = new cLog(); + if (this._pabResults && this.pabResults.variatedParameters.length > 0) { + this.mergeGlobalLog(this._pabResults.result, l); + // un problème avec la PAB en général / les cloisons, à une étape quelconque ? + if ( + (this.pabResults.hasLog) + && l.messages.length === 0 // existing global messages make generic message below useless + ) { + const logStats = this.logStats(); + const m = new Message(MessageCode.WARNING_PROBLEMS_ENCOUNTERED); + m.extraVar.info = "" + logStats.info; // to avoid displaying fixed number of digits + m.extraVar.warning = "" + logStats.warning; + m.extraVar.error = "" + logStats.error; + l.add(m); + // l.add(new Message(MessageCode.WARNING_PROBLEMS_ENCOUNTERED)); + } + } // sinon pas de log global (aucun paramètre ne varie) + return l; } - private get mergedGlobalLogs(): cLog { - const res = new cLog(); + /** + * Retourne les logs à afficher dans le composant de log global, au dessus + * du sélecteur d'itération : messages globaux et / ou résumé des messages + * spécifiques à chaque ResultElement + */ + private get iterationLog(): cLog { + const l = new cLog(); if (this._pabResults) { - this.mergeLog(this._pabResults.result, res); - // log des cloisons - for (const c of this._pabResults.cloisonsResults) { - this.mergeLog(c, res); + if (this.pabResults.variatedParameters.length > 0) { + // A. si un paramètre varie + const vi = this._pabResults.variableIndex; + // log de la PAB pour l'itération en cours + if ( + this._pabResults.result + && this._pabResults.result.hasResultElements + && this._pabResults.result.resultElements[vi] + && this._pabResults.result.resultElements[vi].hasLog + ) { + l.addLog(this._pabResults.result.resultElements[vi].log); + } + // logs des enfants pour l'itération en cours + for (const cr of this._pabResults.cloisonsResults) { + if (cr && cr.hasResultElements && cr.resultElements[vi].hasLog) { + l.addLog(cr.resultElements[vi].log); + } + } + if (this._pabResults.cloisonAvalResults && this._pabResults.cloisonAvalResults.resultElements[vi].hasLog) { + l.addLog(this._pabResults.cloisonAvalResults.resultElements[vi].log); + } + } else { + // B. si aucun paramètre ne varie + this.mergeGlobalLog(this._pabResults.result, l); // faut bien mettre le log global quelque part + // logs des enfants + for (const cr of this._pabResults.cloisonsResults) { + if (cr && cr.hasResultElements && cr.resultElement.hasLog) { + l.addLog(cr.resultElement.log); + } + } + if (this._pabResults.cloisonAvalResults && this._pabResults.cloisonAvalResults.resultElement.hasLog) { + l.addLog(this._pabResults.cloisonAvalResults.resultElement.log); + } } - // log de la cloison aval - this.mergeLog(this._pabResults.cloisonAvalResults, res); } - return res; + return l; } /** @@ -106,28 +269,45 @@ export class PabResultsComponent implements DoCheck { */ private updateResults() { let pabUpdated: boolean; - let graphUpdated: boolean; + let resultsGraphUpdated: boolean; + let profileGraphUpdated: boolean; + let selectorUpdated: boolean; + + // results or not, there might be a log + const logUpdated = (this.iterationLogComponent !== undefined || this.generalLogComponent !== undefined); // gne ? + if (logUpdated) { + // order of logs is important ! + this.iterationLogComponent.log = this.iterationLog; + this.generalLogComponent.log = this.globalLog; + } + if (this.hasResults) { pabUpdated = this.pabResultsTableComponent !== undefined; if (pabUpdated) { this.pabResultsTableComponent.results = this._pabResults; } - graphUpdated = this.resultsGraphComponent !== undefined; - if (graphUpdated) { + selectorUpdated = this.pabVariableResultsSelectorComponent !== undefined; + if (selectorUpdated) { + this.pabVariableResultsSelectorComponent.results = this._pabResults; + } + resultsGraphUpdated = this.resultsGraphComponent !== undefined; + if (resultsGraphUpdated) { this.resultsGraphComponent.results = this.plottableResults; this.resultsGraphComponent.updateView(); } + profileGraphUpdated = this.profileGraphComponent !== undefined; + if (profileGraphUpdated) { + this.profileGraphComponent.results = this._pabResults; + this.profileGraphComponent.updateView(); + } } else { pabUpdated = true; - graphUpdated = true; - } - - const logUpdated = this.logComponent !== undefined; - if (logUpdated) { - this.logComponent.log = this.mergedGlobalLogs; + resultsGraphUpdated = true; + profileGraphUpdated = true; + selectorUpdated = true; } - return pabUpdated && logUpdated && graphUpdated; + return pabUpdated && logUpdated && resultsGraphUpdated && profileGraphUpdated && selectorUpdated; } public get pabResults() { @@ -147,9 +327,29 @@ export class PabResultsComponent implements DoCheck { return this._pabResults && this._pabResults.hasResults; } + public get hasDisplayableResults(): boolean { + let ret = this._pabResults && this._pabResults.hasResults; + if ( + this._pabResults + && this._pabResults.variatedParameters + && this._pabResults.variatedParameters.length > 0 + ) { + ret = ret + && this._pabResults.variableIndex !== undefined + && this._pabResults.result.resultElements[this._pabResults.variableIndex] !== undefined + && this._pabResults.result.resultElements[this._pabResults.variableIndex].ok; + } + return ret; + } + + public get uitextGeneralLogTitle(): string { + return this.i18nService.localizeText("INFO_TITREJOURNAL_GLOBAL"); + } + /** builds a set of PlottableData from PabResults, to feed the graph */ protected get plottableResults(): PlottableData { this._plottableResults.setPabResults(this.pabResults); return this._plottableResults; } + } diff --git a/src/app/components/pab-results/pab-variable-results-selector.component.html b/src/app/components/pab-results/pab-variable-results-selector.component.html new file mode 100644 index 0000000000000000000000000000000000000000..a6cc6df2405b9cec9f568f12a04806d39e4ca02d --- /dev/null +++ b/src/app/components/pab-results/pab-variable-results-selector.component.html @@ -0,0 +1,11 @@ +<div class="pab-variable-results-selector" *ngIf="hasVariableResults" fxLayout="row wrap" fxLayoutAlign="center center"> + <div fxFlex="1 1 100%"> + <mat-form-field> + <mat-select id="pab-variating-element" [placeholder]="label" [(value)]="selectedValue"> + <mat-option *ngFor="let e of entries" [value]="e"> + {{ entryLabel(e) }} + </mat-option> + </mat-select> + </mat-form-field> + </div> +</div> diff --git a/src/app/components/pab-results/pab-variable-results-selector.component.scss b/src/app/components/pab-results/pab-variable-results-selector.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..d3c5d0c6d7819fcde1dabef15aa146abd2fe58b7 --- /dev/null +++ b/src/app/components/pab-results/pab-variable-results-selector.component.scss @@ -0,0 +1,10 @@ +:host { + margin-top: 2em; +} + +mat-form-field { + width: 100%; + max-width: 400px; + margin-top: 1em; + margin-bottom: -1em; +} diff --git a/src/app/components/pab-results/pab-variable-results-selector.component.ts b/src/app/components/pab-results/pab-variable-results-selector.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..4986bfbf8502342932af0831c8104985d575f3a9 --- /dev/null +++ b/src/app/components/pab-results/pab-variable-results-selector.component.ts @@ -0,0 +1,104 @@ +import { Component, Output, EventEmitter } from "@angular/core"; + +import { PabResults } from "../../results/pab-results"; +import { I18nService } from "../../services/internationalisation/internationalisation.service"; +import { ApplicationSetupService } from "../../services/app-setup/app-setup.service"; + +@Component({ + selector: "pab-variable-results-selector", + templateUrl: "./pab-variable-results-selector.component.html", + styleUrls: [ + "./pab-variable-results-selector.component.scss" + ] +}) +export class PabVariableResultsSelectorComponent { + + /** résultats non mis en forme */ + private _pabResults: PabResults; + + private _selectedValue: number; + + /** size of the longest variable value */ + private size = 0; + + /** inferred extended values list for each variating parameter */ + private varValues = []; + + @Output() + protected indexChange = new EventEmitter(); + + constructor( + protected intlService: I18nService, + protected appSetupService: ApplicationSetupService + ) { + this._selectedValue = 0; + } + + public set results(r: PabResults) { + this._pabResults = r; + + if (this._pabResults) { + // pre-extract variable parameters values + this.varValues = []; + const nDigits = this.appSetupService.displayDigits; + // find longest list + this.size = 0; + for (let i = 0; i < this._pabResults.variatedParameters.length; i++) { + const vs = this._pabResults.variatedParameters[i].valuesIterator.count(); + if (vs > this.size) { + this.size = vs; + } + } + // get extended values lists for each variable parameter + for (const v of this._pabResults.variatedParameters) { + const vv = []; + const iter = v.getExtendedValuesIterator(this.size); + while (iter.hasNext) { + const nv = iter.next(); + vv.push(nv.value.toFixed(nDigits)); + } + this.varValues.push(vv); + } + } + } + + public get hasVariableResults(): boolean { + return ( + this._pabResults + && this._pabResults.hasResults + && this._pabResults.variatedParameters.length > 0 + ); + } + + public get entries(): number[] { + const ret: number[] = []; + for (let i = 0; i < this.size; i++) { + ret.push(i); + } + return ret; + } + + protected entryLabel(index: number): string { + let i = 0; + return this.varValues.map((vv) => { + const vp = this._pabResults.variatedParameters[i]; + i++; + let value = "0"; + value = vv[index]; + return `${vp.symbol} = ${value}`; + }).join(", "); + } + + public get selectedValue(): number { + return this._selectedValue; + } + + public set selectedValue(v: number) { + this._pabResults.variableIndex = v; + this.indexChange.emit(); + } + + public get label() { + return this.intlService.localizeText("INFO_PARAMFIELD_BOUNDARY_CONDITIONS"); + } +} diff --git a/src/app/components/pab-table/pab-table.component.html b/src/app/components/pab-table/pab-table.component.html new file mode 100644 index 0000000000000000000000000000000000000000..93864ec7b69efc4a816a047dcdc6d7d11800df14 --- /dev/null +++ b/src/app/components/pab-table/pab-table.component.html @@ -0,0 +1,98 @@ +<mat-card-header class="bg-accent-light"> + <mat-card-title> + {{ title }} + </mat-card-title> +</mat-card-header> + +<mat-card-content> + + <div id="pab-table-toolbar"> + <button type="button" id="edit-pab-table" mat-raised-button color="accent" + [disabled]="! enableEditPabButton" (click)="showEditPab()"> + {{ uitextEditPabTable }} + </button> + + <div class="hyd-window-btns"> + <span class="related-entity-title"> + {{ relatedEntityTitle }} + </span> + <mat-select id="add-many-children" [(value)]="childrenToAdd"> + <mat-option *ngFor="let i of addManyOptionsList" [value]="i"> + {{ i }} + </mat-option> + </mat-select> + <button type="button" mat-icon-button color="primary" [disabled]="! enableAddButton" (click)="onAddClick()"> + <mat-icon>add_box</mat-icon> + </button> + <button type="button" mat-icon-button color="primary" [disabled]="! enableCopyButton" (click)="onCopyClick()"> + <mat-icon>content_copy</mat-icon> + </button> + | + <button type="button" mat-icon-button color="primary" [disabled]="! enableRemoveButton" (click)="onRemoveClick()"> + <mat-icon>delete</mat-icon> + </button> + <button type="button" mat-icon-button color="primary" [disabled]="! enableUpButton" (click)="onMoveUpClick()"> + <mat-icon *ngIf="! selectionIsOneDevice">arrow_upward</mat-icon> + <mat-icon *ngIf="selectionIsOneDevice">arrow_back</mat-icon> + </button> + <button type="button" mat-icon-button color="primary" [disabled]="! enableDownButton" (click)="onMoveDownClick()"> + <mat-icon *ngIf="! selectionIsOneDevice">arrow_downward</mat-icon> + <mat-icon *ngIf="selectionIsOneDevice">arrow_forward</mat-icon> + </button> + </div> + </div> + + <p-table [columns]="cols" [value]="rows" class="pab-data-table"> + + <ng-template pTemplate="header" let-columns> + <tr> + <th *ngFor="let h of headers" + (click)="toggleSelection(h, $event)" + (mousedown)="preventCtrlClickBorder($event)" + [attr.rowspan]="h.rowspan ? h.rowspan : null" [attr.colspan]="h.colspan ? h.colspan : null" + [class.selectable-cell]="isSelectable(h)" [class.selected-cell]="isSelected(h)"> + + {{ h.title }} + </th> + </tr> + <tr> + <th *ngFor="let col of columns" + (click)="toggleSelection(col, $event)" + (mousedown)="preventCtrlClickBorder($event)" + [class.selectable-cell]="isSelectable(col)" [class.selected-cell]="isSelected(col)"> + + {{ col.title }} + </th> + </tr> + </ng-template> + + <ng-template pTemplate="body" let-row> + <tr [class.selected-row]="isSelected(row)"> + <td *ngFor="let cell of row.cells" + (click)="toggleSelection(cell, $event)" + (mousedown)="preventCtrlClickBorder($event)" + [ngClass]="cell.class" + [class.editable-cell]="hasModel(cell)" [class.readonly-cell]="! hasModel(cell)" + [class.selectable-cell]="isSelectable(cell)" [class.selected-cell]="isSelected(cell) && ! isSelected(row)" + [class.invalid-cell]="isInvalid(cell)" [class.select]="isSelect(cell)" + [attr.rowspan]="rowSpan(cell)" [attr.colspan]="colSpan(cell)" + [title]="cellTitle(cell)"> + + <input matInput *ngIf="isNumberInput(cell)" step="0.00000000000001" type="number" + [(ngModel)]="cell.model.singleValue" (input)="inputValueChanged($event, cell)"> + + <mat-select #selectWidget *ngIf="isSelect(cell)" [value]="cell.modelValue" + (selectionChange)="loiDebitSelected($event, cell)"> + + <mat-option *ngFor="let opt of cell.options" [value]="opt.value"> + {{ opt.label }} + </mat-option> + </mat-select> + + <span *ngIf="! hasModel(cell)">{{ cellValue(cell) }}</span> + </td> + </tr> + </ng-template> + </p-table> + +</mat-card-content> diff --git a/src/app/components/pab-table/pab-table.component.scss b/src/app/components/pab-table/pab-table.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..3f27ab369593e68ad608eac5d927aa51e326d40b --- /dev/null +++ b/src/app/components/pab-table/pab-table.component.scss @@ -0,0 +1,150 @@ +:host { + display: block; + // reduce margins to avoid inner field-sets being too narrow on 360px display + margin-left: -8px; + margin-right: -8px; +} + +mat-card-header { + margin-left: -8px; + margin-right: -8px; + padding-left: 16px; + padding-top: 8px; + color: white; + + // Pourquoi n'est-ce pas hérité de calculator.component.scss ? + // À cause de la surcharge de mat-card-header ci-dessus ? + mat-card-title { + font-size: 16px !important; + margin-bottom: 8px; + } +} + +mat-card-content { + margin-top: 1em; +} + +// cells colors + +@import "../../../theme.scss"; + +.editable-cell-bg { + @extend .bg-accent-extralight; +} +.selected-row-bg { + // @extend .bg-warn-extralight; + @extend .bg-accent-extralight; +} +.selected-cell-bg { + // @extend .bg-warn-extralight; + @extend .bg-accent-extralight; +} +.selected-editable-cell-bg { + @extend .bg-accent-verylight; +} +.invalid-cell-bg { + @extend .bg-error; +} + +#pab-table-toolbar { + #edit-pab-table { + float: left; + } + .related-entity-title { + vertical-align: middle; + font-weight: bold; + } + .hyd-window-btns { + text-align: right; + + #add-many-children { + width: 3em; + vertical-align: middle; + } + + button.mat-icon-button { + width: 32px; + } + } +} + +.pab-data-table { + ::ng-deep .ui-table.ui-widget { + table { + thead.ui-table-thead { + > tr { + > th { + font-size: .8em; + padding: 6px 8px; + &.selectable-cell { + cursor: pointer; + } + &.selected-cell { + @extend .selected-cell-bg; + } + } + } + } + tbody.ui-table-tbody { + > tr { + &.selected-row { + @extend .selected-row-bg; + > td { + &.editable-cell { + @extend .selected-editable-cell-bg; + } + } + } + > td { + &:nth-child(1) { + width: 50px; + } + font-size: .8em; + &.readonly-cell { + cursor: default; + padding: 4px 8px; + } + &.editable-cell { + @extend .editable-cell-bg; + padding: 0; + > input.mat-input-element { + padding: 4px 8px; + } + &:focus-within { + @extend .bg-warn-extralight; + } + } + &.selectable-cell { + cursor: pointer; + } + &.selected-cell { + @extend .selected-cell-bg; + &.editable-cell { + @extend .selected-editable-cell-bg; + } + } + &.invalid-cell { + @extend .invalid-cell-bg; + } + &.basin_number { + text-align: center; + font-weight: bold; + } + > mat-select.mat-select { + padding: 0 8px; + } + + > input[type="number"] { + -moz-appearance: textfield; + } + input[type=number]::-webkit-outer-spin-button, + input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + } + } + } + } + } +} diff --git a/src/app/components/pab-table/pab-table.component.ts b/src/app/components/pab-table/pab-table.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf4561da10fcce41d95e7ab5b6cfad5a9c1e0a81 --- /dev/null +++ b/src/app/components/pab-table/pab-table.component.ts @@ -0,0 +1,1201 @@ +import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit } from "@angular/core"; + +import { MatDialog } from "@angular/material"; + +import { + Pab, + Session, + Props, + CalculatorType, + Cloisons, + Nub, + Structure, + ParallelStructure, + ParamDefinition + } from "jalhyd"; + + import { sprintf } from "sprintf-js"; + +import { I18nService } from "../../services/internationalisation/internationalisation.service"; +import { FormulaireService } from "../../services/formulaire/formulaire.service"; +import { ApplicationSetupService } from "../../services/app-setup/app-setup.service"; +import { NotificationsService } from "../../services/notifications/notifications.service"; +import { PabTable } from "../../formulaire/pab-table"; +import { DialogEditPabComponent } from "../dialog-edit-pab/dialog-edit-pab.component"; + +/** + * 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 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: Nub[]; + + /** used for shift+click implementation */ + private latestClickedCell: any; + + public constructor( + private i18nService: I18nService, + private formService: FormulaireService, + private editPabDialog: MatDialog, + private appSetupService: ApplicationSetupService, + private notifService: NotificationsService + ) { + this.selectedItems = []; + } + + public get title(): string { + return this.i18nService.localizeText("INFO_PAB_TABLE"); + } + + /** Global Pab validity */ + 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; + } + } + } + + /** "title" tooltip to display in a cell */ + public cellTitle(cell: any) { + if (cell !== undefined && cell.title !== undefined) { + return cell.title; + } else { + return ""; + } + } + + 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; + } + + /** + * Checks that input value is a valid number, according to input[type="number"] algorithm, + * and stores it in cell.uiValidity, so that the <td> element can access it and get angry + * if input is invalid + */ + public inputValueChanged($event, cell) { + // console.log("input value changed", $event.target.validity.valid, $event.target.validity); + if ($event && $event.target && $event.target.validity) { + cell.uiValidity = $event.target.validity.valid; + } + this.updateValidity(); + // send input change event (used to reset form results) + this.inputChange.emit(); + } + + /** + * Returns true if current cell is bound to a model that says its input value is + * no valid, or if characters typed in the input field are not a valid number + * (read from cell.uiValidity, see inputValueChanged() above) + */ + public isInvalid(cell: any): boolean { + let valid = true; + if (this.hasModel(cell) && cell.model instanceof ParamDefinition) { + valid = valid && cell.model.isValid; + } + if (cell.uiValidity !== undefined) { + valid = valid && cell.uiValidity; + } + return ! valid; + } + + /** 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 + * - 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 + // interpolate from this.latestClickedCell to this one + if (! Array.isArray(cell.selectable)) { // multiselectable cells are not managed + const wallsUIDs = this.getSortedWallsUIDs(); + let posOld: number; + let posNew: number; + // find positions depending on types, and only if types are equal + if (cell.selectable instanceof ParallelStructure) { + if (this.latestClickedCell.selectable instanceof ParallelStructure) { + // interpolate walls + posOld = wallsUIDs.indexOf(this.latestClickedCell.selectable.uid); + posNew = wallsUIDs.indexOf(cell.selectable.uid); + if (posOld !== posNew) { + if (posOld > posNew) { + // invert order + [ posOld, posNew ] = [ posNew, posOld ]; + } + // go + for (let i = posOld; i <= posNew; i++) { + if (i < this.model.children.length) { + // push regular wall + this.selectedItems.push(this.model.children[i]); + } else { + // push downwall + this.selectedItems.push(this.model.downWall); + } + this.latestClickedCell = cell; + } + } + } + } else if (cell.selectable instanceof Structure) { + if (this.latestClickedCell.selectable instanceof Structure) { + // accept interpolation only if both devices are on the same column + const columnOld = this.latestClickedCell.selectable.findPositionInParent(); + const columnNew = cell.selectable.findPositionInParent(); + if (columnOld === columnNew) { + // interpolate devices + posOld = wallsUIDs.indexOf(this.latestClickedCell.selectable.parent.uid); + posNew = wallsUIDs.indexOf(cell.selectable.parent.uid); + if (posOld !== posNew) { + if (posOld > posNew) { + // invert order + [ posOld, posNew ] = [ posNew, posOld ]; + } + // go + for (let i = posOld; i <= posNew; i++) { + if (i < this.model.children.length) { + // push regular wall + this.selectedItems.push(this.model.children[i].structures[columnOld]); + } else { + // push downwall + this.selectedItems.push(this.model.downWall.structures[columnOld]); + } + } + this.latestClickedCell = cell; + } + } + } + } + // clean selected items list (deduplicate, sort) + this.selectedItems = this.selectedItems.filter( + (item, index) => this.selectedItems.indexOf(item) === index // deduplicate + ); + this.sortSelectedItems(); + } + + } 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); + } + } 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); + } + this.sortSelectedItems(); + } + this.latestClickedCell = cell; + + } 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); + + this.clearSelection(); + $event.preventDefault(); + $event.stopPropagation(); + return false; + } + } + + // quick getter for 1st selected item + public get selectedItem() { + if (this.selectedItems.length === 0) { + throw new Error("get selectedItem() : no item selected"); + } + return this.selectedItems[0]; + } + + // 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); + } + + // at this time @Input data is supposed to be already populated + public ngOnInit() { + this.model = this.pabTable.pab; + this.refresh(); + } + + /** Unselects all selected text (side-effect of shift+clicking) */ + private clearSelection() { + if (window.getSelection) { + const sel = window.getSelection(); + sel.removeAllRanges(); + } + } + + // extract PAB walls order + private getSortedWallsUIDs(): string[] { + const wallsUIDs: string[] = []; + for (const c of this.pabTable.pab.children) { + wallsUIDs.push(c.uid); + } + wallsUIDs.push(this.pabTable.pab.downWall.uid); + return wallsUIDs; + } + + /** + * Ensures that this.selectedItems elements are ordered according to + * the walls order in the PAB (important for interpolation) + */ + private sortSelectedItems() { + const wallsUIDs = this.getSortedWallsUIDs(); + // are items walls or devices ? + if (this.onlyWallsAreSelected(false)) { + // 1. walls : order by uid, according to model + this.selectedItems.sort((a, b) => { + const posA = wallsUIDs.indexOf(a.uid); + const posB = wallsUIDs.indexOf(b.uid); + return posA - posB; + }); + } else { + // 2. devices : order by parent (wall) uid, according to model + this.selectedItems.sort((a, b) => { + const posA = wallsUIDs.indexOf(a.parent.uid); + const posB = wallsUIDs.indexOf(b.parent.uid); + return posA - posB; + }); + } + return this.selectedItems; + } + + /** + * Builds the editable data grid from the Pab model + */ + private refresh() { + 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: this.i18nService.localizeText("INFO_PAB_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: sprintf(this.i18nService.localizeText("INFO_PAB_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: this.i18nService.localizeText("INFO_PAB_NUM_BASSIN"), + selectable: bs + }); + this.cols.push({ + title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "LB"), + selectable: bs + }); + this.cols.push({ + title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "BB"), + selectable: bs + }); + this.cols.push({ + title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "QA"), + selectable: bs + }); + this.cols.push({ + title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "ZRMB"), + selectable: bs + }); + this.cols.push({ + title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "ZRAM"), + 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++) { + this.cols.push({ + title: this.i18nService.localizeText("INFO_PAB_HEADER_TYPE"), + selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]), + selectableColumn: i + }); + this.cols.push({ + title: this.i18nService.localizeText("INFO_PAB_HEADER_PARAMETERS"), + selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]), + selectableColumn: i + }); + this.cols.push({ + title: this.i18nService.localizeText("INFO_PAB_HEADER_VALUES"), + 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) { + // admissible LoiDebit + const loisCloisons = cloison.getLoisAdmissiblesArray().map(l => { // @TODO move up ? (same for all cloisons) + return { + label: this.i18nService.localizeLoiDebit(l), + value: l + }; + }); + // as much rows as the greatest number of parameters among its devices + const maxNbParams = this.findMaxNumberOfDeviceParameters(cloison); + 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); + const nvParamTitle = nvParam ? this.formService.expandVariableNameAndUnit(CalculatorType.Pab, nvParam.symbol) : ""; + // cell 1 : device type + if (i === 0) { // 1st row + deviceParamRow.cells.push({ + model: ouvrage, + modelValue: ouvrage.properties.getPropValue("loiDebit"), + options: loisCloisons, + 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, + title: nvParamTitle, + selectable: ouvrage + }); + } else { + deviceParamRow.cells.push({ + selectable: ouvrage + }); + } + // cell 3 : param value + if (nvParam) { + deviceParamRow.cells.push({ + model: nvParam, + title: nvParamTitle, + 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, + title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "LB") + }, + { + model: cloison.prms.BB, + title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "BB") + }, + { + model: cloison.prms.QA, + title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "QA") + }, + { + model: cloison.prms.ZRMB, + title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "ZRMB") + }, + { + model: cloison.prms.ZRAM, + title: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, "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 + // admissible LoiDebit + const loisAval = this.model.downWall.getLoisAdmissiblesArray().map(l => { + return { + label: this.i18nService.localizeLoiDebit(l), + value: l + }; + }); + // 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 + 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); + const nvParamTitle = nvParam ? this.formService.expandVariableNameAndUnit(CalculatorType.Pab, nvParam.symbol) : ""; + // cell 1 : device type + if (i === 0) { // 1st row + deviceParamRowDW.cells.push({ + model: ouvrage, + modelValue: ouvrage.properties.getPropValue("loiDebit"), + options: loisAval + }); + } + // 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, + title: nvParamTitle, + selectable: ouvrage + }); + } else { + deviceParamRowDW.cells.push({ + selectable: ouvrage + }); + } + // cell 3 : param value + if (nvParam) { + deviceParamRowDW.cells.push({ + model: nvParam, + title: nvParamTitle, + 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 + }); + } + } + // done ! + this.rows.push(deviceParamRowDW); + } + + this.updateValidity(); + } + + 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.selectedItem 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(excludeDownwall: boolean = true) { + let ok = false; + if (this.selectedItems.length > 0) { + ok = true; + for (const s of this.selectedItems) { + if (excludeDownwall) { + ok = ok && (s instanceof Cloisons); + } else { + ok = ok && (s instanceof ParallelStructure); + } + } + } + return ok; + } + + public get relatedEntityTitle() { + 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 + && this.selectedItem.parent // exclude downwall + ); + } + + public get enableCopyButton() { + return ( + this.selectedItems.length === 1 + && this.selectedItem.parent // exclude downwall + ); + } + + public get enableUpButton() { + return ( + this.selectedItems.length === 1 + && this.selectedItem.parent + && this.selectedItem.findPositionInParent() !== 0 + ); + } + + public get enableDownButton() { + return ( + this.selectedItems.length === 1 + && this.selectedItem.parent + && this.selectedItem.findPositionInParent() < (this.selectedItem.parent.getChildren().length - 1) + ); + } + + public get enableRemoveButton() { + return ( + this.selectedItems.length === 1 + && this.selectedItem.parent + && this.selectedItem.parent.getChildren().length > 1 + ); + } + + /** + * returns true if at least one object is selected + */ + public get enableEditPabButton() { + return ( + this.selectedItems.length > 0 + && ( + this.onlyDevicesAreSelected() + || this.onlyWallsAreSelected(false) + ) + ); + } + + public onAddClick() { + // add default item + for (let i = 0; i < this.childrenToAdd; i++) { + if (this.selectedItem instanceof Structure) { + // add new default device for wall parent + const newDevice = Session.getInstance().createNub( + new Props({ + calcType: CalculatorType.Structure, + loiDebit: (this.selectedItem.parent as ParallelStructure).getDefaultLoiDebit() + }) + ); + this.selectedItem.parent.addChild(newDevice, this.selectedItem.findPositionInParent()); + + } else { + // add new default wall for PAB parent + const newWall = Session.getInstance().createNub( + new Props({ + calcType: CalculatorType.Cloisons + }) + ); + // add new default device for new wall + const newDevice = Session.getInstance().createNub( + new Props({ + calcType: CalculatorType.Structure, + loiDebit: (newWall as ParallelStructure).getDefaultLoiDebit() + }) + ); + newWall.addChild(newDevice); + this.model.addChild(newWall, this.selectedItem.findPositionInParent()); + } + } + this.refresh(); + + // notify + const pos = this.selectedItem.findPositionInParent() + 1; + let msg: string; + if (this.childrenToAdd === 1) { + if (this.selectedItem instanceof Structure) { + msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_ADDED"), pos); + } else { + msg = sprintf(this.i18nService.localizeText("INFO_WALL_ADDED"), pos); + } + } else { + if (this.selectedItem instanceof Structure) { + msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_ADDED_N_TIMES"), this.childrenToAdd); + } else { + msg = sprintf(this.i18nService.localizeText("INFO_WALL_ADDED_N_TIMES"), this.childrenToAdd); + } + } + this.notifService.notify(msg); + + this.childrenToAdd = 1; // reinit to avoid confusion + } + + public onCopyClick() { + // cloned selected item + for (let i = 0; i < this.childrenToAdd; i++) { + const newChild = Session.getInstance().createNub( + this.selectedItem.properties.clone(), + this.selectedItem.parent + ); + // copy parameter values + for (const p of this.selectedItem.prms) { + newChild.getParameter(p.symbol).singleValue = p.singleValue; + } + // copy children + if (this.selectedItem instanceof ParallelStructure) { + for (const c of this.selectedItem.getChildren()) { + const newGrandChild = Session.getInstance().createNub( + c.properties.clone(), + newChild + ); + // copy children parameters values + for (const p of c.prms) { + newGrandChild.getParameter(p.symbol).singleValue = p.singleValue; + } + // add to parent + newChild.addChild( + newGrandChild, + c.findPositionInParent() + ); + } + } + // add to parent + this.selectedItem.parent.addChild( + newChild, + this.selectedItem.findPositionInParent() + ); + } + this.refresh(); + + // notify + const pos = this.selectedItem.findPositionInParent() + 1; + let msg: string; + if (this.childrenToAdd === 1) { + if (this.selectedItem instanceof Structure) { + msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_COPIED"), pos); + } else { + msg = sprintf(this.i18nService.localizeText("INFO_WALL_COPIED"), pos); + } + } else { + if (this.selectedItem instanceof Structure) { + msg = sprintf(this.i18nService.localizeText("INFO_DEVICE_COPIED_N_TIMES"), pos, this.childrenToAdd); + } else { + msg = sprintf(this.i18nService.localizeText("INFO_WALL_COPIED_N_TIMES"), pos, this.childrenToAdd); + } + } + this.notifService.notify(msg); + + this.childrenToAdd = 1; // reinit to avoid confusion + } + + public onMoveUpClick() { + const pos = this.selectedItem.findPositionInParent() + 1; + this.selectedItem.parent.moveChildUp(this.selectedItem); + if (this.selectedItem instanceof Structure) { + this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_DEVICE_MOVED"), pos)); + } else { + this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_WALL_MOVED"), pos)); + } + this.refresh(); + } + + public onMoveDownClick() { + const pos = this.selectedItem.findPositionInParent() + 1; + this.selectedItem.parent.moveChildDown(this.selectedItem); + if (this.selectedItem instanceof Structure) { + this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_DEVICE_MOVED"), pos)); + } else { + this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_WALL_MOVED"), pos)); + } + this.refresh(); + } + + public onRemoveClick() { + const pos = this.selectedItem.findPositionInParent() + 1; + this.selectedItem.parent.deleteChild(this.selectedItem.findPositionInParent()); + if (this.selectedItem instanceof Structure) { + this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_DEVICE_REMOVED"), pos)); + } else { + this.notifService.notify(sprintf(this.i18nService.localizeText("INFO_WALL_REMOVED"), pos)); + } + this.selectedItems = []; + this.refresh(); + } + + /** Replace device Nub when LoiDebit is changed */ + public loiDebitSelected($event: any, cell: any) { + const device = cell.model as Nub; + // create new child device + const newDevice = Session.getInstance().createNub( + new Props({ + calcType: CalculatorType.Structure, + loiDebit: $event.value + }) + ); + // replace the current one + device.parent.replaceChildInplace(device, newDevice); + this.refresh(); + } + + // show modal dialog for values edition + public showEditPab() { + if (this.selectedItems.length > 0) { + + // list variables eligible to modification + const availableVariables: { label: string, value: string, occurrences: number, first: number, last: number }[] = []; + for (const c of this.selectedItems) { + for (const p of c.parameterIterator) { // deep one + if ( + p.visible && + ! availableVariables.map(av => av.value).includes(p.symbol) + ) { + availableVariables.push({ + label: this.formService.expandVariableNameAndUnit(CalculatorType.Pab, p.symbol), + value: p.symbol, + occurrences: 0, + first: undefined, + last: undefined + }); + } + } + } + // find their min/max values (2nd pass) + for (const av of availableVariables) { + for (const c of this.selectedItems) { + for (const p of c.parameterIterator) { + if (p.visible && p.symbol === av.value) { + av.occurrences ++; + if (av.first === undefined) { + av.first = p.singleValue; + } + av.last = p.singleValue; + } + } + } + } + + // sum up selected items + const walls: ParallelStructure[] = []; + const wallsDevices: Structure[] = []; + const devices: Structure[] = []; + let vertical = true; // @TODO vertical AND consecutive ! + let firstDevicePosition: number; + // 1st pass + for (const s of this.selectedItems) { + if (s instanceof ParallelStructure) { + walls.push(s); + for (const c of s.structures) { + if (firstDevicePosition === undefined) { + firstDevicePosition = c.findPositionInParent(); + } else { + vertical = (vertical && (c.findPositionInParent() === firstDevicePosition)); + } + wallsDevices.push(c); + } + } + } + // 2nd pass + for (const c of this.selectedItems) { + if (c instanceof Structure) { + if (! wallsDevices.includes(c)) { + if (firstDevicePosition === undefined) { + firstDevicePosition = c.findPositionInParent(); + } else { + vertical = (vertical && (c.findPositionInParent() === firstDevicePosition)); + } + devices.push(c); + } + } + } + + // open dialog + const dialogRef = this.editPabDialog.open( + DialogEditPabComponent, + { + data: { + availableVariables: availableVariables, + selectedItemsAbstract: { + walls: walls.length, + wallsDevices: wallsDevices.length, + devices: devices.length + }, + vertical: vertical // used to enable interpolation + }, + disableClose: true + } + ); + + // apply modifications + dialogRef.afterClosed().subscribe(result => { + if (result) { + /* console.log("Apply values in parent !!", result.action, result.variable, result.value, + result.delta, result.variableDetails); */ + switch (result.action) { + case "set-value": + for (const s of this.selectedItems) { + for (const p of s.parameterIterator) { // deep + if (p.symbol === result.variable) { + p.singleValue = result.value; + } + } + } + break; + + case "delta": + for (const s of this.selectedItems) { + for (const p of s.parameterIterator) { // deep + if (p.symbol === result.variable) { + p.singleValue += result.delta; + } + } + } + break; + + case "interpolate": + if (result.variableDetails.occurrences > 1) { + const nDigits = this.appSetupService.displayDigits; + // build values list + const interpolatedValues: number[] = []; + const step = ( + (result.variableDetails.last - result.variableDetails.first) + / (result.variableDetails.occurrences - 1) + ); + interpolatedValues.push(result.variableDetails.first); + let currentValue: number = result.variableDetails.first; + for (let i = 0; i < result.variableDetails.occurrences - 2; i++) { + currentValue += step; + interpolatedValues.push(Number(currentValue.toFixed(nDigits))); + } + interpolatedValues.push(result.variableDetails.last); + // apply + let idx = 0; + for (const s of this.selectedItems) { + for (const p of s.parameterIterator) { // deep + if (p.symbol === result.variable) { + p.singleValue = interpolatedValues[idx]; + idx ++; + } + } + } + } else { + throw new Error( + `showEditPab() : cannot interpolate, too few occurrences (${result.variableDetails.occurrences})` + ); + } + break; + } + } + }); + } + } + + public ngAfterViewInit() { + this.updateValidity(); + } + + /** + * 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); + } + } + this.validChange.emit(); + } + + /** + * 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"; + } +} diff --git a/src/app/components/param-values/param-values.component.ts b/src/app/components/param-values/param-values.component.ts index 41cefb196e912a830554167f5c8207b8478c8ce2..3e1e933d51f2b66edd2222424c4b9a5a8adade7c 100644 --- a/src/app/components/param-values/param-values.component.ts +++ b/src/app/components/param-values/param-values.component.ts @@ -57,7 +57,7 @@ export class ParamValuesComponent implements AfterViewInit, Observer { this.editValuesDialog.open( DialogEditParamValuesComponent, { - disableClose: true, + disableClose: false, data: { param: this.param }, diff --git a/src/app/components/results-graph/results-graph.component.html b/src/app/components/results-graph/results-graph.component.html index c30eebd7b950b4b1df94e68a27a8b31ed4c7ae61..1c15bdcc25844e4fcb2681d0ed6559a9757eb38d 100644 --- a/src/app/components/results-graph/results-graph.component.html +++ b/src/app/components/results-graph/results-graph.component.html @@ -1,13 +1,16 @@ <div class="graph-results-container" #graphResults fxLayout="row wrap" fxLayoutAlign="center center"> <div fxFlex="1 1 100%"> <div class="graph-results-buttons"> - <button mat-icon-button (click)="exportAsImage(graphResults)"> + <button mat-icon-button (click)="resetZoom()" [disabled]="! zoomWasChanged" [title]="uitextResetZoomTitle"> + <mat-icon color="primary">replay</mat-icon> + </button> + <button mat-icon-button (click)="exportAsImage(graphResults)" [title]="uitextExportImageTitle"> <mat-icon color="primary">image</mat-icon> </button> - <button mat-icon-button *ngIf="! isFullscreen" (click)="setFullscreen(graphResults)"> + <button mat-icon-button *ngIf="! isFullscreen" (click)="setFullscreen(graphResults)" [title]="uitextEnterFSTitle"> <mat-icon color="primary" class="scaled12">fullscreen</mat-icon> </button> - <button mat-icon-button *ngIf="isFullscreen" (click)="exitFullscreen()"> + <button mat-icon-button *ngIf="isFullscreen" (click)="exitFullscreen()" [title]="uitextExitFSTitle"> <mat-icon color="primary" class="scaled12">fullscreen_exit</mat-icon> </button> </div> diff --git a/src/app/components/results-graph/results-graph.component.scss b/src/app/components/results-graph/results-graph.component.scss index c50b430fde0e544b56c71b98f899ce694674414b..4eab50fee6ca30b52c952b3aed76d350a987e95f 100644 --- a/src/app/components/results-graph/results-graph.component.scss +++ b/src/app/components/results-graph/results-graph.component.scss @@ -19,6 +19,12 @@ transform: scale(1.2); } } + + &:disabled { + mat-icon { + color: #bfbfbf; + } + } } } diff --git a/src/app/components/results-graph/results-graph.component.ts b/src/app/components/results-graph/results-graph.component.ts index 7e775c89a5dd14cd0f761a21cb7ea25b6326f6d5..0edf3e57c99ed46506e76d4c8f9e9b97db46f811 100644 --- a/src/app/components/results-graph/results-graph.component.ts +++ b/src/app/components/results-graph/results-graph.component.ts @@ -1,4 +1,6 @@ -import { Component, ViewChild, AfterContentInit, OnInit } from "@angular/core"; +import { Component, ViewChild, AfterContentInit, ChangeDetectorRef } from "@angular/core"; + +import { ChartComponent } from "angular2-chartjs"; import { Observer } from "jalhyd"; @@ -17,11 +19,17 @@ import { ResultsComponent } from "../fixedvar-results/results.component"; ] }) export class ResultsGraphComponent extends ResultsComponent implements AfterContentInit, Observer { + + @ViewChild(ChartComponent) + private chartComponent; + private _results: PlottableData; /** used to briefly destroy/rebuild the chart component, to refresh axis labels (@see bug #137) */ public displayChart = true; + private _zoomWasChanged = false; + @ViewChild(GraphTypeSelectComponent) private _graphTypeComponent: GraphTypeSelectComponent; @@ -52,9 +60,32 @@ export class ResultsGraphComponent extends ResultsComponent implements AfterCont public constructor( private appSetup: ApplicationSetupService, - private intlService: I18nService + private intlService: I18nService, + private cd: ChangeDetectorRef ) { super(); + // enable zoom and pan (using "chartjs-plugin-zoom" package) + const that = this; + this.graph_options["plugins"] = { + zoom: { + pan: { + enabled: false, // conflicts with drag zoom + mode: "xy", + }, + zoom: { + enabled: true, + drag: { // conflicts with pan; set to false to enable mouse wheel zoom, + borderColor: "rgba(225,225,225,0.3)", + borderWidth: 1, + backgroundColor: "rgba(0,0,0,0.25)" + }, + mode: "xy", + // percentage of zoom on a wheel event + // speed: 0.1, + onZoomComplete: function(t: any) { return function() { t.zoomComplete(); }; }(that) + } + } + }; } public set results(r: PlottableData) { @@ -116,6 +147,15 @@ export class ResultsGraphComponent extends ResultsComponent implements AfterCont return this.intlService.localizeText("INFO_PARAMFIELD_GRAPH_SELECT_Y_AXIS"); } + private zoomComplete() { + this._zoomWasChanged = true; + this.cd.detectChanges(); + } + + public get zoomWasChanged(): boolean { + return this._zoomWasChanged; + } + public updateView() { // (re)generate chart switch (this._graphTypeComponent.selectedValue) { @@ -316,6 +356,27 @@ export class ResultsGraphComponent extends ResultsComponent implements AfterCont }); // defaults to image/png } + public resetZoom() { + this.chartComponent.chart.resetZoom(); + this._zoomWasChanged = false; + } + + public get uitextResetZoomTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_RESET_ZOOM"); + } + + public get uitextExportImageTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_EXPORT_IMAGE"); + } + + public get uitextEnterFSTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_ENTER_FS"); + } + + public get uitextExitFSTitle() { + return this.intlService.localizeText("INFO_GRAPH_BUTTON_TITLE_EXIT_FS"); + } + // interface Observer update(sender: any, data: any) { diff --git a/src/app/formulaire/definition/concrete/form-pab.ts b/src/app/formulaire/definition/concrete/form-pab.ts index 18b63e068b240f0ba53f36ab2bdfee004887847b..aa5bac6865d805850182ac1912022910e9bc3568 100644 --- a/src/app/formulaire/definition/concrete/form-pab.ts +++ b/src/app/formulaire/definition/concrete/form-pab.ts @@ -1,11 +1,5 @@ -import { Nub, Props, Session, PabCloisons, Pab } from "jalhyd"; +import { Pab } from "jalhyd"; -import { FieldsetContainer } from "../../fieldset-container"; -import { FieldSet } from "../../fieldset"; -import { SelectField } from "../../select-field"; -import { NgParameter } from "../../ngparam"; -import { FieldsetTemplate } from "../../fieldset-template"; -import { FormulaireNode } from "../../formulaire-node"; import { FormulaireBase } from "./form-base"; import { FormComputePab } from "../form-compute-pab"; import { FormResultPab } from "../form-result-pab"; @@ -16,12 +10,6 @@ import { FormResultPab } from "../form-result-pab"; */ export class FormulairePab extends FormulaireBase { - /** id du select configurant le modèle de cloisons */ - private _modeleCloisonsSelectId: string; - - /** id du select configurant le modèle de la cloison aval */ - private _modeleCloisonAvalSelectId: string; - constructor() { super(); this._formResult = new FormResultPab(this); @@ -31,254 +19,26 @@ export class FormulairePab extends FormulaireBase { this._formCompute = new FormComputePab(this, (this._formResult as FormResultPab)); } - public get modeleCloisonsSelectId(): string { - return this._modeleCloisonsSelectId; - } - - public get modeleCloisonAvalSelectId(): string { - return this._modeleCloisonAvalSelectId; - } - - /** - * Creates a virgin PabCloisons when a new fieldset is added through the GUI, - * to ensure consistency; this object is not related to any Cloisons - */ - private createDummyPabCloisons(templ: FieldsetTemplate): Nub { - const params = {}; - params["calcType"] = templ.calcTypeFromConfig; - return this.createBassin(new Props(params)); - } - - /** - * ajoute un Nub PabCloisons - * @param after position après laquelle insérer la structure, à la fin sinon - */ - private addPabCloisons(st: PabCloisons, after?: number) { - this.pabNub.addChild(st, after); - } - - private get pabNub(): Pab { + public get pabNub(): Pab { return this.currentNub as Pab; } - /** - * Asks JaLHyd to create a PabCloisons Nub as a child of the current Calculator Module - * and return it; does not store it in the Session (for PabCloisons, not for Calculator Modules) - * @param p properties for the new Nub - */ - protected createBassin(p: Props): PabCloisons { - return Session.getInstance().createNub(p, this.currentNub as Pab) as PabCloisons; - } - - /** - * Replaces the current Nub in the current calculator module, with a new one built with properties "params" - * @param params properties to build the new Nub (calcType, loiDebit...) - */ - protected replaceNub(sn: PabCloisons, params: Props): Nub { - const parent = (this.currentNub as Pab); - const newBassin = this.createBassin(params); - parent.replaceChildInplace(sn, newBassin); - return newBassin; - } - - /** - * Deleted the given child Nub in the current calculator module - * @param params properties to build the new Nub (calcType, loiDebit...) - */ - protected deleteNub(sn: PabCloisons) { - const parent = (this.currentNub as PabCloisons); - parent.deleteChild(parent.getIndexForChild(sn)); - } - - public doCompute() { + /* public doCompute() { this.dumpPabStructure(this.pabNub); super.doCompute(); - } - - public createFieldset(parent: FormulaireNode, json: {}, data?: {}, nub?: Nub): FieldSet { - if (json["calcType"] === "PabCloisons") { - // indice après lequel insérer le nouveau FieldSet - const after = data["after"]; - - const res: FieldSet = new FieldSet(parent); - let n: Nub; - if (nub) { // use existing Nub (build interface based on model) - n = nub; - } else { - n = this.createDummyPabCloisons(data["template"]); - this.addPabCloisons(n as PabCloisons, after); - } - res.setNub(n, false); - - if (after !== undefined) { - parent.kids.splice(after + 1, 0, res); - } else { - parent.kids.push(res); - } - - this.resetResults(); - - return res; - } else { - return super.createFieldset(parent, json, data); - } - } - - protected parseOptions(json: {}) { - super.parseOptions(json); - // id du select configurant les modèles de cloisons - this._modeleCloisonsSelectId = this.getOption(json, "modeleCloisonsSelectId"); - // id du select configurant le modèle de cloison aval - this._modeleCloisonAvalSelectId = this.getOption(json, "modeleCloisonAvalSelectId"); - } - - public afterParseFieldset(fs: FieldSet) { - // si le FieldSet contient le select de modèles de cloisons - if (this._modeleCloisonsSelectId) { - const node = fs.getFormulaireNodeById(this._modeleCloisonsSelectId); - const sel = (node as SelectField); - if (sel) { - // on abonne le formulaire aux propriétés du FieldSet - fs.properties.addObserver(this); - } - } - // si le FieldSet contient le select du modèle de la cloison aval - if (this._modeleCloisonAvalSelectId) { - const node = fs.getFormulaireNodeById(this._modeleCloisonAvalSelectId); - const sel = (node as SelectField); - if (sel) { - // on abonne le formulaire aux propriétés du FieldSet - fs.properties.addObserver(this); - } - } - } - - public moveFieldsetUp(fs: FieldSet) { - if (fs.nub instanceof PabCloisons) { - // déplacement du nub - fs.nub.parent.moveChildUp(fs.nub); - // déplacement du fieldset - this.fieldsetContainer.moveFieldsetUp(fs); - - this.resetResults(); - } else { - super.moveFieldsetUp(fs); - } - } - - public moveFieldsetDown(fs: FieldSet) { - if (fs.nub instanceof PabCloisons) { - // déplacement du nub - fs.nub.parent.moveChildDown(fs.nub); - // déplacement du fieldset - this.fieldsetContainer.moveFieldsetDown(fs); - - this.resetResults(); - } else { - super.moveFieldsetDown(fs); - } - } - - public removeFieldset(fs: FieldSet) { - if (fs.nub instanceof PabCloisons) { - // suppression du sous-nub dans le Nub parent - this.deleteNub(fs.nub); - - // suppression du fieldset - this.fieldsetContainer.removeFieldset(fs); - - this.resetResults(); - } else { - super.removeFieldset(fs); - } - } - - protected completeParse(json: {}) { - this.subscribeFieldsetContainer(); - } - - private get fieldsetContainer(): FieldsetContainer { - const n = this.getFormulaireNodeById("bassin_container"); - if (n === undefined || !(n instanceof FieldsetContainer)) { - throw new Error("l'élément 'bassin_container' n'est pas du type FieldsetContainer"); - } - return n as FieldsetContainer; - } - - /** - * abonnement en tant qu'observateur du FieldsetContainer - */ - private subscribeFieldsetContainer() { - this.fieldsetContainer.addObserver(this); - } - - /** - * abonnement en tant qu'observateur des NgParameter des FieldSet contenus dans le FieldsetContainer - */ - private subscribeBasinFields(fs: FieldSet) { - for (const n of fs.allFormElements) { - if (n instanceof NgParameter || n instanceof SelectField) { - n.addObserver(this); - } - } - } - - // interface Observer - - public update(sender: any, data: any) { - - super.update(sender, data); - - if (sender instanceof FieldsetContainer) { - switch (data.action) { - case "newFieldset": - this.reset(); - this.subscribeBasinFields(data["fieldset"]); - } - } else if (sender instanceof FieldSet && data.action === "propertyChange") { - switch (sender.id) { - case "fs_bassin": - switch (data.name) { - case "modeleCloisons": - // change PabCloisons property "modeleCloisons" and reinit it - // with new Cloisons values - const newModeleUID = sender.properties.getPropValue("modeleCloisons"); - (sender.nub as PabCloisons).setModel(newModeleUID); - // treat the fieldset as new to re-subscribe to Nub properties change events - this.afterParseFieldset(sender); - this.reset(); - break; - } - break; - - case "fs_cloison_aval": - switch (data.name) { - case "modeleCloisonAval": - // change Pab property "modeleCloisonAval" and reinit it - // with new ParallelStructure values - const newModeleAvalUID = sender.properties.getPropValue("modeleCloisonAval"); - this.pabNub.setDownWall(newModeleAvalUID); - // treat the fieldset as new to re-subscribe to Nub properties change events - this.afterParseFieldset(sender); - this.reset(); - break; - } - break; - } - } - } + } */ // debug method private dumpPabStructure(pab: Pab) { console.log(`PAB: ${pab.uid}, ${pab.children.length} children`); for (const c of pab.children) { console.log( - ` * child: ${c.uid}, based on ${c.properties.getPropValue("modeleCloisons")}` - + ` (cote amont ${c.prms.Z1.singleValue}, longueur ${c.prms.LB.singleValue})` + ` * child: ${c.uid} (cote amont ${c.prms.Z1.singleValue}, longueur ${c.prms.LB.singleValue})` ); } if (pab.downWall) { console.log(`+ downstream wall: ${pab.downWall.uid}`); } + console.log(`>> calculating: ${pab.calculatedParam.symbol}`); } } diff --git a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts index d7d7c8fbe48721db62d1cfbd09bb5f80afe4ef79..79ad66e52b6a2007bab08a85c9657a44a447a170 100644 --- a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts +++ b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts @@ -1,8 +1,7 @@ -import { IObservable, Observer, Session } from "jalhyd"; +import { IObservable, Observer, Session, SectionNub } from "jalhyd"; import { FormDefSection } from "../form-def-section"; import { FieldSet } from "../../fieldset"; import { FormulaireBase } from "./form-base"; -import { SectionNub } from "jalhyd/build/section/section_nub"; export class FormulaireRegimeUniforme extends FormulaireBase implements Observer { diff --git a/src/app/formulaire/definition/form-compute-pab.ts b/src/app/formulaire/definition/form-compute-pab.ts index c5cfd59c22f8dd2953b6d5f099868fdd30775a59..bcde3d155e6f9944a28fa8311c8c1db4d3d8f347 100644 --- a/src/app/formulaire/definition/form-compute-pab.ts +++ b/src/app/formulaire/definition/form-compute-pab.ts @@ -15,18 +15,15 @@ export class FormComputePab extends FormCompute { return this._formResult as FormResultPab; } - private getVariatedParameter(): NgParameter { - const res = this._formBase.getDisplayedParamFromState(ParamRadioConfig.VAR); - if (res !== undefined) { - return res; - } - + private getVariatedParameters(): NgParameter[] { + const res = this._formBase.getDisplayedParamListFromState(ParamRadioConfig.VAR); const pms = this._formBase.getDisplayedParamListFromState(ParamRadioConfig.LINK); for (const p of pms) { if (p.paramDefinition.hasMultipleValues) { - return p; + res.push(p); } } + return res; } protected compute() { @@ -37,7 +34,7 @@ export class FormComputePab extends FormCompute { protected reaffectResultComponents() { const pab: Pab = (this._formBase.currentNub as Pab); const computedParam: NgParameter = this.getComputedParameter(); - const varParam: NgParameter = this.getVariatedParameter(); + const varParams: NgParameter[] = this.getVariatedParameters(); // résultat de calcul de la passe à bassins const pabr = this.formResult.pabResults; @@ -46,28 +43,35 @@ export class FormComputePab extends FormCompute { // résultat de chaque cloison const cr: Result[] = []; - // valeur de Q pour chaque cloison - const cq: number[] = []; - // valeur de QA pour chaque bassin - const bqa: number[] = []; for (const c of pab.children) { cr.push(c.result); - cq.push(c.prms.Q.v); - bqa.push(c.prms.QA.v); } pabr.cloisonsResults = cr, - pabr.cloisonsQ = cq, - pabr.bassinsQA = bqa; // résultat de la cloison aval pabr.cloisonAvalResults = pab.downWall.result; - // débit de la cloison aval - pabr.cloisonAvalQ = pab.downWall.prms.Q.v; + // cote aval de la passe - pabr.Z2 = pab.prms.Z2.v; + if (varParams.length > 0) { + // find longest list + let longest = 0; + for (let i = 0; i < varParams.length; i++) { + const vs = varParams[i].valuesIterator.count(); + if (vs > longest) { + longest = vs; + } + } + // get extended values lists for Z2 + const iter = pab.prms.Z2.getExtendedValuesIterator(longest); + while (iter.hasNext) { + const nv = iter.next(); + pabr.Z2.push(nv.value); + } + } else { + pabr.Z2 = [ pab.prms.Z2.singleValue ]; + } - if (varParam) { - pabr.variatedParameter = varParam; - // pabr.update(false); + if (varParams) { + pabr.variatedParameters = varParams; } } diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts index 859cad4fb3953c45572c4a808327d0bad025fb20..6569081e7588e22c576acc9b0f859860f7daf4b1 100644 --- a/src/app/formulaire/definition/form-definition.ts +++ b/src/app/formulaire/definition/form-definition.ts @@ -13,6 +13,7 @@ import { DeepFormulaireElementIterator } from "../form-iterator/deep-element-ite import { TopFormulaireElementIterator } from "../form-iterator/top-element-iterator"; import { CalculatorResults } from "../../results/calculator-results"; import { ServiceFactory } from "../../services/service-factory"; +import { PabTable } from "../pab-table"; /** * classe de base pour tous les formulaires @@ -187,6 +188,12 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs this.kids.push(fsc); } + private parse_pab_table(json: {}) { + const tab: PabTable = new PabTable(this); + tab.parseConfig(json); + this.kids.push(tab); + } + public parseDependencies(json: {}) { // tslint:disable-next-line:forin for (const conf_index in json) { @@ -271,6 +278,10 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs this.parse_template_container(conf, templates); break; + case "pab_table": // not generic at all + this.parse_pab_table(conf); + break; + default: throw new Error(`type d'objet de module de calcul ${type} non pris en charge`); } diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts index a1eb7b57d4f7b8d49864fad5e3c97f80147b64d0..8d814877614571d60234b3543cf99252e804eb43 100644 --- a/src/app/formulaire/fieldset.ts +++ b/src/app/formulaire/fieldset.ts @@ -8,8 +8,6 @@ import { NgParameter, ParamRadioConfig } from "./ngparam"; import { FormulaireDefinition } from "./definition/form-definition"; import { StringMap } from "../stringmap"; import { FormulaireNode } from "./formulaire-node"; -import { SelectFieldModelCloisons } from "./select-field-model-cloisons"; -import { SelectFieldModelCloisonAval } from "./select-field-model-cloison-aval"; import { FieldsetContainer } from "./fieldset-container"; export class FieldSet extends FormulaireElement implements Observer { @@ -87,24 +85,6 @@ export class FieldSet extends FormulaireElement implements Observer { return res; } - // non-generic version of parse_select for SelectFieldModel because - // downcasting is not possible with @Input() apparently - private parse_select_cloisons(json: {}): SelectField { - const res: SelectFieldModel = new SelectFieldModelCloisons(this, CalculatorType.Cloisons); - res.parseConfig(json); - res.addObserver(this); - return res; - } - - // non-generic version of parse_select for SelectFieldCloisonAval because - // downcasting is not possible with @Input() apparently - private parse_select_cloison_aval(json: {}): SelectField { - const res: SelectFieldModel = new SelectFieldModelCloisonAval(this, CalculatorType.ParallelStructure); - res.parseConfig(json); - res.addObserver(this); - return res; - } - public get properties(): Props { return this.nub.properties; } @@ -182,16 +162,6 @@ export class FieldSet extends FormulaireElement implements Observer { this.addField(param); break; - case "select_cloisons": - param = this.parse_select_cloisons(field); - this.addField(param); - break; - - case "select_cloison_aval": - param = this.parse_select_cloison_aval(field); - this.addField(param); - break; - } } } @@ -410,20 +380,6 @@ export class FieldSet extends FormulaireElement implements Observer { case "select_target": // courbes de remous, variable à calculer this.setPropValue("varCalc", data.value.value); break; - case "select_modele_cloisons": // passe à bassins, modèle de cloisons - let valToSet; - if (data.value) { - valToSet = data.value.id; - } - this.setPropValue("modeleCloisons", valToSet); - break; - case "select_modele_cloison_aval": // passe à bassins, modèle de la cloison aval - let valToSet2; - if (data.value) { - valToSet2 = data.value.id; - } - this.setPropValue("modeleCloisonAval", valToSet2); - break; } break; } diff --git a/src/app/formulaire/pab-table.ts b/src/app/formulaire/pab-table.ts new file mode 100644 index 0000000000000000000000000000000000000000..9464eeef8ba8baaacf454afd9e3eb201b5c3e103 --- /dev/null +++ b/src/app/formulaire/pab-table.ts @@ -0,0 +1,40 @@ +import { Pab } from "jalhyd"; + +import { FormulaireElement } from "./formulaire-element"; +import { FormulaireNode } from "./formulaire-node"; +import { FormulairePab } from "./definition/concrete/form-pab"; + +/** + * The big editable data grid for calculator type "Pab" (form element). + * + * This is just a gateway between the model (FormPab and its underlying Pab) + * and the user interface (PabTableComponent) + */ +export class PabTable extends FormulaireElement { + + constructor(parent: FormulaireNode) { + super(parent); + } + + public parseDependencies(json: {}) { } // implements abstract method of FormulaireNode + + public parseConfig(json: {}) { + this._confId = json["id"]; + } + + /** + * Returns the parent FormulairePab + */ + public get form(): FormulairePab { + return this.parentForm as FormulairePab; + } + + /** + * Returns the Pab model associated to the parent form + */ + public get pab(): Pab { + if (this.form) { + return this.form.pabNub; + } + } +} diff --git a/src/app/formulaire/select-field-model-cloison-aval.ts b/src/app/formulaire/select-field-model-cloison-aval.ts deleted file mode 100644 index b178d9af3530ae24ab4d8a691bc3d6877afa7ea9..0000000000000000000000000000000000000000 --- a/src/app/formulaire/select-field-model-cloison-aval.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Session } from "jalhyd"; -import { SelectEntry } from "./select-entry"; -import { FieldSet } from "./fieldset"; -import { SelectFieldModel } from "./select-field-model"; - -/** - * A select field that populates itself with references to - * available ParallelStructure modules (used by PAB) - */ -export class SelectFieldModelCloisonAval extends SelectFieldModel { - - protected initSelectedValue() { - if (this.parent instanceof FieldSet) { - const mc = this.parent.nub.properties.getPropValue("modeleCloisonAval"); - if (mc) { - this._selectedEntry = new SelectEntry(mc, {}); - } // else if current model is undefined, do not select it so that default ParallelStructures will be chosen (if available) - } - } - - /** - * Populates entries with available ParallelStructures - */ - protected populate() { - const psNubs = Session.getInstance().getParallelStructureNubs(); - for (const cl of psNubs) { - const e = new SelectEntry(cl.uid, cl); - this.addEntry(e); - } - } -} diff --git a/src/app/formulaire/select-field-model-cloisons.ts b/src/app/formulaire/select-field-model-cloisons.ts deleted file mode 100644 index 8a0241a4d83ddcd027fd98771c42bcb1d91570b4..0000000000000000000000000000000000000000 --- a/src/app/formulaire/select-field-model-cloisons.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Session } from "jalhyd"; -import { SelectEntry } from "./select-entry"; -import { FieldSet } from "./fieldset"; -import { SelectFieldModel } from "./select-field-model"; - -/** - * A select field that populates itself with references to - * available Cloisons modules (used by PAB) - */ -export class SelectFieldModelCloisons extends SelectFieldModel { - - protected initSelectedValue() { - if (this.parent instanceof FieldSet) { - const mc = this.parent.nub.properties.getPropValue("modeleCloisons"); - if (mc) { - this._selectedEntry = new SelectEntry(mc, {}); - } // else if current model is undefined, do not select it so that default Cloisons will be chosen (if available) - } - } - - /** - * Populates entries with available Cloisons - */ - protected populate() { - const cloisonsNubs = Session.getInstance().getCloisonsNubs(); - for (const cl of cloisonsNubs) { - const e = new SelectEntry(cl.uid, cl); - this.addEntry(e); - } - } -} diff --git a/src/app/results/pab-results.ts b/src/app/results/pab-results.ts index d87b882b30b8eaaffb928613d707cc1b5b077c86..317914470e2565bcb1e82d22ce853dc42ae0560b 100644 --- a/src/app/results/pab-results.ts +++ b/src/app/results/pab-results.ts @@ -9,23 +9,20 @@ export class PabResults extends CalculatedParamResults { /** résultats des modules Cloisons avant chaque bassin */ public cloisonsResults: Result[]; - /** valeur de Q pour chaque module Cloisons */ - public cloisonsQ: number[]; - - /** valeur de QA pour chaque bassin */ - public bassinsQA: number[]; - /** résultats du module ParallelStructure pour la cloison aval */ public cloisonAvalResults: Result; - /** débit de la cloison aval */ - public cloisonAvalQ: number; + /** + * valeurs de la cote aval de l'ensemble de la passe, pour chaque + * itération : si aucun paramètre ne varie, ne contient qu'un élément + * */ + public Z2: number[]; - /** cote aval de l'ensemble de la passe */ - public Z2: number; + /** paramètres variés */ + public variatedParameters: NgParameter[]; - /** paramètre varié */ - public variatedParameter: NgParameter; + /** index de la valeur du paramètre varié à afficher dans les résultats */ + public variableIndex = 0; /** symboles des colonnes de résultat */ protected _columns: string[]; @@ -42,7 +39,7 @@ export class PabResults extends CalculatedParamResults { "Q", "PV", "YMOY", - "ZRB", + "ZRMB", "QA" ]; } @@ -68,11 +65,31 @@ export class PabResults extends CalculatedParamResults { public reset() { super.reset(); this.cloisonsResults = []; - this.cloisonsQ = []; - this.bassinsQA = []; this.cloisonAvalResults = undefined; - this.cloisonAvalQ = undefined; - this.Z2 = undefined; + this.Z2 = []; + } + + /** + * Returns true if at least one log message is present in the PAB result or any + * of the children results + */ + public get hasLog(): boolean { + if (this.cloisonsResults) { + for (const cr of this.cloisonsResults) { + if (cr && cr.hasLog) { + return true; + } + } + } + return ( + (this.result && this.result.hasLog) + || (this.cloisonAvalResults && this.cloisonAvalResults.hasLog) + ); + } + + // do not test result.ok else log messages will prevent partial results from being displayed + public get hasResults(): boolean { + return this.result !== undefined && ! this.result.hasOnlyErrors; } /** retourne true si au moins un calcul a échoué (le log a un code négatif) */ @@ -89,4 +106,32 @@ export class PabResults extends CalculatedParamResults { return err; } + + /** retourne true si le calcul à l'itération i a échoué */ + public iterationHasError(i: number): boolean { + let err = this.result.resultElements[i].hasErrorMessages; + // logs des cloisons + for (const c of this.cloisonsResults) { + err = (err || c.resultElements[i].hasErrorMessages); + } + // log de la cloison aval + err = (err || this.cloisonAvalResults.resultElements[i].hasErrorMessages); + + return err; + } + + /** retourne true si tous les calculs ont échoué */ + public hasOnlyErrors(): boolean { + let err = true; + // log principal + err = (err && this.result.hasOnlyErrors); + // logs des cloisons + for (const c of this.cloisonsResults) { + err = (err && c.hasOnlyErrors); + } + // log de la cloison aval + err = (err && this.cloisonAvalResults.hasOnlyErrors); + + return err; + } } diff --git a/src/app/results/plottable-pab-results.ts b/src/app/results/plottable-pab-results.ts index 27e5685d9d0c257798729acf56326acf3c22d1dc..57a4dd318b9c815ad3f36d5c65959ea61c78c7d8 100644 --- a/src/app/results/plottable-pab-results.ts +++ b/src/app/results/plottable-pab-results.ts @@ -17,7 +17,7 @@ export class PlottablePabResults implements PlottableData { } // axes par défaut this.chartX = this.chartX || "CLOISON"; - this.chartY = this.chartY || "Z"; + this.chartY = this.chartY || "YMOY"; } /** reaffect pabResults, for ex. when objet was contructed with empty pabResults */ @@ -30,7 +30,11 @@ export class PlottablePabResults implements PlottableData { * @param symbol parameter / result symbol (ex: "Q") */ public getChartAxisLabel(symbol: string): string { - return this.pabResults.headers[this.pabResults.columns.indexOf(symbol)]; + if (symbol === "x") { // specific case for wall abscissa + return ServiceFactory.instance.i18nService.localizeText("INFO_LIB_ABSCISSE_CLOISON"); + } else { + return this.pabResults.headers[this.pabResults.columns.indexOf(symbol)]; + } } public expandLabelFromSymbol(symbol: string): string { @@ -42,7 +46,8 @@ export class PlottablePabResults implements PlottableData { * as X or Y chart axis */ public getAvailableChartAxis(): string[] { - return this.pabResults.columns; + // add wall abscissa on the fly + return [ "x" ].concat(this.pabResults.columns); } // just to implement interface @@ -59,95 +64,65 @@ export class PlottablePabResults implements PlottableData { const pr = this.pabResults; const nDigits = ServiceFactory.instance.applicationSetupService.displayDigits; const l = this.pabResults.cloisonsResults.length; + // when a parameter is variating, index of the variating parameter + // values to build the data from + const vi = this.pabResults.variableIndex; - if (this.hasError()) { + if (this.pabResults.iterationHasError(vi)) { return []; } switch (symbol) { case "CLOISON": data.push(""); - for (let i = 0; i < l; i++) { + for (let i = 0; i <= l; i++) { // <= for one extra step (downwall) data.push("" + (i + 1)); } - data.push(ServiceFactory.instance.i18nService.localizeText("INFO_LIB_AVAL")); break; case "DH": - data.push(""); - for (let i = 0; i < l - 1; i++) { - data.push((pr.cloisonsResults[i].vCalc - pr.cloisonsResults[i + 1].vCalc).toFixed(nDigits)); - } - data.push((pr.cloisonsResults[l - 1].vCalc - pr.cloisonAvalResults.vCalc).toFixed(nDigits)); - data.push((pr.cloisonAvalResults.vCalc - pr.Z2).toFixed(nDigits)); - break; - case "ZRAM": + case "Q": data.push(""); for (let i = 0; i < l; i++) { - const er = pr.cloisonsResults[i].getExtraResult("ZRAM"); + const er = pr.cloisonsResults[i].resultElements[vi].getExtraResult(symbol); data.push((er !== undefined) ? er.toFixed(nDigits) : ""); } - const zrAval = pr.cloisonAvalResults.getExtraResult("ZRAM"); + const zrAval = pr.cloisonAvalResults.resultElements[vi].getExtraResult(symbol); data.push((zrAval !== undefined) ? zrAval.toFixed(nDigits) : ""); break; - case "Q": - data.push(""); - for (let i = 0; i < l; i++) { - data.push(pr.cloisonsQ[i].toFixed(nDigits)); - } - data.push(pr.cloisonAvalQ.toFixed(nDigits)); - break; - case "Z": - for (let i = 0; i < l - 1; i++) { - data.push(pr.cloisonsResults[i].vCalc.toFixed(nDigits)); - } - data.push(pr.cloisonAvalResults.vCalc.toFixed(nDigits)); - data.push(pr.Z2.toFixed(nDigits)); - break; - - case "PV": - data.push(""); for (let i = 0; i < l; i++) { - const er = pr.cloisonsResults[i].getExtraResult("PV"); - data.push((er !== undefined) ? er.toFixed(nDigits) : ""); + data.push(pr.cloisonsResults[i].resultElements[vi].vCalc.toFixed(nDigits)); } - data.push(""); + data.push(pr.cloisonAvalResults.resultElements[vi].vCalc.toFixed(nDigits)); + data.push(pr.Z2[vi].toFixed(nDigits)); break; + case "PV": case "YMOY": + case "ZRMB": + case "QA": data.push(""); for (let i = 0; i < l; i++) { - const er = pr.cloisonsResults[i].getExtraResult("YMOY"); + const er = pr.cloisonsResults[i].resultElements[vi].getExtraResult(symbol); data.push((er !== undefined) ? er.toFixed(nDigits) : ""); } data.push(""); break; - case "ZRB": + case "x": // wall abscissa data.push(""); for (let i = 0; i < l; i++) { - const er = pr.cloisonsResults[i].getExtraResult("ZRB"); + const er = pr.cloisonsResults[i].resultElements[vi].getExtraResult(symbol); data.push((er !== undefined) ? er.toFixed(nDigits) : ""); } - data.push(""); - break; - - case "QA": - data.push(""); - for (let i = 0; i < l; i++) { - data.push(pr.bassinsQA[i].toFixed(nDigits)); - } - data.push(""); + const erXdw = pr.cloisonAvalResults.resultElements[vi].getExtraResult(symbol); + data.push((erXdw !== undefined) ? erXdw.toFixed(nDigits) : ""); break; } return data; } - - protected hasError(): boolean { - return this.pabResults.hasError(); - } } diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts index d33770c26e587986bf63dca797c6f4ffc4774856..c9dea5a285aba0180f1733aa4b870a6526096026 100644 --- a/src/app/services/formulaire/formulaire.service.ts +++ b/src/app/services/formulaire/formulaire.service.ts @@ -3,7 +3,19 @@ import { Injectable } from "@angular/core"; import { decode } from "he"; import { saveAs } from "file-saver"; -import { CalculatorType, LinkedValue, Observable, ParamDefinition, Session, Nub, ParallelStructure, Pab } from "jalhyd"; +import { + CalculatorType, + LinkedValue, + Observable, + ParamDefinition, + Session, + Nub, + ParallelStructure, + Pab, + Props, + Cloisons, + CloisonAval +} from "jalhyd"; import { HttpService } from "../../services/http/http.service"; import { I18nService } from "../../services/internationalisation/internationalisation.service"; @@ -333,16 +345,44 @@ export class FormulaireService extends Observable { } } - // add fieldsets for existing PabCloisons if needed - // (when loading session only) - if (f.currentNub instanceof Pab) { - for (const child of f.currentNub.children) { - for (const e of f.allFormElements) { - if (e instanceof FieldsetContainer) { // @TODO manage many containers one day ? - e.addFromTemplate(0, undefined, child); - } - } - } + // when creating a new Pab, add one wall with one device, plus the downwall + // (when loading session, those items are already present) + if ( + f instanceof FormulairePab + && f.currentNub instanceof Pab + && f.currentNub.children.length === 0 + && f.currentNub.downWall === undefined + ) { + // 1. one wall + const newWall = Session.getInstance().createNub( + new Props({ + calcType: CalculatorType.Cloisons + }) + ) as Cloisons; + // add new default device for new wall + const newDevice = Session.getInstance().createNub( + new Props({ + calcType: CalculatorType.Structure, + loiDebit: newWall.getDefaultLoiDebit() + }) + ); + newWall.addChild(newDevice); + f.pabNub.addChild(newWall); + // 2. downwall + const newDownWall = Session.getInstance().createNub( + new Props({ + calcType: CalculatorType.CloisonAval + }) + ) as CloisonAval; + // add new default device for new downwall + const newDownwallDevice = Session.getInstance().createNub( + new Props({ + calcType: CalculatorType.Structure, + loiDebit: newDownWall.getDefaultLoiDebit() + }) + ); + newDownWall.addChild(newDownwallDevice); + f.pabNub.downWall = newDownWall; } return f; @@ -584,21 +624,6 @@ export class FormulaireService extends Observable { } }); } - // list Cloisons / downWall models dependencies for each PAB Nub - if (e.props.calcType === CalculatorType.Pab) { - // Cloisons models - e.children.forEach((c) => { - if (c.props.calcType === CalculatorType.PabCloisons) { // who knows ? - if (c.props.modeleCloisons && ! nubInfo.requires.includes(c.props.modeleCloisons)) { - nubInfo.requires.push(c.props.modeleCloisons); - } - } - }); - // Downstream wall - if (e.props.modeleCloisonAval && ! nubInfo.requires.includes(e.props.modeleCloisonAval)) { - nubInfo.requires.push(e.props.modeleCloisonAval); - } - } // list children nubs for each Nub if (e.children) { e.children.forEach((p) => { diff --git a/src/app/services/internationalisation/internationalisation.service.ts b/src/app/services/internationalisation/internationalisation.service.ts index bd560fae2a17b65ead3d8a281f61c04dc3a0e958..f95bcc9ba2a26d4f29cc52698770142a585453a8 100644 --- a/src/app/services/internationalisation/internationalisation.service.ts +++ b/src/app/services/internationalisation/internationalisation.service.ts @@ -1,6 +1,6 @@ import { Injectable, isDevMode } from "@angular/core"; -import { Message, MessageCode, Observable, Observer, CalculatorType } from "jalhyd"; +import { Message, MessageCode, Observable, Observer, CalculatorType, LoiDebit } from "jalhyd"; import { StringMap } from "../../stringmap"; import { ApplicationSetupService } from "../app-setup/app-setup.service"; @@ -173,6 +173,14 @@ export class I18nService extends Observable implements Observer { return m; } + /** + * Finds the localized title for a LoiDebit item + * @TODO add StructureType context ? (ex: cem88d / cem88v) + */ + public localizeLoiDebit(l: LoiDebit) { + return this.localizeText("INFO_LOIDEBIT_" + LoiDebit[l]); + } + private replaceAll(str: string, find: string, replace: string) { return str.replace(new RegExp(find, "g"), replace); } diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json index 18aff4ce809251b42820e1da10648c4da458b43e..78e3384d637e0a52cca6d3e71361631bd9639783 100644 --- a/src/locale/messages.en.json +++ b/src/locale/messages.en.json @@ -1,13 +1,14 @@ { + "ERROR_CLOISON_AVAL_UN_OUVRAGE_REGULE": "Only one regulated device is allowed on the downstream wall", "ERROR_DICHO_CONVERGE": "Dichotomy could not converge", "ERROR_DICHO_FUNCTION_VARIATION": "unable to determinate function direction of variation", "ERROR_DICHO_INIT_DOMAIN": "Dichotomy: target %targetSymbol%=%targetValue% does not exist for variable %variableSymbol% valued in interval %variableInterval%", - "ERROR_DICHO_INITVALUE_HIGH": "Dichotomy: initial value %variableSymbol%=%variableInitValue% is too high (target is %targetSymbol%=%targetValue%, %targetSymbol%(%variableSymbol%=%variableInitValue%)=%initTarget%)", - "ERROR_DICHO_INITVALUE_LOW": "Dichotomy: initial value %variableSymbol%=%variableInitValue% is too low (target is %targetSymbol%=%targetValue%, %targetSymbol%(%variableSymbol%=%variableInitValue%)=%initTarget%)", "ERROR_DICHO_INVALID_STEP_GROWTH": "Dichotomy (initial interval search): invalid null step growth", "ERROR_DICHO_NULL_STEP": "Dichotomy (initial interval search): invalid null step", + "ERROR_DICHO_TARGET_TOO_HIGH": "Dichotomy: the solution %targetSymbol%=%targetValue% is greater than the maximum computable value %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)", + "ERROR_DICHO_TARGET_TOO_LOW": "Dichotomy: the solution %targetSymbol%=%targetValue% is lower than the minimum computable value %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)", "ERROR_ELEVATION_ZI_LOWER_THAN_Z2": "Upstream elevation is lower than downstream elevation", - "ERROR_INTERVAL_OUTSIDE": "Interval: value %value% is outside of %interval", + "ERROR_INTERVAL_OUTSIDE": "Interval: value %value% is outside of %interval%", "ERROR_INTERVAL_UNDEF": "Interval: invalid 'undefined' value", "ERROR_INVALID_AT_POSITION": "Position %s:", "ERROR_LOADING_SESSION": "Unable to load session", @@ -15,7 +16,10 @@ "ERROR_MINMAXSTEP_MIN": "Value is not in [%s,%s[", "ERROR_MINMAXSTEP_STEP": "Value is not in %s", "ERROR_NEWTON_DERIVEE_NULLE": "Null function derivative in Newton computation", + "ERROR_PAB_Z1_LOWER_THAN_Z2": "Upstream water elevation should be higher than downstream water elevation", + "ERROR_PAB_Z1_LOWER_THAN_UPSTREAM_WALL": "Upstream water elevation is too low for water to flow through the first wall", "ERROR_PARAM_MUST_BE_A_NUMBER": "Please type a numeric value", + "ERROR_PARAM_MUST_BE_AT_LEAST": "Value must be >= %s", "ERROR_PARAM_MUST_BE_POSITIVE": "Please type a positive numeric value", "ERROR_PARAM_NULL": "Parameter value must not be NULL", "ERROR_PARAMDEF_CALC_UNDEFINED": "calculability of '%symbol%' parameter is undefined", @@ -58,6 +62,15 @@ "INFO_DEVER_TITRE_COURT": "Free weir", "INFO_DEVER_TITRE": "Free flow weir stage-discharge laws", "INFO_DIALOG_COMPUTED_VALUE_TITLE": "Edit initial value", + "INFO_DIALOG_EDIT_PAB_INTERPOLATION_BOUNDS": "between %s and %s", + "INFO_DIALOG_EDIT_PAB_N_DEVICES": "%s device(s)", + "INFO_DIALOG_EDIT_PAB_N_WALLS": "%s wall(s) : %s device(s)", + "INFO_DIALOG_EDIT_PAB_N_WALLS_P_DEVICES": "%s wall(s) + %s device(s) : %s devices", + "INFO_DIALOG_EDIT_PAB_OPTION_VARIABLE": "Variable to edit", + "INFO_DIALOG_EDIT_PAB_OPTION_SET_VALUE": "Fixed value", + "INFO_DIALOG_EDIT_PAB_OPTION_DELTA": "Delta", + "INFO_DIALOG_EDIT_PAB_OPTION_INTERPOLATE": "Interpolate", + "INFO_DIALOG_EDIT_PAB_TITLE": "Edit values", "INFO_DIALOG_EMPTY_CURRENT_SESSION": "Empty current session", "INFO_DIALOG_FIX_MISSING_DEPENDENCIES": "Fix missing dependencies", "INFO_DIALOG_FORMAT_VERSIONS_MISMATCH": "File format versions mismatch (file: %s, jalhyd: %s)", @@ -88,26 +101,42 @@ "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_1": "Partially submerged", "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_2": "Submerged", "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_3": "Zero flow", - "INFO_FSC_FS_ADDED": "1 device added", - "INFO_FSC_FS_ADDED_N_TIMES": "%s devices added", - "INFO_FSC_FS_COPIED": "Device #%s copied", - "INFO_FSC_FS_COPIED_N_TIMES": "Device #%s copied %s times", - "INFO_FSC_FS_MOVED": "Device #%s moved", - "INFO_FSC_FS_REMOVED": "Device #%s removed", + "INFO_GRAPH_BUTTON_TITLE_RESET_ZOOM": "Restore default zoom", + "INFO_GRAPH_BUTTON_TITLE_EXPORT_IMAGE": "Save picture", + "INFO_GRAPH_BUTTON_TITLE_ENTER_FS": "Display fullscreen", + "INFO_GRAPH_BUTTON_TITLE_EXIT_FS": "Exis fullscreen mode", + "INFO_DEVICE_ADDED": "1 device added", + "INFO_DEVICE_ADDED_N_TIMES": "%s devices added", + "INFO_DEVICE_COPIED": "Device #%s copied", + "INFO_DEVICE_COPIED_N_TIMES": "Device #%s copied %s times", + "INFO_DEVICE_MOVED": "Device #%s moved", + "INFO_DEVICE_REMOVED": "Device #%s removed", + "INFO_WALL_ADDED": "1 wall added", + "INFO_WALL_ADDED_N_TIMES": "%s walls added", + "INFO_WALL_COPIED": "Wall #%s copied", + "INFO_WALL_COPIED_N_TIMES": "Wall #%s copied %s times", + "INFO_WALL_MOVED": "Wall #%s moved", + "INFO_WALL_REMOVED": "Wall #%s removed", "INFO_LECHAPTCALMON_TITRE_COURT": "Lechapt-C.", "INFO_LECHAPTCALMON_TITRE": "Lechapt-Calmon", + "INFO_LIB_ABSCISSE_CLOISON": "Wall abscissa", "INFO_LIB_ALPHA": "Alpha coefficient", "INFO_LIB_ALPHA2": "Half-angle at the apex", + "INFO_LIB_AMONT": "Upstream", "INFO_LIB_AVAL": "Downstream", "INFO_LIB_B": "Surface width", + "INFO_LIB_BB": "Pool width", "INFO_LIB_BETA": "Beta coefficient", "INFO_LIB_BT": "Half opening of the triangle", "INFO_LIB_CD": "Discharge coefficient", "INFO_LIB_CLOISON": "Cross wall #", + "INFO_LIB_COTE": "Elevation (m)", + "INFO_LIB_COTE_VANNE_LEVANTE": "Lift gate elevation", "INFO_LIB_CV": "Cv: Velocity coefficient", "INFO_LIB_CVQT": "CV.QT: Corrected discharge", "INFO_LIB_DH": "Fall", "INFO_LIB_DHR": "DHR : Residual fall", + "INFO_LIB_DISTANCE_AMONT": "Distance from upstream (m)", "INFO_LIB_EC": "EC: Kinetic energy", "INFO_LIB_ENUM_MACRORUGOFLOWTYPE": "Flow type", "INFO_LIB_FLU": "Subcritical water line", @@ -115,16 +144,23 @@ "INFO_LIB_FS_OUVRAGE": "Device", "INFO_LIB_FS_PARAM_CALC": "Calculation parameters", "INFO_LIB_FS_PARAM_HYDRO": "Hydraulic parameters", + "INFO_LIB_H1": "Head", "INFO_LIB_HS": "Specific head", "INFO_LIB_HSC": "Critical head", "INFO_LIB_I-J": "Linear variation of specific head", "INFO_LIB_IMP": "Impulse", "INFO_LIB_J": "Head loss", "INFO_LIB_L": "Weir width", - "INFO_LIB_OUVRAGE_Q_MODE": "Mode", - "INFO_LIB_OUVRAGE_Q_REGIME": "Regime", + "INFO_LIB_LB": "Pool length", + "INFO_LIB_LIGNE_D_EAU": "Water line", + "INFO_LIB_MINZDV": "Minimal crest elevation", + "INFO_LIB_MAXZDV": "Maximal crest elevation", + "INFO_LIB_OUVRAGE_Q_ENUM_STRUCTUREFLOWMODE": "Mode", + "INFO_LIB_OUVRAGE_Q_ENUM_STRUCTUREFLOWREGIME": "Regime", "INFO_LIB_OUVRAGE_Q": "Discharge", + "INFO_LIB_OUVRAGE_ZDV": "Sill elevation", "INFO_LIB_P": "Wetted perimeter", + "INFO_LIB_PB": "Basin mean depth", "INFO_LIB_PR": "Display accuracy", "INFO_LIB_PV": "Volumic dissipated power", "INFO_LIB_Q_GUIDETECH": "Technical guide flow", @@ -132,6 +168,7 @@ "INFO_LIB_QA": "Attraction flow", "INFO_LIB_R": "Hydraulic radius", "INFO_LIB_S": "Orifice area", + "INFO_LIB_RADIER": "Basin bottom", "INFO_LIB_SELECT_LOIDEBIT": "Stage-discharge law", "INFO_LIB_SELECT_LOIDEBIT1_KIVI": "Kindsvater-Carter and Villemonte", "INFO_LIB_SELECT_LOIDEBIT1": "Stage-discharge law", @@ -147,6 +184,7 @@ "INFO_LIB_V": "Average speed", "INFO_LIB_VDEB": "Conveyance speed", "INFO_LIB_VMAX": "Maximal speed", + "INFO_LIB_W": "Gate opening", "INFO_LIB_YC": "Critical depth", "INFO_LIB_YCO": "Conjugate depth", "INFO_LIB_YF": "Subcritical depth", @@ -159,7 +197,7 @@ "INFO_LIB_ZDV": "Crest weir elevation or gate base", "INFO_LIB_ZF2": "Downstream bottom elevation", "INFO_LIB_ZRAM": "Upstream apron elevation", - "INFO_LIB_ZRB": "Downstream basin bottom elevation", + "INFO_LIB_ZRMB": "Downstream basin bottom elevation", "INFO_LIB_ZT": "Triangle top elevation", "INFO_LINKED_VALUE_DEVICE_RESULT": "%s (%s, device %s)", "INFO_LINKED_VALUE_DEVICE": "%s (%s, device %s)", @@ -168,6 +206,22 @@ "INFO_LINKED_VALUE_RESULT": "%s (%s)", "INFO_LINKED_VALUE_SECTION_RESULT": "%s (%s, section)", "INFO_LINKED_VALUE_SECTION": "%s (%s, section)", + "INFO_LOIDEBIT_KIVI": "Kindsvater-Carter and Villemonte", + "INFO_LOIDEBIT_WeirSubmergedLarinier": "Submerged slot (Larinier 1992)", + "INFO_LOIDEBIT_OrificeSubmerged": "Submerged orifice", + "INFO_LOIDEBIT_TriangularWeirFree": "Free flow triangular weir", + "INFO_LOIDEBIT_TriangularTruncWeirFree": "Free flow truncated triangular weir", + "INFO_LOIDEBIT_GateCem88d": "Weir/Orifice Cemagref 88", + "INFO_LOIDEBIT_GateCem88v": "Weir/Undershot gate Cemagref 88", + "INFO_LOIDEBIT_WeirCem88d": "Weir/Orifice Cemagref 88", + "INFO_LOIDEBIT_WeirCem88v": "Weir/Undershot gate Cemagref 88", + "INFO_LOIDEBIT_Cunge80": "Cunge 80", + "INFO_LOIDEBIT_RectangularOrificeFree": "Free flow gate", + "INFO_LOIDEBIT_RectangularOrificeSubmerged": "Submerged gate", + "INFO_LOIDEBIT_VanLevLarinier": "Regulated submerged slot (Larinier 1992)", + "INFO_LOIDEBIT_VanLevVillemonte": "Regulated notch (Villemonte 1957)", + "INFO_LOIDEBIT_WeirFree": "Free flow weir", + "INFO_LOIDEBIT_WeirVillemonte": "Notch (Villemonte 1957)", "INFO_MACRORUGO_TITRE_COURT": "Rock-ramp", "INFO_MACRORUGO_TITRE": "Rock-ramp fishpasses", "INFO_MENU_EMPTY_SESSION_TITLE": "New session", @@ -193,8 +247,17 @@ "INFO_OPTION_VALIDATE": "Validate", "INFO_OPTION_YES": "Yes", "INFO_OUVRAGE": "Structure", + "INFO_PAB_BASSIN": "Basin", + "INFO_PAB_NUM_BASSIN": "Basin #", + "INFO_PAB_HEADER_TYPE": "Type", + "INFO_PAB_HEADER_PARAMETERS": "Parameters", + "INFO_PAB_HEADER_VALUES": "Values", + "INFO_PAB_CLOISON_OUVRAGE_N": "Wall : device #%s", + "INFO_PAB_PARAMETRES_FIXES": "All parameters must be fixed", "INFO_PAB_TITRE_COURT": "Fish ladder", "INFO_PAB_TITRE": "Fish ladder", + "INFO_PAB_TITRE_PROFIL": "Fish ladder longitudinal profile", + "INFO_PAB_TABLE": "Fish ladder geometry", "INFO_PABCHUTE_TITRE_COURT": "FL: fall", "INFO_PABCHUTE_TITRE": "Fish ladder: fall", "INFO_PABDIMENSIONS_TITRE_COURT": "FL: dimensions", @@ -206,6 +269,7 @@ "INFO_PARALLELSTRUCTURE_TITRE_COURT": "// structures", "INFO_PARALLELSTRUCTURE_TITRE": "Parallel structures", "INFO_PARAMFIELD_AWAITING_CALCULATION": "Awaiting calculation", + "INFO_PARAMFIELD_BOUNDARY_CONDITIONS": "Boundary conditions", "INFO_PARAMFIELD_CALCULATED": "Calculated", "INFO_PARAMFIELD_CALCULATION_FAILED": "Calculation failed", "INFO_PARAMFIELD_GRAPH_SELECT_X_AXIS": "Variable for X axis", @@ -238,6 +302,7 @@ "INFO_PARAMFIELD_VARIATED": "Variated", "INFO_PARAMMODE_LIST": "Values list", "INFO_PARAMMODE_MINMAX": "Min/max", + "WARNING_PROBLEMS_ENCOUNTERED": "Problems occurred during calculation (info: %info%, warning: %warning%, error: %error%)", "INFO_REGIMEUNIFORME_TITRE_COURT": "Uniform flow", "INFO_REGIMEUNIFORME_TITRE": "Uniform flow calculation", "INFO_REMOUS_CALCUL_FLUVIAL": "Downstream boundary condition >= Critical elevation: calculation of subcritical part from downstream", @@ -282,9 +347,12 @@ "INFO_THEME_PASSE_NATURELLE_DESCRIPTION": "Tools for sizing a natural fish pass also called macroroughness pass or rock-ramp fish pass", "INFO_THEME_PASSE_NATURELLE_TITRE": "Natural pass", "INFO_TITREJOURNAL": "Calculation log", + "INFO_TITREJOURNAL_GLOBAL": "Calculation log synthesis", "INFO_WELCOME_CONTENT": "<p>The Cassiopée software was developed by <a href=\"https://www.afbiodiversite.fr\" target=\"_blank\">AFB</a> (French Agency for Biodiversity) and <a href=\"http://g-eau.fr/index.php/en/\" target=\"_blank\">UMR G-EAU</a> (Joint Research Unit \"Water Management, Actors, Territories\").</p><p>It includes tools for designing fish passes, and hydraulic calculation tools useful for environmental and agricultural engineering.</p><p>For more information, consult <a href=\"assets/docs-fr/mentions_legales.html\" target=\"_blank\">legal notice</a> and <a href=\"assets/docs-fr/index.html\" target=\"_blank\">documentation</a>.</p>", "INFO_WELCOME_SUBTITLE": "Hydraulic calculators", "WARNING_REMOUS_ARRET_CRITIQUE": "Calculation stopped: critical elevation reached at abscissa %x%", "WARNING_STRUCTUREKIVI_HP_TROP_ELEVE": "h/p must not be greater than 2.5. h/p is forced to 2.5", - "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "Threshold height should be greater than 0.1 m. Beta coefficient is forced to 0" + "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "Threshold height should be greater than 0.1 m. Beta coefficient is forced to 0", + "WARNING_VANLEV_ZDV_INF_MIN": "Regulated weir: minimum sill elevation reached", + "WARNING_VANLEV_ZDV_SUP_MAX": "Regulated weir: maximum sill elevation reached" } diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json index 1f1db90016de2f86a955686a596e8f1173854573..e9135bada0233eb094b618f0933407f3d1f5a239 100644 --- a/src/locale/messages.fr.json +++ b/src/locale/messages.fr.json @@ -1,13 +1,14 @@ { + "ERROR_CLOISON_AVAL_UN_OUVRAGE_REGULE": "Un seul ouvrage régulé est autorisé sur la cloison aval", "ERROR_DICHO_CONVERGE": "La dichotomie n'a pas pu converger", "ERROR_DICHO_FUNCTION_VARIATION": "Dichotomie : impossible de determiner le sens de variation de la fonction", "ERROR_DICHO_INIT_DOMAIN": "Dichotomie : la valeur cible %targetSymbol%=%targetValue% n'existe pas pour la variable %variableSymbol% prise dans l'intervalle %variableInterval%", - "ERROR_DICHO_INITVALUE_HIGH": "Dichotomie : la valeur initiale %variableSymbol%=%variableInitValue% est trop grande (la valeur cible est %targetSymbol%=%targetValue%, %targetSymbol%(%variableSymbol%=%variableInitValue%)=%initTarget%)", - "ERROR_DICHO_INITVALUE_LOW": "Dichotomie : la valeur initiale %variableSymbol%=%variableInitValue% est trop petite (la valeur cible est %targetSymbol%=%targetValue%, %targetSymbol%(%variableSymbol%=%variableInitValue%)=%initTarget%)", "ERROR_DICHO_INVALID_STEP_GROWTH": "Dichotomie : l'augmentation du pas pour la recherche de l'intervalle de départ est incorrecte (=0)", "ERROR_DICHO_NULL_STEP": "Dichotomie : le pas pour la recherche de l'intervalle de départ ne devrait pas être nul", + "ERROR_DICHO_TARGET_TOO_HIGH": "Dichotomie : la solution %targetSymbol%=%targetValue% est supérieure à la valeur maximale calculable %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)", + "ERROR_DICHO_TARGET_TOO_LOW": "Dichotomie : la solution %targetSymbol%=%targetValue% est inférieure à la valeur minimale calculable %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)", "ERROR_ELEVATION_ZI_LOWER_THAN_Z2": "La cote amont est plus basse que la cote aval", - "ERROR_INTERVAL_OUTSIDE": "Interval : la valeur %value% est hors de l'intervalle %interval", + "ERROR_INTERVAL_OUTSIDE": "Intervalle : la valeur %value% est hors de l'intervalle %interval%", "ERROR_INTERVAL_UNDEF": "Interval : valeur 'undefined' incorrecte", "ERROR_INVALID_AT_POSITION": "Position %s :", "ERROR_LOADING_SESSION": "Impossible de charger la session", @@ -15,7 +16,10 @@ "ERROR_MINMAXSTEP_MIN": "La valeur n'est pas dans [%s,%s[", "ERROR_MINMAXSTEP_STEP": "La valeur n'est pas dans %s", "ERROR_NEWTON_DERIVEE_NULLE": "Dérivée nulle dans un calcul par la méthode de Newton", + "ERROR_PAB_Z1_LOWER_THAN_Z2": "La cote de l'eau amont doit être supérieure à la cote de l'eau aval", + "ERROR_PAB_Z1_LOWER_THAN_UPSTREAM_WALL": "La cote de l'eau amont est trop basse pour que l'eau s'écoule à travers la première cloison", "ERROR_PARAM_MUST_BE_A_NUMBER": "Veuillez entrer une valeur numérique", + "ERROR_PARAM_MUST_BE_AT_LEAST": "La valeur doit être >= %s", "ERROR_PARAM_MUST_BE_POSITIVE": "Veuillez entrer une valeur numérique positive", "ERROR_PARAM_NULL": "La valeur du paramètre ne peut pas être NULL", "ERROR_PARAMDEF_CALC_UNDEFINED": "La calculabilité du paramètre %symbol% n'est pas définie", @@ -58,6 +62,15 @@ "INFO_DEVER_TITRE_COURT": "Déver. dénoyés", "INFO_DEVER_TITRE": "Lois de déversoirs dénoyés", "INFO_DIALOG_COMPUTED_VALUE_TITLE": "Modifier la valeur initiale", + "INFO_DIALOG_EDIT_PAB_INTERPOLATION_BOUNDS": "entre %s et %s", + "INFO_DIALOG_EDIT_PAB_N_DEVICES": "%s ouvrage(s)", + "INFO_DIALOG_EDIT_PAB_N_WALLS": "%s cloison(s) : %s ouvrage(s)", + "INFO_DIALOG_EDIT_PAB_N_WALLS_P_DEVICES": "%s cloison(s) + %s ouvrage(s) : %s ouvrages", + "INFO_DIALOG_EDIT_PAB_OPTION_VARIABLE": "Variable à modifier", + "INFO_DIALOG_EDIT_PAB_OPTION_SET_VALUE": "Valeur fixe", + "INFO_DIALOG_EDIT_PAB_OPTION_DELTA": "Delta", + "INFO_DIALOG_EDIT_PAB_OPTION_INTERPOLATE": "Interpoler", + "INFO_DIALOG_EDIT_PAB_TITLE": "Modifier les valeurs", "INFO_DIALOG_EMPTY_CURRENT_SESSION": "Vider la session courante", "INFO_DIALOG_FIX_MISSING_DEPENDENCIES": "Résoudre les dépendances", "INFO_DIALOG_FORMAT_VERSIONS_MISMATCH": "Mauvaise version du format de fichier (fichier: %s, jalhyd: %s)", @@ -88,26 +101,42 @@ "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_1": "Partiellement noyé", "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_2": "Noyé", "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_3": "Débit nul", - "INFO_FSC_FS_ADDED": "1 ouvrage ajouté", - "INFO_FSC_FS_ADDED_N_TIMES": "%s ouvrages ajoutés", - "INFO_FSC_FS_COPIED": "Ouvrage n°%s copié", - "INFO_FSC_FS_COPIED_N_TIMES": "Ouvrage n°%s copié %s fois", - "INFO_FSC_FS_MOVED": "Ouvrage n°%s déplacé", - "INFO_FSC_FS_REMOVED": "Ouvrage n°%s supprimé", + "INFO_GRAPH_BUTTON_TITLE_RESET_ZOOM": "Réinitialiser le zoom", + "INFO_GRAPH_BUTTON_TITLE_EXPORT_IMAGE": "Enregistrer l'image", + "INFO_GRAPH_BUTTON_TITLE_ENTER_FS": "Afficher en plein écran", + "INFO_GRAPH_BUTTON_TITLE_EXIT_FS": "Sortir du mode plein écran", + "INFO_DEVICE_ADDED": "1 ouvrage ajouté", + "INFO_DEVICE_ADDED_N_TIMES": "%s ouvrages ajoutés", + "INFO_DEVICE_COPIED": "Ouvrage n°%s copié", + "INFO_DEVICE_COPIED_N_TIMES": "Ouvrage n°%s copié %s fois", + "INFO_DEVICE_MOVED": "Ouvrage n°%s déplacé", + "INFO_DEVICE_REMOVED": "Ouvrage n°%s supprimé", + "INFO_WALL_ADDED": "1 cloison ajoutée", + "INFO_WALL_ADDED_N_TIMES": "%s cloisons ajoutées", + "INFO_WALL_COPIED": "Cloison n°%s copiée", + "INFO_WALL_COPIED_N_TIMES": "Cloison n°%s copiée %s fois", + "INFO_WALL_MOVED": "Cloison n°%s déplacée", + "INFO_WALL_REMOVED": "Cloison n°%s supprimée", "INFO_LECHAPTCALMON_TITRE_COURT": "Lechapt-C.", "INFO_LECHAPTCALMON_TITRE": "Lechapt-Calmon", + "INFO_LIB_ABSCISSE_CLOISON": "Abscisse de la cloison", "INFO_LIB_ALPHA": "Coefficient alpha", "INFO_LIB_ALPHA2": "Demi-angle au sommet", + "INFO_LIB_AMONT": "Amont", "INFO_LIB_AVAL": "Aval", "INFO_LIB_B": "Largeur au miroir", + "INFO_LIB_BB": "Largeur du bassin", "INFO_LIB_BETA": "Coefficient béta", "INFO_LIB_BT": "Demi-ouverture du triangle", "INFO_LIB_CD": "Coefficient de débit", "INFO_LIB_CLOISON": "Cloison n°", + "INFO_LIB_COTE": "Cote (m)", + "INFO_LIB_COTE_VANNE_LEVANTE": "Cote vanne levante", "INFO_LIB_CV": "Cv: Coefficient de vitesse d'approche", "INFO_LIB_CVQT": "CV.QT: Débit corrigé", "INFO_LIB_DH": "Chute", "INFO_LIB_DHR": "DHR: Chute résiduelle", + "INFO_LIB_DISTANCE_AMONT": "Distance depuis l'amont (m)", "INFO_LIB_EC": "EC: Énergie cinétique", "INFO_LIB_ENUM_MACRORUGOFLOWTYPE": "Type d'écoulement", "INFO_LIB_FLU": "Ligne d'eau fluviale", @@ -115,22 +144,30 @@ "INFO_LIB_FS_OUVRAGE": "Ouvrage", "INFO_LIB_FS_PARAM_CALC": "Paramètres de calcul", "INFO_LIB_FS_PARAM_HYDRO": "Paramètres hydrauliques", + "INFO_LIB_H1": "Charge", "INFO_LIB_HS": "Charge spécifique", "INFO_LIB_HSC": "Charge critique", "INFO_LIB_I-J": "Variation linéaire de l'énergie spécifique", "INFO_LIB_IMP": "Impulsion", "INFO_LIB_J": "Perte de charge", "INFO_LIB_L": "Largeur du déversoir", + "INFO_LIB_LB": "Longueur du bassin", + "INFO_LIB_LIGNE_D_EAU": "Ligne d'eau", + "INFO_LIB_MINZDV": "Cote minimale de la crête", + "INFO_LIB_MAXZDV": "Cote maximale de la crête", "INFO_LIB_OUVRAGE_Q_ENUM_STRUCTUREFLOWMODE": "Type d'écoulement", "INFO_LIB_OUVRAGE_Q_ENUM_STRUCTUREFLOWREGIME": "Régime", "INFO_LIB_OUVRAGE_Q": "Débit", + "INFO_LIB_OUVRAGE_ZDV": "Cote du seuil", "INFO_LIB_P": "Périmètre mouillé", + "INFO_LIB_PB": "Profondeur moyenne du bassin", "INFO_LIB_PR": "Précision de calcul", "INFO_LIB_PV": "Puissance volumique dissipée", "INFO_LIB_Q_GUIDETECH": "Débit Guide technique", "INFO_LIB_Q": "Débit", "INFO_LIB_QA": "Débit d'attrait", "INFO_LIB_R": "Rayon hydraulique", + "INFO_LIB_RADIER": "Radier", "INFO_LIB_S": "Surface de l'orifice", "INFO_LIB_SELECT_LOIDEBIT": "Loi de débit", "INFO_LIB_SELECT_LOIDEBIT1_KIVI": "Kindsvater-Carter et Villemonte", @@ -147,6 +184,7 @@ "INFO_LIB_V": "Vitesse moyenne", "INFO_LIB_VDEB": "Vitesse débitante", "INFO_LIB_VMAX": "Vitesse maximale", + "INFO_LIB_W": "Ouverture de vanne", "INFO_LIB_YC": "Tirant d'eau critique", "INFO_LIB_YCO": "Tirant d'eau conjugué", "INFO_LIB_YF": "Tirant d'eau fluvial", @@ -159,7 +197,7 @@ "INFO_LIB_ZDV": "Cote de la crête du déversoir ou du radier de la vanne", "INFO_LIB_ZF2": "Cote de fond aval", "INFO_LIB_ZRAM": "Cote du radier amont", - "INFO_LIB_ZRB": "Cote de radier du bassin aval", + "INFO_LIB_ZRMB": "Cote de radier mi-bassin", "INFO_LIB_ZT": "Cote haute du triangle", "INFO_LINKED_VALUE_DEVICE_RESULT": "%s (%s, ouvrage %s)", "INFO_LINKED_VALUE_DEVICE": "%s (%s, ouvrage %s)", @@ -168,6 +206,22 @@ "INFO_LINKED_VALUE_RESULT": "%s (%s)", "INFO_LINKED_VALUE_SECTION_RESULT": "%s (%s, section)", "INFO_LINKED_VALUE_SECTION": "%s (%s, section)", + "INFO_LOIDEBIT_KIVI": "Kindsvater-Carter et Villemonte", + "INFO_LOIDEBIT_WeirSubmergedLarinier": "Fente noyée (Larinier 1992)", + "INFO_LOIDEBIT_OrificeSubmerged": "Orifice noyé", + "INFO_LOIDEBIT_TriangularWeirFree": "Déversoir triangulaire dénoyé", + "INFO_LOIDEBIT_TriangularTruncWeirFree": "Déversoir triangulaire tronqué dénoyé", + "INFO_LOIDEBIT_GateCem88d": "Déversoir/Orifice Cemagref 88", + "INFO_LOIDEBIT_GateCem88v": "Déversoir/Vanne de fond Cemagref 88", + "INFO_LOIDEBIT_WeirCem88d": "Déversoir/Orifice Cemagref 88", + "INFO_LOIDEBIT_WeirCem88v": "Déversoir/Vanne de fond Cemagref 88", + "INFO_LOIDEBIT_Cunge80": "Cunge 80", + "INFO_LOIDEBIT_RectangularOrificeFree": "Vanne dénoyé", + "INFO_LOIDEBIT_RectangularOrificeSubmerged": "Vanne noyé", + "INFO_LOIDEBIT_VanLevLarinier": "Fente noyée régulée (Larinier 1992)", + "INFO_LOIDEBIT_VanLevVillemonte": "Échancrure régulée (Villemonte 1957)", + "INFO_LOIDEBIT_WeirFree": "Seuil dénoyé", + "INFO_LOIDEBIT_WeirVillemonte": "Échancrure (Villemonte 1957)", "INFO_MACRORUGO_TITRE_COURT": "Macro-rugo.", "INFO_MACRORUGO_TITRE": "Passe à macro-rugosités", "INFO_MENU_EMPTY_SESSION_TITLE": "Nouvelle session", @@ -193,8 +247,17 @@ "INFO_OPTION_VALIDATE": "Valider", "INFO_OPTION_YES": "Oui", "INFO_OUVRAGE": "Ouvrage", + "INFO_PAB_BASSIN": "Bassin", + "INFO_PAB_NUM_BASSIN": "N° de bassin", + "INFO_PAB_HEADER_TYPE": "Type", + "INFO_PAB_HEADER_PARAMETERS": "Paramètres", + "INFO_PAB_HEADER_VALUES": "Valeurs", + "INFO_PAB_CLOISON_OUVRAGE_N": "Cloison : ouvrage n° %s", + "INFO_PAB_PARAMETRES_FIXES": "Tous les paramètres doivent être fixés", "INFO_PAB_TITRE_COURT": "PAB", "INFO_PAB_TITRE": "Passe à bassins", + "INFO_PAB_TITRE_PROFIL": "Profil en long de la passe", + "INFO_PAB_TABLE": "Géométrie de la passe", "INFO_PABCHUTE_TITRE_COURT": "PAB : chute", "INFO_PABCHUTE_TITRE": "Passe à bassins : chute", "INFO_PABDIMENSIONS_TITRE_COURT": "PAB : dimensions", @@ -206,6 +269,7 @@ "INFO_PARALLELSTRUCTURE_TITRE_COURT": "Ouvrages", "INFO_PARALLELSTRUCTURE_TITRE": "Lois d'ouvrages", "INFO_PARAMFIELD_AWAITING_CALCULATION": "En attente de calcul", + "INFO_PARAMFIELD_BOUNDARY_CONDITIONS": "Conditions aux limites", "INFO_PARAMFIELD_CALCULATED": "Calculé", "INFO_PARAMFIELD_CALCULATION_FAILED": "Échec du calcul", "INFO_PARAMFIELD_GRAPH_SELECT_X_AXIS": "Variable en abscisse", @@ -238,6 +302,7 @@ "INFO_PARAMFIELD_VARIATED": "Varié", "INFO_PARAMMODE_LIST": "Liste de valeurs", "INFO_PARAMMODE_MINMAX": "Min/max", + "WARNING_PROBLEMS_ENCOUNTERED": "Des problèmes sont survenus durant le calcul (info: %info%, avertissement: %warning%, erreur: %error%)", "INFO_REGIMEUNIFORME_TITRE_COURT": "R. uniforme", "INFO_REGIMEUNIFORME_TITRE": "Régime uniforme", "INFO_REMOUS_CALCUL_FLUVIAL": "Condition limite aval >= Hauteur critique : calcul de la partie fluviale à partir de l'aval", @@ -282,9 +347,12 @@ "INFO_THEME_PASSE_NATURELLE_DESCRIPTION": "Outils de dimensionnement d'une passe à poissons de type passe naturelle ou encore appelée passe à macro-rugosités", "INFO_THEME_PASSE_NATURELLE_TITRE": "Passe naturelle", "INFO_TITREJOURNAL": "Journal de calcul", + "INFO_TITREJOURNAL_GLOBAL": "Synthèse du journal de calcul", "INFO_WELCOME_CONTENT": "<p>Le logiciel Cassiopée a été développé par l'<a href=\"https://www.afbiodiversite.fr\" target=\"_blank\">AFB</a> (Agence Française pour la Biodiversité) et <a href=\"http://g-eau.fr\" target=\"_blank\">L'UMR G-EAU</a> (UMR Gestion de l'Eau, Acteurs, Usages).</p><p>Il regroupe des outils d'aide à la conception de passes à poissons et des outils de calcul hydraulique utiles pour l'ingénierie en environnement et agriculture.</p><p>Pour plus d'informations, consulter les <a href=\"assets/docs-fr/mentions_legales.html\" target=\"_blank\">mentions légales</a> et la <a href=\"assets/docs-fr/index.html\" target=\"_blank\">documentation</a>.</p>", "INFO_WELCOME_SUBTITLE": "Modules de calcul d'hydraulique", "WARNING_REMOUS_ARRET_CRITIQUE": "Arrêt du calcul : hauteur critique atteinte à l'abscisse %x%", "WARNING_STRUCTUREKIVI_HP_TROP_ELEVE": "h/p ne doit pas être supérieur à 2,5. h/p est forcé à 2,5", - "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "La pelle du seuil doit mesurer au moins 0,1 m. Le coefficient béta est forcé à 0" + "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "La pelle du seuil doit mesurer au moins 0,1 m. Le coefficient béta est forcé à 0", + "WARNING_VANLEV_ZDV_INF_MIN": "Seuil régulé : cote minimale de seuil atteinte", + "WARNING_VANLEV_ZDV_SUP_MAX": "Seuil régulé : cote maximale de seuil atteinte" } diff --git a/src/main.ts b/src/main.ts index 9b8fff7b96bcd6f42218db26ef282901d5136450..018a6f867c32a3cbe4b9eee6682ce72c5b18614a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,8 @@ import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; import { AppModule } from "./app/app.module"; import { environment } from "./environments/environment"; +import "chartjs-plugin-zoom"; + if (environment.production) { enableProdMode(); } diff --git a/src/theme.scss b/src/theme.scss index 7c38e7153a7ffc8bddc72f8fedaaa3bb948c16b2..951487e7019af1ec6232938437127c43e6907e13 100644 --- a/src/theme.scss +++ b/src/theme.scss @@ -171,6 +171,7 @@ $mat-irstea-rouille: ( $nghyd-primary: mat-palette($mat-irstea-marine); $nghyd-accent: mat-palette($mat-irstea-ocean); $nghyd-warn: mat-palette($mat-irstea-prune); +$nghyd-error: mat-palette($mat-irstea-rouille); // $nghyd-warn: mat-palette($mat-irstea-rouille); $nghyd-theme: mat-light-theme($nghyd-primary, $nghyd-accent, $nghyd-warn); @@ -181,14 +182,29 @@ $nghyd-theme: mat-light-theme($nghyd-primary, $nghyd-accent, $nghyd-warn); $primary: map-get($nghyd-theme, primary); $accent: map-get($nghyd-theme, accent); $warn: map-get($nghyd-theme, warn); +// $error: map-get($nghyd-error); // convenience classes (functions mat-* cannot be used outside of this file) + .color-primary { color: mat-color($primary); } .bg-primary { background-color: mat-color($primary); } +.bg-primary-light { + background-color: mat-color($primary, 300); +} +.bg-primary-lighter { + background-color: mat-color($primary, 200); +} +.bg-primary-verylight { + background-color: mat-color($primary, 100); +} +.bg-primary-extralight { + background-color: mat-color($primary, 50); +} + .color-accent { color: mat-color($accent); } @@ -198,12 +214,41 @@ $warn: map-get($nghyd-theme, warn); .bg-accent-light { background-color: mat-color($accent, 300); } +.bg-accent-lighter { + background-color: mat-color($accent, 200); +} +.bg-accent-verylight { + background-color: mat-color($accent, 100); +} +.bg-accent-extralight { + background-color: mat-color($accent, 50); +} + .color-warn { color: mat-color($warn); } .bg-warn { background-color: mat-color($warn); } +.bg-warn-light { + background-color: mat-color($warn, 300); +} +.bg-warn-lighter { + background-color: mat-color($warn, 200); +} +.bg-warn-verylight { + background-color: mat-color($warn, 100); +} +.bg-warn-extralight { + background-color: mat-color($warn, 50); +} + +.color-error { + color: mat-color($nghyd-error); +} +.bg-error { + background-color: mat-color($nghyd-error); +} .mat-button-toggle-checked { background-color: mat-color($accent);