Compare commits
No commits in common. "31bbe5719f7851b7ec7d84faf733dc43693957c4" and "0ff3ffd66b7240ca42c224d709a4330e9672d1d9" have entirely different histories.
31bbe5719f
...
0ff3ffd66b
6
pom.xml
6
pom.xml
@ -11,7 +11,7 @@
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
||||
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
||||
<quarkus.platform.version>3.30.5</quarkus.platform.version>
|
||||
<quarkus.platform.version>3.16.4</quarkus.platform.version>
|
||||
<skipITs>true</skipITs>
|
||||
<surefire-plugin.version>3.2.3</surefire-plugin.version>
|
||||
</properties>
|
||||
@ -68,7 +68,7 @@
|
||||
<dependency>
|
||||
<groupId>io.quarkiverse.tika</groupId>
|
||||
<artifactId>quarkus-tika</artifactId>
|
||||
<version>2.3.0</version>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@ -127,7 +127,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>fop</artifactId>
|
||||
<version>2.11</version>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
|
||||
@ -66,13 +66,4 @@ public class RegisterModel {
|
||||
return membre.club;
|
||||
return club;
|
||||
}
|
||||
|
||||
public Categorie getCategorie2() {
|
||||
Categorie tmp = this.categorie;
|
||||
if (tmp == null)
|
||||
tmp = membre.getCategorie();
|
||||
if (tmp == null)
|
||||
return null;
|
||||
return Categorie.values()[Math.min(tmp.ordinal() + this.overCategory, Categorie.values().length - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import fr.titionfire.ffsaf.rest.client.HelloAssoAuthClient;
|
||||
import fr.titionfire.ffsaf.rest.client.dto.TokenResponse;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.jboss.logging.Logger;
|
||||
@ -12,6 +13,7 @@ import org.jboss.logging.Logger;
|
||||
public class HelloAssoTokenService {
|
||||
private static final Logger LOG = Logger.getLogger(HelloAssoTokenService.class);
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
HelloAssoAuthClient authClient;
|
||||
|
||||
|
||||
@ -58,8 +58,6 @@ public class LoggerService {
|
||||
}
|
||||
|
||||
public Uni<?> append() {
|
||||
if (buffer.isEmpty())
|
||||
return Uni.createFrom().voidItem();
|
||||
return Panache.withTransaction(() -> repository.persist(buffer))
|
||||
.invoke(__ -> buffer.clear());
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ public class MembreService {
|
||||
"EXISTS (SELECT 1 FROM LicenceModel l WHERE l.membre.id = m.id AND l.saison >= %s)",
|
||||
Utils.getSaison() - 1);
|
||||
|
||||
String clubFilter = "(TRUE OR ?3 = ?3)";
|
||||
String clubFilter = "?3 = ?3";
|
||||
if (club != null) {
|
||||
if (club instanceof String club_) {
|
||||
if (!club_.isBlank()) {
|
||||
@ -270,16 +270,14 @@ public class MembreService {
|
||||
if (model.getEmail() != null && !model.getEmail().isBlank()) {
|
||||
if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
|
||||
LOGGER.info("Similar membres found: " + model);
|
||||
throw new DBadRequestException(
|
||||
"Email '" + model.getEmail() + "' déja utilisé par " + model.getFname() + " " + model.getFname());
|
||||
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utilisé");
|
||||
}
|
||||
|
||||
if (StringSimilarity.similarity(model.getLname().toUpperCase(),
|
||||
dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity(
|
||||
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) {
|
||||
LOGGER.info("Similar membres found: " + model);
|
||||
throw new DBadRequestException(
|
||||
"Email '" + model.getEmail() + "' déja utilisé par " + model.getFname() + " " + model.getFname());
|
||||
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utilisé");
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,8 +288,7 @@ public class MembreService {
|
||||
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
|
||||
LOGGER.info("Similar membres found: " + model);
|
||||
throw new DBadRequestException(
|
||||
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide. (tentative de changement non-autotiser de nom sur la licence "
|
||||
+ model.getLicence() + " pour " + model.getFname() + " " + model.getFname() + ")");
|
||||
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide.");
|
||||
}
|
||||
|
||||
ls.logChange("Nom", model.getLname(), dataIn.getNom().toUpperCase(), model);
|
||||
@ -299,7 +296,8 @@ public class MembreService {
|
||||
dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1), model);
|
||||
|
||||
model.setLname(dataIn.getNom().toUpperCase());
|
||||
model.setFname(Utils.formatPrenom(dataIn.getPrenom()));
|
||||
model.setFname(dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1));
|
||||
|
||||
|
||||
if (dataIn.getEmail() != null && !dataIn.getEmail().isBlank()) {
|
||||
ls.logChange("Email", model.getEmail(), dataIn.getEmail(), model);
|
||||
@ -419,7 +417,7 @@ public class MembreService {
|
||||
private Uni<String> update(Uni<MembreModel> uni, FullMemberForm membre, boolean admin) {
|
||||
return uni.chain(target -> {
|
||||
ls.logChange("Prénom", target.getFname(), membre.getFname(), target);
|
||||
target.setFname(Utils.formatPrenom(membre.getFname()));
|
||||
target.setFname(membre.getFname());
|
||||
ls.logChange("Nom", target.getLname(), membre.getLname(), target);
|
||||
target.setLname(membre.getLname().toUpperCase());
|
||||
ls.logChange("Pays", target.getCountry(), membre.getCountry(), target);
|
||||
@ -562,8 +560,8 @@ public class MembreService {
|
||||
|
||||
private static MembreModel getMembreModel(FullMemberForm input, ClubModel clubModel) {
|
||||
MembreModel model = new MembreModel();
|
||||
model.setFname(Utils.formatPrenom(input.getFname()));
|
||||
model.setLname(input.getLname().toUpperCase());
|
||||
model.setFname(input.getFname());
|
||||
model.setLname(input.getLname());
|
||||
model.setEmail(input.getEmail());
|
||||
model.setLicence(null);
|
||||
model.setGenre(input.getGenre());
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package fr.titionfire.ffsaf.domain.service;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.*;
|
||||
import fr.titionfire.ffsaf.data.repository.*;
|
||||
import fr.titionfire.ffsaf.data.repository.CategoryRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.MatchRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.RegisterRepository;
|
||||
import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
import fr.titionfire.ffsaf.utils.*;
|
||||
@ -16,9 +19,7 @@ import lombok.Builder;
|
||||
import org.hibernate.reactive.mutiny.Mutiny;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@WithSession
|
||||
@ -34,12 +35,6 @@ public class ResultService {
|
||||
@Inject
|
||||
MembreService membreService;
|
||||
|
||||
@Inject
|
||||
ClubRepository clubRepository;
|
||||
|
||||
@Inject
|
||||
CompetitionGuestRepository competitionGuestRepository;
|
||||
|
||||
@Inject
|
||||
CategoryRepository categoryRepository;
|
||||
|
||||
@ -58,22 +53,10 @@ public class ResultService {
|
||||
.collect().asList();
|
||||
}
|
||||
|
||||
public Uni<HashMap<String, Long>> getCategoryList(String uuid) {
|
||||
return categoryRepository.list("compet.uuid = ?1", uuid)
|
||||
.map(categoryModels -> {
|
||||
HashMap<String, Long> map = new HashMap<>();
|
||||
categoryModels.stream()
|
||||
.sorted(Comparator.comparing(CategoryModel::getName))
|
||||
.forEachOrdered(categoryModel -> map.put(categoryModel.getName(), categoryModel.getId()));
|
||||
return map;
|
||||
});
|
||||
}
|
||||
|
||||
public Uni<List<ResultCategoryData>> getCategory(String uuid, SecurityCtx securityCtx) {
|
||||
return hasAccess(uuid, securityCtx)
|
||||
.chain(m -> categoryRepository.list("compet.uuid = ?1", uuid)
|
||||
.chain(cats -> matchRepository.list(
|
||||
"(c1_id = ?1 OR c2_id = ?1 OR True) AND category IN ?2", //TODO rm OR True
|
||||
.chain(cats -> matchRepository.list("(c1_id = ?1 OR c2_id = ?1 OR True) AND category IN ?2", //TODO rm OR True
|
||||
m.getMembre(), cats)))
|
||||
.map(matchModels -> {
|
||||
HashMap<Long, List<MatchModel>> map = new HashMap<>();
|
||||
@ -99,8 +82,6 @@ public class ResultService {
|
||||
CategoryModel categoryModel = matchModels.get(0).getCategory();
|
||||
out.setName(categoryModel.getName());
|
||||
out.setType(categoryModel.getType());
|
||||
out.setLiceName(categoryModel.getLiceName() == null ? new String[]{} : categoryModel.getLiceName().split(";"));
|
||||
out.setGenTime(System.currentTimeMillis());
|
||||
|
||||
getArray2(matchModels, out);
|
||||
getTree(categoryModel.getTree(), out);
|
||||
@ -192,12 +173,6 @@ public class ResultService {
|
||||
}
|
||||
}
|
||||
|
||||
public Uni<ResultCategoryData> getCategoryPublic(String uuid, long poule) {
|
||||
return matchRepository.list("category.compet.uuid = ?1 AND category.id = ?2", uuid, poule)
|
||||
.call(list -> Mutiny.fetch(list.get(0).getCategory().getTree()))
|
||||
.map(this::getData);
|
||||
}
|
||||
|
||||
private void getTree(List<TreeModel> treeModels, ResultCategoryData out) {
|
||||
ArrayList<TreeNode<ResultCategoryData.TreeData>> trees = new ArrayList<>();
|
||||
treeModels.stream().filter(t -> t.getLevel() != 0).forEach(treeModel -> {
|
||||
@ -210,230 +185,87 @@ public class ResultService {
|
||||
|
||||
public Uni<CombsArrayData> getAllCombArray(String uuid, SecurityCtx securityCtx) {
|
||||
return hasAccess(uuid, securityCtx)
|
||||
.chain(__ -> getAllCombArray(uuid));
|
||||
}
|
||||
.chain(cm_register -> registerRepository.list("competition.uuid = ?1", uuid)
|
||||
.chain(registers -> matchRepository.list("category.compet.uuid = ?1", uuid)
|
||||
.map(matchModels -> new Pair<>(registers, matchModels)))
|
||||
.map(pair -> {
|
||||
List<RegisterModel> registers = pair.getKey();
|
||||
List<MatchModel> matchModels = pair.getValue();
|
||||
|
||||
public Uni<CombsArrayData> getAllCombArrayPublic(String uuid) {
|
||||
return getAllCombArray(uuid);
|
||||
}
|
||||
CombsArrayData.CombsArrayDataBuilder builder = CombsArrayData.builder();
|
||||
|
||||
public Uni<CombsArrayData> getAllCombArray(String uuid) {
|
||||
return registerRepository.list("competition.uuid = ?1", uuid)
|
||||
.chain(registers -> matchRepository.list("category.compet.uuid = ?1", uuid)
|
||||
.map(matchModels -> new Pair<>(registers, matchModels)))
|
||||
.map(pair -> {
|
||||
List<RegisterModel> registers = pair.getKey();
|
||||
List<MatchModel> matchModels = pair.getValue();
|
||||
List<CombsArrayData.CombsData> combs = matchModels.stream()
|
||||
.flatMap(m -> Stream.of(m.getC1Name(), m.getC2Name()))
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.map(combName -> {
|
||||
var builder2 = CombsArrayData.CombsData.builder();
|
||||
AtomicInteger w = new AtomicInteger(0);
|
||||
AtomicInteger l = new AtomicInteger(0);
|
||||
AtomicInteger pointMake = new AtomicInteger();
|
||||
AtomicInteger pointTake = new AtomicInteger();
|
||||
|
||||
CombsArrayData.CombsArrayDataBuilder builder = CombsArrayData.builder();
|
||||
matchModels.stream()
|
||||
.filter(m -> m.isEnd() && (m.getC1Name().equals(combName)
|
||||
|| m.getC2Name().equals(combName)))
|
||||
.forEach(matchModel -> {
|
||||
int win = matchModel.win();
|
||||
if ((combName.equals(matchModel.getC1Name()) && win > 0) ||
|
||||
combName.equals(matchModel.getC2Name()) && win < 0) {
|
||||
w.getAndIncrement();
|
||||
} else {
|
||||
l.getAndIncrement();
|
||||
}
|
||||
|
||||
List<CombsArrayData.CombsData> combs = matchModels.stream()
|
||||
.flatMap(m -> Stream.of(m.getC1Name(), m.getC2Name()))
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.map(combName -> {
|
||||
var builder2 = CombsArrayData.CombsData.builder();
|
||||
AtomicInteger w = new AtomicInteger(0);
|
||||
AtomicInteger l = new AtomicInteger(0);
|
||||
AtomicInteger pointMake = new AtomicInteger();
|
||||
AtomicInteger pointTake = new AtomicInteger();
|
||||
matchModel.getScores().stream()
|
||||
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
|
||||
.forEach(score -> {
|
||||
if (combName.equals(matchModel.getC1Name())) {
|
||||
pointMake.addAndGet(score.getS1());
|
||||
pointTake.addAndGet(score.getS2());
|
||||
} else {
|
||||
pointMake.addAndGet(score.getS2());
|
||||
pointTake.addAndGet(score.getS1());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
matchModels.stream()
|
||||
.filter(m -> m.isEnd() && (m.getC1Name().equals(combName)
|
||||
|| m.getC2Name().equals(combName)))
|
||||
.forEach(matchModel -> {
|
||||
int win = matchModel.win();
|
||||
if ((combName.equals(matchModel.getC1Name()) && win > 0) ||
|
||||
combName.equals(matchModel.getC2Name()) && win < 0) {
|
||||
w.getAndIncrement();
|
||||
} else {
|
||||
l.getAndIncrement();
|
||||
}
|
||||
Categorie categorie = null;
|
||||
ClubModel club = null;
|
||||
|
||||
matchModel.getScores().stream()
|
||||
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
|
||||
.forEach(score -> {
|
||||
if (combName.equals(matchModel.getC1Name())) {
|
||||
pointMake.addAndGet(score.getS1());
|
||||
pointTake.addAndGet(score.getS2());
|
||||
} else {
|
||||
pointMake.addAndGet(score.getS2());
|
||||
pointTake.addAndGet(score.getS1());
|
||||
}
|
||||
});
|
||||
});
|
||||
Optional<RegisterModel> register = registers.stream()
|
||||
.filter(r -> r.getName().equals(combName)).findFirst();
|
||||
if (register.isPresent()) {
|
||||
categorie = register.get().getCategorie();
|
||||
club = register.get().getClub2();
|
||||
}
|
||||
|
||||
Categorie categorie = null;
|
||||
ClubModel club = null;
|
||||
builder2.cat((categorie == null) ? "---" : categorie.getName(BUNDLE));
|
||||
builder2.name(combName);
|
||||
builder2.w(w.get());
|
||||
builder2.l(l.get());
|
||||
builder2.ratioVictoire((l.get() == 0) ? w.get() : (float) w.get() / l.get());
|
||||
builder2.club((club == null) ? BUNDLE.getString("no.licence") : club.getName());
|
||||
builder2.pointMake(pointMake.get());
|
||||
builder2.pointTake(pointTake.get());
|
||||
builder2.ratioPoint(
|
||||
(pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
|
||||
|
||||
Optional<RegisterModel> register = registers.stream()
|
||||
.filter(r -> r.getName().equals(combName)).findFirst();
|
||||
if (register.isPresent()) {
|
||||
categorie = register.get().getCategorie();
|
||||
club = register.get().getClub2();
|
||||
}
|
||||
return builder2.build();
|
||||
})
|
||||
.sorted(Comparator.comparing(CombsArrayData.CombsData::name))
|
||||
.toList();
|
||||
|
||||
builder2.cat((categorie == null) ? "---" : categorie.getName(BUNDLE));
|
||||
builder2.name(combName);
|
||||
builder2.w(w.get());
|
||||
builder2.l(l.get());
|
||||
builder2.ratioVictoire((l.get() == 0) ? w.get() : (float) w.get() / l.get());
|
||||
builder2.club((club == null) ? BUNDLE.getString("no.licence") : club.getName());
|
||||
builder2.pointMake(pointMake.get());
|
||||
builder2.pointTake(pointTake.get());
|
||||
builder2.ratioPoint(
|
||||
(pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
|
||||
builder.nb_insc(combs.size());
|
||||
builder.tt_match((int) matchModels.stream().filter(MatchModel::isEnd).count());
|
||||
builder.point(combs.stream().mapToInt(CombsArrayData.CombsData::pointMake).sum());
|
||||
builder.combs(combs);
|
||||
|
||||
return builder2.build();
|
||||
})
|
||||
.sorted(Comparator.comparing(CombsArrayData.CombsData::name))
|
||||
.toList();
|
||||
|
||||
builder.nb_insc(combs.size());
|
||||
builder.tt_match((int) matchModels.stream().filter(MatchModel::isEnd).count());
|
||||
builder.point(combs.stream().mapToInt(CombsArrayData.CombsData::pointMake).sum());
|
||||
builder.combs(combs);
|
||||
|
||||
return builder.build();
|
||||
});
|
||||
}
|
||||
|
||||
public Uni<HashMap<String, String>> getCombList(String uuid) {
|
||||
return registerRepository.list("competition.uuid = ?1", uuid)
|
||||
.map(models -> {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
models.forEach(registerEmbeddable -> {
|
||||
map.put(Utils.getFullName(registerEmbeddable.getMembre()),
|
||||
registerEmbeddable.getMembre().getFname() + "¤" + registerEmbeddable.getMembre()
|
||||
.getLname());
|
||||
});
|
||||
return map;
|
||||
})
|
||||
.chain(map -> competitionGuestRepository.list("competition.uuid = ?1", uuid)
|
||||
.map(models -> {
|
||||
models.forEach(guestModel -> map.put(Utils.getFullName(guestModel),
|
||||
guestModel.getFname() + "¤" + guestModel.getLname()));
|
||||
return map;
|
||||
return builder.build();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public Uni<?> getCombArrayPublic(String uuid, String fname, String lname) {
|
||||
CombArrayData.CombArrayDataBuilder builder = CombArrayData.builder();
|
||||
AtomicBoolean guest = new AtomicBoolean(false);
|
||||
AtomicLong id = new AtomicLong(0);
|
||||
|
||||
|
||||
return registerRepository.find("membre.fname = ?1 AND membre.lname = ?2 AND competition.uuid = ?3", fname,
|
||||
lname, uuid).firstResult()
|
||||
.chain(registerModel -> {
|
||||
if (registerModel == null) {
|
||||
return competitionGuestRepository.find("fname = ?1 AND lname = ?2 AND competition.uuid = ?3",
|
||||
fname, lname, uuid).firstResult()
|
||||
.chain(guestModel -> {
|
||||
builder.name(Utils.getFullName(guestModel));
|
||||
builder.club(guestModel.getClub());
|
||||
guest.set(true);
|
||||
id.set(guestModel.getId());
|
||||
builder.cat((guestModel.getCategorie() == null) ? "---" : guestModel.getCategorie()
|
||||
.getName(BUNDLE));
|
||||
|
||||
return matchRepository.list(
|
||||
"category.compet.uuid = ?1 AND (c1_guest = ?2 OR c2_guest = ?2)", uuid,
|
||||
guestModel);
|
||||
});
|
||||
}
|
||||
builder.name(Utils.getFullName(registerModel.getMembre()));
|
||||
builder.club((registerModel.getClub2() == null) ? BUNDLE.getString(
|
||||
"no.licence") : registerModel.getClub2().getName());
|
||||
id.set(registerModel.getMembre().getId());
|
||||
builder.cat((registerModel.getCategorie2() == null) ? "---" : registerModel.getCategorie2()
|
||||
.getName(BUNDLE));
|
||||
|
||||
return matchRepository.list("category.compet.uuid = ?1 AND (c1_id = ?2 OR c2_id = ?2)", uuid,
|
||||
registerModel.getMembre());
|
||||
})
|
||||
.invoke(matchModels -> {
|
||||
List<CategoryModel> pouleModels = matchModels.stream().map(MatchModel::getCategory).distinct()
|
||||
.toList();
|
||||
List<CombArrayData.MatchsData> matchs = new ArrayList<>();
|
||||
|
||||
AtomicInteger sumW = new AtomicInteger();
|
||||
AtomicInteger sumPointMake = new AtomicInteger(0);
|
||||
AtomicInteger sumPointTake = new AtomicInteger(0);
|
||||
|
||||
for (MatchModel matchModel : matchModels) {
|
||||
if ((matchModel.getC1_id() == null && matchModel.getC1_guest() == null) ||
|
||||
(matchModel.getC2_id() == null && matchModel.getC2_guest() == null))
|
||||
continue;
|
||||
|
||||
var builder2 = CombArrayData.MatchsData.builder();
|
||||
builder2.date(matchModel.getDate());
|
||||
builder2.poule(pouleModels.stream().filter(p -> p.equals(matchModel.getCategory()))
|
||||
.map(CategoryModel::getName).findFirst().orElse(""));
|
||||
|
||||
AtomicInteger pointMake = new AtomicInteger();
|
||||
AtomicInteger pointTake = new AtomicInteger();
|
||||
|
||||
if ((!guest.get() && matchModel.getC1_id() != null && matchModel.getC1_id().getId() == id.get())
|
||||
|| (guest.get() && matchModel.getC1_guest() != null && matchModel.getC1_guest()
|
||||
.getId() == id.get())) {
|
||||
builder2.adv(Utils.getFullName(matchModel.getC2_id(), matchModel.getC2_guest()));
|
||||
if (matchModel.isEnd()) {
|
||||
matchModel.getScores().stream()
|
||||
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
|
||||
.forEach(scoreEntity -> {
|
||||
pointMake.addAndGet(scoreEntity.getS1());
|
||||
pointTake.addAndGet(scoreEntity.getS2());
|
||||
});
|
||||
builder2.score(matchModel.getScores().stream()
|
||||
.map(s -> new Integer[]{s.getS1(), s.getS2()}).toList());
|
||||
} else {
|
||||
builder2.score(new ArrayList<>());
|
||||
}
|
||||
builder2.win(matchModel.win() > 0);
|
||||
} else {
|
||||
builder2.adv(Utils.getFullName(matchModel.getC1_id(), matchModel.getC1_guest()));
|
||||
if (matchModel.isEnd()) {
|
||||
matchModel.getScores().stream()
|
||||
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
|
||||
.forEach(scoreEntity -> {
|
||||
pointMake.addAndGet(scoreEntity.getS2());
|
||||
pointTake.addAndGet(scoreEntity.getS1());
|
||||
});
|
||||
builder2.score(matchModel.getScores().stream()
|
||||
.map(s -> new Integer[]{s.getS2(), s.getS1()}).toList());
|
||||
} else {
|
||||
builder2.score(new ArrayList<>());
|
||||
}
|
||||
builder2.win(matchModel.win() < 0);
|
||||
}
|
||||
|
||||
builder2.ratio(
|
||||
(pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
|
||||
|
||||
sumPointMake.addAndGet(pointMake.get());
|
||||
sumPointTake.addAndGet(pointTake.get());
|
||||
|
||||
matchs.add(builder2.build());
|
||||
if (builder2.win)
|
||||
sumW.getAndIncrement();
|
||||
}
|
||||
|
||||
builder.totalWin(sumW.get());
|
||||
builder.pointRatio(
|
||||
(sumPointTake.get() == 0) ? sumPointMake.get() : (float) sumPointMake.get() / sumPointTake.get());
|
||||
builder.pointMake(sumPointMake.get());
|
||||
builder.pointTake(sumPointTake.get());
|
||||
|
||||
matchs.sort(Comparator.comparing(CombArrayData.MatchsData::poule)
|
||||
.thenComparing(CombArrayData.MatchsData::adv));
|
||||
|
||||
builder.matchs(matchs);
|
||||
})
|
||||
.map(__ -> builder.build());
|
||||
}
|
||||
|
||||
|
||||
@Builder
|
||||
@RegisterForReflection
|
||||
@ -445,117 +277,96 @@ public class ResultService {
|
||||
}
|
||||
}
|
||||
|
||||
@Builder
|
||||
@RegisterForReflection
|
||||
public static record CombArrayData(String name, String club, String cat, int totalWin,
|
||||
float pointRatio, int pointMake, int pointTake, List<MatchsData> matchs) {
|
||||
@Builder
|
||||
@RegisterForReflection
|
||||
public static record MatchsData(Date date, String poule, String adv, List<Integer[]> score, float ratio,
|
||||
boolean win) {
|
||||
}
|
||||
}
|
||||
|
||||
public Uni<HashMap<String, Long>> getClubList(String uuid) { // TODO add guest club
|
||||
return registerRepository.list("competition.uuid = ?1", uuid)
|
||||
.map(registers -> {
|
||||
HashMap<String, Long> registerMap = new HashMap<>();
|
||||
registers.stream().map(RegisterModel::getClub2).distinct().filter(Objects::nonNull)
|
||||
.forEach(registerClub -> registerMap.put(registerClub.getName(), registerClub.getId()));
|
||||
return registerMap;
|
||||
});
|
||||
}
|
||||
|
||||
public Uni<ClubArrayData> getClubArray(String uuid, SecurityCtx securityCtx) {
|
||||
return hasAccess(uuid, securityCtx).chain(cm_register -> getClubArray(uuid, cm_register.getClub2()));
|
||||
}
|
||||
|
||||
public Uni<ClubArrayData> getClubArrayPublic(String uuid, Long id) {
|
||||
return clubRepository.findById(id).chain(clubModel -> getClubArray(uuid, clubModel));
|
||||
}
|
||||
|
||||
public Uni<ClubArrayData> getClubArray(String uuid, ClubModel clubModel) {
|
||||
ClubArrayData.ClubArrayDataBuilder builder = ClubArrayData.builder();
|
||||
builder.name(clubModel.getName());
|
||||
|
||||
return registerRepository.list("competition.uuid = ?1 AND membre.club = ?2", uuid, clubModel)
|
||||
.chain(registers -> matchRepository.list("category.compet.uuid = ?1", uuid)
|
||||
.map(matchModels -> new Pair<>(registers, matchModels)))
|
||||
.map(pair -> {
|
||||
List<RegisterModel> registers = pair.getKey();
|
||||
List<MatchModel> matchModels = pair.getValue();
|
||||
return hasAccess(uuid, securityCtx)
|
||||
.invoke(cm_register -> builder.name(cm_register.getClub2().getName()))
|
||||
.chain(cm_register -> registerRepository.list("competition.uuid = ?1 AND membre.club = ?2", uuid,
|
||||
cm_register.getClub2())
|
||||
.chain(registers -> matchRepository.list("category.compet.uuid = ?1", uuid)
|
||||
.map(matchModels -> new Pair<>(registers, matchModels)))
|
||||
.map(pair -> {
|
||||
List<RegisterModel> registers = pair.getKey();
|
||||
List<MatchModel> matchModels = pair.getValue();
|
||||
|
||||
builder.nb_insc(registers.size());
|
||||
builder.nb_insc(registers.size());
|
||||
|
||||
AtomicInteger tt_win = new AtomicInteger(0);
|
||||
AtomicInteger tt_match = new AtomicInteger(0);
|
||||
AtomicInteger tt_win = new AtomicInteger(0);
|
||||
AtomicInteger tt_match = new AtomicInteger(0);
|
||||
|
||||
List<ClubArrayData.CombData> combData = registers.stream().map(register -> {
|
||||
var builder2 = ClubArrayData.CombData.builder();
|
||||
AtomicInteger w = new AtomicInteger(0);
|
||||
AtomicInteger l = new AtomicInteger(0);
|
||||
AtomicInteger pointMake = new AtomicInteger();
|
||||
AtomicInteger pointTake = new AtomicInteger();
|
||||
List<ClubArrayData.CombData> combData = registers.stream()
|
||||
.map(register -> {
|
||||
|
||||
matchModels.stream()
|
||||
.filter(m -> m.isEnd() && (register.getMembre().equals(m.getC1_id())
|
||||
|| register.getMembre().equals(m.getC2_id())))
|
||||
.forEach(matchModel -> {
|
||||
int win = matchModel.win();
|
||||
if ((register.getMembre().equals(matchModel.getC1_id()) && win > 0) ||
|
||||
register.getMembre().equals(matchModel.getC2_id()) && win < 0) {
|
||||
w.getAndIncrement();
|
||||
} else {
|
||||
l.getAndIncrement();
|
||||
}
|
||||
var builder2 = ClubArrayData.CombData.builder();
|
||||
AtomicInteger w = new AtomicInteger(0);
|
||||
AtomicInteger l = new AtomicInteger(0);
|
||||
AtomicInteger pointMake = new AtomicInteger();
|
||||
AtomicInteger pointTake = new AtomicInteger();
|
||||
|
||||
matchModel.getScores().stream()
|
||||
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
|
||||
.forEach(score -> {
|
||||
if (register.getMembre().equals(matchModel.getC1_id())) {
|
||||
pointMake.addAndGet(score.getS1());
|
||||
pointTake.addAndGet(score.getS2());
|
||||
} else {
|
||||
pointMake.addAndGet(score.getS2());
|
||||
pointTake.addAndGet(score.getS1());
|
||||
}
|
||||
});
|
||||
});
|
||||
matchModels.stream()
|
||||
.filter(m -> m.isEnd() && (register.getMembre().equals(m.getC1_id())
|
||||
|| register.getMembre().equals(m.getC2_id())))
|
||||
.forEach(matchModel -> {
|
||||
int win = matchModel.win();
|
||||
if ((register.getMembre()
|
||||
.equals(matchModel.getC1_id()) && win > 0) ||
|
||||
register.getMembre()
|
||||
.equals(matchModel.getC2_id()) && win < 0) {
|
||||
w.getAndIncrement();
|
||||
} else {
|
||||
l.getAndIncrement();
|
||||
}
|
||||
|
||||
Categorie categorie = register.getCategorie();
|
||||
if (categorie == null)
|
||||
categorie = register.getMembre().getCategorie();
|
||||
matchModel.getScores().stream()
|
||||
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
|
||||
.forEach(score -> {
|
||||
if (register.getMembre()
|
||||
.equals(matchModel.getC1_id())) {
|
||||
pointMake.addAndGet(score.getS1());
|
||||
pointTake.addAndGet(score.getS2());
|
||||
} else {
|
||||
pointMake.addAndGet(score.getS2());
|
||||
pointTake.addAndGet(score.getS1());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
builder2.cat((categorie == null) ? "---" : categorie.getName(BUNDLE));
|
||||
builder2.name(register.getName());
|
||||
builder2.w(w.get());
|
||||
builder2.l(l.get());
|
||||
builder2.ratioVictoire((l.get() == 0) ? w.get() : (float) w.get() / l.get());
|
||||
builder2.pointMake(pointMake.get());
|
||||
builder2.pointTake(pointTake.get());
|
||||
builder2.ratioPoint(
|
||||
(pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
|
||||
Categorie categorie = register.getCategorie();
|
||||
if (categorie == null)
|
||||
categorie = register.getMembre().getCategorie();
|
||||
|
||||
tt_win.addAndGet(w.get());
|
||||
tt_match.addAndGet(w.get() + l.get());
|
||||
builder2.cat((categorie == null) ? "---" : categorie.getName(BUNDLE));
|
||||
builder2.name(register.getName());
|
||||
builder2.w(w.get());
|
||||
builder2.l(l.get());
|
||||
builder2.ratioVictoire((l.get() == 0) ? w.get() : (float) w.get() / l.get());
|
||||
builder2.pointMake(pointMake.get());
|
||||
builder2.pointTake(pointTake.get());
|
||||
builder2.ratioPoint(
|
||||
(pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
|
||||
|
||||
return builder2.build();
|
||||
})
|
||||
.sorted(Comparator.comparing(ClubArrayData.CombData::name))
|
||||
.toList();
|
||||
tt_win.addAndGet(w.get());
|
||||
tt_match.addAndGet(w.get() + l.get());
|
||||
|
||||
builder.nb_match(tt_match.get());
|
||||
builder.match_w(tt_win.get());
|
||||
builder.ratioVictoire((float) combData.stream().filter(c -> c.l + c.w != 0)
|
||||
.mapToDouble(ClubArrayData.CombData::ratioVictoire).average().orElse(0L));
|
||||
builder.pointMake(combData.stream().mapToInt(ClubArrayData.CombData::pointMake).sum());
|
||||
builder.pointTake(combData.stream().mapToInt(ClubArrayData.CombData::pointTake).sum());
|
||||
builder.ratioPoint((float) combData.stream().filter(c -> c.l + c.w != 0)
|
||||
.mapToDouble(ClubArrayData.CombData::ratioPoint).average().orElse(0L));
|
||||
builder.combs(combData);
|
||||
return builder2.build();
|
||||
})
|
||||
.sorted(Comparator.comparing(ClubArrayData.CombData::name))
|
||||
.toList();
|
||||
|
||||
return builder.build();
|
||||
});
|
||||
builder.nb_match(tt_match.get());
|
||||
builder.match_w(tt_win.get());
|
||||
builder.ratioVictoire((float) combData.stream().filter(c -> c.l + c.w != 0)
|
||||
.mapToDouble(ClubArrayData.CombData::ratioVictoire).average().orElse(0L));
|
||||
builder.pointMake(combData.stream().mapToInt(ClubArrayData.CombData::pointMake).sum());
|
||||
builder.pointTake(combData.stream().mapToInt(ClubArrayData.CombData::pointTake).sum());
|
||||
builder.ratioPoint((float) combData.stream().filter(c -> c.l + c.w != 0)
|
||||
.mapToDouble(ClubArrayData.CombData::ratioPoint).average().orElse(0L));
|
||||
builder.combs(combData);
|
||||
|
||||
return builder.build();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Builder
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
package fr.titionfire.ffsaf.domain.service;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@ApplicationScoped
|
||||
public class UpdateService { // For public result page
|
||||
static HashMap<Long, Long> lastUpdate = new HashMap<>();
|
||||
|
||||
public void setNewData(long id) {
|
||||
lastUpdate.put(id, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public boolean needUpdate(long id, long last_update) {
|
||||
if (!lastUpdate.containsKey(id)) {
|
||||
lastUpdate.put(id, System.currentTimeMillis() - 5000);
|
||||
return true;
|
||||
}
|
||||
|
||||
return lastUpdate.getOrDefault(id, 0L) > last_update;
|
||||
}
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
package fr.titionfire.ffsaf.rest;
|
||||
|
||||
import fr.titionfire.ffsaf.domain.service.ResultService;
|
||||
import fr.titionfire.ffsaf.domain.service.UpdateService;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@Path("api/public/result/{id}")
|
||||
public class ExternalResultEndpoints {
|
||||
|
||||
@Inject
|
||||
ResultService resultService;
|
||||
|
||||
@Inject
|
||||
UpdateService updateService;
|
||||
|
||||
@PathParam("id")
|
||||
private String id;
|
||||
|
||||
@GET
|
||||
@Path("/poule/list")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<HashMap<String, Long>> list() {
|
||||
return resultService.getCategoryList(id);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/poule/data")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<?> getArray(@QueryParam("poule") long poule, @DefaultValue("-1") @QueryParam("rf") long rf) {
|
||||
if (poule == 0)
|
||||
return Uni.createFrom().voidItem();
|
||||
|
||||
if (updateService.needUpdate(poule, rf)) {
|
||||
return resultService.getCategoryPublic(id, poule);
|
||||
} else {
|
||||
return Uni.createFrom().voidItem();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/comb/list")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<HashMap<String, String>> combList() {
|
||||
return resultService.getCombList(id);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/comb/data")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<?> getArray(@QueryParam("comb") String comb) {
|
||||
if (comb.equals("0"))
|
||||
return Uni.createFrom().item("");
|
||||
return resultService.getCombArrayPublic(id, comb.substring(0, comb.indexOf('¤')), comb.substring(comb.indexOf('¤') + 1));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/comb/get_all")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<?> getAll() {
|
||||
return resultService.getAllCombArrayPublic(id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GET
|
||||
@Path("/club/list")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<HashMap<String, Long>> clubList() {
|
||||
return resultService.getClubList(id);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/club/data")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<?> getClubArray(@QueryParam("club") long club) {
|
||||
if (club == 0)
|
||||
return Uni.createFrom().item("");
|
||||
return resultService.getClubArrayPublic(id, club);
|
||||
}
|
||||
}
|
||||
@ -21,8 +21,6 @@ public class ResultCategoryData {
|
||||
HashMap<Character, List<PouleArrayData>> matchs = new HashMap<>();
|
||||
HashMap<Character, List<RankArray>> rankArray = new HashMap<>();
|
||||
ArrayList<TreeNode<TreeData>> trees;
|
||||
String[] liceName;
|
||||
long genTime;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
package fr.titionfire.ffsaf.utils;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
@ -337,46 +335,4 @@ public class Utils {
|
||||
|
||||
return (int) ((calendar.getTimeInMillis() - now.getTimeInMillis()) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
public static String formatPrenom(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
String[] mots = input.split(" ");
|
||||
|
||||
for (String mot : mots) {
|
||||
if (!mot.isEmpty()) {
|
||||
String[] parties = mot.split("[-']");
|
||||
StringBuilder motFormate = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < parties.length; i++) {
|
||||
if (!parties[i].isEmpty()) {
|
||||
String premiereLettre = parties[i].substring(0, 1).toUpperCase();
|
||||
String reste = parties[i].substring(1).toLowerCase();
|
||||
motFormate.append(premiereLettre).append(reste);
|
||||
}
|
||||
|
||||
if (i < parties.length - 1) {
|
||||
motFormate.append(mot.charAt(mot.indexOf(parties[i]) + parties[i].length()));
|
||||
}
|
||||
}
|
||||
result.append(motFormate).append(" ");
|
||||
}
|
||||
}
|
||||
return result.toString().trim();
|
||||
}
|
||||
|
||||
public static String getFullName(Object ...models) {
|
||||
for (Object model : models){
|
||||
if (model == null)
|
||||
continue;
|
||||
if (model instanceof MembreModel membreModel)
|
||||
return membreModel.getFname() + " " + membreModel.getLname();
|
||||
if (model instanceof CompetitionGuestModel guestModel)
|
||||
return guestModel.getFname() + " " + guestModel.getLname();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ import org.jboss.logging.Logger;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
|
||||
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
|
||||
@ -167,7 +166,6 @@ public class CompetitionWS {
|
||||
return Uni.createFrom().item(makeError(message, "Permission denied")).toMulti();
|
||||
return ((Uni<?>) method.invoke(entry.getValue(), connection,
|
||||
MAPPER.treeToValue(message.data(), method.getParameterTypes()[1])))
|
||||
.ifNoItem().after(Duration.ofSeconds(5)).fail()
|
||||
.map(o -> makeReply(message, o))
|
||||
.onFailure()
|
||||
.recoverWithItem(t -> {
|
||||
|
||||
@ -118,8 +118,7 @@ public class RCategorie {
|
||||
uni = uni.chain(__ -> treeRepository.delete("category = ?1", cat.getId()))
|
||||
.chain(__ -> matchRepository.delete("category = ?1 AND category_ord = -42", cat));
|
||||
}
|
||||
Uni<Long> finalUni = uni;
|
||||
return Panache.withTransaction(() -> finalUni);
|
||||
return uni;
|
||||
})
|
||||
.call(cat -> SSCategorie.sendCategory(connection, cat))
|
||||
.replaceWithVoid();
|
||||
@ -206,16 +205,6 @@ public class RCategorie {
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@WSReceiver(code = "deleteCategory", permission = PermLevel.ADMIN)
|
||||
public Uni<Void> deleteCategory(WebSocketConnection connection, Long id) {
|
||||
return getById(id, connection)
|
||||
.call(cat -> Panache.withTransaction(() -> treeRepository.delete("category = ?1", cat.getId())
|
||||
.call(__ -> matchRepository.delete("category = ?1", cat))))
|
||||
.chain(cat -> Panache.withTransaction(() -> categoryRepository.delete(cat)))
|
||||
.call(__ -> SSCategorie.sendDelCategory(connection, id))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record JustCategorie(long id, String name, int type, String liceName) {
|
||||
public static JustCategorie from(CategoryModel m) {
|
||||
|
||||
@ -30,8 +30,4 @@ public class SSCategorie {
|
||||
public static Uni<Void> sendTreeCategory(WebSocketConnection connection, List<TreeEntity> treeEntities) {
|
||||
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", treeEntities);
|
||||
}
|
||||
|
||||
public static Uni<?> sendDelCategory(WebSocketConnection connection, Long id) {
|
||||
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendDelCategory", id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,4 +75,6 @@ helloasso.client-id=changeme
|
||||
helloasso.client-secret=changeme
|
||||
|
||||
quarkus.rest-client.helloasso-auth.url=${helloasso.api}/oauth2
|
||||
quarkus.rest-client.helloasso-auth.scope=javax.inject.Singleton
|
||||
|
||||
quarkus.rest-client.helloasso-api.url=${helloasso.api}/v5
|
||||
|
||||
1
src/main/webapp/.gitignore
vendored
1
src/main/webapp/.gitignore
vendored
@ -22,4 +22,3 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
/public/result_test.html
|
||||
|
||||
164
src/main/webapp/package-lock.json
generated
164
src/main/webapp/package-lock.json
generated
@ -18,9 +18,7 @@
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"axios": "^1.6.5",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"obs-websocket-js": "^5.0.7",
|
||||
"proj4": "^2.11.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@ -1060,14 +1058,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@msgpack/msgpack": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz",
|
||||
"integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -1855,11 +1845,6 @@
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
@ -1885,11 +1870,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||
},
|
||||
"node_modules/css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
@ -2027,6 +2007,7 @@
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
@ -2999,11 +2980,6 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
@ -3042,7 +3018,8 @@
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/internal-slot": {
|
||||
"version": "1.0.6",
|
||||
@ -3383,14 +3360,6 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/isomorphic-ws": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
||||
"integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
|
||||
"peerDependencies": {
|
||||
"ws": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/iterator.prototype": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz",
|
||||
@ -3478,17 +3447,6 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jszip": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
||||
"dependencies": {
|
||||
"lie": "~3.3.0",
|
||||
"pako": "~1.0.2",
|
||||
"readable-stream": "~2.3.6",
|
||||
"setimmediate": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@ -3516,14 +3474,6 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@ -3609,7 +3559,8 @@
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
@ -3745,39 +3696,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/obs-websocket-js": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/obs-websocket-js/-/obs-websocket-js-5.0.7.tgz",
|
||||
"integrity": "sha512-SdSNSyrLVR6F0ogInKr7qcadV1tYaTUse/vbabxjkUL8hU3P3dyifxkZ7pEkDDrtCp3TkQ53Enx23kgZO0Cjcw==",
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^2.7.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"debug": "^4.3.2",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"type-fest": "^3.11.0",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/obs-websocket-js/node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
|
||||
},
|
||||
"node_modules/obs-websocket-js/node_modules/type-fest": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
|
||||
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@ -3834,11 +3752,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"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",
|
||||
@ -3942,11 +3855,6 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"node_modules/proj4": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/proj4/-/proj4-2.11.0.tgz",
|
||||
@ -4147,25 +4055,6 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz",
|
||||
@ -4367,11 +4256,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/safe-regex-test": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz",
|
||||
@ -4435,11 +4319,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
|
||||
},
|
||||
"node_modules/shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
@ -4499,14 +4378,6 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.matchall": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz",
|
||||
@ -4852,11 +4723,6 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/uzip": {
|
||||
"version": "0.20201231.0",
|
||||
"resolved": "https://registry.npmjs.org/uzip/-/uzip-0.20201231.0.tgz",
|
||||
@ -5056,26 +4922,6 @@
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xlsx": {
|
||||
"version": "0.18.5",
|
||||
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||
|
||||
@ -20,9 +20,7 @@
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"axios": "^1.6.5",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"obs-websocket-js": "^5.0.7",
|
||||
"proj4": "^2.11.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@ -1,946 +0,0 @@
|
||||
let apiUrlRoot = "";
|
||||
const rootDiv = document.getElementById("safca_api_data");
|
||||
|
||||
const header = `<h4>Résultat de la compétition :</h4>`
|
||||
const backButton = `<a onclick="setSubPage('home')" href="javascript:void(0);">Retour</a>`
|
||||
const cupImg = `<img decoding="async" loading="lazy" width="16" height="16" class="wp-image-1635"
|
||||
style="width: 16px;" src="https://intra.ffsaf.fr/img/171891.png"
|
||||
alt="">`
|
||||
|
||||
const voidFunction = () => {
|
||||
}
|
||||
let lastRf = 0;
|
||||
let rfFonction = voidFunction;
|
||||
|
||||
setInterval(() => {
|
||||
rfFonction();
|
||||
}, 15000);
|
||||
|
||||
function setSubPage(name) {
|
||||
window.location.hash = name;
|
||||
const location = name.split('/');
|
||||
console.log(location);
|
||||
|
||||
switch (location[0]) {
|
||||
case 'home':
|
||||
homePage();
|
||||
break;
|
||||
case 'poule':
|
||||
poulePage(location);
|
||||
break;
|
||||
case 'comb':
|
||||
combPage(location);
|
||||
break;
|
||||
case 'club':
|
||||
clubPage(location);
|
||||
break;
|
||||
case 'all':
|
||||
combsPage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function homePage() {
|
||||
rootDiv.innerHTML = header;
|
||||
|
||||
let content = document.createElement('div');
|
||||
content.innerHTML = `
|
||||
<ul>
|
||||
<li><a onclick="setSubPage('poule')" href="javascript:void(0);">Par poule</a></li>
|
||||
<li><a onclick="setSubPage('comb')" href="javascript:void(0);">Par combattant</a></li>
|
||||
<li><a onclick="setSubPage('club')" href="javascript:void(0);">Par club</a></li>
|
||||
<li><a onclick="setSubPage('all')" href="javascript:void(0);">Tous les combattants</a></li>
|
||||
</ul>
|
||||
`
|
||||
rootDiv.append(content)
|
||||
}
|
||||
|
||||
let loadingAnimationStep = 0;
|
||||
|
||||
function startLoading(root) {
|
||||
const id = "loading" + Math.random().toString(36);
|
||||
let element = document.createElement('h2');
|
||||
element.id = id;
|
||||
|
||||
const anim = setInterval(() => {
|
||||
let str = "Chargement";
|
||||
for (let i = 0; i < loadingAnimationStep; i++) {
|
||||
str += ".";
|
||||
}
|
||||
loadingAnimationStep = (loadingAnimationStep + 1) % 4;
|
||||
element.innerText = str;
|
||||
}, 500);
|
||||
|
||||
root.append(element)
|
||||
return {interval: anim, root: root, element: element};
|
||||
}
|
||||
|
||||
function stopLoading(loading) {
|
||||
clearInterval(loading['interval']);
|
||||
loading['root'].removeChild(loading['element']);
|
||||
}
|
||||
|
||||
function scoreToString(score) {
|
||||
const scorePrint = (s1) => {
|
||||
switch (s1) {
|
||||
case -997:
|
||||
return "disc.";
|
||||
case -998:
|
||||
return "abs.";
|
||||
case -999:
|
||||
return "for.";
|
||||
case -1000:
|
||||
return "";
|
||||
default:
|
||||
return String(s1);
|
||||
}
|
||||
}
|
||||
|
||||
return score.map(o => scorePrint(o.at(0)) + "-" + scorePrint(o.at(1))).join(" | ");
|
||||
}
|
||||
|
||||
function dateToString(date) {
|
||||
if (date === null || date === undefined)
|
||||
return "";
|
||||
|
||||
const date_ = new Date(date);
|
||||
const date_2 = new Date(date);
|
||||
const current = new Date();
|
||||
current.setHours(0, 0, 0, 0);
|
||||
date_2.setHours(0, 0, 0, 0);
|
||||
|
||||
let d = Math.floor((current - date_2) / (1000 * 60 * 60 * 24));
|
||||
if (d === 0)
|
||||
return "Aujourd'hui à " + date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
|
||||
else if (d === 1)
|
||||
return "Hier à " + date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
|
||||
else if (d === 2)
|
||||
return "Avant-hier à " + date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
|
||||
else
|
||||
return date_.toLocaleDateString();
|
||||
}
|
||||
|
||||
function buildPouleMenu(isPoule, change_view) {
|
||||
const menuDiv = document.createElement('div');
|
||||
menuDiv.id = 'menu';
|
||||
menuDiv.style.borderBottom = '1px solid #9EA0A1';
|
||||
menuDiv.style.paddingBottom = '25px';
|
||||
|
||||
const ul = document.createElement('ul');
|
||||
ul.id = 'onglets';
|
||||
ul.style.position = 'absolute';
|
||||
ul.style.border = '1px solid transparent';
|
||||
ul.style.padding = '0';
|
||||
ul.style.font = 'bold 11px Batang, arial, serif';
|
||||
ul.style.listStyleType = 'none';
|
||||
ul.style.left = '50%';
|
||||
ul.style.marginTop = '0';
|
||||
ul.style.width = '430px';
|
||||
ul.style.marginLeft = '-215px';
|
||||
|
||||
function createTab(text, isActive, onClickHandler) {
|
||||
const li = document.createElement('li');
|
||||
if (isActive) {
|
||||
li.className = 'active';
|
||||
li.style.borderBottom = '1px solid #fff';
|
||||
li.style.backgroundColor = '#fff';
|
||||
} else {
|
||||
li.style.backgroundColor = '#F4F9FD';
|
||||
}
|
||||
li.style.float = 'left';
|
||||
li.style.height = '21px';
|
||||
li.style.margin = '2px 2px 0 2px !important';
|
||||
li.style.margin = '1px 2px 0 2px';
|
||||
li.style.border = '1px solid #9EA0A1';
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = 'javascript:void(0);';
|
||||
a.onclick = onClickHandler;
|
||||
a.textContent = text;
|
||||
a.style.display = 'block';
|
||||
a.style.color = '#666';
|
||||
a.style.textDecoration = 'none';
|
||||
a.style.padding = '4px';
|
||||
|
||||
a.addEventListener('mouseover', function () {
|
||||
a.style.background = '#fff';
|
||||
});
|
||||
|
||||
a.addEventListener('mouseout', function () {
|
||||
if (!isActive)
|
||||
a.style.background = '#F4F9FD';
|
||||
});
|
||||
|
||||
li.appendChild(a);
|
||||
return li;
|
||||
}
|
||||
|
||||
const li1 = createTab('Poule', isPoule, function () {
|
||||
change_view(true);
|
||||
});
|
||||
ul.appendChild(li1);
|
||||
const li2 = createTab('Tournois', !isPoule, function () {
|
||||
change_view(false);
|
||||
});
|
||||
ul.appendChild(li2);
|
||||
|
||||
menuDiv.appendChild(ul);
|
||||
|
||||
return menuDiv;
|
||||
}
|
||||
|
||||
function buildMatchArray(matchs) {
|
||||
const pouleDiv = document.createElement('div');
|
||||
let arrayContent = `
|
||||
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
|
||||
<table style="width: 800px;overflow: auto"><thead>
|
||||
<tr>
|
||||
<th class="has-text-align-center" data-align="center">Rouge</th>
|
||||
<th class="has-text-align-center" data-align="center"></th>
|
||||
<th class="has-text-align-center" data-align="center">Scores</th>
|
||||
<th class="has-text-align-center" data-align="center"></th>
|
||||
<th class="has-text-align-center" data-align="center">Bleu</th>
|
||||
<th class="has-text-align-center" data-align="center">Date</th>
|
||||
</tr>
|
||||
</thead><tbody>`
|
||||
for (const match of matchs) {
|
||||
arrayContent += `
|
||||
<tr>
|
||||
<td class="has-text-align-right" data-align="right">${match.red}</td>
|
||||
<td class="has-text-align-center" data-align="center">${match.red_w ? cupImg : ""}</td>
|
||||
<td class="has-text-align-center" data-align="center">${scoreToString(match.score)}</td>
|
||||
<td class="has-text-align-center" data-align="center">${match.blue_w ? cupImg : ""}</td>
|
||||
<td class="has-text-align-left" data-align="left">${match.blue}</td>
|
||||
<td class="has-text-align-center" data-align="center">${dateToString((match.red_w || match.blue_w) ? match.date : null)}</td>
|
||||
</tr>`
|
||||
}
|
||||
arrayContent += `</tbody></table></figure>`
|
||||
pouleDiv.innerHTML = arrayContent;
|
||||
return pouleDiv;
|
||||
}
|
||||
|
||||
function buildRankArray(rankArray) {
|
||||
const arrayDiv = document.createElement('div');
|
||||
let arrayContent = `<figure class="wp-block-table is-style-stripes" style="font-size: 16px; margin-top: 2em">
|
||||
<table style="width: 600px;overflow: auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="has-text-align-center" data-align="center">Place</th>
|
||||
<th class="has-text-align-center" data-align="center">Nom</th>
|
||||
<th class="has-text-align-center" data-align="center">Victoire</th>
|
||||
<th class="has-text-align-center" data-align="center">Ratio</th>
|
||||
<th class="has-text-align-center" data-align="center">Points marqués</th>
|
||||
<th class="has-text-align-center" data-align="center">Points reçus</th>
|
||||
</tr>
|
||||
</thead><tbody>`
|
||||
for (const row of rankArray) {
|
||||
arrayContent += `
|
||||
<tr>
|
||||
<td class="has-text-align-center" data-align="center">${row.rank}</td>
|
||||
<td class="has-text-align-left" data-align="left">${row.name}</td>
|
||||
<td class="has-text-align-center" data-align="center">${row.win}</td>
|
||||
<td class="has-text-align-center" data-align="center">${row.pointRate.toFixed(3)}</td>
|
||||
<td class="has-text-align-center" data-align="center">${row.pointMake}</td>
|
||||
<td class="has-text-align-center" data-align="center">${row.pointTake}</td>
|
||||
</tr>`
|
||||
|
||||
}
|
||||
arrayContent += `</tbody></table></figure>`
|
||||
arrayDiv.innerHTML = arrayContent;
|
||||
return arrayDiv;
|
||||
}
|
||||
|
||||
function buildTree(treeData) {
|
||||
return drawGraph(initTree(treeData))
|
||||
}
|
||||
|
||||
function poulePage(location) {
|
||||
rootDiv.innerHTML = header + backButton;
|
||||
const content = document.createElement('div');
|
||||
content.style.marginTop = '1em';
|
||||
content.innerHTML = '<h4>Recherche par poule</h4>';
|
||||
|
||||
const dataContainer = document.createElement('div');
|
||||
dataContainer.id = 'data-container';
|
||||
|
||||
let currentPoule = 0;
|
||||
const loadPoule = () => {
|
||||
const loading = startLoading(content);
|
||||
fetch(`${apiUrlRoot}/poule/data?poule=${currentPoule}&rf=${lastRf}`)
|
||||
.then(response => {
|
||||
if (response.status === 204) // No content => no update
|
||||
return;
|
||||
|
||||
response.json().then(poule => {
|
||||
lastRf = (poule.genTime !== undefined) ? poule.genTime : 0;
|
||||
// console.log(poule);
|
||||
|
||||
if (location.length === 3 && poule.type < 3) {
|
||||
location.pop();
|
||||
window.location.hash = location.join('/');
|
||||
}
|
||||
|
||||
dataContainer.replaceChildren();
|
||||
|
||||
if (poule.type === 1) {
|
||||
for (const g in poule.matchs) {
|
||||
if (Object.keys(poule.matchs).length > 1) {
|
||||
const text = document.createElement('h4');
|
||||
text.textContent = `Groupe ${g}`;
|
||||
text.style.marginTop = '2em';
|
||||
dataContainer.append(text);
|
||||
}
|
||||
dataContainer.append(buildMatchArray(poule.matchs[g]));
|
||||
dataContainer.append(buildRankArray(poule.rankArray[g]));
|
||||
}
|
||||
} else if (poule.type === 2) {
|
||||
dataContainer.append(buildTree(poule['trees']));
|
||||
} else {
|
||||
const change_view = (isPoule) => {
|
||||
dataContainer.replaceChildren(buildPouleMenu(isPoule, change_view));
|
||||
|
||||
if (isPoule) {
|
||||
for (const g in poule.matchs) {
|
||||
if (Object.keys(poule.matchs).length > 1) {
|
||||
const text = document.createElement('h4');
|
||||
text.textContent = `Groupe ${g}`;
|
||||
text.style.marginTop = '2em';
|
||||
dataContainer.append(text);
|
||||
}
|
||||
dataContainer.append(buildMatchArray(poule.matchs[g]));
|
||||
dataContainer.append(buildRankArray(poule.rankArray[g]));
|
||||
}
|
||||
} else {
|
||||
dataContainer.append(buildTree(poule['trees']));
|
||||
}
|
||||
|
||||
location[2] = isPoule ? 1 : 2;
|
||||
window.location.hash = location.join('/');
|
||||
}
|
||||
change_view((location.length === 3) ? location[2] === '1' : true);
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
dataContainer.replaceChildren(new Text("Erreur de chargement de la poule"));
|
||||
})
|
||||
.finally(() => stopLoading(loading));
|
||||
}
|
||||
|
||||
const loading = startLoading(content);
|
||||
fetch(`${apiUrlRoot}/poule/list`)
|
||||
.then(response => response.json())
|
||||
.then(poule => {
|
||||
const select = document.createElement('select');
|
||||
select.setAttribute('id', poule.id);
|
||||
select.innerHTML = `<option value="0">--Sélectionner une poule--</option>`;
|
||||
for (const pouleKey of Object.keys(poule).sort()) {
|
||||
select.innerHTML += `<option value="${poule[pouleKey]}">${pouleKey}</option>`;
|
||||
}
|
||||
select.addEventListener('change', e => {
|
||||
location[1] = Object.keys(poule).find(key => poule[key] === Number(e.target.value));
|
||||
location[1] = encodeURI(location[1]);
|
||||
window.location.hash = location.join('/');
|
||||
lastRf = 0;
|
||||
currentPoule = e.target.value;
|
||||
loadPoule();
|
||||
})
|
||||
content.append(select);
|
||||
content.appendChild(dataContainer);
|
||||
|
||||
if (location.length > 1 && location[1] !== undefined && location[1] !== '') {
|
||||
const tmp = poule[decodeURI(location[1])];
|
||||
select.value = tmp;
|
||||
currentPoule = tmp
|
||||
loadPoule();
|
||||
}
|
||||
})
|
||||
.catch(() => rootDiv.append(new Text("Erreur de chargement des poules")))
|
||||
.finally(() => stopLoading(loading));
|
||||
|
||||
rfFonction = () => {
|
||||
if (currentPoule !== 0)
|
||||
loadPoule();
|
||||
}
|
||||
|
||||
rootDiv.append(content)
|
||||
}
|
||||
|
||||
function buildCombView(comb) {
|
||||
const pouleDiv = document.createElement('div');
|
||||
let arrayContent = `
|
||||
<h3>Info :</h3>
|
||||
<ul>
|
||||
<li>Nom Prénom : ${comb.name}</li>
|
||||
<li>Club : ${comb.club}</li>
|
||||
<li>Catégorie : ${comb.cat}</li>
|
||||
</ul>
|
||||
<h3>Statistique :</h3>
|
||||
<ul>
|
||||
<li>Taux de victoire : ${comb.matchs.length === 0 ? "---" : (comb.totalWin / comb.matchs.length * 100).toFixed(0)}% (${comb.totalWin} sur ${comb.matchs.length})</li>
|
||||
<li>Points marqués : ${comb.pointMake}</li>
|
||||
<li>Points reçus : ${comb.pointTake}</li>
|
||||
<li>Ratio du score (point marqué / point reçu): ${comb.pointRatio.toFixed(3)}</li>
|
||||
</ul>
|
||||
|
||||
<h3>Liste des matchs:</h3>
|
||||
|
||||
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
|
||||
<table style="width: 700px;overflow: auto"><thead>
|
||||
<tr>
|
||||
<th class="has-text-align-center" data-align="center">Date</th>
|
||||
<th class="has-text-align-center" data-align="center">Poule</th>
|
||||
<th class="has-text-align-center" data-align="center">Adversaire</th>
|
||||
<th class="has-text-align-center" data-align="center">Scores</th>
|
||||
<th class="has-text-align-center" data-align="center">Ratio</th>
|
||||
<th class="has-text-align-center" data-align="center"></th>
|
||||
</tr>
|
||||
</thead><tbody>`
|
||||
for (const match of comb.matchs) {
|
||||
arrayContent += `
|
||||
<tr>
|
||||
<td class="has-text-align-center" data-align="center">${dateToString(match.date)}</td>
|
||||
<td class="has-text-align-center" data-align="center">${match.poule}</td>
|
||||
<td class="has-text-align-center" data-align="center">${match.adv}</td>
|
||||
<td class="has-text-align-center" data-align="center">${scoreToString(match.score)}</td>
|
||||
<td class="has-text-align-center" data-align="center">${match.ratio.toFixed(3)}</td>
|
||||
<td class="has-text-align-center" data-align="center">${match.win ? cupImg : ""}</td>
|
||||
</tr>`
|
||||
}
|
||||
arrayContent += `</tbody></table></figure>`
|
||||
pouleDiv.innerHTML = arrayContent;
|
||||
return pouleDiv;
|
||||
}
|
||||
|
||||
function combPage(location) {
|
||||
rootDiv.innerHTML = header + backButton;
|
||||
const content = document.createElement('div');
|
||||
content.style.marginTop = '1em';
|
||||
content.innerHTML = '<h4>Recherche par combattant</h4>';
|
||||
|
||||
const dataContainer = document.createElement('div');
|
||||
dataContainer.id = 'data-container';
|
||||
|
||||
const loadComb = (id) => {
|
||||
const loading = startLoading(content);
|
||||
fetch(`${apiUrlRoot}/comb/data?comb=${id}`)
|
||||
.then(response => response.json())
|
||||
.then(comb => {
|
||||
console.log(comb);
|
||||
dataContainer.replaceChildren(buildCombView(comb));
|
||||
})
|
||||
.catch(() => dataContainer.replaceChildren(new Text("Erreur de chargement du combattant")))
|
||||
.finally(() => stopLoading(loading));
|
||||
}
|
||||
|
||||
const loading = startLoading(content);
|
||||
fetch(`${apiUrlRoot}/comb/list`)
|
||||
.then(response => response.json())
|
||||
.then(combs => {
|
||||
const select = document.createElement('select');
|
||||
select.innerHTML = `<option value="0">--Sélectionner un combattant--</option>`;
|
||||
for (const comb of Object.keys(combs).sort()) {
|
||||
select.innerHTML += `<option value="${combs[comb]}">${comb}</option>`;
|
||||
}
|
||||
select.addEventListener('change', e => {
|
||||
location[1] = Object.keys(combs).find(key => combs[key] === e.target.value);
|
||||
location[1] = encodeURI(location[1]);
|
||||
window.location.hash = location.join('/');
|
||||
loadComb(e.target.value);
|
||||
})
|
||||
content.append(select);
|
||||
content.appendChild(dataContainer);
|
||||
|
||||
if (location.length > 1 && location[1] !== undefined && location[1] !== '') {
|
||||
const tmp = combs[decodeURI(location[1])];
|
||||
select.value = tmp;
|
||||
loadComb(tmp);
|
||||
}
|
||||
})
|
||||
.catch(() => rootDiv.append(new Text("Erreur de chargement des combattants")))
|
||||
.finally(() => stopLoading(loading));
|
||||
|
||||
rootDiv.append(content)
|
||||
}
|
||||
|
||||
function buildClubView(club) {
|
||||
const pouleDiv = document.createElement('div');
|
||||
let arrayContent = `
|
||||
<h3>Info :</h3>
|
||||
<ul>
|
||||
<li>Nom : ${club.name}</li>
|
||||
<li>Nombre d'inscris : ${club.nb_insc}</li>
|
||||
</ul>
|
||||
<h3>Statistique :</h3>
|
||||
<ul>
|
||||
<li>Nombre de match disputé : ${club.nb_match}</li>
|
||||
<li>Nombre de victoires : ${club.match_w}</li>
|
||||
<li>Ratio de victoires moyen : ${club.ratioVictoire.toFixed(3)}</li>
|
||||
<li>Points marqués : ${club.pointMake}</li>
|
||||
<li>Points reçus : ${club.pointTake}</li>
|
||||
<li>Ratio de points moyen : ${club.ratioPoint.toFixed(3)}</li>
|
||||
</ul>
|
||||
|
||||
<h3>Liste des menbres :</h3>
|
||||
|
||||
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
|
||||
<table style="width: 800px;overflow: auto"><thead>
|
||||
<tr>
|
||||
<th class="has-text-align-center" data-align="center">Catégorie</th>
|
||||
<th class="has-text-align-center" data-align="center">Nom</th>
|
||||
<th class="has-text-align-center" data-align="center">Victoires</th>
|
||||
<th class="has-text-align-center" data-align="center">Défaites</th>
|
||||
<th class="has-text-align-center" data-align="center">Ratio victoires</th>
|
||||
<th class="has-text-align-center" data-align="center">Points marqués</th>
|
||||
<th class="has-text-align-center" data-align="center">Points reçus</th>
|
||||
<th class="has-text-align-center" data-align="center">Ratio points</th>
|
||||
</tr>
|
||||
</thead><tbody>`
|
||||
for (const comb of club.combs) {
|
||||
arrayContent += `
|
||||
<tr>
|
||||
<td class="has-text-align-center" data-align="center">${comb.cat}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.name}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.w}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.l}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.ratioVictoire.toFixed(3)}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.pointMake}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.pointTake}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.ratioPoint.toFixed(3)}</td>
|
||||
</tr>`
|
||||
}
|
||||
arrayContent += `</tbody></table></figure>`
|
||||
pouleDiv.innerHTML = arrayContent;
|
||||
return pouleDiv;
|
||||
}
|
||||
|
||||
function clubPage(location) {
|
||||
rootDiv.innerHTML = header + backButton;
|
||||
const content = document.createElement('div');
|
||||
content.style.marginTop = '1em';
|
||||
content.innerHTML = '<h4>Recherche par club</h4>';
|
||||
|
||||
const dataContainer = document.createElement('div');
|
||||
dataContainer.id = 'data-container';
|
||||
|
||||
const loadComb = (id) => {
|
||||
const loading = startLoading(content);
|
||||
fetch(`${apiUrlRoot}/club/data?club=${id}`)
|
||||
.then(response => response.json())
|
||||
.then(club => {
|
||||
console.log(club);
|
||||
dataContainer.replaceChildren(buildClubView(club));
|
||||
})
|
||||
.catch(() => dataContainer.replaceChildren(new Text("Erreur de chargement du club")))
|
||||
.finally(() => stopLoading(loading));
|
||||
}
|
||||
|
||||
const loading = startLoading(content);
|
||||
fetch(`${apiUrlRoot}/club/list`)
|
||||
.then(response => response.json())
|
||||
.then(clubs => {
|
||||
const select = document.createElement('select');
|
||||
select.innerHTML = `<option value="0">--Sélectionner un club--</option>`;
|
||||
for (const club of Object.keys(clubs).sort()) {
|
||||
select.innerHTML += `<option value="${clubs[club]}">${club}</option>`;
|
||||
}
|
||||
select.addEventListener('change', e => {
|
||||
if (e.target.value === '0')
|
||||
return;
|
||||
location[1] = Object.keys(clubs).find(key => clubs[key] === Number(e.target.value));
|
||||
location[1] = encodeURI(location[1]);
|
||||
window.location.hash = location.join('/');
|
||||
loadComb(e.target.value);
|
||||
})
|
||||
content.append(select);
|
||||
content.appendChild(dataContainer);
|
||||
|
||||
if (location.length > 1 && location[1] !== undefined && location[1] !== '') {
|
||||
const tmp = clubs[decodeURI(location[1])];
|
||||
select.value = tmp;
|
||||
loadComb(tmp);
|
||||
}
|
||||
})
|
||||
.catch(() => rootDiv.append(new Text("Erreur de chargement des clubs")))
|
||||
.finally(() => stopLoading(loading));
|
||||
|
||||
rootDiv.append(content)
|
||||
}
|
||||
|
||||
function buildCombsView(combs) {
|
||||
const pouleDiv = document.createElement('div');
|
||||
let arrayContent = `
|
||||
<h3>Statistique :</h3>
|
||||
<ul>
|
||||
<li>Nombre d'inscris : ${combs.nb_insc}</li>
|
||||
<li>Nombre de match disputé : ${combs.tt_match}</li>
|
||||
<li>Points marqués : ${combs.point}</li>
|
||||
</ul>
|
||||
|
||||
<h3>Liste des combattants :</h3>
|
||||
|
||||
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
|
||||
<table style="width: 1200px;overflow: auto"><thead>
|
||||
<tr>
|
||||
<th class="has-text-align-center" data-align="center">Catégorie</th>
|
||||
<th class="has-text-align-center" data-align="center">Club</th>
|
||||
<th class="has-text-align-center" data-align="center">Nom</th>
|
||||
<th class="has-text-align-center" data-align="center">Victoires</th>
|
||||
<th class="has-text-align-center" data-align="center">Défaites</th>
|
||||
<th class="has-text-align-center" data-align="center">Ratio victoires</th>
|
||||
<th class="has-text-align-center" data-align="center">Points marqués</th>
|
||||
<th class="has-text-align-center" data-align="center">Points reçus</th>
|
||||
<th class="has-text-align-center" data-align="center">Ratios points</th>
|
||||
</tr>
|
||||
</thead><tbody>`
|
||||
for (const comb of combs.combs) {
|
||||
arrayContent += `
|
||||
<tr>
|
||||
<td class="has-text-align-center" data-align="center">${comb.cat}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.club}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.name}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.w}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.l}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.ratioVictoire.toFixed(3)}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.pointMake}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.pointTake}</td>
|
||||
<td class="has-text-align-center" data-align="center">${comb.ratioPoint.toFixed(3)}</td>
|
||||
</tr>`
|
||||
}
|
||||
arrayContent += `</tbody></table></figure>`
|
||||
pouleDiv.innerHTML = arrayContent;
|
||||
return pouleDiv;
|
||||
}
|
||||
|
||||
function combsPage() {
|
||||
rootDiv.innerHTML = header + backButton;
|
||||
const content = document.createElement('div');
|
||||
content.style.marginTop = '1em';
|
||||
|
||||
const dataContainer = document.createElement('div');
|
||||
dataContainer.id = 'data-container';
|
||||
|
||||
const loading = startLoading(content);
|
||||
fetch(`${apiUrlRoot}/comb/get_all`)
|
||||
.then(response => response.json())
|
||||
.then(combs => {
|
||||
console.log(combs);
|
||||
dataContainer.replaceChildren(buildCombsView(combs));
|
||||
})
|
||||
.catch(() => dataContainer.replaceChildren(new Text("Erreur de chargement de la liste")))
|
||||
.finally(() => stopLoading(loading));
|
||||
|
||||
content.append(dataContainer);
|
||||
rootDiv.append(content)
|
||||
}
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
let path = document.getElementById('safca_api_script').src;
|
||||
const urlParams = new URLSearchParams(new URL(path).search);
|
||||
apiUrlRoot = path.substring(0, path.lastIndexOf('/')) + "/api/public/result/" + urlParams.get("id");
|
||||
|
||||
console.log("apiUrlRoot:", apiUrlRoot)
|
||||
|
||||
let hash = window.location.hash.substring(1);
|
||||
if (hash.length === 0)
|
||||
setSubPage('home');
|
||||
else
|
||||
setSubPage(hash);
|
||||
});
|
||||
|
||||
class TreeNode {
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
this.left = null;
|
||||
this.right = null;
|
||||
}
|
||||
|
||||
death() {
|
||||
let dg = 0;
|
||||
let dd = 0;
|
||||
|
||||
if (this.right != null)
|
||||
dg = this.right.death();
|
||||
|
||||
if (this.left != null)
|
||||
dg = this.left.death();
|
||||
|
||||
return 1 + Math.max(dg, dd);
|
||||
}
|
||||
}
|
||||
|
||||
function initTree(data_in) {
|
||||
out = [];
|
||||
for (const din of data_in) {
|
||||
out.push(parseTree(din));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseTree(data_in) {
|
||||
if (data_in?.data == null)
|
||||
return null;
|
||||
|
||||
let node = new TreeNode(data_in.data);
|
||||
node.left = parseTree(data_in?.left);
|
||||
node.right = parseTree(data_in?.right);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
const max_x = 500;
|
||||
const size = 24;
|
||||
|
||||
function getBounds(root) {
|
||||
let px = max_x;
|
||||
let py;
|
||||
let maxx, minx, miny, maxy
|
||||
|
||||
function drawNode(tree, px, py) {
|
||||
let death = tree.death() - 1
|
||||
|
||||
if (death === 0) {
|
||||
if (miny > py - size - ((size * 1.5 / 2) | 0)) miny = py - size - (size * 1.5 / 2) | 0;
|
||||
if (maxy < py + size + ((size * 1.5 / 2) | 0)) maxy = py + size + (size * 1.5 / 2) | 0;
|
||||
} else {
|
||||
if (miny > py - size * 2 * death - ((size * 1.5 / 2) | 0))
|
||||
miny = py - size * 2 * death - ((size * 1.5 / 2) | 0);
|
||||
if (maxy < py + size * 2 * death + ((size * 1.5 / 2) | 0))
|
||||
maxy = py + size * 2 * death + ((size * 1.5 / 2) | 0);
|
||||
}
|
||||
if (minx > px - size * 2 - size * 8) minx = px - size * 2 - size * 8;
|
||||
|
||||
if (tree.left != null) drawNode(tree.left, px - size * 2 - size * 8, py - size * 2 * death);
|
||||
if (tree.right != null) drawNode(tree.right, px - size * 2 - size * 8, py + size * 2 * death);
|
||||
}
|
||||
|
||||
if (root != null) {
|
||||
py = (size * 2 * root.at(0).death() + (((size * 1.5 / 2) | 0) + size) * root.at(0).death()) * 2;
|
||||
|
||||
maxx = px;
|
||||
minx = px;
|
||||
miny = py - (size * 1.5 / 2) | 0;
|
||||
maxy = py + (size * 1.5 / 2) | 0;
|
||||
|
||||
for (const node of root) {
|
||||
px = px - size * 2 - size * 8;
|
||||
if (minx > px) minx = px;
|
||||
|
||||
drawNode(node, px, py);
|
||||
//graphics2D.drawRect(minx, miny, maxx - minx, maxy - miny);
|
||||
py = maxy + ((size * 2 * node.death() + ((size * 1.5 / 2) | 0)));
|
||||
px = maxx;
|
||||
}
|
||||
} else {
|
||||
minx = 0;
|
||||
maxx = 0;
|
||||
miny = 0;
|
||||
maxy = 0;
|
||||
}
|
||||
|
||||
return [minx, maxx, miny, maxy];
|
||||
}
|
||||
|
||||
function drawGraph(root = []) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.id = "myCanvas";
|
||||
canvas.style.border = "1px solid grey";
|
||||
canvas.style.marginTop = "10px";
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
const [minx, maxx, miny, maxy] = getBounds(root);
|
||||
canvas.width = maxx - minx;
|
||||
canvas.height = maxy - miny;
|
||||
ctx.translate(-minx, -miny);
|
||||
|
||||
|
||||
ctx.fillStyle = "#000000"
|
||||
ctx.lineWidth = 2
|
||||
ctx.strokeStyle = "#000000"
|
||||
|
||||
function printText(s, x, y, width, height, lineG, lineD) {
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
|
||||
let tSize = 17;
|
||||
let ratioX = height * 1. / 20.;
|
||||
|
||||
ctx.font = "100 " + tSize + "px Arial";
|
||||
|
||||
let mw = width - (ratioX * 2) | 0;
|
||||
if (ctx.measureText(s).width > mw) {
|
||||
let dTextSize = true;
|
||||
do {
|
||||
tSize--;
|
||||
ctx.font = tSize + "px Arial";
|
||||
} while (ctx.measureText(s).width > mw && tSize > 10)
|
||||
|
||||
if (!dTextSize || ctx.measureText(s).width > mw) {
|
||||
let s = "";
|
||||
for (const string2 in s.split(" ")) {
|
||||
if (ctx.measureText(s + string2).width >= mw) {
|
||||
s += "...";
|
||||
break;
|
||||
} else {
|
||||
s += string2 + " "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const text = ctx.measureText(s)
|
||||
let dx = (width - text.width) / 2;
|
||||
let dy = ((height - text.actualBoundingBoxDescent) / 2) + (text.actualBoundingBoxAscent / 2);
|
||||
|
||||
ctx.fillText(s, dx, dy, width - dy);
|
||||
ctx.restore();
|
||||
|
||||
ctx.beginPath();
|
||||
if (lineD) {
|
||||
ctx.moveTo((ratioX * 2.5 + x + dx + text.width) | 0, y + height / 2);
|
||||
ctx.lineTo(x + width, y + height / 2)
|
||||
}
|
||||
if (lineG) {
|
||||
ctx.moveTo(x, y + height / 2);
|
||||
ctx.lineTo((dx + x - ratioX * 2.5) | 0, y + height / 2)
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function printScores(scores, px, py, scale) {
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(px - size * 2, py - size * scale);
|
||||
ctx.font = "100 " + 14 + "px Arial";
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
for (let i = 0; i < scores.length; i++) {
|
||||
const score = scores[i].s1+"-"+scores[i].s2;
|
||||
const div = (scores.length <= 2) ? 2 : (scores.length >= 4) ? 4 : 3;
|
||||
const text = ctx.measureText(score);
|
||||
let dx = (size * 2 - text.width) / 2;
|
||||
let dy = ((size * 2 / div - text.actualBoundingBoxDescent) / 2) + (text.actualBoundingBoxAscent / 2);
|
||||
|
||||
ctx.fillStyle = '#ffffffdd';
|
||||
ctx.fillRect(dx, size * 2 * scale / div * (i) + dy, text.width, 14);
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.fillText(score, dx, size * 2 * scale / div * (i) + dy, size * 2);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawNode(tree, px, py) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(px, py)
|
||||
ctx.lineTo(px - size, py)
|
||||
ctx.stroke();
|
||||
|
||||
let death = tree.death() - 1
|
||||
let match = tree.data
|
||||
|
||||
if (death === 0) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(px - size, py + size)
|
||||
ctx.lineTo(px - size, py - size)
|
||||
|
||||
ctx.moveTo(px - size, py + size)
|
||||
ctx.lineTo(px - size * 2, py + size)
|
||||
ctx.moveTo(px - size, py - size)
|
||||
ctx.lineTo(px - size * 2, py - size)
|
||||
ctx.stroke();
|
||||
|
||||
printScores(match.scores, px, py, 1);
|
||||
|
||||
ctx.fillStyle = "#FF0000";
|
||||
printText((match.c1FullName == null) ? "" : match.c1FullName, px - size * 2 - size * 8, py - size - (size * 1.5 / 2) | 0,
|
||||
size * 8, (size * 1.5) | 0, false, true)
|
||||
|
||||
ctx.fillStyle = "#0000FF";
|
||||
printText((match.c2FullName == null) ? "" : match.c2FullName, px - size * 2 - size * 8, py + size - (size * 1.5 / 2) | 0,
|
||||
size * 8, (size * 1.5) | 0, false, true)
|
||||
|
||||
if (max_y < py + size + ((size * 1.5 / 2) | 0)) max_y = py + size + (size * 1.5 / 2) | 0;
|
||||
} else {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(px - size, py)
|
||||
ctx.lineTo(px - size, py + size * 2 * death)
|
||||
ctx.moveTo(px - size, py)
|
||||
ctx.lineTo(px - size, py - size * 2 * death)
|
||||
|
||||
ctx.moveTo(px - size, py + size * 2 * death)
|
||||
ctx.lineTo(px - size * 2, py + size * 2 * death)
|
||||
ctx.moveTo(px - size, py - size * 2 * death)
|
||||
ctx.lineTo(px - size * 2, py - size * 2 * death)
|
||||
ctx.stroke();
|
||||
|
||||
printScores(match.scores, px, py, 1.5);
|
||||
|
||||
ctx.fillStyle = "#FF0000";
|
||||
printText((match.c1FullName == null) ? "" : match.c1FullName, px - size * 2 - size * 8, py - size * 2 * death - (size * 1.5 / 2) | 0,
|
||||
size * 8, (size * 1.5) | 0, true, true)
|
||||
|
||||
ctx.fillStyle = "#0000FF";
|
||||
printText((match.c2FullName == null) ? "" : match.c2FullName, px - size * 2 - size * 8, py + size * 2 * death - (size * 1.5 / 2) | 0,
|
||||
size * 8, (size * 1.5) | 0, true, true)
|
||||
|
||||
if (max_y < py + size * 2 * death + ((size * 1.5 / 2) | 0))
|
||||
max_y = py + size * 2 * death + ((size * 1.5 / 2) | 0);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
if (tree.left != null) drawNode(tree.left, px - size * 2 - size * 8, py - size * 2 * death);
|
||||
if (tree.right != null) drawNode(tree.right, px - size * 2 - size * 8, py + size * 2 * death);
|
||||
}
|
||||
|
||||
function win(scores) {
|
||||
let sum = 0;
|
||||
for (const score of scores) {
|
||||
if (score.s1 === -1000 || score.s2 === -1000)
|
||||
continue;
|
||||
|
||||
if (score.s1 > score.s2)
|
||||
sum++;
|
||||
else if (score.s1 < score.s2)
|
||||
sum--;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
let px = max_x;
|
||||
let py;
|
||||
let max_y
|
||||
|
||||
if (root != null) {
|
||||
py = (size * 2 * root.at(0).death() + (((size * 1.5 / 2) | 0) + size) * root.at(0).death()) * 2;
|
||||
|
||||
max_y = py + (size * 1.5 / 2) | 0;
|
||||
|
||||
for (const node of root) {
|
||||
let win_name = "";
|
||||
if (node.data.end) {
|
||||
if (win(node.data.scores) > 0)
|
||||
win_name = (node.data.c1FullName === null) ? "???" : node.data.c1FullName;
|
||||
else
|
||||
win_name = (node.data.c2FullName === null) ? "???" : node.data.c2FullName;
|
||||
}
|
||||
|
||||
|
||||
ctx.fillStyle = "#18A918";
|
||||
printText(win_name, px - size * 2 - size * 8, py - ((size * 1.5 / 2) | 0),
|
||||
size * 8, (size * 1.5) | 0, true, false);
|
||||
|
||||
px = px - size * 2 - size * 8;
|
||||
|
||||
drawNode(node, px, py);
|
||||
|
||||
py = max_y + ((size * 2 * node.death() + ((size * 1.5 / 2) | 0)));
|
||||
px = max_x;
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
@ -1,828 +0,0 @@
|
||||
{
|
||||
"current_scene": "Ovarlay",
|
||||
"current_program_scene": "Ovarlay",
|
||||
"scene_order": [
|
||||
{
|
||||
"name": "Ovarlay"
|
||||
},
|
||||
{
|
||||
"name": "Scène 2"
|
||||
}
|
||||
],
|
||||
"name": "saf",
|
||||
"groups": [],
|
||||
"quick_transitions": [
|
||||
{
|
||||
"name": "Coupure",
|
||||
"duration": 300,
|
||||
"hotkeys": [],
|
||||
"id": 5,
|
||||
"fade_to_black": false
|
||||
},
|
||||
{
|
||||
"name": "Fondu",
|
||||
"duration": 300,
|
||||
"hotkeys": [],
|
||||
"id": 6,
|
||||
"fade_to_black": false
|
||||
}
|
||||
],
|
||||
"transitions": [],
|
||||
"saved_projectors": [],
|
||||
"canvases": [],
|
||||
"current_transition": "Fondu",
|
||||
"transition_duration": 300,
|
||||
"preview_locked": false,
|
||||
"scaling_enabled": false,
|
||||
"scaling_level": -9,
|
||||
"scaling_off_x": 0.0,
|
||||
"scaling_off_y": 0.0,
|
||||
"virtual-camera": {
|
||||
"type2": 3
|
||||
},
|
||||
"modules": {
|
||||
"auto-scene-switcher": {
|
||||
"interval": 300,
|
||||
"non_matching_scene": "",
|
||||
"switch_if_not_matching": false,
|
||||
"active": false,
|
||||
"switches": []
|
||||
},
|
||||
"captions": {
|
||||
"source": "",
|
||||
"enabled": false,
|
||||
"lang_id": 1036,
|
||||
"provider": "mssapi"
|
||||
},
|
||||
"output-timer": {
|
||||
"streamTimerHours": 0,
|
||||
"streamTimerMinutes": 0,
|
||||
"streamTimerSeconds": 30,
|
||||
"recordTimerHours": 0,
|
||||
"recordTimerMinutes": 0,
|
||||
"recordTimerSeconds": 30,
|
||||
"autoStartStreamTimer": false,
|
||||
"autoStartRecordTimer": false,
|
||||
"pauseRecordTimer": false
|
||||
},
|
||||
"scripts-tool": []
|
||||
},
|
||||
"version": 1,
|
||||
"sources": [
|
||||
{
|
||||
"prev_ver": 536870916,
|
||||
"name": "Capture d'écran",
|
||||
"uuid": "62b56523-7c2e-4ae2-b4e6-9ca3a36e904b",
|
||||
"id": "monitor_capture",
|
||||
"versioned_id": "monitor_capture",
|
||||
"settings": {
|
||||
"monitor_id": "\\\\?\\DISPLAY#HWP3320#5&3974db33&0&UID143619#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"
|
||||
},
|
||||
"mixers": 0,
|
||||
"sync": 0,
|
||||
"flags": 0,
|
||||
"volume": 1.0,
|
||||
"balance": 0.5,
|
||||
"enabled": true,
|
||||
"muted": false,
|
||||
"push-to-mute": false,
|
||||
"push-to-mute-delay": 0,
|
||||
"push-to-talk": false,
|
||||
"push-to-talk-delay": 0,
|
||||
"hotkeys": {},
|
||||
"deinterlace_mode": 0,
|
||||
"deinterlace_field_order": 0,
|
||||
"monitoring_type": 0,
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"prev_ver": 536870916,
|
||||
"name": "Ovarlay",
|
||||
"uuid": "670a1c48-dac6-405a-959a-d40662bab4d8",
|
||||
"id": "scene",
|
||||
"versioned_id": "scene",
|
||||
"settings": {
|
||||
"custom_size": false,
|
||||
"id_counter": 21,
|
||||
"items": [
|
||||
{
|
||||
"name": "sub1.img.blue",
|
||||
"source_uuid": "0a37157d-f356-4657-a2ad-fa4193a298f8",
|
||||
"visible": true,
|
||||
"locked": false,
|
||||
"rot": 0.0,
|
||||
"align": 5,
|
||||
"bounds_type": 0,
|
||||
"bounds_align": 0,
|
||||
"bounds_crop": false,
|
||||
"crop_left": 0,
|
||||
"crop_top": 0,
|
||||
"crop_right": 0,
|
||||
"crop_bottom": 0,
|
||||
"id": 19,
|
||||
"group_item_backup": false,
|
||||
"pos": {
|
||||
"x": 1789.0,
|
||||
"y": 950.0
|
||||
},
|
||||
"scale": {
|
||||
"x": 0.11296296119689941,
|
||||
"y": 0.11296296119689941
|
||||
},
|
||||
"bounds": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"scale_filter": "disable",
|
||||
"blend_method": "default",
|
||||
"blend_type": "normal",
|
||||
"show_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"hide_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"name": "sub1.img.rouge",
|
||||
"source_uuid": "2dfe69bc-9b10-419d-8ee1-6cd9cc53a502",
|
||||
"visible": true,
|
||||
"locked": false,
|
||||
"rot": 0.0,
|
||||
"align": 5,
|
||||
"bounds_type": 0,
|
||||
"bounds_align": 0,
|
||||
"bounds_crop": false,
|
||||
"crop_left": 0,
|
||||
"crop_top": 0,
|
||||
"crop_right": 0,
|
||||
"crop_bottom": 0,
|
||||
"id": 14,
|
||||
"group_item_backup": false,
|
||||
"pos": {
|
||||
"x": 9.0,
|
||||
"y": 950.0
|
||||
},
|
||||
"scale": {
|
||||
"x": 0.11296296119689941,
|
||||
"y": 0.11296296119689941
|
||||
},
|
||||
"bounds": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"scale_filter": "disable",
|
||||
"blend_method": "default",
|
||||
"blend_type": "normal",
|
||||
"show_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"hide_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"name": "sub1.comb.blue",
|
||||
"source_uuid": "4e75e437-20ed-4834-bfac-b668f5cac960",
|
||||
"visible": true,
|
||||
"locked": true,
|
||||
"rot": 0.0,
|
||||
"align": 5,
|
||||
"bounds_type": 0,
|
||||
"bounds_align": 0,
|
||||
"bounds_crop": false,
|
||||
"crop_left": 0,
|
||||
"crop_top": 0,
|
||||
"crop_right": 0,
|
||||
"crop_bottom": 0,
|
||||
"id": 17,
|
||||
"group_item_backup": false,
|
||||
"pos": {
|
||||
"x": 960.0,
|
||||
"y": 945.0
|
||||
},
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"bounds": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"scale_filter": "disable",
|
||||
"blend_method": "default",
|
||||
"blend_type": "normal",
|
||||
"show_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"hide_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"name": "sub1.comb.rouge",
|
||||
"source_uuid": "69594be1-dfdc-4eee-893c-fb546e830a6f",
|
||||
"visible": true,
|
||||
"locked": true,
|
||||
"rot": 0.0,
|
||||
"align": 5,
|
||||
"bounds_type": 0,
|
||||
"bounds_align": 0,
|
||||
"bounds_crop": false,
|
||||
"crop_left": 0,
|
||||
"crop_top": 0,
|
||||
"crop_right": 0,
|
||||
"crop_bottom": 0,
|
||||
"id": 8,
|
||||
"group_item_backup": false,
|
||||
"pos": {
|
||||
"x": 140.0,
|
||||
"y": 945.0
|
||||
},
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"bounds": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"scale_filter": "disable",
|
||||
"blend_method": "default",
|
||||
"blend_type": "normal",
|
||||
"show_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"hide_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"name": "sub1.score.blue",
|
||||
"source_uuid": "264866ab-58e1-45ec-a32f-c440c32057cd",
|
||||
"visible": true,
|
||||
"locked": true,
|
||||
"rot": 0.0,
|
||||
"align": 5,
|
||||
"bounds_type": 0,
|
||||
"bounds_align": 0,
|
||||
"bounds_crop": false,
|
||||
"crop_left": 0,
|
||||
"crop_top": 0,
|
||||
"crop_right": 0,
|
||||
"crop_bottom": 0,
|
||||
"id": 20,
|
||||
"group_item_backup": false,
|
||||
"pos": {
|
||||
"x": 1108.0,
|
||||
"y": 10.0
|
||||
},
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"bounds": {
|
||||
"x": 0.0,
|
||||
"y": 0.0
|
||||
},
|
||||
"scale_filter": "disable",
|
||||
"blend_method": "default",
|
||||
"blend_type": "normal",
|
||||
"show_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"hide_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"name": "sub1.score.rouge",
|
||||
"source_uuid": "8a532b22-7bcd-4742-aa0e-8ea556f39a12",
|
||||
"visible": true,
|
||||
"locked": true,
|
||||
"rot": 0.0,
|
||||
"align": 5,
|
||||
"bounds_type": 0,
|
||||
"bounds_align": 0,
|
||||
"bounds_crop": false,
|
||||
"crop_left": 0,
|
||||
"crop_top": 0,
|
||||
"crop_right": 0,
|
||||
"crop_bottom": 0,
|
||||
"id": 16,
|
||||
"group_item_backup": false,
|
||||
"pos": {
|
||||
"x": 686.0,
|
||||
"y": 10.0
|
||||
},
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"bounds": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"scale_filter": "disable",
|
||||
"blend_method": "default",
|
||||
"blend_type": "normal",
|
||||
"show_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"hide_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"name": "sub1.temps",
|
||||
"source_uuid": "a961104f-2cad-4f65-9ff8-d50ff36f8418",
|
||||
"visible": true,
|
||||
"locked": true,
|
||||
"rot": 0.0,
|
||||
"align": 5,
|
||||
"bounds_type": 0,
|
||||
"bounds_align": 0,
|
||||
"bounds_crop": false,
|
||||
"crop_left": 0,
|
||||
"crop_top": 0,
|
||||
"crop_right": 0,
|
||||
"crop_bottom": 0,
|
||||
"id": 21,
|
||||
"group_item_backup": false,
|
||||
"pos": {
|
||||
"x": 584.0,
|
||||
"y": 0.0
|
||||
},
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"bounds": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"scale_filter": "disable",
|
||||
"blend_method": "default",
|
||||
"blend_type": "normal",
|
||||
"show_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"hide_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"private_settings": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"mixers": 0,
|
||||
"sync": 0,
|
||||
"flags": 0,
|
||||
"volume": 1.0,
|
||||
"balance": 0.5,
|
||||
"enabled": true,
|
||||
"muted": false,
|
||||
"push-to-mute": false,
|
||||
"push-to-mute-delay": 0,
|
||||
"push-to-talk": false,
|
||||
"push-to-talk-delay": 0,
|
||||
"hotkeys": {
|
||||
"OBSBasic.SelectScene": [],
|
||||
"libobs.show_scene_item.19": [],
|
||||
"libobs.hide_scene_item.19": [],
|
||||
"libobs.show_scene_item.14": [],
|
||||
"libobs.hide_scene_item.14": [],
|
||||
"libobs.show_scene_item.17": [],
|
||||
"libobs.hide_scene_item.17": [],
|
||||
"libobs.show_scene_item.8": [],
|
||||
"libobs.hide_scene_item.8": [],
|
||||
"libobs.show_scene_item.20": [],
|
||||
"libobs.hide_scene_item.20": [],
|
||||
"libobs.show_scene_item.16": [],
|
||||
"libobs.hide_scene_item.16": [],
|
||||
"libobs.show_scene_item.21": [],
|
||||
"libobs.hide_scene_item.21": []
|
||||
},
|
||||
"deinterlace_mode": 0,
|
||||
"deinterlace_field_order": 0,
|
||||
"monitoring_type": 0,
|
||||
"canvas_uuid": "6c69626f-6273-4c00-9d88-c5136d61696e",
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"prev_ver": 536870916,
|
||||
"name": "Scène 2",
|
||||
"uuid": "d430fe13-0e4a-4d4b-a28e-7c833eb27bf6",
|
||||
"id": "scene",
|
||||
"versioned_id": "scene",
|
||||
"settings": {
|
||||
"custom_size": false,
|
||||
"id_counter": 2,
|
||||
"items": [
|
||||
{
|
||||
"name": "Capture d'écran",
|
||||
"source_uuid": "62b56523-7c2e-4ae2-b4e6-9ca3a36e904b",
|
||||
"visible": false,
|
||||
"locked": false,
|
||||
"rot": 0.0,
|
||||
"align": 5,
|
||||
"bounds_type": 0,
|
||||
"bounds_align": 0,
|
||||
"bounds_crop": false,
|
||||
"crop_left": 0,
|
||||
"crop_top": 0,
|
||||
"crop_right": 0,
|
||||
"crop_bottom": 0,
|
||||
"id": 2,
|
||||
"group_item_backup": false,
|
||||
"pos": {
|
||||
"x": 0.0,
|
||||
"y": 0.0
|
||||
},
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"bounds": {
|
||||
"x": 0.0,
|
||||
"y": 0.0
|
||||
},
|
||||
"scale_filter": "disable",
|
||||
"blend_method": "default",
|
||||
"blend_type": "normal",
|
||||
"show_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"hide_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"name": "Ovarlay",
|
||||
"source_uuid": "670a1c48-dac6-405a-959a-d40662bab4d8",
|
||||
"visible": true,
|
||||
"locked": false,
|
||||
"rot": 0.0,
|
||||
"align": 5,
|
||||
"bounds_type": 0,
|
||||
"bounds_align": 0,
|
||||
"bounds_crop": false,
|
||||
"crop_left": 0,
|
||||
"crop_top": 0,
|
||||
"crop_right": 0,
|
||||
"crop_bottom": 0,
|
||||
"id": 1,
|
||||
"group_item_backup": false,
|
||||
"pos": {
|
||||
"x": 0.0,
|
||||
"y": 0.0
|
||||
},
|
||||
"scale": {
|
||||
"x": 1.0,
|
||||
"y": 1.0
|
||||
},
|
||||
"bounds": {
|
||||
"x": 0.0,
|
||||
"y": 0.0
|
||||
},
|
||||
"scale_filter": "disable",
|
||||
"blend_method": "default",
|
||||
"blend_type": "normal",
|
||||
"show_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"hide_transition": {
|
||||
"duration": 0
|
||||
},
|
||||
"private_settings": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"mixers": 0,
|
||||
"sync": 0,
|
||||
"flags": 0,
|
||||
"volume": 1.0,
|
||||
"balance": 0.5,
|
||||
"enabled": true,
|
||||
"muted": false,
|
||||
"push-to-mute": false,
|
||||
"push-to-mute-delay": 0,
|
||||
"push-to-talk": false,
|
||||
"push-to-talk-delay": 0,
|
||||
"hotkeys": {
|
||||
"OBSBasic.SelectScene": [],
|
||||
"libobs.show_scene_item.2": [],
|
||||
"libobs.hide_scene_item.2": [],
|
||||
"libobs.show_scene_item.1": [],
|
||||
"libobs.hide_scene_item.1": []
|
||||
},
|
||||
"deinterlace_mode": 0,
|
||||
"deinterlace_field_order": 0,
|
||||
"monitoring_type": 0,
|
||||
"canvas_uuid": "6c69626f-6273-4c00-9d88-c5136d61696e",
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"prev_ver": 536870916,
|
||||
"name": "sub1.comb.blue",
|
||||
"uuid": "4e75e437-20ed-4834-bfac-b668f5cac960",
|
||||
"id": "text_gdiplus",
|
||||
"versioned_id": "text_gdiplus_v2",
|
||||
"settings": {
|
||||
"chatlog": false,
|
||||
"extents": true,
|
||||
"gradient": false,
|
||||
"outline": false,
|
||||
"undo_sname": "sub1.comb_rouge",
|
||||
"vertical": false,
|
||||
"font": {
|
||||
"face": "Arial",
|
||||
"flags": 0,
|
||||
"size": 65,
|
||||
"style": "Normal"
|
||||
},
|
||||
"align": "right",
|
||||
"valign": "bottom",
|
||||
"color": 4294917120,
|
||||
"extents_cx": 835,
|
||||
"extents_cy": 120,
|
||||
"antialiasing": true,
|
||||
"overlay": true,
|
||||
"text": "Xavier Login"
|
||||
},
|
||||
"mixers": 0,
|
||||
"sync": 0,
|
||||
"flags": 0,
|
||||
"volume": 1.0,
|
||||
"balance": 0.5,
|
||||
"enabled": true,
|
||||
"muted": false,
|
||||
"push-to-mute": false,
|
||||
"push-to-mute-delay": 0,
|
||||
"push-to-talk": false,
|
||||
"push-to-talk-delay": 0,
|
||||
"hotkeys": {},
|
||||
"deinterlace_mode": 0,
|
||||
"deinterlace_field_order": 0,
|
||||
"monitoring_type": 0,
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"prev_ver": 536870916,
|
||||
"name": "sub1.comb.rouge",
|
||||
"uuid": "69594be1-dfdc-4eee-893c-fb546e830a6f",
|
||||
"id": "text_gdiplus",
|
||||
"versioned_id": "text_gdiplus_v2",
|
||||
"settings": {
|
||||
"chatlog": false,
|
||||
"extents": true,
|
||||
"gradient": false,
|
||||
"outline": false,
|
||||
"undo_sname": "sub1.comb_rouge",
|
||||
"vertical": false,
|
||||
"font": {
|
||||
"face": "Arial",
|
||||
"flags": 0,
|
||||
"size": 65,
|
||||
"style": "Normal"
|
||||
},
|
||||
"align": "left",
|
||||
"valign": "bottom",
|
||||
"color": 4278190335,
|
||||
"extents_cx": 820,
|
||||
"extents_cy": 120,
|
||||
"antialiasing": true,
|
||||
"overlay": true,
|
||||
"text": "Xavier Login"
|
||||
},
|
||||
"mixers": 0,
|
||||
"sync": 0,
|
||||
"flags": 0,
|
||||
"volume": 1.0,
|
||||
"balance": 0.5,
|
||||
"enabled": true,
|
||||
"muted": false,
|
||||
"push-to-mute": false,
|
||||
"push-to-mute-delay": 0,
|
||||
"push-to-talk": false,
|
||||
"push-to-talk-delay": 0,
|
||||
"hotkeys": {},
|
||||
"deinterlace_mode": 0,
|
||||
"deinterlace_field_order": 0,
|
||||
"monitoring_type": 0,
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"prev_ver": 536870916,
|
||||
"name": "sub1.img.blue",
|
||||
"uuid": "0a37157d-f356-4657-a2ad-fa4193a298f8",
|
||||
"id": "slideshow",
|
||||
"versioned_id": "slideshow",
|
||||
"settings": {
|
||||
"files": [],
|
||||
"transition": "slide",
|
||||
"slide_time": 9000,
|
||||
"transition_speed": 1300,
|
||||
"use_custom_size": "1:1",
|
||||
"playback_behavior": "stop_restart",
|
||||
"slide_mode": "mode_auto",
|
||||
"overlay": true
|
||||
},
|
||||
"mixers": 0,
|
||||
"sync": 0,
|
||||
"flags": 0,
|
||||
"volume": 1.0,
|
||||
"balance": 0.5,
|
||||
"enabled": true,
|
||||
"muted": false,
|
||||
"push-to-mute": false,
|
||||
"push-to-mute-delay": 0,
|
||||
"push-to-talk": false,
|
||||
"push-to-talk-delay": 0,
|
||||
"hotkeys": {
|
||||
"SlideShow.PlayPause": [],
|
||||
"SlideShow.Restart": [],
|
||||
"SlideShow.Stop": [],
|
||||
"SlideShow.NextSlide": [],
|
||||
"SlideShow.PreviousSlide": []
|
||||
},
|
||||
"deinterlace_mode": 0,
|
||||
"deinterlace_field_order": 0,
|
||||
"monitoring_type": 0,
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"prev_ver": 536870916,
|
||||
"name": "sub1.img.rouge",
|
||||
"uuid": "2dfe69bc-9b10-419d-8ee1-6cd9cc53a502",
|
||||
"id": "slideshow",
|
||||
"versioned_id": "slideshow",
|
||||
"settings": {
|
||||
"files": [],
|
||||
"transition": "slide",
|
||||
"slide_time": 9000,
|
||||
"transition_speed": 1300,
|
||||
"use_custom_size": "1:1",
|
||||
"playback_behavior": "stop_restart",
|
||||
"slide_mode": "mode_auto",
|
||||
"overlay": true
|
||||
},
|
||||
"mixers": 0,
|
||||
"sync": 0,
|
||||
"flags": 0,
|
||||
"volume": 1.0,
|
||||
"balance": 0.5,
|
||||
"enabled": true,
|
||||
"muted": false,
|
||||
"push-to-mute": false,
|
||||
"push-to-mute-delay": 0,
|
||||
"push-to-talk": false,
|
||||
"push-to-talk-delay": 0,
|
||||
"hotkeys": {
|
||||
"SlideShow.PlayPause": [],
|
||||
"SlideShow.Restart": [],
|
||||
"SlideShow.Stop": [],
|
||||
"SlideShow.NextSlide": [],
|
||||
"SlideShow.PreviousSlide": []
|
||||
},
|
||||
"deinterlace_mode": 0,
|
||||
"deinterlace_field_order": 0,
|
||||
"monitoring_type": 0,
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"prev_ver": 536870916,
|
||||
"name": "sub1.score.blue",
|
||||
"uuid": "264866ab-58e1-45ec-a32f-c440c32057cd",
|
||||
"id": "text_gdiplus",
|
||||
"versioned_id": "text_gdiplus_v2",
|
||||
"settings": {
|
||||
"chatlog": false,
|
||||
"extents": true,
|
||||
"gradient": false,
|
||||
"outline": false,
|
||||
"text": "0",
|
||||
"undo_sname": "sub1.comb_rouge",
|
||||
"vertical": false,
|
||||
"font": {
|
||||
"face": "Arial",
|
||||
"flags": 0,
|
||||
"size": 80,
|
||||
"style": "Normal"
|
||||
},
|
||||
"align": "center",
|
||||
"valign": "bottom",
|
||||
"color": 4294917120,
|
||||
"extents_wrap": true,
|
||||
"extents_cx": 163,
|
||||
"extents_cy": 80,
|
||||
"antialiasing": true
|
||||
},
|
||||
"mixers": 0,
|
||||
"sync": 0,
|
||||
"flags": 0,
|
||||
"volume": 1.0,
|
||||
"balance": 0.5,
|
||||
"enabled": true,
|
||||
"muted": false,
|
||||
"push-to-mute": false,
|
||||
"push-to-mute-delay": 0,
|
||||
"push-to-talk": false,
|
||||
"push-to-talk-delay": 0,
|
||||
"hotkeys": {},
|
||||
"deinterlace_mode": 0,
|
||||
"deinterlace_field_order": 0,
|
||||
"monitoring_type": 0,
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"prev_ver": 536870916,
|
||||
"name": "sub1.score.rouge",
|
||||
"uuid": "8a532b22-7bcd-4742-aa0e-8ea556f39a12",
|
||||
"id": "text_gdiplus",
|
||||
"versioned_id": "text_gdiplus_v2",
|
||||
"settings": {
|
||||
"chatlog": false,
|
||||
"extents": true,
|
||||
"gradient": false,
|
||||
"outline": false,
|
||||
"text": "0",
|
||||
"undo_sname": "sub1.comb_rouge",
|
||||
"vertical": false,
|
||||
"font": {
|
||||
"face": "Arial",
|
||||
"flags": 0,
|
||||
"size": 80,
|
||||
"style": "Normal"
|
||||
},
|
||||
"align": "center",
|
||||
"valign": "bottom",
|
||||
"color": 4278190335,
|
||||
"extents_wrap": true,
|
||||
"extents_cx": 163,
|
||||
"extents_cy": 80,
|
||||
"antialiasing": true
|
||||
},
|
||||
"mixers": 0,
|
||||
"sync": 0,
|
||||
"flags": 0,
|
||||
"volume": 1.0,
|
||||
"balance": 0.5,
|
||||
"enabled": true,
|
||||
"muted": false,
|
||||
"push-to-mute": false,
|
||||
"push-to-mute-delay": 0,
|
||||
"push-to-talk": false,
|
||||
"push-to-talk-delay": 0,
|
||||
"hotkeys": {},
|
||||
"deinterlace_mode": 0,
|
||||
"deinterlace_field_order": 0,
|
||||
"monitoring_type": 0,
|
||||
"private_settings": {}
|
||||
},
|
||||
{
|
||||
"prev_ver": 536870916,
|
||||
"name": "sub1.temps",
|
||||
"uuid": "a961104f-2cad-4f65-9ff8-d50ff36f8418",
|
||||
"id": "text_gdiplus",
|
||||
"versioned_id": "text_gdiplus_v2",
|
||||
"settings": {
|
||||
"extents": true,
|
||||
"text": "00:10",
|
||||
"font": {
|
||||
"face": "Arial",
|
||||
"flags": 0,
|
||||
"size": 110,
|
||||
"style": "Normal"
|
||||
},
|
||||
"align": "center",
|
||||
"color": -16711936,
|
||||
"extents_wrap": true,
|
||||
"extents_cx": 800,
|
||||
"extents_cy": 110
|
||||
},
|
||||
"mixers": 0,
|
||||
"sync": 0,
|
||||
"flags": 0,
|
||||
"volume": 1.0,
|
||||
"balance": 0.5,
|
||||
"enabled": true,
|
||||
"muted": false,
|
||||
"push-to-mute": false,
|
||||
"push-to-mute-delay": 0,
|
||||
"push-to-talk": false,
|
||||
"push-to-talk-delay": 0,
|
||||
"hotkeys": {},
|
||||
"deinterlace_mode": 0,
|
||||
"deinterlace_field_order": 0,
|
||||
"monitoring_type": 0,
|
||||
"private_settings": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
import {
|
||||
IconDefinition,
|
||||
IconName,
|
||||
IconPrefix
|
||||
} from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
export const SimpleIconsOBS: IconDefinition = {
|
||||
icon: [
|
||||
// SVG viewbox width (in pixels)
|
||||
20,
|
||||
|
||||
// SVG viewbox height (in pixels)
|
||||
20,
|
||||
|
||||
// Aliases (not needed)
|
||||
[],
|
||||
|
||||
// Unicode as hex value (not needed)
|
||||
"",
|
||||
|
||||
// SVG path data
|
||||
"M10 0C4.486 0 0 4.486 0 10s4.486 10 10 10 10-4.485 10-10S15.515 0 10 0m8.159 14.075c.55-1.617-.057-3.491-1.551-4.448-1.75-1.12-5.196 1.14-5.196 1.14s-.763 2.815-.026 4.007a5.4 5.4 0 0 1-1.77 1.709c-2.348 1.4-5.372.848-7.054-1.209a9 9 0 0 1-.235-.346c1.139 1.423 3.219 1.831 4.855.908 1.809-1.02 1.427-5.124 1.427-5.124S6.598 8.774 5.26 8.799a5.424 5.424 0 0 1 2.993-7.76c.157-.03.317-.05.476-.072a3.76 3.76 0 1 0 4.525 5.435 5.4 5.4 0 0 1 2.794.793c2.199 1.345 3.119 4.041 2.344 6.397-.072.166-.154.324-.234.483"
|
||||
],
|
||||
iconName: "simple-icons-obs" as IconName,
|
||||
prefix: "simple-icons" as IconPrefix
|
||||
};
|
||||
@ -2,21 +2,44 @@ import {useEffect, useRef, useState, memo} from 'react';
|
||||
|
||||
const cache = {};
|
||||
|
||||
export function detectOptimalBackground(blob,
|
||||
export function SmartLogoBackground({
|
||||
src,
|
||||
darkBackground = '#333333',
|
||||
lightBackground = '#f0f0f0',
|
||||
defaultBackground = 'transparent',
|
||||
tolerance = 55,
|
||||
minPixels = 10) {
|
||||
return new Promise((resolve) => {
|
||||
const imgUrl = URL.createObjectURL(blob);
|
||||
minPixels = 10, // Seuil minimal de pixels détectés pour appliquer un fond
|
||||
alt = 'Logo',
|
||||
style = {},
|
||||
imgClassName = '',
|
||||
}) {
|
||||
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');
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
|
||||
img.crossOrigin = 'Anonymous';
|
||||
img.src = src;
|
||||
|
||||
// Prevent error logging
|
||||
img.onerror = function () {
|
||||
return true;
|
||||
}
|
||||
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
@ -46,9 +69,12 @@ export function detectOptimalBackground(blob,
|
||||
const i = Math.round(y * canvas.width + x) * 4;
|
||||
if (isTransparent(i)) {
|
||||
const neighbors = [
|
||||
i - 4, i + 4, // Haut/Bas
|
||||
i - 4 * canvas.width, i + 4 * canvas.width, // Gauche/Droite
|
||||
i - 4, // Haut
|
||||
i + 4, // Bas
|
||||
i - 4 * canvas.width, // Gauche
|
||||
i + 4 * canvas.width, // Droite
|
||||
];
|
||||
|
||||
for (const neighbor of neighbors) {
|
||||
if (neighbor >= 0 && neighbor < pixels.length) {
|
||||
if (isLightColor(neighbor)) lightBorderCount++;
|
||||
@ -56,64 +82,24 @@ export function detectOptimalBackground(blob,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (lightBorderCount >= 25 || darkBorderCount >= 25)
|
||||
break
|
||||
}
|
||||
|
||||
if (lightBorderCount > darkBorderCount && lightBorderCount >= minPixels) {
|
||||
cache[src] = darkBackground;
|
||||
setBackground(darkBackground)
|
||||
} else if (darkBorderCount > lightBorderCount && darkBorderCount >= minPixels) {
|
||||
cache[src] = lightBackground;
|
||||
setBackground(lightBackground)
|
||||
} else {
|
||||
cache[src] = defaultBackground;
|
||||
setBackground(defaultBackground)
|
||||
}
|
||||
}
|
||||
|
||||
URL.revokeObjectURL(imgUrl);
|
||||
if (lightBorderCount > darkBorderCount && lightBorderCount >= minPixels) {
|
||||
resolve(darkBackground); // Fond sombre
|
||||
} else if (darkBorderCount > lightBorderCount && darkBorderCount >= minPixels) {
|
||||
resolve(lightBackground); // Fond clair
|
||||
} else {
|
||||
resolve(defaultBackground); // Fond transparent
|
||||
}
|
||||
};
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(imgUrl);
|
||||
resolve(defaultBackground); // En cas d'erreur
|
||||
};
|
||||
img.src = imgUrl;
|
||||
});
|
||||
}
|
||||
|
||||
export function SmartLogoBackground({
|
||||
src,
|
||||
darkBackground = '#333333',
|
||||
lightBackground = '#f0f0f0',
|
||||
defaultBackground = 'transparent',
|
||||
tolerance = 55,
|
||||
minPixels = 10, // Seuil minimal de pixels détectés pour appliquer un fond
|
||||
alt = 'Logo',
|
||||
style = {},
|
||||
imgClassName = '',
|
||||
}) {
|
||||
const canvasRef = useRef(null);
|
||||
const [background, setBackground] = useState(defaultBackground);
|
||||
const [load, setLoad] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!load) return;
|
||||
|
||||
const fetchAndDetect = async () => {
|
||||
if (cache[src]) {
|
||||
setBackground(cache[src]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(src);
|
||||
const blob = await response.blob();
|
||||
const detectedBackground = await detectOptimalBackground(blob, darkBackground, lightBackground, defaultBackground, tolerance, minPixels);
|
||||
cache[src] = detectedBackground;
|
||||
setBackground(detectedBackground);
|
||||
} catch (error) {
|
||||
console.error("Erreur de détection du fond:", error);
|
||||
cache[src] = defaultBackground;
|
||||
setBackground(defaultBackground);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAndDetect();
|
||||
}
|
||||
}, [src, darkBackground, lightBackground, defaultBackground, tolerance, minPixels, load]);
|
||||
|
||||
return <>
|
||||
|
||||
@ -1,15 +1,6 @@
|
||||
import {createContext, useContext, useReducer} from "react";
|
||||
|
||||
const PubAffContext = createContext({
|
||||
next: [],
|
||||
c1: undefined,
|
||||
c2: undefined,
|
||||
showScore: true,
|
||||
timeCb: undefined,
|
||||
timeCb2: undefined,
|
||||
scoreRouge: 0,
|
||||
scoreBleu: 0
|
||||
});
|
||||
const PubAffContext = createContext({next: [], c1: undefined, c2: undefined, showScore: true, timeCb: undefined, scoreRouge: 0, scoreBleu: 0});
|
||||
const PubAffDispatchContext = createContext(() => {
|
||||
});
|
||||
|
||||
@ -20,8 +11,6 @@ function reducer(state, action) {
|
||||
case 'CALL_TIME':
|
||||
if (state.timeCb)
|
||||
state.timeCb(action.payload)
|
||||
if (state.timeCb2)
|
||||
state.timeCb2(action.payload)
|
||||
return state
|
||||
case 'CLEAR_CB_TIME':
|
||||
return {...state, timeCb: undefined}
|
||||
|
||||
@ -1,183 +0,0 @@
|
||||
import {createContext, useContext, useEffect, useRef, useState} from "react";
|
||||
import OBSWebSocket from "obs-websocket-js";
|
||||
import {hex2rgb} from "../utils/Tools.js";
|
||||
|
||||
const OBSContext = createContext({connected: false, obs: null})
|
||||
|
||||
export function OBSProvider({children}) {
|
||||
const obs = useRef(null)
|
||||
const obsParm = useRef(null)
|
||||
const assets = useRef(null)
|
||||
const [connected, setConnected] = useState(false)
|
||||
const [doReconnect, setDoReconnect] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!doReconnect)
|
||||
return;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (obs.current && !connected && doReconnect) {
|
||||
console.log("Reconnecting to OBS WebSocket...")
|
||||
obs.current.connect(obsParm.current.adresse, obsParm.current.password)
|
||||
.then(data => {
|
||||
console.log("Reconnected to OBS WebSocket", data)
|
||||
setDoReconnect(false)
|
||||
})
|
||||
}
|
||||
}, 5000);
|
||||
return () => clearInterval(timer);
|
||||
}, [connected, doReconnect]);
|
||||
|
||||
const connect = (adresse, password = undefined, assets_dir = undefined) => {
|
||||
if (connected && obs.current)
|
||||
return;
|
||||
assets.current = assets_dir;
|
||||
|
||||
const obs_ = new OBSWebSocket();
|
||||
obs_.connect(adresse, password)
|
||||
.then(data => {
|
||||
console.log("Connected to OBS WebSocket", data)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Failed to connect to OBS WebSocket", err)
|
||||
});
|
||||
obs_.on('ConnectionOpened', () => {
|
||||
setConnected(true)
|
||||
console.log("OBS WebSocket connection opened")
|
||||
});
|
||||
obs_.on('ConnectionClosed', err => {
|
||||
setConnected(false)
|
||||
console.log("OBS WebSocket connection closed", err.code, err.message)
|
||||
if (err.code === 1000 || err.code === 4009) // 1000 = Normal Closure, 4009 = Authentication Failure
|
||||
return;
|
||||
obsParm.current = {adresse, password}
|
||||
setDoReconnect(true)
|
||||
});
|
||||
obs_.on('error', err => {
|
||||
console.error("OBS WebSocket error", err)
|
||||
});
|
||||
|
||||
obs.current = obs_;
|
||||
}
|
||||
|
||||
const disconnect = () => {
|
||||
if (obs.current && connected) {
|
||||
obs.current.disconnect();
|
||||
obs.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
const ret = {connected, obs: obs.current, connect, disconnect, assets: assets.current}
|
||||
return <OBSContext.Provider value={ret}>
|
||||
{children}
|
||||
</OBSContext.Provider>
|
||||
}
|
||||
|
||||
function getElementName(element) {
|
||||
return `sub${sessionStorage.getItem("obs_prefix") || 1}.${element}`
|
||||
}
|
||||
|
||||
export function useOBS() {
|
||||
const {connected, obs, connect, disconnect, assets} = useContext(OBSContext)
|
||||
const setTextAndColor = (element, text, color) => {
|
||||
if (!connected)
|
||||
return;
|
||||
if (color.startsWith("#")){
|
||||
const tmp = hex2rgb(color);
|
||||
color = (tmp.b << 16) + (tmp.g << 8) + tmp.r;
|
||||
}
|
||||
|
||||
obs.call('SetInputSettings', {
|
||||
inputName: getElementName(element),
|
||||
inputSettings: {
|
||||
text: text,
|
||||
color: color,
|
||||
overlay: true,
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error("Failed to set text and color for OBS element", element, err)
|
||||
});
|
||||
}
|
||||
const setText = (element, text) => {
|
||||
if (!connected)
|
||||
return;
|
||||
obs.call('SetInputSettings', {
|
||||
inputName: getElementName(element),
|
||||
inputSettings: {
|
||||
text: text,
|
||||
overlay: true,
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error("Failed to set text for OBS element", element, err)
|
||||
});
|
||||
}
|
||||
|
||||
const setDiapo = (element, files) => {
|
||||
if (!connected)
|
||||
return;
|
||||
const arr = [];
|
||||
for (const file of files) {
|
||||
arr.push({
|
||||
hidden: false,
|
||||
selected: false,
|
||||
value: (assets.endsWith('/') ? assets : assets + '/') + file,
|
||||
})
|
||||
}
|
||||
obs.call('SetInputSettings', {
|
||||
inputName: getElementName(element),
|
||||
inputSettings: {
|
||||
files: arr,
|
||||
overlay: true,
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error("Failed to set diapo for OBS element", element, err)
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
connected,
|
||||
obs,
|
||||
connect,
|
||||
disconnect,
|
||||
setText,
|
||||
setTextAndColor,
|
||||
setDiapo,
|
||||
}
|
||||
}
|
||||
|
||||
export function exportOBSConfiguration(adresse, password, assets_dir) {
|
||||
const config = {
|
||||
adresse: adresse,
|
||||
password: password,
|
||||
assets_dir: assets_dir,
|
||||
};
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(config, null, 2));
|
||||
const downloadAnchorNode = document.createElement('a');
|
||||
downloadAnchorNode.setAttribute("href", dataStr);
|
||||
downloadAnchorNode.setAttribute("download", "obs_configuration.json");
|
||||
document.body.appendChild(downloadAnchorNode); // required for firefox
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
}
|
||||
|
||||
export async function importOBSConfiguration() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'application/json';
|
||||
input.onchange = e => {
|
||||
const file = e.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = event => {
|
||||
try {
|
||||
const config = JSON.parse(event.target.result);
|
||||
resolve(config);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
input.click();
|
||||
});
|
||||
}
|
||||
@ -1,40 +1,30 @@
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {useEffect, useRef, useState} from "react";
|
||||
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
||||
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||
import {toast} from "react-toastify";
|
||||
import {build_tree, resize_tree} from "../../../utils/TreeUtils.js"
|
||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||
import {CategoryContent} from "./CategoryAdminContent.jsx";
|
||||
import {exportOBSConfiguration} from "../../../hooks/useOBS.jsx";
|
||||
import {createPortal} from "react-dom";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
||||
import JSZip from "jszip";
|
||||
import {detectOptimalBackground} from "../../../components/SmartLogoBackground.jsx";
|
||||
import {faGlobe} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
export function CMAdmin({compUuid}) {
|
||||
export function CMAdmin() {
|
||||
const [catId, setCatId] = useState(null);
|
||||
const [cat, setCat] = useState(null);
|
||||
const menuActions = useRef({});
|
||||
const {dispatch} = useWS();
|
||||
|
||||
useEffect(() => {
|
||||
const categoryListener = ({data}) => {
|
||||
if (!cat || data.id !== cat.id)
|
||||
return
|
||||
setCat(cat_ => ({
|
||||
...cat_,
|
||||
setCat({
|
||||
...cat,
|
||||
name: data.name,
|
||||
liceName: data.liceName,
|
||||
type: data.type
|
||||
}))
|
||||
})
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
|
||||
return () => dispatch({type: 'removeListener', payload: categoryListener})
|
||||
}, [cat]);
|
||||
}, []);
|
||||
|
||||
return <>
|
||||
<div className="card">
|
||||
@ -45,261 +35,10 @@ export function CMAdmin({compUuid}) {
|
||||
<div className="card-body">
|
||||
<LoadingProvider>
|
||||
<div className="row">
|
||||
<CategoryContent cat={cat} catId={catId} setCat={setCat} menuActions={menuActions}/>
|
||||
<CategoryContent cat={cat} catId={catId} setCat={setCat}/>
|
||||
</div>
|
||||
</LoadingProvider>
|
||||
</div>
|
||||
<Menu menuActions={menuActions} compUuid={compUuid}/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
let tto = [];
|
||||
|
||||
function resizeImageWithOptimalBackground(blob) {
|
||||
return new Promise(async (resolve) => {
|
||||
const background = await detectOptimalBackground(blob);
|
||||
const imgUrl = URL.createObjectURL(blob);
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 1080;
|
||||
canvas.height = 1080;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Dessiner l'image centrée
|
||||
const scale = Math.min(1080 / img.width, 1080 / img.height);
|
||||
const newWidth = img.width * scale;
|
||||
const newHeight = img.height * scale;
|
||||
const x = (1080 - newWidth) / 2;
|
||||
const y = (1080 - newHeight) / 2;
|
||||
|
||||
ctx.filter = `drop-shadow(0 0 2rem ${background})`;
|
||||
ctx.drawImage(img, x, y, newWidth, newHeight);
|
||||
|
||||
// Exporter en PNG
|
||||
canvas.toBlob((newBlob) => {
|
||||
resolve(newBlob);
|
||||
URL.revokeObjectURL(imgUrl);
|
||||
}, 'image/png', 1.0);
|
||||
};
|
||||
img.onerror = () => resolve(blob); // Retourne l'original en cas d'erreur
|
||||
img.src = imgUrl;
|
||||
});
|
||||
}
|
||||
|
||||
async function downloadResourcesAsZip(resourceList) {
|
||||
const zip = new JSZip();
|
||||
const modal = new bootstrap.Modal(document.getElementById('progressModal'));
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressText = document.getElementById('progressText');
|
||||
let completed = 0;
|
||||
|
||||
if (!resourceList.some(d => d.url === '/obs_template.json'))
|
||||
resourceList.push({url: '/obs_template.json', name: 'saf_obs_template.json'});
|
||||
|
||||
// Afficher la modale
|
||||
modal.show();
|
||||
|
||||
// Fonction pour télécharger une ressource et l'ajouter au ZIP
|
||||
const addResourceToZip = async (data) => {
|
||||
try {
|
||||
const response = await fetch(data.url);
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
return {success: false, filename: data.name || data.url.split('/').pop()};
|
||||
}
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
throw new Error(`Erreur HTTP: ${response.status}`);
|
||||
}
|
||||
const blob = await response.blob();
|
||||
const filename = data.name || data.url.split('/').pop();
|
||||
const format = filename.split('.').pop().toLowerCase();
|
||||
|
||||
if (['png', 'jpg', 'jpeg', 'svg'].includes(format)) {
|
||||
const resizedBlob = await resizeImageWithOptimalBackground(blob);
|
||||
const pngFilename = filename.replace(/\.[^/.]+$/, ".png");
|
||||
zip.file(pngFilename, resizedBlob);
|
||||
return {success: true, pngFilename};
|
||||
} else {
|
||||
zip.file(filename, blob);
|
||||
return {success: true, filename};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Impossible d'ajouter ${data.url} au ZIP:`, error);
|
||||
return {success: false, filename: data.name || data.url.split('/').pop()};
|
||||
}
|
||||
};
|
||||
|
||||
// Télécharger toutes les ressources et mettre à jour la progression
|
||||
await Promise.all(
|
||||
resourceList.map(async (data) => {
|
||||
const result = await addResourceToZip(data);
|
||||
completed++;
|
||||
const progress = Math.round((completed / resourceList.length) * 100);
|
||||
progressBar.style.width = `${progress}%`;
|
||||
progressText.textContent = `Téléchargement (${completed}/${resourceList.length}) : ${result.filename}`;
|
||||
return result;
|
||||
})
|
||||
);
|
||||
|
||||
// Générer le ZIP et déclencher le téléchargement
|
||||
const zipBlob = await zip.generateAsync({type: 'blob'});
|
||||
const zipUrl = URL.createObjectURL(zipBlob);
|
||||
const a = document.createElement('a');
|
||||
a.href = zipUrl;
|
||||
a.download = 'ressources.zip';
|
||||
a.click();
|
||||
URL.revokeObjectURL(zipUrl);
|
||||
|
||||
// Fermer la modale
|
||||
modal.hide();
|
||||
progressText.textContent = "Téléchargement terminé !";
|
||||
}
|
||||
|
||||
function Menu({menuActions, compUuid}) {
|
||||
const e = document.getElementById("actionMenu")
|
||||
const longPress = useRef({time: null, timer: null, button: null});
|
||||
const obsModal = useRef(null);
|
||||
|
||||
for (const x of tto)
|
||||
x.dispose();
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip2"]')
|
||||
tto = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
|
||||
const longTimeAction = (button) => {
|
||||
if (button === "obs") {
|
||||
obsModal.current.click();
|
||||
}
|
||||
}
|
||||
|
||||
const longPressDown = (button) => {
|
||||
longPress.current.button = button;
|
||||
longPress.current.time = new Date();
|
||||
longPress.current.timer = setTimeout(() => {
|
||||
longTimeAction(button);
|
||||
|
||||
longPress.current.time = null;
|
||||
longPress.current.button = null;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const longPressUp = (button) => {
|
||||
clearTimeout(longPress.current.timer);
|
||||
|
||||
if (longPress.current.time) {
|
||||
const diff = new Date() - longPress.current.time;
|
||||
if (longPress.current.button === button) {
|
||||
if (diff >= 1000) {
|
||||
longTimeAction(button);
|
||||
} else {
|
||||
if (button === "obs") {
|
||||
downloadResourcesAsZip(menuActions.current.resourceList || [])
|
||||
.then(__ => console.log("Ressources téléchargées"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
longPress.current.time = null;
|
||||
longPress.current.button = null;
|
||||
}
|
||||
}
|
||||
|
||||
const handleOBSSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
const adresse = form[0].value;
|
||||
const password = form[1].value;
|
||||
const assets_dir = form[2].value;
|
||||
|
||||
exportOBSConfiguration(adresse, password, assets_dir)
|
||||
}
|
||||
|
||||
const copyScriptToClipboard = () => {
|
||||
navigator.clipboard.writeText(`<div id='safca_api_data'></div>
|
||||
<script id="safca_api_script" type="text/javascript" src="${vite_url}/competition.js?id=${compUuid}"></script>`
|
||||
).then(() => {
|
||||
toast.success("Texte copié dans le presse-papier ! Collez-le dans une balise HTML sur votre WordPress.");
|
||||
}).catch(err => {
|
||||
toast.error("Erreur lors de la copie dans le presse-papier : " + err);
|
||||
});
|
||||
}
|
||||
|
||||
if (!e)
|
||||
return <></>;
|
||||
return <>
|
||||
{createPortal(
|
||||
<>
|
||||
<div className="vr" style={{margin: "0 0.5em", height: "100%"}}></div>
|
||||
<FontAwesomeIcon icon={SimpleIconsOBS} size="xl"
|
||||
style={{color: "#6c757d", cursor: "pointer", marginRight: "0.25em"}}
|
||||
onMouseDown={() => longPressDown("obs")}
|
||||
onMouseUp={() => longPressUp("obs")}
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||
data-bs-title="Clique court : Télécharger les ressources. Clique long : Créer la configuration obs"/>
|
||||
<FontAwesomeIcon icon={faGlobe} size="xl"
|
||||
style={{color: "#6c757d", cursor: "pointer"}}
|
||||
onClick={() => copyScriptToClipboard()}
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||
data-bs-title="Copier le scripte d'intégration"/>
|
||||
</>, document.getElementById("actionMenu"))}
|
||||
|
||||
<button ref={obsModal} type="button" className="btn btn-link" data-bs-toggle="modal" data-bs-target="#OBSModal" style={{display: 'none'}}>
|
||||
Launch OBS Modal
|
||||
</button>
|
||||
<div className="modal fade" id="OBSModal" tabIndex="-1" aria-labelledby="OBSModalLabel" aria-hidden="true">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Configuration OBS</h5>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form onSubmit={handleOBSSubmit}>
|
||||
<div className="modal-body">
|
||||
<strong>/!\ Le mot de passe va être stoker en claire, il est recommandé de ne l'utiliser que sur obs websocket et d'en
|
||||
changer entre chaque compétition</strong>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Adresse du serveur</span>
|
||||
<span className="input-group-text">ws://</span>
|
||||
<input type="text" className="form-control" placeholder="127.0.0.1:4455" aria-label=""
|
||||
defaultValue={"127.0.0.1:4455"}/>
|
||||
<span className="input-group-text">/</span>
|
||||
</div>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Mot de passe du serveur</span>
|
||||
<input type="password" className="form-control" placeholder="12345" aria-label=""
|
||||
defaultValue={""}/>
|
||||
</div>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Dossier des resources</span>
|
||||
<input type="text" className="form-control" placeholder="" aria-label="" required/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Exporter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="modal fade" id="progressModal" tabIndex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Téléchargement en cours...</h5>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="progress">
|
||||
<div id="progressBar" className="progress-bar" role="progressbar" style={{width: "0%"}}></div>
|
||||
</div>
|
||||
<div id="progressText" className="mt-2">Préparation...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@ -321,34 +60,18 @@ function CategoryHeader({cat, setCatId}) {
|
||||
data
|
||||
])
|
||||
}
|
||||
const sendAddCategory = ({data}) => {
|
||||
setCats([...cats, data])
|
||||
}
|
||||
const sendDelCategory = ({data}) => {
|
||||
setCatId(catId => {
|
||||
if (catId === data) return null;
|
||||
return catId;
|
||||
})
|
||||
setCats([...cats.filter(c => c.id !== data)])
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
|
||||
dispatch({type: 'addListener', payload: {callback: sendAddCategory, code: 'sendAddCategory'}})
|
||||
dispatch({type: 'addListener', payload: {callback: sendDelCategory, code: 'sendDelCategory'}})
|
||||
return () => {
|
||||
dispatch({type: 'removeListener', payload: categoryListener})
|
||||
dispatch({type: 'removeListener', payload: sendAddCategory})
|
||||
dispatch({type: 'removeListener', payload: sendDelCategory})
|
||||
}
|
||||
return () => dispatch({type: 'removeListener', payload: categoryListener})
|
||||
}, [cats]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cats && cats.length > 0 && !cat || (cats && !cats.find(c => c.id === cat.id))) {
|
||||
if (cats && cats.length > 0 && !cat) {
|
||||
setCatId(cats.sort((a, b) => a.name.localeCompare(b.name))[0].id);
|
||||
} else if (cats && cats.length === 0) {
|
||||
setModal({});
|
||||
bthRef.current.click();
|
||||
}
|
||||
}, [cats, cat]);
|
||||
}, [cats]);
|
||||
|
||||
const handleCatChange = (e) => {
|
||||
const selectedCatId = e.target.value;
|
||||
@ -365,7 +88,7 @@ function CategoryHeader({cat, setCatId}) {
|
||||
<div className="col-auto">
|
||||
<div className="input-group">
|
||||
<h5 style={{margin: "auto 0.5em auto 0"}}>Edition de la catégorie</h5>
|
||||
<select className="form-select" onChange={handleCatChange} value={cat?.id || ""}>
|
||||
<select className="form-select" onChange={handleCatChange}>
|
||||
{cats && cats.sort((a, b) => a.name.localeCompare(b.name)).map(c => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>))}
|
||||
{cats && <option value={-1}>Nouvelle...</option>}
|
||||
@ -375,7 +98,7 @@ function CategoryHeader({cat, setCatId}) {
|
||||
<div className="col" style={{margin: "auto 0", textAlign: "center"}}>
|
||||
{cat &&
|
||||
<div>Type: {(cat.type & 1) !== 0 ? "Poule" : ""}{cat.type === 3 ? " & " : ""}{(cat.type & 2) !== 0 ? "Tournois" : ""} |
|
||||
Zone: {cat.liceName}</div>}
|
||||
Lice: {cat.liceName}</div>}
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
<button className="btn btn-primary float-end" onClick={() => {
|
||||
@ -405,7 +128,7 @@ function CategoryHeader({cat, setCatId}) {
|
||||
|
||||
function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
const [name, setName] = useState("")
|
||||
const [lice, setLice] = useState("A")
|
||||
const [lice, setLice] = useState("1")
|
||||
const [poule, setPoule] = useState(true)
|
||||
const [tournoi, setTournoi] = useState(false)
|
||||
const [size, setSize] = useState(4)
|
||||
@ -415,7 +138,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
|
||||
useEffect(() => {
|
||||
setName(state.name || "");
|
||||
setLice(state.liceName || "A");
|
||||
setLice(state.liceName || "1");
|
||||
setPoule(((state.type || 1) & 1) !== 0);
|
||||
setTournoi((state.type & 2) !== 0);
|
||||
|
||||
@ -441,7 +164,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
|
||||
const regex = /^([^;]+;)*[^;]+$/;
|
||||
if (regex.test(lice.trim()) === false) {
|
||||
toast.error("Le format du nom des zones de combat est invalide. Veuillez séparer les noms par des ';'.");
|
||||
toast.error("Le format du nom des lices est invalide. Veuillez séparer les noms par des ';'.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -547,6 +270,8 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
error: 'Erreur lors de la création de la catégorie'
|
||||
}
|
||||
).then(id => {
|
||||
setCatId(id);
|
||||
|
||||
if (tournoi) {
|
||||
const trees = build_tree(size, loserMatch)
|
||||
console.log("Creating trees for new category:", trees);
|
||||
@ -557,9 +282,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
success: 'Arbres créés !',
|
||||
error: 'Erreur lors de la création des arbres'
|
||||
}
|
||||
).finally(() => setCatId(id))
|
||||
} else {
|
||||
setCatId(id);
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -576,7 +299,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">{state.id === undefined ? "Ajouter" : "Modifier"} une catégorie</h1>
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">Ajouter une catégorie</h1>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
@ -588,8 +311,8 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="liceInput1" className="form-label">Nom des zones de combat <small>(séparée par des ';')</small></label>
|
||||
<input type="text" className="form-control" id="liceInput1" placeholder="A;B" name="zone de combat" value={lice}
|
||||
<label htmlFor="liceInput1" className="form-label">Nom des lices <small>(séparée par des ';')</small></label>
|
||||
<input type="text" className="form-control" id="liceInput1" placeholder="1;2" name="lice" value={lice}
|
||||
onChange={e => setLice(e.target.value)}/>
|
||||
</div>
|
||||
|
||||
@ -650,22 +373,6 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Enregistrer</button>
|
||||
{state.id !== undefined && <button type="button" className="btn btn-danger" data-bs-dismiss="modal" onClick={() => {
|
||||
setConfirm({
|
||||
title: "Suppression de la catégorie",
|
||||
message: `Voulez-vous vraiment supprimer la catégorie ${state.name}. Cela va supprimer tous les matchs associés !`,
|
||||
confirm: () => {
|
||||
toast.promise(sendRequest('deleteCategory', state.id),
|
||||
{
|
||||
pending: 'Suppression de la catégorie...',
|
||||
success: 'Catégorie supprimée !',
|
||||
error: 'Erreur lors de la suppression de la catégorie'
|
||||
}
|
||||
).then(() => setCatId(null));
|
||||
}
|
||||
})
|
||||
confirmRef.current.click();
|
||||
}}>Supprimer</button>}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ export function ChronoPanel() {
|
||||
})
|
||||
const [chrono, setChrono] = useState({time: 0, startTime: 0})
|
||||
const chronoText = useRef(null)
|
||||
const state = useRef({chronoState: 0, countBlink: 20, lastColor: "#000000", lastTimeStr: "00:00"})
|
||||
const state = useRef({chronoState: 0, countBlink: 20, lastColor: "black", lastTimeStr: "00:00"})
|
||||
const publicAffDispatch = usePubAffDispatch();
|
||||
|
||||
const addTime = (time) => setChrono(prev => ({...prev, time: prev.time - time}))
|
||||
@ -34,12 +34,12 @@ export function ChronoPanel() {
|
||||
|
||||
const timer = setInterval(() => {
|
||||
let currentDuration = config.time
|
||||
let color = "#000000"
|
||||
let color = "black"
|
||||
if (state_.chronoState === 1) {
|
||||
color = (state_.countBlink < blinkRfDuration) ? "#000000" : "#ff0000"
|
||||
color = (state_.countBlink < blinkRfDuration) ? "black" : "red"
|
||||
} else if (state_.chronoState === 2) {
|
||||
currentDuration = (state_.chronoState === 0) ? 10000 : config.pause
|
||||
color = (state_.countBlink < blinkRfDuration) ? "#008000" : "#ff0000"
|
||||
color = (state_.countBlink < blinkRfDuration) ? "green" : "red"
|
||||
}
|
||||
const timeStr = timePrint(currentDuration - getTime())
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faCircleQuestion} from "@fortawesome/free-regular-svg-icons";
|
||||
import {toast} from "react-toastify";
|
||||
import "./CMTMatchPanel.css"
|
||||
import {useOBS} from "../../../hooks/useOBS.jsx";
|
||||
|
||||
function CupImg() {
|
||||
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
||||
@ -23,36 +22,17 @@ export function CategorieSelect({catId, setCatId, menuActions}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data: cats, setData: setCats} = useRequestWS('getAllCategory', {}, setLoading);
|
||||
const {dispatch} = useWS();
|
||||
const {connected, setText} = useOBS();
|
||||
|
||||
useEffect(() => {
|
||||
const categoryListener = ({data}) => {
|
||||
setCats([...cats.filter(c => c.id !== data.id), data])
|
||||
}
|
||||
const sendAddCategory = ({data}) => {
|
||||
setCats([...cats, data])
|
||||
}
|
||||
const sendDelCategory = ({data}) => {
|
||||
if (catId === data)
|
||||
setCatId(-1);
|
||||
setCats([...cats.filter(c => c.id !== data)])
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
|
||||
dispatch({type: 'addListener', payload: {callback: sendAddCategory, code: 'sendAddCategory'}})
|
||||
dispatch({type: 'addListener', payload: {callback: sendDelCategory, code: 'sendDelCategory'}})
|
||||
return () => {
|
||||
dispatch({type: 'removeListener', payload: categoryListener})
|
||||
dispatch({type: 'removeListener', payload: sendAddCategory})
|
||||
dispatch({type: 'removeListener', payload: sendDelCategory})
|
||||
}
|
||||
return () => dispatch({type: 'removeListener', payload: categoryListener})
|
||||
}, [cats]);
|
||||
|
||||
const cat = cats?.find(c => c.id === catId);
|
||||
|
||||
useEffect(() => {
|
||||
setText("poule",cat ? cat.name : "");
|
||||
}, [cat, connected]);
|
||||
|
||||
return <>
|
||||
<div className="input-group">
|
||||
<h6 style={{margin: "auto 0.5em auto 0"}}>Catégorie</h6>
|
||||
@ -151,15 +131,10 @@ function ListMatch({cat, matches, trees, menuActions}) {
|
||||
const [type, setType] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
if (!cat)
|
||||
return;
|
||||
if ((cat.type & type) === 0)
|
||||
setType(cat.type);
|
||||
}, [cat]);
|
||||
|
||||
if (!cat)
|
||||
return <></>;
|
||||
|
||||
return <div style={{marginTop: "1em"}}>
|
||||
{cat && cat.type === 3 && <>
|
||||
<ul className="nav nav-tabs">
|
||||
@ -198,10 +173,6 @@ function MatchList({matches, cat, menuActions}) {
|
||||
.map(m => ({...m, win: win(m.scores)}))
|
||||
const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1;
|
||||
|
||||
const isActiveMatch = (index) => {
|
||||
return liceName.length === 1 || (liceName[(index - firstIndex) % liceName.length] === lice)
|
||||
}
|
||||
|
||||
const match = matches.find(m => m.id === activeMatch)
|
||||
useEffect(() => {
|
||||
if (!match) {
|
||||
@ -212,7 +183,7 @@ function MatchList({matches, cat, menuActions}) {
|
||||
payload: {
|
||||
c1: match.c1,
|
||||
c2: match.c2,
|
||||
next: marches2.filter((m, index) => !m.end && isActiveMatch(index) && m.id !== activeMatch).map(m => ({
|
||||
next: marches2.filter((m, index) => !m.end && liceName[(index - firstIndex) % liceName.length] === lice && m.id !== activeMatch).map(m => ({
|
||||
c1: m.c1,
|
||||
c2: m.c2
|
||||
}))
|
||||
@ -227,7 +198,7 @@ function MatchList({matches, cat, menuActions}) {
|
||||
|
||||
useEffect(() => {
|
||||
if (match && match.poule !== lice)
|
||||
setActiveMatch(marches2.find((m, index) => !m.end && isActiveMatch(index))?.id)
|
||||
setActiveMatch(marches2.find((m, index) => !m.end && liceName[(index - firstIndex) % liceName.length] === lice)?.id)
|
||||
}, [lice]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -236,12 +207,12 @@ function MatchList({matches, cat, menuActions}) {
|
||||
if (marches2.some(m => m.id === activeMatch))
|
||||
return;
|
||||
|
||||
setActiveMatch(marches2.find((m, index) => !m.end && isActiveMatch(index))?.id);
|
||||
setActiveMatch(marches2.find((m, index) => !m.end && liceName[(index - firstIndex) % liceName.length] === lice)?.id);
|
||||
}, [matches])
|
||||
return <>
|
||||
{liceName.length > 1 &&
|
||||
<div className="input-group" style={{maxWidth: "15em", marginTop: "0.5em"}}>
|
||||
<label className="input-group-text" htmlFor="selectLice">Zone de combat</label>
|
||||
<div className="input-group" style={{maxWidth: "10em", marginTop: "0.5em"}}>
|
||||
<label className="input-group-text" htmlFor="selectLice">Lice</label>
|
||||
<select className="form-select" id="selectLice" value={lice} onChange={e => {
|
||||
setLice(e.target.value);
|
||||
localStorage.setItem("cm_lice", e.target.value);
|
||||
@ -253,11 +224,11 @@ function MatchList({matches, cat, menuActions}) {
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="table-responsive-xxl overflow-y-auto" style={{maxHeight: "50vh"}}>
|
||||
<div className="table-responsive-xxl">
|
||||
<table className="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Z</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">L</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">P</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">N°</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
@ -269,7 +240,7 @@ function MatchList({matches, cat, menuActions}) {
|
||||
<tbody className="table-group-divider">
|
||||
{marches2.map((m, index) => (
|
||||
<tr key={m.id}
|
||||
className={m.id === activeMatch ? "table-info" : (isActiveMatch(index) ? "" : "table-warning")}
|
||||
className={m.id === activeMatch ? "table-info" : (liceName[(index - firstIndex) % liceName.length] === lice ? "" : "table-warning")}
|
||||
onClick={() => setActiveMatch(m.id)}>
|
||||
<td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>
|
||||
{liceName[(index - firstIndex) % liceName.length]}</td>
|
||||
@ -288,7 +259,7 @@ function MatchList({matches, cat, menuActions}) {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{activeMatch && <LoadingProvider><ScorePanel matchId={activeMatch} matchs={matches} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
{activeMatch && <LoadingProvider><ScorePanel matchId={activeMatch} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
</>
|
||||
}
|
||||
|
||||
@ -354,29 +325,27 @@ function BuildTree({treeData, matches, menuActions}) {
|
||||
|
||||
|
||||
return <div>
|
||||
<div className="overflow-y-auto" style={{maxHeight: "50vh"}}>
|
||||
<div ref={scrollRef} className="overflow-x-auto" style={{position: "relative"}}>
|
||||
<DrawGraph root={trees} scrollRef={scrollRef} onMatchClick={onMatchClick} onClickVoid={onClickVoid}
|
||||
matchSelect={currentMatch?.matchSelect} matchNext={currentMatch?.matchNext} size={23}/>
|
||||
</div>
|
||||
<div ref={scrollRef} className="overflow-x-auto" style={{position: "relative"}}>
|
||||
<DrawGraph root={trees} scrollRef={scrollRef} onMatchClick={onMatchClick} onClickVoid={onClickVoid}
|
||||
matchSelect={currentMatch?.matchSelect} matchNext={currentMatch?.matchNext} size={23}/>
|
||||
</div>
|
||||
|
||||
{currentMatch?.matchSelect &&
|
||||
<LoadingProvider><ScorePanel matchId={currentMatch?.matchSelect} matchs={matches} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
<LoadingProvider><ScorePanel matchId={currentMatch?.matchSelect} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
</div>
|
||||
}
|
||||
|
||||
function ScorePanel({matchId, matchs, match, menuActions}) {
|
||||
function ScorePanel({matchId, match, menuActions}) {
|
||||
const onClickVoid = useRef(() => {
|
||||
});
|
||||
|
||||
return <div className="row" onClick={onClickVoid.current}>
|
||||
<ScorePanel_ matchId={matchId} matchs={matchs} match={match} menuActions={menuActions} onClickVoid_={onClickVoid}/>
|
||||
<ScorePanel_ matchId={matchId} match={match} menuActions={menuActions} onClickVoid_={onClickVoid}/>
|
||||
<CardPanel matchId={matchId} match={match}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
function ScorePanel_({matchId, match, menuActions, onClickVoid_}) {
|
||||
const {sendRequest} = useWS()
|
||||
const setLoading = useLoadingSwitcher()
|
||||
|
||||
@ -386,11 +355,6 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
const tableRef = useRef(null)
|
||||
const scoreRef = useRef([])
|
||||
const lastScoreClick = useRef(null)
|
||||
const scoreInRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
scoreInRef.current = scoreIn;
|
||||
}, [scoreIn]);
|
||||
|
||||
useEffect(() => {
|
||||
menuActions.current.saveScore = (scoreRed, scoreBlue) => {
|
||||
@ -439,12 +403,9 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
const {matchId, round, comb} = lastScoreClick.current;
|
||||
lastScoreClick.current = null;
|
||||
|
||||
const scoreIn_ = String(scoreInRef.current).trim() === "" ? -1000 : Number(scoreInRef.current);
|
||||
|
||||
const score = matchs.find(m => m.id === matchId).scores.find(s => s.n_round === round);
|
||||
|
||||
console.log("Updating score", matchId, round, comb, scoreIn_, score);
|
||||
const scoreIn_ = String(scoreIn).trim() === "" ? -1000 : Number(scoreIn);
|
||||
|
||||
const score = match.scores.find(s => s.n_round === round);
|
||||
let newScore;
|
||||
if (score) {
|
||||
if (comb === 1)
|
||||
@ -513,18 +474,6 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
setEnd(match.end);
|
||||
}, [match]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (inputRef.current && !inputRef.current.contains(event.target)) {
|
||||
onClickVoid();
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
const o = [...tooltipTriggerList]
|
||||
o.map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
|
||||
@ -1,20 +1,16 @@
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {useRequestWS} from "../../../hooks/useWS.jsx";
|
||||
import {useCombs, useCombsDispatch} from "../../../hooks/useComb.jsx";
|
||||
import {useCombsDispatch} from "../../../hooks/useComb.jsx";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
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 {faArrowRightArrowLeft, faDisplay} from "@fortawesome/free-solid-svg-icons";
|
||||
import {PubAffWindow} from "./PubAffWindow.jsx";
|
||||
import {SimpleIconsScore} from "../../../assets/SimpleIconsScore.ts";
|
||||
import {ChronoPanel} from "./CMTChronoPanel.jsx";
|
||||
import {CategorieSelect} from "./CMTMatchPanel.jsx";
|
||||
import {PointPanel} from "./CMTPoint.jsx";
|
||||
import {importOBSConfiguration, OBSProvider, useOBS} from "../../../hooks/useOBS.jsx";
|
||||
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
||||
import {timePrint} from "../../../utils/Tools.js";
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
export function CMTable() {
|
||||
const combDispatch = useCombsDispatch()
|
||||
@ -28,39 +24,39 @@ export function CMTable() {
|
||||
combDispatch({type: 'SET_ALL', payload: {source: "register", data: data}});
|
||||
}, [data]);
|
||||
|
||||
return <OBSProvider>
|
||||
<PubAffProvider>
|
||||
<div className="text-center">
|
||||
<div className="row">
|
||||
<div className="col-md-12 col-lg">
|
||||
<div className="card mb-3">
|
||||
<div className="card-header">Chronomètre</div>
|
||||
<div className="card-body">
|
||||
<ChronoPanel/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card mb-3">
|
||||
<div className="card-header">Score</div>
|
||||
<div className="card-body">
|
||||
<PointPanel menuActions={menuActions}/>
|
||||
</div>
|
||||
return <PubAffProvider>
|
||||
<div className="text-center">
|
||||
<div className="row">
|
||||
<div className="col-md-12 col-lg">
|
||||
<div className="card mb-3">
|
||||
<div className="card-header">Chronomètre</div>
|
||||
<div className="card-body">
|
||||
<ChronoPanel/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-12 col-xl-6 col-xxl-5">
|
||||
<div className="card mb-3">
|
||||
<div className="card-header">Matches</div>
|
||||
<div className="card-body">
|
||||
<CategorieSelect catId={catId} setCatId={setCatId} menuActions={menuActions}/>
|
||||
</div>
|
||||
|
||||
<div className="card mb-3">
|
||||
<div className="card-header">Score</div>
|
||||
<div className="card-body">
|
||||
<PointPanel menuActions={menuActions}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Menu menuActions={menuActions}/>
|
||||
<ObsAutoSyncWhitPubAff/>
|
||||
<div className="col-md-12 col-xl-6 col-xxl-5">
|
||||
<div className="card mb-3">
|
||||
<div className="card-header">Matches</div>
|
||||
<div className="card-body">
|
||||
<CategorieSelect catId={catId} setCatId={setCatId} menuActions={menuActions}/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{backgroundColor: "#c70000"}}>
|
||||
D
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PubAffProvider>
|
||||
</OBSProvider>
|
||||
<Menu menuActions={menuActions}/>
|
||||
</div>
|
||||
</PubAffProvider>
|
||||
}
|
||||
|
||||
const windowName = "FFSAFScorePublicWindow";
|
||||
@ -72,9 +68,6 @@ function Menu({menuActions}) {
|
||||
const publicAffDispatch = usePubAffDispatch()
|
||||
const [showPubAff, setShowPubAff] = useState(false)
|
||||
const [showScore, setShowScore] = useState(true)
|
||||
const {connected, connect, disconnect} = useOBS();
|
||||
const longPress = useRef({time: null, timer: null, button: null});
|
||||
const obsModal = useRef(null);
|
||||
|
||||
const externalWindow = useRef(null)
|
||||
const containerEl = useRef(document.createElement("div"))
|
||||
@ -83,6 +76,11 @@ function Menu({menuActions}) {
|
||||
if (sessionStorage.getItem(windowName + "_open") === "true") {
|
||||
handlePubAff();
|
||||
}
|
||||
//return () => {
|
||||
// if (!externalWindow.current)
|
||||
// return;
|
||||
// externalWindow.current.close();
|
||||
//}
|
||||
}, []);
|
||||
|
||||
const handlePubAff = __ => {
|
||||
@ -120,76 +118,15 @@ function Menu({menuActions}) {
|
||||
menuActions.current.switchSore?.();
|
||||
}
|
||||
|
||||
const longTimeAction = (button) => {
|
||||
if (button === "obs") {
|
||||
obsModal.current.click();
|
||||
}
|
||||
}
|
||||
|
||||
const longPressDown = (button) => {
|
||||
longPress.current.button = button;
|
||||
longPress.current.time = new Date();
|
||||
longPress.current.timer = setTimeout(() => {
|
||||
longTimeAction(button);
|
||||
|
||||
longPress.current.time = null;
|
||||
longPress.current.button = null;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const longPressUp = (button) => {
|
||||
clearTimeout(longPress.current.timer);
|
||||
|
||||
if (longPress.current.time) {
|
||||
const diff = new Date() - longPress.current.time;
|
||||
if (longPress.current.button === button) {
|
||||
if (diff >= 1000) {
|
||||
longTimeAction(button);
|
||||
} else {
|
||||
if (button === "obs") {
|
||||
if (connected) {
|
||||
disconnect();
|
||||
} else {
|
||||
importOBSConfiguration()
|
||||
.then(config => {
|
||||
connect("ws://" + config.adresse + "/", config.password, config.assets_dir);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Aucune configuration OBS trouvée, veuillez en importer une");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
longPress.current.time = null;
|
||||
longPress.current.button = null;
|
||||
}
|
||||
}
|
||||
|
||||
const handleOBSSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
const prefix = form[0].value;
|
||||
|
||||
sessionStorage.setItem("obs_prefix", prefix);
|
||||
}
|
||||
|
||||
if (!e)
|
||||
return <></>;
|
||||
return <>
|
||||
{createPortal(
|
||||
<>
|
||||
<div className="vr" style={{margin: "0 0.5em", height: "100%"}}></div>
|
||||
<FontAwesomeIcon icon={faArrowRightArrowLeft} size="xl" style={{color: "#6c757d", cursor: "pointer", marginRight: "0.25em"}}
|
||||
onClick={handleSwitchScore} data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||
data-bs-title="Inverser la position des combattants sur cette écran"/>
|
||||
<FontAwesomeIcon icon={SimpleIconsOBS} size="xl"
|
||||
style={{color: connected ? "#00c700" : "#6c757d", cursor: "pointer"}}
|
||||
onMouseDown={() => longPressDown("obs")}
|
||||
onMouseUp={() => longPressUp("obs")}
|
||||
<FontAwesomeIcon icon={faArrowRightArrowLeft} size="xl" style={{color: "#6c757d", cursor: "pointer"}} onClick={handleSwitchScore}
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||
data-bs-title="Clique court : Charger la configuration et se connecter. Clique long : Configuration de la lice"/>
|
||||
data-bs-title="Inverser la position des combattants sur cette écran"/>
|
||||
<div className="vr" style={{margin: "0 0.5em", height: "100%"}}></div>
|
||||
<FontAwesomeIcon icon={faDisplay} size="xl"
|
||||
style={{color: showPubAff ? "#00c700" : "#6c757d", cursor: "pointer", marginRight: "0.25em"}}
|
||||
@ -200,102 +137,5 @@ function Menu({menuActions}) {
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top" data-bs-title="Afficher les scores sur l'affichage public"/>
|
||||
</>, document.getElementById("actionMenu"))}
|
||||
{externalWindow.current && createPortal(<PubAffWindow document={externalWindow.current.document}/>, containerEl.current)}
|
||||
|
||||
<button ref={obsModal} type="button" className="btn btn-link" data-bs-toggle="modal" data-bs-target="#OBSModal" style={{display: 'none'}}>
|
||||
Launch OBS Modal
|
||||
</button>
|
||||
<div className="modal fade" id="OBSModal" tabIndex="-1" aria-labelledby="OBSModalLabel" aria-hidden="true">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Configuration OBS</h5>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form onSubmit={handleOBSSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Préfix des sources</span>
|
||||
<span className="input-group-text">sub</span>
|
||||
<input type="text" className="form-control" placeholder="1" aria-label="" size={1} minLength={1} maxLength={1}
|
||||
defaultValue={localStorage.getItem("obs_prefix") || "1"} required/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Sauvegarde</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function ObsAutoSyncWhitPubAff() {
|
||||
const {connected, setText, setTextAndColor, setDiapo} = useOBS();
|
||||
const oldState = useRef({timeColor: "#000000", timeStr: "--:--", c1: null, c2: null, showScore: true, scoreRouge: 0, scoreBleu: 0});
|
||||
const state = usePubAffState();
|
||||
const {getComb} = useCombs();
|
||||
|
||||
useEffect(() => {
|
||||
if (state.c1 !== oldState.current.c1) {
|
||||
const comb = getComb(state.c1);
|
||||
setText("comb.rouge", comb ? (comb?.fname + " " + comb?.lname) : "");
|
||||
const files = []
|
||||
if (comb?.club_uuid) files.push(`club_${comb.club_uuid}.png`)
|
||||
if (comb?.country) files.push(`flag_${comb.country.toLowerCase()}.png`)
|
||||
setDiapo("img.rouge", files);
|
||||
oldState.current.c1 = state.c1;
|
||||
}
|
||||
|
||||
if (state.c2 !== oldState.current.c2) {
|
||||
const comb = getComb(state.c2);
|
||||
setText("comb.blue", comb ? (comb?.fname + " " + comb?.lname) : "");
|
||||
const files = []
|
||||
if (comb?.club_uuid) files.push(`club_${comb.club_uuid}.png`)
|
||||
if (comb?.country) files.push(`flag_${comb.country.toLowerCase()}.png`)
|
||||
setDiapo("img.blue", files);
|
||||
oldState.current.c2 = state.c2;
|
||||
}
|
||||
|
||||
if (state.showScore !== oldState.current.showScore) {
|
||||
setText("score.rouge", state.showScore ? state.scoreRouge.toString() : "");
|
||||
setText("score.blue", state.showScore ? state.scoreBleu.toString() : "");
|
||||
oldState.current.showScore = state.showScore;
|
||||
}
|
||||
|
||||
if (state.showScore === undefined || state.showScore) {
|
||||
if (state.scoreRouge !== oldState.current.scoreRouge) {
|
||||
setText("score.rouge", (state.scoreRouge || 0).toString());
|
||||
oldState.current.scoreRouge = state.scoreRouge;
|
||||
}
|
||||
if (state.scoreBleu !== oldState.current.scoreBleu) {
|
||||
setText("score.blue", (state.scoreBleu || 0).toString());
|
||||
oldState.current.scoreBleu = state.scoreBleu;
|
||||
}
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
state.timeCb2 = (payload) => {
|
||||
if (payload.timeStr && payload.timeColor) {
|
||||
setTextAndColor("temps", payload.timeStr, payload.timeColor === "#000000" ? "#ffffff" : payload.timeColor);
|
||||
|
||||
oldState.current.timeStr = payload.timeStr;
|
||||
oldState.current.lastColor = payload.timeColor;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!connected)
|
||||
return;
|
||||
// Initial sync
|
||||
const comb = getComb(oldState.current.c1);
|
||||
const comb2 = getComb(oldState.current.c2);
|
||||
setText("comb.rouge", comb ? (comb?.fname + " " + comb?.lname) : "");
|
||||
setText("comb.blue", comb2 ? (comb2?.fname + " " + comb2?.lname) : "");
|
||||
setTextAndColor("temps", oldState.current.timeStr, oldState.current.timeColor === "#000000" ? "#ffffff" : oldState.current.timeColor);
|
||||
setText("score.rouge", oldState.current.showScore === undefined || oldState.current.showScore ? oldState.current.scoreRouge.toString() : "");
|
||||
setText("score.blue", oldState.current.showScore === undefined || oldState.current.showScore ? oldState.current.scoreBleu.toString() : "");
|
||||
|
||||
}, [connected])
|
||||
}
|
||||
|
||||
@ -17,15 +17,13 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||
import {win} from "../../../utils/Tools.js";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
function CupImg() {
|
||||
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
||||
style={{width: "16px"}} src="/img/171891.png"
|
||||
alt=""/>
|
||||
}
|
||||
|
||||
export function CategoryContent({cat, catId, setCat, menuActions}) {
|
||||
export function CategoryContent({cat, catId, setCat}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {sendRequest, dispatch} = useWS();
|
||||
const [matches, reducer] = useReducer(MarchReducer, []);
|
||||
@ -49,10 +47,10 @@ export function CategoryContent({cat, catId, setCat, menuActions}) {
|
||||
const treeListener = ({data}) => {
|
||||
if (!cat || data.length < 1 || data[0].categorie !== cat.id)
|
||||
return
|
||||
setCat(cat_ => ({
|
||||
...cat_,
|
||||
trees: data.sort((a, b) => a.level - b.level).map(d => from_sendTree(d, true))
|
||||
}))
|
||||
setCat({
|
||||
...cat,
|
||||
trees: data.map(d => from_sendTree(d, true))
|
||||
})
|
||||
|
||||
let matches2 = [];
|
||||
let combsToAdd = [];
|
||||
@ -66,16 +64,10 @@ export function CategoryContent({cat, catId, setCat, menuActions}) {
|
||||
reducer({type: 'UPDATE_OR_ADD', payload: {...data, c1: data.c1?.id, c2: data.c2?.id}});
|
||||
combDispatch({type: 'SET_ALL', payload: {source: "match", data: [data.c1, data.c2].filter(d => d != null)}});
|
||||
|
||||
setGroups(prev => {
|
||||
if (data.c1 !== null && !prev.some(g => g.id === data.c1?.id))
|
||||
return [...prev, {id: data.c1?.id, poule: data.poule}];
|
||||
return prev;
|
||||
})
|
||||
setGroups(prev => {
|
||||
if (data.c2 !== null && !prev.some(g => g.id === data.c2?.id))
|
||||
return [...prev, {id: data.c2?.id, poule: data.poule}];
|
||||
return prev;
|
||||
})
|
||||
if (data.c1 !== null && !groupsRef.current.some(g => g.id === data.c1?.id))
|
||||
setGroups(prev => [...prev, {id: data.c1?.id, poule: data.poule}]);
|
||||
if (data.c2 !== null && !groupsRef.current.some(g => g.id === data.c2?.id))
|
||||
setGroups(prev => [...prev, {id: data.c2?.id, poule: data.poule}]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +103,7 @@ export function CategoryContent({cat, catId, setCat, menuActions}) {
|
||||
name: data.name,
|
||||
liceName: data.liceName,
|
||||
type: data.type,
|
||||
trees: data.trees.sort((a, b) => a.level - b.level).map(d => from_sendTree(d, true))
|
||||
trees: data.trees.map(d => from_sendTree(d, true))
|
||||
})
|
||||
|
||||
let matches2 = [];
|
||||
@ -150,7 +142,7 @@ export function CategoryContent({cat, catId, setCat, menuActions}) {
|
||||
|
||||
return <>
|
||||
<div className="col-md-3">
|
||||
<AddComb groups={groups} setGroups={setGroups} removeGroup={removeGroup} menuActions={menuActions}/>
|
||||
<AddComb groups={groups} setGroups={setGroups} removeGroup={removeGroup}/>
|
||||
</div>
|
||||
<div className="col-md-9">
|
||||
{cat && <ListMatch cat={cat} matches={matches} groups={groups} reducer={reducer}/>}
|
||||
@ -158,7 +150,7 @@ export function CategoryContent({cat, catId, setCat, menuActions}) {
|
||||
</>
|
||||
}
|
||||
|
||||
function AddComb({groups, setGroups, removeGroup, menuActions}) {
|
||||
function AddComb({groups, setGroups, removeGroup}) {
|
||||
const {data, setData} = useRequestWS("getRegister", null)
|
||||
const combDispatch = useCombsDispatch()
|
||||
const {dispatch} = useWS()
|
||||
@ -192,21 +184,6 @@ function AddComb({groups, setGroups, removeGroup, menuActions}) {
|
||||
if (data === null)
|
||||
return;
|
||||
combDispatch({type: 'SET_ALL', payload: {source: "register", data: data}});
|
||||
|
||||
const resourceList = []
|
||||
data.forEach(d => {
|
||||
if (d.club_uuid) {
|
||||
const url = `${vite_url}/api/club/${d.club_uuid}/logo`;
|
||||
if (!resourceList.some(d => d.url === url))
|
||||
resourceList.push({url: url, name: `club_${d.club_uuid}.png`});
|
||||
}
|
||||
if (d.country) {
|
||||
const url = `/flags/svg/${d.country.toLowerCase()}.svg`;
|
||||
if (!resourceList.some(d => d.url === url))
|
||||
resourceList.push({url: url, name: `flag_${d.country.toLowerCase()}.svg`});
|
||||
}
|
||||
})
|
||||
menuActions.current.resourceList = resourceList
|
||||
}, [data]);
|
||||
|
||||
return <>
|
||||
@ -577,7 +554,7 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
<tr>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">N°</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Poule</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Zone</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Lice</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col">Rouge</th>
|
||||
<th style={{textAlign: "center"}} scope="col">Blue</th>
|
||||
|
||||
@ -66,7 +66,7 @@ function HomeComp() {
|
||||
<LoadingProvider>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home2 perm={perm}/>}/>
|
||||
<Route path="/admin" element={<CMAdmin compUuid={compUuid}/>}/>
|
||||
<Route path="/admin" element={<CMAdmin/>}/>
|
||||
<Route path="/table" element={<CMTable/>}/>
|
||||
</Routes>
|
||||
</LoadingProvider>
|
||||
@ -95,7 +95,7 @@ function WSStatus({setPerm}) {
|
||||
return () => dispatch({type: 'removeListener', payload: welcomeListener})
|
||||
}, [])
|
||||
|
||||
return <div className="row" style={{marginRight: "inherit"}}>
|
||||
return <div className="row">
|
||||
<h2 className="col">{name}</h2>
|
||||
<div className="col-auto" style={{margin: "auto 0", padding: 0}}>Serveur: <ColoredCircle
|
||||
color={isReady ? (inWait ? "#ffad32" : "#00c700") : "#e50000"}/>
|
||||
|
||||
@ -14,16 +14,16 @@ const text2Style = {fontSize: "min(1.7vw, 7vh)", fontWeight: "bold"};
|
||||
|
||||
export function PubAffWindow({document}) {
|
||||
const chronoText = useRef(null)
|
||||
const state2 = useRef({lastColor: "#ffffff", lastTimeStr: "--:--"})
|
||||
const state2 = useRef({lastColor: "white", lastTimeStr: "--:--"})
|
||||
const state = usePubAffState();
|
||||
|
||||
document.title = "Affichage Public";
|
||||
document.title = "A React portal window"
|
||||
document.body.className = "bg-dark text-white overflow-hidden";
|
||||
|
||||
state.timeCb = (payload) => {
|
||||
state2.current = {lastColor: payload.timeColor === "#000000" ? "#ffffff" : payload.timeColor, lastTimeStr: payload.timeStr}
|
||||
state2.current = {lastColor: payload.timeColor === "black" ? "white" : payload.timeColor, lastTimeStr: payload.timeStr}
|
||||
chronoText.current.textContent = payload.timeStr
|
||||
chronoText.current.style.color = payload.timeColor === "#000000" ? "#ffffff" : payload.timeColor
|
||||
chronoText.current.style.color = payload.timeColor === "black" ? "white" : payload.timeColor
|
||||
}
|
||||
|
||||
const showScore = state.showScore ?? true;
|
||||
|
||||
@ -153,31 +153,3 @@ export function timePrint(time, negSign = false) {
|
||||
String(min).padStart(2, '0') + ":" +
|
||||
String(sec).padStart(2, '0')
|
||||
}
|
||||
|
||||
//create full hex
|
||||
function fullHex (hex) {
|
||||
let r = hex.slice(1,2);
|
||||
let g = hex.slice(2,3);
|
||||
let b = hex.slice(3,4);
|
||||
|
||||
r = parseInt(r+r, 16);
|
||||
g = parseInt(g+g, 16);
|
||||
b = parseInt(b+b, 16);
|
||||
|
||||
// return {r, g, b}
|
||||
return { r, g, b };
|
||||
}
|
||||
|
||||
//convert hex to rgb
|
||||
export function hex2rgb (hex) {
|
||||
if(hex.length === 4){
|
||||
return fullHex(hex);
|
||||
}
|
||||
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
|
||||
// return {r, g, b}
|
||||
return { r, g, b };
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user