From a22225ee7a4b1f67cef2f2a8e937b4708698e689 Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Mon, 23 Feb 2026 11:58:39 +0100 Subject: [PATCH 1/4] feat: classement club --- .../ffsaf/domain/service/ResultService.java | 106 +++++++++++++++++- .../ffsaf/rest/ExternalResultEndpoints.java | 8 ++ .../ffsaf/rest/ResultEndpoints.java | 8 +- src/main/webapp/public/competition.js | 59 ++++++++++ src/main/webapp/public/locales/en/result.json | 7 +- src/main/webapp/public/locales/fr/result.json | 5 + .../webapp/src/pages/result/ResultView.jsx | 44 +++++++- 7 files changed, 232 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java index a9cc7bd..d1ed494 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java @@ -7,6 +7,8 @@ import fr.titionfire.ffsaf.rest.data.ResultCategoryData; import fr.titionfire.ffsaf.rest.exception.DBadRequestException; import fr.titionfire.ffsaf.rest.exception.DForbiddenException; import fr.titionfire.ffsaf.utils.*; +import io.quarkus.cache.Cache; +import io.quarkus.cache.CacheName; import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.quarkus.runtime.annotations.RegisterForReflection; import io.smallrye.mutiny.Multi; @@ -15,6 +17,7 @@ import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import lombok.Builder; +import lombok.Data; import org.hibernate.reactive.mutiny.Mutiny; import java.util.*; @@ -52,6 +55,10 @@ public class ResultService { @Inject TradService trad; + @Inject + @CacheName("club-classement") + Cache cacheClubClassement; + private static final HashMap combTempIds = new HashMap<>(); private static String getCombTempId(Long key) { @@ -214,7 +221,7 @@ public class ResultService { } public void getClassementArray(CategoryModel categoryModel, MembreModel membreModel, List cards, - ResultCategoryData out) { + ResultCategoryData out) { if ((categoryModel.getType() & 2) != 0) { AtomicInteger rank = new AtomicInteger(0); categoryModel.getTree().stream() @@ -231,7 +238,8 @@ public class ResultService { } else { for (List list : out.getRankArray().values()) { for (ResultCategoryData.RankArray r : list) { - out.getClassement().add(new ResultCategoryData.ClassementData(r.getRank(), r.getComb(), r.getName())); + out.getClassement() + .add(new ResultCategoryData.ClassementData(r.getRank(), r.getComb(), r.getName())); } } } @@ -724,6 +732,100 @@ public class ResultService { } } + public Uni> getAllClubArray(String uuid) { + return getAllClubArray(uuid, true); + } + + public Uni> getAllClubArray(String uuid, SecurityCtx securityCtx) { + return hasAccess(uuid, securityCtx).chain(membreModel -> getAllClubArray(uuid, true)); + } + + public Uni> getAllClubArray(String uuid, boolean cache) { + List cards = new java.util.ArrayList<>(); + + //noinspection unchecked + cacheClubClassement.invalidateIf( + (p) -> ((Pair>) p).getKey() > System.currentTimeMillis()); + + if (!cache) + cacheClubClassement.invalidate(uuid); + + return cacheClubClassement.getAsync(uuid, k -> cardRepository.list("competition.uuid = ?1", uuid) + .invoke(__ -> System.out.println("Cache miss for club classement with uuid " + uuid)) + .invoke(cards::addAll) + .chain(__ -> matchRepository.list("category.compet.uuid = ?1", uuid)) + .chain(matchs -> { + HashMap> map = new HashMap<>(); + for (MatchModel match : matchs) { + if (!map.containsKey(match.getCategory())) + map.put(match.getCategory(), new java.util.ArrayList<>()); + map.get(match.getCategory()).add(match); + } + + return Multi.createFrom().iterable(map.entrySet()) + .onItem().call(entry -> Mutiny.fetch(entry.getKey().getTree())) + .map(entry -> { + ResultCategoryData tmp = new ResultCategoryData(); + + getArray2(entry.getValue().stream().map(m -> new MatchModelExtend(m, cards)).toList(), + null, tmp); + getClassementArray(entry.getKey(), null, cards, tmp); + + return tmp; + }) + .collect().asList(); + }) + .map(categoryData -> { + HashMap clubMap = new HashMap<>(); + + categoryData.forEach(c -> c.getClassement().forEach(classementData -> { + if (classementData.rank() > 3) + return; + + if (classementData.comb() != null) { + long clubId = 0L; + String clubName = ""; + if (classementData.comb() instanceof MembreModel membreModel2) { + clubId = (membreModel2.getClub() != null) ? membreModel2.getClub().getId() : 0; + clubName = (membreModel2.getClub() != null) ? membreModel2.getClub().getName() : ""; + } else if (classementData.comb() instanceof CompetitionGuestModel guestModel) { + if (guestModel.getClub() != null && !guestModel.getClub().isBlank()) + clubId = getClubTempId(guestModel.getClub()); + clubName = guestModel.getClub(); + } + + if (clubId != 0) { + clubMap.putIfAbsent(clubId, new ClubClassement(clubName)); + ClubClassement entity = clubMap.get(clubId); + entity.score[classementData.rank() - 1]++; + entity.tt_score += 4 - classementData.rank(); + } + } + })); + + return clubMap.values().stream() + .sorted(Comparator.comparingInt((ClubClassement c) -> c.tt_score) + .thenComparingInt(c -> c.score[0]) + .thenComparingInt(c -> c.score[1]) + .thenComparingInt(c -> c.score[2]).reversed()) + .toList(); + }) + .map(l -> new Pair<>(System.currentTimeMillis() + 60 * 1000L, l)) + ).map(Pair::getValue); + } + + @Data + @RegisterForReflection + public static class ClubClassement { + String name; + Integer[] score = new Integer[]{0, 0, 0}; + int tt_score = 0; + + public ClubClassement(String name) { + this.name = name; + } + } + @RegisterForReflection public static class CombStat { public int w; diff --git a/src/main/java/fr/titionfire/ffsaf/rest/ExternalResultEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/ExternalResultEndpoints.java index 259c3bb..5ba0b13 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/ExternalResultEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/ExternalResultEndpoints.java @@ -8,6 +8,7 @@ import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import java.util.HashMap; +import java.util.List; @Path("api/public/result/{id}") public class ExternalResultEndpoints { @@ -73,6 +74,13 @@ public class ExternalResultEndpoints { return resultService.getClubList(id); } + @GET + @Path("/club/classement") + @Produces(MediaType.APPLICATION_JSON) + public Uni> clubClassement() { + return resultService.getAllClubArray(id); + } + @GET @Path("/club/data") @Produces(MediaType.APPLICATION_JSON) diff --git a/src/main/java/fr/titionfire/ffsaf/rest/ResultEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/ResultEndpoints.java index 25e1a6e..b4d0184 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/ResultEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/ResultEndpoints.java @@ -47,6 +47,12 @@ public class ResultEndpoints { return resultService.getClubList(uuid, securityCtx); } + @GET + @Path("{uuid}/club/classement") + public Uni> getClubClassement(@PathParam("uuid") String uuid) { + return resultService.getAllClubArray(uuid, securityCtx); + } + @GET @Path("{uuid}/club/{id}") public Uni getClub(@PathParam("uuid") String uuid, @PathParam("id") long id) { @@ -64,7 +70,7 @@ public class ResultEndpoints { public Uni getCombList(@PathParam("uuid") String uuid, @PathParam("id") String id) { return resultService.getCombArrayPublic(uuid, id, securityCtx); } - + @GET @Path("{uuid}/comb") public Uni getComb(@PathParam("uuid") String uuid) { diff --git a/src/main/webapp/public/competition.js b/src/main/webapp/public/competition.js index 28cde4b..a13e8c0 100644 --- a/src/main/webapp/public/competition.js +++ b/src/main/webapp/public/competition.js @@ -39,6 +39,9 @@ function setSubPage(name) { case 'club': clubPage(location); break; + case 'clubRank': + clubRankPage(); + break; case 'all': combsPage(); break; @@ -54,6 +57,7 @@ function homePage() {
  • ${i18next.t('parCatégorie')}
  • ${i18next.t('parCombattant')}
  • ${i18next.t('parClub')}
  • +
  • ${i18next.t('classementClub')}
  • ${i18next.t('tousLesCombattants')}
  • ` @@ -62,6 +66,7 @@ function homePage() { document.getElementById('pouleLink').addEventListener('click', () => setSubPage('poule')); document.getElementById('combLink').addEventListener('click', () => setSubPage('comb')); document.getElementById('clubLink').addEventListener('click', () => setSubPage('club')); + document.getElementById('clubClassement').addEventListener('click', () => setSubPage('clubRank')); document.getElementById('allLink').addEventListener('click', () => setSubPage('all')); } @@ -640,6 +645,60 @@ function clubPage(location) { rootDiv.append(content) } +function buildClubsView(clubs) { + const pouleDiv = document.createElement('div'); + let arrayContent = ` +

    ${i18next.t('classementDesClub')} :

    + +
    + + + + + + + + + ` + for (const club of clubs) { + arrayContent += ` + + + + + + + ` + } + arrayContent += `
    ${i18next.t('club')}${i18next.t('1er')}${i18next.t('2eme')}${i18next.t('3eme')}${i18next.t('scores')}
    ${club.name}${club.score[0]}${club.score[1]}${club.score[2]}${club.tt_score}
    ` + pouleDiv.innerHTML = arrayContent; + return pouleDiv; +} + +function clubRankPage() { + rootDiv.innerHTML = `

    ${i18next.t('résultatDeLaCompétition')} :

    ${i18next.t('back')}`; + document.getElementById('homeLink').addEventListener('click', () => setSubPage('home')); + + const content = document.createElement('div'); + content.style.marginTop = '1em'; + + const dataContainer = document.createElement('div'); + dataContainer.id = 'data-container'; + + const loading = startLoading(content); + fetch(`${apiUrlRoot}/club/classement`) + .then(response => response.json()) + .then(clubs => { + console.log(clubs); + dataContainer.replaceChildren(buildClubsView(clubs)); + }) + .catch(() => dataContainer.replaceChildren(new Text(i18next.t('erreurDeChargementDeLaListe')))) + .finally(() => stopLoading(loading)); + + content.append(dataContainer); + rootDiv.append(content) +} + function buildCombsView(combs) { const pouleDiv = document.createElement('div'); let arrayContent = ` diff --git a/src/main/webapp/public/locales/en/result.json b/src/main/webapp/public/locales/en/result.json index 4de482a..517ca3d 100644 --- a/src/main/webapp/public/locales/en/result.json +++ b/src/main/webapp/public/locales/en/result.json @@ -2,6 +2,9 @@ "--sélectionnerUnClub--": "--Select a club--", "--sélectionnerUnCombattant--": "--Select a fighter--", "--sélectionnerUneCatégorie--": "--Select a category--", + "1er": "Gold medal", + "2eme": "Silver medal", + "3eme": "Bronze medal", "abs.": "abs.", "adversaire": "Opponent", "aujourdhuià": "Today at {{time}}", @@ -11,7 +14,9 @@ "catégorie": "Category", "chargement": "Loading", "classement": "Ranking", - "classementFinal": "Final standings", + "classementClub": "Club ranking", + "classementDesClub": "Clubs ranking", + "classementFinal": "Final ranking", "club": "Club", "combattant": "Fighter", "combattants": "Fighters", diff --git a/src/main/webapp/public/locales/fr/result.json b/src/main/webapp/public/locales/fr/result.json index 115350d..d0fe75e 100644 --- a/src/main/webapp/public/locales/fr/result.json +++ b/src/main/webapp/public/locales/fr/result.json @@ -2,6 +2,9 @@ "--sélectionnerUnClub--": "--Sélectionner un club--", "--sélectionnerUnCombattant--": "--Sélectionner un combattant--", "--sélectionnerUneCatégorie--": "--Sélectionner une catégorie--", + "1er": "Médaille d'or", + "2eme": "Médaille d'argent", + "3eme": "Médaille de bronze", "abs.": "abs.", "adversaire": "Adversaire", "aujourdhuià": "Aujourd'hui à {{time}}", @@ -11,6 +14,8 @@ "catégorie": "Catégorie", "chargement": "Chargement", "classement": "Classement", + "classementClub": "Classement club", + "classementDesClub": "Classement des club", "classementFinal": "Classement final", "club": "Club", "combattant": "Combattant", diff --git a/src/main/webapp/src/pages/result/ResultView.jsx b/src/main/webapp/src/pages/result/ResultView.jsx index 5557842..7709b60 100644 --- a/src/main/webapp/src/pages/result/ResultView.jsx +++ b/src/main/webapp/src/pages/result/ResultView.jsx @@ -39,7 +39,8 @@ export function ResultView() { {resultShow && resultShow === "cat" && || resultShow && resultShow === "club" && || resultShow && resultShow === "comb" && - || resultShow && resultShow === "combs" && } + || resultShow && resultShow === "combs" && + || resultShow && resultShow === "clubs" && } @@ -66,6 +67,10 @@ function MenuBar({resultShow, setResultShow}) { setResultShow("combs")}>{t('combattants')} +
  • + setResultShow("clubs")}>{t('classementClub')} +
  • /* @@ -450,6 +455,43 @@ function CombResult({uuid, combId}) { } +function ClubsResult({uuid}) { + const setLoading = useLoadingSwitcher() + const {data, error} = useFetch(`/result/${uuid}/club/classement`, setLoading, 1) + const {t} = useTranslation('result'); + + return <> + {data ? <> +

    {t('classementDesClub')} :

    + + + + + + + + + + + + + {data.map((club, idx) => + + + + + + )} + +
    {t('club')}{t('1er')}{t('2eme')}{t('3eme')}{t('scores')}
    {club.name}{club.score[0]}{club.score[1]}{club.score[2]}{club.tt_score}
    + + : error + ? + : + } + +} + function CombsResult({uuid}) { const setLoading = useLoadingSwitcher() const {data, error} = useFetch(`/result/${uuid}/comb`, setLoading, 1) -- 2.49.0 From d6d9f86254f34d220df65ed902e50c46698a6dd0 Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Fri, 27 Feb 2026 11:10:31 +0100 Subject: [PATCH 2/4] feat: PDF for classement club --- .../fr/titionfire/ffsaf/ws/recv/RPDF.java | 5 ++ src/main/webapp/public/locales/en/cm.json | 2 + src/main/webapp/public/locales/fr/cm.json | 2 + .../src/pages/competition/editor/CMAdmin.jsx | 22 ++++++- src/main/webapp/src/utils/cmPdf.js | 61 ++++++++++++++++++- 5 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RPDF.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RPDF.java index 7aa1a17..ba732fd 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/recv/RPDF.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RPDF.java @@ -99,6 +99,11 @@ public class RPDF { }); } + @WSReceiver(code = "getPodiumClub", permission = PermLevel.VIEW) + public Uni> getPodiumClub(WebSocketConnection connection, Object o) { + return resultService.getAllClubArray(connection.pathParam("uuid"), false); + } + @RegisterForReflection public static record PodiumEntity(String poule_name, String source, Categorie categorie, List podium) { diff --git a/src/main/webapp/public/locales/en/cm.json b/src/main/webapp/public/locales/en/cm.json index db892dc..a0ce0af 100644 --- a/src/main/webapp/public/locales/en/cm.json +++ b/src/main/webapp/public/locales/en/cm.json @@ -120,6 +120,8 @@ "obs.préfixDesSources": "Source prefix", "pays": "Country", "personnaliser": "Personalize", + "podium": "Podium", + "podiumDesClubs": "Club podium", "poids": "Weight", "poule": "Pool", "poulePour": "Pool for: ", diff --git a/src/main/webapp/public/locales/fr/cm.json b/src/main/webapp/public/locales/fr/cm.json index 86a559f..7e8ba4c 100644 --- a/src/main/webapp/public/locales/fr/cm.json +++ b/src/main/webapp/public/locales/fr/cm.json @@ -120,6 +120,8 @@ "obs.préfixDesSources": "Préfix des sources", "pays": "Pays", "personnaliser": "Personnaliser", + "podium": "Podium", + "podiumDesClubs": "Podium des clubs", "poids": "Poids", "poule": "Poule", "poulePour": "Poule pour: ", diff --git a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx index 8ee1395..bbf4d7d 100644 --- a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx @@ -407,6 +407,7 @@ function PrintModal({menuActions}) { const [allCatEmpty, setAllCatEmpty] = useState(false); const [podium, setPodium] = useState(false); const [podiumRank, setPodiumRank] = useState(4); + const [podiumClub, setPodiumClub] = useState(false); const [presetSelect, setPresetSelect] = useState(-1) @@ -416,12 +417,20 @@ function PrintModal({menuActions}) { const podiumPromise = (podiumRank_) => { return sendRequest("getPodium", {}).then(data => { - return [welcomeData?.name + " - " + "Podium", [ + return [welcomeData?.name + " - " + t('podium'), [ {type: "podium", params: ({data, maxRank: podiumRank_, minRank: Math.min(4, podiumRank_)})}, ]]; }); } + const podiumClubPromise = () => { + return sendRequest("getPodiumClub", {}).then(data => { + return [welcomeData?.name + " - " + t('classementDesClub', {ns: "result"}), [ + {type: "podiumClub", params: ({data})}, + ]]; + }); + } + const print = (action) => { const pagesPromise = []; @@ -437,6 +446,9 @@ function PrintModal({menuActions}) { if (podium) pagesPromise.push(podiumPromise(podiumRank)); + if (podiumClub) + pagesPromise.push(podiumClubPromise()); + toast.promise( toDataURL("/Logo-FFSAF-2023.png").then(logo => { return Promise.allSettled(pagesPromise).then(results => { @@ -513,7 +525,7 @@ function PrintModal({menuActions}) {
    setPodium(e.target.checked)}/> - +
    {podium &&
    @@ -521,6 +533,12 @@ function PrintModal({menuActions}) { setPodiumRank(Number(e.target.value))}/>
    } + +
    + setPodiumClub(e.target.checked)}/> + +
    diff --git a/src/main/webapp/src/utils/cmPdf.js b/src/main/webapp/src/utils/cmPdf.js index 767b952..9effb55 100644 --- a/src/main/webapp/src/utils/cmPdf.js +++ b/src/main/webapp/src/utils/cmPdf.js @@ -45,6 +45,9 @@ export function makePDF(action, pagesList, name, c_name, getComb, t, logo) { case "podium": generatePodium(context); break; + case "podiumClub": + generateClubPodium(context); + break; default: break } @@ -372,7 +375,7 @@ function generateCategoriePDF({pdf_doc, cat, matches, groups, getComb, cards_v, } function generatePodium({pdf_doc, data, t, logo, c_name, minRank = 4, maxRank = 4}) { - makeHeader(pdf_doc, c_name, "Podium", logo) + makeHeader(pdf_doc, c_name, t('podium'), logo) const data2 = data.sort((a, b) => { let tmp = sortCategories(a.categorie, b.categorie); @@ -441,3 +444,59 @@ function generatePodium({pdf_doc, data, t, logo, c_name, minRank = 4, maxRank = } pdf_doc.lastAutoTable.finalY = Math.max(finalY2, pdf_doc.lastAutoTable.finalY); } + +function generateClubPodium({pdf_doc, data, t, logo, c_name}) { + makeHeader(pdf_doc, c_name, t('podiumDesClubs'), logo) + + const body = data.map(c => [ + {content: [c.name], styles: {halign: "center"}}, + {content: c.score[0], styles: {halign: "center"}}, + {content: c.score[1], styles: {halign: "center"}}, + {content: c.score[2], styles: {halign: "center"}}, + {content: c.tt_score, styles: {halign: "center"}}, + ]); + + let rank = 0; + let lastScores = null; + let lastB = null; + for (const b of body) { + const scores = [b[1].content, b[2].content, b[3].content, b[4].content]; + if (lastScores !== scores) { + rank++; + b.unshift({content: rank, styles: {halign: "center"}}) + lastB = b + } else { + lastB[1].content.push(b[0].content); + delete body.indexOf(b); + } + } + for (const b of body) { + b[1].content = b[1].content.join(", ") + } + + autoTable(pdf_doc, { + startY: pdf_doc.lastAutoTable.finalY + 7, + styles: {fontSize: 10, cellPadding: 3}, + columnStyles: { + 0: {cellWidth: 35}, + 1: {cellWidth: "auto"}, + 2: {cellWidth: 45}, + 3: {cellWidth: 45}, + 4: {cellWidth: 45}, + 5: {cellWidth: 40}, + }, + pageBreak: "avoid", + showHead: 'firstPage', + head: [[ + {content: t('place', {ns: "result"}), styles: {halign: "center"}}, + {content: t('club', {ns: 'result'}), styles: {halign: "center"}}, + {content: t('1er', {ns: 'result'}), styles: {halign: "center"}}, + {content: t('2eme', {ns: 'result'}), styles: {halign: "center"}}, + {content: t('3eme', {ns: 'result'}), styles: {halign: "center"}}, + {content: t('scores', {ns: 'result'}), styles: {halign: "center"}}, + ]], + body: body, + rowPageBreak: 'auto', + theme: 'grid', + }) +} -- 2.49.0 From 812d873d5d24442f78976977972e6368c761dd68 Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Sun, 8 Mar 2026 21:58:57 +0100 Subject: [PATCH 3/4] fix: Match reorder refresh --- .../src/pages/competition/editor/CategoryAdminContent.jsx | 4 +++- src/main/webapp/src/utils/MatchReducer.jsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx b/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx index 95ed1ab..b7abacc 100644 --- a/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx +++ b/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx @@ -568,7 +568,9 @@ function MatchList({matches, cat, groups, reducer, classement = false}) { const {active, over} = event; if (active.id !== over.id) { - const newIndex = marches2.findIndex(m => m.id === over.id); + let newIndex = marches2.findIndex(m => m.id === over.id); + if (newIndex > 0) + newIndex = marches2[newIndex].categorie_ord; reducer({type: 'REORDER', payload: {id: active.id, pos: newIndex}}); sendRequest('updateMatchOrder', {id: active.id, pos: newIndex}).then(__ => { }) diff --git a/src/main/webapp/src/utils/MatchReducer.jsx b/src/main/webapp/src/utils/MatchReducer.jsx index 20e7514..e591d73 100644 --- a/src/main/webapp/src/utils/MatchReducer.jsx +++ b/src/main/webapp/src/utils/MatchReducer.jsx @@ -56,8 +56,10 @@ export function MarchReducer(datas, action) { return datas.sort(action.payload) case 'REORDER': const oldIndex = datas.findIndex(data => data.id === action.payload.id) - if (oldIndex === -1 || datas[oldIndex].categorie_ord === action.payload.pos) + if (oldIndex === -1) return datas // Do nothing + if (datas[oldIndex].categorie_ord === action.payload.pos) + return [...datas] // Do nothing const oldPos = datas[oldIndex].categorie_ord const newPos = action.payload.pos -- 2.49.0 From ee0a7d87e958b7ab5eb6341422a4002a856d4bf5 Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Mon, 9 Mar 2026 13:06:05 +0100 Subject: [PATCH 4/4] feat: cm Add audio encodeur generator --- src/main/webapp/package-lock.json | 705 ++++++++---------- src/main/webapp/package.json | 38 +- src/main/webapp/public/processor-dtmf.js | 84 +++ .../webapp/src/components/cm/AudioEncoder.jsx | 207 +++++ .../editor/CompetitionManagerRoot.jsx | 2 + .../pages/competition/editor/StateWindow.jsx | 12 +- 6 files changed, 640 insertions(+), 408 deletions(-) create mode 100644 src/main/webapp/public/processor-dtmf.js create mode 100644 src/main/webapp/src/components/cm/AudioEncoder.jsx diff --git a/src/main/webapp/package-lock.json b/src/main/webapp/package-lock.json index e9606cb..5d79019 100644 --- a/src/main/webapp/package-lock.json +++ b/src/main/webapp/package-lock.json @@ -11,42 +11,42 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@fortawesome/fontawesome-svg-core": "^7.1.0", - "@fortawesome/free-brands-svg-icons": "^7.1.0", - "@fortawesome/free-regular-svg-icons": "^7.1.0", - "@fortawesome/free-solid-svg-icons": "^7.1.0", - "@fortawesome/react-fontawesome": "^3.1.1", - "axios": "^1.13.2", + "@fortawesome/fontawesome-svg-core": "^7.2.0", + "@fortawesome/free-brands-svg-icons": "^7.2.0", + "@fortawesome/free-regular-svg-icons": "^7.2.0", + "@fortawesome/free-solid-svg-icons": "^7.2.0", + "@fortawesome/react-fontawesome": "^3.2.0", + "axios": "^1.13.6", "browser-image-compression": "^2.0.2", - "i18next": "^25.8.0", - "i18next-browser-languagedetector": "^8.2.0", + "i18next": "^25.8.14", + "i18next-browser-languagedetector": "^8.2.1", "i18next-http-backend": "^3.0.2", - "jspdf": "^4.1.0", + "jspdf": "^4.2.0", "jspdf-autotable": "^5.0.7", "jszip": "^3.10.1", "leaflet": "^1.9.4", "obs-websocket-js": "^5.0.7", - "proj4": "^2.20.2", - "react": "^19.2.3", - "react-dom": "^19.2.3", - "react-i18next": "^16.5.3", - "react-is": "^19.2.3", + "proj4": "^2.20.3", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-i18next": "^16.5.5", + "react-is": "^19.2.4", "react-leaflet": "^5.0.0", "react-loader-spinner": "^8.0.2", - "react-router-dom": "^7.12.0", + "react-router-dom": "^7.13.1", "react-toastify": "^11.0.5", "recharts": "^3.7.0", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "xlsx-js-style": "^1.2.0" }, "devDependencies": { - "@types/react": "^19.2.9", + "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.1.2", - "eslint": "^9.39.2", + "@vitejs/plugin-react": "^5.1.4", + "eslint": "^10.0.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.26", + "eslint-plugin-react-refresh": "^0.5.2", "vite": "^7.3.1" } }, @@ -60,10 +60,11 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", @@ -83,21 +84,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -114,13 +116,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -234,12 +237,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -279,9 +283,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -301,17 +306,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -319,10 +325,11 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" @@ -855,157 +862,172 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", + "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", + "@eslint/object-schema": "^3.0.2", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^10.2.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", + "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0" + "@eslint/core": "^1.1.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", + "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", + "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^1.1.0", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@fortawesome/fontawesome-common-types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.1.0.tgz", - "integrity": "sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.2.0.tgz", + "integrity": "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz", - "integrity": "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.2.0.tgz", + "integrity": "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q==", + "license": "MIT", "peer": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "7.1.0" + "@fortawesome/fontawesome-common-types": "7.2.0" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-brands-svg-icons": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-7.1.0.tgz", - "integrity": "sha512-9byUd9bgNfthsZAjBl6GxOu1VPHgBuRUP9juI7ZoM98h8xNPTCTagfwUFyYscdZq4Hr7gD1azMfM9s5tIWKZZA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-7.2.0.tgz", + "integrity": "sha512-VNG8xqOip1JuJcC3zsVsKRQ60oXG9+oYNDCosjoU/H9pgYmLTEwWw8pE0jhPz/JWdHeUuK6+NQ3qsM4gIbdbYQ==", + "license": "(CC-BY-4.0 AND MIT)", "dependencies": { - "@fortawesome/fontawesome-common-types": "7.1.0" + "@fortawesome/fontawesome-common-types": "7.2.0" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-7.1.0.tgz", - "integrity": "sha512-0e2fdEyB4AR+e6kU4yxwA/MonnYcw/CsMEP9lH82ORFi9svA6/RhDyhxIv5mlJaldmaHLLYVTb+3iEr+PDSZuQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-7.2.0.tgz", + "integrity": "sha512-iycmlN51EULlQ4D/UU9WZnHiN0CvjJ2TuuCrAh+1MVdzD+4ViKYH2deNAll4XAAYlZa8WAefHR5taSK8hYmSMw==", + "license": "(CC-BY-4.0 AND MIT)", "dependencies": { - "@fortawesome/fontawesome-common-types": "7.1.0" + "@fortawesome/fontawesome-common-types": "7.2.0" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.1.0.tgz", - "integrity": "sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.2.0.tgz", + "integrity": "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==", + "license": "(CC-BY-4.0 AND MIT)", "dependencies": { - "@fortawesome/fontawesome-common-types": "7.1.0" + "@fortawesome/fontawesome-common-types": "7.2.0" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/react-fontawesome": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-3.1.1.tgz", - "integrity": "sha512-EDllr9hpodc21odmUywHS1alXNiCd4E8sp5GJ5s7wYINz8vSmMiNWpALTiuYODb865YyQ/NlyiN4mbXp7HCNqg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-3.2.0.tgz", + "integrity": "sha512-E9Gu1hqd6JussVO26EC4WqRZssXMnQr2ol7ZNWkkFOH8jZUaxDJ9Z9WF9wIVkC+kJGXUdY3tlffpDwEKfgQrQw==", + "license": "MIT", "engines": { "node": ">=20" }, @@ -1160,10 +1182,11 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", - "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", - "dev": true + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.55.1", @@ -1595,6 +1618,13 @@ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1605,7 +1635,8 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/pako": { "version": "2.0.4", @@ -1621,9 +1652,9 @@ "optional": true }, "node_modules/@types/react": { - "version": "19.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", - "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", "peer": true, @@ -1659,15 +1690,16 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" }, "node_modules/@vitejs/plugin-react": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", - "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", + "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.28.5", + "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.53", + "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, @@ -1679,10 +1711,11 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, + "license": "MIT", "peer": true, "bin": { "acorn": "bin/acorn" @@ -1696,6 +1729,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -1709,10 +1743,11 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1724,12 +1759,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -1891,12 +1920,13 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -2023,15 +2053,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -2132,6 +2153,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -2705,33 +2727,31 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", + "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.2", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.0", + "@eslint/plugin-kit": "^0.6.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", + "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.1", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -2741,8 +2761,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2750,7 +2769,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -2816,12 +2835,13 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.26", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", - "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", "dev": true, + "license": "MIT", "peerDependencies": { - "eslint": ">=8.40" + "eslint": "^9 || ^10" } }, "node_modules/eslint-plugin-react/node_modules/doctrine": { @@ -2837,82 +2857,60 @@ } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", + "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/eslint/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "18 || 20 || >=22" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/eslint/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "18 || 20 || >=22" } }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2925,49 +2923,46 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/eslint/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "has-flag": "^4.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=8" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", + "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -2980,6 +2975,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -3022,13 +3018,15 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -3312,18 +3310,6 @@ "node": ">=10.13.0" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -3464,9 +3450,9 @@ } }, "node_modules/i18next": { - "version": "25.8.0", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.0.tgz", - "integrity": "sha512-urrg4HMFFMQZ2bbKRK7IZ8/CTE7D8H4JRlAwqA2ZwDRFfdd0K/4cdbNNLgfn9mo+I/h9wJu61qJzH7jCFAhUZQ==", + "version": "25.8.14", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.14.tgz", + "integrity": "sha512-paMUYkfWJMsWPeE/Hejcw+XLhHrQPehem+4wMo+uELnvIwvCG019L9sAIljwjCmEMtFQQO3YeitJY8Kctei3iA==", "funding": [ { "type": "individual", @@ -3496,9 +3482,10 @@ } }, "node_modules/i18next-browser-languagedetector": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", - "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", + "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2" } @@ -3516,6 +3503,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -3534,22 +3522,6 @@ "url": "https://opencollective.com/immer" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3990,18 +3962,6 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -4024,7 +3984,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -4045,12 +4006,12 @@ } }, "node_modules/jspdf": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.1.0.tgz", - "integrity": "sha512-xd1d/XRkwqnsq6FP3zH1Q+Ejqn2ULIJeDZ+FTKpaabVpZREjsJKRJwuokTNgdqOU+fl55KgbvgZ1pRTSWCP2kQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.0.tgz", + "integrity": "sha512-hR/hnRevAXXlrjeqU5oahOE+Ln9ORJUB5brLHHqH67A+RBQZuFr5GkbI9XQI8OUFSEezKegsi45QRpc4bGj75Q==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", + "@babel/runtime": "^7.28.6", "fast-png": "^6.2.0", "fflate": "^0.8.1" }, @@ -4153,12 +4114,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4475,18 +4430,6 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4603,12 +4546,13 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/proj4": { - "version": "2.20.2", - "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.20.2.tgz", - "integrity": "sha512-ipfBRfQly0HhHTO7hnC1GfaX8bvroO7VV4KH889ehmADSE8C/qzp2j+Jj6783S9Tj6c2qX/hhYm7oH0kgXzBAA==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.20.3.tgz", + "integrity": "sha512-uKJXnf/RkHhExxnWHqQqy2J1bPc5Qo8XSGzrMSJTdPWUQDo1DkunIRBfAS0crQaP9bZCSKNjqYJdYWVov0hDXw==", + "license": "MIT", "dependencies": { "mgrs": "1.0.0", - "wkt-parser": "^1.5.1" + "wkt-parser": "^1.5.3" }, "funding": { "url": "https://github.com/sponsors/ahocevar" @@ -4641,6 +4585,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -4656,30 +4601,33 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", "peer": true, "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/react-i18next": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.3.tgz", - "integrity": "sha512-fo+/NNch37zqxOzlBYrWMx0uy/yInPkRfjSuy4lqKdaecR17nvCHnEUt3QyzA8XjQ2B/0iW/5BhaHR3ZmukpGw==", + "version": "16.5.5", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.5.tgz", + "integrity": "sha512-5Z35e2JMALNR16FK/LDNQoAatQTVuO/4m4uHrIzewOPXIyf75gAHzuNLSWwmj5lRDJxDvXRJDECThkxWSAReng==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.4", "html-parse-stringify": "^3.0.1", @@ -4703,9 +4651,10 @@ } }, "node_modules/react-is": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz", - "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT", "peer": true }, "node_modules/react-leaflet": { @@ -4770,9 +4719,10 @@ } }, "node_modules/react-router": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", - "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "license": "MIT", "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -4791,11 +4741,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz", - "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", + "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", + "license": "MIT", "dependencies": { - "react-router": "7.12.0" + "react-router": "7.13.1" }, "engines": { "node": ">=20.0.0" @@ -4951,15 +4902,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/rgbcolor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", @@ -5088,7 +5030,8 @@ "node_modules/set-cookie-parser": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==" + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" }, "node_modules/set-function-length": { "version": "1.2.2", @@ -5382,18 +5325,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/styled-components": { "version": "6.3.8", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.8.tgz", @@ -5638,6 +5569,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -5917,9 +5849,10 @@ } }, "node_modules/wkt-parser": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.5.2.tgz", - "integrity": "sha512-1ZUiV1FTwSiSrgWzV9KXJuOF2BVW91KY/mau04BhnmgOdroRQea7Q0s5TVqwGLm0D2tZwObd/tBYXW49sSxp3Q==" + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.5.3.tgz", + "integrity": "sha512-myla+RrMj+WTlnHc8Y4HEwjBcBF9dqJ3vjff/zmlrn9V3OKOM1mZVIyNjlPEmOM9Jjr/PPut0tnaTs9NyHcK8Q==", + "license": "MIT" }, "node_modules/wmf": { "version": "1.0.2", diff --git a/src/main/webapp/package.json b/src/main/webapp/package.json index fd64a9f..e116de7 100644 --- a/src/main/webapp/package.json +++ b/src/main/webapp/package.json @@ -13,42 +13,42 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@fortawesome/fontawesome-svg-core": "^7.1.0", - "@fortawesome/free-brands-svg-icons": "^7.1.0", - "@fortawesome/free-regular-svg-icons": "^7.1.0", - "@fortawesome/free-solid-svg-icons": "^7.1.0", - "@fortawesome/react-fontawesome": "^3.1.1", - "axios": "^1.13.2", + "@fortawesome/fontawesome-svg-core": "^7.2.0", + "@fortawesome/free-brands-svg-icons": "^7.2.0", + "@fortawesome/free-regular-svg-icons": "^7.2.0", + "@fortawesome/free-solid-svg-icons": "^7.2.0", + "@fortawesome/react-fontawesome": "^3.2.0", + "axios": "^1.13.6", "browser-image-compression": "^2.0.2", - "i18next": "^25.8.0", - "i18next-browser-languagedetector": "^8.2.0", + "i18next": "^25.8.14", + "i18next-browser-languagedetector": "^8.2.1", "i18next-http-backend": "^3.0.2", - "jspdf": "^4.1.0", + "jspdf": "^4.2.0", "jspdf-autotable": "^5.0.7", "jszip": "^3.10.1", "leaflet": "^1.9.4", "obs-websocket-js": "^5.0.7", - "proj4": "^2.20.2", - "react": "^19.2.3", - "react-dom": "^19.2.3", - "react-i18next": "^16.5.3", - "react-is": "^19.2.3", + "proj4": "^2.20.3", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-i18next": "^16.5.5", + "react-is": "^19.2.4", "react-leaflet": "^5.0.0", "react-loader-spinner": "^8.0.2", - "react-router-dom": "^7.12.0", + "react-router-dom": "^7.13.1", "react-toastify": "^11.0.5", "recharts": "^3.7.0", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "xlsx-js-style": "^1.2.0" }, "devDependencies": { - "@types/react": "^19.2.9", + "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.1.2", - "eslint": "^9.39.2", + "@vitejs/plugin-react": "^5.1.4", + "eslint": "^10.0.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.26", + "eslint-plugin-react-refresh": "^0.5.2", "vite": "^7.3.1" } } diff --git a/src/main/webapp/public/processor-dtmf.js b/src/main/webapp/public/processor-dtmf.js new file mode 100644 index 0000000..112a5bd --- /dev/null +++ b/src/main/webapp/public/processor-dtmf.js @@ -0,0 +1,84 @@ +class ProcessorDTMF extends AudioWorkletProcessor { + constructor() { + super(); + this.sampleRate = sampleRate; + this.symbolDuration = 0.03; // 50 ms par symbole + this.samplesPerSymbol = Math.floor(this.sampleRate * this.symbolDuration); + this.encodeLowPrio = []; + this.symbolSamples = []; + this.lastBlackStep = 0; + this.port.onmessage = (e) => { + if (e.data.type === 'encode') { + this.symbolSamples.push(...this.encodeSymbols(e.data.symbols)); + this.symbolSamples.push(...this.encodeBlack(this.sampleRate * 0.02)); + } + if (e.data.type === 'encodeLowPrio') { + this.encodeLowPrio.push(e.data.symbols); + } + }; + } + + dtmfFrequencies = [ + [697, 770, 852, 941], // Fréquences basses + [1209, 1336, 1477, 1633] // Fréquences hautes + ]; + + encodeSymbols(symbols) { + const samples = []; + for (const symbol of symbols) { + const lf = this.dtmfFrequencies[0][symbol % 4]; // Fréquence basse + const hf = this.dtmfFrequencies[1][Math.floor(symbol / 4)]; // Fréquence haute + // console.log(`Symbol: ${symbol}, LF: ${lf} Hz, HF: ${hf} Hz`); + for (let i = 0; i < this.samplesPerSymbol; i++) { + const t = i / this.sampleRate; + const t2 = (this.lastBlackStep + i) / this.sampleRate; + samples.push(0.5 * Math.sin(2 * Math.PI * lf * t) + 0.5 * Math.sin(2 * Math.PI * hf * t) // Signal DTMF + + Math.sin(2 * Math.PI * 150 * t2) * (0.0625 * (Math.sin(2 * Math.PI * 0.5 * t2) + 1))); // Ajouter un signal à 150 Hz pour le "black" + } + this.lastBlackStep += this.samplesPerSymbol; + + // ajouter un silence de 10 ms entre les symboles + samples.push(...this.encodeBlack(this.sampleRate * 0.01)); // Silence + } + return samples; + } + + encodeBlack(size) { + const samples = []; + + for (let i = 0; i < size; i++) { + const t = (this.lastBlackStep + i) / this.sampleRate; + samples.push(Math.sin(2 * Math.PI * 150 * t) * (0.0625 * (Math.sin(2 * Math.PI * 0.5 * t) + 1))); // Signal à 350 Hz pour le "black" + } + this.lastBlackStep += size; + this.lastBlackStep %= this.sampleRate * 2; // Réinitialiser tous les 2 secondes pour éviter les débordements + + return samples; + } + + process(inputs, outputs, parameters) { + const output = outputs[0]; // output est un tableau de canaux (ex: [Float32Array, ...]) + const channelData = output[0]; // Accéder au premier canal (mono) + + if (this.symbolSamples.length === 0 && this.encodeLowPrio.length > 0) { + this.symbolSamples.push(...this.encodeSymbols(this.encodeLowPrio.shift())); + this.symbolSamples.push(...this.encodeBlack(this.sampleRate * 0.02)); + } + + if (this.symbolSamples.length === 0) { + const samples = this.encodeBlack(channelData.length) + for (let i = 0; i < channelData.length; i++) { + channelData[i] = samples[i] || 0; + } + return true; + } + + for (let i = 0; i < channelData.length; i++) { + channelData[i] = this.symbolSamples.shift() || 0; // Prendre le prochain échantillon ou 0 si vide + } + + return true; + } +} + +registerProcessor('dtmf-processor', ProcessorDTMF); diff --git a/src/main/webapp/src/components/cm/AudioEncoder.jsx b/src/main/webapp/src/components/cm/AudioEncoder.jsx new file mode 100644 index 0000000..68712cc --- /dev/null +++ b/src/main/webapp/src/components/cm/AudioEncoder.jsx @@ -0,0 +1,207 @@ +import React, {useEffect, useRef, useState} from 'react'; +import {useTablesState} from "../../pages/competition/editor/StateWindow.jsx"; +import {timePrint} from "../../utils/Tools.js"; + +let initialized = false; +const AudioEncoder = () => { + const audioContextRef = useRef(null); + const qpskProcessorRef = useRef(null); + const [isReady, setIsReady] = useState(false); + const [table, setTable] = useState('1'); + + const lastSend = useRef({id: 0}); + const {state} = useTablesState(); + + // Initialisation de l'AudioContext et du AudioWorklet + useEffect(() => { + const initAudio = async () => { + if (initialized) + return; + console.log("Initialisation de l'audio après interaction utilisateur"); + initialized = true; + + try { + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + console.log("AudioContext state:", audioContext.state); + + if (audioContext.state === 'suspended') { + await audioContext.resume(); // Nécessaire pour démarrer le contexte audio + console.log("AudioContext resumed"); + } + + await audioContext.audioWorklet.addModule('/processor-dtmf.js'); + await new Promise(r => setTimeout(r, 100)); + const processor = new AudioWorkletNode(audioContext, 'dtmf-processor'); + processor.connect(audioContext.destination); + qpskProcessorRef.current = processor; + audioContextRef.current = audioContext; + + await new Promise(r => setTimeout(r, 100)); + setIsReady(true); + } catch (err) { + initialized = false; + console.error("Erreur d'initialisation AudioWorklet:", err); + } + }; + + // Initialiser après un clic utilisateur (pour contourner les restrictions des navigateurs) + const handleUserInteraction = () => { + document.removeEventListener('click', handleUserInteraction); + initAudio(); + }; + + document.addEventListener('click', handleUserInteraction); + return () => { + if (audioContextRef.current?.state !== 'closed') { + audioContextRef.current?.close(); + } + }; + }, []); + + // Fonction pour encoder et envoyer un message + const encodeAndSend = (data, lowPrio = false) => { + if (!isReady) return; + + const symbols = Array.from(data).flatMap(byte => [byte >> 4, byte & 0x0F]); + console.log("Bits :", symbols); + + // 5. Envoyer les symboles au processeur audio + if (lowPrio) { + qpskProcessorRef.current.port.postMessage({type: 'encodeLowPrio', symbols}); + } else { + qpskProcessorRef.current.port.postMessage({type: 'encode', symbols}); + } + } + + useEffect(() => { + const t = state.find(o => o.liceName === table) + if (!t) + return + + // console.log("Data for table 1:", t, t.selectedMatch) + const last = lastSend.current; + if (t.selectedMatch !== last.id) { + clearTimeout(last.time_id) + last.time_id = setTimeout(() => { + last.time_id = null + if (t.selectedMatch === null) { + encodeAndSend(new Uint8Array([0, 0, 0, 0, 0, 0, 0])); + } else { + const data = []; + for (let i = 0; i < 7; i++) { // MaxSafeInteger est sur 7 bytes (53 bits de précision) + data.unshift(Number((BigInt(t.selectedMatch) >> BigInt(i * 8)) & 0xFFn)) + } + data[0] = data[0] & 0x1F // 3 premiers bits à 0 pour différencier des autres types de messages (ex: score, chrono, etc.) + // console.log("Data to send (selectedMatch):", data) + encodeAndSend(new Uint8Array(data), false); + } + }, 250) + + last.id = t.selectedMatch + } + + const isRunning = (c) => c.startTime !== 0 + const getTime = (c) => { + if (c.startTime === 0) + return c.time + return c.time + Date.now() - c.startTime + } + + const timeStr = last.chronoState ? timePrint((last.chronoState.state === 2) ? last.chronoState.configPause : last.chronoState.configTime - getTime(last.chronoState)) : "-" + const timeStr2 = timePrint((t.chronoState.state === 2) ? t.chronoState.configPause : t.chronoState.configTime - getTime(t.chronoState)) + + if (timeStr !== timeStr2) { + clearInterval(lastSend.current.time_chronoInter) + clearTimeout(lastSend.current.time_chronoText) + lastSend.current.time_chronoText = setTimeout(() => { + let time = (t.chronoState.state === 2) ? t.chronoState.configPause : t.chronoState.configTime - getTime(t.chronoState) + const ms = time % 1000 + time = (time - ms) / 1000 + + const data = [((time >> 8) & 0x1F) + 0x20, time & 0xFF]; + // console.log("Data to send (time):", data) + encodeAndSend(new Uint8Array(data)); + + lastSend.current.time_chronoInter = setInterval(() => { + let time = (t.chronoState.state === 2) ? t.chronoState.configPause : t.chronoState.configTime - getTime(t.chronoState) + const ms = time % 1000 + time = (time - ms) / 1000 + + const data = [((time >> 8) & 0x1F) + 0x20, time & 0xFF]; + // console.log("Data to send (time-auto):", data) + encodeAndSend(new Uint8Array(data), true); + }, 10000); + }, 150) + } + + if (!last.chronoState || last.chronoState.state !== t.chronoState.state || isRunning(last.chronoState) !== isRunning(t.chronoState)) { + let time = (t.chronoState.state === 2) ? t.chronoState.configPause : t.chronoState.configTime - getTime(t.chronoState) + const ms = Math.round((time % 1000) / 250) + + const data = [0x40 + (t.chronoState.state << 3) + (isRunning(t.chronoState) << 2) + (ms & 0x03)]; + // console.log("Data to send (chrono state):", data) + encodeAndSend(new Uint8Array(data)); + } + + last.chronoState = {...t.chronoState} + + // console.log(timeStr, timeStr2) + // console.log(last.chronoState, t.chronoState) + + if (last.scoreRouge !== t.scoreState.scoreRouge) { + clearTimeout(last.time_sr) + last.time_sr = setTimeout(() => { + if (last.scoreRouge !== t.scoreState.scoreRouge) { + const b = t.scoreState.scoreRouge < 0 + const s = b ? -t.scoreState.scoreRouge : t.scoreState.scoreRouge + const data = [0x60 + (b << 3) + ((s >> 8) & 0x07), (s & 0xFF)]; + console.log("Data to send (score r):", data) + encodeAndSend(new Uint8Array(data), true); + last.scoreRouge = t.scoreState.scoreRouge + } + }, 250) + } + + if (last.scoreBleu !== t.scoreState.scoreBleu) { + clearTimeout(last.time_sb) + last.time_sb = setTimeout(() => { + if (last.scoreBleu !== t.scoreState.scoreBleu) { + const b = t.scoreState.scoreBleu < 0 + const s = b ? -t.scoreState.scoreBleu : t.scoreState.scoreBleu + const data = [0x60 + 0x10 + (b << 3) + ((s >> 8) & 0x07), (s & 0xFF)]; + console.log("Data to send (score b):", data) + encodeAndSend(new Uint8Array(data), true); + last.scoreBleu = t.scoreState.scoreBleu + } + }, 250) + } + + }, [state]) + + useEffect(() => { + const last = lastSend.current; + clearTimeout(last.scoreBleu) + clearTimeout(last.scoreRouge) + clearTimeout(last.time_id) + clearTimeout(last.time_chronoText) + clearInterval(last.time_chronoInter) + last.id = 0 + last.scoreBleu = 0 + last.scoreRouge = 0 + last.chronoState = null + }, [table]) + + return ( +
    + setTable(e.target.value)} + placeholder="Nom de la zone" + /> + {isReady ? 'Actif' : "Zone non configurée"} +
    + ); +}; + +export default AudioEncoder; diff --git a/src/main/webapp/src/pages/competition/editor/CompetitionManagerRoot.jsx b/src/main/webapp/src/pages/competition/editor/CompetitionManagerRoot.jsx index 179d424..1df87e2 100644 --- a/src/main/webapp/src/pages/competition/editor/CompetitionManagerRoot.jsx +++ b/src/main/webapp/src/pages/competition/editor/CompetitionManagerRoot.jsx @@ -11,6 +11,7 @@ import {AxiosError} from "../../../components/AxiosError.jsx"; import {useFetch} from "../../../hooks/useFetch.js"; import {useTranslation} from "react-i18next"; import {CardsProvider} from "../../../hooks/useCard.jsx"; +import AudioEncoder from "../../../components/cm/AudioEncoder.jsx"; const vite_url = import.meta.env.VITE_URL; @@ -73,6 +74,7 @@ function HomeComp() { }/> }/> }/> + }/> diff --git a/src/main/webapp/src/pages/competition/editor/StateWindow.jsx b/src/main/webapp/src/pages/competition/editor/StateWindow.jsx index dfe2951..04520c3 100644 --- a/src/main/webapp/src/pages/competition/editor/StateWindow.jsx +++ b/src/main/webapp/src/pages/competition/editor/StateWindow.jsx @@ -19,7 +19,7 @@ function reducer(state, action) { } } -export function StateWindow({document}) { +export function useTablesState() { const {sendRequest, dispatch} = useWS(); const [state, dispatchState] = useReducer(reducer, []) @@ -58,6 +58,12 @@ export function StateWindow({document}) { } }, []) + return {state}; +} + +export function StateWindow({document}) { + const {state} = useTablesState(); + document.title = "État des tables de marque"; document.body.className = "overflow-hidden"; @@ -66,7 +72,7 @@ export function StateWindow({document}) {
    {state.sort((a, b) => a.liceName.localeCompare(b.liceName)).map((table, index) =>
    - +
    ) }
    @@ -191,7 +197,7 @@ function PrintChrono({chrono}) { const timer = setInterval(() => { let currentDuration = chrono.configTime if (chrono.state === 2) { - currentDuration = (chrono.state === 0) ? 10000 : chrono.configPause + currentDuration = chrono.configPause } const timeStr = (chrono.state === 1 ? " Match - " : " Pause - ") + timePrint(currentDuration - getTime()) + (isRunning() ? "" : " (arrêté)") -- 2.49.0