add a costume editor
This commit is contained in:
1282
costume-editor-backup.txt
Normal file
1282
costume-editor-backup.txt
Normal file
File diff suppressed because it is too large
Load Diff
463
package-lock.json
generated
463
package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
||||
"@fortawesome/fontawesome-free": "^7.0.1",
|
||||
"blockly": "^12.2.0",
|
||||
"fabric": "^7.1.0",
|
||||
"jszip": "^3.10.1",
|
||||
"pako": "^2.1.0",
|
||||
"pixi.js-legacy": "^7.4.3",
|
||||
@@ -1455,6 +1456,54 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/blockly": {
|
||||
"version": "12.2.0",
|
||||
"resolved": "https://registry.npmjs.org/blockly/-/blockly-12.2.0.tgz",
|
||||
@@ -1467,6 +1516,31 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
@@ -1496,6 +1570,28 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/canvas": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.1.tgz",
|
||||
"integrity": "sha512-ej1sPFR5+0YWtaVp6S1N1FVz69TQCqmrkGeRvQxZeAB1nAIcjNTHVwrZtYtWFFBmQsF40/uDLehsW5KuYC99mg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.0.0",
|
||||
"prebuild-install": "^7.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.12.0 || >= 20.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
@@ -1551,6 +1647,42 @@
|
||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -1571,6 +1703,16 @@
|
||||
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.6.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
||||
@@ -1721,6 +1863,29 @@
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"license": "(MIT OR WTFPL)",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/fabric": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fabric/-/fabric-7.1.0.tgz",
|
||||
"integrity": "sha512-061QsoSw6xn7UoRXYq816qMyvObP4gRNVph0jAFWtG5E2kBlfdjrYBiLPRuaAHSmVQUz9RjbPpePB/hljiYJIw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"canvas": "^3.2.0",
|
||||
"jsdom": "^26.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
||||
@@ -1736,6 +1901,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -1797,6 +1969,13 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
@@ -1883,6 +2062,27 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
@@ -1895,6 +2095,13 @@
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/is-potential-custom-element-name": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||
@@ -1994,6 +2201,36 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -2019,6 +2256,33 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.87.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
|
||||
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/nwsapi": {
|
||||
"version": "2.2.20",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
|
||||
@@ -2037,6 +2301,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
@@ -2169,12 +2443,50 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^2.0.0",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
|
||||
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -2199,6 +2511,22 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
@@ -2284,6 +2612,19 @@
|
||||
"node": ">=v12.22.7"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
@@ -2362,6 +2703,53 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
||||
@@ -2443,12 +2831,67 @@
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.14",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
||||
@@ -2508,6 +2951,19 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/url": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
|
||||
@@ -2663,6 +3119,13 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
||||
"@fortawesome/fontawesome-free": "^7.0.1",
|
||||
"blockly": "^12.2.0",
|
||||
"fabric": "^7.1.0",
|
||||
"jszip": "^3.10.1",
|
||||
"pako": "^2.1.0",
|
||||
"pixi.js-legacy": "^7.4.3",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 258 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.5 KiB |
@@ -4,10 +4,10 @@ html {
|
||||
--font: "Inter", sans-serif;
|
||||
--dark: #2b323b;
|
||||
--dark-light: #424c5a;
|
||||
--primary: #833bf6;
|
||||
--primary-dark: #8930dc;
|
||||
--primary: #ff5959;
|
||||
--primary-dark: #ff7575;
|
||||
--danger: #f63b3b;
|
||||
--danger-dark: #dd3434;
|
||||
--danger-dark: #ff3434;
|
||||
--color1: #f3f4f6;
|
||||
--color2: #e4e5e7;
|
||||
--color3: #cbcdcf;
|
||||
@@ -18,8 +18,8 @@ html {
|
||||
html.dark {
|
||||
--dark: #e2e8f0;
|
||||
--dark-light: #c8cdd4;
|
||||
--primary: #833bf6;
|
||||
--primary-dark: #8930dc;
|
||||
--primary: #ff5959;
|
||||
--primary-dark: #ff7575;
|
||||
--danger: #f63b3b;
|
||||
--danger-dark: #dd3434;
|
||||
--color1: #262d36;
|
||||
|
||||
590
src/scripts/costumeEditor.js
Normal file
590
src/scripts/costumeEditor.js
Normal file
@@ -0,0 +1,590 @@
|
||||
import { Canvas, PencilBrush, Circle, Rect, Line, IText, FabricImage, Path } from 'fabric';
|
||||
let canvas = null;
|
||||
let editorContainer = null;
|
||||
let currentTool = 'draw';
|
||||
let currentColor = '#000000';
|
||||
let currentSize = 5;
|
||||
let fillEnabled = true;
|
||||
|
||||
export function openCostumeEditor(existingCostume = null, onSave) {
|
||||
closeCostumeEditor();
|
||||
|
||||
editorContainer = document.createElement('div');
|
||||
editorContainer.className = 'costume-editor-overlay';
|
||||
editorContainer.innerHTML = `
|
||||
<div class="costume-editor-modal">
|
||||
<div class="costume-editor-header">
|
||||
<h2>Costume Editor</h2>
|
||||
<button class="close-editor-btn">×</button>
|
||||
</div>
|
||||
<div class="costume-editor-content">
|
||||
<div class="costume-editor-toolbar">
|
||||
<div class="tool-group">
|
||||
<button class="tool-btn active" data-tool="draw" title="Draw">✏️</button>
|
||||
<button class="tool-btn" data-tool="line" title="Line">📏</button>
|
||||
<button class="tool-btn" data-tool="rect" title="Rectangle">⬜</button>
|
||||
<button class="tool-btn" data-tool="circle" title="Circle">⭕</button>
|
||||
<button class="tool-btn" data-tool="text" title="Text">T</button>
|
||||
<button class="tool-btn" data-tool="bucket" title="Paint Bucket">🪣</button>
|
||||
<button class="tool-btn" data-tool="erase" title="Eraser">🧹</button>
|
||||
<button class="tool-btn" data-tool="select" title="Select">👆</button>
|
||||
</div>
|
||||
<div class="tool-group">
|
||||
<label>Fill: <input type="color" id="color-picker" value="#000000"></label>
|
||||
<label><input type="checkbox" id="fill-enabled" checked></label>
|
||||
</div>
|
||||
<div class="tool-group">
|
||||
<label>Outline: <input type="color" id="outline-color-picker" value="#000000"></label>
|
||||
<label>Width: <input type="number" id="outline-width" min="0" max="50" value="2" style="width: 60px;"></label>
|
||||
</div>
|
||||
<div class="tool-group">
|
||||
<label>Size: <input type="range" id="brush-size" min="1" max="50" value="5"><span id="size-display">5px</span></label>
|
||||
</div>
|
||||
<div class="tool-group">
|
||||
<button class="action-btn" id="clear-canvas">🗑️ Clear</button>
|
||||
<button class="action-btn" id="delete-selected">❌ Delete</button>
|
||||
<button class="action-btn" id="undo-btn" disabled>↶ Undo</button>
|
||||
<button class="action-btn" id="redo-btn" disabled>↷ Redo</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="costume-editor-canvas-container">
|
||||
<canvas id="fabric-canvas"></canvas>
|
||||
</div>
|
||||
<div class="costume-editor-footer">
|
||||
<button class="cancel-btn">Cancel</button>
|
||||
<button class="save-btn primary">Save Costume</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(editorContainer);
|
||||
|
||||
// Initialize Fabric.js canvas
|
||||
const canvasEl = document.getElementById('fabric-canvas');
|
||||
canvas = new Canvas(canvasEl, {
|
||||
width: 720,
|
||||
height: 480,
|
||||
backgroundColor: null,
|
||||
isDrawingMode: false
|
||||
});
|
||||
|
||||
// Load existing costume if provided
|
||||
setupControls(onSave, existingCostume);
|
||||
}
|
||||
|
||||
function setupControls(onSave, existingCostume) {
|
||||
const colorPicker = document.getElementById('color-picker');
|
||||
const brushSize = document.getElementById('brush-size');
|
||||
const sizeDisplay = document.getElementById('size-display');
|
||||
const fillEnabledCheckbox = document.getElementById('fill-enabled');
|
||||
const toolButtons = document.querySelectorAll('.tool-btn');
|
||||
|
||||
let isDrawing = false;
|
||||
let startPoint = null;
|
||||
let currentShape = null;
|
||||
let eraserPaths = [];
|
||||
let outlineColor = '#000000';
|
||||
let outlineWidth = 2;
|
||||
|
||||
// History setup
|
||||
const history = [];
|
||||
let historyStep = -1;
|
||||
|
||||
function saveHistory() {
|
||||
if (historyStep < history.length - 1) {
|
||||
history.splice(historyStep + 1);
|
||||
}
|
||||
const json = canvas.toJSON();
|
||||
history.push(JSON.stringify(json));
|
||||
historyStep++;
|
||||
if (history.length > 50) {
|
||||
history.shift();
|
||||
historyStep--;
|
||||
}
|
||||
updateUndoRedoButtons();
|
||||
}
|
||||
|
||||
function updateUndoRedoButtons() {
|
||||
document.getElementById('undo-btn').disabled = historyStep <= 0;
|
||||
document.getElementById('redo-btn').disabled = historyStep >= history.length - 1;
|
||||
}
|
||||
|
||||
// Tool selection
|
||||
toolButtons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
toolButtons.forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
currentTool = btn.dataset.tool;
|
||||
|
||||
// Clean up existing listeners
|
||||
canvas.off('mouse:down');
|
||||
canvas.off('mouse:move');
|
||||
canvas.off('mouse:up');
|
||||
canvas.isDrawingMode = false;
|
||||
canvas.selection = false;
|
||||
|
||||
if (currentTool === 'draw') {
|
||||
canvas.isDrawingMode = true;
|
||||
canvas.freeDrawingBrush = new PencilBrush(canvas);
|
||||
canvas.freeDrawingBrush.color = currentColor;
|
||||
canvas.freeDrawingBrush.width = currentSize;
|
||||
} else if (currentTool === 'erase') {
|
||||
canvas.isDrawingMode = true;
|
||||
const eraserBrush = new PencilBrush(canvas);
|
||||
eraserBrush.width = currentSize * 2;
|
||||
eraserBrush.color = '#FFFFFF';
|
||||
eraserBrush.inverted = true;
|
||||
canvas.freeDrawingBrush = eraserBrush;
|
||||
|
||||
// Use destination-out for actual erasing
|
||||
canvas.on('before:path:created', (e) => {
|
||||
e.path.globalCompositeOperation = 'destination-out';
|
||||
});
|
||||
} else if (currentTool === 'select') {
|
||||
canvas.selection = true;
|
||||
} else if (currentTool === 'text') {
|
||||
canvas.on('mouse:down', (options) => {
|
||||
if (options.target) return; // Don't create new text if clicking existing object
|
||||
|
||||
const pointer = canvas.getScenePoint(options.e);
|
||||
const text = new IText('Text', {
|
||||
left: pointer.x,
|
||||
top: pointer.y,
|
||||
fill: currentColor,
|
||||
fontSize: Math.max(20, currentSize * 4),
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
canvas.add(text);
|
||||
canvas.setActiveObject(text);
|
||||
text.enterEditing();
|
||||
text.selectAll();
|
||||
setTimeout(() => saveHistory(), 100);
|
||||
});
|
||||
} else if (currentTool === 'line' || currentTool === 'rect' || currentTool === 'circle') {
|
||||
canvas.on('mouse:down', (options) => {
|
||||
if (options.target) return; // Don't draw if clicking on existing object
|
||||
|
||||
isDrawing = true;
|
||||
const pointer = canvas.getScenePoint(options.e);
|
||||
startPoint = { x: pointer.x, y: pointer.y };
|
||||
|
||||
if (currentTool === 'line') {
|
||||
currentShape = new Line([startPoint.x, startPoint.y, startPoint.x, startPoint.y], {
|
||||
stroke: outlineColor,
|
||||
strokeWidth: outlineWidth,
|
||||
selectable: false
|
||||
});
|
||||
} else if (currentTool === 'rect') {
|
||||
currentShape = new Rect({
|
||||
left: startPoint.x,
|
||||
top: startPoint.y,
|
||||
width: 1,
|
||||
height: 1,
|
||||
fill: fillEnabled ? currentColor : 'transparent',
|
||||
stroke: outlineColor,
|
||||
strokeWidth: outlineWidth,
|
||||
selectable: false
|
||||
});
|
||||
} else if (currentTool === 'circle') {
|
||||
currentShape = new Circle({
|
||||
left: startPoint.x,
|
||||
top: startPoint.y,
|
||||
radius: 1,
|
||||
fill: fillEnabled ? currentColor : 'transparent',
|
||||
stroke: outlineColor,
|
||||
strokeWidth: outlineWidth,
|
||||
selectable: false,
|
||||
originX: 'center',
|
||||
originY: 'center'
|
||||
});
|
||||
}
|
||||
|
||||
if (currentShape) {
|
||||
canvas.add(currentShape);
|
||||
}
|
||||
});
|
||||
|
||||
canvas.on('mouse:move', (options) => {
|
||||
if (!isDrawing || !currentShape) return;
|
||||
|
||||
const pointer = canvas.getScenePoint(options.e);
|
||||
|
||||
if (currentTool === 'line') {
|
||||
currentShape.set({ x2: pointer.x, y2: pointer.y });
|
||||
} else if (currentTool === 'rect') {
|
||||
const width = pointer.x - startPoint.x;
|
||||
const height = pointer.y - startPoint.y;
|
||||
currentShape.set({
|
||||
width: Math.abs(width),
|
||||
height: Math.abs(height),
|
||||
left: width > 0 ? startPoint.x : pointer.x,
|
||||
top: height > 0 ? startPoint.y : pointer.y
|
||||
});
|
||||
} else if (currentTool === 'circle') {
|
||||
const radius = Math.sqrt(
|
||||
Math.pow(pointer.x - startPoint.x, 2) +
|
||||
Math.pow(pointer.y - startPoint.y, 2)
|
||||
);
|
||||
currentShape.set({ radius: Math.max(1, radius) });
|
||||
}
|
||||
|
||||
canvas.renderAll();
|
||||
});
|
||||
|
||||
canvas.on('mouse:up', () => {
|
||||
if (isDrawing && currentShape) {
|
||||
currentShape.setCoords();
|
||||
currentShape.set({ selectable: true });
|
||||
saveHistory();
|
||||
}
|
||||
isDrawing = false;
|
||||
currentShape = null;
|
||||
});
|
||||
} else if (currentTool === 'bucket') {
|
||||
canvas.on('mouse:down', (options) => {
|
||||
if (!options.target) return;
|
||||
|
||||
const target = options.target;
|
||||
|
||||
if (target.type === 'rect' || target.type === 'circle' || target.type === 'triangle' || target.type === 'polygon') {
|
||||
target.set({
|
||||
fill: currentColor,
|
||||
stroke: outlineColor,
|
||||
strokeWidth: outlineWidth
|
||||
});
|
||||
canvas.renderAll();
|
||||
saveHistory();
|
||||
} else if (target.type === 'line' || target.type === 'path') {
|
||||
target.set({
|
||||
stroke: currentColor,
|
||||
strokeWidth: outlineWidth
|
||||
});
|
||||
canvas.renderAll();
|
||||
saveHistory();
|
||||
} else if (target.type === 'i-text' || target.type === 'text') {
|
||||
target.set('fill', currentColor);
|
||||
canvas.renderAll();
|
||||
saveHistory();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
colorPicker.addEventListener('input', (e) => {
|
||||
currentColor = e.target.value;
|
||||
if (canvas.freeDrawingBrush && currentTool === 'draw') {
|
||||
canvas.freeDrawingBrush.color = currentColor;
|
||||
}
|
||||
});
|
||||
|
||||
const outlineColorPicker = document.getElementById('outline-color-picker');
|
||||
const outlineWidthInput = document.getElementById('outline-width');
|
||||
|
||||
outlineColorPicker.addEventListener('input', (e) => {
|
||||
outlineColor = e.target.value;
|
||||
});
|
||||
|
||||
outlineWidthInput.addEventListener('input', (e) => {
|
||||
outlineWidth = parseInt(e.target.value);
|
||||
});
|
||||
|
||||
brushSize.addEventListener('input', (e) => {
|
||||
currentSize = parseInt(e.target.value);
|
||||
sizeDisplay.textContent = `${currentSize}px`;
|
||||
if (canvas.freeDrawingBrush) {
|
||||
canvas.freeDrawingBrush.width = currentSize;
|
||||
}
|
||||
});
|
||||
|
||||
fillEnabledCheckbox.addEventListener('change', (e) => {
|
||||
fillEnabled = e.target.checked;
|
||||
});
|
||||
|
||||
document.getElementById('clear-canvas').addEventListener('click', () => {
|
||||
if (confirm('Clear entire canvas?')) {
|
||||
canvas.clear();
|
||||
canvas.backgroundColor = null;
|
||||
canvas.renderAll();
|
||||
saveHistory();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('delete-selected').addEventListener('click', () => {
|
||||
const activeObjects = canvas.getActiveObjects();
|
||||
if (activeObjects.length > 0) {
|
||||
activeObjects.forEach(obj => canvas.remove(obj));
|
||||
canvas.discardActiveObject();
|
||||
canvas.renderAll();
|
||||
saveHistory();
|
||||
}
|
||||
});
|
||||
|
||||
canvas.on('path:created', saveHistory);
|
||||
canvas.on('object:added', (e) => {
|
||||
if (e.target && e.target.type !== 'path') {
|
||||
saveHistory();
|
||||
}
|
||||
});
|
||||
canvas.on('object:modified', saveHistory);
|
||||
|
||||
// Snap to center functionality
|
||||
const SNAP_DISTANCE = 15;
|
||||
const centerX = 240;
|
||||
const centerY = 180;
|
||||
|
||||
canvas.on('object:moving', (e) => {
|
||||
const obj = e.target;
|
||||
|
||||
// Snap to horizontal center
|
||||
if (Math.abs(obj.left - centerX) < SNAP_DISTANCE) {
|
||||
obj.set({ left: centerX });
|
||||
}
|
||||
|
||||
// Snap to vertical center
|
||||
if (Math.abs(obj.top - centerY) < SNAP_DISTANCE) {
|
||||
obj.set({ top: centerY });
|
||||
}
|
||||
|
||||
canvas.renderAll();
|
||||
});
|
||||
|
||||
document.getElementById('undo-btn').addEventListener('click', () => {
|
||||
if (historyStep > 0) {
|
||||
historyStep--;
|
||||
canvas.loadFromJSON(history[historyStep]).then(() => {
|
||||
canvas.renderAll();
|
||||
updateUndoRedoButtons();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('redo-btn').addEventListener('click', () => {
|
||||
if (historyStep < history.length - 1) {
|
||||
historyStep++;
|
||||
canvas.loadFromJSON(history[historyStep]).then(() => {
|
||||
canvas.renderAll();
|
||||
updateUndoRedoButtons();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger draw mode by default
|
||||
// Trigger draw mode by default
|
||||
document.querySelector('[data-tool="draw"]').click();
|
||||
|
||||
// Load existing costume if provided
|
||||
if (existingCostume && existingCostume.texture) {
|
||||
const url = existingCostume.texture.baseTexture?.resource?.url || existingCostume.texture.baseTexture?.cacheId;
|
||||
|
||||
if (url) {
|
||||
FabricImage.fromURL(url).then((img) => {
|
||||
img.set({
|
||||
left: 240,
|
||||
top: 180,
|
||||
originX: 'center',
|
||||
originY: 'center'
|
||||
});
|
||||
|
||||
const scale = Math.min(460 / img.width, 340 / img.height, 1);
|
||||
img.scale(scale);
|
||||
|
||||
canvas.add(img);
|
||||
canvas.renderAll();
|
||||
saveHistory();
|
||||
}).catch(err => {
|
||||
console.error('Failed to load costume:', err);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Save initial empty state
|
||||
saveHistory();
|
||||
}
|
||||
|
||||
document.querySelector('.save-btn').addEventListener('click', () => {
|
||||
const dataURL = canvas.toDataURL({
|
||||
format: 'png',
|
||||
quality: 1,
|
||||
multiplier: 1
|
||||
});
|
||||
if (onSave) onSave(dataURL);
|
||||
closeCostumeEditor();
|
||||
});
|
||||
|
||||
document.querySelector('.cancel-btn').addEventListener('click', closeCostumeEditor);
|
||||
document.querySelector('.close-editor-btn').addEventListener('click', closeCostumeEditor);
|
||||
}
|
||||
|
||||
export function closeCostumeEditor() {
|
||||
if (editorContainer) {
|
||||
editorContainer.remove();
|
||||
editorContainer = null;
|
||||
}
|
||||
if (canvas) {
|
||||
canvas.dispose();
|
||||
canvas = null;
|
||||
}
|
||||
}
|
||||
|
||||
// CSS (same as before)
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.costume-editor-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
}
|
||||
.costume-editor-modal {
|
||||
background: #2b2b2b;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
}
|
||||
.costume-editor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
.costume-editor-header h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
.close-editor-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.close-editor-btn:hover {
|
||||
color: #ff4444;
|
||||
}
|
||||
.costume-editor-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.costume-editor-toolbar {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #444;
|
||||
flex-wrap: wrap;
|
||||
background: #333;
|
||||
align-items: center;
|
||||
}
|
||||
.tool-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.tool-btn, .action-btn {
|
||||
background: #444;
|
||||
border: 2px solid transparent;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 16px;
|
||||
}
|
||||
.tool-btn:hover, .action-btn:hover {
|
||||
background: #555;
|
||||
}
|
||||
.tool-btn.active {
|
||||
background: #0066ff;
|
||||
border-color: #0044cc;
|
||||
}
|
||||
.costume-editor-toolbar label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
#color-picker {
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
#brush-size {
|
||||
width: 100px;
|
||||
}
|
||||
#size-display {
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
min-width: 35px;
|
||||
}
|
||||
.costume-editor-canvas-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #1a1a1a;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.canvas-container {
|
||||
border: 2px solid #444;
|
||||
border-radius: 4px;
|
||||
background-image:
|
||||
linear-gradient(45deg, #666 25%, transparent 25%),
|
||||
linear-gradient(-45deg, #666 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, #666 75%),
|
||||
linear-gradient(-45deg, transparent 75%, #666 75%);
|
||||
background-size: 20px 20px;
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
||||
}
|
||||
.costume-editor-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 15px 20px;
|
||||
border-top: 1px solid #444;
|
||||
}
|
||||
.costume-editor-footer button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.cancel-btn {
|
||||
background: #555;
|
||||
color: white;
|
||||
}
|
||||
.cancel-btn:hover {
|
||||
background: #666;
|
||||
}
|
||||
.save-btn {
|
||||
background: #0066ff;
|
||||
color: white;
|
||||
}
|
||||
.save-btn:hover {
|
||||
background: #0055dd;
|
||||
}
|
||||
.primary {
|
||||
font-weight: bold;
|
||||
}
|
||||
.action-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
@@ -6,6 +6,7 @@ import * as PIXI from "pixi.js-legacy";
|
||||
import pako from "pako";
|
||||
import JSZip from "jszip";
|
||||
import { io } from "socket.io-client";
|
||||
import { openCostumeEditor, closeCostumeEditor } from "./costumeEditor.js";
|
||||
|
||||
import CustomRenderer from "../functions/render.js";
|
||||
import { setupThemeButton } from "../functions/theme.js";
|
||||
@@ -64,6 +65,19 @@ const tabButtons = document.querySelectorAll(".tab-button");
|
||||
const tabContents = document.querySelectorAll(".tab-content");
|
||||
const fullscreenButton = document.getElementById("fullscreen-button");
|
||||
|
||||
// Add this after the costumes-list setup or in your HTML
|
||||
const createCostumeButton = document.createElement('button');
|
||||
createCostumeButton.id = 'create-costume-button';
|
||||
createCostumeButton.className = 'primary';
|
||||
createCostumeButton.innerHTML = '<i class="fa-solid fa-paintbrush"></i> Create New Costume (WORK IN PROGRESS)';
|
||||
createCostumeButton.style.margin = '10px';
|
||||
|
||||
// Insert it before the costumes list
|
||||
const costumesTab = document.getElementById('costumes-tab');
|
||||
if (costumesTab) {
|
||||
costumesTab.insertBefore(createCostumeButton, costumesList);
|
||||
}
|
||||
|
||||
export const BASE_WIDTH = 480;
|
||||
export const BASE_HEIGHT = 360;
|
||||
const MAX_HTTP_BUFFER = 20 * 1024 * 1024;
|
||||
@@ -109,7 +123,7 @@ createPenGraphics();
|
||||
window.projectVariables = {};
|
||||
export const projectVariables = window.projectVariables;
|
||||
window.sprites = [];
|
||||
export const sprites = window.sprites;
|
||||
export let sprites = window.sprites;
|
||||
export let activeSprite = null;
|
||||
window.projectSounds = [];
|
||||
window.projectCostumes = ["default"];
|
||||
@@ -394,7 +408,9 @@ function deleteSprite(id, emit = false) {
|
||||
}
|
||||
});
|
||||
|
||||
sprites = sprites.filter(s => s.id !== sprite.id);
|
||||
window.sprites = sprites.filter(s => s.id !== sprite.id);
|
||||
sprites.length = 0;
|
||||
window.sprites.forEach(s => sprites.push(s));
|
||||
|
||||
workspace.clear();
|
||||
|
||||
@@ -541,7 +557,6 @@ function renderCostumesList() {
|
||||
const oldName = costume.name;
|
||||
costume.name = newName;
|
||||
|
||||
// ADD THIS CODE:
|
||||
const oldIndex = window.projectCostumes.indexOf(oldName);
|
||||
if (oldIndex !== -1 && !window.projectCostumes.includes(newName)) {
|
||||
window.projectCostumes[oldIndex] = newName;
|
||||
@@ -573,11 +588,46 @@ function renderCostumesList() {
|
||||
});
|
||||
}
|
||||
|
||||
// ADD THIS: Edit button
|
||||
const editBtn = document.createElement("button");
|
||||
editBtn.innerHTML = '<i class="fa-solid fa-pen-to-square"></i>';
|
||||
editBtn.className = "button";
|
||||
editBtn.draggable = false;
|
||||
editBtn.title = "Edit costume";
|
||||
editBtn.onclick = () => {
|
||||
openCostumeEditor(costume, async (dataURL) => {
|
||||
if (!dataURL) return;
|
||||
|
||||
// Update the existing costume
|
||||
const newTexture = PIXI.Texture.from(dataURL);
|
||||
costume.texture = newTexture;
|
||||
|
||||
// Update sprite if this is the current costume
|
||||
if (activeSprite.pixiSprite.texture === costume.texture) {
|
||||
activeSprite.pixiSprite.texture = newTexture;
|
||||
}
|
||||
|
||||
renderCostumesList();
|
||||
showNotification({ message: '✓ Costume updated' });
|
||||
|
||||
if (currentSocket && currentRoom) {
|
||||
currentSocket.emit("projectUpdate", {
|
||||
roomId: currentRoom,
|
||||
type: "updateCostume",
|
||||
data: {
|
||||
spriteId: activeSprite.id,
|
||||
name: costume.name,
|
||||
texture: dataURL,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const deleteBtn = createDeleteButton(() => {
|
||||
const deleted = activeSprite.costumes[index];
|
||||
activeSprite.costumes.splice(index, 1);
|
||||
|
||||
// ADD THIS CODE to remove from global array if not used elsewhere:
|
||||
if (deleted) {
|
||||
const existsElsewhere = sprites.some(s =>
|
||||
s.id !== activeSprite.id && s.costumes.some(c => c.name === deleted.name)
|
||||
@@ -593,8 +643,6 @@ function renderCostumesList() {
|
||||
activeSprite.pixiSprite.texture = PIXI.Texture.EMPTY;
|
||||
}
|
||||
renderCostumesList();
|
||||
|
||||
// ADD THIS LINE to refresh toolbox:
|
||||
workspace.updateToolbox(document.getElementById('toolbox'));
|
||||
|
||||
if (currentSocket && currentRoom && deleted) {
|
||||
@@ -611,6 +659,7 @@ function renderCostumesList() {
|
||||
|
||||
costumeContainer.appendChild(img);
|
||||
costumeContainer.appendChild(renameableLabel);
|
||||
costumeContainer.appendChild(editBtn);
|
||||
costumeContainer.appendChild(deleteBtn);
|
||||
costumeContainer.appendChild(sizeLabel);
|
||||
|
||||
@@ -1776,6 +1825,51 @@ loadButton.addEventListener("click", () => {
|
||||
});
|
||||
loadInput.addEventListener("change", loadProject);
|
||||
|
||||
// Create new costume with editor
|
||||
document.getElementById('create-costume-button')?.addEventListener('click', () => {
|
||||
if (!activeSprite) {
|
||||
showNotification({ message: 'Please select a sprite first' });
|
||||
return;
|
||||
}
|
||||
|
||||
openCostumeEditor(null, async (dataURL) => {
|
||||
if (!dataURL || !activeSprite) return;
|
||||
|
||||
const texture = PIXI.Texture.from(dataURL);
|
||||
|
||||
let uniqueName = 'costume';
|
||||
let counter = 1;
|
||||
const nameExists = name => activeSprite.costumes.some(c => c.name === name);
|
||||
|
||||
while (nameExists(uniqueName)) {
|
||||
counter++;
|
||||
uniqueName = `costume_${counter}`;
|
||||
}
|
||||
|
||||
activeSprite.costumes.push({ name: uniqueName, texture });
|
||||
|
||||
if (!window.projectCostumes.includes(uniqueName)) {
|
||||
window.projectCostumes.push(uniqueName);
|
||||
}
|
||||
workspace.updateToolbox(document.getElementById('toolbox'));
|
||||
|
||||
if (currentSocket && currentRoom) {
|
||||
currentSocket.emit("projectUpdate", {
|
||||
roomId: currentRoom,
|
||||
type: "addCostume",
|
||||
data: {
|
||||
spriteId: activeSprite.id,
|
||||
name: uniqueName,
|
||||
texture: dataURL,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
renderCostumesList();
|
||||
showNotification({ message: '✓ Costume created successfully' });
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("costume-upload").addEventListener("change", e => {
|
||||
const file = e.target.files[0];
|
||||
if (!file || !activeSprite) return;
|
||||
|
||||
Reference in New Issue
Block a user