mirror of
https://github.com/DragonMinded/bemaniutils.git
synced 2026-07-02 00:11:48 -05:00
Initial implementation of DanEvo frontend, only including records, all players, profiles, top scores and personal settings.
This commit is contained in:
parent
8b60aff379
commit
13bebc392a
|
|
@ -412,6 +412,47 @@ def navigation() -> Dict[str, Any]:
|
|||
},
|
||||
)
|
||||
|
||||
if GameConstants.DANCE_EVOLUTION in g.config.support:
|
||||
# Dance Evolution pages
|
||||
danevo_entries = []
|
||||
if len([p for p in profiles if p[0] == GameConstants.DANCE_EVOLUTION]) > 0:
|
||||
danevo_entries.extend(
|
||||
[
|
||||
{
|
||||
"label": "Game Options",
|
||||
"uri": url_for("danevo_pages.viewsettings"),
|
||||
},
|
||||
{
|
||||
"label": "Personal Profile",
|
||||
"uri": url_for("danevo_pages.viewplayer", userid=g.userID),
|
||||
},
|
||||
{
|
||||
"label": "Personal Records",
|
||||
"uri": url_for("danevo_pages.viewrecords", userid=g.userID),
|
||||
},
|
||||
]
|
||||
)
|
||||
danevo_entries.extend(
|
||||
[
|
||||
{
|
||||
"label": "Global Records",
|
||||
"uri": url_for("danevo_pages.viewnetworkrecords"),
|
||||
},
|
||||
{
|
||||
"label": "All Players",
|
||||
"uri": url_for("danevo_pages.viewplayers"),
|
||||
},
|
||||
]
|
||||
)
|
||||
pages.append(
|
||||
{
|
||||
"label": "Dance Evolution",
|
||||
"entries": danevo_entries,
|
||||
"base_uri": app.blueprints["danevo_pages"].url_prefix,
|
||||
"gamecode": GameConstants.DANCE_EVOLUTION.value,
|
||||
},
|
||||
)
|
||||
|
||||
if GameConstants.DDR in g.config.support:
|
||||
# DDR pages
|
||||
ddr_entries = []
|
||||
|
|
|
|||
8
bemani/frontend/danevo/__init__.py
Normal file
8
bemani/frontend/danevo/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from bemani.frontend.danevo.endpoints import danevo_pages
|
||||
from bemani.frontend.danevo.cache import DanceEvolutionCache
|
||||
|
||||
|
||||
__all__ = [
|
||||
"DanceEvolutionCache",
|
||||
"danevo_pages",
|
||||
]
|
||||
10
bemani/frontend/danevo/cache.py
Normal file
10
bemani/frontend/danevo/cache.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from bemani.common import cache
|
||||
from bemani.data import Config, Data
|
||||
from bemani.frontend.danevo.danevo import DanceEvolutionFrontend
|
||||
|
||||
|
||||
class DanceEvolutionCache:
|
||||
@classmethod
|
||||
def preload(cls, data: Data, config: Config) -> None:
|
||||
frontend = DanceEvolutionFrontend(data, config, cache)
|
||||
frontend.get_all_songs(force_db_load=True)
|
||||
88
bemani/frontend/danevo/danevo.py
Normal file
88
bemani/frontend/danevo/danevo.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# vim: set fileencoding=utf-8
|
||||
from typing import Any, Dict, Iterator, List, Tuple
|
||||
|
||||
from bemani.backend.danevo import DanceEvolutionFactory, DanceEvolutionBase
|
||||
from bemani.common import Profile, ValidatedDict, GameConstants
|
||||
from bemani.data import Attempt, Score, Song, UserID
|
||||
from bemani.frontend.base import FrontendBase
|
||||
|
||||
|
||||
class DanceEvolutionFrontend(FrontendBase):
|
||||
game: GameConstants = GameConstants.DANCE_EVOLUTION
|
||||
|
||||
valid_charts: List[int] = [
|
||||
DanceEvolutionBase.CHART_TYPE_LIGHT,
|
||||
DanceEvolutionBase.CHART_TYPE_STANDARD,
|
||||
DanceEvolutionBase.CHART_TYPE_EXTREME,
|
||||
DanceEvolutionBase.CHART_TYPE_STEALTH,
|
||||
DanceEvolutionBase.CHART_TYPE_MASTER,
|
||||
# Only included so that we can grab the play count for this song.
|
||||
DanceEvolutionBase.CHART_TYPE_PLAYTRACKING,
|
||||
]
|
||||
|
||||
valid_rival_types: List[str] = []
|
||||
|
||||
def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]:
|
||||
yield from DanceEvolutionFactory.all_games()
|
||||
|
||||
def get_all_songs(self, force_db_load: bool = False) -> Dict[int, Dict[str, Any]]:
|
||||
def is_valid(data: Dict[str, Any]) -> bool:
|
||||
if "levels" not in data:
|
||||
return False
|
||||
levels = data["levels"]
|
||||
if not isinstance(levels, list):
|
||||
return False
|
||||
|
||||
for x in levels:
|
||||
if x == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
songs = super().get_all_songs(force_db_load)
|
||||
filtered_songs = {sid: contents for sid, contents in songs.items() if is_valid(contents)}
|
||||
return filtered_songs
|
||||
|
||||
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")
|
||||
formatted_score["full_combo"] = score.data.get_bool("full_combo")
|
||||
formatted_score["medal"] = score.data.get_int("grade")
|
||||
formatted_score["grade"] = {
|
||||
DanceEvolutionBase.GRADE_FAILED: "FAILED",
|
||||
DanceEvolutionBase.GRADE_E: "E",
|
||||
DanceEvolutionBase.GRADE_D: "D",
|
||||
DanceEvolutionBase.GRADE_C: "C",
|
||||
DanceEvolutionBase.GRADE_B: "B",
|
||||
DanceEvolutionBase.GRADE_A: "A",
|
||||
DanceEvolutionBase.GRADE_AA: "AA",
|
||||
DanceEvolutionBase.GRADE_AAA: "AAA",
|
||||
}.get(score.data.get_int("grade"), "NO PLAY")
|
||||
return formatted_score
|
||||
|
||||
def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]:
|
||||
raise NotImplementedError("Dance Evolution does not have attempts!")
|
||||
|
||||
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["player_class"] = profile.get_int("class")
|
||||
return formatted_profile
|
||||
|
||||
def format_song(self, song: Song) -> Dict[str, Any]:
|
||||
levels = [0, 0, 0, 0, 0]
|
||||
levels[song.chart] = song.data.get_int("level")
|
||||
|
||||
formatted_song = super().format_song(song)
|
||||
formatted_song["levels"] = levels
|
||||
formatted_song["bpm_min"] = song.data.get_int("bpm_min")
|
||||
formatted_song["bpm_max"] = song.data.get_int("bpm_max")
|
||||
return formatted_song
|
||||
|
||||
def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]:
|
||||
if new.chart == DanceEvolutionBase.CHART_TYPE_PLAYTRACKING:
|
||||
return existing
|
||||
|
||||
new_song = super().merge_song(existing, new)
|
||||
if existing["levels"][new.chart] == 0:
|
||||
new_song["levels"][new.chart] = new.data.get_int("level")
|
||||
return new_song
|
||||
280
bemani/frontend/danevo/endpoints.py
Normal file
280
bemani/frontend/danevo/endpoints.py
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
# vim: set fileencoding=utf-8
|
||||
import re
|
||||
from typing import Any, Dict
|
||||
from flask import Blueprint, request, Response, url_for, abort
|
||||
from bemani.common import GameConstants
|
||||
from bemani.data import UserID
|
||||
from bemani.frontend.app import loginrequired, jsonify, render_react
|
||||
from bemani.frontend.danevo.danevo import DanceEvolutionFrontend
|
||||
from bemani.frontend.templates import templates_location
|
||||
from bemani.frontend.static import static_location
|
||||
from bemani.frontend.types import g
|
||||
|
||||
|
||||
danevo_pages = Blueprint(
|
||||
"danevo_pages",
|
||||
__name__,
|
||||
url_prefix=f"/{GameConstants.DANCE_EVOLUTION.value}",
|
||||
template_folder=templates_location,
|
||||
static_folder=static_location,
|
||||
)
|
||||
|
||||
|
||||
@danevo_pages.route("/records")
|
||||
@loginrequired
|
||||
def viewnetworkrecords() -> Response:
|
||||
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
|
||||
network_records = frontend.get_network_records()
|
||||
versions = {version: name for (game, version, name) in frontend.all_games()}
|
||||
|
||||
return render_react(
|
||||
"Global Dance Evolution Records",
|
||||
"danevo/records.react.js",
|
||||
{
|
||||
"records": network_records["records"],
|
||||
"songs": frontend.get_all_songs(),
|
||||
"players": network_records["players"],
|
||||
"versions": versions,
|
||||
"shownames": True,
|
||||
"showpersonalsort": False,
|
||||
"filterempty": False,
|
||||
},
|
||||
{
|
||||
"refresh": url_for("danevo_pages.listnetworkrecords"),
|
||||
"player": url_for("danevo_pages.viewplayer", userid=-1),
|
||||
"individual_score": url_for("danevo_pages.viewtopscores", musicid=-1),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@danevo_pages.route("/records/list")
|
||||
@jsonify
|
||||
@loginrequired
|
||||
def listnetworkrecords() -> Dict[str, Any]:
|
||||
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
|
||||
return frontend.get_network_records()
|
||||
|
||||
|
||||
@danevo_pages.route("/records/<int:userid>")
|
||||
@loginrequired
|
||||
def viewrecords(userid: UserID) -> Response:
|
||||
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
|
||||
info = frontend.get_latest_player_info([userid]).get(userid)
|
||||
if info is None:
|
||||
abort(404)
|
||||
versions = {version: name for (game, version, name) in frontend.all_games()}
|
||||
|
||||
return render_react(
|
||||
f'{info["name"]}\'s Dance Evolution Records',
|
||||
"danevo/records.react.js",
|
||||
{
|
||||
"records": frontend.get_records(userid),
|
||||
"songs": frontend.get_all_songs(),
|
||||
"players": {},
|
||||
"versions": versions,
|
||||
"shownames": False,
|
||||
"showpersonalsort": True,
|
||||
"filterempty": True,
|
||||
},
|
||||
{
|
||||
"refresh": url_for("danevo_pages.listrecords", userid=userid),
|
||||
"player": url_for("danevo_pages.viewplayer", userid=-1),
|
||||
"individual_score": url_for("danevo_pages.viewtopscores", musicid=-1),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@danevo_pages.route("/records/<int:userid>/list")
|
||||
@jsonify
|
||||
@loginrequired
|
||||
def listrecords(userid: UserID) -> Dict[str, Any]:
|
||||
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
|
||||
return {
|
||||
"records": frontend.get_records(userid),
|
||||
"players": {},
|
||||
}
|
||||
|
||||
|
||||
@danevo_pages.route("/topscores/<int:musicid>")
|
||||
@loginrequired
|
||||
def viewtopscores(musicid: int) -> Response:
|
||||
# We just want to find the latest mix that this song exists in
|
||||
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
|
||||
versions = sorted(
|
||||
[version for (game, version, name) in frontend.all_games()],
|
||||
reverse=True,
|
||||
)
|
||||
name = None
|
||||
artist = None
|
||||
genre = None
|
||||
levels = [0, 0, 0, 0, 0]
|
||||
|
||||
for version in versions:
|
||||
for chart in [0, 1, 2, 3, 4]:
|
||||
details = g.data.local.music.get_song(GameConstants.DANCE_EVOLUTION, version, musicid, chart)
|
||||
if details is not None:
|
||||
if name is None:
|
||||
name = details.name
|
||||
if artist is None:
|
||||
artist = details.artist
|
||||
if genre is None:
|
||||
genre = details.genre
|
||||
if levels[chart] == 0:
|
||||
levels[chart] = details.data.get_int("level")
|
||||
|
||||
if name is None or not [x for x in levels if x > 0]:
|
||||
# Not a real song!
|
||||
abort(404)
|
||||
|
||||
top_scores = frontend.get_top_scores(musicid)
|
||||
|
||||
return render_react(
|
||||
f"Top Dance Evolution Scores for {artist} - {name}",
|
||||
"danevo/topscores.react.js",
|
||||
{
|
||||
"name": name,
|
||||
"artist": artist,
|
||||
"genre": genre,
|
||||
"levels": levels,
|
||||
"players": top_scores["players"],
|
||||
"topscores": top_scores["topscores"],
|
||||
},
|
||||
{
|
||||
"refresh": url_for("danevo_pages.listtopscores", musicid=musicid),
|
||||
"player": url_for("danevo_pages.viewplayer", userid=-1),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@danevo_pages.route("/topscores/<int:musicid>/list")
|
||||
@jsonify
|
||||
@loginrequired
|
||||
def listtopscores(musicid: int) -> Dict[str, Any]:
|
||||
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
|
||||
return frontend.get_top_scores(musicid)
|
||||
|
||||
|
||||
@danevo_pages.route("/players")
|
||||
@loginrequired
|
||||
def viewplayers() -> Response:
|
||||
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
|
||||
return render_react(
|
||||
"All Dance Evolution Players",
|
||||
"danevo/allplayers.react.js",
|
||||
{"players": frontend.get_all_players()},
|
||||
{
|
||||
"refresh": url_for("danevo_pages.listplayers"),
|
||||
"player": url_for("danevo_pages.viewplayer", userid=-1),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@danevo_pages.route("/players/list")
|
||||
@jsonify
|
||||
@loginrequired
|
||||
def listplayers() -> Dict[str, Any]:
|
||||
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
|
||||
return {
|
||||
"players": frontend.get_all_players(),
|
||||
}
|
||||
|
||||
|
||||
@danevo_pages.route("/players/<int:userid>")
|
||||
@loginrequired
|
||||
def viewplayer(userid: UserID) -> Response:
|
||||
frontend = DanceEvolutionFrontend(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 Dance Evolution Profile',
|
||||
"danevo/player.react.js",
|
||||
{
|
||||
"playerid": userid,
|
||||
"own_profile": userid == g.userID,
|
||||
"player": info,
|
||||
"versions": {version: name for (game, version, name) in frontend.all_games()},
|
||||
},
|
||||
{
|
||||
"refresh": url_for("danevo_pages.listplayer", userid=userid),
|
||||
"records": url_for("danevo_pages.viewrecords", userid=userid),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@danevo_pages.route("/players/<int:userid>/list")
|
||||
@jsonify
|
||||
@loginrequired
|
||||
def listplayer(userid: UserID) -> Dict[str, Any]:
|
||||
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
|
||||
info = frontend.get_all_player_info([userid])[userid]
|
||||
|
||||
return {
|
||||
"player": info,
|
||||
}
|
||||
|
||||
|
||||
@danevo_pages.route("/options")
|
||||
@loginrequired
|
||||
def viewsettings() -> Response:
|
||||
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
|
||||
userid = g.userID
|
||||
info = frontend.get_all_player_info([userid])[userid]
|
||||
if not info:
|
||||
abort(404)
|
||||
|
||||
return render_react(
|
||||
"Dance Evolution Game Settings",
|
||||
"danevo/settings.react.js",
|
||||
{
|
||||
"player": info,
|
||||
"versions": {version: name for (game, version, name) in frontend.all_games()},
|
||||
},
|
||||
{
|
||||
"updatename": url_for("danevo_pages.updatename"),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@danevo_pages.route("/options/name/update", methods=["POST"])
|
||||
@jsonify
|
||||
@loginrequired
|
||||
def updatename() -> Dict[str, Any]:
|
||||
version = int(request.get_json()["version"])
|
||||
name = request.get_json()["name"]
|
||||
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 name
|
||||
profile = g.data.local.user.get_profile(GameConstants.DANCE_EVOLUTION, version, user.id)
|
||||
if profile is None:
|
||||
raise Exception("Unable to find profile to update!")
|
||||
if len(name) == 0 or len(name) > 10:
|
||||
raise Exception("Invalid profile name!")
|
||||
if (
|
||||
re.match(
|
||||
"^["
|
||||
+ "\uff20-\uff3a"
|
||||
+ "\uff41-\uff5a" # widetext A-Z and @
|
||||
+ "\uff10-\uff19" # widetext a-z
|
||||
+ "\uff0c\uff0e\uff3f" # widetext 0-9
|
||||
+ "\u3041-\u308d\u308f\u3092\u3093" # widetext ,._
|
||||
+ "\u30a1-\u30ed\u30ef\u30f2\u30f3\u30fc" # hiragana
|
||||
+ "\u2605\u266a" # allowed symbols
|
||||
+ "]*$", # katakana
|
||||
name,
|
||||
)
|
||||
is None
|
||||
):
|
||||
raise Exception("Invalid profile name!")
|
||||
profile.replace_str("name", name)
|
||||
g.data.local.user.put_profile(GameConstants.DANCE_EVOLUTION, version, user.id, profile)
|
||||
|
||||
# Return that we updated
|
||||
return {
|
||||
"version": version,
|
||||
"name": name,
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*** @jsx React.DOM */
|
||||
|
||||
var all_players = createReactClass({
|
||||
|
||||
getInitialState: function(props) {
|
||||
return {
|
||||
players: window.players,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.refreshPlayers();
|
||||
},
|
||||
|
||||
refreshPlayers: function() {
|
||||
AJAX.get(
|
||||
Link.get('refresh'),
|
||||
function(response) {
|
||||
this.setState({
|
||||
players: response.players,
|
||||
});
|
||||
// Refresh every 30 seconds
|
||||
setTimeout(this.refreshPlayers, 30000);
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<div className="section">
|
||||
<Table
|
||||
className="list players"
|
||||
columns={[
|
||||
{
|
||||
name: 'Name',
|
||||
render: function(userid) {
|
||||
var player = this.state.players[userid];
|
||||
return <a href={Link.get('player', userid)}>{ player.name }</a>;
|
||||
}.bind(this),
|
||||
sort: function(aid, bid) {
|
||||
var a = this.state.players[aid];
|
||||
var b = this.state.players[bid];
|
||||
return a.name.localeCompare(b.name);
|
||||
}.bind(this),
|
||||
},
|
||||
{
|
||||
name: 'Dance Evolution ID',
|
||||
render: function(userid) {
|
||||
var player = this.state.players[userid];
|
||||
return player.extid;
|
||||
}.bind(this),
|
||||
sort: function(aid, bid) {
|
||||
var a = this.state.players[aid];
|
||||
var b = this.state.players[bid];
|
||||
return a.extid.localeCompare(b.extid);
|
||||
}.bind(this),
|
||||
},
|
||||
{
|
||||
name: 'Total Rounds',
|
||||
render: function(userid) {
|
||||
var player = this.state.players[userid];
|
||||
return player.plays;
|
||||
}.bind(this),
|
||||
sort: function(aid, bid) {
|
||||
var a = this.state.players[aid];
|
||||
var b = this.state.players[bid];
|
||||
return a.plays - b.plays;
|
||||
}.bind(this),
|
||||
reverse: true,
|
||||
},
|
||||
{
|
||||
name: 'Class',
|
||||
render: function(userid) {
|
||||
var player = this.state.players[userid];
|
||||
return player.player_class;
|
||||
}.bind(this),
|
||||
sort: function(aid, bid) {
|
||||
var a = this.state.players[aid];
|
||||
var b = this.state.players[bid];
|
||||
return a.player_class - b.player_class;
|
||||
}.bind(this),
|
||||
},
|
||||
]}
|
||||
rows={Object.keys(this.state.players)}
|
||||
paginate={10}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
React.createElement(all_players, null),
|
||||
document.getElementById('content')
|
||||
);
|
||||
113
bemani/frontend/static/controllers/danevo/player.react.js
Normal file
113
bemani/frontend/static/controllers/danevo/player.react.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*** @jsx React.DOM */
|
||||
|
||||
var valid_versions = Object.keys(window.versions);
|
||||
var pagenav = new History(valid_versions);
|
||||
|
||||
var profile_view = createReactClass({
|
||||
|
||||
getInitialState: function(props) {
|
||||
var profiles = Object.keys(window.player);
|
||||
return {
|
||||
player: window.player,
|
||||
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)
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.player[this.state.version]) {
|
||||
var player = this.state.player[this.state.version];
|
||||
return (
|
||||
<div>
|
||||
<div className="section danevo-nav">
|
||||
<h3>{player.name}'s profile</h3>
|
||||
{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))}
|
||||
</div>
|
||||
<div className="section">
|
||||
<LabelledSection label="User ID">{player.extid}</LabelledSection>
|
||||
<LabelledSection label="Profile Created">
|
||||
<Timestamp timestamp={player.first_play_time}/>
|
||||
</LabelledSection>
|
||||
<LabelledSection label="Last Played">
|
||||
<Timestamp timestamp={player.last_play_time}/>
|
||||
</LabelledSection>
|
||||
<LabelledSection label="Total Rounds">
|
||||
{player.plays}回
|
||||
</LabelledSection>
|
||||
<LabelledSection label="Class">
|
||||
{player.player_class}
|
||||
</LabelledSection>
|
||||
</div>
|
||||
<div className="section">
|
||||
<a href={Link.get('records')}>{ window.own_profile ?
|
||||
<span>view your records</span> :
|
||||
<span>view {player.name}'s records</span>
|
||||
}</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
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))}
|
||||
</div>
|
||||
<div className="section">
|
||||
This player has no profile for {window.versions[this.state.version]}!
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
React.createElement(profile_view, null),
|
||||
document.getElementById('content')
|
||||
);
|
||||
529
bemani/frontend/static/controllers/danevo/records.react.js
Normal file
529
bemani/frontend/static/controllers/danevo/records.react.js
Normal file
|
|
@ -0,0 +1,529 @@
|
|||
/*** @jsx React.DOM */
|
||||
|
||||
var valid_sorts = ['series', 'name', 'popularity'];
|
||||
var valid_charts = ['Light', 'Standard', 'Extreme', 'Master', 'Stealth'];
|
||||
var valid_mixes = Object.keys(window.versions);
|
||||
var valid_subsorts = [valid_mixes, false, false, valid_charts, valid_charts];
|
||||
if (window.showpersonalsort) {
|
||||
valid_sorts.push('score');
|
||||
valid_sorts.push('grade');
|
||||
}
|
||||
var pagenav = new History(valid_sorts, valid_subsorts);
|
||||
var sort_names = {
|
||||
'series': 'Series',
|
||||
'name': 'Song Name',
|
||||
'popularity': 'Popularity',
|
||||
'score': 'Score',
|
||||
'grade': 'Grade',
|
||||
};
|
||||
|
||||
var HighScore = createReactClass({
|
||||
render: function() {
|
||||
if (!this.props.score) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="score">
|
||||
<div>
|
||||
<span className="label">Score</span>
|
||||
<span className="score">{this.props.score.points}</span>
|
||||
<span className="label">Grade</span>
|
||||
<span className="score">{this.props.score.grade}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="label">Combo</span>
|
||||
<span className="score">{this.props.score.combo <= 0 ? '-' : this.props.score.combo}</span>
|
||||
{ this.props.score.full_combo ? <span className="label">full combo</span> : null }
|
||||
</div>
|
||||
{ this.props.score.userid && window.shownames ?
|
||||
<div><a href={Link.get('player', this.props.score.userid)}>{
|
||||
this.props.players[this.props.score.userid].name
|
||||
}</a></div> : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var network_records = createReactClass({
|
||||
|
||||
sortRecords: function(records) {
|
||||
var sorted_records = {};
|
||||
|
||||
records.forEach(function(record) {
|
||||
if (!(record.songid in sorted_records)) {
|
||||
sorted_records[record.songid] = {}
|
||||
}
|
||||
sorted_records[record.songid][record.chart] = record;
|
||||
});
|
||||
|
||||
return sorted_records;
|
||||
},
|
||||
|
||||
getInitialState: function(props) {
|
||||
return {
|
||||
songs: window.songs,
|
||||
records: this.sortRecords(window.records),
|
||||
players: window.players,
|
||||
versions: window.versions,
|
||||
sort: pagenav.getInitialState('series', '1'),
|
||||
subtab: this.getSubIndex('series', pagenav.getInitialSubState('series', '1')),
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
};
|
||||
},
|
||||
|
||||
getSubIndex: function(sort, subsort) {
|
||||
var subtab = 0;
|
||||
window.valid_sorts.forEach(function(potential, index) {
|
||||
if (window.valid_subsorts[index]) {
|
||||
window.valid_subsorts[index].forEach(function(subpotential, subindex) {
|
||||
if (subpotential == subsort) {
|
||||
subtab = subindex;
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
}.bind(this));
|
||||
return subtab;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
pagenav.onChange(function(sort, subsort) {
|
||||
var subtab = this.getSubIndex(sort, subsort);
|
||||
this.setState({sort: sort, offset: 0, subtab: subtab});
|
||||
}.bind(this));
|
||||
this.refreshRecords();
|
||||
},
|
||||
|
||||
refreshRecords: function() {
|
||||
AJAX.get(
|
||||
Link.get('refresh'),
|
||||
function(response) {
|
||||
this.setState({
|
||||
records: this.sortRecords(response.records),
|
||||
players: response.players,
|
||||
});
|
||||
// Refresh every 15 seconds
|
||||
setTimeout(this.refreshRecords, 15000);
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
renderLevel: function(songid, chart) {
|
||||
if (this.state.songs[songid].levels[chart] == 0) {
|
||||
return <span className="level">--</span>;
|
||||
} else {
|
||||
return <span className="level">{this.state.songs[songid].levels[chart]}</span>;
|
||||
}
|
||||
},
|
||||
|
||||
getPlays: function(record) {
|
||||
if (!record) { return 0; }
|
||||
var plays = 0;
|
||||
|
||||
// Play counts are only storted in the play statistics chart.
|
||||
if (record[5]) { plays += record[5].plays; }
|
||||
|
||||
return plays;
|
||||
},
|
||||
|
||||
renderBySeries: function() {
|
||||
var songids = Object.keys(this.state.songs);
|
||||
if (window.filterempty) {
|
||||
songids = songids.filter(function(songid) {
|
||||
return this.getPlays(this.state.records[songid]) > 0;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
if (songids.length == 0) {
|
||||
return (
|
||||
<div>
|
||||
No records to display!
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var curpage = -1;
|
||||
var curbutton = -1;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="section" key="contents">
|
||||
<table className="list records">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="subheader">Song / Artist / Level</th>
|
||||
<th className="subheader">Light</th>
|
||||
<th className="subheader">Standard</th>
|
||||
<th className="subheader">Extreme</th>
|
||||
<th className="subheader">Master</th>
|
||||
<th className="subheader">Stealth</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{songids.map(function(songid) {
|
||||
var records = this.state.records[songid];
|
||||
if (!records) {
|
||||
records = {};
|
||||
}
|
||||
|
||||
var levels = this.state.songs[songid].levels;
|
||||
return (
|
||||
<tr key={songid.toString()}>
|
||||
<td className="center">
|
||||
<div>
|
||||
<a href={Link.get('individual_score', songid)}>
|
||||
<div className="songname">{ this.state.songs[songid].name }</div>
|
||||
<div className="songartist">{ this.state.songs[songid].artist }</div>
|
||||
<div className="songgenre">{ this.state.songs[songid].genre }</div>
|
||||
</a>
|
||||
</div>
|
||||
<div className="songlevels">
|
||||
Level {this.renderLevel(songid, 0)}
|
||||
</div>
|
||||
</td>
|
||||
<td className={levels[0] > 0 ? "" : "nochart"}>
|
||||
<HighScore
|
||||
players={this.state.players}
|
||||
songid={songid}
|
||||
chart={0}
|
||||
score={records[0]}
|
||||
/>
|
||||
</td>
|
||||
<td className={levels[1] > 0 ? "" : "nochart"}>
|
||||
<HighScore
|
||||
players={this.state.players}
|
||||
songid={songid}
|
||||
chart={1}
|
||||
score={records[1]}
|
||||
/>
|
||||
</td>
|
||||
<td className={levels[2] > 0 ? "" : "nochart"}>
|
||||
<HighScore
|
||||
players={this.state.players}
|
||||
songid={songid}
|
||||
chart={2}
|
||||
score={records[2]}
|
||||
/>
|
||||
</td>
|
||||
<td className={levels[4] > 0 ? "" : "nochart"}>
|
||||
<HighScore
|
||||
players={this.state.players}
|
||||
songid={songid}
|
||||
chart={4}
|
||||
score={records[4]}
|
||||
/>
|
||||
</td>
|
||||
<td className={levels[3] > 0 ? "" : "nochart"}>
|
||||
<HighScore
|
||||
players={this.state.players}
|
||||
songid={songid}
|
||||
chart={3}
|
||||
score={records[3]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}.bind(this))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
||||
renderByName: function() {
|
||||
var songids = Object.keys(this.state.songs).sort(function(a, b) {
|
||||
var an = this.state.songs[a].name;
|
||||
var bn = this.state.songs[b].name;
|
||||
var c = an.localeCompare(bn);
|
||||
if (c == 0) {
|
||||
return parseInt(a) - parseInt(b)
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}.bind(this));
|
||||
if (window.filterempty) {
|
||||
songids = songids.filter(function(songid) {
|
||||
return this.getPlays(this.state.records[songid]) > 0;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
return this.renderBySongIDList(songids, false);
|
||||
},
|
||||
|
||||
renderByPopularity: function() {
|
||||
var songids = Object.keys(this.state.songs).sort(function(a, b) {
|
||||
var ap = this.getPlays(this.state.records[a]);
|
||||
var bp = this.getPlays(this.state.records[b]);
|
||||
if (bp == ap) {
|
||||
return parseInt(a) - parseInt(b)
|
||||
} else {
|
||||
return bp - ap;
|
||||
}
|
||||
}.bind(this));
|
||||
if (window.filterempty) {
|
||||
songids = songids.filter(function(songid) {
|
||||
return this.getPlays(this.state.records[songid]) > 0;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
return this.renderBySongIDList(songids, true);
|
||||
},
|
||||
|
||||
renderByScore: function() {
|
||||
var songids = Object.keys(this.state.songs).sort(function(a, b) {
|
||||
// Grab records for this song
|
||||
var ar = this.state.records[a];
|
||||
var br = this.state.records[b];
|
||||
var ac = null;
|
||||
var bc = null;
|
||||
var as = 0;
|
||||
var bs = 0;
|
||||
|
||||
// Fill in record for current chart only if it exists
|
||||
if (ar) { ac = ar[this.state.subtab]; }
|
||||
if (br) { bc = br[this.state.subtab]; }
|
||||
if (ac) { as = ac.points; }
|
||||
if (bc) { bs = bc.points; }
|
||||
|
||||
if (bs == as) {
|
||||
return parseInt(a) - parseInt(b);
|
||||
} else {
|
||||
return bs - as;
|
||||
}
|
||||
}.bind(this));
|
||||
if (window.filterempty) {
|
||||
songids = songids.filter(function(songid) {
|
||||
return this.getPlays(this.state.records[songid]) > 0;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="section">
|
||||
{window.valid_charts.map(function(chartname, index) {
|
||||
return (
|
||||
<Nav
|
||||
title={ chartname }
|
||||
active={ this.state.subtab == index }
|
||||
onClick={function(event) {
|
||||
if (this.state.subtab == index) { return; }
|
||||
this.setState({subtab: index, offset: 0});
|
||||
pagenav.navigate(this.state.sort, window.valid_charts[index]);
|
||||
}.bind(this)}
|
||||
/>
|
||||
);
|
||||
}.bind(this))}
|
||||
</div>
|
||||
{ this.renderBySongIDList(songids, false) }
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
||||
renderByClearGrade: function() {
|
||||
var songids = Object.keys(this.state.songs).sort(function(a, b) {
|
||||
// Grab records for this song
|
||||
var ar = this.state.records[a];
|
||||
var br = this.state.records[b];
|
||||
var ac = null;
|
||||
var bc = null;
|
||||
var al = 0;
|
||||
var bl = 0;
|
||||
|
||||
// Fill in record for current chart only if it exists
|
||||
if (ar) { ac = ar[this.state.subtab]; }
|
||||
if (br) { bc = br[this.state.subtab]; }
|
||||
if (ac) { al = ac.medal; }
|
||||
if (bc) { bl = bc.medal; }
|
||||
|
||||
if (al == bl) {
|
||||
return parseInt(a) - parseInt(b)
|
||||
} else {
|
||||
return bl - al;
|
||||
}
|
||||
}.bind(this));
|
||||
if (window.filterempty) {
|
||||
songids = songids.filter(function(songid) {
|
||||
return this.getPlays(this.state.records[songid]) > 0;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="section">
|
||||
{window.valid_charts.map(function(chartname, index) {
|
||||
return (
|
||||
<Nav
|
||||
title={ chartname }
|
||||
active={ this.state.subtab == index }
|
||||
onClick={function(event) {
|
||||
if (this.state.subtab == index) { return; }
|
||||
this.setState({subtab: index, offset: 0});
|
||||
pagenav.navigate(this.state.sort, window.valid_charts[index]);
|
||||
}.bind(this)}
|
||||
/>
|
||||
);
|
||||
}.bind(this))}
|
||||
</div>
|
||||
{ this.renderBySongIDList(songids, false) }
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
||||
renderBySongIDList: function(songids, showplays) {
|
||||
return (
|
||||
<div className="section">
|
||||
<table className="list records">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="subheader">Song / Artist / Level</th>
|
||||
<th className="subheader">Light</th>
|
||||
<th className="subheader">Standard</th>
|
||||
<th className="subheader">Extreme</th>
|
||||
<th className="subheader">Master</th>
|
||||
<th className="subheader">Stealth</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{songids.map(function(songid, index) {
|
||||
if (index < this.state.offset || index >= this.state.offset + this.state.limit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var records = this.state.records[songid];
|
||||
if (!records) {
|
||||
records = {};
|
||||
}
|
||||
|
||||
var plays = this.getPlays(records);
|
||||
var levels = this.state.songs[songid].levels;
|
||||
return (
|
||||
<tr key={songid.toString()}>
|
||||
<td className="center">
|
||||
<div>
|
||||
<a href={Link.get('individual_score', songid)}>
|
||||
<div className="songname">{ this.state.songs[songid].name }</div>
|
||||
<div className="songartist">{ this.state.songs[songid].artist }</div>
|
||||
<div className="songgenre">{ this.state.songs[songid].genre }</div>
|
||||
</a>
|
||||
</div>
|
||||
<div className="songlevels">
|
||||
Level {this.renderLevel(songid, 0)}
|
||||
</div>
|
||||
{ showplays ? <div className="songplays">#{index + 1} - {plays}{plays == 1 ? ' play' : ' plays'}</div> : null }
|
||||
</td>
|
||||
<td className={levels[0] > 0 ? "" : "nochart"}>
|
||||
<HighScore
|
||||
players={this.state.players}
|
||||
songid={songid}
|
||||
chart={0}
|
||||
score={records[0]}
|
||||
/>
|
||||
</td>
|
||||
<td className={levels[1] > 0 ? "" : "nochart"}>
|
||||
<HighScore
|
||||
players={this.state.players}
|
||||
songid={songid}
|
||||
chart={1}
|
||||
score={records[1]}
|
||||
/>
|
||||
</td>
|
||||
<td className={levels[2] > 0 ? "" : "nochart"}>
|
||||
<HighScore
|
||||
players={this.state.players}
|
||||
songid={songid}
|
||||
chart={2}
|
||||
score={records[2]}
|
||||
/>
|
||||
</td>
|
||||
<td className={levels[4] > 0 ? "" : "nochart"}>
|
||||
<HighScore
|
||||
players={this.state.players}
|
||||
songid={songid}
|
||||
chart={4}
|
||||
score={records[4]}
|
||||
/>
|
||||
</td>
|
||||
<td className={levels[3] > 0 ? "" : "nochart"}>
|
||||
<HighScore
|
||||
players={this.state.players}
|
||||
songid={songid}
|
||||
chart={3}
|
||||
score={records[3]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}.bind(this))}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colSpan={5}>
|
||||
{ this.state.offset > 0 ?
|
||||
<Prev onClick={function(event) {
|
||||
var page = this.state.offset - this.state.limit;
|
||||
if (page < 0) { page = 0; }
|
||||
this.setState({offset: page});
|
||||
}.bind(this)}/> : null
|
||||
}
|
||||
{ (this.state.offset + this.state.limit) < songids.length ?
|
||||
<Next style={ {float: 'right'} } onClick={function(event) {
|
||||
var page = this.state.offset + this.state.limit;
|
||||
if (page >= songids.length) { return }
|
||||
this.setState({offset: page});
|
||||
}.bind(this)}/> :
|
||||
null
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = null;
|
||||
if (this.state.sort == 'series') {
|
||||
data = this.renderBySeries();
|
||||
} else if (this.state.sort == 'popularity') {
|
||||
data = this.renderByPopularity();
|
||||
} else if (this.state.sort == 'name') {
|
||||
data = this.renderByName();
|
||||
} else if (this.state.sort == 'score') {
|
||||
data = this.renderByScore();
|
||||
} else if (this.state.sort == 'grade') {
|
||||
data = this.renderByClearGrade();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="section">
|
||||
{ window.valid_sorts.map(function(sort, index) {
|
||||
return (
|
||||
<Nav
|
||||
title={"Records Sorted by " + window.sort_names[sort]}
|
||||
active={this.state.sort == sort}
|
||||
onClick={function(event) {
|
||||
if (this.state.sort == sort) { return; }
|
||||
this.setState({sort: sort, offset: 0, subtab: 0});
|
||||
pagenav.navigate(sort, window.valid_subsorts[index][0]);
|
||||
}.bind(this)}
|
||||
/>
|
||||
);
|
||||
}.bind(this)) }
|
||||
</div>
|
||||
<div className="section">
|
||||
{data}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
React.createElement(network_records, null),
|
||||
document.getElementById('content')
|
||||
);
|
||||
195
bemani/frontend/static/controllers/danevo/settings.react.js
Normal file
195
bemani/frontend/static/controllers/danevo/settings.react.js
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/*** @jsx React.DOM */
|
||||
|
||||
var valid_versions = Object.keys(window.versions);
|
||||
var pagenav = new History(valid_versions);
|
||||
|
||||
var settings_view = createReactClass({
|
||||
|
||||
getInitialState: function(props) {
|
||||
var profiles = Object.keys(window.player);
|
||||
var version = pagenav.getInitialState(profiles[profiles.length - 1]);
|
||||
return {
|
||||
player: window.player,
|
||||
profiles: profiles,
|
||||
version: version,
|
||||
new_name: window.player[version].name,
|
||||
editing_name: false,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
pagenav.onChange(function(version) {
|
||||
this.setState({version: version});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
if (this.focus_element && this.focus_element != this.already_focused) {
|
||||
this.focus_element.focus();
|
||||
this.already_focused = this.focus_element;
|
||||
}
|
||||
},
|
||||
|
||||
saveName: function(event) {
|
||||
AJAX.post(
|
||||
Link.get('updatename'),
|
||||
{
|
||||
version: this.state.version,
|
||||
name: this.state.new_name,
|
||||
},
|
||||
function(response) {
|
||||
var player = this.state.player;
|
||||
player[response.version].name = response.name;
|
||||
this.setState({
|
||||
player: player,
|
||||
new_name: this.state.player[response.version].name,
|
||||
editing_name: false,
|
||||
});
|
||||
}.bind(this)
|
||||
);
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
renderName: function(player) {
|
||||
return (
|
||||
<LabelledSection vertical={true} label="Name">{
|
||||
!this.state.editing_name ?
|
||||
<>
|
||||
<span>{player.name}</span>
|
||||
<Edit
|
||||
onClick={function(event) {
|
||||
this.setState({editing_name: true});
|
||||
}.bind(this)}
|
||||
/>
|
||||
</> :
|
||||
<form className="inline" onSubmit={this.saveName}>
|
||||
<input
|
||||
type="text"
|
||||
className="inline"
|
||||
maxlength="10"
|
||||
size="12"
|
||||
autofocus="true"
|
||||
ref={c => (this.focus_element = c)}
|
||||
value={this.state.new_name}
|
||||
onChange={function(event) {
|
||||
var rawvalue = event.target.value;
|
||||
var value = "";
|
||||
// Nasty conversion to change typing into wide text
|
||||
for (var i = 0; i < rawvalue.length; i++) {
|
||||
var c = rawvalue.charCodeAt(i);
|
||||
if (c >= '0'.charCodeAt(0) && c <= '9'.charCodeAt(0)) {
|
||||
c = 0xFF10 + (c - '0'.charCodeAt(0));
|
||||
} else if(c >= 'A'.charCodeAt(0) && c <= 'Z'.charCodeAt(0)) {
|
||||
c = 0xFF21 + (c - 'A'.charCodeAt(0));
|
||||
} else if(c >= 'a'.charCodeAt(0) && c <= 'z'.charCodeAt(0)) {
|
||||
c = 0xFF41 + (c - 'a'.charCodeAt(0));
|
||||
} else if(c == '@'.charCodeAt(0)) {
|
||||
c = 0xFF20;
|
||||
} else if(c == ','.charCodeAt(0)) {
|
||||
c = 0xFF0C;
|
||||
} else if(c == '.'.charCodeAt(0)) {
|
||||
c = 0xFF0E;
|
||||
} else if(c == '_'.charCodeAt(0)) {
|
||||
c = 0xFF3F;
|
||||
}
|
||||
value = value + String.fromCharCode(c);
|
||||
}
|
||||
var nameRegex = new RegExp(
|
||||
"^[" +
|
||||
"\uFF20-\uFF3A" + // widetext A-Z and @
|
||||
"\uFF41-\uFF5A" + // widetext a-z
|
||||
"\uFF10-\uFF19" + // widetext 0-9
|
||||
"\uFF0C\uFF0E\uFF3F" + // widetext ,._
|
||||
"\u3041-\u308D\u308F\u3092\u3093" + // hiragana
|
||||
"\u30A1-\u30ED\u30EF\u30F2\u30F3\u30FC" + // katakana
|
||||
"\u2605\u266A" + // allowed symbols
|
||||
"]*$"
|
||||
);
|
||||
if (value.length <= 10 && nameRegex.test(value)) {
|
||||
this.setState({new_name: value});
|
||||
}
|
||||
}.bind(this)}
|
||||
name="name"
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
value="save"
|
||||
/>
|
||||
<input
|
||||
type="button"
|
||||
value="cancel"
|
||||
onClick={function(event) {
|
||||
this.setState({
|
||||
new_name: this.state.player[this.state.version].name,
|
||||
editing_name: false,
|
||||
});
|
||||
}.bind(this)}
|
||||
/>
|
||||
</form>
|
||||
}</LabelledSection>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.player[this.state.version]) {
|
||||
var player = this.state.player[this.state.version];
|
||||
return (
|
||||
<div>
|
||||
<div className="section danevo-nav">
|
||||
{this.state.profiles.map(function(version) {
|
||||
return (
|
||||
<Nav
|
||||
title={window.versions[version]}
|
||||
active={this.state.version == version}
|
||||
onClick={function(event) {
|
||||
if (this.state.editing_name) { return; }
|
||||
if (this.state.version == version) { return; }
|
||||
this.setState({
|
||||
version: version,
|
||||
new_name: this.state.player[version].name,
|
||||
});
|
||||
pagenav.navigate(version);
|
||||
}.bind(this)}
|
||||
/>
|
||||
);
|
||||
}.bind(this))}
|
||||
</div>
|
||||
<div className="section">
|
||||
<h3>User Profile</h3>
|
||||
{this.renderName(player)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<div className="section">
|
||||
You have no profile for {window.versions[this.state.version]}!
|
||||
</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))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
React.createElement(settings_view, null),
|
||||
document.getElementById('content')
|
||||
);
|
||||
151
bemani/frontend/static/controllers/danevo/topscores.react.js
Normal file
151
bemani/frontend/static/controllers/danevo/topscores.react.js
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/*** @jsx React.DOM */
|
||||
|
||||
var valid_charts = ['Light', 'Standard', 'Extreme', 'Master', 'Stealth'];
|
||||
var pagenav = new History(valid_charts);
|
||||
|
||||
var top_scores = createReactClass({
|
||||
|
||||
sortTopScores: function(topscores) {
|
||||
var newscores = [[], [], [], [], []];
|
||||
topscores.map(function(score) {
|
||||
// Skip over the play stats dummy chart.
|
||||
if (score.chart != 5) {
|
||||
newscores[score.chart].push(score);
|
||||
}
|
||||
}.bind(this));
|
||||
return newscores;
|
||||
},
|
||||
|
||||
getInitialState: function(props) {
|
||||
return {
|
||||
topscores: this.sortTopScores(window.topscores),
|
||||
players: window.players,
|
||||
chart: pagenav.getInitialState(valid_charts[0]),
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
pagenav.onChange(function(chart) {
|
||||
this.setState({chart: chart});
|
||||
}.bind(this));
|
||||
this.refreshScores();
|
||||
},
|
||||
|
||||
refreshScores: function() {
|
||||
AJAX.get(
|
||||
Link.get('refresh'),
|
||||
function(response) {
|
||||
this.setState({
|
||||
topscores: this.sortTopScores(response.topscores),
|
||||
players: response.players,
|
||||
});
|
||||
// Refresh every 15 seconds
|
||||
setTimeout(this.refreshScores, 15000);
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
convertChart: function(chart) {
|
||||
switch(chart) {
|
||||
case 'Light':
|
||||
return 0;
|
||||
case 'Standard':
|
||||
return 1;
|
||||
case 'Extreme':
|
||||
return 2;
|
||||
case 'Stealth':
|
||||
return 3;
|
||||
case 'Master':
|
||||
return 4;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var chart = this.convertChart(this.state.chart);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="section">
|
||||
<div className="songname">{window.name}</div>
|
||||
<div className="songartist">{window.artist}</div>
|
||||
<div className="songgenre">{window.genre}</div>
|
||||
<div className="songlevels">Level {window.levels[chart]}</div>
|
||||
</div>
|
||||
<div className="section">
|
||||
{valid_charts.map(function(chart) {
|
||||
return (
|
||||
<Nav
|
||||
title={chart}
|
||||
active={this.state.chart == chart}
|
||||
onClick={function(event) {
|
||||
if (this.state.chart == chart) { return; }
|
||||
this.setState({chart: chart});
|
||||
pagenav.navigate(chart);
|
||||
}.bind(this)}
|
||||
/>
|
||||
);
|
||||
}.bind(this))}
|
||||
</div>
|
||||
<div className="section">
|
||||
<Table
|
||||
className="list topscores"
|
||||
columns={[
|
||||
{
|
||||
name: 'Name',
|
||||
render: function(topscore) {
|
||||
return (
|
||||
<a href={Link.get('player', topscore.userid)}>{
|
||||
this.state.players[topscore.userid].name
|
||||
}</a>
|
||||
);
|
||||
}.bind(this),
|
||||
sort: function(a, b) {
|
||||
var an = this.state.players[a.userid].name;
|
||||
var bn = this.state.players[b.userid].name;
|
||||
return an.localeCompare(bn);
|
||||
}.bind(this),
|
||||
},
|
||||
{
|
||||
name: 'Score',
|
||||
render: function(topscore) { return topscore.points; },
|
||||
sort: function(a, b) {
|
||||
return a.points - b.points;
|
||||
},
|
||||
reverse: true,
|
||||
},
|
||||
{
|
||||
name: 'Grade',
|
||||
render: function(topscore) { return topscore.grade; },
|
||||
},
|
||||
{
|
||||
name: 'Combo',
|
||||
render: function(topscore) {
|
||||
return (
|
||||
(topscore.combo >= 0 ? topscore.combo : '-') +
|
||||
(topscore.full_combo ? ' (full combo)' : '')
|
||||
);
|
||||
},
|
||||
sort: function(a, b) {
|
||||
return a.combo - b.combo;
|
||||
},
|
||||
reverse: true,
|
||||
},
|
||||
]}
|
||||
defaultsort='Score'
|
||||
rows={this.state.topscores[chart]}
|
||||
key={chart}
|
||||
paginate={10}
|
||||
emptymessage="There are no scores for this chart."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
React.createElement(top_scores, null),
|
||||
document.getElementById('content')
|
||||
);
|
||||
|
|
@ -33,3 +33,7 @@
|
|||
.sdvx.border {
|
||||
border-color: #710162;
|
||||
}
|
||||
|
||||
.danevo.border {
|
||||
border-color: #2e5eee;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,3 +33,7 @@
|
|||
.sdvx.border {
|
||||
border-color: #710162;
|
||||
}
|
||||
|
||||
.danevo.border {
|
||||
border-color: #2e5eee;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from bemani.frontend.ddr import ddr_pages
|
|||
from bemani.frontend.sdvx import sdvx_pages
|
||||
from bemani.frontend.reflec import reflec_pages
|
||||
from bemani.frontend.museca import museca_pages
|
||||
from bemani.frontend.danevo import danevo_pages
|
||||
from bemani.utils.config import (
|
||||
load_config as base_load_config,
|
||||
instantiate_cache as base_instantiate_cache,
|
||||
|
|
@ -47,7 +48,8 @@ def register_blueprints() -> None:
|
|||
app.register_blueprint(reflec_pages)
|
||||
if GameConstants.MUSECA in config.support:
|
||||
app.register_blueprint(museca_pages)
|
||||
# TODO: DanEvo frontend here.
|
||||
if GameConstants.DANCE_EVOLUTION in config.support:
|
||||
app.register_blueprint(danevo_pages)
|
||||
|
||||
|
||||
def register_games() -> None:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from bemani.frontend.ddr import DDRCache
|
|||
from bemani.frontend.sdvx import SoundVoltexCache
|
||||
from bemani.frontend.reflec import ReflecBeatCache
|
||||
from bemani.frontend.museca import MusecaCache
|
||||
from bemani.frontend.danevo import DanceEvolutionCache
|
||||
from bemani.common import GameConstants, Time
|
||||
from bemani.data import Config, Data
|
||||
from bemani.utils.config import load_config, instantiate_cache
|
||||
|
|
@ -60,7 +61,7 @@ def run_scheduled_work(config: Config) -> None:
|
|||
enabled_caches.append(MusecaCache)
|
||||
if GameConstants.DANCE_EVOLUTION in config.support:
|
||||
enabled_factories.append(DanceEvolutionFactory)
|
||||
# TODO: Frontend cache here.
|
||||
enabled_caches.append(DanceEvolutionCache)
|
||||
|
||||
# First, run any backend scheduled work
|
||||
for factory in enabled_factories:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user