mirror of
https://github.com/CorentinTh/it-tools.git
synced 2026-05-13 13:56:43 +00:00
Add icon generator with ZIP export and optional PWA manifest
This commit is contained in:
parent
d505845f91
commit
9efd7d795e
9 changed files with 764 additions and 176 deletions
|
|
@ -93,6 +93,32 @@ tools:
|
|||
button:
|
||||
copy: Copy
|
||||
refresh: Refresh
|
||||
|
||||
icon-generator:
|
||||
title: Icon generator
|
||||
description: Generate resized app icons from one source image for PWA, Android, and iOS presets.
|
||||
presets: Presets
|
||||
presetLabels:
|
||||
pwa: PWA
|
||||
android: Android
|
||||
ios: iOS
|
||||
fitMode: Fit mode
|
||||
cover: Cover (crop)
|
||||
contain: Contain
|
||||
clearPresetSizes: Clear preset sizes
|
||||
addSize: Add size
|
||||
baseName: Base name
|
||||
baseNameHint: File names use "<baseName>-<size>x<size>.png". Invalid characters are replaced with "-".
|
||||
includeManifest: Include manifest.json in ZIP
|
||||
selectedSizes: Selected output sizes
|
||||
uploadTitle: Drag and drop an image here, or click to select an image
|
||||
noImage: Upload an image to start.
|
||||
generate: Generate icons
|
||||
output: Generated icons
|
||||
download: Download
|
||||
downloadAll: Download all
|
||||
downloadZip: Download zip
|
||||
|
||||
percentage-calculator:
|
||||
title: Percentage calculator
|
||||
description: Easily calculate percentages from a value to another value, or from a percentage to a value.
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
"ibantools": "^4.3.3",
|
||||
"js-base64": "^3.7.6",
|
||||
"json5": "^2.2.3",
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"libphonenumber-js": "^1.10.28",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
|
|||
292
pnpm-lock.yaml
generated
292
pnpm-lock.yaml
generated
|
|
@ -49,7 +49,7 @@ importers:
|
|||
version: 10.3.0(vue@3.3.4)
|
||||
'@vueuse/head':
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0(typescript@5.2.2)(vue@3.3.4)
|
||||
version: 1.0.0(vue@3.3.4)
|
||||
'@vueuse/router':
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(vue-router@4.1.6(vue@3.3.4))(vue@3.3.4)
|
||||
|
|
@ -113,6 +113,9 @@ importers:
|
|||
json5:
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3
|
||||
jszip:
|
||||
specifier: ^3.10.1
|
||||
version: 3.10.1
|
||||
jwt-decode:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2
|
||||
|
|
@ -2303,9 +2306,6 @@ packages:
|
|||
'@vue/compiler-core@3.3.7':
|
||||
resolution: {integrity: sha512-pACdY6YnTNVLXsB86YD8OF9ihwpolzhhtdLVHhBL6do/ykr6kKXNYABRtNMGrsQXpEXXyAdwvWWkuTbs4MFtPQ==}
|
||||
|
||||
'@vue/compiler-core@3.5.13':
|
||||
resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
|
||||
|
||||
'@vue/compiler-dom@3.2.47':
|
||||
resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==}
|
||||
|
||||
|
|
@ -2315,18 +2315,12 @@ packages:
|
|||
'@vue/compiler-dom@3.3.7':
|
||||
resolution: {integrity: sha512-0LwkyJjnUPssXv/d1vNJ0PKfBlDoQs7n81CbO6Q0zdL7H1EzqYRrTVXDqdBVqro0aJjo/FOa1qBAPVI4PGSHBw==}
|
||||
|
||||
'@vue/compiler-dom@3.5.13':
|
||||
resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==}
|
||||
|
||||
'@vue/compiler-sfc@3.2.47':
|
||||
resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==}
|
||||
|
||||
'@vue/compiler-sfc@3.3.4':
|
||||
resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==}
|
||||
|
||||
'@vue/compiler-sfc@3.5.13':
|
||||
resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==}
|
||||
|
||||
'@vue/compiler-ssr@3.2.47':
|
||||
resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==}
|
||||
|
||||
|
|
@ -2336,9 +2330,6 @@ packages:
|
|||
'@vue/compiler-ssr@3.3.7':
|
||||
resolution: {integrity: sha512-TxOfNVVeH3zgBc82kcUv+emNHo+vKnlRrkv8YvQU5+Y5LJGJwSNzcmLUoxD/dNzv0bhQ/F0s+InlgV0NrApJZg==}
|
||||
|
||||
'@vue/compiler-ssr@3.5.13':
|
||||
resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==}
|
||||
|
||||
'@vue/devtools-api@6.5.0':
|
||||
resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
|
||||
|
||||
|
|
@ -2359,21 +2350,12 @@ packages:
|
|||
'@vue/reactivity@3.3.4':
|
||||
resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==}
|
||||
|
||||
'@vue/reactivity@3.5.13':
|
||||
resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
|
||||
|
||||
'@vue/runtime-core@3.3.4':
|
||||
resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==}
|
||||
|
||||
'@vue/runtime-core@3.5.13':
|
||||
resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==}
|
||||
|
||||
'@vue/runtime-dom@3.3.4':
|
||||
resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==}
|
||||
|
||||
'@vue/runtime-dom@3.5.13':
|
||||
resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==}
|
||||
|
||||
'@vue/server-renderer@3.3.4':
|
||||
resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -2384,11 +2366,6 @@ packages:
|
|||
peerDependencies:
|
||||
vue: 3.3.7
|
||||
|
||||
'@vue/server-renderer@3.5.13':
|
||||
resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==}
|
||||
peerDependencies:
|
||||
vue: 3.5.13
|
||||
|
||||
'@vue/shared@3.2.47':
|
||||
resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==}
|
||||
|
||||
|
|
@ -2398,9 +2375,6 @@ packages:
|
|||
'@vue/shared@3.3.7':
|
||||
resolution: {integrity: sha512-N/tbkINRUDExgcPTBvxNkvHGu504k8lzlNQRITVnm6YjOjwa4r0nnbd4Jb01sNpur5hAllyRJzSK5PvB9PPwRg==}
|
||||
|
||||
'@vue/shared@3.5.13':
|
||||
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
|
||||
|
||||
'@vue/test-utils@2.3.2':
|
||||
resolution: {integrity: sha512-hJnVaYhbrIm0yBS0+e1Y0Sj85cMyAi+PAbK4JHqMRUZ6S622Goa+G7QzkRSyvCteG8wop7tipuEbHoZo26wsSA==}
|
||||
peerDependencies:
|
||||
|
|
@ -2434,8 +2408,10 @@ packages:
|
|||
'@vueuse/shared@10.3.0':
|
||||
resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==}
|
||||
|
||||
'@vueuse/shared@12.0.0':
|
||||
resolution: {integrity: sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==}
|
||||
'@vueuse/shared@14.2.1':
|
||||
resolution: {integrity: sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
'@zhead/schema@1.0.0-beta.13':
|
||||
resolution: {integrity: sha512-P1A1vRGFBhITco8Iw4/hvnDYoE/SoVrd71dW1pBFdXJb3vP+pBtoOuhbEKy0ROJGOyzQuqvFibcwzyLlWMqNiQ==}
|
||||
|
|
@ -2798,7 +2774,7 @@ packages:
|
|||
hasBin: true
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
confbox@0.1.8:
|
||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||
|
|
@ -2831,6 +2807,9 @@ packages:
|
|||
core-js-compat@3.39.0:
|
||||
resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==}
|
||||
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
|
||||
country-code-lookup@0.1.0:
|
||||
resolution: {integrity: sha512-IOI66HEG+8bXfWPy+sTzuN7161vmDZOHg1wgIPFf3WfD73FeLajnn6C+fnxOIa9RL1WRBDMXQQWW/FOaOYaQ3w==}
|
||||
|
||||
|
|
@ -2896,9 +2875,6 @@ packages:
|
|||
csstype@3.1.2:
|
||||
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
|
||||
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
dash-get@1.0.2:
|
||||
resolution: {integrity: sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==}
|
||||
|
||||
|
|
@ -3246,6 +3222,7 @@ packages:
|
|||
eslint-plugin-i@2.28.0-2:
|
||||
resolution: {integrity: sha512-z48kG4qmE4TmiLcxbmvxMT5ycwvPkXaWW0XpU1L768uZaTbiDbxsHMEdV24JHlOR1xDsPpKW39BfP/pRdYIwFA==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Please migrate to the brand new `eslint-plugin-import-x` instead
|
||||
peerDependencies:
|
||||
eslint: ^7.2.0 || ^8
|
||||
|
||||
|
|
@ -3649,10 +3626,6 @@ packages:
|
|||
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
hasown@2.0.0:
|
||||
resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -3737,6 +3710,9 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
hasBin: true
|
||||
|
||||
immediate@3.0.6:
|
||||
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
||||
|
||||
import-fresh@3.3.0:
|
||||
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -3817,9 +3793,6 @@ packages:
|
|||
is-core-module@2.13.0:
|
||||
resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==}
|
||||
|
||||
is-core-module@2.13.1:
|
||||
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
|
||||
|
||||
is-core-module@2.16.0:
|
||||
resolution: {integrity: sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -3974,6 +3947,9 @@ packages:
|
|||
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
isarray@1.0.0:
|
||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||
|
||||
isarray@2.0.5:
|
||||
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
|
||||
|
||||
|
|
@ -4096,6 +4072,9 @@ packages:
|
|||
resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
jszip@3.10.1:
|
||||
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
|
||||
|
||||
jwt-decode@3.1.2:
|
||||
resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==}
|
||||
|
||||
|
|
@ -4122,6 +4101,9 @@ packages:
|
|||
libphonenumber-js@1.10.28:
|
||||
resolution: {integrity: sha512-1eAgjLrZA0+2Wgw4hs+4Q/kEBycxQo8ZLYnmOvZ3AlM8ImAVAJgDPlZtISLEzD1vunc2q8s2Pn7XwB7I8U3Kzw==}
|
||||
|
||||
lie@3.3.0:
|
||||
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
|
||||
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
|
|
@ -4498,6 +4480,9 @@ packages:
|
|||
package-manager-detector@0.2.7:
|
||||
resolution: {integrity: sha512-g4+387DXDKlZzHkP+9FLt8yKj8+/3tOkPv7DVTJGGRm00RkEWgqbFstX1mXJ4M0VDYhUqsTOiISqNOJnhAu3PQ==}
|
||||
|
||||
pako@1.0.11:
|
||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||
|
||||
param-case@2.1.1:
|
||||
resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
|
||||
|
||||
|
|
@ -4665,6 +4650,9 @@ packages:
|
|||
resolution: {integrity: sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
||||
process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
prosemirror-changeset@2.2.1:
|
||||
resolution: {integrity: sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==}
|
||||
|
||||
|
|
@ -4788,6 +4776,9 @@ packages:
|
|||
resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
|
@ -4868,10 +4859,6 @@ packages:
|
|||
resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==}
|
||||
hasBin: true
|
||||
|
||||
resolve@1.22.8:
|
||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||
hasBin: true
|
||||
|
||||
resolve@1.22.9:
|
||||
resolution: {integrity: sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==}
|
||||
hasBin: true
|
||||
|
|
@ -4919,6 +4906,9 @@ packages:
|
|||
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
||||
safe-buffer@5.1.2:
|
||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
|
|
@ -4996,6 +4986,9 @@ packages:
|
|||
resolution: {integrity: sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==}
|
||||
engines: {node: '>=11.0'}
|
||||
|
||||
setimmediate@1.0.5:
|
||||
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -5071,6 +5064,7 @@ packages:
|
|||
source-map@0.8.0-beta.0:
|
||||
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
|
||||
engines: {node: '>= 8'}
|
||||
deprecated: The work that was done in this beta branch won't be included in future versions
|
||||
|
||||
sourcemap-codec@1.4.8:
|
||||
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
|
||||
|
|
@ -5132,6 +5126,9 @@ packages:
|
|||
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
string_decoder@1.1.1:
|
||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||
|
||||
string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
|
||||
|
|
@ -5701,14 +5698,6 @@ packages:
|
|||
vue@3.3.4:
|
||||
resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==}
|
||||
|
||||
vue@3.5.13:
|
||||
resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
vuedraggable@4.1.0:
|
||||
resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
|
||||
peerDependencies:
|
||||
|
|
@ -6117,7 +6106,7 @@ snapshots:
|
|||
'@babel/traverse': 7.23.2
|
||||
'@babel/types': 7.23.0
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
|
|
@ -7000,7 +6989,7 @@ snapshots:
|
|||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/parser': 7.22.10
|
||||
'@babel/types': 7.22.10
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -7015,7 +7004,7 @@ snapshots:
|
|||
'@babel/helper-split-export-declaration': 7.22.5
|
||||
'@babel/parser': 7.22.5
|
||||
'@babel/types': 7.22.5
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -7030,7 +7019,7 @@ snapshots:
|
|||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/parser': 7.23.0
|
||||
'@babel/types': 7.23.0
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -7228,7 +7217,7 @@ snapshots:
|
|||
'@eslint/eslintrc@2.1.2':
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
espree: 9.6.1
|
||||
globals: 13.20.0
|
||||
ignore: 5.2.4
|
||||
|
|
@ -7244,7 +7233,7 @@ snapshots:
|
|||
'@humanwhocodes/config-array@0.11.10':
|
||||
dependencies:
|
||||
'@humanwhocodes/object-schema': 1.2.1
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
minimatch: 3.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -7264,7 +7253,7 @@ snapshots:
|
|||
'@antfu/install-pkg': 0.1.1
|
||||
'@antfu/utils': 0.7.6
|
||||
'@iconify/types': 2.0.0
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
kolorist: 1.8.0
|
||||
local-pkg: 0.4.3
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -7401,7 +7390,7 @@ snapshots:
|
|||
|
||||
'@linaria/logger@4.0.0':
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
picocolors: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -7922,7 +7911,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@typescript-eslint/typescript-estree': 6.4.1(typescript@5.2.2)
|
||||
'@typescript-eslint/utils': 6.4.1(eslint@8.47.0)(typescript@5.2.2)
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
eslint: 8.47.0
|
||||
ts-api-utils: 1.0.1(typescript@5.2.2)
|
||||
optionalDependencies:
|
||||
|
|
@ -7940,10 +7929,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@typescript-eslint/types': 5.60.0
|
||||
'@typescript-eslint/visitor-keys': 5.60.0
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
semver: 7.5.4
|
||||
semver: 7.6.3
|
||||
tsutils: 3.21.0(typescript@5.2.2)
|
||||
optionalDependencies:
|
||||
typescript: 5.2.2
|
||||
|
|
@ -7954,10 +7943,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@typescript-eslint/types': 6.4.1
|
||||
'@typescript-eslint/visitor-keys': 6.4.1
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
semver: 7.5.4
|
||||
semver: 7.6.3
|
||||
ts-api-utils: 1.0.1(typescript@5.2.2)
|
||||
optionalDependencies:
|
||||
typescript: 5.2.2
|
||||
|
|
@ -7968,10 +7957,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@typescript-eslint/types': 6.9.1
|
||||
'@typescript-eslint/visitor-keys': 6.9.1
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
semver: 7.5.4
|
||||
semver: 7.6.3
|
||||
ts-api-utils: 1.0.1(typescript@5.2.2)
|
||||
optionalDependencies:
|
||||
typescript: 5.2.2
|
||||
|
|
@ -7988,7 +7977,7 @@ snapshots:
|
|||
'@typescript-eslint/typescript-estree': 5.60.0(typescript@5.2.2)
|
||||
eslint: 8.47.0
|
||||
eslint-scope: 5.1.1
|
||||
semver: 7.5.4
|
||||
semver: 7.6.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
|
@ -8002,7 +7991,7 @@ snapshots:
|
|||
'@typescript-eslint/types': 6.4.1
|
||||
'@typescript-eslint/typescript-estree': 6.4.1(typescript@5.2.2)
|
||||
eslint: 8.47.0
|
||||
semver: 7.5.4
|
||||
semver: 7.6.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
|
@ -8016,7 +8005,7 @@ snapshots:
|
|||
'@typescript-eslint/types': 6.9.1
|
||||
'@typescript-eslint/typescript-estree': 6.9.1(typescript@5.2.2)
|
||||
eslint: 8.47.0
|
||||
semver: 7.5.4
|
||||
semver: 7.6.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
|
@ -8049,15 +8038,13 @@ snapshots:
|
|||
dependencies:
|
||||
'@unhead/schema': 0.5.1
|
||||
|
||||
'@unhead/vue@0.5.1(typescript@5.2.2)(vue@3.3.4)':
|
||||
'@unhead/vue@0.5.1(vue@3.3.4)':
|
||||
dependencies:
|
||||
'@unhead/dom': 0.5.1
|
||||
'@unhead/schema': 0.5.1
|
||||
'@vueuse/shared': 12.0.0(typescript@5.2.2)
|
||||
'@vueuse/shared': 14.2.1(vue@3.3.4)
|
||||
unhead: 0.5.1
|
||||
vue: 3.3.4
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@unocss/astro@0.65.1(rollup@2.79.2)(vite@4.4.9(@types/node@18.15.11)(less@4.1.3)(terser@5.37.0))(vue@3.3.4)':
|
||||
dependencies:
|
||||
|
|
@ -8341,14 +8328,6 @@ snapshots:
|
|||
source-map-js: 1.0.2
|
||||
optional: true
|
||||
|
||||
'@vue/compiler-core@3.5.13':
|
||||
dependencies:
|
||||
'@babel/parser': 7.26.3
|
||||
'@vue/shared': 3.5.13
|
||||
entities: 4.5.0
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-dom@3.2.47':
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.2.47
|
||||
|
|
@ -8365,11 +8344,6 @@ snapshots:
|
|||
'@vue/shared': 3.3.7
|
||||
optional: true
|
||||
|
||||
'@vue/compiler-dom@3.5.13':
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.5.13
|
||||
'@vue/shared': 3.5.13
|
||||
|
||||
'@vue/compiler-sfc@3.2.47':
|
||||
dependencies:
|
||||
'@babel/parser': 7.21.4
|
||||
|
|
@ -8396,18 +8370,6 @@ snapshots:
|
|||
postcss: 8.4.28
|
||||
source-map-js: 1.0.2
|
||||
|
||||
'@vue/compiler-sfc@3.5.13':
|
||||
dependencies:
|
||||
'@babel/parser': 7.26.3
|
||||
'@vue/compiler-core': 3.5.13
|
||||
'@vue/compiler-dom': 3.5.13
|
||||
'@vue/compiler-ssr': 3.5.13
|
||||
'@vue/shared': 3.5.13
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.15
|
||||
postcss: 8.4.49
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-ssr@3.2.47':
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.2.47
|
||||
|
|
@ -8424,11 +8386,6 @@ snapshots:
|
|||
'@vue/shared': 3.3.7
|
||||
optional: true
|
||||
|
||||
'@vue/compiler-ssr@3.5.13':
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.13
|
||||
'@vue/shared': 3.5.13
|
||||
|
||||
'@vue/devtools-api@6.5.0': {}
|
||||
|
||||
'@vue/language-core@1.8.1(typescript@5.2.2)':
|
||||
|
|
@ -8464,33 +8421,17 @@ snapshots:
|
|||
dependencies:
|
||||
'@vue/shared': 3.3.4
|
||||
|
||||
'@vue/reactivity@3.5.13':
|
||||
dependencies:
|
||||
'@vue/shared': 3.5.13
|
||||
|
||||
'@vue/runtime-core@3.3.4':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.3.4
|
||||
'@vue/shared': 3.3.4
|
||||
|
||||
'@vue/runtime-core@3.5.13':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.13
|
||||
'@vue/shared': 3.5.13
|
||||
|
||||
'@vue/runtime-dom@3.3.4':
|
||||
dependencies:
|
||||
'@vue/runtime-core': 3.3.4
|
||||
'@vue/shared': 3.3.4
|
||||
csstype: 3.1.2
|
||||
|
||||
'@vue/runtime-dom@3.5.13':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.13
|
||||
'@vue/runtime-core': 3.5.13
|
||||
'@vue/shared': 3.5.13
|
||||
csstype: 3.1.3
|
||||
|
||||
'@vue/server-renderer@3.3.4(vue@3.3.4)':
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.3.4
|
||||
|
|
@ -8504,12 +8445,6 @@ snapshots:
|
|||
vue: 3.3.4
|
||||
optional: true
|
||||
|
||||
'@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.2.2))':
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.5.13
|
||||
'@vue/shared': 3.5.13
|
||||
vue: 3.5.13(typescript@5.2.2)
|
||||
|
||||
'@vue/shared@3.2.47': {}
|
||||
|
||||
'@vue/shared@3.3.4': {}
|
||||
|
|
@ -8517,8 +8452,6 @@ snapshots:
|
|||
'@vue/shared@3.3.7':
|
||||
optional: true
|
||||
|
||||
'@vue/shared@3.5.13': {}
|
||||
|
||||
'@vue/test-utils@2.3.2(vue@3.3.4)':
|
||||
dependencies:
|
||||
js-beautify: 1.14.6
|
||||
|
|
@ -8546,14 +8479,12 @@ snapshots:
|
|||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/head@1.0.0(typescript@5.2.2)(vue@3.3.4)':
|
||||
'@vueuse/head@1.0.0(vue@3.3.4)':
|
||||
dependencies:
|
||||
'@unhead/schema': 0.5.1
|
||||
'@unhead/ssr': 0.5.1
|
||||
'@unhead/vue': 0.5.1(typescript@5.2.2)(vue@3.3.4)
|
||||
'@unhead/vue': 0.5.1(vue@3.3.4)
|
||||
vue: 3.3.4
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@vueuse/metadata@10.3.0': {}
|
||||
|
||||
|
|
@ -8580,11 +8511,9 @@ snapshots:
|
|||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/shared@12.0.0(typescript@5.2.2)':
|
||||
'@vueuse/shared@14.2.1(vue@3.3.4)':
|
||||
dependencies:
|
||||
vue: 3.5.13(typescript@5.2.2)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
vue: 3.3.4
|
||||
|
||||
'@zhead/schema@1.0.0-beta.13': {}
|
||||
|
||||
|
|
@ -8608,7 +8537,7 @@ snapshots:
|
|||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -8780,7 +8709,7 @@ snapshots:
|
|||
|
||||
builtins@5.0.1:
|
||||
dependencies:
|
||||
semver: 7.5.4
|
||||
semver: 7.6.3
|
||||
|
||||
bundle-require@5.0.0(esbuild@0.23.1):
|
||||
dependencies:
|
||||
|
|
@ -9022,6 +8951,8 @@ snapshots:
|
|||
dependencies:
|
||||
browserslist: 4.24.3
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
country-code-lookup@0.1.0: {}
|
||||
|
||||
crelt@1.0.6: {}
|
||||
|
|
@ -9084,8 +9015,6 @@ snapshots:
|
|||
|
||||
csstype@3.1.2: {}
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
dash-get@1.0.2: {}
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
|
@ -9971,10 +9900,6 @@ snapshots:
|
|||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hasown@2.0.0:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hasown@2.0.2:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
|
@ -10016,14 +9941,14 @@ snapshots:
|
|||
dependencies:
|
||||
'@tootallnate/once': 2.0.0
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -10071,6 +9996,8 @@ snapshots:
|
|||
image-size@0.5.5:
|
||||
optional: true
|
||||
|
||||
immediate@3.0.6: {}
|
||||
|
||||
import-fresh@3.3.0:
|
||||
dependencies:
|
||||
parent-module: 1.0.1
|
||||
|
|
@ -10160,10 +10087,6 @@ snapshots:
|
|||
dependencies:
|
||||
has: 1.0.3
|
||||
|
||||
is-core-module@2.13.1:
|
||||
dependencies:
|
||||
hasown: 2.0.0
|
||||
|
||||
is-core-module@2.16.0:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
|
|
@ -10293,6 +10216,8 @@ snapshots:
|
|||
dependencies:
|
||||
is-docker: 2.2.1
|
||||
|
||||
isarray@1.0.0: {}
|
||||
|
||||
isarray@2.0.5: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
|
@ -10412,6 +10337,13 @@ snapshots:
|
|||
|
||||
jsonpointer@5.0.1: {}
|
||||
|
||||
jszip@3.10.1:
|
||||
dependencies:
|
||||
lie: 3.3.0
|
||||
pako: 1.0.11
|
||||
readable-stream: 2.3.8
|
||||
setimmediate: 1.0.5
|
||||
|
||||
jwt-decode@3.1.2: {}
|
||||
|
||||
kind-of@6.0.3: {}
|
||||
|
|
@ -10443,6 +10375,10 @@ snapshots:
|
|||
|
||||
libphonenumber-js@1.10.28: {}
|
||||
|
||||
lie@3.3.0:
|
||||
dependencies:
|
||||
immediate: 3.0.6
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
linkify-it@4.0.1:
|
||||
|
|
@ -10753,7 +10689,7 @@ snapshots:
|
|||
normalize-package-data@2.5.0:
|
||||
dependencies:
|
||||
hosted-git-info: 2.8.9
|
||||
resolve: 1.22.8
|
||||
resolve: 1.22.9
|
||||
semver: 5.7.2
|
||||
validate-npm-package-license: 3.0.4
|
||||
|
||||
|
|
@ -10857,6 +10793,8 @@ snapshots:
|
|||
|
||||
package-manager-detector@0.2.7: {}
|
||||
|
||||
pako@1.0.11: {}
|
||||
|
||||
param-case@2.1.1:
|
||||
dependencies:
|
||||
no-case: 2.3.2
|
||||
|
|
@ -11017,6 +10955,8 @@ snapshots:
|
|||
ansi-styles: 5.2.0
|
||||
react-is: 18.2.0
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
|
||||
prosemirror-changeset@2.2.1:
|
||||
dependencies:
|
||||
prosemirror-transform: 1.7.3
|
||||
|
|
@ -11184,6 +11124,16 @@ snapshots:
|
|||
parse-json: 5.2.0
|
||||
type-fest: 0.6.0
|
||||
|
||||
readable-stream@2.3.8:
|
||||
dependencies:
|
||||
core-util-is: 1.0.3
|
||||
inherits: 2.0.4
|
||||
isarray: 1.0.0
|
||||
process-nextick-args: 2.0.1
|
||||
safe-buffer: 5.1.2
|
||||
string_decoder: 1.1.1
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
readable-stream@3.6.2:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
|
|
@ -11261,19 +11211,13 @@ snapshots:
|
|||
|
||||
resolve@1.22.2:
|
||||
dependencies:
|
||||
is-core-module: 2.13.1
|
||||
is-core-module: 2.16.0
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
resolve@1.22.4:
|
||||
dependencies:
|
||||
is-core-module: 2.13.1
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
resolve@1.22.8:
|
||||
dependencies:
|
||||
is-core-module: 2.13.1
|
||||
is-core-module: 2.16.0
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
|
|
@ -11322,6 +11266,8 @@ snapshots:
|
|||
has-symbols: 1.1.0
|
||||
isarray: 2.0.5
|
||||
|
||||
safe-buffer@5.1.2: {}
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safe-regex-test@1.1.0:
|
||||
|
|
@ -11401,6 +11347,8 @@ snapshots:
|
|||
is-plain-object: 2.0.4
|
||||
is-primitive: 3.0.1
|
||||
|
||||
setimmediate@1.0.5: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
|
@ -11565,6 +11513,10 @@ snapshots:
|
|||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.0.0
|
||||
|
||||
string_decoder@1.1.1:
|
||||
dependencies:
|
||||
safe-buffer: 5.1.2
|
||||
|
||||
string_decoder@1.3.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
|
@ -12018,7 +11970,7 @@ snapshots:
|
|||
vite-node@0.34.0(@types/node@18.15.11)(less@4.1.3)(terser@5.37.0):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
mlly: 1.4.0
|
||||
pathe: 1.1.1
|
||||
picocolors: 1.0.0
|
||||
|
|
@ -12125,14 +12077,14 @@ snapshots:
|
|||
|
||||
vue-eslint-parser@9.3.1(eslint@8.47.0):
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
eslint: 8.47.0
|
||||
eslint-scope: 7.2.2
|
||||
eslint-visitor-keys: 3.4.3
|
||||
espree: 9.6.1
|
||||
esquery: 1.5.0
|
||||
lodash: 4.17.21
|
||||
semver: 7.5.4
|
||||
semver: 7.6.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -12180,16 +12132,6 @@ snapshots:
|
|||
'@vue/server-renderer': 3.3.4(vue@3.3.4)
|
||||
'@vue/shared': 3.3.4
|
||||
|
||||
vue@3.5.13(typescript@5.2.2):
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.13
|
||||
'@vue/compiler-sfc': 3.5.13
|
||||
'@vue/runtime-dom': 3.5.13
|
||||
'@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.2.2))
|
||||
'@vue/shared': 3.5.13
|
||||
optionalDependencies:
|
||||
typescript: 5.2.2
|
||||
|
||||
vuedraggable@4.1.0(vue@3.3.4):
|
||||
dependencies:
|
||||
sortablejs: 1.14.0
|
||||
|
|
|
|||
56
src/tools/icon-generator/icon-generator.e2e.spec.ts
Normal file
56
src/tools/icon-generator/icon-generator.e2e.spec.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { Buffer } from 'node:buffer';
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const sampleSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512">
|
||||
<rect width="512" height="512" fill="#3b82f6"/>
|
||||
<circle cx="256" cy="256" r="140" fill="#f59e0b"/>
|
||||
</svg>`;
|
||||
|
||||
test.describe('Tool - Icon generator', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/icon-generator');
|
||||
});
|
||||
|
||||
test('has correct title', async ({ page }) => {
|
||||
await expect(page).toHaveTitle('Icon generator - IT Tools');
|
||||
});
|
||||
|
||||
test('shows generator controls and default state', async ({ page }) => {
|
||||
await expect(page.getByText('Generate icons')).toBeVisible();
|
||||
await expect(page.getByText('Download zip')).toBeVisible();
|
||||
await expect(page.getByText('Selected output sizes: -')).toBeVisible();
|
||||
await expect(page.getByRole('checkbox', { name: 'Include manifest.json in ZIP' })).toBeDisabled();
|
||||
});
|
||||
|
||||
test('applies preset sizes and toggles manifest option with reset', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'PWA' }).click();
|
||||
await expect(page.getByText('Selected output sizes: 72, 96, 128, 144, 152, 192, 384, 512')).toBeVisible();
|
||||
|
||||
const manifestCheckbox = page.getByRole('checkbox', { name: 'Include manifest.json in ZIP' });
|
||||
await expect(manifestCheckbox).toBeEnabled();
|
||||
await manifestCheckbox.check();
|
||||
await expect(manifestCheckbox).toBeChecked();
|
||||
|
||||
await page.getByRole('button', { name: 'Clear preset sizes' }).click();
|
||||
await expect(page.getByText('Selected output sizes: -')).toBeVisible();
|
||||
await expect(manifestCheckbox).toBeDisabled();
|
||||
await expect(manifestCheckbox).not.toBeChecked();
|
||||
});
|
||||
|
||||
test('generates icons and downloads zip', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'PWA' }).click();
|
||||
await page.locator('input[type="file"]').setInputFiles({
|
||||
name: 'source.svg',
|
||||
mimeType: 'image/svg+xml',
|
||||
buffer: Buffer.from(sampleSvg),
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: 'Generate icons' }).click();
|
||||
await expect(page.getByText('Generated icons (8)')).toBeVisible();
|
||||
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await page.getByRole('button', { name: 'Download zip' }).click();
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBe('icon-icons.zip');
|
||||
});
|
||||
});
|
||||
103
src/tools/icon-generator/icon-generator.service.test.ts
Normal file
103
src/tools/icon-generator/icon-generator.service.test.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
ICON_PRESETS,
|
||||
buildIconFilename,
|
||||
calculateDrawArea,
|
||||
createWebAppManifest,
|
||||
getPresetSizes,
|
||||
normalizeSizes,
|
||||
sanitizeBaseName,
|
||||
} from './icon-generator.service';
|
||||
|
||||
describe('icon-generator', () => {
|
||||
describe('normalizeSizes', () => {
|
||||
it('should remove invalid values and deduplicate sizes', () => {
|
||||
expect(normalizeSizes([192, 192, 0, -1, 512.8, 72])).toEqual([72, 192, 512]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPresetSizes', () => {
|
||||
it('should return merged and sorted sizes from multiple presets', () => {
|
||||
const sizes = getPresetSizes(['pwa', 'android']);
|
||||
|
||||
expect(sizes).toEqual([48, 72, 96, 128, 144, 152, 192, 384, 512]);
|
||||
});
|
||||
|
||||
it('should include known iOS size', () => {
|
||||
const sizes = getPresetSizes(['ios']);
|
||||
expect(sizes).toContain(180);
|
||||
expect(sizes).toContain(1024);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateDrawArea', () => {
|
||||
it('should crop in cover mode for rectangular source', () => {
|
||||
const drawArea = calculateDrawArea({
|
||||
sourceWidth: 400,
|
||||
sourceHeight: 200,
|
||||
targetSize: 100,
|
||||
fitMode: 'cover',
|
||||
});
|
||||
|
||||
expect(drawArea.width).toBeGreaterThan(100);
|
||||
expect(drawArea.height).toBe(100);
|
||||
expect(drawArea.x).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it('should keep full image in contain mode for rectangular source', () => {
|
||||
const drawArea = calculateDrawArea({
|
||||
sourceWidth: 400,
|
||||
sourceHeight: 200,
|
||||
targetSize: 100,
|
||||
fitMode: 'contain',
|
||||
});
|
||||
|
||||
expect(drawArea.width).toBe(100);
|
||||
expect(drawArea.height).toBeLessThan(100);
|
||||
expect(drawArea.y).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should expose 3 platform presets', () => {
|
||||
expect(ICON_PRESETS.map(preset => preset.key)).toEqual(['pwa', 'android', 'ios']);
|
||||
});
|
||||
|
||||
it('should sanitize base name and use it in icon filename', () => {
|
||||
expect(sanitizeBaseName(' My App/Icon ')).toBe('My-App-Icon');
|
||||
expect(buildIconFilename(192, ' My App/Icon ')).toBe('My-App-Icon-192x192.png');
|
||||
expect(buildIconFilename(192, ' ')).toBe('icon-192x192.png');
|
||||
});
|
||||
|
||||
it('should create manifest json with provided icons', () => {
|
||||
const manifestJson = createWebAppManifest({
|
||||
appName: 'my-app',
|
||||
description: 'my generated icons',
|
||||
icons: [
|
||||
{ filename: 'my-app-192x192.png', size: 192 },
|
||||
{ filename: 'my-app-512x512.png', size: 512 },
|
||||
],
|
||||
});
|
||||
|
||||
const manifest = JSON.parse(manifestJson) as {
|
||||
id: string
|
||||
name: string
|
||||
short_name: string
|
||||
description: string
|
||||
lang: string
|
||||
start_url: string
|
||||
scope: string
|
||||
icons: { src: string; sizes: string; type: string; purpose: string }[]
|
||||
};
|
||||
expect(manifest.id).toBe('/');
|
||||
expect(manifest.name).toBe('my-app');
|
||||
expect(manifest.short_name).toBe('my-app');
|
||||
expect(manifest.description).toBe('my generated icons');
|
||||
expect(manifest.lang).toBe('en');
|
||||
expect(manifest.start_url).toBe('/');
|
||||
expect(manifest.scope).toBe('/');
|
||||
expect(manifest.icons).toEqual([
|
||||
{ src: 'my-app-192x192.png', sizes: '192x192', type: 'image/png', purpose: 'any maskable' },
|
||||
{ src: 'my-app-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'any maskable' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
179
src/tools/icon-generator/icon-generator.service.ts
Normal file
179
src/tools/icon-generator/icon-generator.service.ts
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
export type IconPresetKey = 'pwa' | 'android' | 'ios';
|
||||
export type IconFitMode = 'cover' | 'contain';
|
||||
|
||||
export interface IconPreset {
|
||||
key: IconPresetKey
|
||||
sizes: number[]
|
||||
}
|
||||
|
||||
export interface GeneratedIcon {
|
||||
size: number
|
||||
filename: string
|
||||
dataUrl: string
|
||||
}
|
||||
|
||||
export function createWebAppManifest({
|
||||
appName,
|
||||
icons,
|
||||
description,
|
||||
startUrl = '/',
|
||||
scope = '/',
|
||||
lang = 'en',
|
||||
backgroundColor = '#ffffff',
|
||||
themeColor = '#ffffff',
|
||||
}: {
|
||||
appName: string
|
||||
icons: { filename: string; size: number }[]
|
||||
description?: string
|
||||
startUrl?: string
|
||||
scope?: string
|
||||
lang?: string
|
||||
backgroundColor?: string
|
||||
themeColor?: string
|
||||
}) {
|
||||
return JSON.stringify({
|
||||
id: startUrl,
|
||||
name: appName,
|
||||
short_name: appName,
|
||||
description: description ?? `${appName} icons`,
|
||||
lang,
|
||||
start_url: startUrl,
|
||||
scope,
|
||||
display: 'standalone',
|
||||
background_color: backgroundColor,
|
||||
theme_color: themeColor,
|
||||
icons: icons.map(icon => ({
|
||||
src: icon.filename,
|
||||
sizes: `${icon.size}x${icon.size}`,
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable',
|
||||
})),
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
export const ICON_PRESETS: IconPreset[] = [
|
||||
{ key: 'pwa', sizes: [72, 96, 128, 144, 152, 192, 384, 512] },
|
||||
{ key: 'android', sizes: [48, 72, 96, 144, 192, 512] },
|
||||
{ key: 'ios', sizes: [20, 29, 40, 58, 60, 76, 80, 87, 120, 152, 167, 180, 1024] },
|
||||
];
|
||||
|
||||
export function normalizeSizes(sizes: number[]) {
|
||||
return Array.from(
|
||||
new Set(
|
||||
sizes
|
||||
.map(size => Math.floor(size))
|
||||
.filter(size => Number.isFinite(size) && size > 0),
|
||||
),
|
||||
).sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
export function getPresetSizes(presetKeys: IconPresetKey[]) {
|
||||
const selectedSizes = ICON_PRESETS
|
||||
.filter(preset => presetKeys.includes(preset.key))
|
||||
.flatMap(preset => preset.sizes);
|
||||
|
||||
return normalizeSizes(selectedSizes);
|
||||
}
|
||||
|
||||
export function sanitizeBaseName(baseName: string) {
|
||||
const normalized = baseName.trim().replace(/[<>:"/\\|?*]+/g, '-').replace(/\s+/g, '-');
|
||||
return normalized || 'icon';
|
||||
}
|
||||
|
||||
export function buildIconFilename(size: number, baseName = 'icon') {
|
||||
const safeBaseName = sanitizeBaseName(baseName);
|
||||
return `${safeBaseName}-${size}x${size}.png`;
|
||||
}
|
||||
|
||||
export function calculateDrawArea({
|
||||
sourceWidth,
|
||||
sourceHeight,
|
||||
targetSize,
|
||||
fitMode,
|
||||
}: {
|
||||
sourceWidth: number
|
||||
sourceHeight: number
|
||||
targetSize: number
|
||||
fitMode: IconFitMode
|
||||
}) {
|
||||
const scale = fitMode === 'cover'
|
||||
? Math.max(targetSize / sourceWidth, targetSize / sourceHeight)
|
||||
: Math.min(targetSize / sourceWidth, targetSize / sourceHeight);
|
||||
|
||||
const drawWidth = sourceWidth * scale;
|
||||
const drawHeight = sourceHeight * scale;
|
||||
|
||||
return {
|
||||
x: (targetSize - drawWidth) / 2,
|
||||
y: (targetSize - drawHeight) / 2,
|
||||
width: drawWidth,
|
||||
height: drawHeight,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadImageFromFile(file: File) {
|
||||
return new Promise<HTMLImageElement>((resolve, reject) => {
|
||||
const url = URL.createObjectURL(file);
|
||||
const image = new Image();
|
||||
image.onload = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
resolve(image);
|
||||
};
|
||||
image.onerror = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
reject(new Error('Unable to load image file.'));
|
||||
};
|
||||
image.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
export function createResizedIcon({
|
||||
image,
|
||||
size,
|
||||
fitMode,
|
||||
}: {
|
||||
image: HTMLImageElement
|
||||
size: number
|
||||
fitMode: IconFitMode
|
||||
}) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Unable to get canvas context.');
|
||||
}
|
||||
|
||||
const drawArea = calculateDrawArea({
|
||||
sourceWidth: image.width,
|
||||
sourceHeight: image.height,
|
||||
targetSize: size,
|
||||
fitMode,
|
||||
});
|
||||
|
||||
context.clearRect(0, 0, size, size);
|
||||
context.imageSmoothingEnabled = true;
|
||||
context.imageSmoothingQuality = 'high';
|
||||
context.drawImage(image, drawArea.x, drawArea.y, drawArea.width, drawArea.height);
|
||||
|
||||
return canvas.toDataURL('image/png');
|
||||
}
|
||||
|
||||
export function createResizedIcons({
|
||||
image,
|
||||
sizes,
|
||||
fitMode,
|
||||
baseName,
|
||||
}: {
|
||||
image: HTMLImageElement
|
||||
sizes: number[]
|
||||
fitMode: IconFitMode
|
||||
baseName?: string
|
||||
}) {
|
||||
return normalizeSizes(sizes).map((size): GeneratedIcon => ({
|
||||
size,
|
||||
filename: buildIconFilename(size, baseName),
|
||||
dataUrl: createResizedIcon({ image, size, fitMode }),
|
||||
}));
|
||||
}
|
||||
267
src/tools/icon-generator/icon-generator.vue
Normal file
267
src/tools/icon-generator/icon-generator.vue
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
<script setup lang="ts">
|
||||
import JSZip from 'jszip';
|
||||
import {
|
||||
ICON_PRESETS,
|
||||
createResizedIcons,
|
||||
createWebAppManifest,
|
||||
getPresetSizes,
|
||||
loadImageFromFile,
|
||||
normalizeSizes,
|
||||
sanitizeBaseName,
|
||||
} from './icon-generator.service';
|
||||
import type { GeneratedIcon, IconFitMode, IconPresetKey } from './icon-generator.service';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const selectedSizesModel = ref<number[]>([]);
|
||||
const customSizeInput = ref<number | null>(null);
|
||||
const fitMode = ref<IconFitMode>('cover');
|
||||
const baseName = ref('icon');
|
||||
const includeManifest = ref(false);
|
||||
|
||||
const sourceFile = ref<File | null>(null);
|
||||
const sourcePreviewUrl = ref('');
|
||||
|
||||
const generatedIcons = ref<GeneratedIcon[]>([]);
|
||||
const isGenerating = ref(false);
|
||||
|
||||
const availableSizes = computed(() =>
|
||||
normalizeSizes([...ICON_PRESETS.flatMap(preset => preset.sizes), ...selectedSizesModel.value]),
|
||||
);
|
||||
const selectedSizes = computed(() => normalizeSizes(selectedSizesModel.value));
|
||||
const isPwaPresetApplied = computed(() => isPresetApplied('pwa'));
|
||||
|
||||
const canGenerate = computed(() => sourceFile.value !== null && selectedSizes.value.length > 0 && !isGenerating.value);
|
||||
|
||||
function readFileAsDataUrl(file: File) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(String(reader.result || ''));
|
||||
reader.onerror = () => reject(new Error('Unable to read image file.'));
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
async function onUpload(file: File) {
|
||||
sourceFile.value = file;
|
||||
sourcePreviewUrl.value = await readFileAsDataUrl(file);
|
||||
generatedIcons.value = [];
|
||||
}
|
||||
|
||||
function addCustomSize() {
|
||||
if (customSizeInput.value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size = Math.floor(customSizeInput.value);
|
||||
if (size <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSizesModel.value = normalizeSizes([...selectedSizesModel.value, size]);
|
||||
customSizeInput.value = null;
|
||||
}
|
||||
|
||||
function applyPreset(presetKey: IconPresetKey) {
|
||||
const sizes = getPresetSizes([presetKey]);
|
||||
selectedSizesModel.value = normalizeSizes([...selectedSizesModel.value, ...sizes]);
|
||||
}
|
||||
|
||||
function isPresetApplied(presetKey: IconPresetKey) {
|
||||
const sizes = getPresetSizes([presetKey]);
|
||||
return sizes.every(size => selectedSizesModel.value.includes(size));
|
||||
}
|
||||
|
||||
function clearPresetSizes() {
|
||||
const presetSizes = normalizeSizes(ICON_PRESETS.flatMap(preset => preset.sizes));
|
||||
selectedSizesModel.value = selectedSizesModel.value.filter(size => !presetSizes.includes(size));
|
||||
includeManifest.value = false;
|
||||
}
|
||||
|
||||
function getPresetLabel(presetKey: IconPresetKey) {
|
||||
return t(`tools.icon-generator.presetLabels.${presetKey}`);
|
||||
}
|
||||
|
||||
async function generateIcons() {
|
||||
if (!sourceFile.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
isGenerating.value = true;
|
||||
try {
|
||||
const image = await loadImageFromFile(sourceFile.value);
|
||||
generatedIcons.value = createResizedIcons({
|
||||
image,
|
||||
sizes: selectedSizes.value,
|
||||
fitMode: fitMode.value,
|
||||
baseName: baseName.value,
|
||||
});
|
||||
}
|
||||
finally {
|
||||
isGenerating.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function downloadIcon(icon: GeneratedIcon) {
|
||||
const link = document.createElement('a');
|
||||
link.href = icon.dataUrl;
|
||||
link.download = icon.filename;
|
||||
link.click();
|
||||
}
|
||||
|
||||
async function downloadIconsAsZip() {
|
||||
const zip = new JSZip();
|
||||
|
||||
generatedIcons.value.forEach((icon) => {
|
||||
const [, base64Content] = icon.dataUrl.split(',');
|
||||
if (!base64Content) {
|
||||
return;
|
||||
}
|
||||
zip.file(icon.filename, base64Content, { base64: true });
|
||||
});
|
||||
|
||||
if (includeManifest.value && isPwaPresetApplied.value) {
|
||||
const appName = sanitizeBaseName(baseName.value);
|
||||
zip.file('manifest.json', createWebAppManifest({
|
||||
appName,
|
||||
icons: generatedIcons.value.map(icon => ({ filename: icon.filename, size: icon.size })),
|
||||
}));
|
||||
}
|
||||
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
const zipUrl = URL.createObjectURL(zipBlob);
|
||||
const link = document.createElement('a');
|
||||
link.href = zipUrl;
|
||||
link.download = `${sanitizeBaseName(baseName.value)}-icons.zip`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(zipUrl);
|
||||
}
|
||||
|
||||
watch(isPwaPresetApplied, (pwaEnabled) => {
|
||||
if (!pwaEnabled) {
|
||||
includeManifest.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<c-card :title="t('tools.icon-generator.presets')">
|
||||
<n-space vertical :size="12">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<c-button
|
||||
v-for="preset in ICON_PRESETS"
|
||||
:key="preset.key"
|
||||
:type="isPresetApplied(preset.key) ? 'primary' : 'default'"
|
||||
@click="applyPreset(preset.key)"
|
||||
>
|
||||
{{ getPresetLabel(preset.key) }}
|
||||
</c-button>
|
||||
<c-button secondary @click="clearPresetSizes">
|
||||
{{ t('tools.icon-generator.clearPresetSizes') }}
|
||||
</c-button>
|
||||
</div>
|
||||
|
||||
<c-buttons-select
|
||||
v-model:value="fitMode"
|
||||
:label="`${t('tools.icon-generator.fitMode')}:`"
|
||||
label-position="left"
|
||||
:options="[
|
||||
{ label: t('tools.icon-generator.cover'), value: 'cover' },
|
||||
{ label: t('tools.icon-generator.contain'), value: 'contain' },
|
||||
]"
|
||||
/>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<n-input-number v-model:value="customSizeInput" :min="16" :max="2048" placeholder="256" />
|
||||
<c-button @click="addCustomSize">
|
||||
{{ t('tools.icon-generator.addSize') }}
|
||||
</c-button>
|
||||
</div>
|
||||
|
||||
<c-input-text
|
||||
v-model:value="baseName"
|
||||
:label="`${t('tools.icon-generator.baseName')}:`"
|
||||
placeholder="icon"
|
||||
label-position="left"
|
||||
/>
|
||||
<n-text depth="3">
|
||||
{{ t('tools.icon-generator.baseNameHint') }}
|
||||
</n-text>
|
||||
|
||||
<n-checkbox v-model:checked="includeManifest" :disabled="!isPwaPresetApplied">
|
||||
{{ t('tools.icon-generator.includeManifest') }}
|
||||
</n-checkbox>
|
||||
|
||||
<n-checkbox-group v-model:value="selectedSizesModel">
|
||||
<n-space>
|
||||
<n-checkbox
|
||||
v-for="size in availableSizes"
|
||||
:key="size"
|
||||
:value="size"
|
||||
:label="`${size}x${size}`"
|
||||
/>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
|
||||
<n-text depth="3">
|
||||
{{ t('tools.icon-generator.selectedSizes') }}: {{ selectedSizes.join(', ') || '-' }}
|
||||
</n-text>
|
||||
</n-space>
|
||||
</c-card>
|
||||
|
||||
<c-card>
|
||||
<c-file-upload
|
||||
:title="t('tools.icon-generator.uploadTitle')"
|
||||
accept="image/png,image/jpeg,image/webp,image/svg+xml,image/x-icon,image/vnd.microsoft.icon"
|
||||
@file-upload="onUpload"
|
||||
/>
|
||||
|
||||
<div mt-4 flex flex-col items-center gap-3>
|
||||
<n-image v-if="sourcePreviewUrl" :src="sourcePreviewUrl" width="180" />
|
||||
<n-text v-else depth="3">
|
||||
{{ t('tools.icon-generator.noImage') }}
|
||||
</n-text>
|
||||
|
||||
<div flex flex-wrap justify-center gap-2>
|
||||
<c-button :disabled="!canGenerate" @click="generateIcons">
|
||||
{{ t('tools.icon-generator.generate') }}
|
||||
</c-button>
|
||||
<c-button :disabled="generatedIcons.length === 0" secondary @click="downloadIconsAsZip">
|
||||
{{ t('tools.icon-generator.downloadZip') }}
|
||||
</c-button>
|
||||
</div>
|
||||
</div>
|
||||
</c-card>
|
||||
|
||||
<c-card v-if="generatedIcons.length > 0" :title="`${t('tools.icon-generator.output')} (${generatedIcons.length})`">
|
||||
<n-grid cols="1 520:2 760:3" :x-gap="12" :y-gap="12">
|
||||
<n-gi v-for="icon in generatedIcons" :key="icon.filename">
|
||||
<div class="generated-item">
|
||||
<n-image :src="icon.dataUrl" width="96" preview-disabled />
|
||||
<div class="text-center">
|
||||
<n-text strong>
|
||||
{{ icon.size }}x{{ icon.size }}
|
||||
</n-text>
|
||||
</div>
|
||||
<c-button size="small" @click="downloadIcon(icon)">
|
||||
{{ t('tools.icon-generator.download') }}
|
||||
</c-button>
|
||||
</div>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</c-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.generated-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--c-border-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
13
src/tools/icon-generator/index.ts
Normal file
13
src/tools/icon-generator/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { ArrowsShuffle } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
import { translate } from '@/plugins/i18n.plugin';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: translate('tools.icon-generator.title'),
|
||||
path: '/icon-generator',
|
||||
description: translate('tools.icon-generator.description'),
|
||||
keywords: ['icon', 'generator', 'pwa', 'android', 'ios', 'app icon', 'resize', 'favicon'],
|
||||
component: () => import('./icon-generator.vue'),
|
||||
icon: ArrowsShuffle,
|
||||
createdAt: new Date('2026-02-26'),
|
||||
});
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { tool as base64FileConverter } from './base64-file-converter';
|
||||
import { tool as base64StringConverter } from './base64-string-converter';
|
||||
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
||||
import { tool as iconGenerator } from './icon-generator';
|
||||
import { tool as emailNormalizer } from './email-normalizer';
|
||||
|
||||
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
||||
|
|
@ -141,7 +142,7 @@ export const toolsByCategory: ToolCategory[] = [
|
|||
},
|
||||
{
|
||||
name: 'Images and videos',
|
||||
components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder],
|
||||
components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder, iconGenerator],
|
||||
},
|
||||
{
|
||||
name: 'Development',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue