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",
|
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
||||||
"@fortawesome/fontawesome-free": "^7.0.1",
|
"@fortawesome/fontawesome-free": "^7.0.1",
|
||||||
"blockly": "^12.2.0",
|
"blockly": "^12.2.0",
|
||||||
|
"fabric": "^7.1.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
"pixi.js-legacy": "^7.4.3",
|
"pixi.js-legacy": "^7.4.3",
|
||||||
@@ -1455,6 +1456,54 @@
|
|||||||
"node": ">= 14"
|
"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": {
|
"node_modules/blockly": {
|
||||||
"version": "12.2.0",
|
"version": "12.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/blockly/-/blockly-12.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/blockly/-/blockly-12.2.0.tgz",
|
||||||
@@ -1467,6 +1516,31 @@
|
|||||||
"node": ">=18"
|
"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": {
|
"node_modules/call-bind-apply-helpers": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"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"
|
"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": {
|
"node_modules/core-util-is": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||||
@@ -1551,6 +1647,42 @@
|
|||||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -1571,6 +1703,16 @@
|
|||||||
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
|
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/engine.io-client": {
|
||||||
"version": "6.6.3",
|
"version": "6.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
||||||
@@ -1721,6 +1863,29 @@
|
|||||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/fdir": {
|
||||||
"version": "6.4.6",
|
"version": "6.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
"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": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -1797,6 +1969,13 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/gopd": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
@@ -1883,6 +2062,27 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/immediate": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
@@ -1895,6 +2095,13 @@
|
|||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/is-potential-custom-element-name": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
"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": ">= 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": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"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": "^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": {
|
"node_modules/nwsapi": {
|
||||||
"version": "2.2.20",
|
"version": "2.2.20",
|
||||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
|
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
|
||||||
@@ -2037,6 +2301,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/pako": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||||
@@ -2169,12 +2443,50 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"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": {
|
"node_modules/process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@@ -2199,6 +2511,22 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/readable-stream": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||||
@@ -2284,6 +2612,19 @@
|
|||||||
"node": ">=v12.22.7"
|
"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": {
|
"node_modules/setimmediate": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
@@ -2362,6 +2703,53 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/socket.io-client": {
|
||||||
"version": "4.8.1",
|
"version": "4.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
||||||
@@ -2443,12 +2831,67 @@
|
|||||||
"safe-buffer": "~5.1.0"
|
"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": {
|
"node_modules/symbol-tree": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.14",
|
"version": "0.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
||||||
@@ -2508,6 +2951,19 @@
|
|||||||
"node": ">=18"
|
"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": {
|
"node_modules/url": {
|
||||||
"version": "0.11.4",
|
"version": "0.11.4",
|
||||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
|
"resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz",
|
||||||
@@ -2663,6 +3119,13 @@
|
|||||||
"node": ">=18"
|
"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": {
|
"node_modules/ws": {
|
||||||
"version": "8.18.3",
|
"version": "8.18.3",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
"@blockly/plugin-strict-connection-checker": "^6.0.1",
|
||||||
"@fortawesome/fontawesome-free": "^7.0.1",
|
"@fortawesome/fontawesome-free": "^7.0.1",
|
||||||
"blockly": "^12.2.0",
|
"blockly": "^12.2.0",
|
||||||
|
"fabric": "^7.1.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
"pixi.js-legacy": "^7.4.3",
|
"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;
|
--font: "Inter", sans-serif;
|
||||||
--dark: #2b323b;
|
--dark: #2b323b;
|
||||||
--dark-light: #424c5a;
|
--dark-light: #424c5a;
|
||||||
--primary: #833bf6;
|
--primary: #ff5959;
|
||||||
--primary-dark: #8930dc;
|
--primary-dark: #ff7575;
|
||||||
--danger: #f63b3b;
|
--danger: #f63b3b;
|
||||||
--danger-dark: #dd3434;
|
--danger-dark: #ff3434;
|
||||||
--color1: #f3f4f6;
|
--color1: #f3f4f6;
|
||||||
--color2: #e4e5e7;
|
--color2: #e4e5e7;
|
||||||
--color3: #cbcdcf;
|
--color3: #cbcdcf;
|
||||||
@@ -18,8 +18,8 @@ html {
|
|||||||
html.dark {
|
html.dark {
|
||||||
--dark: #e2e8f0;
|
--dark: #e2e8f0;
|
||||||
--dark-light: #c8cdd4;
|
--dark-light: #c8cdd4;
|
||||||
--primary: #833bf6;
|
--primary: #ff5959;
|
||||||
--primary-dark: #8930dc;
|
--primary-dark: #ff7575;
|
||||||
--danger: #f63b3b;
|
--danger: #f63b3b;
|
||||||
--danger-dark: #dd3434;
|
--danger-dark: #dd3434;
|
||||||
--color1: #262d36;
|
--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 pako from "pako";
|
||||||
import JSZip from "jszip";
|
import JSZip from "jszip";
|
||||||
import { io } from "socket.io-client";
|
import { io } from "socket.io-client";
|
||||||
|
import { openCostumeEditor, closeCostumeEditor } from "./costumeEditor.js";
|
||||||
|
|
||||||
import CustomRenderer from "../functions/render.js";
|
import CustomRenderer from "../functions/render.js";
|
||||||
import { setupThemeButton } from "../functions/theme.js";
|
import { setupThemeButton } from "../functions/theme.js";
|
||||||
@@ -64,6 +65,19 @@ const tabButtons = document.querySelectorAll(".tab-button");
|
|||||||
const tabContents = document.querySelectorAll(".tab-content");
|
const tabContents = document.querySelectorAll(".tab-content");
|
||||||
const fullscreenButton = document.getElementById("fullscreen-button");
|
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_WIDTH = 480;
|
||||||
export const BASE_HEIGHT = 360;
|
export const BASE_HEIGHT = 360;
|
||||||
const MAX_HTTP_BUFFER = 20 * 1024 * 1024;
|
const MAX_HTTP_BUFFER = 20 * 1024 * 1024;
|
||||||
@@ -109,7 +123,7 @@ createPenGraphics();
|
|||||||
window.projectVariables = {};
|
window.projectVariables = {};
|
||||||
export const projectVariables = window.projectVariables;
|
export const projectVariables = window.projectVariables;
|
||||||
window.sprites = [];
|
window.sprites = [];
|
||||||
export const sprites = window.sprites;
|
export let sprites = window.sprites;
|
||||||
export let activeSprite = null;
|
export let activeSprite = null;
|
||||||
window.projectSounds = [];
|
window.projectSounds = [];
|
||||||
window.projectCostumes = ["default"];
|
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();
|
workspace.clear();
|
||||||
|
|
||||||
@@ -541,7 +557,6 @@ function renderCostumesList() {
|
|||||||
const oldName = costume.name;
|
const oldName = costume.name;
|
||||||
costume.name = newName;
|
costume.name = newName;
|
||||||
|
|
||||||
// ADD THIS CODE:
|
|
||||||
const oldIndex = window.projectCostumes.indexOf(oldName);
|
const oldIndex = window.projectCostumes.indexOf(oldName);
|
||||||
if (oldIndex !== -1 && !window.projectCostumes.includes(newName)) {
|
if (oldIndex !== -1 && !window.projectCostumes.includes(newName)) {
|
||||||
window.projectCostumes[oldIndex] = 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 deleteBtn = createDeleteButton(() => {
|
||||||
const deleted = activeSprite.costumes[index];
|
const deleted = activeSprite.costumes[index];
|
||||||
activeSprite.costumes.splice(index, 1);
|
activeSprite.costumes.splice(index, 1);
|
||||||
|
|
||||||
// ADD THIS CODE to remove from global array if not used elsewhere:
|
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
const existsElsewhere = sprites.some(s =>
|
const existsElsewhere = sprites.some(s =>
|
||||||
s.id !== activeSprite.id && s.costumes.some(c => c.name === deleted.name)
|
s.id !== activeSprite.id && s.costumes.some(c => c.name === deleted.name)
|
||||||
@@ -593,8 +643,6 @@ function renderCostumesList() {
|
|||||||
activeSprite.pixiSprite.texture = PIXI.Texture.EMPTY;
|
activeSprite.pixiSprite.texture = PIXI.Texture.EMPTY;
|
||||||
}
|
}
|
||||||
renderCostumesList();
|
renderCostumesList();
|
||||||
|
|
||||||
// ADD THIS LINE to refresh toolbox:
|
|
||||||
workspace.updateToolbox(document.getElementById('toolbox'));
|
workspace.updateToolbox(document.getElementById('toolbox'));
|
||||||
|
|
||||||
if (currentSocket && currentRoom && deleted) {
|
if (currentSocket && currentRoom && deleted) {
|
||||||
@@ -611,6 +659,7 @@ function renderCostumesList() {
|
|||||||
|
|
||||||
costumeContainer.appendChild(img);
|
costumeContainer.appendChild(img);
|
||||||
costumeContainer.appendChild(renameableLabel);
|
costumeContainer.appendChild(renameableLabel);
|
||||||
|
costumeContainer.appendChild(editBtn);
|
||||||
costumeContainer.appendChild(deleteBtn);
|
costumeContainer.appendChild(deleteBtn);
|
||||||
costumeContainer.appendChild(sizeLabel);
|
costumeContainer.appendChild(sizeLabel);
|
||||||
|
|
||||||
@@ -1776,6 +1825,51 @@ loadButton.addEventListener("click", () => {
|
|||||||
});
|
});
|
||||||
loadInput.addEventListener("change", loadProject);
|
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 => {
|
document.getElementById("costume-upload").addEventListener("change", e => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (!file || !activeSprite) return;
|
if (!file || !activeSprite) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user