Add pb display
This commit is contained in:
parent
86830adc47
commit
3410e3324e
42
index.css
42
index.css
@ -317,6 +317,48 @@ span:empty {
|
||||
max-width: 44rem;
|
||||
}
|
||||
|
||||
.personal-best-row {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
opacity: 0.92;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
#friendScores.has-personal-best .personal-best-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.personal-best-label {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
opacity: 0.75;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.personal-best-acc {
|
||||
font-feature-settings: "tnum";
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.personal-best-delta {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
font-feature-settings: "tnum";
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 4.2ch;
|
||||
}
|
||||
|
||||
.personal-best-delta.personal-best-delta--ahead {
|
||||
color: #3ddc97;
|
||||
}
|
||||
|
||||
.personal-best-delta.personal-best-delta--behind {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
#friendScoresHeader {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
|
||||
@ -39,6 +39,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="friendScores" aria-live="polite">
|
||||
<div id="personalBestRow" class="personal-best-row">
|
||||
<span class="personal-best-label">PB</span>
|
||||
<span id="personalBestAcc" class="personal-best-acc"></span>
|
||||
<span id="personalBestDelta" class="personal-best-delta"></span>
|
||||
</div>
|
||||
<div id="friendScoresHeader"><img id="friendScoresPlayerAvatar" src="images/unknown.svg" alt=""> <span id="friendScoresHeaderText">frenz</span> <img id="friendScoresHeaderImg" src="assets/peepohigh.webp" alt=""></div>
|
||||
<ol id="friendScoresList"></ol>
|
||||
<div id="friendScoresEmpty">No map loaded</div>
|
||||
|
||||
61
index.js
61
index.js
@ -246,6 +246,9 @@ var beatSaberPlus = {
|
||||
switch (data._event) {
|
||||
case "gameState":
|
||||
document.body.dataset.gameState = data.gameStateChanged;
|
||||
if (data.gameStateChanged === "Menu") {
|
||||
resetPersonalBestDeltaPlaceholder();
|
||||
}
|
||||
break;
|
||||
case "mapInfo":
|
||||
void updateMapInfo(data.mapInfoChanged);
|
||||
@ -475,6 +478,9 @@ var friendScoresEmpty = must("friendScoresEmpty");
|
||||
var friendScoresHeaderText = must("friendScoresHeaderText");
|
||||
var friendScoresPlayerAvatar = must("friendScoresPlayerAvatar");
|
||||
var friendScoresHeaderImg = must("friendScoresHeaderImg");
|
||||
var personalBestAcc = must("personalBestAcc");
|
||||
var personalBestDelta = must("personalBestDelta");
|
||||
var currentPbAccuracyPercent = null;
|
||||
var cachedConfiguredPlayerAvatarKey = "";
|
||||
var cachedConfiguredPlayerAvatarSrc = "images/unknown.svg";
|
||||
async function refreshConfiguredPlayerAvatar() {
|
||||
@ -497,12 +503,43 @@ async function refreshConfiguredPlayerAvatar() {
|
||||
cachedConfiguredPlayerAvatarSrc = profile?.avatar?.trim() || "images/unknown.svg";
|
||||
friendScoresPlayerAvatar.src = cachedConfiguredPlayerAvatarSrc;
|
||||
}
|
||||
function applyPersonalBestRow(pbPercent) {
|
||||
currentPbAccuracyPercent = pbPercent;
|
||||
friendScoresPanel.classList.toggle("has-personal-best", pbPercent !== null);
|
||||
if (pbPercent === null) {
|
||||
personalBestAcc.textContent = "";
|
||||
personalBestDelta.textContent = "";
|
||||
personalBestDelta.classList.remove("personal-best-delta--ahead", "personal-best-delta--behind");
|
||||
return;
|
||||
}
|
||||
personalBestAcc.textContent = `${pbPercent.toFixed(2)}%`;
|
||||
resetPersonalBestDeltaPlaceholder();
|
||||
}
|
||||
function resetPersonalBestDeltaPlaceholder() {
|
||||
if (currentPbAccuracyPercent === null) return;
|
||||
personalBestDelta.textContent = "\u2014";
|
||||
personalBestDelta.classList.remove("personal-best-delta--ahead", "personal-best-delta--behind");
|
||||
}
|
||||
function updatePersonalBestDelta(liveAccuracyPercent) {
|
||||
if (currentPbAccuracyPercent === null) return;
|
||||
const delta = liveAccuracyPercent - currentPbAccuracyPercent;
|
||||
personalBestDelta.textContent = `${delta >= 0 ? "+" : ""}${delta.toFixed(2)}%`;
|
||||
personalBestDelta.classList.toggle("personal-best-delta--ahead", delta > 5e-4);
|
||||
personalBestDelta.classList.toggle("personal-best-delta--behind", delta < -5e-4);
|
||||
if (Math.abs(delta) <= 5e-4) {
|
||||
personalBestDelta.classList.remove("personal-best-delta--ahead", "personal-best-delta--behind");
|
||||
}
|
||||
}
|
||||
function updateScore(score) {
|
||||
if (!settings.score) return;
|
||||
if (settings.score) {
|
||||
accuracy.textContent = (score.accuracy * 100).toFixed(1);
|
||||
mistakes.textContent = score.missCount ? String(score.missCount) : "";
|
||||
accuracy.classList.toggle("failed", score.currentHealth === 0);
|
||||
}
|
||||
if (settings.friends) {
|
||||
updatePersonalBestDelta(score.accuracy * 100);
|
||||
}
|
||||
}
|
||||
function avatarFromScore(score) {
|
||||
if (typeof score.player === "object" && score.player?.avatar) {
|
||||
return score.player.avatar;
|
||||
@ -511,6 +548,7 @@ function avatarFromScore(score) {
|
||||
return url || null;
|
||||
}
|
||||
function clearFriendScores(message) {
|
||||
applyPersonalBestRow(null);
|
||||
friendScoresList.replaceChildren();
|
||||
friendScoresEmpty.textContent = message;
|
||||
friendScoresHeaderText.textContent = "frenz?";
|
||||
@ -548,6 +586,7 @@ function friendsRelationListKey(playerId) {
|
||||
}
|
||||
function beginFriendScoresForNewMapContext() {
|
||||
friendScoreRequestId += 1;
|
||||
applyPersonalBestRow(null);
|
||||
if (!settings.friends) return;
|
||||
if (!currentMapHash) {
|
||||
clearFriendScores("No map loaded");
|
||||
@ -578,6 +617,7 @@ async function refreshMapFriendScores() {
|
||||
clearFriendScores("Waiting for BeatLeader player id");
|
||||
return;
|
||||
}
|
||||
applyPersonalBestRow(null);
|
||||
friendScoresList.replaceChildren();
|
||||
friendScoresPanel.classList.remove("has-items");
|
||||
friendScoresPanel.classList.add("is-loading");
|
||||
@ -594,10 +634,12 @@ async function refreshMapFriendScores() {
|
||||
friendsRelationCache = fetched;
|
||||
return fetched;
|
||||
})();
|
||||
const [leaderboards, friends] = await Promise.all([
|
||||
const [leaderboards, friends, selfProfile] = await Promise.all([
|
||||
fetchBLLeaderboardsByHash(hash),
|
||||
friendsPromise
|
||||
friendsPromise,
|
||||
fetchBeatLeaderPlayer(playerId)
|
||||
]);
|
||||
const myBeatLeaderId = selfProfile?.id ?? playerId;
|
||||
if (requestId !== friendScoreRequestId) return;
|
||||
if (leaderboards.length === 0) {
|
||||
clearFriendScores("No BeatLeader leaderboards found");
|
||||
@ -620,6 +662,15 @@ async function refreshMapFriendScores() {
|
||||
}
|
||||
const scores = await fetchAllMapScoresByHash(hash, forPlayMode);
|
||||
if (requestId !== friendScoreRequestId) return;
|
||||
let playerPbAcc = null;
|
||||
for (const score of scores) {
|
||||
const scorePlayerId = score.playerId ?? (typeof score.player === "object" ? score.player?.id : null);
|
||||
const playerKey = scorePlayerId == null ? "" : String(scorePlayerId);
|
||||
if (!playerKey || playerKey !== String(myBeatLeaderId)) continue;
|
||||
const acc = normalizeAccuracy(score.accuracy ?? score.acc);
|
||||
if (acc === null) continue;
|
||||
if (playerPbAcc === null || acc > playerPbAcc) playerPbAcc = acc;
|
||||
}
|
||||
const bestByPlayer = /* @__PURE__ */ new Map();
|
||||
for (const score of scores) {
|
||||
const scorePlayerId = score.playerId ?? (typeof score.player === "object" ? score.player?.id : null);
|
||||
@ -640,6 +691,10 @@ async function refreshMapFriendScores() {
|
||||
});
|
||||
}
|
||||
}
|
||||
if (playerPbAcc !== null) {
|
||||
bestByPlayer.delete(String(myBeatLeaderId));
|
||||
}
|
||||
applyPersonalBestRow(playerPbAcc);
|
||||
const sorted = Array.from(bestByPlayer.values()).sort((a, b) => b.acc - a.acc);
|
||||
renderFriendScores(sorted);
|
||||
} catch {
|
||||
|
||||
@ -72,6 +72,9 @@ const beatSaberPlus = {
|
||||
switch (data._event) {
|
||||
case "gameState":
|
||||
document.body.dataset.gameState = data.gameStateChanged;
|
||||
if (data.gameStateChanged === "Menu") {
|
||||
resetPersonalBestDeltaPlaceholder();
|
||||
}
|
||||
break;
|
||||
case "mapInfo":
|
||||
void updateMapInfo(data.mapInfoChanged);
|
||||
@ -342,6 +345,11 @@ const friendScoresEmpty = must<HTMLElement>("friendScoresEmpty");
|
||||
const friendScoresHeaderText = must<HTMLElement>("friendScoresHeaderText");
|
||||
const friendScoresPlayerAvatar = must<HTMLImageElement>("friendScoresPlayerAvatar");
|
||||
const friendScoresHeaderImg = must<HTMLImageElement>("friendScoresHeaderImg");
|
||||
const personalBestAcc = must<HTMLElement>("personalBestAcc");
|
||||
const personalBestDelta = must<HTMLElement>("personalBestDelta");
|
||||
|
||||
/** BeatLeader PB (percent) for the current map + difficulty; null if none or friends panel inactive. */
|
||||
let currentPbAccuracyPercent: number | null = null;
|
||||
|
||||
let cachedConfiguredPlayerAvatarKey = "";
|
||||
let cachedConfiguredPlayerAvatarSrc = "images/unknown.svg";
|
||||
@ -367,12 +375,46 @@ async function refreshConfiguredPlayerAvatar() {
|
||||
friendScoresPlayerAvatar.src = cachedConfiguredPlayerAvatarSrc;
|
||||
}
|
||||
|
||||
function applyPersonalBestRow(pbPercent: number | null) {
|
||||
currentPbAccuracyPercent = pbPercent;
|
||||
friendScoresPanel.classList.toggle("has-personal-best", pbPercent !== null);
|
||||
if (pbPercent === null) {
|
||||
personalBestAcc.textContent = "";
|
||||
personalBestDelta.textContent = "";
|
||||
personalBestDelta.classList.remove("personal-best-delta--ahead", "personal-best-delta--behind");
|
||||
return;
|
||||
}
|
||||
personalBestAcc.textContent = `${pbPercent.toFixed(2)}%`;
|
||||
resetPersonalBestDeltaPlaceholder();
|
||||
}
|
||||
|
||||
function resetPersonalBestDeltaPlaceholder() {
|
||||
if (currentPbAccuracyPercent === null) return;
|
||||
personalBestDelta.textContent = "—";
|
||||
personalBestDelta.classList.remove("personal-best-delta--ahead", "personal-best-delta--behind");
|
||||
}
|
||||
|
||||
function updatePersonalBestDelta(liveAccuracyPercent: number) {
|
||||
if (currentPbAccuracyPercent === null) return;
|
||||
const delta = liveAccuracyPercent - currentPbAccuracyPercent;
|
||||
personalBestDelta.textContent = `${delta >= 0 ? "+" : ""}${delta.toFixed(2)}%`;
|
||||
personalBestDelta.classList.toggle("personal-best-delta--ahead", delta > 0.0005);
|
||||
personalBestDelta.classList.toggle("personal-best-delta--behind", delta < -0.0005);
|
||||
if (Math.abs(delta) <= 0.0005) {
|
||||
personalBestDelta.classList.remove("personal-best-delta--ahead", "personal-best-delta--behind");
|
||||
}
|
||||
}
|
||||
|
||||
function updateScore(score: Score) {
|
||||
if (!settings.score) return;
|
||||
if (settings.score) {
|
||||
accuracy.textContent = (score.accuracy * 100).toFixed(1);
|
||||
mistakes.textContent = score.missCount ? String(score.missCount) : "";
|
||||
accuracy.classList.toggle("failed", score.currentHealth === 0);
|
||||
}
|
||||
if (settings.friends) {
|
||||
updatePersonalBestDelta(score.accuracy * 100);
|
||||
}
|
||||
}
|
||||
|
||||
function avatarFromScore(score: BeatLeaderScore): string | null {
|
||||
if (typeof score.player === "object" && score.player?.avatar) {
|
||||
@ -383,6 +425,7 @@ function avatarFromScore(score: BeatLeaderScore): string | null {
|
||||
}
|
||||
|
||||
function clearFriendScores(message: string) {
|
||||
applyPersonalBestRow(null);
|
||||
friendScoresList.replaceChildren();
|
||||
friendScoresEmpty.textContent = message;
|
||||
friendScoresHeaderText.textContent = "frenz?";
|
||||
@ -427,6 +470,7 @@ function friendsRelationListKey(playerId: string): string {
|
||||
*/
|
||||
function beginFriendScoresForNewMapContext() {
|
||||
friendScoreRequestId += 1;
|
||||
applyPersonalBestRow(null);
|
||||
if (!settings.friends) return;
|
||||
if (!currentMapHash) {
|
||||
clearFriendScores("No map loaded");
|
||||
@ -458,6 +502,7 @@ async function refreshMapFriendScores() {
|
||||
clearFriendScores("Waiting for BeatLeader player id");
|
||||
return;
|
||||
}
|
||||
applyPersonalBestRow(null);
|
||||
friendScoresList.replaceChildren();
|
||||
friendScoresPanel.classList.remove("has-items");
|
||||
friendScoresPanel.classList.add("is-loading");
|
||||
@ -474,10 +519,12 @@ async function refreshMapFriendScores() {
|
||||
friendsRelationCache = fetched;
|
||||
return fetched;
|
||||
})();
|
||||
const [leaderboards, friends] = await Promise.all([
|
||||
const [leaderboards, friends, selfProfile] = await Promise.all([
|
||||
fetchBLLeaderboardsByHash(hash),
|
||||
friendsPromise,
|
||||
fetchBeatLeaderPlayer(playerId),
|
||||
]);
|
||||
const myBeatLeaderId = selfProfile?.id ?? playerId;
|
||||
if (requestId !== friendScoreRequestId) return;
|
||||
if (leaderboards.length === 0) {
|
||||
clearFriendScores("No BeatLeader leaderboards found");
|
||||
@ -501,6 +548,15 @@ async function refreshMapFriendScores() {
|
||||
}
|
||||
const scores = await fetchAllMapScoresByHash(hash, forPlayMode);
|
||||
if (requestId !== friendScoreRequestId) return;
|
||||
let playerPbAcc: number | null = null;
|
||||
for (const score of scores) {
|
||||
const scorePlayerId = score.playerId ?? (typeof score.player === "object" ? score.player?.id : null);
|
||||
const playerKey = scorePlayerId == null ? "" : String(scorePlayerId);
|
||||
if (!playerKey || playerKey !== String(myBeatLeaderId)) continue;
|
||||
const acc = normalizeAccuracy(score.accuracy ?? score.acc);
|
||||
if (acc === null) continue;
|
||||
if (playerPbAcc === null || acc > playerPbAcc) playerPbAcc = acc;
|
||||
}
|
||||
const bestByPlayer = new Map<string, { name: string; acc: number; avatar: string | null }>();
|
||||
for (const score of scores) {
|
||||
const scorePlayerId = score.playerId ?? (typeof score.player === "object" ? score.player?.id : null);
|
||||
@ -523,6 +579,10 @@ async function refreshMapFriendScores() {
|
||||
});
|
||||
}
|
||||
}
|
||||
if (playerPbAcc !== null) {
|
||||
bestByPlayer.delete(String(myBeatLeaderId));
|
||||
}
|
||||
applyPersonalBestRow(playerPbAcc);
|
||||
const sorted = Array.from(bestByPlayer.values()).sort((a, b) => b.acc - a.acc);
|
||||
renderFriendScores(sorted);
|
||||
} catch {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user