Mercurial > gemma
view client/src/components/fairway/Fairwayprofile.vue @ 4231:6f31a99cd92d
clinet: fix translations process and update source strings
* move strings for translations from *.po files to the component itself to let gettext() mark only the strings without the html elements.
(make makemessages complains to have html elements in the .po files and stops the process).
author | Fadi Abbud <fadi.abbud@intevation.de> |
---|---|
date | Wed, 21 Aug 2019 11:13:12 +0200 |
parents | a41fd26c1644 |
children | 51aae07d5f7b |
line wrap: on
line source
<template> <div class="d-flex flex-column flex-fill"> <UIBoxHeader icon="chart-area" :title="title" :closeCallback="close" /> <div class="d-flex flex-fill" v-if="openLayersMap()"> <DiagramLegend> <div class="legend"> <span style="background-color: #5995ff; width: 20px; height: 20px;" ></span> Water </div> <div class="legend"> <span :style=" 'width: 16px; height: 16px; background-color: rgba(' + this.getLayerStyle(1).fillColor.join(',') + ',' + this.getLayerStyle(1).fillOpacity + '); border: dotted 2px rgba(' + this.getLayerStyle(1).strokeColor.join(',') + ',' + this.getLayerStyle(1).strokeOpacity + '); background-clip: padding-box; box-sizing: content-box;' " ></span> Fairway (LOS 1) </div> <div class="legend"> <span :style=" 'width: 16px; height: 16px; background-color: rgba(' + this.getLayerStyle(2).fillColor.join(',') + ',' + this.getLayerStyle(2).fillOpacity + '); border: dashed 2px rgba(' + this.getLayerStyle(2).strokeColor.join(',') + ',' + this.getLayerStyle(2).strokeOpacity + '); background-clip: padding-box; box-sizing: content-box;' " ></span> Fairway (LOS 2) </div> <div class="legend"> <span :style=" 'width: 16px; height: 16px; background-color: rgba(' + this.getLayerStyle(3).fillColor.join(',') + ',' + this.getLayerStyle(3).fillOpacity + '); border: solid 2px rgba(' + this.getLayerStyle(3).strokeColor.join(',') + ',' + this.getLayerStyle(3).strokeOpacity + '); background-clip: padding-box; box-sizing: content-box;' " ></span> Fairway (LOS 3) </div> <div class="legend"> <span style="width: 14px; height: 14px; background-color: #4a2f06; border: solid 3px black; background-clip: padding-box; box-sizing: content-box;" ></span> Sediment </div> <div class="legend"> <span style="width: 14px; height: 14px; background-color: rgba(74, 47, 6, 0.6); border: solid 3px #943007; background-clip: padding-box; box-sizing: content-box;" ></span> Sediment (Compare) </div> <div> <select v-model="form.template" @change="applyChange" class="form-control d-block custom-select-sm w-100" > <option v-for="template in templates" :value="template" :key="template.name" > {{ template.name }} </option> </select> <button @click="downloadPDF" type="button" class="btn btn-sm btn-info d-block w-100 mt-2" > <translate>Export to PDF</translate> </button> </div> </DiagramLegend> <div ref="pdfContainer" id="pdfContainer" class="d-flex flex-fill justify-content-center align-items-center diagram-container position-relative" > <div class="direction-indicator"></div> <div v-if="!fairwayData"> <translate>No data available.</translate> </div> </div> </div> </div> </template> <style lang="sass" scoped> .direction-indicator width: 70px height: 0 border-top: dashed 2px #333 position: absolute bottom: 50px left: 115px margin-left: -35px &::after content: "" width: 0 height: 0 border-width: 10px border-top-width: 5px border-bottom-width: 5px border-style: solid border-color: transparent border-left-color: #333 position: absolute right: -20px top: -6px </style> <script> /* This is Free Software under GNU Affero General Public License v >= 3.0 * without warranty, see README.md and license for details. * * SPDX-License-Identifier: AGPL-3.0-or-later * License-Filename: LICENSES/AGPL-3.0.txt * * Copyright (C) 2018, 2019 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * * Author(s): * Thomas Junk <thomas.junk@intevation.de> * Markus Kottländer <markus.kottlaender@intevation.de> * Fadi Abbud <fadi.abbud@intevation.de> */ import * as d3 from "d3"; import { mapState, mapGetters } from "vuex"; import debounce from "debounce"; import { diagram, pdfgen, templateLoader } from "@/lib/mixins"; import { HTTP } from "@/lib/http"; import { displayError } from "@/lib/errors"; import { defaultDiagramTemplate } from "@/lib/DefaultDiagramTemplate"; const GROUND_COLOR = "#4A2F06"; const WATER_COLOR = "#005DFF"; export default { mixins: [diagram, pdfgen, templateLoader], name: "fairwayprofile", components: { DiagramLegend: () => import("@/components/DiagramLegend") }, data() { return { resizeListenerFunction: null, width: null, height: null, form: { template: null }, templates: [], defaultTemplate: defaultDiagramTemplate, pdf: { doc: null, width: 32, height: 297 }, templateData: null }; }, computed: { ...mapGetters("map", ["openLayersMap"]), ...mapGetters("fairwayprofile", ["totalLength"]), ...mapState("fairwayprofile", [ "additionalSurvey", "currentProfile", "startPoint", "endPoint", "fairwayData", "maxAlt", "selectedWaterLevel" ]), ...mapState("bottlenecks", ["selectedSurvey", "selectedBottleneck"]), ...mapState("application", ["paneSetup"]), title() { let dates = [this.selectedSurvey.date_info]; let waterlevelLabel = this.selectedWaterLevel === "ref" ? this.selectedSurvey.depth_reference : "Current"; if (this.additionalSurvey) dates.push(this.additionalSurvey.date_info); dates.map(d => this.$options.filters.dateTime(d, true)); return `${this.$gettext("Fairwayprofile")}: ${ this.selectedBottleneck } (${dates.join( ", " )}) WL: ${waterlevelLabel} (${this.$options.filters.waterlevel( this.waterlevel )} m)`; }, currentData() { if ( !this.selectedSurvey || !this.currentProfile.hasOwnProperty(this.selectedSurvey.date_info) ) return []; return this.currentProfile[this.selectedSurvey.date_info].points; }, additionalData() { if ( !this.additionalSurvey || !this.currentProfile.hasOwnProperty(this.additionalSurvey.date_info) ) return []; return this.currentProfile[this.additionalSurvey.date_info].points; }, bottleneck() { return this.openLayersMap() .getLayer("BOTTLENECKS") .getSource() .getFeatures() .find(f => f.get("objnam") === this.selectedBottleneck); }, waterlevel() { return this.selectedWaterLevel === "ref" ? this.refWaterlevel : this.bottleneck.get("gm_waterlevel"); }, refWaterlevel() { return this.selectedSurvey.waterlevel_value; } }, watch: { currentData() { this.drawDiagram(); }, additionalData() { this.drawDiagram(); }, width() { this.drawDiagram(); }, height() { this.drawDiagram(); }, waterLevels() { this.drawDiagram(); }, selectedWaterLevel() { this.drawDiagram(); }, fairwayData() { this.drawDiagram(); }, selectedBottleneck() { this.$store.commit("application/paneSetup", "DEFAULT"); } }, methods: { close() { this.$store.commit( "application/paneSetup", this.paneSetup === "COMPARESURVEYS_FAIRWAYPROFILE" ? "COMPARESURVEYS" : "DEFAULT" ); this.$store.dispatch("fairwayprofile/clearCurrentProfile"); }, getLayerStyle(los) { let style = this.openLayersMap() .getLayer("FAIRWAYDIMENSIONSLOS" + los) .getStyle()()[0]; // use spread operator to clone arrays let fillColor = [...style.getFill().getColor()]; let fillOpacity = fillColor.pop(); let strokeColor = [...style.getStroke().getColor()]; let strokeOpacity = strokeColor.pop(); let strokeDash = style.getStroke().getLineDash(); return { fillColor, fillOpacity, strokeColor, strokeOpacity, strokeDash }; }, applyChange() { if (this.form.template.hasOwnProperty("properties")) { this.templateData = this.defaultTemplate; return; } if (this.form.template) { this.loadTemplates("/templates/diagram/" + this.form.template.name) .then(response => { this.prepareImages(response.data.template_data.elements).then( values => { values.forEach(v => { response.data.template_data.elements[v.index].url = v.url; }); this.templateData = response.data.template_data; } ); }) .catch(e => { const { status, data } = e.response; displayError({ title: this.$gettext("Backend Error"), message: `${status}: ${data.message || data}` }); }); } }, downloadPDF() { let fairwayInfo = this.selectedBottleneck + " (" + this.selectedSurvey.date_info + ")"; this.generatePDF({ templateData: this.templateData, diagramTitle: fairwayInfo }); this.pdf.doc.save( this.title.replace(/\s/g, "_").replace(/[():,]/g, "") + ".pdf" ); }, // Diagram legend addDiagramLegend(position, offset, color) { let x = offset.x, y = offset.y; this.pdf.doc.setFontSize(10); let width = (this.pdf.doc.getStringUnitWidth("Sediment (Compare)") * 10) / (72 / 25.6) + 4; if (["topright", "bottomright"].indexOf(position) !== -1) { x = this.pdf.width - offset.x - width; } if (["bottomright", "bottomleft"].indexOf(position) !== -1) { y = this.pdf.height - offset.y - this.getTextHeight(8); } this.pdf.doc.setTextColor(color); this.pdf.doc.setDrawColor("#5995ff"); this.pdf.doc.setFillColor("#5995ff"); this.pdf.doc.circle(x, y, 2, "FD"); this.pdf.doc.text(x + 3, y + 1, "Water"); this.pdf.doc.setLineDashPattern([0.8], 0); this.pdf.doc.setDrawColor("#0000ff"); this.pdf.doc.setFillColor("#fcfacc"); this.pdf.doc.circle(x, y + 5, 2, "FD"); this.pdf.doc.text(x + 3, y + 6, "Fairway (LOS 1)"); this.pdf.doc.setLineDashPattern([1.8], 0); this.pdf.doc.setFillColor("#fdfce5"); this.pdf.doc.circle(x, y + 10, 2, "FD"); this.pdf.doc.text(x + 3, y + 11, "Fairway (LOS 2)"); this.pdf.doc.setLineDashPattern([], 0); this.pdf.doc.setFillColor("#ffffff"); this.pdf.doc.circle(x, y + 15, 2, "FD"); this.pdf.doc.text(x + 3, y + 16, "Fairway (LOS 3)"); this.pdf.doc.setDrawColor("black"); this.pdf.doc.setFillColor("#4a2e06"); this.pdf.doc.circle(x, y + 20, 2, "FD"); this.pdf.doc.text(x + 3, y + 21, "Sediment"); this.pdf.doc.setDrawColor("#943007"); this.pdf.doc.setFillColor("#928269"); this.pdf.doc.circle(x, y + 25, 2, "FD"); this.pdf.doc.text(x + 3, y + 26, "Sediment (Compare)"); }, getPrintLayout(svgHeight, svgWidth) { return { main: { top: Math.floor(0.08 * svgHeight), right: Math.floor(0.08 * svgWidth), bottom: Math.floor(0.2 * svgHeight), left: Math.floor(0.08 * svgWidth) } }; }, drawDiagram() { d3.select(".diagram-container svg").remove(); this.scaleFairwayProfile(); if (!this.height || !this.width) return; // do not try to render when height and width are unknown const layout = this.getPrintLayout(this.height, this.width); this.renderTo({ element: ".diagram-container", dimensions: this.getDimensions({ svgWidth: this.width, svgHeight: this.height, ...layout }) }); }, renderTo({ element, dimensions }) { let svg = d3.select(element).append("svg"); svg.attr("width", "100%"); svg.attr("height", "100%"); const width = dimensions.width; const height = dimensions.mainHeight; const offsetY = 15; const currentData = this.currentData; const additionalData = this.additionalData; const { xScale, yScaleRight, graph } = this.generateScalesAndGraph({ svg, height, width, dimensions, offsetY }); this.drawWaterlevel({ graph, xScale, yScaleRight, height, offsetY }); this.drawLabels({ graph, dimensions }); if (currentData) { this.drawProfile({ graph, xScale, yScaleRight, currentData, height, color: GROUND_COLOR, strokeColor: "black", opacity: 1, offsetY }); } if (additionalData) { this.drawProfile({ graph, xScale, yScaleRight, currentData: additionalData, height, color: GROUND_COLOR, strokeColor: "#943007", opacity: 0.6, offsetY }); } this.drawFairway({ graph, xScale, yScaleRight, offsetY }); }, drawFairway({ graph, xScale, yScaleRight, offsetY }) { if (this.fairwayData === undefined) { return; } for (let data of this.fairwayData) { data.coordinates.forEach(coordinates => { const [startPoint, endPoint, depth] = coordinates; let fairwayArea = d3 .area() .x(function(d) { return xScale(d.x); }) .y0(yScaleRight(0)) .y1(function(d) { return yScaleRight(d.y); }); graph .append("path") .datum([{ x: startPoint, y: depth }, { x: endPoint, y: depth }]) .attr( "fill", `#${this.getLayerStyle(data.los) .fillColor.map(x => { if (x < 10) return "0" + x.toString(16); return x.toString(16); }) .join("")}` ) .attr("fill-opacity", this.getLayerStyle(data.los).fillOpacity) .attr( "stroke", `#${this.getLayerStyle(data.los) .strokeColor.map(x => { if (x < 10) return "0" + x.toString(16); return x.toString(16); }) .join("")}` ) .attr("stroke-opacity", this.getLayerStyle(data.los).strokeOpacity) .attr("stroke-dasharray", this.getLayerStyle(data.los).strokeDash) .attr("d", fairwayArea) .attr("transform", `translate(0 ${-offsetY})`); }); } }, drawLabels({ graph, dimensions }) { graph .append("text") .attr("transform", ["rotate(-90)"]) .attr("y", dimensions.width + Math.floor(0.06 * dimensions.width)) .attr("x", -dimensions.mainHeight / 2) .attr("fill", "black") .style("text-anchor", "middle") .text("Depth [m]"); graph .append("text") .attr("transform", ["rotate(-90)"]) .attr("y", -1 * Math.floor(0.065 * dimensions.width)) .attr("x", -dimensions.mainHeight / 2) .attr("fill", "black") .style("text-anchor", "middle") .text("Waterlevel [m]"); graph .append("text") .attr("y", 0) .attr("x", 0) .attr("dy", "1em") .attr("fill", "black") .style("text-anchor", "middle") .attr("transform", [ `translate(${dimensions.width / 2} ${dimensions.mainHeight})`, "rotate(0)" ]) .text("Width [m]"); }, generateScalesAndGraph({ svg, height, width, dimensions, offsetY }) { let xScale = d3 .scaleLinear() .domain([0, this.totalLength]) .rangeRound([0, width]); let yScaleRight = d3 .scaleLinear() .domain([ this.maxAlt * 1.1 + Math.abs(this.waterlevel - this.refWaterlevel) / 100, -(this.maxAlt * 0.1) ]) .rangeRound([height, 0]); let yScaleLeft = d3 .scaleLinear() .domain([ this.waterlevel - (this.maxAlt * 100 + Math.abs(this.waterlevel - this.refWaterlevel)), this.waterlevel + this.maxAlt * 0.1 * 100 ]) .rangeRound([height, 0]); let xAxis = d3.axisBottom(xScale).ticks(5); let yAxisRight = d3.axisRight(yScaleRight); let yAxisLeft = d3 .axisLeft(yScaleLeft) .tickFormat(d => this.$options.filters.waterlevel(d)); let graph = svg .append("g") .attr( "transform", "translate(" + dimensions.mainMargin.left + "," + dimensions.mainMargin.top + ")" ); graph .append("g") .attr("transform", `translate(0 ${height - offsetY})`) .call(xAxis) .selectAll(".tick text") .attr("fill", "black") .select(function() { return this.parentNode; }) .selectAll(".tick line") .attr("stroke", "black"); graph .append("g") .attr("transform", `translate(${width} ${-offsetY})`) .call(yAxisRight) .selectAll(".tick text") .attr("fill", "black") .select(function() { return this.parentNode; }) .selectAll(".tick line") .attr("stroke", "black"); graph .append("g") .attr("transform", `translate(0 ${-offsetY})`) .call(yAxisLeft) .selectAll(".tick text") .attr("fill", "black") .select(function() { return this.parentNode; }) .selectAll(".tick line") .attr("stroke", "black"); graph.selectAll(".domain").attr("stroke", "black"); return { xScale, yScaleRight, graph }; }, drawWaterlevel({ graph, xScale, yScaleRight, height, offsetY }) { let waterArea = d3 .area() .x(function(d) { return xScale(d.x); }) .y0(height) .y1(function(d) { return yScaleRight(d.y); }); graph .append("path") .datum([{ x: 0, y: 0 }, { x: this.totalLength, y: 0 }]) .attr("fill-opacity", 0.65) .attr("fill", WATER_COLOR) .attr("stroke", "transparent") .attr("d", waterArea) .attr("transform", `translate(0 ${-offsetY})`); }, drawProfile({ graph, xScale, yScaleRight, currentData, height, color, strokeColor, opacity, offsetY }) { for (let part of currentData) { let profileLine = d3 .line() .x(d => { return xScale(d.x); }) .y(d => yScaleRight( d.y + Math.abs(this.waterlevel - this.refWaterlevel) / 100 ) ); let profileArea = d3 .area() .x(function(d) { return xScale(d.x); }) .y0(height) .y1(d => yScaleRight( d.y + Math.abs(this.waterlevel - this.refWaterlevel) / 100 ) ); graph .append("path") .datum(part) .attr("fill", color) .attr("stroke", "transparent") .attr("fill-opacity", opacity) .attr("d", profileArea) .attr("transform", `translate(0 ${-offsetY})`); graph .append("path") .datum(part) .attr("fill", "none") .attr("stroke", strokeColor) .attr("stroke-linejoin", "round") .attr("stroke-linecap", "round") .attr("stroke-width", 3) .attr("stroke-opacity", opacity) .attr("fill-opacity", 0) .attr("d", profileLine) .attr("transform", `translate(0 ${-offsetY})`); } }, scaleFairwayProfile() { if (!document.querySelector(".diagram-container")) return; const clientHeight = document.querySelector(".diagram-container") .clientHeight; const clientWidth = document.querySelector(".diagram-container") .clientWidth; if (!clientHeight || !clientWidth) return; this.height = clientHeight; this.width = clientWidth; } }, created() { this.resizeListenerFunction = debounce(this.drawDiagram, 100); window.addEventListener("resize", this.resizeListenerFunction); }, mounted() { // Nasty but necessary if we don't want to use the updated hook to re-draw // the diagram because this would re-draw it also for irrelevant reasons. // In this case we need to wait for the child component (DiagramLegend) to // render. According to the docs (https://vuejs.org/v2/api/#mounted) this // should be possible with $nextTick() but it doesn't work because it does // not guarantee that the DOM is not only updated but also re-painted on the // screen. setTimeout(this.drawDiagram, 150); this.templates[0] = this.defaultTemplate; this.form.template = this.templates[0]; this.templateData = this.form.template; HTTP.get("/templates/diagram", { headers: { "X-Gemma-Auth": localStorage.getItem("token"), "Content-type": "text/xml; charset=UTF-8" } }) .then(response => { if (response.data.length) { this.templates = response.data; this.form.template = this.templates[0]; this.templates[this.templates.length] = this.defaultTemplate; this.applyChange(); } }) .catch(e => { const { status, data } = e.response; displayError({ title: this.$gettext("Backend Error"), message: `${status}: ${data.message || data}` }); }); }, updated() { this.drawDiagram(); }, destroyed() { window.removeEventListener("resize", this.resizeListenerFunction); } }; </script>