diff --git a/backend/pom.xml b/backend/pom.xml index 1a815ca..c8b3106 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -52,15 +52,45 @@ ai.timefold.solver timefold-solver-quarkus-jackson + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + ai.timefold.solver + timefold-solver-test + 1.13.0 + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + 3.26.3 + test + io.quarkus - quarkus-junit5 - test + quarkus-jdbc-postgresql - io.rest-assured - rest-assured - test + io.quarkus + quarkus-hibernate-orm-panache @@ -91,10 +121,10 @@ - native - - native - + native + + native + \ No newline at end of file diff --git a/backend/src/main/java/org/acme/GreetingResource.java b/backend/src/main/java/org/acme/GreetingResource.java deleted file mode 100644 index 244f294..0000000 --- a/backend/src/main/java/org/acme/GreetingResource.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.acme; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; - -@Path("/hello") -public class GreetingResource { - - @GET - @Produces(MediaType.TEXT_PLAIN) - public String hello() { - return "Hello from Quarkus REST"; - } -} diff --git a/backend/src/main/java/org/acme/TimetableResource.java b/backend/src/main/java/org/acme/TimetableResource.java new file mode 100644 index 0000000..61d86e0 --- /dev/null +++ b/backend/src/main/java/org/acme/TimetableResource.java @@ -0,0 +1,95 @@ +package org.acme; + +import ai.timefold.solver.core.api.solver.SolverManager; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.acme.domain.Room; +import org.acme.domain.Student; +import org.acme.domain.Timetable; +import org.acme.domain.Unit; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.LocalTime; +import java.util.List; +import java.util.concurrent.ExecutionException; + +/** + * Entry to the timetabling program. + * Receives a timetabling problem and outputs the solution + * with the best optimised scores according to the provided constraints. + * + * @author Jet Edge + */ +@Path("/timetabling") +public class TimetableResource { + + @Inject + SolverManager solverManager; + + private int jobId = 0; + + @POST + public Timetable handleRequest(Timetable problem) throws ExecutionException, InterruptedException { + jobId += 1; + String name = "Job" + Integer.toString(jobId); + + Timetable solution = solverManager.solve(name, problem).getFinalBestSolution(); + return solution; + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Timetable solveExample() throws ExecutionException, InterruptedException { + + Student a = new Student("a"); + Student b = new Student("b"); + Student c = new Student("c"); + Student d = new Student("d"); + Student e = new Student("e"); + Student f = new Student("f"); + Student g = new Student("g"); + Student h = new Student("h"); + Student i = new Student("i"); + + Room r1 = new Room("Room1", 2, true); + Room r2 = new Room("Room2", 4, false); + Room r3 = new Room("Room3", 4, false); + + var problem = new Timetable( + List.of( + new Unit(1, "1", Duration.ofHours(2), List.of(a, b), true), + new Unit(2, "2", Duration.ofHours(2), List.of(a, c, d, e), true), + new Unit(3, "3", Duration.ofHours(2), List.of(f, g, h, i), false), + new Unit(4, "4", Duration.ofHours(2), List.of(a, b), false) +// new Unit(5, "5", Duration.ofHours(2), List.of(c, d, e)), +// new Unit(6, "6", Duration.ofHours(2), List.of(f, g, h, i)) + ), + + List.of( + DayOfWeek.MONDAY, + DayOfWeek.TUESDAY, + DayOfWeek.WEDNESDAY +// DayOfWeek.THURSDAY, +// DayOfWeek.FRIDAY + ), + + List.of( + LocalTime.of(15, 0) +// LocalTime.of(17, 0) +// LocalTime.of(16,0), +// LocalTime.of(23,0) + ), + List.of(r1, r2, r3) + ); + + Timetable solution = solverManager.solve("job 1", problem).getFinalBestSolution(); + + return solution; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/domain/ConflictingUnit.java b/backend/src/main/java/org/acme/domain/ConflictingUnit.java new file mode 100644 index 0000000..14be70b --- /dev/null +++ b/backend/src/main/java/org/acme/domain/ConflictingUnit.java @@ -0,0 +1,62 @@ +package org.acme.domain; + +/** + * Represents a pair of conflicting units, which are units with common students. + * + * @author Jet Edge + */ +public class ConflictingUnit { + private Unit unit1; + + private Unit unit2; + + private int numStudent; + + /** + * Creates a pair of conflicting units. + * + * @param first The first unit. + * @param second The second unit. + */ + public ConflictingUnit(Unit first, Unit second) { + this.unit1 = first; + this.unit2 = second; + } + + /** + * Creates a pair of conflicting units. + * + * @param first The first unit. + * @param second The second unit. + * @param numStudent The number of common students. + */ + public ConflictingUnit(Unit first, Unit second, int numStudent) { + this.unit1 = first; + this.unit2 = second; + this.numStudent = numStudent; + } + + public Unit getUnit1() { + return unit1; + } + + public void setUnit1(Unit unit1) { + this.unit1 = unit1; + } + + public Unit getUnit2() { + return unit2; + } + + public void setUnit2(Unit unit2) { + this.unit2 = unit2; + } + + public int getNumStudent() { + return numStudent; + } + + public void setNumStudent(int numStudent) { + this.numStudent = numStudent; + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/domain/Room.java b/backend/src/main/java/org/acme/domain/Room.java new file mode 100644 index 0000000..57c7bbd --- /dev/null +++ b/backend/src/main/java/org/acme/domain/Room.java @@ -0,0 +1,56 @@ +package org.acme.domain; + +import ai.timefold.solver.core.api.domain.lookup.PlanningId; + +/** + * Represents a room. + * + * @author Jet Edge + */ +public class Room { + @PlanningId + private String id; + + private int capacity; + private boolean isLab; + + public Room() { + } + + /** + * Creates a room with its ID and capacity. + * + * @param id The room’s id. + * @param capacity The room's capacity. + * @param isLab Whether the room is a laboratory. + */ + public Room(String id, int capacity, boolean isLab) { + this.id = id; + this.capacity = capacity; + this.isLab = isLab; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getCapacity() { + return capacity; + } + + public void setCapacity(int capacity) { + this.capacity = capacity; + } + + public boolean isLab() { + return isLab; + } + + public void setLab(boolean lab) { + isLab = lab; + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/domain/Student.java b/backend/src/main/java/org/acme/domain/Student.java new file mode 100644 index 0000000..42093dd --- /dev/null +++ b/backend/src/main/java/org/acme/domain/Student.java @@ -0,0 +1,48 @@ +package org.acme.domain; + +import java.util.Objects; + +/** + * Represents a student. + * + * @author Jet Edge + */ +public class Student { + + // String studentID; + + String name; + + public Student() { + } + + /** + * Creates a student with the specified name. + * + * @param name The student’s name. + */ + public Student(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Student student = (Student) o; + return Objects.equals(name, student.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/domain/Timetable.java b/backend/src/main/java/org/acme/domain/Timetable.java new file mode 100644 index 0000000..b6f9826 --- /dev/null +++ b/backend/src/main/java/org/acme/domain/Timetable.java @@ -0,0 +1,148 @@ +package org.acme.domain; + +import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; +import ai.timefold.solver.core.api.domain.solution.PlanningScore; +import ai.timefold.solver.core.api.domain.solution.PlanningSolution; +import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; +import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; + +import java.time.DayOfWeek; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a timetable, the solution from the program. + * + * @author Jet Edge + */ +@PlanningSolution +public class Timetable { + + @ValueRangeProvider + private List daysOfWeek; + @ValueRangeProvider + private List startTimes; + + @ProblemFactCollectionProperty + @ValueRangeProvider + private List rooms; + + @PlanningEntityCollectionProperty + private List units; + + @PlanningScore + private HardSoftScore score; + + public Timetable() { + + } + + /** + * Creates a timetable. + * + * @param units The list of units to be allocated. + * @param startTimes The list of available starting times. + */ + public Timetable(List units, List startTimes) { + this.units = units; + this.startTimes = startTimes; + } + + /** + * Creates a timetable. + * + * @param units The list of units to be allocated. + * @param startTimes The list of available starting times. + * @param rooms The list of available rooms. + */ + public Timetable(List units, List startTimes, List rooms) { + this.units = units; + this.startTimes = startTimes; + this.rooms = rooms; + } + + /** + * Creates a timetable. + * + * @param units The list of units to be allocated. + * @param daysOfWeek The list of available days of the week. + * @param startTimes The list of available starting times. + * @param rooms The list of available rooms. + */ + public Timetable(List units, List daysOfWeek, List startTimes, List rooms) { + this.units = units; + this.daysOfWeek = daysOfWeek; + this.startTimes = startTimes; + this.rooms = rooms; + } + + public List getDaysOfWeek() { + return daysOfWeek; + } + + public void setDaysOfWeek(List daysOfWeek) { + this.daysOfWeek = daysOfWeek; + } + + public List getStartTimes() { + return startTimes; + } + + public void setStartTimes(List startTimes) { + this.startTimes = startTimes; + } + + public List getRooms() { + return rooms; + } + + public void setRooms(List rooms) { + this.rooms = rooms; + } + + public List getUnits() { + return units; + } + + public void setUnits(List units) { + this.units = units; + } + + public HardSoftScore getScore() { + return score; + } + + public void setScore(HardSoftScore score) { + this.score = score; + } + + /** + * Identify conflicting units having common students at the same time. + * + * @return A list of conflicting units. + */ + @ProblemFactCollectionProperty + public List calculateSoftUnitConflictList() { + ArrayList out = new ArrayList(); + for (var first : units) { + for (var second : units) { + if (first.getUnitID() >= second.getUnitID()) { + continue; + } + int numStudents = 0; + for (Student firstStudent : first.getStudents()) { + if (second.getStudents().contains(firstStudent)) { + numStudents++; + } + } + if (numStudents > 0) { + out.add(new ConflictingUnit(first, second, numStudents)); + } + } + } + return out; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/domain/Unit.java b/backend/src/main/java/org/acme/domain/Unit.java new file mode 100644 index 0000000..c6b955b --- /dev/null +++ b/backend/src/main/java/org/acme/domain/Unit.java @@ -0,0 +1,167 @@ +package org.acme.domain; + +import ai.timefold.solver.core.api.domain.entity.PlanningEntity; +import ai.timefold.solver.core.api.domain.lookup.PlanningId; +import ai.timefold.solver.core.api.domain.variable.PlanningVariable; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.LocalTime; +import java.util.List; + + +/** + * Represents a unit. + * + * @author Jet Edge + */ +@PlanningEntity +public class Unit { + + private List students; + @PlanningId + private int unitID; + private String name; + private Duration duration; + @PlanningVariable + private DayOfWeek dayOfWeek; + @PlanningVariable + private LocalTime startTime; + @PlanningVariable + private Room room; + + private boolean wantsLab; + + public Unit() { + } + + /** + * Creates a unit. + * + * @param unitID The unit’s ID. + * @param name The unit’s ID. + * @param duration The unit’s duration. + * @param students The list of students enrolled in the unit. + */ + public Unit(int unitID, String name, Duration duration, List students) { + this.unitID = unitID; + this.name = name; + this.duration = duration; + this.students = students; + } + + /** + * Creates a unit. + * + * @param unitID The unit’s ID. + * @param name The unit’s ID. + * @param duration The unit’s duration. + * @param students The list of students enrolled in the unit. + * @param wantsLab Whether the unit wants a laboratory room. + */ + public Unit(int unitID, String name, Duration duration, List students, boolean wantsLab) { + this.unitID = unitID; + this.name = name; + this.duration = duration; + this.students = students; + this.wantsLab = wantsLab; + } + + /** + * Creates a unit. + * + * @param unitID The unit’s ID. + * @param name The unit’s ID. + * @param duration The unit’s duration. + * @param students The list of students enrolled in the unit. + * @param wantsLab Whether the unit wants a laboratory room. + * @param room The unit's room. + */ + public Unit(int unitID, String name, DayOfWeek dayOfWeek, LocalTime startTime, Duration duration, List students, boolean wantsLab, Room room) { + this.unitID = unitID; + this.name = name; + this.dayOfWeek = dayOfWeek; + this.startTime = startTime; + this.duration = duration; + this.students = students; + this.wantsLab = wantsLab; + this.room = room; + } + + public int getUnitID() { + return unitID; + } + + public void setUnitID(int unitID) { + this.unitID = unitID; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Duration getDuration() { + return duration; + } + + public void setDuration(Duration duration) { + this.duration = duration; + } + + public DayOfWeek getDayOfWeek() { + return dayOfWeek; + } + + public void setDayOfWeek(DayOfWeek dayOfWeek) { + this.dayOfWeek = dayOfWeek; + } + + public LocalTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalTime startTime) { + this.startTime = startTime; + } + + public LocalTime getEnd() { + return startTime.plus(duration); + } + + public List getStudents() { + return students; + } + + public void setStudents(List students) { + this.students = students; + } + + /** + * Get the number of students enrolled in the unit. + * + * @return An int representing the number of students. + */ + public int getStudentSize() { + return students.size(); + } + + public Room getRoom() { + return room; + } + + public void setRoom(Room room) { + this.room = room; + } + + public boolean isWantsLab() { + return wantsLab; + } + + public void setWantsLab(boolean wantsLab) { + this.wantsLab = wantsLab; + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/schooltimetabling/domain/Campus.java b/backend/src/main/java/org/acme/schooltimetabling/domain/Campus.java new file mode 100644 index 0000000..d4c49cc --- /dev/null +++ b/backend/src/main/java/org/acme/schooltimetabling/domain/Campus.java @@ -0,0 +1,21 @@ +package org.acme.schooltimetabling.domain; + +// import java.util.List; +import jakarta.persistence.*; +import io.quarkus.hibernate.orm.panache.PanacheEntity; + +@Entity +public class Campus extends PanacheEntity { + + public String name; + + // empty constructor + public Campus() { + } + + // constructor with name input + public Campus(String name) { + this.name = name; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/schooltimetabling/domain/CampusResource.java b/backend/src/main/java/org/acme/schooltimetabling/domain/CampusResource.java new file mode 100644 index 0000000..7b380c9 --- /dev/null +++ b/backend/src/main/java/org/acme/schooltimetabling/domain/CampusResource.java @@ -0,0 +1,30 @@ +package org.acme.schooltimetabling.domain; + +import java.util.List; + +import jakarta.transaction.Transactional; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +@Path("/campuses") +public class CampusResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + public List list() { + return Campus.listAll(); + } + + @POST + @Transactional + @Consumes(MediaType.APPLICATION_JSON) + public Response createCampus(Campus campus) { + campus.persist(); + return Response.status(Response.Status.CREATED).entity(campus).build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/schooltimetabling/domain/Lesson.java b/backend/src/main/java/org/acme/schooltimetabling/domain/Lesson.java deleted file mode 100644 index 59ba6ec..0000000 --- a/backend/src/main/java/org/acme/schooltimetabling/domain/Lesson.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.acme.schooltimetabling.domain; - -import ai.timefold.solver.core.api.domain.entity.PlanningEntity; -import ai.timefold.solver.core.api.domain.lookup.PlanningId; -import ai.timefold.solver.core.api.domain.variable.PlanningVariable; - -@PlanningEntity -public class Lesson { - - @PlanningId - private String id; - - private String subject; - private String teacher; - private String studentGroup; - - @PlanningVariable - private Timeslot timeslot; - @PlanningVariable - private Room room; - - public Lesson() { - } - - public Lesson(String id, String subject, String teacher, String studentGroup) { - this.id = id; - this.subject = subject; - this.teacher = teacher; - this.studentGroup = studentGroup; - } - - public String getId() { - return id; - } - - public String getSubject() { - return subject; - } - - public String getTeacher() { - return teacher; - } - - public String getStudentGroup() { - return studentGroup; - } - - public Timeslot getTimeslot() { - return timeslot; - } - - public void setTimeslot(Timeslot timeslot) { - this.timeslot = timeslot; - } - - public Room getRoom() { - return room; - } - - public void setRoom(Room room) { - this.room = room; - } - - @Override - public String toString() { - return subject + "(" + id + ")"; - } - -} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/schooltimetabling/domain/Room.java b/backend/src/main/java/org/acme/schooltimetabling/domain/Room.java deleted file mode 100644 index 84dd324..0000000 --- a/backend/src/main/java/org/acme/schooltimetabling/domain/Room.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.acme.schooltimetabling.domain; - -public class Room { - - private String name; - - public Room() { - } - - public Room(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - @Override - public String toString() { - return name; - } - -} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java b/backend/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java deleted file mode 100644 index dc9bdb3..0000000 --- a/backend/src/main/java/org/acme/schooltimetabling/domain/Timeslot.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.acme.schooltimetabling.domain; - -import java.time.DayOfWeek; -import java.time.LocalTime; - -public class Timeslot { - - private DayOfWeek dayOfWeek; - private LocalTime startTime; - private LocalTime endTime; - - public Timeslot() { - } - - public Timeslot(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) { - this.dayOfWeek = dayOfWeek; - this.startTime = startTime; - this.endTime = endTime; - } - - public DayOfWeek getDayOfWeek() { - return dayOfWeek; - } - - public LocalTime getStartTime() { - return startTime; - } - - public LocalTime getEndTime() { - return endTime; - } - - @Override - public String toString() { - return dayOfWeek + " " + startTime; - } - -} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/schooltimetabling/domain/Timetable.java b/backend/src/main/java/org/acme/schooltimetabling/domain/Timetable.java deleted file mode 100644 index 16e5500..0000000 --- a/backend/src/main/java/org/acme/schooltimetabling/domain/Timetable.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.acme.schooltimetabling.domain; - -import java.util.List; - -import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty; -import ai.timefold.solver.core.api.domain.solution.PlanningScore; -import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; -import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; -import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; - -@PlanningSolution -public class Timetable { - - @ValueRangeProvider - @ProblemFactCollectionProperty - private List timeslots; - @ValueRangeProvider - @ProblemFactCollectionProperty - private List rooms; - @PlanningEntityCollectionProperty - private List lessons; - - @PlanningScore - private HardSoftScore score; - - public Timetable() { - } - - public Timetable(List timeslots, List rooms, List lessons) { - this.timeslots = timeslots; - this.rooms = rooms; - this.lessons = lessons; - } - - public List getTimeslots() { - return timeslots; - } - - public List getRooms() { - return rooms; - } - - public List getLessons() { - return lessons; - } - - public HardSoftScore getScore() { - return score; - } - -} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/schooltimetabling/rest/TimetableResource.java b/backend/src/main/java/org/acme/schooltimetabling/rest/TimetableResource.java deleted file mode 100644 index 66d8ef9..0000000 --- a/backend/src/main/java/org/acme/schooltimetabling/rest/TimetableResource.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.acme.schooltimetabling.rest; - -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import jakarta.inject.Inject; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; - -import org.acme.schooltimetabling.domain.Timetable; -import ai.timefold.solver.core.api.solver.SolverJob; -import ai.timefold.solver.core.api.solver.SolverManager; - -@Path("/timetables") -public class TimetableResource { - - @Inject - SolverManager solverManager; - - @POST - @Path("/solve") - public Timetable solve(Timetable problem) { - UUID problemId = UUID.randomUUID(); - // Submit the problem to start solving - SolverJob solverJob = solverManager.solve(problemId, problem); - Timetable solution; - try { - // Wait until the solving ends - solution = solverJob.getFinalBestSolution(); - } catch (InterruptedException | ExecutionException e) { - throw new IllegalStateException("Solving failed.", e); - } - return solution; - } - -} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/schooltimetabling/solver/TimetableConstraintProvider.java b/backend/src/main/java/org/acme/schooltimetabling/solver/TimetableConstraintProvider.java deleted file mode 100644 index 6344e9c..0000000 --- a/backend/src/main/java/org/acme/schooltimetabling/solver/TimetableConstraintProvider.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.acme.schooltimetabling.solver; - -import org.acme.schooltimetabling.domain.Lesson; -import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; -import ai.timefold.solver.core.api.score.stream.Constraint; -import ai.timefold.solver.core.api.score.stream.ConstraintFactory; -import ai.timefold.solver.core.api.score.stream.ConstraintProvider; -import ai.timefold.solver.core.api.score.stream.Joiners; - -public class TimetableConstraintProvider implements ConstraintProvider { - - @Override - public Constraint[] defineConstraints(ConstraintFactory constraintFactory) { - return new Constraint[] { - // Hard constraints - roomConflict(constraintFactory), - teacherConflict(constraintFactory), - studentGroupConflict(constraintFactory), - // Soft constraints are only implemented in the timefold-quickstarts code - }; - } - - private Constraint roomConflict(ConstraintFactory constraintFactory) { - // A room can accommodate at most one lesson at the same time. - return constraintFactory - // Select each pair of 2 different lessons ... - .forEachUniquePair(Lesson.class, - // ... in the same timeslot ... - Joiners.equal(Lesson::getTimeslot), - // ... in the same room ... - Joiners.equal(Lesson::getRoom)) - // ... and penalize each pair with a hard weight. - .penalize(HardSoftScore.ONE_HARD) - .asConstraint("Room conflict"); - } - - private Constraint teacherConflict(ConstraintFactory constraintFactory) { - // A teacher can teach at most one lesson at the same time. - return constraintFactory - .forEachUniquePair(Lesson.class, - Joiners.equal(Lesson::getTimeslot), - Joiners.equal(Lesson::getTeacher)) - .penalize(HardSoftScore.ONE_HARD) - .asConstraint("Teacher conflict"); - } - - private Constraint studentGroupConflict(ConstraintFactory constraintFactory) { - // A student can attend at most one lesson at the same time. - return constraintFactory - .forEachUniquePair(Lesson.class, - Joiners.equal(Lesson::getTimeslot), - Joiners.equal(Lesson::getStudentGroup)) - .penalize(HardSoftScore.ONE_HARD) - .asConstraint("Student group conflict"); - } - -} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/solver/TimetableConstraintProvider.java b/backend/src/main/java/org/acme/solver/TimetableConstraintProvider.java new file mode 100644 index 0000000..e97de85 --- /dev/null +++ b/backend/src/main/java/org/acme/solver/TimetableConstraintProvider.java @@ -0,0 +1,109 @@ +package org.acme.solver; + +import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.stream.Constraint; +import ai.timefold.solver.core.api.score.stream.ConstraintFactory; +import ai.timefold.solver.core.api.score.stream.ConstraintProvider; +import org.acme.domain.ConflictingUnit; +import org.acme.domain.Unit; + +import java.util.function.Function; + +import static ai.timefold.solver.core.api.score.stream.Joiners.equal; +import static ai.timefold.solver.core.api.score.stream.Joiners.overlapping; + +/** + * Provides the constraints for the timetabling problem. + * + * @author Jet Edge + */ +public class TimetableConstraintProvider implements ConstraintProvider { + + /** + * Enable the various implemented constraints. + * + * @return A list of constraints. + */ + @Override + public Constraint[] defineConstraints(ConstraintFactory constraintFactory) { + return new Constraint[]{ + studentConflict(constraintFactory), + roomConflict(constraintFactory), + roomCapacity(constraintFactory), + labPreference(constraintFactory) + }; + } + + /** + * Penalize 1 hard score for each student with overlapping units. + */ + public Constraint studentConflict(ConstraintFactory constraintFactory) { + // A student can be in at most one unit at the same time. + return constraintFactory + // Select each pair of conflicting units. + .forEach(ConflictingUnit.class) + // Find the first unit. + .join(Unit.class, equal(ConflictingUnit::getUnit1, Function.identity())) + // Find the second unit. + .join(Unit.class, equal((conflictingUnit, unit1) -> conflictingUnit.getUnit2(), Function.identity()), + // Check if the 2 units are on the same weekday ... + equal((conflictingUnit, unit1) -> unit1.getDayOfWeek(), Unit::getDayOfWeek), + // ... in the same timeslot ... + overlapping((conflictingUnit, unit1) -> unit1.getStartTime(), + (conflictingUnit, unit1) -> unit1.getEnd(), + Unit::getStartTime, Unit::getEnd)) + // ... and penalize each pair with a hard weight. + .penalize(HardSoftScore.ofHard(1), + (conflictingUnit, unit1, unit2) -> conflictingUnit.getNumStudent()) + .asConstraint("Student conflict"); + + } + + /** + * Penalize 1 hard score for each room with overlapping units. + */ + public Constraint roomConflict(ConstraintFactory constraintFactory) { + // A room can accommodate at most one unit at the same time. + return constraintFactory + // Select each pair of 2 different units ... + .forEachUniquePair(Unit.class, + // ... on the same weekday ... + equal(Unit::getDayOfWeek), + // ... in the same timeslot ... + overlapping(Unit::getStartTime, Unit::getEnd), + // ... in the same room ... + equal(Unit::getRoom)) + // ... and penalize each pair with a hard weight. + .penalize(HardSoftScore.ofHard(1)) + .asConstraint("Room conflict"); + } + + /** + * Penalize 1 soft score for each student overflowing the capacity of the room. + */ + public Constraint roomCapacity(ConstraintFactory constraintFactory) { + // A room cannot accommodate more students than its capacity. + return constraintFactory + .forEach(Unit.class) + .filter(unit -> unit.getStudentSize() > unit.getRoom().getCapacity()) + .penalize(HardSoftScore.ofSoft(1), + unit -> unit.getStudentSize() - unit.getRoom().getCapacity()) + .asConstraint("Room capacity conflict"); + } + + /** + * Penalize 1 soft score for each laboratory unit not assigned to a laboratory. + */ + public Constraint labPreference(ConstraintFactory constraintFactory) { + // Some units prefer to have a laboratory room. + return constraintFactory + .forEach(Unit.class) + // Select a laboratory unit ... + .filter(Unit::isWantsLab) + // ... in a non-lab room ... + .filter(unit -> !unit.getRoom().isLab()) + .penalize(HardSoftScore.ofSoft(1)) + .asConstraint("Unit laboratory preference"); + } + +} \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index c99914e..43e49f3 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -4,4 +4,13 @@ quarkus.timefold.solver.termination.spent-limit=5s quarkus.http.cors=true quarkus.http.cors.origins=* quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS -quarkus.http.cors.headers=Content-Type,Authorization \ No newline at end of file +quarkus.http.cors.headers=Content-Type,Authorization + +# datasource config +quarkus.datasource.db-kind = postgresql +quarkus.datasource.username = ${QUARKUS_DATASOURCE_USERNAME} +quarkus.datasource.password = ${QUARKUS_DATASOURCE_PASSWORD} +quarkus.datasource.jdbc.url = ${QUARKUS_DATASOURCE_JDBC_URL} + +# drop and create the database at startup (use `update` to only update the schema) +quarkus.hibernate-orm.database.generation = update \ No newline at end of file diff --git a/backend/src/test/java/org/acme/GreetingResourceIT.java b/backend/src/test/java/org/acme/GreetingResourceIT.java deleted file mode 100644 index cfa9d1b..0000000 --- a/backend/src/test/java/org/acme/GreetingResourceIT.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.acme; - -import io.quarkus.test.junit.QuarkusIntegrationTest; - -@QuarkusIntegrationTest -class GreetingResourceIT extends GreetingResourceTest { - // Execute the same tests but in packaged mode. -} diff --git a/backend/src/test/java/org/acme/GreetingResourceTest.java b/backend/src/test/java/org/acme/GreetingResourceTest.java deleted file mode 100644 index 18332e4..0000000 --- a/backend/src/test/java/org/acme/GreetingResourceTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.acme; - -import io.quarkus.test.junit.QuarkusTest; -import org.junit.jupiter.api.Test; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; - -@QuarkusTest -class GreetingResourceTest { - @Test - void testHelloEndpoint() { - given() - .when().get("/hello") - .then() - .statusCode(200) - .body(is("Hello from Quarkus REST")); - } - -} \ No newline at end of file diff --git a/backend/src/test/java/org/acme/solver/TimetableConstraintProviderTest.java b/backend/src/test/java/org/acme/solver/TimetableConstraintProviderTest.java new file mode 100644 index 0000000..4ab03fc --- /dev/null +++ b/backend/src/test/java/org/acme/solver/TimetableConstraintProviderTest.java @@ -0,0 +1,86 @@ +package org.acme.solver; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.LocalTime; +import java.util.List; + +import org.acme.domain.*; + +import jakarta.inject.Inject; +import ai.timefold.solver.test.api.score.stream.ConstraintVerifier; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class TimetableConstraintProviderTest { + private static final Room ROOM1 = new Room("1", 2, true); + private static final Room ROOM2 = new Room("2", 2, false); + private static final Room ROOM3 = new Room("3", 2, true); + private static final Student STUDENT1 = new Student("student1"); + private static final Student STUDENT2 = new Student("student2"); + private static final Student STUDENT3 = new Student("student3"); + private static final Student STUDENT4 = new Student("student4"); + private static final Student STUDENT5 = new Student("student5"); + private static final DayOfWeek DAY_OF_WEEK = DayOfWeek.MONDAY; + private static final LocalTime START_TIME = LocalTime.of(8, 0); + private static final Duration DURATION = Duration.ofMinutes(120); + + @Inject + ConstraintVerifier constraintVerifier; + + /** + * Student Constraint: Penalize 1 if a student is attending two units at the same time. + */ + @Test + void studentConflict() { + Unit firstUnit = new Unit(1, "unit1", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT1), true, ROOM1); + Unit conflictingUnit = new Unit(2, "unit2", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT1), false, ROOM2); + Unit nonConflictingUnit = new Unit(3, "unit3", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT2), true, ROOM3); + ConflictingUnit conflictingUnitPair = new ConflictingUnit(firstUnit, conflictingUnit, 1); + + constraintVerifier.verifyThat(TimetableConstraintProvider::studentConflict) + .given(firstUnit, conflictingUnit, nonConflictingUnit, conflictingUnitPair) + .penalizesBy(1); + } + + /** + * Room Constraint: Penalize 1 if a room is occupied by two units at the same time. + */ + @Test + void roomConflict() { + Unit firstUnit = new Unit(1, "unit1", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT1), true, ROOM1); + Unit conflictingUnit = new Unit(2, "unit2", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT2), true, ROOM1); + Unit nonConflictingUnit = new Unit(3, "unit3", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT3), false, ROOM2); + constraintVerifier.verifyThat(TimetableConstraintProvider::roomConflict) + .given(firstUnit, conflictingUnit, nonConflictingUnit) + .penalizesBy(1); + } + + /** + * Room Capacity Constraint: Penalize 1 if a room is occupied by more students than its capacity. + */ + @Test + void roomCapacityConflict() { + Unit firstUnit = new Unit(1, "unit1", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT1), true, ROOM1); + Unit conflictingUnit = new Unit(2, "unit2", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT2, STUDENT3, STUDENT4), false, ROOM2); + Unit nonConflictingUnit = new Unit(3, "unit3", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT5), true, ROOM3); + constraintVerifier.verifyThat(TimetableConstraintProvider::roomCapacity) + .given(firstUnit, conflictingUnit, nonConflictingUnit) + .penalizesBy(1); + } + + /** + * Lab Constraint: Penalize 1 if a non-lab room is occupied by a lab unit. + */ + @Test + void labConflict() { + Unit firstUnit = new Unit(1, "unit1", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT1), true, ROOM1); + Unit conflictingUnit = new Unit(2, "unit2", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT2), true, ROOM2); + Unit nonConflictingUnit = new Unit(3, "unit3", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT5), true, ROOM3); + constraintVerifier.verifyThat(TimetableConstraintProvider::labPreference) + .given(firstUnit, conflictingUnit, nonConflictingUnit) + .penalizesBy(1); + } +} diff --git a/frontend/Procfile b/frontend/Procfile new file mode 100644 index 0000000..bef5264 --- /dev/null +++ b/frontend/Procfile @@ -0,0 +1 @@ +web: npm run start \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index edf8bbb..47ad553 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,10 +13,14 @@ "@mui/base": "^5.0.0-beta.40", "@mui/icons-material": "^5.16.7", "@mui/material": "^5.16.7", + "dexie": "^4.0.8", + "dexie-react-hooks": "^1.1.7", "jspreadsheet-ce": "^4.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.26.1" + "react-router-dom": "^6.26.1", + "read-excel-file": "^5.8.5", + "serve": "^6.5.8" }, "devDependencies": { "@eslint/js": "^9.9.0", @@ -27,9 +31,11 @@ "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", + "moment": "^2.30.1", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", - "vite": "^5.4.1" + "vite": "^5.4.1", + "vitest": "^2.1.1" } }, "node_modules/@ampproject/remapping": { @@ -1447,9 +1453,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz", - "integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", "cpu": [ "arm" ], @@ -1461,9 +1467,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz", - "integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", "cpu": [ "arm64" ], @@ -1475,9 +1481,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz", - "integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", "cpu": [ "arm64" ], @@ -1489,9 +1495,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz", - "integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", "cpu": [ "x64" ], @@ -1503,9 +1509,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz", - "integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", "cpu": [ "arm" ], @@ -1517,9 +1523,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz", - "integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", "cpu": [ "arm" ], @@ -1531,9 +1537,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz", - "integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", "cpu": [ "arm64" ], @@ -1545,9 +1551,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz", - "integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", "cpu": [ "arm64" ], @@ -1559,9 +1565,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz", - "integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", "cpu": [ "ppc64" ], @@ -1573,9 +1579,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz", - "integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", "cpu": [ "riscv64" ], @@ -1587,9 +1593,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz", - "integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", "cpu": [ "s390x" ], @@ -1601,9 +1607,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz", - "integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", "cpu": [ "x64" ], @@ -1615,9 +1621,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz", - "integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", "cpu": [ "x64" ], @@ -1629,9 +1635,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz", - "integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", "cpu": [ "arm64" ], @@ -1643,9 +1649,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz", - "integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", "cpu": [ "ia32" ], @@ -1657,9 +1663,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz", - "integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", "cpu": [ "x64" ], @@ -2025,6 +2031,163 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/@vitest/expect": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.1.tgz", + "integrity": "sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.1", + "@vitest/utils": "2.1.1", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.1.tgz", + "integrity": "sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "^2.1.0-beta.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.11" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/spy": "2.1.1", + "msw": "^2.3.5", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.1.tgz", + "integrity": "sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.1.tgz", + "integrity": "sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.1", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.1.tgz", + "integrity": "sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.1", + "magic-string": "^0.30.11", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.1.tgz", + "integrity": "sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.1.tgz", + "integrity": "sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.1", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -2048,6 +2211,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2065,6 +2237,39 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", + "license": "MIT", + "optional": true, + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "license": "BSD-3-Clause OR MIT", + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha512-TdlOggdA/zURfMYa7ABC66j+oqfMew58KpJMbUlH3bcZP1b+cBHIHDDn5uH9INsxrHBPjsqM0tDB4jPTF/vgJA==", + "license": "ISC", + "dependencies": { + "string-width": "^2.0.0" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2087,6 +2292,32 @@ "node": ">=4" } }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/arg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz", + "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2094,6 +2325,44 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/args": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/args/-/args-4.0.0.tgz", + "integrity": "sha512-4b7lVF58nlo7sNtq8s2OueroOY/UHn0Nt/NVjsx9zn28u6yDVb9bQ/uy/5jKtHCbUDil4MlMyDLF5+OHEgnTug==", + "license": "MIT", + "dependencies": { + "camelcase": "5.0.0", + "chalk": "2.3.2", + "leven": "2.1.0", + "mri": "1.1.0" + }, + "engines": { + "node": "> 4.0.0" + } + }, + "node_modules/args/node_modules/chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/args/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2104,6 +2373,22 @@ "node": ">=8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "license": "MIT" + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -2126,6 +2411,57 @@ "dev": true, "license": "MIT" }, + "node_modules/basic-auth": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", + "integrity": "sha512-BNzMKPNNIV3+C02TSO9oILCAPS/htf/upL+TRV2gOnPwSQbFKuL249fwVozDNbSq/AeyoRoVeQgJcK4sN77aHg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, + "node_modules/boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2183,6 +2519,25 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2192,6 +2547,15 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001651", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", @@ -2213,6 +2577,37 @@ ], "license": "CC-BY-4.0" }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -2236,6 +2631,60 @@ "node": ">=0.8.0" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clipboardy": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.3.tgz", + "integrity": "sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==", + "license": "MIT", + "dependencies": { + "arch": "^2.1.0", + "execa": "^0.8.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", + "license": "ISC", + "optional": true, + "dependencies": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "node_modules/cliui/node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", + "license": "MIT/X11", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2260,6 +2709,51 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "license": "MIT" }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2267,12 +2761,27 @@ "dev": true, "license": "MIT" }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "license": "MIT" }, + "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==", + "license": "MIT" + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -2310,30 +2819,132 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, - "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "node_modules/dargs": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-5.1.0.tgz", + "integrity": "sha512-Mr5OxT76pdJv7BbLq3hF1gSP8zxlCyDA1afj2Iab2MPKmdAKV+aKGC8YJv6cT8ItdFXAf798JJQD7jmbWZcYTQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha512-Jlk9xvkTDGXwZiIDyoM7+3AsuvJVoyOpRupvEVy9nX3YO3/ieZxhlgh8GpLNZ8AY7HjO6y2YwpMSh1ejhu3uIw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "license": "MIT" + }, + "node_modules/detect-port": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.2.3.tgz", + "integrity": "sha512-IDbrX6PxqnYy8jV4wSHBaJlErYKTJvW8OQb9F7xivl1iQLqiUYHGa+nZ61Do6+N5uuOn/pReXKNqI9rUn04vug==", "license": "MIT", "dependencies": { - "ms": "2.1.2" + "address": "^1.0.1", + "debug": "^2.6.0" }, - "engines": { - "node": ">=6.0" + "bin": { + "detect": "bin/detect-port", + "detect-port": "bin/detect-port" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "engines": { + "node": ">= 4.2.1" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, + "node_modules/detect-port/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/detect-port/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/dexie": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz", + "integrity": "sha512-1G6cJevS17KMDK847V3OHvK2zei899GwpDiqfEXHP1ASvme6eWJmAp9AU4s1son2TeGkWmC0g3y8ezOBPnalgQ==", + "license": "Apache-2.0" + }, + "node_modules/dexie-react-hooks": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.7.tgz", + "integrity": "sha512-Lwv5W0Hk+uOW3kGnsU9GZoR1er1B7WQ5DSdonoNG+focTNeJbHW6vi6nBoX534VKI3/uwHebYzSw1fwY6a7mTw==", + "license": "Apache-2.0", + "peerDependencies": { + "@types/react": ">=16", + "dexie": "^3.2 || ^4.0.1-alpha", + "react": ">=16" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2357,6 +2968,21 @@ "csstype": "^3.0.2" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.13", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", @@ -2364,6 +2990,15 @@ "dev": true, "license": "ISC" }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2422,6 +3057,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2677,6 +3318,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2687,6 +3338,93 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "license": "MIT", + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/execa/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "license": "ISC", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/execa/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "license": "ISC" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2748,6 +3486,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2761,6 +3505,15 @@ "node": ">=16.0.0" } }, + "node_modules/filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2818,6 +3571,29 @@ "dev": true, "license": "ISC" }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2852,6 +3628,25 @@ "node": ">=6.9.0" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2899,6 +3694,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2906,6 +3707,38 @@ "dev": true, "license": "MIT" }, + "node_modules/handlebars": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", + "integrity": "sha512-yrsPfXULrvTP3Aw+zLpett0LUyv2PS/k1jj2UCpYkFElPhMEhspxrCAl4hKGt+vyDjvpxleyYUPF4AMSjCdnhQ==", + "license": "MIT", + "dependencies": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^2.6" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==", + "license": "BSD-3-Clause", + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -2942,6 +3775,36 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha512-STnYGcKMXL9CGdtpeTFnLmgMSHTTNQJSHxiC4DETHKf934Q160Ht5pljrNeH24S0O9xUN+9vsDJZdZtk5js6Ww==", + "license": "MIT", + "dependencies": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2978,12 +3841,37 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha512-rBtCAQAJm8A110nbwn6YdveUnuZH3WrC36IwkRXxDnq53JvXA2NVQvB7IHyKomxK1MJ4VDNw3UtFDdXQ+AvLYA==", + "license": "MIT" + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT" }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT", + "optional": true + }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -3009,6 +3897,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3042,11 +3939,34 @@ "node": ">=8" } }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/js-tokens": { @@ -3120,6 +4040,18 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jspreadsheet-ce": { "version": "4.13.4", "resolved": "https://registry.npmjs.org/jspreadsheet-ce/-/jspreadsheet-ce-4.13.4.tgz", @@ -3144,6 +4076,38 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3187,6 +4151,16 @@ "dev": true, "license": "MIT" }, + "node_modules/longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3199,6 +4173,16 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3209,6 +4193,16 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3216,7 +4210,55 @@ "dev": true, "license": "MIT", "engines": { - "node": ">= 8" + "node": ">= 8" + } + }, + "node_modules/micro": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/micro/-/micro-9.3.1.tgz", + "integrity": "sha512-83uimpPJqfwkfKvJl2WWontBlV3hmzrIgyJ+L2uhDXKNk7Ll+/ezK3zBz7TljubpKPqjM0JdT2Ker4MTPmhjgA==", + "license": "MIT", + "dependencies": { + "arg": "2.0.0", + "chalk": "2.4.0", + "content-type": "1.0.4", + "is-stream": "1.1.0", + "raw-body": "2.3.2" + }, + "bin": { + "micro": "bin/micro.js" + } + }, + "node_modules/micro-compress": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micro-compress/-/micro-compress-1.0.0.tgz", + "integrity": "sha512-1XM/LoxpfCq9mOgtuT5vwumSGXgCziaT+pZPT1o5Jx9CDc0o6YPma6ACyPuLjchnyi8o80AON7EAw5ZnKQLQyQ==", + "license": "MIT", + "dependencies": { + "compression": "^1.6.2" + } + }, + "node_modules/micro/node_modules/chalk": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/micro/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" } }, "node_modules/micromatch": { @@ -3233,6 +4275,45 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "license": "MIT", + "bin": { + "mime": "cli.js" + } + }, + "node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3246,6 +4327,31 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==", + "license": "MIT" + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/mri": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.0.tgz", + "integrity": "sha512-NbJtWIE2QEVbr9xQHXBY92fxX0Tu8EsS9NBwz7Qn3zoeuvcbP3LzBJw3EUJDpfb9IY8qnZvFSWIepeEFQga28w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3278,6 +4384,21 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -3285,6 +4406,36 @@ "dev": true, "license": "MIT" }, + "node_modules/node-version": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/node-version/-/node-version-1.1.3.tgz", + "integrity": "sha512-rEwE51JWn0yN3Wl5BXeGn5d52OGbSXzWiiXRjAQeuyvcGKyvuSILW2rb3G7Xh+nexzLwhTpek6Ehxd6IjvHePg==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3294,6 +4445,55 @@ "node": ">=0.10.0" } }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openssl-self-signed-certificate": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/openssl-self-signed-certificate/-/openssl-self-signed-certificate-1.1.6.tgz", + "integrity": "sha512-OJpqrdIYir5DznRSChodZqvpOx2fsT6sNA4PD9kYOMkIPgjfNYTWDzgac0drm+ZR0yI5Un7tE8bsmP1nZpx23Q==", + "license": "MIT" + }, + "node_modules/opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "license": "MIT", + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==", + "license": "MIT/X11", + "dependencies": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3312,6 +4512,15 @@ "node": ">= 0.8.0" } }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3384,6 +4593,12 @@ "node": ">=8" } }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "license": "(WTFPL OR MIT)" + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3409,10 +4624,27 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "license": "ISC" }, "node_modules/picomatch": { @@ -3428,10 +4660,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -3450,8 +4691,8 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -3467,6 +4708,12 @@ "node": ">= 0.8.0" } }, + "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==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -3484,6 +4731,12 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "license": "ISC" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3515,6 +4768,63 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha512-Ss0DsBxqLxCmQkfG5yazYhtbVVTJqS9jTsZG2lhrNwqzOk2SUC7O/NB/M//CkEBqsrtmlNgJCPccJGuYSFr6Vg==", + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -3604,12 +4914,70 @@ "react-dom": ">=16.6.0" } }, + "node_modules/read-excel-file": { + "version": "5.8.5", + "resolved": "https://registry.npmjs.org/read-excel-file/-/read-excel-file-5.8.5.tgz", + "integrity": "sha512-KDDcSsI3VzXTNUBs8q7RwTYrGRE8RZgNwGUivYq13bQtMp1KJmocyBs/EiPTJaFk4I8Ri9iDF+ht2A4GUrudMg==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.2", + "fflate": "^0.7.3", + "unzipper": "^0.12.2" + } + }, + "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==", + "license": "MIT", + "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/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -3647,10 +5015,23 @@ "node": ">=0.10.0" } }, + "node_modules/right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", + "license": "MIT", + "optional": true, + "dependencies": { + "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rollup": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz", - "integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "dev": true, "license": "MIT", "dependencies": { @@ -3664,22 +5045,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.21.0", - "@rollup/rollup-android-arm64": "4.21.0", - "@rollup/rollup-darwin-arm64": "4.21.0", - "@rollup/rollup-darwin-x64": "4.21.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.0", - "@rollup/rollup-linux-arm-musleabihf": "4.21.0", - "@rollup/rollup-linux-arm64-gnu": "4.21.0", - "@rollup/rollup-linux-arm64-musl": "4.21.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.0", - "@rollup/rollup-linux-riscv64-gnu": "4.21.0", - "@rollup/rollup-linux-s390x-gnu": "4.21.0", - "@rollup/rollup-linux-x64-gnu": "4.21.0", - "@rollup/rollup-linux-x64-musl": "4.21.0", - "@rollup/rollup-win32-arm64-msvc": "4.21.0", - "@rollup/rollup-win32-ia32-msvc": "4.21.0", - "@rollup/rollup-win32-x64-msvc": "4.21.0", + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", "fsevents": "~2.3.2" } }, @@ -3707,6 +5088,12 @@ "queue-microtask": "^1.2.2" } }, + "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==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -3726,6 +5113,175 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve": { + "version": "6.5.8", + "resolved": "https://registry.npmjs.org/serve/-/serve-6.5.8.tgz", + "integrity": "sha512-GZYlJz7f6E7Xq6xbg1rTSvQQV9x4v/yYB/sum6egzSBLa/mdk1PViDSX2JvL0Me83sxu3JpEpQELfakDKbGcrw==", + "license": "MIT", + "dependencies": { + "args": "4.0.0", + "basic-auth": "2.0.0", + "bluebird": "3.5.1", + "boxen": "1.3.0", + "chalk": "2.4.1", + "clipboardy": "1.2.3", + "dargs": "5.1.0", + "detect-port": "1.2.3", + "filesize": "3.6.1", + "fs-extra": "6.0.1", + "handlebars": "4.0.11", + "ip": "1.1.5", + "micro": "9.3.1", + "micro-compress": "1.0.0", + "mime-types": "2.1.18", + "node-version": "1.1.3", + "openssl-self-signed-certificate": "1.1.6", + "opn": "5.3.0", + "path-is-inside": "1.0.2", + "path-type": "3.0.0", + "send": "0.16.2", + "update-check": "1.5.1" + }, + "bin": { + "serve": "bin/serve.js" + }, + "engines": { + "node": ">=7.6.0" + } + }, + "node_modules/serve/node_modules/bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "license": "MIT" + }, + "node_modules/serve/node_modules/chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/serve/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/serve/node_modules/fs-extra": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "node_modules/serve/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/serve/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/serve/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha512-9jphSf3UbIgpOX/RKvX02iw/rN2TKdusnsPpGfO/rkcsrd+IRqgHZb4VGnmL0Cynps8Nj2hN45wsi30BzrHDIw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3749,6 +5305,19 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3769,15 +5338,81 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "license": "MIT", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3791,6 +5426,15 @@ "node": ">=8" } }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3834,6 +5478,96 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha512-7dPUZQGy/+m3/wjVz3ZW5dobSoD/02NxJpoXUX0WIyjfVS3l0c+b/+9phIDFA7FHzkYtwtMFgeGZ/Y8jVTeqQQ==", + "license": "MIT", + "dependencies": { + "execa": "^0.7.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/term-size/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "license": "MIT", + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/term-size/node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/term-size/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "license": "ISC", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/term-size/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/term-size/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/term-size/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/term-size/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "license": "ISC" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3841,6 +5575,50 @@ "dev": true, "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", + "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -3927,6 +5705,64 @@ } } }, + "node_modules/uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "source-map": "~0.5.1", + "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" + } + }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", + "license": "MIT", + "optional": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unzipper": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", + "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "^11.2.0", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", @@ -3958,6 +5794,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/update-check": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.1.tgz", + "integrity": "sha512-M3rjq5KwSrWZrm2GVPIQIF+NXpIn5I9mIV67gGoydptQvzRjLp9ZbM6ctFJeNuaWSm5+mNP7aInELjSiLcIw6A==", + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3968,15 +5814,30 @@ "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==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", - "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", + "version": "5.4.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", + "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.41", + "postcss": "^8.4.43", "rollup": "^4.20.0" }, "bin": { @@ -4028,6 +5889,93 @@ } } }, + "node_modules/vite-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.1.tgz", + "integrity": "sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.6", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.1.tgz", + "integrity": "sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.1", + "@vitest/mocker": "2.1.1", + "@vitest/pretty-format": "^2.1.1", + "@vitest/runner": "2.1.1", + "@vitest/snapshot": "2.1.1", + "@vitest/spy": "2.1.1", + "@vitest/utils": "2.1.1", + "chai": "^5.1.1", + "debug": "^4.3.6", + "magic-string": "^0.30.11", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.1", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.1", + "@vitest/ui": "2.1.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4044,6 +5992,44 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "license": "MIT", + "dependencies": { + "string-width": "^2.1.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==", + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -4054,6 +6040,15 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -4070,6 +6065,29 @@ "node": ">= 6" } }, + "node_modules/yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", + "license": "MIT", + "optional": true, + "dependencies": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + }, + "node_modules/yargs/node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 36456be..34068cd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,8 +6,10 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", + "test": "vitest", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "start": "serve -s dist" }, "dependencies": { "@emotion/react": "^11.13.3", @@ -15,10 +17,14 @@ "@mui/base": "^5.0.0-beta.40", "@mui/icons-material": "^5.16.7", "@mui/material": "^5.16.7", + "dexie": "^4.0.8", + "dexie-react-hooks": "^1.1.7", "jspreadsheet-ce": "^4.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.26.1" + "react-router-dom": "^6.26.1", + "read-excel-file": "^5.8.5", + "serve": "^6.5.8" }, "devDependencies": { "@eslint/js": "^9.9.0", @@ -29,8 +35,10 @@ "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", + "moment": "^2.30.1", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", - "vite": "^5.4.1" + "vite": "^5.4.1", + "vitest": "^2.1.1" } } diff --git a/frontend/src/components/BackButton.tsx b/frontend/src/components/BackButton.tsx index de85c97..b30dedf 100644 --- a/frontend/src/components/BackButton.tsx +++ b/frontend/src/components/BackButton.tsx @@ -1,6 +1,11 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import { Button } from '@mui/material'; +/** + * React component for a back button with outlined style. + * Displays a button with an arrow icon and text 'Back'. + * @returns JSX element representing the back button + */ export default function BackButton() { return ( - } else { - return ; - } -}` ` \ No newline at end of file +/** + * Functional component for rendering a Proceed Button based on whether a file + * is chosen or not. + * + * @param {ProceedButtonProps} fileChosen - Props object containing the fileChosen + * boolean value + * @returns A Button component that allows the user to proceed + * to a specific route when clicked + */ +export default function ProceedButton({ fileChosen }: ProceedButtonProps) { + const navigate = useNavigate(); + if (fileChosen === null) { + return + } else { + return ; + } +} \ No newline at end of file diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index b5ec512..fd86d43 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -16,6 +16,15 @@ interface SidebarProps { } const drawerWidth = 240; +/** + * Renders a sidebar component with navigation links to Campus, Building, Room, + * Course, and Unit. + * + * @param {SidebarProps} props - The properties passed to Sidebar component + * @param {number} props.marginTop - The top margin of the sidebar. + * @param {number} props.width - The width of the sidebar. + * @returns Sidebar component with navigation links. + */ export default function Sidebar({ marginTop, width }: SidebarProps) { return ( - - - - - - - - @@ -67,6 +68,7 @@ export default function Sidebar({ marginTop, width }: SidebarProps) { + ); diff --git a/frontend/src/components/Spreadsheet.tsx b/frontend/src/components/Spreadsheet.tsx index 7af472f..6d71e74 100644 --- a/frontend/src/components/Spreadsheet.tsx +++ b/frontend/src/components/Spreadsheet.tsx @@ -1,23 +1,34 @@ import { useRef, useEffect } from "react"; -import jspreadsheet, { CellValue, JspreadsheetInstance, JspreadsheetInstanceElement, JSpreadsheetOptions } from "jspreadsheet-ce"; +import jspreadsheet, { JspreadsheetInstance, JspreadsheetInstanceElement } from "jspreadsheet-ce"; import "../../node_modules/jspreadsheet-ce/dist/jspreadsheet.css"; import "../styles/spreadsheet.css" import Button from "@mui/material/Button"; +import { getSpreadsheetData, storeSpreadsheetData } from "../scripts/persistence"; interface SpreadsheetProps { headers: string[]; storageKey: string; + columns?: jspreadsheet.Column[] } -type SavedSpreadsheetOpt = { - cellvalues: CellValue[][]; -} - -export default function Spreadsheet({ headers, storageKey }: SpreadsheetProps) { +/** + * Component for rendering a spreadsheet with customisable headers and storage + * functionality. + * + * @param {SpreadsheetProps} props - The properties passed to Spreadsheet component + * @param {SpreadsheetProps.headers} props.headers - Array of header names for the spreadsheet columns. + * @param {SpreadsheetProps.storageKey} props.storageKey - Key for storing and retrieving spreadsheet data + * in storage (i.e. indexedDB). + * @return Div containing a spreadsheet and button to add row. + */ +export default function Spreadsheet({ headers, storageKey, ...other }: SpreadsheetProps) { const jRef = useRef(null); // spreadsheet init options: columns property - const columns = headers.map((headerName) => { + const columns: jspreadsheet.Column[] = headers.map((headerName, idx) => { + if (other.columns && other.columns.length > idx) { + return { title: headerName, width: 200, ...other.columns[idx] }; + } return { title: headerName, width: 200 }; }); @@ -70,7 +81,7 @@ export default function Spreadsheet({ headers, storageKey }: SpreadsheetProps) { } } }); - + return items; }; @@ -78,8 +89,8 @@ export default function Spreadsheet({ headers, storageKey }: SpreadsheetProps) { const options: jspreadsheet.JSpreadsheetOptions = { columns: columns, data: [[]], - minDimensions: [headers.length, 10], - minSpareRows: 1, + minDimensions: [headers.length, 1], + // minSpareRows: 1, allowManualInsertColumn: false, allowInsertColumn: false, includeHeadersOnDownload: true, @@ -107,44 +118,44 @@ export default function Spreadsheet({ headers, storageKey }: SpreadsheetProps) { // ], }; - // mount: create spreadsheet using data from sessionStorage (if exist), + // mount: create spreadsheet using data from indexedDB (if exist), // otherwise create a blank default. useEffect(() => { // console.log(`Mount ${storageKey}`); - if (jRef.current && !jRef.current.jspreadsheet) { - const savedSpreadsheetData = sessionStorage.getItem(storageKey); - - if (savedSpreadsheetData) { - const ssd: SavedSpreadsheetOpt = JSON.parse(savedSpreadsheetData); - options.data = ssd.cellvalues; - } - - jspreadsheet(jRef.current, options); - } + getSpreadsheetData(storageKey) + .then((data) => { + if (data && jRef.current && !jRef.current.jspreadsheet) { + options.data = data; + jspreadsheet(jRef.current, options); + } + }); }); - // unmount: save spreadsheet data to sessionStorage + // unmount: save spreadsheet data to indexedDB useEffect(() => { const instanceElem: JspreadsheetInstanceElement | null = jRef.current; function cleanup() { // console.log(`Unmount ${storageKey}`); - if (instanceElem) { - const newOpts: SavedSpreadsheetOpt = { - cellvalues: instanceElem.jspreadsheet.getData(), - }; - sessionStorage.setItem(storageKey, JSON.stringify(newOpts)); - } - else { - throw new Error( - "JspreadsheetInstanceElement is null" - ) + if (instanceElem && instanceElem.jspreadsheet) { + const data = instanceElem.jspreadsheet.getJson(false); + storeSpreadsheetData(data, storageKey); } } - return cleanup; }) + // page refresh: save spreadsheet data to indexedDB + useEffect(() => { + window.addEventListener("beforeunload", () => { + const instanceElem: JspreadsheetInstanceElement | null = jRef.current; + if (instanceElem && instanceElem.jspreadsheet) { + const data = instanceElem.jspreadsheet.getJson(false); + storeSpreadsheetData(data, storageKey); + } + }); + }) + const addRow = () => { if (jRef.current && jRef.current.jexcel) { jRef.current.jexcel.insertRow(); diff --git a/frontend/src/components/UploadButton.tsx b/frontend/src/components/UploadButton.tsx index 7340f08..892105d 100644 --- a/frontend/src/components/UploadButton.tsx +++ b/frontend/src/components/UploadButton.tsx @@ -1,7 +1,8 @@ -import React, { useState } from "react"; import { styled } from "@mui/material/styles"; import Button from "@mui/material/Button"; import UploadFileIcon from "@mui/icons-material/UploadFile"; +import { getFile, storeFile } from "../scripts/persistence"; +import { getUnitsList } from "../scripts/handleInput"; interface InputFileUploadProps { setFileChosen: (file: File | null) => void; @@ -19,40 +20,35 @@ const VisuallyHiddenInput = styled("input")({ width: 1, }); +/** + * Functional component for an upload button that allows users to select and + * upload a file. + * + * @param {InputFileUploadProps.setFileChosen} setFileChosen - Callback function to set the chosen file. + * @returns Upload button component with file selection and upload functionality. + */ export default function UploadButton ({ setFileChosen }: InputFileUploadProps) { - const [file, setFile] = useState(null); // Handler for file selection const handleFileChange = (event: React.ChangeEvent) => { if (event.target.files && event.target.files.length > 0) { - setFile(event.target.files[0]); - setFileChosen(event.target.files[0]); + storeFile(event.target.files[0]) + .then(() => { + return getFile(); + }) + .then((file) => { + return getUnitsList(file); + }) + .then((file) => { + setFileChosen(file); + }) + .catch((error) => { + alert("Upload failed. Please try again. " + error); + }) console.log("File selected:", event.target.files[0]); } }; - // Example of sending the file to a server - const handleUpload = () => { - if (!file) { - console.error("No file selected for upload."); - return; - } - - const formData = new FormData(); - formData.append("file", file); - - fetch("http://url.com", { - method: "POST", - body: formData, - }) - .then((response) => response.json()) - .then((data) => { - console.log("File uploaded successfully:", data); - }) - .catch((error) => { - console.error("Error uploading file:", error); - }); - }; return ( +
+ + +
+ + + ) +} \ No newline at end of file diff --git a/frontend/src/pages/TimetableMod.tsx b/frontend/src/pages/TimetableMod.tsx index b4900b7..ff9a38b 100644 --- a/frontend/src/pages/TimetableMod.tsx +++ b/frontend/src/pages/TimetableMod.tsx @@ -1,10 +1,17 @@ import { Link } from "react-router-dom"; +/** + * Renders the TimetableMod component to display and modify the generated + * timetable. + * Allows users to navigate back to the campus information page and proceed to + * the download page. + * @returns JSX element containing the page content with navigation links + */ export default function TimetableMod() { return ( <>

This is the page to modify generated timetable

- Go Back + Go Back Go to Next ) diff --git a/frontend/src/pages/spreadsheets/Building.tsx b/frontend/src/pages/spreadsheets/Building.tsx index ca43b12..be3ccc9 100644 --- a/frontend/src/pages/spreadsheets/Building.tsx +++ b/frontend/src/pages/spreadsheets/Building.tsx @@ -1,11 +1,19 @@ import Spreadsheet from '../../components/Spreadsheet.tsx' +import { DB_BUILDINGS } from '../../scripts/persistence.ts'; +/** + * + * @returns Spreadsheet input page for buildings information. + */ export default function Building() { return ( <>

Building

- + ); }; \ No newline at end of file diff --git a/frontend/src/pages/spreadsheets/Campus.tsx b/frontend/src/pages/spreadsheets/Campus.tsx deleted file mode 100644 index 3515dff..0000000 --- a/frontend/src/pages/spreadsheets/Campus.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import Spreadsheet from '../../components/Spreadsheet.tsx' - -export default function Campus() { - - return ( - <> -

Campus

- - - ); -}; \ No newline at end of file diff --git a/frontend/src/pages/spreadsheets/Room.tsx b/frontend/src/pages/spreadsheets/Room.tsx index 64aa2f7..e07db9d 100644 --- a/frontend/src/pages/spreadsheets/Room.tsx +++ b/frontend/src/pages/spreadsheets/Room.tsx @@ -1,13 +1,19 @@ import Spreadsheet from '../../components/Spreadsheet.tsx' +import { DB_ROOMS } from '../../scripts/persistence.ts'; +/** + * + * @returns Spreadsheet input page for rooms information. + */ export default function Room() { return ( <>

Room

); diff --git a/frontend/src/pages/spreadsheets/Unit.tsx b/frontend/src/pages/spreadsheets/Unit.tsx index bb35ddb..b747cc3 100644 --- a/frontend/src/pages/spreadsheets/Unit.tsx +++ b/frontend/src/pages/spreadsheets/Unit.tsx @@ -1,11 +1,20 @@ import Spreadsheet from '../../components/Spreadsheet.tsx' +import { DB_UNITS } from '../../scripts/persistence.ts'; +/** + * + * @returns Spreadsheet input page for units information. + */ export default function Unit() { return ( <>

Unit

- + ); }; \ No newline at end of file diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx index fdfba8e..a0d04eb 100644 --- a/frontend/src/routes.tsx +++ b/frontend/src/routes.tsx @@ -1,13 +1,18 @@ import SemesterInfo from './pages/SemesterInfo.tsx' import TimetableMod from './pages/TimetableMod.tsx' -import Campus from './pages/spreadsheets/Campus.tsx' import Building from './pages/spreadsheets/Building.tsx' import Room from './pages/spreadsheets/Room.tsx' import Unit from './pages/spreadsheets/Unit.tsx' import Download from './pages/Download.tsx' import Enrolment from './pages/Enrolment.tsx' +import SendData from './pages/SendData.tsx' - +/** + * Defines the routes configuration for the application. + * Each route specifies a path and the corresponding component to render. + * + * An array of route objects, each containing path and element information. + */ const routes = [ { path: "/", @@ -17,12 +22,15 @@ const routes = [ path: "seminfo", element: , children: [ - { path: "campus", element: }, { path: "building", element: }, { path: "room", element: }, { path: "unit", element: }, ], }, + { + path: "senddata", + element: , + }, { path: "timetablemod", element: , diff --git a/frontend/src/scripts/api.ts b/frontend/src/scripts/api.ts new file mode 100644 index 0000000..81fa9af --- /dev/null +++ b/frontend/src/scripts/api.ts @@ -0,0 +1,81 @@ +/* Timetable solver backend endpoint URL */ +const API_URL = 'http://localhost:8080/timetabling'; + +/* =========================================== Defining types =========================================== */ + +export type TimetableProblem = TimetableBase & { + units: Unit[], +} + +export type TimetableSolution = TimetableBase & { + units: Required[], +} + +export type TimetableBase = { + daysOfWeek: Weekday[], + startTimes: Time[], + rooms: Room[] +} + +export type Unit = { + unitID: number, + name: string, + duration: number, + students: Student[], + wantsLab: boolean, + // fields to be assigned by backend's algorithm + room?: Room, + studentSize?: number + dayOfWeek?: Weekday, + startTime?: Time, + end?: Time, +}; + +export type Student = { + name: string +}; + +export type Room = { + id: string, + capacity: number, + lab: boolean +} + +export type Weekday = "MONDAY" | "TUESDAY" | "WEDNESDAY" | "THURSDAY" | "FRIDAY" + +export type Time = string; + +/* ====================================================================================================== */ + + +/** + * Sends the timetabling problem to backend for solving. Return the solution received. + * + * @param problem A TimetableProblem is a list of units with no allocated time and room. + * @returns A TimetableSolution with all units allocated a time and a room. + */ +export async function fetchTimetableSolution(problem: TimetableProblem): Promise { + try { + const response = await fetch(API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(problem) + }); + + if (!response.ok) { + if (response.status === 500) { + alert(response.statusText + " " + response.status + ": server was not able to solve the problem. Please check for missing input (i.e. make sure there are at least 1 available room and no rooms with duplicate ID)."); + } + throw new Error(`HTTP error! Status: ${response.status} ${response.statusText}`); + } + + const solution: TimetableSolution = await response.json(); + return solution; + } + catch (error) { + console.log(error); + return null; + } +} \ No newline at end of file diff --git a/frontend/src/scripts/handleInput.ts b/frontend/src/scripts/handleInput.ts new file mode 100644 index 0000000..629f45a --- /dev/null +++ b/frontend/src/scripts/handleInput.ts @@ -0,0 +1,164 @@ +import readXlsxFile, { Row } from 'read-excel-file'; +import { CellValue } from 'jspreadsheet-ce'; +import { TimetableProblem, Unit, Room } from './api'; +import { DB_UNITS, storeSpreadsheetData } from './persistence'; + +/** + * Function to validate uploaded enrolment data file. + * + * @param file enrolment data Excel file + * @returns true if uploaded file is an Excel file + */ +function isExcelFile(file: File) { + const fileExtension = file.name.split('.').pop(); + if (fileExtension === undefined || !['xlsx', 'xls'].includes(fileExtension)) { + alert("Wrong file type, file type must be .xlsx or .xls"); + return false; + } + return true; +} + +/** + * Function to validate uploaded enrolment data file. + * + * @param inputHeader header row of enrolment data Excel file + * @returns true if header row matches expected format for parsing. + */ +function validateEnrolmentHeader(inputHeader: Row) { + const header = ['StudentID', 'Student Name', 'Personal Email', 'University Email', + 'Student Type', 'Offer Type', 'Course Name', 'Campus', 'Original COE Start Date', + 'Course Start Date', 'Course End Date', 'COE Status', 'Specialisation', 'Pathway Indicator']; + + if (inputHeader.length >= header.length && JSON.stringify(header) === JSON.stringify(inputHeader.slice(0, header.length))) { + return true; + } + else { + alert("Enrolment data header row is invalid"); + return false; + } +} + +/** + * Extract list of units from enrolment data and prefill spreadsheet input page. + * + * @param enrolmentExcel enrolment data Excel file + * @returns enrolment data Excel file + */ +export async function getUnitsList(enrolmentExcel: File) { + if (!isExcelFile(enrolmentExcel)) { + throw new Error( + "File is not .xlsx or .xls" + ) + } + + const [header] = await readXlsxFile(enrolmentExcel); + + if (!validateEnrolmentHeader(header)) { + throw new Error( + "Enrolment data has wrong headers" + ) + } + + // console.log(header.slice(14)); + const unitsList = header.slice(14).map(elem => elem.toString()); + const unitsData: Record[] = unitsList.map((u) => { + return { 0: u, 1: '', 2: '', 3: '' }; + }); + + storeSpreadsheetData(unitsData, DB_UNITS); + + return enrolmentExcel; +} + +/** + * Parse user input to create the timetabling problem. + * + * @param enrolmentExcel enrolment data Excel file + * @param roomSpreadsheet information of all rooms (spreadsheet input from user) + * @param unitSpreadsheet information of all units (spreadsheet input from user) + * @returns a TimetableProblem, which includes all available rooms, start times and unallocated units + */ +export async function getTimetableProblem(enrolmentExcel: File, roomSpreadsheet: Record[], unitSpreadsheet: Record[]) { + if (!isExcelFile(enrolmentExcel)) { + throw new Error( + "File is not .xlsx or .xls" + ) + } + + const [header, ...body] = await readXlsxFile(enrolmentExcel); + + if (!validateEnrolmentHeader(header)) { + throw new Error( + "Enrolment data has wrong headers" + ) + } + + const unitsList = header.slice(14); + const units: Unit[] = unitsList.map((value, index) => { + return { + unitID: index, + name: value.toString(), + duration: 0, + students: [], + wantsLab: false + } + }); + + unitSpreadsheet.map((record, index) => { + if (index >= units.length) { + } + else { + const totalDuration = (Number(record['1']) + Number(record['2']) + Number(record['3'])) * 60; + const wantsLab = Number(record['3']) > 0; + units[index].duration = totalDuration; + units[index].wantsLab = wantsLab; + } + }) + + // check each row and add students to each unit they're enrolled in + for (let i = 0; i < body.length; i++) { + const enrolments = body[i].slice(14); + for (let j = 0; j < enrolments.length; j++) { + if (enrolments[j] === "ENRL") { + units[j].students.push({ + name: body[i][0].toString() + }) + } + } + } + + const rooms: Room[] = roomSpreadsheet + .filter((record) => record['5'] as boolean) + .map((record) => { + return { + id: record['2'] as string, + capacity: record['3'] as number, + lab: record['4'] as boolean + } + }); + + + const problem: TimetableProblem = { + units: units, + daysOfWeek: [ + "MONDAY", + "TUESDAY", + "WEDNESDAY", + "THURSDAY", + "FRIDAY" + ], + startTimes: [ + "08:00:00", + "09:00:00", + "10:00:00", + "11:00:00", + "12:00:00", + "13:00:00", + ], + rooms: rooms + } + + console.log(problem); + + return problem; +} diff --git a/frontend/src/scripts/persistence.ts b/frontend/src/scripts/persistence.ts new file mode 100644 index 0000000..ecb9516 --- /dev/null +++ b/frontend/src/scripts/persistence.ts @@ -0,0 +1,147 @@ +import Dexie, { EntityTable } from "dexie"; +import { CellValue } from "jspreadsheet-ce"; + +/* Define storage keys */ +const DB_NAME = 'TimetableInput'; +const DB_BUILDINGS = 'buildings'; +const DB_ROOMS = 'rooms'; +const DB_UNITS = 'units'; + +/* Define record type of `file` objectStore */ +interface FileRecord { + id: number; + file: File; +} + +/* Define record type of each spreadsheets objectStore */ +interface SpreadsheetRecord { + id?: number; + record: Record; +} + +/* Initialise Dexie (indexedDB wrapper) */ +const db = new Dexie(DB_NAME) as Dexie & { + files: EntityTable; + buildings: EntityTable; + rooms: EntityTable; + units: EntityTable; +}; + +/* Initialise indexedDB objectStores */ +db.version(1).stores({ + files: '++id, file', + buildings: '++id, record', + rooms: '++id, record', + units: '++id, record' +}); + +/** + * Save 1 file to the first row of the file table. + * Delete all other files. + * Intend for storing enrolment data Excel file. + * + * @param file any file + * @returns id of saved row + */ +export async function storeFile(file: File): Promise { + await db.files.clear(); + const id = await db.files.add({ + id: 0, + file: file + }); + return id; +} + +/** + * Get first file saved to the `file` objectStore. + * Intend for getting saved enrolment data Excel file. + * + * @returns file saved + */ +export async function getFile(): Promise { + const file = await db.files.orderBy('id').first(); + + if (file === undefined) { + throw new Error( + "getFile() failed" + ); + } + return file.file; +} + +/** + * Store data of an input spreadsheet to the corresponding objectStore in indexedDB. + * + * @param data data in one spreadsheet page + * @param storageObject the key of the objectStore (table) of the database we want to store data into + * @returns void + */ +export async function storeSpreadsheetData(data: Record[], storageObject: string) { + if (!data) { + return; + } + + const records: SpreadsheetRecord[] = data.map((obj, idx) => { + return { + id: idx, + record: obj + } + }); + + try { + if (storageObject === DB_BUILDINGS) { + await db.buildings.clear(); + await db.buildings.bulkPut(records); + } + else if (storageObject === DB_ROOMS) { + await db.rooms.clear(); + await db.rooms.bulkPut(records); + } + else if (storageObject === DB_UNITS) { + await db.units.clear(); + await db.units.bulkPut(records); + } + else { + throw new Error( + "storeSpreadsheetData: storageObject does not exist" + ) + } + } + catch (error) { + console.log(error); + } +} + +/** + * Get data from a specified indexedDB objectStore. + * + * @param storageObject the key of the objectStore (table) that we want to get data out of + * @returns spreadsheet data + */ +export async function getSpreadsheetData(storageObject: string): Promise[] | null> { + try { + if (storageObject === "buildings") { + const data = await db.buildings.toArray(); + return data.map((obj) => obj.record); + } + else if (storageObject === "rooms") { + const data = await db.rooms.toArray(); + return data.map((obj) => obj.record); + } + else if (storageObject === "units") { + const data = await db.units.toArray(); + return data.map((obj) => obj.record); + } + else { + throw new Error( + "getSpreadsheetData: storageObject does not exist" + ) + } + } + catch (error) { + console.log(error); + return null; + } +} + +export { DB_BUILDINGS, DB_ROOMS, DB_UNITS } \ No newline at end of file diff --git a/frontend/src/styles/enrolment.css b/frontend/src/styles/enrolment.css index ca0602e..f2bcc26 100644 --- a/frontend/src/styles/enrolment.css +++ b/frontend/src/styles/enrolment.css @@ -1,13 +1,11 @@ -/* Make sure the entire viewport height is used */ .app-container { display: flex; flex-direction: column; - min-height: 100vh; /* Full viewport height */ + min-height: 100vh; } -/* Content area that grows to fill space */ .content { - flex: 1; /* This allows the content to grow and push the footer down */ + flex: 1; padding: 0; display: grid; grid-template-columns: 1fr 1fr; @@ -27,7 +25,7 @@ } .imageBox { - overflow: hidden; /* Hide any overflow content */ + overflow: hidden; position: relative; } body { diff --git a/frontend/src/styles/seminfo.css b/frontend/src/styles/seminfo.css index c11a47f..fb4ff8c 100644 --- a/frontend/src/styles/seminfo.css +++ b/frontend/src/styles/seminfo.css @@ -3,5 +3,5 @@ display: flex; justify-content: space-around; align-items: center; - gap: 500px; + gap: 50%; } \ No newline at end of file diff --git a/frontend/src/tests/api.test.ts b/frontend/src/tests/api.test.ts new file mode 100644 index 0000000..e6bffd0 --- /dev/null +++ b/frontend/src/tests/api.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect } from 'vitest'; +import { fetchTimetableSolution, TimetableProblem } from '../scripts/api'; +import moment from 'moment'; + +/** + * Test fetchTimetableSolution API method. + * Check if connection to backend is working. + * Check that output matches expected output. + */ +describe('fetchTimetableSolution', () => { + /** + * Validate end-to-end scheduling and data consistency of 1 API method call. + */ + it('return TimetableSolution', async () => { + const problem: TimetableProblem = { + units: [{ unitID: 0, name: "Unit0", duration: 1200, students: [], wantsLab: true }], + daysOfWeek: ["MONDAY"], + startTimes: ["11:00:00"], + rooms: [{ id: "Room A", capacity: 10, lab: true }] + }; + + const solution = await fetchTimetableSolution(problem); + expect(solution).not.toBeNull(); + expect(solution?.units[0].dayOfWeek).toEqual(problem.daysOfWeek[0]); + expect(solution?.units[0].startTime).toEqual(problem.startTimes[0]); + expect(solution?.units[0].end).toEqual(addSecondsToTimeString(problem.startTimes[0], problem.units[0].duration)); + expect(solution?.units[0].room).toEqual(problem.rooms[0]); + expect(solution?.daysOfWeek).toEqual(problem.daysOfWeek); + expect(solution?.startTimes).toEqual(problem.startTimes); + expect(solution?.rooms).toEqual(problem.rooms); + + }); + + /** + * Validate that backend server can handle multiple solve requests concurrently. + */ + it ('can be called multiple times', async () => { + const problem: TimetableProblem = { + units: [{ unitID: 0, name: "Unit0", duration: 1200, students: [], wantsLab: true }], + daysOfWeek: ["MONDAY"], + startTimes: ["11:00:00"], + rooms: [{ id: "Room A", capacity: 10, lab: true }] + }; + + const solutions = await Promise.all([fetchTimetableSolution(problem), fetchTimetableSolution(problem), fetchTimetableSolution(problem)]); + + for (let i = 0; i < solutions.length; i++) { + expect(solutions[i]).not.toBeNull(); + } + + }); + +}); + +/** + * Helper function. + * Add a string representing time with a number representing number of seconds to add. + * + * @param timeString string representing time + * @param secondsToAdd number in seconds + * @returns string representing time after specified seconds have been added to it + */ +function addSecondsToTimeString(timeString: string, secondsToAdd: number) { + const time = moment(timeString, 'HH:mm:ss'); + time.add(secondsToAdd, 'seconds'); + return time.format('HH:mm:ss'); +} \ No newline at end of file diff --git a/frontend/static.json b/frontend/static.json new file mode 100644 index 0000000..94fb8eb --- /dev/null +++ b/frontend/static.json @@ -0,0 +1,3 @@ +{ + "root": "./dist" +} \ No newline at end of file