api specs

This commit is contained in:
2026-04-29 00:45:48 +04:00
parent 27143319e3
commit 8cda54548f
67 changed files with 5052 additions and 93 deletions

View File

@@ -8,9 +8,11 @@
:competition-title="competitionTitle"
:mode="mode"
:language="language"
:is-fullscreen="isFullscreen"
:server-time="state.serverTime ? formatServerTime(state.serverTime) : ''"
@change-mode="mode = $event"
@change-language="setLanguage"
@toggle-fullscreen="toggleFullscreen"
/>
<main class="page-content">
@@ -133,6 +135,7 @@
:display-name="displayName"
:secondary-name="secondaryName"
:score-input-value="scoreInputValue"
:score-input-invalid="scoreInputInvalid"
:on-score-focus="onScoreFocus"
:on-score-input="onScoreInput"
:on-score-commit="onScoreCommit"
@@ -222,6 +225,7 @@ const mode = ref('live')
const viewTab = ref('groups')
const adminTab = ref('players')
const language = ref(localStorage.getItem('shooting_lang') === 'en' ? 'en' : 'ar')
const isFullscreen = ref(false)
const state = ref(createInitialState())
const primaryGroups = ref(loadPrimaryGroups(localStorage))
@@ -640,6 +644,11 @@ watch(
)
onMounted(async () => {
onFullscreenChange()
document.addEventListener('fullscreenchange', onFullscreenChange)
document.addEventListener('webkitfullscreenchange', onFullscreenChange)
document.addEventListener('mozfullscreenchange', onFullscreenChange)
document.addEventListener('MSFullscreenChange', onFullscreenChange)
document.documentElement.lang = language.value
document.documentElement.dir = language.value === 'ar' ? 'rtl' : 'ltr'
await fetchState()
@@ -647,6 +656,10 @@ onMounted(async () => {
})
onBeforeUnmount(() => {
document.removeEventListener('fullscreenchange', onFullscreenChange)
document.removeEventListener('webkitfullscreenchange', onFullscreenChange)
document.removeEventListener('mozfullscreenchange', onFullscreenChange)
document.removeEventListener('MSFullscreenChange', onFullscreenChange)
stopLiveRotation()
stopLiveMonitorRotation()
stopRealtimeSync()
@@ -662,6 +675,57 @@ function setLanguage(next) {
if (next === 'ar' || next === 'en') language.value = next
}
function getFullscreenElement() {
return (
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement ||
null
)
}
function onFullscreenChange() {
isFullscreen.value = Boolean(getFullscreenElement())
}
function requestFullscreenFor(element) {
if (!element) return Promise.resolve()
const fn =
element.requestFullscreen ||
element.webkitRequestFullscreen ||
element.mozRequestFullScreen ||
element.msRequestFullscreen
if (!fn) return Promise.resolve()
const result = fn.call(element)
if (result && typeof result.then === 'function') return result
return Promise.resolve()
}
function exitFullscreen() {
const fn =
document.exitFullscreen ||
document.webkitExitFullscreen ||
document.mozCancelFullScreen ||
document.msExitFullscreen
if (!fn) return Promise.resolve()
const result = fn.call(document)
if (result && typeof result.then === 'function') return result
return Promise.resolve()
}
async function toggleFullscreen() {
try {
if (getFullscreenElement()) {
await exitFullscreen()
return
}
await requestFullscreenFor(document.documentElement)
} catch {
// Ignore fullscreen errors, mostly caused by browser policy.
}
}
function normalizeLiveActiveView(value) {
const normalized = String(value || '').trim().toLowerCase()
const allowed = new Set(['group_live', 'prelim_tie', 'prelim_overall', 'final_groups', 'final_tie', 'podium'])
@@ -1209,7 +1273,7 @@ async function applySuggestedScore() {
if (!scoreAdviceModal.advice) return
const stage = scoreAdviceModal.stage
const playerId = scoreAdviceModal.playerId
const score = Number(scoreAdviceModal.advice.advisedScore || 0)
const score = clampScoreValue(scoreAdviceModal.advice.advisedScore)
await updateScoreValue(stage, playerId, score)
scoreAdviceModal.currentScore = score
closeScoreAdviceModal()
@@ -1287,26 +1351,48 @@ function scoreDraftKey(stage, playerId) {
return `${stage}:${playerId}`
}
function clampScoreValue(value) {
let score = Number(value)
if (!Number.isFinite(score)) score = 0
score = Math.trunc(score)
if (score < 0) return 0
if (score > 100) return 100
return score
}
function scoreInputValue(stage, playerId) {
const draft = scoreDrafts[scoreDraftKey(stage, playerId)]
if (draft) return draft.value
return String(scoreFor(stage, playerId))
}
function scoreInputInvalid(stage, playerId) {
const draft = scoreDrafts[scoreDraftKey(stage, playerId)]
return Boolean(draft?.invalid)
}
function parseDraftScoreValue(value) {
let score = Number(value === '' ? 0 : value)
if (!Number.isFinite(score)) score = 0
return Math.trunc(score)
}
function onScoreFocus(stage, playerId) {
const key = scoreDraftKey(stage, playerId)
if (!scoreDrafts[key]) {
scoreDrafts[key] = { value: String(scoreFor(stage, playerId)), committing: false }
scoreDrafts[key] = { value: String(scoreFor(stage, playerId)), committing: false, invalid: false }
}
}
function onScoreInput(stage, playerId, event) {
const key = scoreDraftKey(stage, playerId)
const raw = String(event?.target?.value ?? '')
const cleaned = raw.replace(/[^\d]/g, '').slice(0, 4)
const current = scoreDrafts[key] || { value: String(scoreFor(stage, playerId)), committing: false }
scoreDrafts[key] = { ...current, value: cleaned }
if (event?.target && event.target.value !== cleaned) event.target.value = cleaned
const digits = raw.replace(/[^\d]/g, '').slice(0, 3)
const parsed = parseDraftScoreValue(digits)
const invalid = digits !== '' && (parsed < 0 || parsed > 100)
const current = scoreDrafts[key] || { value: String(scoreFor(stage, playerId)), committing: false, invalid: false }
scoreDrafts[key] = { ...current, value: digits, invalid }
if (event?.target && event.target.value !== digits) event.target.value = digits
}
async function onScoreCommit(stage, playerId) {
@@ -1314,11 +1400,11 @@ async function onScoreCommit(stage, playerId) {
const draft = scoreDrafts[key]
if (!draft || draft.committing) return
let score = Number(draft.value === '' ? 0 : draft.value)
if (!Number.isFinite(score)) score = 0
score = Math.trunc(score)
const score = parseDraftScoreValue(draft.value)
const invalid = score < 0 || score > 100
if (score < 0 || score > 9999) {
if (invalid) {
scoreDrafts[key] = { ...draft, invalid: true }
showToast(t('messages.invalidScore'), 'error')
return
}
@@ -1328,12 +1414,12 @@ async function onScoreCommit(stage, playerId) {
return
}
scoreDrafts[key] = { ...draft, committing: true }
scoreDrafts[key] = { ...draft, invalid: false, committing: true }
await updateScoreValue(stage, playerId, score, key)
}
async function updateScoreValue(stage, playerId, score, draftKey = '') {
if (score < 0 || score > 9999) {
if (score < 0 || score > 100) {
showToast(t('messages.invalidScore'), 'error')
return
}