Jubeat WebUI update, adds support for emblem editing, stats and jubility breakdown.
This commit is contained in:
parent
ef085ae99e
commit
14374ef2d3
@ -282,6 +282,7 @@ def viewplayer(userid: UserID) -> Response:
|
||||
'refresh': url_for('jubeat_pages.listplayer', userid=userid),
|
||||
'records': url_for('jubeat_pages.viewrecords', userid=userid),
|
||||
'scores': url_for('jubeat_pages.viewscores', userid=userid),
|
||||
'jubility': url_for('jubeat_pages.showjubility', userid=userid),
|
||||
},
|
||||
)
|
||||
|
||||
@ -298,28 +299,100 @@ def listplayer(userid: UserID) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
@jubeat_pages.route('/players/<int:userid>/jubility')
|
||||
@loginrequired
|
||||
def showjubility(userid: UserID) -> Response:
|
||||
frontend = JubeatFrontend(g.data, g.config, g.cache)
|
||||
info = frontend.get_all_player_info([userid])[userid]
|
||||
if not info:
|
||||
abort(404)
|
||||
latest_version = sorted(info.keys(), reverse=True)[0]
|
||||
|
||||
return render_react(
|
||||
f'{info[latest_version]["name"]}\'s Jubility Breakdown',
|
||||
'jubeat/jubility.react.js',
|
||||
{
|
||||
'playerid': userid,
|
||||
'player': info,
|
||||
'songs': frontend.get_all_songs(),
|
||||
'versions': {version: name for (game, version, name) in frontend.all_games()},
|
||||
},
|
||||
{
|
||||
'refresh': url_for('jubeat_pages.listplayer', userid=userid),
|
||||
'individual_score': url_for('jubeat_pages.viewtopscores', musicid=-1),
|
||||
'profile': url_for('jubeat_pages.viewplayer', userid=userid),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@jubeat_pages.route('/options')
|
||||
@loginrequired
|
||||
def viewsettings() -> Response:
|
||||
frontend = JubeatFrontend(g.data, g.config, g.cache)
|
||||
userid = g.userID
|
||||
info = frontend.get_all_player_info([userid])[userid]
|
||||
versions = sorted(
|
||||
[version for (game, version, name) in frontend.all_games()],
|
||||
reverse=True,
|
||||
)
|
||||
if not info:
|
||||
abort(404)
|
||||
|
||||
all_emblems = frontend.get_all_items(versions)
|
||||
return render_react(
|
||||
'Jubeat Game Settings',
|
||||
'jubeat/settings.react.js',
|
||||
{
|
||||
'player': info,
|
||||
'versions': {version: name for (game, version, name) in frontend.all_games()},
|
||||
'emblems': all_emblems,
|
||||
},
|
||||
{
|
||||
'updatename': url_for('jubeat_pages.updatename'),
|
||||
'updateemblem': url_for('jubeat_pages.updateemblem')
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@jubeat_pages.route('/options/emblem/update', methods=['POST'])
|
||||
@jsonify
|
||||
@loginrequired
|
||||
def updateemblem() -> Dict[str, Any]:
|
||||
frontend = JubeatFrontend(g.data, g.config, g.cache)
|
||||
version = int(request.get_json()['version'])
|
||||
emblem = request.get_json()['emblem']
|
||||
user = g.data.local.user.get_user(g.userID)
|
||||
if user is None:
|
||||
raise Exception('Unable to find user to update!')
|
||||
|
||||
# Grab profile and update emblem
|
||||
profile = g.data.local.user.get_profile(GameConstants.JUBEAT, version, user.id)
|
||||
if profile is None:
|
||||
raise Exception('Unable to find profile to update!')
|
||||
|
||||
# Making emblem arr for update
|
||||
emblem_arr = [
|
||||
emblem['background'],
|
||||
emblem['main'],
|
||||
emblem['ornament'],
|
||||
emblem['effect'],
|
||||
emblem['speech_bubble'],
|
||||
]
|
||||
|
||||
# Grab last dict from profile for updating emblem
|
||||
last_dict = profile.get_dict('last')
|
||||
last_dict.replace_int_array('emblem', 5, emblem_arr)
|
||||
|
||||
# Replace last dict that replaced int arr
|
||||
profile.replace_dict('last', last_dict)
|
||||
g.data.local.user.put_profile(GameConstants.JUBEAT, version, user.id, profile)
|
||||
|
||||
return {
|
||||
'version': version,
|
||||
'emblem': frontend.format_emblem(emblem_arr),
|
||||
}
|
||||
|
||||
|
||||
@jubeat_pages.route('/options/name/update', methods=['POST'])
|
||||
@jsonify
|
||||
@loginrequired
|
||||
|
@ -1,5 +1,5 @@
|
||||
# vim: set fileencoding=utf-8
|
||||
from typing import Any, Dict, Iterator, List, Tuple
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
||||
|
||||
from bemani.backend.jubeat import JubeatFactory, JubeatBase
|
||||
from bemani.common import Profile, ValidatedDict, GameConstants, VersionConstants
|
||||
@ -42,6 +42,58 @@ class JubeatFrontend(FrontendBase):
|
||||
if version in mapping:
|
||||
yield (game, mapping[version], name)
|
||||
|
||||
def get_duplicate_id(self, musicid: int, chart: int) -> Optional[Tuple[int, int]]:
|
||||
# In qubell and clan omnimix, PPAP and Bonjour the world are placed
|
||||
# at this arbitrary songid since they weren't assigned one originally
|
||||
# In jubeat festo, these songs were given proper songids so we need to account for this
|
||||
legacy_to_modern_map = {
|
||||
71000001: 70000124, # PPAP
|
||||
71000002: 70000154, # Bonjour the world
|
||||
50000020: 80000037, # 千本桜 was removed and then revived in clan
|
||||
60000063: 70000100, # Khamen break sdvx had the first id for prop(never released officially)
|
||||
}
|
||||
oldid = legacy_to_modern_map.get(musicid)
|
||||
oldchart = chart
|
||||
if oldid is not None:
|
||||
return (oldid, oldchart)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_all_items(self, versions: list) -> Dict[str, List[Dict[str, Any]]]:
|
||||
result = {}
|
||||
for version in versions:
|
||||
emblem = self.__format_jubeat_extras(version)
|
||||
result[version] = emblem['emblems']
|
||||
return result
|
||||
|
||||
def __format_jubeat_extras(self, version: int) -> Dict[str, List[Dict[str, Any]]]:
|
||||
# Gotta look up the unlock catalog
|
||||
items = self.data.local.game.get_items(self.game, version)
|
||||
|
||||
# Format it depending on the version
|
||||
if version in {
|
||||
VersionConstants.JUBEAT_PROP,
|
||||
VersionConstants.JUBEAT_QUBELL,
|
||||
VersionConstants.JUBEAT_CLAN,
|
||||
VersionConstants.JUBEAT_FESTO,
|
||||
}:
|
||||
return {
|
||||
"emblems": [
|
||||
{
|
||||
"index": str(item.id),
|
||||
"song": item.data.get_int("music_id"),
|
||||
"layer": item.data.get_int("layer"),
|
||||
"evolved": item.data.get_int("evolved"),
|
||||
"rarity": item.data.get_int("rarity"),
|
||||
"name": item.data.get_str("name"),
|
||||
}
|
||||
for item in items
|
||||
if item.type == "emblem"
|
||||
],
|
||||
}
|
||||
else:
|
||||
return {"emblems": []}
|
||||
|
||||
def format_score(self, userid: UserID, score: Score) -> Dict[str, Any]:
|
||||
formatted_score = super().format_score(userid, score)
|
||||
formatted_score['combo'] = score.data.get_int('combo', -1)
|
||||
@ -57,6 +109,9 @@ class JubeatFrontend(FrontendBase):
|
||||
JubeatBase.PLAY_MEDAL_NEARLY_EXCELLENT: "NEARLY EXCELLENT",
|
||||
JubeatBase.PLAY_MEDAL_EXCELLENT: "EXCELLENT",
|
||||
}.get(score.data.get_int('medal'), 'NO PLAY')
|
||||
formatted_score['music_rate'] = score.data.get_int('music_rate', 0) / 10
|
||||
formatted_score['clear_cnt'] = score.data.get_int('clear_count', 0)
|
||||
formatted_score['stats'] = score.data.get_dict('stats')
|
||||
return formatted_score
|
||||
|
||||
def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]:
|
||||
@ -74,11 +129,32 @@ class JubeatFrontend(FrontendBase):
|
||||
JubeatBase.PLAY_MEDAL_NEARLY_EXCELLENT: "NEARLY EXCELLENT",
|
||||
JubeatBase.PLAY_MEDAL_EXCELLENT: "EXCELLENT",
|
||||
}.get(attempt.data.get_int('medal'), 'NO PLAY')
|
||||
formatted_attempt['music_rate'] = attempt.data.get_int('music_rate', 0) / 10
|
||||
formatted_attempt['stats'] = attempt.data.get_dict('stats')
|
||||
return formatted_attempt
|
||||
|
||||
def format_emblem(self, emblem: list) -> Dict[str, Any]:
|
||||
return {
|
||||
'background': emblem[0],
|
||||
'main': emblem[1],
|
||||
'ornament': emblem[2],
|
||||
'effect': emblem[3],
|
||||
'speech_bubble': emblem[4],
|
||||
}
|
||||
|
||||
def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]:
|
||||
formatted_profile = super().format_profile(profile, playstats)
|
||||
formatted_profile['plays'] = playstats.get_int('total_plays')
|
||||
formatted_profile['emblem'] = self.format_emblem(profile.get_dict('last').get_int_array('emblem', 5))
|
||||
formatted_profile['jubility'] = profile.get_int('jubility')
|
||||
formatted_profile['pick_up_jubility'] = profile.get_float('pick_up_jubility')
|
||||
# Only reason this is a dictionary of dictionaries is because ValidatedDict doesn't support a list of dictionaries.
|
||||
# Probably intentionally lol. Just listify the pickup/common charts.
|
||||
formatted_profile['pick_up_chart'] = list(profile.get_dict('pick_up_chart').values())
|
||||
formatted_profile['common_jubility'] = profile.get_float('common_jubility')
|
||||
formatted_profile['common_chart'] = list(profile.get_dict('common_chart').values())
|
||||
formatted_profile['ex_count'] = profile.get_int('ex_cnt')
|
||||
formatted_profile['fc_count'] = profile.get_int('fc_cnt')
|
||||
return formatted_profile
|
||||
|
||||
def format_song(self, song: Song) -> Dict[str, Any]:
|
||||
|
@ -69,6 +69,33 @@ var all_players = React.createClass({
|
||||
}.bind(this),
|
||||
reverse: true,
|
||||
},
|
||||
{
|
||||
name: 'Jubility',
|
||||
render: function(userid) {
|
||||
var player = this.state.players[userid];
|
||||
if (player.common_jubility != 0 || player.pick_up_jubility != 0) {
|
||||
return (player.common_jubility + player.pick_up_jubility).toFixed(1);
|
||||
} else if (player.jubility != 0) {
|
||||
return player.jubility / 100
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}.bind(this),
|
||||
sort: function(aid, bid) {
|
||||
var a = this.state.players[aid];
|
||||
var b = this.state.players[bid];
|
||||
if (a.common_jubility != 0 || a.pick_up_jubility != 0)
|
||||
var ajub = a.common_jubility+a.pick_up_jubility;
|
||||
else
|
||||
var ajub = a.jubility / 100;
|
||||
if (b.common_jubility != 0 || b.pick_up_jubility != 0)
|
||||
var bjub = b.common_jubility+b.pick_up_jubility;
|
||||
else
|
||||
var bjub = b.jubility / 100;
|
||||
return ajub-bjub;
|
||||
}.bind(this),
|
||||
reverse: true,
|
||||
},
|
||||
]}
|
||||
rows={Object.keys(this.state.players)}
|
||||
paginate={10}
|
||||
|
228
bemani/frontend/static/controllers/jubeat/jubility.react.js
Normal file
228
bemani/frontend/static/controllers/jubeat/jubility.react.js
Normal file
@ -0,0 +1,228 @@
|
||||
/*** @jsx React.DOM */
|
||||
|
||||
var valid_versions = Object.keys(window.versions);
|
||||
var pagenav = new History(valid_versions);
|
||||
|
||||
var jubility_view = React.createClass({
|
||||
|
||||
getInitialState: function(props) {
|
||||
var profiles = Object.keys(window.player);
|
||||
return {
|
||||
player: window.player,
|
||||
songs: window.songs,
|
||||
profiles: profiles,
|
||||
version: pagenav.getInitialState(profiles[profiles.length - 1]),
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
pagenav.onChange(function(version) {
|
||||
this.setState({version: version});
|
||||
}.bind(this));
|
||||
this.refreshProfile();
|
||||
},
|
||||
|
||||
refreshProfile: function() {
|
||||
AJAX.get(
|
||||
Link.get('refresh'),
|
||||
function(response) {
|
||||
var profiles = Object.keys(response.player);
|
||||
|
||||
this.setState({
|
||||
player: response.player,
|
||||
profiles: profiles,
|
||||
});
|
||||
setTimeout(this.refreshProfile, 5000);
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
renderJubilityBreakdown: function(player) {
|
||||
return (
|
||||
<div className='row'>
|
||||
{this.renderJubilityTable(player, true)}
|
||||
{this.renderJubilityTable(player, false)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderJubilityTable: function(player, pickup) {
|
||||
if (this.state.version != 13) // festo
|
||||
return null;
|
||||
if (pickup == true)
|
||||
jubilityChart = player.pick_up_chart;
|
||||
else
|
||||
jubilityChart = player.common_chart;
|
||||
if (typeof jubilityChart === 'undefined' || jubilityChart.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return(
|
||||
<div className='col-6 col-12-medium'>
|
||||
<p>
|
||||
<b>
|
||||
{pickup == true ? <b>Pick up chart breakdown</b> : <b>Common chart breakdown</b>}
|
||||
</b>
|
||||
</p>
|
||||
<Table
|
||||
className='list jubility'
|
||||
columns={[
|
||||
{
|
||||
name: 'Song',
|
||||
render: function(entry) {
|
||||
return (
|
||||
<a href={Link.get('individual_score', entry.music_id)}>
|
||||
<div>{ this.state.songs[entry.music_id].name }</div>
|
||||
</a>
|
||||
);
|
||||
}.bind(this),
|
||||
},
|
||||
{
|
||||
name: 'Hard Mode',
|
||||
render: function(entry) { return entry.seq >= 3 ? 'Yes' : 'No'; }
|
||||
},
|
||||
{
|
||||
name: 'Music Rate',
|
||||
render: function(entry) { return entry.music_rate.toFixed(1) + '%'; },
|
||||
sort: function(a, b) {
|
||||
return a.music_rate - b.music_rate;
|
||||
},
|
||||
reverse: true,
|
||||
},
|
||||
{
|
||||
name: 'Jubility',
|
||||
render: function(entry) { return entry.value.toFixed(1); },
|
||||
sort: function(a, b) {
|
||||
return a.value - b.value;
|
||||
},
|
||||
reverse: true,
|
||||
},
|
||||
]}
|
||||
defaultsort='Jubility'
|
||||
rows={jubilityChart}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderJubility: function(player) {
|
||||
return(
|
||||
// version == prop ( No Jubility )
|
||||
this.state.version == 10 ?
|
||||
<div>
|
||||
<p>This version of jubeat doesn't support Jubility</p>
|
||||
</div>
|
||||
:
|
||||
// version == qubell ( No Jubility )
|
||||
this.state.version == 11 ?
|
||||
<div>
|
||||
<p>This version of jubeat doesn't support Jubility</p>
|
||||
</div>
|
||||
:
|
||||
// version == festo
|
||||
this.state.version == 13 ?
|
||||
<div>
|
||||
<LabelledSection label='Jubility'>
|
||||
{(player.common_jubility+player.pick_up_jubility).toFixed(1)}
|
||||
</LabelledSection>
|
||||
<LabelledSection label='Common Jubility'>
|
||||
{player.common_jubility.toFixed(1)}
|
||||
</LabelledSection>
|
||||
<LabelledSection label='Pick up Jubility'>
|
||||
{player.pick_up_jubility.toFixed(1)}
|
||||
</LabelledSection>
|
||||
</div>
|
||||
:
|
||||
// Default which version >= Saucer except qubell and festo
|
||||
this.state.version >= 8 ?
|
||||
<div>
|
||||
<LabelledSection label='Jubility'>
|
||||
{player.jubility / 100}
|
||||
</LabelledSection>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<p>This version of jubeat doesn't support Jubility</p>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.player[this.state.version]) {
|
||||
var player = this.state.player[this.state.version];
|
||||
var filteredVersion = Object.values(this.state.profiles).map(function(version) {
|
||||
return Object.values(window.versions)[version-1]
|
||||
});
|
||||
var item = Object.keys(window.versions).map(function(k){
|
||||
return window.versions[k]
|
||||
})
|
||||
return (
|
||||
<div>
|
||||
<section>
|
||||
<p>
|
||||
<b>
|
||||
<a href={Link.get('profile')}>← Back To Profile</a>
|
||||
</b>
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h3>{player.name}'s jubility</h3>
|
||||
<p>
|
||||
{this.state.profiles.map(function(version) {
|
||||
return (
|
||||
<Nav
|
||||
title={window.versions[version]}
|
||||
active={this.state.version == version}
|
||||
onClick={function(event) {
|
||||
if (this.state.version == version) { return; }
|
||||
this.setState({
|
||||
version: version,
|
||||
});
|
||||
pagenav.navigate(version);
|
||||
}.bind(this)}
|
||||
/>
|
||||
);
|
||||
}.bind(this))}
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
{this.renderJubility(player)}
|
||||
</section>
|
||||
<section>
|
||||
{this.renderJubilityBreakdown(player)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
var item = Object.keys(window.versions).map(function(k){
|
||||
return window.versions[k]
|
||||
})
|
||||
return (
|
||||
<div>
|
||||
<section>
|
||||
<p>
|
||||
<SelectVersion
|
||||
name='version'
|
||||
value={ item.indexOf(item[this.state.version - 1]) }
|
||||
versions={ item }
|
||||
onChange={function(event) {
|
||||
var version = item.indexOf(item[event]) + 1
|
||||
if (this.state.version == version) { return; }
|
||||
this.setState({version: version});
|
||||
pagenav.navigate(version);
|
||||
}.bind(this)}
|
||||
/>
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<p>This player has no profile for {window.versions[this.state.version]}!</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
React.createElement(jubility_view, null),
|
||||
document.getElementById('content')
|
||||
);
|
@ -36,9 +36,50 @@ var profile_view = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
renderJubility: function(player) {
|
||||
return(
|
||||
// version == prop ( No Jubility )
|
||||
this.state.version == 10 ?
|
||||
null
|
||||
:
|
||||
// version == qubell ( No Jubility )
|
||||
this.state.version == 11 ?
|
||||
null
|
||||
:
|
||||
// version == festo
|
||||
this.state.version == 13 ?
|
||||
<div>
|
||||
<LabelledSection label="Jubility">
|
||||
{(player.common_jubility+player.pick_up_jubility).toFixed(1)}
|
||||
</LabelledSection>
|
||||
<p>
|
||||
<b>
|
||||
<a href={Link.get('jubility')}>{ window.own_profile ?
|
||||
<span>Your Jubility Breakdown →</span> :
|
||||
<span>{player.name}'s Jubility Breakdown →</span>
|
||||
}</a>
|
||||
</b>
|
||||
</p>
|
||||
</div>
|
||||
:
|
||||
// Default which version >= Saucer except qubell and festo
|
||||
this.state.version >= 8 ?
|
||||
<div>
|
||||
<LabelledSection label="Jubility">
|
||||
{player.jubility / 100}
|
||||
</LabelledSection>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
)
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.player[this.state.version]) {
|
||||
var player = this.state.player[this.state.version];
|
||||
var item = Object.keys(window.versions).map(function(k){
|
||||
return window.versions[k]
|
||||
})
|
||||
return (
|
||||
<div>
|
||||
<div className="section">
|
||||
@ -68,6 +109,13 @@ var profile_view = React.createClass({
|
||||
<LabelledSection label="Total Plays">
|
||||
{player.plays}回
|
||||
</LabelledSection>
|
||||
<LabelledSection label="EXCELLENTs">
|
||||
{player.ex_count}回
|
||||
</LabelledSection>
|
||||
<LabelledSection label="FULL COMBOs">
|
||||
{player.fc_count}回
|
||||
</LabelledSection>
|
||||
{this.renderJubility(player)}
|
||||
</div>
|
||||
<div className="section">
|
||||
<a href={Link.get('records')}>{ window.own_profile ?
|
||||
@ -83,22 +131,23 @@ var profile_view = React.createClass({
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
var item = Object.keys(window.versions).map(function(k){
|
||||
return window.versions[k]
|
||||
})
|
||||
return (
|
||||
<div>
|
||||
<div className="section">
|
||||
{this.state.profiles.map(function(version) {
|
||||
return (
|
||||
<Nav
|
||||
title={window.versions[version]}
|
||||
active={this.state.version == version}
|
||||
onClick={function(event) {
|
||||
if (this.state.version == version) { return; }
|
||||
this.setState({version: version});
|
||||
pagenav.navigate(version);
|
||||
}.bind(this)}
|
||||
/>
|
||||
);
|
||||
}.bind(this))}
|
||||
<SelectVersion
|
||||
name="version"
|
||||
value={ item.indexOf(item[this.state.version - 1]) }
|
||||
versions={ item }
|
||||
onChange={function(event) {
|
||||
var version = item.indexOf(item[event]) + 1
|
||||
if (this.state.version == version) { return; }
|
||||
this.setState({version: version});
|
||||
pagenav.navigate(version);
|
||||
}.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
<div className="section">
|
||||
This player has no profile for {window.versions[this.state.version]}!
|
||||
|
@ -35,6 +35,18 @@ var HighScore = React.createClass({
|
||||
</div>
|
||||
<div>
|
||||
<span className="status">{this.props.score.status}</span>
|
||||
<br/>
|
||||
<span className="bolder">Stats:</span>
|
||||
<br/>
|
||||
{this.props.score.stats.perfect}
|
||||
<span> / </span>
|
||||
{this.props.score.stats.great}
|
||||
<span> / </span>
|
||||
{this.props.score.stats.good}
|
||||
<span> / </span>
|
||||
{this.props.score.stats.poor}
|
||||
<span> / </span>
|
||||
{this.props.score.stats.miss}
|
||||
</div>
|
||||
{ this.props.score.userid && window.shownames ?
|
||||
<div><a href={Link.get('player', this.props.score.userid)}>{
|
||||
|
@ -64,6 +64,18 @@ var network_scores = React.createClass({
|
||||
</div>
|
||||
<div>
|
||||
<span className="status">{score.status}</span>
|
||||
<br/>
|
||||
<span className="bolder">Stats:</span>
|
||||
<br/>
|
||||
{score.stats.perfect}
|
||||
<span> / </span>
|
||||
{score.stats.great}
|
||||
<span> / </span>
|
||||
{score.stats.good}
|
||||
<span> / </span>
|
||||
{score.stats.poor}
|
||||
<span> / </span>
|
||||
{score.stats.miss}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -3,6 +3,22 @@
|
||||
var valid_versions = Object.keys(window.versions);
|
||||
var pagenav = new History(valid_versions);
|
||||
|
||||
var valid_emblem_options = [
|
||||
'background',
|
||||
'main',
|
||||
'ornament',
|
||||
'effect',
|
||||
'speech_bubble',
|
||||
]
|
||||
|
||||
var emblem_option_names = {
|
||||
'main': 'Main',
|
||||
'background': 'Background',
|
||||
'ornament': 'Ornament',
|
||||
'effect': 'Effect',
|
||||
'speech_bubble': 'Speech Bubble',
|
||||
}
|
||||
|
||||
var settings_view = React.createClass({
|
||||
|
||||
getInitialState: function(props) {
|
||||
@ -14,6 +30,9 @@ var settings_view = React.createClass({
|
||||
version: version,
|
||||
new_name: window.player[version].name,
|
||||
editing_name: false,
|
||||
emblem_changed: {},
|
||||
emblem_saving: {},
|
||||
emblem_saved: {},
|
||||
};
|
||||
},
|
||||
|
||||
@ -30,6 +49,42 @@ var settings_view = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
setEmblemChanged: function(val) {
|
||||
this.state.emblem_changed[this.state.version] = val;
|
||||
return this.state.emblem_changed
|
||||
},
|
||||
|
||||
setEmblemSaving: function(val) {
|
||||
this.state.emblem_saving[this.state.version] = val;
|
||||
return this.state.emblem_saving
|
||||
},
|
||||
|
||||
setEmblemSaved: function(val) {
|
||||
this.state.emblem_saved[this.state.version] = val;
|
||||
return this.state.emblem_saved
|
||||
},
|
||||
|
||||
saveEmblem: function(event) {
|
||||
this.setState({ emblem_saving: this.setEmblemSaving(true), emblem_saved: this.setEmblemSaved(false) })
|
||||
AJAX.post(
|
||||
Link.get('updateemblem'),
|
||||
{
|
||||
version: this.state.version,
|
||||
emblem: this.state.player[this.state.version].emblem,
|
||||
},
|
||||
function(response) {
|
||||
var player = this.state.player
|
||||
player[response.version].emblem = response.emblem
|
||||
this.setState({
|
||||
player: player,
|
||||
emblem_saving: this.setEmblemSaving(false),
|
||||
emblem_saved: this.setEmblemSaved(true),
|
||||
emblem_changed: this.setEmblemChanged(false),
|
||||
})
|
||||
}.bind(this)
|
||||
)
|
||||
},
|
||||
|
||||
saveName: function(event) {
|
||||
AJAX.post(
|
||||
Link.get('updatename'),
|
||||
@ -99,6 +154,67 @@ var settings_view = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
renderEmblem: function(player) {
|
||||
return (
|
||||
<div className="section">
|
||||
<h3>Emblem</h3>
|
||||
{
|
||||
valid_emblem_options.map(function(emblem_option) {
|
||||
var player = this.state.player[this.state.version]
|
||||
var layer = valid_emblem_options.indexOf(emblem_option) + 1
|
||||
var items = window.emblems[this.state.version].filter(function (emblem) {
|
||||
return emblem.layer == layer
|
||||
});
|
||||
var results = {};
|
||||
items
|
||||
.map(function(item) { return { 'index': item.index, 'name': `${item.name} (★${item.rarity})` } })
|
||||
.forEach (value => results[value.index] = value.name);
|
||||
if (layer != 2) {
|
||||
results[0] = "None"
|
||||
}
|
||||
return(
|
||||
<LabelledSection
|
||||
className="jubeat emblemoption"
|
||||
vertical={true}
|
||||
label={emblem_option_names[emblem_option]}
|
||||
>
|
||||
<SelectInt
|
||||
name={emblem_option}
|
||||
value={player.emblem[emblem_option]}
|
||||
choices={results}
|
||||
onChange={function(choice) {
|
||||
var player = this.state.player;
|
||||
player[this.state.version].emblem[emblem_option] = choice;
|
||||
this.setState({
|
||||
player: player,
|
||||
emblem_changed: this.setEmblemChanged(true),
|
||||
})
|
||||
}.bind(this)}
|
||||
/>
|
||||
</LabelledSection>
|
||||
)
|
||||
}.bind(this))
|
||||
}
|
||||
<input
|
||||
type="submit"
|
||||
value="save"
|
||||
disabled={!this.state.emblem_changed[this.state.version]}
|
||||
onClick={function(event) {
|
||||
this.saveEmblem(event);
|
||||
}.bind(this)}
|
||||
/>
|
||||
{ this.state.emblem_saving[this.state.version] ?
|
||||
<img className="loading" src={Link.get('static', 'loading-16.gif')} /> :
|
||||
null
|
||||
}
|
||||
{ this.state.emblem_saved[this.state.version] ?
|
||||
<span>✓</span> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.player[this.state.version]) {
|
||||
var player = this.state.player[this.state.version];
|
||||
@ -127,6 +243,9 @@ var settings_view = React.createClass({
|
||||
<h3>User Profile</h3>
|
||||
{this.renderName(player)}
|
||||
</div>
|
||||
{
|
||||
this.state.version > 9 ? this.renderEmblem(player) : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
@ -124,6 +124,26 @@ var top_scores = React.createClass({
|
||||
name: 'Combo',
|
||||
render: function(topscore) { return topscore.combo > 0 ? topscore.combo : '-'; },
|
||||
},
|
||||
{
|
||||
name: 'Perfect',
|
||||
render: function(topscore) { return topscore.stats.perfect }
|
||||
},
|
||||
{
|
||||
name: 'Great',
|
||||
render: function(topscore) { return topscore.stats.great }
|
||||
},
|
||||
{
|
||||
name: 'Good',
|
||||
render: function(topscore) { return topscore.stats.good }
|
||||
},
|
||||
{
|
||||
name: 'Poor',
|
||||
render: function(topscore) { return topscore.stats.poor }
|
||||
},
|
||||
{
|
||||
name: 'Miss',
|
||||
render: function(topscore) { return topscore.stats.miss }
|
||||
},
|
||||
]}
|
||||
defaultsort='Score'
|
||||
rows={this.state.topscores[chart]}
|
||||
|
@ -68,7 +68,29 @@ table.records, table.attempts, table.topscores, table.players, table.events {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.records a, table.attempts a, table.topscores a, table.players a {
|
||||
table.jubility {
|
||||
width: 100%;
|
||||
width: calc(100% - 15px);
|
||||
float: left;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex: 50%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.row {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
table.records a, table.attempts a, table.topscores a, table.players a, table.jubility a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,29 @@ table.records, table.attempts, table.topscores, table.players, table.events {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.records a, table.attempts a, table.topscores a, table.players a {
|
||||
table.jubility {
|
||||
width: 100%;
|
||||
width: calc(100% - 15px);
|
||||
float: left;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex: 50%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.row {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
table.records a, table.attempts a, table.topscores a, table.players a, table.jubility a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user