diff --git a/package-lock.json b/package-lock.json
index c306f08..13e379a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,11 +18,13 @@
"image-size": "^1.1.1",
"jszip": "^3.10.1",
"localforage": "^1.10.0",
+ "toastify-js": "1.12.0",
"zod": "^3.21.4"
},
"devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/node": "^20.4.5",
+ "@types/toastify-js": "1.12.3",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"eslint": "^8.46.0",
@@ -1221,6 +1223,12 @@
"integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
"dev": true
},
+ "node_modules/@types/toastify-js": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/@types/toastify-js/-/toastify-js-1.12.3.tgz",
+ "integrity": "sha512-9RjLlbAHMSaae/KZNHGv19VG4gcLIm3YjvacCXBtfMfYn26h76YP5oxXI8k26q4iKXCB9LNfv18lsoS0JnFPTg==",
+ "dev": true
+ },
"node_modules/@types/unist": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.7.tgz",
@@ -8256,6 +8264,11 @@
"node": ">=8.0"
}
},
+ "node_modules/toastify-js": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz",
+ "integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ=="
+ },
"node_modules/trim-lines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
diff --git a/package.json b/package.json
index 0328495..0ebf341 100644
--- a/package.json
+++ b/package.json
@@ -23,11 +23,13 @@
"image-size": "^1.1.1",
"jszip": "^3.10.1",
"localforage": "^1.10.0",
+ "toastify-js": "1.12.0",
"zod": "^3.21.4"
},
"devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/node": "^20.4.5",
+ "@types/toastify-js": "1.12.3",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"eslint": "^8.46.0",
diff --git a/src/pages/download/_downloadPythonPackage.js b/src/pages/download/_downloadPythonPackage.js
index 6359d2e..0c1e756 100644
--- a/src/pages/download/_downloadPythonPackage.js
+++ b/src/pages/download/_downloadPythonPackage.js
@@ -1,6 +1,7 @@
import JSZip from "jszip";
import localforage from "localforage";
import { saveAs } from "file-saver";
+import { showToast } from "../../scripts/utils/toast/toast";
async function allStorage() {
var values = new Map(),
@@ -43,22 +44,38 @@ function getJSZipDateWithOffset() {
return dateWithOffset;
}
-export async function generateZIP(deviceId) {
+function handlePartialSuccess(failedImages) {
+ const description = `
+
Image(s) failed to generate. Try a different image/device:
+
+ ${failedImages.map((failedImage) => `- ${failedImage}
`).join("")}
+
+ If the issue persists, please report it on
Github
+ `;
+
+ showToast({
+ title: "Partial Success",
+ description: description,
+ avatar: "/images/upload-warning.svg",
+ });
+}
+
+function handleNoGeneratedMockup() {
+ const description = `
+ Try a different image/device.
If the issue persists, please report it on
Github
+ `;
+ showToast({
+ title: "No generated mockup",
+ description: description,
+ avatar: "/images/upload-error.svg",
+ });
+}
+
+function downloadGeneratedMockup(deviceId, images) {
var zip = new JSZip();
var count = 0;
const zipFilename = !!deviceId ? `${deviceId}-mockup.zip` : "mockup.zip";
- var images = new Map();
- var dataurlkey = await allStorage();
- var failedImages = [];
- dataurlkey.forEach(function (value, key) {
- // Only zip successfully generated mockups
- if (value !== null) {
- var file = dataURLtoFile(value, key.substring(3, key.length) + ".png");
- images.set(key, URL.createObjectURL(file));
- } else {
- failedImages.push(key);
- }
- });
+
images.forEach(async function (imgURL, k) {
var filename = unescape(k.substring(3, k.length)) + ".png";
var image = await fetch(imgURL);
@@ -75,3 +92,28 @@ export async function generateZIP(deviceId) {
}
});
}
+
+export async function generateZIP(deviceId) {
+ var images = new Map();
+ var dataurlkey = await allStorage();
+ var failedImages = [];
+ dataurlkey.forEach(function (value, key) {
+ // Only zip successfully generated mockups
+ if (value !== null) {
+ var file = dataURLtoFile(value, key.substring(3, key.length) + ".png");
+ images.set(key, URL.createObjectURL(file));
+ } else {
+ failedImages.push(key);
+ }
+ });
+
+ downloadGeneratedMockup(deviceId, images);
+
+ if (failedImages.length > 0 && images.size > 0) {
+ handlePartialSuccess(failedImages);
+ }
+
+ if (images.size === 0) {
+ handleNoGeneratedMockup();
+ }
+}
diff --git a/src/scripts/utils/toast/toast.css b/src/scripts/utils/toast/toast.css
new file mode 100644
index 0000000..4c53893
--- /dev/null
+++ b/src/scripts/utils/toast/toast.css
@@ -0,0 +1,14 @@
+@import url("toastify-js/src/toastify.css");
+
+.toast-close {
+ color: black !important;
+}
+
+.toast-header {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 8px;
+ font-size: 20px;
+ font-weight: bold;
+ color: black;
+}
diff --git a/src/scripts/utils/toast/toast.ts b/src/scripts/utils/toast/toast.ts
new file mode 100644
index 0000000..2357ed1
--- /dev/null
+++ b/src/scripts/utils/toast/toast.ts
@@ -0,0 +1,42 @@
+import StartToastifyInstance from "toastify-js";
+import Toastify from "toastify-js";
+import "./toast.css";
+
+interface ToastOptions extends StartToastifyInstance.Options {
+ description: string;
+ title?: string;
+}
+
+export function showToast(options: ToastOptions) {
+ const { description, avatar, title } = options;
+ const toastNode = document.createElement("div");
+ const toastHeader = document.createElement("div");
+ const toastMessage = document.createElement("div");
+
+ toastHeader.classList.add("toast-header");
+ toastHeader.innerHTML = `${avatar ? `` : ""}${
+ title ?? ""
+ }`;
+
+ toastMessage.innerHTML = description;
+ toastNode.appendChild(toastHeader);
+ toastNode.appendChild(toastMessage);
+
+ Toastify({
+ node: toastNode,
+ style: {
+ display: "flex",
+ background: "white",
+ color: "black",
+ alignItems: "start",
+ justifyContent: "start",
+ },
+ onClick: function () {},
+ duration: 3000,
+ close: true,
+ gravity: "top",
+ position: "center",
+ stopOnFocus: true,
+ ...options,
+ }).showToast();
+}