Add Dance Mates view to frontend, remove useless version selection buttons for DanEvo.

This commit is contained in:
Jennifer Taylor 2025-10-02 02:58:31 +00:00
parent c27f39a52b
commit fc22d204a9
7 changed files with 148 additions and 35 deletions

View File

@ -426,6 +426,10 @@ def navigation() -> Dict[str, Any]:
"label": "Personal Profile",
"uri": url_for("danevo_pages.viewplayer", userid=g.userID),
},
{
"label": "Dance Mates",
"uri": url_for("danevo_pages.viewdancemates", userid=g.userID),
},
{
"label": "Personal Records",
"uri": url_for("danevo_pages.viewrecords", userid=g.userID),

View File

@ -3,7 +3,7 @@ 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.data import Attempt, Link, Score, Song, UserID
from bemani.frontend.base import FrontendBase
@ -20,7 +20,7 @@ class DanceEvolutionFrontend(FrontendBase):
DanceEvolutionBase.CHART_TYPE_PLAYTRACKING,
]
valid_rival_types: List[str] = []
valid_rival_types: List[str] = ["dancemate"]
def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]:
yield from DanceEvolutionFactory.all_games()
@ -87,3 +87,9 @@ class DanceEvolutionFrontend(FrontendBase):
if existing["levels"][new.chart] == 0:
new_song["levels"][new.chart] = new.data.get_int("level")
return new_song
def format_rival(self, link: Link, profile: Profile) -> Dict[str, Any]:
return {
"userid": str(link.other_userid),
"last_played": link.data.get_int("last_played"),
}

View File

@ -282,3 +282,55 @@ def updatename() -> Dict[str, Any]:
"version": version,
"name": name,
}
@danevo_pages.route("/dancemates/<int:userid>")
@loginrequired
def viewdancemates(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)
# Since we have one version of DanEvo this is an ugly hack.
dancemates_by_version, profiles = frontend.get_rivals(userid)
dancemates = []
for version, actual_dancemates in dancemates_by_version.items():
dancemates.extend(actual_dancemates)
return render_react(
f'{info["name"]}\'s Dance Evolution Dance Mates',
"danevo/dancemates.react.js",
{
"name": info["name"],
"version": version,
"dancemates": dancemates,
"profiles": profiles,
},
{
"refresh": url_for("danevo_pages.listdancemates", userid=userid),
},
)
@danevo_pages.route("/dancemates/<int:userid>/list")
@jsonify
@loginrequired
def listdancemates(userid: UserID) -> Dict[str, Any]:
frontend = DanceEvolutionFrontend(g.data, g.config, g.cache)
info = frontend.get_latest_player_info([userid]).get(userid)
if info is None:
abort(404)
# Since we have one version of DanEvo this is an ugly hack.
dancemates_by_version, profiles = frontend.get_rivals(userid)
dancemates = []
for version, actual_dancemates in dancemates_by_version.items():
dancemates.extend(actual_dancemates)
return {
"name": info["name"],
"version": version,
"dancemates": dancemates,
"profiles": profiles,
}

View File

@ -9,7 +9,7 @@ var Timestamp = createReactClass({
var t = new Date(this.props.timestamp * 1000);
var formatted = t.format('Y/m/d @ g:i:s a');
return (
<div className="timestamp">{ formatted }</div>
<div className={this.props.className || "timestamp"}>{ formatted }</div>
);
},
});

View File

@ -0,0 +1,83 @@
/*** @jsx React.DOM */
var dancemates_view = createReactClass({
getInitialState: function(props) {
return {
name: window.name,
version: window.version,
dancemates: window.dancemates,
profiles: window.profiles,
};
},
componentDidMount: function() {
this.refreshDanceMates();
},
refreshDanceMates: function() {
AJAX.get(
Link.get('refresh'),
function(response) {
this.setState({
name: response.name,
version: response.version,
dancemates: response.dancemates,
profiles: response.profiles,
});
setTimeout(this.refreshDanceMates, 5000);
}.bind(this)
);
},
renderDanceMates: function(player) {
return(
<Table
className='list dancemates'
columns={[
{
name: 'Name',
render: function(entry) {
return this.state.profiles[entry.userid][this.state.version].name;
}.bind(this),
sort: function(aid, bid) {
var a = this.state.profiles[aid.userid][this.state.version].name;
var b = this.state.profiles[bid.userid][this.state.version].name;
return a.localeCompare(b);
}.bind(this),
},
{
name: 'Last Played With',
render: function(entry) {
return <Timestamp className={"dancemate"} timestamp={entry.last_played}/>;
},
sort: function(aid, bid) {
return aid.last_played - bid.last_played;
}.bind(this),
},
]}
defaultsort='Name'
rows={this.state.dancemates}
paginate={10}
/>
);
},
render: function() {
return (
<div>
<section>
<h3>{this.state.name}'s Dance Mates</h3>
<p>
{this.renderDanceMates()}
</p>
</section>
</div>
);
},
});
ReactDOM.render(
React.createElement(dancemates_view, null),
document.getElementById('content')
);

View File

@ -43,19 +43,6 @@ var profile_view = createReactClass({
<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>

View File

@ -135,25 +135,6 @@ var settings_view = createReactClass({
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)}