Almost working
This commit is contained in:
parent
3b8d066cd3
commit
9a4cb2d2a1
190
package-lock.json
generated
190
package-lock.json
generated
@ -108,6 +108,16 @@
|
||||
"url": "https://opencollective.com/babel"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
|
||||
@ -155,6 +165,16 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz",
|
||||
@ -177,6 +197,16 @@
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz",
|
||||
@ -920,19 +950,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@ianvs/prettier-plugin-sort-imports/node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||
@ -1296,43 +1313,43 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.1.tgz",
|
||||
"integrity": "sha512-xvlh4pvfG/bkv0fEtJDABAm1tjtSmSyi2QmS4zyj1EKNI1UiOYiUq1IphSwDsNJ5vJ9cWEGs4rJXpUdCN2kujQ==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.2.tgz",
|
||||
"integrity": "sha512-ZwFnxH+1z8Ehh8bNTMX3YFrYdzAv7JLY5X5X7XSFY+G9QGJVce/P9xb2mh+j5hKt8NceuHmdtllJvAHWKtsNrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"enhanced-resolve": "^5.18.1",
|
||||
"jiti": "^2.4.2",
|
||||
"lightningcss": "1.29.2",
|
||||
"tailwindcss": "4.1.1"
|
||||
"tailwindcss": "4.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.1.tgz",
|
||||
"integrity": "sha512-7+YBgnPQ4+jv6B6WVOerJ6WOzDzNJXrRKDts674v6TKAqFqYRr9+EBtSziO7nNcwQ8JtoZNMeqA+WJDjtCM/7w==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.2.tgz",
|
||||
"integrity": "sha512-Zwz//1QKo6+KqnCKMT7lA4bspGfwEgcPAHlSthmahtgrpKDfwRGk8PKQrW8Zg/ofCDIlg6EtjSTKSxxSufC+CQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-android-arm64": "4.1.1",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.1.1",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.1.1",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.1.1",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.1",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.1",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.1",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.1",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.1",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.1",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.1"
|
||||
"@tailwindcss/oxide-android-arm64": "4.1.2",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.1.2",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.1.2",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.1.2",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.2",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.2",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.2",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.2",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.2",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.2",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.1.tgz",
|
||||
"integrity": "sha512-gTyRzfdParpoCU1yyUC/iN6XK6T0Ra4bDlF8Aeul5NP9cLzKEZDogdNVNGv5WZmCDkVol7qlex7TMmcfytMmmw==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.2.tgz",
|
||||
"integrity": "sha512-IxkXbntHX8lwGmwURUj4xTr6nezHhLYqeiJeqa179eihGv99pRlKV1W69WByPJDQgSf4qfmwx904H6MkQqTA8w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1346,9 +1363,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.1.tgz",
|
||||
"integrity": "sha512-dI0QbdMWBvLB3MtaTKetzUKG9CUUQow8JSP4Nm+OxVokeZ+N+f1OmZW/hW1LzMxpx9RQCBgSRL+IIvKRat5Wdg==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.2.tgz",
|
||||
"integrity": "sha512-ZRtiHSnFYHb4jHKIdzxlFm6EDfijTCOT4qwUhJ3GWxfDoW2yT3z/y8xg0nE7e72unsmSj6dtfZ9Y5r75FIrlpA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1362,9 +1379,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.1.tgz",
|
||||
"integrity": "sha512-2Y+NPQOTRBCItshPgY/CWg4bKi7E9evMg4bgdb6h9iZObCZLOe3doPcuSxGS3DB0dKyMFKE8pTdWtFUbxZBMSA==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.2.tgz",
|
||||
"integrity": "sha512-BiKUNZf1A0pBNzndBvnPnBxonCY49mgbOsPfILhcCE5RM7pQlRoOgN7QnwNhY284bDbfQSEOWnFR0zbPo6IDTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1378,9 +1395,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.1.tgz",
|
||||
"integrity": "sha512-N97NGMsB/7CHShbc5ube4dcsW/bYENkBrg8yWi8ieN9boYVRdw3cZviVryV/Nfu9bKbBV9kUvduFF2qBI7rEqg==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.2.tgz",
|
||||
"integrity": "sha512-Z30VcpUfRGkiddj4l5NRCpzbSGjhmmklVoqkVQdkEC0MOelpY+fJrVhzSaXHmWrmSvnX8yiaEqAbdDScjVujYQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1394,9 +1411,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.1.tgz",
|
||||
"integrity": "sha512-33Lk6KbHnUZbXqza6RWNFo9wqPQ4+H5BAn1CkUUfC1RZ1vYbyDN6+iJPj53wmnWJ3mhRI8jWt3Jt1fO02IVdUQ==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.2.tgz",
|
||||
"integrity": "sha512-w3wsK1ChOLeQ3gFOiwabtWU5e8fY3P1Ss8jR3IFIn/V0va3ir//hZ8AwURveS4oK1Pu6b8i+yxesT4qWnLVUow==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -1410,9 +1427,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.1.tgz",
|
||||
"integrity": "sha512-LyW35RzSUy+80WYScv03HKasAUmMFDaSbNpWfk1gG5gEE9kuRGnDzSrqMoLAmY/kzMCYP/1kqmUiAx8EFLkI2A==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.2.tgz",
|
||||
"integrity": "sha512-oY/u+xJHpndTj7B5XwtmXGk8mQ1KALMfhjWMMpE8pdVAznjJsF5KkCceJ4Fmn5lS1nHMCwZum5M3/KzdmwDMdw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1426,9 +1443,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.1.tgz",
|
||||
"integrity": "sha512-1KPnDMlHdqjPTUSFjx55pafvs8RZXRgxfeRgUrukwDKkuj7gFk28vW3Mx65YdiugAc9NWs3VgueZWaM1Po6uGw==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.2.tgz",
|
||||
"integrity": "sha512-k7G6vcRK/D+JOWqnKzKN/yQq1q4dCkI49fMoLcfs2pVcaUAXEqCP9NmA8Jv+XahBv5DtDjSAY3HJbjosEdKczg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1442,9 +1459,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.1.tgz",
|
||||
"integrity": "sha512-4WdzA+MRlsinEEE6yxNMLJxpw0kE9XVipbAKdTL8BeUpyC2TdA3TL46lBulXzKp3BIxh3nqyR/UCqzl5o+3waQ==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.2.tgz",
|
||||
"integrity": "sha512-fLL+c678TkYKgkDLLNxSjPPK/SzTec7q/E5pTwvpTqrth867dftV4ezRyhPM5PaiCqX651Y8Yk0wRQMcWUGnmQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1458,9 +1475,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.1.tgz",
|
||||
"integrity": "sha512-q7Ugbw3ARcjCW2VMUYrcMbJ6aMQuWPArBBE2EqC/swPZTdGADvMQSlvR0VKusUM4HoSsO7ZbvcZ53YwR57+AKw==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.2.tgz",
|
||||
"integrity": "sha512-0tU1Vjd1WucZ2ooq6y4nI9xyTSaH2g338bhrqk+2yzkMHskBm+pMsOCfY7nEIvALkA1PKPOycR4YVdlV7Czo+A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1474,9 +1491,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.1.tgz",
|
||||
"integrity": "sha512-0KpqsovgHcIzm7eAGzzEZsEs0/nPYXnRBv+aPq/GehpNQuE/NAQu+YgZXIIof+VflDFuyXOEnaFr7T5MZ1INhA==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.2.tgz",
|
||||
"integrity": "sha512-r8QaMo3QKiHqUcn+vXYCypCEha+R0sfYxmaZSgZshx9NfkY+CHz91aS2xwNV/E4dmUDkTPUag7sSdiCHPzFVTg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1490,9 +1507,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.1.tgz",
|
||||
"integrity": "sha512-B1mjeXNS26kBOHv5sXARf6Wd0PWHV9x1TDlW0ummrBUOUAxAy5wcy4Nii1wzNvCdvC448hgiL06ylhwAbNthmg==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.2.tgz",
|
||||
"integrity": "sha512-lYCdkPxh9JRHXoBsPE8Pu/mppUsC2xihYArNAESub41PKhHTnvn6++5RpmFM+GLSt3ewyS8fwCVvht7ulWm6cw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1506,14 +1523,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.1.tgz",
|
||||
"integrity": "sha512-tFTkRZwXq4XKr3S2dUZBxy80wbWYHdDSsu4QOB1yE1HJFKjfxKVpXtup4dyTVdQcLInoHC9lZXFPHnjoBP774g==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.2.tgz",
|
||||
"integrity": "sha512-3r/ZdMW0gxY8uOx1To0lpYa4coq4CzINcCX4laM1rS340Kcn0ac4A/MMFfHN8qba51aorZMYwMcOxYk4wJ9FYg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tailwindcss/node": "4.1.1",
|
||||
"@tailwindcss/oxide": "4.1.1",
|
||||
"tailwindcss": "4.1.1"
|
||||
"@tailwindcss/node": "4.1.2",
|
||||
"@tailwindcss/oxide": "4.1.2",
|
||||
"tailwindcss": "4.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.2.0 || ^6"
|
||||
@ -1997,9 +2014,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001707",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz",
|
||||
"integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==",
|
||||
"version": "1.0.30001709",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001709.tgz",
|
||||
"integrity": "sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -2161,9 +2178,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.130",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.130.tgz",
|
||||
"integrity": "sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==",
|
||||
"version": "1.5.131",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.131.tgz",
|
||||
"integrity": "sha512-fJFRYXVEJgDCiqFOgRGJm8XR97hZ13tw7FXI9k2yC5hgY+nyzC2tMO8baq1cQR7Ur58iCkASx2zrkZPZUnfzPg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@ -3256,13 +3273,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
@ -3373,9 +3393,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz",
|
||||
"integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.2.tgz",
|
||||
"integrity": "sha512-VCsK+fitIbQF7JlxXaibFhxrPq4E2hDcG8apzHUdWFMCQWD8uLdlHg4iSkZ53cgLCCcZ+FZK7vG8VjvLcnBgKw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
@ -3473,9 +3493,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz",
|
||||
"integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==",
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz",
|
||||
"integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
|
@ -2,7 +2,17 @@
|
||||
<!-- Make the outer container always pointer-events-none so clicks pass through -->
|
||||
<div class="fixed inset-0 flex items-center justify-center z-50 pointer-events-none">
|
||||
<!-- Apply pointer-events-auto ONLY to the modal itself so it can be interacted with -->
|
||||
<div class="bg-gray-800 rounded-lg max-w-4xl max-h-[90vh] overflow-auto scrollbar-hide shadow-lg pointer-events-auto" :class="{ invisible: !isModalOpen, visible: isModalOpen }" :style="{ transform: `translate3d(${position.x}px, ${position.y + (isModalOpen ? 0 : -20)}px, 0)` }">
|
||||
<div
|
||||
class="bg-gray-800 rounded-lg overflow-auto scrollbar-hide shadow-lg pointer-events-auto relative"
|
||||
:class="{ invisible: !isModalOpen, visible: isModalOpen }"
|
||||
:style="{
|
||||
transform: `translate3d(${position.x}px, ${position.y + (isModalOpen ? 0 : -20)}px, 0)`,
|
||||
width: `${modalSize.width}px`,
|
||||
height: `${modalSize.height}px`,
|
||||
maxWidth: '90vw',
|
||||
maxHeight: '90vh',
|
||||
}"
|
||||
>
|
||||
<div class="flex items-center justify-between p-4 bg-gray-700 border-b border-gray-600 cursor-move" @mousedown="startDrag">
|
||||
<div class="flex items-center gap-2 text-lg font-semibold select-none">
|
||||
<i class="fas fa-film text-blue-500"></i>
|
||||
@ -13,7 +23,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="p-6 flex flex-col h-[calc(100%-64px)]">
|
||||
<div class="flex flex-wrap items-center gap-4 mb-6">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@ -55,16 +65,82 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center bg-gray-700 p-6 rounded mb-6">
|
||||
<canvas ref="animCanvas" class="block" style="image-rendering: pixelated"></canvas>
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 mb-6">
|
||||
<!-- Zoom controls -->
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="zoomOut" :disabled="previewZoom <= 1" class="flex items-center justify-center w-8 h-8 bg-gray-700 text-gray-200 border border-gray-600 rounded transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:border-blue-500">
|
||||
<i class="fas fa-search-minus"></i>
|
||||
</button>
|
||||
<span class="text-sm text-gray-300">{{ Math.round(previewZoom * 100) }}%</span>
|
||||
<button @click="zoomIn" :disabled="previewZoom >= 5" class="flex items-center justify-center w-8 h-8 bg-gray-700 text-gray-200 border border-gray-600 rounded transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:border-blue-500">
|
||||
<i class="fas fa-search-plus"></i>
|
||||
</button>
|
||||
<button @click="resetZoom" :disabled="previewZoom === 1" class="flex items-center justify-center px-2 h-8 bg-gray-700 text-gray-200 border border-gray-600 rounded text-xs transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:border-blue-500">Reset Zoom</button>
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Show all sprites toggle -->
|
||||
<label class="flex items-center gap-2 text-sm text-gray-300 cursor-pointer">
|
||||
<input type="checkbox" v-model="showAllSprites" class="form-checkbox h-4 w-4 text-blue-500 rounded border-gray-600 bg-gray-700 focus:ring-blue-500" />
|
||||
Show all frames
|
||||
</label>
|
||||
|
||||
<!-- Reset sprite position button -->
|
||||
<button
|
||||
@click="resetSpritePosition"
|
||||
:disabled="!hasSpriteOffset"
|
||||
class="flex items-center gap-1 px-2 h-8 bg-gray-700 text-gray-200 border border-gray-600 rounded text-xs transition-colors disabled:opacity-60 disabled:cursor-not-allowed hover:border-blue-500"
|
||||
title="Reset sprite to original position"
|
||||
>
|
||||
<i class="fas fa-crosshairs"></i>
|
||||
Reset Position
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center items-center bg-gray-700 p-6 rounded mb-6 relative overflow-auto flex-grow">
|
||||
<!-- Tooltip for dragging instructions -->
|
||||
<div class="text-xs text-gray-400 mb-2" v-if="hasSpriteOffset || sprites.length > 0">
|
||||
<span v-if="hasSpriteOffset">Sprite offset: {{ Math.round(spriteOffset.value.x) }}px, {{ Math.round(spriteOffset.value.y) }}px</span>
|
||||
<span v-else>Click and drag the sprite to move it within the cell</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="canvas-container relative transition-transform duration-100 flex items-center justify-center"
|
||||
:style="{
|
||||
minWidth: `${store.cellSize.width * previewZoom}px`,
|
||||
minHeight: `${store.cellSize.height * previewZoom}px`,
|
||||
cursor: previewZoom > 1 ? (isViewportDragging ? 'grabbing' : 'grab') : 'default',
|
||||
}"
|
||||
@mousedown="startViewportDrag"
|
||||
@wheel="handleCanvasWheel"
|
||||
>
|
||||
<div
|
||||
class="sprite-wrapper"
|
||||
:style="{
|
||||
transform: `scale(${previewZoom}) translate(${viewportOffset.x}px, ${viewportOffset.y}px)`,
|
||||
cursor: isCanvasDragging ? 'grabbing' : 'move',
|
||||
}"
|
||||
@mousedown.stop="startCanvasDrag"
|
||||
title="Drag to move sprite within cell"
|
||||
>
|
||||
<canvas ref="animCanvas" class="block" style="image-rendering: pixelated"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resize handle - larger and more noticeable -->
|
||||
<div class="absolute bottom-0 right-0 w-8 h-8 cursor-se-resize flex items-end justify-end bg-gradient-to-br from-transparent to-gray-700 hover:to-blue-500 transition-colors duration-200" @mousedown="startResize">
|
||||
<i class="fas fa-grip-lines-diagonal text-gray-400 hover:text-white p-1"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import { useSpritesheetStore } from '../composables/useSpritesheetStore';
|
||||
|
||||
const store = useSpritesheetStore();
|
||||
@ -80,12 +156,55 @@
|
||||
const isDragging = ref(false);
|
||||
const dragOffset = ref({ x: 0, y: 0 });
|
||||
|
||||
// New state for added features
|
||||
const previewZoom = ref(1);
|
||||
const showAllSprites = ref(false);
|
||||
// Use a computed property to access and modify the store's sprite offset for the current frame
|
||||
const spriteOffset = computed({
|
||||
get: () => {
|
||||
// Get the offset for the current frame
|
||||
const frameOffset = store.getSpriteOffset(currentFrame.value);
|
||||
// Update the current offset for UI display
|
||||
store.currentSpriteOffset.x = frameOffset.x;
|
||||
store.currentSpriteOffset.y = frameOffset.y;
|
||||
return store.currentSpriteOffset;
|
||||
},
|
||||
set: val => {
|
||||
// Update both the current offset and the frame-specific offset
|
||||
store.currentSpriteOffset.x = val.x;
|
||||
store.currentSpriteOffset.y = val.y;
|
||||
|
||||
// Get the frame-specific offset and update it
|
||||
const frameOffset = store.getSpriteOffset(currentFrame.value);
|
||||
frameOffset.x = val.x;
|
||||
frameOffset.y = val.y;
|
||||
},
|
||||
});
|
||||
const isCanvasDragging = ref(false);
|
||||
const canvasDragStart = ref({ x: 0, y: 0 });
|
||||
|
||||
// Canvas viewport navigation state
|
||||
const viewportOffset = ref({ x: 0, y: 0 });
|
||||
const isViewportDragging = ref(false);
|
||||
const viewportDragStart = ref({ x: 0, y: 0 });
|
||||
|
||||
// Modal size state for resize functionality
|
||||
const modalSize = ref({ width: 800, height: 600 });
|
||||
const isResizing = ref(false);
|
||||
const initialSize = ref({ width: 0, height: 0 });
|
||||
const resizeStart = ref({ x: 0, y: 0 });
|
||||
|
||||
const currentFrameDisplay = computed(() => {
|
||||
const totalFrames = Math.max(1, sprites.value.length);
|
||||
const frame = Math.min(currentFrame.value + 1, totalFrames);
|
||||
return `${frame} / ${totalFrames}`;
|
||||
});
|
||||
|
||||
// Computed property to check if sprite has been moved from original position
|
||||
const hasSpriteOffset = computed(() => {
|
||||
return spriteOffset.value.x !== 0 || spriteOffset.value.y !== 0;
|
||||
});
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (!isModalOpen.value) return;
|
||||
|
||||
@ -107,6 +226,51 @@
|
||||
// Previous frame
|
||||
currentFrame.value = (currentFrame.value - 1 + sprites.value.length) % sprites.value.length;
|
||||
updateFrame();
|
||||
} else if (e.key === '+' || e.key === '=') {
|
||||
// Zoom in
|
||||
zoomIn();
|
||||
e.preventDefault();
|
||||
} else if (e.key === '-' || e.key === '_') {
|
||||
// Zoom out
|
||||
zoomOut();
|
||||
e.preventDefault();
|
||||
} else if (e.key === '0') {
|
||||
// Reset zoom
|
||||
resetZoom();
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'r') {
|
||||
if (e.shiftKey) {
|
||||
// Shift+R: Reset sprite position only
|
||||
resetSpritePosition();
|
||||
} else {
|
||||
// R: Reset both sprite position and viewport
|
||||
// Reset the sprite offset for the current frame
|
||||
const frameOffset = store.getSpriteOffset(currentFrame.value);
|
||||
frameOffset.x = 0;
|
||||
frameOffset.y = 0;
|
||||
store.currentSpriteOffset.x = 0;
|
||||
store.currentSpriteOffset.y = 0;
|
||||
viewportOffset.value = { x: 0, y: 0 };
|
||||
updateFrame();
|
||||
store.showNotification('View and position reset');
|
||||
}
|
||||
e.preventDefault();
|
||||
} else if (previewZoom.value > 1) {
|
||||
// Arrow key navigation for panning when zoomed in
|
||||
const panAmount = 10;
|
||||
if (e.key === 'ArrowUp') {
|
||||
viewportOffset.value.y += panAmount / previewZoom.value;
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
viewportOffset.value.y -= panAmount / previewZoom.value;
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'ArrowLeft' && animation.value.isPlaying) {
|
||||
viewportOffset.value.x += panAmount / previewZoom.value;
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'ArrowRight' && animation.value.isPlaying) {
|
||||
viewportOffset.value.x -= panAmount / previewZoom.value;
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -116,8 +280,20 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset position when opening
|
||||
position.value = { x: 0, y: 0 };
|
||||
// Center the modal in the viewport
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
position.value = {
|
||||
x: (viewportWidth - modalSize.value.width) / 2,
|
||||
y: (viewportHeight - modalSize.value.height) / 2,
|
||||
};
|
||||
|
||||
// Reset zoom but keep sprite offset if it exists
|
||||
previewZoom.value = 1;
|
||||
viewportOffset.value = { x: 0, y: 0 };
|
||||
|
||||
// Reset modal size to default
|
||||
modalSize.value = { width: 800, height: 600 };
|
||||
|
||||
// Reset to first frame
|
||||
currentFrame.value = 0;
|
||||
@ -138,7 +314,15 @@
|
||||
|
||||
// Force render the first frame
|
||||
if (sprites.value.length > 0) {
|
||||
store.renderAnimationFrame(0);
|
||||
// Get the frame-specific offset for the first frame
|
||||
const frameOffset = store.getSpriteOffset(0);
|
||||
|
||||
// Update the current offset for UI display
|
||||
store.currentSpriteOffset.x = frameOffset.x;
|
||||
store.currentSpriteOffset.y = frameOffset.y;
|
||||
|
||||
// Render with the frame-specific offset
|
||||
store.renderAnimationFrame(0, showAllSprites.value, frameOffset);
|
||||
}
|
||||
};
|
||||
|
||||
@ -146,6 +330,9 @@
|
||||
if (animCanvas.value && store.cellSize.width && store.cellSize.height) {
|
||||
animCanvas.value.width = store.cellSize.width;
|
||||
animCanvas.value.height = store.cellSize.height;
|
||||
|
||||
// Also update container size
|
||||
updateCanvasContainerSize();
|
||||
}
|
||||
};
|
||||
|
||||
@ -178,7 +365,12 @@
|
||||
const updateFrame = () => {
|
||||
animation.value.currentFrame = currentFrame.value;
|
||||
animation.value.manualUpdate = true;
|
||||
store.renderAnimationFrame(currentFrame.value);
|
||||
|
||||
// Get the frame-specific offset
|
||||
const frameOffset = store.getSpriteOffset(currentFrame.value);
|
||||
|
||||
// Render with the frame-specific offset
|
||||
store.renderAnimationFrame(currentFrame.value, showAllSprites.value, frameOffset);
|
||||
};
|
||||
|
||||
const handleFrameRateChange = () => {
|
||||
@ -189,7 +381,11 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Modal drag functionality
|
||||
const startDrag = (e: MouseEvent) => {
|
||||
// Don't allow drag if currently resizing
|
||||
if (isResizing.value) return;
|
||||
|
||||
isDragging.value = true;
|
||||
dragOffset.value = {
|
||||
x: e.clientX - position.value.x,
|
||||
@ -218,6 +414,45 @@
|
||||
window.removeEventListener('mouseup', stopDrag);
|
||||
};
|
||||
|
||||
// NEW: Modal resize functionality
|
||||
const startResize = (e: MouseEvent) => {
|
||||
isResizing.value = true;
|
||||
initialSize.value = { ...modalSize.value };
|
||||
resizeStart.value = { x: e.clientX, y: e.clientY };
|
||||
|
||||
// Add temporary event listeners
|
||||
window.addEventListener('mousemove', handleResize);
|
||||
window.addEventListener('mouseup', stopResize);
|
||||
|
||||
// Prevent default to avoid text selection
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const handleResize = (e: MouseEvent) => {
|
||||
if (!isResizing.value) return;
|
||||
|
||||
const deltaX = e.clientX - resizeStart.value.x;
|
||||
const deltaY = e.clientY - resizeStart.value.y;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
// Calculate new size with minimum constraints
|
||||
const newWidth = Math.max(400, initialSize.value.width + deltaX);
|
||||
const newHeight = Math.max(400, initialSize.value.height + deltaY);
|
||||
|
||||
// Update modal size
|
||||
modalSize.value = {
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const stopResize = () => {
|
||||
isResizing.value = false;
|
||||
window.removeEventListener('mousemove', handleResize);
|
||||
window.removeEventListener('mouseup', stopResize);
|
||||
};
|
||||
|
||||
const initializeCanvas = async () => {
|
||||
if (!animCanvas.value) {
|
||||
console.error('PreviewModal: Animation canvas not found');
|
||||
@ -238,6 +473,197 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Zoom functions
|
||||
const zoomIn = () => {
|
||||
if (previewZoom.value < 5) {
|
||||
previewZoom.value = Math.min(5, previewZoom.value + 0.5);
|
||||
|
||||
// Adjust container size after zoom change
|
||||
nextTick(() => {
|
||||
updateCanvasContainerSize();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const zoomOut = () => {
|
||||
if (previewZoom.value > 1) {
|
||||
previewZoom.value = Math.max(1, previewZoom.value - 0.5);
|
||||
|
||||
// Adjust container size after zoom change
|
||||
nextTick(() => {
|
||||
updateCanvasContainerSize();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const resetZoom = () => {
|
||||
previewZoom.value = 1;
|
||||
viewportOffset.value = { x: 0, y: 0 };
|
||||
|
||||
// Adjust container size after zoom change
|
||||
nextTick(() => {
|
||||
updateCanvasContainerSize();
|
||||
});
|
||||
};
|
||||
|
||||
// Reset sprite position to original
|
||||
const resetSpritePosition = () => {
|
||||
// Reset the sprite offset for the current frame to zero
|
||||
const frameOffset = store.getSpriteOffset(currentFrame.value);
|
||||
frameOffset.x = 0;
|
||||
frameOffset.y = 0;
|
||||
|
||||
// Also update the current offset
|
||||
store.currentSpriteOffset.x = 0;
|
||||
store.currentSpriteOffset.y = 0;
|
||||
|
||||
// Update the frame to reflect the change
|
||||
updateFrame();
|
||||
|
||||
// Show a notification
|
||||
store.showNotification('Sprite position reset to original');
|
||||
};
|
||||
|
||||
// Update canvas container size based on zoom level
|
||||
const updateCanvasContainerSize = () => {
|
||||
// This ensures the container grows with zoom while keeping the sprite visible
|
||||
if (!store.cellSize.width || !store.cellSize.height) return;
|
||||
|
||||
// We'll update the container if needed through the reactive bindings
|
||||
};
|
||||
|
||||
// Canvas drag functions for moving the sprite within its cell
|
||||
const startCanvasDrag = (e: MouseEvent) => {
|
||||
if (sprites.value.length === 0) return;
|
||||
|
||||
// Don't start sprite dragging if we're already dragging the viewport
|
||||
if (isViewportDragging.value) return;
|
||||
|
||||
isCanvasDragging.value = true;
|
||||
canvasDragStart.value = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
};
|
||||
|
||||
// Add temporary event listeners
|
||||
window.addEventListener('mousemove', handleCanvasDrag);
|
||||
window.addEventListener('mouseup', stopCanvasDrag);
|
||||
|
||||
// Prevent default to avoid text selection
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const handleCanvasDrag = (e: MouseEvent) => {
|
||||
if (!isCanvasDragging.value) return;
|
||||
|
||||
const deltaX = e.clientX - canvasDragStart.value.x;
|
||||
const deltaY = e.clientY - canvasDragStart.value.y;
|
||||
|
||||
// Update sprite offset with the delta, scaled by zoom level
|
||||
// Use requestAnimationFrame for smoother dragging
|
||||
requestAnimationFrame(() => {
|
||||
// Get the frame-specific offset
|
||||
const frameOffset = store.getSpriteOffset(currentFrame.value);
|
||||
|
||||
// Update both the current offset and the frame-specific offset
|
||||
const newX = frameOffset.x + deltaX / previewZoom.value;
|
||||
const newY = frameOffset.y + deltaY / previewZoom.value;
|
||||
|
||||
frameOffset.x = newX;
|
||||
frameOffset.y = newY;
|
||||
store.currentSpriteOffset.x = newX;
|
||||
store.currentSpriteOffset.y = newY;
|
||||
|
||||
// Reset drag start position
|
||||
canvasDragStart.value = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
};
|
||||
|
||||
// Update the frame with the new offset
|
||||
updateFrame();
|
||||
});
|
||||
};
|
||||
|
||||
const stopCanvasDrag = () => {
|
||||
isCanvasDragging.value = false;
|
||||
window.removeEventListener('mousemove', handleCanvasDrag);
|
||||
window.removeEventListener('mouseup', stopCanvasDrag);
|
||||
};
|
||||
|
||||
// Canvas viewport navigation functions
|
||||
const startViewportDrag = (e: MouseEvent) => {
|
||||
// Only enable viewport dragging when zoomed in
|
||||
if (previewZoom.value <= 1 || isCanvasDragging.value) return;
|
||||
|
||||
isViewportDragging.value = true;
|
||||
viewportDragStart.value = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
};
|
||||
|
||||
// Add temporary event listeners
|
||||
window.addEventListener('mousemove', handleViewportDrag);
|
||||
window.addEventListener('mouseup', stopViewportDrag);
|
||||
|
||||
// Prevent default to avoid text selection
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const handleViewportDrag = (e: MouseEvent) => {
|
||||
if (!isViewportDragging.value) return;
|
||||
|
||||
const deltaX = e.clientX - viewportDragStart.value.x;
|
||||
const deltaY = e.clientY - viewportDragStart.value.y;
|
||||
|
||||
// Update viewport offset with the delta, scaled by zoom level
|
||||
viewportOffset.value = {
|
||||
x: viewportOffset.value.x + deltaX / previewZoom.value,
|
||||
y: viewportOffset.value.y + deltaY / previewZoom.value,
|
||||
};
|
||||
|
||||
// Reset drag start position
|
||||
viewportDragStart.value = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
};
|
||||
};
|
||||
|
||||
const stopViewportDrag = () => {
|
||||
isViewportDragging.value = false;
|
||||
window.removeEventListener('mousemove', handleViewportDrag);
|
||||
window.removeEventListener('mouseup', stopViewportDrag);
|
||||
};
|
||||
|
||||
// Handle mouse wheel zooming and panning
|
||||
const handleCanvasWheel = (e: WheelEvent) => {
|
||||
// Prevent the default scroll behavior
|
||||
e.preventDefault();
|
||||
|
||||
if (e.ctrlKey) {
|
||||
// Ctrl + wheel = zoom in/out
|
||||
if (e.deltaY < 0) {
|
||||
zoomIn();
|
||||
} else {
|
||||
zoomOut();
|
||||
}
|
||||
} else {
|
||||
// Just wheel = pan when zoomed in
|
||||
if (previewZoom.value > 1) {
|
||||
// Adjust pan amount by zoom level
|
||||
const panFactor = 0.5 / previewZoom.value;
|
||||
|
||||
if (e.shiftKey) {
|
||||
// Shift + wheel = horizontal pan
|
||||
viewportOffset.value.x -= e.deltaY * panFactor;
|
||||
} else {
|
||||
// Regular wheel = vertical pan
|
||||
viewportOffset.value.y -= e.deltaY * panFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initializeCanvas();
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
@ -247,6 +673,12 @@
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('mousemove', handleDrag);
|
||||
window.removeEventListener('mouseup', stopDrag);
|
||||
window.removeEventListener('mousemove', handleCanvasDrag);
|
||||
window.removeEventListener('mouseup', stopCanvasDrag);
|
||||
window.removeEventListener('mousemove', handleResize);
|
||||
window.removeEventListener('mouseup', stopResize);
|
||||
window.removeEventListener('mousemove', handleViewportDrag);
|
||||
window.removeEventListener('mouseup', stopViewportDrag);
|
||||
});
|
||||
|
||||
// Keep currentFrame in sync with animation.currentFrame
|
||||
@ -263,23 +695,42 @@
|
||||
newSprites => {
|
||||
if (isModalOpen.value && newSprites.length > 0) {
|
||||
updateCanvasSize();
|
||||
store.renderAnimationFrame(currentFrame.value);
|
||||
updateCanvasContainerSize();
|
||||
store.renderAnimationFrame(currentFrame.value, showAllSprites.value, spriteOffset.value);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// Watch zoom changes to update container size
|
||||
watch(
|
||||
() => previewZoom.value,
|
||||
() => {
|
||||
updateCanvasContainerSize();
|
||||
}
|
||||
);
|
||||
|
||||
// Watch for changes in border settings to update the preview
|
||||
watch(
|
||||
() => previewBorder.value,
|
||||
() => {
|
||||
if (isModalOpen.value && sprites.value.length > 0) {
|
||||
store.renderAnimationFrame(currentFrame.value);
|
||||
store.renderAnimationFrame(currentFrame.value, showAllSprites.value, spriteOffset.value);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// Watch for changes in showAllSprites to update the preview
|
||||
watch(
|
||||
() => showAllSprites.value,
|
||||
() => {
|
||||
if (isModalOpen.value && sprites.value.length > 0) {
|
||||
store.renderAnimationFrame(currentFrame.value, showAllSprites.value, spriteOffset.value);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Expose openModal for external use
|
||||
defineExpose({ openModal });
|
||||
</script>
|
||||
@ -316,4 +767,21 @@
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari and Opera */
|
||||
}
|
||||
|
||||
/* Cursor styles */
|
||||
.cursor-se-resize {
|
||||
cursor: se-resize;
|
||||
}
|
||||
|
||||
/* Canvas container styles */
|
||||
.canvas-container {
|
||||
margin: auto;
|
||||
transition:
|
||||
min-width 0.2s,
|
||||
min-height 0.2s;
|
||||
}
|
||||
|
||||
.sprite-wrapper {
|
||||
transform-origin: center;
|
||||
}
|
||||
</style>
|
||||
|
@ -423,6 +423,7 @@ export function useSpritesheetStore() {
|
||||
animation.canvas.width = cellSize.width;
|
||||
animation.canvas.height = cellSize.height;
|
||||
|
||||
// Start the animation loop without resetting sprite offset
|
||||
animationLoop();
|
||||
}
|
||||
|
||||
@ -435,7 +436,7 @@ export function useSpritesheetStore() {
|
||||
}
|
||||
}
|
||||
|
||||
function renderAnimationFrame(frameIndex: number) {
|
||||
function renderAnimationFrame(frameIndex: number, showAllSprites = false, spriteOffset = { x: 0, y: 0 }) {
|
||||
if (sprites.value.length === 0 || !animation.canvas || !animation.ctx) return;
|
||||
|
||||
if (animation.canvas.width !== cellSize.width || animation.canvas.height !== cellSize.height) {
|
||||
@ -449,17 +450,68 @@ export function useSpritesheetStore() {
|
||||
animation.ctx.fillStyle = 'transparent';
|
||||
animation.ctx.fillRect(0, 0, animation.canvas.width, animation.canvas.height);
|
||||
|
||||
// Keep pixel art sharp
|
||||
animation.ctx.imageSmoothingEnabled = false;
|
||||
|
||||
// If showAllSprites is enabled, draw all sprites with transparency
|
||||
if (showAllSprites && sprites.value.length > 1) {
|
||||
// Save the current context state
|
||||
animation.ctx.save();
|
||||
|
||||
// Set global alpha for background sprites
|
||||
animation.ctx.globalAlpha = 0.3;
|
||||
|
||||
// Draw all sprites except the current one
|
||||
sprites.value.forEach((sprite, index) => {
|
||||
if (index !== frameIndex) {
|
||||
const spriteCellX = Math.floor(sprite.x / cellSize.width);
|
||||
const spriteCellY = Math.floor(sprite.y / cellSize.height);
|
||||
|
||||
// Calculate precise offset for pixel-perfect rendering
|
||||
const spriteOffsetX = Math.round(sprite.x - spriteCellX * cellSize.width);
|
||||
const spriteOffsetY = Math.round(sprite.y - spriteCellY * cellSize.height);
|
||||
|
||||
// Draw the sprite with transparency
|
||||
animation.ctx.drawImage(sprite.img, spriteOffsetX, spriteOffsetY);
|
||||
}
|
||||
});
|
||||
|
||||
// Restore the context to full opacity
|
||||
animation.ctx.restore();
|
||||
}
|
||||
|
||||
// Get the current sprite
|
||||
const currentSprite = sprites.value[frameIndex % sprites.value.length];
|
||||
|
||||
const cellX = Math.floor(currentSprite.x / cellSize.width);
|
||||
const cellY = Math.floor(currentSprite.y / cellSize.height);
|
||||
|
||||
// Calculate precise offset for pixel-perfect rendering
|
||||
const offsetX = Math.round(currentSprite.x - cellX * cellSize.width);
|
||||
const offsetY = Math.round(currentSprite.y - cellY * cellSize.height);
|
||||
// Calculate the original position (without user offset)
|
||||
const originalOffsetX = Math.round(currentSprite.x - cellX * cellSize.width);
|
||||
const originalOffsetY = Math.round(currentSprite.y - cellY * cellSize.height);
|
||||
|
||||
// Keep pixel art sharp
|
||||
animation.ctx.imageSmoothingEnabled = false;
|
||||
// Calculate precise offset for pixel-perfect rendering, including the user's drag offset
|
||||
const offsetX = originalOffsetX + spriteOffset.x;
|
||||
const offsetY = originalOffsetY + spriteOffset.y;
|
||||
|
||||
// If the sprite has been moved from its original position, draw a faint outline at the original position
|
||||
if (spriteOffset.x !== 0 || spriteOffset.y !== 0) {
|
||||
// Save context state
|
||||
animation.ctx.save();
|
||||
|
||||
// Draw a faint outline or indicator at the original position
|
||||
animation.ctx.globalAlpha = 0.2;
|
||||
animation.ctx.strokeStyle = '#00FFFF'; // Cyan color for the original position indicator
|
||||
animation.ctx.lineWidth = 1;
|
||||
|
||||
// Draw a rectangle representing the sprite's original position
|
||||
animation.ctx.strokeRect(originalOffsetX, originalOffsetY, currentSprite.width, currentSprite.height);
|
||||
|
||||
// Restore context state
|
||||
animation.ctx.restore();
|
||||
}
|
||||
|
||||
// Draw the current sprite at full opacity at the new position
|
||||
animation.ctx.drawImage(currentSprite.img, offsetX, offsetY);
|
||||
|
||||
// Draw border around the cell if enabled (only for preview, not included in download)
|
||||
@ -477,6 +529,21 @@ export function useSpritesheetStore() {
|
||||
}
|
||||
}
|
||||
|
||||
// Store the current sprite offset for animation playback
|
||||
// We'll use a Map to store offsets for each frame, so they're preserved when switching frames
|
||||
const spriteOffsets = reactive(new Map<number, { x: number; y: number }>());
|
||||
|
||||
// Current sprite offset is a reactive object that will be used for the current frame
|
||||
const currentSpriteOffset = reactive({ x: 0, y: 0 });
|
||||
|
||||
// Helper function to get the offset for a specific frame
|
||||
function getSpriteOffset(frameIndex: number) {
|
||||
if (!spriteOffsets.has(frameIndex)) {
|
||||
spriteOffsets.set(frameIndex, { x: 0, y: 0 });
|
||||
}
|
||||
return spriteOffsets.get(frameIndex)!;
|
||||
}
|
||||
|
||||
function animationLoop(timestamp?: number) {
|
||||
if (!animation.isPlaying) return;
|
||||
|
||||
@ -488,8 +555,17 @@ export function useSpritesheetStore() {
|
||||
animation.lastFrameTime = currentTime;
|
||||
|
||||
if (sprites.value.length > 0) {
|
||||
renderAnimationFrame(animation.currentFrame);
|
||||
// Get the stored offset for the current frame
|
||||
const frameOffset = getSpriteOffset(animation.currentFrame);
|
||||
|
||||
// Update the current offset for rendering
|
||||
currentSpriteOffset.x = frameOffset.x;
|
||||
currentSpriteOffset.y = frameOffset.y;
|
||||
|
||||
// Render the current frame with its offset
|
||||
renderAnimationFrame(animation.currentFrame, false, frameOffset);
|
||||
|
||||
// Move to the next frame
|
||||
animation.currentFrame = (animation.currentFrame + 1) % sprites.value.length;
|
||||
|
||||
if (animation.slider) {
|
||||
@ -549,6 +625,9 @@ export function useSpritesheetStore() {
|
||||
notification,
|
||||
zoomLevel,
|
||||
previewBorder,
|
||||
currentSpriteOffset,
|
||||
spriteOffsets,
|
||||
getSpriteOffset,
|
||||
addSprites,
|
||||
updateCellSize,
|
||||
updateCanvasSize,
|
||||
|
Loading…
x
Reference in New Issue
Block a user