Feature: Manual leaderboard submission button on result screen with notification

This commit is contained in:
2026-01-17 20:14:11 +08:00
parent 9935d70e31
commit 69b92b34d8

View File

@@ -178,7 +178,66 @@ class Scoresheet {
} }
this.game.appendChild(this.tetsuoHana) this.game.appendChild(this.tetsuoHana)
} }
// Add leaderboard submit button if user is logged in
if (account.loggedIn && !this.multiplayer) {
this.leaderboardBtn = document.createElement("div")
this.leaderboardBtn.id = "leaderboard-submit-btn"
this.leaderboardBtn.innerHTML = "🏆 提交排行榜<br><small>Submit to Leaderboard</small>"
this.leaderboardBtn.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
padding: 15px 25px;
background: linear-gradient(135deg, #ff6b9d, #c44db3);
color: white;
border-radius: 15px;
font-size: 18px;
font-family: ${strings.font || "sans-serif"};
text-align: center;
cursor: pointer;
z-index: 1000;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
border: 3px solid #fff;
transition: all 0.2s ease;
user-select: none;
`
this.leaderboardBtn.onmouseover = () => {
if (!this.leaderboardSubmitted) {
this.leaderboardBtn.style.transform = "scale(1.05)"
this.leaderboardBtn.style.boxShadow = "0 6px 20px rgba(0,0,0,0.4)"
} }
}
this.leaderboardBtn.onmouseout = () => {
this.leaderboardBtn.style.transform = "scale(1)"
this.leaderboardBtn.style.boxShadow = "0 4px 15px rgba(0,0,0,0.3)"
}
this.leaderboardBtn.onclick = () => this.onLeaderboardBtnClick()
this.game.appendChild(this.leaderboardBtn)
}
}
onLeaderboardBtnClick() {
if (this.leaderboardSubmitted) {
return
}
if (!this.leaderboardData || !this.leaderboardData.songId) {
this.showLeaderboardNotification("no_song_id")
return
}
// Disable button and show submitting state
this.leaderboardBtn.innerHTML = "⏳ 提交中..."
this.leaderboardBtn.style.background = "linear-gradient(135deg, #888, #666)"
this.leaderboardBtn.style.cursor = "default"
this.submitToLeaderboard(
this.leaderboardData.songId,
this.leaderboardData.difficulty,
this.leaderboardData.scoreObj
)
}
redraw() { redraw() {
if (!this.redrawRunning) { if (!this.redrawRunning) {
@@ -933,6 +992,15 @@ class Scoresheet {
if (clearReached) { if (clearReached) {
crown = this.resultsObj.bad === 0 ? "gold" : "silver" crown = this.resultsObj.bad === 0 ? "gold" : "silver"
} }
// Store data for manual leaderboard submission
this.leaderboardData = {
songId: songId,
difficulty: difficulty,
scoreObj: Object.assign({}, this.resultsObj)
}
this.leaderboardSubmitted = false
if (!oldScore || oldScore.points <= this.resultsObj.points) { if (!oldScore || oldScore.points <= this.resultsObj.points) {
if (oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)) { if (oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)) {
crown = oldScore.crown crown = oldScore.crown
@@ -941,10 +1009,7 @@ class Scoresheet {
delete this.resultsObj.title delete this.resultsObj.title
delete this.resultsObj.difficulty delete this.resultsObj.difficulty
delete this.resultsObj.gauge delete this.resultsObj.gauge
scoreStorage.add(hash, difficulty, this.resultsObj, true, title).then(() => { scoreStorage.add(hash, difficulty, this.resultsObj, true, title).catch(() => {
// Auto-submit to leaderboard if logged in and has song ID
this.submitToLeaderboard(songId, difficulty, this.resultsObj)
}).catch(() => {
this.showWarning = { name: "scoreSaveFailed" } this.showWarning = { name: "scoreSaveFailed" }
}) })
} else if (oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)) { } else if (oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)) {
@@ -957,13 +1022,13 @@ class Scoresheet {
this.scoreSaved = true this.scoreSaved = true
} }
submitToLeaderboard(songId, difficulty, scoreObj) { submitToLeaderboard(songId, difficulty, scoreObj) {
// Only submit if user is logged in and song has an ID // Only submit if user is logged in and song has an ID
if (!account.loggedIn || !songId) { if (!account.loggedIn || !songId) {
return return
} }
var self = this var self = this
loader.getCsrfToken().then(token => { loader.getCsrfToken().then(token => {
var request = new XMLHttpRequest() var request = new XMLHttpRequest()
@@ -976,11 +1041,39 @@ class Scoresheet {
try { try {
var response = JSON.parse(request.responseText) var response = JSON.parse(request.responseText)
if (response.status === "ok") { if (response.status === "ok") {
self.leaderboardSubmitted = true
self.showLeaderboardNotification(response.message) self.showLeaderboardNotification(response.message)
// Update button to show success
if (self.leaderboardBtn) {
self.leaderboardBtn.innerHTML = "✅ 已提交<br><small>Submitted!</small>"
self.leaderboardBtn.style.background = "linear-gradient(135deg, #4CAF50, #45a049)"
}
} else {
// Show error
self.showLeaderboardNotification("error")
if (self.leaderboardBtn) {
self.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
self.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
}
} }
} catch (e) { } catch (e) {
console.error("Failed to parse leaderboard response:", e) console.error("Failed to parse leaderboard response:", e)
self.showLeaderboardNotification("error")
} }
} else {
self.showLeaderboardNotification("error")
if (self.leaderboardBtn) {
self.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
self.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
}
}
}
request.onerror = function () {
self.showLeaderboardNotification("error")
if (self.leaderboardBtn) {
self.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
self.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
} }
} }
@@ -991,9 +1084,15 @@ class Scoresheet {
})) }))
}).catch(() => { }).catch(() => {
console.log("Leaderboard submission failed") console.log("Leaderboard submission failed")
this.showLeaderboardNotification("error")
if (this.leaderboardBtn) {
this.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
this.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
}
}) })
} }
showLeaderboardNotification(message) { showLeaderboardNotification(message) {
var notification = document.createElement("div") var notification = document.createElement("div")
notification.className = "leaderboard-notification" notification.className = "leaderboard-notification"
@@ -1020,6 +1119,8 @@ class Scoresheet {
case "score_updated": text = "🎉 排行榜成绩已更新!"; break case "score_updated": text = "🎉 排行榜成绩已更新!"; break
case "score_not_higher": text = "📊 已有更高成绩"; break case "score_not_higher": text = "📊 已有更高成绩"; break
case "score_too_low": text = "未进入排行榜前50"; break case "score_too_low": text = "未进入排行榜前50"; break
case "error": text = "❌ 提交失败,请重试"; break
case "no_song_id": text = "❌ 无法提交歌曲ID缺失"; break
default: text = "排行榜已更新" default: text = "排行榜已更新"
} }
notification.innerText = text notification.innerText = text
@@ -1063,10 +1164,17 @@ class Scoresheet {
if (!this.multiplayer) { if (!this.multiplayer) {
delete this.tetsuoHana delete this.tetsuoHana
} }
// Clean up leaderboard button
if (this.leaderboardBtn && this.leaderboardBtn.parentNode) {
this.leaderboardBtn.parentNode.removeChild(this.leaderboardBtn)
}
delete this.leaderboardBtn
delete this.leaderboardData
delete this.ctx delete this.ctx
delete this.canvas delete this.canvas
delete this.fadeScreen delete this.fadeScreen
delete this.results delete this.results
delete this.rules delete this.rules
} }
} }