1
0
mirror of synced 2025-01-18 22:24:04 +01:00

Massive overhaul to Jubeat frontend, including the following:

- Clan jubility breakdown
 - Displaying music rate and stats identically across all score pages
 - Ability to choose what jubility (legacy or new) to display and sort on all players.
 - Various tweaks and small quality of life improvements.
This commit is contained in:
Jennifer Taylor 2022-10-08 01:32:59 +00:00
parent 43d04121a0
commit 9feae49667
13 changed files with 503 additions and 225 deletions

View File

@ -146,13 +146,54 @@ class JubeatFrontend(FrontendBase):
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['jubility'] = (
profile.get_int('jubility')
if profile.version not in {VersionConstants.JUBEAT_PROP, VersionConstants.JUBEAT_QUBELL, VersionConstants.JUBEAT_FESTO}
else 0
)
formatted_profile['pick_up_jubility'] = (
profile.get_float('pick_up_jubility')
if profile.version == VersionConstants.JUBEAT_FESTO
else 0
)
formatted_profile['common_jubility'] = (
profile.get_float('common_jubility')
if profile.version == VersionConstants.JUBEAT_FESTO
else 0
)
if profile.version == VersionConstants.JUBEAT_FESTO:
# 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_chart'] = list(profile.get_dict('common_chart').values())
elif profile.version == VersionConstants.JUBEAT_CLAN:
# Look up achievements which is where jubility was stored. This is a bit of a hack
# due to the fact that this could be formatting remote profiles, but then they should
# have no achievements.
userid = self.data.local.user.from_refid(profile.game, profile.version, profile.refid)
if userid is not None:
achievements = self.data.local.user.get_achievements(profile.game, profile.version, userid)
else:
achievements = []
jubeat_entries: List[ValidatedDict] = []
for achievement in achievements:
if achievement.type != 'jubility':
continue
# Figure out for each song, what's the highest value jubility and
# keep that.
bestentry = ValidatedDict()
for chart in [0, 1, 2]:
entry = achievement.data.get_dict(str(chart))
if entry.get_int("value") >= bestentry.get_int("value"):
bestentry = entry.clone()
bestentry.replace_int("songid", achievement.id)
bestentry.replace_int("chart", chart)
jubeat_entries.append(bestentry)
jubeat_entries = sorted(jubeat_entries, key=lambda entry: entry.get_int("value"), reverse=True)[:30]
formatted_profile['chart'] = jubeat_entries
formatted_profile['ex_count'] = profile.get_int('ex_cnt')
formatted_profile['fc_count'] = profile.get_int('fc_cnt')
return formatted_profile

View File

@ -0,0 +1,27 @@
/** @jsx React.DOM */
var Slider = React.createClass({
render: function() {
return (
<div
className={"slider " + (this.props.value ? "on" : "off")}
onClick={function(event) {
event.preventDefault();
event.stopPropagation();
if (this.props.onChange) {
this.props.onChange(!this.props.value);
}
}.bind(this)}
>{ this.props.value ?
<span>
<span className="ball on"></span>
<span className="label on">{this.props.on}</span>
</span> :
<span>
<span className="label off">{this.props.off}</span>
<span className="ball off"></span>
</span>
}</div>
);
},
});

View File

@ -5,6 +5,7 @@ var all_players = React.createClass({
getInitialState: function(props) {
return {
players: window.players,
jubility: true,
};
},
@ -57,7 +58,7 @@ var all_players = React.createClass({
}.bind(this),
},
{
name: 'Play Count',
name: 'Total Rounds',
render: function(userid) {
var player = this.state.players[userid];
return player.plays;
@ -70,28 +71,50 @@ var all_players = React.createClass({
reverse: true,
},
{
name: 'Jubility',
name: <span>
Jubility
<span className="separator" />
<Slider
on="new"
off="old"
value={this.state.jubility}
onChange={function(value) {
this.setState({jubility: value});
}.bind(this)}
/>
</span>,
render: function(userid) {
var player = this.state.players[userid];
if (player.common_jubility != 0 || player.pick_up_jubility != 0) {
if (this.state.jubility && (player.common_jubility != 0 || player.pick_up_jubility != 0)) {
return (player.common_jubility + player.pick_up_jubility).toFixed(1);
} else if (player.jubility != 0) {
} else if (!this.state.jubility && player.jubility != 0) {
return player.jubility / 100
} else {
return 0
return "";
}
}.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;
if (this.state.jubility) {
if (a.common_jubility != 0 || a.pick_up_jubility != 0)
var ajub = a.common_jubility+a.pick_up_jubility;
else
var ajub = 0;
if (b.common_jubility != 0 || b.pick_up_jubility != 0)
var bjub = b.common_jubility+b.pick_up_jubility;
else
var bjub = 0;
} else {
if (a.jubility != 0)
var ajub = a.jubility / 100;
else
var ajub = 0;
if (b.jubility != 0)
var bjub = b.jubility / 100;
else
var bjub = 0;
}
return ajub-bjub;
}.bind(this),
reverse: true,

View File

@ -37,18 +37,87 @@ var jubility_view = React.createClass({
);
},
convertChart: function(chart) {
switch(chart) {
case 0:
return 'Basic';
case 1:
return 'Advanced';
case 2:
return 'Extreme';
case 3:
return 'Hard Mode Basic';
case 4:
return 'Hard Mode Advanced';
case 5:
return 'Hard Mode Extreme';
default:
return 'u broke it';
}
},
renderJubilityBreakdown: function(player) {
return (
<div className='row'>
{this.renderJubilityTable(player, true)}
{this.renderJubilityTable(player, false)}
if (this.state.version == 13) // festo
return (
<div className='row'>
{this.renderFestoJubilityTable(player, true)}
{this.renderFestoJubilityTable(player, false)}
</div>
);
if (this.state.version == 12) // clan
return (
<div className='row'>
{this.renderClanJubilityTable(player)}
</div>
);
return null;
},
renderClanJubilityTable: function(player) {
if (typeof player.chart === 'undefined' || player.chart.length == 0) {
return null;
}
return(
<div className='col-6 col-12-medium'>
<p>
<b><b>Chart breakdown</b></b>
</p>
<p>Individual song jubility gets averaged to calculate player total jubility.</p>
<Table
className='list jubility'
columns={[
{
name: 'Song',
render: function(entry) {
return (
<a href={Link.get('individual_score', entry.songid, this.convertChart(entry.chart))}>
<div>{ this.state.songs[entry.songid].name }</div>
</a>
);
}.bind(this),
},
{
name: 'Hard Mode',
render: function(entry) { return entry.hard_mode ? 'Yes' : 'No'; }
},
{
name: 'Jubility',
render: function(entry) { return (entry.value / 100.0).toFixed(2); },
sort: function(a, b) {
return a.value - b.value;
},
reverse: true,
},
]}
defaultsort='Jubility'
rows={player.chart}
/>
</div>
);
},
renderJubilityTable: function(player, pickup) {
if (this.state.version != 13) // festo
return null;
renderFestoJubilityTable: function(player, pickup) {
if (pickup == true)
jubilityChart = player.pick_up_chart;
else
@ -63,6 +132,7 @@ var jubility_view = React.createClass({
{pickup == true ? <b>Pick up chart breakdown</b> : <b>Common chart breakdown</b>}
</b>
</p>
<p>Individual song jubility gets added to calculate total jubility.</p>
<Table
className='list jubility'
columns={[
@ -70,7 +140,7 @@ var jubility_view = React.createClass({
name: 'Song',
render: function(entry) {
return (
<a href={Link.get('individual_score', entry.music_id)}>
<a href={Link.get('individual_score', entry.music_id, this.convertChart(entry.seq))}>
<div>{ this.state.songs[entry.music_id].name }</div>
</a>
);
@ -149,9 +219,6 @@ var jubility_view = React.createClass({
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]
})
@ -160,7 +227,7 @@ var jubility_view = React.createClass({
<section>
<p>
<b>
<a href={Link.get('profile')}>&larr; Back To Profile</a>
<a href={Link.get('profile', null, this.state.version)}>&larr; Back To Profile</a>
</b>
</p>
</section>
@ -168,6 +235,10 @@ var jubility_view = React.createClass({
<h3>{player.name}'s jubility</h3>
<p>
{this.state.profiles.map(function(version) {
if (version < 12) {
// No breakdown here, no point in displaying.
return null;
}
return (
<Nav
title={window.versions[version]}

View File

@ -48,27 +48,15 @@ var profile_view = React.createClass({
:
// 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 &rarr;</span> :
<span>{player.name}'s Jubility Breakdown &rarr;</span>
}</a>
</b>
</p>
</div>
<LabelledSection label="Jubility">
{(player.common_jubility+player.pick_up_jubility).toFixed(1)}
</LabelledSection>
:
// Default which version >= Saucer except qubell and festo
this.state.version >= 8 ?
<div>
<LabelledSection label="Jubility">
{player.jubility / 100}
</LabelledSection>
</div>
<LabelledSection label="Jubility">
{player.jubility / 100}
</LabelledSection>
:
null
)
@ -100,23 +88,33 @@ var profile_view = React.createClass({
</div>
<div className="section">
<LabelledSection label="User ID">{player.extid}</LabelledSection>
<LabelledSection label="Register Time">
<LabelledSection label="Profile Created">
<Timestamp timestamp={player.first_play_time}/>
</LabelledSection>
<LabelledSection label="Last Play Time">
<LabelledSection label="Last Played">
<Timestamp timestamp={player.last_play_time}/>
</LabelledSection>
<LabelledSection label="Total Plays">
<LabelledSection label="Total Rounds">
{player.plays}
</LabelledSection>
<LabelledSection label="EXCELLENTs">
</div>
<div className="section">
<LabelledSection label="Excellent Clears">
{player.ex_count}
</LabelledSection>
<LabelledSection label="FULL COMBOs">
<LabelledSection label="Full Combo Clears">
{player.fc_count}
</LabelledSection>
{this.renderJubility(player)}
</div>
{this.state.version >= 12 ? <div className="section">
<b>
<a href={Link.get('jubility', null, this.state.version)}>{ window.own_profile ?
<span>Your Jubility Breakdown &rarr;</span> :
<span>{player.name}'s Jubility Breakdown &rarr;</span>
}</a>
</b>
</div> : null}
<div className="section">
<a href={Link.get('records')}>{ window.own_profile ?
<span>view your records</span> :

View File

@ -24,6 +24,14 @@ var HighScore = React.createClass({
if (!this.props.score) {
return null;
}
has_stats = (
this.props.score.stats.perfect > 0 ||
this.props.score.stats.great > 0 ||
this.props.score.stats.good > 0 ||
this.props.score.stats.poor > 0 ||
this.props.score.stats.miss > 0
);
return (
<div className="score">
@ -33,20 +41,23 @@ var HighScore = React.createClass({
<span className="label">Combo</span>
<span className="score">{this.props.score.combo < 0 ? '-' : this.props.score.combo}</span>
</div>
{this.props.score.music_rate > 0 ? <div>
<span className="label">Music Rate</span>
<span className="score">{this.props.score.music_rate <= 0 ? '-' : this.props.score.music_rate}%</span>
</div> : null}
{has_stats ? <div title="perfect / great / good / poor / miss">
{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> : null}
<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)}>{
@ -221,8 +232,10 @@ var network_records = React.createClass({
if (paginate && curpage != this.state.subtab) { return null; }
return (
<tr key={songid.toString()}>
<td className="subheader">{ this.state.versions[songid] }</td>
<tr key={songid.toString()} className="header">
<td className="subheader">{
!paginate ? this.state.versions[songid] : "Song / Artist / Difficulties"
}</td>
<td className="subheader">Basic</td>
<td className="subheader">Advanced</td>
<td className="subheader">Extreme</td>
@ -456,123 +469,125 @@ var network_records = React.createClass({
renderBySongIDList: function(songids, showplays) {
return (
<table className="list records">
<thead>
<tr>
<th className="subheader">Song</th>
<th className="subheader">Basic</th>
<th className="subheader">Advanced</th>
<th className="subheader">Extreme</th>
<th className="subheader">Hard Mode Basic</th>
<th className="subheader">Hard Mode Advanced</th>
<th className="subheader">Hard Mode Extreme</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 difficulties = this.state.songs[songid].difficulties;
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>
</a>
</div>
<div className="songdifficulties">
{this.renderDifficulty(songid, 0)}
<span> / </span>
{this.renderDifficulty(songid, 1)}
<span> / </span>
{this.renderDifficulty(songid, 2)}
</div>
{ showplays ? <div className="songplays">#{index + 1} - {plays}{plays == 1 ? ' play' : ' plays'}</div> : null }
</td>
<td className={difficulties[0] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={0}
score={records[0]}
/>
</td>
<td className={difficulties[1] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={1}
score={records[1]}
/>
</td>
<td className={difficulties[2] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={2}
score={records[2]}
/>
</td>
<td className={difficulties[3] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={3}
score={records[3]}
/>
</td>
<td className={difficulties[4] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={4}
score={records[4]}
/>
</td>
<td className={difficulties[5] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={5}
score={records[5]}
/>
</td>
</tr>
);
}.bind(this))}
</tbody>
<tfoot>
<tr>
<td colSpan={7}>
{ 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
<div className="section">
<table className="list records">
<thead>
<tr>
<th className="subheader">Song / Artist / Difficulties</th>
<th className="subheader">Basic</th>
<th className="subheader">Advanced</th>
<th className="subheader">Extreme</th>
<th className="subheader">Hard Mode Basic</th>
<th className="subheader">Hard Mode Advanced</th>
<th className="subheader">Hard Mode Extreme</th>
</tr>
</thead>
<tbody>
{songids.map(function(songid, index) {
if (index < this.state.offset || index >= this.state.offset + this.state.limit) {
return 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
var records = this.state.records[songid];
if (!records) {
records = {};
}
</td>
</tr>
</tfoot>
</table>
var plays = this.getPlays(records);
var difficulties = this.state.songs[songid].difficulties;
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>
</a>
</div>
<div className="songdifficulties">
{this.renderDifficulty(songid, 0)}
<span> / </span>
{this.renderDifficulty(songid, 1)}
<span> / </span>
{this.renderDifficulty(songid, 2)}
</div>
{ showplays ? <div className="songplays">#{index + 1} - {plays}{plays == 1 ? ' play' : ' plays'}</div> : null }
</td>
<td className={difficulties[0] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={0}
score={records[0]}
/>
</td>
<td className={difficulties[1] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={1}
score={records[1]}
/>
</td>
<td className={difficulties[2] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={2}
score={records[2]}
/>
</td>
<td className={difficulties[3] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={3}
score={records[3]}
/>
</td>
<td className={difficulties[4] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={4}
score={records[4]}
/>
</td>
<td className={difficulties[5] > 0 ? "" : "nochart"}>
<HighScore
players={this.state.players}
songid={songid}
chart={5}
score={records[5]}
/>
</td>
</tr>
);
}.bind(this))}
</tbody>
<tfoot>
<tr>
<td colSpan={7}>
{ 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>
);
},

View File

@ -52,6 +52,13 @@ var network_scores = React.createClass({
},
renderScore: function(score) {
has_stats = (
score.stats.perfect > 0 ||
score.stats.great > 0 ||
score.stats.good > 0 ||
score.stats.poor > 0 ||
score.stats.miss > 0
);
return (
<div className="score">
<div>
@ -59,14 +66,12 @@ var network_scores = React.createClass({
<span className="score">{score.points}</span>
<span className="label">Combo</span>
<span className="score">{score.combo < 0 ? '-' : score.combo}</span>
<span className="label">Music Rate</span>
<span className="score">{score.music_rate < 0 ? '-' : score.music_rate}</span>
{score.music_rate > 0 ? <span>
<span className="label">Music Rate</span>
<span className="score">{score.music_rate <= 0 ? '-' : score.music_rate}%</span>
</span> : null}
</div>
<div>
<span className="status">{score.status}</span>
<br/>
<span className="bolder">Stats:</span>
<br/>
{has_stats ? <div title="perfect / great / good / poor / miss">
{score.stats.perfect}
<span> / </span>
{score.stats.great}
@ -76,6 +81,9 @@ var network_scores = React.createClass({
{score.stats.poor}
<span> / </span>
{score.stats.miss}
</div> : null}
<div>
<span className="status">{score.status}</span>
</div>
</div>
);
@ -89,8 +97,8 @@ var network_scores = React.createClass({
<tr>
{ window.shownames ? <th>Name</th> : null }
<th>Timestamp</th>
<th>Song</th>
<th>Chart</th>
<th>Song / Artist</th>
<th>Difficulty</th>
<th>Score</th>
</tr>
</thead>

View File

@ -122,27 +122,42 @@ var top_scores = React.createClass({
},
{
name: 'Combo',
render: function(topscore) { return topscore.combo > 0 ? topscore.combo : '-'; },
render: function(topscore) { return topscore.combo > 0 ? topscore.combo : ''; },
sort: function(a, b) {
return a.combo - b.combo;
},
reverse: true,
},
{
name: 'Perfect',
render: function(topscore) { return topscore.stats.perfect }
name: 'Music Rate',
render: function(topscore) { return topscore.music_rate > 0 ? topscore.music_rate + "%" : ''; },
sort: function(a, b) {
return a.music_rate - b.music_rate;
},
reverse: true,
},
{
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 }
name: 'Judgement Stats',
render: function(topscore) {
has_stats = (
topscore.stats.perfect > 0 ||
topscore.stats.great > 0 ||
topscore.stats.good > 0 ||
topscore.stats.poor > 0 ||
topscore.stats.miss > 0
);
return has_stats ? <div title="perfect / great / good / poor / miss">
{topscore.stats.perfect}
<span> / </span>
{topscore.stats.great}
<span> / </span>
{topscore.stats.good}
<span> / </span>
{topscore.stats.poor}
<span> / </span>
{topscore.stats.miss}
</div> : null;
}
},
]}
defaultsort='Score'

View File

@ -5,7 +5,11 @@ var Link = {
get: function(name, param, anchor) {
var uri = window.uris[name];
if (!param || !uri) {
return uri;
if (!anchor) {
return uri;
} else {
return uri + '#' + anchor.toString();
}
} else if (!anchor) {
return uri.replace("/-1", "/" + param.toString());
} else {

View File

@ -54,13 +54,6 @@ div.labelledsection.filled {
border: 1px dashed #dddddd;
}
span.separator {
font-size: large;
font-weight: bold;
padding-left: 5px;
padding-right: 5px;
}
div.labelledsection.iidx.themeoption select {
width: 200px;
}

View File

@ -273,3 +273,48 @@ span.checkbox {
padding-right: 5px;
cursor: default;
}
div.slider {
display: inline-block;
border-radius: 10px;
}
div.slider.on {
background: #2196F3;
border: 1px solid #0a6fc2;
}
div.slider.off {
background: #cccccc;
border: 1px solid #a0a0a0;
}
div.slider span.label {
width: 70px;
font-weight: normal;
font-size: small;
height: 18px;
display: inline-block;
}
div.slider span.label.on {
text-align: center;
}
div.slider span.label.off {
text-align: center;
}
div.slider span.ball {
position: absolute;
display: inline-block;
border-radius: 8px;
background: white;
width: 14px;
height: 14px;
margin: 2px;
}
div.slider span.ball.off {
margin-left: -16px;
}

View File

@ -54,13 +54,6 @@ div.labelledsection.filled {
border: 1px dashed #dddddd;
}
span.separator {
font-size: large;
font-weight: bold;
padding-left: 5px;
padding-right: 5px;
}
div.labelledsection.iidx.themeoption select {
width: 200px;
}

View File

@ -267,3 +267,48 @@ span.checkbox {
padding-right: 5px;
cursor: default;
}
div.slider {
display: inline-block;
border-radius: 10px;
}
div.slider.on {
background: #2196F3;
border: 1px solid #0a6fc2;
}
div.slider.off {
background: #cccccc;
border: 1px solid #a0a0a0;
}
div.slider span.label {
width: 70px;
font-weight: normal;
font-size: small;
height: 18px;
display: inline-block;
}
div.slider span.label.on {
text-align: center;
}
div.slider span.label.off {
text-align: center;
}
div.slider span.ball {
position: absolute;
display: inline-block;
border-radius: 8px;
background: white;
width: 14px;
height: 14px;
margin: 2px;
}
div.slider span.ball.off {
margin-left: -16px;
}