diff --git a/src/main/webapp/src/hooks/useExternalWindow.jsx b/src/main/webapp/src/hooks/useExternalWindow.jsx
index 6603d47..1c455a0 100644
--- a/src/main/webapp/src/hooks/useExternalWindow.jsx
+++ b/src/main/webapp/src/hooks/useExternalWindow.jsx
@@ -1,6 +1,6 @@
import {createContext, useContext, useReducer} from "react";
-const PubAffContext = createContext({next: [], c1: undefined, c2: undefined, showScore: true});
+const PubAffContext = createContext({next: [], c1: undefined, c2: undefined, showScore: true, timeCb: undefined});
const PubAffDispatchContext = createContext(() => {
});
@@ -8,6 +8,12 @@ function reducer(state, action) {
switch (action.type) {
case 'SET_DATA':
return {...state, ...action.payload}
+ case 'CALL_TIME':
+ if (state.timeCb)
+ state.timeCb(action.payload)
+ return state
+ case 'CLEAR_CB_TIME':
+ return {...state, timeCb: undefined}
default:
return state
}
diff --git a/src/main/webapp/src/pages/competition/editor/CMTChronoPanel.jsx b/src/main/webapp/src/pages/competition/editor/CMTChronoPanel.jsx
new file mode 100644
index 0000000..f779c92
--- /dev/null
+++ b/src/main/webapp/src/pages/competition/editor/CMTChronoPanel.jsx
@@ -0,0 +1,177 @@
+import React, {useEffect, useRef, useState} from "react";
+import {usePubAffDispatch} from "../../../hooks/useExternalWindow.jsx";
+import {timePrint} from "../../../utils/Tools.js";
+
+export function ChronoPanel() {
+ const [config, setConfig] = useState({
+ time: Number(sessionStorage.getItem("chronoTime") || "90999"),
+ pause: Number(sessionStorage.getItem("chronoPause") || "60999")
+ })
+ const [chrono, setChrono] = useState({time: 0, startTime: 0})
+ const chronoText = useRef(null)
+ const state = useRef({chronoState: 0, countBlink: 20, lastColor: "black", lastTimeStr: "00:00"})
+ const publicAffDispatch = usePubAffDispatch();
+
+ const addTime = (time) => setChrono(prev => ({...prev, time: prev.time - time}))
+ const isRunning = () => chrono.startTime !== 0
+
+ const getTime = () => {
+ if (chrono.startTime === 0)
+ return chrono.time
+ return chrono.time + Date.now() - chrono.startTime
+ }
+
+ useEffect(() => {
+ const blinkRfDuration = 20
+ const state_ = state.current
+ const text_ = chronoText.current
+
+ const timer = setInterval(() => {
+ let currentDuration = config.time
+ let color = "black"
+ if (state_.chronoState === 1) {
+ color = (state_.countBlink < blinkRfDuration) ? "black" : "red"
+ } else if (state_.chronoState === 2) {
+ currentDuration = (state_.chronoState === 0) ? 10000 : config.pause
+ color = (state_.countBlink < blinkRfDuration) ? "green" : "red"
+ }
+ const timeStr = timePrint(currentDuration - getTime())
+
+ if (timeStr !== state_.lastTimeStr || color !== state_.lastColor)
+ publicAffDispatch({type: 'CALL_TIME', payload: {timeStr: timeStr, timeColor: color}})
+
+ if (timeStr !== state_.lastTimeStr) {
+ text_.textContent = timePrint(currentDuration - getTime())
+ state_.lastTimeStr = timeStr
+ }
+ if (color !== state_.lastColor) {
+ text_.style.color = color
+ state_.lastColor = color
+ }
+
+ if (state_.chronoState === 0 && isRunning()) {
+ state_.chronoState = 1
+ } else if (state_.chronoState === 1 && getTime() >= config.time) {
+ setChrono(prev => ({...prev, time: 0, startTime: Date.now()}))
+ state_.chronoState = 2
+ } else if (state_.chronoState === 2 && getTime() >= config.pause) {
+ setChrono(prev => ({...prev, time: 0, startTime: Date.now()}))
+ state_.chronoState = 1
+ }
+
+ if (isRunning()) {
+ state_.countBlink = 19
+ } else {
+ state_.countBlink++
+ if (state_.countBlink > 40)
+ state_.countBlink = 0
+ }
+ if (state_.chronoState === 0) {
+ clearInterval(timer)
+ }
+ }, 50);
+ return () => clearInterval(timer)
+ }, [chrono, config])
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ const form = e.target;
+ const timeStr = form[0].value;
+ const pauseStr = form[1].value;
+
+ const parseTime = (str) => {
+ const parts = str.split(":").map(part => parseInt(part, 10));
+ if (parts.length === 1) {
+ return parts[0] * 1000;
+ } else if (parts.length === 2) {
+ return (parts[0] * 60 + parts[1]) * 1000;
+ } else {
+ return 0;
+ }
+ }
+
+ const newTime = parseTime(timeStr) + 999;
+ const newPause = parseTime(pauseStr) + 999;
+
+ sessionStorage.setItem("chronoPause", newPause);
+ sessionStorage.setItem("chronoTime", newTime);
+
+ setConfig({time: newTime, pause: newPause});
+ }
+
+ return
+
+
+
+
+
+
+
{state.current.lastTimeStr}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Temps: {timePrint(config.time)}, pause: {timePrint(config.pause)}
+
+
+
+
+
+
+
+
+
Edition temps
+
+
+
+
+
+
+
+}
diff --git a/src/main/webapp/src/pages/competition/editor/CMTable.jsx b/src/main/webapp/src/pages/competition/editor/CMTable.jsx
index e030238..9929bc2 100644
--- a/src/main/webapp/src/pages/competition/editor/CMTable.jsx
+++ b/src/main/webapp/src/pages/competition/editor/CMTable.jsx
@@ -15,6 +15,7 @@ import {PubAffProvider, usePubAffDispatch} from "../../../hooks/useExternalWindo
import {faDisplay} from "@fortawesome/free-solid-svg-icons";
import {PubAffWindow} from "./PubAffWindow.jsx";
import {SimpleIconsScore} from "../../../assets/SimpleIconsScore.ts";
+import {ChronoPanel} from "./CMTChronoPanel.jsx";
function CupImg() {
return
-
-
+
Matches
@@ -94,6 +98,7 @@ function Menu() {
setShowPubAff(false);
externalWindow.current.close();
externalWindow.current = null;
+ publicAffDispatch({type: 'CLEAR_CB_TIME', payload: null});
sessionStorage.removeItem(windowName + "_open");
});
setShowPubAff(true);
@@ -279,7 +284,11 @@ function MatchList({matches, cat}) {
} else {
publicAffDispatch({
type: 'SET_DATA',
- payload: {c1: match.c1, c2: match.c2, next: marches2.filter(m => !m.end && m.poule === lice && m.id !== activeMatch).map(m => ({c1: m.c1, c2: m.c2}))}
+ payload: {
+ c1: match.c1,
+ c2: match.c2,
+ next: marches2.filter(m => !m.end && m.poule === lice && m.id !== activeMatch).map(m => ({c1: m.c1, c2: m.c2}))
+ }
});
}
}, [match]);
@@ -333,7 +342,8 @@ function MatchList({matches, cat}) {
{marches2.map((m, index) => (
- setActiveMatch(m.id)}>
+
setActiveMatch(m.id)}>
|
{liceName[(index - firstIndex) % liceName.length]} |
{m.poule} |
diff --git a/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx b/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx
index e2a0895..052b9de 100644
--- a/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx
+++ b/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx
@@ -1,7 +1,7 @@
import {useCombs} from "../../../hooks/useComb.jsx";
import {usePubAffState} from "../../../hooks/useExternalWindow.jsx";
import {SmartLogoBackgroundMemo} from "../../../components/SmartLogoBackground.jsx";
-import {useMemo} from 'react';
+import {useMemo, useRef} from 'react';
const vite_url = import.meta.env.VITE_URL;
@@ -13,18 +13,27 @@ const text1Style = {fontSize: "min(2.25vw, 8vh)", fontWeight: "bold", marginLeft
const text2Style = {fontSize: "min(1.7vw, 7vh)", fontWeight: "bold"};
export function PubAffWindow({document}) {
+ const chronoText = useRef(null)
+ const state2 = useRef({lastColor: "white", lastTimeStr: "01:30"})
const state = usePubAffState();
document.title = "A React portal window"
document.body.className = "bg-dark text-white overflow-hidden";
+ state.timeCb = (payload) => {
+ state2.current = {lastColor: payload.timeColor === "black" ? "white" : payload.timeColor, lastTimeStr: payload.timeStr}
+ chronoText.current.textContent = payload.timeStr
+ chronoText.current.style.color = payload.timeColor === "black" ? "white" : payload.timeColor
+ }
+
const showScore = state.showScore ?? true;
return <>
-
01:30
+
{state2.current.lastTimeStr}
{showScore &&
diff --git a/src/main/webapp/src/utils/Tools.js b/src/main/webapp/src/utils/Tools.js
index 21e9232..5821030 100644
--- a/src/main/webapp/src/utils/Tools.js
+++ b/src/main/webapp/src/utils/Tools.js
@@ -130,3 +130,26 @@ export function scorePrint(s1) {
return String(s1)
}
}
+
+export function timePrint(time, negSign = false) {
+ if (time === null || time === undefined)
+ return ""
+ const neg = time < 0
+ if (neg){
+ if (!negSign)
+ return "00:00"
+ time = -time
+ }
+
+ const ms = time % 1000
+ time = (time - ms) / 1000
+ const sec = time % 60
+ time = (time - sec) / 60
+ const min = time % 60
+ const hr = (time - min) / 60
+
+ return (neg ? "-" : "") +
+ (hr > 0 ? String(hr).padStart(2, '0') + ":" : "") +
+ String(min).padStart(2, '0') + ":" +
+ String(sec).padStart(2, '0')
+}