mirror of
https://github.com/CorentinTh/it-tools.git
synced 2026-05-13 13:56:43 +00:00
Merge 795fc6dd0e into d505845f91
This commit is contained in:
commit
a6bfc0f9c9
10 changed files with 783 additions and 160 deletions
4
.github/workflows/e2e-tests.yml
vendored
4
.github/workflows/e2e-tests.yml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
|||
jobs:
|
||||
test:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
shard: [1/3, 2/3, 3/3]
|
||||
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
|
||||
- name: Get Playwright version
|
||||
id: playwright-version
|
||||
run: echo "PLAYWRIGHT_VERSION=$(jq -r .dependencies.playwright package.json)" >> "$GITHUB_OUTPUT"
|
||||
run: echo "PLAYWRIGHT_VERSION=$(jq -r '.devDependencies[\"@playwright/test\"] // .dependencies.playwright // \"unknown\"' package.json)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -149,5 +150,10 @@
|
|||
"vitest": "^0.34.0",
|
||||
"workbox-window": "^7.0.0",
|
||||
"zx": "^7.2.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@vueuse/shared": "10.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
281
pnpm-lock.yaml
generated
281
pnpm-lock.yaml
generated
|
|
@ -4,6 +4,9 @@ settings:
|
|||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
'@vueuse/shared': 10.3.0
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
|
|
@ -49,7 +52,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 +116,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
|
||||
|
|
@ -356,17 +362,20 @@ packages:
|
|||
|
||||
'@antfu/eslint-config-basic@0.41.0':
|
||||
resolution: {integrity: sha512-zcwFv+nEV/NroeeVWriNdnIGd9soOLRG8wIiVz4VVJ0BjONrqQR56HLG/gDxH/1GUYBnQCEcVxGUmegce08cnw==}
|
||||
deprecated: Deprecated, please migrate to @antfu/eslint-config with the flat config
|
||||
peerDependencies:
|
||||
eslint: '>=7.4.0'
|
||||
|
||||
'@antfu/eslint-config-ts@0.41.0':
|
||||
resolution: {integrity: sha512-ng3GYpJGZgrxGwBVda/MgUpveH3LbEqdPCFi1+S5e62W4kf8rmEVbhc0I8w7/aKN0uNqir5SInYg8gob2maDAQ==}
|
||||
deprecated: Deprecated, please migrate to @antfu/eslint-config with the flat config
|
||||
peerDependencies:
|
||||
eslint: '>=7.4.0'
|
||||
typescript: '>=3.9'
|
||||
|
||||
'@antfu/eslint-config-vue@0.41.0':
|
||||
resolution: {integrity: sha512-iJiEGRUgRmT3mQCmGl0hTMwq/ShXRjRPjpgsDcphKJyEF06ZIR/4gxHn+utQRLT2hD39DQN8gk0ZbpV3gWtf/g==}
|
||||
deprecated: Deprecated, please migrate to @antfu/eslint-config with the flat config
|
||||
peerDependencies:
|
||||
eslint: '>=7.4.0'
|
||||
|
||||
|
|
@ -1464,6 +1473,7 @@ packages:
|
|||
'@humanwhocodes/config-array@0.11.10':
|
||||
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
|
||||
engines: {node: '>=10.10.0'}
|
||||
deprecated: Use @eslint/config-array instead
|
||||
|
||||
'@humanwhocodes/module-importer@1.0.1':
|
||||
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
|
||||
|
|
@ -1471,6 +1481,7 @@ packages:
|
|||
|
||||
'@humanwhocodes/object-schema@1.2.1':
|
||||
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
|
||||
deprecated: Use @eslint/object-schema instead
|
||||
|
||||
'@iconify-json/mdi@1.1.50':
|
||||
resolution: {integrity: sha512-SgbT5w5eHCdOG74ZWPz7HlTGk6VsifIJhNi6lAsxj/5Nlqt6Cz4LlQmSa9eecU9p075Jub2aAx/o7YI+GCahRQ==}
|
||||
|
|
@ -1629,6 +1640,7 @@ packages:
|
|||
'@playwright/test@1.32.3':
|
||||
resolution: {integrity: sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==}
|
||||
engines: {node: '>=14'}
|
||||
deprecated: Please update to the latest version of Playwright to test up-to-date browsers.
|
||||
hasBin: true
|
||||
|
||||
'@polka/url@1.0.0-next.28':
|
||||
|
|
@ -2303,9 +2315,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 +2324,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 +2339,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 +2359,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 +2375,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 +2384,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:
|
||||
|
|
@ -2428,20 +2411,15 @@ packages:
|
|||
peerDependencies:
|
||||
vue-router: '>=4.0.0-rc.1'
|
||||
|
||||
'@vueuse/shared@10.0.0':
|
||||
resolution: {integrity: sha512-Zh3LgJqvUBWVY3SiMvXanTcfAneGbt63QPczBRDNgQ6jd/ehodO9a1lCFzaA6SWJJoI+ugVTjHFYJdoR656DVQ==}
|
||||
|
||||
'@vueuse/shared@10.3.0':
|
||||
resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==}
|
||||
|
||||
'@vueuse/shared@12.0.0':
|
||||
resolution: {integrity: sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==}
|
||||
|
||||
'@zhead/schema@1.0.0-beta.13':
|
||||
resolution: {integrity: sha512-P1A1vRGFBhITco8Iw4/hvnDYoE/SoVrd71dW1pBFdXJb3vP+pBtoOuhbEKy0ROJGOyzQuqvFibcwzyLlWMqNiQ==}
|
||||
|
||||
abab@2.0.6:
|
||||
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
|
||||
deprecated: Use your platform's native atob() and btoa() methods instead
|
||||
|
||||
abbrev@1.1.1:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
|
|
@ -2552,6 +2530,7 @@ packages:
|
|||
|
||||
babel-merge@3.0.0:
|
||||
resolution: {integrity: sha512-eBOBtHnzt9xvnjpYNI5HmaPp/b2vMveE5XggzqHnQeHJ8mFIBrBv6WZEVIj5jJ2uwTItkqKo9gWzEEcBxEq0yw==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
|
||||
|
|
@ -2798,7 +2777,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 +2810,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 +2878,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==}
|
||||
|
||||
|
|
@ -3054,6 +3033,7 @@ packages:
|
|||
domexception@4.0.0:
|
||||
resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Use your platform's native DOMException instead
|
||||
|
||||
domhandler@5.0.3:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
|
|
@ -3246,6 +3226,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
|
||||
|
||||
|
|
@ -3271,6 +3252,7 @@ packages:
|
|||
eslint-plugin-markdown@3.0.1:
|
||||
resolution: {integrity: sha512-8rqoc148DWdGdmYF6WSQFT3uQ6PO7zXYgeBpHAOAakX/zpq+NvFYbDA/H7PYzHajwtmaOzAwfxyl++x0g1/N9A==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
deprecated: Please use @eslint/markdown instead
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
|
|
@ -3337,6 +3319,7 @@ packages:
|
|||
eslint@8.47.0:
|
||||
resolution: {integrity: sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
|
||||
hasBin: true
|
||||
|
||||
espree@9.6.1:
|
||||
|
|
@ -3566,10 +3549,12 @@ packages:
|
|||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
||||
glob@8.1.0:
|
||||
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
||||
globals@11.12.0:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
|
|
@ -3737,6 +3722,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'}
|
||||
|
|
@ -3758,6 +3746,7 @@ packages:
|
|||
|
||||
inflight@1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
|
@ -3974,6 +3963,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 +4088,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 +4117,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==}
|
||||
|
||||
|
|
@ -4172,6 +4170,7 @@ packages:
|
|||
|
||||
loupe@2.3.6:
|
||||
resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
|
||||
deprecated: Please upgrade to 2.3.7 which fixes GHSA-4q6p-r6v2-jvc5
|
||||
|
||||
lower-case-first@1.0.2:
|
||||
resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==}
|
||||
|
|
@ -4380,6 +4379,7 @@ packages:
|
|||
node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
deprecated: Use your platform's native DOMException instead
|
||||
|
||||
node-fetch-native@1.6.4:
|
||||
resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==}
|
||||
|
|
@ -4498,6 +4498,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==}
|
||||
|
||||
|
|
@ -4610,6 +4613,7 @@ packages:
|
|||
plausible-tracker@0.3.8:
|
||||
resolution: {integrity: sha512-lmOWYQ7s9KOUJ1R+YTOR3HrjdbxIS2Z4de0P/Jx2dQPteznJl2eX3tXxKClpvbfyGP59B5bbhW8ftN59HbbFSg==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
|
||||
playwright-core@1.32.3:
|
||||
resolution: {integrity: sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg==}
|
||||
|
|
@ -4665,6 +4669,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 +4795,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 +4878,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
|
||||
|
|
@ -4894,6 +4900,7 @@ packages:
|
|||
|
||||
rimraf@3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
|
||||
rollup@2.79.2:
|
||||
|
|
@ -4919,6 +4926,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 +5006,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,9 +5084,11 @@ 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==}
|
||||
deprecated: Please use @jridgewell/sourcemap-codec instead
|
||||
|
||||
spdx-correct@3.2.0:
|
||||
resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
|
||||
|
|
@ -5132,6 +5147,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==}
|
||||
|
||||
|
|
@ -5569,6 +5587,7 @@ packages:
|
|||
|
||||
vite-plugin-vue-markdown@0.23.5:
|
||||
resolution: {integrity: sha512-NXTZ4y+n691gLPWayMBbh4jldQeaqDp9e9WjWUYbn9obsLqS9qU+hr4RAruDq5kP4siTOp7JDV34Sw5eA7WxLg==}
|
||||
deprecated: '`vite-plugin-vue-markdown` is renamed to `unplugin-vue-markdown`. For usages in Vite, you also need to change the import path to `unplugin-vue-markdown/vite`.'
|
||||
peerDependencies:
|
||||
vite: ^2.0.0 || ^3.0.0-0 || ^4.0.0
|
||||
|
||||
|
|
@ -5675,6 +5694,7 @@ packages:
|
|||
vue-i18n@9.9.1:
|
||||
resolution: {integrity: sha512-xyQ4VspLdNSPTKBFBPWa1tvtj+9HuockZwgFeD2OhxxXuC2CWeNvV4seu2o9+vbQOyQbhAM5Ez56oxUrrnTWdw==}
|
||||
engines: {node: '>= 16'}
|
||||
deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
|
||||
|
|
@ -5701,14 +5721,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:
|
||||
|
|
@ -5754,6 +5766,7 @@ packages:
|
|||
whatwg-encoding@2.0.0:
|
||||
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
|
||||
|
||||
whatwg-mimetype@3.0.0:
|
||||
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
|
||||
|
|
@ -6117,7 +6130,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
|
||||
|
|
@ -7030,7 +7043,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
|
||||
|
|
@ -7401,7 +7414,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 +7935,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 +7953,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
|
||||
|
|
@ -7968,10 +7981,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 +8001,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 +8015,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
|
||||
|
|
@ -8049,15 +8062,15 @@ 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': 10.3.0(vue@3.3.4)
|
||||
unhead: 0.5.1
|
||||
vue: 3.3.4
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
- '@vue/composition-api'
|
||||
|
||||
'@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 +8354,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 +8370,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 +8396,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 +8412,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 +8447,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 +8471,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 +8478,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,33 +8505,26 @@ 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
|
||||
- '@vue/composition-api'
|
||||
|
||||
'@vueuse/metadata@10.3.0': {}
|
||||
|
||||
'@vueuse/router@10.0.0(vue-router@4.1.6(vue@3.3.4))(vue@3.3.4)':
|
||||
dependencies:
|
||||
'@vueuse/shared': 10.0.0(vue@3.3.4)
|
||||
'@vueuse/shared': 10.3.0(vue@3.3.4)
|
||||
vue-demi: 0.14.5(vue@3.3.4)
|
||||
vue-router: 4.1.6(vue@3.3.4)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/shared@10.0.0(vue@3.3.4)':
|
||||
dependencies:
|
||||
vue-demi: 0.14.5(vue@3.3.4)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/shared@10.3.0(vue@3.3.4)':
|
||||
dependencies:
|
||||
vue-demi: 0.14.5(vue@3.3.4)
|
||||
|
|
@ -8580,12 +8532,6 @@ snapshots:
|
|||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/shared@12.0.0(typescript@5.2.2)':
|
||||
dependencies:
|
||||
vue: 3.5.13(typescript@5.2.2)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@zhead/schema@1.0.0-beta.13': {}
|
||||
|
||||
abab@2.0.6: {}
|
||||
|
|
@ -8780,7 +8726,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 +8968,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 +9032,6 @@ snapshots:
|
|||
|
||||
csstype@3.1.2: {}
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
dash-get@1.0.2: {}
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
|
@ -10071,6 +10017,8 @@ snapshots:
|
|||
image-size@0.5.5:
|
||||
optional: true
|
||||
|
||||
immediate@3.0.6: {}
|
||||
|
||||
import-fresh@3.3.0:
|
||||
dependencies:
|
||||
parent-module: 1.0.1
|
||||
|
|
@ -10293,6 +10241,8 @@ snapshots:
|
|||
dependencies:
|
||||
is-docker: 2.2.1
|
||||
|
||||
isarray@1.0.0: {}
|
||||
|
||||
isarray@2.0.5: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
|
@ -10412,6 +10362,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 +10400,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 +10714,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 +10818,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 +10980,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 +11149,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
|
||||
|
|
@ -11267,13 +11242,7 @@ snapshots:
|
|||
|
||||
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 +11291,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 +11372,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 +11538,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
|
||||
|
|
@ -12180,16 +12157,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
|
||||
|
|
|
|||
61
src/tools/icon-generator/icon-generator.e2e.spec.ts
Normal file
61
src/tools/icon-generator/icon-generator.e2e.spec.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
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();
|
||||
const manifestCheckbox = page.getByRole('checkbox', { name: 'Include manifest.json in ZIP' });
|
||||
await expect(manifestCheckbox).not.toBeChecked();
|
||||
|
||||
await manifestCheckbox.click();
|
||||
await expect(manifestCheckbox).not.toBeChecked();
|
||||
});
|
||||
|
||||
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 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).not.toBeChecked();
|
||||
|
||||
await manifestCheckbox.click();
|
||||
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