diff --git a/package-lock.json b/package-lock.json index b4d865740dbb3c446007dc429f1584b8e4ca54d6..53e6302d007663e57102636b7e3460b35dc4cbdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-svelte": "^2.33.0", "fs-extra": "^11.1.0", + "html-to-image": "^1.11.11", "iconify-icon": "^2.1.0", "intro.js": "^7.0.1", "js-yaml": "^4.1.0", @@ -2048,12 +2049,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3568,9 +3569,9 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.5.tgz", - "integrity": "sha512-sWvP1Pl8H03B8oFJpFR3HE31HUfwtX7Rlf9BNsvdpujD4n7WMhfmu8h9wOV2u+c1k0ZilTADhPqypzx2J690ZQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "dev": true, "funding": [ { @@ -3640,9 +3641,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -4125,6 +4126,12 @@ "node": ">=18" } }, + "node_modules/html-to-image": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz", + "integrity": "sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==", + "dev": true + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -5035,12 +5042,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -5577,9 +5584,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -5652,9 +5659,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "dev": true, "funding": [ { @@ -5672,8 +5679,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -6561,9 +6568,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -7590,14 +7597,14 @@ } }, "node_modules/vite": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", - "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.1.tgz", + "integrity": "sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.41", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -7616,6 +7623,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -7633,6 +7641,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -7644,6 +7655,412 @@ } } }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -7932,9 +8349,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index b27756ce1e405d9cf38e0dc619efe406d8c8b224..fb10e9f15511a44ac306ed7dc21c009a26d9224a 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-svelte": "^2.33.0", "fs-extra": "^11.1.0", + "html-to-image": "^1.11.11", "iconify-icon": "^2.1.0", "intro.js": "^7.0.1", "js-yaml": "^4.1.0", diff --git a/src/lib/components/piece_of_cake/Svg.svelte b/src/lib/components/piece_of_cake/Svg.svelte index 8b6ab1501f731f7404ce1f2474e7453e91a4683e..791b51a5608eb580d30e192ad3749b2db9d35a32 100644 --- a/src/lib/components/piece_of_cake/Svg.svelte +++ b/src/lib/components/piece_of_cake/Svg.svelte @@ -21,6 +21,11 @@ /// The layer's z-index. export let zIndex: number | undefined = undefined + const paddingRotatedTextAxisX = { + bottom: 40, + right: 20, + } + $: modifiedModelGroups = modelGroups.map((modelGroup) => modelGroup.map((model) => model.setPadding(padding)), ) @@ -29,14 +34,16 @@ <svg bind:this={element} class="absolute overflow-visible top-0 left-0" - height={modifiedModelGroups?.[0]?.[0]?.containerHeight} + height={modifiedModelGroups?.[0]?.[0]?.containerHeight + + paddingRotatedTextAxisX.bottom} style:clip-path={clipOverflow ? `inset(${padding.top - 1}px 0 ${padding.bottom - 1}px 0)` : null} style:pointer-events={pointerEvents === false ? "none" : null} style:z-index={zIndex} {viewBox} - width={modifiedModelGroups?.[0]?.[0]?.containerWidth} + width={modifiedModelGroups?.[0]?.[0]?.containerWidth + + paddingRotatedTextAxisX.right} > <defs> <slot name="defs" /> diff --git a/src/lib/components/test_cases/TestCaseGraph.svelte b/src/lib/components/test_cases/TestCaseGraph.svelte index 6bdff891096040dcea8b206369802c399456f81f..1937242fff857d80916540e3e90a52ea661e0f9d 100644 --- a/src/lib/components/test_cases/TestCaseGraph.svelte +++ b/src/lib/components/test_cases/TestCaseGraph.svelte @@ -1156,7 +1156,7 @@ </span> </div> </div> - <div class="w-full h-screen max-h-64"> + <div id="test_case_graph" class="w-full h-screen max-h-64 pb-10 pr-5 bg-white"> <PieceOfCake let:modelGroups modelGroups={variableValues.map((variableValuesGroup) => @@ -1434,7 +1434,7 @@ </PieceOfCake> </div> <div - class="mt-10 col-start-2 w-full flex justify-between items-start" + class="col-start-2 w-full flex justify-between items-start" style:padding-right="{svgPadding.right}px" > <div class="flex flex-col justify-center"> diff --git a/src/lib/server/test_cases.ts b/src/lib/server/test_cases.ts index 609f7a7666bcfd437b7d71d39f37af3af8965996..1b10fbe81a0d1e08eb3a2ce21349c80bee8a83d4 100644 --- a/src/lib/server/test_cases.ts +++ b/src/lib/server/test_cases.ts @@ -23,3 +23,14 @@ export function getTestCasesCacheDirPath(digest: string) { export function getTestCasesCacheFilePath(digest: string) { return path.join(getTestCasesCacheDirPath(digest), `${digest}.json`) } + +export function getTestCasesOpenGraphsDirPath(digest: string) { + return path.join( + config.simulationsTestCasesDir, + "opengraphs", + digest.substring(0, 2), + ) +} +export function getTestCasesOpenGraphsFilePath(digest: string) { + return path.join(getTestCasesOpenGraphsDirPath(digest), `${digest}.png`) +} diff --git a/src/lib/simulations.ts b/src/lib/simulations.ts index 1d9532c7a7770511a73eadbe42114c5fcf969cef..32ea3b316344b18cd69ac36d1598671be9ec802c 100644 --- a/src/lib/simulations.ts +++ b/src/lib/simulations.ts @@ -41,6 +41,7 @@ export interface Simulation { } export async function publishTestCaseSimulation( + blobImageOpenGraph: Blob | null | undefined, displayMode: DisplayMode, inputInstantsByVariableNameArray: Array<{ [name: string]: Set<string> @@ -49,8 +50,16 @@ export async function publishTestCaseSimulation( testCases: Situation[], ): Promise<string | undefined> { const urlString = "/test_cases/simulations" - const res = await fetch(urlString, { - body: JSON.stringify({ + + const formData = new FormData() + + if (blobImageOpenGraph !== undefined && blobImageOpenGraph !== null) { + formData.append("blob", blobImageOpenGraph) + } + + formData.append( + "body", + JSON.stringify({ displayMode: { ...displayMode, parameterHash: undefined, @@ -69,12 +78,13 @@ export async function publishTestCaseSimulation( parametricReform: parametricReform, testCases: testCases, }), - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, + ) + + const res = await fetch(urlString, { + body: formData, method: "POST", }) + if (!res.ok) { console.error( `Error ${ @@ -83,6 +93,8 @@ export async function publishTestCaseSimulation( ) return } + const { token } = await res.json() + return token } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 25299071c8b94b5b41289608c16ee735b7d145f5..d49447f7f32f80fc9de1658314bd11806428559b 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,6 +12,7 @@ auditOptions, } from "@auditors/core" import type { PopulationWithoutId, Waterfall } from "@openfisca/json-model" + import * as htmlToImage from "html-to-image" import introJs from "intro.js" import type { IntroStep } from "intro.js/src/core/steps" import { getContext, setContext } from "svelte" @@ -886,52 +887,27 @@ } async function shareTestCaseSimulationLink(): Promise<void> { - const urlString = "/test_cases/simulations" - const res = await fetch(urlString, { - body: JSON.stringify({ - displayMode: { - ...$displayModeWritable, - parameterHash: undefined, - }, - inputInstantsByVariableNameArray: $inputInstantsByVariableNameArray.map( - (inputInstantsByVariableName) => - Object.fromEntries( - Object.entries(inputInstantsByVariableName).map( - ([variableName, inputInstants]) => [ - variableName, - [...inputInstants], - ], - ), - ), - ), - parametricReform: $parametricReform, - testCases: $testCases, - }), - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - method: "POST", - }) - if (!res.ok) { - console.error( - `Error ${ - res.status - } while creating a share link at ${urlString}\n\n${await res.text()}`, - ) - return + let blob: Blob | null | undefined + + const testCaseGraphEl = document.getElementById("test_case_graph") + if (testCaseGraphEl) { + blob = await htmlToImage.toBlob(testCaseGraphEl) } + const token = await publishTestCaseSimulation( + blob, $displayModeWritable!, $inputInstantsByVariableNameArray, $parametricReform, $testCases, ) + if (token !== undefined) { testCaseSharingModal = { open: true, token, } + trackTestCaseShare() } } diff --git a/src/routes/test_cases/og_image/[simulation]/+server.ts b/src/routes/test_cases/og_image/[simulation]/+server.ts new file mode 100644 index 0000000000000000000000000000000000000000..79c03bffe102aa0f172f39c9cc36642ec66c63ec --- /dev/null +++ b/src/routes/test_cases/og_image/[simulation]/+server.ts @@ -0,0 +1,19 @@ +import fs from "fs-extra" + +import type { RequestHandler } from "./$types" + +import { getTestCasesOpenGraphsFilePath } from "$lib/server/test_cases" + +export const GET: RequestHandler = async ({ params }) => { + const { simulation: digest } = params as { simulation: string } + + const openGraphsFilePath = getTestCasesOpenGraphsFilePath(digest) + const image = await fs.readFile(openGraphsFilePath) + const response = new Response(image) + response.headers.append("Content-Type", "image/png") + response.headers.append( + "Cache-Control", + "s-maxage=604800, stale-while-revalidate=604800", + ) + return response +} diff --git a/src/routes/test_cases/simulations/+server.ts b/src/routes/test_cases/simulations/+server.ts index 43fe2a2d3098297dd4ae8a42f16149a684dc0faf..8308baf2fba92e06c0c4f8dc62a8a37c9aa8c869 100644 --- a/src/routes/test_cases/simulations/+server.ts +++ b/src/routes/test_cases/simulations/+server.ts @@ -11,6 +11,8 @@ import type { RequestHandler } from "./$types" import { hashString } from "$lib/hash" import { + getTestCasesOpenGraphsDirPath, + getTestCasesOpenGraphsFilePath, getTestCasesRequestsDirPath, getTestCasesRequestsFilePath, } from "$lib/server/test_cases" @@ -69,7 +71,12 @@ function auditBody(audit: Audit, dataUnknown: unknown): [unknown, unknown] { } export const POST: RequestHandler = async ({ request, url }) => { - const [body, bodyError] = auditBody(cleanAudit, await request.json()) + const formData = await request.formData() + + const [body, bodyError] = auditBody( + cleanAudit, + JSON.parse(formData.get("body") as string), + ) if (bodyError !== null) { console.error( `Error in ${url.pathname} body:\n${JSON.stringify( @@ -84,6 +91,24 @@ export const POST: RequestHandler = async ({ request, url }) => { const digest = hashString(bodyJson) + const blob = formData.get("blob") as Blob | null | undefined + + if (blob !== null && blob !== undefined) { + const openGraphsDir = getTestCasesOpenGraphsDirPath(digest) + const openGraphsFilePath = getTestCasesOpenGraphsFilePath(digest) + if (!(await fs.pathExists(openGraphsFilePath))) { + await fs.ensureDir(openGraphsDir) + + const fileStream = fs.createWriteStream(openGraphsFilePath) + const arrayBuffer = await new File([blob], `${digest}.png`, { + type: blob.type, + }).arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + fileStream.write(buffer) + fileStream.end() + } + } + const simulationDir = getTestCasesRequestsDirPath(digest) const simulationFilePath = getTestCasesRequestsFilePath(digest) if (!(await fs.pathExists(simulationFilePath))) { diff --git a/src/routes/test_cases/simulations/[simulation]/+page.svelte b/src/routes/test_cases/simulations/[simulation]/+page.svelte index 66387d2f8a29d685b6d0f75a6d13340d6f336e20..17f502e786d6c1923bac9c5423a674fd97460686 100644 --- a/src/routes/test_cases/simulations/[simulation]/+page.svelte +++ b/src/routes/test_cases/simulations/[simulation]/+page.svelte @@ -5,10 +5,12 @@ import type { PageData } from "./$types" import { goto } from "$app/navigation" + import { page } from "$app/stores" import { requestAllTestCasesCalculations, type RequestedCalculations, } from "$lib/calculations" + import OpenGraph from "$lib/components/transverse_pages/OpenGraph.svelte" import type { EvaluationByName } from "$lib/decompositions" import type { ParametricReform } from "$lib/reforms" import type { Situation } from "$lib/situations" @@ -71,3 +73,17 @@ ) }) </script> + +<svelte:head> + <title> + Votre simulation cas type + </title> +</svelte:head> + +<OpenGraph + description="{simulation.testCases[simulation.displayMode.testCasesIndex[0]].title}" + image={new URL(`test_cases/og_image/${$page.params.simulation}`, data.baseUrl).toString()} + title="Votre simulation cas type" + titleSuffix={null} + url={$page.url.toString()} +/>