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 @@
@@ -294,10 +294,10 @@
@@ -346,12 +346,12 @@
@@ -384,25 +384,25 @@
-