class Account{ constructor(...args){ this.init(...args) } init(touchEnabled){ this.touchEnabled = touchEnabled cancelTouch = false this.locked = false if(account.loggedIn){ this.accountForm() }else{ this.loginForm() } this.selected = this.items.length - 1 this.keyboard = new Keyboard({ confirm: ["enter", "space", "don_l", "don_r"], previous: ["left", "up", "ka_l"], next: ["right", "down", "ka_r"], back: ["escape"] }, this.keyPressed.bind(this)) this.gamepad = new Gamepad({ "confirm": ["b", "ls", "rs"], "previous": ["u", "l", "lb", "lt", "lsu", "lsl"], "next": ["d", "r", "rb", "rt", "lsd", "lsr"], "back": ["start", "a"] }, this.keyPressed.bind(this)) pageEvents.send("account", account.loggedIn) } accountForm(){ loader.changePage("account", true) this.mode = "account" this.setAltText(this.getElement("view-title"), account.username) this.items = [] this.inputForms = [] this.shownDiv = "" this.errorDiv = this.getElement("error-div") this.getElement("displayname-hint").innerText = strings.account.displayName this.displayname = this.getElement("displayname") this.displayname.placeholder = strings.account.displayName this.displayname.value = account.displayName this.inputForms.push(this.displayname) this.redrawRunning = true this.redrawPaused = matchMedia("(prefers-reduced-motion: reduce)").matches this.redrawForce = true this.customdonRedrawBind = this.customdonRedraw.bind(this) this.start = new Date().getTime() this.frames = [ 0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,4 ,3 ,2 ,1 , 0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,4 ,3 ,2 ,1 , 0 ,0 ,0 ,0 ,1 ,2 ,3 ,4 ,5 ,6 ,6 ,5 ,7 ,8 ,9 ,10, 11,11,11,11,10,9 ,8 ,7 ,13,12,12,13,14,15,16,17 ] this.customdonCache = new CanvasCache() this.customdonCache.resize(723 * 2, 1858, 1) this.customdonCanvas = this.getElement("customdon-canvas") pageEvents.add(this.customdonCanvas, "click", this.customdonPause.bind(this)) this.customdonCtx = this.customdonCanvas.getContext("2d") this.customdonBodyFill = this.getElement("customdon-bodyfill") this.customdonBodyFill.value = account.don.body_fill var parent = this.customdonBodyFill.parentNode parent.insertBefore(document.createTextNode(strings.account.customdon.bodyFill), parent.firstChild) pageEvents.add(this.customdonBodyFill, ["change", "input"], this.customdonChange.bind(this)) this.customdonFaceFill = this.getElement("customdon-facefill") this.customdonFaceFill.value = account.don.face_fill var parent = this.customdonFaceFill.parentNode parent.insertBefore(document.createTextNode(strings.account.customdon.faceFill), parent.firstChild) pageEvents.add(this.customdonFaceFill, ["change", "input"], this.customdonChange.bind(this)) this.customdonResetBtn = this.getElement("customdon-reset") this.customdonResetBtn.value = strings.account.customdon.reset pageEvents.add(this.customdonResetBtn, ["click", "touchstart"], this.customdonReset.bind(this)) this.customdonChange() this.customdonRedraw() this.accountPassButton = this.getElement("accountpass-btn") this.setAltText(this.accountPassButton, strings.account.changePassword) pageEvents.add(this.accountPassButton, ["click", "touchstart"], event => { this.showDiv(event, "pass") }) this.accountPass = this.getElement("accountpass-form") for(var i = 0; i < this.accountPass.length; i++){ this.accountPass[i].placeholder = strings.account.currentNewRepeat[i] this.inputForms.push(this.accountPass[i]) } this.accountPassDiv = this.getElement("accountpass-div") this.accountDelButton = this.getElement("accountdel-btn") this.setAltText(this.accountDelButton, strings.account.deleteAccount) pageEvents.add(this.accountDelButton, ["click", "touchstart"], event => { this.showDiv(event, "del") }) this.accountDel = this.getElement("accountdel-form") this.accountDel.password.placeholder = strings.account.verifyPassword this.inputForms.push(this.accountDel.password) this.accountDelDiv = this.getElement("accountdel-div") this.linkPrivacy = this.getElement("privacy-btn") this.setAltText(this.linkPrivacy, strings.account.privacy) pageEvents.add(this.linkPrivacy, ["mousedown", "touchstart"], this.openPrivacy.bind(this)) this.items.push(this.linkPrivacy) this.logoutButton = this.getElement("logout-btn") this.setAltText(this.logoutButton, strings.account.logout) pageEvents.add(this.logoutButton, ["mousedown", "touchstart"], this.onLogout.bind(this)) this.items.push(this.logoutButton) this.endButton = this.getElement("view-end-button") this.setAltText(this.endButton, strings.account.cancel) pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) this.items.push(this.endButton) this.saveButton = this.getElement("save-btn") this.setAltText(this.saveButton, strings.account.save) pageEvents.add(this.saveButton, ["mousedown", "touchstart"], this.onSave.bind(this)) this.items.push(this.saveButton) for(var i = 0; i < this.inputForms.length; i++){ pageEvents.add(this.inputForms[i], ["keydown", "keyup", "keypress"], this.onFormPress.bind(this)) } } customdonPause(){ this.redrawPaused = !this.redrawPaused this.redrawForce = true this.start = new Date().getTime() } customdonChange(){ var ctx = this.customdonCtx this.customdonCache.clear() var w = 722 var h = 1858 this.customdonCache.set({ w: w, h: h, id: "bodyFill" }, ctx => { ctx.drawImage(assets.image["don_anim_normal_b1"], 0, 0) ctx.globalCompositeOperation = "source-atop" ctx.fillStyle = this.customdonBodyFill.value ctx.fillRect(0, 0, w, h) }) this.customdonCache.set({ w: w, h: h, id: "faceFill" }, ctx => { ctx.drawImage(assets.image["don_anim_normal_b2"], 0, 0) ctx.globalCompositeOperation = "source-atop" ctx.fillStyle = this.customdonFaceFill.value ctx.fillRect(0, 0, w, h) ctx.globalCompositeOperation = "source-over" this.customdonCache.get({ ctx: ctx, x: 0, y: 0, w: w, h: h, id: "bodyFill" }) }) this.redrawForce = true } customdonReset(event){ if(event.type === "touchstart"){ event.preventDefault() } this.customdonBodyFill.value = defaultDon.body_fill this.customdonFaceFill.value = defaultDon.face_fill this.customdonChange() } customdonRedraw(){ if(!this.redrawRunning){ return } requestAnimationFrame(this.customdonRedrawBind) if(!document.hasFocus() || this.redrawPaused && !this.redrawForce){ return } var ms = new Date().getTime() var ctx = this.customdonCtx if(this.redrawPaused){ var frame = 0 }else{ var frame = this.frames[Math.floor((ms - this.start) / 30) % this.frames.length] } var w = 360 var h = 184 var sx = Math.floor(frame / 10) * (w + 2) var sy = (frame % 10) * (h + 2) ctx.clearRect(0, 0, w, h) this.customdonCache.get({ ctx: ctx, sx: sx, sy: sy, sw: w, sh: h, x: -26, y: 0, w: w, h: h, id: "faceFill" }) ctx.drawImage(assets.image["don_anim_normal_a"], sx, sy, w, h, -26, 0, w, h ) this.redrawForce = false } showDiv(event, div){ if(event){ if(event.type === "touchstart"){ event.preventDefault() }else if(event.which !== 1){ return } } if(this.locked){ return } var otherDiv = this.shownDiv && this.shownDiv !== div var display = this.shownDiv === div ? "" : "block" this.shownDiv = display ? div : "" switch(div){ case "pass": if(otherDiv){ this.accountDelDiv.style.display = "" } this.accountPassDiv.style.display = display break case "del": if(otherDiv){ this.accountPassDiv.style.display = "" } this.accountDelDiv.style.display = display break } } loginForm(register, fromSwitch){ loader.changePage("login", true) this.mode = register ? "register" : "login" this.setAltText(this.getElement("view-title"), strings.account[this.mode]) this.errorDiv = this.getElement("error-div") this.items = [] this.form = this.getElement("login-form") this.getElement("username-hint").innerText = strings.account.username this.form.username.placeholder = strings.account.enterUsername this.getElement("password-hint").innerText = strings.account.password this.form.password.placeholder = strings.account.enterPassword this.password2 = this.getElement("password2-div") this.remember = this.getElement("remember-div") this.getElement("remember-label").appendChild(document.createTextNode(strings.account.remember)) this.loginButton = this.getElement("login-btn") this.registerButton = this.getElement("register-btn") if(register){ var pass2 = document.createElement("input") pass2.type = "password" pass2.name = "password2" pass2.required = true pass2.placeholder = strings.account.repeatPassword this.password2.appendChild(pass2) this.password2.style.display = "block" this.remember.style.display = "none" this.setAltText(this.loginButton, strings.account.registerAccount) this.setAltText(this.registerButton, strings.account.login) }else{ this.setAltText(this.loginButton, strings.account.login) this.setAltText(this.registerButton, strings.account.register) } pageEvents.add(this.form, "submit", this.onLogin.bind(this)) pageEvents.add(this.loginButton, ["mousedown", "touchstart"], this.onLogin.bind(this)) pageEvents.add(this.registerButton, ["mousedown", "touchstart"], this.onSwitchMode.bind(this)) this.items.push(this.registerButton) this.linkPrivacy = this.getElement("privacy-btn") this.setAltText(this.linkPrivacy, strings.account.privacy) pageEvents.add(this.linkPrivacy, ["mousedown", "touchstart"], this.openPrivacy.bind(this)) this.items.push(this.linkPrivacy) if(!register){ this.items.push(this.loginButton) } for(var i = 0; i < this.form.length; i++){ pageEvents.add(this.form[i], ["keydown", "keyup", "keypress"], this.onFormPress.bind(this)) } this.endButton = this.getElement("view-end-button") this.setAltText(this.endButton, strings.account.back) pageEvents.add(this.endButton, ["mousedown", "touchstart"], this.onEnd.bind(this)) this.items.push(this.endButton) if(fromSwitch){ this.selected = 0 this.endButton.classList.remove("selected") this.registerButton.classList.add("selected") } } getElement(name){ return loader.screen.getElementsByClassName(name)[0] } setAltText(element, text){ element.innerText = text element.setAttribute("alt", text) } keyPressed(pressed, name){ if(!pressed || this.locked){ return } var selected = this.items[this.selected] if(name === "confirm"){ if(selected === this.endButton){ this.onEnd() }else if(selected === this.registerButton){ this.onSwitchMode() }else if(selected === this.loginButton){ this.onLogin() }else if(selected === this.linkPrivacy){ assets.sounds["se_don"].play() this.openPrivacy() } }else if(name === "previous" || name === "next"){ selected.classList.remove("selected") this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1)) this.items[this.selected].classList.add("selected") if(this.items[this.selected] === this.linkPrivacy){ this.items[this.selected].scrollIntoView() } assets.sounds["se_ka"].play() }else if(name === "back"){ this.onEnd() } } mod(length, index){ return ((index % length) + length) % length } onFormPress(event){ event.stopPropagation() if(event.type === "keypress" && event.keyCode === 13){ event.preventDefault() if(this.mode === "account"){ this.onSave() }else{ this.onLogin() } } } onSwitchMode(event){ if(event){ if(event.type === "touchstart"){ event.preventDefault() }else if(event.which !== 1){ return } } if(this.locked){ return } this.clean(true) this.loginForm(this.mode === "login", true) } onLogin(event){ if(event){ if(event.type === "touchstart"){ event.preventDefault() }else if(event.which !== 1){ return } } if(this.locked){ return } var obj = { username: this.form.username.value, password: this.form.password.value } if(!obj.username || !obj.password){ this.error(strings.account.cannotBeEmpty.replace("%s", strings.account[!obj.username ? "username" : "password"])) return } if(this.mode === "login"){ obj.remember = this.form.remember.checked }else{ if(obj.password !== this.form.password2.value){ this.error(strings.account.passwordsDoNotMatch) return } } this.request(this.mode, obj).then(response => { account.loggedIn = true account.username = response.username account.displayName = response.display_name account.don = response.don var loadScores = scores => { scoreStorage.load(scores) this.onEnd(false, true, true) pageEvents.send("login", account.username) } if(this.mode === "login"){ this.request("scores/get", false, true).then(response => { loadScores(response.scores) }, () => { loadScores({}) }) }else{ scoreStorage.save().catch(() => {}).finally(() => { this.onEnd(false, true, true) pageEvents.send("login", account.username) }) } }, response => { if(response && response.status === "error" && response.message){ if(response.message in strings.serverError){ this.error(strings.serverError[response.message]) }else{ this.error(response.message) } }else{ this.error(strings.account.error) } }) } openPrivacy(event){ if(event){ if(event.type === "touchstart"){ event.preventDefault() }else if(event.which !== 1){ return } } if(this.locked){ return } open("privacy") } onLogout(){ if(event){ if(event.type === "touchstart"){ event.preventDefault() }else if(event.which !== 1){ return } } if(this.locked){ return } account.loggedIn = false delete account.username delete account.displayName delete account.don var loadScores = () => { scoreStorage.load() this.onEnd(false, true) pageEvents.send("logout") } this.request("logout").then(loadScores, loadScores) } onSave(event){ if(event){ if(event.type === "touchstart"){ event.preventDefault() }else if(event.which !== 1){ return } } if(this.locked){ return } this.clearError() var promises = [] var noNameChange = false if(this.shownDiv === "pass"){ var passwords = [] for(var i = 0; i < this.accountPass.length; i++){ passwords.push(this.accountPass[i].value) } if(passwords[1] === passwords[2]){ promises.push(this.request("account/password", { current_password: passwords[0], new_password: passwords[1] })) }else{ this.error(strings.account.newPasswordsDoNotMatch) return } } if(this.shownDiv === "del" && this.accountDel.password.value){ noNameChange = true promises.push(this.request("account/remove", { password: this.accountDel.password.value }).then(() => { account.loggedIn = false delete account.username delete account.displayName delete account.don scoreStorage.load() pageEvents.send("logout") return Promise.resolve })) } var newName = this.displayname.value.trim() if(!noNameChange && newName !== account.displayName){ promises.push(this.request("account/display_name", { display_name: newName }).then(response => { account.displayName = response.display_name })) } var bodyFill = this.customdonBodyFill.value var faceFill = this.customdonFaceFill.value if(!noNameChange && (bodyFill !== account.body_fill || this.customdonFaceFill.value !== account.face_fill)){ promises.push(this.request("account/don", { body_fill: bodyFill, face_fill: faceFill }).then(response => { account.don = response.don })) } var error = false var errorFunc = response => { if(error){ return } if(response && response.message){ if(response.message in strings.serverError){ this.error(strings.serverError[response.message]) }else{ this.error(response.message) } }else{ this.error(strings.account.error) } } Promise.all(promises).then(() => { this.onEnd(false, true) }, errorFunc).catch(errorFunc) } onEnd(event, noSound, noReset){ var touched = false if(event){ if(event.type === "touchstart"){ event.preventDefault() touched = true }else if(event.which !== 1){ return } } if(this.locked){ return } this.clean(false, noReset) assets.sounds["se_don"].play() setTimeout(() => { new SongSelect(false, false, touched) }, 500) } request(url, obj, get){ this.lock(true) var doRequest = token => { return new Promise((resolve, reject) => { var request = new XMLHttpRequest() request.open(get ? "GET" : "POST", "api/" + url) pageEvents.load(request).then(() => { this.lock(false) if(request.status !== 200){ reject() return } try{ var json = JSON.parse(request.response) }catch(e){ reject() return } if(json.status === "ok"){ resolve(json) }else{ reject(json) } }, () => { this.lock(false) reject() }) if(!get){ request.setRequestHeader("X-CSRFToken", token) } if(obj){ request.setRequestHeader("Content-Type", "application/json;charset=UTF-8") request.send(JSON.stringify(obj)) }else{ request.send() } }) } if(get){ return doRequest() }else{ return loader.getCsrfToken().then(doRequest) } } lock(isLocked){ this.locked = isLocked if(this.mode === "login" || this.mode === "register"){ for(var i = 0; i < this.form.length; i++){ this.form[i].disabled = isLocked } }else if(this.mode === "account"){ for(var i = 0; i < this.inputForms.length; i++){ this.inputForms[i].disabled = isLocked } } } error(text){ this.errorDiv.innerText = text this.errorDiv.style.display = "block" } clearError(){ this.errorDiv.innerText = "" this.errorDiv.style.display = "" } clean(eventsOnly, noReset){ if(!eventsOnly){ cancelTouch = true this.keyboard.clean() this.gamepad.clean() } if(this.mode === "account"){ if(!noReset){ this.accountPass.reset() this.accountDel.reset() } this.redrawRunning = false this.customdonCache.clean() pageEvents.remove(this.customdonCanvas, "click") pageEvents.remove(this.customdonBodyFill, ["change", "input"]) pageEvents.remove(this.customdonFaceFill, ["change", "input"]) pageEvents.remove(this.customdonResetBtn, ["click", "touchstart"]) pageEvents.remove(this.accounPassButton, ["click", "touchstart"]) pageEvents.remove(this.accountDelButton, ["click", "touchstart"]) pageEvents.remove(this.linkPrivacy, ["mousedown", "touchstart"]) pageEvents.remove(this.logoutButton, ["mousedown", "touchstart"]) pageEvents.remove(this.saveButton, ["mousedown", "touchstart"]) for(var i = 0; i < this.inputForms.length; i++){ pageEvents.remove(this.inputForms[i], ["keydown", "keyup", "keypress"]) } delete this.errorDiv delete this.displayname delete this.frames delete this.customdonCanvas delete this.customdonCtx delete this.customdonBodyFill delete this.customdonFaceFill delete this.customdonResetBtn delete this.accountPassButton delete this.accountPass delete this.accountPassDiv delete this.accountDelButton delete this.accountDel delete this.accountDelDiv delete this.linkPrivacy delete this.logoutButton delete this.saveButton delete this.inputForms }else if(this.mode === "login" || this.mode === "register"){ if(!eventsOnly && !noReset){ this.form.reset() } pageEvents.remove(this.form, "submit") pageEvents.remove(this.loginButton, ["mousedown", "touchstart"]) pageEvents.remove(this.registerButton, ["mousedown", "touchstart"]) for(var i = 0; i < this.form.length; i++){ pageEvents.remove(this.registerButton, ["keydown", "keyup", "keypress"]) } pageEvents.remove(this.linkPrivacy, ["mousedown", "touchstart"]) delete this.errorDiv delete this.form delete this.password2 delete this.remember delete this.loginButton delete this.registerButton delete this.linkPrivacy } pageEvents.remove(this.endButton, ["mousedown", "touchstart"]) delete this.endButton delete this.items } }