Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
T
Taiko Web
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nanahira
Taiko Web
Commits
2af924a9
Commit
2af924a9
authored
Mar 13, 2020
by
LoveEevee
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add everything for accounts
parent
47545e9d
Changes
21
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
1291 additions
and
146 deletions
+1291
-146
public/src/css/view.css
public/src/css/view.css
+77
-0
public/src/js/account.js
public/src/js/account.js
+482
-0
public/src/js/assets.js
public/src/js/assets.js
+6
-2
public/src/js/canvasdraw.js
public/src/js/canvasdraw.js
+99
-3
public/src/js/game.js
public/src/js/game.js
+3
-1
public/src/js/importsongs.js
public/src/js/importsongs.js
+31
-18
public/src/js/lib/js.cookie.min.js
public/src/js/lib/js.cookie.min.js
+2
-0
public/src/js/loader.js
public/src/js/loader.js
+81
-53
public/src/js/loadsong.js
public/src/js/loadsong.js
+2
-1
public/src/js/main.js
public/src/js/main.js
+1
-0
public/src/js/p2.js
public/src/js/p2.js
+5
-0
public/src/js/pageevents.js
public/src/js/pageevents.js
+3
-0
public/src/js/scoresheet.js
public/src/js/scoresheet.js
+28
-0
public/src/js/scorestorage.js
public/src/js/scorestorage.js
+65
-13
public/src/js/session.js
public/src/js/session.js
+4
-1
public/src/js/songselect.js
public/src/js/songselect.js
+163
-45
public/src/js/strings.js
public/src/js/strings.js
+109
-0
public/src/js/view.js
public/src/js/view.js
+55
-0
public/src/views/account.html
public/src/views/account.html
+33
-0
public/src/views/login.html
public/src/views/login.html
+24
-0
server.py
server.py
+18
-9
No files found.
public/src/css/view.css
View file @
2af924a9
...
...
@@ -291,3 +291,80 @@ kbd{
.left-buttons
.taibtn
{
z-index
:
1
;
}
.accountpass-form
,
.accountdel-form
,
.login-form
{
text-align
:
center
;
width
:
80%
;
margin
:
auto
;
}
.accountpass-form
.accountpass-div
,
.accountdel-form
.accountdel-div
,
.login-form
.password2-div
{
display
:
none
;
}
.account-view
.displayname
,
.accountpass-form
input
[
type
=
password
],
.accountdel-form
input
[
type
=
password
],
.login-form
input
[
type
=
text
],
.login-form
input
[
type
=
password
]
{
width
:
100%
;
font-size
:
1.4em
;
margin
:
0.1em
0
;
padding
:
0.3em
;
box-sizing
:
border-box
;
}
.accountpass-form
input
[
type
=
password
]
{
width
:
calc
(
100%
/
3
);
}
.accountpass-form
input
[
type
=
password
]
::placeholder
{
font-size
:
0.8em
;
}
.login-form
input
[
type
=
checkbox
]
{
transform
:
scale
(
1.4
);
}
.account-view
.displayname-hint
,
.login-form
.username-hint
,
.login-form
.password-hint
,
.login-form
.remember-label
{
display
:
block
;
font-size
:
1.1em
;
padding
:
0.5em
;
}
.login-form
.remember-label
{
padding
:
0.85em
;
}
.account-view
.save-btn
{
float
:
right
;
padding
:
0.4em
1.5em
;
font-weight
:
bold
;
border-color
:
#000
;
color
:
#000
;
z-index
:
1
;
}
.account-view
.view-end-button
{
margin-right
:
0.4em
;
font-weight
:
normal
;
border-color
:
#dacdb2
;
color
:
#555
;
}
.account-view
.save-btn
:hover
,
.account-view
.save-btn.selected
,
.account-view
.view-end-button
:hover
,
.account-view
.view-end-button.selected
{
color
:
#fff
;
border-color
:
#fff
;
}
.account-view
.displayname-div
{
width
:
80%
;
margin
:
0
auto
;
}
.accountpass-form
.accountpass-btn
,
.accountdel-form
.accountdel-btn
,
.login-form
.login-btn
{
z-index
:
1
;
}
.accountpass-form
,
.accountdel-form
{
margin
:
0.3em
auto
;
}
public/src/js/account.js
0 → 100644
View file @
2af924a9
class
Account
{
constructor
(
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
.
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
.
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
.
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
))
}
}
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
.
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
)
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
(
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
"
)
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
){
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
){
alert
(
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
){
alert
(
strings
.
account
.
passwordsDoNotMatch
)
return
}
}
this
.
request
(
this
.
mode
,
obj
).
then
(
response
=>
{
account
.
loggedIn
=
true
account
.
username
=
response
.
username
account
.
displayName
=
response
.
display_name
var
loadScores
=
scores
=>
{
scoreStorage
.
load
(
scores
)
this
.
onEnd
(
false
,
true
)
pageEvents
.
send
(
"
login
"
,
account
.
username
)
}
if
(
this
.
mode
===
"
login
"
){
this
.
request
(
"
scores/get
"
).
then
(
response
=>
{
loadScores
(
response
.
scores
)
},
()
=>
{
loadScores
({})
})
}
else
{
scoreStorage
.
save
().
catch
(()
=>
{}).
finally
(()
=>
{
this
.
onEnd
(
false
,
true
)
pageEvents
.
send
(
"
login
"
,
account
.
username
)
})
}
},
response
=>
{
if
(
response
&&
response
.
status
===
"
error
"
&&
response
.
message
){
alert
(
response
.
message
)
}
else
{
alert
(
strings
.
account
.
error
)
}
})
}
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
var
loadScores
=
scores
=>
{
Cookies
.
remove
(
"
token
"
)
scoreStorage
.
load
()
this
.
onEnd
(
false
,
true
)
pageEvents
.
send
(
"
logout
"
)
}
this
.
request
(
"
logout
"
).
then
(
response
=>
{
loadScores
()
},
()
=>
{
loadScores
()
})
}
onSave
(
event
){
if
(
event
){
if
(
event
.
type
===
"
touchstart
"
){
event
.
preventDefault
()
}
else
if
(
event
.
which
!==
1
){
return
}
}
if
(
this
.
locked
){
return
}
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
{
alert
(
strings
.
account
.
passwordsDoNotMatch
)
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
Cookies
.
remove
(
"
token
"
)
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
}))
}
var
error
=
false
var
errorFunc
=
response
=>
{
if
(
error
){
return
}
if
(
response
&&
response
.
message
){
alert
(
response
.
message
)
}
else
{
alert
(
strings
.
account
.
error
)
}
}
Promise
.
all
(
promises
).
then
(()
=>
{
this
.
onEnd
(
false
,
true
)
},
errorFunc
).
catch
(
errorFunc
)
}
onEnd
(
event
,
noSound
){
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
()
assets
.
sounds
[
"
se_don
"
].
play
()
setTimeout
(()
=>
{
new
SongSelect
(
false
,
false
,
touched
)
},
500
)
}
request
(
url
,
obj
){
this
.
lock
(
true
)
return
new
Promise
((
resolve
,
reject
)
=>
{
var
request
=
new
XMLHttpRequest
()
request
.
open
(
obj
?
"
POST
"
:
"
GET
"
,
"
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
(
obj
){
request
.
setRequestHeader
(
"
Content-Type
"
,
"
application/json;charset=UTF-8
"
)
request
.
send
(
JSON
.
stringify
(
obj
))
}
else
{
request
.
send
()
}
})
}
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
}
}
}
clean
(
eventsOnly
){
if
(
!
eventsOnly
){
cancelTouch
=
true
this
.
keyboard
.
clean
()
this
.
gamepad
.
clean
()
}
if
(
this
.
mode
===
"
account
"
){
pageEvents
.
remove
(
this
.
accounPassButton
,
[
"
click
"
,
"
touchstart
"
])
pageEvents
.
remove
(
this
.
accountDelButton
,
[
"
click
"
,
"
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
"
])
}
this
.
accountPass
.
reset
()
this
.
accountDel
.
reset
()
delete
this
.
displayname
delete
this
.
accountPassButton
delete
this
.
accountPass
delete
this
.
accountPassDiv
delete
this
.
accountDelButton
delete
this
.
accountDel
delete
this
.
accountDelDiv
delete
this
.
logoutButton
delete
this
.
saveButton
delete
this
.
inputForms
}
else
if
(
this
.
mode
===
"
login
"
||
this
.
mode
===
"
register
"
){
if
(
!
eventsOnly
){
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
"
])
}
delete
this
.
form
delete
this
.
password2
delete
this
.
remember
delete
this
.
loginButton
delete
this
.
registerButton
}
pageEvents
.
remove
(
this
.
endButton
,
[
"
mousedown
"
,
"
touchstart
"
])
delete
this
.
endButton
delete
this
.
items
}
}
public/src/js/assets.js
View file @
2af924a9
var
assets
=
{
"
js
"
:
[
"
lib/md5.min.js
"
,
"
lib/js.cookie.min.js
"
,
"
loadsong.js
"
,
"
parseosu.js
"
,
"
titlescreen.js
"
,
...
...
@@ -31,7 +32,8 @@ var assets = {
"
importsongs.js
"
,
"
logo.js
"
,
"
settings.js
"
,
"
scorestorage.js
"
"
scorestorage.js
"
,
"
account.js
"
],
"
css
"
:
[
"
main.css
"
,
...
...
@@ -137,7 +139,9 @@ var assets = {
"
about.html
"
,
"
debug.html
"
,
"
session.html
"
,
"
settings.html
"
"
settings.html
"
,
"
account.html
"
,
"
login.html
"
],
"
songs
"
:
[],
...
...
public/src/js/canvasdraw.js
View file @
2af924a9
...
...
@@ -706,12 +706,12 @@
})
}
else
if
(
r
.
smallHiragana
.
test
(
symbol
)){
// Small hiragana, small katakana
drawn
.
push
({
text
:
symbol
,
x
:
0
,
y
:
0
,
w
:
30
})
drawn
.
push
({
text
:
symbol
,
kana
:
true
,
x
:
0
,
y
:
0
,
w
:
30
})
}
else
if
(
r
.
hiragana
.
test
(
symbol
)){
// Hiragana, katakana
drawn
.
push
({
text
:
symbol
,
x
:
0
,
y
:
0
,
w
:
35
})
drawn
.
push
({
text
:
symbol
,
kana
:
true
,
x
:
0
,
y
:
0
,
w
:
35
})
}
else
{
drawn
.
push
({
text
:
symbol
,
x
:
0
,
y
:
0
,
w
:
39
})
drawn
.
push
({
text
:
symbol
,
kana
:
true
,
x
:
0
,
y
:
0
,
w
:
39
})
}
}
...
...
@@ -720,6 +720,9 @@
if
(
config
.
letterSpacing
){
symbol
.
w
+=
config
.
letterSpacing
}
if
(
config
.
kanaSpacing
&&
symbol
.
kana
){
symbol
.
w
+=
config
.
kanaSpacing
}
drawnWidth
+=
symbol
.
w
*
mul
}
...
...
@@ -1549,6 +1552,99 @@
ctx
.
restore
()
}
nameplate
(
config
){
var
ctx
=
config
.
ctx
var
w
=
264
var
h
=
57
var
r
=
h
/
2
var
pi
=
Math
.
PI
ctx
.
save
()
ctx
.
translate
(
config
.
x
,
config
.
y
)
if
(
config
.
scale
){
ctx
.
scale
(
config
.
scale
,
config
.
scale
)
}
ctx
.
fillStyle
=
"
rgba(0, 0, 0, 0.25)
"
ctx
.
beginPath
()
ctx
.
arc
(
r
+
4
,
r
+
5
,
r
,
pi
/
2
,
pi
/
-
2
)
ctx
.
arc
(
w
-
r
+
4
,
r
+
5
,
r
,
pi
/
-
2
,
pi
/
2
)
ctx
.
fill
()
ctx
.
beginPath
()
ctx
.
moveTo
(
r
,
0
)
this
.
roundedCorner
(
ctx
,
w
,
0
,
r
,
1
)
ctx
.
lineTo
(
r
,
r
)
ctx
.
fillStyle
=
config
.
blue
?
"
#67cecb
"
:
"
#ff421d
"
ctx
.
fill
()
ctx
.
beginPath
()
ctx
.
moveTo
(
r
,
r
)
this
.
roundedCorner
(
ctx
,
w
,
h
,
r
,
2
)
ctx
.
lineTo
(
r
,
h
)
ctx
.
fillStyle
=
"
rgba(255, 255, 255, 0.8)
"
ctx
.
fill
()
ctx
.
strokeStyle
=
"
#000
"
ctx
.
lineWidth
=
4
ctx
.
beginPath
()
ctx
.
moveTo
(
r
,
0
)
ctx
.
arc
(
w
-
r
,
r
,
r
,
pi
/
-
2
,
pi
/
2
)
ctx
.
lineTo
(
r
,
h
)
ctx
.
stroke
()
ctx
.
beginPath
()
ctx
.
moveTo
(
r
,
r
-
1
)
ctx
.
lineTo
(
w
,
r
-
1
)
ctx
.
lineWidth
=
2
ctx
.
stroke
()
ctx
.
beginPath
()
ctx
.
arc
(
r
,
r
,
r
,
0
,
pi
*
2
)
ctx
.
fillStyle
=
config
.
blue
?
"
#67cecb
"
:
"
#ff421d
"
ctx
.
fill
()
ctx
.
lineWidth
=
4
ctx
.
stroke
()
ctx
.
font
=
this
.
bold
(
config
.
font
)
+
"
28px
"
+
config
.
font
ctx
.
textAlign
=
"
center
"
ctx
.
textBaseline
=
"
middle
"
ctx
.
lineWidth
=
5
ctx
.
miterLimit
=
1
ctx
.
strokeStyle
=
"
#fff
"
ctx
.
fillStyle
=
"
#000
"
var
text
=
config
.
blue
?
"
2P
"
:
"
1P
"
ctx
.
strokeText
(
text
,
r
+
2
,
r
+
1
)
ctx
.
fillText
(
text
,
r
+
2
,
r
+
1
)
if
(
config
.
rank
){
this
.
layeredText
({
ctx
:
ctx
,
text
:
config
.
rank
,
fontSize
:
20
,
fontFamily
:
config
.
font
,
x
:
w
/
2
+
r
*
0.7
,
y
:
r
*
0.5
,
width
:
180
,
align
:
"
center
"
,
baseline
:
"
middle
"
},
[
{
fill
:
"
#000
"
}
])
}
this
.
layeredText
({
ctx
:
ctx
,
text
:
config
.
name
||
""
,
fontSize
:
21
,
fontFamily
:
config
.
font
,
x
:
w
/
2
+
r
*
0.7
,
y
:
r
*
1.5
-
0.5
,
width
:
180
,
kanaSpacing
:
10
,
align
:
"
center
"
,
baseline
:
"
middle
"
},
[
{
outline
:
"
#000
"
,
letterBorder
:
6
},
{
fill
:
"
#fff
"
}
])
ctx
.
restore
()
}
alpha
(
amount
,
ctx
,
callback
,
winW
,
winH
){
if
(
amount
>=
1
){
return
callback
(
ctx
)
...
...
public/src/js/game.js
View file @
2af924a9
...
...
@@ -505,7 +505,9 @@ class Game{
var
musicDuration
=
duration
*
1000
-
this
.
controller
.
offset
if
(
this
.
musicFadeOut
===
0
){
if
(
this
.
controller
.
multiplayer
===
1
){
p2
.
send
(
"
gameresults
"
,
this
.
getGlobalScore
())
var
obj
=
this
.
getGlobalScore
()
obj
.
name
=
account
.
loggedIn
?
account
.
displayName
:
strings
.
defaultName
p2
.
send
(
"
gameresults
"
,
obj
)
}
this
.
musicFadeOut
++
}
else
if
(
this
.
musicFadeOut
===
1
&&
ms
>=
started
+
1600
){
...
...
public/src/js/importsongs.js
View file @
2af924a9
...
...
@@ -202,12 +202,16 @@
var
tja
=
new
ParseTja
(
data
,
"
oni
"
,
0
,
0
,
true
)
var
songObj
=
{
id
:
index
+
1
,
order
:
index
+
1
,
type
:
"
tja
"
,
chart
:
file
,
stars
:
[]
,
stars
:
{}
,
music
:
"
muted
"
}
var
coursesAdded
=
false
var
titleLang
=
{}
var
titleLangAdded
=
false
var
subtitleLangAdded
=
false
var
subtitleLang
=
{}
var
dir
=
file
.
webkitRelativePath
.
toLowerCase
()
dir
=
dir
.
slice
(
0
,
dir
.
lastIndexOf
(
"
/
"
)
+
1
)
...
...
@@ -221,7 +225,11 @@
}
songObj
.
subtitle
=
subtitle
songObj
.
preview
=
meta
.
demostart
||
0
songObj
.
stars
[
this
.
courseTypes
[
diff
]]
=
(
meta
.
level
||
"
0
"
)
+
(
meta
.
branch
?
"
B
"
:
""
)
songObj
.
courses
[
diff
]
=
{
stars
:
meta
.
level
||
0
,
branch
:
!!
meta
.
branch
}
coursesAdded
=
true
if
(
meta
.
wave
){
songObj
.
music
=
this
.
otherFiles
[
dir
+
meta
.
wave
.
toLowerCase
()]
||
songObj
.
music
}
...
...
@@ -264,32 +272,27 @@
}
if
(
meta
[
"
title
"
+
id
]){
titleLang
[
id
]
=
meta
[
"
title
"
+
id
]
titleLangAdded
=
true
}
else
if
(
songTitle
in
this
.
songTitle
&&
this
.
songTitle
[
songTitle
][
id
]){
titleLang
[
id
]
=
this
.
songTitle
[
songTitle
][
id
]
+
ura
titleLangAdded
=
true
}
if
(
meta
[
"
subtitle
"
+
id
]){
subtitleLang
[
id
]
=
meta
[
"
subtitle
"
+
id
]
subtitleLangAdded
=
true
}
}
}
var
titleLangArray
=
[]
for
(
var
id
in
titleLang
){
titleLangArray
.
push
(
id
+
"
"
+
titleLang
[
id
])
}
if
(
titleLangArray
.
length
!==
0
){
songObj
.
title_lang
=
titleLangArray
.
join
(
"
\n
"
)
}
var
subtitleLangArray
=
[]
for
(
var
id
in
subtitleLang
){
subtitleLangArray
.
push
(
id
+
"
"
+
subtitleLang
[
id
])
if
(
titleLangAdded
){
songObj
.
title_lang
=
titleLang
}
if
(
subtitleLangA
rray
.
length
!==
0
){
songObj
.
subtitle_lang
=
subtitleLang
Array
.
join
(
"
\n
"
)
if
(
subtitleLangA
dded
){
songObj
.
subtitle_lang
=
subtitleLang
}
if
(
!
songObj
.
category
){
songObj
.
category
=
category
||
this
.
getCategory
(
file
,
[
songTitle
||
songObj
.
title
,
file
.
name
.
slice
(
0
,
file
.
name
.
lastIndexOf
(
"
.
"
))])
}
if
(
songObj
.
stars
.
length
!==
0
){
if
(
coursesAdded
){
this
.
songs
[
index
]
=
songObj
}
var
hash
=
md5
.
base64
(
event
.
target
.
result
).
slice
(
0
,
-
2
)
...
...
@@ -316,12 +319,20 @@
dir
=
dir
.
slice
(
0
,
dir
.
lastIndexOf
(
"
/
"
)
+
1
)
var
songObj
=
{
id
:
index
+
1
,
order
:
index
+
1
,
type
:
"
osu
"
,
chart
:
file
,
subtitle
:
osu
.
metadata
.
ArtistUnicode
||
osu
.
metadata
.
Artist
,
subtitle_lang
:
osu
.
metadata
.
Artist
||
osu
.
metadata
.
ArtistUnicode
,
subtitle_lang
:
{
en
:
osu
.
metadata
.
Artist
||
osu
.
metadata
.
ArtistUnicode
},
preview
:
osu
.
generalInfo
.
PreviewTime
/
1000
,
stars
:
[
null
,
null
,
null
,
parseInt
(
osu
.
difficulty
.
overallDifficulty
)
||
1
],
courses
:
{
oni
:{
stars
:
parseInt
(
osu
.
difficulty
.
overallDifficulty
)
||
0
,
branch
:
false
}
},
music
:
this
.
otherFiles
[
dir
+
osu
.
generalInfo
.
AudioFilename
.
toLowerCase
()]
||
"
muted
"
}
var
filename
=
file
.
name
.
slice
(
0
,
file
.
name
.
lastIndexOf
(
"
.
"
))
...
...
@@ -333,7 +344,9 @@
suffix
=
"
"
+
matches
[
0
]
}
songObj
.
title
=
title
+
suffix
songObj
.
title_lang
=
(
osu
.
metadata
.
Title
||
osu
.
metadata
.
TitleUnicode
)
+
suffix
songObj
.
title_lang
=
{
en
:
(
osu
.
metadata
.
Title
||
osu
.
metadata
.
TitleUnicode
)
+
suffix
}
}
else
{
songObj
.
title
=
filename
}
...
...
public/src/js/lib/js.cookie.min.js
0 → 100644
View file @
2af924a9
/*! js-cookie v3.0.0-rc.0 | MIT */
!
function
(
e
,
t
){
"
object
"
==
typeof
exports
&&
"
undefined
"
!=
typeof
module
?
module
.
exports
=
t
():
"
function
"
==
typeof
define
&&
define
.
amd
?
define
(
t
):(
e
=
e
||
self
,
function
(){
var
r
=
e
.
Cookies
,
n
=
e
.
Cookies
=
t
();
n
.
noConflict
=
function
(){
return
e
.
Cookies
=
r
,
n
}}())}(
this
,
function
(){
"
use strict
"
;
function
e
(
e
){
for
(
var
t
=
1
;
t
<
arguments
.
length
;
t
++
){
var
r
=
arguments
[
t
];
for
(
var
n
in
r
)
e
[
n
]
=
r
[
n
]}
return
e
}
var
t
=
{
read
:
function
(
e
){
return
e
.
replace
(
/%3B/g
,
"
;
"
)},
write
:
function
(
e
){
return
e
.
replace
(
/;/g
,
"
%3B
"
)}};
return
function
r
(
n
,
i
){
function
o
(
r
,
o
,
u
){
if
(
"
undefined
"
!=
typeof
document
){
"
number
"
==
typeof
(
u
=
e
({},
i
,
u
)).
expires
&&
(
u
.
expires
=
new
Date
(
Date
.
now
()
+
864
e5
*
u
.
expires
)),
u
.
expires
&&
(
u
.
expires
=
u
.
expires
.
toUTCString
()),
r
=
t
.
write
(
r
).
replace
(
/=/g
,
"
%3D
"
),
o
=
n
.
write
(
String
(
o
),
r
);
var
c
=
""
;
for
(
var
f
in
u
)
u
[
f
]
&&
(
c
+=
"
;
"
+
f
,
!
0
!==
u
[
f
]
&&
(
c
+=
"
=
"
+
u
[
f
].
split
(
"
;
"
)[
0
]));
return
document
.
cookie
=
r
+
"
=
"
+
o
+
c
}}
return
Object
.
create
({
set
:
o
,
get
:
function
(
e
){
if
(
"
undefined
"
!=
typeof
document
&&
(
!
arguments
.
length
||
e
)){
for
(
var
r
=
document
.
cookie
?
document
.
cookie
.
split
(
"
;
"
):[],
i
=
{},
o
=
0
;
o
<
r
.
length
;
o
++
){
var
u
=
r
[
o
].
split
(
"
=
"
),
c
=
u
.
slice
(
1
).
join
(
"
=
"
),
f
=
t
.
read
(
u
[
0
]).
replace
(
/%3D/g
,
"
=
"
);
if
(
i
[
f
]
=
n
.
read
(
c
,
f
),
e
===
f
)
break
}
return
e
?
i
[
e
]:
i
}},
remove
:
function
(
t
,
r
){
o
(
t
,
""
,
e
({},
r
,{
expires
:
-
1
}))},
withAttributes
:
function
(
t
){
return
r
(
this
.
converter
,
e
({},
this
.
attributes
,
t
))},
withConverter
:
function
(
t
){
return
r
(
e
({},
this
.
converter
,
t
),
this
.
attributes
)}},{
attributes
:{
value
:
Object
.
freeze
(
i
)},
converter
:{
value
:
Object
.
freeze
(
n
)}})}(
t
,{
path
:
"
/
"
})});
public/src/js/loader.js
View file @
2af924a9
...
...
@@ -104,11 +104,12 @@ class Loader{
}))
this
.
afterJSCount
=
[
"
blurPerformance
"
,
"
P2Connection
"
].
length
+
[
"
blurPerformance
"
].
length
+
assets
.
audioSfx
.
length
+
assets
.
audioMusic
.
length
+
assets
.
audioSfxLR
.
length
+
assets
.
audioSfxLoud
.
length
assets
.
audioSfxLoud
.
length
+
(
gameConfig
.
_accounts
?
1
:
0
)
Promise
.
all
(
this
.
promises
).
then
(()
=>
{
...
...
@@ -155,65 +156,92 @@ class Loader{
}
}))
var
readyEvent
=
"
normal
"
var
songId
var
hashLower
=
location
.
hash
.
toLowerCase
()
p2
=
new
P2Connection
()
if
(
hashLower
.
startsWith
(
"
#song=
"
)){
var
number
=
parseInt
(
location
.
hash
.
slice
(
6
))
if
(
number
>
0
){
songId
=
number
readyEvent
=
"
song-id
"
}
}
else
if
(
location
.
hash
.
length
===
6
){
p2
.
hashLock
=
true
this
.
addPromise
(
new
Promise
(
resolve
=>
{
p2
.
open
()
pageEvents
.
add
(
p2
,
"
message
"
,
response
=>
{
if
(
response
.
type
===
"
session
"
){
pageEvents
.
send
(
"
session-start
"
,
"
invited
"
)
readyEvent
=
"
session-start
"
resolve
()
}
else
if
(
response
.
type
===
"
gameend
"
){
p2
.
hash
(
""
)
p2
.
hashLock
=
false
readyEvent
=
"
session-expired
"
resolve
()
}
})
p2
.
send
(
"
invite
"
,
location
.
hash
.
slice
(
1
).
toLowerCase
())
setTimeout
(()
=>
{
if
(
p2
.
socket
.
readyState
!==
1
){
p2
.
hash
(
""
)
p2
.
hashLock
=
false
resolve
()
if
(
gameConfig
.
_accounts
){
var
token
=
Cookies
.
get
(
"
token
"
)
if
(
token
){
this
.
addPromise
(
this
.
ajax
(
"
/api/scores/get
"
).
then
(
response
=>
{
response
=
JSON
.
parse
(
response
)
if
(
response
.
status
===
"
ok
"
){
account
.
loggedIn
=
true
account
.
username
=
response
.
username
account
.
displayName
=
response
.
display_name
scoreStorage
.
load
(
response
.
scores
)
pageEvents
.
send
(
"
login
"
,
account
.
username
)
}
},
10000
)
}).
then
(()
=>
{
pageEvents
.
remove
(
p2
,
"
message
"
)
}))
}
else
{
p2
.
hash
(
""
)
}))
}
else
{
this
.
assetLoaded
()
}
}
settings
=
new
Settings
()
pageEvents
.
setKbd
()
scoreStorage
=
new
ScoreStorage
()
for
(
var
i
in
assets
.
songsDefault
){
var
song
=
assets
.
songsDefault
[
i
]
if
(
!
song
.
hash
){
song
.
hash
=
song
.
title
}
scoreStorage
.
songTitles
[
song
.
title
]
=
song
.
hash
var
score
=
scoreStorage
.
get
(
song
.
hash
,
false
,
true
)
if
(
score
){
score
.
title
=
song
.
title
}
}
Promise
.
all
(
this
.
promises
).
then
(()
=>
{
this
.
canvasTest
.
drawAllImages
().
then
(
result
=>
{
if
(
!
account
.
loggedIn
){
scoreStorage
.
load
()
}
for
(
var
i
in
assets
.
songsDefault
){
var
song
=
assets
.
songsDefault
[
i
]
if
(
!
song
.
hash
){
song
.
hash
=
song
.
title
}
scoreStorage
.
songTitles
[
song
.
title
]
=
song
.
hash
var
score
=
scoreStorage
.
get
(
song
.
hash
,
false
,
true
)
if
(
score
){
score
.
title
=
song
.
title
}
}
var
promises
=
[]
var
readyEvent
=
"
normal
"
var
songId
var
hashLower
=
location
.
hash
.
toLowerCase
()
p2
=
new
P2Connection
()
if
(
hashLower
.
startsWith
(
"
#song=
"
)){
var
number
=
parseInt
(
location
.
hash
.
slice
(
6
))
if
(
number
>
0
){
songId
=
number
readyEvent
=
"
song-id
"
}
}
else
if
(
location
.
hash
.
length
===
6
){
p2
.
hashLock
=
true
promises
.
push
(
new
Promise
(
resolve
=>
{
p2
.
open
()
pageEvents
.
add
(
p2
,
"
message
"
,
response
=>
{
if
(
response
.
type
===
"
session
"
){
pageEvents
.
send
(
"
session-start
"
,
"
invited
"
)
readyEvent
=
"
session-start
"
resolve
()
}
else
if
(
response
.
type
===
"
gameend
"
){
p2
.
hash
(
""
)
p2
.
hashLock
=
false
readyEvent
=
"
session-expired
"
resolve
()
}
})
p2
.
send
(
"
invite
"
,
{
id
:
location
.
hash
.
slice
(
1
).
toLowerCase
(),
name
:
account
.
loggedIn
?
account
.
displayName
:
null
})
setTimeout
(()
=>
{
if
(
p2
.
socket
.
readyState
!==
1
){
p2
.
hash
(
""
)
p2
.
hashLock
=
false
resolve
()
}
},
10000
)
}).
then
(()
=>
{
pageEvents
.
remove
(
p2
,
"
message
"
)
}))
}
else
{
p2
.
hash
(
""
)
}
promises
.
push
(
this
.
canvasTest
.
drawAllImages
())
Promise
.
all
(
promises
).
then
(
result
=>
{
perf
.
allImg
=
result
perf
.
load
=
Date
.
now
()
-
this
.
startTime
this
.
canvasTest
.
clean
()
...
...
public/src/js/loadsong.js
View file @
2af924a9
...
...
@@ -297,7 +297,8 @@ class LoadSong{
})
p2
.
send
(
"
join
"
,
{
id
:
song
.
folder
,
diff
:
song
.
difficulty
diff
:
song
.
difficulty
,
name
:
account
.
loggedIn
?
account
.
displayName
:
null
})
}
else
{
this
.
clean
()
...
...
public/src/js/main.js
View file @
2af924a9
...
...
@@ -84,6 +84,7 @@ var strings
var
vectors
var
settings
var
scoreStorage
var
account
=
{}
pageEvents
.
add
(
root
,
[
"
touchstart
"
,
"
touchmove
"
,
"
touchend
"
],
event
=>
{
if
(
event
.
cancelable
&&
cancelTouch
&&
event
.
target
.
tagName
!==
"
SELECT
"
){
...
...
public/src/js/p2.js
View file @
2af924a9
...
...
@@ -3,6 +3,7 @@ class P2Connection{
this
.
closed
=
true
this
.
lastMessages
=
{}
this
.
otherConnected
=
false
this
.
name
=
null
this
.
allEvents
=
new
Map
()
this
.
addEventListener
(
"
message
"
,
this
.
message
.
bind
(
this
))
this
.
currentHash
=
""
...
...
@@ -123,6 +124,7 @@ class P2Connection{
this
.
hash
(
""
)
this
.
hashLock
=
false
}
this
.
name
=
null
break
case
"
gameresults
"
:
this
.
results
=
{}
...
...
@@ -151,6 +153,9 @@ class P2Connection{
this
.
otherConnected
=
true
this
.
session
=
true
break
case
"
name
"
:
this
.
name
=
(
response
.
value
||
""
).
toString
()
||
null
break
}
}
onhashchange
(){
...
...
public/src/js/pageevents.js
View file @
2af924a9
...
...
@@ -86,6 +86,9 @@ class PageEvents{
})
}
keyEvent
(
event
){
if
(
!
(
"
key
"
in
event
)){
return
}
if
(
this
.
kbd
.
indexOf
(
event
.
key
.
toLowerCase
())
!==
-
1
){
this
.
lastKeyEvent
=
Date
.
now
()
event
.
preventDefault
()
...
...
public/src/js/scoresheet.js
View file @
2af924a9
...
...
@@ -39,6 +39,7 @@ class Scoresheet{
this
.
draw
=
new
CanvasDraw
(
noSmoothing
)
this
.
canvasCache
=
new
CanvasCache
(
noSmoothing
)
this
.
nameplateCache
=
new
CanvasCache
(
noSmoothing
)
this
.
keyboard
=
new
Keyboard
({
confirm
:
[
"
enter
"
,
"
space
"
,
"
esc
"
,
"
don_l
"
,
"
don_r
"
]
...
...
@@ -208,6 +209,7 @@ class Scoresheet{
this
.
canvas
.
style
.
height
=
(
winH
/
this
.
pixelRatio
)
+
"
px
"
this
.
canvasCache
.
resize
(
winW
/
ratio
,
80
+
1
,
ratio
)
this
.
nameplateCache
.
resize
(
274
,
134
,
ratio
+
0.2
)
if
(
!
this
.
multiplayer
){
this
.
tetsuoHana
.
style
.
setProperty
(
"
--scale
"
,
ratio
/
this
.
pixelRatio
)
...
...
@@ -233,6 +235,9 @@ class Scoresheet{
if
(
!
this
.
canvasCache
.
canvas
){
this
.
canvasCache
.
resize
(
winW
/
ratio
,
80
+
1
,
ratio
)
}
if
(
!
this
.
nameplateCache
.
canvas
){
this
.
nameplateCache
.
resize
(
274
,
67
,
ratio
+
0.2
)
}
}
this
.
winW
=
winW
this
.
winH
=
winH
...
...
@@ -450,6 +455,29 @@ class Scoresheet{
ctx
.
fillText
(
text
,
395
,
308
)
ctx
.
miterLimit
=
10
if
(
p
===
0
){
var
name
=
account
.
loggedIn
?
account
.
displayName
:
strings
.
defaultName
}
else
{
var
name
=
results
.
name
}
this
.
nameplateCache
.
get
({
ctx
:
ctx
,
x
:
259
,
y
:
92
,
w
:
273
,
h
:
66
,
id
:
p
.
toString
()
+
"
p
"
,
},
ctx
=>
{
this
.
draw
.
nameplate
({
ctx
:
ctx
,
x
:
3
,
y
:
3
,
name
:
name
,
font
:
this
.
font
,
blue
:
p
===
1
})
})
if
(
this
.
controller
.
autoPlayEnabled
){
ctx
.
drawImage
(
assets
.
image
[
"
badge_auto
"
],
431
,
311
,
34
,
34
...
...
public/src/js/scorestorage.js
View file @
2af924a9
...
...
@@ -5,17 +5,22 @@ class ScoreStorage{
this
.
difficulty
=
[
"
oni
"
,
"
ura
"
,
"
hard
"
,
"
normal
"
,
"
easy
"
]
this
.
scoreKeys
=
[
"
points
"
,
"
good
"
,
"
ok
"
,
"
bad
"
,
"
maxCombo
"
,
"
drumroll
"
]
this
.
crownValue
=
[
""
,
"
silver
"
,
"
gold
"
]
this
.
load
()
}
load
(){
load
(
strings
){
this
.
scores
=
{}
this
.
scoreStrings
=
{}
try
{
var
localScores
=
localStorage
.
getItem
(
"
scoreStorage
"
)
if
(
localScores
){
this
.
scoreStrings
=
JSON
.
parse
(
localScores
)
}
}
catch
(
e
){}
if
(
strings
){
this
.
scoreStrings
=
strings
}
else
if
(
account
.
loggedIn
){
return
}
else
{
this
.
scoreStrings
=
{}
try
{
var
localScores
=
localStorage
.
getItem
(
"
scoreStorage
"
)
if
(
localScores
){
this
.
scoreStrings
=
JSON
.
parse
(
localScores
)
}
}
catch
(
e
){}
}
for
(
var
hash
in
this
.
scoreStrings
){
var
scoreString
=
this
.
scoreStrings
[
hash
]
var
songAdded
=
false
...
...
@@ -46,16 +51,22 @@ class ScoreStorage{
}
}
}
save
(){
save
(
localOnly
){
for
(
var
hash
in
this
.
scores
){
this
.
writeString
(
hash
)
}
this
.
write
()
return
this
.
sendToServer
({
scores
:
this
.
scoreStrings
,
is_import
:
true
})
}
write
(){
try
{
localStorage
.
setItem
(
"
scoreStorage
"
,
JSON
.
stringify
(
this
.
scoreStrings
))
}
catch
(
e
){}
if
(
!
account
.
loggedIn
){
try
{
localStorage
.
setItem
(
"
scoreStorage
"
,
JSON
.
stringify
(
this
.
scoreStrings
))
}
catch
(
e
){}
}
}
writeString
(
hash
){
var
score
=
this
.
scores
[
hash
]
...
...
@@ -112,6 +123,11 @@ class ScoreStorage{
this
.
scores
[
hash
][
difficulty
]
=
scoreObject
this
.
writeString
(
hash
)
this
.
write
()
var
obj
=
{}
obj
[
hash
]
=
this
.
scoreStrings
[
hash
]
this
.
sendToServer
({
scores
:
obj
}).
catch
(()
=>
this
.
add
.
apply
(
this
,
arguments
))
}
template
(){
var
template
=
{
crown
:
""
}
...
...
@@ -146,6 +162,42 @@ class ScoreStorage{
delete
this
.
scoreStrings
[
hash
]
}
this
.
write
()
this
.
sendToServer
({
scores
:
this
.
scoreStrings
,
is_import
:
true
})
}
}
sendToServer
(
obj
,
retry
){
if
(
account
.
loggedIn
){
var
request
=
new
XMLHttpRequest
()
request
.
open
(
"
POST
"
,
"
api/scores/save
"
)
var
promise
=
pageEvents
.
load
(
request
).
then
(
response
=>
{
if
(
request
.
status
!==
200
){
return
Promise
.
reject
()
}
}).
catch
(()
=>
{
if
(
retry
){
account
.
loggedIn
=
false
delete
account
.
username
delete
account
.
displayName
Cookies
.
remove
(
"
token
"
)
this
.
load
()
pageEvents
.
send
(
"
logout
"
)
return
Promise
.
reject
()
}
else
{
return
new
Promise
(
resolve
=>
{
setTimeout
(()
=>
{
resolve
()
},
3000
)
}).
then
(()
=>
this
.
sendToServer
(
obj
,
true
))
}
})
request
.
setRequestHeader
(
"
Content-Type
"
,
"
application/json;charset=UTF-8
"
)
request
.
send
(
JSON
.
stringify
(
obj
))
return
promise
}
else
{
return
Promise
.
resolve
()
}
}
}
public/src/js/session.js
View file @
2af924a9
...
...
@@ -34,7 +34,10 @@ class Session{
pageEvents
.
send
(
"
session-start
"
,
"
host
"
)
}
})
p2
.
send
(
"
invite
"
)
p2
.
send
(
"
invite
"
,
{
id
:
null
,
name
:
account
.
loggedIn
?
account
.
displayName
:
null
})
pageEvents
.
send
(
"
session
"
)
}
getElement
(
name
){
...
...
public/src/js/songselect.js
View file @
2af924a9
...
...
@@ -116,7 +116,7 @@ class SongSelect{
originalTitle
:
song
.
title
,
subtitle
:
subtitle
,
skin
:
song
.
category
in
this
.
songSkin
?
this
.
songSkin
[
song
.
category
]
:
this
.
songSkin
.
default
,
stars
:
song
.
star
s
,
courses
:
song
.
course
s
,
category
:
song
.
category
,
preview
:
song
.
preview
||
0
,
type
:
song
.
type
,
...
...
@@ -126,14 +126,19 @@ class SongSelect{
volume
:
song
.
volume
,
maker
:
song
.
maker
,
canJump
:
true
,
hash
:
song
.
hash
||
song
.
title
hash
:
song
.
hash
||
song
.
title
,
order
:
song
.
order
})
}
this
.
songs
.
sort
((
a
,
b
)
=>
{
var
catA
=
a
.
category
in
this
.
songSkin
?
this
.
songSkin
[
a
.
category
]
:
this
.
songSkin
.
default
var
catB
=
b
.
category
in
this
.
songSkin
?
this
.
songSkin
[
b
.
category
]
:
this
.
songSkin
.
default
if
(
catA
.
sort
===
catB
.
sort
){
return
a
.
id
>
b
.
id
?
1
:
-
1
if
(
a
.
order
===
b
.
order
){
return
a
.
id
>
b
.
id
?
1
:
-
1
}
else
{
return
a
.
order
>
b
.
order
?
1
:
-
1
}
}
else
{
return
catA
.
sort
>
catB
.
sort
?
1
:
-
1
}
...
...
@@ -226,6 +231,7 @@ class SongSelect{
this
.
difficultyCache
=
new
CanvasCache
(
noSmoothing
)
this
.
sessionCache
=
new
CanvasCache
(
noSmoothing
)
this
.
currentSongCache
=
new
CanvasCache
(
noSmoothing
)
this
.
nameplateCache
=
new
CanvasCache
(
noSmoothing
)
this
.
difficulty
=
[
strings
.
easy
,
strings
.
normal
,
strings
.
hard
,
strings
.
oni
]
this
.
difficultyId
=
[
"
easy
"
,
"
normal
"
,
"
hard
"
,
"
oni
"
,
"
ura
"
]
...
...
@@ -450,7 +456,11 @@ class SongSelect{
if
(
this
.
state
.
screen
===
"
song
"
){
if
(
20
<
mouse
.
y
&&
mouse
.
y
<
90
&&
410
<
mouse
.
x
&&
mouse
.
x
<
880
&&
(
mouse
.
x
<
540
||
mouse
.
x
>
750
)){
this
.
categoryJump
(
mouse
.
x
<
640
?
-
1
:
1
)
}
else
if
(
mouse
.
x
>
641
&&
mouse
.
y
>
603
){
}
else
if
(
!
p2
.
session
&&
60
<
mouse
.
x
&&
mouse
.
x
<
332
&&
640
<
mouse
.
y
&&
mouse
.
y
<
706
&&
gameConfig
.
_accounts
){
this
.
toAccount
()
}
else
if
(
p2
.
session
&&
438
<
mouse
.
x
&&
mouse
.
x
<
834
&&
mouse
.
y
>
603
){
this
.
toSession
()
}
else
if
(
!
p2
.
session
&&
mouse
.
x
>
641
&&
mouse
.
y
>
603
&&
p2
.
socket
.
readyState
===
1
&&
!
assets
.
customSongs
){
this
.
toSession
()
}
else
{
var
moveBy
=
this
.
songSelMouse
(
mouse
.
x
,
mouse
.
y
)
...
...
@@ -501,11 +511,15 @@ class SongSelect{
if
(
this
.
state
.
screen
===
"
song
"
){
if
(
20
<
mouse
.
y
&&
mouse
.
y
<
90
&&
410
<
mouse
.
x
&&
mouse
.
x
<
880
&&
(
mouse
.
x
<
540
||
mouse
.
x
>
750
)){
moveTo
=
mouse
.
x
<
640
?
"
categoryPrev
"
:
"
categoryNext
"
}
else
if
(
mouse
.
x
>
641
&&
mouse
.
y
>
603
&&
p2
.
socket
.
readyState
===
1
&&
!
assets
.
customSongs
){
}
else
if
(
!
p2
.
session
&&
60
<
mouse
.
x
&&
mouse
.
x
<
332
&&
640
<
mouse
.
y
&&
mouse
.
y
<
706
&&
gameConfig
.
_accounts
){
moveTo
=
"
account
"
}
else
if
(
p2
.
session
&&
438
<
mouse
.
x
&&
mouse
.
x
<
834
&&
mouse
.
y
>
603
){
moveTo
=
"
session
"
}
else
if
(
!
p2
.
session
&&
mouse
.
x
>
641
&&
mouse
.
y
>
603
&&
p2
.
socket
.
readyState
===
1
&&
!
assets
.
customSongs
){
moveTo
=
"
session
"
}
else
{
var
moveTo
=
this
.
songSelMouse
(
mouse
.
x
,
mouse
.
y
)
if
(
moveTo
===
null
&&
this
.
state
.
moveHover
===
0
&&
!
this
.
songs
[
this
.
selectedSong
].
star
s
){
if
(
moveTo
===
null
&&
this
.
state
.
moveHover
===
0
&&
!
this
.
songs
[
this
.
selectedSong
].
course
s
){
this
.
state
.
moveMS
=
this
.
getMS
()
-
this
.
songSelecting
.
speed
}
}
...
...
@@ -544,7 +558,7 @@ class SongSelect{
var
dir
=
x
>
0
?
1
:
-
1
x
=
Math
.
abs
(
x
)
var
selectedWidth
=
this
.
songAsset
.
selectedWidth
if
(
!
this
.
songs
[
this
.
selectedSong
].
star
s
){
if
(
!
this
.
songs
[
this
.
selectedSong
].
course
s
){
selectedWidth
=
this
.
songAsset
.
width
}
var
moveBy
=
Math
.
ceil
((
x
-
selectedWidth
/
2
-
this
.
songAsset
.
marginLeft
/
2
)
/
(
this
.
songAsset
.
width
+
this
.
songAsset
.
marginLeft
))
*
dir
...
...
@@ -565,7 +579,13 @@ class SongSelect{
}
else
if
(
550
<
x
&&
x
<
1050
&&
95
<
y
&&
y
<
524
){
var
moveBy
=
Math
.
floor
((
x
-
550
)
/
((
1050
-
550
)
/
5
))
+
this
.
diffOptions
.
length
var
currentSong
=
this
.
songs
[
this
.
selectedSong
]
if
(
this
.
state
.
ura
&&
moveBy
===
this
.
diffOptions
.
length
+
3
||
currentSong
.
stars
[
moveBy
-
this
.
diffOptions
.
length
]){
if
(
this
.
state
.
ura
&&
moveBy
===
this
.
diffOptions
.
length
+
3
||
currentSong
.
courses
[
this
.
difficultyId
[
moveBy
-
this
.
diffOptions
.
length
]
]
){
return
moveBy
}
}
...
...
@@ -583,7 +603,7 @@ class SongSelect{
})
}
}
else
if
(
this
.
state
.
locked
!==
1
||
fromP2
){
if
(
this
.
songs
[
this
.
selectedSong
].
star
s
&&
(
this
.
state
.
locked
===
0
||
fromP2
)){
if
(
this
.
songs
[
this
.
selectedSong
].
course
s
&&
(
this
.
state
.
locked
===
0
||
fromP2
)){
this
.
state
.
moveMS
=
ms
}
else
{
this
.
state
.
moveMS
=
ms
-
this
.
songSelecting
.
speed
*
this
.
songSelecting
.
resize
...
...
@@ -645,7 +665,7 @@ class SongSelect{
toSelectDifficulty
(
fromP2
){
var
currentSong
=
this
.
songs
[
this
.
selectedSong
]
if
(
p2
.
session
&&
!
fromP2
&&
currentSong
.
action
!==
"
random
"
){
if
(
this
.
songs
[
this
.
selectedSong
].
star
s
){
if
(
this
.
songs
[
this
.
selectedSong
].
course
s
){
if
(
!
this
.
state
.
selLock
){
this
.
state
.
selLock
=
true
p2
.
send
(
"
songsel
"
,
{
...
...
@@ -655,7 +675,7 @@ class SongSelect{
}
}
}
else
if
(
this
.
state
.
locked
===
0
||
fromP2
){
if
(
currentSong
.
star
s
){
if
(
currentSong
.
course
s
){
this
.
state
.
screen
=
"
difficulty
"
this
.
state
.
screenMS
=
this
.
getMS
()
this
.
state
.
locked
=
true
...
...
@@ -677,7 +697,7 @@ class SongSelect{
this
.
state
.
locked
=
true
do
{
var
i
=
Math
.
floor
(
Math
.
random
()
*
this
.
songs
.
length
)
}
while
(
!
this
.
songs
[
i
].
star
s
)
}
while
(
!
this
.
songs
[
i
].
course
s
)
var
moveBy
=
i
-
this
.
selectedSong
setTimeout
(()
=>
{
this
.
moveToSong
(
moveBy
)
...
...
@@ -744,17 +764,18 @@ class SongSelect{
}
else
if
(
p2
.
socket
.
readyState
===
1
&&
!
assets
.
customSongs
){
multiplayer
=
ctrl
}
var
diff
=
this
.
difficultyId
[
difficulty
]
new
LoadSong
({
"
title
"
:
selectedSong
.
title
,
"
originalTitle
"
:
selectedSong
.
originalTitle
,
"
folder
"
:
selectedSong
.
id
,
"
difficulty
"
:
this
.
difficultyId
[
difficulty
]
,
"
difficulty
"
:
diff
,
"
category
"
:
selectedSong
.
category
,
"
type
"
:
selectedSong
.
type
,
"
offset
"
:
selectedSong
.
offset
,
"
songSkin
"
:
selectedSong
.
songSkin
,
"
stars
"
:
selectedSong
.
stars
[
difficulty
]
,
"
stars
"
:
selectedSong
.
courses
[
diff
].
stars
,
"
hash
"
:
selectedSong
.
hash
},
autoplay
,
multiplayer
,
touch
)
}
...
...
@@ -797,6 +818,13 @@ class SongSelect{
new
SettingsView
(
this
.
touchEnabled
)
},
500
)
}
toAccount
(){
this
.
playSound
(
"
se_don
"
)
this
.
clean
()
setTimeout
(()
=>
{
new
Account
(
this
.
touchEnabled
)
},
500
)
}
toSession
(){
if
(
p2
.
socket
.
readyState
!==
1
||
assets
.
customSongs
){
return
...
...
@@ -893,6 +921,8 @@ class SongSelect{
var
textW
=
strings
.
id
===
"
en
"
?
350
:
280
this
.
selectTextCache
.
resize
((
textW
+
53
+
60
+
1
)
*
2
,
this
.
songAsset
.
marginTop
+
15
,
ratio
+
0.5
)
this
.
nameplateCache
.
resize
(
274
,
134
,
ratio
+
0.2
)
var
categories
=
0
var
lastCategory
this
.
songs
.
forEach
(
song
=>
{
...
...
@@ -921,7 +951,7 @@ class SongSelect{
fontFamily
:
this
.
font
,
x
:
w
/
2
,
y
:
38
/
2
,
width
:
w
-
30
,
width
:
id
===
"
sessionend
"
?
385
:
w
-
30
,
align
:
"
center
"
,
baseline
:
"
middle
"
},
[
...
...
@@ -969,7 +999,7 @@ class SongSelect{
}
if
(
screen
===
"
song
"
){
if
(
this
.
songs
[
this
.
selectedSong
].
star
s
){
if
(
this
.
songs
[
this
.
selectedSong
].
course
s
){
selectedWidth
=
this
.
songAsset
.
selectedWidth
}
...
...
@@ -1054,7 +1084,7 @@ class SongSelect{
if
(
elapsed
<
resize
){
selectedWidth
=
this
.
songAsset
.
width
+
(((
resize
-
elapsed
)
/
resize
)
*
(
selectedWidth
-
this
.
songAsset
.
width
))
}
else
if
(
elapsed
>
resize2
){
this
.
playBgm
(
!
this
.
songs
[
this
.
selectedSong
].
star
s
)
this
.
playBgm
(
!
this
.
songs
[
this
.
selectedSong
].
course
s
)
this
.
state
.
locked
=
1
selectedWidth
=
this
.
songAsset
.
width
+
((
elapsed
-
resize2
)
/
resize
*
(
selectedWidth
-
this
.
songAsset
.
width
))
}
else
{
...
...
@@ -1062,7 +1092,7 @@ class SongSelect{
selectedWidth
=
this
.
songAsset
.
width
}
}
else
{
this
.
playBgm
(
!
this
.
songs
[
this
.
selectedSong
].
star
s
)
this
.
playBgm
(
!
this
.
songs
[
this
.
selectedSong
].
course
s
)
this
.
state
.
locked
=
0
}
}
else
if
(
screen
===
"
difficulty
"
){
...
...
@@ -1071,7 +1101,7 @@ class SongSelect{
this
.
state
.
locked
=
0
}
if
(
this
.
state
.
move
){
var
hasUra
=
currentSong
.
stars
[
4
]
var
hasUra
=
currentSong
.
courses
.
ura
var
previousSelection
=
this
.
selectedDiff
do
{
if
(
hasUra
&&
this
.
state
.
move
>
0
){
...
...
@@ -1089,12 +1119,12 @@ class SongSelect{
this
.
selectedDiff
=
this
.
mod
(
this
.
diffOptions
.
length
+
5
,
this
.
selectedDiff
+
this
.
state
.
move
)
}
}
while
(
this
.
selectedDiff
>=
this
.
diffOptions
.
length
&&
!
currentSong
.
stars
[
this
.
selectedDiff
-
this
.
diffOptions
.
length
]
this
.
selectedDiff
>=
this
.
diffOptions
.
length
&&
!
currentSong
.
courses
[
this
.
difficultyId
[
this
.
selectedDiff
-
this
.
diffOptions
.
length
]
]
||
this
.
selectedDiff
===
this
.
diffOptions
.
length
+
3
&&
this
.
state
.
ura
||
this
.
selectedDiff
===
this
.
diffOptions
.
length
+
4
&&
!
this
.
state
.
ura
)
this
.
state
.
move
=
0
}
else
if
(
this
.
selectedDiff
<
0
||
this
.
selectedDiff
>=
this
.
diffOptions
.
length
&&
!
currentSong
.
stars
[
this
.
selectedDiff
-
this
.
diffOptions
.
length
]){
}
else
if
(
this
.
selectedDiff
<
0
||
this
.
selectedDiff
>=
this
.
diffOptions
.
length
&&
!
currentSong
.
courses
[
this
.
difficultyId
[
this
.
selectedDiff
-
this
.
diffOptions
.
length
]
]){
this
.
selectedDiff
=
0
}
}
...
...
@@ -1164,7 +1194,7 @@ class SongSelect{
var
currentSong
=
this
.
songs
[
this
.
selectedSong
]
var
highlight
=
0
if
(
!
currentSong
.
star
s
){
if
(
!
currentSong
.
course
s
){
highlight
=
2
}
if
(
this
.
state
.
moveHover
===
0
){
...
...
@@ -1418,7 +1448,7 @@ class SongSelect{
}
}
var
drawDifficulty
=
(
ctx
,
i
,
currentUra
)
=>
{
if
(
currentSong
.
stars
[
i
]
||
currentUra
){
if
(
currentSong
.
courses
[
this
.
difficultyId
[
i
]
]
||
currentUra
){
var
score
=
scoreStorage
.
get
(
currentSong
.
hash
,
false
,
true
)
var
crownDiff
=
currentUra
?
"
ura
"
:
this
.
difficultyId
[
i
]
var
crownType
=
""
...
...
@@ -1502,9 +1532,9 @@ class SongSelect{
outlineSize
:
currentUra
?
this
.
songAsset
.
letterBorder
:
0
})
})
var
songStars
Array
=
(
currentUra
?
currentSong
.
stars
[
4
]
:
currentSong
.
stars
[
i
]).
toString
().
split
(
"
"
)
var
songStars
=
songStars
Array
[
0
]
var
songBranch
=
songStars
Array
[
1
]
===
"
B
"
var
songStars
Obj
=
(
currentUra
?
currentSong
.
courses
.
ura
:
currentSong
.
courses
[
this
.
difficultyId
[
i
]]
)
var
songStars
=
songStars
Obj
.
stars
var
songBranch
=
songStars
Obj
.
branch
var
elapsedMS
=
this
.
state
.
screenMS
>
this
.
state
.
moveMS
||
!
songSel
?
this
.
state
.
screenMS
:
this
.
state
.
moveMS
var
fade
=
((
ms
-
elapsedMS
)
%
2000
)
/
2000
if
(
songBranch
&&
fade
>
0.25
&&
fade
<
0.75
){
...
...
@@ -1591,8 +1621,8 @@ class SongSelect{
}
}
}
for
(
var
i
=
0
;
currentSong
.
star
s
&&
i
<
4
;
i
++
){
var
currentUra
=
i
===
3
&&
(
this
.
state
.
ura
&&
!
songSel
||
currentSong
.
stars
[
4
]
&&
songSel
)
for
(
var
i
=
0
;
currentSong
.
course
s
&&
i
<
4
;
i
++
){
var
currentUra
=
i
===
3
&&
(
this
.
state
.
ura
&&
!
songSel
||
currentSong
.
courses
.
ura
&&
songSel
)
if
(
songSel
&&
currentUra
){
drawDifficulty
(
ctx
,
i
,
false
)
var
elapsedMS
=
this
.
state
.
screenMS
>
this
.
state
.
moveMS
?
this
.
state
.
screenMS
:
this
.
state
.
moveMS
...
...
@@ -1753,7 +1783,7 @@ class SongSelect{
}
}
if
(
!
songSel
&&
currentSong
.
stars
[
4
]
){
if
(
!
songSel
&&
currentSong
.
courses
.
ura
){
var
fade
=
((
ms
-
this
.
state
.
screenMS
)
%
1200
)
/
1200
var
_x
=
x
+
402
+
4
*
100
+
fade
*
25
var
_y
=
y
+
258
...
...
@@ -1842,7 +1872,7 @@ class SongSelect{
ctx
.
fillRect
(
0
,
frameTop
+
595
,
1280
+
frameLeft
*
2
,
125
+
frameTop
)
var
x
=
0
var
y
=
frameTop
+
603
var
w
=
frameLeft
+
638
var
w
=
p2
.
session
?
frameLeft
+
638
-
200
:
frameLeft
+
638
var
h
=
117
+
frameTop
this
.
draw
.
pattern
({
ctx
:
ctx
,
...
...
@@ -1869,7 +1899,81 @@ class SongSelect{
ctx
.
lineTo
(
x
+
w
-
4
,
y
+
h
)
ctx
.
lineTo
(
x
+
w
-
4
,
y
+
4
)
ctx
.
fill
()
x
=
frameLeft
+
642
this
.
nameplateCache
.
get
({
ctx
:
ctx
,
x
:
frameLeft
+
60
,
y
:
frameTop
+
640
,
w
:
273
,
h
:
66
,
id
:
"
1p
"
,
},
ctx
=>
{
this
.
draw
.
nameplate
({
ctx
:
ctx
,
x
:
3
,
y
:
3
,
name
:
account
.
loggedIn
?
account
.
displayName
:
strings
.
defaultName
,
rank
:
account
.
loggedIn
||
!
gameConfig
.
_accounts
||
p2
.
session
?
false
:
strings
.
notLoggedIn
,
font
:
this
.
font
})
})
if
(
this
.
state
.
moveHover
===
"
account
"
){
this
.
draw
.
highlight
({
ctx
:
ctx
,
x
:
frameLeft
+
59.5
,
y
:
frameTop
+
639.5
,
w
:
271
,
h
:
64
,
radius
:
28.5
,
opacity
:
0.8
,
size
:
10
})
}
if
(
p2
.
session
){
x
=
x
+
w
+
4
w
=
396
this
.
draw
.
pattern
({
ctx
:
ctx
,
img
:
assets
.
image
[
"
bg_settings
"
],
x
:
x
,
y
:
y
,
w
:
w
,
h
:
h
,
dx
:
frameLeft
+
11
,
dy
:
frameTop
+
45
,
scale
:
3.1
})
ctx
.
fillStyle
=
"
rgba(255, 255, 255, 0.5)
"
ctx
.
beginPath
()
ctx
.
moveTo
(
x
,
y
+
h
)
ctx
.
lineTo
(
x
,
y
)
ctx
.
lineTo
(
x
+
w
,
y
)
ctx
.
lineTo
(
x
+
w
,
y
+
4
)
ctx
.
lineTo
(
x
+
4
,
y
+
4
)
ctx
.
lineTo
(
x
+
4
,
y
+
h
)
ctx
.
fill
()
ctx
.
fillStyle
=
"
rgba(0, 0, 0, 0.25)
"
ctx
.
beginPath
()
ctx
.
moveTo
(
x
+
w
,
y
)
ctx
.
lineTo
(
x
+
w
,
y
+
h
)
ctx
.
lineTo
(
x
+
w
-
4
,
y
+
h
)
ctx
.
lineTo
(
x
+
w
-
4
,
y
+
4
)
ctx
.
fill
()
if
(
this
.
state
.
moveHover
===
"
session
"
){
this
.
draw
.
highlight
({
ctx
:
ctx
,
x
:
x
,
y
:
y
,
w
:
w
,
h
:
h
,
opacity
:
0.8
})
}
}
x
=
p2
.
session
?
frameLeft
+
642
+
200
:
frameLeft
+
642
w
=
p2
.
session
?
frameLeft
+
638
-
200
:
frameLeft
+
638
if
(
p2
.
session
){
this
.
draw
.
pattern
({
ctx
:
ctx
,
...
...
@@ -1925,7 +2029,7 @@ class SongSelect{
}
this
.
sessionCache
.
get
({
ctx
:
ctx
,
x
:
winW
/
2
,
x
:
p2
.
session
?
winW
/
4
:
winW
/
2
,
y
:
y
+
(
h
-
32
)
/
2
,
w
:
winW
/
2
,
h
:
38
,
...
...
@@ -1933,7 +2037,7 @@ class SongSelect{
})
ctx
.
globalAlpha
=
1
}
if
(
this
.
state
.
moveHover
===
"
session
"
){
if
(
!
p2
.
session
&&
this
.
state
.
moveHover
===
"
session
"
){
this
.
draw
.
highlight
({
ctx
:
ctx
,
x
:
x
,
...
...
@@ -1944,6 +2048,25 @@ class SongSelect{
})
}
}
if
(
p2
.
session
){
this
.
nameplateCache
.
get
({
ctx
:
ctx
,
x
:
frameLeft
+
949
,
y
:
frameTop
+
640
,
w
:
273
,
h
:
66
,
id
:
"
2p
"
,
},
ctx
=>
{
this
.
draw
.
nameplate
({
ctx
:
ctx
,
x
:
3
,
y
:
3
,
name
:
p2
.
name
,
font
:
this
.
font
,
blue
:
true
})
})
}
if
(
screen
===
"
titleFadeIn
"
){
ctx
.
save
()
...
...
@@ -2019,7 +2142,7 @@ class SongSelect{
if
(
!
score
){
break
}
if
(
config
.
song
.
stars
[
i
]
&&
score
[
diff
]
&&
score
[
diff
].
crown
){
if
(
config
.
song
.
courses
[
this
.
difficultyId
[
i
]
]
&&
score
[
diff
]
&&
score
[
diff
].
crown
){
this
.
draw
.
crown
({
ctx
:
ctx
,
type
:
score
[
diff
].
crown
,
...
...
@@ -2148,7 +2271,7 @@ class SongSelect{
})
if
(
currentSong
){
currentSong
.
p2Cursor
=
diffId
if
(
p2
.
session
&&
currentSong
.
star
s
){
if
(
p2
.
session
&&
currentSong
.
course
s
){
this
.
selectedSong
=
index
this
.
state
.
move
=
0
if
(
this
.
state
.
screen
!==
"
difficulty
"
){
...
...
@@ -2192,7 +2315,7 @@ class SongSelect{
}
this
.
moveToSong
(
moveBy
,
true
)
}
}
else
if
(
this
.
songs
[
song
].
star
s
){
}
else
if
(
this
.
songs
[
song
].
course
s
){
this
.
selectedSong
=
song
this
.
state
.
move
=
0
if
(
this
.
state
.
screen
!==
"
difficulty
"
){
...
...
@@ -2238,16 +2361,11 @@ class SongSelect{
getLocalTitle
(
title
,
titleLang
){
if
(
titleLang
){
titleLang
=
titleLang
.
split
(
"
\n
"
)
titleLang
.
forEach
(
line
=>
{
var
space
=
line
.
indexOf
(
"
"
)
var
id
=
line
.
slice
(
0
,
space
)
if
(
id
===
strings
.
id
){
title
=
line
.
slice
(
space
+
1
)
}
else
if
(
titleLang
.
length
===
1
&&
strings
.
id
===
"
en
"
&&
!
(
id
in
allStrings
)){
title
=
line
for
(
var
id
in
titleLang
){
if
(
id
===
strings
.
id
&&
titleLang
[
id
]){
return
titleLang
[
id
]
}
}
)
}
}
return
title
}
...
...
public/src/js/strings.js
View file @
2af924a9
...
...
@@ -36,6 +36,8 @@
this
.
hard
=
"
むずかしい
"
this
.
oni
=
"
おに
"
this
.
songBranch
=
"
譜面分岐あり
"
this
.
defaultName
=
"
どんちゃん
"
this
.
notLoggedIn
=
"
ログインしていない
"
this
.
sessionStart
=
"
オンラインセッションを開始する!
"
this
.
sessionEnd
=
"
オンラインセッションを終了する
"
this
.
loading
=
"
ロード中...
"
...
...
@@ -184,6 +186,24 @@
content
:
"
Audio latency: %s
\n
Video latency: %s
\n\n
You can configure these latency values in the settings.
"
}
}
this
.
account
=
{
username
:
"
ユーザー名
"
,
enterUsername
:
"
ユーザー名を入力
"
,
password
:
"
パスワード
"
,
enterPassword
:
"
パスワードを入力
"
,
repeatPassword
:
"
パスワードを再入力
"
,
remember
:
"
ログイン状態を保持する
"
,
login
:
"
ログイン
"
,
register
:
"
登録
"
,
registerAccount
:
"
アカウントを登録
"
,
passwordsDoNotMatch
:
"
パスワードが一致しません
"
,
cannotBeEmpty
:
"
%sは空にできません
"
,
error
:
"
リクエストの処理中にエラーが発生しました
"
,
logout
:
"
ログアウト
"
,
back
:
"
もどる
"
,
cancel
:
"
Cancel
"
,
save
:
"
Save
"
}
this
.
browserSupport
=
{
browserWarning
:
"
サポートされていないブラウザを実行しています (%s)
"
,
details
:
"
詳しく
"
,
...
...
@@ -233,6 +253,8 @@ function StringsEn(){
this
.
hard
=
"
Hard
"
this
.
oni
=
"
Extreme
"
this
.
songBranch
=
"
Diverge Notes
"
this
.
defaultName
=
"
Don-chan
"
this
.
notLoggedIn
=
"
Not logged in
"
this
.
sessionStart
=
"
Begin an Online Session!
"
this
.
sessionEnd
=
"
End Online Session
"
this
.
loading
=
"
Loading...
"
...
...
@@ -381,6 +403,33 @@ function StringsEn(){
content
:
"
Audio latency: %s
\n
Video latency: %s
\n\n
You can configure these latency values in the settings.
"
}
}
this
.
account
=
{
username
:
"
Username
"
,
enterUsername
:
"
Enter Username
"
,
password
:
"
Password
"
,
enterPassword
:
"
Enter Password
"
,
repeatPassword
:
"
Repeat Password
"
,
remember
:
"
Remember me
"
,
login
:
"
Log In
"
,
register
:
"
Register
"
,
registerAccount
:
"
Register account
"
,
passwordsDoNotMatch
:
"
Passwords do not match
"
,
cannotBeEmpty
:
"
%s cannot be empty
"
,
error
:
"
An error occurred while processing your request
"
,
logout
:
"
Log Out
"
,
back
:
"
Back
"
,
cancel
:
"
Cancel
"
,
save
:
"
Save
"
,
displayName
:
"
Displayed Name
"
,
changePassword
:
"
Change Password
"
,
currentNewRepeat
:
[
"
Current Password
"
,
"
New Password
"
,
"
Repeat New Password
"
],
deleteAccount
:
"
Delete Account
"
,
verifyPassword
:
"
Verify password to delete this account
"
}
this
.
browserSupport
=
{
browserWarning
:
"
You are running an unsupported browser (%s)
"
,
details
:
"
Details...
"
,
...
...
@@ -430,6 +479,8 @@ function StringsCn(){
this
.
hard
=
"
困难
"
this
.
oni
=
"
魔王
"
this
.
songBranch
=
"
有谱面分歧
"
this
.
defaultName
=
"
小咚
"
this
.
notLoggedIn
=
"
未登录
"
this
.
sessionStart
=
"
开始在线会话!
"
this
.
sessionEnd
=
"
结束在线会话
"
this
.
loading
=
"
加载中...
"
...
...
@@ -578,6 +629,24 @@ function StringsCn(){
content
:
"
Audio latency: %s
\n
Video latency: %s
\n\n
You can configure these latency values in the settings.
"
}
}
this
.
account
=
{
username
:
"
登录名
"
,
enterUsername
:
"
输入用户名
"
,
password
:
"
密码
"
,
enterPassword
:
"
输入密码
"
,
repeatPassword
:
"
重新输入密码
"
,
remember
:
"
记住登录
"
,
login
:
"
登录
"
,
register
:
"
注册
"
,
registerAccount
:
"
注册帐号
"
,
passwordsDoNotMatch
:
"
密码不匹配
"
,
cannotBeEmpty
:
"
%s不能为空
"
,
error
:
"
处理您的请求时发生错误
"
,
logout
:
"
登出
"
,
back
:
"
返回
"
,
cancel
:
"
Cancel
"
,
save
:
"
Save
"
}
this
.
browserSupport
=
{
browserWarning
:
"
You are running an unsupported browser (%s)
"
,
details
:
"
Details...
"
,
...
...
@@ -627,6 +696,8 @@ function StringsTw(){
this
.
hard
=
"
困難
"
this
.
oni
=
"
魔王
"
this
.
songBranch
=
"
有譜面分歧
"
this
.
defaultName
=
"
小咚
"
this
.
notLoggedIn
=
"
未登錄
"
this
.
sessionStart
=
"
開始多人模式!
"
this
.
sessionEnd
=
"
結束多人模式
"
this
.
loading
=
"
讀取中...
"
...
...
@@ -775,6 +846,24 @@ function StringsTw(){
content
:
"
Audio latency: %s
\n
Video latency: %s
\n\n
You can configure these latency values in the settings.
"
}
}
this
.
account
=
{
username
:
"
使用者名稱
"
,
enterUsername
:
"
輸入用戶名
"
,
password
:
"
密碼
"
,
enterPassword
:
"
輸入密碼
"
,
repeatPassword
:
"
再次輸入密碼
"
,
remember
:
"
記住登錄
"
,
login
:
"
登入
"
,
register
:
"
註冊
"
,
registerAccount
:
"
註冊帳號
"
,
passwordsDoNotMatch
:
"
密碼不匹配
"
,
cannotBeEmpty
:
"
%s不能為空
"
,
error
:
"
處理您的請求時發生錯誤
"
,
logout
:
"
登出
"
,
back
:
"
返回
"
,
cancel
:
"
Cancel
"
,
save
:
"
Save
"
}
this
.
browserSupport
=
{
browserWarning
:
"
You are running an unsupported browser (%s)
"
,
details
:
"
Details...
"
,
...
...
@@ -824,6 +913,8 @@ function StringsKo(){
this
.
hard
=
"
어려움
"
this
.
oni
=
"
귀신
"
this
.
songBranch
=
"
악보 분기 있습니다
"
this
.
defaultName
=
"
동이
"
this
.
notLoggedIn
=
"
로그인하지 않았습니다
"
this
.
sessionStart
=
"
온라인 세션 시작!
"
this
.
sessionEnd
=
"
온라인 세션 끝내기
"
this
.
loading
=
"
로딩 중...
"
...
...
@@ -972,6 +1063,24 @@ function StringsKo(){
content
:
"
Audio latency: %s
\n
Video latency: %s
\n\n
You can configure these latency values in the settings.
"
}
}
this
.
account
=
{
username
:
"
사용자 이름
"
,
enterUsername
:
"
사용자 이름을 입력하십시오
"
,
password
:
"
비밀번호
"
,
enterPassword
:
"
비밀번호 입력
"
,
repeatPassword
:
"
비밀번호 재입력
"
,
remember
:
"
자동 로그인
"
,
login
:
"
로그인
"
,
register
:
"
가입하기
"
,
registerAccount
:
"
계정 등록
"
,
passwordsDoNotMatch
:
"
비밀번호가 일치하지 않습니다
"
,
cannotBeEmpty
:
"
%s 비어 있을 수 없습니다
"
,
error
:
"
요청을 처리하는 동안 오류가 발생했습니다
"
,
logout
:
"
로그 아웃
"
,
back
:
"
돌아간다
"
,
cancel
:
"
Cancel
"
,
save
:
"
Save
"
}
this
.
browserSupport
=
{
browserWarning
:
"
You are running an unsupported browser (%s)
"
,
details
:
"
Details...
"
,
...
...
public/src/js/view.js
View file @
2af924a9
...
...
@@ -126,6 +126,7 @@
this
.
comboCache
=
new
CanvasCache
(
noSmoothing
)
this
.
pauseCache
=
new
CanvasCache
(
noSmoothing
)
this
.
branchCache
=
new
CanvasCache
(
noSmoothing
)
this
.
nameplateCache
=
new
CanvasCache
(
noSmoothing
)
this
.
multiplayer
=
this
.
controller
.
multiplayer
...
...
@@ -235,6 +236,11 @@
if
(
!
this
.
multiplayer
){
this
.
pauseCache
.
resize
(
81
*
this
.
pauseOptions
.
length
*
2
,
464
,
ratio
)
}
if
(
this
.
portrait
){
this
.
nameplateCache
.
resize
(
220
,
54
,
ratio
+
0.2
)
}
else
{
this
.
nameplateCache
.
resize
(
274
,
67
,
ratio
+
0.2
)
}
this
.
fillComboCache
()
this
.
setDonBgHeight
()
resized
=
true
...
...
@@ -388,6 +394,32 @@
h
:
130
}
if
(
this
.
multiplayer
!==
2
){
this
.
nameplateCache
.
get
({
ctx
:
ctx
,
x
:
167
,
y
:
160
,
w
:
219
,
h
:
53
,
id
:
"
1p
"
,
},
ctx
=>
{
if
(
this
.
multiplayer
===
2
){
var
name
=
p2
.
name
||
strings
.
defaultName
}
else
{
var
name
=
account
.
loggedIn
?
account
.
displayName
:
strings
.
defaultName
}
this
.
draw
.
nameplate
({
ctx
:
ctx
,
x
:
3
,
y
:
3
,
scale
:
0.8
,
name
:
name
,
font
:
this
.
font
,
blue
:
this
.
multiplayer
===
2
})
})
}
ctx
.
fillStyle
=
"
#000
"
ctx
.
fillRect
(
0
,
...
...
@@ -547,6 +579,29 @@
}
var
taikoPos
=
{
x
:
179
,
y
:
frameTop
+
190
,
w
:
138
,
h
:
162
}
this
.
nameplateCache
.
get
({
ctx
:
ctx
,
x
:
320
,
y
:
this
.
multiplayer
===
2
?
frameTop
+
305
:
frameTop
+
20
,
w
:
273
,
h
:
66
,
id
:
"
1p
"
,
},
ctx
=>
{
if
(
this
.
multiplayer
===
2
){
var
name
=
p2
.
name
||
strings
.
defaultName
}
else
{
var
name
=
account
.
loggedIn
?
account
.
displayName
:
strings
.
defaultName
}
this
.
draw
.
nameplate
({
ctx
:
ctx
,
x
:
3
,
y
:
3
,
name
:
name
,
font
:
this
.
font
,
blue
:
this
.
multiplayer
===
2
})
})
ctx
.
fillStyle
=
"
#000
"
ctx
.
fillRect
(
0
,
...
...
public/src/views/account.html
0 → 100644
View file @
2af924a9
<div
class=
"view-outer"
>
<div
class=
"view account-view"
>
<div
class=
"view-title stroke-sub"
></div>
<div
class=
"view-content"
>
<div
class=
"displayname-div"
>
<div
class=
"displayname-hint"
></div>
<input
type=
"text"
class=
"displayname"
>
</div>
<form
class=
"accountpass-form"
>
<div>
<div
class=
"accountpass-btn taibtn stroke-sub link-btn"
></div>
</div>
<div
class=
"accountpass-div"
>
<input
type=
"password"
name=
"password"
><input
type=
"password"
name=
"newpassword"
autocomplete=
"new-password"
><input
type=
"password"
name=
"newpassword2"
autocomplete=
"new-password"
>
</div>
</form>
<form
class=
"accountdel-form"
>
<div>
<div
class=
"accountdel-btn taibtn stroke-sub link-btn"
></div>
</div>
<div
class=
"accountdel-div"
>
<input
type=
"password"
name=
"password"
>
</div>
</form>
</div>
<div
id=
"diag-txt"
></div>
<div
class=
"left-buttons"
>
<div
class=
"logout-btn taibtn stroke-sub link-btn"
></div>
</div>
<div
class=
"save-btn taibtn stroke-sub selected"
></div>
<div
class=
"view-end-button taibtn stroke-sub"
></div>
</div>
</div>
public/src/views/login.html
0 → 100644
View file @
2af924a9
<div
class=
"view-outer"
>
<div
class=
"view"
>
<div
class=
"view-title stroke-sub"
></div>
<div
class=
"view-content"
>
<form
class=
"login-form"
>
<div
class=
"username-hint"
></div>
<input
type=
"text"
name=
"username"
required
>
<div
class=
"password-hint"
></div>
<input
type=
"password"
name=
"password"
required
>
<div
class=
"password2-div"
></div>
<div
class=
"remember-div"
>
<label
class=
"remember-label"
>
<input
type=
"checkbox"
checked=
"checked"
name=
"remember"
>
</label>
</div>
<div
class=
"login-btn taibtn stroke-sub link-btn"
></div>
</form>
</div>
<div
class=
"left-buttons"
>
<div
class=
"register-btn taibtn stroke-sub link-btn"
></div>
</div>
<div
class=
"view-end-button taibtn stroke-sub selected"
></div>
</div>
</div>
server.py
View file @
2af924a9
...
...
@@ -42,7 +42,8 @@ async def connection(ws, path):
user
=
{
"ws"
:
ws
,
"action"
:
"ready"
,
"session"
:
False
"session"
:
False
,
"name"
:
None
}
server_status
[
"users"
]
.
append
(
user
)
try
:
...
...
@@ -79,6 +80,7 @@ async def connection(ws, path):
waiting
=
server_status
[
"waiting"
]
id
=
value
[
"id"
]
if
"id"
in
value
else
None
diff
=
value
[
"diff"
]
if
"diff"
in
value
else
None
user
[
"name"
]
=
value
[
"name"
]
if
"name"
in
value
else
None
if
not
id
or
not
diff
:
continue
if
id
not
in
waiting
:
...
...
@@ -92,6 +94,7 @@ async def connection(ws, path):
await
ws
.
send
(
msgobj
(
"waiting"
))
else
:
# Join the other user and start game
user
[
"name"
]
=
value
[
"name"
]
if
"name"
in
value
else
None
user
[
"other_user"
]
=
waiting
[
id
][
"user"
]
waiting_diff
=
waiting
[
id
][
"diff"
]
del
waiting
[
id
]
...
...
@@ -101,7 +104,9 @@ async def connection(ws, path):
user
[
"other_user"
][
"other_user"
]
=
user
await
asyncio
.
wait
([
ws
.
send
(
msgobj
(
"gameload"
,
waiting_diff
)),
user
[
"other_user"
][
"ws"
]
.
send
(
msgobj
(
"gameload"
,
diff
))
user
[
"other_user"
][
"ws"
]
.
send
(
msgobj
(
"gameload"
,
diff
)),
ws
.
send
(
msgobj
(
"name"
,
user
[
"other_user"
][
"name"
])),
user
[
"other_user"
][
"ws"
]
.
send
(
msgobj
(
"name"
,
user
[
"name"
]))
])
else
:
# Wait for another user
...
...
@@ -116,27 +121,31 @@ async def connection(ws, path):
# Update others on waiting players
await
notify_status
()
elif
type
==
"invite"
:
if
value
==
None
:
if
value
and
"id"
in
value
and
value
[
"id"
]
==
None
:
# Session invite link requested
invite
=
get_invite
()
server_status
[
"invites"
][
invite
]
=
user
user
[
"action"
]
=
"invite"
user
[
"session"
]
=
invite
user
[
"name"
]
=
value
[
"name"
]
if
"name"
in
value
else
None
await
ws
.
send
(
msgobj
(
"invite"
,
invite
))
elif
value
in
server_status
[
"invites"
]:
elif
value
and
"id"
in
value
and
value
[
"id"
]
in
server_status
[
"invites"
]:
# Join a session with the other user
user
[
"other_user"
]
=
server_status
[
"invites"
][
value
]
del
server_status
[
"invites"
][
value
]
user
[
"name"
]
=
value
[
"name"
]
if
"name"
in
value
else
None
user
[
"other_user"
]
=
server_status
[
"invites"
][
value
[
"id"
]]
del
server_status
[
"invites"
][
value
[
"id"
]]
if
"ws"
in
user
[
"other_user"
]:
user
[
"other_user"
][
"other_user"
]
=
user
user
[
"action"
]
=
"invite"
user
[
"session"
]
=
value
user
[
"session"
]
=
value
[
"id"
]
sent_msg
=
msgobj
(
"session"
)
await
asyncio
.
wait
([
ws
.
send
(
sent_msg
),
user
[
"other_user"
][
"ws"
]
.
send
(
sent_msg
)
user
[
"other_user"
][
"ws"
]
.
send
(
sent_msg
),
ws
.
send
(
msgobj
(
"invite"
)),
ws
.
send
(
msgobj
(
"name"
,
user
[
"other_user"
][
"name"
])),
user
[
"other_user"
][
"ws"
]
.
send
(
msgobj
(
"name"
,
user
[
"name"
]))
])
await
ws
.
send
(
msgobj
(
"invite"
))
else
:
del
user
[
"other_user"
]
await
ws
.
send
(
msgobj
(
"gameend"
))
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment