# HG changeset patch # User Thomas Junk # Date 1562761660 -7200 # Node ID 59dba489ec7b21142b6dd49b5f67d307e4b9382e # Parent 20be498adaf737c6d121c4a340e8c269ce25ed9c# Parent ccd77fe5072cd4d1c607d8b515acc3318abbd230 Merge with default diff -r 20be498adaf7 -r 59dba489ec7b .hgtags --- a/.hgtags Mon Jun 24 10:28:01 2019 +0200 +++ b/.hgtags Wed Jul 10 14:27:40 2019 +0200 @@ -7,3 +7,4 @@ 4d69b79e9df6e2b646ac60f75f6655bb3c64f6c6 v2.1.0 4d7b481e1d392cd68c48a4ad19fc2865110e724e v3-uat2 d4a5d11e23a043026a747c626d133b45047d17df v3 +ad93adaeb68827534b1b3dd96c50096b3fa9a24c v3.1-preview20190626 diff -r 20be498adaf7 -r 59dba489ec7b 3rdpartylibs.sh --- a/3rdpartylibs.sh Mon Jun 24 10:28:01 2019 +0200 +++ b/3rdpartylibs.sh Wed Jul 10 14:27:40 2019 +0200 @@ -44,6 +44,10 @@ go get -u -v gonum.org/v1/gonum/stat # BSD-3-Clause +go get -u -v github.com/sergi/go-diff/diffmatchpatch +# MIT +# Only used in tests. + # Only needed when generating SVG graphics for debugging. # go get -u -v github.com/ajstarks/svgo # Attribution 3.0 United States (CC BY 3.0 US) diff -r 20be498adaf7 -r 59dba489ec7b client/package.json --- a/client/package.json Mon Jun 24 10:28:01 2019 +0200 +++ b/client/package.json Wed Jul 10 14:27:40 2019 +0200 @@ -39,13 +39,13 @@ "debounce": "^1.2.0", "file-saver": "^2.0.2", "glob-all": "^3.1.0", - "jspdf": "^1.5.3", "locale2": "^2.2.0", "lodash.debounce": "^4.0.8", "ol": "^5.3.0", "path": "^0.12.7", "prettier": "^1.15.3", "purgecss-webpack-plugin": "^1.4.0", + "svg2pdf.js": "https://github.com/Intevation/svg2pdf.js.git#production", "v-tooltip": "^2.0.0-rc.33", "vue": "^2.5.16", "vue-clipboard2": "^0.2.1", @@ -54,6 +54,7 @@ "vue-js-toggle-button": "^1.3.0", "vue-router": "^3.0.2", "vue-snotify": "^3.2.1", + "vue-virtual-scroll-list": "^1.3.9", "vuex": "^3.0.1", "webpack-cli": "^3.1.2" }, diff -r 20be498adaf7 -r 59dba489ec7b client/src/assets/tooltip.scss --- a/client/src/assets/tooltip.scss Mon Jun 24 10:28:01 2019 +0200 +++ b/client/src/assets/tooltip.scss Wed Jul 10 14:27:40 2019 +0200 @@ -23,6 +23,7 @@ border-radius: 0.25rem; padding: 5px 10px 4px; font-size: 0.8rem; + max-width: 500px; } .tooltip .tooltip-arrow { diff -r 20be498adaf7 -r 59dba489ec7b client/src/components/App.vue --- a/client/src/components/App.vue Mon Jun 24 10:28:01 2019 +0200 +++ b/client/src/components/App.vue Wed Jul 10 14:27:40 2019 +0200 @@ -29,6 +29,10 @@ +
diff -r 20be498adaf7 -r 59dba489ec7b client/src/components/Bottlenecks.vue --- a/client/src/components/Bottlenecks.vue Mon Jun 24 10:28:01 2019 +0200 +++ b/client/src/components/Bottlenecks.vue Wed Jul 10 14:27:40 2019 +0200 @@ -144,7 +144,7 @@ .then(() => { this.$store.dispatch("map/moveToFeauture", { feature: bottleneck, - zoom: 17, + zoom: 16, preventZoomOut: true }); }); @@ -158,7 +158,7 @@ .then(() => { this.$store.dispatch("map/moveToFeauture", { feature: bottleneck, - zoom: 17, + zoom: 16, preventZoomOut: true }); }); diff -r 20be498adaf7 -r 59dba489ec7b client/src/components/Pdftool.vue --- a/client/src/components/Pdftool.vue Mon Jun 24 10:28:01 2019 +0200 +++ b/client/src/components/Pdftool.vue Wed Jul 10 14:27:40 2019 +0200 @@ -101,21 +101,23 @@ * * Fadi Abbud */ import { mapState, mapGetters } from "vuex"; -import jsPDF from "jspdf"; +import jsPDF from "jspdf-yworks"; import "@/lib/font-linbiolinum"; import { getPointResolution } from "ol/proj"; import { HTTP } from "@/lib/http"; import { displayError } from "@/lib/errors"; -import { pdfgen } from "@/lib/mixins"; +import { pdfgen, templateLoader } from "@/lib/mixins"; -var paperSizes = { +const paperSizes = { // in millimeter, landscape [width, height] a3: [420, 297], a4: [297, 210] }; +const DEFAULT_TEMPLATE = "Default"; + export default { - mixins: [pdfgen], + mixins: [pdfgen, templateLoader], name: "pdftool", data() { return { @@ -128,7 +130,7 @@ }, templates: [ { - name: "Default", + name: DEFAULT_TEMPLATE, properties: { format: "landscape", paperSize: "a4", @@ -150,7 +152,7 @@ }, { type: "northarrow", - position: "topright", + position: "topleft", offset: { x: 6, y: 4 }, size: 2 } @@ -210,24 +212,19 @@ // When a template is chosen from the dropdown, its propoerties are // applied to the rest of the form. applyTemplateToForm() { - if (this.form.template) { - HTTP.get( - "/templates/" + - this.form.template.type + - "/" + - this.form.template.name, - { - headers: { - "X-Gemma-Auth": localStorage.getItem("token"), - "Content-type": "text/xml; charset=UTF-8" - } - } + if (this.form.template && this.form.template.name !== DEFAULT_TEMPLATE) { + this.loadTemplates( + `/templates/${this.form.template.type}/${this.form.template.name}` ) .then(response => { - this.templateData = response.data.template_data; - this.form.format = this.templateData.properties.format; - this.form.paperSize = this.templateData.properties.paperSize; - this.form.resolution = this.templateData.properties.resolution; + this.prepareImages(response.data.template_data.elements).then( + values => { + values.forEach(v => { + response.data.template_data.elements[v.index].url = v.url; + }); + this.setTemplate(response.data.template_data); + } + ); }) .catch(e => { const { status, data } = e.response; @@ -236,8 +233,16 @@ message: `${status}: ${data.message || data}` }); }); + } else { + this.setTemplate(this.templates[0]); } }, + setTemplate(template) { + this.templateData = template; + this.form.format = this.templateData.properties.format; + this.form.paperSize = this.templateData.properties.paperSize; + this.form.resolution = this.templateData.properties.resolution; + }, download() { // disable button while working on it this.readyToGenerate = false; @@ -794,18 +799,9 @@ textOptions ); } - }, - getTextHeight(numberOfLines) { - return ( - numberOfLines * - ((this.pdf.doc.getFontSize() * 25.4) / parseInt(this.form.resolution)) * - this.pdf.doc.getLineHeightFactor() - ); } }, mounted() { - this.form.template = this.templates[0]; - this.templateData = this.form.template; HTTP.get("/templates/map", { headers: { "X-Gemma-Auth": localStorage.getItem("token"), @@ -814,9 +810,12 @@ }) .then(response => { if (response.data.length) { - this.templates = response.data; + this.templates = [...this.templates, ...response.data]; + this.form.template = this.templates[1]; + this.applyTemplateToForm(); + } else { this.form.template = this.templates[0]; - this.applyTemplateToForm(); + this.templateData = this.form.template; } }) .catch(e => { diff -r 20be498adaf7 -r 59dba489ec7b client/src/components/Search.vue --- a/client/src/components/Search.vue Mon Jun 24 10:28:01 2019 +0200 +++ b/client/src/components/Search.vue Wed Jul 10 14:27:40 2019 +0200 @@ -65,12 +65,17 @@ fixed-width /> - {{ entry.name }} {{ entry.locationcode }} + {{ entry.name }} + ({{ entry.location + }}, {{ entry.locationcode }}) @@ -187,6 +192,7 @@ ]), ...mapState("imports", ["startDate", "endDate"]), ...mapGetters("imports", ["filters"]), + ...mapGetters("map", ["openLayersMap"]), searchQuery: { get() { return this.$store.state.application.searchQuery; @@ -299,17 +305,56 @@ this.searchQueryIsDirty = false; }, moveToSearchResult(resultEntry) { + let zoom = 16; + if (resultEntry.type === "bottleneck") { + this.openLayersMap() + .getLayer("BOTTLENECKS") + .setVisible(true); + } + if (resultEntry.type === "rhm") { + this.openLayersMap() + .getLayer("DISTANCEMARKSAXIS") + .setVisible(true); + } + if (resultEntry.type === "gauge") { + this.openLayersMap() + .getLayer("GAUGES") + .setVisible(true); + } + if (resultEntry.type === "stretch") { + this.$store.commit( + "imports/selectedStretchId", + "stretches_geoserver." + resultEntry.id + ); + this.openLayersMap() + .getLayer("STRETCHES") + .setVisible(true); + } + if (resultEntry.type === "section") { + this.$store.commit( + "imports/selectedSectionId", + "sections_geoserver." + resultEntry.id + ); + this.openLayersMap() + .getLayer("SECTIONS") + .setVisible(true); + } + if (resultEntry.type === "city") zoom = 13; + if (resultEntry.geom.type == "Point") { - let zoom = 11; - if (resultEntry.type === "bottleneck") zoom = 17; - if (resultEntry.type === "rhm") zoom = 15; - if (resultEntry.type === "city") zoom = 13; - if (resultEntry.type === "gauge") zoom = 15; this.$store.dispatch("map/moveMap", { coordinates: resultEntry.geom.coordinates, zoom, preventZoomOut: true }); + } else if (resultEntry.geom.type == "Polygon") { + const boundingBox = [ + Math.min(...resultEntry.geom.coordinates[0].map(c => c[0])), + Math.min(...resultEntry.geom.coordinates[0].map(c => c[1])), + Math.max(...resultEntry.geom.coordinates[0].map(c => c[0])), + Math.max(...resultEntry.geom.coordinates[0].map(c => c[1])) + ]; + this.$store.dispatch("map/moveToBoundingBox", { boundingBox, zoom }); } // this.searchQuery = ""; // clear search query again this.toggleSearchbar(); diff -r 20be498adaf7 -r 59dba489ec7b client/src/components/fairway/AvailableFairwayDepth.vue --- a/client/src/components/fairway/AvailableFairwayDepth.vue Mon Jun 24 10:28:01 2019 +0200 +++ b/client/src/components/fairway/AvailableFairwayDepth.vue Wed Jul 10 14:27:40 2019 +0200 @@ -61,24 +61,22 @@ * SPDX-License-Identifier: AGPL-3.0-or-later * License-Filename: LICENSES/AGPL-3.0.txt * - * Copyright (C) 2018 by via donau + * Copyright (C) 2018, 2019 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * * Author(s): - * Thomas Junk - * Markus Kottländer - * Fadi Abbud + * * Thomas Junk + * * Markus Kottländer + * * Fadi Abbud */ import * as d3 from "d3"; import app from "@/main"; import debounce from "debounce"; -import { diagram } from "@/lib/mixins"; import { mapState } from "vuex"; import filters from "@/lib/filters.js"; -import jsPDF from "jspdf"; -import canvg from "canvg"; -import { pdfgen } from "@/lib/mixins"; +import svg2pdf from "svg2pdf.js"; +import { diagram, pdfgen, templateLoader } from "@/lib/mixins"; import { HTTP } from "@/lib/http"; import { displayError } from "@/lib/errors"; import { FREQUENCIES } from "@/store/fairwayavailability"; @@ -86,7 +84,7 @@ const hoursInDays = x => x / 24; export default { - mixins: [diagram, pdfgen], + mixins: [diagram, pdfgen, templateLoader], components: { DiagramLegend: () => import("@/components/DiagramLegend") }, @@ -120,7 +118,7 @@ { type: "diagram", position: "topleft", - offset: { x: 20, y: 60 }, + offset: { x: 20, y: 30 }, width: 290, height: 100 }, @@ -134,7 +132,7 @@ { type: "diagramlegend", position: "topleft", - offset: { x: 30, y: 160 }, + offset: { x: 30, y: 170 }, color: "black" } ] @@ -269,14 +267,16 @@ return; } if (this.form.template) { - HTTP.get("/templates/diagram/" + this.form.template.name, { - headers: { - "X-Gemma-Auth": localStorage.getItem("token"), - "Content-type": "text/xml; charset=UTF-8" - } - }) + this.loadTemplates("/templates/diagram/" + this.form.template.name) .then(response => { - this.templateData = response.data.template_data; + 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; @@ -288,119 +288,18 @@ } }, downloadPDF() { - this.pdf.doc = new jsPDF( - "l", - "mm", - this.templateData.properties.paperSize - ); - // pdf width and height in millimeter (landscape) - this.pdf.width = - this.templateData.properties.paperSize === "a3" ? 420 : 297; - this.pdf.height = - this.templateData.properties.paperSize === "a3" ? 297 : 210; - if (this.templateData) { - // default values if some are missing in template - let defaultFontSize = 11, - defaultColor = "black", - defaultWidth = 70, - defaultTextColor = "black", - defaultBorderColor = "white", - defaultBgColor = "white", - defaultRounding = 2, - defaultPadding = 2, - defaultOffset = { x: 0, y: 0 }; - this.templateData.elements.forEach(e => { - switch (e.type) { - case "diagram": { - this.addDiagram( - e.position, - e.offset || defaultOffset, - e.width, - e.height - ); - break; - } - case "diagramtitle": { - let title = `Available Fairway Depth: ${this.featureName}`; - this.addDiagramTitle( - e.position, - e.offset || defaultOffset, - e.fontsize || defaultFontSize, - e.color || defaultColor, - title - ); - break; - } - case "diagramlegend": { - this.addDiagramLegend( - e.position, - e.offset || defaultOffset, - e.color || defaultColor - ); - break; - } - case "text": { - this.addText( - e.position, - e.offset || defaultOffset, - e.width || defaultWidth, - e.fontsize || defaultFontSize, - e.color || defaultTextColor, - e.text || "" - ); - break; - } - case "image": { - this.addImage( - e.url, - e.format || "", - e.position, - e.offset || defaultOffset, - e.width || 90, - e.height || 60 - ); - break; - } - case "box": { - this.addBox( - e.position, - e.offset || defaultOffset, - e.width || 90, - e.height || 60, - e.rounding === 0 || e.rounding ? e.rounding : defaultRounding, - e.color || defaultBgColor, - e.brcolor || defaultBorderColor - ); - break; - } - case "textbox": { - this.addTextBox( - e.position, - e.offset || defaultOffset, - e.width, - e.height, - e.rounding === 0 || e.rounding ? e.rounding : defaultRounding, - e.padding || defaultPadding, - e.fontsize || defaultFontSize, - e.color || defaultTextColor, - e.background || defaultBgColor, - e.text || "", - e.brcolor || defaultBorderColor - ); - break; - } - } - }); - } + let title = `Available Fairway Depth: ${this.featureName}`; + this.generatePDF({ + templateData: this.templateData, + diagramTitle: title + }); this.pdf.doc.save(`Available Fairway Depth: ${this.featureName}`); }, addDiagram(position, offset, width, height) { let x = offset.x, y = offset.y; - var svg = this.$refs.diagramContainer.innerHTML; - if (svg) { - svg = svg.replace(/\r?\n|\r/g, "").trim(); - } + var svgElement = this.$refs.diagramContainer.firstElementChild; + // use default width,height if they are missing in the template definition if (!width) { width = this.templateData.properties.paperSize === "a3" ? 380 : 290; @@ -414,16 +313,12 @@ if (["bottomright", "bottomleft"].indexOf(position) !== -1) { y = this.pdf.height - offset.y - height; } - var canvas = document.createElement("canvas"); - canvas.width = window.innerWidth; - canvas.height = window.innerHeight / 2; - canvg(canvas, svg, { - ignoreMouse: true, - ignoreAnimation: true, - ignoreDimensions: true + + svg2pdf(svgElement, this.pdf.doc, { + xOffset: x, + yOffset: y, + scale: this.templateData.properties.paperSize === "a3" ? 0.45 : 0.18 // TODO depend on the size and aspect ration on paper }); - var imgData = canvas.toDataURL("image/png"); - this.pdf.doc.addImage(imgData, "PNG", x, y, width, height); }, addDiagramLegend(position, offset, color) { let x = offset.x, @@ -648,7 +543,7 @@ .attr("y", this.dimensions.mainHeight + this.paddingTop - 5) .attr("x", this.widthPerItem / 2) .attr("text-anchor", "middle") - .attr("font-size", "smaller"); + .attr("font-size", "9"); }, drawScaleLabel() { this.diagram @@ -657,10 +552,10 @@ .attr("text-anchor", "middle") .attr("x", 0) .attr("y", 0) - .attr("dy", "1.5em") + .attr("dy", "10") .attr( "transform", - `translate(0, ${(this.dimensions.mainHeight + this.paddingTop) / + `translate(2, ${(this.dimensions.mainHeight + this.paddingTop) / 2}), rotate(-90)` ); }, diff -r 20be498adaf7 -r 59dba489ec7b client/src/components/fairway/AvailableFairwayDepthDialogue.vue --- a/client/src/components/fairway/AvailableFairwayDepthDialogue.vue Mon Jun 24 10:28:01 2019 +0200 +++ b/client/src/components/fairway/AvailableFairwayDepthDialogue.vue Wed Jul 10 14:27:40 2019 +0200 @@ -70,7 +70,7 @@ >