api specs
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user