From 930fc0644bf3bc4b32562e586aff7768da060523 Mon Sep 17 00:00:00 2001
From: teh_coderer <me@tehcoderer.com>
Date: Thu, 13 Apr 2023 01:26:18 -0400
Subject: [PATCH 1/5] init

---
 openbb_terminal/core/plots/plotly.html   | 21 +++++++++++++++++-
 openbb_terminal/core/plots/web/main.js   | 10 ++++++++-
 openbb_terminal/core/plots/web/popups.js | 28 +++++++++++++++++++++++-
 3 files changed, 56 insertions(+), 3 deletions(-)

diff --git a/openbb_terminal/core/plots/plotly.html b/openbb_terminal/core/plots/plotly.html
index 070eb5cea66c..6d9475dbbbc6 100644
--- a/openbb_terminal/core/plots/plotly.html
+++ b/openbb_terminal/core/plots/plotly.html
@@ -160,6 +160,23 @@
 
         <!-- Title Popup -->
         <div class="popup_content" id="popup_title"></div>
+
+        <!-- Download Popup -->
+        <div class="popup_content" id="popup_download">
+          <p>File saved to:</p>
+          <p id="download_path"></p>
+          <button class="_btn" id="openfile_button" style="margin-top: 20px">
+            Open File
+          </button>
+          <button class="_btn" id="openfolder_button">Open Folder</button>
+          <button
+            class="_btn-tertiary"
+            id="close_button"
+            onclick="closePopup()"
+          >
+            Close
+          </button>
+        </div>
       </div>
     </div>
 
@@ -248,12 +265,14 @@
           CSV_DIV = document.getElementById("popup_csv");
           TEXT_DIV = document.getElementById("popup_text");
           TITLE_DIV = document.getElementById("popup_title");
+          DOWNLOAD_DIV = document.getElementById("popup_download");
           OpenBBMain(
             window.plotly_figure,
             CHART_DIV,
             CSV_DIV,
             TEXT_DIV,
-            TITLE_DIV
+            TITLE_DIV,
+            DOWNLOAD_DIV
           );
         }
       }, 20);
diff --git a/openbb_terminal/core/plots/web/main.js b/openbb_terminal/core/plots/web/main.js
index 548636e64c51..86d1086a03da 100644
--- a/openbb_terminal/core/plots/web/main.js
+++ b/openbb_terminal/core/plots/web/main.js
@@ -32,12 +32,20 @@ const non_blocking = (func, delay) => {
   };
 };
 
-function OpenBBMain(plotly_figure, chartdiv, csvdiv, textdiv, titlediv) {
+function OpenBBMain(
+  plotly_figure,
+  chartdiv,
+  csvdiv,
+  textdiv,
+  titlediv,
+  downloaddiv
+) {
   // Main function that plots the graphs and initializes the bar menus
   globals.CHART_DIV = chartdiv;
   globals.TITLE_DIV = titlediv;
   globals.TEXT_DIV = textdiv;
   globals.CSV_DIV = csvdiv;
+  globals.DOWNLOAD_DIV = downloaddiv;
   console.log("main.js loaded");
   console.log("plotly_figure", plotly_figure);
   let graphs = plotly_figure;
diff --git a/openbb_terminal/core/plots/web/popups.js b/openbb_terminal/core/plots/web/popups.js
index bfb90633983f..5e1ab8ac0b56 100644
--- a/openbb_terminal/core/plots/web/popups.js
+++ b/openbb_terminal/core/plots/web/popups.js
@@ -183,9 +183,35 @@ function get_popup(data = null, popup_id = null) {
       `;
     popup = globals.TEXT_DIV;
     console.log("upload");
+  } else if (popup_id == "download") {
+    popup = globals.DOWNLOAD_DIV;
+
+    popup.querySelector("#download_path").innerHTML = `
+    <textarea style="${style} width: 100%; max-width: 100%; max-height: 200px;
+    margin-top: 8px;" rows="4" cols="50" value="${data}"
+      placeholder="Enter text here">${data}</textarea><br>
+      `;
+
+    openfile_button = popup.querySelector("#openfile_button");
+    openfile_button.onclick = function () {
+      window.ipc.postMessage(`#OPEN:${data}`);
+    };
+
+    openfolder_button = popup.querySelector("#openfolder_button");
+    openfolder_button.onclick = function () {
+      folder_path = data.split("\\").slice(0, -1).join("\\");
+      window.ipc.postMessage(`#OPEN:${folder_path}`);
+    };
+
+    globals.DOWNLOAD_DIV.style.display = "inline-block";
   }
 
-  let popup_divs = [globals.TITLE_DIV, globals.TEXT_DIV, globals.CSV_DIV];
+  let popup_divs = [
+    globals.TITLE_DIV,
+    globals.TEXT_DIV,
+    globals.CSV_DIV,
+    globals.DOWNLOAD_DIV,
+  ];
   popup_divs.forEach(function (div) {
     if (div.id != popup.id) {
       div.style.display = "none";

From b1fc9d30fe6e7ea130282c45530445a969caa0cd Mon Sep 17 00:00:00 2001
From: teh_coderer <me@tehcoderer.com>
Date: Thu, 13 Apr 2023 02:09:20 -0400
Subject: [PATCH 2/5] Update plotly.html

---
 openbb_terminal/core/plots/plotly.html | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/openbb_terminal/core/plots/plotly.html b/openbb_terminal/core/plots/plotly.html
index 6d9475dbbbc6..0c0a887f1cc6 100644
--- a/openbb_terminal/core/plots/plotly.html
+++ b/openbb_terminal/core/plots/plotly.html
@@ -165,17 +165,17 @@
         <div class="popup_content" id="popup_download">
           <p>File saved to:</p>
           <p id="download_path"></p>
-          <button class="_btn" id="openfile_button" style="margin-top: 20px">
-            Open File
-          </button>
-          <button class="_btn" id="openfolder_button">Open Folder</button>
-          <button
-            class="_btn-tertiary"
-            id="close_button"
-            onclick="closePopup()"
-          >
-            Close
-          </button>
+          <div style="float: right; margin-top: 20px">
+            <button class="_btn" id="openfile_button">Open File</button>
+            <button class="_btn" id="openfolder_button">Open Folder</button>
+            <button
+              class="_btn-tertiary"
+              id="close_button"
+              onclick="closePopup()"
+            >
+              Close
+            </button>
+          </div>
         </div>
       </div>
     </div>

From 053342d6af87e4db6c74277183e6d4bf11f7f2f0 Mon Sep 17 00:00:00 2001
From: teh_coderer <me@tehcoderer.com>
Date: Thu, 13 Apr 2023 12:26:44 -0400
Subject: [PATCH 3/5] save as

---
 openbb_terminal/core/plots/web/bar_menus.js | 88 +++++++++++++++------
 openbb_terminal/core/plots/web/main.js      | 55 ++++++++++---
 2 files changed, 110 insertions(+), 33 deletions(-)

diff --git a/openbb_terminal/core/plots/web/bar_menus.js b/openbb_terminal/core/plots/web/bar_menus.js
index 93f4146afb00..81c911aeb6e2 100644
--- a/openbb_terminal/core/plots/web/bar_menus.js
+++ b/openbb_terminal/core/plots/web/bar_menus.js
@@ -234,7 +234,7 @@ function uploadImage() {
     });
 }
 
-function downloadImage(filename, extension) {
+async function downloadImage(filename, extension, writable = undefined) {
   const loader = document.getElementById("loader");
   loader.classList.add("show");
 
@@ -247,20 +247,38 @@ function downloadImage(filename, extension) {
     // } else if (extension == "svg") {
     //   imageDownload = domtoimage.toSvg;
   } else if (["svg", "pdf"].includes(extension)) {
-    Plotly.downloadImage(globals.CHART_DIV, {
-      format: "svg",
-      height: globals.CHART_DIV.clientHeight,
-      width: globals.CHART_DIV.clientWidth,
-      filename: filename,
-    });
+    if (window.showSaveFilePicker && writable) {
+      blob = await Plotly.toImage(globals.CHART_DIV, {
+        format: "svg",
+        height: globals.CHART_DIV.clientHeight,
+        width: globals.CHART_DIV.clientWidth,
+      }).then(async function (dataUrl) {
+        blob = await fetch(dataUrl).then((r) => r.blob());
+        await writable.write(blob);
+        await writable.close();
+      });
+    } else {
+      Plotly.downloadImage(globals.CHART_DIV, {
+        format: "svg",
+        height: globals.CHART_DIV.clientHeight,
+        width: globals.CHART_DIV.clientWidth,
+        filename: filename,
+      });
+    }
     return;
   } else {
     console.log("Invalid extension");
     return;
   }
   imageDownload(document.getElementById("openbb_container"))
-    .then(function (dataUrl) {
-      downloadURI(dataUrl, filename + "." + extension);
+    .then(async function (dataUrl) {
+      if (window.showSaveFilePicker && writable) {
+        blob = await fetch(dataUrl).then((r) => r.blob());
+        await writable.write(blob);
+        await writable.close();
+      } else {
+        downloadURI(dataUrl, filename + "." + extension);
+      }
       loader.classList.remove("show");
     })
     .catch(function (error) {
@@ -279,11 +297,31 @@ function downloadURI(uri, name) {
   document.body.removeChild(link);
 }
 
-function downloadData(gd) {
+async function downloadData(gd) {
   let data = gd.data;
   let candlestick = false;
   let csv = undefined;
 
+  let filename = globals.filename;
+  let writable;
+
+  if (window.showSaveFilePicker) {
+    const handle = await showSaveFilePicker({
+      suggestedName: `${filename}.csv`,
+      types: [
+        {
+          description: "CSV File",
+          accept: {
+            "image/csv": [".csv"],
+          },
+        },
+      ],
+      excludeAcceptAllOption: true,
+    });
+    writable = await handle.createWritable();
+    filename = handle.name;
+  }
+
   data.forEach(function (trace) {
     // check if candlestick
     if (trace.type == "candlestick") {
@@ -363,23 +401,29 @@ function downloadData(gd) {
     }
   }
 
-  let filename = globals.filename;
   let blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
   if (navigator.msSaveBlob) {
     // IE 10+
     navigator.msSaveBlob(blob, filename);
   } else {
-    let link = window.document.createElement("a");
-    if (link.download !== undefined) {
-      // feature detection
-      // Browsers that support HTML5 download attribute
-      let url = URL.createObjectURL(blob);
-      link.setAttribute("href", url);
-      link.setAttribute("download", filename);
-      link.style.visibility = "hidden";
-      window.document.body.appendChild(link);
-      link.click();
-      window.document.body.removeChild(link);
+    // feature detection
+    // Browsers that support showSaveFilePicker or HTML5 download attribute
+    if (window.showSaveFilePicker && writable) {
+      await writable.write(blob);
+      await writable.close();
+    } else {
+      let link = window.document.createElement("a");
+      if (link.download !== undefined) {
+        // feature detection
+        // Browsers that support HTML5 download attribute
+        let url = URL.createObjectURL(blob);
+        link.setAttribute("href", url);
+        link.setAttribute("download", filename);
+        link.style.visibility = "hidden";
+        window.document.body.appendChild(link);
+        link.click();
+        window.document.body.removeChild(link);
+      }
     }
   }
 }
diff --git a/openbb_terminal/core/plots/web/main.js b/openbb_terminal/core/plots/web/main.js
index 86d1086a03da..58095dde1b00 100644
--- a/openbb_terminal/core/plots/web/main.js
+++ b/openbb_terminal/core/plots/web/main.js
@@ -90,15 +90,15 @@ function OpenBBMain(
         {
           name: "Download CSV (Ctrl+Shift+S)",
           icon: ICONS.downloadCsv,
-          click: function (gd) {
-            downloadCsvButton(gd);
+          click: async function (gd) {
+            await downloadCsvButton(gd);
           },
         },
         {
           name: "Download PNG (Ctrl+S)",
           icon: ICONS.downloadImage,
-          click: function () {
-            downloadImageButton();
+          click: async function () {
+            await downloadImageButton();
           },
         },
         // {
@@ -357,11 +357,44 @@ function OpenBBMain(
     button_pressed(title, active);
   }
 
-  function downloadImageButton() {
-    loadingOverlay("Saving PNG");
+  async function downloadImageButton() {
+    let filename = globals.filename;
+    let ext = "png";
+    let writable;
+    if (window.showSaveFilePicker) {
+      const handle = await showSaveFilePicker({
+        suggestedName: `${filename}.${ext}`,
+        types: [
+          {
+            description: "PNG Image",
+            accept: {
+              "image/png": [".png"],
+            },
+          },
+          {
+            description: "JPEG Image",
+            accept: {
+              "image/jpeg": [".jpeg"],
+            },
+          },
+          {
+            description: "SVG Image",
+            accept: {
+              "image/svg+xml": [".svg"],
+            },
+          },
+        ],
+        excludeAcceptAllOption: true,
+      });
+      writable = await handle.createWritable();
+      filename = handle.name;
+      ext = handle.name.split(".").pop();
+    }
+
+    loadingOverlay(`Saving ${ext.toUpperCase()}`);
     hideModebar();
-    non_blocking(function () {
-      downloadImage(globals.filename, "png");
+    non_blocking(async function () {
+      await downloadImage(filename, ext, writable);
       setTimeout(function () {
         setTimeout(function () {
           loading.classList.remove("show");
@@ -371,10 +404,10 @@ function OpenBBMain(
     }, 2)();
   }
 
-  function downloadCsvButton(gd) {
+  async function downloadCsvButton(gd) {
     loadingOverlay("Saving CSV");
-    setTimeout(function () {
-      downloadData(gd);
+    setTimeout(async function () {
+      await downloadData(gd);
     }, 500);
     setTimeout(function () {
       loading.classList.remove("show");

From 7370d2661899ed55c8af4474697e0ac00cc266d9 Mon Sep 17 00:00:00 2001
From: teh_coderer <me@tehcoderer.com>
Date: Thu, 13 Apr 2023 12:55:18 -0400
Subject: [PATCH 4/5] move savefilepicker to before csv overlay

---
 openbb_terminal/core/plots/web/bar_menus.js | 30 ++++-----------------
 openbb_terminal/core/plots/web/main.js      | 22 ++++++++++++++-
 2 files changed, 26 insertions(+), 26 deletions(-)

diff --git a/openbb_terminal/core/plots/web/bar_menus.js b/openbb_terminal/core/plots/web/bar_menus.js
index 81c911aeb6e2..a96db68d24cc 100644
--- a/openbb_terminal/core/plots/web/bar_menus.js
+++ b/openbb_terminal/core/plots/web/bar_menus.js
@@ -248,12 +248,12 @@ async function downloadImage(filename, extension, writable = undefined) {
     //   imageDownload = domtoimage.toSvg;
   } else if (["svg", "pdf"].includes(extension)) {
     if (window.showSaveFilePicker && writable) {
-      blob = await Plotly.toImage(globals.CHART_DIV, {
+      await Plotly.toImage(globals.CHART_DIV, {
         format: "svg",
         height: globals.CHART_DIV.clientHeight,
         width: globals.CHART_DIV.clientWidth,
       }).then(async function (dataUrl) {
-        blob = await fetch(dataUrl).then((r) => r.blob());
+        const blob = await fetch(dataUrl).then((r) => r.blob());
         await writable.write(blob);
         await writable.close();
       });
@@ -273,7 +273,7 @@ async function downloadImage(filename, extension, writable = undefined) {
   imageDownload(document.getElementById("openbb_container"))
     .then(async function (dataUrl) {
       if (window.showSaveFilePicker && writable) {
-        blob = await fetch(dataUrl).then((r) => r.blob());
+        const blob = await fetch(dataUrl).then((r) => r.blob());
         await writable.write(blob);
         await writable.close();
       } else {
@@ -297,31 +297,11 @@ function downloadURI(uri, name) {
   document.body.removeChild(link);
 }
 
-async function downloadData(gd) {
+async function downloadData(gd, filename, writable = undefined) {
   let data = gd.data;
   let candlestick = false;
   let csv = undefined;
 
-  let filename = globals.filename;
-  let writable;
-
-  if (window.showSaveFilePicker) {
-    const handle = await showSaveFilePicker({
-      suggestedName: `${filename}.csv`,
-      types: [
-        {
-          description: "CSV File",
-          accept: {
-            "image/csv": [".csv"],
-          },
-        },
-      ],
-      excludeAcceptAllOption: true,
-    });
-    writable = await handle.createWritable();
-    filename = handle.name;
-  }
-
   data.forEach(function (trace) {
     // check if candlestick
     if (trace.type == "candlestick") {
@@ -401,7 +381,7 @@ async function downloadData(gd) {
     }
   }
 
-  let blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
+  const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
   if (navigator.msSaveBlob) {
     // IE 10+
     navigator.msSaveBlob(blob, filename);
diff --git a/openbb_terminal/core/plots/web/main.js b/openbb_terminal/core/plots/web/main.js
index 58095dde1b00..ff8480c0aef3 100644
--- a/openbb_terminal/core/plots/web/main.js
+++ b/openbb_terminal/core/plots/web/main.js
@@ -405,9 +405,29 @@ function OpenBBMain(
   }
 
   async function downloadCsvButton(gd) {
+    let filename = globals.filename;
+    let writable;
+
+    if (window.showSaveFilePicker) {
+      const handle = await showSaveFilePicker({
+        suggestedName: `${filename}.csv`,
+        types: [
+          {
+            description: "CSV File",
+            accept: {
+              "image/csv": [".csv"],
+            },
+          },
+        ],
+        excludeAcceptAllOption: true,
+      });
+      writable = await handle.createWritable();
+      filename = handle.name;
+    }
+
     loadingOverlay("Saving CSV");
     setTimeout(async function () {
-      await downloadData(gd);
+      await downloadData(gd, filename, writable);
     }, 500);
     setTimeout(function () {
       loading.classList.remove("show");

From 69759cc760989826ebd5bf52be88749d0f5cee06 Mon Sep 17 00:00:00 2001
From: teh_coderer <me@tehcoderer.com>
Date: Thu, 13 Apr 2023 14:03:14 -0400
Subject: [PATCH 5/5] adds watermarks to svg saves

---
 openbb_terminal/core/plots/plotly.html      |  9 +++-
 openbb_terminal/core/plots/web/bar_menus.js | 51 +++++++++++++++++++++
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/openbb_terminal/core/plots/plotly.html b/openbb_terminal/core/plots/plotly.html
index 0c0a887f1cc6..5138d8058a8a 100644
--- a/openbb_terminal/core/plots/plotly.html
+++ b/openbb_terminal/core/plots/plotly.html
@@ -226,9 +226,13 @@
                 !["svg", "pdf"].includes(extension)
               ) {
                 if (annotation.text[0] == "/") {
-                  cmd_src = annotation.text;
+                  globals.cmd_src = annotation.text;
+                  globals.cmd_idx =
+                    window.plotly_figure.layout.annotations.indexOf(annotation);
                   annotation.text = "";
+
                   let margin = window.plotly_figure.layout.margin;
+                  globals.old_margin = { ...margin };
                   if (margin.t != undefined && margin.t > 40) {
                     margin.t = 40;
                   }
@@ -236,7 +240,6 @@
                   if (cmd_src == "/stocks/candle") {
                     margin.r -= 40;
                   }
-                  window.plotly_figure.layout.margin = margin;
                 }
               }
             });
@@ -252,6 +255,8 @@
           let title =
             window.plotly_figure?.layout?.title?.text ?? "Interactive Chart";
 
+          globals.title = title;
+
           if (title.length > 50) {
             document.getElementById("title").style.fontSize = "12px";
           }
diff --git a/openbb_terminal/core/plots/web/bar_menus.js b/openbb_terminal/core/plots/web/bar_menus.js
index a96db68d24cc..c3f8fd32848a 100644
--- a/openbb_terminal/core/plots/web/bar_menus.js
+++ b/openbb_terminal/core/plots/web/bar_menus.js
@@ -234,6 +234,48 @@ function uploadImage() {
     });
 }
 
+const openbb_watermark = {
+  yref: "paper",
+  xref: "paper",
+  x: 1,
+  y: 0,
+  text: "OpenBB Terminal",
+  font_size: 17,
+  font_color: "gray",
+  opacity: 0.5,
+  xanchor: "right",
+  yanchor: "bottom",
+  yshift: -80,
+  xshift: 40,
+};
+
+async function setWatermarks(margin, old_index, init = false) {
+  if (init) {
+    globals.CHART_DIV.layout.annotations.push(openbb_watermark);
+    if (globals.cmd_idx && globals.cmd_src) {
+      globals.CHART_DIV.layout.annotations[globals.cmd_idx].text =
+        globals.cmd_src;
+    }
+
+    Plotly.relayout(globals.CHART_DIV, {
+      "title.text": globals.title,
+      margin: globals.old_margin,
+    });
+  }
+
+  if (!init) {
+    if (globals.cmd_idx && globals.cmd_src) {
+      globals.CHART_DIV.layout.annotations[globals.cmd_idx].text = "";
+    }
+    globals.CHART_DIV.layout.annotations.splice(old_index, 1);
+
+    Plotly.relayout(globals.CHART_DIV, {
+      "title.text": "",
+      margin: margin,
+    });
+  }
+}
+
 async function downloadImage(filename, extension, writable = undefined) {
   const loader = document.getElementById("loader");
   loader.classList.add("show");
@@ -247,6 +289,11 @@ async function downloadImage(filename, extension, writable = undefined) {
     // } else if (extension == "svg") {
     //   imageDownload = domtoimage.toSvg;
   } else if (["svg", "pdf"].includes(extension)) {
+    const margin = globals.CHART_DIV.layout.margin;
+    const old_index = globals.CHART_DIV.layout.annotations.length;
+
+    await setWatermarks(margin, old_index, true);
+
     if (window.showSaveFilePicker && writable) {
       await Plotly.toImage(globals.CHART_DIV, {
         format: "svg",
@@ -265,11 +312,15 @@ async function downloadImage(filename, extension, writable = undefined) {
         filename: filename,
       });
     }
+
+    await setWatermarks(margin, old_index, false);
+
     return;
   } else {
     console.log("Invalid extension");
     return;
   }
+
   imageDownload(document.getElementById("openbb_container"))
     .then(async function (dataUrl) {
       if (window.showSaveFilePicker && writable) {