diff --git a/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java b/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java index de99f36..4126db2 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java @@ -17,7 +17,7 @@ public class CombEntity { private String lname; private String fname; Categorie categorie; - Long club; + String club_uuid; String club_str; Genre genre; String country; @@ -29,7 +29,7 @@ public class CombEntity { return null; return new CombEntity(model.getId(), model.getLname(), model.getFname(), model.getCategorie(), - model.getClub() == null ? null : model.getClub().getId(), + model.getClub() == null ? null : model.getClub().getClubId(), model.getClub() == null ? "Sans club" : model.getClub().getName(), model.getGenre(), model.getCountry(), 0, null); } @@ -49,7 +49,7 @@ public class CombEntity { MembreModel model = registerModel.getMembre(); return new CombEntity(model.getId(), model.getLname(), model.getFname(), registerModel.getCategorie(), - registerModel.getClub2() == null ? null : registerModel.getClub2().getId(), + registerModel.getClub2() == null ? null : registerModel.getClub2().getClubId(), registerModel.getClub2() == null ? "Sans club" : registerModel.getClub2().getName(), model.getGenre(), model.getCountry(), registerModel.getOverCategory(), registerModel.getWeight()); } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java index 16ddb09..fdaf2ea 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java @@ -186,6 +186,6 @@ public class AffiliationRequestEndpoints { public Uni getStatus( @Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id) throws URISyntaxException { return Utils.getMediaFile(id, media, "aff_request/status", "affiliation_request_" + id, - Uni.createFrom().nullItem()); + Uni.createFrom().nullItem(), false); } } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java index 7048e4f..a329192 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java @@ -335,7 +335,7 @@ public class ClubEndpoints { @Parameter(description = "Identifiant long (clubId) de club") @PathParam("clubId") String clubId) { return clubService.getByClubId(clubId).chain(Unchecked.function(clubModel -> { try { - return Utils.getMediaFile((clubModel != null) ? clubModel.getId() : -1, media, "ppClub", + return Utils.getMediaFileNoDefault((clubModel != null) ? clubModel.getId() : -1, media, "ppClub", Uni.createFrom().nullItem()); } catch (URISyntaxException e) { throw new InternalError(); @@ -358,7 +358,7 @@ public class ClubEndpoints { return clubService.getById(id).onItem().invoke(checkPerm).chain(Unchecked.function(clubModel -> { try { return Utils.getMediaFile(clubModel.getId(), media, "clubStatus", - "statue-" + clubModel.getName(), Uni.createFrom().nullItem()); + "statue-" + clubModel.getName(), Uni.createFrom().nullItem(), false); } catch (URISyntaxException e) { throw new InternalError(); } diff --git a/src/main/java/fr/titionfire/ffsaf/utils/Utils.java b/src/main/java/fr/titionfire/ffsaf/utils/Utils.java index b4a74a9..550c6e8 100644 --- a/src/main/java/fr/titionfire/ffsaf/utils/Utils.java +++ b/src/main/java/fr/titionfire/ffsaf/utils/Utils.java @@ -150,11 +150,16 @@ public class Utils { public static Uni getMediaFile(long id, String media, String dirname, Uni uniBase) throws URISyntaxException { - return getMediaFile(id, media, dirname, null, uniBase); + return getMediaFile(id, media, dirname, null, uniBase, true); + } + + public static Uni getMediaFileNoDefault(long id, String media, String dirname, + Uni uniBase) throws URISyntaxException { + return getMediaFile(id, media, dirname, null, uniBase, false); } public static Uni getMediaFile(long id, String media, String dirname, String out_filename, - Uni uniBase) throws URISyntaxException { + Uni uniBase, boolean default_) throws URISyntaxException { Future> future = CompletableFuture.supplyAsync(() -> { FilenameFilter filter = (directory, filename) -> filename.startsWith(id + "."); File[] files = new File(media, dirname).listFiles(filter); @@ -182,19 +187,25 @@ public class Utils { return uniBase.chain(__ -> Uni.createFrom().future(future) .chain(filePair -> { if (filePair == null) { - return Uni.createFrom().future(future2).map(data -> { - if (data == null) - return Response.noContent().build(); + if (default_) { + return Uni.createFrom().future(future2).map(data -> { + if (data == null) + return Response.noContent().build(); - String mimeType = "image/apng"; - Response.ResponseBuilder resp = Response.ok(data); - resp.type(MediaType.APPLICATION_OCTET_STREAM); - resp.header(HttpHeaders.CONTENT_LENGTH, data.length); - resp.header(HttpHeaders.CONTENT_TYPE, mimeType); - resp.header(HttpHeaders.CONTENT_DISPOSITION, - "inline; " + ((out_filename == null) ? "" : "filename=\"" + out_filename + "\"")); - return resp.build(); - }); + String mimeType = "image/apng"; + Response.ResponseBuilder resp = Response.ok(data); + resp.type(MediaType.APPLICATION_OCTET_STREAM); + resp.header(HttpHeaders.CONTENT_LENGTH, data.length); + resp.header(HttpHeaders.CONTENT_TYPE, mimeType); + resp.header(HttpHeaders.CONTENT_DISPOSITION, + "inline; " + ((out_filename == null) ? "" : "filename=\"" + out_filename + "\"")); + return resp.build(); + }); + } else { + Response.ResponseBuilder resp = Response.status(404); + resp.header(HttpHeaders.CACHE_CONTROL, "max-age=600"); + return Uni.createFrom().item(resp.build()); + } } else { return Uni.createFrom().item(() -> { String mimeType = URLConnection.guessContentTypeFromName(filePair.getKey().getName()); diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RRegister.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RRegister.java index b5189ca..c1f7f7c 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/recv/RRegister.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RRegister.java @@ -22,7 +22,7 @@ public class RRegister { @Inject CompetitionRepository competitionRepository; - @WSReceiver(code = "getRegister", permission = PermLevel.ADMIN) + @WSReceiver(code = "getRegister", permission = PermLevel.TABLE) public Uni> getRegister(WebSocketConnection connection, Object o) { return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult() .call(cm -> Mutiny.fetch(cm.getInsc())) diff --git a/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java b/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java index 4815298..9197d2e 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java @@ -40,7 +40,7 @@ public class SRegister { public Uni send(String uuid, String code, Object data) { List> queue = connections.findByEndpointId(CompetitionWS.class.getCanonicalName()).stream() .filter(c -> c.pathParam("uuid").equals(uuid) && PermLevel.valueOf( - c.userData().get(UserData.TypedKey.forString("prem"))).ordinal() >= PermLevel.ADMIN.ordinal()) + c.userData().get(UserData.TypedKey.forString("prem"))).ordinal() >= PermLevel.TABLE.ordinal()) .map(c -> c.sendText( new MessageOut(UUID.randomUUID(), code, MessageType.NOTIFY, data))) .toList(); diff --git a/src/main/webapp/src/assets/SimpleIconsScore.ts b/src/main/webapp/src/assets/SimpleIconsScore.ts new file mode 100644 index 0000000..5be0353 --- /dev/null +++ b/src/main/webapp/src/assets/SimpleIconsScore.ts @@ -0,0 +1,26 @@ +import { + IconDefinition, + IconName, + IconPrefix +} from "@fortawesome/fontawesome-svg-core"; + +export const SimpleIconsScore: IconDefinition = { + icon: [ + // SVG viewbox width (in pixels) + 122.88, + + // SVG viewbox height (in pixels) + 100.08, + + // Aliases (not needed) + [], + + // Unicode as hex value (not needed) + "", + + // SVG path data + "M5.49,0h55.95h55.95c1.51,0,2.89,0.62,3.88,1.61c0.99,0.99,1.61,2.37,1.61,3.88v75.79c0,1.51-0.62,2.89-1.61,3.88 c-0.99,0.99-2.37,1.61-3.88,1.61h-25v12.15c0,0.64-0.52,1.16-1.16,1.16H31.66c-0.65,0-1.17-0.53-1.17-1.17V86.77h-25 c-1.51,0-2.89-0.62-3.88-1.61C0.62,84.17,0,82.8,0,81.28V5.49C0,3.98,0.62,2.6,1.61,1.61C2.6,0.62,3.98,0,5.49,0L5.49,0z M45.45,37.11v13.88c0,3.16-0.18,5.45-0.52,6.89c-0.34,1.45-1.05,2.79-2.13,4.05c-1.08,1.25-2.38,2.15-3.9,2.69 c-1.52,0.55-3.22,0.82-5.11,0.82c-2.48,0-4.54-0.29-6.19-0.86c-1.64-0.58-2.95-1.47-3.93-2.68c-0.97-1.22-1.67-2.5-2.08-3.84 c-0.41-1.35-0.61-3.49-0.61-6.42V37.11c0-3.83,0.33-6.69,0.99-8.59c0.66-1.9,1.97-3.43,3.93-4.58c1.96-1.15,4.33-1.72,7.12-1.72 c2.28,0,4.32,0.39,6.12,1.19c1.8,0.8,3.14,1.77,4.03,2.92c0.89,1.15,1.5,2.44,1.82,3.88C45.29,31.66,45.45,33.95,45.45,37.11 L45.45,37.11z M35.08,33.63c0-2.21-0.11-3.59-0.32-4.15c-0.21-0.55-0.71-0.83-1.51-0.83c-0.77,0-1.28,0.3-1.53,0.89 c-0.25,0.59-0.38,1.96-0.38,4.1v20.29c0,2.41,0.11,3.87,0.35,4.36c0.23,0.5,0.73,0.75,1.51,0.75c0.77,0,1.27-0.29,1.52-0.88 c0.24-0.58,0.36-1.89,0.36-3.92V33.63L35.08,33.63z M98.87,23.01v41.64H88.49V42.3c0-3.23-0.07-5.18-0.23-5.83 c-0.15-0.65-0.58-1.15-1.27-1.48c-0.69-0.33-2.24-0.5-4.63-0.5h-1.03v-4.83c5.02-1.07,8.83-3.29,11.42-6.64H98.87L98.87,23.01z M64.96,7.05v72.68h50.87V7.05H64.96L64.96,7.05z M57.92,79.73V7.05H7.05v72.68H57.92L57.92,79.73z" + ], + iconName: "simple-icons-score" as IconName, + prefix: "simple-icons" as IconPrefix +}; diff --git a/src/main/webapp/src/components/SmartLogoBackground.jsx b/src/main/webapp/src/components/SmartLogoBackground.jsx index afa630d..ca5a42a 100644 --- a/src/main/webapp/src/components/SmartLogoBackground.jsx +++ b/src/main/webapp/src/components/SmartLogoBackground.jsx @@ -15,12 +15,15 @@ export function SmartLogoBackground({ }) { const canvasRef = useRef(null); const [background, setBackground] = useState(defaultBackground); + const [load, setLoad] = useState(false) useEffect(() => { if (cache[src]) { setBackground(cache[src]); return; } + if (!load) + return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); @@ -29,6 +32,11 @@ export function SmartLogoBackground({ img.crossOrigin = 'Anonymous'; img.src = src; + // Prevent error logging + img.onerror = function () { + return true; + } + img.onload = () => { canvas.width = img.width; canvas.height = img.height; @@ -92,22 +100,18 @@ export function SmartLogoBackground({ } } } - - // Prevent error logging - img.onerror = e => { - //e.stopPropagation() - //e.stopImmediatePropagation() - //e.preventDefault() - } - }, [src, darkBackground, lightBackground, defaultBackground, tolerance, minPixels]); + }, [src, darkBackground, lightBackground, defaultBackground, tolerance, minPixels, load]); return <> {alt} { - e.preventDefault() e.target.style.opacity = "0" - } - } onLoad={e => e.target.style.opacity = "1"}/> + setLoad(false) + }} + onLoad={e => { + e.target.style.opacity = "1" + setLoad(true) + }}/> } diff --git a/src/main/webapp/src/hooks/useComb.jsx b/src/main/webapp/src/hooks/useComb.jsx index 6fe3604..4f1fb7b 100644 --- a/src/main/webapp/src/hooks/useComb.jsx +++ b/src/main/webapp/src/hooks/useComb.jsx @@ -1,4 +1,5 @@ -import {createContext, useContext, useReducer} from "react"; +import {createContext, useContext, useEffect, useReducer} from "react"; +import {useWS} from "./useWS.jsx"; const CombsContext = createContext({}); const CombsDispatchContext = createContext(() => { @@ -16,11 +17,18 @@ function compareCombs(a, b) { function reducer(state, action) { switch (action.type) { case 'SET_COMB': - if (state[action.payload.id] === undefined || !compareCombs(state[action.payload.id], action.payload)) { - console.debug("Updating comb", action.payload); + const comb = (action.payload.source === "register") ? action.payload.data : ({ + id: action.payload.data.id, + fname: action.payload.data.fname, + lname: action.payload.data.lname, + genre: action.payload.data.genre, + country: action.payload.data.country, + }) + if (state[comb.id] === undefined || !compareCombs(comb, state[comb.id])) { + console.debug("Updating comb", comb); return { ...state, - [action.payload.id]: action.payload.value + [comb.id]: comb } } return state @@ -58,12 +66,30 @@ function reducer(state, action) { } } +function WSListener({dispatch}) { + const {dispatch: dispatchWS} = useWS() + + useEffect(() => { + const sendRegister = ({data}) => { + dispatch({type: 'SET_ALL', payload: {source: "register", data: data}}); + } + + dispatchWS({type: 'addListener', payload: {callback: sendRegister, code: 'sendRegister'}}) + return () => { + dispatchWS({type: 'removeListener', payload: {callback: sendRegister, code: 'sendRegister'}}) + } + }, []); + + return <> +} + export function CombsProvider({children}) { const [combs, dispatch] = useReducer(reducer, {}) return {children} + } diff --git a/src/main/webapp/src/hooks/useExternalWindow.jsx b/src/main/webapp/src/hooks/useExternalWindow.jsx index cb144d2..6603d47 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}); +const PubAffContext = createContext({next: [], c1: undefined, c2: undefined, showScore: true}); const PubAffDispatchContext = createContext(() => { }); diff --git a/src/main/webapp/src/pages/competition/editor/CMTable.jsx b/src/main/webapp/src/pages/competition/editor/CMTable.jsx index 2e52036..e030238 100644 --- a/src/main/webapp/src/pages/competition/editor/CMTable.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMTable.jsx @@ -11,9 +11,10 @@ import {scorePrint, win} from "../../../utils/Tools.js"; import {toast} from "react-toastify"; import {createPortal} from "react-dom"; import {copyStyles} from "../../../utils/copyStyles.js"; -import {PubAffProvider, usePubAffDispatch, usePubAffState} from "../../../hooks/useExternalWindow.jsx"; +import {PubAffProvider, usePubAffDispatch} from "../../../hooks/useExternalWindow.jsx"; import {faDisplay} from "@fortawesome/free-solid-svg-icons"; import {PubAffWindow} from "./PubAffWindow.jsx"; +import {SimpleIconsScore} from "../../../assets/SimpleIconsScore.ts"; function CupImg() { return { + if (data === null) + return; + combDispatch({type: 'SET_ALL', payload: {source: "register", data: data}}); + }, [data]); return
@@ -40,7 +48,7 @@ export function CMTable() {
Matches
- +
@@ -48,16 +56,18 @@ export function CMTable() {
- + } const windowName = "FFSAFScorePublicWindow"; -function Menu({menuAction}) { +function Menu() { const e = document.getElementById("actionMenu") + const publicAffDispatch = usePubAffDispatch() const [showPubAff, setShowPubAff] = useState(false) + const [showScore, setShowScore] = useState(true) const externalWindow = useRef(null) const containerEl = useRef(document.createElement("div")) @@ -93,20 +103,28 @@ function Menu({menuAction}) { } } + const handleScore = __ => { + setShowScore(!showScore); + publicAffDispatch({type: 'SET_DATA', payload: {showScore: !showScore}}); + } + if (!e) return <>; return <> {createPortal( <>
- + , document.getElementById("actionMenu"))} {externalWindow.current && createPortal(, containerEl.current)} } -function CategorieSelect({catId, setCatId, menuAction}) { +function CategorieSelect({catId, setCatId}) { const setLoading = useLoadingSwitcher() const {data: cats, setData: setCats} = useRequestWS('getAllCategory', {}, setLoading); const {dispatch} = useWS(); @@ -130,11 +148,11 @@ function CategorieSelect({catId, setCatId, menuAction}) { ))} - {catId !== -1 && } + {catId !== -1 && } } -function MatchPanel({catId, cat, menuAction}) { +function MatchPanel({catId, cat}) { const setLoading = useLoadingSwitcher() const {sendRequest, dispatch} = useWS(); const [trees, setTrees] = useState([]); @@ -206,10 +224,10 @@ function MatchPanel({catId, cat, menuAction}) { } }, [catId]); - return + return } -function ListMatch({cat, matches, trees, menuAction}) { +function ListMatch({cat, matches, trees}) { const [type, setType] = useState(1); useEffect(() => { @@ -246,6 +264,7 @@ function ListMatch({cat, matches, trees, menuAction}) { function MatchList({matches, cat}) { const [activeMatch, setActiveMatch] = useState(null) + const [lice, setLice] = useState(localStorage.getItem("cm_lice") || "A") const publicAffDispatch = usePubAffDispatch(); const liceName = (cat.liceName || "N/A").split(";"); @@ -260,7 +279,7 @@ function MatchList({matches, cat}) { } else { publicAffDispatch({ type: 'SET_DATA', - payload: {c1: match.c1, c2: match.c2, next: marches2.filter(m => !m.end && 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]); @@ -269,17 +288,36 @@ function MatchList({matches, cat}) { // setActiveMatch(null); //}, [cat]) + useEffect(() => { + if (match && match.poule !== lice) + setActiveMatch(marches2.find(m => !m.end && m.poule === lice)?.id) + }, [lice]); + useEffect(() => { if (marches2.length === 0) return; if (marches2.some(m => m.id === activeMatch)) return; - setActiveMatch(marches2.find(m => !m.end)?.id); + setActiveMatch(marches2.find(m => !m.end && m.poule === lice)?.id); }, [matches]) const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1; return <> + {liceName.length > 1 && +
+ + +
+ } +
@@ -295,7 +333,7 @@ function MatchList({matches, cat}) { {marches2.map((m, index) => ( - setActiveMatch(m.id)}> + setActiveMatch(m.id)}> diff --git a/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx b/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx index 64ddb7b..e2a0895 100644 --- a/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx +++ b/src/main/webapp/src/pages/competition/editor/PubAffWindow.jsx @@ -1,5 +1,9 @@ import {useCombs} from "../../../hooks/useComb.jsx"; import {usePubAffState} from "../../../hooks/useExternalWindow.jsx"; +import {SmartLogoBackgroundMemo} from "../../../components/SmartLogoBackground.jsx"; +import {useMemo} from 'react'; + +const vite_url = import.meta.env.VITE_URL; const noMP = {margin: 0, padding: 0}; const redBackground = "radial-gradient(circle, #C80000FF 0%, #000000FF 100%)" @@ -14,7 +18,7 @@ export function PubAffWindow({document}) { document.title = "A React portal window" document.body.className = "bg-dark text-white overflow-hidden"; - const showScore = false; + const showScore = state.showScore ?? true; return <>
@@ -99,7 +101,8 @@ function MatchDisplay({state}) {
{c2.fname} {c2.lname}
- {index !== combs.length - 1 &&
} + {index !== combs.length - 1 && +
}
})} @@ -107,11 +110,18 @@ function MatchDisplay({state}) {
} +const logoStyle = {width: "6vw", height: "min(11vh, 6vw)", objectFit: "contain", margin: "0 .5vw"}; + function CombDisplay({combId, background, children}) { const {getComb} = useCombs(); const comb = getComb(combId, ""); - //console.log("Rendering CombDisplay for", combId, comb); + const logoAlt = useMemo(() => { + return comb?.club_str + }, [comb]); + const logoSrc = useMemo(() => { + return `${vite_url}/api/club/${comb?.club_uuid}/logo` + }, [comb]); return
{comb !== "" && <> - {"fr"} -
{comb.fname} {comb.lname}
+ +
{comb.fname} {comb.lname}
{comb.country} }
{liceName[(index - firstIndex) % liceName.length]} {m.poule}