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:
+ +
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(); +}