view client/src/lib/mixins.js @ 5560:f2204f91d286

Join the log lines of imports to the log exports to recover data from them. Used in SR export to extract information that where in the meta json but now are only found in the log.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 09 Feb 2022 18:34:40 +0100
parents a857d6ae1264
children
line wrap: on
line source

/* 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):
 * * Markus Kottländer <markus.kottlaender@intevation.de>
 * * Fadi Abbud <fadi.abbud@intevation.de>
 * * Bernhard Reiter <bernhard.reiter@intevation.de>
 */
import jsPDF from "jspdf-yworks";
import svg2pdf from "svg2pdf.js";
import locale2 from "locale2";
import { mapState } from "vuex";
import { HTTP } from "@/lib/http";
import * as d3 from "d3";
import sanitize from "sanitize-filename";
import canvg from "canvg";

/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "[debugSVG|_]" }]*/
const debugSVG = ({ svg, svgWidth, svgHeight }) => {
  d3.select(svg)
    .append("rect")
    .attr("width", svgWidth)
    .attr("height", svgHeight)
    .attr("fill-opacity", 0)
    .attr("stroke", "#ff0000");
};

export const sortTable = {
  data() {
    return {
      sortColumn: "",
      sortDirection: "ASC",
      pageSize: 20,
      page: 1
    };
  },
  methods: {
    sortTable(sorting) {
      this.sortColumn = sorting.sortColumn;
      this.sortDirection = sorting.sortDirection;
    }
  }
};
/**
 * Since the names of LDC and HDC aren't normalized, we have to do guesswork
 * best fit is key with HDC or LDC in it
 */
export const refwaterlevels = {
  methods: {
    determineLDCHDC(refWaterLevels) {
      let HDC =
        refWaterLevels[Object.keys(refWaterLevels).find(e => /HDC/.test(e))];
      let LDC =
        refWaterLevels[Object.keys(refWaterLevels).find(e => /LDC/.test(e))];
      return { LDC, HDC };
    }
  }
};

export const diagram = {
  methods: {
    getDimensions({ svgWidth, svgHeight, main, nav }) {
      const mainMargin = 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)
      };
      const navMargin = nav || {
        top: Math.floor(0.78 * svgHeight),
        right: Math.floor(0.013 * svgWidth),
        bottom: Math.floor(0.095 * svgHeight),
        left: Math.floor(0.07 * svgWidth)
      };
      const width = Number(svgWidth) - mainMargin.left - mainMargin.right;
      const mainHeight = Number(svgHeight) - mainMargin.top - mainMargin.bottom;
      const navHeight = Number(svgHeight) - navMargin.top - navMargin.bottom;
      return { width, mainHeight, navHeight, mainMargin, navMargin };
    }
  }
};

export const pane = {
  computed: {
    paneId() {
      return this.$parent.pane.id;
    }
  }
};

export const templateLoader = {
  methods: {
    downloadFilename(type, name) {
      return `${type}-${sanitize(name).replace(
        / /g,
        "-"
      )}-${this.dateForPDF()}`;
    },
    loadTemplates(url) {
      return new Promise((resolve, reject) => {
        HTTP.get(url, {
          headers: {
            "X-Gemma-Auth": localStorage.getItem("token"),
            "Content-type": "text/xml; charset=UTF-8"
          }
        })
          .then(response => {
            resolve(response);
          })
          .catch(error => {
            reject(error);
          });
      });
    },
    prepareImages(elements) {
      /**
       * In order to render the images from the template, we need to convert
       * each image to dataURIs. Since this happens asynchronous,
       * we need to wrap each image into its own promise and only after all are
       * finished, we continue with the flow.
       */
      return new Promise(resolve => {
        const imageElementLoaders = elements.reduce((o, n, i) => {
          if (n.type === "image") {
            o.push(
              new Promise(resolve => {
                const image = new Image();
                image.onload = function() {
                  var canvas = document.createElement("canvas");
                  canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
                  canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size
                  canvas.getContext("2d").drawImage(this, 0, 0);
                  resolve({
                    index: i,
                    url: canvas.toDataURL("image/png")
                  });
                };
                let URL = n.URL;
                if (!URL) {
                  URL = this.logoForPDF
                    ? this.logoForPDF
                    : "/img/gemma-logo-for-pdf.png";
                }
                image.src = URL;
              })
            );
          }
          return o;
        }, []);
        Promise.all(imageElementLoaders).then(values => {
          resolve(values);
        });
      });
    }
  }
};

export const pdfgen = {
  computed: {
    ...mapState("application", ["logoForPDF"]),
    ...mapState("user", ["user"]),
    ...mapState("bottlenecks", ["soundingInfo"])
  },
  methods: {
    downloadImage(elementName, title) {
      const offScreen = document.querySelector("#offScreen");
      const DPI = 96;
      const svgWidth = this.millimeter2pixels(428, DPI);
      const svgHeight = this.millimeter2pixels(119, DPI);
      offScreen.style.width = `${svgWidth}px`;
      offScreen.style.height = `${svgHeight}px`;
      let zoomLevel = this.zoomStore;
      const layout = this.getPrintLayout(svgHeight, svgWidth);
      this.renderTo({
        element: offScreen,
        dimensions: this.getDimensions({
          svgWidth: svgWidth,
          svgHeight: svgHeight,
          ...layout
        }),
        zoomLevel // passing the zoom level to draw the diagram on pdf at this point
      });
      const diagramContainer = document.getElementById("offScreen");
      const { clientHeight, clientWidth } = diagramContainer;
      diagramContainer.querySelector("svg").setAttribute("width", clientWidth);
      diagramContainer
        .querySelector("svg")
        .setAttribute("height", clientHeight + 180);
      const svg = diagramContainer.querySelector("svg").outerHTML;
      const canvas = document.createElement("canvas");
      canvg(canvas, svg, { offsetY: 70 });
      const ctx = canvas.getContext("2d");
      // Draw white rectangular and place the title on it
      ctx.beginPath();
      ctx.fillStyle = "#ffffff";
      ctx.fillRect(0, 0, clientWidth, 70);
      ctx.stroke();
      ctx.fillStyle = "steelblue";
      ctx.font = "500 30px sans-serif";
      ctx.textAlign = "center";
      ctx.fillText(title, clientWidth / 2, 35);
      ctx.closePath();
      // Add diagramlegend
      this.addLegendToCanvas(ctx, {
        height: clientHeight + 100,
        width: clientWidth
      });

      const imgData = canvas.toDataURL("image/png");
      document.getElementById(elementName).setAttribute("href", imgData);
      offScreen.removeChild(offScreen.firstChild);
    },
    addDiagram(position, offset, width, height) {
      let x = offset.x,
        y = offset.y;
      const DPI = 80;
      const svgWidth = this.millimeter2pixels(width, DPI);
      const svgHeight = this.millimeter2pixels(height, DPI);
      // draw the diagram in a separated html element to get the full size
      const offScreen = document.querySelector("#offScreen");
      offScreen.style.width = `${svgWidth}px`;
      offScreen.style.height = `${svgHeight}px`;
      let zoomLevel = this.zoomStore;
      const layout = this.getPrintLayout(svgHeight, svgWidth);
      this.renderTo({
        element: offScreen,
        dimensions: this.getDimensions({
          svgWidth: svgWidth,
          svgHeight: svgHeight,
          ...layout
        }),
        zoomLevel // passing the zoom level to draw the diagram on pdf at this point
      });
      var svg = offScreen.querySelector("svg");
      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 - height;
      }
      //debugSVG({ svg, svgWidth, svgHeight });
      svg2pdf(svg, this.pdf.doc, {
        xOffset: x,
        yOffset: y,
        scale: this.pixel2millimeter(1, DPI)
      });
      offScreen.removeChild(svg);
    },
    getPaperDimensions(format) {
      const dims = {
        A3: {
          height: 297,
          width: 420
        },
        A4: {
          height: 210,
          width: 297
        }
      };
      return dims[format.toUpperCase()];
    },
    millimeter2pixels(length, dpi) {
      return (dpi * length) / 25.4;
    },
    pixel2millimeter(pixels, dpi) {
      return (pixels * 25.4) / dpi;
    },
    isrsInfo(gauge) {
      // See https://www.elwis.de/DE/Service/Daten-und-Fakten/RIS-Index/RIS-Index-node.html
      const [
        _,
        countryCode,
        loCode,
        fairwaySection,
        orc,
        hectometre
      ] = gauge.properties.isrs_code.match(
        /(\w{2})(\w{3})(\w{5})(\w{5})(\w{5})/
      );
      return {
        countryCode: countryCode,
        loCode: loCode,
        fairwaySection: fairwaySection,
        orc: orc,
        hectometre: hectometre
      };
    },
    generatePDF(params) {
      // creates a new jsPDF object into this.pdf.doc
      // will call functions that the calling context has to provide
      // as specified in the templateData
      let templateData = params["templateData"];
      let diagramTitle = params["diagramTitle"];

      this.pdf.doc = new jsPDF("l", "mm", templateData.properties.paperSize);
      // pdf width and height in millimeter (landscape)
      if (templateData.properties.paperSize === "a3") {
        this.pdf.width = 420;
        this.pdf.height = 297;
      } else {
        this.pdf.width = 297;
        this.pdf.height = 210;
      }
      // check the template elements
      if (templateData) {
        this.pdf.doc.setFont("linbiolinum", "normal");
        let defaultFontSize = 11,
          defaultColor = "black",
          defaultTextColor = "black",
          defaultBorderColor = "white",
          defaultBgColor = "white",
          defaultRounding = 2,
          defaultPadding = 2,
          defaultOffset = { x: 0, y: 0 };
        templateData.elements.forEach(e => {
          switch (e.type) {
            case "diagram": {
              this.addDiagram(
                e.position,
                e.offset || defaultOffset,
                // use default width,height if they are missing in template definition
                e.width ||
                  (this.templateData.properties.paperSize === "a3" ? 318 : 230),
                e.height ||
                  (this.templateData.properties.paperSize === "a3" ? 104 : 110)
              );
              break;
            }
            case "diagramlegend": {
              this.addDiagramLegend(
                e.position,
                e.offset || defaultOffset,
                e.color || defaultColor
              );
              break;
            }
            case "diagramtitle": {
              this.addDiagramTitle(
                e.position,
                e.offset || defaultOffset,
                e.fontsize || defaultFontSize,
                e.color || defaultColor,
                diagramTitle
              );
              break;
            }
            case "text": {
              this.addText(
                e.position,
                e.offset || defaultOffset,
                e.width,
                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;
            }
          }
        });
      }
    },
    // add text at specific coordinates and do line breaks
    addText(position, offset, width, fontSize, color, text) {
      text = this.replacePlaceholders(text);
      // split the incoming string to an array, each element is a string of
      // words in a single line
      this.pdf.doc.setFontStyle("normal");
      this.pdf.doc.setTextColor(color);
      this.pdf.doc.setFontSize(fontSize);
      // x/y defaults to offset for topleft corner (normal x/y coordinates)
      let x = offset.x;
      let y = offset.y;
      // if position is on the right, x needs to be calculate with pdf width and
      // the size of the element
      if (!width) {
        width = this.pdf.doc.getTextWidth(text);
      }
      var textLines = this.pdf.doc.splitTextToSize(text, width);
      if (
        ["topright", "topleft"].indexOf(position) !== -1 &&
        y < this.getTextHeight(1)
      ) {
        y = this.getTextHeight(1);
      }
      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(textLines.length);
      }
      this.pdf.doc.text(textLines, x, y, { baseline: "hanging" });
    },
    replacePlaceholders(text) {
      if (text.includes("{date}")) {
        text = text.replace("{date}", new Date().toLocaleString(locale2));
      }
      const shortDate = d => {
        return (
          (d.getDate() < 10 ? "0" : "") +
          d.getDate() +
          "." +
          (d.getMonth() + 1 < 10 ? "0" : "") +
          (d.getMonth() + 1) +
          "." +
          d.getFullYear()
        );
      };
      const hasVisibleSurvey =
        this.soundingInfo &&
        this.bottleneckForPrint &&
        this.selectedSurvey &&
        this.openLayersMap()
          .getLayer("BOTTLENECKISOLINE")
          .getVisible();
      if (text.includes("{surveydate}") && hasVisibleSurvey) {
        const dateFromSurvey = new Date(this.selectedSurvey["date_info"]);
        let dt = shortDate(dateFromSurvey);
        text = text.replace("{surveydate}", dt.toLocaleString(locale2));
      } else {
        let dt = shortDate(new Date());
        text = text.replace("{surveydate}", dt.toLocaleString(locale2));
      }
      // get only day,month and year from the Date object
      if (text.includes("{date-minor}")) {
        var date = new Date();
        let dt = shortDate(date);
        text = text.replace("{date-minor}", dt.toLocaleString(locale2));
      }
      if (text.includes("{user}")) {
        text = text.replace("{user}", this.user);
      }
      if (text.includes("{zpg-exception}")) {
        // Print the text followed by "zpg-exception" if this value set to true
        if (
          this.soundingInfo &&
          this.soundingInfo.number > 0 &&
          this.soundingInfo.feature.properties.zpg_exception
        ) {
          text = text.replace("{zpg-exception}", "");
          // Otherwise nothing to print
        } else {
          text = "";
        }
      }
      return text;
    },
    addImage(url, format, position, offset, width, height) {
      let x = offset.x;
      let y = offset.y;
      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 - height;
      }
      let image = new Image();
      image.src = url;
      if (format === "") {
        let tmp = image.src.split(".");
        format = tmp[tmp.length - 1].toUpperCase();
      }
      this.pdf.doc.addImage(image, format, x, y, width, height);
    },
    // add text at specific coordinates with a background box
    addBox(position, offset, width, height, rounding, color, brcolor) {
      // x/y defaults to offset for topleft corner (normal x/y coordinates)
      let x = offset.x;
      let y = offset.y;

      // if position is on the right, x needs to be calculate with pdf width and
      // the size of the element
      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 - height;
      }
      this.addRoundedBox(x, y, width, height, color, rounding, brcolor);
    },
    getTextHeight(numberOfLines) {
      // Return estimated height in mm.

      // FontSize is given in desktop publishing points defined as 1/72 inch.
      // aka 25.4 / 72 mm
      let fontSize = this.pdf.doc.getFontSize();
      let lineHeightFactor = 1.15; // default from jspdf-yworks 2.0.2
      if (typeof this.pdf.doc.getLineHeightFactor !== "undefined") {
        lineHeightFactor = this.pdf.doc.getLineHeightFactor();
      }
      return numberOfLines * fontSize * (25.4 / 72) * lineHeightFactor;
    },
    // title for diagram
    addDiagramTitle(position, offset, size, color, text) {
      let x = offset.x,
        y = offset.y;
      this.pdf.doc.setFontSize(size);
      this.pdf.doc.setFontStyle("bold");
      this.pdf.doc.setTextColor(color);
      let width =
        (this.pdf.doc.getStringUnitWidth(text) * size) / (72 / 25.6) + size / 2;
      // if position is on the right, x needs to be calculate with pdf width and
      // the size of the element
      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(1);
      }
      this.pdf.doc.text(text, x, y, { baseline: "hanging" });
    },
    addRoundedBox(x, y, w, h, color, rounding, brcolor) {
      this.pdf.doc.setDrawColor(brcolor);
      this.pdf.doc.setFillColor(color);
      this.pdf.doc.roundedRect(x, y, w, h, rounding, rounding, "FD");
    },
    addTextBox(
      position,
      offset,
      width,
      height,
      rounding,
      padding,
      fontSize,
      color,
      background,
      text,
      brcolor
    ) {
      this.pdf.doc.setFontSize(fontSize);
      text = this.replacePlaceholders(text);

      if (!width) {
        width = this.pdf.doc.getTextWidth(text) + 2 * padding;
      }
      let textWidth = width - 2 * padding;
      let textLines = this.pdf.doc.splitTextToSize(text, textWidth);
      if (!height) {
        height = this.getTextHeight(textLines.length) + 2 * padding;
      }
      this.addBox(
        position,
        offset,
        width,
        height,
        rounding,
        background,
        brcolor
      );
      let yForText =
        ["bottomright", "bottomleft"].indexOf(position) !== -1
          ? offset.y
          : offset.y + height - this.getTextHeight(textLines.length);
      this.addText(
        position,
        { x: offset.x + padding, y: yForText },
        textWidth,
        fontSize,
        color,
        text
      );
    },
    dateForPDF() {
      return new Date()
        .toISOString()
        .slice(0, 10)
        .replace(/-/g, "");
    }
  }
};