diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76d23b8..2ac3b8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: Install dependencies run: | - uv pip install --system -e ".[dev]" + uv pip install --system -e ".[dev,web]" - name: Run ruff format check run: ruff format --check src tests diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 66abaa2..cb24afe 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,6 +32,20 @@ jobs: with: python-version: "3.13" + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: web-ui/package-lock.json + + - name: Install web UI dependencies + run: npm ci + working-directory: web-ui + + - name: Build web UI and copy into package + run: make build-release + - name: Install build dependencies run: | uv pip install --system twine diff --git a/.gitignore b/.gitignore index bba9107..5f24fc7 100644 --- a/.gitignore +++ b/.gitignore @@ -39,13 +39,13 @@ debug/ /test_*.xml /test_*.py +# Bundled web UI (built artifact — not source) +src/SVG2DrawIOLib/web/ + # web-ui (Next.js) web-ui/node_modules/ web-ui/.next/ web-ui/out/ web-ui/.env.local web-ui/.env*.local - -# api (FastAPI) -api/__pycache__/ -api/**/__pycache__/ +!web-ui/.env.production diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b3b2009..6ec6774 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,9 @@ repos: - types-click - types-setuptools - pytest - args: [--strict] + - pydantic + - fastapi + args: [--strict, --no-warn-unused-ignores] files: ^(src|tests)/ - repo: https://github.com/PyCQA/bandit diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 96645c1..1ffb0cd 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -32,19 +32,41 @@ src/SVG2DrawIOLib/ ├── icon_analyzer.py # Icon extraction and analysis service ├── library_validator.py # Library validation service ├── path_splitter.py # SVG path splitting logic -└── cli/ # Modular CLI with dynamic loading - ├── __init__.py # CLI entry point with dynamic command discovery - ├── helpers.py # Shared CLI utilities - ├── create.py # Create command - ├── create_helpers.py # Business logic for create command - ├── add.py # Add command - ├── remove.py # Remove command - ├── list.py # List command - ├── extract.py # Extract command - ├── rename.py # Rename command - ├── inspect.py # Inspect command - ├── validate.py # Validate command - └── split_paths.py # Split paths command +├── cli/ # Modular CLI with dynamic loading +│ ├── __init__.py # CLI entry point with dynamic command discovery +│ ├── helpers.py # Shared CLI utilities +│ ├── create.py # Create command +│ ├── create_helpers.py # Business logic for create command +│ ├── add.py # Add command +│ ├── remove.py # Remove command +│ ├── list.py # List command +│ ├── extract.py # Extract command +│ ├── rename.py # Rename command +│ ├── inspect.py # Inspect command +│ ├── validate.py # Validate command +│ ├── split_paths.py # Split paths command +│ └── web.py # Web UI launch command +├── api/ # FastAPI sidecar (optional web dependency) +│ ├── main.py # FastAPI app, CORS, router registration, StaticFiles mount +│ ├── dependencies.py # get_temp_dir() async generator (auto-cleanup) +│ ├── exceptions.py # Exception → HTTP response handlers +│ ├── models/ +│ │ └── responses.py # Pydantic response models +│ ├── routers/ # One file per endpoint +│ │ ├── create.py # POST /api/create +│ │ ├── add.py # POST /api/add +│ │ ├── remove.py # POST /api/remove +│ │ ├── rename.py # POST /api/rename +│ │ ├── list.py # POST /api/list +│ │ ├── extract.py # POST /api/extract +│ │ ├── inspect.py # POST /api/inspect +│ │ ├── validate.py # POST /api/validate +│ │ └── split_paths.py # POST /api/split-paths +│ └── services/ +│ └── processing.py # sanitize_svg_upload(), build_processing_options() +└── web/ # Pre-built Next.js static export (gitignored) + # Generated by `make build-release`; bundled into wheel + # via pyproject.toml hatchling artifacts ``` ## Core Components @@ -272,6 +294,40 @@ for filepath in COMMAND_DIR.iterdir(): - Shared helpers module - Consistent error handling +### 8. FastAPI API Sidecar (`api/`) + +Optional browser-based UI, installed with `pip install "SVG2DrawIOLib[web]"`. + +**Request lifecycle:** +``` +Browser (multipart/form-data POST) + ↓ +FastAPI router (e.g. routers/create.py) + ↓ +get_temp_dir() dependency — creates tmp dir, yields Path, cleans up in finally + ↓ +sanitize_svg_upload() — strips scripts/event-handlers, enforces 10 MB limit + ↓ +Same Python services as CLI (SVGProcessor, LibraryManager, etc.) + ↓ +Response(content=out_path.read_bytes(), ...) — file read into memory before cleanup + ↓ +Browser receives XML/SVG/ZIP blob +``` + +**Key design decisions:** + +- All imports use absolute package paths (`from SVG2DrawIOLib.api.dependencies import ...`), never relative imports. Required because uvicorn addresses the app by dotted path (`SVG2DrawIOLib.api.main:app`). +- `Response(content=bytes)` instead of `FileResponse(path)` for all file-returning endpoints. `FileResponse` streams lazily and races with the `get_temp_dir()` finally-block cleanup on Windows, causing "Failed to fetch" with no server error logged. +- The StaticFiles mount (`/`) is registered **after** all API routers so `/api/*` routes always take priority. +- UI path resolution order: `SVG2DRAWIO_UI_DIR` env var → `src/SVG2DrawIOLib/web/` (bundled) → `web-ui/out/` (dev checkout). +- CORS allows `http://localhost:3000` by default (Next.js dev server). The bundled UI is served from the same origin as the API, so CORS is not needed in production — the frontend uses relative URLs (`API_BASE = ""`). + +**Static export bundling:** +The `make build-release` target builds `web-ui/` with Next.js and copies the output to `src/SVG2DrawIOLib/web/`. This directory is gitignored but included in the wheel via `pyproject.toml` hatchling artifacts. After copying, a post-processing step renames Next.js RSC page payload files from subdirectory form (`create/__next.create/__PAGE__.txt`) to flat dot-separated form (`create/__next.create.__PAGE__.txt`) so Starlette's StaticFiles can serve them correctly. + +--- + ## CLI Commands ### Library Management Commands diff --git a/CHANGELOG.md b/CHANGELOG.md index b0b05ef..82d822d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.0] - 2026-02-21 + +### Added + +- **Browser-based Web UI**: New optional web interface that exposes all CLI commands as a browser UI. Install with `pip install "SVG2DrawIOLib[web]"` and launch with `svg2drawio web`. The server automatically opens the browser and serves both the API and frontend from a single process on port 8000. + - **Six tabs**: Create (convert SVGs to a new library), Manage (add icons, remove icons, rename icons), Extract (save icons as SVG files), Inspect (view icon details and SVG previews), Validate (structural integrity report), Split Paths (split compound paths for per-path color control) + - **FastAPI sidecar** (`src/SVG2DrawIOLib/api/`): Nine REST endpoints under `/api/*` wrapping the same Python services used by the CLI. Reuses `process_svg_files()`, `determine_sizing_strategy()`, `sanitize_filename()`, and `safe_path_join()` directly — no logic duplication. + - **SVG sanitization**: Uploaded SVG files are stripped of `' + result = sanitize_svg_upload(svg) + assert b" None: + """Test that elements are removed.""" + svg = b"
html
" + result = sanitize_svg_upload(svg) + assert b"foreignObject" not in result + assert b" None: + """Test that case-variant ' + result = sanitize_svg_upload(svg) + result_str = result.decode("utf-8") + assert "script" not in result_str.lower() + assert "alert" not in result_str + assert "' + result = sanitize_svg_upload(svg) + result_str = result.decode("utf-8") + assert "script" not in result_str.lower() + assert "alert" not in result_str + + # Test with namespace + svg = b'' + result = sanitize_svg_upload(svg) + result_str = result.decode("utf-8") + assert "script" not in result_str.lower() + assert "alert" not in result_str + + def test_strips_case_variant_foreign_object(self) -> None: + """Test that case-variant elements are removed (Bug #28).""" + # Test uppercase + svg = b"
html
" + result = sanitize_svg_upload(svg) + result_str = result.decode("utf-8") + assert "foreignobject" not in result_str.lower() + assert "
html
" + result = sanitize_svg_upload(svg) + result_str = result.decode("utf-8") + assert "foreignobject" not in result_str.lower() + assert " None: + """Test that event handler attributes are removed.""" + svg = b'' + result = sanitize_svg_upload(svg) + assert b"onclick" not in result + assert b"onload" not in result + assert b"alert" not in result + assert b" None: + """Test that javascript: hrefs are removed.""" + svg = b'' + result = sanitize_svg_upload(svg) + assert b"javascript:" not in result + assert b"alert" not in result + assert b" None: + """Test that safe hrefs are preserved.""" + svg = b'' + result = sanitize_svg_upload(svg) + assert b"https://example.com" in result + + def test_strips_javascript_xlink_href(self) -> None: + """Test that javascript: xlink:href are removed.""" + svg = b'' + result = sanitize_svg_upload(svg) + assert b"javascript:" not in result + assert b"alert" not in result + + def test_strips_javascript_src(self) -> None: + """Test that javascript: src attributes are removed.""" + svg = b'' + result = sanitize_svg_upload(svg) + assert b"javascript:" not in result + assert b"alert" not in result + + def test_nested_dangerous_elements(self) -> None: + """Test that nested dangerous elements are removed.""" + svg = b"" + result = sanitize_svg_upload(svg) + assert b" None: + """Test that multiple event handlers are all removed.""" + svg = b'' + result = sanitize_svg_upload(svg) + assert b"onclick" not in result + assert b"onmouseover" not in result + assert b"onload" not in result + + def test_preserves_safe_attributes(self) -> None: + """Test that safe attributes are preserved.""" + svg = b'' + result = sanitize_svg_upload(svg) + assert b"width" in result + assert b"height" in result + assert b"viewBox" in result + assert b"fill" in result + + def test_strips_javascript_uri_with_leading_whitespace(self) -> None: + """Test that javascript: URIs with leading whitespace are stripped (Bug #8).""" + # Test with space + svg = b'' + result = sanitize_svg_upload(svg) + assert b"href" not in result + assert b"javascript" not in result + + # Test with tab + svg = b'' + result = sanitize_svg_upload(svg) + assert b"href" not in result + assert b"javascript" not in result + + # Test with newline + svg = b'' + result = sanitize_svg_upload(svg) + assert b"href" not in result + assert b"javascript" not in result + + # Test with multiple whitespace characters + svg = b'' + result = sanitize_svg_upload(svg) + assert b"href" not in result + assert b"javascript" not in result + + def test_rejects_non_svg_root_with_svg_namespace(self) -> None: + """Test that non- root elements are rejected even with SVG namespace (Bug #9).""" + # Test with rect as root (has SVG namespace but wrong element) + non_svg = b'' + with pytest.raises(HTTPException) as exc_info: + sanitize_svg_upload(non_svg) + assert exc_info.value.status_code == 422 + assert "does not appear to be an SVG" in exc_info.value.detail + + # Test with circle as root + non_svg = b'' + with pytest.raises(HTTPException) as exc_info: + sanitize_svg_upload(non_svg) + assert exc_info.value.status_code == 422 + assert "does not appear to be an SVG" in exc_info.value.detail + + def test_preserves_svg_namespace_without_prefix(self) -> None: + """Test that SVG namespace is preserved without ns0: prefix (Bug #16).""" + svg = b'' + result = sanitize_svg_upload(svg) + result_str = result.decode("utf-8") + + # Should have not + assert " not + assert " None: + """Test that dangerous data: URIs are stripped from href attributes (Bug #20, #27).""" + # Test data:text/html with script (using HTML entities to avoid XML parsing issues) + svg = b'' + result = sanitize_svg_upload(svg) + assert b"data:text/html" not in result + assert b"' + result = sanitize_svg_upload(svg) + assert b"data:text/javascript" not in result + assert b"' + result = sanitize_svg_upload(svg) + assert b"data:text/html" not in result + + def test_strips_data_uri_xlink_href(self) -> None: + """Test that dangerous data: URIs are stripped from xlink:href attributes (Bug #20, #27).""" + svg = b'' + result = sanitize_svg_upload(svg) + assert b"data:text/html" not in result + + def test_strips_data_uri_src(self) -> None: + """Test that dangerous data: URIs are stripped from src attributes (Bug #20, #27).""" + svg = b'' + result = sanitize_svg_upload(svg) + assert b"data:text/html" not in result + + def test_strips_data_uri_with_whitespace(self) -> None: + """Test that dangerous data: URIs with leading whitespace are stripped (Bug #20, #27).""" + svg = b'' + result = sanitize_svg_upload(svg) + assert b"data:text/html" not in result + + def test_strips_data_uri_case_insensitive(self) -> None: + """Test that dangerous data: URIs are stripped regardless of case (Bug #20, #27).""" + # Test uppercase + svg = b'' + result = sanitize_svg_upload(svg) + result_str = result.decode("utf-8") + assert "data:text/html" not in result_str.lower() + + # Test mixed case + svg = b'' + result = sanitize_svg_upload(svg) + result_str = result.decode("utf-8") + assert "data:text/html" not in result_str.lower() + + def test_preserves_safe_image_data_uris(self) -> None: + """Test that safe image data: URIs are preserved (Bug #27).""" + # Test data:image/png + svg = b'' + result = sanitize_svg_upload(svg) + assert b"data:image/png" in result + + # Test data:image/jpeg + svg = b'' + result = sanitize_svg_upload(svg) + assert b"data:image/jpeg" in result + + # Test data:image/svg+xml + svg = b'' + result = sanitize_svg_upload(svg) + assert b"data:image/svg+xml" in result + + # Test data:image/gif + svg = b'' + result = sanitize_svg_upload(svg) + assert b"data:image/gif" in result + + def test_strips_javascript_application_data_uris(self) -> None: + """Test that application/javascript data: URIs are stripped (Bug #27).""" + # Test data:application/javascript + svg = b'' + result = sanitize_svg_upload(svg) + assert b"data:application/javascript" not in result + + # Test data:application/x-javascript + svg = b'' + result = sanitize_svg_upload(svg) + assert b"data:application/x-javascript" not in result + + +class TestBuildProcessingOptions: + """Tests for building SVG processing options.""" + + def test_default_options(self) -> None: + """Test default processing options.""" + options = build_processing_options() + assert options.add_css is False + assert options.css_mode == "fill" + assert options.css_color == "#000000" + assert options.css_stroke_color == "#000000" + assert options.preserve_current_color is True + assert options.css_tag == "path" + + def test_custom_options(self) -> None: + """Test custom processing options.""" + options = build_processing_options( + add_css=True, + css_mode="stroke", + css_color="#ff0000", + css_stroke_color="#00ff00", + preserve_current_color=False, + css_tag="circle", + ) + assert options.add_css is True + assert options.css_mode == "stroke" + assert options.css_color == "#ff0000" + assert options.css_stroke_color == "#00ff00" + assert options.preserve_current_color is False + assert options.css_tag == "circle" diff --git a/tests/test_api_routers.py b/tests/test_api_routers.py new file mode 100644 index 0000000..7001805 --- /dev/null +++ b/tests/test_api_routers.py @@ -0,0 +1,613 @@ +"""Tests for API routers.""" + +import io +import json +from pathlib import Path + +import pytest +from fastapi.testclient import TestClient + +from SVG2DrawIOLib.api.main import app + +client = TestClient(app) + + +@pytest.fixture +def simple_svg() -> bytes: + """Simple valid SVG for testing.""" + return b'' + + +@pytest.fixture +def simple_library(tmp_path: Path) -> Path: + """Create a simple library file for testing.""" + from SVG2DrawIOLib.library_manager import LibraryManager + from SVG2DrawIOLib.models import DrawIOIcon, SVGDimensions + + lib_path = tmp_path / "test.xml" + icon = DrawIOIcon( + name="test-icon", + xml_data=b'', + dimensions=SVGDimensions(width=100, height=100), + ) + LibraryManager().create_library([icon], lib_path) + return lib_path + + +class TestHealthEndpoint: + """Tests for health check endpoint.""" + + def test_health_check(self) -> None: + """Test health check returns version.""" + response = client.get("/api/health") + assert response.status_code == 200 + data = response.json() + assert data["status"] == "ok" + assert "version" in data + + +class TestCreateEndpoint: + """Tests for create library endpoint.""" + + def test_create_library_single_file(self, simple_svg: bytes) -> None: + """Test creating library from single SVG.""" + files = [("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml"))] + response = client.post("/api/create", files=files, data={"output_name": "test"}) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/xml" + assert b"" in response.content + + def test_create_library_multiple_files(self, simple_svg: bytes) -> None: + """Test creating library from multiple SVGs.""" + files = [ + ("svg_files", ("icon1.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ("svg_files", ("icon2.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ] + response = client.post("/api/create", files=files) + assert response.status_code == 200 + assert b"" in response.content + + def test_create_library_duplicate_filenames(self, simple_svg: bytes) -> None: + """Test that duplicate filenames are handled correctly.""" + files = [ + ("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ] + response = client.post("/api/create", files=files) + assert response.status_code == 200 + # Should have 3 icons despite duplicate names - response is XML not JSON + content = response.content.decode() + # Check that we have 3 separate icon entries + assert content.count('"title"') == 3 # Each icon should have a title field + + def test_create_library_sanitized_duplicate_filenames(self, simple_svg: bytes) -> None: + """Test that filenames that sanitize to the same name are deduplicated (Bug #22).""" + # These filenames sanitize to the same value: icon_1_.svg + files = [ + ("svg_files", ("icon<1>.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ("svg_files", ("icon_1_.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ] + response = client.post("/api/create", files=files) + assert response.status_code == 200 + content = response.content.decode() + # Should have 2 icons with different names (deduplication should add suffix) + assert content.count('"title"') == 2 + # Both sanitized names should be present (one with suffix) + assert "icon_1_" in content + assert "icon_1_-1" in content or "icon_1_-2" in content + + def test_create_library_strips_dangerous_content(self) -> None: + """Test that dangerous SVG content is stripped.""" + dangerous_svg = b'' + files = [("svg_files", ("icon.svg", io.BytesIO(dangerous_svg), "image/svg+xml"))] + response = client.post("/api/create", files=files) + assert response.status_code == 200 + assert b"script" not in response.content + assert b"alert" not in response.content + + def test_create_library_with_css(self, simple_svg: bytes) -> None: + """Test creating library with CSS injection.""" + files = [("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml"))] + data = {"add_css": "true", "css_mode": "fill", "css_color": "#ff0000"} + response = client.post("/api/create", files=files, data=data) + assert response.status_code == 200 + + def test_create_library_with_uppercase_extension(self, simple_svg: bytes) -> None: + """Test that files with uppercase .SVG extension are processed (Bug #12).""" + files = [ + ("svg_files", ("icon1.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ("svg_files", ("icon2.SVG", io.BytesIO(simple_svg), "image/svg+xml")), + ("svg_files", ("icon3.Svg", io.BytesIO(simple_svg), "image/svg+xml")), + ] + response = client.post("/api/create", files=files) + assert response.status_code == 200 + # All 3 icons should be in the library + content = response.content.decode() + assert content.count('"title"') == 3 + + +class TestListEndpoint: + """Tests for list icons endpoint.""" + + def test_list_icons(self, simple_library: Path) -> None: + """Test listing icons in a library.""" + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + response = client.post("/api/list", files=files) + assert response.status_code == 200 + data = response.json() + assert "icon_names" in data + assert "count" in data + assert data["count"] == 1 + assert "test-icon" in data["icon_names"] + + +class TestValidateEndpoint: + """Tests for validate library endpoint.""" + + def test_validate_valid_library(self, simple_svg: bytes) -> None: + """Test validating a valid library.""" + # Create a library via the API first + files = [("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml"))] + create_response = client.post("/api/create", files=files) + assert create_response.status_code == 200 + + # Now validate it + lib_content = create_response.content + files = [("library_file", ("test.xml", io.BytesIO(lib_content), "application/xml"))] + response = client.post("/api/validate", files=files) + assert response.status_code == 200 + data = response.json() + assert data["valid"] is True + assert len(data["errors"]) == 0 + + def test_validate_invalid_library(self) -> None: + """Test validating an invalid library.""" + invalid_lib = b"invalid" + files = [("library_file", ("test.xml", io.BytesIO(invalid_lib), "application/xml"))] + response = client.post("/api/validate", files=files) + assert response.status_code == 200 + data = response.json() + assert data["valid"] is False + assert len(data["errors"]) > 0 + + +class TestAddEndpoint: + """Tests for add icons endpoint.""" + + def test_add_icons(self, simple_library: Path, simple_svg: bytes) -> None: + """Test adding icons to existing library.""" + with open(simple_library, "rb") as lib_f: + files = [ + ("library_file", ("test.xml", lib_f, "application/xml")), + ("svg_files", ("new-icon.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ] + response = client.post("/api/add", files=files) # type: ignore[arg-type] # httpx type variance + assert response.status_code == 200 + assert b"" in response.content + # Should have 2 icons now - response is XML not JSON + content = response.content.decode() + assert content.count('"title"') == 2 + + def test_add_icons_duplicate_filenames(self, simple_library: Path, simple_svg: bytes) -> None: + """Test adding icons with duplicate filenames.""" + with open(simple_library, "rb") as lib_f: + files = [ + ("library_file", ("test.xml", lib_f, "application/xml")), + ("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ] + response = client.post("/api/add", files=files) # type: ignore[arg-type] # httpx type variance + assert response.status_code == 200 + # Should have 3 icons total (1 original + 2 new) - response is XML not JSON + content = response.content.decode() + assert content.count('"title"') == 3 + + def test_add_icons_sanitized_duplicate_filenames( + self, simple_library: Path, simple_svg: bytes + ) -> None: + """Test that filenames that sanitize to the same name are deduplicated (Bug #22).""" + with open(simple_library, "rb") as lib_f: + files = [ + ("library_file", ("test.xml", lib_f, "application/xml")), + ("svg_files", ("icon<1>.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ("svg_files", ("icon_1_.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ] + response = client.post("/api/add", files=files) # type: ignore[arg-type] # httpx type variance + assert response.status_code == 200 + content = response.content.decode() + # Should have 3 icons total (1 original + 2 new with deduplication) + assert content.count('"title"') == 3 + # Both sanitized names should be present (one with suffix) + assert "icon_1_" in content + + def test_add_icons_with_uppercase_extension( + self, simple_library: Path, simple_svg: bytes + ) -> None: + """Test that files with uppercase .SVG extension are processed (Bug #12).""" + with open(simple_library, "rb") as lib_f: + files = [ + ("library_file", ("test.xml", lib_f, "application/xml")), + ("svg_files", ("icon1.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ("svg_files", ("icon2.SVG", io.BytesIO(simple_svg), "image/svg+xml")), + ] + response = client.post("/api/add", files=files) # type: ignore[arg-type] # httpx type variance + assert response.status_code == 200 + # Should have 3 icons total (1 original + 2 new with different extensions) + content = response.content.decode() + assert content.count('"title"') == 3 + + +class TestRemoveEndpoint: + """Tests for remove icons endpoint.""" + + def test_remove_icons(self, simple_library: Path) -> None: + """Test removing icons from library.""" + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + data = {"icon_names": json.dumps(["test-icon"])} + response = client.post("/api/remove", files=files, data=data) + assert response.status_code == 200 + assert "X-Icons-Removed" in response.headers + assert response.headers["X-Icons-Removed"] == "1" + + def test_remove_invalid_json(self, simple_library: Path) -> None: + """Test removing with invalid JSON.""" + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + data = {"icon_names": "not-json"} + response = client.post("/api/remove", files=files, data=data) + assert response.status_code == 422 + + def test_remove_non_list_json(self, simple_library: Path) -> None: + """Test that non-list JSON is rejected (Bug #23).""" + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + # Test with string + data = {"icon_names": json.dumps("test-icon")} + response = client.post("/api/remove", files=files, data=data) + assert response.status_code == 422 + assert "must be a JSON array" in response.json()["detail"] + assert "str" in response.json()["detail"] + + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + # Test with number + data = {"icon_names": json.dumps(123)} + response = client.post("/api/remove", files=files, data=data) + assert response.status_code == 422 + assert "must be a JSON array" in response.json()["detail"] + assert "int" in response.json()["detail"] + + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + # Test with object + data = {"icon_names": json.dumps({"name": "test"})} + response = client.post("/api/remove", files=files, data=data) + assert response.status_code == 422 + assert "must be a JSON array" in response.json()["detail"] + assert "dict" in response.json()["detail"] + + def test_remove_non_string_elements(self, simple_library: Path) -> None: + """Test that non-string list elements are rejected (Bug #26).""" + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + # Test with dict in list + data = {"icon_names": json.dumps([{"name": "test"}])} + response = client.post("/api/remove", files=files, data=data) + assert response.status_code == 422 + assert "must contain only strings" in response.json()["detail"] + assert "dict" in response.json()["detail"] + + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + # Test with number in list + data = {"icon_names": json.dumps([123])} + response = client.post("/api/remove", files=files, data=data) + assert response.status_code == 422 + assert "must contain only strings" in response.json()["detail"] + assert "int" in response.json()["detail"] + + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + # Test with mixed types + data = {"icon_names": json.dumps(["valid", 123, "another"])} + response = client.post("/api/remove", files=files, data=data) + assert response.status_code == 422 + assert "must contain only strings" in response.json()["detail"] + assert "index 1" in response.json()["detail"] + + +class TestRenameEndpoint: + """Tests for rename icon endpoint.""" + + def test_rename_icon(self, simple_library: Path) -> None: + """Test renaming an icon.""" + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + data = {"old_name": "test-icon", "new_name": "renamed-icon"} + response = client.post("/api/rename", files=files, data=data) + assert response.status_code == 200 + content = response.content.decode() + # Check that renamed icon exists + assert "renamed-icon" in content + assert "test-icon" not in content or content.count("test-icon") == 0 + + def test_rename_icon_conflict_returns_409(self, simple_svg: bytes) -> None: + """Test that renaming to existing name returns 409 (Bug #13).""" + # Create a library with two icons + files = [ + ("svg_files", ("icon1.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ("svg_files", ("icon2.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ] + create_response = client.post("/api/create", files=files) + assert create_response.status_code == 200 + + # Try to rename icon1 to icon2 without overwrite + lib_content = create_response.content + with io.BytesIO(lib_content) as lib_f: + files = [("library_file", ("library.xml", lib_f, "application/xml"))] + data = {"old_name": "icon1", "new_name": "icon2", "overwrite": "false"} + response = client.post("/api/rename", files=files, data=data) + + # Should return 409 Conflict, not 400 + assert response.status_code == 409 + assert "already exists" in response.json()["detail"] + + def test_rename_invalid_library_returns_422(self) -> None: + """Test that invalid library format returns 422 (Bug #32).""" + # Create an invalid library file + invalid_lib = b"invalid" + with io.BytesIO(invalid_lib) as lib_f: + files = [("library_file", ("library.xml", lib_f, "application/xml"))] + data = {"old_name": "test", "new_name": "renamed"} + response = client.post("/api/rename", files=files, data=data) + + # Should return 422, not 500 + assert response.status_code == 422 + assert "Invalid library" in response.json()["detail"] + + +class TestExtractEndpoint: + """Tests for extract icons endpoint.""" + + def test_extract_all_icons(self, simple_svg: bytes) -> None: + """Test extracting all icons as ZIP.""" + # Create a library via the API first + files = [("svg_files", ("test-icon.svg", io.BytesIO(simple_svg), "image/svg+xml"))] + create_response = client.post("/api/create", files=files) + assert create_response.status_code == 200 + + # Now extract from it + lib_content = create_response.content + files = [("library_file", ("test.xml", io.BytesIO(lib_content), "application/xml"))] + response = client.post("/api/extract", files=files) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/zip" + # Verify it's a valid ZIP + import zipfile + + zip_data = io.BytesIO(response.content) + with zipfile.ZipFile(zip_data, "r") as zf: + assert len(zf.namelist()) == 1 + assert "test-icon.svg" in zf.namelist() + + def test_extract_specific_icons(self, simple_svg: bytes) -> None: + """Test extracting specific icons.""" + # Create a library via the API first + files = [("svg_files", ("test-icon.svg", io.BytesIO(simple_svg), "image/svg+xml"))] + create_response = client.post("/api/create", files=files) + assert create_response.status_code == 200 + + # Now extract specific icon + lib_content = create_response.content + files = [("library_file", ("test.xml", io.BytesIO(lib_content), "application/xml"))] + data = {"icon_names": json.dumps(["test-icon"])} + response = client.post("/api/extract", files=files, data=data) + assert response.status_code == 200 + + def test_extract_non_list_json(self, simple_svg: bytes) -> None: + """Test that non-list JSON is rejected (Bug #23).""" + # Create a library via the API first + files = [("svg_files", ("test-icon.svg", io.BytesIO(simple_svg), "image/svg+xml"))] + create_response = client.post("/api/create", files=files) + assert create_response.status_code == 200 + + lib_content = create_response.content + files = [("library_file", ("test.xml", io.BytesIO(lib_content), "application/xml"))] + # Test with string instead of list + data = {"icon_names": json.dumps("test-icon")} + response = client.post("/api/extract", files=files, data=data) + assert response.status_code == 422 + assert "must be a JSON array" in response.json()["detail"] + assert "str" in response.json()["detail"] + + +class TestInspectEndpoint: + """Tests for inspect icons endpoint.""" + + def test_inspect_icons(self, simple_library: Path) -> None: + """Test inspecting icons in library.""" + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + response = client.post("/api/inspect", files=files) + assert response.status_code == 200 + data = response.json() + assert "icons" in data + assert "count" in data + assert data["count"] == 1 + assert len(data["icons"]) == 1 + icon = data["icons"][0] + assert icon["name"] == "test-icon" + assert icon["width"] == 100 + assert icon["height"] == 100 + + def test_inspect_non_list_json(self, simple_library: Path) -> None: + """Test that non-list JSON is rejected (Bug #23).""" + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + # Test with string instead of list + data = {"icon_names": json.dumps("test-icon")} + response = client.post("/api/inspect", files=files, data=data) + assert response.status_code == 422 + assert "must be a JSON array" in response.json()["detail"] + assert "str" in response.json()["detail"] + + +class TestSplitPathsEndpoint: + """Tests for split paths endpoint.""" + + def test_split_paths(self) -> None: + """Test splitting compound SVG paths.""" + svg_with_compound_path = ( + b'' + ) + files = [("svg_file", ("test.svg", io.BytesIO(svg_with_compound_path), "image/svg+xml"))] + response = client.post("/api/split-paths", files=files) + assert response.status_code == 200 + assert response.headers["content-type"] == "image/svg+xml" + assert "X-Paths-Processed" in response.headers + assert "X-Subpaths-Created" in response.headers + + def test_split_paths_sanitizes_input(self) -> None: + """Test that split-paths sanitizes dangerous content.""" + dangerous_svg = b'' + files = [("svg_file", ("test.svg", io.BytesIO(dangerous_svg), "image/svg+xml"))] + response = client.post("/api/split-paths", files=files) + assert response.status_code == 200 + assert b"script" not in response.content + assert b"alert" not in response.content + + +class TestErrorHandling: + """Tests for error handling behavior.""" + + def test_library_format_error_returns_422_not_400(self, simple_svg: bytes) -> None: + """Test that library format errors return 422, not 400 or 500 (Bug #17).""" + # Create a malformed library with invalid XML structure + malformed_library = b"invalid" + + with io.BytesIO(malformed_library) as lib_f: + files = [("library_file", ("library.xml", lib_f, "application/xml"))] + # Try to add icons to this malformed library + svg_files = [("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml"))] + response = client.post("/api/add", files=files + svg_files) + + # Should return 422 (unprocessable entity) for library format errors + assert response.status_code == 422 + assert "Invalid library" in response.json()["detail"] + + def test_remove_library_format_error_returns_422(self) -> None: + """Test that remove endpoint returns 422 for invalid library format.""" + malformed_library = b"invalid" + + with io.BytesIO(malformed_library) as lib_f: + files = [("library_file", ("library.xml", lib_f, "application/xml"))] + data = {"icon_names": json.dumps(["test-icon"])} + response = client.post("/api/remove", files=files, data=data) + + assert response.status_code == 422 + assert "Invalid library" in response.json()["detail"] + + def test_list_library_format_error_returns_422(self) -> None: + """Test that list endpoint returns 422 for invalid library format.""" + malformed_library = b"invalid" + + with io.BytesIO(malformed_library) as lib_f: + files = [("library_file", ("library.xml", lib_f, "application/xml"))] + response = client.post("/api/list", files=files) + + assert response.status_code == 422 + assert "Invalid library" in response.json()["detail"] + + def test_extract_library_format_error_returns_422(self) -> None: + """Test that extract endpoint returns 422 for invalid library format.""" + malformed_library = b"invalid" + + with io.BytesIO(malformed_library) as lib_f: + files = [("library_file", ("library.xml", lib_f, "application/xml"))] + response = client.post("/api/extract", files=files) + + assert response.status_code == 422 + assert "Invalid library" in response.json()["detail"] + + def test_inspect_library_format_error_returns_422(self) -> None: + """Test that inspect endpoint returns 422 for invalid library format.""" + malformed_library = b"invalid" + + with io.BytesIO(malformed_library) as lib_f: + files = [("library_file", ("library.xml", lib_f, "application/xml"))] + response = client.post("/api/inspect", files=files) + + assert response.status_code == 422 + assert "Invalid library" in response.json()["detail"] + + def test_rename_validation_error_returns_400(self, simple_library: Path) -> None: + """Test that rename endpoint returns 400 for validation errors.""" + with open(simple_library, "rb") as f: + files = [("library_file", ("test.xml", f, "application/xml"))] + # Whitespace-only new_name should return 400 + data = {"old_name": "test-icon", "new_name": " "} + response = client.post("/api/rename", files=files, data=data) + + assert response.status_code == 400 + assert "cannot be empty" in response.json()["detail"] + + def test_create_invalid_css_mode_returns_422(self, simple_svg: bytes) -> None: + """Test that create endpoint returns 422 for invalid css_mode (Bug #19).""" + files = [("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml"))] + data = {"css_mode": "invalid"} + response = client.post("/api/create", files=files, data=data) + + assert response.status_code == 422 + detail = response.json()["detail"] + assert "Invalid css_mode" in detail + assert "fill" in detail and "stroke" in detail and "both" in detail + + def test_add_invalid_css_mode_returns_422( + self, simple_library: Path, simple_svg: bytes + ) -> None: + """Test that add endpoint returns 422 for invalid css_mode (Bug #19).""" + with open(simple_library, "rb") as lib_f: + files = [ + ("library_file", ("test.xml", lib_f, "application/xml")), + ("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ] + data = {"css_mode": "invalid"} + response = client.post("/api/add", files=files, data=data) # type: ignore[arg-type] + + assert response.status_code == 422 + detail = response.json()["detail"] + assert "Invalid css_mode" in detail + assert "fill" in detail and "stroke" in detail and "both" in detail + + def test_create_partial_dimensions_returns_422(self, simple_svg: bytes) -> None: + """Test that create endpoint returns 422 for partial dimensions (Bug #24).""" + files = [("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml"))] + # Test with only width + data = {"width": "100"} + response = client.post("/api/create", files=files, data=data) + assert response.status_code == 422 + assert "Both width and height must be specified" in response.json()["detail"] + + # Test with only height + data = {"height": "100"} + response = client.post("/api/create", files=files, data=data) + assert response.status_code == 422 + assert "Both width and height must be specified" in response.json()["detail"] + + def test_add_partial_dimensions_returns_422( + self, simple_library: Path, simple_svg: bytes + ) -> None: + """Test that add endpoint returns 422 for partial dimensions (Bug #24).""" + with open(simple_library, "rb") as lib_f: + files = [ + ("library_file", ("test.xml", lib_f, "application/xml")), + ("svg_files", ("icon.svg", io.BytesIO(simple_svg), "image/svg+xml")), + ] + # Test with only width + data = {"width": "100"} + response = client.post("/api/add", files=files, data=data) # type: ignore[arg-type] + assert response.status_code == 422 + assert "Both width and height must be specified" in response.json()["detail"] diff --git a/tests/test_cli_web.py b/tests/test_cli_web.py new file mode 100644 index 0000000..2c83cde --- /dev/null +++ b/tests/test_cli_web.py @@ -0,0 +1,297 @@ +"""Tests for the web CLI command.""" + +import os +from pathlib import Path +from unittest.mock import MagicMock, Mock, patch + +import pytest +from click.testing import CliRunner + +from SVG2DrawIOLib.cli.web import web + + +@pytest.fixture +def runner() -> CliRunner: + """Create a CLI runner for testing.""" + return CliRunner() + + +@pytest.fixture +def mock_ui_dir(tmp_path: Path) -> Path: + """Create a mock UI directory.""" + ui_dir = tmp_path / "ui" + ui_dir.mkdir() + (ui_dir / "index.html").write_text("") + return ui_dir + + +class TestWebCommand: + """Tests for the web CLI command.""" + + def test_web_missing_ui_dir(self, runner: CliRunner, tmp_path: Path) -> None: + """Test error when UI directory doesn't exist.""" + nonexistent = tmp_path / "nonexistent" + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke(web, ["--ui-dir", str(nonexistent), "--no-browser"]) + assert result.exit_code != 0 + assert "Web UI build not found" in result.output + + def test_web_missing_uvicorn(self, runner: CliRunner, mock_ui_dir: Path) -> None: + """Test error when uvicorn is not installed.""" + # Remove uvicorn from sys.modules if it exists, and prevent import + with patch.dict("sys.modules", {"uvicorn": None}): + result = runner.invoke(web, ["--ui-dir", str(mock_ui_dir)]) + assert result.exit_code != 0 + assert "uvicorn is not installed" in result.output + + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + @patch("SVG2DrawIOLib.cli.web.webbrowser") + def test_web_basic( + self, mock_webbrowser: Mock, mock_thread: Mock, runner: CliRunner, mock_ui_dir: Path + ) -> None: + """Test basic web command execution.""" + # Mock uvicorn module + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke(web, ["--ui-dir", str(mock_ui_dir), "--no-browser"]) + assert result.exit_code == 0 + mock_uvicorn.run.assert_called_once() + # Verify uvicorn.run was called with correct parameters + call_kwargs = mock_uvicorn.run.call_args[1] + assert call_kwargs["host"] == "localhost" + assert call_kwargs["port"] == 8000 + assert call_kwargs["reload"] is False + + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + def test_web_custom_host_port( + self, mock_thread: Mock, runner: CliRunner, mock_ui_dir: Path + ) -> None: + """Test web command with custom host and port.""" + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke( + web, + [ + "--host", + "0.0.0.0", + "--port", + "9000", + "--ui-dir", + str(mock_ui_dir), + "--no-browser", + ], + ) + assert result.exit_code == 0 + call_kwargs = mock_uvicorn.run.call_args[1] + assert call_kwargs["host"] == "0.0.0.0" + assert call_kwargs["port"] == 9000 + + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + def test_web_verbose(self, mock_thread: Mock, runner: CliRunner, mock_ui_dir: Path) -> None: + """Test web command with verbose logging.""" + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke(web, ["--ui-dir", str(mock_ui_dir), "--no-browser", "--verbose"]) + assert result.exit_code == 0 + call_kwargs = mock_uvicorn.run.call_args[1] + assert call_kwargs["log_level"] == "debug" + + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + def test_web_quiet(self, mock_thread: Mock, runner: CliRunner, mock_ui_dir: Path) -> None: + """Test web command with quiet logging.""" + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke(web, ["--ui-dir", str(mock_ui_dir), "--no-browser", "--quiet"]) + assert result.exit_code == 0 + call_kwargs = mock_uvicorn.run.call_args[1] + assert call_kwargs["log_level"] == "warning" + + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + def test_web_opens_browser( + self, + mock_thread: Mock, + runner: CliRunner, + mock_ui_dir: Path, + ) -> None: + """Test that browser opens when --no-browser is not set.""" + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke(web, ["--ui-dir", str(mock_ui_dir)]) + assert result.exit_code == 0 + # Thread should be started for browser opening + mock_thread.assert_called_once() + thread_call = mock_thread.call_args + assert thread_call[1]["daemon"] is True + + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + def test_web_no_browser_flag( + self, mock_thread: Mock, runner: CliRunner, mock_ui_dir: Path + ) -> None: + """Test that browser doesn't open with --no-browser flag.""" + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke(web, ["--ui-dir", str(mock_ui_dir), "--no-browser"]) + assert result.exit_code == 0 + # Thread should not be started when --no-browser is set + mock_thread.assert_not_called() + + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + def test_web_sets_environment_variable( + self, mock_thread: Mock, runner: CliRunner, mock_ui_dir: Path + ) -> None: + """Test that SVG2DRAWIO_UI_DIR environment variable is set.""" + # Clear any existing env var + os.environ.pop("SVG2DRAWIO_UI_DIR", None) + + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke(web, ["--ui-dir", str(mock_ui_dir), "--no-browser"]) + assert result.exit_code == 0 + assert "SVG2DRAWIO_UI_DIR" in os.environ + assert os.environ["SVG2DRAWIO_UI_DIR"] == str(mock_ui_dir.resolve()) + + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + def test_web_output_messages( + self, mock_thread: Mock, runner: CliRunner, mock_ui_dir: Path + ) -> None: + """Test that appropriate messages are displayed.""" + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke(web, ["--ui-dir", str(mock_ui_dir), "--no-browser"]) + assert result.exit_code == 0 + assert "SVG2DrawIO Web UI" in result.output + assert "http://localhost:8000" in result.output + assert "Serving UI from" in result.output + assert "Ctrl+C" in result.output + + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + def test_web_custom_url_in_output( + self, mock_thread: Mock, runner: CliRunner, mock_ui_dir: Path + ) -> None: + """Test that custom host/port appears in output.""" + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke( + web, + [ + "--host", + "127.0.0.1", + "--port", + "3000", + "--ui-dir", + str(mock_ui_dir), + "--no-browser", + ], + ) + assert result.exit_code == 0 + assert "http://127.0.0.1:3000" in result.output + + @patch("SVG2DrawIOLib.cli.web._BUNDLED_UI", Path("/fake/bundled/ui")) + @patch("SVG2DrawIOLib.cli.web._DEV_UI") + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + def test_web_uses_bundled_ui( + self, mock_thread: Mock, mock_dev_ui: Mock, runner: CliRunner, mock_ui_dir: Path + ) -> None: + """Test that bundled UI is used when available.""" + # Clear any existing env var + os.environ.pop("SVG2DRAWIO_UI_DIR", None) + + # Make bundled UI appear to exist + with patch("pathlib.Path.is_dir") as mock_is_dir: + # First call checks _BUNDLED_UI.is_dir() -> True + # Second call checks ui_path.is_dir() -> True + mock_is_dir.side_effect = [True, True] + + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke(web, ["--no-browser"]) + assert result.exit_code == 0 + assert "SVG2DRAWIO_UI_DIR" in os.environ + + @patch("SVG2DrawIOLib.cli.web._BUNDLED_UI", Path("/fake/bundled/ui")) + @patch("SVG2DrawIOLib.cli.web._DEV_UI") + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + def test_web_uses_dev_ui( + self, mock_thread: Mock, mock_dev_ui: Mock, runner: CliRunner, tmp_path: Path + ) -> None: + """Test that dev UI is used when bundled UI is not available.""" + # Clear any existing env var + os.environ.pop("SVG2DRAWIO_UI_DIR", None) + + dev_ui = tmp_path / "dev-ui" + dev_ui.mkdir() + mock_dev_ui.return_value = dev_ui + + with patch("pathlib.Path.is_dir") as mock_is_dir: + # First call checks _BUNDLED_UI.is_dir() -> False + # Second call checks _DEV_UI.is_dir() -> True + mock_is_dir.side_effect = [False, True] + + mock_uvicorn = MagicMock() + with patch.dict("sys.modules", {"uvicorn": mock_uvicorn}): + result = runner.invoke(web, ["--no-browser"]) + assert result.exit_code == 0 + + @patch("SVG2DrawIOLib.cli.web.webbrowser") + @patch("SVG2DrawIOLib.cli.web.time.sleep") + def test_web_browser_opens_with_delay( + self, mock_sleep: Mock, mock_webbrowser: Mock, runner: CliRunner, mock_ui_dir: Path + ) -> None: + """Test that browser opens with a delay when not using --no-browser.""" + mock_uvicorn = MagicMock() + + # Track if the thread function was called + thread_target = None + + def capture_thread(*args: object, **kwargs: object) -> Mock: + nonlocal thread_target + thread_target = kwargs.get("target") + mock_thread = MagicMock() + # Actually call the target function to test webbrowser.open + if thread_target and callable(thread_target): + thread_target() + return mock_thread + + with ( + patch("SVG2DrawIOLib.cli.web.threading.Thread", side_effect=capture_thread), + patch.dict("sys.modules", {"uvicorn": mock_uvicorn}), + ): + result = runner.invoke(web, ["--ui-dir", str(mock_ui_dir)]) + assert result.exit_code == 0 + # Verify sleep was called with 1.2 seconds + mock_sleep.assert_called_once_with(1.2) + # Verify webbrowser.open was called + mock_webbrowser.open.assert_called_once_with("http://localhost:8000") + + @patch("SVG2DrawIOLib.cli.web.threading.Thread") + @patch("SVG2DrawIOLib.cli.web.webbrowser") + def test_web_uses_localhost_for_wildcard_host( + self, mock_webbrowser: Mock, mock_thread: Mock, runner: CliRunner, mock_ui_dir: Path + ) -> None: + """Test that 0.0.0.0 host displays as localhost in URL (Bug #15).""" + mock_uvicorn = MagicMock() + + # Track if the thread function was called + thread_target = None + + def capture_thread(*args: object, **kwargs: object) -> Mock: + nonlocal thread_target + thread_target = kwargs.get("target") + mock_thread_obj = MagicMock() + # Actually call the target function to test webbrowser.open + if thread_target and callable(thread_target): + thread_target() + return mock_thread_obj + + with ( + patch("SVG2DrawIOLib.cli.web.threading.Thread", side_effect=capture_thread), + patch.dict("sys.modules", {"uvicorn": mock_uvicorn}), + ): + result = runner.invoke(web, ["--host", "0.0.0.0", "--ui-dir", str(mock_ui_dir)]) + assert result.exit_code == 0 + # Should display localhost in output, not 0.0.0.0 + assert "http://localhost:8000" in result.output + assert "http://0.0.0.0" not in result.output + # Browser should open localhost URL + mock_webbrowser.open.assert_called_once_with("http://localhost:8000") diff --git a/uv.lock b/uv.lock index a655e30..197269d 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,36 @@ version = 1 revision = 3 requires-python = ">=3.13" +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + [[package]] name = "bandit" version = "1.9.3" @@ -238,6 +268,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, ] +[[package]] +name = "fastapi" +version = "0.129.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/cc/1b0d90ed759ff8c9dbc4800de7475d4e9256a81b97b45bd05a1affcb350a/fastapi-0.129.2.tar.gz", hash = "sha256:e2b3637a2b47856e704dbd9a3a09393f6df48e8b9cb6c7a3e26ba44d2053f9ab", size = 368211, upload-time = "2026-02-21T17:25:49.198Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/d0/a89a640308016c7fff8d2a47b86cc03ee7cca780b5079d0b69f466f9e1a9/fastapi-0.129.2-py3-none-any.whl", hash = "sha256:e21d9f6e8db376655187905ad0145edd6f6a4e5f2bff241c4efb8a0bffd6a540", size = 103227, upload-time = "2026-02-21T17:25:47.745Z" }, +] + [[package]] name = "filelock" version = "3.20.3" @@ -247,6 +293,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + [[package]] name = "id" version = "1.6.1" @@ -564,6 +669,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -603,6 +776,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -763,6 +954,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, ] +[[package]] +name = "starlette" +version = "0.52.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, +] + [[package]] name = "stevedore" version = "5.6.0" @@ -785,6 +988,7 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "bandit" }, + { name = "httpx" }, { name = "mypy" }, { name = "pip" }, { name = "pre-commit" }, @@ -794,24 +998,33 @@ dev = [ { name = "twine" }, { name = "uv" }, ] +web = [ + { name = "fastapi" }, + { name = "python-multipart" }, + { name = "uvicorn", extra = ["standard"] }, +] [package.metadata] requires-dist = [ { name = "bandit", marker = "extra == 'dev'", specifier = ">=1.7.0" }, { name = "click", specifier = ">=8.1.0" }, + { name = "fastapi", marker = "extra == 'web'", specifier = ">=0.115.0" }, + { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.27.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.18.0" }, { name = "pip", marker = "extra == 'dev'", specifier = ">=26.0.1" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.4.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.4.2" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0" }, + { name = "python-multipart", marker = "extra == 'web'", specifier = ">=0.0.12" }, { name = "rich", specifier = ">=13.0.0" }, { name = "rich-click", specifier = ">=1.8.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14.4" }, { name = "svgelements", specifier = ">=1.9.0" }, { name = "twine", marker = "extra == 'dev'", specifier = ">=5.0.0" }, { name = "uv", marker = "extra == 'dev'", specifier = ">=0.10.0" }, + { name = "uvicorn", extras = ["standard"], marker = "extra == 'web'", specifier = ">=0.32.0" }, ] -provides-extras = ["dev"] +provides-extras = ["dev", "web"] [[package]] name = "svgelements" @@ -851,6 +1064,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + [[package]] name = "urllib3" version = "2.6.3" @@ -885,6 +1110,56 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/76/1034c46244feafec2c274ac52b094f35d47c94cdb11461c24cf4be8a0c0c/uv-0.10.0-py3-none-win_arm64.whl", hash = "sha256:e90c509749b3422eebb54057434b7119892330d133b9690a88f8a6b0f3116be3", size = 21880261, upload-time = "2026-02-05T20:57:14.724Z" }, ] +[[package]] +name = "uvicorn" +version = "0.41.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + [[package]] name = "virtualenv" version = "20.36.1" @@ -898,3 +1173,96 @@ sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544 wheels = [ { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, ] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] diff --git a/web-ui/.env.local.example b/web-ui/.env.local.example new file mode 100644 index 0000000..600de8d --- /dev/null +++ b/web-ui/.env.local.example @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=http://localhost:8000 diff --git a/web-ui/.env.production b/web-ui/.env.production new file mode 100644 index 0000000..70dcba4 --- /dev/null +++ b/web-ui/.env.production @@ -0,0 +1,3 @@ +# When the static export is served by FastAPI on the same origin, +# leave NEXT_PUBLIC_API_URL empty so the browser uses same-origin /api/* requests. +NEXT_PUBLIC_API_URL= diff --git a/web-ui/next-env.d.ts b/web-ui/next-env.d.ts new file mode 100644 index 0000000..9edff1c --- /dev/null +++ b/web-ui/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/web-ui/next.config.ts b/web-ui/next.config.ts new file mode 100644 index 0000000..1b7ca02 --- /dev/null +++ b/web-ui/next.config.ts @@ -0,0 +1,21 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + reactStrictMode: true, + // Produce a fully-static export in web-ui/out/ so FastAPI can serve it. + // When NEXT_PUBLIC_API_URL is empty the frontend makes same-origin /api/* calls. + // Only set output: "export" in production to avoid warning about rewrites in dev mode. + output: process.env.NODE_ENV === "production" ? "export" : undefined, + trailingSlash: true, +}; + +// In dev mode (next dev), proxy /api/* to the FastAPI server so no .env.local is needed. +// Rewrites are not supported in static exports — this block is skipped during `next build`. +if (process.env.NODE_ENV !== "production") { + const apiBase = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; + nextConfig.rewrites = async () => [ + { source: "/api/:path*", destination: `${apiBase}/api/:path*` }, + ]; +} + +export default nextConfig; diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json new file mode 100644 index 0000000..aba20ea --- /dev/null +++ b/web-ui/package-lock.json @@ -0,0 +1,986 @@ +{ + "name": "svg2drawio-web-ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "svg2drawio-web-ui", + "version": "0.1.0", + "dependencies": { + "lucide-react": "^0.575.0", + "next": "^16.1.6", + "react": "^19.2.4", + "react-dom": "^19.2.4" + }, + "devDependencies": { + "@types/node": "^25.3.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "typescript": "^5.7.2" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@next/env": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/node": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lucide-react": { + "version": "0.575.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.575.0.tgz", + "integrity": "sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", + "license": "MIT", + "dependencies": { + "@next/env": "16.1.6", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/web-ui/package.json b/web-ui/package.json new file mode 100644 index 0000000..d58af0b --- /dev/null +++ b/web-ui/package.json @@ -0,0 +1,24 @@ +{ + "name": "svg2drawio-web-ui", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "next": "^16.1.6", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "lucide-react": "^0.575.0" + }, + "devDependencies": { + "@types/node": "^25.3.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "typescript": "^5.7.2" + } +} diff --git a/web-ui/src/app/create/page.tsx b/web-ui/src/app/create/page.tsx new file mode 100644 index 0000000..2400147 --- /dev/null +++ b/web-ui/src/app/create/page.tsx @@ -0,0 +1,5 @@ +import { CreateTab } from "@/components/tabs/CreateTab"; + +export default function CreatePage() { + return ; +} diff --git a/web-ui/src/app/extract/page.tsx b/web-ui/src/app/extract/page.tsx new file mode 100644 index 0000000..c60ba2c --- /dev/null +++ b/web-ui/src/app/extract/page.tsx @@ -0,0 +1,5 @@ +import { ExtractTab } from "@/components/tabs/ExtractTab"; + +export default function ExtractPage() { + return ; +} diff --git a/web-ui/src/app/globals.css b/web-ui/src/app/globals.css new file mode 100644 index 0000000..99c6664 --- /dev/null +++ b/web-ui/src/app/globals.css @@ -0,0 +1,235 @@ +/* Design tokens */ +:root { + --color-bg: #ffffff; + --color-surface: #f4f4f5; + --color-border: #e4e4e7; + --color-text-primary: #18181b; + --color-text-secondary: #71717a; + --color-accent: #2563eb; + --color-accent-hover: #1d4ed8; + --color-success: #16a34a; + --color-error: #dc2626; + --color-warning: #d97706; + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-mono: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace; + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07); +} + +[data-theme="dark"] { + --color-bg: #09090b; + --color-surface: #18181b; + --color-border: #27272a; + --color-text-primary: #fafafa; + --color-text-secondary: #a1a1aa; + --color-accent: #3b82f6; + --color-accent-hover: #2563eb; + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4); +} + +/* Reset */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-family: var(--font-sans); + color: var(--color-text-primary); + background-color: var(--color-bg); + font-size: 16px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +body { + min-height: 100vh; + background-color: var(--color-bg); + color: var(--color-text-primary); +} + +/* Typography */ +h1, h2, h3, h4 { + font-weight: 600; + line-height: 1.3; +} + +/* Utility classes */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +/* Button base */ +.btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + font-size: 0.875rem; + font-weight: 500; + border-radius: var(--radius-md); + border: 1px solid transparent; + cursor: pointer; + transition: background-color 0.15s, border-color 0.15s, opacity 0.15s; + text-decoration: none; + white-space: nowrap; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-primary { + background-color: var(--color-accent); + color: #fff; +} + +.btn-primary:hover:not(:disabled) { + background-color: var(--color-accent-hover); +} + +.btn-secondary { + background-color: var(--color-surface); + color: var(--color-text-primary); + border-color: var(--color-border); +} + +.btn-secondary:hover:not(:disabled) { + background-color: var(--color-border); +} + +.btn-danger { + background-color: var(--color-error); + color: #fff; +} + +.btn-danger:hover:not(:disabled) { + opacity: 0.9; +} + +/* Card */ +.card { + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: 1.5rem; +} + +/* Form elements */ +.form-group { + display: flex; + flex-direction: column; + gap: 0.375rem; +} + +.form-label { + font-size: 0.875rem; + font-weight: 500; + color: var(--color-text-primary); +} + +.form-hint { + font-size: 0.75rem; + color: var(--color-text-secondary); +} + +.form-input, +.form-select { + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background-color: var(--color-bg); + color: var(--color-text-primary); + transition: border-color 0.15s; + width: 100%; +} + +.form-input:focus, +.form-select:focus { + outline: none; + border-color: var(--color-accent); +} + +/* Checkbox */ +.checkbox-group { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.checkbox-group input[type="checkbox"] { + width: 1rem; + height: 1rem; + accent-color: var(--color-accent); + cursor: pointer; +} + +/* Badge */ +.badge { + display: inline-flex; + align-items: center; + padding: 0.2rem 0.6rem; + border-radius: 9999px; + font-size: 0.75rem; + font-weight: 600; +} + +.badge-success { + background-color: #dcfce7; + color: #166534; +} + +.badge-error { + background-color: #fee2e2; + color: #991b1b; +} + +.badge-warning { + background-color: #fef3c7; + color: #92400e; +} + +[data-theme="dark"] .badge-success { + background-color: #14532d; + color: #86efac; +} + +[data-theme="dark"] .badge-error { + background-color: #7f1d1d; + color: #fca5a5; +} + +[data-theme="dark"] .badge-warning { + background-color: #78350f; + color: #fcd34d; +} + +/* Divider */ +.divider { + border: none; + border-top: 1px solid var(--color-border); + margin: 1rem 0; +} + +/* Mono text */ +.mono { + font-family: var(--font-mono); + font-size: 0.8125rem; +} diff --git a/web-ui/src/app/inspect/page.tsx b/web-ui/src/app/inspect/page.tsx new file mode 100644 index 0000000..d18dc70 --- /dev/null +++ b/web-ui/src/app/inspect/page.tsx @@ -0,0 +1,5 @@ +import { InspectTab } from "@/components/tabs/InspectTab"; + +export default function InspectPage() { + return ; +} diff --git a/web-ui/src/app/layout.tsx b/web-ui/src/app/layout.tsx new file mode 100644 index 0000000..f0fc197 --- /dev/null +++ b/web-ui/src/app/layout.tsx @@ -0,0 +1,33 @@ +import type { Metadata } from "next"; +import "./globals.css"; +import { ThemeProvider } from "@/context/ThemeContext"; +import { AppStateProvider } from "@/context/AppStateContext"; +import { NavBar } from "@/components/layout/NavBar"; + +export const metadata: Metadata = { + title: "SVG2DrawIO — Shape Library Builder", + description: "Convert SVG files into DrawIO shape libraries from your browser.", +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + + +
+ {children} +
+
+
+ + + ); +} diff --git a/web-ui/src/app/manage/page.tsx b/web-ui/src/app/manage/page.tsx new file mode 100644 index 0000000..a4d6837 --- /dev/null +++ b/web-ui/src/app/manage/page.tsx @@ -0,0 +1,5 @@ +import { ManageTab } from "@/components/tabs/ManageTab"; + +export default function ManagePage() { + return ; +} diff --git a/web-ui/src/app/page.tsx b/web-ui/src/app/page.tsx new file mode 100644 index 0000000..a668039 --- /dev/null +++ b/web-ui/src/app/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; + +export default function Home() { + const router = useRouter(); + useEffect(() => { + router.replace("/create"); + }, [router]); + return null; +} diff --git a/web-ui/src/app/split-paths/page.tsx b/web-ui/src/app/split-paths/page.tsx new file mode 100644 index 0000000..d203db7 --- /dev/null +++ b/web-ui/src/app/split-paths/page.tsx @@ -0,0 +1,5 @@ +import { SplitPathsTab } from "@/components/tabs/SplitPathsTab"; + +export default function SplitPathsPage() { + return ; +} diff --git a/web-ui/src/app/validate/page.tsx b/web-ui/src/app/validate/page.tsx new file mode 100644 index 0000000..d85be7e --- /dev/null +++ b/web-ui/src/app/validate/page.tsx @@ -0,0 +1,5 @@ +import { ValidateTab } from "@/components/tabs/ValidateTab"; + +export default function ValidatePage() { + return ; +} diff --git a/web-ui/src/components/forms/ProcessingOptionsForm.tsx b/web-ui/src/components/forms/ProcessingOptionsForm.tsx new file mode 100644 index 0000000..8ea4cb3 --- /dev/null +++ b/web-ui/src/components/forms/ProcessingOptionsForm.tsx @@ -0,0 +1,139 @@ +"use client"; + +import React, { useState } from "react"; +import { ChevronDown, ChevronRight } from "lucide-react"; +import type { ProcessingOptions } from "@/lib/types"; + +interface ProcessingOptionsFormProps { + value: ProcessingOptions; + onChange: (opts: ProcessingOptions) => void; + collapsed?: boolean; +} + +export function ProcessingOptionsForm({ + value, + onChange, + collapsed = true, +}: ProcessingOptionsFormProps) { + const [open, setOpen] = useState(!collapsed); + + function update(key: K, val: ProcessingOptions[K]) { + onChange({ ...value, [key]: val }); + } + + return ( +
+ + + {open && ( +
+
+ + Enables color editing in DrawIO via CSS class injection. +
+ + {value.add_css && ( + <> +
+ + +
+ +
+ + update("css_color", e.target.value)} + style={{ height: "2.5rem", padding: "0.25rem 0.5rem" }} + /> +
+ + {(value.css_mode === "stroke" || value.css_mode === "both") && ( +
+ + update("css_stroke_color", e.target.value)} + style={{ height: "2.5rem", padding: "0.25rem 0.5rem" }} + /> +
+ )} + +
+ + update("css_tag", e.target.value)} + placeholder="path" + /> +
+ +
+ +
+ + )} +
+ )} +
+ ); +} diff --git a/web-ui/src/components/forms/SizingOptionsForm.tsx b/web-ui/src/components/forms/SizingOptionsForm.tsx new file mode 100644 index 0000000..49de40e --- /dev/null +++ b/web-ui/src/components/forms/SizingOptionsForm.tsx @@ -0,0 +1,101 @@ +"use client"; + +import React from "react"; +import type { SizingOptions } from "@/lib/types"; + +interface SizingOptionsFormProps { + value: SizingOptions; + onChange: (opts: SizingOptions) => void; +} + +export function SizingOptionsForm({ value, onChange }: SizingOptionsFormProps) { + function update(key: K, val: SizingOptions[K]) { + onChange({ ...value, [key]: val }); + } + + function parseOptionalFloat(s: string): number | null { + const n = parseFloat(s); + return isNaN(n) ? null : n; + } + + const mode: "default" | "maxsize" | "fixed" = + value.width !== null && value.height !== null + ? "fixed" + : value.max_size !== null + ? "maxsize" + : "default"; + + function setMode(m: "default" | "maxsize" | "fixed") { + if (m === "default") onChange({ width: null, height: null, max_size: null }); + else if (m === "maxsize") onChange({ width: null, height: null, max_size: 40 }); + else onChange({ width: 40, height: 40, max_size: null }); + } + + return ( +
+
+ +
+ {(["default", "maxsize", "fixed"] as const).map((m) => ( + + ))} +
+
+ + {mode === "maxsize" && ( +
+ + update("max_size", parseOptionalFloat(e.target.value))} + /> +
+ )} + + {mode === "fixed" && ( +
+
+ + update("width", parseOptionalFloat(e.target.value))} + /> +
+ × +
+ + update("height", parseOptionalFloat(e.target.value))} + /> +
+
+ )} +
+ ); +} diff --git a/web-ui/src/components/layout/NavBar.tsx b/web-ui/src/components/layout/NavBar.tsx new file mode 100644 index 0000000..f1a0dca --- /dev/null +++ b/web-ui/src/components/layout/NavBar.tsx @@ -0,0 +1,83 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { ThemeToggle } from "./ThemeToggle"; + +const NAV_LINKS = [ + { href: "/create", label: "Create" }, + { href: "/manage", label: "Manage" }, + { href: "/extract", label: "Extract" }, + { href: "/inspect", label: "Inspect" }, + { href: "/validate", label: "Validate" }, + { href: "/split-paths", label: "Split Paths" }, +]; + +export function NavBar() { + const pathname = usePathname(); + + return ( +
+ +
+ ); +} diff --git a/web-ui/src/components/layout/ThemeToggle.tsx b/web-ui/src/components/layout/ThemeToggle.tsx new file mode 100644 index 0000000..0324b33 --- /dev/null +++ b/web-ui/src/components/layout/ThemeToggle.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { Moon, Sun } from "lucide-react"; +import { useTheme } from "@/context/ThemeContext"; + +export function ThemeToggle() { + const { theme, toggleTheme } = useTheme(); + + return ( + + ); +} diff --git a/web-ui/src/components/shared/DownloadButton.tsx b/web-ui/src/components/shared/DownloadButton.tsx new file mode 100644 index 0000000..542f282 --- /dev/null +++ b/web-ui/src/components/shared/DownloadButton.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { Download } from "lucide-react"; +import { downloadBlob } from "@/lib/download"; + +interface DownloadButtonProps { + blob: Blob; + filename: string; + label?: string; +} + +export function DownloadButton({ blob, filename, label }: DownloadButtonProps) { + return ( + + ); +} diff --git a/web-ui/src/components/shared/FileDropZone.tsx b/web-ui/src/components/shared/FileDropZone.tsx new file mode 100644 index 0000000..9ae5a29 --- /dev/null +++ b/web-ui/src/components/shared/FileDropZone.tsx @@ -0,0 +1,138 @@ +"use client"; + +import React, { useRef, useState } from "react"; +import { Upload, X } from "lucide-react"; + +const DEFAULT_MAX_SIZE = 10 * 1024 * 1024; // 10 MB + +interface FileDropZoneProps { + accept?: string; + multiple?: boolean; + onFilesSelected: (files: File[]) => void; + selectedFiles?: File[]; + maxFileSizeBytes?: number; + label?: string; +} + +export function FileDropZone({ + accept, + multiple = false, + onFilesSelected, + selectedFiles = [], + maxFileSizeBytes = DEFAULT_MAX_SIZE, + label, +}: FileDropZoneProps) { + const [isDragging, setIsDragging] = useState(false); + const [sizeError, setSizeError] = useState(null); + const inputRef = useRef(null); + + function validateAndEmit(files: File[]) { + const oversized = files.filter((f) => f.size > maxFileSizeBytes); + if (oversized.length > 0) { + const mb = (maxFileSizeBytes / (1024 * 1024)).toFixed(0); + setSizeError(`${oversized.map((f) => f.name).join(", ")} exceed ${mb} MB limit.`); + return; + } + setSizeError(null); + onFilesSelected(files); + } + + function handleDrop(e: React.DragEvent) { + e.preventDefault(); + setIsDragging(false); + const files = Array.from(e.dataTransfer.files); + validateAndEmit(files); + } + + function handleChange(e: React.ChangeEvent) { + const files = Array.from(e.target.files ?? []); + validateAndEmit(files); + // Reset input so same file can be re-selected + e.target.value = ""; + } + + function removeFile(index: number) { + const updated = selectedFiles.filter((_, i) => i !== index); + onFilesSelected(updated); + } + + return ( +
+
inputRef.current?.click()} + onKeyDown={(e) => e.key === "Enter" && inputRef.current?.click()} + onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }} + onDragLeave={() => setIsDragging(false)} + onDrop={handleDrop} + style={{ + border: `2px dashed ${isDragging ? "var(--color-accent)" : "var(--color-border)"}`, + borderRadius: "var(--radius-lg)", + padding: "2rem", + textAlign: "center", + cursor: "pointer", + backgroundColor: isDragging ? "rgba(37, 99, 235, 0.05)" : "var(--color-surface)", + transition: "border-color 0.15s, background-color 0.15s", + }} + > + +

+ {label ?? (multiple ? "Drag & drop SVG files here, or click to browse" : "Drag & drop a file here, or click to browse")} +

+

+ Max {(maxFileSizeBytes / (1024 * 1024)).toFixed(0)} MB per file +

+ +
+ + {sizeError && ( +

{sizeError}

+ )} + + {selectedFiles.length > 0 && ( +
    + {selectedFiles.map((file, i) => ( +
  • + + {file.name} + + + + {(file.size / 1024).toFixed(1)} KB + + + +
  • + ))} +
+ )} +
+ ); +} diff --git a/web-ui/src/components/shared/IconList.tsx b/web-ui/src/components/shared/IconList.tsx new file mode 100644 index 0000000..89eaf08 --- /dev/null +++ b/web-ui/src/components/shared/IconList.tsx @@ -0,0 +1,120 @@ +"use client"; + +import React, { useMemo, useState } from "react"; +import { Search } from "lucide-react"; + +interface IconListProps { + iconNames: string[]; + selected: string[]; + onSelectionChange: (selected: string[]) => void; + showSelectAll?: boolean; + placeholder?: string; +} + +export function IconList({ + iconNames, + selected, + onSelectionChange, + showSelectAll = true, + placeholder = "Search icons…", +}: IconListProps) { + const [query, setQuery] = useState(""); + + const filtered = useMemo( + () => iconNames.filter((n) => n.toLowerCase().includes(query.toLowerCase())), + [iconNames, query], + ); + + const allFilteredSelected = filtered.length > 0 && filtered.every((n) => selected.includes(n)); + + function toggleSelectAll() { + if (allFilteredSelected) { + // Deselect all filtered + const filteredSet = new Set(filtered); + onSelectionChange(selected.filter((n) => !filteredSet.has(n))); + } else { + // Add all filtered + const merged = Array.from(new Set([...selected, ...filtered])); + onSelectionChange(merged); + } + } + + function toggleOne(name: string) { + if (selected.includes(name)) { + onSelectionChange(selected.filter((n) => n !== name)); + } else { + onSelectionChange([...selected, name]); + } + } + + return ( +
+
+ + setQuery(e.target.value)} + placeholder={placeholder} + style={{ paddingLeft: "2rem" }} + /> +
+ + {showSelectAll && filtered.length > 0 && ( + + )} + +
+ {filtered.length === 0 && ( +

+ No icons match your search. +

+ )} + {filtered.map((name) => ( + + ))} +
+ + {selected.length > 0 && ( +

+ {selected.length} icon{selected.length !== 1 ? "s" : ""} selected +

+ )} +
+ ); +} diff --git a/web-ui/src/components/shared/ProgressIndicator.tsx b/web-ui/src/components/shared/ProgressIndicator.tsx new file mode 100644 index 0000000..5a3d492 --- /dev/null +++ b/web-ui/src/components/shared/ProgressIndicator.tsx @@ -0,0 +1,39 @@ +import React from "react"; + +interface ProgressIndicatorProps { + message?: string; +} + +export function ProgressIndicator({ message = "Processing…" }: ProgressIndicatorProps) { + return ( +
+
+

{message}

+ +
+ ); +} diff --git a/web-ui/src/components/shared/StatusBanner.tsx b/web-ui/src/components/shared/StatusBanner.tsx new file mode 100644 index 0000000..041bfaf --- /dev/null +++ b/web-ui/src/components/shared/StatusBanner.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import { AlertCircle, AlertTriangle, CheckCircle, Info } from "lucide-react"; + +type Variant = "success" | "error" | "warning" | "info"; + +interface StatusBannerProps { + variant: Variant; + title?: string; + message: string; +} + +const STYLES: Record = { + success: { + bg: "#f0fdf4", + border: "#bbf7d0", + color: "#166534", + icon: , + }, + error: { + bg: "#fef2f2", + border: "#fecaca", + color: "#991b1b", + icon: , + }, + warning: { + bg: "#fffbeb", + border: "#fde68a", + color: "#92400e", + icon: , + }, + info: { + bg: "#eff6ff", + border: "#bfdbfe", + color: "#1e40af", + icon: , + }, +}; + +export function StatusBanner({ variant, title, message }: StatusBannerProps) { + const s = STYLES[variant]; + return ( +
+ {s.icon} +
+ {title &&

{title}

} +

{message}

+
+
+ ); +} diff --git a/web-ui/src/components/shared/SvgPreview.tsx b/web-ui/src/components/shared/SvgPreview.tsx new file mode 100644 index 0000000..ae3af2c --- /dev/null +++ b/web-ui/src/components/shared/SvgPreview.tsx @@ -0,0 +1,44 @@ +import React from "react"; + +interface SvgPreviewProps { + svgContent: string; + alt?: string; + size?: number; +} + +/** + * Safely renders an SVG by base64-encoding it as a data URI. + * Uses TextEncoder for proper UTF-8 handling (works on server + browser). + * Never uses dangerouslySetInnerHTML. + */ +function svgToBase64(svgContent: string): string { + // TextEncoder is available in Node.js 18+ and all modern browsers. + const bytes = new TextEncoder().encode(svgContent); + let binary = ""; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]); + } + return btoa(binary); +} + +export function SvgPreview({ svgContent, alt = "SVG preview", size = 80 }: SvgPreviewProps) { + const encoded = svgToBase64(svgContent); + const src = `data:image/svg+xml;base64,${encoded}`; + + return ( + // eslint-disable-next-line @next/next/no-img-element + {alt} + ); +} diff --git a/web-ui/src/components/tabs/CreateTab.tsx b/web-ui/src/components/tabs/CreateTab.tsx new file mode 100644 index 0000000..b601b49 --- /dev/null +++ b/web-ui/src/components/tabs/CreateTab.tsx @@ -0,0 +1,168 @@ +"use client"; + +import React, { useState } from "react"; +import { RotateCcw } from "lucide-react"; +import { FileDropZone } from "@/components/shared/FileDropZone"; +import { StatusBanner } from "@/components/shared/StatusBanner"; +import { DownloadButton } from "@/components/shared/DownloadButton"; +import { ProgressIndicator } from "@/components/shared/ProgressIndicator"; +import { ProcessingOptionsForm } from "@/components/forms/ProcessingOptionsForm"; +import { SizingOptionsForm } from "@/components/forms/SizingOptionsForm"; +import { apiCreate } from "@/lib/api"; +import type { ProcessingOptions, SizingOptions } from "@/lib/types"; + +type Stage = "upload" | "configure" | "processing" | "done"; + +const DEFAULT_OPTIONS: ProcessingOptions = { + add_css: true, + css_mode: "fill", + css_color: "#000000", + css_stroke_color: "#000000", + preserve_current_color: true, + css_tag: "path", +}; + +const DEFAULT_SIZING: SizingOptions = { + width: null, + height: null, + max_size: null, +}; + +export function CreateTab() { + const [stage, setStage] = useState("upload"); + const [svgFiles, setSvgFiles] = useState([]); + const [outputName, setOutputName] = useState("library"); + const [opts, setOpts] = useState(DEFAULT_OPTIONS); + const [sizing, setSizing] = useState(DEFAULT_SIZING); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); + + function handleFilesSelected(files: File[]) { + setSvgFiles(files); + if (files.length > 0) setStage("configure"); + } + + async function handleCreate() { + setStage("processing"); + setError(null); + try { + const blob = await apiCreate(svgFiles, outputName, opts, sizing); + setResult(blob); + setStage("done"); + } catch (err) { + setError(err instanceof Error ? err.message : "An unexpected error occurred."); + setStage("configure"); + } + } + + function handleReset() { + setSvgFiles([]); + setOutputName("library"); + setOpts(DEFAULT_OPTIONS); + setSizing(DEFAULT_SIZING); + setResult(null); + setError(null); + setStage("upload"); + } + + return ( +
+
+

+ Create Library +

+

+ Upload SVG files and package them into a DrawIO shape library. +

+
+ + {stage === "upload" && ( +
+ +
+ )} + + {stage === "configure" && ( +
+
+

Files

+ +
+ +
+

Output

+
+ + setOutputName(e.target.value)} + placeholder="library" + /> + Filename stem for the output .xml file. +
+
+ +
+

Sizing

+ +
+ +
+ +
+ + {error && } + +
+ + +
+
+ )} + + {stage === "processing" && ( +
+ +
+ )} + + {stage === "done" && result && ( +
+ +
+ + +
+
+ )} +
+ ); +} diff --git a/web-ui/src/components/tabs/ExtractTab.tsx b/web-ui/src/components/tabs/ExtractTab.tsx new file mode 100644 index 0000000..6e8f01c --- /dev/null +++ b/web-ui/src/components/tabs/ExtractTab.tsx @@ -0,0 +1,141 @@ +"use client"; + +import React, { useState } from "react"; +import { RotateCcw } from "lucide-react"; +import { FileDropZone } from "@/components/shared/FileDropZone"; +import { StatusBanner } from "@/components/shared/StatusBanner"; +import { DownloadButton } from "@/components/shared/DownloadButton"; +import { ProgressIndicator } from "@/components/shared/ProgressIndicator"; +import { IconList } from "@/components/shared/IconList"; +import { apiExtract, apiList } from "@/lib/api"; + +type Stage = "upload" | "select" | "processing" | "done"; + +export function ExtractTab() { + const [stage, setStage] = useState("upload"); + const [libraryFile, setLibraryFile] = useState(null); + const [iconNames, setIconNames] = useState([]); + const [selected, setSelected] = useState([]); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); + const [loadingList, setLoadingList] = useState(false); + + async function handleFileSelected(files: File[]) { + const file = files[0] ?? null; + setLibraryFile(file); + setIconNames([]); + setSelected([]); + setResult(null); + setError(null); + + if (!file) { setStage("upload"); return; } + + setLoadingList(true); + try { + const data = await apiList(file); + setIconNames(data.icon_names); + setStage("select"); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to list icons."); + } finally { + setLoadingList(false); + } + } + + async function handleExtract() { + if (!libraryFile) return; + setStage("processing"); + setError(null); + try { + const blob = await apiExtract(libraryFile, selected); + setResult(blob); + setStage("done"); + } catch (err) { + setError(err instanceof Error ? err.message : "Extraction failed."); + setStage("select"); + } + } + + function handleReset() { + setLibraryFile(null); + setIconNames([]); + setSelected([]); + setResult(null); + setError(null); + setStage("upload"); + } + + const archiveName = `${libraryFile?.name?.replace(".xml", "") ?? "icons"}-icons.zip`; + + return ( +
+
+

+ Extract Icons +

+

+ Extract icons from a DrawIO library as individual SVG files in a ZIP archive. +

+
+ +
+ + {loadingList && } +
+ + {stage === "select" && ( +
+

+ Select Icons ({iconNames.length} available) +

+

+ Leave all unselected to extract every icon. +

+ + {error && } +
+ + +
+
+ )} + + {stage === "processing" && ( +
+ +
+ )} + + {stage === "done" && result && ( +
+ +
+ + +
+
+ )} +
+ ); +} diff --git a/web-ui/src/components/tabs/InspectTab.tsx b/web-ui/src/components/tabs/InspectTab.tsx new file mode 100644 index 0000000..52f7cac --- /dev/null +++ b/web-ui/src/components/tabs/InspectTab.tsx @@ -0,0 +1,160 @@ +"use client"; + +import React, { useState } from "react"; +import { ChevronDown, ChevronRight } from "lucide-react"; +import { FileDropZone } from "@/components/shared/FileDropZone"; +import { StatusBanner } from "@/components/shared/StatusBanner"; +import { ProgressIndicator } from "@/components/shared/ProgressIndicator"; +import { SvgPreview } from "@/components/shared/SvgPreview"; +import { apiInspect } from "@/lib/api"; +import type { IconInfo, InspectResponse } from "@/lib/types"; + +function IconCard({ icon }: { icon: IconInfo }) { + const [expanded, setExpanded] = useState(false); + + return ( +
+
+ {icon.svg_content && } +
+

+ {icon.name} +

+

+ {icon.width} × {icon.height} px +

+ {icon.css_classes.length > 0 && ( +

+ CSS: {icon.css_classes.join(", ")} +

+ )} +
+
+ + {icon.svg_content && ( +
+ + {expanded && ( +
+              {icon.svg_content}
+            
+ )} +
+ )} +
+ ); +} + +export function InspectTab() { + const [libraryFile, setLibraryFile] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + + async function handleInspect(file: File) { + setLoading(true); + setError(null); + setResult(null); + try { + const data = await apiInspect(file, [], true); + setResult(data); + } catch (err) { + setError(err instanceof Error ? err.message : "Inspect request failed."); + } finally { + setLoading(false); + } + } + + function handleFileSelected(files: File[]) { + const file = files[0] ?? null; + setLibraryFile(file); + setResult(null); + setError(null); + if (file) handleInspect(file); + } + + return ( +
+
+

+ Inspect Library +

+

+ View icon cards with SVG previews, dimensions, and CSS class information. +

+
+ +
+ +
+ + {loading && ( +
+ +
+ )} + + {error && } + + {result && ( +
+

+ {result.count} icon{result.count !== 1 ? "s" : ""} found +

+
+ {result.icons.map((icon) => ( + + ))} +
+
+ )} +
+ ); +} diff --git a/web-ui/src/components/tabs/ManageTab.tsx b/web-ui/src/components/tabs/ManageTab.tsx new file mode 100644 index 0000000..928f524 --- /dev/null +++ b/web-ui/src/components/tabs/ManageTab.tsx @@ -0,0 +1,404 @@ +"use client"; + +import React, { useState } from "react"; +import { RotateCcw } from "lucide-react"; +import { FileDropZone } from "@/components/shared/FileDropZone"; +import { StatusBanner } from "@/components/shared/StatusBanner"; +import { DownloadButton } from "@/components/shared/DownloadButton"; +import { ProgressIndicator } from "@/components/shared/ProgressIndicator"; +import { IconList } from "@/components/shared/IconList"; +import { ProcessingOptionsForm } from "@/components/forms/ProcessingOptionsForm"; +import { SizingOptionsForm } from "@/components/forms/SizingOptionsForm"; +import { apiAdd, apiList, apiRemove, apiRename } from "@/lib/api"; +import { useAppState } from "@/context/AppStateContext"; +import type { ProcessingOptions, SizingOptions } from "@/lib/types"; + +type SubTab = "add" | "remove" | "rename"; + +const DEFAULT_OPTS: ProcessingOptions = { + add_css: true, + css_mode: "fill", + css_color: "#000000", + css_stroke_color: "#000000", + preserve_current_color: true, + css_tag: "path", +}; + +const DEFAULT_SIZING: SizingOptions = { width: null, height: null, max_size: null }; + +// ─── Add Icons Sub-Tab ──────────────────────────────────────────────────────── + +function AddIconsTab({ + libraryFile, + onResult, +}: { + libraryFile: File; + onResult: (blob: Blob) => void; +}) { + const [svgFiles, setSvgFiles] = useState([]); + const [replaceDupes, setReplaceDupes] = useState(false); + const [addDupes, setAddDupes] = useState(false); + const [opts, setOpts] = useState(DEFAULT_OPTS); + const [sizing, setSizing] = useState(DEFAULT_SIZING); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function handleAdd() { + setLoading(true); + setError(null); + try { + const blob = await apiAdd(libraryFile, svgFiles, replaceDupes, addDupes, opts, sizing); + onResult(blob); + } catch (err) { + setError(err instanceof Error ? err.message : "Add failed."); + } finally { + setLoading(false); + } + } + + return ( +
+
+

SVG Files to Add

+ +
+ +
+

Duplicate Handling

+ + +
+ +
+ +
+ +
+ +
+ + {error && } + {loading && } + {!loading && ( + + )} +
+ ); +} + +// ─── Remove Icons Sub-Tab ───────────────────────────────────────────────────── + +function RemoveIconsTab({ + libraryFile, + iconNames, + onResult, +}: { + libraryFile: File; + iconNames: string[]; + onResult: (blob: Blob, removedCount: number) => void; +}) { + const [selected, setSelected] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function handleRemove() { + if (selected.length === 0) return; + setLoading(true); + setError(null); + try { + const { blob, removedCount } = await apiRemove(libraryFile, selected); + onResult(blob, removedCount); + } catch (err) { + setError(err instanceof Error ? err.message : "Remove failed."); + } finally { + setLoading(false); + } + } + + return ( +
+
+

Select Icons to Remove

+ + {error && } + {loading && } + {!loading && ( + + )} +
+
+ ); +} + +// ─── Rename Icon Sub-Tab ────────────────────────────────────────────────────── + +function RenameIconTab({ + libraryFile, + iconNames, + onResult, +}: { + libraryFile: File; + iconNames: string[]; + onResult: (blob: Blob, wasOverwritten: boolean) => void; +}) { + const [oldName, setOldName] = useState(""); + const [newName, setNewName] = useState(""); + const [overwrite, setOverwrite] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function handleRename() { + if (!oldName || !newName) return; + setLoading(true); + setError(null); + try { + const { blob, wasOverwritten } = await apiRename(libraryFile, oldName, newName, overwrite); + onResult(blob, wasOverwritten); + } catch (err) { + setError(err instanceof Error ? err.message : "Rename failed."); + } finally { + setLoading(false); + } + } + + return ( +
+

Rename Icon

+
+ + +
+ +
+ + setNewName(e.target.value)} + placeholder="Enter new name" + /> +
+ + + + {error && } + {loading && } + {!loading && ( + + )} +
+ ); +} + +// ─── ManageTab ──────────────────────────────────────────────────────────────── + +type ResultState = { + blob: Blob; + message: string; + filename: string; +} | null; + +export function ManageTab() { + const [subTab, setSubTab] = useState("add"); + const { sharedLibraryFile, sharedIconNames, setSharedLibraryFile, setSharedIconNames } = + useAppState(); + const [loadingList, setLoadingList] = useState(false); + const [listError, setListError] = useState(null); + const [result, setResult] = useState(null); + + async function handleLibraryFileSelected(files: File[]) { + const file = files[0] ?? null; + setSharedLibraryFile(file); + setSharedIconNames([]); + setResult(null); + setListError(null); + + if (!file) return; + + setLoadingList(true); + try { + const data = await apiList(file); + setSharedIconNames(data.icon_names); + } catch (err) { + setListError(err instanceof Error ? err.message : "Failed to list icons."); + } finally { + setLoadingList(false); + } + } + + function handleReset() { + setSharedLibraryFile(null); + setSharedIconNames([]); + setResult(null); + setListError(null); + } + + function handleAddResult(blob: Blob) { + const name = sharedLibraryFile?.name ?? "library.xml"; + setResult({ blob, message: "Icons added to library.", filename: name }); + } + + function handleRemoveResult(blob: Blob, removedCount: number) { + const name = sharedLibraryFile?.name ?? "library.xml"; + setResult({ blob, message: `${removedCount} icon${removedCount !== 1 ? "s" : ""} removed.`, filename: name }); + } + + function handleRenameResult(blob: Blob, wasOverwritten: boolean) { + const name = sharedLibraryFile?.name ?? "library.xml"; + setResult({ + blob, + message: wasOverwritten ? "Icon renamed (overwritten existing)." : "Icon renamed successfully.", + filename: name, + }); + } + + return ( +
+
+

+ Manage Library +

+

+ Add, remove, or rename icons in an existing DrawIO library. +

+
+ + {/* Library file upload */} +
+ + {loadingList && } + {listError && } + {sharedIconNames.length > 0 && ( +

+ {sharedIconNames.length} icons loaded +

+ )} +
+ + {/* Result download */} + {result && ( +
+ +
+ + + +
+
+ )} + + {sharedLibraryFile && !result && ( + <> + {/* Sub-tab navigation */} +
+ {(["add", "remove", "rename"] as SubTab[]).map((t) => ( + + ))} +
+ + {subTab === "add" && ( + + )} + {subTab === "remove" && ( + + )} + {subTab === "rename" && ( + + )} + + )} +
+ ); +} diff --git a/web-ui/src/components/tabs/SplitPathsTab.tsx b/web-ui/src/components/tabs/SplitPathsTab.tsx new file mode 100644 index 0000000..6f8c82a --- /dev/null +++ b/web-ui/src/components/tabs/SplitPathsTab.tsx @@ -0,0 +1,135 @@ +"use client"; + +import React, { useState } from "react"; +import { RotateCcw } from "lucide-react"; +import { FileDropZone } from "@/components/shared/FileDropZone"; +import { StatusBanner } from "@/components/shared/StatusBanner"; +import { DownloadButton } from "@/components/shared/DownloadButton"; +import { ProgressIndicator } from "@/components/shared/ProgressIndicator"; +import { apiSplitPaths } from "@/lib/api"; +import type { SplitPathsStats } from "@/lib/types"; + +type Stage = "upload" | "processing" | "done"; + +export function SplitPathsTab() { + const [stage, setStage] = useState("upload"); + const [svgFile, setSvgFile] = useState(null); + const [result, setResult] = useState(null); + const [stats, setStats] = useState(null); + const [error, setError] = useState(null); + + async function handleProcess() { + if (!svgFile) return; + setStage("processing"); + setError(null); + try { + const { blob, stats: s } = await apiSplitPaths(svgFile); + setResult(blob); + setStats(s); + setStage("done"); + } catch (err) { + setError(err instanceof Error ? err.message : "Split paths failed."); + setStage("upload"); + } + } + + function handleReset() { + setSvgFile(null); + setResult(null); + setStats(null); + setError(null); + setStage("upload"); + } + + const stem = svgFile?.name?.replace(/\.svg$/i, "") ?? "output"; + + return ( +
+
+

+ Split Paths +

+

+ Split compound SVG paths (multiple M commands) into separate{" "} + <path> elements, preserving donut holes. +

+
+ + {stage !== "processing" && ( +
+ { setSvgFile(files[0] ?? null); setError(null); }} + selectedFiles={svgFile ? [svgFile] : []} + label="Drop an SVG file here, or click to browse" + /> + {error && } + {stage === "upload" && ( + + )} +
+ )} + + {stage === "processing" && ( +
+ +
+ )} + + {stage === "done" && result && stats && ( +
+ + +
+ {[ + { label: "Paths Processed", value: stats.paths_processed }, + { label: "Subpaths Created", value: stats.subpaths_created }, + { label: "Holes Preserved", value: stats.holes_preserved }, + ].map(({ label, value }) => ( +
+

+ {value} +

+

{label}

+
+ ))} +
+ +
+ + +
+
+ )} +
+ ); +} diff --git a/web-ui/src/components/tabs/ValidateTab.tsx b/web-ui/src/components/tabs/ValidateTab.tsx new file mode 100644 index 0000000..4c2c76b --- /dev/null +++ b/web-ui/src/components/tabs/ValidateTab.tsx @@ -0,0 +1,160 @@ +"use client"; + +import React, { useState } from "react"; +import { CheckCircle, XCircle } from "lucide-react"; +import { FileDropZone } from "@/components/shared/FileDropZone"; +import { StatusBanner } from "@/components/shared/StatusBanner"; +import { ProgressIndicator } from "@/components/shared/ProgressIndicator"; +import { apiValidate } from "@/lib/api"; +import type { ValidateResponse } from "@/lib/types"; + +export function ValidateTab() { + const [libraryFile, setLibraryFile] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + + async function handleValidate() { + if (!libraryFile) return; + setLoading(true); + setError(null); + setResult(null); + try { + const data = await apiValidate(libraryFile); + setResult(data); + } catch (err) { + setError(err instanceof Error ? err.message : "Validation request failed."); + } finally { + setLoading(false); + } + } + + return ( +
+
+

+ Validate Library +

+

+ Check a DrawIO library for structural and content integrity issues. +

+
+ +
+ { setLibraryFile(files[0] ?? null); setResult(null); }} + selectedFiles={libraryFile ? [libraryFile] : []} + label="Drop a DrawIO library (.xml) here, or click to browse" + /> + +
+ + {loading && ( +
+ +
+ )} + + {error && } + + {result && ( +
+ {/* Overall badge */} +
+ {result.valid ? ( + + ) : ( + + )} +
+ + {result.valid ? "Valid" : "Invalid"} + +

+ {result.checks.icon_count} icons — {result.checks.icons_validated} valid, {result.checks.icons_failed} failed +

+
+
+ + {/* Checks table */} +
+

Checks

+ + + {[ + { label: "XML Structure", pass: result.checks.xml_structure }, + { label: "JSON Format", pass: result.checks.json_format }, + { + label: `Icon Validation (${result.checks.icons_validated}/${result.checks.icon_count})`, + pass: result.checks.icons_failed === 0, + }, + ].map(({ label, pass }) => ( + + + + + ))} + +
{label} + + {pass ? "Pass" : "Fail"} + +
+
+ + {/* Errors & Warnings */} + {(result.errors.length > 0 || result.warnings.length > 0) && ( +
+ {result.errors.map((e, i) => ( + + ))} + {result.warnings.map((w, i) => ( + + ))} +
+ )} + + {/* Icon Issues */} + {result.icon_issues.length > 0 && ( +
+

+ Icon Issues ({result.icon_issues.length}) +

+ + + + + + + + + + {result.icon_issues.map((issue, i) => ( + + + + + + ))} + +
SeverityIconMessage
+ + {issue.severity} + + {issue.icon}{issue.message}
+
+ )} +
+ )} +
+ ); +} diff --git a/web-ui/src/context/AppStateContext.tsx b/web-ui/src/context/AppStateContext.tsx new file mode 100644 index 0000000..4f196fa --- /dev/null +++ b/web-ui/src/context/AppStateContext.tsx @@ -0,0 +1,49 @@ +"use client"; + +import React, { createContext, useContext, useState } from "react"; + +interface AppState { + sharedLibraryFile: File | null; + sharedIconNames: string[]; + globalError: string | null; +} + +interface AppStateContextValue extends AppState { + setSharedLibraryFile: (file: File | null) => void; + setSharedIconNames: (names: string[]) => void; + setGlobalError: (error: string | null) => void; +} + +const AppStateContext = createContext({ + sharedLibraryFile: null, + sharedIconNames: [], + globalError: null, + setSharedLibraryFile: () => {}, + setSharedIconNames: () => {}, + setGlobalError: () => {}, +}); + +export function AppStateProvider({ children }: { children: React.ReactNode }) { + const [sharedLibraryFile, setSharedLibraryFile] = useState(null); + const [sharedIconNames, setSharedIconNames] = useState([]); + const [globalError, setGlobalError] = useState(null); + + return ( + + {children} + + ); +} + +export function useAppState() { + return useContext(AppStateContext); +} diff --git a/web-ui/src/context/ThemeContext.tsx b/web-ui/src/context/ThemeContext.tsx new file mode 100644 index 0000000..f0c3499 --- /dev/null +++ b/web-ui/src/context/ThemeContext.tsx @@ -0,0 +1,45 @@ +"use client"; + +import React, { createContext, useContext, useEffect, useState } from "react"; + +type Theme = "light" | "dark"; + +interface ThemeContextValue { + theme: Theme; + toggleTheme: () => void; +} + +const ThemeContext = createContext({ + theme: "light", + toggleTheme: () => {}, +}); + +export function ThemeProvider({ children }: { children: React.ReactNode }) { + const [theme, setTheme] = useState("light"); + + useEffect(() => { + const stored = localStorage.getItem("theme") as Theme | null; + const preferred = stored ?? (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"); + setTheme(preferred); + document.documentElement.setAttribute("data-theme", preferred); + }, []); + + function toggleTheme() { + setTheme((prev) => { + const next: Theme = prev === "light" ? "dark" : "light"; + localStorage.setItem("theme", next); + document.documentElement.setAttribute("data-theme", next); + return next; + }); + } + + return ( + + {children} + + ); +} + +export function useTheme() { + return useContext(ThemeContext); +} diff --git a/web-ui/src/hooks/useApi.ts b/web-ui/src/hooks/useApi.ts new file mode 100644 index 0000000..5f73d24 --- /dev/null +++ b/web-ui/src/hooks/useApi.ts @@ -0,0 +1,58 @@ +"use client"; + +import { useCallback, useState } from "react"; +import { ApiError } from "@/lib/api"; + +interface ApiState { + data: T | null; + loading: boolean; + error: string | null; +} + +interface UseApiReturn extends ApiState { + execute: (...args: Args) => Promise; + reset: () => void; +} + +/** + * Generic hook for managing API call state (loading, error, data). + * + * @param fn - The async API function to call. + * @returns State and an `execute` function that invokes the API call. + */ +export function useApi( + fn: (...args: Args) => Promise, +): UseApiReturn { + const [state, setState] = useState>({ + data: null, + loading: false, + error: null, + }); + + const execute = useCallback( + async (...args: Args): Promise => { + setState({ data: null, loading: true, error: null }); + try { + const result = await fn(...args); + setState({ data: result, loading: false, error: null }); + return result; + } catch (err) { + const message = + err instanceof ApiError + ? err.detail + : err instanceof Error + ? err.message + : "An unexpected error occurred"; + setState({ data: null, loading: false, error: message }); + return null; + } + }, + [fn], + ); + + const reset = useCallback(() => { + setState({ data: null, loading: false, error: null }); + }, []); + + return { ...state, execute, reset }; +} diff --git a/web-ui/src/lib/api.ts b/web-ui/src/lib/api.ts new file mode 100644 index 0000000..57a2b41 --- /dev/null +++ b/web-ui/src/lib/api.ts @@ -0,0 +1,195 @@ +import type { + InspectResponse, + ListResponse, + ProcessingOptions, + SizingOptions, + SplitPathsStats, + ValidateResponse, +} from "./types"; + +// In dev mode (next dev), rewrites in next.config.ts proxy /api/* to FastAPI. +// In production (static export served by FastAPI), relative URLs hit the same server. +// Set NEXT_PUBLIC_API_URL to an absolute URL only when the API is on a different origin. +const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ""; + +/** Typed API error containing HTTP status and server detail message. */ +export class ApiError extends Error { + constructor( + public readonly status: number, + public readonly detail: string, + ) { + super(detail); + this.name = "ApiError"; + } +} + +async function checkResponse(res: Response): Promise { + if (!res.ok) { + let detail = `HTTP ${res.status}`; + try { + const body = await res.json(); + detail = body.detail ?? detail; + } catch { + // ignore parse error + } + throw new ApiError(res.status, detail); + } +} + +function appendProcessingOptions(form: FormData, opts: ProcessingOptions): void { + form.append("add_css", String(opts.add_css)); + form.append("css_mode", opts.css_mode); + form.append("css_color", opts.css_color); + form.append("css_stroke_color", opts.css_stroke_color); + form.append("preserve_current_color", String(opts.preserve_current_color)); + form.append("css_tag", opts.css_tag); +} + +function appendSizingOptions(form: FormData, sizing: SizingOptions): void { + if (sizing.width !== null) form.append("width", String(sizing.width)); + if (sizing.height !== null) form.append("height", String(sizing.height)); + if (sizing.max_size !== null) form.append("max_size", String(sizing.max_size)); +} + +/** Create a new DrawIO library from SVG files. Returns the library XML as a Blob. */ +export async function apiCreate( + svgFiles: File[], + outputName: string, + opts: ProcessingOptions, + sizing: SizingOptions, +): Promise { + const form = new FormData(); + svgFiles.forEach((f) => form.append("svg_files", f)); + form.append("output_name", outputName); + appendProcessingOptions(form, opts); + appendSizingOptions(form, sizing); + + const res = await fetch(`${API_BASE}/api/create`, { method: "POST", body: form }); + await checkResponse(res); + return res.blob(); +} + +/** Add SVG icons to an existing library. Returns updated library XML as a Blob. */ +export async function apiAdd( + libraryFile: File, + svgFiles: File[], + replaceDupes: boolean, + addDupes: boolean, + opts: ProcessingOptions, + sizing: SizingOptions, +): Promise { + const form = new FormData(); + form.append("library_file", libraryFile); + svgFiles.forEach((f) => form.append("svg_files", f)); + form.append("replace_duplicates", String(replaceDupes)); + form.append("add_duplicates", String(addDupes)); + appendProcessingOptions(form, opts); + appendSizingOptions(form, sizing); + + const res = await fetch(`${API_BASE}/api/add`, { method: "POST", body: form }); + await checkResponse(res); + return res.blob(); +} + +/** Remove icons from a library. Returns updated library and count removed. */ +export async function apiRemove( + libraryFile: File, + iconNames: string[], +): Promise<{ blob: Blob; removedCount: number }> { + const form = new FormData(); + form.append("library_file", libraryFile); + form.append("icon_names", JSON.stringify(iconNames)); + + const res = await fetch(`${API_BASE}/api/remove`, { method: "POST", body: form }); + await checkResponse(res); + const removedCount = parseInt(res.headers.get("X-Icons-Removed") ?? "0", 10); + const blob = await res.blob(); + return { blob, removedCount }; +} + +/** Rename an icon in a library. Returns updated library and whether an icon was overwritten. */ +export async function apiRename( + libraryFile: File, + oldName: string, + newName: string, + overwrite: boolean, +): Promise<{ blob: Blob; wasOverwritten: boolean }> { + const form = new FormData(); + form.append("library_file", libraryFile); + form.append("old_name", oldName); + form.append("new_name", newName); + form.append("overwrite", String(overwrite)); + + const res = await fetch(`${API_BASE}/api/rename`, { method: "POST", body: form }); + await checkResponse(res); + const wasOverwritten = res.headers.get("X-Icon-Was-Overwritten") === "true"; + const blob = await res.blob(); + return { blob, wasOverwritten }; +} + +/** List icon names in a library. */ +export async function apiList(libraryFile: File): Promise { + const form = new FormData(); + form.append("library_file", libraryFile); + + const res = await fetch(`${API_BASE}/api/list`, { method: "POST", body: form }); + await checkResponse(res); + return res.json() as Promise; +} + +/** Extract icons from a library as a ZIP blob. Pass empty array for all icons. */ +export async function apiExtract(libraryFile: File, iconNames: string[]): Promise { + const form = new FormData(); + form.append("library_file", libraryFile); + form.append("icon_names", JSON.stringify(iconNames)); + + const res = await fetch(`${API_BASE}/api/extract`, { method: "POST", body: form }); + await checkResponse(res); + return res.blob(); +} + +/** Inspect icons in a library. Pass empty array for all icons. */ +export async function apiInspect( + libraryFile: File, + iconNames: string[], + includeSvg: boolean, +): Promise { + const form = new FormData(); + form.append("library_file", libraryFile); + form.append("icon_names", JSON.stringify(iconNames)); + form.append("include_svg", String(includeSvg)); + + const res = await fetch(`${API_BASE}/api/inspect`, { method: "POST", body: form }); + await checkResponse(res); + return res.json() as Promise; +} + +/** Validate a library. Always returns HTTP 200; check `valid` field. */ +export async function apiValidate(libraryFile: File): Promise { + const form = new FormData(); + form.append("library_file", libraryFile); + + const res = await fetch(`${API_BASE}/api/validate`, { method: "POST", body: form }); + await checkResponse(res); + return res.json() as Promise; +} + +/** Split compound SVG paths. Returns the modified SVG blob and stats. */ +export async function apiSplitPaths( + svgFile: File, +): Promise<{ blob: Blob; stats: SplitPathsStats }> { + const form = new FormData(); + form.append("svg_file", svgFile); + + const res = await fetch(`${API_BASE}/api/split-paths`, { method: "POST", body: form }); + await checkResponse(res); + + const stats: SplitPathsStats = { + paths_processed: parseInt(res.headers.get("X-Paths-Processed") ?? "0", 10), + subpaths_created: parseInt(res.headers.get("X-Subpaths-Created") ?? "0", 10), + holes_preserved: parseInt(res.headers.get("X-Holes-Preserved") ?? "0", 10), + }; + + const blob = await res.blob(); + return { blob, stats }; +} diff --git a/web-ui/src/lib/download.ts b/web-ui/src/lib/download.ts new file mode 100644 index 0000000..2530ee2 --- /dev/null +++ b/web-ui/src/lib/download.ts @@ -0,0 +1,16 @@ +/** + * Trigger a browser download from a Blob by creating a temporary object URL, + * auto-clicking a hidden anchor, then immediately revoking the URL. + */ +export function downloadBlob(blob: Blob, filename: string): void { + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = filename; + anchor.style.display = "none"; + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + // Revoke after a short delay so the browser has time to initiate the download + setTimeout(() => URL.revokeObjectURL(url), 1000); +} diff --git a/web-ui/src/lib/types.ts b/web-ui/src/lib/types.ts new file mode 100644 index 0000000..fb94e4c --- /dev/null +++ b/web-ui/src/lib/types.ts @@ -0,0 +1,77 @@ +/** Health check response from /api/health */ +export interface HealthResponse { + status: string; + version: string; +} + +/** Response from /api/list */ +export interface ListResponse { + icon_names: string[]; + count: number; +} + +/** A single icon issue from validation */ +export interface IconIssue { + severity: "error" | "warning"; + icon: string; + message: string; +} + +/** Validation checks summary */ +export interface ValidationChecks { + xml_structure: boolean; + json_format: boolean; + icon_count: number; + icons_validated: number; + icons_failed: number; +} + +/** Full validation response from /api/validate */ +export interface ValidateResponse { + valid: boolean; + errors: string[]; + warnings: string[]; + checks: ValidationChecks; + icon_issues: IconIssue[]; +} + +/** Information about a single icon */ +export interface IconInfo { + name: string; + width: number; + height: number; + shape_type: string | null; + css_classes: string[]; + inline_styles: string | null; + svg_content: string | null; +} + +/** Inspect response from /api/inspect */ +export interface InspectResponse { + icons: IconInfo[]; + count: number; +} + +/** Stats returned by /api/split-paths (via headers) */ +export interface SplitPathsStats { + paths_processed: number; + subpaths_created: number; + holes_preserved: number; +} + +/** Processing options shared across create/add endpoints */ +export interface ProcessingOptions { + add_css: boolean; + css_mode: "fill" | "stroke" | "both"; + css_color: string; + css_stroke_color: string; + preserve_current_color: boolean; + css_tag: string; +} + +/** Sizing options for create/add endpoints */ +export interface SizingOptions { + width: number | null; + height: number | null; + max_size: number | null; +} diff --git a/web-ui/tsconfig.json b/web-ui/tsconfig.json new file mode 100644 index 0000000..877b650 --- /dev/null +++ b/web-ui/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./src/*" + ] + }, + "target": "ES2017" + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/web-ui/tsconfig.tsbuildinfo b/web-ui/tsconfig.tsbuildinfo new file mode 100644 index 0000000..d8f3136 --- /dev/null +++ b/web-ui/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/typescript/lib/lib.es2024.d.ts","./node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2024.collection.d.ts","./node_modules/typescript/lib/lib.es2024.object.d.ts","./node_modules/typescript/lib/lib.es2024.promise.d.ts","./node_modules/typescript/lib/lib.es2024.regexp.d.ts","./node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2024.string.d.ts","./node_modules/typescript/lib/lib.esnext.array.d.ts","./node_modules/typescript/lib/lib.esnext.collection.d.ts","./node_modules/typescript/lib/lib.esnext.intl.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.promise.d.ts","./node_modules/typescript/lib/lib.esnext.decorators.d.ts","./node_modules/typescript/lib/lib.esnext.iterator.d.ts","./node_modules/typescript/lib/lib.esnext.float16.d.ts","./node_modules/typescript/lib/lib.esnext.error.d.ts","./node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/next/dist/styled-jsx/types/css.d.ts","./node_modules/next/dist/styled-jsx/types/macro.d.ts","./node_modules/next/dist/styled-jsx/types/style.d.ts","./node_modules/next/dist/styled-jsx/types/global.d.ts","./node_modules/next/dist/styled-jsx/types/index.d.ts","./node_modules/next/dist/server/get-page-files.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/blob.d.ts","./node_modules/@types/node/web-globals/console.d.ts","./node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/encoding.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/utility.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client-stats.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/round-robin-pool.d.ts","./node_modules/undici-types/h2c-client.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-call-history.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/snapshot-agent.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/cache-interceptor.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/importmeta.d.ts","./node_modules/@types/node/web-globals/messaging.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/performance.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/web-globals/streams.d.ts","./node_modules/@types/node/web-globals/timers.d.ts","./node_modules/@types/node/web-globals/url.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/inspector/promises.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/path/posix.d.ts","./node_modules/@types/node/path/win32.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/quic.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/test/reporters.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/util/types.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/@types/react/canary.d.ts","./node_modules/@types/react/experimental.d.ts","./node_modules/@types/react-dom/index.d.ts","./node_modules/@types/react-dom/canary.d.ts","./node_modules/@types/react-dom/experimental.d.ts","./node_modules/next/dist/lib/fallback.d.ts","./node_modules/next/dist/compiled/webpack/webpack.d.ts","./node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts","./node_modules/next/dist/shared/lib/entry-constants.d.ts","./node_modules/next/dist/shared/lib/constants.d.ts","./node_modules/next/dist/server/config.d.ts","./node_modules/next/dist/lib/load-custom-routes.d.ts","./node_modules/next/dist/shared/lib/image-config.d.ts","./node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts","./node_modules/next/dist/server/body-streams.d.ts","./node_modules/next/dist/server/lib/cache-control.d.ts","./node_modules/next/dist/lib/setup-exception-listeners.d.ts","./node_modules/next/dist/lib/worker.d.ts","./node_modules/next/dist/lib/constants.d.ts","./node_modules/next/dist/lib/bundler.d.ts","./node_modules/next/dist/server/lib/experimental/ppr.d.ts","./node_modules/next/dist/lib/page-types.d.ts","./node_modules/next/dist/build/segment-config/app/app-segment-config.d.ts","./node_modules/next/dist/build/segment-config/pages/pages-segment-config.d.ts","./node_modules/next/dist/build/analysis/get-page-static-info.d.ts","./node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts","./node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts","./node_modules/next/dist/server/require-hook.d.ts","./node_modules/next/dist/server/node-polyfill-crypto.d.ts","./node_modules/next/dist/server/node-environment-baseline.d.ts","./node_modules/next/dist/server/node-environment-extensions/error-inspect.d.ts","./node_modules/next/dist/server/node-environment-extensions/console-file.d.ts","./node_modules/next/dist/server/node-environment-extensions/console-exit.d.ts","./node_modules/next/dist/server/node-environment-extensions/console-dim.external.d.ts","./node_modules/next/dist/server/node-environment-extensions/unhandled-rejection.d.ts","./node_modules/next/dist/server/node-environment-extensions/random.d.ts","./node_modules/next/dist/server/node-environment-extensions/date.d.ts","./node_modules/next/dist/server/node-environment-extensions/web-crypto.d.ts","./node_modules/next/dist/server/node-environment-extensions/node-crypto.d.ts","./node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.d.ts","./node_modules/next/dist/server/node-environment.d.ts","./node_modules/next/dist/build/page-extensions-type.d.ts","./node_modules/next/dist/server/route-kind.d.ts","./node_modules/next/dist/server/route-definitions/route-definition.d.ts","./node_modules/next/dist/server/route-definitions/app-page-route-definition.d.ts","./node_modules/next/dist/server/lib/cache-handlers/types.d.ts","./node_modules/next/dist/server/response-cache/types.d.ts","./node_modules/next/dist/server/resume-data-cache/cache-store.d.ts","./node_modules/next/dist/server/resume-data-cache/resume-data-cache.d.ts","./node_modules/next/dist/client/components/app-router-headers.d.ts","./node_modules/next/dist/server/render-result.d.ts","./node_modules/next/dist/server/instrumentation/types.d.ts","./node_modules/next/dist/lib/coalesced-function.d.ts","./node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts","./node_modules/next/dist/server/lib/router-utils/types.d.ts","./node_modules/next/dist/trace/types.d.ts","./node_modules/next/dist/trace/trace.d.ts","./node_modules/next/dist/trace/shared.d.ts","./node_modules/next/dist/trace/index.d.ts","./node_modules/next/dist/build/load-jsconfig.d.ts","./node_modules/@next/env/dist/index.d.ts","./node_modules/next/dist/build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils.d.ts","./node_modules/next/dist/build/webpack/plugins/telemetry-plugin/telemetry-plugin.d.ts","./node_modules/next/dist/telemetry/storage.d.ts","./node_modules/next/dist/build/build-context.d.ts","./node_modules/next/dist/shared/lib/bloom-filter.d.ts","./node_modules/next/dist/build/webpack-config.d.ts","./node_modules/next/dist/build/swc/generated-native.d.ts","./node_modules/next/dist/build/swc/types.d.ts","./node_modules/next/dist/server/dev/parse-version-info.d.ts","./node_modules/next/dist/next-devtools/shared/types.d.ts","./node_modules/next/dist/server/dev/dev-indicator-server-state.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/cache-indicator.d.ts","./node_modules/next/dist/server/lib/parse-stack.d.ts","./node_modules/next/dist/next-devtools/server/shared.d.ts","./node_modules/next/dist/next-devtools/shared/stack-frame.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/utils/get-error-by-type.d.ts","./node_modules/@types/react/jsx-runtime.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/container/runtime-error/render-error.d.ts","./node_modules/next/dist/next-devtools/dev-overlay/shared.d.ts","./node_modules/next/dist/server/dev/debug-channel.d.ts","./node_modules/next/dist/server/dev/hot-reloader-types.d.ts","./node_modules/next/dist/server/lib/i18n-provider.d.ts","./node_modules/next/dist/server/web/next-url.d.ts","./node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts","./node_modules/next/dist/server/web/spec-extension/cookies.d.ts","./node_modules/next/dist/server/web/spec-extension/request.d.ts","./node_modules/next/dist/server/after/builtin-request-context.d.ts","./node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts","./node_modules/next/dist/server/web/spec-extension/response.d.ts","./node_modules/next/dist/build/segment-config/middleware/middleware-config.d.ts","./node_modules/next/dist/server/web/types.d.ts","./node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts","./node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts","./node_modules/next/dist/server/route-definitions/locale-route-definition.d.ts","./node_modules/next/dist/server/route-definitions/pages-route-definition.d.ts","./node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts","./node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts","./node_modules/next/dist/shared/lib/deep-readonly.d.ts","./node_modules/next/dist/next-devtools/userspace/pages/pages-dev-overlay-setup.d.ts","./node_modules/next/dist/server/render.d.ts","./node_modules/next/dist/shared/lib/mitt.d.ts","./node_modules/next/dist/client/with-router.d.ts","./node_modules/next/dist/client/router.d.ts","./node_modules/next/dist/client/route-loader.d.ts","./node_modules/next/dist/client/page-loader.d.ts","./node_modules/next/dist/shared/lib/router/router.d.ts","./node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts","./node_modules/next/dist/client/components/readonly-url-search-params.d.ts","./node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/app-router-types.d.ts","./node_modules/next/dist/client/flight-data-helpers.d.ts","./node_modules/next/dist/client/components/router-reducer/ppr-navigations.d.ts","./node_modules/next/dist/client/components/segment-cache/types.d.ts","./node_modules/next/dist/client/components/segment-cache/navigation.d.ts","./node_modules/next/dist/client/components/segment-cache/cache-key.d.ts","./node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts","./node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts","./node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts","./node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/server/route-modules/pages/module.compiled.d.ts","./node_modules/next/dist/build/templates/pages.d.ts","./node_modules/next/dist/server/route-modules/pages/module.d.ts","./node_modules/next/dist/server/route-modules/pages/builtin/_error.d.ts","./node_modules/next/dist/server/load-default-error-components.d.ts","./node_modules/next/dist/server/base-http/node.d.ts","./node_modules/next/dist/server/response-cache/index.d.ts","./node_modules/next/dist/server/route-definitions/pages-api-route-definition.d.ts","./node_modules/next/dist/server/route-matches/pages-api-route-match.d.ts","./node_modules/next/dist/server/route-matchers/route-matcher.d.ts","./node_modules/next/dist/server/route-matcher-providers/route-matcher-provider.d.ts","./node_modules/next/dist/server/route-matcher-managers/route-matcher-manager.d.ts","./node_modules/next/dist/server/normalizers/normalizer.d.ts","./node_modules/next/dist/server/normalizers/locale-route-normalizer.d.ts","./node_modules/next/dist/server/normalizers/request/pathname-normalizer.d.ts","./node_modules/next/dist/server/normalizers/request/suffix.d.ts","./node_modules/next/dist/server/normalizers/request/rsc.d.ts","./node_modules/next/dist/server/normalizers/request/next-data.d.ts","./node_modules/next/dist/server/normalizers/request/segment-prefix-rsc.d.ts","./node_modules/next/dist/build/static-paths/types.d.ts","./node_modules/next/dist/server/base-server.d.ts","./node_modules/next/dist/server/lib/async-callback-set.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts","./node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts","./node_modules/sharp/lib/index.d.ts","./node_modules/next/dist/server/image-optimizer.d.ts","./node_modules/next/dist/server/next-server.d.ts","./node_modules/next/dist/server/lib/types.d.ts","./node_modules/next/dist/server/lib/lru-cache.d.ts","./node_modules/next/dist/server/lib/dev-bundler-service.d.ts","./node_modules/next/dist/server/use-cache/cache-life.d.ts","./node_modules/next/dist/server/dev/static-paths-worker.d.ts","./node_modules/next/dist/server/dev/next-dev-server.d.ts","./node_modules/next/dist/server/next.d.ts","./node_modules/next/dist/server/lib/render-server.d.ts","./node_modules/next/dist/server/lib/router-server.d.ts","./node_modules/next/dist/shared/lib/router/utils/path-match.d.ts","./node_modules/next/dist/server/lib/router-utils/filesystem.d.ts","./node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts","./node_modules/next/dist/server/lib/router-utils/router-server-context.d.ts","./node_modules/next/dist/server/route-modules/route-module.d.ts","./node_modules/next/dist/server/load-components.d.ts","./node_modules/next/dist/server/web/adapter.d.ts","./node_modules/next/dist/server/app-render/types.d.ts","./node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts","./node_modules/next/dist/build/webpack/loaders/next-app-loader/index.d.ts","./node_modules/next/dist/server/lib/app-dir-module.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts","./node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts","./node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts","./node_modules/next/dist/server/app-render/cache-signal.d.ts","./node_modules/next/dist/server/app-render/dynamic-rendering.d.ts","./node_modules/next/dist/server/request/fallback-params.d.ts","./node_modules/next/dist/server/app-render/work-unit-async-storage-instance.d.ts","./node_modules/next/dist/server/lib/lazy-result.d.ts","./node_modules/next/dist/server/lib/implicit-tags.d.ts","./node_modules/next/dist/server/app-render/staged-rendering.d.ts","./node_modules/next/dist/server/app-render/work-unit-async-storage.external.d.ts","./node_modules/next/dist/shared/lib/router/utils/parse-relative-url.d.ts","./node_modules/next/dist/server/app-render/app-render.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.d.ts","./node_modules/next/dist/client/components/error-boundary.d.ts","./node_modules/next/dist/client/components/layout-router.d.ts","./node_modules/next/dist/client/components/render-from-template-context.d.ts","./node_modules/next/dist/server/app-render/action-async-storage-instance.d.ts","./node_modules/next/dist/server/app-render/action-async-storage.external.d.ts","./node_modules/next/dist/client/components/client-page.d.ts","./node_modules/next/dist/client/components/client-segment.d.ts","./node_modules/next/dist/server/request/search-params.d.ts","./node_modules/next/dist/client/components/hooks-server-context.d.ts","./node_modules/next/dist/client/components/http-access-fallback/error-boundary.d.ts","./node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts","./node_modules/next/dist/lib/metadata/types/extra-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-types.d.ts","./node_modules/next/dist/lib/metadata/types/manifest-types.d.ts","./node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts","./node_modules/next/dist/lib/metadata/types/twitter-types.d.ts","./node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts","./node_modules/next/dist/lib/metadata/types/resolvers.d.ts","./node_modules/next/dist/lib/metadata/types/icons.d.ts","./node_modules/next/dist/lib/metadata/resolve-metadata.d.ts","./node_modules/next/dist/lib/metadata/metadata.d.ts","./node_modules/next/dist/lib/framework/boundary-components.d.ts","./node_modules/next/dist/server/app-render/rsc/preloads.d.ts","./node_modules/next/dist/server/app-render/rsc/postpone.d.ts","./node_modules/next/dist/server/app-render/rsc/taint.d.ts","./node_modules/next/dist/shared/lib/segment-cache/segment-value-encoding.d.ts","./node_modules/next/dist/server/app-render/collect-segment-data.d.ts","./node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.d.ts","./node_modules/next/dist/server/app-render/entry-base.d.ts","./node_modules/next/dist/build/templates/app-page.d.ts","./node_modules/next/dist/build/rendering-mode.d.ts","./node_modules/@types/react/jsx-dev-runtime.d.ts","./node_modules/@types/react/compiler-runtime.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/rsc/entrypoints.d.ts","./node_modules/@types/react-dom/client.d.ts","./node_modules/@types/react-dom/static.d.ts","./node_modules/@types/react-dom/server.d.ts","./node_modules/next/dist/server/route-modules/app-page/vendored/ssr/entrypoints.d.ts","./node_modules/next/dist/server/route-modules/app-page/module.d.ts","./node_modules/next/dist/server/route-modules/app-page/module.compiled.d.ts","./node_modules/next/dist/server/route-definitions/app-route-route-definition.d.ts","./node_modules/next/dist/server/async-storage/work-store.d.ts","./node_modules/next/dist/server/web/http.d.ts","./node_modules/next/dist/server/route-modules/app-route/shared-modules.d.ts","./node_modules/next/dist/client/components/redirect-status-code.d.ts","./node_modules/next/dist/client/components/redirect-error.d.ts","./node_modules/next/dist/build/templates/app-route.d.ts","./node_modules/next/dist/server/route-modules/app-route/module.d.ts","./node_modules/next/dist/server/route-modules/app-route/module.compiled.d.ts","./node_modules/next/dist/build/segment-config/app/app-segments.d.ts","./node_modules/next/dist/build/utils.d.ts","./node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.d.ts","./node_modules/next/dist/build/turborepo-access-trace/types.d.ts","./node_modules/next/dist/build/turborepo-access-trace/result.d.ts","./node_modules/next/dist/build/turborepo-access-trace/helpers.d.ts","./node_modules/next/dist/build/turborepo-access-trace/index.d.ts","./node_modules/next/dist/export/routes/types.d.ts","./node_modules/next/dist/export/types.d.ts","./node_modules/next/dist/export/worker.d.ts","./node_modules/next/dist/build/worker.d.ts","./node_modules/next/dist/build/index.d.ts","./node_modules/next/dist/server/lib/incremental-cache/index.d.ts","./node_modules/next/dist/server/after/after.d.ts","./node_modules/next/dist/server/after/after-context.d.ts","./node_modules/next/dist/server/app-render/work-async-storage-instance.d.ts","./node_modules/next/dist/server/app-render/create-error-handler.d.ts","./node_modules/next/dist/shared/lib/action-revalidation-kind.d.ts","./node_modules/next/dist/server/app-render/work-async-storage.external.d.ts","./node_modules/next/dist/server/request/params.d.ts","./node_modules/next/dist/server/route-matches/route-match.d.ts","./node_modules/next/dist/server/request-meta.d.ts","./node_modules/next/dist/cli/next-test.d.ts","./node_modules/next/dist/shared/lib/size-limit.d.ts","./node_modules/next/dist/server/config-shared.d.ts","./node_modules/next/dist/server/base-http/index.d.ts","./node_modules/next/dist/server/api-utils/index.d.ts","./node_modules/next/dist/build/adapter/build-complete.d.ts","./node_modules/next/dist/types.d.ts","./node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts","./node_modules/next/dist/shared/lib/utils.d.ts","./node_modules/next/dist/pages/_app.d.ts","./node_modules/next/app.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts","./node_modules/next/dist/server/web/spec-extension/revalidate.d.ts","./node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts","./node_modules/next/dist/server/use-cache/cache-tag.d.ts","./node_modules/next/cache.d.ts","./node_modules/next/dist/pages/_document.d.ts","./node_modules/next/document.d.ts","./node_modules/next/dist/shared/lib/dynamic.d.ts","./node_modules/next/dynamic.d.ts","./node_modules/next/dist/pages/_error.d.ts","./node_modules/next/error.d.ts","./node_modules/next/dist/shared/lib/head.d.ts","./node_modules/next/head.d.ts","./node_modules/next/dist/server/request/cookies.d.ts","./node_modules/next/dist/server/request/headers.d.ts","./node_modules/next/dist/server/request/draft-mode.d.ts","./node_modules/next/headers.d.ts","./node_modules/next/dist/shared/lib/get-img-props.d.ts","./node_modules/next/dist/client/image-component.d.ts","./node_modules/next/dist/shared/lib/image-external.d.ts","./node_modules/next/image.d.ts","./node_modules/next/dist/client/link.d.ts","./node_modules/next/link.d.ts","./node_modules/next/dist/client/components/unrecognized-action-error.d.ts","./node_modules/next/dist/client/components/redirect.d.ts","./node_modules/next/dist/client/components/not-found.d.ts","./node_modules/next/dist/client/components/forbidden.d.ts","./node_modules/next/dist/client/components/unauthorized.d.ts","./node_modules/next/dist/client/components/unstable-rethrow.server.d.ts","./node_modules/next/dist/client/components/unstable-rethrow.d.ts","./node_modules/next/dist/client/components/navigation.react-server.d.ts","./node_modules/next/dist/client/components/navigation.d.ts","./node_modules/next/navigation.d.ts","./node_modules/next/router.d.ts","./node_modules/next/dist/client/script.d.ts","./node_modules/next/script.d.ts","./node_modules/next/dist/server/web/spec-extension/user-agent.d.ts","./node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts","./node_modules/next/dist/server/web/spec-extension/image-response.d.ts","./node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts","./node_modules/next/dist/compiled/@vercel/og/emoji/index.d.ts","./node_modules/next/dist/compiled/@vercel/og/types.d.ts","./node_modules/next/dist/server/after/index.d.ts","./node_modules/next/dist/server/request/connection.d.ts","./node_modules/next/server.d.ts","./node_modules/next/types/global.d.ts","./node_modules/next/types/compiled.d.ts","./node_modules/next/types.d.ts","./node_modules/next/index.d.ts","./node_modules/next/image-types/global.d.ts","./.next/dev/types/routes.d.ts","./next-env.d.ts","./next.config.ts","./src/lib/types.ts","./src/lib/api.ts","./src/hooks/useapi.ts","./src/lib/download.ts","./src/context/themecontext.tsx","./src/context/appstatecontext.tsx","./node_modules/lucide-react/dist/lucide-react.d.ts","./src/components/layout/themetoggle.tsx","./src/components/layout/navbar.tsx","./src/app/layout.tsx","./src/app/page.tsx","./src/components/shared/filedropzone.tsx","./src/components/shared/statusbanner.tsx","./src/components/shared/downloadbutton.tsx","./src/components/shared/progressindicator.tsx","./src/components/forms/processingoptionsform.tsx","./src/components/forms/sizingoptionsform.tsx","./src/components/tabs/createtab.tsx","./src/app/create/page.tsx","./src/components/shared/iconlist.tsx","./src/components/tabs/extracttab.tsx","./src/app/extract/page.tsx","./src/components/shared/svgpreview.tsx","./src/components/tabs/inspecttab.tsx","./src/app/inspect/page.tsx","./src/components/tabs/managetab.tsx","./src/app/manage/page.tsx","./src/components/tabs/splitpathstab.tsx","./src/app/split-paths/page.tsx","./src/components/tabs/validatetab.tsx","./src/app/validate/page.tsx","./.next/dev/types/cache-life.d.ts","./.next/dev/types/validator.ts"],"fileIdsList":[[94,157,165,169,172,174,175,176,188,482,483,484,485],[94,157,165,169,172,174,175,176,188],[94,157,165,169,172,174,175,176,188,291,529,532,544,545,553,556,559,561,563,565],[94,157,165,169,172,174,175,176,188,530,531,532],[94,157,165,169,172,174,175,176,188,291,530],[94,154,155,157,165,169,172,174,175,176,188],[94,156,157,165,169,172,174,175,176,188],[157,165,169,172,174,175,176,188],[94,157,165,169,172,174,175,176,188,196],[94,157,158,163,165,168,169,172,174,175,176,178,188,193,205],[94,157,158,159,165,168,169,172,174,175,176,188],[94,157,160,165,169,172,174,175,176,188,206],[94,157,161,162,165,169,172,174,175,176,179,188],[94,157,162,165,169,172,174,175,176,188,193,202],[94,157,163,165,168,169,172,174,175,176,178,188],[94,156,157,164,165,169,172,174,175,176,188],[94,157,165,166,169,172,174,175,176,188],[94,157,165,167,168,169,172,174,175,176,188],[94,156,157,165,168,169,172,174,175,176,188],[94,157,165,168,169,170,172,174,175,176,188,193,205],[94,157,165,168,169,170,172,174,175,176,188,193,196],[94,144,157,165,168,169,171,172,174,175,176,178,188,193,205],[94,157,165,168,169,171,172,174,175,176,178,188,193,202,205],[94,157,165,169,171,172,173,174,175,176,188,193,202,205],[92,93,94,95,96,97,98,99,100,101,102,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212],[94,157,165,168,169,172,174,175,176,188],[94,157,165,169,172,174,176,188],[94,157,165,169,172,174,175,176,177,188,205],[94,157,165,168,169,172,174,175,176,178,188,193],[94,157,165,169,172,174,175,176,179,188],[94,157,165,169,172,174,175,176,180,188],[94,157,165,168,169,172,174,175,176,183,188],[94,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212],[94,157,165,169,172,174,175,176,185,188],[94,157,165,169,172,174,175,176,186,188],[94,157,162,165,169,172,174,175,176,178,188,196],[94,157,165,168,169,172,174,175,176,188,189],[94,157,165,169,172,174,175,176,188,190,206,209],[94,157,165,168,169,172,174,175,176,188,193,195,196],[94,157,165,169,172,174,175,176,188,194,196],[94,157,165,169,172,174,175,176,188,196,206],[94,157,165,169,172,174,175,176,188,197],[94,154,157,165,169,172,174,175,176,188,193,199,205],[94,157,165,169,172,174,175,176,188,193,198],[94,157,165,168,169,172,174,175,176,188,200,201],[94,157,165,169,172,174,175,176,188,200,201],[94,157,162,165,169,172,174,175,176,178,188,193,202],[94,157,165,169,172,174,175,176,188,203],[94,157,165,169,172,174,175,176,178,188,204],[94,157,165,169,171,172,174,175,176,186,188,205],[94,157,165,169,172,174,175,176,188,206,207],[94,157,162,165,169,172,174,175,176,188,207],[94,157,165,169,172,174,175,176,188,193,208],[94,157,165,169,172,174,175,176,177,188,209],[94,157,165,169,172,174,175,176,188,210],[94,157,160,165,169,172,174,175,176,188],[94,157,162,165,169,172,174,175,176,188],[94,157,165,169,172,174,175,176,188,206],[94,144,157,165,169,172,174,175,176,188],[94,157,165,169,172,174,175,176,188,205],[94,157,165,169,172,174,175,176,188,211],[94,157,165,169,172,174,175,176,183,188],[94,157,165,169,172,174,175,176,188,201],[94,144,157,165,168,169,170,172,174,175,176,183,188,193,196,205,208,209,211],[94,157,165,169,172,174,175,176,188,193,212],[85,89,94,157,165,169,172,174,175,176,188,214,215,216,218,477,523],[85,94,157,165,169,172,174,175,176,188],[85,89,94,157,165,169,172,174,175,176,188,214,215,216,217,434,477,523],[85,89,94,157,165,169,172,174,175,176,188,214,215,217,218,477,523],[85,94,157,165,169,172,174,175,176,188,218,434,435],[85,94,157,165,169,172,174,175,176,188,218,434],[85,89,94,157,165,169,172,174,175,176,188,215,216,217,218,477,523],[85,89,94,157,165,169,172,174,175,176,188,214,216,217,218,477,523],[83,84,94,157,165,169,172,174,175,176,188],[94,157,165,169,172,174,175,176,188,480],[94,157,165,169,172,174,175,176,188,223,225,229,240,430,460,473],[94,157,165,169,172,174,175,176,188,225,235,236,237,239,473],[94,157,165,169,172,174,175,176,188,225,272,274,276,277,280,473,475],[94,157,165,169,172,174,175,176,188,225,229,231,232,233,263,358,430,450,451,459,473,475],[94,157,165,169,172,174,175,176,188,473],[94,157,165,169,172,174,175,176,188,236,328,439,448,468],[94,157,165,169,172,174,175,176,188,225],[94,157,165,169,172,174,175,176,188,219,328,468],[94,157,165,169,172,174,175,176,188,282],[94,157,165,169,172,174,175,176,188,281,473],[94,157,165,169,171,172,174,175,176,188,428,439,528],[94,157,165,169,171,172,174,175,176,188,396,408,448,467],[94,157,165,169,171,172,174,175,176,188,339],[94,157,165,169,172,174,175,176,188,453],[94,157,165,169,172,174,175,176,188,452,453,454],[94,157,165,169,172,174,175,176,188,452],[91,94,157,165,169,171,172,174,175,176,188,219,225,229,232,234,236,240,241,254,255,282,358,369,449,460,473,477],[94,157,165,169,172,174,175,176,188,223,225,238,272,273,278,279,473,528],[94,157,165,169,172,174,175,176,188,238,528],[94,157,165,169,172,174,175,176,188,223,255,383,473,528],[94,157,165,169,172,174,175,176,188,528],[94,157,165,169,172,174,175,176,188,225,238,239,528],[94,157,165,169,172,174,175,176,188,275,528],[94,157,165,169,172,174,175,176,188,241,450,458],[94,157,165,169,172,174,175,176,186,188,291,468],[94,157,165,169,172,174,175,176,188,291,468],[85,94,157,165,169,172,174,175,176,188,291],[85,94,157,165,169,172,174,175,176,188,400],[94,157,165,169,172,174,175,176,188,326,336,337,468,505,512],[94,157,165,169,172,174,175,176,188,325,445,506,507,508,509,511],[94,157,165,169,172,174,175,176,188,444],[94,157,165,169,172,174,175,176,188,444,445],[94,157,165,169,172,174,175,176,188,263,328,329,333],[94,157,165,169,172,174,175,176,188,328],[94,157,165,169,172,174,175,176,188,328,332,334],[94,157,165,169,172,174,175,176,188,328,329,330,331],[94,157,165,169,172,174,175,176,188,510],[85,94,157,165,169,172,174,175,176,188,226,499],[85,94,157,165,169,172,174,175,176,188,205],[85,94,157,165,169,172,174,175,176,188,238,318],[85,94,157,165,169,172,174,175,176,188,238,460],[94,157,165,169,172,174,175,176,188,316,320],[85,94,157,165,169,172,174,175,176,188,317,479],[85,89,94,157,165,169,171,172,174,175,176,188,213,214,215,216,217,218,477,521,522],[94,157,165,169,171,172,174,175,176,188],[94,157,165,169,171,172,174,175,176,188,229,262,314,359,380,382,455,456,460,473,474],[94,157,165,169,172,174,175,176,188,254,457],[94,157,165,169,172,174,175,176,188,477],[94,157,165,169,172,174,175,176,188,224],[85,94,157,165,169,172,174,175,176,188,385,398,407,417,419,467],[94,157,165,169,172,174,175,176,186,188,385,398,416,417,418,467,527],[94,157,165,169,172,174,175,176,188,410,411,412,413,414,415],[94,157,165,169,172,174,175,176,188,412],[94,157,165,169,172,174,175,176,188,416],[94,157,165,169,172,174,175,176,188,289,290,291,293],[85,94,157,165,169,172,174,175,176,188,283,284,285,286,292],[94,157,165,169,172,174,175,176,188,289,292],[94,157,165,169,172,174,175,176,188,287],[94,157,165,169,172,174,175,176,188,288],[85,94,157,165,169,172,174,175,176,188,291,317,479],[85,94,157,165,169,172,174,175,176,188,291,478,479],[85,94,157,165,169,172,174,175,176,188,291,479],[94,157,165,169,172,174,175,176,188,359,462],[94,157,165,169,172,174,175,176,188,462],[94,157,165,169,171,172,174,175,176,188,474,479],[94,157,165,169,172,174,175,176,188,404],[94,156,157,165,169,172,174,175,176,188,403],[94,157,165,169,172,174,175,176,188,264,328,345,382,391,394,396,397,438,467,470,474],[94,157,165,169,172,174,175,176,188,310,328,425],[94,157,165,169,172,174,175,176,188,396,467],[85,94,157,165,169,172,174,175,176,188,396,401,402,404,405,406,407,408,409,420,421,422,423,424,426,427,467,468,528],[94,157,165,169,172,174,175,176,188,390],[94,157,165,169,171,172,174,175,176,186,188,226,262,265,286,311,312,359,369,380,381,438,461,473,474,475,477,528],[94,157,165,169,172,174,175,176,188,467],[94,156,157,165,169,172,174,175,176,188,236,312,369,393,461,463,464,465,466,474],[94,157,165,169,172,174,175,176,188,396],[94,156,157,165,169,172,174,175,176,188,262,299,345,386,387,388,389,390,391,392,394,395,467,468],[94,157,165,169,171,172,174,175,176,188,299,300,386,474,475],[94,157,165,169,172,174,175,176,188,236,359,369,382,461,467,474],[94,157,165,169,171,172,174,175,176,188,473,475],[94,157,165,169,171,172,174,175,176,188,193,470,474,475],[94,157,165,169,171,172,174,175,176,186,188,205,219,229,238,264,265,267,296,301,306,310,311,312,314,343,345,347,350,352,355,356,357,358,380,382,460,461,468,470,473,474,475],[94,157,165,169,171,172,174,175,176,188,193],[94,157,165,169,172,174,175,176,188,225,226,227,234,470,471,472,477,479,528],[94,157,165,169,172,174,175,176,188,223,473],[94,157,165,169,172,174,175,176,188,295],[94,157,165,169,171,172,174,175,176,188,193,205,257,280,282,283,284,285,286,293,294,528],[94,157,165,169,172,174,175,176,186,188,205,219,257,272,305,306,307,343,344,345,350,358,359,365,368,370,380,382,461,468,470,473],[94,157,165,169,172,174,175,176,188,234,241,254,358,369,461,473],[94,157,165,169,171,172,174,175,176,188,205,226,229,345,363,470,473],[94,157,165,169,172,174,175,176,188,384],[94,157,165,169,171,172,174,175,176,188,295,366,367,377],[94,157,165,169,172,174,175,176,188,470,473],[94,157,165,169,172,174,175,176,188,391,393],[94,157,165,169,172,174,175,176,188,312,345,460,479],[94,157,165,169,171,172,174,175,176,186,188,268,272,344,350,365,368,372,470],[94,157,165,169,171,172,174,175,176,188,241,254,272,373],[94,157,165,169,172,174,175,176,188,225,267,375,460,473],[94,157,165,169,171,172,174,175,176,188,205,286,473],[94,157,165,169,171,172,174,175,176,188,238,266,267,268,277,295,374,376,460,473],[91,94,157,165,169,171,172,174,175,176,188,312,379,477,479],[94,157,165,169,172,174,175,176,188,342,380],[94,157,165,169,171,172,174,175,176,186,188,205,229,240,241,254,264,265,301,305,306,307,311,343,344,345,347,359,360,362,364,380,382,460,461,468,469,470,479],[94,157,165,169,171,172,174,175,176,188,193,241,365,371,377,470],[94,157,165,169,172,174,175,176,188,244,245,246,247,248,249,250,251,252,253],[94,157,165,169,172,174,175,176,188,296,351],[94,157,165,169,172,174,175,176,188,353],[94,157,165,169,172,174,175,176,188,351],[94,157,165,169,172,174,175,176,188,353,354],[94,157,165,169,171,172,174,175,176,188,229,232,262,263,474],[94,157,165,169,171,172,174,175,176,186,188,224,226,264,310,311,312,313,341,380,470,475,477,479],[94,157,165,169,171,172,174,175,176,186,188,205,228,263,313,345,391,461,469,474],[94,157,165,169,172,174,175,176,188,386],[94,157,165,169,172,174,175,176,188,387],[94,157,165,169,172,174,175,176,188,328,358,438],[94,157,165,169,172,174,175,176,188,388],[94,157,165,169,172,174,175,176,188,256,260],[94,157,165,169,171,172,174,175,176,188,229,256,264],[94,157,165,169,172,174,175,176,188,259,260],[94,157,165,169,172,174,175,176,188,261],[94,157,165,169,172,174,175,176,188,256,257],[94,157,165,169,172,174,175,176,188,256,308],[94,157,165,169,172,174,175,176,188,256],[94,157,165,169,172,174,175,176,188,296,349,469],[94,157,165,169,172,174,175,176,188,348],[94,157,165,169,172,174,175,176,188,257,468,469],[94,157,165,169,172,174,175,176,188,346,469],[94,157,165,169,172,174,175,176,188,257,468],[94,157,165,169,172,174,175,176,188,438],[94,157,165,169,172,174,175,176,188,229,258,264,312,328,345,379,382,385,391,398,399,429,430,433,437,460,470,474],[94,157,165,169,172,174,175,176,188,321,324,326,327,336,337],[85,94,157,165,169,172,174,175,176,188,216,218,291,431,432],[85,94,157,165,169,172,174,175,176,188,216,218,291,431,432,436],[94,157,165,169,172,174,175,176,188,447],[94,157,165,169,172,174,175,176,188,236,300,312,379,382,396,404,408,440,441,442,443,445,446,449,460,467,473],[94,157,165,169,172,174,175,176,188,336],[94,157,165,169,171,172,174,175,176,188,341],[94,157,165,169,172,174,175,176,188,341],[94,157,165,169,171,172,174,175,176,188,264,309,314,338,340,379,470,477,479],[94,157,165,169,172,174,175,176,188,321,322,323,324,326,327,336,337,478],[91,94,157,165,169,171,172,174,175,176,186,188,205,256,257,265,311,312,345,377,378,380,460,461,470,473,474,477],[94,157,165,169,172,174,175,176,188,300,302,305,461],[94,157,165,169,171,172,174,175,176,188,296,473],[94,157,165,169,172,174,175,176,188,299,396],[94,157,165,169,172,174,175,176,188,298],[94,157,165,169,172,174,175,176,188,300,301],[94,157,165,169,172,174,175,176,188,297,299,473],[94,157,165,169,171,172,174,175,176,188,228,300,302,303,304,473,474],[85,94,157,165,169,172,174,175,176,188,328,335,468],[94,157,165,169,172,174,175,176,188,221,222],[85,94,157,165,169,172,174,175,176,188,226],[85,94,157,165,169,172,174,175,176,188,325,468],[85,91,94,157,165,169,172,174,175,176,188,311,312,477,479],[94,157,165,169,172,174,175,176,188,226,499,500],[85,94,157,165,169,172,174,175,176,188,320],[85,94,157,165,169,172,174,175,176,186,188,205,224,279,315,317,319,479],[94,157,165,169,172,174,175,176,188,238,468,474],[94,157,165,169,172,174,175,176,188,361,468],[85,94,157,165,169,171,172,174,175,176,186,188,223,224,274,320,477,478],[85,94,157,165,169,172,174,175,176,188,214,215,216,217,218,477,523],[85,86,87,88,89,94,157,165,169,172,174,175,176,188],[94,157,165,169,172,174,175,176,188,269,270,271],[94,157,165,169,172,174,175,176,188,269],[85,89,94,157,165,169,171,172,173,174,175,176,186,188,213,214,215,216,217,218,219,224,265,372,416,475,476,479,523],[94,157,165,169,172,174,175,176,188,487],[94,157,165,169,172,174,175,176,188,489],[94,157,165,169,172,174,175,176,188,491],[94,157,165,169,172,174,175,176,188,493],[94,157,165,169,172,174,175,176,188,495,496,497],[94,157,165,169,172,174,175,176,188,501],[90,94,157,165,169,172,174,175,176,188,481,486,488,490,492,494,498,502,504,514,515,517,526,527,528,529],[94,157,165,169,172,174,175,176,188,503],[94,157,165,169,172,174,175,176,188,513],[94,157,165,169,172,174,175,176,188,317],[94,157,165,169,172,174,175,176,188,516],[94,156,157,165,169,172,174,175,176,188,300,302,303,305,518,519,520,523,524,525],[94,157,165,169,172,174,175,176,188,213],[94,157,165,169,172,174,175,176,188,193,213],[94,109,112,115,116,157,165,169,172,174,175,176,188,205],[94,112,157,165,169,172,174,175,176,188,193,205],[94,112,116,157,165,169,172,174,175,176,188,205],[94,157,165,169,172,174,175,176,188,193],[94,106,157,165,169,172,174,175,176,188],[94,110,157,165,169,172,174,175,176,188],[94,108,109,112,157,165,169,172,174,175,176,188,205],[94,157,165,169,172,174,175,176,178,188,202],[94,106,157,165,169,172,174,175,176,188,213],[94,108,112,157,165,169,172,174,175,176,178,188,205],[94,103,104,105,107,111,157,165,168,169,172,174,175,176,188,193,205],[94,112,121,129,157,165,169,172,174,175,176,188],[94,104,110,157,165,169,172,174,175,176,188],[94,112,138,139,157,165,169,172,174,175,176,188],[94,104,107,112,157,165,169,172,174,175,176,188,196,205,213],[94,112,157,165,169,172,174,175,176,188],[94,108,112,157,165,169,172,174,175,176,188,205],[94,103,157,165,169,172,174,175,176,188],[94,106,107,108,110,111,112,113,114,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,139,140,141,142,143,157,165,169,172,174,175,176,188],[94,112,131,134,157,165,169,172,174,175,176,188],[94,112,121,122,123,157,165,169,172,174,175,176,188],[94,110,112,122,124,157,165,169,172,174,175,176,188],[94,111,157,165,169,172,174,175,176,188],[94,104,106,112,157,165,169,172,174,175,176,188],[94,112,116,122,124,157,165,169,172,174,175,176,188],[94,116,157,165,169,172,174,175,176,188],[94,110,112,115,157,165,169,172,174,175,176,188,205],[94,104,108,112,121,157,165,169,172,174,175,176,188],[94,112,131,157,165,169,172,174,175,176,188],[94,124,157,165,169,172,174,175,176,188],[94,106,112,138,157,165,169,172,174,175,176,188,196,211,213],[94,157,165,169,172,174,175,176,188,291,552],[94,157,165,169,172,174,175,176,188,291,555],[94,157,165,169,172,174,175,176,188,291,558],[94,157,165,169,172,174,175,176,188,291,530,539,540,543],[94,157,165,169,172,174,175,176,188,291,560],[94,157,165,169,172,174,175,176,188,291,514],[94,157,165,169,172,174,175,176,188,291,562],[94,157,165,169,172,174,175,176,188,291,564],[85,94,157,165,169,172,174,175,176,188,291,535,541],[85,94,157,165,169,172,174,175,176,188,291,535],[94,157,165,169,172,174,175,176,188,291,504,514,542],[94,157,165,169,172,174,175,176,188,291,539,541],[94,157,165,169,172,174,175,176,188,291,538,541],[85,94,157,165,169,172,174,175,176,188,291,541],[85,94,157,165,169,172,174,175,176,188,291,535,536,541,546,547,548,549,550,551],[85,94,157,165,169,172,174,175,176,188,291,536,541,546,547,548,549,554],[85,94,157,165,169,172,174,175,176,188,291,535,536,541,546,547,549,557],[85,94,157,165,169,172,174,175,176,188,291,535,536,540,541,546,547,548,549,550,551,554],[85,94,157,165,169,172,174,175,176,188,291,535,536,541,546,547,548,549],[85,94,157,165,169,172,174,175,176,188,291,535,536,541,546,547,549],[85,94,157,165,169,172,174,175,176,188,291,536],[94,157,165,169,172,174,175,176,188,291,535],[94,157,165,169,172,174,175,176,188,291]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"8fd575e12870e9944c7e1d62e1f5a73fcf23dd8d3a321f2a2c74c20d022283fe","impliedFormat":1},{"version":"2ab096661c711e4a81cc464fa1e6feb929a54f5340b46b0a07ac6bbf857471f0","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"87dc0f382502f5bbce5129bdc0aea21e19a3abbc19259e0b43ae038a9fc4e326","affectsGlobalScope":true,"impliedFormat":1},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true,"impliedFormat":1},{"version":"56e4ed5aab5f5920980066a9409bfaf53e6d21d3f8d020c17e4de584d29600ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ece9f17b3866cc077099c73f4983bddbcb1dc7ddb943227f1ec070f529dedd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a6282c8827e4b9a95f4bf4f5c205673ada31b982f50572d27103df8ceb8013c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1c9319a09485199c1f7b0498f2988d6d2249793ef67edda49d1e584746be9032","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3a2a0cee0f03ffdde24d89660eba2685bfbdeae955a6c67e8c4c9fd28928eeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"60037901da1a425516449b9a20073aa03386cce92f7a1fd902d7602be3a7c2e9","affectsGlobalScope":true,"impliedFormat":1},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true,"impliedFormat":1},{"version":"22adec94ef7047a6c9d1af3cb96be87a335908bf9ef386ae9fd50eeb37f44c47","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"73f78680d4c08509933daf80947902f6ff41b6230f94dd002ae372620adb0f60","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5239f5c01bcfa9cd32f37c496cf19c61d69d37e48be9de612b541aac915805b","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"dc0a7f107690ee5cd8afc8dbf05c4df78085471ce16bdd9881642ec738bc81fe","impliedFormat":1},{"version":"acd8fd5090ac73902278889c38336ff3f48af6ba03aa665eb34a75e7ba1dccc4","impliedFormat":1},{"version":"d6258883868fb2680d2ca96bc8b1352cab69874581493e6d52680c5ffecdb6cc","impliedFormat":1},{"version":"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153","impliedFormat":1},{"version":"f258e3960f324a956fc76a3d3d9e964fff2244ff5859dcc6ce5951e5413ca826","impliedFormat":1},{"version":"643f7232d07bf75e15bd8f658f664d6183a0efaca5eb84b48201c7671a266979","impliedFormat":1},{"version":"21da358700a3893281ce0c517a7a30cbd46be020d9f0c3f2834d0a8ad1f5fc75","impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"0ccdaa19852d25ecd84eec365c3bfa16e7859cadecf6e9ca6d0dbbbee439743f","affectsGlobalScope":true,"impliedFormat":1},{"version":"438b41419b1df9f1fbe33b5e1b18f5853432be205991d1b19f5b7f351675541e","affectsGlobalScope":true,"impliedFormat":1},{"version":"096116f8fedc1765d5bd6ef360c257b4a9048e5415054b3bf3c41b07f8951b0b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5e01375c9e124a83b52ee4b3244ed1a4d214a6cfb54ac73e164a823a4a7860a","affectsGlobalScope":true,"impliedFormat":1},{"version":"f90ae2bbce1505e67f2f6502392e318f5714bae82d2d969185c4a6cecc8af2fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b58e207b93a8f1c88bbf2a95ddc686ac83962b13830fe8ad3f404ffc7051fb4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1fefabcb2b06736a66d2904074d56268753654805e829989a46a0161cd8412c5","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"c18a99f01eb788d849ad032b31cafd49de0b19e083fe775370834c5675d7df8e","affectsGlobalScope":true,"impliedFormat":1},{"version":"5247874c2a23b9a62d178ae84f2db6a1d54e6c9a2e7e057e178cc5eea13757fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"156a859e21ef3244d13afeeba4e49760a6afa035c149dda52f0c45ea8903b338","impliedFormat":1},{"version":"10ec5e82144dfac6f04fa5d1d6c11763b3e4dbbac6d99101427219ab3e2ae887","impliedFormat":1},{"version":"615754924717c0b1e293e083b83503c0a872717ad5aa60ed7f1a699eb1b4ea5c","impliedFormat":1},{"version":"074de5b2fdead0165a2757e3aaef20f27a6347b1c36adea27d51456795b37682","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"24371e69a38fc33e268d4a8716dbcda430d6c2c414a99ff9669239c4b8f40dea","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"3e11fce78ad8c0e1d1db4ba5f0652285509be3acdd519529bc8fcef85f7dafd9","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"9c32412007b5662fd34a8eb04292fb5314ec370d7016d1c2fb8aa193c807fe22","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"4d327f7d72ad0918275cea3eee49a6a8dc8114ae1d5b7f3f5d0774de75f7439a","impliedFormat":1},{"version":"6ebe8ebb8659aaa9d1acbf3710d7dae3e923e97610238b9511c25dc39023a166","impliedFormat":1},{"version":"e85d7f8068f6a26710bff0cc8c0fc5e47f71089c3780fbede05857331d2ddec9","impliedFormat":1},{"version":"7befaf0e76b5671be1d47b77fcc65f2b0aad91cc26529df1904f4a7c46d216e9","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"8aee8b6d4f9f62cf3776cda1305fb18763e2aade7e13cea5bbe699112df85214","impliedFormat":1},{"version":"c63b9ada8c72f95aac5db92aea07e5e87ec810353cdf63b2d78f49a58662cf6c","impliedFormat":1},{"version":"1cc2a09e1a61a5222d4174ab358a9f9de5e906afe79dbf7363d871a7edda3955","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"b64d4d1c5f877f9c666e98e833f0205edb9384acc46e98a1fef344f64d6aba44","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"12950411eeab8563b349cb7959543d92d8d02c289ed893d78499a19becb5a8cc","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"c9381908473a1c92cb8c516b184e75f4d226dad95c3a85a5af35f670064d9a2f","impliedFormat":1},{"version":"c3f5289820990ab66b70c7fb5b63cb674001009ff84b13de40619619a9c8175f","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3275d55fac10b799c9546804126239baf020d220136163f763b55a74e50e750","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa68a0a3b7cb32c00e39ee3cd31f8f15b80cac97dce51b6ee7fc14a1e8deb30b","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"6c36e755bced82df7fb6ce8169265d0a7bb046ab4e2cb6d0da0cb72b22033e89","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"7a93de4ff8a63bafe62ba86b89af1df0ccb5e40bb85b0c67d6bbcfdcf96bf3d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"90e85f9bc549dfe2b5749b45fe734144e96cd5d04b38eae244028794e142a77e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e0a5deeb610b2a50a6350bd23df6490036a1773a8a71d70f2f9549ab009e67ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"435b3711465425770ed2ee2f1cf00ce071835265e0851a7dc4600ab4b007550e","impliedFormat":1},{"version":"7e49f52a159435fc8df4de9dc377ef5860732ca2dc9efec1640531d3cf5da7a3","impliedFormat":1},{"version":"dd4bde4bdc2e5394aed6855e98cf135dfdf5dd6468cad842e03116d31bbcc9bc","impliedFormat":1},{"version":"4d4e879009a84a47c05350b8dca823036ba3a29a3038efed1be76c9f81e45edf","affectsGlobalScope":true,"impliedFormat":1},{"version":"237ba5ac2a95702a114a309e39c53a5bddff5f6333b325db9764df9b34f3502b","impliedFormat":1},{"version":"9ba13b47cb450a438e3076c4a3f6afb9dc85e17eae50f26d4b2d72c0688c9251","impliedFormat":1},{"version":"b64cd4401633ea4ecadfd700ddc8323a13b63b106ac7127c1d2726f32424622c","impliedFormat":1},{"version":"37c6e5fe5715814412b43cc9b50b24c67a63c4e04e753e0d1305970d65417a60","impliedFormat":1},{"version":"1d024184fb57c58c5c91823f9d10b4915a4867b7934e89115fd0d861a9df27c8","impliedFormat":1},{"version":"ee0e4946247f842c6dd483cbb60a5e6b484fee07996e3a7bc7343dfb68a04c5d","impliedFormat":1},{"version":"ef051f42b7e0ef5ca04552f54c4552eac84099d64b6c5ad0ef4033574b6035b8","impliedFormat":1},{"version":"853a43154f1d01b0173d9cbd74063507ece57170bad7a3b68f3fa1229ad0a92f","impliedFormat":1},{"version":"56231e3c39a031bfb0afb797690b20ed4537670c93c0318b72d5180833d98b72","impliedFormat":1},{"version":"5cc7c39031bfd8b00ad58f32143d59eb6ffc24f5d41a20931269011dccd36c5e","impliedFormat":1},{"version":"b0b69c61b0f0ec8ca15db4c8c41f6e77f4cacb784d42bca948f42dea33e8757e","affectsGlobalScope":true,"impliedFormat":1},{"version":"f96a48183254c00d24575401f1a761b4ce4927d927407e7862a83e06ce5d6964","impliedFormat":1},{"version":"cc25940cfb27aa538e60d465f98bb5068d4d7d33131861ace43f04fe6947d68f","impliedFormat":1},{"version":"f83fb2b1338afbb3f9d733c7d6e8b135826c41b0518867df0c0ace18ae1aa270","impliedFormat":1},{"version":"01ff95aa1443e3f7248974e5a771f513cb2ac158c8898f470a1792f817bee497","impliedFormat":1},{"version":"757227c8b345c57d76f7f0e3bbad7a91ffca23f1b2547cbed9e10025816c9cb7","impliedFormat":1},{"version":"42a05d8f239f74587d4926aba8cc54792eed8e8a442c7adc9b38b516642aadfe","impliedFormat":1},{"version":"5d21b58d60383cc6ab9ad3d3e265d7d25af24a2c9b506247e0e50b0a884920be","impliedFormat":1},{"version":"101f482fd48cb4c7c0468dcc6d62c843d842977aea6235644b1edd05e81fbf22","impliedFormat":1},{"version":"ae6757460f37078884b1571a3de3ebaf724d827d7e1d53626c02b3c2a408ac63","affectsGlobalScope":true,"impliedFormat":1},{"version":"9451a46a89ed209e2e08329e6cac59f89356eae79a7230f916d8cc38725407c7","impliedFormat":1},{"version":"3ef397f12387eff17f550bc484ea7c27d21d43816bbe609d495107f44b97e933","impliedFormat":1},{"version":"1023282e2ba810bc07905d3668349fbd37a26411f0c8f94a70ef3c05fe523fcf","impliedFormat":1},{"version":"b214ebcf76c51b115453f69729ee8aa7b7f8eccdae2a922b568a45c2d7ff52f7","impliedFormat":1},{"version":"429c9cdfa7d126255779efd7e6d9057ced2d69c81859bbab32073bad52e9ba76","impliedFormat":1},{"version":"e236b5eba291f51bdf32c231673e6cab81b5410850e61f51a7a524dddadc0f95","impliedFormat":1},{"version":"f7ba0e839daa0702e3ff1a1a871c0d8ea2d586ce684dd8a72c786c36a680b1d9","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f2c62938251b45715fd2a9887060ec4fbc8724727029d1cbce373747252bdd7","impliedFormat":1},{"version":"e3ace08b6bbd84655d41e244677b474fd995923ffef7149ddb68af8848b60b05","impliedFormat":1},{"version":"132580b0e86c48fab152bab850fc57a4b74fe915c8958d2ccb052b809a44b61c","impliedFormat":1},{"version":"af4ab0aa8908fc9a655bb833d3bc28e117c4f0e1038c5a891546158beb25accb","impliedFormat":1},{"version":"69c9a5a9392e8564bd81116e1ed93b13205201fb44cb35a7fde8c9f9e21c4b23","impliedFormat":1},{"version":"5f8fc37f8434691ffac1bfd8fc2634647da2c0e84253ab5d2dd19a7718915b35","impliedFormat":1},{"version":"5981c2340fd8b076cae8efbae818d42c11ffc615994cb060b1cd390795f1be2b","impliedFormat":1},{"version":"f64deb26664af64dc274637343bde8d82f930c77af05a412c7d310b77207a448","impliedFormat":1},{"version":"ed4f674fc8c0c993cc7e145069ac44129e03519b910c62be206a0cc777bdc60b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0250da3eb85c99624f974e77ef355cdf86f43980251bc371475c2b397ba55bcd","impliedFormat":1},{"version":"f1c93e046fb3d9b7f8249629f4b63dc068dd839b824dd0aa39a5e68476dc9420","impliedFormat":1},{"version":"3d3a5f27ffbc06c885dd4d5f9ee20de61faf877fe2c3a7051c4825903d9a7fdc","impliedFormat":1},{"version":"12806f9f085598ef930edaf2467a5fa1789a878fba077cd27e85dc5851e11834","impliedFormat":1},{"version":"bce309f4d9b67c18d4eeff5bba6cf3e67b2b0aead9f03f75d6060c553974d7ba","impliedFormat":1},{"version":"a43fe41c33d0a192a0ecaf9b92e87bef3709c9972e6d53c42c49251ccb962d69","impliedFormat":1},{"version":"a177959203c017fad3ecc4f3d96c8757a840957a4959a3ae00dab9d35961ca6c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6fc727ccf9b36e257ff982ea0badeffbfc2c151802f741bddff00c6af3b784cf","impliedFormat":1},{"version":"2a00d005e3af99cd1cfa75220e60c61b04bfb6be7ca7453bfe2ef6cca37cc03c","impliedFormat":1},{"version":"4844a4c9b4b1e812b257676ed8a80b3f3be0e29bf05e742cc2ea9c3c6865e6c6","impliedFormat":1},{"version":"064878a60367e0407c42fb7ba02a2ea4d83257357dc20088e549bd4d89433e9c","impliedFormat":1},{"version":"14d4bd22d1b05824971b98f7e91b2484c90f1a684805c330476641417c3d9735","impliedFormat":1},{"version":"c3877fef8a43cd434f9728f25a97575b0eb73d92f38b5c87c840daccc3e21d97","impliedFormat":1},{"version":"b484ec11ba00e3a2235562a41898d55372ccabe607986c6fa4f4aba72093749f","impliedFormat":1},{"version":"1dbd83860e7634f9c236647f45dbc5d3c4f9eba8827d87209d6e9826fdf4dbd5","impliedFormat":1},{"version":"41ef7992c555671a8fe54db302788adefa191ded810a50329b79d20a6772d14c","impliedFormat":1},{"version":"041a7781b9127ab568d2cdcce62c58fdea7c7407f40b8c50045d7866a2727130","impliedFormat":1},{"version":"b37f83e7deea729aa9ce5593f78905afb45b7532fdff63041d374f60059e7852","impliedFormat":1},{"version":"e1cb68f3ef3a8dd7b2a9dfb3de482ed6c0f1586ba0db4e7d73c1d2147b6ffc51","impliedFormat":1},{"version":"55cdbeebe76a1fa18bbd7e7bf73350a2173926bd3085bb050cf5a5397025ee4e","impliedFormat":1},{"version":"2beff543f6e9a9701df88daeee3cdd70a34b4a1c11cb4c734472195a5cb2af54","impliedFormat":1},{"version":"2e07abf27aa06353d46f4448c0bbac73431f6065eef7113128a5cd804d0c384d","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1},{"version":"42bc0e1a903408137c3df2b06dfd7e402cdab5bbfa5fcfb871b22ebfdb30bd0b","impliedFormat":1},{"version":"9894dafe342b976d251aac58e616ac6df8db91fb9d98934ff9dd103e9e82578f","impliedFormat":1},{"version":"413df52d4ea14472c2fa5bee62f7a40abd1eb49be0b9722ee01ee4e52e63beb2","impliedFormat":1},{"version":"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96","impliedFormat":1},{"version":"446a50749b24d14deac6f8843e057a6355dd6437d1fac4f9e5ce4a5071f34bff","impliedFormat":1},{"version":"182e9fcbe08ac7c012e0a6e2b5798b4352470be29a64fdc114d23c2bab7d5106","impliedFormat":1},{"version":"5c9b31919ea1cb350a7ae5e71c9ced8f11723e4fa258a8cc8d16ae46edd623c7","impliedFormat":1},{"version":"4aa42ce8383b45823b3a1d3811c0fdd5f939f90254bc4874124393febbaf89f6","impliedFormat":1},{"version":"96ffa70b486207241c0fcedb5d9553684f7fa6746bc2b04c519e7ebf41a51205","impliedFormat":1},{"version":"3677988e03b749874eb9c1aa8dc88cd77b6005e5c4c39d821cda7b80d5388619","impliedFormat":1},{"version":"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f","impliedFormat":1},{"version":"ad0d1d75d129b1c80f911be438d6b61bfa8703930a8ff2be2f0e1f8a91841c64","impliedFormat":1},{"version":"ce75b1aebb33d510ff28af960a9221410a3eaf7f18fc5f21f9404075fba77256","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"02436d7e9ead85e09a2f8e27d5f47d9464bced31738dec138ca735390815c9f0","impliedFormat":1},{"version":"f4625edcb57b37b84506e8b276eb59ca30d31f88c6656d29d4e90e3bc58e69df","impliedFormat":1},{"version":"78a2869ad0cbf3f9045dda08c0d4562b7e1b2bfe07b19e0db072f5c3c56e9584","impliedFormat":1},{"version":"f8d5ff8eafd37499f2b6a98659dd9b45a321de186b8db6b6142faed0fea3de77","impliedFormat":1},{"version":"c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa","impliedFormat":1},{"version":"c685d9f68c70fe11ce527287526585a06ea13920bb6c18482ca84945a4e433a7","impliedFormat":1},{"version":"540cc83ab772a2c6bc509fe1354f314825b5dba3669efdfbe4693ecd3048e34f","impliedFormat":1},{"version":"121b0696021ab885c570bbeb331be8ad82c6efe2f3b93a6e63874901bebc13e3","impliedFormat":1},{"version":"4e01846df98d478a2a626ec3641524964b38acaac13945c2db198bf9f3df22ee","impliedFormat":1},{"version":"678d6d4c43e5728bf66e92fc2269da9fa709cb60510fed988a27161473c3853f","impliedFormat":1},{"version":"ffa495b17a5ef1d0399586b590bd281056cee6ce3583e34f39926f8dcc6ecdb5","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"aa14cee20aa0db79f8df101fc027d929aec10feb5b8a8da3b9af3895d05b7ba2","impliedFormat":1},{"version":"493c700ac3bd317177b2eb913805c87fe60d4e8af4fb39c41f04ba81fae7e170","impliedFormat":1},{"version":"aeb554d876c6b8c818da2e118d8b11e1e559adbe6bf606cc9a611c1b6c09f670","impliedFormat":1},{"version":"acf5a2ac47b59ca07afa9abbd2b31d001bf7448b041927befae2ea5b1951d9f9","impliedFormat":1},{"version":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881","impliedFormat":1},{"version":"d71291eff1e19d8762a908ba947e891af44749f3a2cbc5bd2ec4b72f72ea795f","impliedFormat":1},{"version":"c0480e03db4b816dff2682b347c95f2177699525c54e7e6f6aa8ded890b76be7","impliedFormat":1},{"version":"e2a37ac938c4bede5bb284b9d2d042da299528f1e61f6f57538f1bd37d760869","impliedFormat":1},{"version":"76def37aff8e3a051cf406e10340ffba0f28b6991c5d987474cc11137796e1eb","impliedFormat":1},{"version":"b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8","impliedFormat":1},{"version":"3e7efde639c6a6c3edb9847b3f61e308bf7a69685b92f665048c45132f51c218","impliedFormat":1},{"version":"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5","impliedFormat":1},{"version":"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c","impliedFormat":1},{"version":"ee8df1cb8d0faaca4013a1b442e99130769ce06f438d18d510fed95890067563","impliedFormat":1},{"version":"bfb7f8475428637bee12bdd31bd9968c1c8a1cc2c3e426c959e2f3a307f8936f","impliedFormat":1},{"version":"6f491d0108927478d3247bbbc489c78c2da7ef552fd5277f1ab6819986fdf0b1","impliedFormat":1},{"version":"594fe24fc54645ab6ccb9dba15d3a35963a73a395b2ef0375ea34bf181ccfd63","impliedFormat":1},{"version":"7cb0ee103671d1e201cd53dda12bc1cd0a35f1c63d6102720c6eeb322cb8e17e","impliedFormat":1},{"version":"15a234e5031b19c48a69ccc1607522d6e4b50f57d308ecb7fe863d44cd9f9eb3","impliedFormat":1},{"version":"148679c6d0f449210a96e7d2e562d589e56fcde87f843a92808b3ff103f1a774","impliedFormat":1},{"version":"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f","impliedFormat":1},{"version":"2f9c89cbb29d362290531b48880a4024f258c6033aaeb7e59fbc62db26819650","impliedFormat":1},{"version":"bb37588926aba35c9283fe8d46ebf4e79ffe976343105f5c6d45f282793352b2","impliedFormat":1},{"version":"05c97cddbaf99978f83d96de2d8af86aded9332592f08ce4a284d72d0952c391","impliedFormat":1},{"version":"72179f9dd22a86deaad4cc3490eb0fe69ee084d503b686985965654013f1391b","impliedFormat":1},{"version":"2e6114a7dd6feeef85b2c80120fdbfb59a5529c0dcc5bfa8447b6996c97a69f5","impliedFormat":1},{"version":"7b6ff760c8a240b40dab6e4419b989f06a5b782f4710d2967e67c695ef3e93c4","impliedFormat":1},{"version":"c8f004e6036aa1c764ad4ec543cf89a5c1893a9535c80ef3f2b653e370de45e6","impliedFormat":1},{"version":"dd80b1e600d00f5c6a6ba23f455b84a7db121219e68f89f10552c54ba46e4dc9","impliedFormat":1},{"version":"b064c36f35de7387d71c599bfcf28875849a1dbc733e82bd26cae3d1cd060521","impliedFormat":1},{"version":"05c7280d72f3ed26f346cbe7cbbbb002fb7f15739197cbbee6ab3fd1a6cb9347","impliedFormat":1},{"version":"8de9fe97fa9e00ec00666fa77ab6e91b35d25af8ca75dabcb01e14ad3299b150","impliedFormat":1},{"version":"803cd2aaf1921c218916c2c7ee3fce653e852d767177eb51047ff15b5b253893","impliedFormat":1},{"version":"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369","impliedFormat":1},{"version":"7ab12b2f1249187223d11a589f5789c75177a0b597b9eb7f8e2e42d045393347","impliedFormat":1},{"version":"ad37fb4be61c1035b68f532b7220f4e8236cf245381ce3b90ac15449ecfe7305","impliedFormat":1},{"version":"93436bd74c66baba229bfefe1314d122c01f0d4c1d9e35081a0c4f0470ac1a6c","impliedFormat":1},{"version":"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072","impliedFormat":1},{"version":"50256e9c31318487f3752b7ac12ff365c8949953e04568009c8705db802776fb","impliedFormat":1},{"version":"7d73b24e7bf31dfb8a931ca6c4245f6bb0814dfae17e4b60c9e194a631fe5f7b","impliedFormat":1},{"version":"d130c5f73768de51402351d5dc7d1b36eaec980ca697846e53156e4ea9911476","impliedFormat":1},{"version":"413586add0cfe7369b64979d4ec2ed56c3f771c0667fbde1bf1f10063ede0b08","impliedFormat":1},{"version":"06472528e998d152375ad3bd8ebcb69ff4694fd8d2effaf60a9d9f25a37a097a","impliedFormat":1},{"version":"50b5bc34ce6b12eccb76214b51aadfa56572aa6cc79c2b9455cdbb3d6c76af1d","impliedFormat":1},{"version":"b7e16ef7f646a50991119b205794ebfd3a4d8f8e0f314981ebbe991639023d0e","impliedFormat":1},{"version":"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","impliedFormat":1},{"version":"a401617604fa1f6ce437b81689563dfdc377069e4c58465dbd8d16069aede0a5","impliedFormat":1},{"version":"6e9082e91370de5040e415cd9f24e595b490382e8c7402c4e938a8ce4bccc99f","impliedFormat":1},{"version":"8695dec09ad439b0ceef3776ea68a232e381135b516878f0901ed2ea114fd0fe","impliedFormat":1},{"version":"304b44b1e97dd4c94697c3313df89a578dca4930a104454c99863f1784a54357","impliedFormat":1},{"version":"d682336018141807fb602709e2d95a192828fcb8d5ba06dda3833a8ea98f69e3","impliedFormat":1},{"version":"6124e973eab8c52cabf3c07575204efc1784aca6b0a30c79eb85fe240a857efa","impliedFormat":1},{"version":"0d891735a21edc75df51f3eb995e18149e119d1ce22fd40db2b260c5960b914e","impliedFormat":1},{"version":"3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85","impliedFormat":1},{"version":"4fbd3116e00ed3a6410499924b6403cc9367fdca303e34838129b328058ede40","impliedFormat":1},{"version":"b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f","impliedFormat":1},{"version":"0a437ae178f999b46b6153d79095b60c42c996bc0458c04955f1c996dc68b971","impliedFormat":1},{"version":"74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f","impliedFormat":1},{"version":"4a7baeb6325920044f66c0f8e5e6f1f52e06e6d87588d837bdf44feb6f35c664","impliedFormat":1},{"version":"12d218a49dbe5655b911e6cc3c13b2c655e4c783471c3b0432137769c79e1b3c","impliedFormat":1},{"version":"7274fbffbd7c9589d8d0ffba68157237afd5cecff1e99881ea3399127e60572f","impliedFormat":1},{"version":"6b0fc04121360f752d196ba35b6567192f422d04a97b2840d7d85f8b79921c92","impliedFormat":1},{"version":"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9","impliedFormat":1},{"version":"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801","impliedFormat":1},{"version":"a365c4d3bed3be4e4e20793c999c51f5cd7e6792322f14650949d827fbcd170f","impliedFormat":1},{"version":"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5","impliedFormat":1},{"version":"9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5","impliedFormat":1},{"version":"f374cb24e93e7798c4d9e83ff872fa52d2cdb36306392b840a6ddf46cb925cb6","impliedFormat":1},{"version":"42b81043b00ff27c6bd955aea0f6e741545f2265978bf364b614702b72a027ab","impliedFormat":1},{"version":"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d","impliedFormat":1},{"version":"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5","impliedFormat":1},{"version":"1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027","impliedFormat":1},{"version":"97e5ccc7bb88419005cbdf812243a5b3186cdef81b608540acabe1be163fc3e4","affectsGlobalScope":true,"impliedFormat":1},{"version":"3fbdd025f9d4d820414417eeb4107ffa0078d454a033b506e22d3a23bc3d9c41","affectsGlobalScope":true,"impliedFormat":1},{"version":"a8f8e6ab2fa07b45251f403548b78eaf2022f3c2254df3dc186cb2671fe4996d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e","impliedFormat":1},{"version":"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b","impliedFormat":1},{"version":"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa","affectsGlobalScope":true,"impliedFormat":1},{"version":"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6","impliedFormat":1},{"version":"9f9bb6755a8ce32d656ffa4763a8144aa4f274d6b69b59d7c32811031467216e","impliedFormat":1},{"version":"5c32bdfbd2d65e8fffbb9fbda04d7165e9181b08dad61154961852366deb7540","impliedFormat":1},{"version":"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441","impliedFormat":1},{"version":"6b3453eebd474cc8acf6d759f1668e6ce7425a565e2996a20b644c72916ecf75","impliedFormat":1},{"version":"0c05e9842ec4f8b7bfebfd3ca61604bb8c914ba8da9b5337c4f25da427a005f2","impliedFormat":1},{"version":"89cd3444e389e42c56fd0d072afef31387e7f4107651afd2c03950f22dc36f77","impliedFormat":1},{"version":"7f2aa4d4989a82530aaac3f72b3dceca90e9c25bee0b1a327e8a08a1262435ad","impliedFormat":1},{"version":"e39a304f882598138a8022106cb8de332abbbb87f3fee71c5ca6b525c11c51fc","impliedFormat":1},{"version":"faed7a5153215dbd6ebe76dfdcc0af0cfe760f7362bed43284be544308b114cf","impliedFormat":1},{"version":"fcdf3e40e4a01b9a4b70931b8b51476b210c511924fcfe3f0dae19c4d52f1a54","impliedFormat":1},{"version":"345c4327b637d34a15aba4b7091eb068d6ab40a3dedaab9f00986253c9704e53","impliedFormat":1},{"version":"3a788c7fb7b1b1153d69a4d1d9e1d0dfbcf1127e703bdb02b6d12698e683d1fb","impliedFormat":1},{"version":"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9","impliedFormat":1},{"version":"d38530db0601215d6d767f280e3a3c54b2a83b709e8d9001acb6f61c67e965fc","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"4805f6161c2c8cefb8d3b8bd96a080c0fe8dbc9315f6ad2e53238f9a79e528a6","impliedFormat":1},{"version":"b83cb14474fa60c5f3ec660146b97d122f0735627f80d82dd03e8caa39b4388c","impliedFormat":1},{"version":"2b5b70d7782fe028487a80a1c214e67bd610532b9f978b78fa60f5b4a359f77e","impliedFormat":1},{"version":"7ee86fbb3754388e004de0ef9e6505485ddfb3be7640783d6d015711c03d302d","impliedFormat":1},{"version":"1a82deef4c1d39f6882f28d275cad4c01f907b9b39be9cbc472fcf2cf051e05b","impliedFormat":1},{"version":"7580e62139cb2b44a0270c8d01abcbfcba2819a02514a527342447fa69b34ef1","impliedFormat":1},{"version":"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647","impliedFormat":1},{"version":"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23","impliedFormat":1},{"version":"20865ac316b8893c1a0cc383ccfc1801443fbcc2a7255be166cf90d03fac88c9","impliedFormat":1},{"version":"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23","impliedFormat":1},{"version":"461d0ad8ae5f2ff981778af912ba71b37a8426a33301daa00f21c6ccb27f8156","impliedFormat":1},{"version":"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577","impliedFormat":1},{"version":"fcafff163ca5e66d3b87126e756e1b6dfa8c526aa9cd2a2b0a9da837d81bbd72","impliedFormat":1},{"version":"70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657","impliedFormat":1},{"version":"f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b","impliedFormat":1},{"version":"772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc","impliedFormat":1},{"version":"802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7","impliedFormat":1},{"version":"8b4327413e5af38cd8cb97c59f48c3c866015d5d642f28518e3a891c469f240e","impliedFormat":1},{"version":"7e6ac205dcb9714f708354fd863bffa45cee90740706cc64b3b39b23ebb84744","impliedFormat":1},{"version":"61dc6e3ac78d64aa864eedd0a208b97b5887cc99c5ba65c03287bf57d83b1eb9","impliedFormat":1},{"version":"4b20fcf10a5413680e39f5666464859fc56b1003e7dfe2405ced82371ebd49b6","impliedFormat":1},{"version":"c06ef3b2569b1c1ad99fcd7fe5fba8d466e2619da5375dfa940a94e0feea899b","impliedFormat":1},{"version":"f7d628893c9fa52ba3ab01bcb5e79191636c4331ee5667ecc6373cbccff8ae12","impliedFormat":1},{"version":"1d879125d1ec570bf04bc1f362fdbe0cb538315c7ac4bcfcdf0c1e9670846aa6","impliedFormat":1},{"version":"f730b468deecf26188ad62ee8950dc29aa2aea9543bb08ed714c3db019359fd9","impliedFormat":1},{"version":"933aee906d42ea2c53b6892192a8127745f2ec81a90695df4024308ba35a8ff4","impliedFormat":1},{"version":"d663134457d8d669ae0df34eabd57028bddc04fc444c4bc04bc5215afc91e1f4","impliedFormat":1},{"version":"144bc326e90b894d1ec78a2af3ffb2eb3733f4d96761db0ca0b6239a8285f972","impliedFormat":1},{"version":"a3e3f0efcae272ab8ee3298e4e819f7d9dd9ff411101f45444877e77cfeca9a4","impliedFormat":1},{"version":"43e96a3d5d1411ab40ba2f61d6a3192e58177bcf3b133a80ad2a16591611726d","impliedFormat":1},{"version":"58659b06d33fa430bee1105b75cf876c0a35b2567207487c8578aec51ca2d977","impliedFormat":1},{"version":"71d9eb4c4e99456b78ae182fb20a5dfc20eb1667f091dbb9335b3c017dd1c783","impliedFormat":1},{"version":"cfa846a7b7847a1d973605fbb8c91f47f3a0f0643c18ac05c47077ebc72e71c7","impliedFormat":1},{"version":"30e6520444df1a004f46fdc8096f3fe06f7bbd93d09c53ada9dcdde59919ccca","impliedFormat":1},{"version":"6c800b281b9e89e69165fd11536195488de3ff53004e55905e6c0059a2d8591e","impliedFormat":1},{"version":"7d4254b4c6c67a29d5e7f65e67d72540480ac2cfb041ca484847f5ae70480b62","impliedFormat":1},{"version":"a58beefce74db00dbb60eb5a4bb0c6726fb94c7797c721f629142c0ae9c94306","impliedFormat":1},{"version":"41eeb453ccb75c5b2c3abef97adbbd741bd7e9112a2510e12f03f646dc9ad13d","impliedFormat":1},{"version":"502fa5863df08b806dbf33c54bee8c19f7e2ad466785c0fc35465d7c5ff80995","impliedFormat":1},{"version":"c91a2d08601a1547ffef326201be26db94356f38693bb18db622ae5e9b3d7c92","impliedFormat":1},{"version":"888cda0fa66d7f74e985a3f7b1af1f64b8ff03eb3d5e80d051c3cbdeb7f32ab7","impliedFormat":1},{"version":"60681e13f3545be5e9477acb752b741eae6eaf4cc01658a25ec05bff8b82a2ef","impliedFormat":1},{"version":"9586918b63f24124a5ca1d0cc2979821a8a57f514781f09fc5aa9cae6d7c0138","impliedFormat":1},{"version":"a57b1802794433adec9ff3fed12aa79d671faed86c49b09e02e1ac41b4f1d33a","impliedFormat":1},{"version":"ad10d4f0517599cdeca7755b930f148804e3e0e5b5a3847adce0f1f71bbccd74","impliedFormat":1},{"version":"1042064ece5bb47d6aba91648fbe0635c17c600ebdf567588b4ca715602f0a9d","impliedFormat":1},{"version":"c49469a5349b3cc1965710b5b0f98ed6c028686aa8450bcb3796728873eb923e","impliedFormat":1},{"version":"4a889f2c763edb4d55cb624257272ac10d04a1cad2ed2948b10ed4a7fda2a428","impliedFormat":1},{"version":"7bb79aa2fead87d9d56294ef71e056487e848d7b550c9a367523ee5416c44cfa","impliedFormat":1},{"version":"d88ea80a6447d7391f52352ec97e56b52ebec934a4a4af6e2464cfd8b39c3ba8","impliedFormat":1},{"version":"55095860901097726220b6923e35a812afdd49242a1246d7b0942ee7eb34c6e4","impliedFormat":1},{"version":"96171c03c2e7f314d66d38acd581f9667439845865b7f85da8df598ff9617476","impliedFormat":1},{"version":"27ff4196654e6373c9af16b6165120e2dd2169f9ad6abb5c935af5abd8c7938c","impliedFormat":1},{"version":"bb8f2dbc03533abca2066ce4655c119bff353dd4514375beb93c08590c03e023","impliedFormat":1},{"version":"d193c8a86144b3a87b22bc1f5534b9c3e0f5a187873ec337c289a183973a58fe","impliedFormat":1},{"version":"1a6e6ba8a07b74e3ad237717c0299d453f9ceb795dbc2f697d1f2dd07cb782d2","impliedFormat":1},{"version":"58d70c38037fc0f949243388ff7ae20cf43321107152f14a9d36ca79311e0ada","impliedFormat":1},{"version":"f56bdc6884648806d34bc66d31cdb787c4718d04105ce2cd88535db214631f82","impliedFormat":1},{"version":"190da5eac6478d61ab9731ab2146fbc0164af2117a363013249b7e7992f1cccb","impliedFormat":1},{"version":"01479d9d5a5dda16d529b91811375187f61a06e74be294a35ecce77e0b9e8d6c","impliedFormat":1},{"version":"49f95e989b4632c6c2a578cc0078ee19a5831832d79cc59abecf5160ea71abad","impliedFormat":1},{"version":"9666533332f26e8995e4d6fe472bdeec9f15d405693723e6497bf94120c566c8","impliedFormat":1},{"version":"ce0df82a9ae6f914ba08409d4d883983cc08e6d59eb2df02d8e4d68309e7848b","impliedFormat":1},{"version":"796273b2edc72e78a04e86d7c58ae94d370ab93a0ddf40b1aa85a37a1c29ecd7","impliedFormat":1},{"version":"5df15a69187d737d6d8d066e189ae4f97e41f4d53712a46b2710ff9f8563ec9f","impliedFormat":1},{"version":"1a4dc28334a926d90ba6a2d811ba0ff6c22775fcc13679521f034c124269fd40","impliedFormat":1},{"version":"f05315ff85714f0b87cc0b54bcd3dde2716e5a6b99aedcc19cad02bf2403e08c","impliedFormat":1},{"version":"8a8c64dafaba11c806efa56f5c69f611276471bef80a1db1f71316ec4168acef","impliedFormat":1},{"version":"43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a","impliedFormat":1},{"version":"5fad3b31fc17a5bc58095118a8b160f5260964787c52e7eb51e3d4fcf5d4a6f0","impliedFormat":1},{"version":"72105519d0390262cf0abe84cf41c926ade0ff475d35eb21307b2f94de985778","impliedFormat":1},{"version":"d0a4cac61fa080f2be5ebb68b82726be835689b35994ba0e22e3ed4d2bc45e3b","impliedFormat":1},{"version":"c857e0aae3f5f444abd791ec81206020fbcc1223e187316677e026d1c1d6fe08","impliedFormat":1},{"version":"ccf6dd45b708fb74ba9ed0f2478d4eb9195c9dfef0ff83a6092fa3cf2ff53b4f","impliedFormat":1},{"version":"2d7db1d73456e8c5075387d4240c29a2a900847f9c1bff106a2e490da8fbd457","impliedFormat":1},{"version":"2b15c805f48e4e970f8ec0b1915f22d13ca6212375e8987663e2ef5f0205e832","impliedFormat":1},{"version":"205a31b31beb7be73b8df18fcc43109cbc31f398950190a0967afc7a12cb478c","impliedFormat":1},{"version":"8fca3039857709484e5893c05c1f9126ab7451fa6c29e19bb8c2411a2e937345","impliedFormat":1},{"version":"35069c2c417bd7443ae7c7cafd1de02f665bf015479fec998985ffbbf500628c","impliedFormat":1},{"version":"dba6c7006e14a98ec82999c6f89fbbbfd1c642f41db148535f3b77b8018829b8","impliedFormat":1},{"version":"7f897b285f22a57a5c4dc14a27da2747c01084a542b4d90d33897216dceeea2e","impliedFormat":1},{"version":"7e0b7f91c5ab6e33f511efc640d36e6f933510b11be24f98836a20a2dc914c2d","impliedFormat":1},{"version":"045b752f44bf9bbdcaffd882424ab0e15cb8d11fa94e1448942e338c8ef19fba","impliedFormat":1},{"version":"2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab","impliedFormat":1},{"version":"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a","impliedFormat":1},{"version":"d96b39301d0ded3f1a27b47759676a33a02f6f5049bfcbde81e533fd10f50dcb","impliedFormat":1},{"version":"2ded4f930d6abfaa0625cf55e58f565b7cbd4ab5b574dd2cb19f0a83a2f0be8b","impliedFormat":1},{"version":"0aedb02516baf3e66b2c1db9fef50666d6ed257edac0f866ea32f1aa05aa474f","impliedFormat":1},{"version":"ca0f4d9068d652bad47e326cf6ba424ac71ab866e44b24ddb6c2bd82d129586a","affectsGlobalScope":true,"impliedFormat":1},{"version":"04d36005fcbeac741ac50c421181f4e0316d57d148d37cc321a8ea285472462b","impliedFormat":1},{"version":"9e2739b32f741859263fdba0244c194ca8e96da49b430377930b8f721d77c000","impliedFormat":1},{"version":"56ccb49443bfb72e5952f7012f0de1a8679f9f75fc93a5c1ac0bafb28725fc5f","impliedFormat":1},{"version":"20fa37b636fdcc1746ea0738f733d0aed17890d1cd7cb1b2f37010222c23f13e","impliedFormat":1},{"version":"d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943","impliedFormat":1},{"version":"bc03c3c352f689e38c0ddd50c39b1e65d59273991bfc8858a9e3c0ebb79c023b","impliedFormat":1},{"version":"19df3488557c2fc9b4d8f0bac0fd20fb59aa19dec67c81f93813951a81a867f8","affectsGlobalScope":true,"impliedFormat":1},{"version":"b25350193e103ae90423c5418ddb0ad1168dc9c393c9295ef34980b990030617","affectsGlobalScope":true,"impliedFormat":1},{"version":"bef86adb77316505c6b471da1d9b8c9e428867c2566270e8894d4d773a1c4dc2","impliedFormat":1},{"version":"a46dba563f70f32f9e45ae015f3de979225f668075d7a427f874e0f6db584991","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"2652448ac55a2010a1f71dd141f828b682298d39728f9871e1cdf8696ef443fd","impliedFormat":1},{"version":"02c4fc9e6bb27545fa021f6056e88ff5fdf10d9d9f1467f1d10536c6e749ac50","impliedFormat":1},{"version":"120599fd965257b1f4d0ff794bc696162832d9d8467224f4665f713a3119078b","impliedFormat":1},{"version":"5433f33b0a20300cca35d2f229a7fc20b0e8477c44be2affeb21cb464af60c76","impliedFormat":1},{"version":"db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195","impliedFormat":1},{"version":"bd4131091b773973ca5d2326c60b789ab1f5e02d8843b3587effe6e1ea7c9d86","impliedFormat":1},{"version":"c7f6485931085bf010fbaf46880a9b9ec1a285ad9dc8c695a9e936f5a48f34b4","impliedFormat":1},{"version":"14f6b927888a1112d662877a5966b05ac1bf7ed25d6c84386db4c23c95a5363b","impliedFormat":1},{"version":"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff","impliedFormat":1},{"version":"622694a8522b46f6310c2a9b5d2530dde1e2854cb5829354e6d1ff8f371cf469","impliedFormat":1},{"version":"d24ff95760ea2dfcc7c57d0e269356984e7046b7e0b745c80fea71559f15bdd8","impliedFormat":1},{"version":"a9e6c0ff3f8186fccd05752cf75fc94e147c02645087ac6de5cc16403323d870","impliedFormat":1},{"version":"49c346823ba6d4b12278c12c977fb3a31c06b9ca719015978cb145eb86da1c61","impliedFormat":1},{"version":"bfac6e50eaa7e73bb66b7e052c38fdc8ccfc8dbde2777648642af33cf349f7f1","impliedFormat":1},{"version":"92f7c1a4da7fbfd67a2228d1687d5c2e1faa0ba865a94d3550a3941d7527a45d","impliedFormat":1},{"version":"f53b120213a9289d9a26f5af90c4c686dd71d91487a0aa5451a38366c70dc64b","impliedFormat":1},{"version":"83fe880c090afe485a5c02262c0b7cdd76a299a50c48d9bde02be8e908fb4ae6","impliedFormat":1},{"version":"13c1b657932e827a7ed510395d94fc8b743b9d053ab95b7cd829b2bc46fb06db","impliedFormat":1},{"version":"57d67b72e06059adc5e9454de26bbfe567d412b962a501d263c75c2db430f40e","impliedFormat":1},{"version":"6511e4503cf74c469c60aafd6589e4d14d5eb0a25f9bf043dcbecdf65f261972","impliedFormat":1},{"version":"078131f3a722a8ad3fc0b724cd3497176513cdcb41c80f96a3acbda2a143b58e","impliedFormat":1},{"version":"8c70ddc0c22d85e56011d49fddfaae3405eb53d47b59327b9dd589e82df672e7","impliedFormat":1},{"version":"a67b87d0281c97dfc1197ef28dfe397fc2c865ccd41f7e32b53f647184cc7307","impliedFormat":1},{"version":"771ffb773f1ddd562492a6b9aaca648192ac3f056f0e1d997678ff97dbb6bf9b","impliedFormat":1},{"version":"232f70c0cf2b432f3a6e56a8dc3417103eb162292a9fd376d51a3a9ea5fbbf6f","impliedFormat":1},{"version":"9e155d2255348d950b1f65643fb26c0f14f5109daf8bd9ee24a866ad0a743648","affectsGlobalScope":true,"impliedFormat":1},{"version":"0b103e9abfe82d14c0ad06a55d9f91d6747154ef7cacc73cf27ecad2bfb3afcf","impliedFormat":1},{"version":"7a883e9c84e720810f86ef4388f54938a65caa0f4d181a64e9255e847a7c9f51","impliedFormat":1},{"version":"a0ba218ac1baa3da0d5d9c1ec1a7c2f8676c284e6f5b920d6d049b13fa267377","impliedFormat":1},{"version":"8a0e762ceb20c7e72504feef83d709468a70af4abccb304f32d6b9bac1129b2c","impliedFormat":1},{"version":"d408d6f32de8d1aba2ff4a20f1aa6a6edd7d92c997f63b90f8ad3f9017cf5e46","impliedFormat":1},{"version":"9252d498a77517aab5d8d4b5eb9d71e4b225bbc7123df9713e08181de63180f6","impliedFormat":1},{"version":"b1f1d57fde8247599731b24a733395c880a6561ec0c882efaaf20d7df968c5af","impliedFormat":1},{"version":"9d622ea608d43eb463c0c4538fd5baa794bc18ea0bb8e96cd2ab6fd483d55fe2","impliedFormat":1},{"version":"35e6379c3f7cb27b111ad4c1aa69538fd8e788ab737b8ff7596a1b40e96f4f90","impliedFormat":1},{"version":"1fffe726740f9787f15b532e1dc870af3cd964dbe29e191e76121aa3dd8693f2","impliedFormat":1},{"version":"371bf6127c1d427836de95197155132501cb6b69ef8709176ce6e0b85d059264","impliedFormat":1},{"version":"2bafd700e617d3693d568e972d02b92224b514781f542f70d497a8fdf92d52a2","affectsGlobalScope":true,"impliedFormat":1},{"version":"5542d8a7ea13168cb573be0d1ba0d29460d59430fb12bb7bf4674efd5604e14c","impliedFormat":1},{"version":"af48e58339188d5737b608d41411a9c054685413d8ae88b8c1d0d9bfabdf6e7e","impliedFormat":1},{"version":"616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790","impliedFormat":1},{"version":"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0","impliedFormat":1},{"version":"f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49","impliedFormat":1},{"version":"1de8c302fd35220d8f29dea378a4ae45199dc8ff83ca9923aca1400f2b28848a","impliedFormat":1},{"version":"77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c","impliedFormat":1},{"version":"98a787be42bd92f8c2a37d7df5f13e5992da0d967fab794adbb7ee18370f9849","impliedFormat":1},{"version":"332248ee37cca52903572e66c11bef755ccc6e235835e63d3c3e60ddda3e9b93","impliedFormat":1},{"version":"94e8cc88ae2ef3d920bb3bdc369f48436db123aa2dc07f683309ad8c9968a1e1","impliedFormat":1},{"version":"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35","impliedFormat":1},{"version":"320f4091e33548b554d2214ce5fc31c96631b513dffa806e2e3a60766c8c49d9","impliedFormat":1},{"version":"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff","impliedFormat":1},{"version":"d90d5f524de38889d1e1dbc2aeef00060d779f8688c02766ddb9ca195e4a713d","impliedFormat":1},{"version":"a3f41ed1b4f2fc3049394b945a68ae4fdefd49fa1739c32f149d32c0545d67f5","impliedFormat":1},{"version":"b0309e1eda99a9e76f87c18992d9c3689b0938266242835dd4611f2b69efe456","impliedFormat":1},{"version":"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e","impliedFormat":1},{"version":"6ceb10ca57943be87ff9debe978f4ab73593c0c85ee802c051a93fc96aaf7a20","impliedFormat":1},{"version":"1de3ffe0cc28a9fe2ac761ece075826836b5a02f340b412510a59ba1d41a505a","impliedFormat":1},{"version":"e46d6cc08d243d8d0d83986f609d830991f00450fb234f5b2f861648c42dc0d8","impliedFormat":1},{"version":"1c0a98de1323051010ce5b958ad47bc1c007f7921973123c999300e2b7b0ecc0","impliedFormat":1},{"version":"ff863d17c6c659440f7c5c536e4db7762d8c2565547b2608f36b798a743606ca","impliedFormat":1},{"version":"5412ad0043cd60d1f1406fc12cb4fb987e9a734decbdd4db6f6acf71791e36fe","impliedFormat":1},{"version":"ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5","impliedFormat":1},{"version":"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7","impliedFormat":1},{"version":"b6c1f64158da02580f55e8a2728eda6805f79419aed46a930f43e68ad66a38fc","impliedFormat":1},{"version":"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e","impliedFormat":1},{"version":"bc9ee0192f056b3d5527bcd78dc3f9e527a9ba2bdc0a2c296fbc9027147df4b2","impliedFormat":1},{"version":"330896c1a2b9693edd617be24fbf9e5895d6e18c7955d6c08f028f272b37314d","impliedFormat":1},{"version":"1d9c0a9a6df4e8f29dc84c25c5aa0bb1da5456ebede7a03e03df08bb8b27bae6","impliedFormat":1},{"version":"84380af21da938a567c65ef95aefb5354f676368ee1a1cbb4cae81604a4c7d17","impliedFormat":1},{"version":"1af3e1f2a5d1332e136f8b0b95c0e6c0a02aaabd5092b36b64f3042a03debf28","impliedFormat":1},{"version":"30d8da250766efa99490fc02801047c2c6d72dd0da1bba6581c7e80d1d8842a4","impliedFormat":1},{"version":"03566202f5553bd2d9de22dfab0c61aa163cabb64f0223c08431fb3fc8f70280","impliedFormat":1},{"version":"4c0a1233155afb94bd4d7518c75c84f98567cd5f13fc215d258de196cdb40d91","impliedFormat":1},{"version":"e7765aa8bcb74a38b3230d212b4547686eb9796621ffb4367a104451c3f9614f","impliedFormat":1},{"version":"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450","impliedFormat":1},{"version":"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d","impliedFormat":1},{"version":"5bf5c7a44e779790d1eb54c234b668b15e34affa95e78eada73e5757f61ed76a","impliedFormat":1},{"version":"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70","impliedFormat":1},{"version":"5c634644d45a1b6bc7b05e71e05e52ec04f3d73d9ac85d5927f647a5f965181a","impliedFormat":1},{"version":"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872","impliedFormat":1},{"version":"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7","impliedFormat":1},{"version":"a68d4b3182e8d776cdede7ac9630c209a7bfbb59191f99a52479151816ef9f9e","impliedFormat":99},{"version":"39644b343e4e3d748344af8182111e3bbc594930fff0170256567e13bbdbebb0","impliedFormat":99},{"version":"ed7fd5160b47b0de3b1571c5c5578e8e7e3314e33ae0b8ea85a895774ee64749","impliedFormat":99},{"version":"63a7595a5015e65262557f883463f934904959da563b4f788306f699411e9bac","impliedFormat":1},{"version":"4ba137d6553965703b6b55fd2000b4e07ba365f8caeb0359162ad7247f9707a6","impliedFormat":1},{"version":"6de125ea94866c736c6d58d68eb15272cf7d1020a5b459fea1c660027eca9a90","affectsGlobalScope":true,"impliedFormat":1},{"version":"8fac4a15690b27612d8474fb2fc7cc00388df52d169791b78d1a3645d60b4c8b","affectsGlobalScope":true,"impliedFormat":1},{"version":"064ac1c2ac4b2867c2ceaa74bbdce0cb6a4c16e7c31a6497097159c18f74aa7c","impliedFormat":1},{"version":"3dc14e1ab45e497e5d5e4295271d54ff689aeae00b4277979fdd10fa563540ae","impliedFormat":1},{"version":"d3b315763d91265d6b0e7e7fa93cfdb8a80ce7cdd2d9f55ba0f37a22db00bdb8","impliedFormat":1},{"version":"b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9","impliedFormat":1},{"version":"da1d85ccc707ce0dae18d9f1c78be42e72bcac299651d1bbe5208c242ee967dc","affectsGlobalScope":true},"083e23c4c5e7761db151134ea1ef7896120c86c5888cdc8a861f534f7e86d6fd",{"version":"1ad12e077c5699718fea70711f595f1bdfec6ba3050e6ff299bbb7661fb6873f","signature":"435a1e418e8338be3f39614b96b81a9aa2700bc8c27bc6b98f064ff9ce17c363"},{"version":"930cb8e52e6c3e4ef5d6332500835df351fe101e98b37a45137447a6062bb2ab","signature":"fab2a8e5bc4c4d4bee2e3833549c23d25bde19888c716725023eb27a80c06698"},{"version":"74fb35a944c80b17607f260e8f81da3f81fd6838960ffb70ad1f4d1c6768ee1c","signature":"eed923ce1fd6f61f21d25d4e85362311a7b6b6804615cc5007b2ee5aa2a58126"},{"version":"782dd68d2910c17c5905075d37ec17d39e77dcb0a8ea193607123f2e652b3ae2","signature":"72d5042899f0191b2f5bd7bf3d27a0e36d98506a2b62f2bfa66503bf33ce5b8d"},{"version":"5fa8319d2e062bd41ac4ad42334571e3d9e29ab61db0fc39f405063ea42ec117","signature":"bd9628fa5545672f558ca59e739e34ef38b38a022ac5516ef7a8d162936fc1bc"},{"version":"3378838fdaf13f4eef72ef1f15fe301c79b55d30f85212dc914b276d14616940","signature":"a65f395ee2bcae7c0fbef9a5b46b7c99485f3b882a07d135064d3e5a329facb2"},{"version":"5d083ffb112576c6124e4859c9da2d15674ccd7eda6ea8187861ba28bbb820ec","signature":"972f8dff030d00fddbb4cb4966ffbecadee0d3a66b7a2d7f364a9fa22b0cfacd"},{"version":"2bcc0421514a6402d22bbcb618a6b487a563c8cfc65a7f987f3f524a7f6e8de5","impliedFormat":1},{"version":"97be7713f1dcdceecd6ca0a4a1d6e055de4e4154271577c3ff2ec8fe226a0f9a","signature":"d37676a4a785fdfca846f0072e1a73ed12a65474687006de46dde9f3e8955151"},{"version":"f8109d593f4b807b0881c9caf3d587a91bef45405854ac96fd63df06278a822a","signature":"a6d3cb9f151f1da84338a7161b37ffb4cc9a93810160257710c446e8cc8d527e"},{"version":"9f515caa885bd421eb3d4981b4bf6b3111ce4f7db530ae987bdefea134f9579b","signature":"2e66fdffc8dc6d1387535d5137750f1a1138fa19d8c9c7e0c702ff76218bd560"},{"version":"f40cdff7722d79b0e09b8ee316169846842a6fa0aca2cf6cb65dd34fdf0d35e6","signature":"f5b5d4d2565e15fa4ea35249f6dfac49afe8d18383b09d6f2732010ea11ff387"},{"version":"d2ffe9952db807a19cf268ecc3c3e8e46ccc65f45c4fd67f85a4c56522b0893f","signature":"951007a44288357ca453f798e76924d3d8e1f10e5cb64b6df3092ae1f84028cb"},{"version":"a76d78ddfcfe5ad36e9b39c25512b4c18235f1568a7a327b11b82a99d3f35228","signature":"d5a418cd7b36898e596f8359a4feee968200778721c6ca4a9181f4c173a0a009"},{"version":"fecb0d6f25af0fc4d493b4561da134700f00392c69b1510ba1dfc436d582c0a1","signature":"3a511ba8f1f213c3f5366d968d285fd942dd3ca02a6f010694574549605f144b"},{"version":"9198951513281090579d79a5bc8cb569f2473a3cc9925683ae00ef5faf754069","signature":"c702f4ceabbdbcd76365df2ca3d31384af35db9e6709d2d9b8ded4602e1ce5c2"},{"version":"a4cb5e23519a41b912951d64b5533b9d926cc80d4b5c85f38b9b9482bea1f561","signature":"59bef7b73df0028e4603dcf4d7090ba255e3fdf575038976d45a4da267d239d3"},{"version":"2530905750a893d6271ed2760c95308507163453b2b275e5b8643026526ca559","signature":"771bfcb3e45fb8823a6fb2e8456a28c705f8119ea7400a8ec185c13634d125f8"},{"version":"a77ce2b74dfd30ef49cfaed6a7f5722e69fc2087eb52dd621e2ce52993d44f02","signature":"028607ddbf9ede2fad771628d7803d40c0ef66df5c024f8c7ba81ddbfb3beb00"},{"version":"85cd90eed99be36458bbecdd288282704a4668c8ef737acdd8b5bc59a804ef40","signature":"66f7592dd62c471ee3ed3501984945202795ae354a92079f461bb766dc15b34c"},{"version":"6175d7bc17e1ec94408f8c5440f9642735739e556e21674bc2977584866175b1","signature":"da6c0dada63079029e436713173277a14472645fab402707fbf4b464e36aae88"},{"version":"7cfed42a638d52e0420297c689acef86b241a0a2c38afdc3f9d33467c53d16ba","signature":"bc68295534e511fe03c5eafd61363284eb5950c5c939a68407d62db0ac3cb0fb"},{"version":"964eeea3fb010bee2407869b9ebc85065398ce12a03db2f4faf0ced71995159a","signature":"8414bcd18d723dc2835335b0ec193134e0d95588ba58bb96196c3667710ceced"},{"version":"fad888963187287ac751125145a68aae481a27a7baf77d44fe9e4b77aba81b10","signature":"3138a2f3163da96fb819710ba59ae17b9541c1e1279da5a44e9747e1df359756"},{"version":"40d88cac63e06c4136463fff6899eaf372982a430aaea47e8d162d6764400af2","signature":"ca85bc36b8897021318d7365562983eacc5257bf36a91f49d89fa4e9992049ca"},{"version":"b410782d329077e940bd6c0fe334709070c90c8f4b397f740399e8a57c138bc9","signature":"2d78c51c2279643d9bb3ec1b301210e742de36ef2ca0a0259117891350ae5841"},{"version":"b59ad590cc9594281a56aef2232caf58f0b77ec249dd10d4efb094ea409ef365","signature":"148c6f28d3b2075322b758aaede53e78d4e6fa408085e7120a91b074c414453f"},{"version":"f5b3abb83cd58dec4ec45d1adc2ae03a89d56167f7ca0273c1a80f9f5fff058c","signature":"8ff973a3d742d655173b1367e8f1bc209f98f2139222d6f50324af0b67054e15"},{"version":"dbabe6c078501d88d502fa57e6e8ef8b19f6e345d4b6755e8dc2551eed1f1cb7","signature":"8d9692899011af2402e19016c16d53cb26eb5d79e9dd9b514a257239da9913fe"},{"version":"375ac78f05379bfe3762d5ac0ca7ab58afb0f73a063ef4b098b195554adcea1f","signature":"01869475e381c7d395bcc63748ab433339d8b75cac70e6663e9620589764e6a6"},{"version":"bb13ea84aec62f374b9fade3d19ab11b0869ef81350319467908712d2463de5b","signature":"c98fcce2f7c92eb4ec8b38f6e3462ebe580f4f8c11584794777ef841a25f9180"},{"version":"24b31437b30639b7cbf9725235767185e1c4915a2197ea5902bd5e8a373e4177","signature":"b9333858842bf6ff78837ae9d77b1a070050dee9362c2161167676189815b86a"},"d1986184a09a52db8228cb2bb2a61a8c05c9354e5b93cec8e2628d8579c892d7",{"version":"70887969d195416e030bb153c6b8df6e54a166218e100d650ec8d8770663a041","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"}],"root":[[532,540],[542,567]],"options":{"allowJs":true,"esModuleInterop":true,"jsx":4,"module":99,"skipLibCheck":true,"strict":true,"target":4},"referencedMap":[[566,1],[532,2],[567,3],[533,4],[534,5],[274,2],[154,6],[155,6],[156,7],[94,8],[157,9],[158,10],[159,11],[92,2],[160,12],[161,13],[162,14],[163,15],[164,16],[165,17],[166,17],[167,18],[168,19],[169,20],[170,21],[95,2],[93,2],[171,22],[172,23],[173,24],[213,25],[174,26],[175,27],[176,26],[177,28],[178,29],[179,30],[180,31],[181,31],[182,31],[183,32],[184,33],[185,34],[186,35],[187,36],[188,37],[189,37],[190,38],[191,2],[192,2],[193,39],[194,40],[195,39],[196,41],[197,42],[198,43],[199,44],[200,45],[201,46],[202,47],[203,48],[204,49],[205,50],[206,51],[207,52],[208,53],[209,54],[210,55],[96,26],[97,2],[98,56],[99,57],[100,2],[101,58],[102,2],[145,59],[146,60],[147,61],[148,61],[149,62],[150,2],[151,9],[152,63],[153,60],[211,64],[212,65],[217,66],[434,67],[218,68],[216,69],[436,70],[435,71],[214,72],[432,2],[215,73],[83,2],[85,74],[431,67],[291,67],[84,2],[541,67],[481,75],[486,1],[476,76],[238,77],[278,78],[460,79],[273,80],[255,2],[430,2],[236,2],[449,81],[304,82],[237,2],[358,83],[281,84],[282,85],[429,86],[446,87],[340,88],[454,89],[455,90],[453,91],[452,2],[450,92],[280,93],[239,94],[383,2],[384,95],[310,96],[240,97],[311,96],[306,96],[227,96],[276,98],[275,2],[459,99],[471,2],[263,2],[405,100],[406,101],[400,67],[508,2],[408,2],[409,102],[401,103],[513,104],[512,105],[507,2],[325,2],[445,106],[444,2],[506,107],[402,67],[334,108],[330,109],[335,110],[333,2],[332,111],[331,2],[509,2],[505,2],[511,112],[510,2],[329,109],[500,113],[503,114],[319,115],[318,116],[317,117],[516,67],[316,118],[298,2],[519,2],[522,2],[521,67],[523,119],[220,2],[456,120],[457,121],[458,122],[233,2],[266,2],[232,123],[219,2],[421,67],[225,124],[420,125],[419,126],[410,2],[411,2],[418,2],[413,2],[416,127],[412,2],[414,128],[417,129],[415,128],[235,2],[230,2],[231,96],[286,2],[292,130],[293,131],[290,132],[288,133],[289,134],[284,2],[427,102],[313,102],[480,135],[487,136],[491,137],[463,138],[462,2],[301,2],[524,139],[475,140],[403,141],[404,142],[398,143],[389,2],[426,144],[465,67],[390,145],[428,146],[423,147],[422,2],[424,2],[395,2],[382,148],[464,149],[467,150],[392,151],[396,152],[387,153],[441,154],[474,155],[344,156],[359,157],[228,158],[473,159],[224,160],[294,161],[285,2],[295,162],[371,163],[283,2],[370,164],[91,2],[364,165],[265,2],[385,166],[360,2],[229,2],[259,2],[368,167],[234,2],[296,168],[394,169],[461,170],[393,2],[367,2],[287,2],[373,171],[374,172],[451,2],[376,173],[378,174],[377,175],[268,2],[366,158],[380,176],[343,177],[365,178],[372,179],[243,2],[247,2],[246,2],[245,2],[250,2],[244,2],[253,2],[252,2],[249,2],[248,2],[251,2],[254,180],[242,2],[352,181],[351,2],[356,182],[353,183],[355,184],[357,182],[354,183],[264,185],[314,186],[470,187],[525,2],[495,188],[497,189],[391,190],[496,191],[468,149],[407,149],[241,2],[345,192],[260,193],[261,194],[262,195],[258,196],[440,196],[308,196],[346,197],[309,197],[257,198],[256,2],[350,199],[349,200],[348,201],[347,202],[469,203],[439,204],[438,205],[399,206],[433,207],[437,208],[448,209],[447,210],[443,211],[342,212],[339,213],[341,214],[338,215],[379,216],[369,2],[485,2],[381,217],[442,2],[297,218],[388,120],[386,219],[299,220],[302,221],[520,2],[300,222],[303,222],[483,2],[482,2],[484,2],[518,2],[305,223],[466,2],[336,224],[328,67],[279,2],[223,225],[312,2],[489,67],[222,2],[499,226],[327,67],[493,102],[326,227],[478,228],[324,226],[226,2],[501,229],[322,67],[323,67],[315,2],[221,2],[321,230],[320,231],[267,232],[397,35],[307,35],[375,2],[362,233],[361,2],[425,109],[337,67],[472,123],[479,234],[86,67],[89,235],[90,236],[87,67],[88,2],[277,57],[272,237],[271,2],[270,238],[269,2],[477,239],[488,240],[490,241],[492,242],[494,243],[498,244],[531,245],[502,245],[530,246],[504,247],[514,248],[515,249],[517,250],[526,251],[529,123],[528,2],[527,252],[363,253],[81,2],[82,2],[13,2],[14,2],[16,2],[15,2],[2,2],[17,2],[18,2],[19,2],[20,2],[21,2],[22,2],[23,2],[24,2],[3,2],[25,2],[26,2],[4,2],[27,2],[31,2],[28,2],[29,2],[30,2],[32,2],[33,2],[34,2],[5,2],[35,2],[36,2],[37,2],[38,2],[6,2],[42,2],[39,2],[40,2],[41,2],[43,2],[7,2],[44,2],[49,2],[50,2],[45,2],[46,2],[47,2],[48,2],[8,2],[54,2],[51,2],[52,2],[53,2],[55,2],[9,2],[56,2],[57,2],[58,2],[60,2],[59,2],[61,2],[62,2],[10,2],[63,2],[64,2],[65,2],[11,2],[66,2],[67,2],[68,2],[69,2],[70,2],[1,2],[71,2],[72,2],[12,2],[76,2],[74,2],[79,2],[78,2],[73,2],[77,2],[75,2],[80,2],[121,254],[133,255],[118,256],[134,257],[143,258],[109,259],[110,260],[108,261],[142,252],[137,262],[141,263],[112,264],[130,265],[111,266],[140,267],[106,268],[107,262],[113,269],[114,2],[120,270],[117,269],[104,271],[144,272],[135,273],[124,274],[123,269],[125,275],[128,276],[122,277],[126,278],[138,252],[115,279],[116,280],[129,281],[105,257],[132,282],[131,269],[119,280],[127,283],[136,2],[103,2],[139,284],[553,285],[556,286],[559,287],[544,288],[561,289],[545,290],[563,291],[565,292],[550,293],[551,294],[543,295],[542,296],[548,297],[546,298],[554,298],[549,102],[547,298],[557,102],[552,299],[555,300],[558,301],[560,302],[562,303],[564,304],[540,102],[539,102],[537,305],[536,306],[538,307],[535,307]],"affectedFilesPendingEmit":[567,534,553,556,559,544,561,545,563,565,550,551,543,542,548,546,554,549,547,557,552,555,558,560,562,564,540,539,537,536,538,535],"version":"5.9.3"}