diff --git a/Makefile b/Makefile index ecfd1f6..3571e9f 100644 --- a/Makefile +++ b/Makefile @@ -52,5 +52,8 @@ docker-run: mkdir -p data docker run --rm -p 8080:8080 -v $(PWD)/data:/app/data $(CONTAINER_REG)/$(IMAGE_NAME):$(ARCH) +release: + $(MAKE) docker-build + clean: rm -rf bin frontend/dist backend/web diff --git a/backend/db.go b/backend/db.go index dde0784..563c6c3 100644 --- a/backend/db.go +++ b/backend/db.go @@ -66,7 +66,30 @@ CREATE TABLE IF NOT EXISTS app_settings ( ); INSERT OR IGNORE INTO app_settings(key, value) VALUES - ('view_proof_in_view', '0'); + ('view_proof_in_view', '0'), + ('live_active_view', 'group_live'), + ('live_show_group_live', '1'), + ('live_group_display_mode', 'rotate'), + ('live_group_fixed_code', ''), + ('live_show_prelim_tie', '1'), + ('live_show_prelim_overall', '1'), + ('live_show_final_groups', '1'), + ('live_final_display_mode', 'rotate'), + ('live_final_fixed_group', '1'), + ('live_show_final_tie', '1'), + ('live_show_podium', '1'), + ('live_rotation_interval_sec', '5'), + ('live_rotation_player_count', '12'); + +INSERT OR IGNORE INTO scores(stage, player_id, score) +SELECT 'prelim_r1', player_id, score FROM scores WHERE stage = 'preliminary'; +INSERT OR IGNORE INTO scores(stage, player_id, score) +SELECT 'final_r1', player_id, score FROM scores WHERE stage = 'final'; + +INSERT OR IGNORE INTO score_attachments(stage, player_id, image_data) +SELECT 'prelim_r1', player_id, image_data FROM score_attachments WHERE stage = 'preliminary'; +INSERT OR IGNORE INTO score_attachments(stage, player_id, image_data) +SELECT 'final_r1', player_id, image_data FROM score_attachments WHERE stage = 'final'; ` if _, err := db.Exec(schema); err != nil { return nil, fmt.Errorf("apply schema: %w", err) diff --git a/backend/handlers.go b/backend/handlers.go index 0792b06..46bc026 100644 --- a/backend/handlers.go +++ b/backend/handlers.go @@ -58,11 +58,14 @@ func (a *App) handleUpdateAdminSettings(c echo.Context) error { if err := c.Bind(&req); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } - if req.ViewProofInView == nil { - return writeError(c, http.StatusBadRequest, "viewProofInView is required") + if req.ViewProofInView == nil && req.LiveMode == nil { + return writeError(c, http.StatusBadRequest, "at least one settings field is required") } - if err := a.updateViewProofInView(*req.ViewProofInView); err != nil { + if err := a.updateSettings(req); err != nil { + if strings.Contains(err.Error(), "no settings to update") { + return writeError(c, http.StatusBadRequest, err.Error()) + } return writeError(c, http.StatusInternalServerError, err.Error()) } @@ -334,7 +337,7 @@ ON CONFLICT(stage, player_id) DO UPDATE SET score = excluded.score, updated_at = } func (a *App) handleResetStageScores(c echo.Context) error { - stage, err := validateStage(c.Param("stage")) + targetStages, err := resolveResetStages(c.Param("stage")) if err != nil { return writeError(c, http.StatusBadRequest, err.Error()) } @@ -344,13 +347,24 @@ func (a *App) handleResetStageScores(c echo.Context) error { return writeError(c, http.StatusBadRequest, "invalid request body") } - if _, err := a.db.Exec(`UPDATE scores SET score = 0, updated_at = CURRENT_TIMESTAMP WHERE stage = ?`, stage); err != nil { - return writeError(c, http.StatusInternalServerError, fmt.Sprintf("reset stage scores: %v", err)) + tx, err := a.db.Begin() + if err != nil { + return writeError(c, http.StatusInternalServerError, "failed to start reset transaction") } - if req.ResetProofs { - if _, err := a.db.Exec(`DELETE FROM score_attachments WHERE stage = ?`, stage); err != nil { - return writeError(c, http.StatusInternalServerError, fmt.Sprintf("reset stage proofs: %v", err)) + defer tx.Rollback() + + for _, stage := range targetStages { + if _, err := tx.Exec(`UPDATE scores SET score = 0, updated_at = CURRENT_TIMESTAMP WHERE stage = ?`, stage); err != nil { + return writeError(c, http.StatusInternalServerError, fmt.Sprintf("reset stage scores: %v", err)) } + if req.ResetProofs { + if _, err := tx.Exec(`DELETE FROM score_attachments WHERE stage = ?`, stage); err != nil { + return writeError(c, http.StatusInternalServerError, fmt.Sprintf("reset stage proofs: %v", err)) + } + } + } + if err := tx.Commit(); err != nil { + return writeError(c, http.StatusInternalServerError, fmt.Sprintf("commit stage reset: %v", err)) } a.events.Broadcast() @@ -363,6 +377,23 @@ func (a *App) handleResetStageScores(c echo.Context) error { return c.JSON(http.StatusOK, state) } +func resolveResetStages(stage string) ([]string, error) { + switch strings.ToLower(strings.TrimSpace(stage)) { + case "preliminary": + return append([]string{}, preliminaryRoundStages...), nil + case "final": + return append([]string{}, finalRoundStages...), nil + case "prelim_tiebreak", "final_tiebreak": + normalized, err := validateStage(stage) + if err != nil { + return nil, err + } + return []string{normalized}, nil + default: + return nil, fmt.Errorf("invalid stage") + } +} + func (a *App) handleUpdateScoreProof(c echo.Context) error { stage, err := validateStage(c.Param("stage")) if err != nil { diff --git a/backend/models.go b/backend/models.go index bebdb65..5c02683 100644 --- a/backend/models.go +++ b/backend/models.go @@ -6,7 +6,11 @@ import ( "time" ) -var scoreStages = []string{"preliminary", "prelim_tiebreak", "final", "final_tiebreak"} +var preliminaryRoundStages = []string{"prelim_r1", "prelim_r2", "prelim_r3"} +var finalRoundStages = []string{"final_r1", "final_r2"} +var tieBreakStages = []string{"prelim_tiebreak", "final_tiebreak"} + +var scoreStages = append(append(append([]string{}, preliminaryRoundStages...), finalRoundStages...), tieBreakStages...) type App struct { db *sql.DB @@ -90,7 +94,24 @@ type StateResponse struct { } type AppSettings struct { - ViewProofInView bool `json:"viewProofInView"` + ViewProofInView bool `json:"viewProofInView"` + LiveMode LiveModeSettings `json:"liveMode"` +} + +type LiveModeSettings struct { + ActiveView string `json:"activeView"` + ShowGroupLive bool `json:"showGroupLive"` + GroupDisplayMode string `json:"groupDisplayMode"` + GroupFixedCode string `json:"groupFixedCode"` + ShowPrelimTie bool `json:"showPrelimTie"` + ShowPrelimOverall bool `json:"showPrelimOverall"` + ShowFinalGroups bool `json:"showFinalGroups"` + FinalDisplayMode string `json:"finalDisplayMode"` + FinalFixedGroup int `json:"finalFixedGroup"` + ShowFinalTie bool `json:"showFinalTie"` + ShowPodium bool `json:"showPodium"` + RotationIntervalSec int `json:"rotationIntervalSec"` + RotationPlayerCount int `json:"rotationPlayerCount"` } type AdminLoginRequest struct { @@ -121,7 +142,24 @@ type ScoreProofUpdateRequest struct { } type AdminSettingsUpdateRequest struct { - ViewProofInView *bool `json:"viewProofInView"` + ViewProofInView *bool `json:"viewProofInView"` + LiveMode *LiveModeSettingsUpdateRequest `json:"liveMode"` +} + +type LiveModeSettingsUpdateRequest struct { + ActiveView *string `json:"activeView"` + ShowGroupLive *bool `json:"showGroupLive"` + GroupDisplayMode *string `json:"groupDisplayMode"` + GroupFixedCode *string `json:"groupFixedCode"` + ShowPrelimTie *bool `json:"showPrelimTie"` + ShowPrelimOverall *bool `json:"showPrelimOverall"` + ShowFinalGroups *bool `json:"showFinalGroups"` + FinalDisplayMode *string `json:"finalDisplayMode"` + FinalFixedGroup *int `json:"finalFixedGroup"` + ShowFinalTie *bool `json:"showFinalTie"` + ShowPodium *bool `json:"showPodium"` + RotationIntervalSec *int `json:"rotationIntervalSec"` + RotationPlayerCount *int `json:"rotationPlayerCount"` } type ResetStageRequest struct { diff --git a/backend/settings.go b/backend/settings.go index 3e706bd..b7f69ce 100644 --- a/backend/settings.go +++ b/backend/settings.go @@ -3,33 +3,275 @@ package main import ( "database/sql" "fmt" + "strconv" "strings" ) -func (a *App) readSettings() (AppSettings, error) { - var raw string - err := a.db.QueryRow(`SELECT value FROM app_settings WHERE key = 'view_proof_in_view'`).Scan(&raw) - if err != nil { - if err == sql.ErrNoRows { - return AppSettings{ViewProofInView: false}, nil - } - return AppSettings{}, fmt.Errorf("read app setting view_proof_in_view: %w", err) +const ( + settingViewProofInView = "view_proof_in_view" + settingLiveActiveView = "live_active_view" + settingLiveShowGroupLive = "live_show_group_live" + settingLiveGroupDisplayMode = "live_group_display_mode" + settingLiveGroupFixedCode = "live_group_fixed_code" + settingLiveShowPrelimTie = "live_show_prelim_tie" + settingLiveShowPrelimOverall = "live_show_prelim_overall" + settingLiveShowFinalGroups = "live_show_final_groups" + settingLiveFinalDisplayMode = "live_final_display_mode" + settingLiveFinalFixedGroup = "live_final_fixed_group" + settingLiveShowFinalTie = "live_show_final_tie" + settingLiveShowPodium = "live_show_podium" + settingLiveRotationInterval = "live_rotation_interval_sec" + settingLiveRotationPlayers = "live_rotation_player_count" +) + +func defaultLiveModeSettings() LiveModeSettings { + return LiveModeSettings{ + ActiveView: "group_live", + ShowGroupLive: true, + GroupDisplayMode: "rotate", + GroupFixedCode: "", + ShowPrelimTie: true, + ShowPrelimOverall: true, + ShowFinalGroups: true, + FinalDisplayMode: "rotate", + FinalFixedGroup: 1, + ShowFinalTie: true, + ShowPodium: true, + RotationIntervalSec: 5, + RotationPlayerCount: 12, } - return AppSettings{ViewProofInView: settingBool(raw)}, nil } -func (a *App) updateViewProofInView(enabled bool) error { - _, err := a.db.Exec(` -INSERT INTO app_settings(key, value, updated_at) -VALUES('view_proof_in_view', ?, CURRENT_TIMESTAMP) -ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP -`, settingString(enabled)) +func (a *App) readSettings() (AppSettings, error) { + live := defaultLiveModeSettings() + + viewProofRaw, err := a.readSettingValue(settingViewProofInView) if err != nil { - return fmt.Errorf("update app setting view_proof_in_view: %w", err) + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingViewProofInView, err) + } + + if raw, err := a.readSettingValue(settingLiveShowGroupLive); err == nil { + live.ShowGroupLive = settingBool(raw) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowGroupLive, err) + } + if raw, err := a.readSettingValue(settingLiveActiveView); err == nil { + live.ActiveView = normalizeLiveActiveView(raw, live.ActiveView) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveActiveView, err) + } + + if raw, err := a.readSettingValue(settingLiveGroupDisplayMode); err == nil { + live.GroupDisplayMode = normalizeLiveDisplayMode(raw, live.GroupDisplayMode) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveGroupDisplayMode, err) + } + + if raw, err := a.readSettingValue(settingLiveGroupFixedCode); err == nil { + live.GroupFixedCode = strings.ToUpper(strings.TrimSpace(raw)) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveGroupFixedCode, err) + } + + if raw, err := a.readSettingValue(settingLiveShowPrelimTie); err == nil { + live.ShowPrelimTie = settingBool(raw) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowPrelimTie, err) + } + + if raw, err := a.readSettingValue(settingLiveShowPrelimOverall); err == nil { + live.ShowPrelimOverall = settingBool(raw) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowPrelimOverall, err) + } + + if raw, err := a.readSettingValue(settingLiveShowFinalGroups); err == nil { + live.ShowFinalGroups = settingBool(raw) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowFinalGroups, err) + } + + if raw, err := a.readSettingValue(settingLiveFinalDisplayMode); err == nil { + live.FinalDisplayMode = normalizeLiveDisplayMode(raw, live.FinalDisplayMode) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveFinalDisplayMode, err) + } + + if raw, err := a.readSettingValue(settingLiveFinalFixedGroup); err == nil { + live.FinalFixedGroup = normalizeLiveFinalFixedGroup(raw, live.FinalFixedGroup) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveFinalFixedGroup, err) + } + + if raw, err := a.readSettingValue(settingLiveShowFinalTie); err == nil { + live.ShowFinalTie = settingBool(raw) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowFinalTie, err) + } + + if raw, err := a.readSettingValue(settingLiveShowPodium); err == nil { + live.ShowPodium = settingBool(raw) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowPodium, err) + } + + if raw, err := a.readSettingValue(settingLiveRotationInterval); err == nil { + live.RotationIntervalSec = normalizeRotationInterval(raw, live.RotationIntervalSec) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveRotationInterval, err) + } + if raw, err := a.readSettingValue(settingLiveRotationPlayers); err == nil { + live.RotationPlayerCount = normalizeRotationPlayerCount(raw, live.RotationPlayerCount) + } else { + return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveRotationPlayers, err) + } + + return AppSettings{ + ViewProofInView: settingBool(viewProofRaw), + LiveMode: live, + }, nil +} + +func (a *App) updateSettings(req AdminSettingsUpdateRequest) error { + tx, err := a.db.Begin() + if err != nil { + return fmt.Errorf("begin settings transaction: %w", err) + } + defer tx.Rollback() + + hasUpdate := false + + if req.ViewProofInView != nil { + hasUpdate = true + if err := upsertSetting(tx, settingViewProofInView, settingString(*req.ViewProofInView)); err != nil { + return err + } + } + + if req.LiveMode != nil { + patch := req.LiveMode + if patch.ActiveView != nil { + hasUpdate = true + value := normalizeLiveActiveView(*patch.ActiveView, "group_live") + if err := upsertSetting(tx, settingLiveActiveView, value); err != nil { + return err + } + } + if patch.ShowGroupLive != nil { + hasUpdate = true + if err := upsertSetting(tx, settingLiveShowGroupLive, settingString(*patch.ShowGroupLive)); err != nil { + return err + } + } + if patch.GroupDisplayMode != nil { + hasUpdate = true + value := normalizeLiveDisplayMode(*patch.GroupDisplayMode, "rotate") + if err := upsertSetting(tx, settingLiveGroupDisplayMode, value); err != nil { + return err + } + } + if patch.GroupFixedCode != nil { + hasUpdate = true + value := strings.ToUpper(strings.TrimSpace(*patch.GroupFixedCode)) + if err := upsertSetting(tx, settingLiveGroupFixedCode, value); err != nil { + return err + } + } + if patch.ShowPrelimTie != nil { + hasUpdate = true + if err := upsertSetting(tx, settingLiveShowPrelimTie, settingString(*patch.ShowPrelimTie)); err != nil { + return err + } + } + if patch.ShowPrelimOverall != nil { + hasUpdate = true + if err := upsertSetting(tx, settingLiveShowPrelimOverall, settingString(*patch.ShowPrelimOverall)); err != nil { + return err + } + } + if patch.ShowFinalGroups != nil { + hasUpdate = true + if err := upsertSetting(tx, settingLiveShowFinalGroups, settingString(*patch.ShowFinalGroups)); err != nil { + return err + } + } + if patch.FinalDisplayMode != nil { + hasUpdate = true + value := normalizeLiveDisplayMode(*patch.FinalDisplayMode, "rotate") + if err := upsertSetting(tx, settingLiveFinalDisplayMode, value); err != nil { + return err + } + } + if patch.FinalFixedGroup != nil { + hasUpdate = true + value := normalizeLiveFinalFixedGroupInt(*patch.FinalFixedGroup) + if err := upsertSetting(tx, settingLiveFinalFixedGroup, strconv.Itoa(value)); err != nil { + return err + } + } + if patch.ShowFinalTie != nil { + hasUpdate = true + if err := upsertSetting(tx, settingLiveShowFinalTie, settingString(*patch.ShowFinalTie)); err != nil { + return err + } + } + if patch.ShowPodium != nil { + hasUpdate = true + if err := upsertSetting(tx, settingLiveShowPodium, settingString(*patch.ShowPodium)); err != nil { + return err + } + } + if patch.RotationIntervalSec != nil { + hasUpdate = true + value := normalizeRotationIntervalInt(*patch.RotationIntervalSec) + if err := upsertSetting(tx, settingLiveRotationInterval, strconv.Itoa(value)); err != nil { + return err + } + } + if patch.RotationPlayerCount != nil { + hasUpdate = true + value := normalizeRotationPlayerCountInt(*patch.RotationPlayerCount) + if err := upsertSetting(tx, settingLiveRotationPlayers, strconv.Itoa(value)); err != nil { + return err + } + } + } + + if !hasUpdate { + return fmt.Errorf("no settings to update") + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("commit settings transaction: %w", err) + } + + return nil +} + +func upsertSetting(tx *sql.Tx, key string, value string) error { + _, err := tx.Exec(` +INSERT INTO app_settings(key, value, updated_at) +VALUES(?, ?, CURRENT_TIMESTAMP) +ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP +`, key, value) + if err != nil { + return fmt.Errorf("update app setting %s: %w", key, err) } return nil } +func (a *App) readSettingValue(key string) (string, error) { + var raw string + err := a.db.QueryRow(`SELECT value FROM app_settings WHERE key = ?`, key).Scan(&raw) + if err != nil { + if err == sql.ErrNoRows { + return "", nil + } + return "", err + } + return raw, nil +} + func settingBool(value string) bool { switch strings.TrimSpace(strings.ToLower(value)) { case "1", "true", "yes", "on": @@ -45,3 +287,80 @@ func settingString(value bool) string { } return "0" } + +func normalizeLiveDisplayMode(value string, fallback string) string { + normalized := strings.TrimSpace(strings.ToLower(value)) + if normalized == "fixed" { + return "fixed" + } + if normalized == "rotate" { + return "rotate" + } + if strings.TrimSpace(strings.ToLower(fallback)) == "fixed" { + return "fixed" + } + return "rotate" +} + +func normalizeLiveFinalFixedGroup(value string, fallback int) int { + parsed, err := strconv.Atoi(strings.TrimSpace(value)) + if err != nil { + return normalizeLiveFinalFixedGroupInt(fallback) + } + return normalizeLiveFinalFixedGroupInt(parsed) +} + +func normalizeLiveFinalFixedGroupInt(value int) int { + if value == 2 { + return 2 + } + return 1 +} + +func normalizeRotationInterval(raw string, fallback int) int { + parsed, err := strconv.Atoi(strings.TrimSpace(raw)) + if err != nil { + return normalizeRotationIntervalInt(fallback) + } + return normalizeRotationIntervalInt(parsed) +} + +func normalizeRotationIntervalInt(value int) int { + if value < 3 { + return 3 + } + if value > 30 { + return 30 + } + return value +} + +func normalizeRotationPlayerCount(raw string, fallback int) int { + parsed, err := strconv.Atoi(strings.TrimSpace(raw)) + if err != nil { + return normalizeRotationPlayerCountInt(fallback) + } + return normalizeRotationPlayerCountInt(parsed) +} + +func normalizeRotationPlayerCountInt(value int) int { + if value < 3 { + return 3 + } + if value > 40 { + return 40 + } + return value +} + +func normalizeLiveActiveView(value string, fallback string) string { + switch strings.TrimSpace(strings.ToLower(value)) { + case "group_live", "prelim_tie", "prelim_overall", "final_groups", "final_tie", "podium": + return strings.TrimSpace(strings.ToLower(value)) + default: + if strings.TrimSpace(strings.ToLower(fallback)) != "" { + return strings.TrimSpace(strings.ToLower(fallback)) + } + return "group_live" + } +} diff --git a/backend/state.go b/backend/state.go index 899bb45..61dd0c7 100644 --- a/backend/state.go +++ b/backend/state.go @@ -219,7 +219,7 @@ func computeDerived(players []Player, scores map[string]map[int]int) DerivedStat NameEn: p.NameEn, GroupCode: p.GroupCode, ImageData: p.ImageData, - Score: scores["preliminary"][p.ID], + Score: scoreTotal(scores, p.ID, preliminaryRoundStages), TieBreak: scores["prelim_tiebreak"][p.ID], }) } @@ -349,7 +349,7 @@ func computeDerived(players []Player, scores map[string]map[int]int) DerivedStat NameEn: p.NameEn, GroupCode: p.GroupCode, ImageData: p.ImageData, - Score: scores["final"][finalist.PlayerID], + Score: scoreTotal(scores, finalist.PlayerID, finalRoundStages), TieBreak: scores["final_tiebreak"][finalist.PlayerID], Seed: finalist.Seed, FinalGroup: finalist.FinalGroup, @@ -460,6 +460,18 @@ func computeDerived(players []Player, scores map[string]map[int]int) DerivedStat } } +func scoreTotal(scores map[string]map[int]int, playerID int, stages []string) int { + total := 0 + for _, stage := range stages { + stageMap, ok := scores[stage] + if !ok { + continue + } + total += stageMap[playerID] + } + return total +} + func assignDenseRankByScore(rows []RankingRow) { assignDenseRankBy(rows, func(a, b RankingRow) bool { return a.Score == b.Score diff --git a/frontend/public/bg.png b/frontend/public/bg.png new file mode 100644 index 0000000..c7472ce Binary files /dev/null and b/frontend/public/bg.png differ diff --git a/frontend/public/bg_live.png b/frontend/public/bg_live.png new file mode 100644 index 0000000..ffda60e Binary files /dev/null and b/frontend/public/bg_live.png differ diff --git a/frontend/public/logo.png b/frontend/public/logo.png new file mode 100644 index 0000000..a0f5c94 Binary files /dev/null and b/frontend/public/logo.png differ diff --git a/frontend/public/logo.svg b/frontend/public/logo.svg new file mode 100644 index 0000000..c8fbb20 --- /dev/null +++ b/frontend/public/logo.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 6e32f52..f747003 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -47,12 +47,15 @@ :final-group2="finalGroup2" :final-rows="finalRows" :final-tie="finalTie" - :score-for="scoreFor" + :final-total-score="finalTotalScore" + :preliminary-proof-stage="preliminaryProofStage" + :final-proof-stage="finalProofStage" :podium-ordered="podiumOrdered" :podium-image="podiumImage" :podium-name="podiumName" :podium-has-result="podiumHasResult" - :podium-score-display="podiumScoreDisplay" + :podium-score-main="podiumScoreMain" + :podium-tie-subtitle="podiumTieSubtitle" :can-view-proofs="canViewProofs" :has-score-proof="hasScoreProof" :score-proof-for="scoreProofFor" @@ -62,6 +65,32 @@ @open-proof="onOpenProofFromPayload" /> + +