From deb091932afe739a7cb47a3e16806d2e942a55ed Mon Sep 17 00:00:00 2001 From: Ash Monty Date: Sat, 13 Aug 2022 16:52:06 +0200 Subject: [PATCH] feat(miieditor): switch to mii-js, add missing props --- package-lock.json | 164 +++------------- package.json | 1 + public/assets/css/miieditor.css | 49 ++++- public/assets/images/copy.svg | 1 + public/assets/images/miieditor.svg | 34 +++- public/assets/images/share.svg | 1 + public/assets/images/star.svg | 1 + public/assets/js/MiiClass.js | 214 -------------------- public/assets/js/miieditor.js | 301 +++++++++++++++++++++-------- views/account/miieditor.handlebars | 197 +++++++++++-------- 10 files changed, 441 insertions(+), 522 deletions(-) create mode 100644 public/assets/images/copy.svg create mode 100644 public/assets/images/share.svg create mode 100644 public/assets/images/star.svg delete mode 100644 public/assets/js/MiiClass.js diff --git a/package-lock.json b/package-lock.json index 2c751d3..f3b11fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,11 +25,11 @@ "gray-matter": "^4.0.3", "kaitai-struct": "^0.9.0", "marked": "^4.0.10", + "mii-js": "github:PretendoNetwork/mii-js#v1.0.2", "mongoose": "^6.4.0", "morgan": "^1.10.0", "nodemailer": "^6.7.5", "stripe": "^9.9.0", - "trello": "^0.11.0", "uuid": "^8.3.2" }, "devDependencies": { @@ -576,6 +576,11 @@ "node": ">= 0.8" } }, + "node_modules/bit-buffer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz", + "integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==" + }, "node_modules/bn.js": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", @@ -1489,11 +1494,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-promise": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", - "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=" - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2915,6 +2915,14 @@ "node": ">= 0.6" } }, + "node_modules/mii-js": { + "version": "1.0.2", + "resolved": "git+ssh://git@github.com/PretendoNetwork/mii-js.git#d3386c348b8428fc8514dbb97a7c24ffefa3017e", + "license": "ISC", + "dependencies": { + "bit-buffer": "^0.2.5" + } + }, "node_modules/miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -3661,33 +3669,6 @@ "lowercase-keys": "^2.0.0" } }, - "node_modules/restler": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/restler/-/restler-3.3.0.tgz", - "integrity": "sha512-TrnPEY3DJgFJgPd5vJmHpOj0WlAzLcX2eIcAqc/EB/YlgPFz0aMD55GkQN16d3nX0ydlq93lthZWGKyBpVnFEg==", - "dependencies": { - "iconv-lite": "0.2.11", - "qs": "1.2.0", - "xml2js": "0.4.0", - "yaml": "0.2.3" - }, - "engines": { - "node": ">= 0.10.x" - } - }, - "node_modules/restler/node_modules/iconv-lite": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", - "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/restler/node_modules/qs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-1.2.0.tgz", - "integrity": "sha512-XUf0O7rlGjbH+n7uqyT+xn362fmoPe4ehtHL6VK1nbSgQ7CqG0ZZLr1nU2EyXlRq++YphPdQ/5scjIWNMSPnhg==" - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -3734,11 +3715,6 @@ "node": ">=6" } }, - "node_modules/sax": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", - "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=" - }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -4274,19 +4250,6 @@ "node": ">=12" } }, - "node_modules/trello": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/trello/-/trello-0.11.0.tgz", - "integrity": "sha512-WMjfSTA+ybGnd36ZoMX7Ia7wfpU9KP2lsafv9mjLwFcE1ECp8b9/rX3uW4ivNO7jVLnHRyQ1pHsirbMvK0Ix2Q==", - "dependencies": { - "es6-promise": "~3.0.2", - "object-assign": "~4.1.0", - "restler": "~3.3.0" - }, - "engines": { - "node": ">= 0.10.x" - } - }, "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -4578,23 +4541,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "node_modules/xml2js": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.0.tgz", - "integrity": "sha1-Ek/EEUtBKcgQgA7LKshs8lRiy5o=", - "dependencies": { - "sax": "0.5.x", - "xmlbuilder": ">=0.4.2" - } - }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "engines": { - "node": ">=8.0" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -4608,14 +4554,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true - }, - "node_modules/yaml": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-0.2.3.tgz", - "integrity": "sha1-tUUOkudu82td0k42YAkeuu7z5cc=", - "engines": { - "node": "*" - } } }, "dependencies": { @@ -5045,6 +4983,11 @@ "safe-buffer": "5.1.2" } }, + "bit-buffer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz", + "integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==" + }, "bn.js": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", @@ -5812,11 +5755,6 @@ "is-symbol": "^1.0.2" } }, - "es6-promise": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", - "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=" - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -6869,6 +6807,13 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, + "mii-js": { + "version": "git+ssh://git@github.com/PretendoNetwork/mii-js.git#d3386c348b8428fc8514dbb97a7c24ffefa3017e", + "from": "mii-js@github:PretendoNetwork/mii-js#v1.0.2", + "requires": { + "bit-buffer": "^0.2.5" + } + }, "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -7450,29 +7395,6 @@ "lowercase-keys": "^2.0.0" } }, - "restler": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/restler/-/restler-3.3.0.tgz", - "integrity": "sha512-TrnPEY3DJgFJgPd5vJmHpOj0WlAzLcX2eIcAqc/EB/YlgPFz0aMD55GkQN16d3nX0ydlq93lthZWGKyBpVnFEg==", - "requires": { - "iconv-lite": "0.2.11", - "qs": "1.2.0", - "xml2js": "0.4.0", - "yaml": "0.2.3" - }, - "dependencies": { - "iconv-lite": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", - "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=" - }, - "qs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-1.2.0.tgz", - "integrity": "sha512-XUf0O7rlGjbH+n7uqyT+xn362fmoPe4ehtHL6VK1nbSgQ7CqG0ZZLr1nU2EyXlRq++YphPdQ/5scjIWNMSPnhg==" - } - } - }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -7510,11 +7432,6 @@ "sparse-bitfield": "^3.0.3" } }, - "sax": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", - "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=" - }, "section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -7932,16 +7849,6 @@ "punycode": "^2.1.1" } }, - "trello": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/trello/-/trello-0.11.0.tgz", - "integrity": "sha512-WMjfSTA+ybGnd36ZoMX7Ia7wfpU9KP2lsafv9mjLwFcE1ECp8b9/rX3uW4ivNO7jVLnHRyQ1pHsirbMvK0Ix2Q==", - "requires": { - "es6-promise": "~3.0.2", - "object-assign": "~4.1.0", - "restler": "~3.3.0" - } - }, "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -8166,20 +8073,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "xml2js": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.0.tgz", - "integrity": "sha1-Ek/EEUtBKcgQgA7LKshs8lRiy5o=", - "requires": { - "sax": "0.5.x", - "xmlbuilder": ">=0.4.2" - } - }, - "xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==" - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -8190,11 +8083,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true - }, - "yaml": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-0.2.3.tgz", - "integrity": "sha1-tUUOkudu82td0k42YAkeuu7z5cc=" } } } diff --git a/package.json b/package.json index e89df07..b6f5838 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "gray-matter": "^4.0.3", "kaitai-struct": "^0.9.0", "marked": "^4.0.10", + "mii-js": "github:PretendoNetwork/mii-js#v1.0.2", "mongoose": "^6.4.0", "morgan": "^1.10.0", "nodemailer": "^6.7.5", diff --git a/public/assets/css/miieditor.css b/public/assets/css/miieditor.css index f7986e8..8371908 100644 --- a/public/assets/css/miieditor.css +++ b/public/assets/css/miieditor.css @@ -190,6 +190,49 @@ div.subtabs .subtabbtn.active:hover::before { transform: scale(3); } +.has-textinput { + grid-template-columns: 1fr 1fr; + grid-template-rows: auto; + grid-auto-rows: auto; + gap: 24px; +} +.has-textinput label { + display: block; + margin-bottom: 6px; + text-transform: uppercase; + font-size: 12px; +} +div.birthday { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: end; + gap: 12px; +} +div.birthday span { + margin-bottom: 12px; +} +.has-textinput .icons { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; +} +.has-textinput .icons input[type="checkbox"] { + box-sizing: border-box; + margin: 0; + height: 49px; + width: 49px; +} + +input[type="checkbox"]#allowCopying:checked { + background: no-repeat center/80% url(../images/copy.svg), var(--accent-shade-0); +} +input[type="checkbox"]#disableSharing:checked { + background: no-repeat center/80% url(../images/share.svg), var(--accent-shade-0); +} +input[type="checkbox"]#favorite:checked { + background: no-repeat center/80% url(../images/star.svg), var(--accent-shade-0); +} + form.params { grid-template-columns: repeat(2, auto); height: 618px; @@ -593,7 +636,7 @@ button * { grid-row: 4; } - fieldset:not(.has-sliders) { + fieldset:not(.has-sliders, .has-textinput) { position: relative; margin-bottom: -60px; } @@ -648,6 +691,10 @@ button * { width: 36px; height: 36px; } + + .has-textinput { + grid-template-columns: 1fr; + } } @media screen and (max-width: 360px) { diff --git a/public/assets/images/copy.svg b/public/assets/images/copy.svg new file mode 100644 index 0000000..67e47f7 --- /dev/null +++ b/public/assets/images/copy.svg @@ -0,0 +1 @@ + diff --git a/public/assets/images/miieditor.svg b/public/assets/images/miieditor.svg index 27724a6..0ce9d34 100644 --- a/public/assets/images/miieditor.svg +++ b/public/assets/images/miieditor.svg @@ -27,11 +27,11 @@ inkscape:deskcolor="#d1d1d1" showgrid="false" inkscape:lockguides="false" - inkscape:zoom="5.979798" - inkscape:cx="65.888513" - inkscape:cy="316.23142" + inkscape:zoom="5.6568543" + inkscape:cx="104.56341" + inkscape:cy="351.43207" inkscape:window-width="1920" - inkscape:window-height="1023" + inkscape:window-height="1025" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" @@ -75,7 +75,7 @@ d="m 38.391224,328.78155 c -0.402278,0.001 -0.847854,0.0668 -1.343327,0.20523 a 0.43421497,0.4342385 0 0 0 -0.041,0.0153 c -0.576068,0.22485 -0.849154,0.72713 -0.944648,1.11432 -0.095,0.3872 -0.053,0.72084 -0.053,0.72084 a 0.43421497,0.4342385 0 0 0 0.642766,0.32564 c 1.156837,-0.6469 1.853099,-0.71378 2.39647,-0.61567 0.54327,0.0981 0.981247,0.4064 1.575614,0.64112 a 0.43421497,0.4342385 0 0 0 0.537671,-0.61568 c -0.252786,-0.45231 -0.562569,-0.89979 -1.012545,-1.23983 -0.450075,-0.34005 -1.039843,-0.5546 -1.758805,-0.55123 z m 7.218009,0 c -0.718861,-0.004 -1.308729,0.21119 -1.758704,0.55123 -0.450076,0.34005 -0.759759,0.78753 -1.012546,1.23982 a 0.43421497,0.4342385 0 0 0 0.537671,0.61568 c 0.594368,-0.2347 1.032244,-0.54301 1.575615,-0.64111 0.54327,-0.0981 1.239633,-0.0313 2.39647,0.61567 a 0.43421497,0.4342385 0 0 0 0.642665,-0.32565 c 0,0 0.043,-0.33363 -0.053,-0.72083 -0.09499,-0.38719 -0.36858,-0.88946 -0.944649,-1.11432 a 0.43421497,0.4342385 0 0 0 -0.041,-0.0153 c -0.495374,-0.13835 -0.941049,-0.20326 -1.343228,-0.20522 z" /> + + + + + + + diff --git a/public/assets/images/share.svg b/public/assets/images/share.svg new file mode 100644 index 0000000..23fa1fb --- /dev/null +++ b/public/assets/images/share.svg @@ -0,0 +1 @@ + diff --git a/public/assets/images/star.svg b/public/assets/images/star.svg new file mode 100644 index 0000000..d035a75 --- /dev/null +++ b/public/assets/images/star.svg @@ -0,0 +1 @@ + diff --git a/public/assets/js/MiiClass.js b/public/assets/js/MiiClass.js deleted file mode 100644 index 9a097cb..0000000 --- a/public/assets/js/MiiClass.js +++ /dev/null @@ -1,214 +0,0 @@ -// Fetched from https://github.com/PretendoNetwork/account/blob/master/src/mii.js on 2021-12-16 - -const KaitaiStream = require('kaitai-struct/KaitaiStream'); - -class Mii extends KaitaiStream { - constructor(arrayBuffer, byteOffset) { - super(arrayBuffer, byteOffset); - - this.decode(); - } - - decode() { - // Decode raw data - // A lot of this goes unused - this.unknown1 = this.readU1(); - this.characterSet = this.readBitsIntBe(2); - this.regionLock = this.readBitsIntBe(2); - this.profanityFlag = this.readBitsIntBe(1) !== 0; - this.copying = this.readBitsIntBe(1) !== 0; - this.unknown2 = this.readBitsIntBe(2); - this.slotIndex = this.readBitsIntBe(4); - this.pageIndex = this.readBitsIntBe(4); - this.version = this.readBitsIntBe(4); - this.unknown3 = this.readBitsIntBe(4); - this.systemId = Array(8).fill().map(() => this.readU1()); - this.avatarId = Array(4).fill().map(() => this.readU1()); - this.clientId = Array(6).fill().map(() => this.readU1()); - this.padding = this.readU2le(); - this.miiMetaData = this.readU2le(); - this.miiName = Buffer.from(this.readBytes(20)).toString('utf16le'); - this.height = this.readU1(); - this.build = this.readU1(); - this.faceColor = this.readBitsIntBe(3); - this.faceType = this.readBitsIntBe(4); - this.mingle = this.readBitsIntBe(1) !== 0; - this.faceMakeup = this.readBitsIntBe(4); - this.faceWrinkles = this.readBitsIntBe(4); - this.alignToByte(); - this.hairType = this.readU1(); - this.unknown5 = this.readBitsIntBe(4); - this.hairFlip = this.readBitsIntBe(1) !== 0; - this.hairColor = this.readBitsIntBe(3); - this.alignToByte(); - this.eyeData = this.readU4le(); - this.eyebrowData = this.readU4le(); - this.noseData = this.readU2le(); - this.mouthData = this.readU2le(); - this.mouthData2 = this.readU2le(); - this.facialHairData = this.readU2le(); - this.glassesData = this.readU2le(); - this.moleData = this.readU2le(); - this.creatorName = Buffer.from(this.readBytes(20)).toString('utf16le'); - this.padding2 = this.readU2le(); - this.checksum = this.readU2le(); - - // Carve out more specific data from the above values - // TODO: read these bits directly instead of getting them later - - this.gender = (this.miiMetaData & 1); - this.birthMonth = ((this.miiMetaData >> 1) & 15); - this.birthDay = ((this.miiMetaData >> 5) & 31); - this.favoriteColor = ((this.miiMetaData >> 10) & 15); - this.favorite = ((this.miiMetaData >> 14) & 1); - - this.eyeType = (this.eyeData & 63); - this.eyeColor = ((this.eyeData >> 6) & 7); - this.eyeSize = ((this.eyeData >> 9) & 7); - this.eyeStretch = ((this.eyeData >> 13) & 7); - this.eyeRotation = ((this.eyeData >> 16) & 31); - this.eyeHorizontal = ((this.eyeData >> 21) & 15); - this.eyeVertical = ((this.eyeData >> 25) & 31); - - this.eyebrowType = (this.eyebrowData & 31); - this.eyebrowColor = ((this.eyebrowData >> 5) & 7); - this.eyebrowSize = ((this.eyebrowData >> 8) & 15); - this.eyebrowStretch = ((this.eyebrowData >> 12) & 7); - this.eyebrowRotation = ((this.eyebrowData >> 16) & 15); - this.eyebrowHorizontal = ((this.eyebrowData >> 21) & 15); - this.eyebrowVertical = ((this.eyebrowData >> 25) & 31); - - this.noseType = (this.noseData & 31); - this.noseSize = ((this.noseData >> 5) & 15); - this.noseVertical = ((this.noseData >> 9) & 31); - - - this.mouthType = (this.mouthData & 63); - this.mouthColor = ((this.mouthData >> 6) & 7); - this.mouthSize = ((this.mouthData >> 9) & 15); - this.mouthStretch = ((this.mouthData >> 13) & 7); - - this.mouthVertical = (this.mouthData2 & 31); - this.facialHairMustache = ((this.mouthData2 >> 5) & 7); - - this.facialHairType = (this.facialHairData & 7); - this.facialHairColor = ((this.facialHairData >> 3) & 7); - this.facialHairSize = ((this.facialHairData >> 6) & 15); - this.facialHairVertical = ((this.facialHairData >> 10) & 31); - - this.glassesType = (this.glassesData & 15); - this.glassesColor = (this.glassesData >> 4) & 7; - this.glassesSize = (this.glassesData >> 7) & 15; - this.glassesVertical = (this.glassesData >> 11) & 15; - - this.moleEnable = (this.moleData >> 15); - this.moleSize = ((this.moleData >> 1) & 15); - this.moleHorizontal = ((this.moleData >> 5) & 31); - this.moleVertical = ((this.moleData >> 10) & 31); - } - - toStudioMii() { - /* - Can also disable randomization with: - let miiStudioData = Buffer.alloc(0x2F); - let next = 256; - and removing "randomizer" and the "miiStudioData.writeUInt8(randomizer);" call - */ - const miiStudioData = Buffer.alloc(0x2F); - const randomizer = Math.floor(256 * Math.random()); - let next = randomizer; - let pos = 1; - - function encodeMiiPart(partValue) { - const encoded = (7 + (partValue ^ next)) % 256; - next = encoded; - - miiStudioData.writeUInt8(encoded, pos); - pos++; - } - - miiStudioData.writeUInt8(randomizer); - - if (this.facialHairColor === 0) { - encodeMiiPart(8); - } else { - encodeMiiPart(this.facialHairColor); - } - - encodeMiiPart(this.facialHairType); - encodeMiiPart(this.build); - encodeMiiPart(this.eyeStretch); - encodeMiiPart(this.eyeColor + 8); - encodeMiiPart(this.eyeRotation); - encodeMiiPart(this.eyeSize); - encodeMiiPart(this.eyeType); - encodeMiiPart(this.eyeHorizontal); - encodeMiiPart(this.eyeVertical); - encodeMiiPart(this.eyebrowStretch); - - if (this.eyebrowColor === 0) { - encodeMiiPart(8); - } else { - encodeMiiPart(this.eyebrowColor); - } - - encodeMiiPart(this.eyebrowRotation); - encodeMiiPart(this.eyebrowSize); - encodeMiiPart(this.eyebrowType); - encodeMiiPart(this.eyebrowHorizontal); - encodeMiiPart(this.eyebrowVertical); - encodeMiiPart(this.faceColor); - encodeMiiPart(this.faceMakeup); - encodeMiiPart(this.faceType); - encodeMiiPart(this.faceWrinkles); - encodeMiiPart(this.favoriteColor); - encodeMiiPart(this.gender); - - if (this.glassesColor == 0) { - encodeMiiPart(8); - } else if (this.glassesColor < 6) { - encodeMiiPart(this.glassesColor + 13); - } else { - encodeMiiPart(0); - } - - encodeMiiPart(this.glassesSize); - encodeMiiPart(this.glassesType); - encodeMiiPart(this.glassesVertical); - - if (this.hairColor == 0) { - encodeMiiPart(8); - } else { - encodeMiiPart(this.hairColor); - } - - encodeMiiPart(this.hairFlip ? 1 : 0); - encodeMiiPart(this.hairType); - encodeMiiPart(this.height); - encodeMiiPart(this.moleSize); - encodeMiiPart(this.moleEnable); - encodeMiiPart(this.moleHorizontal); - encodeMiiPart(this.moleVertical); - encodeMiiPart(this.mouthStretch); - - if (this.mouthColor < 4) { - encodeMiiPart(this.mouthColor + 19); - } else { - encodeMiiPart(0); - } - - encodeMiiPart(this.mouthSize); - encodeMiiPart(this.mouthType); - encodeMiiPart(this.mouthVertical); - encodeMiiPart(this.facialHairSize); - encodeMiiPart(this.facialHairMustache); - encodeMiiPart(this.facialHairVertical); - encodeMiiPart(this.noseSize); - encodeMiiPart(this.noseType); - encodeMiiPart(this.noseVertical); - - return miiStudioData; - } -} - -module.exports = Mii; diff --git a/public/assets/js/miieditor.js b/public/assets/js/miieditor.js index 928510d..5d91c5a 100644 --- a/public/assets/js/miieditor.js +++ b/public/assets/js/miieditor.js @@ -1,4 +1,4 @@ -// This file gets automatically bundled with browserify when running the start script. This also means that after any update you're gonna need to restart the server. +// This file gets automatically bundled with browserify when running the start script. This also means that after any update you're gonna need to restart the server to see any changes. // Prevent the user from reloading or leaving the page window.addEventListener('beforeunload', function (e) { @@ -6,7 +6,7 @@ window.addEventListener('beforeunload', function (e) { e.returnValue = ''; }); -const Mii = require('./MiiClass.js'); +const Mii = require('mii-js'); // MII RENDERER @@ -15,32 +15,52 @@ const encodedUserMiiData = document.querySelector( 'script#encodedUserMiiData' ).textContent; document.querySelector('script#encodedUserMiiData').remove(); -// We initialize the Mii object with the encoded data and render the Mii + +// We initialize the Mii object const mii = new Mii(Buffer.from(encodedUserMiiData, 'base64')); + +// We set the img sources for the unedited miis in the save animation +const miiStudioNeutralUrl = mii.studioUrl({ + width: 512, + bgColor: '13173300', +}); +const miiStudioSorrowUrl = mii.studioUrl({ + width: 512, + bgColor: '13173300', + expression: 'sorrow', +}); +document.querySelector('.mii-comparison img.old-mii').src = miiStudioNeutralUrl; +document.querySelector('.mii-comparison.confirmed img.old-mii').src = + miiStudioSorrowUrl; + +// Initial mii render renderMii(); -const oldMiiStudioData = mii.toStudioMii().toString('hex'); - -// initial setup for saving animation -document.querySelector('.mii-comparison img.old-mii').src = `https://studio.mii.nintendo.com/miis/image.png?data=${oldMiiStudioData}&type=face&expression=normal&width=512&bgColor=13173300`; -document.querySelector('.mii-comparison.confirmed img.old-mii').src = `https://studio.mii.nintendo.com/miis/image.png?data=${oldMiiStudioData}&type=face&expression=sorrow&width=512&bgColor=13173300`; - // This function renders the Mii on the page function renderMii(type) { - type = type || 'all_body'; // Can be 'all_body' or 'face' + const miiStudioUrl = mii.studioUrl({ + width: 512, + bgColor: '13173300', + type: type || 'all_body', + }); - const miiStudioData = mii.toStudioMii().toString('hex'); - document.querySelector( - 'img#mii-img' - ).src = `https://studio.mii.nintendo.com/miis/image.png?data=${miiStudioData}&type=${type}&expression=normal&width=512&bgColor=13173300`; + const faceMiiStudioUrl = mii.studioUrl({ + width: 512, + bgColor: '13173300', + }); + + const faceMiiStudioSmileUrl = mii.studioUrl({ + width: 512, + bgColor: '13173300', + expression: 'smile' + }); + + // sets the mii + document.querySelector('img#mii-img').src = miiStudioUrl; // sets the new mii in the save tab to the new mii - document.querySelector( - '.mii-comparison img.new-mii' - ).src = `https://studio.mii.nintendo.com/miis/image.png?data=${miiStudioData}&type=face&expression=normal&width=512&bgColor=13173300`; - document.querySelector( - '.mii-comparison.confirmed img.new-mii' - ).src = `https://studio.mii.nintendo.com/miis/image.png?data=${miiStudioData}&type=face&expression=smile&width=512&bgColor=13173300`; + document.querySelector('.mii-comparison img.new-mii').src = faceMiiStudioUrl; + document.querySelector('.mii-comparison.confirmed img.new-mii').src = faceMiiStudioSmileUrl; // this sets the mii height so that the face width stays the same document.querySelector('img#mii-img').style.height = `${ @@ -58,27 +78,91 @@ function renderMii(type) { // This function updates a prop of the Mii and rerenders it function updateMii(e, type) { const prop = e.target.name; - const value = e.target.value; - mii[prop] = parseInt(value); + let value = e.target.value || e.target.defaultValue; + + // if the value comes from a checkbox, we use the checked property + if (value === 'on' || value === 'off') { + value = e.target.checked; + } + + // if the prop is disableSharing, we set the value to the opposite of the current value + if (prop === 'disableSharing') { + value = !value; + } + + // Handle booleans, on/offs and strings + if (value === 'true' || value === 'false') { + mii[prop] = value === 'true'; + } else if (value === 'on' || value === 'off') { + mii[prop] = value === 'on'; + } else if (isNaN(parseInt(value))) { + mii[prop] = value; + } else { + mii[prop] = parseInt(value); + } + renderMii(type); } +// prevent the user from inputting values out of the range +function handleRange(e) { + if (e.target.value === '') return; + + const value = parseInt(e.target.value); + + // if the value is out of range, set to the nearest in-range value + if (value > e.target.max) { + e.target.value = e.target.max; + } else if (value < e.target.min) { + e.target.value = e.target.min; + } +} + +function handleCalendar(e) { + const month = parseInt(e.target.value); + + // we get the days in the month (hardcoded to 2024 'cause it's a leap year) + const daysInMonth = new Date(2024, month, 0).getDate(); + + const birthDayElement = document.querySelector('input[type=\'number\']#birthDay'); + + // we set the max value for the day selector to the days in the month + birthDayElement.max = daysInMonth; + + // if the day is greater than the days in the month, set it to the max + if (parseInt(birthDayElement.value) > daysInMonth) { + birthDayElement.value = daysInMonth; + } +} + +function preventEmpty(e) { + if (e.target.value !== '') return; + + e.target.value = e.target.defaultValue; +} + document.querySelectorAll('fieldset').forEach((fieldset) => { fieldset.addEventListener('change', (e) => updateMii(e)); }); -document.querySelectorAll('input[type=\'range\']').forEach((fieldset) => { - fieldset.addEventListener('input', (e) => updateMii(e)); +document.querySelectorAll('input[type=\'range\']').forEach((input) => { + input.addEventListener('input', (e) => updateMii(e)); }); +document.querySelectorAll('input[type=\'text\'], input[type=\'number\']').forEach((input) => { + input.addEventListener('blur', (e) => preventEmpty(e)); +}); + +document.querySelector('input[type=\'number\']#birthMonth').addEventListener('blur', (e) => handleCalendar(e)); + // FORM // Here we preselect the options corresponding to the Mii's current values [ 'faceType', - 'faceColor', - 'faceMakeup', - 'faceWrinkles', + 'skinColor', + 'makeupType', + 'wrinklesType', 'hairType', 'hairColor', 'eyebrowType', @@ -90,45 +174,78 @@ document.querySelectorAll('input[type=\'range\']').forEach((fieldset) => { 'mouthColor', 'glassesType', 'glassesColor', - 'facialHairType', + 'beardType', 'facialHairColor', - 'facialHairMustache', - 'moleEnable', + 'mustacheType', + 'moleEnabled', 'gender', - 'favoriteColor' + 'favoriteColor', ].forEach((prop) => { - document.querySelector(`#${prop}${mii[prop]}`).checked = true; + const el = document.querySelector(`#${prop}${mii[prop]}`); + if (el) { + el.checked = true; + } + console.log(`[info] preselected value for ${prop}`); }); [ - 'eyebrowVertical', - 'eyebrowHorizontal', - 'eyebrowRotation', - 'eyebrowSize', - 'eyebrowStretch', - 'eyeVertical', - 'eyeHorizontal', - 'eyeRotation', - 'eyeSize', - 'eyeStretch', - 'noseVertical', - 'noseSize', - 'mouthVertical', - 'mouthSize', - 'mouthStretch', - 'glassesVertical', - 'glassesSize', - 'facialHairVertical', - 'facialHairSize', - 'moleVertical', - 'moleHorizontal', - 'moleSize', - 'height', - 'build' + 'favorite', + 'allowCopying' ].forEach((prop) => { - document.querySelector(`#${prop}`).value = mii[prop]; + const el = document.querySelector(`#${prop}`); + if (el) { + el.checked = mii[prop]; + } + console.log(`[info] preselected value for ${prop}`); }); +document.querySelector('#disableSharing').checked = !mii.disableSharing; +console.log('[info] preselected value for disableSharing'); + +[ + 'eyebrowYPosition', + 'eyebrowSpacing', + 'eyebrowRotation', + 'eyebrowScale', + 'eyebrowVerticalStretch', + 'eyeYPosition', + 'eyeSpacing', + 'eyeRotation', + 'eyeScale', + 'eyeVerticalStretch', + 'noseYPosition', + 'noseScale', + 'mouthYPosition', + 'mouthScale', + 'mouthHorizontalStretch', + 'glassesYPosition', + 'glassesScale', + 'mustacheYPosition', + 'mustacheScale', + 'moleYPosition', + 'moleXPosition', + 'moleScale', + 'height', + 'build', + 'miiName', + 'creatorName', + 'birthDay', + 'birthMonth', +].forEach((prop) => { + document.querySelector(`#${prop}`).value = mii[prop]; + document.querySelector(`#${prop}`).defaultValue = mii[prop]; + console.log(`[info] preselected value for ${prop}`); +}); + + +[ + 'birthDay', + 'birthMonth' +].forEach((prop) => { + document.querySelector(`#${prop}`).addEventListener('input', (e) => { + handleRange(e); + }); +}); // TABS, SUBTABS, AND ALL THE INHERENT JANK @@ -136,19 +253,22 @@ function openTab(e, tabType) { e.preventDefault(); // Deselect all subpages - document.querySelectorAll('.subtab.has-subpages .subpage.active').forEach((el) => { - el.classList?.remove('active'); - }); + document + .querySelectorAll('.subtab.has-subpages .subpage.active') + .forEach((el) => { + el.classList?.remove('active'); + }); document.querySelectorAll('.subtab.active').forEach((el) => { el.classList?.remove('active'); }); - const buttonReplacement = tabType.charAt(0).toUpperCase() + tabType.slice(1); + const buttonReplacement = + tabType.charAt(0).toUpperCase() + tabType.slice(1); - document.querySelectorAll(`.${tabType}.active`).forEach(el => { + document.querySelectorAll(`.${tabType}.active`).forEach((el) => { el?.classList?.remove('active'); }); - document.querySelectorAll(`.${tabType}btn.active`).forEach(el => { + document.querySelectorAll(`.${tabType}btn.active`).forEach((el) => { el?.classList?.remove('active'); }); @@ -170,9 +290,7 @@ function openTab(e, tabType) { }); // Selects the first subpage if there is one - document - .querySelector(`#${selectedID} .subpage`) - ?.classList?.add('active'); + document.querySelector(`#${selectedID} .subpage`)?.classList?.add('active'); } // Here we bind all of the functions to the corresponding buttons @@ -223,26 +341,41 @@ document.querySelectorAll('button.page-btn').forEach((el) => { }); // mii saving business (animation jank & actual saving) -document.querySelector('#saveTab #saveButton').addEventListener('click', (e) => { - e.preventDefault(); +document + .querySelector('#saveTab #saveButton') + .addEventListener('click', (e) => { + e.preventDefault(); - document.querySelector('#saveTab #saveButton').classList.add('inactive', 'fade-out'); - document.querySelector('.tabs').style.pointerEvents = 'none'; - document.querySelector('.mii-comparison.confirmed').style.opacity = 1; - document.querySelector('#saveTab p.save-prompt').classList.add('fade-out'); + document + .querySelector('#saveTab #saveButton') + .classList.add('inactive', 'fade-out'); + document.querySelector('.tabs').style.pointerEvents = 'none'; + document.querySelector('.mii-comparison.confirmed').style.opacity = 1; + document + .querySelector('#saveTab p.save-prompt') + .classList.add('fade-out'); - setTimeout(() => { - document.querySelector('.mii-comparison.unconfirmed').style.opacity = 0; - }, 500); + setTimeout(() => { + document.querySelector( + '.mii-comparison.unconfirmed' + ).style.opacity = 0; + }, 500); - setTimeout(() => { - document.querySelector('.mii-comparison.confirmed .old-mii').classList.add('fade-out'); - document.querySelector('.mii-comparison.confirmed svg').classList.add('fade-out'); - }, 1500); + setTimeout(() => { + document + .querySelector('.mii-comparison.confirmed .old-mii') + .classList.add('fade-out'); + document + .querySelector('.mii-comparison.confirmed svg') + .classList.add('fade-out'); + }, 1500); - setTimeout(() => { - document.querySelector('.mii-comparison.confirmed .new-mii-wrapper').classList.add('centered-mii-img'); - }, 2000); + setTimeout(() => { + document + .querySelector('.mii-comparison.confirmed .new-mii-wrapper') + .classList.add('centered-mii-img'); + }, 2000); - -}); + alert(mii.encode().toString('base64')); + // CHECK IF MII IS VALID SERVERSIDE + }); diff --git a/views/account/miieditor.handlebars b/views/account/miieditor.handlebars index 421d0f2..8116077 100644 --- a/views/account/miieditor.handlebars +++ b/views/account/miieditor.handlebars @@ -37,8 +37,8 @@ - - + + @@ -46,9 +46,9 @@
- - - + + +
@@ -58,32 +58,32 @@ {{/each}}
-
- - - - - - - - - - - - +
+ + + + + + + + + + + +
-
+
{{#each editorJSON.arrayOf12}} - - + + {{/each}}
-
+
{{#each editorJSON.arrayOf12}} - - + + {{/each}}
@@ -173,16 +173,16 @@
- - - - + + + + - - - - + + + +
@@ -251,16 +251,16 @@
- - - - + + + + - - - - + + + +
@@ -294,10 +294,10 @@
- - - - + + + +
@@ -346,12 +346,12 @@
- - - - - - + + + + + +
@@ -384,25 +384,25 @@
- - - - + + + +
-
+
- - + +
-
+
{{#each editorJSON.arrayOf6}} - - + + {{/each}}
@@ -425,40 +425,40 @@
-
+
{{#each editorJSON.arrayOf6}} - - + + {{/each}}
- - - - + + + +
-
+
- +
-
- - - - +
+ + + +
- - - - - - + + + + + +
@@ -467,6 +467,7 @@ +
@@ -483,6 +484,42 @@
+
+
+ + +
+
+ + +
+
+
+ + +
+ / +
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+