From bb795cb9acf7200da9f37c5ee1598cbc998a4fe8 Mon Sep 17 00:00:00 2001 From: Ren Koya Date: Thu, 22 May 2025 11:37:47 +0900 Subject: [PATCH 1/3] support mapd --- README.md | 106 ++- public/demo_random-32-32-20.txt | 146 ++-- src/AnimationControl.tsx | 281 +++++--- src/App.tsx | 216 +++--- src/ConfigBar.tsx | 486 +++++++------- src/Graph.tsx | 16 +- src/Params.tsx | 18 +- src/PixiApp.tsx | 1113 ++++++++++++++++++------------- src/Solution.tsx | 91 ++- src/Visualizer.tsx | 149 +++-- src/main.tsx | 32 +- 11 files changed, 1565 insertions(+), 1089 deletions(-) diff --git a/README.md b/README.md index 6e56593..d46d8a7 100644 --- a/README.md +++ b/README.md @@ -3,22 +3,23 @@ [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) [![ci](https://github.com/JustinShetty/mapf-visualizer/actions/workflows/ci.yml/badge.svg)](https://github.com/JustinShetty/mapf-visualizer/actions/workflows/ci.yml) -This repository hosts the web-based version of the MAPF (Multi-Agent Pathfinding) Visualizer, adapted from the original [Kei18/mapf-visualizer](https://github.com/Kei18/mapf-visualizer). The app provides an interactive and intuitive way to visualize MAPF solutions directly in your browser. +This repository hosts the web-based version of the MAPF (Multi-Agent Pathfinding) and MAPD (Multi-Agent Pickup and Delivery) Visualizer, adapted from the original [Kei18/mapf-visualizer](https://github.com/Kei18/mapf-visualizer). The app provides an interactive and intuitive way to visualize MAPF solutions directly in your browser. This project runs entirely client-side and is built using [React](https://reactjs.org/) and [PixiJS](https://pixijs.com/). ## Features -- **Browser-Based Interface**: No installation required, simply access the app through the GitHub Pages site. -- **Customizable Input**: Upload your own MAPF maps and solutions. -- **Real-Time Visualization**: Observe agent movements step-by-step. +- **Browser-Based Interface**: No installation required, simply access the app through the GitHub Pages site. +- **Customizable Input**:Upload your own MAPF and MAPD maps and solutions. +- **Real-Time Visualization**: Observe agent movements step-by-step. ## Demo + ![demo](./assets/demo.gif) ## Usage -1. **Upload a Map File**: Load your MAPF map file (.txt format). +1. **Upload a Map File**: Load your map file (.txt format). 2. **Upload a Solution File**: Load the corresponding solution file (.txt format). 3. **Visualize**: The solution will automatically play 4. **Controls**: @@ -35,11 +36,12 @@ This project runs entirely client-side and is built using [React](https://reactj - **Toggle Goal Markers**: Show or hide the goal markers for the agents. - **Toggle Goal Vectors**: Show or hide the vectors pointing to the agents' goals. - ## File Format ### Map File + The map file defines the grid layout with open locations and obstacles. Format example: + ``` type octile height 4 @@ -50,15 +52,36 @@ map @...@... @@@@@@@. ``` -- **type**: Always `octile` for MAPF visualizations. -- **height**: Number of rows in the grid. -- **width**: Number of columns in the grid. -- **map**: Grid definition using characters: - - `.`: Open location. - - `@`: Obstacle. + +- **type**: Always `octile` for MAPF visualizations. +- **height**: Number of rows in the grid. +- **width**: Number of columns in the grid. +- **map**: Grid definition using characters: + - `.`: Open location. + - `@`: Obstacle. ### Solution File -The solution file specifies the paths agents will take. Format example: + +Each line represents a **single timestep**: + +``` +: (,[,][,]),(,[,][,]),... +``` + +- `time_step`: An integer timestamp. +- `x`, `y`: Grid coordinates. +- `orientation` (optional): Direction the agent is facing (e.g. `X_PLUS`, `Y_MINUS`). +- `state` (optional, MAPD only): The agent’s current state (see below). + +- Each line defines the agents' states at a particular timestep: + - **Timestep**: Integer identifier before the colon. + - **Pose**: Each `(...),` represents an agent's pose. The first two elements are the x and y coordinates respectively. The third element (e.g. `X_PLUS`) is optional if your solver considers orientation. + +> [!WARNING] +> Please note that either **all** or **none** of the poses must contain orientation. A mix of orientation and orientation-less poses is not supported. + +#### 📦 MAPF Example (Path Finding Only) + ``` 0:(0,0,Y_MINUS), 1:(0,0,X_PLUS), @@ -67,12 +90,29 @@ The solution file specifies the paths agents will take. Format example: 4:(1,1,Y_PLUS), 5:(1,1,X_PLUS), ``` -- Each line defines the agents' states at a particular timestep: - - **Timestep**: Integer identifier before the colon. - - **Pose**: Each `(...),` represents an agent's pose. The first two elements are the x and y coordinates respectively. The third element (e.g. `X_PLUS`) is optional if your solver considers orientation. -> [!WARNING] -> Please note that either **all** or **none** of the poses must contain orientation. A mix of orientation and orientation-less poses is not supported. +#### 📦 MAPD Example (Pickup and Delivery) + +``` +0:(18,26,PICKING),(29,21,PICKING), +1:(17,26,PICKING),(29,22,PICKING), +2:(16,26,CARRYING),(28,22,CARRYING), +3:(15,26,CARRYING),(27,22,DELIVERED), +4:(15,25,CARRYING),(26,22,IDLE), + +``` + +#### 🎯 Acceptable state is + +``` +export enum AgentState { + PICKING, + CARRYING, + DELIVERED, + IDLE, + NONE, +} +``` ## License @@ -87,6 +127,7 @@ Special thanks to [Kei18](https://github.com/Kei18) for creating the original MA For questions or support, feel free to open an issue. ## Contributing + If you wish to contribute, please open a pull request and I'll review the changes as soon as practical.
@@ -97,29 +138,30 @@ If you wish to contribute, please open a pull request and I'll review the change To run the development server locally, follow these steps: 1. **Clone the Repository**: - ```sh - git clone https://github.com/JustinShetty/mapf-visualizer.git - cd mapf-visualizer - ``` + + ```sh + git clone https://github.com/JustinShetty/mapf-visualizer.git + cd mapf-visualizer + ``` 2. **Install Dependencies**: - ```sh - npm install - ``` + + ```sh + npm install + ``` 3. **Start the Development Server**: - ```sh - npm run dev - ``` + ```sh + npm run dev + ``` ### Linting the Codebase To maintain code quality, lint the codebase using the following commands: 1. **Run Linter**: - ```sh - npm run lint - ``` + ```sh + npm run lint + ```
- diff --git a/public/demo_random-32-32-20.txt b/public/demo_random-32-32-20.txt index e9a63f4..08208a3 100644 --- a/public/demo_random-32-32-20.txt +++ b/public/demo_random-32-32-20.txt @@ -1,39 +1,107 @@ -0:(18,26),(29,21),(18,24),(14,7),(27,29),(20,27),(28,13),(18,0),(10,28),(29,10), -1:(17,26),(29,22),(18,25),(14,6),(27,28),(19,27),(27,13),(19,0),(10,27),(29,9), -2:(16,26),(28,22),(18,26),(13,6),(27,27),(18,27),(27,12),(20,0),(11,27),(29,8), -3:(15,26),(27,22),(17,26),(12,6),(28,27),(17,27),(27,11),(20,1),(12,27),(28,8), -4:(15,25),(26,22),(16,26),(12,5),(29,27),(16,27),(26,11),(20,2),(12,26),(28,7), -5:(14,25),(25,22),(15,26),(12,4),(29,26),(15,27),(25,11),(20,3),(12,25),(28,6), -6:(14,24),(24,22),(15,25),(11,4),(29,25),(14,27),(25,10),(20,4),(12,24),(28,5), -7:(14,23),(23,22),(14,25),(11,3),(29,24),(13,27),(24,10),(20,5),(12,23),(28,4), -8:(14,22),(22,22),(13,25),(11,2),(29,23),(12,27),(24,9),(20,6),(12,23),(28,3), -9:(13,22),(21,22),(13,24),(11,1),(29,22),(11,27),(23,9),(20,7),(12,23),(27,3), -10:(13,21),(20,22),(12,24),(11,0),(29,21),(10,27),(22,9),(20,8),(12,23),(26,3), -11:(12,21),(20,23),(12,23),(11,0),(30,21),(9,27),(21,9),(20,9),(11,23),(25,3), -12:(11,21),(19,23),(11,23),(11,0),(30,20),(9,28),(21,8),(20,10),(10,23),(24,3), -13:(10,21),(19,24),(11,22),(11,0),(30,19),(8,28),(20,8),(21,10),(11,23),(23,3), -14:(10,20),(19,25),(11,21),(11,0),(30,19),(8,29),(19,8),(21,11),(12,23),(22,3), -15:(9,20),(18,25),(11,20),(11,0),(30,19),(8,30),(19,7),(21,12),(12,23),(21,3), -16:(9,19),(17,25),(10,20),(11,0),(30,19),(8,31),(18,7),(21,13),(12,23),(21,2), -17:(9,18),(17,26),(9,20),(11,0),(30,19),(7,31),(17,7),(21,14),(12,23),(20,2), -18:(8,18),(17,27),(9,19),(11,0),(30,19),(6,31),(16,7),(21,15),(12,23),(19,2), -19:(7,18),(16,27),(8,19),(11,0),(30,19),(5,31),(15,7),(21,16),(12,23),(18,2), -20:(7,17),(16,28),(7,19),(11,0),(30,19),(5,31),(14,7),(21,17),(12,23),(18,1), -21:(7,16),(16,29),(7,18),(11,0),(30,19),(5,31),(13,7),(21,18),(12,23),(17,1), -22:(7,15),(16,30),(7,17),(11,0),(30,19),(5,31),(12,7),(20,18),(12,23),(16,1), -23:(6,15),(15,30),(6,17),(11,0),(30,19),(5,31),(11,7),(20,19),(12,23),(15,1), -24:(6,14),(14,30),(5,17),(11,0),(30,19),(5,31),(10,7),(20,20),(12,23),(14,1), -25:(6,13),(13,30),(4,17),(11,0),(30,19),(5,31),(9,7),(20,21),(12,23),(13,1), -26:(6,12),(13,31),(4,16),(11,0),(30,19),(5,31),(8,7),(20,22),(12,23),(12,1), -27:(5,12),(12,31),(3,16),(11,0),(30,19),(5,31),(8,6),(20,23),(12,23),(11,1), -28:(4,12),(11,31),(3,15),(11,0),(30,19),(5,31),(8,6),(20,24),(12,23),(10,1), -29:(3,12),(10,31),(2,15),(11,0),(30,19),(5,31),(8,6),(20,25),(12,23),(9,1), -30:(3,12),(9,31),(2,14),(11,0),(30,19),(5,31),(8,6),(21,25),(12,23),(8,1), -31:(3,12),(8,31),(1,14),(11,0),(30,19),(5,31),(8,6),(22,25),(12,23),(8,0), -32:(3,12),(7,31),(0,14),(11,0),(30,19),(5,31),(8,6),(22,26),(12,23),(7,0), -33:(3,12),(6,31),(0,14),(11,0),(30,19),(5,31),(8,6),(22,27),(12,23),(6,0), -34:(3,12),(5,31),(0,14),(11,0),(30,19),(4,31),(8,6),(22,28),(12,23),(6,0), -35:(3,12),(4,31),(0,14),(11,0),(30,19),(4,30),(8,6),(23,28),(12,23),(6,0), -36:(3,12),(3,31),(0,14),(11,0),(30,19),(4,31),(8,6),(24,28),(12,23),(6,0), -37:(3,12),(2,31),(0,14),(11,0),(30,19),(5,31),(8,6),(24,29),(12,23),(6,0), -38:(3,12),(2,31),(0,14),(11,0),(30,19),(5,31),(8,6),(25,29),(12,23),(6,0), +0:(11,27,PICKING),(5,26,PICKING),(30,6,PICKING),(18,16,PICKING),(12,26,PICKING),(30,26,PICKING),(0,2,PICKING),(29,27,PICKING),(19,19,PICKING),(24,9,PICKING),(29,4,PICKING),(26,31,PICKING) +1:(12,27,PICKING),(5,25,PICKING),(29,6,PICKING),(19,16,PICKING),(13,26,PICKING),(29,26,PICKING),(1,2,PICKING),(28,27,PICKING),(19,20,PICKING),(25,9,PICKING),(28,4,PICKING),(25,31,PICKING) +2:(13,27,PICKING),(5,24,PICKING),(29,5,PICKING),(19,17,PICKING),(13,25,PICKING),(28,26,PICKING),(1,1,PICKING),(27,27,PICKING),(18,20,PICKING),(25,10,PICKING),(28,3,PICKING),(25,30,PICKING) +3:(14,27,PICKING),(5,23,PICKING),(29,4,PICKING),(19,17,CARRYING),(13,24,PICKING),(27,26,PICKING),(1,0,PICKING),(26,27,PICKING),(17,20,PICKING),(25,11,PICKING),(27,3,PICKING),(25,29,PICKING) +4:(15,27,PICKING),(5,22,PICKING),(29,4,CARRYING),(19,16,CARRYING),(14,24,PICKING),(27,25,PICKING),(0,0,PICKING),(26,28,PICKING),(16,20,PICKING),(26,11,PICKING),(26,3,PICKING),(25,28,PICKING) +5:(16,27,PICKING),(5,21,PICKING),(28,4,CARRYING),(18,16,CARRYING),(14,23,PICKING),(27,25,CARRYING),(0,0,CARRYING),(26,28,CARRYING),(15,20,PICKING),(27,11,PICKING),(26,4,PICKING),(25,27,PICKING) +6:(17,27,PICKING),(4,21,PICKING),(28,3,CARRYING),(18,15,CARRYING),(14,22,PICKING),(26,25,CARRYING),(1,0,CARRYING),(25,28,CARRYING),(15,19,PICKING),(27,10,PICKING),(26,4,CARRYING),(25,26,PICKING) +7:(18,27,PICKING),(4,20,PICKING),(27,3,CARRYING),(18,14,CARRYING),(14,21,PICKING),(26,26,CARRYING),(2,0,CARRYING),(24,28,CARRYING),(15,18,PICKING),(28,10,PICKING),(25,4,CARRYING),(25,26,CARRYING) +8:(19,27,PICKING),(4,19,PICKING),(26,3,CARRYING),(17,14,CARRYING),(14,20,PICKING),(25,26,CARRYING),(3,0,CARRYING),(23,28,CARRYING),(15,18,CARRYING),(29,10,PICKING),(25,3,CARRYING),(24,26,CARRYING) +9:(20,27,PICKING),(3,19,PICKING),(25,3,CARRYING),(16,14,CARRYING),(14,19,PICKING),(24,26,CARRYING),(4,0,CARRYING),(23,27,CARRYING),(14,18,CARRYING),(29,9,PICKING),(24,3,CARRYING),(23,26,CARRYING) +10:(21,27,PICKING),(3,18,PICKING),(24,3,CARRYING),(15,14,CARRYING),(14,18,PICKING),(24,25,CARRYING),(5,0,CARRYING),(23,26,CARRYING),(14,17,CARRYING),(29,8,PICKING),(23,3,CARRYING),(22,26,CARRYING) +11:(21,27,CARRYING),(3,17,PICKING),(23,3,CARRYING),(15,13,CARRYING),(14,18,CARRYING),(24,24,CARRYING),(6,0,CARRYING),(23,25,CARRYING),(14,16,CARRYING),(28,8,PICKING),(22,3,CARRYING),(22,25,CARRYING) +12:(20,27,CARRYING),(3,16,PICKING),(22,3,CARRYING),(14,13,CARRYING),(14,17,CARRYING),(25,24,CARRYING),(7,0,CARRYING),(22,25,CARRYING),(14,15,CARRYING),(27,8,PICKING),(21,3,CARRYING),(21,25,CARRYING) +13:(19,27,CARRYING),(3,16,CARRYING),(21,3,CARRYING),(13,13,CARRYING),(14,16,CARRYING),(25,23,CARRYING),(8,0,CARRYING),(21,25,CARRYING),(14,14,CARRYING),(27,7,PICKING),(21,2,CARRYING),(20,25,CARRYING) +14:(18,27,CARRYING),(4,16,CARRYING),(20,3,CARRYING),(12,13,CARRYING),(14,15,CARRYING),(25,22,CARRYING),(9,0,CARRYING),(20,25,CARRYING),(14,13,CARRYING),(27,7,CARRYING),(20,2,CARRYING),(20,24,CARRYING) +15:(18,26,CARRYING),(4,17,CARRYING),(19,3,CARRYING),(11,13,CARRYING),(14,14,CARRYING),(24,22,CARRYING),(9,1,CARRYING),(20,24,CARRYING),(14,12,CARRYING),(27,8,CARRYING),(19,2,CARRYING),(20,23,CARRYING) +16:(17,26,CARRYING),(4,18,CARRYING),(19,4,CARRYING),(11,12,CARRYING),(15,14,CARRYING),(24,21,CARRYING),(10,1,CARRYING),(20,23,CARRYING),(14,11,CARRYING),(28,8,CARRYING),(18,2,CARRYING),(20,22,CARRYING) +17:(16,26,CARRYING),(5,18,CARRYING),(19,5,CARRYING),(11,11,CARRYING),(16,14,CARRYING),(24,20,CARRYING),(11,1,CARRYING),(20,22,CARRYING),(13,11,CARRYING),(29,8,CARRYING),(17,2,CARRYING),(20,21,CARRYING) +18:(15,26,CARRYING),(6,18,CARRYING),(18,5,CARRYING),(10,11,CARRYING),(17,14,CARRYING),(24,19,DELIVERED),(12,1,CARRYING),(20,21,CARRYING),(13,10,CARRYING),(29,9,CARRYING),(16,2,CARRYING),(20,20,CARRYING) +19:(15,25,CARRYING),(7,18,CARRYING),(18,6,CARRYING),(10,10,CARRYING),(17,13,CARRYING),(24,19,PICKING),(13,1,CARRYING),(20,20,CARRYING),(13,9,CARRYING),(29,10,CARRYING),(15,2,CARRYING),(21,20,CARRYING) +20:(15,24,CARRYING),(8,18,CARRYING),(18,7,CARRYING),(9,10,CARRYING),(17,12,CARRYING),(25,19,PICKING),(14,1,CARRYING),(21,20,CARRYING),(13,8,CARRYING),(28,10,CARRYING),(15,1,CARRYING),(21,19,CARRYING) +21:(15,23,CARRYING),(9,18,CARRYING),(18,8,CARRYING),(9,9,CARRYING),(18,12,CARRYING),(25,18,PICKING),(15,1,CARRYING),(21,19,CARRYING),(13,7,CARRYING),(27,10,CARRYING),(15,0,CARRYING),(21,18,CARRYING) +22:(15,22,CARRYING),(9,19,CARRYING),(18,9,CARRYING),(8,9,CARRYING),(19,12,CARRYING),(26,18,PICKING),(16,1,CARRYING),(21,18,CARRYING),(13,6,CARRYING),(27,11,CARRYING),(14,0,CARRYING),(21,17,CARRYING) +23:(15,21,CARRYING),(9,20,CARRYING),(18,10,CARRYING),(7,9,CARRYING),(19,11,CARRYING),(27,18,PICKING),(17,1,CARRYING),(21,17,CARRYING),(12,6,CARRYING),(26,11,CARRYING),(14,1,DELIVERED),(22,17,CARRYING) +24:(14,21,CARRYING),(10,20,CARRYING),(17,10,CARRYING),(7,8,CARRYING),(19,10,CARRYING),(27,19,PICKING),(18,1,CARRYING),(21,16,CARRYING),(12,5,CARRYING),(25,11,CARRYING),(14,1,PICKING),(22,16,DELIVERED) +25:(14,20,CARRYING),(10,21,CARRYING),(17,11,CARRYING),(7,7,CARRYING),(20,10,CARRYING),(27,19,CARRYING),(18,2,CARRYING),(21,15,CARRYING),(12,4,CARRYING),(24,11,CARRYING),(13,1,PICKING),(22,16,PICKING) +26:(14,19,CARRYING),(11,21,CARRYING),(17,12,CARRYING),(7,6,CARRYING),(21,10,DELIVERED),(27,18,CARRYING),(19,2,CARRYING),(21,14,CARRYING),(11,4,CARRYING),(23,11,CARRYING),(13,2,PICKING),(21,16,PICKING) +27:(14,18,CARRYING),(12,21,CARRYING),(17,13,DELIVERED),(7,5,CARRYING),(21,10,PICKING),(27,17,CARRYING),(20,2,CARRYING),(21,13,CARRYING),(10,4,CARRYING),(23,10,CARRYING),(13,3,PICKING),(21,15,PICKING) +28:(13,18,CARRYING),(13,21,CARRYING),(17,13,PICKING),(7,4,CARRYING),(20,10,PICKING),(27,16,CARRYING),(20,3,CARRYING),(21,12,CARRYING),(9,4,CARRYING),(23,9,CARRYING),(12,3,PICKING),(21,14,PICKING) +29:(12,18,CARRYING),(14,21,CARRYING),(16,13,PICKING),(6,4,CARRYING),(20,9,PICKING),(27,15,CARRYING),(20,4,DELIVERED),(21,11,CARRYING),(8,4,CARRYING),(22,9,CARRYING),(12,4,PICKING),(20,14,PICKING) +30:(11,18,CARRYING),(14,22,CARRYING),(15,13,PICKING),(6,3,DELIVERED),(20,8,PICKING),(26,15,CARRYING),(20,4,PICKING),(21,10,CARRYING),(7,4,CARRYING),(21,9,CARRYING),(12,5,PICKING),(19,14,PICKING) +31:(11,17,CARRYING),(14,23,CARRYING),(14,13,PICKING),(6,3,PICKING),(19,8,PICKING),(25,15,CARRYING),(19,4,PICKING),(21,9,CARRYING),(6,4,DELIVERED),(20,9,CARRYING),(12,6,PICKING),(18,14,PICKING) +32:(11,16,CARRYING),(14,24,CARRYING),(14,13,CARRYING),(6,2,PICKING),(18,8,PICKING),(24,15,CARRYING),(19,3,PICKING),(21,8,CARRYING),(6,4,PICKING),(20,10,CARRYING),(11,6,PICKING),(17,14,PICKING) +33:(10,16,CARRYING),(14,25,CARRYING),(13,13,CARRYING),(7,2,PICKING),(17,8,PICKING),(23,15,CARRYING),(19,2,PICKING),(20,8,CARRYING),(5,4,PICKING),(19,10,CARRYING),(11,7,PICKING),(17,13,PICKING) +34:(9,16,CARRYING),(14,26,CARRYING),(12,13,CARRYING),(7,2,CARRYING),(17,7,PICKING),(22,15,CARRYING),(18,2,PICKING),(20,7,CARRYING),(5,5,PICKING),(18,10,CARRYING),(11,8,PICKING),(16,13,PICKING) +35:(9,15,CARRYING),(15,26,CARRYING),(11,13,CARRYING),(8,2,CARRYING),(17,6,PICKING),(21,15,CARRYING),(18,1,PICKING),(20,6,CARRYING),(5,5,CARRYING),(17,10,CARRYING),(11,9,PICKING),(16,12,PICKING) +36:(9,14,CARRYING),(16,26,CARRYING),(10,13,CARRYING),(8,3,CARRYING),(17,5,PICKING),(21,14,CARRYING),(18,0,PICKING),(21,6,CARRYING),(5,4,CARRYING),(16,10,CARRYING),(11,9,CARRYING),(16,11,PICKING) +37:(9,13,CARRYING),(17,26,CARRYING),(10,14,CARRYING),(8,4,CARRYING),(17,5,CARRYING),(20,14,CARRYING),(18,0,CARRYING),(21,5,CARRYING),(6,4,CARRYING),(15,10,CARRYING),(11,10,CARRYING),(15,11,PICKING) +38:(9,12,DELIVERED),(18,26,CARRYING),(9,14,CARRYING),(8,5,CARRYING),(18,5,CARRYING),(19,14,CARRYING),(18,1,CARRYING),(21,4,CARRYING),(6,3,CARRYING),(15,9,CARRYING),(11,11,CARRYING),(14,11,PICKING) +39:(9,12,PICKING),(19,26,CARRYING),(8,14,CARRYING),(8,6,CARRYING),(19,5,CARRYING),(18,14,CARRYING),(17,1,CARRYING),(21,3,CARRYING),(6,2,DELIVERED),(14,9,CARRYING),(11,12,CARRYING),(13,11,PICKING) +40:(9,11,PICKING),(19,27,CARRYING),(7,14,CARRYING),(8,7,CARRYING),(20,5,CARRYING),(17,14,CARRYING),(16,1,CARRYING),(21,2,CARRYING),(6,2,IDLE),(13,9,CARRYING),(11,13,CARRYING),(13,10,PICKING) +41:(8,11,PICKING),(20,27,CARRYING),(7,15,CARRYING),(8,8,CARRYING),(20,6,CARRYING),(16,14,CARRYING),(16,0,DELIVERED),(22,2,CARRYING),(6,2,IDLE),(12,9,CARRYING),(11,14,CARRYING),(13,9,PICKING) +42:(7,11,PICKING),(21,27,CARRYING),(7,16,CARRYING),(9,8,CARRYING),(20,7,DELIVERED),(15,14,CARRYING),(16,0,IDLE),(22,1,CARRYING),(6,2,IDLE),(11,9,CARRYING),(11,15,CARRYING),(12,9,PICKING) +43:(7,11,CARRYING),(22,27,CARRYING),(7,17,CARRYING),(10,8,CARRYING),(20,7,IDLE),(14,14,CARRYING),(16,0,IDLE),(23,1,DELIVERED),(6,2,IDLE),(10,9,CARRYING),(11,16,CARRYING),(11,9,PICKING) +44:(8,11,CARRYING),(23,27,CARRYING),(6,17,CARRYING),(11,8,CARRYING),(20,7,IDLE),(14,13,CARRYING),(16,0,IDLE),(23,1,PICKING),(6,2,IDLE),(9,9,CARRYING),(11,17,CARRYING),(10,9,PICKING) +45:(9,11,CARRYING),(24,27,CARRYING),(6,18,CARRYING),(11,9,CARRYING),(20,7,IDLE),(13,13,CARRYING),(16,0,IDLE),(22,1,PICKING),(6,2,IDLE),(8,9,CARRYING),(11,18,CARRYING),(9,9,PICKING) +46:(10,11,CARRYING),(25,27,CARRYING),(5,18,CARRYING),(12,9,CARRYING),(20,7,IDLE),(12,13,CARRYING),(16,0,IDLE),(22,2,PICKING),(6,2,IDLE),(7,9,CARRYING),(11,19,CARRYING),(8,9,PICKING) +47:(11,11,CARRYING),(26,27,CARRYING),(4,18,CARRYING),(13,9,CARRYING),(20,7,IDLE),(11,13,CARRYING),(16,0,IDLE),(21,2,PICKING),(6,2,IDLE),(6,9,CARRYING),(11,20,CARRYING),(7,9,PICKING) +48:(11,12,CARRYING),(27,27,CARRYING),(4,19,CARRYING),(13,10,CARRYING),(20,7,IDLE),(10,13,CARRYING),(16,0,IDLE),(20,2,PICKING),(6,2,IDLE),(5,9,CARRYING),(10,20,CARRYING),(6,9,PICKING) +49:(11,13,CARRYING),(27,28,CARRYING),(3,19,CARRYING),(13,11,CARRYING),(20,7,IDLE),(9,13,CARRYING),(16,0,IDLE),(19,2,PICKING),(6,2,IDLE),(5,10,CARRYING),(9,20,CARRYING),(5,9,PICKING) +50:(12,13,CARRYING),(28,28,CARRYING),(2,19,CARRYING),(14,11,CARRYING),(20,7,IDLE),(8,13,DELIVERED),(16,0,IDLE),(18,2,PICKING),(6,2,IDLE),(4,10,CARRYING),(8,20,DELIVERED),(5,8,PICKING) +51:(13,13,CARRYING),(28,29,CARRYING),(2,20,DELIVERED),(14,12,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(17,2,PICKING),(6,2,IDLE),(3,10,DELIVERED),(8,20,IDLE),(4,8,PICKING) +52:(14,13,CARRYING),(28,30,DELIVERED),(2,20,IDLE),(14,12,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(16,2,PICKING),(6,2,IDLE),(3,10,PICKING),(8,20,IDLE),(3,8,PICKING) +53:(15,13,CARRYING),(28,30,PICKING),(2,20,IDLE),(14,12,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(15,2,PICKING),(6,2,IDLE),(3,11,PICKING),(8,20,IDLE),(2,8,PICKING) +54:(16,13,CARRYING),(28,29,PICKING),(2,20,IDLE),(14,13,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(15,1,PICKING),(6,2,IDLE),(2,11,PICKING),(8,20,IDLE),(1,8,PICKING) +55:(17,13,CARRYING),(27,29,PICKING),(2,20,IDLE),(14,14,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(14,1,PICKING),(6,2,IDLE),(1,11,PICKING),(8,20,IDLE),(0,8,PICKING) +56:(17,14,CARRYING),(26,29,PICKING),(2,20,IDLE),(14,15,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(14,1,CARRYING),(6,2,IDLE),(0,11,PICKING),(8,20,IDLE),(0,7,PICKING) +57:(18,14,CARRYING),(26,28,PICKING),(2,20,IDLE),(14,16,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(13,1,CARRYING),(6,2,IDLE),(0,11,CARRYING),(8,20,IDLE),(0,6,PICKING) +58:(19,14,CARRYING),(26,27,PICKING),(2,20,IDLE),(14,17,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(13,2,CARRYING),(6,2,IDLE),(1,11,CARRYING),(8,20,IDLE),(0,5,PICKING) +59:(20,14,CARRYING),(25,27,PICKING),(2,20,IDLE),(14,18,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(13,3,CARRYING),(6,2,IDLE),(2,11,CARRYING),(8,20,IDLE),(0,5,CARRYING) +60:(21,14,CARRYING),(25,26,PICKING),(2,20,IDLE),(15,18,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(13,4,CARRYING),(6,2,IDLE),(3,11,CARRYING),(8,20,IDLE),(1,5,CARRYING) +61:(21,15,CARRYING),(24,26,PICKING),(2,20,IDLE),(15,19,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(13,5,CARRYING),(6,2,IDLE),(4,11,CARRYING),(8,20,IDLE),(1,6,CARRYING) +62:(21,16,CARRYING),(24,25,PICKING),(2,20,IDLE),(15,20,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(13,6,CARRYING),(6,2,IDLE),(5,11,CARRYING),(8,20,IDLE),(2,6,CARRYING) +63:(21,17,DELIVERED),(24,25,CARRYING),(2,20,IDLE),(16,20,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(13,7,CARRYING),(6,2,IDLE),(6,11,CARRYING),(8,20,IDLE),(3,6,CARRYING) +64:(21,17,IDLE),(23,25,CARRYING),(2,20,IDLE),(16,21,CARRYING),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(13,8,CARRYING),(6,2,IDLE),(6,12,CARRYING),(8,20,IDLE),(3,7,CARRYING) +65:(21,17,IDLE),(22,25,CARRYING),(2,20,IDLE),(16,22,DELIVERED),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(13,9,CARRYING),(6,2,IDLE),(6,13,CARRYING),(8,20,IDLE),(4,7,CARRYING) +66:(21,17,IDLE),(21,25,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(12,9,CARRYING),(6,2,IDLE),(6,14,CARRYING),(8,20,IDLE),(4,8,CARRYING) +67:(21,17,IDLE),(20,25,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(11,9,CARRYING),(6,2,IDLE),(6,15,CARRYING),(8,20,IDLE),(5,8,CARRYING) +68:(21,17,IDLE),(20,24,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(10,9,CARRYING),(6,2,IDLE),(7,15,CARRYING),(8,20,IDLE),(5,9,CARRYING) +69:(21,17,IDLE),(20,23,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(9,9,CARRYING),(6,2,IDLE),(7,16,CARRYING),(8,20,IDLE),(6,9,CARRYING) +70:(21,17,IDLE),(20,22,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(8,9,CARRYING),(6,2,IDLE),(7,17,CARRYING),(8,20,IDLE),(6,10,CARRYING) +71:(21,17,IDLE),(20,21,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(7,9,CARRYING),(6,2,IDLE),(7,18,CARRYING),(8,20,IDLE),(6,11,CARRYING) +72:(21,17,IDLE),(19,21,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(7,10,CARRYING),(6,2,IDLE),(7,19,CARRYING),(8,20,IDLE),(7,11,CARRYING) +73:(21,17,IDLE),(19,20,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(7,11,CARRYING),(6,2,IDLE),(8,19,CARRYING),(8,20,IDLE),(8,11,CARRYING) +74:(21,17,IDLE),(18,20,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(7,12,CARRYING),(6,2,IDLE),(9,19,CARRYING),(8,20,IDLE),(9,11,CARRYING) +75:(21,17,IDLE),(17,20,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(7,13,CARRYING),(6,2,IDLE),(9,20,CARRYING),(8,20,IDLE),(10,11,CARRYING) +76:(21,17,IDLE),(17,19,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(7,14,CARRYING),(6,2,IDLE),(10,20,CARRYING),(8,20,IDLE),(11,11,CARRYING) +77:(21,17,IDLE),(17,18,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(6,14,CARRYING),(6,2,IDLE),(10,21,CARRYING),(8,20,IDLE),(11,12,CARRYING) +78:(21,17,IDLE),(16,18,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(6,15,CARRYING),(6,2,IDLE),(10,22,CARRYING),(8,20,IDLE),(11,13,CARRYING) +79:(21,17,IDLE),(16,17,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,15,CARRYING),(6,2,IDLE),(10,23,CARRYING),(8,20,IDLE),(12,13,CARRYING) +80:(21,17,IDLE),(16,16,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,16,CARRYING),(6,2,IDLE),(10,24,CARRYING),(8,20,IDLE),(13,13,CARRYING) +81:(21,17,IDLE),(16,15,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,17,CARRYING),(6,2,IDLE),(11,24,CARRYING),(8,20,IDLE),(14,13,CARRYING) +82:(21,17,IDLE),(16,14,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,DELIVERED),(6,2,IDLE),(11,25,CARRYING),(8,20,IDLE),(15,13,CARRYING) +83:(21,17,IDLE),(16,13,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(11,26,CARRYING),(8,20,IDLE),(15,14,CARRYING) +84:(21,17,IDLE),(16,12,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(11,27,CARRYING),(8,20,IDLE),(16,14,CARRYING) +85:(21,17,IDLE),(16,11,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(12,27,CARRYING),(8,20,IDLE),(16,15,CARRYING) +86:(21,17,IDLE),(15,11,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(13,27,CARRYING),(8,20,IDLE),(16,16,CARRYING) +87:(21,17,IDLE),(15,10,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,27,CARRYING),(8,20,IDLE),(17,16,CARRYING) +88:(21,17,IDLE),(15,9,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,28,CARRYING),(8,20,IDLE),(17,17,CARRYING) +89:(21,17,IDLE),(14,9,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,DELIVERED),(8,20,IDLE),(17,18,CARRYING) +90:(21,17,IDLE),(13,9,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(17,19,CARRYING) +91:(21,17,IDLE),(12,9,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(17,20,CARRYING) +92:(21,17,IDLE),(12,8,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(18,20,CARRYING) +93:(21,17,IDLE),(11,8,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(19,20,CARRYING) +94:(21,17,IDLE),(10,8,CARRYING),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(19,21,CARRYING) +95:(21,17,IDLE),(9,8,DELIVERED),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(20,21,CARRYING) +96:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(20,22,CARRYING) +97:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(20,23,CARRYING) +98:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(20,24,CARRYING) +99:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(20,25,CARRYING) +100:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(21,25,CARRYING) +101:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(22,25,CARRYING) +102:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(23,25,CARRYING) +103:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(24,25,CARRYING) +104:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(24,26,CARRYING) +105:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(25,26,DELIVERED) +106:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(25,26,IDLE) diff --git a/src/AnimationControl.tsx b/src/AnimationControl.tsx index 263bc12..33a2965 100644 --- a/src/AnimationControl.tsx +++ b/src/AnimationControl.tsx @@ -1,49 +1,49 @@ -import SkipPreviousIcon from '@mui/icons-material/SkipPrevious'; -import PlayArrowIcon from '@mui/icons-material/PlayArrow'; -import PauseTwoToneIcon from '@mui/icons-material/PauseTwoTone'; -import SkipNextIcon from '@mui/icons-material/SkipNext'; -import Button from '@mui/material/Button'; -import ButtonGroup from '@mui/material/ButtonGroup'; -import Box from '@mui/material/Box'; -import RestartAltIcon from '@mui/icons-material/RestartAlt'; -import Slider from '@mui/material/Slider'; -import RepeatIcon from '@mui/icons-material/Repeat'; -import RepeatOnIcon from '@mui/icons-material/RepeatOn'; -import Stack from '@mui/material/Stack'; -import Tooltip from '@mui/material/Tooltip'; -import LooksOneIcon from '@mui/icons-material/LooksOne'; -import LooksOneOutlinedIcon from '@mui/icons-material/LooksOneOutlined'; -import { useEffect } from 'react'; -import DirectionsIcon from '@mui/icons-material/Directions'; -import DirectionsOutlinedIcon from '@mui/icons-material/DirectionsOutlined'; -import FilterCenterFocusOutlinedIcon from '@mui/icons-material/FilterCenterFocusOutlined'; -import ScreenshotMonitorOutlinedIcon from '@mui/icons-material/ScreenshotMonitorOutlined'; -import StartIcon from '@mui/icons-material/Start'; -import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined'; -import SmartToyIcon from '@mui/icons-material/SmartToy'; -import FlagIcon from '@mui/icons-material/Flag'; -import OutlinedFlagIcon from '@mui/icons-material/OutlinedFlag'; -import PolylineIcon from '@mui/icons-material/Polyline'; -import PolylineOutlinedIcon from '@mui/icons-material/PolylineOutlined'; +import SkipPreviousIcon from "@mui/icons-material/SkipPrevious"; +import PlayArrowIcon from "@mui/icons-material/PlayArrow"; +import PauseTwoToneIcon from "@mui/icons-material/PauseTwoTone"; +import SkipNextIcon from "@mui/icons-material/SkipNext"; +import Button from "@mui/material/Button"; +import ButtonGroup from "@mui/material/ButtonGroup"; +import Box from "@mui/material/Box"; +import RestartAltIcon from "@mui/icons-material/RestartAlt"; +import Slider from "@mui/material/Slider"; +import RepeatIcon from "@mui/icons-material/Repeat"; +import RepeatOnIcon from "@mui/icons-material/RepeatOn"; +import Stack from "@mui/material/Stack"; +import Tooltip from "@mui/material/Tooltip"; +import LooksOneIcon from "@mui/icons-material/LooksOne"; +import LooksOneOutlinedIcon from "@mui/icons-material/LooksOneOutlined"; +import { useEffect } from "react"; +import DirectionsIcon from "@mui/icons-material/Directions"; +import DirectionsOutlinedIcon from "@mui/icons-material/DirectionsOutlined"; +import FilterCenterFocusOutlinedIcon from "@mui/icons-material/FilterCenterFocusOutlined"; +import ScreenshotMonitorOutlinedIcon from "@mui/icons-material/ScreenshotMonitorOutlined"; +import StartIcon from "@mui/icons-material/Start"; +import SmartToyOutlinedIcon from "@mui/icons-material/SmartToyOutlined"; +import SmartToyIcon from "@mui/icons-material/SmartToy"; +import FlagIcon from "@mui/icons-material/Flag"; +import OutlinedFlagIcon from "@mui/icons-material/OutlinedFlag"; +import PolylineIcon from "@mui/icons-material/Polyline"; +import PolylineOutlinedIcon from "@mui/icons-material/PolylineOutlined"; const STEP_SIZE_INCREMENT = 0.2; const STEP_SIZE_MAX = 10; const STEP_SIZE_MIN = 0.2; -const STEP_BACKWARD_KEY = 'ArrowLeft'; -const PLAY_PAUSE_KEY = ' '; -const STEP_FORWARD_KEY = 'ArrowRight'; -const RESTART_KEY = 'r'; -const LOOP_KEY = 'l'; -const FIT_VIEW_KEY = 'f'; -const SHOW_AGENT_ID_KEY = 'a'; -const STEP_SIZE_UP_KEY = 'ArrowUp'; -const STEP_SIZE_DOWN_KEY = 'ArrowDown'; -const TRACE_PATHS_KEY = 'p'; -const SCREENSHOT_KEY = 's'; -const SHOW_CELL_ID_KEY = 'c'; -const SHOW_GOALS_KEY = 'g'; -const SHOW_GOAL_VECTORS_KEY = 'v'; +const STEP_BACKWARD_KEY = "ArrowLeft"; +const PLAY_PAUSE_KEY = " "; +const STEP_FORWARD_KEY = "ArrowRight"; +const RESTART_KEY = "r"; +const LOOP_KEY = "l"; +const FIT_VIEW_KEY = "f"; +const SHOW_AGENT_ID_KEY = "a"; +const STEP_SIZE_UP_KEY = "ArrowUp"; +const STEP_SIZE_DOWN_KEY = "ArrowDown"; +const TRACE_PATHS_KEY = "p"; +const SCREENSHOT_KEY = "s"; +const SHOW_CELL_ID_KEY = "c"; +const SHOW_GOALS_KEY = "g"; +const SHOW_GOAL_VECTORS_KEY = "v"; interface AnimationControlProps { playAnimation: boolean; @@ -53,7 +53,7 @@ interface AnimationControlProps { onRestart: () => void; stepSize: number; onStepSizeChange: (speed: number) => void; - loopAnimation: boolean, + loopAnimation: boolean; onLoopAnimationChange: (loopAnimation: boolean) => void; onFitView: () => void; showAgentId: boolean; @@ -71,9 +71,9 @@ interface AnimationControlProps { } function AnimationControl({ - playAnimation, - onPlayAnimationChange, - onSkipBackward, + playAnimation, + onPlayAnimationChange, + onSkipBackward, onSkipForward, onRestart, stepSize, @@ -93,14 +93,14 @@ function AnimationControl({ setShowGoals, showGoalVectors, setShowGoalVectors, -}: AnimationControlProps) { +}: AnimationControlProps) { const roundAndSetStepSize = (value: number) => { onStepSizeChange(Number(value.toFixed(1))); - } - + }; + const handleSliderChange = (event: Event, value: number | number[]) => { event.preventDefault(); - if (typeof value === 'number') roundAndSetStepSize(value); + if (typeof value === "number") roundAndSetStepSize(value); }; useEffect(() => { @@ -113,7 +113,7 @@ function AnimationControl({ onSkipBackward(); } else if (event.key === PLAY_PAUSE_KEY) { onPlayAnimationChange(!playAnimation); - } else if (event.key === STEP_FORWARD_KEY) { + } else if (event.key === STEP_FORWARD_KEY) { onSkipForward(); } else if (event.key === RESTART_KEY) { onRestart(); @@ -123,9 +123,15 @@ function AnimationControl({ onFitView(); } else if (event.key === SHOW_AGENT_ID_KEY) { onShowAgentIdChange(!showAgentId); - } else if (event.key === STEP_SIZE_UP_KEY && stepSize + STEP_SIZE_INCREMENT <= STEP_SIZE_MAX) { + } else if ( + event.key === STEP_SIZE_UP_KEY && + stepSize + STEP_SIZE_INCREMENT <= STEP_SIZE_MAX + ) { roundAndSetStepSize(stepSize + STEP_SIZE_INCREMENT); - } else if (event.key === STEP_SIZE_DOWN_KEY && stepSize - STEP_SIZE_INCREMENT >= STEP_SIZE_MIN) { + } else if ( + event.key === STEP_SIZE_DOWN_KEY && + stepSize - STEP_SIZE_INCREMENT >= STEP_SIZE_MIN + ) { roundAndSetStepSize(stepSize - STEP_SIZE_INCREMENT); } else if (event.key === TRACE_PATHS_KEY) { onTracePathsChange(!tracePaths); @@ -139,24 +145,42 @@ function AnimationControl({ setShowGoalVectors(!showGoalVectors); } }; - window.addEventListener('keydown', handleKeyDown); + window.addEventListener("keydown", handleKeyDown); return () => { - window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener("keydown", handleKeyDown); }; - }, [playAnimation, onPlayAnimationChange, loopAnimation, onFitView, - onLoopAnimationChange, onRestart, onShowAgentIdChange, onSkipBackward, - onSkipForward, onStepSizeChange, showAgentId, stepSize, onTracePathsChange, tracePaths, - takeScreenshot, showCellId, setShowCellId, showGoals, setShowGoals, showGoalVectors, - setShowGoalVectors]); + }, [ + playAnimation, + onPlayAnimationChange, + loopAnimation, + onFitView, + onLoopAnimationChange, + onRestart, + onShowAgentIdChange, + onSkipBackward, + onSkipForward, + onStepSizeChange, + showAgentId, + stepSize, + onTracePathsChange, + tracePaths, + takeScreenshot, + showCellId, + setShowCellId, + showGoals, + setShowGoals, + showGoalVectors, + setShowGoalVectors, + ]); return ( - - Adjust animation step size - ({STEP_SIZE_UP_KEY}/{STEP_SIZE_DOWN_KEY}) +
+ Adjust animation step size ({STEP_SIZE_UP_KEY}/ + {STEP_SIZE_DOWN_KEY})
} > @@ -168,7 +192,7 @@ function AnimationControl({ max={STEP_SIZE_MAX} valueLabelDisplay="auto" onChange={handleSliderChange} - sx={{ width: '40%', height: "auto"}} + sx={{ width: "40%", height: "auto" }} />
@@ -182,13 +206,23 @@ function AnimationControl({ - - - + + + @@ -205,11 +239,18 @@ function AnimationControl({ - - @@ -219,7 +260,10 @@ function AnimationControl({ - @@ -228,45 +272,82 @@ function AnimationControl({ - - - + - + - + - -
- ); + ); } export default AnimationControl; diff --git a/src/App.tsx b/src/App.tsx index 504daf7..05a021f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,116 +1,124 @@ -import Box from '@mui/material/Box'; -import Grid from '@mui/material/Grid2'; -import ConfigBar from './ConfigBar'; -import Visualizer from './Visualizer'; -import { Graph } from './Graph'; -import { Solution } from './Solution'; -import React, { useCallback } from 'react'; -import { StrictMode, useRef } from 'react'; +import Box from "@mui/material/Box"; +import Grid from "@mui/material/Grid2"; +import ConfigBar from "./ConfigBar"; +import Visualizer from "./Visualizer"; +import { Graph } from "./Graph"; +import { Solution } from "./Solution"; +import React, { useCallback } from "react"; +import { StrictMode, useRef } from "react"; function App() { - const pixiAppRef = useRef<{ - skipBackward?: () => void; - skipForward?: () => void; - restart?: () => void; - fit?: () => void; - takeScreenshot?: () => void; - }>(null); + const pixiAppRef = useRef<{ + skipBackward?: () => void; + skipForward?: () => void; + restart?: () => void; + fit?: () => void; + takeScreenshot?: () => void; + }>(null); - const [graph, setGraph] = React.useState(null); - const [solution, setSolution] = React.useState(null); - const [playAnimation, setPlayAnimation] = React.useState(true); - const [stepSize, setStepSize] = React.useState(1.0); - const [loopAnimation, setLoopAnimation] = React.useState(true); - const [showAgentId, setShowAgentId] = React.useState(false); - const [tracePaths, setTracePaths] = React.useState(true); - const [canScreenshot, setCanScreenshot] = React.useState(true); - const [showCellId, setShowCellId] = React.useState(false); - const [showGoals, setShowGoals] = React.useState(true); - const [showGoalVectors, setShowGoalVectors] = React.useState(false); + const [graph, setGraph] = React.useState(null); + const [solution, setSolution] = React.useState(null); + const [playAnimation, setPlayAnimation] = React.useState(true); + const [stepSize, setStepSize] = React.useState(1.0); + const [loopAnimation, setLoopAnimation] = React.useState(true); + const [showAgentId, setShowAgentId] = React.useState(false); + const [tracePaths, setTracePaths] = React.useState(true); + const [canScreenshot, setCanScreenshot] = React.useState(true); + const [showCellId, setShowCellId] = React.useState(false); + const [showGoals, setShowGoals] = React.useState(true); + const [showGoalVectors, setShowGoalVectors] = + React.useState(false); - const handleSkipBackward = () => { - if (pixiAppRef.current?.skipBackward) { - pixiAppRef.current.skipBackward(); - } - } + const handleSkipBackward = () => { + if (pixiAppRef.current?.skipBackward) { + pixiAppRef.current.skipBackward(); + } + }; - const handleSkipForward = () => { - if (pixiAppRef.current?.skipForward) { - pixiAppRef.current.skipForward(); - } - } + const handleSkipForward = () => { + if (pixiAppRef.current?.skipForward) { + pixiAppRef.current.skipForward(); + } + }; - const handleRestart = () => { - if (pixiAppRef.current?.restart) { - pixiAppRef.current.restart(); - } - } + const handleRestart = () => { + if (pixiAppRef.current?.restart) { + pixiAppRef.current.restart(); + } + }; - const handleFitView = () => { - if (pixiAppRef.current?.fit) { - pixiAppRef.current.fit(); - } - } + const handleFitView = () => { + if (pixiAppRef.current?.fit) { + pixiAppRef.current.fit(); + } + }; - const handleTakeScreenshot = () => { - if (pixiAppRef.current?.takeScreenshot) { - pixiAppRef.current.takeScreenshot(); - } - } + const handleTakeScreenshot = () => { + if (pixiAppRef.current?.takeScreenshot) { + pixiAppRef.current.takeScreenshot(); + } + }; - return ( - - - - - - - - setGraph(graph), [])} - onSolutionChange={useCallback((solution: Solution | null) => setSolution(solution), [])} - playAnimation={playAnimation} - onPlayAnimationChange={setPlayAnimation} - onSkipBackward={handleSkipBackward} - onSkipForward={handleSkipForward} - onRestart={handleRestart} - stepSize={stepSize} - onStepSizeChange={setStepSize} - loopAnimation={loopAnimation} - onLoopAnimationChange={setLoopAnimation} - onFitView={handleFitView} - showAgentId={showAgentId} - onShowAgentIdChange={setShowAgentId} - tracePaths={tracePaths} - onTracePathsChange={setTracePaths} - canScreenshot={canScreenshot} - takeScreenshot={handleTakeScreenshot} - showCellId={showCellId} - setShowCellId={setShowCellId} - showGoals={showGoals} - setShowGoals={setShowGoals} - showGoalVectors={showGoalVectors} - setShowGoalVectors={setShowGoalVectors} - /> - - - - - ); + return ( + + + + + + + + setGraph(graph), + [] + )} + onSolutionChange={useCallback( + (solution: Solution | null) => + setSolution(solution), + [] + )} + playAnimation={playAnimation} + onPlayAnimationChange={setPlayAnimation} + onSkipBackward={handleSkipBackward} + onSkipForward={handleSkipForward} + onRestart={handleRestart} + stepSize={stepSize} + onStepSizeChange={setStepSize} + loopAnimation={loopAnimation} + onLoopAnimationChange={setLoopAnimation} + onFitView={handleFitView} + showAgentId={showAgentId} + onShowAgentIdChange={setShowAgentId} + tracePaths={tracePaths} + onTracePathsChange={setTracePaths} + canScreenshot={canScreenshot} + takeScreenshot={handleTakeScreenshot} + showCellId={showCellId} + setShowCellId={setShowCellId} + showGoals={showGoals} + setShowGoals={setShowGoals} + showGoalVectors={showGoalVectors} + setShowGoalVectors={setShowGoalVectors} + /> + + + + + ); } export default App; diff --git a/src/ConfigBar.tsx b/src/ConfigBar.tsx index 6f863f5..e31901e 100644 --- a/src/ConfigBar.tsx +++ b/src/ConfigBar.tsx @@ -1,243 +1,283 @@ -import AnimationControl from './AnimationControl'; -import { Graph } from './Graph'; -import { parseSolution, Solution } from './Solution'; -import { Divider, Stack, Button } from '@mui/material'; +import AnimationControl from "./AnimationControl"; +import { Graph } from "./Graph"; +import { parseSolution, Solution } from "./Solution"; +import { Divider, Stack, Button } from "@mui/material"; import { MuiFileInput } from "mui-file-input"; -import React, { useEffect } from 'react'; -import ClearIcon from '@mui/icons-material/Clear'; -import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined'; -import Tooltip from '@mui/material/Tooltip'; +import React, { useEffect } from "react"; +import ClearIcon from "@mui/icons-material/Clear"; +import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; +import Tooltip from "@mui/material/Tooltip"; interface ConfigBarProps { - graph: Graph | null; - onGraphChange: (graph: Graph | null) => void; - onSolutionChange: (solution: Solution | null) => void; - playAnimation: boolean; - onPlayAnimationChange: (playAnimation: boolean) => void; - onSkipBackward: () => void; - onSkipForward: () => void; - onRestart: () => void; - stepSize: number; - onStepSizeChange: (speed: number) => void; - loopAnimation: boolean, - onLoopAnimationChange: (loopAnimation: boolean) => void; - onFitView: () => void; - showAgentId: boolean; - onShowAgentIdChange: (showAgentId: boolean) => void; - tracePaths: boolean; - onTracePathsChange: (tracePaths: boolean) => void; - canScreenshot: boolean; - takeScreenshot: () => void; - showCellId: boolean; - setShowCellId: (showCellId: boolean) => void; - showGoals: boolean; - setShowGoals: (showGoals: boolean) => void; - showGoalVectors: boolean; - setShowGoalVectors: (showGoalVectors: boolean) => void; + graph: Graph | null; + onGraphChange: (graph: Graph | null) => void; + onSolutionChange: (solution: Solution | null) => void; + playAnimation: boolean; + onPlayAnimationChange: (playAnimation: boolean) => void; + onSkipBackward: () => void; + onSkipForward: () => void; + onRestart: () => void; + stepSize: number; + onStepSizeChange: (speed: number) => void; + loopAnimation: boolean; + onLoopAnimationChange: (loopAnimation: boolean) => void; + onFitView: () => void; + showAgentId: boolean; + onShowAgentIdChange: (showAgentId: boolean) => void; + tracePaths: boolean; + onTracePathsChange: (tracePaths: boolean) => void; + canScreenshot: boolean; + takeScreenshot: () => void; + showCellId: boolean; + setShowCellId: (showCellId: boolean) => void; + showGoals: boolean; + setShowGoals: (showGoals: boolean) => void; + showGoalVectors: boolean; + setShowGoalVectors: (showGoalVectors: boolean) => void; } function ConfigBar({ - graph, - onGraphChange, - onSolutionChange, - playAnimation, - onPlayAnimationChange, - onSkipBackward, - onSkipForward, - onRestart, - stepSize, - onStepSizeChange, - loopAnimation, - onLoopAnimationChange, - onFitView, - showAgentId, - onShowAgentIdChange, - tracePaths, - onTracePathsChange, - canScreenshot, - takeScreenshot, - showCellId, - setShowCellId, - showGoals, - setShowGoals, - showGoalVectors, - setShowGoalVectors, + graph, + onGraphChange, + onSolutionChange, + playAnimation, + onPlayAnimationChange, + onSkipBackward, + onSkipForward, + onRestart, + stepSize, + onStepSizeChange, + loopAnimation, + onLoopAnimationChange, + onFitView, + showAgentId, + onShowAgentIdChange, + tracePaths, + onTracePathsChange, + canScreenshot, + takeScreenshot, + showCellId, + setShowCellId, + showGoals, + setShowGoals, + showGoalVectors, + setShowGoalVectors, }: ConfigBarProps) { - const repoName = "JustinShetty/mapf-visualizer"; - const [mapFile, setMapFile] = React.useState(null); - const [mapError, setMapError] = React.useState(null); - const [solutionFile, setSolutionFile] = React.useState(null); - const [solutionError, setSolutionError] = React.useState(null); + const repoName = "RenKoya1/mapd-visualizer"; + const [mapFile, setMapFile] = React.useState(null); + const [mapError, setMapError] = React.useState(null); + const [solutionFile, setSolutionFile] = React.useState(null); + const [solutionError, setSolutionError] = React.useState( + null + ); - const blurActiveElement = () => { - // Blur (remove focus from) the file input - if (document.activeElement instanceof HTMLElement) { - document.activeElement.blur(); - } - } + const blurActiveElement = () => { + // Blur (remove focus from) the file input + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + }; - const handleLoadDemo = (mapName: string) => { - fetch(`${import.meta.env.BASE_URL}/${mapName}.map`) - .then((response) => response.text()) - .then((text) => { - handleMapChange(new File([text], `${mapName}.map`)); - return fetch(`${import.meta.env.BASE_URL}/demo_${mapName}.txt`); - }) - .then((response) => response.text()) - .then((text) => { - handleSolutionChange(new File([text], `demo_${mapName}.txt`)); - }); - }; + const handleLoadDemo = (mapName: string) => { + fetch(`${import.meta.env.BASE_URL}/${mapName}.map`) + .then((response) => response.text()) + .then((text) => { + handleMapChange(new File([text], `${mapName}.map`)); + return fetch(`${import.meta.env.BASE_URL}/demo_${mapName}.txt`); + }) + .then((response) => response.text()) + .then((text) => { + handleSolutionChange(new File([text], `demo_${mapName}.txt`)); + }); + }; - useEffect(() => { - if (mapFile === null) { - onGraphChange(null); - return; - } - mapFile.text().then((text) => { - try { - onGraphChange(new Graph(text)); - setMapError(null); - } catch (e) { - setMapFile(null); - onGraphChange(null); - setMapError(e instanceof Error ? e.message : "An unexpected error occurred"); - } - }); - }, [mapFile, onGraphChange]); + useEffect(() => { + if (mapFile === null) { + onGraphChange(null); + return; + } + mapFile.text().then((text) => { + try { + onGraphChange(new Graph(text)); + setMapError(null); + } catch (e) { + setMapFile(null); + onGraphChange(null); + setMapError( + e instanceof Error + ? e.message + : "An unexpected error occurred" + ); + } + }); + }, [mapFile, onGraphChange]); - useEffect(() => { - if (solutionFile === null) { - onSolutionChange(null); - return - } - solutionFile.text().then((text) => { - try { - if (graph === null) throw new Error("Map must be loaded before solution"); - const soln = parseSolution(text); - soln.forEach((config) => { - config.forEach((pose) => { - if (pose.position.x > graph.width || pose.position.y > graph.height) { - throw new Error(`Invalid solution: position ${pose.position} is out of bounds`); + useEffect(() => { + if (solutionFile === null) { + onSolutionChange(null); + return; + } + solutionFile.text().then((text) => { + try { + if (graph === null) + throw new Error("Map must be loaded before solution"); + const soln = parseSolution(text); + soln.forEach((config) => { + config.forEach((pose) => { + if ( + pose.position.x > graph.width || + pose.position.y > graph.height + ) { + throw new Error( + `Invalid solution: position ${pose.position} is out of bounds` + ); + } + }); + }); + onSolutionChange(soln); + setSolutionError(null); + } catch (e) { + setSolutionFile(null); + setSolutionError( + e instanceof Error + ? e.message + : "An unexpected error occurred" + ); } - }); }); - onSolutionChange(soln); - setSolutionError(null); - } catch (e) { - setSolutionFile(null); - setSolutionError(e instanceof Error ? e.message : "An unexpected error occurred"); - } - }); - }, [graph, solutionFile, onSolutionChange]); + }, [graph, solutionFile, onSolutionChange]); - const handleMapChange = (newValue: File | null) => { - setMapFile(newValue); - setSolutionFile(null); - blurActiveElement(); - }; + const handleMapChange = (newValue: File | null) => { + setMapFile(newValue); + setSolutionFile(null); + blurActiveElement(); + }; - const handleSolutionChange = (newValue: File | null) => { - setSolutionFile(newValue); - blurActiveElement(); - }; + const handleSolutionChange = (newValue: File | null) => { + setSolutionFile(newValue); + blurActiveElement(); + }; - const downloadFile = (file: File) => { - const url = URL.createObjectURL(file); - const a = document.createElement('a'); - a.href = url; - a.download = file.name; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } + const downloadFile = (file: File) => { + const url = URL.createObjectURL(file); + const a = document.createElement("a"); + a.href = url; + a.download = file.name; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; - return ( - - - - - - - -

Map

- - - }} - /> - {mapFile && - - - - } - - {mapError &&

{mapError}

} -
- - -

Solution

- - - }} - /> - {solutionFile && - - - - } - - {solutionError &&

{solutionError}

} -
- - - - - {repoName} - -
- ); + +
+ + +

Map

+ + , + }} + /> + {mapFile && ( + + + + )} + + {mapError &&

{mapError}

} +
+ + +

Solution

+ + , + }} + /> + {solutionFile && ( + + + + )} + + {solutionError && ( +

{solutionError}

+ )} +
+ + + + + {repoName} + + + ); } -export default ConfigBar; \ No newline at end of file +export default ConfigBar; diff --git a/src/Graph.tsx b/src/Graph.tsx index 9825241..19161c3 100644 --- a/src/Graph.tsx +++ b/src/Graph.tsx @@ -2,7 +2,7 @@ export class Coordinate { public x: number = 0; public y: number = 0; - constructor (x: number, y: number) { + constructor(x: number, y: number) { this.x = x; this.y = y; } @@ -22,13 +22,13 @@ export class Graph { } private parseGraph(fileContent: string) { - const lines = fileContent.trim().split('\n'); + const lines = fileContent.trim().split("\n"); if (lines.length < 4) { - throw new Error('Invalid map file'); + throw new Error("Invalid map file"); } const height = Number(lines[1].split(" ")[1]); if (height !== lines.length - 4) { - throw new Error('Invalid map file, check height'); + throw new Error("Invalid map file, check height"); } this.height = height; const width = Number(lines[2].split(" ")[1]); @@ -36,13 +36,13 @@ export class Graph { const graph = lines.slice(4); for (let y = 0; y < graph.length; y++) { if (graph[y].length !== width) { - throw new Error('Invalid map file, check width'); + throw new Error("Invalid map file, check width"); } for (let x = 0; x < this.width; x++) { - if (graph[y][x] !== '.') { - this.obstacles.set((new Coordinate(x, y)).toString(), true); + if (graph[y][x] !== ".") { + this.obstacles.set(new Coordinate(x, y).toString(), true); } } } } -} \ No newline at end of file +} diff --git a/src/Params.tsx b/src/Params.tsx index 390fdc1..91155ff 100644 --- a/src/Params.tsx +++ b/src/Params.tsx @@ -1,18 +1,8 @@ -export const BACKGROUND_COLOR = 0xFFFFFF; +export const BACKGROUND_COLOR = 0xffffff; export const GRID_COLOR = 0x000000; export const TEXT_COLOR = GRID_COLOR; export const AGENT_COLORS: number[] = [ - 0xE91E63, - 0x2196F3, - 0x4CAF50, - 0xFF9800, - 0x00BCD4, - 0x9C27B0, - 0x795548, - 0xFFBB3B, - 0xF44336, - 0x607D8B, - 0x009688, - 0x3F51B5 - ]; + 0xe91e63, 0x2196f3, 0x4caf50, 0xff9800, 0x00bcd4, 0x9c27b0, 0x795548, + 0xffbb3b, 0xf44336, 0x607d8b, 0x009688, 0x3f51b5, +]; diff --git a/src/PixiApp.tsx b/src/PixiApp.tsx index df37dca..32c1b7f 100644 --- a/src/PixiApp.tsx +++ b/src/PixiApp.tsx @@ -1,10 +1,27 @@ -import { useEffect, useRef, useState, forwardRef, useImperativeHandle, useCallback } from 'react'; -import * as PIXI from 'pixi.js'; -import { Viewport } from 'pixi-viewport'; -import { Graph } from './Graph'; -import { Solution, Orientation, orientationToRotation } from './Solution'; -import { Coordinate } from './Graph'; -import { BACKGROUND_COLOR, GRID_COLOR, TEXT_COLOR, AGENT_COLORS } from './Params'; +import { + useEffect, + useRef, + useState, + forwardRef, + useImperativeHandle, + useCallback, +} from "react"; +import * as PIXI from "pixi.js"; +import { Viewport } from "pixi-viewport"; +import { Graph } from "./Graph"; +import { + Solution, + Orientation, + orientationToRotation, + AgentState, +} from "./Solution"; +import { Coordinate } from "./Graph"; +import { + BACKGROUND_COLOR, + GRID_COLOR, + TEXT_COLOR, + AGENT_COLORS, +} from "./Params"; const GRID_UNIT_TO_PX: number = 100; const FONT_SUPER_RESOLUTION_SCALE = 3; @@ -20,486 +37,672 @@ interface PixiAppProps { showAgentId: boolean; tracePaths: boolean; setCanScreenshot: (canScreenshot: boolean) => void; - showCellId: boolean, - showGoals: boolean, - showGoalVectors: boolean, + showCellId: boolean; + showGoals: boolean; + showGoalVectors: boolean; } -const PixiApp = forwardRef(({ - width, - height, - graph, - solution, - playAnimation, - stepSize, - loopAnimation, - showAgentId, - tracePaths, - setCanScreenshot, - showCellId, - showGoals, - showGoalVectors, -}: PixiAppProps, ref) => { - // this is a mess of state and refs, but how I got everything to work... - // maybe someday I will clean this up or maybe someone who knows React better than me can help - // - // the variables that are used inside the animation callbacks must - // be stored in refs because the callbacks are created "once" and - // the variables are updated outside of the callbacks - const [app, setApp] = useState(null); - const [viewport, setViewport] = useState(null); - const canvasRef = useRef(null); - const [grid, setGrid] = useState(null); - const playAnimationRef = useRef(playAnimation); - const timestepRef = useRef(0.0); - const stepSizeRef = useRef(1.0); - const loopAnimationRef = useRef(loopAnimation); - const hudRef = useRef(null); - const timestepTextRef = useRef(null); - const showAgentIdRef = useRef(showAgentId); - const tickerCallbackRef = useRef<() => void>(() => {}); - const agentsRef = useRef(null); - const agentPathsRef = useRef<{ full: PIXI.Container, partial: PIXI.Container }>({ - full: new PIXI.Container(), - partial: new PIXI.Container() - }); // same order as agentsRef - const goalMarkersRef = useRef(new PIXI.Container()); - const goalVectorsRef = useRef(new PIXI.Container()); - - // Scale a position from grid units to pixels - const scalePosition = (position: number) : number => { - return position * GRID_UNIT_TO_PX + GRID_UNIT_TO_PX / 2; - } - - function resetTimestep() { - timestepRef.current = 0.0; - } - - function takeScreenshot() { - if (app && viewport && grid) { - app.renderer.extract.base64(viewport).then((data) => { - const link = document.createElement('a'); - link.download = 'screenshot.png'; - link.href = data; - link.click(); - link.remove(); - }); +const PixiApp = forwardRef( + ( + { + width, + height, + graph, + solution, + playAnimation, + stepSize, + loopAnimation, + showAgentId, + tracePaths, + setCanScreenshot, + showCellId, + showGoals, + showGoalVectors, + }: PixiAppProps, + ref + ) => { + // this is a mess of state and refs, but how I got everything to work... + // maybe someday I will clean this up or maybe someone who knows React better than me can help + // + // the variables that are used inside the animation callbacks must + // be stored in refs because the callbacks are created "once" and + // the variables are updated outside of the callbacks + const [app, setApp] = useState(null); + const [viewport, setViewport] = useState(null); + const canvasRef = useRef(null); + const [grid, setGrid] = useState(null); + const playAnimationRef = useRef(playAnimation); + const timestepRef = useRef(0.0); + const stepSizeRef = useRef(1.0); + const loopAnimationRef = useRef(loopAnimation); + const hudRef = useRef(null); + const timestepTextRef = useRef(null); + const showAgentIdRef = useRef(showAgentId); + const tickerCallbackRef = useRef<() => void>(() => {}); + const agentsRef = useRef(null); + const pickingMarkersRef = useRef(new PIXI.Container()); + const deliveredMarkersRef = useRef( + new PIXI.Container() + ); + const agentPathsRef = useRef<{ + full: PIXI.Container; + partial: PIXI.Container; + }>({ + full: new PIXI.Container(), + partial: new PIXI.Container(), + }); // same order as agentsRef + const goalMarkersRef = useRef(new PIXI.Container()); + const goalVectorsRef = useRef(new PIXI.Container()); + + // Scale a position from grid units to pixels + const scalePosition = (position: number): number => { + return position * GRID_UNIT_TO_PX + GRID_UNIT_TO_PX / 2; + }; + + function resetTimestep() { + timestepRef.current = 0.0; } - } - useImperativeHandle(ref, () => ({ - skipBackward: () => { - timestepRef.current = Math.max(0, timestepRef.current - stepSizeRef.current); - }, - skipForward: () => { - if (solution) { - timestepRef.current = Math.min(timestepRef.current + stepSizeRef.current, solution.length - 1); + function takeScreenshot() { + if (app && viewport && grid) { + app.renderer.extract.base64(viewport).then((data) => { + const link = document.createElement("a"); + link.download = "screenshot.png"; + link.href = data; + link.click(); + link.remove(); + }); } - }, - restart: () => { - resetTimestep(); - }, - fit: () => { - fit(); - }, - takeScreenshot: () => { - takeScreenshot(); } - })); - - // Fit the viewport to the grid - const fit = useCallback(() => { - if (viewport === null || grid === null) return; - viewport.fitWorld(); - viewport.moveCenter( - grid.position.x + grid.width / 2, - grid.position.y + grid.height / 2 + + useImperativeHandle(ref, () => ({ + skipBackward: () => { + timestepRef.current = Math.max( + 0, + timestepRef.current - stepSizeRef.current + ); + }, + skipForward: () => { + if (solution) { + timestepRef.current = Math.min( + timestepRef.current + stepSizeRef.current, + solution.length - 1 + ); + } + }, + restart: () => { + resetTimestep(); + }, + fit: () => { + fit(); + }, + takeScreenshot: () => { + takeScreenshot(); + }, + })); + + // Fit the viewport to the grid + const fit = useCallback(() => { + if (viewport === null || grid === null) return; + viewport.fitWorld(); + viewport.moveCenter( + grid.position.x + grid.width / 2, + grid.position.y + grid.height / 2 + ); + }, [viewport, grid]); + + const moveAndRotateSprites = useCallback( + (agents: PIXI.Container[], currentTime: number) => { + if (!solution) return; + + const currentTimestep = Math.floor(currentTime); + const interpolationProgress = currentTime - currentTimestep; + const currentState = solution[currentTimestep]; + const nextState = + solution[ + Math.min(currentTimestep + 1, solution.length - 1) + ]; + + // Interpolate between current and next states + agents.forEach((agent, index) => { + // Show or hide agent ID + const idText = agent.children[1]; + if (idText !== undefined) { + idText.visible = showAgentIdRef.current; + } + + const startPose = currentState[index]; + const endPose = nextState[index]; + + // Interpolate position + agent.x = + startPose.position.x + + (endPose.position.x - startPose.position.x) * + interpolationProgress; + agent.y = + startPose.position.y + + (endPose.position.y - startPose.position.y) * + interpolationProgress; + agent.x = scalePosition(agent.x); + agent.y = scalePosition(agent.y); + + // orientation-aware visualization has two objects for each sprite + const circleContainer: PIXI.Container = agent.children[0]; + if ( + circleContainer === undefined || + circleContainer.children.length < 2 + ) + return; + + // Interpolate rotation + const startRotation = orientationToRotation( + startPose.orientation + ); + const endRotation = orientationToRotation( + endPose.orientation + ); + + circleContainer.rotation = + startRotation + + (endRotation - startRotation) * interpolationProgress; + }); + }, + [solution] ); - }, [viewport, grid]); - - const moveAndRotateSprites = useCallback((agents: PIXI.Container[], currentTime: number) => { - if (!solution) return; - - const currentTimestep = Math.floor(currentTime); - const interpolationProgress = currentTime - currentTimestep; - const currentState = solution[currentTimestep]; - const nextState = solution[Math.min(currentTimestep + 1, solution.length - 1)]; - - // Interpolate between current and next states - agents.forEach((agent, index) => { - // Show or hide agent ID - const idText = agent.children[1]; - if (idText !== undefined) { - idText.visible = showAgentIdRef.current; - } + const updateStateMarkers = useCallback(() => { + if (!solution) return; + + const currentTimestep = Math.floor(timestepRef.current); + pickingMarkersRef.current.removeChildren(); + deliveredMarkersRef.current.removeChildren(); + + solution[0].forEach((_pose, agentId) => { + let lastPickingPose: (typeof solution)[0][0] | null = null; + let deliveredPose: (typeof solution)[0][0] | null = null; + + for (let t = currentTimestep + 1; t < solution.length; t++) { + const pose = solution[t][agentId]; + if (pose.state === AgentState.NONE) { + deliveredPose = solution[solution.length - 1][agentId]; + break; + } + if (pose.state === AgentState.IDLE) { + break; + } - const startPose = currentState[index]; - const endPose = nextState[index]; - - // Interpolate position - agent.x = - startPose.position.x + - (endPose.position.x - startPose.position.x) * interpolationProgress; - agent.y = - startPose.position.y + - (endPose.position.y - startPose.position.y) * interpolationProgress; - agent.x = scalePosition(agent.x); - agent.y = scalePosition(agent.y); - - // orientation-aware visualization has two objects for each sprite - const circleContainer: PIXI.Container = agent.children[0]; - if (circleContainer === undefined || circleContainer.children.length < 2) return; - - // Interpolate rotation - const startRotation = orientationToRotation(startPose.orientation); - const endRotation = orientationToRotation(endPose.orientation); - - circleContainer.rotation = - startRotation + - (endRotation - startRotation) * interpolationProgress; - }); - }, [solution]); - - const updatePaths = useCallback((agents: PIXI.Container[], currentTime: number) => { - if (!solution) return; - - const currentTimestep = Math.floor(currentTime); - const interpolationProgress = currentTime - currentTimestep; - - - agents.forEach((_agent, index) => { - const agentLineStyle = { - width: GRID_UNIT_TO_PX / 10, - color: AGENT_COLORS[index % AGENT_COLORS.length], - cap: "round" as const - }; + if (pose.state === AgentState.PICKING) { + lastPickingPose = pose; + continue; + } - const full_segments = agentPathsRef.current.full.children[index] as PIXI.Container - const partial_segments = agentPathsRef.current.partial.children[index] as PIXI.Container + if (!deliveredPose && pose.state === AgentState.DELIVERED) { + deliveredPose = pose; + break; + } + } + if (lastPickingPose) { + const marker = new PIXI.Graphics(); + const r = GRID_UNIT_TO_PX / 5; + marker + .circle( + scalePosition(lastPickingPose.position.x), + scalePosition(lastPickingPose.position.y), + r + ) + .fill(AGENT_COLORS[agentId % AGENT_COLORS.length]); + pickingMarkersRef.current.addChild(marker); + } + + if (deliveredPose) { + const marker = new PIXI.Graphics(); + const size = GRID_UNIT_TO_PX / 4; + marker + .rect( + scalePosition(deliveredPose.position.x) - size / 2, + scalePosition(deliveredPose.position.y) - size / 2, + size, + size + ) + .fill(AGENT_COLORS[agentId % AGENT_COLORS.length]); + deliveredMarkersRef.current.addChild(marker); + } + }); + }, [solution]); + + const updatePaths = useCallback( + (agents: PIXI.Container[], currentTime: number) => { + if (!solution) return; + + const currentTimestep = Math.floor(currentTime); + const interpolationProgress = currentTime - currentTimestep; + + agents.forEach((_agent, index) => { + const agentLineStyle = { + width: GRID_UNIT_TO_PX / 10, + color: AGENT_COLORS[index % AGENT_COLORS.length], + cap: "round" as const, + }; + + const full_segments = agentPathsRef.current.full.children[ + index + ] as PIXI.Container; + const partial_segments = agentPathsRef.current.partial + .children[index] as PIXI.Container; + + while (full_segments.children.length > currentTimestep) { + if (full_segments.children.length === 0) break; + full_segments.removeChildAt( + full_segments.children.length - 1 + ); + } + partial_segments.removeChildren(); + + // Full segments + while (full_segments.children.length < currentTimestep) { + const segIndex = full_segments.children.length; + const segment = full_segments.addChild( + new PIXI.Graphics() + ); + segment.moveTo( + scalePosition(solution[segIndex][index].position.x), + scalePosition(solution[segIndex][index].position.y) + ); + segment.lineTo( + scalePosition( + solution[segIndex + 1][index].position.x + ), + scalePosition( + solution[segIndex + 1][index].position.y + ) + ); + segment.stroke(agentLineStyle); + } + + // Partial segment + if ( + interpolationProgress > 0 && + currentTimestep < solution.length - 1 + ) { + const segment = partial_segments.addChild( + new PIXI.Graphics() + ); + // const segment = path.children.length === currentTimestep ? path.addChild(new PIXI.Graphics()) : path.children[currentTimestep] as PIXI.Graphics; + segment.moveTo( + scalePosition( + solution[currentTimestep][index].position.x + ), + scalePosition( + solution[currentTimestep][index].position.y + ) + ); + const interpolatedPosition = { + x: + solution[currentTimestep][index].position.x + + (solution[currentTimestep + 1][index].position + .x - + solution[currentTimestep][index].position + .x) * + interpolationProgress, + y: + solution[currentTimestep][index].position.y + + (solution[currentTimestep + 1][index].position + .y - + solution[currentTimestep][index].position + .y) * + interpolationProgress, + }; + segment.lineTo( + scalePosition(interpolatedPosition.x), + scalePosition(interpolatedPosition.y) + ); + segment.stroke(agentLineStyle); + } + }); + }, + [solution] + ); + + const updateGoalVectors = useCallback( + (agents: PIXI.Container[]) => { + if (!solution) return; + + const currentTimestep = Math.floor(timestepRef.current); + + agents.forEach((agent, index) => { + const goalVector = goalVectorsRef.current.children[ + index + ] as PIXI.Graphics; + + let goalPose = null; + for ( + let t = currentTimestep + 1; + t < solution.length; + t++ + ) { + const pose = solution[t][index]; + if (pose.state === AgentState.DELIVERED) { + goalPose = pose; + break; + } + } + + goalVector.clear(); + if (goalPose) { + goalVector + .moveTo(agent.x, agent.y) + .lineTo( + scalePosition(goalPose.position.x), + scalePosition(goalPose.position.y) + ) + .stroke({ + color: AGENT_COLORS[ + index % AGENT_COLORS.length + ], + width: Math.max(1, GRID_UNIT_TO_PX / 25), + cap: "round" as const, + }); + } + }); + }, + [solution] + ); - while(full_segments.children.length > currentTimestep) { - if (full_segments.children.length === 0) break; - full_segments.removeChildAt(full_segments.children.length - 1); + // Animate the solution + const animateSolution = useCallback(() => { + if (app === null || viewport === null) return; + if (tickerCallbackRef.current) { + app.ticker.remove(tickerCallbackRef.current); + if (agentsRef.current) viewport.removeChild(agentsRef.current); + if (agentPathsRef.current) { + agentPathsRef.current.full.removeChildren(); + agentPathsRef.current.partial.removeChildren(); + } + if (timestepTextRef.current) timestepTextRef.current.text = ""; + if (goalMarkersRef.current) + goalMarkersRef.current.removeChildren(); + if (goalVectorsRef.current) + goalVectorsRef.current.removeChildren(); } - partial_segments.removeChildren(); - - // Full segments - while (full_segments.children.length < currentTimestep) { - const segIndex = full_segments.children.length; - const segment = full_segments.addChild(new PIXI.Graphics()); - segment.moveTo( - scalePosition(solution[segIndex][index].position.x), - scalePosition(solution[segIndex][index].position.y) + if (solution === null) return; + resetTimestep(); + + // Check if the solution is orientation-aware + const orientationAware: boolean = + solution[0][0].orientation !== Orientation.NONE; + + // Picking markers + viewport.addChild(pickingMarkersRef.current); + viewport.addChild(deliveredMarkersRef.current); + + // Paths + viewport.addChild(agentPathsRef.current.full); + viewport.addChild(agentPathsRef.current.partial); + solution[0].forEach(() => { + agentPathsRef.current.full.addChild(new PIXI.Container()); + agentPathsRef.current.partial.addChild(new PIXI.Container()); + }); + + // Goal vectors + const goalVectors = viewport.addChild(goalVectorsRef.current); + solution[solution.length - 1].forEach(() => { + goalVectors.addChild(new PIXI.Graphics()); + }); + + // Agents + const agents = viewport.addChild(new PIXI.Container()); + agentsRef.current = agents; + solution[0].forEach((_pose, agentId) => { + // build agent + const agent = agents.addChild(new PIXI.Container()); + const circleContainer = agent.addChild(new PIXI.Container()); + const circle = circleContainer.addChild(new PIXI.Graphics()); + const agentColor = AGENT_COLORS[agentId % AGENT_COLORS.length]; + circle.circle(0, 0, GRID_UNIT_TO_PX / 3).fill(agentColor); + if (orientationAware) { + const radius = circle.width / 2; + const triangle = circleContainer.addChild( + new PIXI.Graphics() + ); + triangle + .poly([0, radius, 0, -radius, radius, 0]) + .fill(BACKGROUND_COLOR); + } + const idText = agent.addChild( + new PIXI.Text({ + text: `${agentId}`, + style: { + fontFamily: "Arial", + fontSize: circle.width / 2, + fill: TEXT_COLOR, + }, + }) ); - segment.lineTo( - scalePosition(solution[segIndex + 1][index].position.x), - scalePosition(solution[segIndex + 1][index].position.y) + idText.style.fontSize *= FONT_SUPER_RESOLUTION_SCALE; + idText.scale.set( + 1 / FONT_SUPER_RESOLUTION_SCALE, + 1 / FONT_SUPER_RESOLUTION_SCALE ); - segment.stroke(agentLineStyle); - } + idText.x = -idText.width / 2; + idText.y = -idText.height / 2; + }); - // Partial segment - if (interpolationProgress > 0 && currentTimestep < solution.length - 1) { - const segment = partial_segments.addChild(new PIXI.Graphics()); - // const segment = path.children.length === currentTimestep ? path.addChild(new PIXI.Graphics()) : path.children[currentTimestep] as PIXI.Graphics; - segment.moveTo( - scalePosition(solution[currentTimestep][index].position.x), - scalePosition(solution[currentTimestep][index].position.y) - ); - const interpolatedPosition = { - x: solution[currentTimestep][index].position.x + - (solution[currentTimestep + 1][index].position.x - solution[currentTimestep][index].position.x) * interpolationProgress, - y: solution[currentTimestep][index].position.y + - (solution[currentTimestep + 1][index].position.y - solution[currentTimestep][index].position.y) * interpolationProgress, - } - segment.lineTo(scalePosition(interpolatedPosition.x), scalePosition(interpolatedPosition.y)); - segment.stroke(agentLineStyle); - } - }); - }, [solution]); - - const updateGoalVectors = useCallback((agents: PIXI.Container[]) => { - if (!solution) return; - agents.forEach((agent, index) => { - const goal = solution[solution.length - 1][index]; - const goalVector = goalVectorsRef.current.children[index] as PIXI.Graphics; - goalVector.clear() - .moveTo(agent.x, agent.y) - .lineTo( - scalePosition(goal.position.x), - scalePosition(goal.position.y) - ) - .stroke({ - color: AGENT_COLORS[index % AGENT_COLORS.length], - width: Math.max(1, GRID_UNIT_TO_PX / 25), - cap: "round" as const - }); - }); - }, [solution]); - - // Animate the solution - const animateSolution = useCallback(() => { - if (app === null || viewport === null) return; - if (tickerCallbackRef.current) { - app.ticker.remove(tickerCallbackRef.current); - if (agentsRef.current) viewport.removeChild(agentsRef.current); - if (agentPathsRef.current) { - agentPathsRef.current.full.removeChildren(); - agentPathsRef.current.partial.removeChildren(); - } - if (timestepTextRef.current) timestepTextRef.current.text = ""; - if (goalMarkersRef.current) goalMarkersRef.current.removeChildren(); - if (goalVectorsRef.current) goalVectorsRef.current.removeChildren(); - } - if (solution === null) return; - resetTimestep(); - - // Check if the solution is orientation-aware - const orientationAware: boolean = solution[0][0].orientation !== Orientation.NONE; - - // Goal markers - const goalMarkers = viewport.addChild(goalMarkersRef.current); - solution[solution.length - 1].forEach((pose, agentId) => { - const marker = goalMarkers.addChild(new PIXI.Graphics()); - const width = GRID_UNIT_TO_PX / 4; - marker.rect( - scalePosition(pose.position.x) - width / 2, - scalePosition(pose.position.y) - width / 2, - width, width) - .fill(AGENT_COLORS[agentId % AGENT_COLORS.length]); - }); - - // Paths - viewport.addChild(agentPathsRef.current.full); - viewport.addChild(agentPathsRef.current.partial); - solution[0].forEach(() => { - agentPathsRef.current.full.addChild(new PIXI.Container()); - agentPathsRef.current.partial.addChild(new PIXI.Container()); - }); - - // Goal vectors - const goalVectors = viewport.addChild(goalVectorsRef.current); - solution[solution.length - 1].forEach(() => { - goalVectors.addChild(new PIXI.Graphics()); - }); - - // Agents - const agents = viewport.addChild(new PIXI.Container()); - agentsRef.current = agents; - solution[0].forEach((_pose, agentId) => { - // build agent - const agent = agents.addChild(new PIXI.Container()); - const circleContainer = agent.addChild(new PIXI.Container()); - const circle = circleContainer.addChild(new PIXI.Graphics()); - const agentColor = AGENT_COLORS[agentId % AGENT_COLORS.length]; - circle - .circle(0, 0, GRID_UNIT_TO_PX/3) - .fill(agentColor); - if (orientationAware) { - const radius = circle.width / 2; - const triangle = circleContainer.addChild(new PIXI.Graphics()); - triangle - .poly([0, radius, 0, -radius, radius, 0]) - .fill(BACKGROUND_COLOR); - } - const idText = agent.addChild(new PIXI.Text({ - text: `${agentId}`, - style: { - fontFamily: 'Arial', - fontSize: circle.width / 2, - fill: TEXT_COLOR, + const animate = () => { + if (timestepTextRef.current) { + timestepTextRef.current.text = `${timestepRef.current.toFixed( + 1 + )} / ${(solution.length - 1).toFixed(1)}`; } - })); - idText.style.fontSize *= FONT_SUPER_RESOLUTION_SCALE; - idText.scale.set(1 / FONT_SUPER_RESOLUTION_SCALE, 1 / FONT_SUPER_RESOLUTION_SCALE); - idText.x = -idText.width / 2; - idText.y = -idText.height / 2; - }); - - const animate = () => { - if(timestepTextRef.current) { - timestepTextRef.current.text = `${timestepRef.current.toFixed(1)} / ${(solution.length - 1).toFixed(1)}`; - } - if (playAnimationRef.current === true) { - if (timestepRef.current < solution.length - 1) { - const approximateFramerate = 60; - timestepRef.current += stepSizeRef.current / approximateFramerate; - } else if (loopAnimationRef.current) { - resetTimestep(); + if (playAnimationRef.current === true) { + if (timestepRef.current < solution.length - 1) { + const approximateFramerate = 60; + timestepRef.current += + stepSizeRef.current / approximateFramerate; + } else if (loopAnimationRef.current) { + resetTimestep(); + } } - } - moveAndRotateSprites(agents.children as PIXI.Container[], timestepRef.current); - updatePaths(agents.children as PIXI.Container[], timestepRef.current); - updateGoalVectors(agents.children as PIXI.Container[]); - } - app.ticker.add(animate); - tickerCallbackRef.current = animate; - }, [app, viewport, solution, moveAndRotateSprites, updatePaths, updateGoalVectors]); - - // Initialize the app and viewport when the canvas is ready - useEffect(() => { - if (app === null) { - const canvas = canvasRef.current; - if (canvas) { - const pixiApp = new PIXI.Application(); - pixiApp.init({ - width: width, - height: height, - canvas: canvas, - background: BACKGROUND_COLOR, - antialias: true, // for smooooooth circles - }).then(() => { - setApp(pixiApp); - }); - } - } else { - app.canvas.style.position = "absolute"; - if (viewport === null) { - const viewport = new Viewport({ - screenWidth: width, - screenHeight: height, - worldWidth: width*2, - worldHeight: height*2, - events: app.renderer.events, - }); - viewport.drag().pinch().wheel(); - setViewport(viewport); + moveAndRotateSprites( + agents.children as PIXI.Container[], + timestepRef.current + ); + updatePaths( + agents.children as PIXI.Container[], + timestepRef.current + ); + updateGoalVectors(agents.children as PIXI.Container[]); + updateStateMarkers(); + }; + app.ticker.add(animate); + tickerCallbackRef.current = animate; + }, [ + app, + viewport, + solution, + moveAndRotateSprites, + updatePaths, + updateGoalVectors, + ]); + + // Initialize the app and viewport when the canvas is ready + useEffect(() => { + if (app === null) { + const canvas = canvasRef.current; + if (canvas) { + const pixiApp = new PIXI.Application(); + pixiApp + .init({ + width: width, + height: height, + canvas: canvas, + background: BACKGROUND_COLOR, + antialias: true, // for smooooooth circles + }) + .then(() => { + setApp(pixiApp); + }); + } } else { - if (app.stage.children.length === 0) { - app.stage.addChild(viewport); - hudRef.current = app.stage.addChild(new PIXI.Container()); - const textStyle = new PIXI.TextStyle({ - fontSize: 24, - fill: TEXT_COLOR, - fontFamily: "Arial", - fontWeight: "bold", - stroke: { - color: BACKGROUND_COLOR, - width: 4 - }, + app.canvas.style.position = "absolute"; + if (viewport === null) { + const viewport = new Viewport({ + screenWidth: width, + screenHeight: height, + worldWidth: width * 2, + worldHeight: height * 2, + events: app.renderer.events, }); - timestepTextRef.current = hudRef.current.addChild( - new PIXI.Text({ - x: width / 100, - y: height / 100, - style: textStyle, - }) - ); + viewport.drag().pinch().wheel(); + setViewport(viewport); + } else { + if (app.stage.children.length === 0) { + app.stage.addChild(viewport); + hudRef.current = app.stage.addChild( + new PIXI.Container() + ); + const textStyle = new PIXI.TextStyle({ + fontSize: 24, + fill: TEXT_COLOR, + fontFamily: "Arial", + fontWeight: "bold", + stroke: { + color: BACKGROUND_COLOR, + width: 4, + }, + }); + timestepTextRef.current = hudRef.current.addChild( + new PIXI.Text({ + x: width / 100, + y: height / 100, + style: textStyle, + }) + ); + + hudRef.current.addChild( + new PIXI.Text({ + x: width - width / 100, + y: height / 100, + anchor: new PIXI.Point(1, 0), + text: "Click and drag to pan. Scroll to zoom.", + style: textStyle, + }) + ); + } + app.start(); + } + } + return () => { + app?.stop(); + }; + }, [app, viewport, height, width]); - hudRef.current.addChild( + const drawGrid = useCallback(() => { + if (viewport === null || graph === null) return null; + const grid = viewport.addChild(new PIXI.Container()); + + for (let x: number = 0; x < graph.width; x++) { + for (let y: number = 0; y < graph.height; y++) { + const cellContainer = grid.addChild(new PIXI.Container()); + const cellGraphic = cellContainer.addChild( + new PIXI.Graphics() + ); + const cellX = x * GRID_UNIT_TO_PX; + const cellY = y * GRID_UNIT_TO_PX; + const strokeWidth = GRID_UNIT_TO_PX / 10; + cellGraphic + .rect(cellX, cellY, GRID_UNIT_TO_PX, GRID_UNIT_TO_PX) + .stroke({ color: GRID_COLOR, width: strokeWidth }); + if (graph.obstacles.has(new Coordinate(x, y).toString())) { + cellGraphic.fill({ color: GRID_COLOR }); + } + const idText = cellContainer.addChild( new PIXI.Text({ - x: width - width / 100, - y: height / 100, - anchor: new PIXI.Point(1, 0), - text: "Click and drag to pan. Scroll to zoom.", - style: textStyle, + text: `${x + y * graph.width}`, + style: { + fontFamily: "Arial", + fontSize: cellGraphic.width / 6, + fill: TEXT_COLOR, + }, }) ); + idText.style.fontSize *= FONT_SUPER_RESOLUTION_SCALE; + idText.scale.set( + 1 / FONT_SUPER_RESOLUTION_SCALE, + 1 / FONT_SUPER_RESOLUTION_SCALE + ); + idText.x = cellX + strokeWidth; + idText.y = cellY + strokeWidth; } - app.start(); } - } - return () => {app?.stop()}; - }, [app, viewport, height, width]); - - const drawGrid = useCallback(() => { - if (viewport === null || graph === null) return null; - const grid = viewport.addChild(new PIXI.Container()); - - for (let x: number = 0; x < graph.width; x++) { - for (let y: number = 0; y < graph.height; y++) { - const cellContainer = grid.addChild(new PIXI.Container()); - const cellGraphic = cellContainer.addChild(new PIXI.Graphics()); - const cellX = x * GRID_UNIT_TO_PX; - const cellY = y * GRID_UNIT_TO_PX; - const strokeWidth = GRID_UNIT_TO_PX / 10; - cellGraphic.rect(cellX, cellY, GRID_UNIT_TO_PX, GRID_UNIT_TO_PX) - .stroke({color: GRID_COLOR, width: strokeWidth}); - if (graph.obstacles.has(new Coordinate(x, y).toString())) { - cellGraphic.fill({color: GRID_COLOR}); + + viewport.worldHeight = grid.height * 1.2; + viewport.worldWidth = grid.width * 1.2; + return grid; + }, [viewport, graph]); + + useEffect(() => { + if (app !== null && viewport !== null) { + app.renderer.resize(width, height); + viewport.resize(width, height); + if (hudRef.current) { + hudRef.current.children[0].x = width / 100; + hudRef.current.children[0].y = height / 100; + hudRef.current.children[1].x = width - width / 100; + hudRef.current.children[1].y = height / 100; } - const idText = cellContainer.addChild(new PIXI.Text({ - text: `${x + y * graph.width}`, - style: { - fontFamily: 'Arial', - fontSize: cellGraphic.width / 6, - fill: TEXT_COLOR, - } - })); - idText.style.fontSize *= FONT_SUPER_RESOLUTION_SCALE; - idText.scale.set(1 / FONT_SUPER_RESOLUTION_SCALE, 1 / FONT_SUPER_RESOLUTION_SCALE); - idText.x = cellX + strokeWidth; - idText.y = cellY + strokeWidth; + fit(); } - } - - viewport.worldHeight = grid.height * 1.2; - viewport.worldWidth = grid.width * 1.2; - return grid; - }, [viewport, graph]); - - // Resize the viewport when the width or height changes - useEffect(() => { - if (app !== null && viewport !== null) { - app.renderer.resize(width, height); - viewport.resize(width, height); - if (hudRef.current) { - hudRef.current.children[0].x = width / 100; - hudRef.current.children[0].y = height / 100; - hudRef.current.children[1].x = width - width / 100; - hudRef.current.children[1].y = height / 100; + }, [app, fit, viewport, width, height]); + + // Draw the grid when the graph changes + useEffect(() => { + if (app && viewport) { + if (grid) viewport.removeChild(grid); + if (graph) setGrid(drawGrid()); } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [app, graph, viewport]); // Excluding 'grid' to prevent infinite loop + + // Fit the viewport and try to animate the solution when the grid or solution changes + useEffect(() => { fit(); - } - }, [app, fit, viewport, width, height]); + animateSolution(); + setCanScreenshot(!!solution); + }, [grid, solution, animateSolution, fit, setCanScreenshot]); + + // Update the playAnimationRef when the playAnimation changes + useEffect(() => { + playAnimationRef.current = playAnimation; + stepSizeRef.current = stepSize; + loopAnimationRef.current = loopAnimation; + showAgentIdRef.current = showAgentId; + }, [playAnimation, stepSize, loopAnimation, showAgentId]); + + useEffect(() => { + if (!grid) return; + grid.children.forEach((cellContainer) => { + const idText = cellContainer.children[1]; + if (idText) { + idText.visible = showCellId; + } + }); + }, [showCellId, grid]); - // Draw the grid when the graph changes - useEffect(() => { - if (app && viewport) { - if (grid) viewport.removeChild(grid); - if (graph) setGrid(drawGrid()); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [app, graph, viewport]); // Excluding 'grid' to prevent infinite loop - - // Fit the viewport and try to animate the solution when the grid or solution changes - useEffect(() => { - fit(); - animateSolution(); - setCanScreenshot(!!solution); - }, [grid, solution, animateSolution, fit, setCanScreenshot]); - - // Update the playAnimationRef when the playAnimation changes - useEffect(() => { - playAnimationRef.current = playAnimation; - stepSizeRef.current = stepSize; - loopAnimationRef.current = loopAnimation; - showAgentIdRef.current = showAgentId; - }, [playAnimation, stepSize, loopAnimation, showAgentId]); - - useEffect(() => { - if (!grid) return; - grid.children.forEach((cellContainer) => { - const idText = cellContainer.children[1]; - if (idText) { - idText.visible = showCellId; + useEffect(() => { + if (agentPathsRef.current) { + agentPathsRef.current.full.visible = tracePaths; + agentPathsRef.current.partial.visible = tracePaths; } - }); - }, [showCellId, grid]); + if (goalMarkersRef.current) + goalMarkersRef.current.visible = showGoals; + if (goalVectorsRef.current) + goalVectorsRef.current.visible = showGoalVectors; + }, [tracePaths, showGoals, showGoalVectors]); - useEffect(() => { - if (agentPathsRef.current) { - agentPathsRef.current.full.visible = tracePaths; - agentPathsRef.current.partial.visible = tracePaths; - } - if (goalMarkersRef.current) goalMarkersRef.current.visible = showGoals; - if (goalVectorsRef.current) goalVectorsRef.current.visible = showGoalVectors; - }, [tracePaths, showGoals, showGoalVectors]); - - return -}); + return ; + } +); -export default PixiApp; \ No newline at end of file +export default PixiApp; diff --git a/src/Solution.tsx b/src/Solution.tsx index 26dbae2..022e0a7 100644 --- a/src/Solution.tsx +++ b/src/Solution.tsx @@ -1,40 +1,63 @@ -import { Coordinate } from './Graph'; +import { Coordinate } from "./Graph"; +export enum AgentState { + PICKING, + CARRYING, + DELIVERED, + IDLE, + NONE, +} export enum Orientation { NONE, X_MINUS, X_PLUS, Y_MINUS, - Y_PLUS + Y_PLUS, } export function orientationToRotation(o: Orientation): number { switch (o) { - case Orientation.NONE: return 0; - case Orientation.X_MINUS: return Math.PI; - case Orientation.X_PLUS: return 0; - case Orientation.Y_MINUS: return -Math.PI / 2; - case Orientation.Y_PLUS: return Math.PI / 2; + case Orientation.NONE: + return 0; + case Orientation.X_MINUS: + return Math.PI; + case Orientation.X_PLUS: + return 0; + case Orientation.Y_MINUS: + return -Math.PI / 2; + case Orientation.Y_PLUS: + return Math.PI / 2; } -}; +} function orientationFromString(s: string): Orientation { switch (s) { - case "X_MINUS": return Orientation.X_MINUS; - case "X_PLUS": return Orientation.X_PLUS; - case "Y_MINUS": return Orientation.Y_MINUS; - case "Y_PLUS": return Orientation.Y_PLUS; - default: return Orientation.NONE; + case "X_MINUS": + return Orientation.X_MINUS; + case "X_PLUS": + return Orientation.X_PLUS; + case "Y_MINUS": + return Orientation.Y_MINUS; + case "Y_PLUS": + return Orientation.Y_PLUS; + default: + return Orientation.NONE; } } export class Pose { public position: Coordinate = new Coordinate(0, 0); public orientation: Orientation = Orientation.NONE; + public state: AgentState = AgentState.NONE; - constructor(position: Coordinate = new Coordinate(0, 0), orientation: Orientation = Orientation.NONE) { + constructor( + position: Coordinate = new Coordinate(0, 0), + orientation: Orientation = Orientation.NONE, + state: AgentState = AgentState.NONE + ) { this.position = position; this.orientation = orientation; + this.state = state; } } @@ -45,24 +68,36 @@ export function parseSolution(text: string): Solution { const lines = text.trim().split("\n"); const solution: Solution = []; + const regex = + /\((\d+),(\d+)(?:,([XY]_[A-Z]{4,5}))?(?:,(PICKING|CARRYING|DELIVERED|IDLE|NONE))?\)/g; + for (const line of lines) { const config: Config = []; - const pos_re = /(\((\d+),(\d+),?([XY]{1}_[A-Z]{4,5})?\),)/g; - while (true) { - const m = pos_re.exec(line); - if (m === null) break; - if (m === null || m.length !== 5) throw new Error("Invalid solution"); - const x = Number(m[2]); - if (x < 0) throw new Error(`Invalid solution: position ${x} is negative`); - const y = Number(m[3]); - if (y < 0) throw new Error(`Invalid solution: position ${y} is negative`); - const o = orientationFromString(m[4]); - const pose = new Pose(new Coordinate(x, y), o); - config.push(pose); + for (const match of line.matchAll(regex)) { + const x = parseInt(match[1], 10); + const y = parseInt(match[2], 10); + const o = orientationFromString(match[3] || ""); // may be undefined + const stateStr = match[4] || "NONE"; + + console.log(stateStr); + + if (x < 0 || y < 0) { + throw new Error(`Invalid position: (${x}, ${y})`); + } + + const coordinate = new Coordinate(x, y); + const agentState = AgentState[stateStr as keyof typeof AgentState]; + + config.push(new Pose(coordinate, o, agentState)); + } + + if (config.length === 0) { + throw new Error("Invalid solution: no poses found"); } - if (config.length === 0) throw new Error("Invalid solution"); solution.push(config); } + + console.log("Parsed solution:", solution); return solution; -} \ No newline at end of file +} diff --git a/src/Visualizer.tsx b/src/Visualizer.tsx index 3ce6053..f0b1b8b 100644 --- a/src/Visualizer.tsx +++ b/src/Visualizer.tsx @@ -1,80 +1,89 @@ -import Box from '@mui/material/Box'; -import { Solution } from './Solution'; -import { Graph } from './Graph'; -import PixiApp from './PixiApp'; -import { useState, useRef, useEffect } from 'react'; +import Box from "@mui/material/Box"; +import { Solution } from "./Solution"; +import { Graph } from "./Graph"; +import PixiApp from "./PixiApp"; +import { useState, useRef, useEffect } from "react"; interface VisualizerProps { - graph: Graph | null; - solution: Solution | null; - playAnimation: boolean; - pixiAppRef: React.MutableRefObject<{ skipBackward?: () => void; skipForward?: () => void; restart?: () => void; } | null>; - stepSize: number; - loopAnimation: boolean; - showAgentId: boolean; - tracePaths: boolean; - setCanScreenshot: (canScreenshot: boolean) => void; - showCellId: boolean; - showGoals: boolean; - showGoalVectors: boolean; + graph: Graph | null; + solution: Solution | null; + playAnimation: boolean; + pixiAppRef: React.MutableRefObject<{ + skipBackward?: () => void; + skipForward?: () => void; + restart?: () => void; + } | null>; + stepSize: number; + loopAnimation: boolean; + showAgentId: boolean; + tracePaths: boolean; + setCanScreenshot: (canScreenshot: boolean) => void; + showCellId: boolean; + showGoals: boolean; + showGoalVectors: boolean; } function Visualizer({ - graph, - solution, - playAnimation, - pixiAppRef, - stepSize, - loopAnimation, - showAgentId, - tracePaths, - setCanScreenshot, - showCellId, - showGoals, - showGoalVectors, + graph, + solution, + playAnimation, + pixiAppRef, + stepSize, + loopAnimation, + showAgentId, + tracePaths, + setCanScreenshot, + showCellId, + showGoals, + showGoalVectors, }: VisualizerProps) { - const [viewportSize, setViewportSize] = useState<{ width: number; height: number } | null>(null); - const boxRef = useRef(null); - const canvasRef = useRef(null); + const [viewportSize, setViewportSize] = useState<{ + width: number; + height: number; + } | null>(null); + const boxRef = useRef(null); + const canvasRef = useRef(null); - useEffect(() => { - const handleResize = () => { - if (boxRef.current && canvasRef.current) { - setViewportSize({ - width: boxRef.current.clientWidth, - height: window.innerHeight - canvasRef.current.getBoundingClientRect().top - }); - } - }; - handleResize(); - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, []); + useEffect(() => { + const handleResize = () => { + if (boxRef.current && canvasRef.current) { + setViewportSize({ + width: boxRef.current.clientWidth, + height: + window.innerHeight - + canvasRef.current.getBoundingClientRect().top, + }); + } + }; + handleResize(); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); - return ( - -
- {viewportSize !== null && - - } -
-
-); + return ( + +
+ {viewportSize !== null && ( + + )} +
+
+ ); } -export default Visualizer; \ No newline at end of file +export default Visualizer; diff --git a/src/main.tsx b/src/main.tsx index 1ded165..3cd3ad8 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,20 +1,20 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import { ThemeProvider, createTheme } from '@mui/material/styles'; -import { CssBaseline } from '@mui/material'; -import App from './App.tsx' +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { ThemeProvider, createTheme } from "@mui/material/styles"; +import { CssBaseline } from "@mui/material"; +import App from "./App.tsx"; const darkTheme = createTheme({ - palette: { - mode: 'dark', - }, + palette: { + mode: "dark", + }, }); -createRoot(document.getElementById('root')!).render( - - - - - - , -) +createRoot(document.getElementById("root")!).render( + + + + + + +); From 862a1eea4ecdbe5cf7916c5d88098ff0c4cbd160 Mon Sep 17 00:00:00 2001 From: Ren Koya Date: Tue, 10 Jun 2025 15:29:21 +0900 Subject: [PATCH 2/3] remove format --- package-lock.json | 1175 +++++++++++++++++-------------- public/demo_random-32-32-20.txt | 2 +- src/AnimationControl.tsx | 283 +++----- src/App.tsx | 218 +++--- src/ConfigBar.tsx | 497 +++++++------ src/Params.tsx | 18 +- src/Solution.tsx | 63 +- src/Visualizer.tsx | 149 ++-- src/main.tsx | 18 +- 9 files changed, 1218 insertions(+), 1205 deletions(-) diff --git a/package-lock.json b/package-lock.json index 36dca7a..0258665 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,23 +46,23 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", - "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", "dev": true, "license": "MIT", "engines": { @@ -70,22 +70,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -100,14 +100,21 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/generator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", - "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.3", - "@babel/types": "^7.26.3", + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -117,14 +124,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -134,28 +141,28 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -165,9 +172,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, "license": "MIT", "engines": { @@ -175,27 +182,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -203,26 +210,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", - "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.3" + "@babel/types": "^7.27.1" }, "bin": { "parser": "bin/babel-parser.js" @@ -232,13 +239,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", - "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -248,13 +255,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", - "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -264,42 +271,39 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", - "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.3", - "@babel/parser": "^7.26.3", - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.3", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -317,13 +321,13 @@ } }, "node_modules/@babel/types": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", - "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -348,12 +352,6 @@ "stylis": "4.2.0" } }, - "node_modules/@emotion/babel-plugin/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/@emotion/cache": { "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", @@ -482,9 +480,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", "cpu": [ "ppc64" ], @@ -499,9 +497,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", "cpu": [ "arm" ], @@ -516,9 +514,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", "cpu": [ "arm64" ], @@ -533,9 +531,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", "cpu": [ "x64" ], @@ -550,9 +548,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", "cpu": [ "arm64" ], @@ -567,9 +565,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", "cpu": [ "x64" ], @@ -584,9 +582,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", "cpu": [ "arm64" ], @@ -601,9 +599,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", "cpu": [ "x64" ], @@ -618,9 +616,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", "cpu": [ "arm" ], @@ -635,9 +633,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", "cpu": [ "arm64" ], @@ -652,9 +650,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", "cpu": [ "ia32" ], @@ -669,9 +667,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", "cpu": [ "loong64" ], @@ -686,9 +684,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", "cpu": [ "mips64el" ], @@ -703,9 +701,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", "cpu": [ "ppc64" ], @@ -720,9 +718,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", "cpu": [ "riscv64" ], @@ -737,9 +735,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", "cpu": [ "s390x" ], @@ -754,9 +752,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", "cpu": [ "x64" ], @@ -771,9 +769,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", "cpu": [ "arm64" ], @@ -788,9 +786,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", "cpu": [ "x64" ], @@ -805,9 +803,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", "cpu": [ "arm64" ], @@ -822,9 +820,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", "cpu": [ "x64" ], @@ -839,9 +837,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", "cpu": [ "x64" ], @@ -856,9 +854,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "cpu": [ "arm64" ], @@ -873,9 +871,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", "cpu": [ "ia32" ], @@ -890,9 +888,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", "cpu": [ "x64" ], @@ -907,9 +905,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "license": "MIT", "dependencies": { @@ -949,13 +947,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.5", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -963,10 +961,20 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", - "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -977,9 +985,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1014,19 +1022,22 @@ } }, "node_modules/@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1034,12 +1045,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", - "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.14.0", "levn": "^0.4.1" }, "engines": { @@ -1099,9 +1111,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1161,9 +1173,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.3.1.tgz", - "integrity": "sha512-2OmnEyoHpj5//dJJpMuxOeLItCCHdf99pjMFfUFdBteCunAK9jW+PwEo4mtdGcLs7P+IgZ+85ypd52eY4AigoQ==", + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.11.tgz", + "integrity": "sha512-CzAQs9CTzlwbsF9ZYB4o4lLwBv1/qNE264NjuYao+ctAXsmlPtYa8RtER4UsUXSMxNN9Qi+aQdYcKl2sUpnmAw==", "license": "MIT", "funding": { "type": "opencollective", @@ -1171,9 +1183,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.3.1.tgz", - "integrity": "sha512-nJmWj1PBlwS3t1PnoqcixIsftE+7xrW3Su7f0yrjPw4tVjYrgkhU0hrRp+OlURfZ3ptdSkoBkalee9Bhf1Erfw==", + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.11.tgz", + "integrity": "sha512-+jjJGIrB1awNbMv4ZVPPdN/p7O1UKFZ+xqRvNIQ8B1KnlID5hPMPBLM6UUbRF4bu3UDCbu79rn9Nye5LGNzmeA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0" @@ -1186,7 +1198,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.3.1", + "@mui/material": "^6.4.11", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1197,16 +1209,16 @@ } }, "node_modules/@mui/material": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.3.1.tgz", - "integrity": "sha512-ynG9ayhxgCsHJ/dtDcT1v78/r2GwQyP3E0hPz3GdPRl0uFJz/uUTtI5KFYwadXmbC+Uv3bfB8laZ6+Cpzh03gA==", + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.11.tgz", + "integrity": "sha512-k2D3FLJS+/qD0qnd6ZlAjGFvaaxe1Dl10NyvpeDzIebMuYdn8VqYe6XBgGueEAtnzSJM4V03VD9kb5Fi24dnTA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.3.1", - "@mui/system": "^6.3.1", - "@mui/types": "^7.2.21", - "@mui/utils": "^6.3.1", + "@mui/core-downloads-tracker": "^6.4.11", + "@mui/system": "^6.4.11", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", @@ -1225,7 +1237,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.3.1", + "@mui/material-pigment-css": "^6.4.11", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1246,13 +1258,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.3.1.tgz", - "integrity": "sha512-g0u7hIUkmXmmrmmf5gdDYv9zdAig0KoxhIQn1JN8IVqApzf/AyRhH3uDGx5mSvs8+a1zb4+0W6LC260SyTTtdQ==", + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", + "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.3.1", + "@mui/utils": "^6.4.9", "prop-types": "^15.8.1" }, "engines": { @@ -1273,9 +1285,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.3.1.tgz", - "integrity": "sha512-/7CC0d2fIeiUxN5kCCwYu4AWUDd9cCTxWCyo0v/Rnv6s8uk6hWgJC3VLZBoDENBHf/KjqDZuYJ2CR+7hD6QYww==", + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.11.tgz", + "integrity": "sha512-74AUmlHXaGNbyUqdK/+NwDJOZqgRQw6BcNvhoWYLq3LGbLTkE+khaJ7soz6cIabE4CPYqO2/QAIU1Z/HEjjpcw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -1307,16 +1319,16 @@ } }, "node_modules/@mui/system": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.3.1.tgz", - "integrity": "sha512-AwqQ3EAIT2np85ki+N15fF0lFXX1iFPqenCzVOSl3QXKy2eifZeGd9dGtt7pGMoFw5dzW4dRGGzRpLAq9rkl7A==", + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.11.tgz", + "integrity": "sha512-gibtsrZEwnDaT5+I/KloOj/yHluX5G8heknuxBpQOdEQ3Gc0avjSImn5hSeKp8D4thiwZiApuggIjZw1dQguUA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.3.1", - "@mui/styled-engine": "^6.3.1", - "@mui/types": "^7.2.21", - "@mui/utils": "^6.3.1", + "@mui/private-theming": "^6.4.9", + "@mui/styled-engine": "^6.4.11", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1347,9 +1359,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.21", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", - "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", "license": "MIT", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1361,13 +1373,13 @@ } }, "node_modules/@mui/utils": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.3.1.tgz", - "integrity": "sha512-sjGjXAngoio6lniQZKJ5zGfjm+LD2wvLwco7FbKe1fu8A7VIFmz2SwkLb+MDPLNX1lE7IscvNNyh1pobtZg2tw==", + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", + "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/types": "^7.2.21", + "@mui/types": "~7.2.24", "@types/prop-types": "^15.7.14", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -1446,9 +1458,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", - "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", + "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", "cpu": [ "arm" ], @@ -1460,9 +1472,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", - "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", + "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", "cpu": [ "arm64" ], @@ -1474,9 +1486,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", - "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", + "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", "cpu": [ "arm64" ], @@ -1488,9 +1500,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", - "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", + "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", "cpu": [ "x64" ], @@ -1502,9 +1514,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", - "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", + "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", "cpu": [ "arm64" ], @@ -1516,9 +1528,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", - "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", + "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", "cpu": [ "x64" ], @@ -1530,9 +1542,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", - "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", + "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", "cpu": [ "arm" ], @@ -1544,9 +1556,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", - "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", + "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", "cpu": [ "arm" ], @@ -1558,9 +1570,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", - "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", + "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", "cpu": [ "arm64" ], @@ -1572,9 +1584,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", - "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", + "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==", "cpu": [ "arm64" ], @@ -1586,9 +1598,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", - "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", + "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", "cpu": [ "loong64" ], @@ -1600,9 +1612,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", - "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", + "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", "cpu": [ "ppc64" ], @@ -1614,9 +1626,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", - "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", + "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", + "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", "cpu": [ "riscv64" ], @@ -1628,9 +1654,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", - "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", + "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", "cpu": [ "s390x" ], @@ -1642,9 +1668,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", - "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", + "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==", "cpu": [ "x64" ], @@ -1656,9 +1682,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", - "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz", + "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==", "cpu": [ "x64" ], @@ -1670,9 +1696,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", - "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", + "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", "cpu": [ "arm64" ], @@ -1684,9 +1710,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", - "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", + "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", "cpu": [ "ia32" ], @@ -1698,9 +1724,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", - "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", + "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", "cpu": [ "x64" ], @@ -1726,9 +1752,9 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { @@ -1747,9 +1773,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", - "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", "dev": true, "license": "MIT", "dependencies": { @@ -1771,9 +1797,9 @@ "peer": true }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, @@ -1797,9 +1823,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", - "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "version": "18.3.22", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz", + "integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==", "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1807,9 +1833,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.3.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", - "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1826,21 +1852,21 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz", - "integrity": "sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", + "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/type-utils": "8.19.0", - "@typescript-eslint/utils": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/type-utils": "8.32.1", + "@typescript-eslint/utils": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1852,20 +1878,30 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz", - "integrity": "sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", + "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/typescript-estree": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "engines": { @@ -1877,18 +1913,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz", - "integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", + "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0" + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1899,16 +1935,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz", - "integrity": "sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", + "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.19.0", - "@typescript-eslint/utils": "8.19.0", + "@typescript-eslint/typescript-estree": "8.32.1", + "@typescript-eslint/utils": "8.32.1", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1919,13 +1955,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", - "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", + "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", "dev": true, "license": "MIT", "engines": { @@ -1937,20 +1973,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz", - "integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", + "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1960,7 +1996,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -1990,9 +2026,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -2003,16 +2039,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz", - "integrity": "sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", + "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/typescript-estree": "8.19.0" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2023,17 +2059,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz", - "integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", + "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2045,17 +2081,17 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", - "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", + "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.26.0", + "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" + "react-refresh": "^0.17.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -2065,9 +2101,9 @@ } }, "node_modules/@webgpu/types": { - "version": "0.1.52", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.52.tgz", - "integrity": "sha512-eI883Nlag2hGIkhXxAnq8s4APpqXWuPL3Gbn2ghiU12UjLvfCbVqHK4XfXl3eLRTatqcMmeK7jws7IwWsGfbzw==", + "version": "0.1.60", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.60.tgz", + "integrity": "sha512-8B/tdfRFKdrnejqmvq95ogp8tf52oZ51p3f4QD5m5Paey/qlX4Rhhy5Y8tgFMi7Ms70HzcMMw3EQjH/jdhTwlA==", "license": "BSD-3-Clause", "peer": true }, @@ -2082,9 +2118,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", "bin": { @@ -2191,9 +2227,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", - "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "dev": true, "funding": [ { @@ -2211,10 +2247,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -2233,9 +2269,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001690", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", - "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", "dev": true, "funding": [ { @@ -2307,10 +2343,9 @@ "license": "MIT" }, "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, + "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/cosmiconfig": { @@ -2360,9 +2395,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2401,9 +2436,9 @@ "peer": true }, "node_modules/electron-to-chromium": { - "version": "1.5.76", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", - "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==", + "version": "1.5.155", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", + "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", "dev": true, "license": "ISC" }, @@ -2417,9 +2452,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2430,31 +2465,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" } }, "node_modules/escalade": { @@ -2480,22 +2515,23 @@ } }, "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -2503,7 +2539,7 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", + "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", @@ -2540,9 +2576,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz", - "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", "dev": true, "license": "MIT", "engines": { @@ -2553,9 +2589,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.16.tgz", - "integrity": "sha512-slterMlxAhov/DZO8NScf6mEeMBBXodFUolijDvrtTxyezyLoTQaa73FyYus/VbTdftd8wBgBxPMRk3poleXNQ==", + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2563,9 +2599,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2671,9 +2707,9 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -2681,7 +2717,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -2715,9 +2751,9 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", - "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2788,9 +2824,9 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -2828,6 +2864,16 @@ "node": ">=6.9.0" } }, + "node_modules/gifuct-js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz", + "integrity": "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==", + "license": "MIT", + "peer": true, + "dependencies": { + "js-binary-schema-parser": "^2.0.3" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2842,9 +2888,9 @@ } }, "node_modules/globals": { - "version": "15.14.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", - "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true, "license": "MIT", "engines": { @@ -2909,9 +2955,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -3002,6 +3048,13 @@ "license": "MIT", "peer": true }, + "node_modules/js-binary-schema-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz", + "integrity": "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==", + "license": "MIT", + "peer": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3214,9 +3267,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -3406,9 +3459,9 @@ } }, "node_modules/pixi.js": { - "version": "8.6.6", - "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.6.6.tgz", - "integrity": "sha512-o5pw7G2yuIrnBx0G4npBlmFp+XGNcapI/Ufs62rRj/4XKxc1Zo74YJr/BtEXcXTraTKd+pQvYOLvnfxRjxBMvQ==", + "version": "8.9.2", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.9.2.tgz", + "integrity": "sha512-oLFBkOOA/O6OpT5T8o05AxgZB9x9yWNzEQ+WTNZZFoCvfU2GdT4sFTjpVFuHQzgZPmAm/1IFhKdNiXVnlL8PRw==", "license": "MIT", "peer": true, "dependencies": { @@ -3419,6 +3472,7 @@ "@xmldom/xmldom": "^0.8.10", "earcut": "^2.2.4", "eventemitter3": "^5.0.1", + "gifuct-js": "^2.1.2", "ismobilejs": "^1.1.1", "parse-svg-path": "^0.1.2" } @@ -3548,15 +3602,15 @@ } }, "node_modules/react-is": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", - "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", "license": "MIT" }, "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, "license": "MIT", "engines": { @@ -3579,12 +3633,6 @@ "react-dom": ">=16.6.0" } }, - "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/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -3615,9 +3663,9 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -3626,13 +3674,13 @@ } }, "node_modules/rollup": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", - "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz", + "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.7" }, "bin": { "rollup": "dist/bin/rollup" @@ -3642,25 +3690,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.8", - "@rollup/rollup-android-arm64": "4.34.8", - "@rollup/rollup-darwin-arm64": "4.34.8", - "@rollup/rollup-darwin-x64": "4.34.8", - "@rollup/rollup-freebsd-arm64": "4.34.8", - "@rollup/rollup-freebsd-x64": "4.34.8", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", - "@rollup/rollup-linux-arm-musleabihf": "4.34.8", - "@rollup/rollup-linux-arm64-gnu": "4.34.8", - "@rollup/rollup-linux-arm64-musl": "4.34.8", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", - "@rollup/rollup-linux-riscv64-gnu": "4.34.8", - "@rollup/rollup-linux-s390x-gnu": "4.34.8", - "@rollup/rollup-linux-x64-gnu": "4.34.8", - "@rollup/rollup-linux-x64-musl": "4.34.8", - "@rollup/rollup-win32-arm64-msvc": "4.34.8", - "@rollup/rollup-win32-ia32-msvc": "4.34.8", - "@rollup/rollup-win32-x64-msvc": "4.34.8", + "@rollup/rollup-android-arm-eabi": "4.41.0", + "@rollup/rollup-android-arm64": "4.41.0", + "@rollup/rollup-darwin-arm64": "4.41.0", + "@rollup/rollup-darwin-x64": "4.41.0", + "@rollup/rollup-freebsd-arm64": "4.41.0", + "@rollup/rollup-freebsd-x64": "4.41.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.0", + "@rollup/rollup-linux-arm-musleabihf": "4.41.0", + "@rollup/rollup-linux-arm64-gnu": "4.41.0", + "@rollup/rollup-linux-arm64-musl": "4.41.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-musl": "4.41.0", + "@rollup/rollup-linux-s390x-gnu": "4.41.0", + "@rollup/rollup-linux-x64-gnu": "4.41.0", + "@rollup/rollup-linux-x64-musl": "4.41.0", + "@rollup/rollup-win32-arm64-msvc": "4.41.0", + "@rollup/rollup-win32-ia32-msvc": "4.41.0", + "@rollup/rollup-win32-x64-msvc": "4.41.0", "fsevents": "~2.3.2" } }, @@ -3793,6 +3842,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3807,16 +3901,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/type-check": { @@ -3847,15 +3941,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.19.0.tgz", - "integrity": "sha512-Ni8sUkVWYK4KAcTtPjQ/UTiRk6jcsuDhPpxULapUDi8A/l8TSBk+t1GtJA1RsCzIJg0q6+J7bf35AwQigENWRQ==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz", + "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.19.0", - "@typescript-eslint/parser": "8.19.0", - "@typescript-eslint/utils": "8.19.0" + "@typescript-eslint/eslint-plugin": "8.32.1", + "@typescript-eslint/parser": "8.32.1", + "@typescript-eslint/utils": "8.32.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3866,13 +3960,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -3891,7 +3985,7 @@ "license": "MIT", "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -3911,15 +4005,18 @@ } }, "node_modules/vite": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", - "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.30.1" + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" @@ -3982,6 +4079,34 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4016,9 +4141,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "dev": true, "license": "ISC", "optional": true, @@ -4027,7 +4152,7 @@ "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yocto-queue": { diff --git a/public/demo_random-32-32-20.txt b/public/demo_random-32-32-20.txt index 08208a3..547c2c8 100644 --- a/public/demo_random-32-32-20.txt +++ b/public/demo_random-32-32-20.txt @@ -104,4 +104,4 @@ 103:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(24,25,CARRYING) 104:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(24,26,CARRYING) 105:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(25,26,DELIVERED) -106:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(25,26,IDLE) +106:(21,17,IDLE),(9,8,IDLE),(2,20,IDLE),(16,22,IDLE),(20,7,IDLE),(8,13,IDLE),(16,0,IDLE),(5,18,IDLE),(6,2,IDLE),(14,29,IDLE),(8,20,IDLE),(25,26,IDLE) \ No newline at end of file diff --git a/src/AnimationControl.tsx b/src/AnimationControl.tsx index 33a2965..6c4a030 100644 --- a/src/AnimationControl.tsx +++ b/src/AnimationControl.tsx @@ -1,49 +1,49 @@ -import SkipPreviousIcon from "@mui/icons-material/SkipPrevious"; -import PlayArrowIcon from "@mui/icons-material/PlayArrow"; -import PauseTwoToneIcon from "@mui/icons-material/PauseTwoTone"; -import SkipNextIcon from "@mui/icons-material/SkipNext"; -import Button from "@mui/material/Button"; -import ButtonGroup from "@mui/material/ButtonGroup"; -import Box from "@mui/material/Box"; -import RestartAltIcon from "@mui/icons-material/RestartAlt"; -import Slider from "@mui/material/Slider"; -import RepeatIcon from "@mui/icons-material/Repeat"; -import RepeatOnIcon from "@mui/icons-material/RepeatOn"; -import Stack from "@mui/material/Stack"; -import Tooltip from "@mui/material/Tooltip"; -import LooksOneIcon from "@mui/icons-material/LooksOne"; -import LooksOneOutlinedIcon from "@mui/icons-material/LooksOneOutlined"; -import { useEffect } from "react"; -import DirectionsIcon from "@mui/icons-material/Directions"; -import DirectionsOutlinedIcon from "@mui/icons-material/DirectionsOutlined"; -import FilterCenterFocusOutlinedIcon from "@mui/icons-material/FilterCenterFocusOutlined"; -import ScreenshotMonitorOutlinedIcon from "@mui/icons-material/ScreenshotMonitorOutlined"; -import StartIcon from "@mui/icons-material/Start"; -import SmartToyOutlinedIcon from "@mui/icons-material/SmartToyOutlined"; -import SmartToyIcon from "@mui/icons-material/SmartToy"; -import FlagIcon from "@mui/icons-material/Flag"; -import OutlinedFlagIcon from "@mui/icons-material/OutlinedFlag"; -import PolylineIcon from "@mui/icons-material/Polyline"; -import PolylineOutlinedIcon from "@mui/icons-material/PolylineOutlined"; +import SkipPreviousIcon from '@mui/icons-material/SkipPrevious'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import PauseTwoToneIcon from '@mui/icons-material/PauseTwoTone'; +import SkipNextIcon from '@mui/icons-material/SkipNext'; +import Button from '@mui/material/Button'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import Box from '@mui/material/Box'; +import RestartAltIcon from '@mui/icons-material/RestartAlt'; +import Slider from '@mui/material/Slider'; +import RepeatIcon from '@mui/icons-material/Repeat'; +import RepeatOnIcon from '@mui/icons-material/RepeatOn'; +import Stack from '@mui/material/Stack'; +import Tooltip from '@mui/material/Tooltip'; +import LooksOneIcon from '@mui/icons-material/LooksOne'; +import LooksOneOutlinedIcon from '@mui/icons-material/LooksOneOutlined'; +import { useEffect } from 'react'; +import DirectionsIcon from '@mui/icons-material/Directions'; +import DirectionsOutlinedIcon from '@mui/icons-material/DirectionsOutlined'; +import FilterCenterFocusOutlinedIcon from '@mui/icons-material/FilterCenterFocusOutlined'; +import ScreenshotMonitorOutlinedIcon from '@mui/icons-material/ScreenshotMonitorOutlined'; +import StartIcon from '@mui/icons-material/Start'; +import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined'; +import SmartToyIcon from '@mui/icons-material/SmartToy'; +import FlagIcon from '@mui/icons-material/Flag'; +import OutlinedFlagIcon from '@mui/icons-material/OutlinedFlag'; +import PolylineIcon from '@mui/icons-material/Polyline'; +import PolylineOutlinedIcon from '@mui/icons-material/PolylineOutlined'; const STEP_SIZE_INCREMENT = 0.2; const STEP_SIZE_MAX = 10; const STEP_SIZE_MIN = 0.2; -const STEP_BACKWARD_KEY = "ArrowLeft"; -const PLAY_PAUSE_KEY = " "; -const STEP_FORWARD_KEY = "ArrowRight"; -const RESTART_KEY = "r"; -const LOOP_KEY = "l"; -const FIT_VIEW_KEY = "f"; -const SHOW_AGENT_ID_KEY = "a"; -const STEP_SIZE_UP_KEY = "ArrowUp"; -const STEP_SIZE_DOWN_KEY = "ArrowDown"; -const TRACE_PATHS_KEY = "p"; -const SCREENSHOT_KEY = "s"; -const SHOW_CELL_ID_KEY = "c"; -const SHOW_GOALS_KEY = "g"; -const SHOW_GOAL_VECTORS_KEY = "v"; +const STEP_BACKWARD_KEY = 'ArrowLeft'; +const PLAY_PAUSE_KEY = ' '; +const STEP_FORWARD_KEY = 'ArrowRight'; +const RESTART_KEY = 'r'; +const LOOP_KEY = 'l'; +const FIT_VIEW_KEY = 'f'; +const SHOW_AGENT_ID_KEY = 'a'; +const STEP_SIZE_UP_KEY = 'ArrowUp'; +const STEP_SIZE_DOWN_KEY = 'ArrowDown'; +const TRACE_PATHS_KEY = 'p'; +const SCREENSHOT_KEY = 's'; +const SHOW_CELL_ID_KEY = 'c'; +const SHOW_GOALS_KEY = 'g'; +const SHOW_GOAL_VECTORS_KEY = 'v'; interface AnimationControlProps { playAnimation: boolean; @@ -53,7 +53,7 @@ interface AnimationControlProps { onRestart: () => void; stepSize: number; onStepSizeChange: (speed: number) => void; - loopAnimation: boolean; + loopAnimation: boolean, onLoopAnimationChange: (loopAnimation: boolean) => void; onFitView: () => void; showAgentId: boolean; @@ -71,9 +71,9 @@ interface AnimationControlProps { } function AnimationControl({ - playAnimation, - onPlayAnimationChange, - onSkipBackward, + playAnimation, + onPlayAnimationChange, + onSkipBackward, onSkipForward, onRestart, stepSize, @@ -93,14 +93,14 @@ function AnimationControl({ setShowGoals, showGoalVectors, setShowGoalVectors, -}: AnimationControlProps) { +}: AnimationControlProps) { const roundAndSetStepSize = (value: number) => { onStepSizeChange(Number(value.toFixed(1))); - }; - + } + const handleSliderChange = (event: Event, value: number | number[]) => { event.preventDefault(); - if (typeof value === "number") roundAndSetStepSize(value); + if (typeof value === 'number') roundAndSetStepSize(value); }; useEffect(() => { @@ -113,7 +113,7 @@ function AnimationControl({ onSkipBackward(); } else if (event.key === PLAY_PAUSE_KEY) { onPlayAnimationChange(!playAnimation); - } else if (event.key === STEP_FORWARD_KEY) { + } else if (event.key === STEP_FORWARD_KEY) { onSkipForward(); } else if (event.key === RESTART_KEY) { onRestart(); @@ -123,15 +123,9 @@ function AnimationControl({ onFitView(); } else if (event.key === SHOW_AGENT_ID_KEY) { onShowAgentIdChange(!showAgentId); - } else if ( - event.key === STEP_SIZE_UP_KEY && - stepSize + STEP_SIZE_INCREMENT <= STEP_SIZE_MAX - ) { + } else if (event.key === STEP_SIZE_UP_KEY && stepSize + STEP_SIZE_INCREMENT <= STEP_SIZE_MAX) { roundAndSetStepSize(stepSize + STEP_SIZE_INCREMENT); - } else if ( - event.key === STEP_SIZE_DOWN_KEY && - stepSize - STEP_SIZE_INCREMENT >= STEP_SIZE_MIN - ) { + } else if (event.key === STEP_SIZE_DOWN_KEY && stepSize - STEP_SIZE_INCREMENT >= STEP_SIZE_MIN) { roundAndSetStepSize(stepSize - STEP_SIZE_INCREMENT); } else if (event.key === TRACE_PATHS_KEY) { onTracePathsChange(!tracePaths); @@ -145,42 +139,24 @@ function AnimationControl({ setShowGoalVectors(!showGoalVectors); } }; - window.addEventListener("keydown", handleKeyDown); + window.addEventListener('keydown', handleKeyDown); return () => { - window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener('keydown', handleKeyDown); }; - }, [ - playAnimation, - onPlayAnimationChange, - loopAnimation, - onFitView, - onLoopAnimationChange, - onRestart, - onShowAgentIdChange, - onSkipBackward, - onSkipForward, - onStepSizeChange, - showAgentId, - stepSize, - onTracePathsChange, - tracePaths, - takeScreenshot, - showCellId, - setShowCellId, - showGoals, - setShowGoals, - showGoalVectors, - setShowGoalVectors, - ]); + }, [playAnimation, onPlayAnimationChange, loopAnimation, onFitView, + onLoopAnimationChange, onRestart, onShowAgentIdChange, onSkipBackward, + onSkipForward, onStepSizeChange, showAgentId, stepSize, onTracePathsChange, tracePaths, + takeScreenshot, showCellId, setShowCellId, showGoals, setShowGoals, showGoalVectors, + setShowGoalVectors]); return ( - - Adjust animation step size ({STEP_SIZE_UP_KEY}/ - {STEP_SIZE_DOWN_KEY}) +
+ Adjust animation step size + ({STEP_SIZE_UP_KEY}/{STEP_SIZE_DOWN_KEY})
} > @@ -192,7 +168,7 @@ function AnimationControl({ max={STEP_SIZE_MAX} valueLabelDisplay="auto" onChange={handleSliderChange} - sx={{ width: "40%", height: "auto" }} + sx={{ width: '40%', height: "auto"}} />
@@ -206,23 +182,13 @@ function AnimationControl({ - - - + + + @@ -239,18 +205,11 @@ function AnimationControl({ - - @@ -260,10 +219,7 @@ function AnimationControl({ - @@ -272,82 +228,45 @@ function AnimationControl({ - - - + - + - + - -
- ); + ); } -export default AnimationControl; +export default AnimationControl; \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 05a021f..93b34ce 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,124 +1,116 @@ -import Box from "@mui/material/Box"; -import Grid from "@mui/material/Grid2"; -import ConfigBar from "./ConfigBar"; -import Visualizer from "./Visualizer"; -import { Graph } from "./Graph"; -import { Solution } from "./Solution"; -import React, { useCallback } from "react"; -import { StrictMode, useRef } from "react"; +import Box from '@mui/material/Box'; +import Grid from '@mui/material/Grid2'; +import ConfigBar from './ConfigBar'; +import Visualizer from './Visualizer'; +import { Graph } from './Graph'; +import { Solution } from './Solution'; +import React, { useCallback } from 'react'; +import { StrictMode, useRef } from 'react'; function App() { - const pixiAppRef = useRef<{ - skipBackward?: () => void; - skipForward?: () => void; - restart?: () => void; - fit?: () => void; - takeScreenshot?: () => void; - }>(null); + const pixiAppRef = useRef<{ + skipBackward?: () => void; + skipForward?: () => void; + restart?: () => void; + fit?: () => void; + takeScreenshot?: () => void; + }>(null); - const [graph, setGraph] = React.useState(null); - const [solution, setSolution] = React.useState(null); - const [playAnimation, setPlayAnimation] = React.useState(true); - const [stepSize, setStepSize] = React.useState(1.0); - const [loopAnimation, setLoopAnimation] = React.useState(true); - const [showAgentId, setShowAgentId] = React.useState(false); - const [tracePaths, setTracePaths] = React.useState(true); - const [canScreenshot, setCanScreenshot] = React.useState(true); - const [showCellId, setShowCellId] = React.useState(false); - const [showGoals, setShowGoals] = React.useState(true); - const [showGoalVectors, setShowGoalVectors] = - React.useState(false); + const [graph, setGraph] = React.useState(null); + const [solution, setSolution] = React.useState(null); + const [playAnimation, setPlayAnimation] = React.useState(true); + const [stepSize, setStepSize] = React.useState(1.0); + const [loopAnimation, setLoopAnimation] = React.useState(true); + const [showAgentId, setShowAgentId] = React.useState(false); + const [tracePaths, setTracePaths] = React.useState(true); + const [canScreenshot, setCanScreenshot] = React.useState(true); + const [showCellId, setShowCellId] = React.useState(false); + const [showGoals, setShowGoals] = React.useState(true); + const [showGoalVectors, setShowGoalVectors] = React.useState(false); - const handleSkipBackward = () => { - if (pixiAppRef.current?.skipBackward) { - pixiAppRef.current.skipBackward(); - } - }; + const handleSkipBackward = () => { + if (pixiAppRef.current?.skipBackward) { + pixiAppRef.current.skipBackward(); + } + } - const handleSkipForward = () => { - if (pixiAppRef.current?.skipForward) { - pixiAppRef.current.skipForward(); - } - }; + const handleSkipForward = () => { + if (pixiAppRef.current?.skipForward) { + pixiAppRef.current.skipForward(); + } + } - const handleRestart = () => { - if (pixiAppRef.current?.restart) { - pixiAppRef.current.restart(); - } - }; + const handleRestart = () => { + if (pixiAppRef.current?.restart) { + pixiAppRef.current.restart(); + } + } - const handleFitView = () => { - if (pixiAppRef.current?.fit) { - pixiAppRef.current.fit(); - } - }; + const handleFitView = () => { + if (pixiAppRef.current?.fit) { + pixiAppRef.current.fit(); + } + } - const handleTakeScreenshot = () => { - if (pixiAppRef.current?.takeScreenshot) { - pixiAppRef.current.takeScreenshot(); - } - }; + const handleTakeScreenshot = () => { + if (pixiAppRef.current?.takeScreenshot) { + pixiAppRef.current.takeScreenshot(); + } + } - return ( - - - - - - - - setGraph(graph), - [] - )} - onSolutionChange={useCallback( - (solution: Solution | null) => - setSolution(solution), - [] - )} - playAnimation={playAnimation} - onPlayAnimationChange={setPlayAnimation} - onSkipBackward={handleSkipBackward} - onSkipForward={handleSkipForward} - onRestart={handleRestart} - stepSize={stepSize} - onStepSizeChange={setStepSize} - loopAnimation={loopAnimation} - onLoopAnimationChange={setLoopAnimation} - onFitView={handleFitView} - showAgentId={showAgentId} - onShowAgentIdChange={setShowAgentId} - tracePaths={tracePaths} - onTracePathsChange={setTracePaths} - canScreenshot={canScreenshot} - takeScreenshot={handleTakeScreenshot} - showCellId={showCellId} - setShowCellId={setShowCellId} - showGoals={showGoals} - setShowGoals={setShowGoals} - showGoalVectors={showGoalVectors} - setShowGoalVectors={setShowGoalVectors} - /> - - - - - ); + return ( + + + + + + + + setGraph(graph), [])} + onSolutionChange={useCallback((solution: Solution | null) => setSolution(solution), [])} + playAnimation={playAnimation} + onPlayAnimationChange={setPlayAnimation} + onSkipBackward={handleSkipBackward} + onSkipForward={handleSkipForward} + onRestart={handleRestart} + stepSize={stepSize} + onStepSizeChange={setStepSize} + loopAnimation={loopAnimation} + onLoopAnimationChange={setLoopAnimation} + onFitView={handleFitView} + showAgentId={showAgentId} + onShowAgentIdChange={setShowAgentId} + tracePaths={tracePaths} + onTracePathsChange={setTracePaths} + canScreenshot={canScreenshot} + takeScreenshot={handleTakeScreenshot} + showCellId={showCellId} + setShowCellId={setShowCellId} + showGoals={showGoals} + setShowGoals={setShowGoals} + showGoalVectors={showGoalVectors} + setShowGoalVectors={setShowGoalVectors} + /> + + + + + ); } -export default App; +export default App; \ No newline at end of file diff --git a/src/ConfigBar.tsx b/src/ConfigBar.tsx index e31901e..5b8aec6 100644 --- a/src/ConfigBar.tsx +++ b/src/ConfigBar.tsx @@ -9,275 +9,264 @@ import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; import Tooltip from "@mui/material/Tooltip"; interface ConfigBarProps { - graph: Graph | null; - onGraphChange: (graph: Graph | null) => void; - onSolutionChange: (solution: Solution | null) => void; - playAnimation: boolean; - onPlayAnimationChange: (playAnimation: boolean) => void; - onSkipBackward: () => void; - onSkipForward: () => void; - onRestart: () => void; - stepSize: number; - onStepSizeChange: (speed: number) => void; - loopAnimation: boolean; - onLoopAnimationChange: (loopAnimation: boolean) => void; - onFitView: () => void; - showAgentId: boolean; - onShowAgentIdChange: (showAgentId: boolean) => void; - tracePaths: boolean; - onTracePathsChange: (tracePaths: boolean) => void; - canScreenshot: boolean; - takeScreenshot: () => void; - showCellId: boolean; - setShowCellId: (showCellId: boolean) => void; - showGoals: boolean; - setShowGoals: (showGoals: boolean) => void; - showGoalVectors: boolean; - setShowGoalVectors: (showGoalVectors: boolean) => void; + graph: Graph | null; + onGraphChange: (graph: Graph | null) => void; + onSolutionChange: (solution: Solution | null) => void; + playAnimation: boolean; + onPlayAnimationChange: (playAnimation: boolean) => void; + onSkipBackward: () => void; + onSkipForward: () => void; + onRestart: () => void; + stepSize: number; + onStepSizeChange: (speed: number) => void; + loopAnimation: boolean; + onLoopAnimationChange: (loopAnimation: boolean) => void; + onFitView: () => void; + showAgentId: boolean; + onShowAgentIdChange: (showAgentId: boolean) => void; + tracePaths: boolean; + onTracePathsChange: (tracePaths: boolean) => void; + canScreenshot: boolean; + takeScreenshot: () => void; + showCellId: boolean; + setShowCellId: (showCellId: boolean) => void; + showGoals: boolean; + setShowGoals: (showGoals: boolean) => void; + showGoalVectors: boolean; + setShowGoalVectors: (showGoalVectors: boolean) => void; } function ConfigBar({ - graph, - onGraphChange, - onSolutionChange, - playAnimation, - onPlayAnimationChange, - onSkipBackward, - onSkipForward, - onRestart, - stepSize, - onStepSizeChange, - loopAnimation, - onLoopAnimationChange, - onFitView, - showAgentId, - onShowAgentIdChange, - tracePaths, - onTracePathsChange, - canScreenshot, - takeScreenshot, - showCellId, - setShowCellId, - showGoals, - setShowGoals, - showGoalVectors, - setShowGoalVectors, + graph, + onGraphChange, + onSolutionChange, + playAnimation, + onPlayAnimationChange, + onSkipBackward, + onSkipForward, + onRestart, + stepSize, + onStepSizeChange, + loopAnimation, + onLoopAnimationChange, + onFitView, + showAgentId, + onShowAgentIdChange, + tracePaths, + onTracePathsChange, + canScreenshot, + takeScreenshot, + showCellId, + setShowCellId, + showGoals, + setShowGoals, + showGoalVectors, + setShowGoalVectors, }: ConfigBarProps) { - const repoName = "RenKoya1/mapd-visualizer"; - const [mapFile, setMapFile] = React.useState(null); - const [mapError, setMapError] = React.useState(null); - const [solutionFile, setSolutionFile] = React.useState(null); - const [solutionError, setSolutionError] = React.useState( - null - ); + const repoName = "JustinShetty/mapf-visualizer"; + const [mapFile, setMapFile] = React.useState(null); + const [mapError, setMapError] = React.useState(null); + const [solutionFile, setSolutionFile] = React.useState(null); + const [solutionError, setSolutionError] = React.useState(null); - const blurActiveElement = () => { - // Blur (remove focus from) the file input - if (document.activeElement instanceof HTMLElement) { - document.activeElement.blur(); - } - }; + const blurActiveElement = () => { + // Blur (remove focus from) the file input + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + }; - const handleLoadDemo = (mapName: string) => { - fetch(`${import.meta.env.BASE_URL}/${mapName}.map`) - .then((response) => response.text()) - .then((text) => { - handleMapChange(new File([text], `${mapName}.map`)); - return fetch(`${import.meta.env.BASE_URL}/demo_${mapName}.txt`); - }) - .then((response) => response.text()) - .then((text) => { - handleSolutionChange(new File([text], `demo_${mapName}.txt`)); - }); - }; + const handleLoadDemo = (mapName: string) => { + fetch(`${import.meta.env.BASE_URL}/${mapName}.map`) + .then((response) => response.text()) + .then((text) => { + handleMapChange(new File([text], `${mapName}.map`)); + return fetch(`${import.meta.env.BASE_URL}/demo_${mapName}.txt`); + }) + .then((response) => response.text()) + .then((text) => { + handleSolutionChange(new File([text], `demo_${mapName}.txt`)); + }); + }; - useEffect(() => { - if (mapFile === null) { - onGraphChange(null); - return; - } - mapFile.text().then((text) => { - try { - onGraphChange(new Graph(text)); - setMapError(null); - } catch (e) { - setMapFile(null); - onGraphChange(null); - setMapError( - e instanceof Error - ? e.message - : "An unexpected error occurred" - ); - } - }); - }, [mapFile, onGraphChange]); + useEffect(() => { + if (mapFile === null) { + onGraphChange(null); + return; + } + mapFile.text().then((text) => { + try { + onGraphChange(new Graph(text)); + setMapError(null); + } catch (e) { + setMapFile(null); + onGraphChange(null); + setMapError( + e instanceof Error ? e.message : "An unexpected error occurred" + ); + } + }); + }, [mapFile, onGraphChange]); - useEffect(() => { - if (solutionFile === null) { - onSolutionChange(null); - return; - } - solutionFile.text().then((text) => { - try { - if (graph === null) - throw new Error("Map must be loaded before solution"); - const soln = parseSolution(text); - soln.forEach((config) => { - config.forEach((pose) => { - if ( - pose.position.x > graph.width || - pose.position.y > graph.height - ) { - throw new Error( - `Invalid solution: position ${pose.position} is out of bounds` - ); - } - }); - }); - onSolutionChange(soln); - setSolutionError(null); - } catch (e) { - setSolutionFile(null); - setSolutionError( - e instanceof Error - ? e.message - : "An unexpected error occurred" - ); + useEffect(() => { + if (solutionFile === null) { + onSolutionChange(null); + return; + } + solutionFile.text().then((text) => { + try { + if (graph === null) + throw new Error("Map must be loaded before solution"); + const soln = parseSolution(text); + soln.forEach((config) => { + config.forEach((pose) => { + if ( + pose.position.x > graph.width || + pose.position.y > graph.height + ) { + throw new Error( + `Invalid solution: position ${pose.position} is out of bounds` + ); } + }); }); - }, [graph, solutionFile, onSolutionChange]); - - const handleMapChange = (newValue: File | null) => { - setMapFile(newValue); + onSolutionChange(soln); + setSolutionError(null); + } catch (e) { setSolutionFile(null); - blurActiveElement(); - }; + setSolutionError( + e instanceof Error ? e.message : "An unexpected error occurred" + ); + } + }); + }, [graph, solutionFile, onSolutionChange]); - const handleSolutionChange = (newValue: File | null) => { - setSolutionFile(newValue); - blurActiveElement(); - }; + const handleMapChange = (newValue: File | null) => { + setMapFile(newValue); + setSolutionFile(null); + blurActiveElement(); + }; - const downloadFile = (file: File) => { - const url = URL.createObjectURL(file); - const a = document.createElement("a"); - a.href = url; - a.download = file.name; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - }; + const handleSolutionChange = (newValue: File | null) => { + setSolutionFile(newValue); + blurActiveElement(); + }; - return ( - - - - - - - -

Map

- - , - }} - /> - {mapFile && ( - - - - )} - - {mapError &&

{mapError}

} -
- - -

Solution

- - , - }} - /> - {solutionFile && ( - - - - )} - - {solutionError && ( -

{solutionError}

- )} -
- - - - - {repoName} - + const downloadFile = (file: File) => { + const url = URL.createObjectURL(file); + const a = document.createElement("a"); + a.href = url; + a.download = file.name; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + return ( + + + + + + + +

Map

+ + , + }} + /> + {mapFile && ( + + + + )} + + {mapError &&

{mapError}

} +
+ + +

Solution

+ + , + }} + /> + {solutionFile && ( + + + + )} - ); + {solutionError &&

{solutionError}

} +
+ + + + + {repoName} + +
+ ); } export default ConfigBar; diff --git a/src/Params.tsx b/src/Params.tsx index 91155ff..980584e 100644 --- a/src/Params.tsx +++ b/src/Params.tsx @@ -1,8 +1,18 @@ -export const BACKGROUND_COLOR = 0xffffff; +export const BACKGROUND_COLOR = 0xFFFFFF; export const GRID_COLOR = 0x000000; export const TEXT_COLOR = GRID_COLOR; export const AGENT_COLORS: number[] = [ - 0xe91e63, 0x2196f3, 0x4caf50, 0xff9800, 0x00bcd4, 0x9c27b0, 0x795548, - 0xffbb3b, 0xf44336, 0x607d8b, 0x009688, 0x3f51b5, -]; + 0xE91E63, + 0x2196F3, + 0x4CAF50, + 0xFF9800, + 0x00BCD4, + 0x9C27B0, + 0x795548, + 0xFFBB3B, + 0xF44336, + 0x607D8B, + 0x009688, + 0x3F51B5 + ]; \ No newline at end of file diff --git a/src/Solution.tsx b/src/Solution.tsx index 022e0a7..401cc85 100644 --- a/src/Solution.tsx +++ b/src/Solution.tsx @@ -1,47 +1,40 @@ -import { Coordinate } from "./Graph"; + +import { Coordinate } from './Graph'; export enum AgentState { - PICKING, - CARRYING, - DELIVERED, - IDLE, - NONE, + PICKING, + CARRYING, + DELIVERED, + IDLE, + NONE, } + + export enum Orientation { NONE, X_MINUS, X_PLUS, Y_MINUS, - Y_PLUS, + Y_PLUS } export function orientationToRotation(o: Orientation): number { switch (o) { - case Orientation.NONE: - return 0; - case Orientation.X_MINUS: - return Math.PI; - case Orientation.X_PLUS: - return 0; - case Orientation.Y_MINUS: - return -Math.PI / 2; - case Orientation.Y_PLUS: - return Math.PI / 2; + case Orientation.NONE: return 0; + case Orientation.X_MINUS: return Math.PI; + case Orientation.X_PLUS: return 0; + case Orientation.Y_MINUS: return -Math.PI / 2; + case Orientation.Y_PLUS: return Math.PI / 2; } -} +}; function orientationFromString(s: string): Orientation { switch (s) { - case "X_MINUS": - return Orientation.X_MINUS; - case "X_PLUS": - return Orientation.X_PLUS; - case "Y_MINUS": - return Orientation.Y_MINUS; - case "Y_PLUS": - return Orientation.Y_PLUS; - default: - return Orientation.NONE; + case "X_MINUS": return Orientation.X_MINUS; + case "X_PLUS": return Orientation.X_PLUS; + case "Y_MINUS": return Orientation.Y_MINUS; + case "Y_PLUS": return Orientation.Y_PLUS; + default: return Orientation.NONE; } } @@ -50,11 +43,7 @@ export class Pose { public orientation: Orientation = Orientation.NONE; public state: AgentState = AgentState.NONE; - constructor( - position: Coordinate = new Coordinate(0, 0), - orientation: Orientation = Orientation.NONE, - state: AgentState = AgentState.NONE - ) { + constructor(position: Coordinate = new Coordinate(0, 0), orientation: Orientation = Orientation.NONE, state: AgentState = AgentState.NONE) { this.position = position; this.orientation = orientation; this.state = state; @@ -68,8 +57,7 @@ export function parseSolution(text: string): Solution { const lines = text.trim().split("\n"); const solution: Solution = []; - const regex = - /\((\d+),(\d+)(?:,([XY]_[A-Z]{4,5}))?(?:,(PICKING|CARRYING|DELIVERED|IDLE|NONE))?\)/g; + const regex = /\((\d+),(\d+)(?:,([XY]_[A-Z]{4,5}))?(?:,(PICKING|CARRYING|DELIVERED|IDLE|NONE))?\)/g; for (const line of lines) { const config: Config = []; @@ -77,7 +65,7 @@ export function parseSolution(text: string): Solution { for (const match of line.matchAll(regex)) { const x = parseInt(match[1], 10); const y = parseInt(match[2], 10); - const o = orientationFromString(match[3] || ""); // may be undefined + const o = orientationFromString(match[3] || ""); const stateStr = match[4] || "NONE"; console.log(stateStr); @@ -95,9 +83,8 @@ export function parseSolution(text: string): Solution { if (config.length === 0) { throw new Error("Invalid solution: no poses found"); } + if (config.length === 0) throw new Error("Invalid solution"); solution.push(config); } - - console.log("Parsed solution:", solution); return solution; } diff --git a/src/Visualizer.tsx b/src/Visualizer.tsx index f0b1b8b..3ce6053 100644 --- a/src/Visualizer.tsx +++ b/src/Visualizer.tsx @@ -1,89 +1,80 @@ -import Box from "@mui/material/Box"; -import { Solution } from "./Solution"; -import { Graph } from "./Graph"; -import PixiApp from "./PixiApp"; -import { useState, useRef, useEffect } from "react"; +import Box from '@mui/material/Box'; +import { Solution } from './Solution'; +import { Graph } from './Graph'; +import PixiApp from './PixiApp'; +import { useState, useRef, useEffect } from 'react'; interface VisualizerProps { - graph: Graph | null; - solution: Solution | null; - playAnimation: boolean; - pixiAppRef: React.MutableRefObject<{ - skipBackward?: () => void; - skipForward?: () => void; - restart?: () => void; - } | null>; - stepSize: number; - loopAnimation: boolean; - showAgentId: boolean; - tracePaths: boolean; - setCanScreenshot: (canScreenshot: boolean) => void; - showCellId: boolean; - showGoals: boolean; - showGoalVectors: boolean; + graph: Graph | null; + solution: Solution | null; + playAnimation: boolean; + pixiAppRef: React.MutableRefObject<{ skipBackward?: () => void; skipForward?: () => void; restart?: () => void; } | null>; + stepSize: number; + loopAnimation: boolean; + showAgentId: boolean; + tracePaths: boolean; + setCanScreenshot: (canScreenshot: boolean) => void; + showCellId: boolean; + showGoals: boolean; + showGoalVectors: boolean; } function Visualizer({ - graph, - solution, - playAnimation, - pixiAppRef, - stepSize, - loopAnimation, - showAgentId, - tracePaths, - setCanScreenshot, - showCellId, - showGoals, - showGoalVectors, + graph, + solution, + playAnimation, + pixiAppRef, + stepSize, + loopAnimation, + showAgentId, + tracePaths, + setCanScreenshot, + showCellId, + showGoals, + showGoalVectors, }: VisualizerProps) { - const [viewportSize, setViewportSize] = useState<{ - width: number; - height: number; - } | null>(null); - const boxRef = useRef(null); - const canvasRef = useRef(null); + const [viewportSize, setViewportSize] = useState<{ width: number; height: number } | null>(null); + const boxRef = useRef(null); + const canvasRef = useRef(null); - useEffect(() => { - const handleResize = () => { - if (boxRef.current && canvasRef.current) { - setViewportSize({ - width: boxRef.current.clientWidth, - height: - window.innerHeight - - canvasRef.current.getBoundingClientRect().top, - }); - } - }; - handleResize(); - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, []); + useEffect(() => { + const handleResize = () => { + if (boxRef.current && canvasRef.current) { + setViewportSize({ + width: boxRef.current.clientWidth, + height: window.innerHeight - canvasRef.current.getBoundingClientRect().top + }); + } + }; + handleResize(); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); - return ( - -
- {viewportSize !== null && ( - - )} -
-
- ); + return ( + +
+ {viewportSize !== null && + + } +
+
+); } -export default Visualizer; +export default Visualizer; \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index 3cd3ad8..85ea2b7 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,16 +5,16 @@ import { CssBaseline } from "@mui/material"; import App from "./App.tsx"; const darkTheme = createTheme({ - palette: { - mode: "dark", - }, + palette: { + mode: "dark", + }, }); createRoot(document.getElementById("root")!).render( - - - - - - + + + + + + ); From 58e7d5fa36a3d75045f15be3cd6e148c4c1276d6 Mon Sep 17 00:00:00 2001 From: Ren Koya Date: Tue, 10 Jun 2025 15:53:55 +0900 Subject: [PATCH 3/3] remove format --- src/ConfigBar.tsx | 145 +++--- src/Graph.tsx | 16 +- src/PixiApp.tsx | 1160 ++++++++++++++++++++------------------------- src/main.tsx | 18 +- 4 files changed, 588 insertions(+), 751 deletions(-) diff --git a/src/ConfigBar.tsx b/src/ConfigBar.tsx index 5b8aec6..6f863f5 100644 --- a/src/ConfigBar.tsx +++ b/src/ConfigBar.tsx @@ -1,12 +1,12 @@ -import AnimationControl from "./AnimationControl"; -import { Graph } from "./Graph"; -import { parseSolution, Solution } from "./Solution"; -import { Divider, Stack, Button } from "@mui/material"; +import AnimationControl from './AnimationControl'; +import { Graph } from './Graph'; +import { parseSolution, Solution } from './Solution'; +import { Divider, Stack, Button } from '@mui/material'; import { MuiFileInput } from "mui-file-input"; -import React, { useEffect } from "react"; -import ClearIcon from "@mui/icons-material/Clear"; -import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; -import Tooltip from "@mui/material/Tooltip"; +import React, { useEffect } from 'react'; +import ClearIcon from '@mui/icons-material/Clear'; +import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined'; +import Tooltip from '@mui/material/Tooltip'; interface ConfigBarProps { graph: Graph | null; @@ -19,7 +19,7 @@ interface ConfigBarProps { onRestart: () => void; stepSize: number; onStepSizeChange: (speed: number) => void; - loopAnimation: boolean; + loopAnimation: boolean, onLoopAnimationChange: (loopAnimation: boolean) => void; onFitView: () => void; showAgentId: boolean; @@ -39,7 +39,7 @@ interface ConfigBarProps { function ConfigBar({ graph, onGraphChange, - onSolutionChange, + onSolutionChange, playAnimation, onPlayAnimationChange, onSkipBackward, @@ -74,7 +74,7 @@ function ConfigBar({ if (document.activeElement instanceof HTMLElement) { document.activeElement.blur(); } - }; + } const handleLoadDemo = (mapName: string) => { fetch(`${import.meta.env.BASE_URL}/${mapName}.map`) @@ -101,9 +101,7 @@ function ConfigBar({ } catch (e) { setMapFile(null); onGraphChange(null); - setMapError( - e instanceof Error ? e.message : "An unexpected error occurred" - ); + setMapError(e instanceof Error ? e.message : "An unexpected error occurred"); } }); }, [mapFile, onGraphChange]); @@ -111,22 +109,16 @@ function ConfigBar({ useEffect(() => { if (solutionFile === null) { onSolutionChange(null); - return; + return } solutionFile.text().then((text) => { try { - if (graph === null) - throw new Error("Map must be loaded before solution"); + if (graph === null) throw new Error("Map must be loaded before solution"); const soln = parseSolution(text); soln.forEach((config) => { config.forEach((pose) => { - if ( - pose.position.x > graph.width || - pose.position.y > graph.height - ) { - throw new Error( - `Invalid solution: position ${pose.position} is out of bounds` - ); + if (pose.position.x > graph.width || pose.position.y > graph.height) { + throw new Error(`Invalid solution: position ${pose.position} is out of bounds`); } }); }); @@ -134,9 +126,7 @@ function ConfigBar({ setSolutionError(null); } catch (e) { setSolutionFile(null); - setSolutionError( - e instanceof Error ? e.message : "An unexpected error occurred" - ); + setSolutionError(e instanceof Error ? e.message : "An unexpected error occurred"); } }); }, [graph, solutionFile, onSolutionChange]); @@ -154,86 +144,71 @@ function ConfigBar({ const downloadFile = (file: File) => { const url = URL.createObjectURL(file); - const a = document.createElement("a"); + const a = document.createElement('a'); a.href = url; a.download = file.name; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); - }; + } return ( - + - - + +

Map

, - }} + value={mapFile} + onChange={handleMapChange} + placeholder="Select a map file" + sx={{width: '100%'}} + clearIconButtonProps={{ + title: "Clear", + children: + }} /> - {mapFile && ( + {mapFile && - - )} + } - {mapError &&

{mapError}

} + {mapError &&

{mapError}

}
-

Solution

- - , - }} - /> - {solutionFile && ( - - - - )} - - {solutionError &&

{solutionError}

} + /> + {solutionFile && + + + + } +
+ {solutionError &&

{solutionError}

}
- - - + + {repoName}
); } -export default ConfigBar; +export default ConfigBar; \ No newline at end of file diff --git a/src/Graph.tsx b/src/Graph.tsx index 19161c3..9825241 100644 --- a/src/Graph.tsx +++ b/src/Graph.tsx @@ -2,7 +2,7 @@ export class Coordinate { public x: number = 0; public y: number = 0; - constructor(x: number, y: number) { + constructor (x: number, y: number) { this.x = x; this.y = y; } @@ -22,13 +22,13 @@ export class Graph { } private parseGraph(fileContent: string) { - const lines = fileContent.trim().split("\n"); + const lines = fileContent.trim().split('\n'); if (lines.length < 4) { - throw new Error("Invalid map file"); + throw new Error('Invalid map file'); } const height = Number(lines[1].split(" ")[1]); if (height !== lines.length - 4) { - throw new Error("Invalid map file, check height"); + throw new Error('Invalid map file, check height'); } this.height = height; const width = Number(lines[2].split(" ")[1]); @@ -36,13 +36,13 @@ export class Graph { const graph = lines.slice(4); for (let y = 0; y < graph.length; y++) { if (graph[y].length !== width) { - throw new Error("Invalid map file, check width"); + throw new Error('Invalid map file, check width'); } for (let x = 0; x < this.width; x++) { - if (graph[y][x] !== ".") { - this.obstacles.set(new Coordinate(x, y).toString(), true); + if (graph[y][x] !== '.') { + this.obstacles.set((new Coordinate(x, y)).toString(), true); } } } } -} +} \ No newline at end of file diff --git a/src/PixiApp.tsx b/src/PixiApp.tsx index 32c1b7f..a0138c6 100644 --- a/src/PixiApp.tsx +++ b/src/PixiApp.tsx @@ -1,27 +1,11 @@ -import { - useEffect, - useRef, - useState, - forwardRef, - useImperativeHandle, - useCallback, -} from "react"; -import * as PIXI from "pixi.js"; -import { Viewport } from "pixi-viewport"; -import { Graph } from "./Graph"; -import { - Solution, - Orientation, - orientationToRotation, - AgentState, -} from "./Solution"; -import { Coordinate } from "./Graph"; -import { - BACKGROUND_COLOR, - GRID_COLOR, - TEXT_COLOR, - AGENT_COLORS, -} from "./Params"; + +import { useEffect, useRef, useState, forwardRef, useImperativeHandle, useCallback } from 'react'; +import * as PIXI from 'pixi.js'; +import { Viewport } from 'pixi-viewport'; +import { Graph } from './Graph'; +import { Solution, Orientation, orientationToRotation, AgentState } from './Solution'; +import { Coordinate } from './Graph'; +import { BACKGROUND_COLOR, GRID_COLOR, TEXT_COLOR, AGENT_COLORS } from './Params'; const GRID_UNIT_TO_PX: number = 100; const FONT_SUPER_RESOLUTION_SCALE = 3; @@ -37,672 +21,554 @@ interface PixiAppProps { showAgentId: boolean; tracePaths: boolean; setCanScreenshot: (canScreenshot: boolean) => void; - showCellId: boolean; - showGoals: boolean; - showGoalVectors: boolean; + showCellId: boolean, + showGoals: boolean, + showGoalVectors: boolean, } -const PixiApp = forwardRef( - ( - { - width, - height, - graph, - solution, - playAnimation, - stepSize, - loopAnimation, - showAgentId, - tracePaths, - setCanScreenshot, - showCellId, - showGoals, - showGoalVectors, - }: PixiAppProps, - ref - ) => { - // this is a mess of state and refs, but how I got everything to work... - // maybe someday I will clean this up or maybe someone who knows React better than me can help - // - // the variables that are used inside the animation callbacks must - // be stored in refs because the callbacks are created "once" and - // the variables are updated outside of the callbacks - const [app, setApp] = useState(null); - const [viewport, setViewport] = useState(null); - const canvasRef = useRef(null); - const [grid, setGrid] = useState(null); - const playAnimationRef = useRef(playAnimation); - const timestepRef = useRef(0.0); - const stepSizeRef = useRef(1.0); - const loopAnimationRef = useRef(loopAnimation); - const hudRef = useRef(null); - const timestepTextRef = useRef(null); - const showAgentIdRef = useRef(showAgentId); - const tickerCallbackRef = useRef<() => void>(() => {}); - const agentsRef = useRef(null); - const pickingMarkersRef = useRef(new PIXI.Container()); - const deliveredMarkersRef = useRef( - new PIXI.Container() - ); - const agentPathsRef = useRef<{ - full: PIXI.Container; - partial: PIXI.Container; - }>({ - full: new PIXI.Container(), - partial: new PIXI.Container(), - }); // same order as agentsRef - const goalMarkersRef = useRef(new PIXI.Container()); - const goalVectorsRef = useRef(new PIXI.Container()); - - // Scale a position from grid units to pixels - const scalePosition = (position: number): number => { - return position * GRID_UNIT_TO_PX + GRID_UNIT_TO_PX / 2; - }; - - function resetTimestep() { - timestepRef.current = 0.0; +const PixiApp = forwardRef(({ + width, + height, + graph, + solution, + playAnimation, + stepSize, + loopAnimation, + showAgentId, + tracePaths, + setCanScreenshot, + showCellId, + showGoals, + showGoalVectors, +}: PixiAppProps, ref) => { + // this is a mess of state and refs, but how I got everything to work... + // maybe someday I will clean this up or maybe someone who knows React better than me can help + // + // the variables that are used inside the animation callbacks must + // be stored in refs because the callbacks are created "once" and + // the variables are updated outside of the callbacks + const [app, setApp] = useState(null); + const [viewport, setViewport] = useState(null); + const canvasRef = useRef(null); + const [grid, setGrid] = useState(null); + const playAnimationRef = useRef(playAnimation); + const timestepRef = useRef(0.0); + const stepSizeRef = useRef(1.0); + const loopAnimationRef = useRef(loopAnimation); + const hudRef = useRef(null); + const timestepTextRef = useRef(null); + const showAgentIdRef = useRef(showAgentId); + const tickerCallbackRef = useRef<() => void>(() => {}); + const agentsRef = useRef(null); + const pickingMarkersRef = useRef(new PIXI.Container()); + const deliveredMarkersRef = useRef(new PIXI.Container()); + const agentPathsRef = useRef<{ full: PIXI.Container, partial: PIXI.Container }>({ + full: new PIXI.Container(), + partial: new PIXI.Container() + }); // same order as agentsRef + const goalMarkersRef = useRef(new PIXI.Container()); + const goalVectorsRef = useRef(new PIXI.Container()); + + // Scale a position from grid units to pixels + const scalePosition = (position: number) : number => { + return position * GRID_UNIT_TO_PX + GRID_UNIT_TO_PX / 2; + } + + function resetTimestep() { + timestepRef.current = 0.0; + } + + function takeScreenshot() { + if (app && viewport && grid) { + app.renderer.extract.base64(viewport).then((data) => { + const link = document.createElement('a'); + link.download = 'screenshot.png'; + link.href = data; + link.click(); + link.remove(); + }); } + } - function takeScreenshot() { - if (app && viewport && grid) { - app.renderer.extract.base64(viewport).then((data) => { - const link = document.createElement("a"); - link.download = "screenshot.png"; - link.href = data; - link.click(); - link.remove(); - }); + useImperativeHandle(ref, () => ({ + skipBackward: () => { + timestepRef.current = Math.max(0, timestepRef.current - stepSizeRef.current); + }, + skipForward: () => { + if (solution) { + timestepRef.current = Math.min(timestepRef.current + stepSizeRef.current, solution.length - 1); } + }, + restart: () => { + resetTimestep(); + }, + fit: () => { + fit(); + }, + takeScreenshot: () => { + takeScreenshot(); } - - useImperativeHandle(ref, () => ({ - skipBackward: () => { - timestepRef.current = Math.max( - 0, - timestepRef.current - stepSizeRef.current - ); - }, - skipForward: () => { - if (solution) { - timestepRef.current = Math.min( - timestepRef.current + stepSizeRef.current, - solution.length - 1 - ); - } - }, - restart: () => { - resetTimestep(); - }, - fit: () => { - fit(); - }, - takeScreenshot: () => { - takeScreenshot(); - }, - })); - - // Fit the viewport to the grid - const fit = useCallback(() => { - if (viewport === null || grid === null) return; - viewport.fitWorld(); - viewport.moveCenter( - grid.position.x + grid.width / 2, - grid.position.y + grid.height / 2 - ); - }, [viewport, grid]); - - const moveAndRotateSprites = useCallback( - (agents: PIXI.Container[], currentTime: number) => { - if (!solution) return; - - const currentTimestep = Math.floor(currentTime); - const interpolationProgress = currentTime - currentTimestep; - const currentState = solution[currentTimestep]; - const nextState = - solution[ - Math.min(currentTimestep + 1, solution.length - 1) - ]; - - // Interpolate between current and next states - agents.forEach((agent, index) => { - // Show or hide agent ID - const idText = agent.children[1]; - if (idText !== undefined) { - idText.visible = showAgentIdRef.current; - } - - const startPose = currentState[index]; - const endPose = nextState[index]; - - // Interpolate position - agent.x = - startPose.position.x + - (endPose.position.x - startPose.position.x) * - interpolationProgress; - agent.y = - startPose.position.y + - (endPose.position.y - startPose.position.y) * - interpolationProgress; - agent.x = scalePosition(agent.x); - agent.y = scalePosition(agent.y); - - // orientation-aware visualization has two objects for each sprite - const circleContainer: PIXI.Container = agent.children[0]; - if ( - circleContainer === undefined || - circleContainer.children.length < 2 - ) - return; - - // Interpolate rotation - const startRotation = orientationToRotation( - startPose.orientation - ); - const endRotation = orientationToRotation( - endPose.orientation - ); - - circleContainer.rotation = - startRotation + - (endRotation - startRotation) * interpolationProgress; - }); - }, - [solution] + })); + + // Fit the viewport to the grid + const fit = useCallback(() => { + if (viewport === null || grid === null) return; + viewport.fitWorld(); + viewport.moveCenter( + grid.position.x + grid.width / 2, + grid.position.y + grid.height / 2 ); - const updateStateMarkers = useCallback(() => { - if (!solution) return; - - const currentTimestep = Math.floor(timestepRef.current); - pickingMarkersRef.current.removeChildren(); - deliveredMarkersRef.current.removeChildren(); - - solution[0].forEach((_pose, agentId) => { - let lastPickingPose: (typeof solution)[0][0] | null = null; - let deliveredPose: (typeof solution)[0][0] | null = null; - - for (let t = currentTimestep + 1; t < solution.length; t++) { - const pose = solution[t][agentId]; - if (pose.state === AgentState.NONE) { - deliveredPose = solution[solution.length - 1][agentId]; - break; - } - if (pose.state === AgentState.IDLE) { - break; - } - - if (pose.state === AgentState.PICKING) { - lastPickingPose = pose; - continue; - } + }, [viewport, grid]); + + const moveAndRotateSprites = useCallback((agents: PIXI.Container[], currentTime: number) => { + if (!solution) return; + + const currentTimestep = Math.floor(currentTime); + const interpolationProgress = currentTime - currentTimestep; + const currentState = solution[currentTimestep]; + const nextState = solution[Math.min(currentTimestep + 1, solution.length - 1)]; + + // Interpolate between current and next states + agents.forEach((agent, index) => { + // Show or hide agent ID + const idText = agent.children[1]; + if (idText !== undefined) { + idText.visible = showAgentIdRef.current; + } - if (!deliveredPose && pose.state === AgentState.DELIVERED) { - deliveredPose = pose; - break; - } + const startPose = currentState[index]; + const endPose = nextState[index]; + + // Interpolate position + agent.x = + startPose.position.x + + (endPose.position.x - startPose.position.x) * interpolationProgress; + agent.y = + startPose.position.y + + (endPose.position.y - startPose.position.y) * interpolationProgress; + agent.x = scalePosition(agent.x); + agent.y = scalePosition(agent.y); + + // orientation-aware visualization has two objects for each sprite + const circleContainer: PIXI.Container = agent.children[0]; + if (circleContainer === undefined || circleContainer.children.length < 2) return; + + // Interpolate rotation + const startRotation = orientationToRotation(startPose.orientation); + const endRotation = orientationToRotation(endPose.orientation); + + circleContainer.rotation = + startRotation + + (endRotation - startRotation) * interpolationProgress; + }); + }, [solution]); + + const updateStateMarkers = useCallback(() => { + if (!solution) return; + + const currentTimestep = Math.floor(timestepRef.current); + pickingMarkersRef.current.removeChildren(); + deliveredMarkersRef.current.removeChildren(); + + solution[0].forEach((_pose, agentId) => { + let lastPickingPose: (typeof solution)[0][0] | null = null; + let deliveredPose: (typeof solution)[0][0] | null = null; + + for (let t = currentTimestep + 1; t < solution.length; t++) { + const pose = solution[t][agentId]; + if (pose.state === AgentState.NONE) { + deliveredPose = solution[solution.length - 1][agentId]; + break; } - - if (lastPickingPose) { - const marker = new PIXI.Graphics(); - const r = GRID_UNIT_TO_PX / 5; - marker - .circle( - scalePosition(lastPickingPose.position.x), - scalePosition(lastPickingPose.position.y), - r - ) - .fill(AGENT_COLORS[agentId % AGENT_COLORS.length]); - pickingMarkersRef.current.addChild(marker); + if (pose.state === AgentState.IDLE) { + break; } - if (deliveredPose) { - const marker = new PIXI.Graphics(); - const size = GRID_UNIT_TO_PX / 4; - marker - .rect( - scalePosition(deliveredPose.position.x) - size / 2, - scalePosition(deliveredPose.position.y) - size / 2, - size, - size - ) - .fill(AGENT_COLORS[agentId % AGENT_COLORS.length]); - deliveredMarkersRef.current.addChild(marker); + if (pose.state === AgentState.PICKING) { + lastPickingPose = pose; + continue; } - }); - }, [solution]); - - const updatePaths = useCallback( - (agents: PIXI.Container[], currentTime: number) => { - if (!solution) return; - - const currentTimestep = Math.floor(currentTime); - const interpolationProgress = currentTime - currentTimestep; - - agents.forEach((_agent, index) => { - const agentLineStyle = { - width: GRID_UNIT_TO_PX / 10, - color: AGENT_COLORS[index % AGENT_COLORS.length], - cap: "round" as const, - }; - - const full_segments = agentPathsRef.current.full.children[ - index - ] as PIXI.Container; - const partial_segments = agentPathsRef.current.partial - .children[index] as PIXI.Container; - - while (full_segments.children.length > currentTimestep) { - if (full_segments.children.length === 0) break; - full_segments.removeChildAt( - full_segments.children.length - 1 - ); - } - partial_segments.removeChildren(); - - // Full segments - while (full_segments.children.length < currentTimestep) { - const segIndex = full_segments.children.length; - const segment = full_segments.addChild( - new PIXI.Graphics() - ); - segment.moveTo( - scalePosition(solution[segIndex][index].position.x), - scalePosition(solution[segIndex][index].position.y) - ); - segment.lineTo( - scalePosition( - solution[segIndex + 1][index].position.x - ), - scalePosition( - solution[segIndex + 1][index].position.y - ) - ); - segment.stroke(agentLineStyle); - } - - // Partial segment - if ( - interpolationProgress > 0 && - currentTimestep < solution.length - 1 - ) { - const segment = partial_segments.addChild( - new PIXI.Graphics() - ); - // const segment = path.children.length === currentTimestep ? path.addChild(new PIXI.Graphics()) : path.children[currentTimestep] as PIXI.Graphics; - segment.moveTo( - scalePosition( - solution[currentTimestep][index].position.x - ), - scalePosition( - solution[currentTimestep][index].position.y - ) - ); - const interpolatedPosition = { - x: - solution[currentTimestep][index].position.x + - (solution[currentTimestep + 1][index].position - .x - - solution[currentTimestep][index].position - .x) * - interpolationProgress, - y: - solution[currentTimestep][index].position.y + - (solution[currentTimestep + 1][index].position - .y - - solution[currentTimestep][index].position - .y) * - interpolationProgress, - }; - segment.lineTo( - scalePosition(interpolatedPosition.x), - scalePosition(interpolatedPosition.y) - ); - segment.stroke(agentLineStyle); - } - }); - }, - [solution] - ); - const updateGoalVectors = useCallback( - (agents: PIXI.Container[]) => { - if (!solution) return; - - const currentTimestep = Math.floor(timestepRef.current); - - agents.forEach((agent, index) => { - const goalVector = goalVectorsRef.current.children[ - index - ] as PIXI.Graphics; - - let goalPose = null; - for ( - let t = currentTimestep + 1; - t < solution.length; - t++ - ) { - const pose = solution[t][index]; - if (pose.state === AgentState.DELIVERED) { - goalPose = pose; - break; - } - } + if (!deliveredPose && pose.state === AgentState.DELIVERED) { + deliveredPose = pose; + break; + } + } - goalVector.clear(); - if (goalPose) { - goalVector - .moveTo(agent.x, agent.y) - .lineTo( - scalePosition(goalPose.position.x), - scalePosition(goalPose.position.y) - ) - .stroke({ - color: AGENT_COLORS[ - index % AGENT_COLORS.length - ], - width: Math.max(1, GRID_UNIT_TO_PX / 25), - cap: "round" as const, - }); - } - }); - }, - [solution] - ); + if (lastPickingPose) { + const marker = new PIXI.Graphics(); + const r = GRID_UNIT_TO_PX / 5; + marker + .circle( + scalePosition(lastPickingPose.position.x), + scalePosition(lastPickingPose.position.y), + r + ) + .fill(AGENT_COLORS[agentId % AGENT_COLORS.length]); + pickingMarkersRef.current.addChild(marker); + } - // Animate the solution - const animateSolution = useCallback(() => { - if (app === null || viewport === null) return; - if (tickerCallbackRef.current) { - app.ticker.remove(tickerCallbackRef.current); - if (agentsRef.current) viewport.removeChild(agentsRef.current); - if (agentPathsRef.current) { - agentPathsRef.current.full.removeChildren(); - agentPathsRef.current.partial.removeChildren(); - } - if (timestepTextRef.current) timestepTextRef.current.text = ""; - if (goalMarkersRef.current) - goalMarkersRef.current.removeChildren(); - if (goalVectorsRef.current) - goalVectorsRef.current.removeChildren(); + if (deliveredPose) { + const marker = new PIXI.Graphics(); + const size = GRID_UNIT_TO_PX / 4; + marker + .rect( + scalePosition(deliveredPose.position.x) - size / 2, + scalePosition(deliveredPose.position.y) - size / 2, + size, + size + ) + .fill(AGENT_COLORS[agentId % AGENT_COLORS.length]); + deliveredMarkersRef.current.addChild(marker); } - if (solution === null) return; - resetTimestep(); + }); + }, [solution]) - // Check if the solution is orientation-aware - const orientationAware: boolean = - solution[0][0].orientation !== Orientation.NONE; + const updatePaths = useCallback((agents: PIXI.Container[], currentTime: number) => { + if (!solution) return; - // Picking markers - viewport.addChild(pickingMarkersRef.current); - viewport.addChild(deliveredMarkersRef.current); + const currentTimestep = Math.floor(currentTime); + const interpolationProgress = currentTime - currentTimestep; - // Paths - viewport.addChild(agentPathsRef.current.full); - viewport.addChild(agentPathsRef.current.partial); - solution[0].forEach(() => { - agentPathsRef.current.full.addChild(new PIXI.Container()); - agentPathsRef.current.partial.addChild(new PIXI.Container()); - }); + + agents.forEach((_agent, index) => { + const agentLineStyle = { + width: GRID_UNIT_TO_PX / 10, + color: AGENT_COLORS[index % AGENT_COLORS.length], + cap: "round" as const + }; - // Goal vectors - const goalVectors = viewport.addChild(goalVectorsRef.current); - solution[solution.length - 1].forEach(() => { - goalVectors.addChild(new PIXI.Graphics()); - }); + const full_segments = agentPathsRef.current.full.children[index] as PIXI.Container + const partial_segments = agentPathsRef.current.partial.children[index] as PIXI.Container - // Agents - const agents = viewport.addChild(new PIXI.Container()); - agentsRef.current = agents; - solution[0].forEach((_pose, agentId) => { - // build agent - const agent = agents.addChild(new PIXI.Container()); - const circleContainer = agent.addChild(new PIXI.Container()); - const circle = circleContainer.addChild(new PIXI.Graphics()); - const agentColor = AGENT_COLORS[agentId % AGENT_COLORS.length]; - circle.circle(0, 0, GRID_UNIT_TO_PX / 3).fill(agentColor); - if (orientationAware) { - const radius = circle.width / 2; - const triangle = circleContainer.addChild( - new PIXI.Graphics() - ); - triangle - .poly([0, radius, 0, -radius, radius, 0]) - .fill(BACKGROUND_COLOR); - } - const idText = agent.addChild( - new PIXI.Text({ - text: `${agentId}`, - style: { - fontFamily: "Arial", - fontSize: circle.width / 2, - fill: TEXT_COLOR, - }, - }) + + while(full_segments.children.length > currentTimestep) { + if (full_segments.children.length === 0) break; + full_segments.removeChildAt(full_segments.children.length - 1); + } + partial_segments.removeChildren(); + + // Full segments + while (full_segments.children.length < currentTimestep) { + const segIndex = full_segments.children.length; + const segment = full_segments.addChild(new PIXI.Graphics()); + segment.moveTo( + scalePosition(solution[segIndex][index].position.x), + scalePosition(solution[segIndex][index].position.y) ); - idText.style.fontSize *= FONT_SUPER_RESOLUTION_SCALE; - idText.scale.set( - 1 / FONT_SUPER_RESOLUTION_SCALE, - 1 / FONT_SUPER_RESOLUTION_SCALE + segment.lineTo( + scalePosition(solution[segIndex + 1][index].position.x), + scalePosition(solution[segIndex + 1][index].position.y) ); - idText.x = -idText.width / 2; - idText.y = -idText.height / 2; - }); + segment.stroke(agentLineStyle); + } - const animate = () => { - if (timestepTextRef.current) { - timestepTextRef.current.text = `${timestepRef.current.toFixed( - 1 - )} / ${(solution.length - 1).toFixed(1)}`; + // Partial segment + if (interpolationProgress > 0 && currentTimestep < solution.length - 1) { + const segment = partial_segments.addChild(new PIXI.Graphics()); + // const segment = path.children.length === currentTimestep ? path.addChild(new PIXI.Graphics()) : path.children[currentTimestep] as PIXI.Graphics; + segment.moveTo( + scalePosition(solution[currentTimestep][index].position.x), + scalePosition(solution[currentTimestep][index].position.y) + ); + const interpolatedPosition = { + x: solution[currentTimestep][index].position.x + + (solution[currentTimestep + 1][index].position.x - solution[currentTimestep][index].position.x) * interpolationProgress, + y: solution[currentTimestep][index].position.y + + (solution[currentTimestep + 1][index].position.y - solution[currentTimestep][index].position.y) * interpolationProgress, } - - if (playAnimationRef.current === true) { - if (timestepRef.current < solution.length - 1) { - const approximateFramerate = 60; - timestepRef.current += - stepSizeRef.current / approximateFramerate; - } else if (loopAnimationRef.current) { - resetTimestep(); - } + segment.lineTo(scalePosition(interpolatedPosition.x), scalePosition(interpolatedPosition.y)); + segment.stroke(agentLineStyle); + } + }); + }, [solution]); + + const updateGoalVectors = useCallback((agents: PIXI.Container[]) => { + if (!solution) return; + const currentTimestep = Math.floor(timestepRef.current); + agents.forEach((agent, index) => { + const goalVector = goalVectorsRef.current.children[index] as PIXI.Graphics; + let goalPose = null; + for (let t = currentTimestep + 1; t < solution.length; t++) { + const pose = solution[t][index]; + if (pose.state === AgentState.DELIVERED) { + goalPose = pose; + break; } + } - moveAndRotateSprites( - agents.children as PIXI.Container[], - timestepRef.current - ); - updatePaths( - agents.children as PIXI.Container[], - timestepRef.current - ); - updateGoalVectors(agents.children as PIXI.Container[]); - updateStateMarkers(); - }; - app.ticker.add(animate); - tickerCallbackRef.current = animate; - }, [ - app, - viewport, - solution, - moveAndRotateSprites, - updatePaths, - updateGoalVectors, - ]); - - // Initialize the app and viewport when the canvas is ready - useEffect(() => { - if (app === null) { - const canvas = canvasRef.current; - if (canvas) { - const pixiApp = new PIXI.Application(); - pixiApp - .init({ - width: width, - height: height, - canvas: canvas, - background: BACKGROUND_COLOR, - antialias: true, // for smooooooth circles - }) - .then(() => { - setApp(pixiApp); - }); - } - } else { - app.canvas.style.position = "absolute"; - if (viewport === null) { - const viewport = new Viewport({ - screenWidth: width, - screenHeight: height, - worldWidth: width * 2, - worldHeight: height * 2, - events: app.renderer.events, - }); - viewport.drag().pinch().wheel(); - setViewport(viewport); - } else { - if (app.stage.children.length === 0) { - app.stage.addChild(viewport); - hudRef.current = app.stage.addChild( - new PIXI.Container() - ); - const textStyle = new PIXI.TextStyle({ - fontSize: 24, - fill: TEXT_COLOR, - fontFamily: "Arial", - fontWeight: "bold", - stroke: { - color: BACKGROUND_COLOR, - width: 4, - }, - }); - timestepTextRef.current = hudRef.current.addChild( - new PIXI.Text({ - x: width / 100, - y: height / 100, - style: textStyle, - }) - ); - - hudRef.current.addChild( - new PIXI.Text({ - x: width - width / 100, - y: height / 100, - anchor: new PIXI.Point(1, 0), - text: "Click and drag to pan. Scroll to zoom.", - style: textStyle, - }) - ); - } - app.start(); + goalVector.clear(); + if (goalPose) { + goalVector + .moveTo(agent.x, agent.y) + .lineTo( + scalePosition(goalPose.position.x), + scalePosition(goalPose.position.y) + ) + .stroke({ + color: AGENT_COLORS[index % AGENT_COLORS.length], + width: Math.max(1, GRID_UNIT_TO_PX / 25), + cap: "round" as const, + }); + } + }); + }, [solution]); + + // Animate the solution + const animateSolution = useCallback(() => { + if (app === null || viewport === null) return; + if (tickerCallbackRef.current) { + app.ticker.remove(tickerCallbackRef.current); + if (agentsRef.current) viewport.removeChild(agentsRef.current); + if (agentPathsRef.current) { + agentPathsRef.current.full.removeChildren(); + agentPathsRef.current.partial.removeChildren(); + } + if (timestepTextRef.current) timestepTextRef.current.text = ""; + if (goalMarkersRef.current) goalMarkersRef.current.removeChildren(); + if (goalVectorsRef.current) goalVectorsRef.current.removeChildren(); + } + if (solution === null) return; + resetTimestep(); + + // Check if the solution is orientation-aware + const orientationAware: boolean = solution[0][0].orientation !== Orientation.NONE; + + // Picking markers + viewport.addChild(pickingMarkersRef.current); + viewport.addChild(deliveredMarkersRef.current); + + // Paths + viewport.addChild(agentPathsRef.current.full); + viewport.addChild(agentPathsRef.current.partial); + solution[0].forEach(() => { + agentPathsRef.current.full.addChild(new PIXI.Container()); + agentPathsRef.current.partial.addChild(new PIXI.Container()); + }); + + // Goal vectors + const goalVectors = viewport.addChild(goalVectorsRef.current); + solution[solution.length - 1].forEach(() => { + goalVectors.addChild(new PIXI.Graphics()); + }); + + // Agents + const agents = viewport.addChild(new PIXI.Container()); + agentsRef.current = agents; + solution[0].forEach((_pose, agentId) => { + // build agent + const agent = agents.addChild(new PIXI.Container()); + const circleContainer = agent.addChild(new PIXI.Container()); + const circle = circleContainer.addChild(new PIXI.Graphics()); + const agentColor = AGENT_COLORS[agentId % AGENT_COLORS.length]; + circle + .circle(0, 0, GRID_UNIT_TO_PX/3) + .fill(agentColor); + if (orientationAware) { + const radius = circle.width / 2; + const triangle = circleContainer.addChild(new PIXI.Graphics()); + triangle + .poly([0, radius, 0, -radius, radius, 0]) + .fill(BACKGROUND_COLOR); + } + const idText = agent.addChild(new PIXI.Text({ + text: `${agentId}`, + style: { + fontFamily: 'Arial', + fontSize: circle.width / 2, + fill: TEXT_COLOR, } + })); + idText.style.fontSize *= FONT_SUPER_RESOLUTION_SCALE; + idText.scale.set(1 / FONT_SUPER_RESOLUTION_SCALE, 1 / FONT_SUPER_RESOLUTION_SCALE); + idText.x = -idText.width / 2; + idText.y = -idText.height / 2; + }); + + const animate = () => { + if(timestepTextRef.current) { + timestepTextRef.current.text = `${timestepRef.current.toFixed(1)} / ${(solution.length - 1).toFixed(1)}`; } - return () => { - app?.stop(); - }; - }, [app, viewport, height, width]); - const drawGrid = useCallback(() => { - if (viewport === null || graph === null) return null; - const grid = viewport.addChild(new PIXI.Container()); + if (playAnimationRef.current === true) { + if (timestepRef.current < solution.length - 1) { + const approximateFramerate = 60; + timestepRef.current += stepSizeRef.current / approximateFramerate; + } else if (loopAnimationRef.current) { + resetTimestep(); + } + } - for (let x: number = 0; x < graph.width; x++) { - for (let y: number = 0; y < graph.height; y++) { - const cellContainer = grid.addChild(new PIXI.Container()); - const cellGraphic = cellContainer.addChild( - new PIXI.Graphics() - ); - const cellX = x * GRID_UNIT_TO_PX; - const cellY = y * GRID_UNIT_TO_PX; - const strokeWidth = GRID_UNIT_TO_PX / 10; - cellGraphic - .rect(cellX, cellY, GRID_UNIT_TO_PX, GRID_UNIT_TO_PX) - .stroke({ color: GRID_COLOR, width: strokeWidth }); - if (graph.obstacles.has(new Coordinate(x, y).toString())) { - cellGraphic.fill({ color: GRID_COLOR }); - } - const idText = cellContainer.addChild( + moveAndRotateSprites(agents.children as PIXI.Container[], timestepRef.current); + updatePaths(agents.children as PIXI.Container[], timestepRef.current); + updateGoalVectors(agents.children as PIXI.Container[]); + updateStateMarkers(); + } + app.ticker.add(animate); + tickerCallbackRef.current = animate; + }, [app, viewport, solution, moveAndRotateSprites, updatePaths, updateGoalVectors]); + + // Initialize the app and viewport when the canvas is ready + useEffect(() => { + if (app === null) { + const canvas = canvasRef.current; + if (canvas) { + const pixiApp = new PIXI.Application(); + pixiApp.init({ + width: width, + height: height, + canvas: canvas, + background: BACKGROUND_COLOR, + antialias: true, // for smooooooth circles + }).then(() => { + setApp(pixiApp); + }); + } + } else { + app.canvas.style.position = "absolute"; + if (viewport === null) { + const viewport = new Viewport({ + screenWidth: width, + screenHeight: height, + worldWidth: width*2, + worldHeight: height*2, + events: app.renderer.events, + }); + viewport.drag().pinch().wheel(); + setViewport(viewport); + } else { + if (app.stage.children.length === 0) { + app.stage.addChild(viewport); + hudRef.current = app.stage.addChild(new PIXI.Container()); + const textStyle = new PIXI.TextStyle({ + fontSize: 24, + fill: TEXT_COLOR, + fontFamily: "Arial", + fontWeight: "bold", + stroke: { + color: BACKGROUND_COLOR, + width: 4 + }, + }); + timestepTextRef.current = hudRef.current.addChild( new PIXI.Text({ - text: `${x + y * graph.width}`, - style: { - fontFamily: "Arial", - fontSize: cellGraphic.width / 6, - fill: TEXT_COLOR, - }, + x: width / 100, + y: height / 100, + style: textStyle, }) ); - idText.style.fontSize *= FONT_SUPER_RESOLUTION_SCALE; - idText.scale.set( - 1 / FONT_SUPER_RESOLUTION_SCALE, - 1 / FONT_SUPER_RESOLUTION_SCALE + + hudRef.current.addChild( + new PIXI.Text({ + x: width - width / 100, + y: height / 100, + anchor: new PIXI.Point(1, 0), + text: "Click and drag to pan. Scroll to zoom.", + style: textStyle, + }) ); - idText.x = cellX + strokeWidth; - idText.y = cellY + strokeWidth; } + app.start(); } - - viewport.worldHeight = grid.height * 1.2; - viewport.worldWidth = grid.width * 1.2; - return grid; - }, [viewport, graph]); - - useEffect(() => { - if (app !== null && viewport !== null) { - app.renderer.resize(width, height); - viewport.resize(width, height); - if (hudRef.current) { - hudRef.current.children[0].x = width / 100; - hudRef.current.children[0].y = height / 100; - hudRef.current.children[1].x = width - width / 100; - hudRef.current.children[1].y = height / 100; + } + return () => {app?.stop()}; + }, [app, viewport, height, width]); + + const drawGrid = useCallback(() => { + if (viewport === null || graph === null) return null; + const grid = viewport.addChild(new PIXI.Container()); + + for (let x: number = 0; x < graph.width; x++) { + for (let y: number = 0; y < graph.height; y++) { + const cellContainer = grid.addChild(new PIXI.Container()); + const cellGraphic = cellContainer.addChild(new PIXI.Graphics()); + const cellX = x * GRID_UNIT_TO_PX; + const cellY = y * GRID_UNIT_TO_PX; + const strokeWidth = GRID_UNIT_TO_PX / 10; + cellGraphic.rect(cellX, cellY, GRID_UNIT_TO_PX, GRID_UNIT_TO_PX) + .stroke({color: GRID_COLOR, width: strokeWidth}); + if (graph.obstacles.has(new Coordinate(x, y).toString())) { + cellGraphic.fill({color: GRID_COLOR}); } - fit(); + const idText = cellContainer.addChild(new PIXI.Text({ + text: `${x + y * graph.width}`, + style: { + fontFamily: 'Arial', + fontSize: cellGraphic.width / 6, + fill: TEXT_COLOR, + } + })); + idText.style.fontSize *= FONT_SUPER_RESOLUTION_SCALE; + idText.scale.set(1 / FONT_SUPER_RESOLUTION_SCALE, 1 / FONT_SUPER_RESOLUTION_SCALE); + idText.x = cellX + strokeWidth; + idText.y = cellY + strokeWidth; } - }, [app, fit, viewport, width, height]); - - // Draw the grid when the graph changes - useEffect(() => { - if (app && viewport) { - if (grid) viewport.removeChild(grid); - if (graph) setGrid(drawGrid()); + } + + viewport.worldHeight = grid.height * 1.2; + viewport.worldWidth = grid.width * 1.2; + return grid; + }, [viewport, graph]); + + // Resize the viewport when the width or height changes + useEffect(() => { + if (app !== null && viewport !== null) { + app.renderer.resize(width, height); + viewport.resize(width, height); + if (hudRef.current) { + hudRef.current.children[0].x = width / 100; + hudRef.current.children[0].y = height / 100; + hudRef.current.children[1].x = width - width / 100; + hudRef.current.children[1].y = height / 100; } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [app, graph, viewport]); // Excluding 'grid' to prevent infinite loop - - // Fit the viewport and try to animate the solution when the grid or solution changes - useEffect(() => { fit(); - animateSolution(); - setCanScreenshot(!!solution); - }, [grid, solution, animateSolution, fit, setCanScreenshot]); - - // Update the playAnimationRef when the playAnimation changes - useEffect(() => { - playAnimationRef.current = playAnimation; - stepSizeRef.current = stepSize; - loopAnimationRef.current = loopAnimation; - showAgentIdRef.current = showAgentId; - }, [playAnimation, stepSize, loopAnimation, showAgentId]); - - useEffect(() => { - if (!grid) return; - grid.children.forEach((cellContainer) => { - const idText = cellContainer.children[1]; - if (idText) { - idText.visible = showCellId; - } - }); - }, [showCellId, grid]); + } + }, [app, fit, viewport, width, height]); - useEffect(() => { - if (agentPathsRef.current) { - agentPathsRef.current.full.visible = tracePaths; - agentPathsRef.current.partial.visible = tracePaths; + // Draw the grid when the graph changes + useEffect(() => { + if (app && viewport) { + if (grid) viewport.removeChild(grid); + if (graph) setGrid(drawGrid()); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [app, graph, viewport]); // Excluding 'grid' to prevent infinite loop + + // Fit the viewport and try to animate the solution when the grid or solution changes + useEffect(() => { + fit(); + animateSolution(); + setCanScreenshot(!!solution); + }, [grid, solution, animateSolution, fit, setCanScreenshot]); + + // Update the playAnimationRef when the playAnimation changes + useEffect(() => { + playAnimationRef.current = playAnimation; + stepSizeRef.current = stepSize; + loopAnimationRef.current = loopAnimation; + showAgentIdRef.current = showAgentId; + }, [playAnimation, stepSize, loopAnimation, showAgentId]); + + useEffect(() => { + if (!grid) return; + grid.children.forEach((cellContainer) => { + const idText = cellContainer.children[1]; + if (idText) { + idText.visible = showCellId; } - if (goalMarkersRef.current) - goalMarkersRef.current.visible = showGoals; - if (goalVectorsRef.current) - goalVectorsRef.current.visible = showGoalVectors; - }, [tracePaths, showGoals, showGoalVectors]); + }); + }, [showCellId, grid]); - return ; - } -); + useEffect(() => { + if (agentPathsRef.current) { + agentPathsRef.current.full.visible = tracePaths; + agentPathsRef.current.partial.visible = tracePaths; + } + if (goalMarkersRef.current) goalMarkersRef.current.visible = showGoals; + if (goalVectorsRef.current) goalVectorsRef.current.visible = showGoalVectors; + }, [tracePaths, showGoals, showGoalVectors]); + + return +}); export default PixiApp; diff --git a/src/main.tsx b/src/main.tsx index 85ea2b7..8e805fa 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,20 +1,20 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import { ThemeProvider, createTheme } from "@mui/material/styles"; -import { CssBaseline } from "@mui/material"; -import App from "./App.tsx"; +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { CssBaseline } from '@mui/material'; +import App from './App.tsx' const darkTheme = createTheme({ palette: { - mode: "dark", + mode: 'dark', }, }); -createRoot(document.getElementById("root")!).render( +createRoot(document.getElementById('root')!).render( - -); + , +) \ No newline at end of file