Add .vl2 export

This commit is contained in:
Brian Beck 2022-12-04 11:00:39 -08:00
parent e0e2b3d276
commit 941f908cb5
31 changed files with 378 additions and 81 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[70],{8070:function(e,n,t){t.r(n),t.d(n,{createZipFile:function(){return u},savePngFile:function(){return r},saveZipFile:function(){return c}});var a=t(5733),i=t.n(a),s=t(3162);function u(e){let n=new(i());for(let t of e)n.file("textures/skins/".concat(t.name),t.data);return n}async function c(e,n){let t=await e.generateAsync({type:"blob"});(0,s.saveAs)(t,n)}function r(e,n){(0,s.saveAs)(e,n)}}}]);
//# sourceMappingURL=70.0ba1ca12d54bca2d.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"static/chunks/70.0ba1ca12d54bca2d.js","mappings":"ACAA,aACA,CAACA,KAAK,gBAAmB,CAAGA,KAAK,gBAAmB,EAAI,EAAE,EAAEC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAEjE,KACC,SAASC,CAAuB,CAAEC,CAAmB,CAAEC,CAAmB,CAAE,CAEnFA,EAAoBC,CAAC,CAACF,GACDC,EAAoBE,CAAC,CAACH,EAAqB,CACzC,cAAiB,UAAW,CAAE,OAAqBI,CAAe,EAClE,YAAe,UAAW,CAAE,OAAqBC,CAAa,EAC9D,YAAe,UAAW,CAAE,OAAqBC,CAAa,CAChE,GACA,IAAIC,EAAqCN,EAAoB,MACzDO,EAA0DP,EAAoBQ,CAAC,CAACF,GAChFG,EAA0CT,EAAoB,MDTrF,SACAG,EAAgBO,CAAA,EAChB,IAAKC,EAAM,GAAAJ,CAAAA,GAAe,MACxB,IAAIK,KAAKF,EACXC,EAAAC,IAAA,mBAAAC,MAAA,CAAAD,EAAAE,IAAA,EAAAF,EAAAG,IAAA,EAED,OAAAJ,CAEM,CAAqD,eACpDN,EAAiBM,CAAA,CAAAG,CAAA,EAAc,IAAEE,EAAM,MAAAL,EAAAM,aAAA,EAAOC,KAAA,MACpD,GACD,GAAAT,EAAAU,MAAA,EAAAH,EAAAF,EAEM,CAAqD,SAC1DV,EAAAgB,CAAA,CAAAN,CAAA,EACD,GAAAL,EAAAU,MAAA,EAAAC,EAAAN,EAAA","sources":["webpack://_N_E/./src/exportUtils.ts","webpack://_N_E/<anon>"],"sourcesContent":["import JSZip from \"jszip\";\nimport { saveAs } from \"file-saver\";\n\nexport function createZipFile(\n files: Array<{ name: string; data: ArrayBuffer }>\n) {\n const zip = new JSZip();\n for (const file of files) {\n zip.file(`textures/skins/${file.name}`, file.data);\n }\n return zip;\n}\n\nexport async function saveZipFile(zip: JSZip, name: string) {\n const blob = await zip.generateAsync({ type: \"blob\" });\n saveAs(blob, name);\n}\n\nexport function savePngFile(imageUrl: string, name: string) {\n saveAs(imageUrl, name);\n}\n","\"use strict\";\n(self[\"webpackChunk_N_E\"] = self[\"webpackChunk_N_E\"] || []).push([[70],{\n\n/***/ 8070:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"createZipFile\": function() { return /* binding */ createZipFile; },\n/* harmony export */ \"savePngFile\": function() { return /* binding */ savePngFile; },\n/* harmony export */ \"saveZipFile\": function() { return /* binding */ saveZipFile; }\n/* harmony export */ });\n/* harmony import */ var jszip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5733);\n/* harmony import */ var jszip__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(jszip__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var file_saver__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3162);\n/* harmony import */ var file_saver__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(file_saver__WEBPACK_IMPORTED_MODULE_1__);\n\n\nfunction createZipFile(files) {\n const zip = new (jszip__WEBPACK_IMPORTED_MODULE_0___default())();\n for (const file of files){\n zip.file(\"textures/skins/\".concat(file.name), file.data);\n }\n return zip;\n}\nasync function saveZipFile(zip, name) {\n const blob = await zip.generateAsync({\n type: \"blob\"\n });\n (0,file_saver__WEBPACK_IMPORTED_MODULE_1__.saveAs)(blob, name);\n}\nfunction savePngFile(imageUrl, name) {\n (0,file_saver__WEBPACK_IMPORTED_MODULE_1__.saveAs)(imageUrl, name);\n}\n\n\n/***/ })\n\n}]);"],"names":["self","push","__unused_webpack_module","__webpack_exports__","__webpack_require__","r","d","createZipFile","savePngFile","saveZipFile","jszip__WEBPACK_IMPORTED_MODULE_0__","jszip__WEBPACK_IMPORTED_MODULE_0___default","n","file_saver__WEBPACK_IMPORTED_MODULE_1__","files","zip","file","concat","name","data","blob","generateAsync","type","saveAs","imageUrl"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
!function(){"use strict";var e,t,n,r,o,u,i,c={},a={};function f(e){var t=a[e];if(void 0!==t)return t.exports;var n=a[e]={exports:{}},r=!0;try{c[e](n,n.exports,f),r=!1}finally{r&&delete a[e]}return n.exports}f.m=c,e=[],f.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,a=0;a<n.length;a++)i>=o&&Object.keys(f.O).every(function(e){return f.O[e](n[a])})?n.splice(a--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var s=r();void 0!==s&&(t=s)}}return t},f.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(t,{a:t}),t},f.d=function(e,t){for(var n in t)f.o(t,n)&&!f.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},f.f={},f.e=function(e){return Promise.all(Object.keys(f.f).reduce(function(t,n){return f.f[n](e,t),t},[]))},f.u=function(e){return"static/chunks/"+(737===e?"fb7d5399":e)+"."+({258:"53d25aa602c4d686",737:"ddae1e8fed13bcfe",990:"ccb4bc1efe5cd94f"})[e]+".js"},f.miniCssF=function(e){return"static/css/984ea732efa6a7ed.css"},f.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),f.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t={},n="_N_E:",f.l=function(e,r,o,u){if(t[e]){t[e].push(r);return}if(void 0!==o)for(var i,c,a=document.getElementsByTagName("script"),s=0;s<a.length;s++){var l=a[s];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==n+o){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,f.nc&&i.setAttribute("nonce",f.nc),i.setAttribute("data-webpack",n+o),i.src=f.tu(e)),t[e]=[r];var d=function(n,r){i.onerror=i.onload=null,clearTimeout(p);var o=t[e];if(delete t[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(r)}),n)return n(r)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=d.bind(null,i.onerror),i.onload=d.bind(null,i.onload),c&&document.head.appendChild(i)},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.tt=function(){return void 0===r&&(r={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(r=trustedTypes.createPolicy("nextjs#bundler",r))),r},f.tu=function(e){return f.tt().createScriptURL(e)},f.p="/t2-model-skinner/_next/",o={272:0},f.f.j=function(e,t){var n=f.o(o,e)?o[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(272!=e){var r=new Promise(function(t,r){n=o[e]=[t,r]});t.push(n[2]=r);var u=f.p+f.u(e),i=Error();f.l(u,function(t){if(f.o(o,e)&&(0!==(n=o[e])&&(o[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),u=t&&t.target&&t.target.src;i.message="Loading chunk "+e+" failed.\n("+r+": "+u+")",i.name="ChunkLoadError",i.type=r,i.request=u,n[1](i)}},"chunk-"+e,e)}else o[e]=0}},f.O.j=function(e){return 0===o[e]},u=function(e,t){var n,r,u=t[0],i=t[1],c=t[2],a=0;if(u.some(function(e){return 0!==o[e]})){for(n in i)f.o(i,n)&&(f.m[n]=i[n]);if(c)var s=c(f)}for(e&&e(t);a<u.length;a++)r=u[a],f.o(o,r)&&o[r]&&o[r][0](),o[r]=0;return f.O(s)},(i=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(u.bind(null,0)),i.push=u.bind(null,i.push.bind(i))}();
//# sourceMappingURL=webpack-60c094168b41ae2e.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
!function(){"use strict";var e,t,n,r,o,u,i,c={},a={};function f(e){var t=a[e];if(void 0!==t)return t.exports;var n=a[e]={exports:{}},r=!0;try{c[e].call(n.exports,n,n.exports,f),r=!1}finally{r&&delete a[e]}return n.exports}f.m=c,e=[],f.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,a=0;a<n.length;a++)i>=o&&Object.keys(f.O).every(function(e){return f.O[e](n[a])})?n.splice(a--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var l=r();void 0!==l&&(t=l)}}return t},f.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(t,{a:t}),t},f.d=function(e,t){for(var n in t)f.o(t,n)&&!f.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},f.f={},f.e=function(e){return Promise.all(Object.keys(f.f).reduce(function(t,n){return f.f[n](e,t),t},[]))},f.u=function(e){return"static/chunks/"+(737===e?"fb7d5399":e)+"."+({70:"0ba1ca12d54bca2d",258:"53d25aa602c4d686",354:"3289d8281650c8de",737:"ddae1e8fed13bcfe",990:"ccb4bc1efe5cd94f"})[e]+".js"},f.miniCssF=function(e){return"static/css/43b5c0f72e817aaa.css"},f.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),f.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t={},n="_N_E:",f.l=function(e,r,o,u){if(t[e]){t[e].push(r);return}if(void 0!==o)for(var i,c,a=document.getElementsByTagName("script"),l=0;l<a.length;l++){var s=a[l];if(s.getAttribute("src")==e||s.getAttribute("data-webpack")==n+o){i=s;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,f.nc&&i.setAttribute("nonce",f.nc),i.setAttribute("data-webpack",n+o),i.src=f.tu(e)),t[e]=[r];var d=function(n,r){i.onerror=i.onload=null,clearTimeout(p);var o=t[e];if(delete t[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(r)}),n)return n(r)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=d.bind(null,i.onerror),i.onload=d.bind(null,i.onload),c&&document.head.appendChild(i)},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.tt=function(){return void 0===r&&(r={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(r=trustedTypes.createPolicy("nextjs#bundler",r))),r},f.tu=function(e){return f.tt().createScriptURL(e)},f.p="/t2-model-skinner/_next/",o={272:0},f.f.j=function(e,t){var n=f.o(o,e)?o[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(272!=e){var r=new Promise(function(t,r){n=o[e]=[t,r]});t.push(n[2]=r);var u=f.p+f.u(e),i=Error();f.l(u,function(t){if(f.o(o,e)&&(0!==(n=o[e])&&(o[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),u=t&&t.target&&t.target.src;i.message="Loading chunk "+e+" failed.\n("+r+": "+u+")",i.name="ChunkLoadError",i.type=r,i.request=u,n[1](i)}},"chunk-"+e,e)}else o[e]=0}},f.O.j=function(e){return 0===o[e]},u=function(e,t){var n,r,u=t[0],i=t[1],c=t[2],a=0;if(u.some(function(e){return 0!==o[e]})){for(n in i)f.o(i,n)&&(f.m[n]=i[n]);if(c)var l=c(f)}for(e&&e(t);a<u.length;a++)r=u[a],f.o(o,r)&&o[r]&&o[r][0](),o[r]=0;return f.O(l)},(i=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(u.bind(null,0)),i.push=u.bind(null,i.push.bind(i))}();
//# sourceMappingURL=webpack-fe0974dd0529d880.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
self.__BUILD_MANIFEST={__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":["static/chunks/78e521c3-312593b4f3190cc4.js","static/chunks/95b64a6e-6f3d919198a9be32.js","static/chunks/31664189-c9b38f80aa85a6f1.js","static/chunks/545f34e4-f96cf9e26b6b92a5.js","static/chunks/1bfc9850-6b316c8ef06e5170.js","static/chunks/d7eeaac4-06e64d251e2cbda7.js","static/chunks/f580fadb-a8e2c6896615a304.js","static/chunks/50-df9866125e9c5260.js","static/chunks/pages/index-8090c57b7d69540b.js"],"/_error":["static/chunks/pages/_error-479484f6c157e921.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
self.__BUILD_MANIFEST={__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":["static/chunks/78e521c3-312593b4f3190cc4.js","static/chunks/95b64a6e-6f3d919198a9be32.js","static/chunks/31664189-c9b38f80aa85a6f1.js","static/chunks/545f34e4-f96cf9e26b6b92a5.js","static/chunks/1bfc9850-6b316c8ef06e5170.js","static/chunks/d7eeaac4-06e64d251e2cbda7.js","static/chunks/f580fadb-a8e2c6896615a304.js","static/chunks/50-df9866125e9c5260.js","static/chunks/pages/index-9965ba0672cc2b7e.js"],"/_error":["static/chunks/pages/_error-479484f6c157e921.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

File diff suppressed because one or more lines are too long

207
package-lock.json generated
View file

@ -13,8 +13,10 @@
"@popperjs/core": "^2.11.6",
"@tanstack/react-query": "^4.19.0",
"comlink": "^4.3.1",
"file-saver": "^2.0.5",
"get-stream": "^6.0.1",
"globby": "^13.1.2",
"jszip": "^3.10.1",
"lodash.orderby": "^4.6.0",
"next": "^13.0.5",
"pngjs": "^6.0.0",
@ -28,6 +30,7 @@
"devDependencies": {
"@next/eslint-plugin-next": "^13.0.5",
"@types/fabric": "^4.5.13",
"@types/file-saver": "^2.0.5",
"@types/pngjs": "^6.0.1",
"@types/react": "18.0.25",
"@typescript-eslint/eslint-plugin": "^5.45.0",
@ -515,6 +518,12 @@
"integrity": "sha512-+K2ce3hH1QfRlp5y8AialFl/rc7fkwzPQ9fK+fswPWTn3djkstCpm8Iou05NTZy3SxRA9dS7puSF+tjpo6ny7Q==",
"dev": true
},
"node_modules/@types/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -1326,6 +1335,11 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": 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",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -2005,6 +2019,11 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@ -2354,6 +2373,11 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -2392,8 +2416,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/internal-slot": {
"version": "1.0.3",
@ -2615,6 +2638,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -2748,6 +2776,17 @@
"node": ">=4.0"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
@ -2761,6 +2800,14 @@
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lit": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.4.1.tgz",
@ -3176,6 +3223,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -3297,6 +3349,11 @@
"node": ">= 0.8.0"
}
},
"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=="
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@ -3443,6 +3500,25 @@
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/readable-stream/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -3656,6 +3732,11 @@
"randombytes": "^2.1.0"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
},
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
@ -3733,6 +3814,19 @@
"source-map": "^0.6.0"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/string_decoder/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/string.prototype.matchall": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
@ -4108,6 +4202,11 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/w3c-xmlserializer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
@ -4652,6 +4751,12 @@
"integrity": "sha512-+K2ce3hH1QfRlp5y8AialFl/rc7fkwzPQ9fK+fswPWTn3djkstCpm8Iou05NTZy3SxRA9dS7puSF+tjpo6ny7Q==",
"dev": true
},
"@types/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -5267,6 +5372,11 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -5780,6 +5890,11 @@
"flat-cache": "^3.0.4"
}
},
"file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@ -6030,6 +6145,11 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ=="
},
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -6059,8 +6179,7 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"internal-slot": {
"version": "1.0.3",
@ -6207,6 +6326,11 @@
"call-bind": "^1.0.2"
}
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -6310,6 +6434,17 @@
"object.assign": "^4.1.3"
}
},
"jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"requires": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
@ -6320,6 +6455,14 @@
"type-check": "~0.3.2"
}
},
"lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"requires": {
"immediate": "~3.0.5"
}
},
"lit": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.4.1.tgz",
@ -6622,6 +6765,11 @@
"p-limit": "^3.0.2"
}
},
"pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -6700,6 +6848,11 @@
"integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
"dev": true
},
"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=="
},
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@ -6805,6 +6958,27 @@
"warning": "^4.0.2"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -6940,6 +7114,11 @@
"randombytes": "^2.1.0"
}
},
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
},
"shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
@ -6996,6 +7175,21 @@
"source-map": "^0.6.0"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
},
"string.prototype.matchall": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
@ -7244,6 +7438,11 @@
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"requires": {}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"w3c-xmlserializer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",

View file

@ -15,8 +15,10 @@
"@popperjs/core": "^2.11.6",
"@tanstack/react-query": "^4.19.0",
"comlink": "^4.3.1",
"file-saver": "^2.0.5",
"get-stream": "^6.0.1",
"globby": "^13.1.2",
"jszip": "^3.10.1",
"lodash.orderby": "^4.6.0",
"next": "^13.0.5",
"pngjs": "^6.0.0",
@ -30,6 +32,7 @@
"devDependencies": {
"@next/eslint-plugin-next": "^13.0.5",
"@types/fabric": "^4.5.13",
"@types/file-saver": "^2.0.5",
"@types/pngjs": "^6.0.1",
"@types/react": "18.0.25",
"@typescript-eslint/eslint-plugin": "^5.45.0",

View file

@ -1,5 +1,4 @@
import { InputHTMLAttributes, useEffect, useRef, useState } from "react";
import getConfig from "next/config";
import useCanvas from "./useCanvas";
import useTools from "./useTools";
import { usePopper } from "react-popper";
@ -10,17 +9,11 @@ import { GoArrowUp, GoArrowDown } from "react-icons/go";
import { GiArrowCursor } from "react-icons/gi";
import { IoMdBrush } from "react-icons/io";
import { ImPlus } from "react-icons/im";
import useSettings from "./useSettings";
import useWarrior from "./useWarrior";
import useImageWorker from "./useImageWorker";
const { publicRuntimeConfig } = getConfig();
const { materials } = publicRuntimeConfig;
export default function CanvasTools() {
const nameInputRef = useRef<HTMLInputElement | null>(null);
const fileInputRef = useRef<HTMLInputElement | null>(null);
const { actualModel, selectedModelType } = useWarrior();
const fileTypeRef = useRef<HTMLSelectElement | null>(null);
const {
activeCanvas,
backgroundColor,
@ -38,19 +31,10 @@ export default function CanvasTools() {
brushSize,
setBrushSize,
activeCanvasType,
selectedMaterialIndex,
textureSize,
addImages,
exportSkin,
} = useTools();
const materialDefs = materials[actualModel];
const materialDef = materialDefs[selectedMaterialIndex];
const colorCanvasId = materialDef ? `${materialDef.name}:color` : null;
const metallicCanvasId = materialDef ? `${materialDef.name}:metallic` : null;
const { isDrawingMode, setDrawingMode } = useCanvas(activeCanvas);
const { canvas: colorCanvas } = useCanvas(colorCanvasId);
const { canvas: metallicCanvas } = useCanvas(metallicCanvasId);
const { canvasPadding } = useSettings();
const { combineColorAndAlphaImageUrls } = useImageWorker();
// Brush popup
const [referenceElement, setReferenceElement] = useState<HTMLElement | null>(
@ -345,50 +329,23 @@ export default function CanvasTools() {
name="CustomSkinName"
placeholder="Skin Name"
size={12}
hidden={selectedModelType !== "player"}
/>
<button
type="button"
onClick={async (event) => {
const colorImageUrl = colorCanvas.toDataURL({
top: canvasPadding,
left: canvasPadding,
width: textureSize[0],
height: textureSize[1],
});
let downloadUrl;
if (metallicCanvas) {
const metallicImageUrl = metallicCanvas.toDataURL({
top: canvasPadding,
left: canvasPadding,
width: textureSize[0],
height: textureSize[1],
});
downloadUrl = await combineColorAndAlphaImageUrls({
colorImageUrl,
metallicImageUrl,
});
} else {
downloadUrl = colorImageUrl;
}
let name = "";
if (nameInputRef.current) {
name = nameInputRef.current.value.trim() || "MyCustomSkin";
}
const link = document.createElement("a");
link.href = downloadUrl;
link.download =
selectedModelType === "player"
? `${name}.${actualModel}.png`
: materialDef
? `${materialDef.file ?? materialDef.name}.png`
: `weapon_${actualModel}.png`;
link.click();
onClick={() => {
const name = nameInputRef.current ? nameInputRef.current.value : "";
const format = fileTypeRef.current
? fileTypeRef.current.value
: ".png";
exportSkin({ name, format });
}}
>
Export
</button>
<select ref={fileTypeRef}>
<option value="png">.png</option>
<option value="vl2">.vl2</option>
</select>
</div>
</div>
);

View file

@ -5,6 +5,10 @@ import { ToolsContext } from "./useTools";
import useCanvas from "./useCanvas";
import useWarrior from "./useWarrior";
import { createFabricImage } from "./fabricUtils";
import useImageWorker from "./useImageWorker";
import { MaterialDefinition } from "./Material";
import useSettings from "./useSettings";
import { imageUrlToArrayBuffer } from "./imageUtils";
const { publicRuntimeConfig } = getConfig();
@ -33,12 +37,10 @@ function isActiveSelection(
}
export default function ToolsProvider({ children }: { children: ReactNode }) {
const { actualModel } = useWarrior();
const { actualModel, selectedModelType } = useWarrior();
const [selectedMaterialIndex, setSelectedMaterialIndex] = useState(0);
const materialDef = useMemo(
() => materials[actualModel][selectedMaterialIndex] ?? null,
[actualModel, selectedMaterialIndex]
);
const materialDefs = materials[actualModel];
const materialDef = materialDefs[selectedMaterialIndex] ?? null;
const textureSize = useMemo(
() => materialDef.size ?? [512, 512],
@ -69,9 +71,12 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
? `${materialDef.name}:${activeCanvasType}`
: null;
const metallicCanvasId = materialDef ? `${materialDef.name}:metallic` : null;
const { canvases } = useCanvas();
const { canvas, notifyChange } = useCanvas(activeCanvas);
const { canvas: metallicCanvas } = useCanvas(metallicCanvasId);
const [isDrawingMode, setDrawingMode] = useState(false);
const { combineColorAndAlphaImageUrls } = useImageWorker();
const { canvasPadding } = useSettings();
const lockSelection = useCallback(() => {
if (selectedObjects.length) {
@ -193,6 +198,94 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
// forceUpdateRef.current();
}, [canvas]);
const exportSkin = useCallback(
async ({ format, name = "" }: { format: string; name: string }) => {
const { savePngFile, saveZipFile, createZipFile } = await import(
"./exportUtils"
);
name = name.trim() || "MyCustomSkin";
const materialExports = await Promise.all(
materialDefs.map(async (materialDef: MaterialDefinition) => {
const colorCanvas = canvases[`${materialDef.name}:color`]?.canvas;
const metallicCanvas =
canvases[`${materialDef.name}:metallic`]?.canvas;
const textureSize = materialDef.size ?? [512, 512];
let outputImageUrl;
const colorImageUrl = colorCanvas.toDataURL({
top: canvasPadding,
left: canvasPadding,
width: textureSize[0],
height: textureSize[1],
});
if (metallicCanvas) {
const metallicImageUrl = metallicCanvas.toDataURL({
top: canvasPadding,
left: canvasPadding,
width: textureSize[0],
height: textureSize[1],
});
outputImageUrl = await combineColorAndAlphaImageUrls({
colorImageUrl,
metallicImageUrl,
});
} else {
outputImageUrl = colorImageUrl;
}
const filename =
selectedModelType === "player"
? `${name}.${actualModel}.png`
: materialDef
? `${materialDef.file ?? materialDef.name}.png`
: `weapon_${actualModel}.png`;
return { imageUrl: outputImageUrl, filename };
})
);
switch (format) {
case "png": {
const { imageUrl, filename } = materialExports[selectedMaterialIndex];
savePngFile(imageUrl, filename);
break;
}
case "vl2": {
const files = await Promise.all(
materialExports.map(async (materialExport) => ({
data: await imageUrlToArrayBuffer(materialExport.imageUrl),
name: materialExport.filename,
}))
);
const zip = createZipFile(files);
const camelCaseName = actualModel.replace(
/(?:^([a-z])|_([a-z]))/g,
(match, a, b) => (a || b).toUpperCase()
);
const zipFileName =
selectedModelType === "player"
? `zPlayerSkin-${name}.vl2`
: `zWeapon${camelCaseName}-${name}.vl2`;
await saveZipFile(zip, zipFileName);
}
}
return;
},
[
actualModel,
canvasPadding,
canvases,
combineColorAndAlphaImageUrls,
materialDefs,
selectedMaterialIndex,
selectedModelType,
]
);
const context = useMemo(
() => ({
activeCanvas,
@ -214,6 +307,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
addImages,
duplicate,
deleteSelection,
exportSkin,
isDrawingMode,
setDrawingMode,
selectedMaterialIndex,
@ -236,6 +330,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
addImages,
duplicate,
deleteSelection,
exportSkin,
isDrawingMode,
selectedMaterialIndex,
textureSize,

21
src/exportUtils.ts Normal file
View file

@ -0,0 +1,21 @@
import JSZip from "jszip";
import { saveAs } from "file-saver";
export function createZipFile(
files: Array<{ name: string; data: ArrayBuffer }>
) {
const zip = new JSZip();
for (const file of files) {
zip.file(`textures/skins/${file.name}`, file.data);
}
return zip;
}
export async function saveZipFile(zip: JSZip, name: string) {
const blob = await zip.generateAsync({ type: "blob" });
saveAs(blob, name);
}
export function savePngFile(imageUrl: string, name: string) {
saveAs(imageUrl, name);
}

View file

@ -140,8 +140,9 @@ export function setMetallicFromGrayscale(rgba: Uint8Array) {
// Red meanings nothing, set to 0.
rgba[i] = 0;
// Green maps to roughness. We want more metallic to be less rough.
rgba[i + 1] = 255 - grayscale;
rgba[i + 1] = 255 - Math.min(Math.floor(grayscale * 2), 255);
// Blue and alpha values should already be correct.
rgba[i + 2] = Math.min(Math.floor(grayscale * 2), 255);
}
}

View file

@ -55,7 +55,8 @@ select {
}
.CanvasBackgroundColor {
display: flex;
/* display: flex; */
display: none;
align-items: center;
}
@ -174,6 +175,13 @@ select {
margin-left: 3px;
}
.Export select {
width: 6em;
min-width: 6em;
flex: 0 0 auto;
margin-left: 3px;
}
.SliderContainer {
min-width: 200px;
min-height: 30px;

View file

@ -17,6 +17,13 @@ interface ToolsContextValue {
bringForward: () => void;
lockSelection: () => void;
unlockSelection: () => void;
exportSkin: ({
name,
format,
}: {
name: string;
format: string;
}) => Promise<void>;
lockedObjects: Set<fabric.Object>;
backgroundColor: string;
setBackgroundColor: (backgroundColor: string) => void;