dev-comp #72
@ -1,6 +1,6 @@
|
|||||||
import {createContext, useContext, useReducer} from "react";
|
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(() => {
|
const PubAffDispatchContext = createContext(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -8,6 +8,12 @@ function reducer(state, action) {
|
|||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'SET_DATA':
|
case 'SET_DATA':
|
||||||
return {...state, ...action.payload}
|
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:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
177
src/main/webapp/src/pages/competition/editor/CMTChronoPanel.jsx
Normal file
177
src/main/webapp/src/pages/competition/editor/CMTChronoPanel.jsx
Normal file
@ -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 <div>
|
||||||
|
<div className="row">
|
||||||
|
<button className="btn btn-primary col-6 col-sm-8 col-md-9"
|
||||||
|
onClick={__ => isRunning() ?
|
||||||
|
setChrono(prev => ({...prev, time: prev.time + Date.now() - prev.startTime, startTime: 0})) :
|
||||||
|
setChrono(prev => ({...prev, startTime: Date.now()}))}>
|
||||||
|
{isRunning() ? "Arrêter" : "Démarrer"}</button>
|
||||||
|
<button className="btn btn-danger col" onClick={__ => {
|
||||||
|
setChrono(prev => ({...prev, time: 0, startTime: 0}))
|
||||||
|
state.current.chronoState = 0
|
||||||
|
}}>Réinitialiser
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="row" style={{marginTop: "0.5em"}}>
|
||||||
|
<div className="col-12 col-sm-8 col-md-9">
|
||||||
|
<h1 ref={chronoText}
|
||||||
|
style={{fontSize: "min(19vw, 7.5em)", textAlign: "center", color: state.current.lastColor}}>{state.current.lastTimeStr}</h1>
|
||||||
|
</div>
|
||||||
|
<div className="col" style={{margin: "auto 0"}}>
|
||||||
|
<div className="row">
|
||||||
|
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(-10000)}>-10 s</button>
|
||||||
|
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(10000)}>+10 s</button>
|
||||||
|
</div>
|
||||||
|
<div className="row" style={{marginTop: "0.5em"}}>
|
||||||
|
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(-1000)}>-1 s</button>
|
||||||
|
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(1000)}>+1 s</button>
|
||||||
|
</div>
|
||||||
|
<div className="row" style={{marginTop: "0.5em"}}>
|
||||||
|
<button className="btn btn-outline-secondary col-12" onClick={__ => {
|
||||||
|
const timeStr = prompt("Entrez le temps en s", "0");
|
||||||
|
if (timeStr === null)
|
||||||
|
return;
|
||||||
|
addTime(parseInt(timeStr, 10) * 1000);
|
||||||
|
}}>+/- ... s
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="row" style={{marginTop: "0.5em"}}>
|
||||||
|
<div className="col-12 col-sm-8" style={{margin: 'auto 0'}}>
|
||||||
|
<div>Temps: {timePrint(config.time)}, pause: {timePrint(config.pause)}</div>
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-secondary col" data-bs-toggle="modal" data-bs-target="#timeModal">Définir le temps
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal fade" id="timeModal" tabIndex="-1" aria-labelledby="timeModalLabel" aria-hidden="true">
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h5 className="modal-title">Edition temps</h5>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="modal-body">
|
||||||
|
<div className="input-group mb-3">
|
||||||
|
<span className="input-group-text">Durée round</span>
|
||||||
|
<input type="text" className="form-control" placeholder="0" aria-label="Min" defaultValue={timePrint(config.time)}/>
|
||||||
|
<span className="input-group-text">(mm:ss)</span>
|
||||||
|
</div>
|
||||||
|
<div className="input-group mb-3">
|
||||||
|
<span className="input-group-text">Durée pause</span>
|
||||||
|
<input type="text" className="form-control" placeholder="0" aria-label="Min" defaultValue={timePrint(config.pause)}/>
|
||||||
|
<span className="input-group-text">(mm:ss)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Valider</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ import {PubAffProvider, usePubAffDispatch} from "../../../hooks/useExternalWindo
|
|||||||
import {faDisplay} from "@fortawesome/free-solid-svg-icons";
|
import {faDisplay} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {PubAffWindow} from "./PubAffWindow.jsx";
|
import {PubAffWindow} from "./PubAffWindow.jsx";
|
||||||
import {SimpleIconsScore} from "../../../assets/SimpleIconsScore.ts";
|
import {SimpleIconsScore} from "../../../assets/SimpleIconsScore.ts";
|
||||||
|
import {ChronoPanel} from "./CMTChronoPanel.jsx";
|
||||||
|
|
||||||
function CupImg() {
|
function CupImg() {
|
||||||
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
||||||
@ -37,15 +38,18 @@ export function CMTable() {
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-12 col-lg">
|
<div className="col-md-12 col-lg">
|
||||||
<div style={{backgroundColor: "#00c700"}}>
|
<div className="card mb-3">
|
||||||
A
|
<div className="card-header">Chronomètre</div>
|
||||||
|
<div className="card-body">
|
||||||
|
<ChronoPanel/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{backgroundColor: "#0099c7"}}>
|
<div style={{backgroundColor: "#0099c7"}}>
|
||||||
B
|
B
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-12 col-xl-6 col-xxl-5">
|
<div className="col-md-12 col-xl-6 col-xxl-5">
|
||||||
<div className="card">
|
<div className="card mb-3">
|
||||||
<div className="card-header">Matches</div>
|
<div className="card-header">Matches</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<CategorieSelect catId={catId} setCatId={setCatId}/>
|
<CategorieSelect catId={catId} setCatId={setCatId}/>
|
||||||
@ -94,6 +98,7 @@ function Menu() {
|
|||||||
setShowPubAff(false);
|
setShowPubAff(false);
|
||||||
externalWindow.current.close();
|
externalWindow.current.close();
|
||||||
externalWindow.current = null;
|
externalWindow.current = null;
|
||||||
|
publicAffDispatch({type: 'CLEAR_CB_TIME', payload: null});
|
||||||
sessionStorage.removeItem(windowName + "_open");
|
sessionStorage.removeItem(windowName + "_open");
|
||||||
});
|
});
|
||||||
setShowPubAff(true);
|
setShowPubAff(true);
|
||||||
@ -279,7 +284,11 @@ function MatchList({matches, cat}) {
|
|||||||
} else {
|
} else {
|
||||||
publicAffDispatch({
|
publicAffDispatch({
|
||||||
type: 'SET_DATA',
|
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]);
|
}, [match]);
|
||||||
@ -333,7 +342,8 @@ function MatchList({matches, cat}) {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="table-group-divider">
|
<tbody className="table-group-divider">
|
||||||
{marches2.map((m, index) => (
|
{marches2.map((m, index) => (
|
||||||
<tr key={m.id} className={m.id === activeMatch ? "table-info" : (m.poule === lice ? "" : "table-warning")} onClick={() => setActiveMatch(m.id)}>
|
<tr key={m.id} className={m.id === activeMatch ? "table-info" : (m.poule === lice ? "" : "table-warning")}
|
||||||
|
onClick={() => setActiveMatch(m.id)}>
|
||||||
<td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>
|
<td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>
|
||||||
{liceName[(index - firstIndex) % liceName.length]}</td>
|
{liceName[(index - firstIndex) % liceName.length]}</td>
|
||||||
<td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>{m.poule}</td>
|
<td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>{m.poule}</td>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {useCombs} from "../../../hooks/useComb.jsx";
|
import {useCombs} from "../../../hooks/useComb.jsx";
|
||||||
import {usePubAffState} from "../../../hooks/useExternalWindow.jsx";
|
import {usePubAffState} from "../../../hooks/useExternalWindow.jsx";
|
||||||
import {SmartLogoBackgroundMemo} from "../../../components/SmartLogoBackground.jsx";
|
import {SmartLogoBackgroundMemo} from "../../../components/SmartLogoBackground.jsx";
|
||||||
import {useMemo} from 'react';
|
import {useMemo, useRef} from 'react';
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
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"};
|
const text2Style = {fontSize: "min(1.7vw, 7vh)", fontWeight: "bold"};
|
||||||
|
|
||||||
export function PubAffWindow({document}) {
|
export function PubAffWindow({document}) {
|
||||||
|
const chronoText = useRef(null)
|
||||||
|
const state2 = useRef({lastColor: "white", lastTimeStr: "01:30"})
|
||||||
const state = usePubAffState();
|
const state = usePubAffState();
|
||||||
|
|
||||||
document.title = "A React portal window"
|
document.title = "A React portal window"
|
||||||
document.body.className = "bg-dark text-white overflow-hidden";
|
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;
|
const showScore = state.showScore ?? true;
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div className="row text-center"
|
<div className="row text-center"
|
||||||
style={{background: "linear-gradient(to bottom, #000000, #323232)", height: `calc(100vh - ${combHeight} * 2)`, ...noMP}}>
|
style={{background: "linear-gradient(to bottom, #000000, #323232)", height: `calc(100vh - ${combHeight} * 2)`, ...noMP}}>
|
||||||
<div>
|
<div>
|
||||||
<div style={{fontSize: "30vh", lineHeight: "30vh"}}>01:30</div>
|
<div ref={chronoText}
|
||||||
|
style={{fontSize: "30vh", lineHeight: "30vh", color: state2.current.lastColor}}>{state2.current.lastTimeStr}</div>
|
||||||
{showScore &&
|
{showScore &&
|
||||||
<div className="row" style={noMP}>
|
<div className="row" style={noMP}>
|
||||||
<div className="col-4" style={noMP}>
|
<div className="col-4" style={noMP}>
|
||||||
|
|||||||
@ -130,3 +130,26 @@ export function scorePrint(s1) {
|
|||||||
return String(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')
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user