diff --git a/.gitignore b/.gitignore index 9c96306..27a71e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .conda/* local_data/* beets_flask/beets/* -tinker.ipynb +tinker/* static/bootstrap/* static/bootstrap-icons/* docker-compose-custom.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 245a29d..ad3bb93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ 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.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.3] - 24-08-01 + +### Fixed +- default config: mandatory fields cannot be set in the yaml, or they +might persist although the user sets them. moved to config loading in python. +- tmux session now restarts on page load if it is not alive. +- navbar, tags, inbox are now more friendly for mobile +- folder paths are now better escaped for terminal imports + +### Added +- Backend to get cover art from metadata of music files. +- Impoved library view (mobile friendly, and a browser header component) +- Library search + +### Changed +- Simplified folder structure of frontend +- Removed `include_paths` option from config and library backend (most of the frontend needs some form of file paths. thus, the option was not / could not be respected consistently) ## [0.0.2] - 24-07-16 @@ -15,4 +32,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.0.1 - 24-05-22 - initial commit +[0.0.3]: https://github.com/pSpitzner/beets-flask/compare/v0.0.2...v0.0.3 [0.0.2]: https://github.com/pSpitzner/beets-flask/compare/v0.0.1...v0.0.2 diff --git a/README.md b/README.md index cd33727..6d19bf8 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ This is the main idea with beets-flask: For all folders in your inbox, we genera - Autogenerate previews before importing - Import via GUI (if found matches are okay) - Import via Web-Terminal using beets as you know it (to correct matches) -- Undo imports (uses web terminal) +- Undo imports - Monitor multiple inboxes -- A basic library view +- A basic library view and search - Most File/Tag actions sit in a context menu (right-click, or long-press on touch) ![demo gif](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExcDZmZjJ0NzA0Z3h4Z2tycnBlMG1mbm9mMXFoMWM1bjJwdDBsOXR1NiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Z3lL2fo5m6UNf85dZT/giphy.gif) @@ -29,7 +29,8 @@ This is the main idea with beets-flask: For all folders in your inbox, we genera - Clone the repo - Adjust config files - Place a folder with music files into your inbox -- Build and run `docker compose up --build`, check for problems +- Build and run `docker compose up --build` +- Check the webinterface, by default at `http://localhost:5001` - Once happy, you can run the container as a daemon with `docker compose up -d --build` ### Config @@ -90,11 +91,11 @@ gui: library: readonly: no - include_paths: yes tags: expand_tags: yes # for tag groups, on page load, show tag details? recent_days: 14 # Number of days to consider for the "recent" tag group + order_by: "name" # how to sort tags within the trag groups: "name" (the album folder basename) | "date_created" | "date_modified" terminal: start_path: "/music/inbox" # the directory where to start new terminal sessions @@ -102,7 +103,6 @@ gui: inbox: concat_nested_folders: yes # show multiple folders in one line if they only have one child expand_files: no # on page load, show files in (album) folders, or collapse them - order_by: "name" # how to sort tags within the trag groups: "name" (the album folder basename) | "date_created" | "date_modified" folders: # keep in mind to volume-map these folders in your docker-compose.yml Inbox: @@ -118,7 +118,11 @@ To access the tmux from the host: ``` docker exec -it beets-flask /usr/bin/tmux attach-session -t beets-socket-term ``` -Beware, you can close the tmux session, and we have not yet implemented a way to restart it. (Just restart the container) + +If you use iTerm on macOS and want to create a profile for connecting to the tmux session natively: +``` +ssh -t yourserver "/usr/bin/docker exec -it beets-flask /usr/bin/tmux -CC new -A -s beets-socket-term" +``` ## Roadmap @@ -126,13 +130,15 @@ For the current state, there is a [KanBan board](https://github.com/users/pSpitz Major things that are planned: -- An actual library view, with search, covers and audio preview. The backend is likely up for the task already. +- Better library view, improved cover handling and audio preview. - Push the image to dockerhub -- Mobile friendly +- Mobile friendly (started) # Developing +The current state is pretty much a playground. Only essential features are included, but most tools are in place to easily add whatever you feel like. + ## Tech Stack - Backend: @@ -153,7 +159,7 @@ Major things that are planned: ## Notes, Design Choices and Ideas -- The current docker-compose already creates the dev container: +- See [docker-compose-dev.yaml](/docker-compose-dev.yaml) to createe the dev container: - maps `.repo` to edit the source from the host. - runs `entrypoint_dev.sh`, starting redis workers, flask, and the vite dev server - It seems that our vite dev setup **does not work with safari** because it uses CORS @@ -202,3 +208,15 @@ The library view backend is adapted from the existing beets webplugin that is al ### Testing We have started on a version of the container that runs some (backend) tests, but coverage is pretty non-existent. + + +### Convention for typescript imports + +We have an eslint sorting rule for imports: + +Order: +1. other modules/components +2. our modules/components +3. css (first others, then ours) + +Try to use absolute paths with `@/` prefix if not in the same folder. diff --git a/backend/beets_flask/_version.py b/backend/beets_flask/_version.py index d18f409..ffcc925 100644 --- a/backend/beets_flask/_version.py +++ b/backend/beets_flask/_version.py @@ -1 +1 @@ -__version__ = '0.0.2' +__version__ = '0.0.3' diff --git a/backend/beets_flask/config.py b/backend/beets_flask/config.py index bc02958..0372b2e 100644 --- a/backend/beets_flask/config.py +++ b/backend/beets_flask/config.py @@ -19,6 +19,7 @@ """ import confuse +import os from beets_flask.utility import log from beets import config @@ -31,3 +32,16 @@ # running as module. but we should place the default config where confuse looks for it. default_source = confuse.YamlSource("./configs/default.yaml", default=True) config.add(default_source) # .add inserts with lowest priority + +# add placeholders for required keys if they are not configred, +# so the docker container starts and can show some help. + +if not os.path.exists("/home/beetle/.config/beets/config.yaml"): + config["directory"] = "/music/imported" + +if len(config["gui"]["inbox"]["folders"].keys()) == 0: + config["gui"]["inbox"]["folders"]["Placeholder"] = { + "name": "Please check your config!", + "path": "/music/inbox", + "autotag": False, + } diff --git a/backend/beets_flask/routes/library.py b/backend/beets_flask/routes/library.py index ce95f0c..995e81f 100644 --- a/backend/beets_flask/routes/library.py +++ b/backend/beets_flask/routes/library.py @@ -21,6 +21,8 @@ import os from pathlib import Path from typing import Optional, TypedDict, cast +from io import BytesIO +from PIL import Image as PILImage import time from flask import ( @@ -38,6 +40,7 @@ from werkzeug.routing import BaseConverter, PathConverter import beets.library +from mediafile import MediaFile # comes with the beets install from beets import ui, util from beets.ui import _open_library from beets_flask.config import config @@ -59,22 +62,28 @@ def _rep(obj, expand=False, minimal=False): """ out = dict(obj) - # For out client side, we want to have a consistent name for each kind of item. + # For our client side, we want to have a consistent name for each kind of item. # for tracks its the title, for albums album name... out["name"] = ( out.get("title", None) or out.get("album", None) or out.get("artist", None) ) - if minimal: - out = {k: v for k, v in out.items() if k in ["id", "name"]} - if isinstance(obj, beets.library.Item): + if minimal: + fields = [ + "id", + "name", + "artist", + "albumartist", + "album", + "album_id", + "year", + "isrc", + ] + out = {k: v for k, v in out.items() if k in fields} if not minimal: - if config["gui"]["library"]["include_paths"].get(bool): - out["path"] = util.displayable_path(out["path"]) - else: - del out["path"] + out["path"] = util.displayable_path(out["path"]) for key, value in out.items(): if isinstance(out[key], bytes): @@ -90,11 +99,11 @@ def _rep(obj, expand=False, minimal=False): return out elif isinstance(obj, beets.library.Album): - if not minimal: - if config["gui"]["library"]["include_paths"].get(bool): - out["artpath"] = util.displayable_path(out["artpath"]) - else: - del out["artpath"] + if minimal: + fields = ["id", "name", "albumartist", "year"] + out = {k: v for k, v in out.items() if k in fields} + else: + out["artpath"] = util.displayable_path(out["artpath"]) if expand: out["items"] = [ @@ -218,8 +227,21 @@ def resource_query(name, patchable=False): def make_responder(query_func): def responder(queries): + # we set the route to use a path converter before us, + # so queries is a single string. + # edgecase: trailing escape character `\` would crash. we should + # also avoid this in the frontend. + if ( + queries.endswith("\\") + and (len(queries) - len(queries.rstrip("\\"))) % 2 == 1 + ): + # only remove the last character if it is a single escape character + queries = queries[:-1] + entities = query_func(queries) + log.debug(queries) + if get_method() == "DELETE": if config["gui"]["library"]["readonly"].get(bool): return abort(405) @@ -409,7 +431,7 @@ def item_file(item_id): return response -@library_bp.route("/item/query/", methods=["GET", "DELETE", "PATCH"]) +@library_bp.route("/item/query/", methods=["GET", "DELETE", "PATCH"]) @resource_query("items", patchable=True) def item_query(queries): return g.lib.items(queries) @@ -453,21 +475,12 @@ def all_albums(): return g.lib.albums() -@library_bp.route("/album/query/", methods=["GET", "DELETE"]) +@library_bp.route("/album/query/", methods=["GET", "DELETE"]) @resource_query("albums") def album_query(queries): return g.lib.albums(queries) -@library_bp.route("/album//art") -def album_art(album_id): - album = g.lib.get_album(album_id) - if album and album.artpath: - return send_file(album.artpath.decode()) - else: - return abort(404) - - @library_bp.route("/album/values/") def album_unique_field_values(key): sort_key = request.args.get("sort_key", key) @@ -487,6 +500,67 @@ def album_items(album_id): return abort(404) +# ------------------------------------------------------------------------------------ # +# Artwork # +# ------------------------------------------------------------------------------------ # + + +@library_bp.route("/item//art") +def item_art(item_id): + log.debug(f"Item art query for '{item_id}'") + item: beets.library.Item = g.lib.get_item(item_id) + item_path = util.py3_path(item.path) + if not os.path.exists(item_path): + return abort(404, description="Media file not found") + mediafile = MediaFile(item_path) + if mediafile.art: + return _send_image(BytesIO(mediafile.art)) + else: + abort(404, description="Item has no cover art") + + +@library_bp.route("/album//art") +def album_art(album_id): + log.debug(f"Art art query for album id '{album_id}'") + album = g.lib.get_album(album_id) + if album and album.artpath: + return _send_image(BytesIO(album.artpath.decode())) + elif album: + # Check the first item in the album for embedded cover art + try: + first_item: beets.library.Item = album.items()[0] + item_path = util.py3_path(first_item.path) + if not os.path.exists(item_path): + return abort(404, description="Media file not found") + mediafile = MediaFile(item_path) + if mediafile.art: + return _send_image(BytesIO(mediafile.art)) + else: + return abort(404, description="Item has no cover art") + except: + return abort(500, description="Failed to get album items") + + else: + return abort(404, description="No art for this album id, or id does not exist") + + +def _send_image(img_data: BytesIO): + max_size = (200, 200) + img = _resize(img_data, max_size) + response = make_response(send_file(img, mimetype="image/jpeg")) + response.headers["Cache-Control"] = "public, max-age=86400" + return response + + +def _resize(img_data: BytesIO, size: tuple[int, int]) -> BytesIO: + image = PILImage.open(img_data) + image.thumbnail(size) + image_io = BytesIO() + image.save(image_io, format="JPEG") + image_io.seek(0) + return image_io + + # ------------------------------------------------------------------------------------ # # Hierachical API: artist > album > track # # ------------------------------------------------------------------------------------ # diff --git a/backend/beets_flask/terminal.py b/backend/beets_flask/terminal.py index d0b6232..8a5e972 100644 --- a/backend/beets_flask/terminal.py +++ b/backend/beets_flask/terminal.py @@ -48,11 +48,23 @@ def register_tmux(): pane = window.active_pane or window.split_window(attach=True) +def is_session_alive(): + try: + if len(session.windows) > 0: # type: ignore + # session.windows should raise if the session is not alive. + return True + else: + return False + except: + return False + + def emit_output(): try: - # this approach to capture screen content keeps extra whitespaces, but we can - # fix that client side. - current = pane.cmd("capture-pane", "-p", "-N", "-T", "-e").stdout + if is_session_alive(): + current = pane.cmd("capture-pane", "-p", "-N", "-T", "-e").stdout + else: + current = ["Session ended. Reload page to restart!"] sio.emit("ptyOutput", {"output": current}, namespace="/terminal") except Exception as e: log.error(f"Error reading from pty: {e}") @@ -69,7 +81,10 @@ def emit_output_continuously(sleep_seconds=0.1): while True: sio.sleep(sleep_seconds) # type: ignore try: - current = pane.cmd("capture-pane", "-p", "-N", "-T", "-e").stdout + if is_session_alive(): + current = pane.cmd("capture-pane", "-p", "-N", "-T", "-e").stdout + else: + current = ["Session ended. Reload page to restart!"] if current != prev: sio.emit("ptyOutput", {"output": current}, namespace="/terminal") emit_cursor_position() @@ -132,6 +147,7 @@ def resend_output(sid): def connect(sid, environ): """new client connected""" log.debug(f"TerminalSocket new client connected {sid}") + register_tmux() @sio.on("disconnect", namespace="/terminal") # type: ignore diff --git a/configs/default.yaml b/configs/default.yaml index c580904..c35ab5a 100644 --- a/configs/default.yaml +++ b/configs/default.yaml @@ -10,7 +10,6 @@ gui: library: readonly: no - include_paths: yes tags: expand_tags: yes @@ -24,13 +23,4 @@ gui: concat_nested_folders: yes expand_files: no - # you must set inbox folders and the music directory in *your* beets config! - # (we only keep it around so the container can launch when nothing is configured) - folders: - Placeholder: - name: "Please configure your inbox!" - path: "/music/inbox" - autotag: no # no | "preview" | "import" - -directory: /music/imported diff --git a/configs/example.yaml b/configs/example.yaml index 6939efe..7e31628 100644 --- a/configs/example.yaml +++ b/configs/example.yaml @@ -5,7 +5,6 @@ gui: library: readonly: no - include_paths: yes tags: expand_tags: yes # for tag groups, on page load, show tag details? @@ -203,3 +202,7 @@ match: # track_index: 1.0 # track_length: 2.0 # track_id: 5.0 + + +spotify: + source_weight: 0.0 # 0.0 gives spotify the same priority as musicbrainz diff --git a/docker-compose-dev.yaml b/docker-compose-dev.yaml index 9f7ddc4..c9c58b5 100644 --- a/docker-compose-dev.yaml +++ b/docker-compose-dev.yaml @@ -1,4 +1,3 @@ -version: "3" services: beets-flask: container_name: beets-flask diff --git a/docker-compose-tests.yaml b/docker-compose-tests.yaml index 1ea3809..87214de 100644 --- a/docker-compose-tests.yaml +++ b/docker-compose-tests.yaml @@ -1,4 +1,3 @@ -version: '3' services: beets-flask-tests: container_name: beets-flask-tests diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 2d92dba..8b96ae7 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -2,33 +2,52 @@ module.exports = { root: true, env: { browser: true, es2020: true }, extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended-type-checked', - 'plugin:@typescript-eslint/stylistic-type-checked', - 'plugin:react/recommended', - 'plugin:react/jsx-runtime', + "eslint:recommended", + "plugin:@typescript-eslint/recommended-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:react/recommended", + "plugin:react/jsx-runtime", "plugin:react-hooks/recommended", - 'plugin:@tanstack/eslint-plugin-query/recommended' + "plugin:@tanstack/eslint-plugin-query/recommended", ], - ignorePatterns: ['dist', '.eslintrc.cjs', 'tailwind.config.js', 'postcss.config.js', 'vite.config.ts', '*.md'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], + ignorePatterns: [ + "dist", + ".eslintrc.cjs", + "tailwind.config.js", + "postcss.config.js", + "vite.config.ts", + "*.md", + ], + parser: "@typescript-eslint/parser", + plugins: ["react-refresh", "simple-import-sort"], rules: { - 'react-refresh/only-export-components': [ - 0, - ], + "react-refresh/only-export-components": [0], "@typescript-eslint/no-empty-function": "off", + "simple-import-sort/imports": [ + "warn", + { + groups: [ + // External modules + ["^\\w", "^@[a-z]"], + // Internal modules + ["^@/[a-z]"], + ["^src"], + ["^\\."], + // Styles (ending with .css, .scss, .sass, or .less) + ["^.+\\.(css|scss|sass|less)$"], + ], + }, + ], }, settings: { react: { - version: '18.2', + version: "18.2", }, }, parserOptions: { ecmaVersion: "latest", sourceType: "module", - project: ["./tsconfig.json",], + project: ["./tsconfig.json"], tsconfigRootDir: __dirname, }, - -} +}; diff --git a/frontend/package.json b/frontend/package.json index 1efca93..9ca29d4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "0.0.2", + "version": "0.0.3", "type": "module", "scripts": { "dev": "vite --host --cors", @@ -47,6 +47,7 @@ "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.6", + "eslint-plugin-simple-import-sort": "^12.1.1", "postcss": "^8.4.38", "radix-themes-tw": "^0.2.3", "tailwindcss": "^3.4.3", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..8b05608 --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,5441 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@curvenote/ansi-to-react': + specifier: ^7.0.0 + version: 7.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@emotion/react': + specifier: ^11.11.4 + version: 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': + specifier: ^11.11.5 + version: 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@material-tailwind/react': + specifier: ^2.1.9 + version: 2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/material': + specifier: ^5.15.18 + version: 5.16.4(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collapsible': + specifier: ^1.0.3 + version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-hover-card': + specifier: ^1.0.7 + version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-query': + specifier: ^5.40.0 + version: 5.51.5(react@18.3.1) + '@tanstack/react-router': + specifier: ^1.34.3 + version: 1.45.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/router-cli': + specifier: ^1.43.1 + version: 1.45.2 + '@xterm/addon-fit': + specifier: ^0.10.0 + version: 0.10.0(@xterm/xterm@5.5.0) + '@xterm/xterm': + specifier: ^5.5.0 + version: 5.5.0 + lucide-react: + specifier: ^0.379.0 + version: 0.379.0(react@18.3.1) + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + react-virtualized-auto-sizer: + specifier: ^1.0.24 + version: 1.0.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-window: + specifier: ^1.8.10 + version: 1.8.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + sass: + specifier: ^1.77.2 + version: 1.77.8 + socket.io-client: + specifier: ^4.7.5 + version: 4.7.5 + zod: + specifier: ^3.23.8 + version: 3.23.8 + devDependencies: + '@tanstack/eslint-plugin-query': + specifier: ^5.35.6 + version: 5.51.1(eslint@8.57.0)(typescript@5.5.3) + '@tanstack/router-devtools': + specifier: ^1.34.4 + version: 1.45.4(@tanstack/react-router@1.45.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/router-vite-plugin': + specifier: ^1.34.1 + version: 1.45.3(vite@5.3.4(sass@1.77.8)) + '@types/react': + specifier: ^18.2.66 + version: 18.3.3 + '@types/react-dom': + specifier: ^18.2.22 + version: 18.3.0 + '@types/react-window': + specifier: ^1.8.8 + version: 1.8.8 + '@typescript-eslint/eslint-plugin': + specifier: ^7.2.0 + version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/parser': + specifier: ^7.2.0 + version: 7.16.1(eslint@8.57.0)(typescript@5.5.3) + '@vitejs/plugin-react-swc': + specifier: ^3.5.0 + version: 3.7.0(vite@5.3.4(sass@1.77.8)) + autoprefixer: + specifier: ^10.4.19 + version: 10.4.19(postcss@8.4.39) + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-plugin-react: + specifier: ^7.34.1 + version: 7.34.4(eslint@8.57.0) + eslint-plugin-react-hooks: + specifier: ^4.6.2 + version: 4.6.2(eslint@8.57.0) + eslint-plugin-react-refresh: + specifier: ^0.4.6 + version: 0.4.8(eslint@8.57.0) + eslint-plugin-simple-import-sort: + specifier: ^12.1.1 + version: 12.1.1(eslint@8.57.0) + postcss: + specifier: ^8.4.38 + version: 8.4.39 + radix-themes-tw: + specifier: ^0.2.3 + version: 0.2.3 + tailwindcss: + specifier: ^3.4.3 + version: 3.4.6 + typescript: + specifier: ^5.2.2 + version: 5.5.3 + vite: + specifier: ^5.2.0 + version: 5.3.4(sass@1.77.8) + vite-tsconfig-paths: + specifier: ^4.3.2 + version: 4.3.2(typescript@5.5.3)(vite@5.3.4(sass@1.77.8)) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.24.9': + resolution: {integrity: sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.24.9': + resolution: {integrity: sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.24.10': + resolution: {integrity: sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.24.8': + resolution: {integrity: sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-environment-visitor@7.24.7': + resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-function-name@7.24.7': + resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-hoist-variables@7.24.7': + resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.24.7': + resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.24.9': + resolution: {integrity: sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.24.8': + resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-simple-access@7.24.7': + resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-split-export-declaration@7.24.7': + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.24.8': + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.24.8': + resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.24.8': + resolution: {integrity: sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.24.8': + resolution: {integrity: sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.24.7': + resolution: {integrity: sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.24.7': + resolution: {integrity: sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.24.8': + resolution: {integrity: sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.24.7': + resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.24.8': + resolution: {integrity: sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.24.9': + resolution: {integrity: sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==} + engines: {node: '>=6.9.0'} + + '@curvenote/ansi-to-react@7.0.0': + resolution: {integrity: sha512-+m4V86QPmaZ7udMp7Yg81A31dLBGO8gglkPGWPzUJNxLCSyOrJ04pQmdZQelNBEA7MSGz8wf+6RHcuEaujdhHw==} + engines: {node: '>=16'} + peerDependencies: + react: ^16.3.2 || ^17.0.0 || ^18.0.0 + react-dom: ^16.3.2 || ^17.0.0 || ^18.0.0 + + '@emotion/babel-plugin@11.11.0': + resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} + + '@emotion/cache@11.11.0': + resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==} + + '@emotion/hash@0.9.1': + resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} + + '@emotion/is-prop-valid@0.8.8': + resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} + + '@emotion/is-prop-valid@1.2.2': + resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} + + '@emotion/memoize@0.7.4': + resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} + + '@emotion/memoize@0.8.1': + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} + + '@emotion/react@11.11.4': + resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.1.4': + resolution: {integrity: sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==} + + '@emotion/sheet@1.2.2': + resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} + + '@emotion/styled@11.11.5': + resolution: {integrity: sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + + '@emotion/use-insertion-effect-with-fallbacks@1.0.1': + resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.2.1': + resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==} + + '@emotion/weak-memoize@0.3.1': + resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.11.0': + resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.0': + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@floating-ui/core@1.6.4': + resolution: {integrity: sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==} + + '@floating-ui/dom@1.6.7': + resolution: {integrity: sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==} + + '@floating-ui/react-dom@1.3.0': + resolution: {integrity: sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react-dom@2.1.1': + resolution: {integrity: sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.19.0': + resolution: {integrity: sha512-fgYvN4ksCi5OvmPXkyOT8o5a8PSKHMzPHt+9mR6KYWdF16IAjWRLZPAAziI2sznaWT23drRFrYw64wdvYqqaQw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.4': + resolution: {integrity: sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==} + + '@humanwhocodes/config-array@0.11.14': + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@material-tailwind/react@2.1.9': + resolution: {integrity: sha512-3uPlJE9yK4JF9DEQO4I1QbjR8o05+4fysLqoZ0v38TDOLE2tvDRhTBVhn6Mp9vSsq5CoJOKgemG7kbkOFAji4A==} + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 + + '@motionone/animation@10.18.0': + resolution: {integrity: sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==} + + '@motionone/dom@10.12.0': + resolution: {integrity: sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==} + + '@motionone/easing@10.18.0': + resolution: {integrity: sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==} + + '@motionone/generators@10.18.0': + resolution: {integrity: sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg==} + + '@motionone/types@10.17.1': + resolution: {integrity: sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==} + + '@motionone/utils@10.18.0': + resolution: {integrity: sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==} + + '@mui/core-downloads-tracker@5.16.4': + resolution: {integrity: sha512-rNdHXhclwjEZnK+//3SR43YRx0VtjdHnUFhMSGYmAMJve+KiwEja/41EYh8V3pZKqF2geKyfcFUenTfDTYUR4w==} + + '@mui/material@5.16.4': + resolution: {integrity: sha512-dBnh3/zRYgEVIS3OE4oTbujse3gifA0qLMmuUk13ywsDCbngJsdgwW5LuYeiT5pfA8PGPGSqM7mxNytYXgiMCw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/private-theming@5.16.4': + resolution: {integrity: sha512-ZsAm8cq31SJ37SVWLRlu02v9SRthxnfQofaiv14L5Bht51B0dz6yQEoVU/V8UduZDCCIrWkBHuReVfKhE/UuXA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/styled-engine@5.16.4': + resolution: {integrity: sha512-0+mnkf+UiAmTVB8PZFqOhqf729Yh0Cxq29/5cA3VAyDVTRIUUQ8FXQhiAhUIbijFmM72rY80ahFPXIm4WDbzcA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/system@5.16.4': + resolution: {integrity: sha512-ET1Ujl2/8hbsD611/mqUuNArMCGv/fIWO/f8B3ZqF5iyPHM2aS74vhTNyjytncc4i6dYwGxNk+tLa7GwjNS0/w==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/types@7.2.15': + resolution: {integrity: sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@5.16.4': + resolution: {integrity: sha512-nlppYwq10TBIFqp7qxY0SvbACOXeOjeVL3pOcDsK0FT8XjrEXh9/+lkg8AEIzD16z7YfiJDQjaJG2OLkE7BxNg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@radix-ui/primitive@1.1.0': + resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + + '@radix-ui/react-arrow@1.1.0': + resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.0': + resolution: {integrity: sha512-zQY7Epa8sTL0mq4ajSJpjgn2YmCgyrG7RsQgLp3C0LQVkG7+Tf6Pv1CeNWZLyqMjhdPkBa5Lx7wYBeSu7uCSTA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.0': + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.0': + resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.0': + resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.1.1': + resolution: {integrity: sha512-IwzAOP97hQpDADYVKrEEHUH/b2LA+9MgB0LgdmnbFO2u/3M5hmEofjjr2M6CyzUblaAqJdFm6B7oFtU72DPXrA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.0': + resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-popper@1.2.0': + resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.1': + resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.0': + resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.0.0': + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.1.0': + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.0': + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.0': + resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.0': + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.0': + resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.0': + resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/rect@1.1.0': + resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + + '@rollup/rollup-android-arm-eabi@4.18.1': + resolution: {integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.18.1': + resolution: {integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.18.1': + resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.18.1': + resolution: {integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.18.1': + resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.18.1': + resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.18.1': + resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.18.1': + resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.18.1': + resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.18.1': + resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.18.1': + resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.18.1': + resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.18.1': + resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.18.1': + resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.18.1': + resolution: {integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.18.1': + resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==} + cpu: [x64] + os: [win32] + + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + + '@swc/core-darwin-arm64@1.6.13': + resolution: {integrity: sha512-SOF4buAis72K22BGJ3N8y88mLNfxLNprTuJUpzikyMGrvkuBFNcxYtMhmomO0XHsgLDzOJ+hWzcgjRNzjMsUcQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.6.13': + resolution: {integrity: sha512-AW8akFSC+tmPE6YQQvK9S2A1B8pjnXEINg+gGgw0KRUUXunvu1/OEOeC5L2Co1wAwhD7bhnaefi06Qi9AiwOag==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.6.13': + resolution: {integrity: sha512-f4gxxvDXVUm2HLYXRd311mSrmbpQF2MZ4Ja6XCQz1hWAxXdhRl1gpnZ+LH/xIfGSwQChrtLLVrkxdYUCVuIjFg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.6.13': + resolution: {integrity: sha512-Nf/eoW2CbG8s+9JoLtjl9FByBXyQ5cjdBsA4efO7Zw4p+YSuXDgc8HRPC+E2+ns0praDpKNZtLvDtmF2lL+2Gg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.6.13': + resolution: {integrity: sha512-2OysYSYtdw79prJYuKIiux/Gj0iaGEbpS2QZWCIY4X9sGoETJ5iMg+lY+YCrIxdkkNYd7OhIbXdYFyGs/w5LDg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.6.13': + resolution: {integrity: sha512-PkR4CZYJNk5hcd2+tMWBpnisnmYsUzazI1O5X7VkIGFcGePTqJ/bWlfUIVVExWxvAI33PQFzLbzmN5scyIUyGQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.6.13': + resolution: {integrity: sha512-OdsY7wryTxCKwGQcwW9jwWg3cxaHBkTTHi91+5nm7hFPpmZMz1HivJrWAMwVE7iXFw+M4l6ugB/wCvpYrUAAjA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.6.13': + resolution: {integrity: sha512-ap6uNmYjwk9M/+bFEuWRNl3hq4VqgQ/Lk+ID/F5WGqczNr0L7vEf+pOsRAn0F6EV+o/nyb3ePt8rLhE/wjHpPg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.6.13': + resolution: {integrity: sha512-IJ8KH4yIUHTnS/U1jwQmtbfQals7zWPG0a9hbEfIr4zI0yKzjd83lmtS09lm2Q24QBWOCFGEEbuZxR4tIlvfzA==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.6.13': + resolution: {integrity: sha512-f6/sx6LMuEnbuxtiSL/EkR0Y6qUHFw1XVrh6rwzKXptTipUdOY+nXpKoh+1UsBm/r7H0/5DtOdrn3q5ZHbFZjQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.6.13': + resolution: {integrity: sha512-eailUYex6fkfaQTev4Oa3mwn0/e3mQU4H8y1WPuImYQESOQDtVrowwUGDSc19evpBbHpKtwM+hw8nLlhIsF+Tw==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.9': + resolution: {integrity: sha512-qKnCno++jzcJ4lM4NTfYifm1EFSCeIfKiAHAfkENZAV5Kl9PjJIyd2yeeVv6c/2CckuLyv2NmRC5pv6pm2WQBg==} + + '@tanstack/eslint-plugin-query@5.51.1': + resolution: {integrity: sha512-M1IBJTtD9V69Zf4Efnkizg+DKdn4yTt+OfD0my5hZRok4wPZ44+Y8XNUFpwy6nsEV/Qt+KsS8IWPjd+eERQUSA==} + peerDependencies: + eslint: ^8 || ^9 + + '@tanstack/history@1.45.3': + resolution: {integrity: sha512-n4XXInV9irIq0obRvINIkESkGk280Q+xkIIbswmM0z9nAu2wsIRZNvlmPrtYh6bgNWtItOWWoihFUjLTW8g6Jg==} + engines: {node: '>=12'} + + '@tanstack/query-core@5.51.5': + resolution: {integrity: sha512-qovOto6hFet2zA4Pf3cDO+qkOqskO6xP39PlKnr6YKPtjRsePWyZnTaMf59+VnlOLY8gpku1I4WPC4dqBXo4FQ==} + + '@tanstack/react-query@5.51.5': + resolution: {integrity: sha512-jaYYPGF55HT3DSV2NxFHa7zGRUm6LiRENw1rspkzqNiOU93umP5YCdE/l4S61/ZdLnjzwYIM4FU96EQt+imq5Q==} + peerDependencies: + react: ^18.0.0 + + '@tanstack/react-router@1.45.4': + resolution: {integrity: sha512-tr4EtH9hAyFRp1eB65aJKNXPT1qZtBF51585kUI1dKMftB2M5BhuXO+5tSIaOB2kkQyHGndzXDN5u6rW09M2dA==} + engines: {node: '>=12'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + '@tanstack/react-store@0.5.5': + resolution: {integrity: sha512-1orYXGatBqXCYKuroFwV8Ll/6aDa5E3pU6RR4h7RvRk7TmxF1+zLCsWALZaeijXkySNMGmvawSbUXRypivg2XA==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + + '@tanstack/router-cli@1.45.2': + resolution: {integrity: sha512-s/HK8xsgIwXTghiRJ5zzAUn6xLd2DPkDFEnpxZJXgE2/2UIrsv0WoGXixMPNselCUe80/FJ+Zi2Fa2FPSeX05w==} + engines: {node: '>=12'} + hasBin: true + + '@tanstack/router-devtools@1.45.4': + resolution: {integrity: sha512-FaGhm0EgiPhD3VLClNOT6DTNBVjPr5KFqnLD5UgwTGxjclhoer/YCXSA3E0fCluk5fn5H+rhk8B/IZFRejIFfg==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/react-router': ^1.45.4 + react: '>=18' + react-dom: '>=18' + + '@tanstack/router-generator@1.45.2': + resolution: {integrity: sha512-Z8/6FSh2/XH7DsQUpIXp2R8r93PxTdWVgJnMtXsAdmlX0/pSPemaq7KiWlpJK+KyekfcKACTIjnfbmKJg18MHQ==} + engines: {node: '>=12'} + + '@tanstack/router-plugin@1.45.3': + resolution: {integrity: sha512-W2LIvYVc8Vus5FRXbQ0MXJi5i6/EenP9RQrqFSKLZv12uih06avxcMXtFS40cFVLxqC9WIG+DQ30hwkiPyWICw==} + engines: {node: '>=12'} + peerDependencies: + '@rsbuild/core': '>=0.7.9' + vite: '>=5.0.13' + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + vite: + optional: true + webpack: + optional: true + + '@tanstack/router-vite-plugin@1.45.3': + resolution: {integrity: sha512-HAZ5F7wtQNBM8MubjEGS8lD+s8bqCVpHgfZuYcWxLmvHlDBJeaw5EAP8AXOt3PXWKwipipghLJ3O7JO3qh5NSg==} + engines: {node: '>=12'} + + '@tanstack/store@0.5.5': + resolution: {integrity: sha512-EOSrgdDAJExbvRZEQ/Xhh9iZchXpMN+ga1Bnk8Nmygzs8TfiE6hbzThF+Pr2G19uHL6+DTDTHhJ8VQiOd7l4tA==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.12': + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + + '@types/react-dom@18.3.0': + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + + '@types/react-transition-group@4.4.10': + resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==} + + '@types/react-window@1.8.8': + resolution: {integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==} + + '@types/react@18.3.3': + resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + + '@typescript-eslint/eslint-plugin@7.16.1': + resolution: {integrity: sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@7.16.1': + resolution: {integrity: sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@7.16.1': + resolution: {integrity: sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/scope-manager@8.0.0-alpha.30': + resolution: {integrity: sha512-FGW/iPWGyPFamAVZ60oCAthMqQrqafUGebF8UKuq/ha+e9SVG6YhJoRzurlQXOVf8dHfOhJ0ADMXyFnMc53clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@7.16.1': + resolution: {integrity: sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@7.16.1': + resolution: {integrity: sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/types@8.0.0-alpha.30': + resolution: {integrity: sha512-4WzLlw27SO9pK9UFj/Hu7WGo8WveT0SEiIpFVsV2WwtQmLps6kouwtVCB8GJPZKJyurhZhcqCoQVQFmpv441Vg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@7.16.1': + resolution: {integrity: sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/typescript-estree@8.0.0-alpha.30': + resolution: {integrity: sha512-WSXbc9ZcXI+7yC+6q95u77i8FXz6HOLsw3ST+vMUlFy1lFbXyFL/3e6HDKQCm2Clt0krnoCPiTGvIn+GkYPn4Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@7.16.1': + resolution: {integrity: sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/utils@8.0.0-alpha.30': + resolution: {integrity: sha512-rfhqfLqFyXhHNDwMnHiVGxl/Z2q/3guQ1jLlGQ0hi9Rb7inmwz42crM+NnLPR+2vEnwyw1P/g7fnQgQ3qvFx4g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + '@typescript-eslint/visitor-keys@7.16.1': + resolution: {integrity: sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/visitor-keys@8.0.0-alpha.30': + resolution: {integrity: sha512-XZuNurZxBqmr6ZIRIwWFq7j5RZd6ZlkId/HZEWyfciK+CWoyOxSF9Pv2VXH9Rlu2ZG2PfbhLz2Veszl4Pfn7yA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + '@vitejs/plugin-react-swc@3.7.0': + resolution: {integrity: sha512-yrknSb3Dci6svCd/qhHqhFPDSw0QtjumcqdKMoNNzmOl5lMXTTiqzjWtG4Qask2HdvvzaNgSunbQGet8/GrKdA==} + peerDependencies: + vite: ^4 || ^5 + + '@xterm/addon-fit@0.10.0': + resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/xterm@5.5.0': + resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + anser@2.1.1: + resolution: {integrity: sha512-nqLm4HxOTpeLOxcmB3QWmV5TcDFhW9y/fyQ+hivtDFcK4OQ+pQ5fzPnXHM1Mfcm0VkLtvVi1TCPr++Qy0Q/3EQ==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + + array.prototype.toreversed@1.1.2: + resolution: {integrity: sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + + autoprefixer@10.4.19: + resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + babel-dead-code-elimination@1.0.6: + resolution: {integrity: sha512-JxFi9qyRJpN0LjEbbjbN8g0ux71Qppn9R8Qe3k6QzHg2CaKsbUQtbn307LQGiDLGjV6JCtEFqfxzVig9MyDCHQ==} + + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.23.2: + resolution: {integrity: sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001642: + resolution: {integrity: sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + classnames@2.3.2: + resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.2.2: + resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} + engines: {node: '>=0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.4.829: + resolution: {integrity: sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + engine.io-client@6.5.4: + resolution: {integrity: sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==} + + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.0.19: + resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-carriage@1.3.1: + resolution: {integrity: sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@4.6.2: + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + + eslint-plugin-react-refresh@0.4.8: + resolution: {integrity: sha512-MIKAclwaDFIiYtVBLzDdm16E+Ty4GwhB6wZlCAG1R3Ur+F9Qbo6PRxpA5DK7XtDgm+WlCoAY2WxAwqhmIDHg6Q==} + peerDependencies: + eslint: '>=7' + + eslint-plugin-react@7.34.4: + resolution: {integrity: sha512-Np+jo9bUwJNxCsT12pXtrGhJgT3T44T1sHhn1Ssr42XFn8TES0267wPGo5nNrMHi8qkyimDAX2BUmkf9pSaVzA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-plugin-simple-import-sort@12.1.1: + resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==} + peerDependencies: + eslint: '>=5.0.0' + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + foreground-child@3.2.1: + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + engines: {node: '>=14'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + framer-motion@6.5.1: + resolution: {integrity: sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==} + peerDependencies: + react: '>=16.8 || ^17.0.0 || ^18.0.0' + react-dom: '>=16.8 || ^17.0.0 || ^18.0.0' + + framesync@6.0.1: + resolution: {integrity: sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + goober@2.1.14: + resolution: {integrity: sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==} + peerDependencies: + csstype: ^3.0.10 + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hey-listen@1.0.8: + resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + + immutable@4.3.6: + resolution: {integrity: sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.14.0: + resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.2: + resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.379.0: + resolution: {integrity: sha512-KcdeVPqmhRldldAAgptb8FjIunM2x2Zy26ZBh1RsEUcdLIvsEmbcw7KpzFYUy5BbpGeWhPu9Z9J5YXfStiXwhg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + + material-ripple-effects@2.0.1: + resolution: {integrity: sha512-hHlUkZAuXbP94lu02VgrPidbZ3hBtgXBtjlwR8APNqOIgDZMV8MCIcsclL8FmGJQHvnORyvoQgC965vPsiyXLQ==} + + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.17: + resolution: {integrity: sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + popmotion@11.0.3: + resolution: {integrity: sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==} + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.0.1: + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.1: + resolution: {integrity: sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.39: + resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + radix-themes-tw@0.2.3: + resolution: {integrity: sha512-n3Luc4LzB5Zcv6SSvHWGey4Wzx1qtt+3Yj6OmRXzAD6RtZ2+RkT0OoilyxlaKr+kqJuvKhF4YfLbcMAkFu5gEw==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react-virtualized-auto-sizer@1.0.24: + resolution: {integrity: sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==} + peerDependencies: + react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 + react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 + + react-window@1.8.10: + resolution: {integrity: sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==} + engines: {node: '>8.0.0'} + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + reflect.getprototypeof@1.0.6: + resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} + engines: {node: '>= 0.4'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.18.1: + resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} + + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + + sass@1.77.8: + resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + socket.io-client@4.7.5: + resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==} + engines: {node: '>=10.0.0'} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.matchall@4.0.11: + resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + style-value-types@5.0.0: + resolution: {integrity: sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==} + + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + + tailwind-merge@1.8.1: + resolution: {integrity: sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww==} + + tailwindcss@3.4.6: + resolution: {integrity: sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==} + engines: {node: '>=14.0.0'} + hasBin: true + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsconfck@3.1.1: + resolution: {integrity: sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} + + typescript@5.5.3: + resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + + unplugin@1.11.0: + resolution: {integrity: sha512-3r7VWZ/webh0SGgJScpWl2/MRCZK5d3ZYFcNaeci/GQ7Teop7zf0Nl2pUuz7G21BwPd9pcUPOC5KmJ2L3WgC5g==} + engines: {node: '>=14.0.0'} + + update-browserslist-db@1.1.0: + resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-sync-external-store@1.2.2: + resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite-tsconfig-paths@4.3.2: + resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@5.3.4: + resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-builtin-type@1.1.3: + resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xmlhttprequest-ssl@2.0.0: + resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} + engines: {node: '>=0.4.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yaml@2.4.5: + resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/compat-data@7.24.9': {} + + '@babel/core@7.24.9': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.10 + '@babel/helper-compilation-targets': 7.24.8 + '@babel/helper-module-transforms': 7.24.9(@babel/core@7.24.9) + '@babel/helpers': 7.24.8 + '@babel/parser': 7.24.8 + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.8 + '@babel/types': 7.24.9 + convert-source-map: 2.0.0 + debug: 4.3.5 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.24.10': + dependencies: + '@babel/types': 7.24.9 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + + '@babel/helper-compilation-targets@7.24.8': + dependencies: + '@babel/compat-data': 7.24.9 + '@babel/helper-validator-option': 7.24.8 + browserslist: 4.23.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-environment-visitor@7.24.7': + dependencies: + '@babel/types': 7.24.9 + + '@babel/helper-function-name@7.24.7': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.9 + + '@babel/helper-hoist-variables@7.24.7': + dependencies: + '@babel/types': 7.24.9 + + '@babel/helper-module-imports@7.24.7': + dependencies: + '@babel/traverse': 7.24.8 + '@babel/types': 7.24.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.24.9(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.24.8': {} + + '@babel/helper-simple-access@7.24.7': + dependencies: + '@babel/traverse': 7.24.8 + '@babel/types': 7.24.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-split-export-declaration@7.24.7': + dependencies: + '@babel/types': 7.24.9 + + '@babel/helper-string-parser@7.24.8': {} + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/helper-validator-option@7.24.8': {} + + '@babel/helpers@7.24.8': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.9 + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@babel/parser@7.24.8': + dependencies: + '@babel/types': 7.24.9 + + '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.24.9)': + dependencies: + '@babel/core': 7.24.9 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/runtime@7.24.8': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.24.8 + '@babel/types': 7.24.9 + + '@babel/traverse@7.24.8': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.10 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/parser': 7.24.8 + '@babel/types': 7.24.9 + debug: 4.3.5 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.24.9': + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@curvenote/ansi-to-react@7.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + anser: 2.1.1 + escape-carriage: 1.3.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@emotion/babel-plugin@11.11.0': + dependencies: + '@babel/helper-module-imports': 7.24.7 + '@babel/runtime': 7.24.8 + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/serialize': 1.1.4 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.11.0': + dependencies: + '@emotion/memoize': 0.8.1 + '@emotion/sheet': 1.2.2 + '@emotion/utils': 1.2.1 + '@emotion/weak-memoize': 0.3.1 + stylis: 4.2.0 + + '@emotion/hash@0.9.1': {} + + '@emotion/is-prop-valid@0.8.8': + dependencies: + '@emotion/memoize': 0.7.4 + optional: true + + '@emotion/is-prop-valid@1.2.2': + dependencies: + '@emotion/memoize': 0.8.1 + + '@emotion/memoize@0.7.4': + optional: true + + '@emotion/memoize@0.8.1': {} + + '@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.8 + '@emotion/babel-plugin': 11.11.0 + '@emotion/cache': 11.11.0 + '@emotion/serialize': 1.1.4 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.3.1) + '@emotion/utils': 1.2.1 + '@emotion/weak-memoize': 0.3.1 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.1.4': + dependencies: + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/unitless': 0.8.1 + '@emotion/utils': 1.2.1 + csstype: 3.1.3 + + '@emotion/sheet@1.2.2': {} + + '@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.8 + '@emotion/babel-plugin': 11.11.0 + '@emotion/is-prop-valid': 1.2.2 + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/serialize': 1.1.4 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.3.1) + '@emotion/utils': 1.2.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.8.1': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@emotion/utils@1.2.1': {} + + '@emotion/weak-memoize@0.3.1': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.11.0': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.3.5 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.0': {} + + '@floating-ui/core@1.6.4': + dependencies: + '@floating-ui/utils': 0.2.4 + + '@floating-ui/dom@1.6.7': + dependencies: + '@floating-ui/core': 1.6.4 + '@floating-ui/utils': 0.2.4 + + '@floating-ui/react-dom@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.6.7 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/react-dom@2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.6.7 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/react@0.19.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tabbable: 6.2.0 + + '@floating-ui/utils@0.2.4': {} + + '@humanwhocodes/config-array@0.11.14': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.3.5 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@material-tailwind/react@2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react': 0.19.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.3.2 + deepmerge: 4.2.2 + framer-motion: 6.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + material-ripple-effects: 2.0.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tailwind-merge: 1.8.1 + + '@motionone/animation@10.18.0': + dependencies: + '@motionone/easing': 10.18.0 + '@motionone/types': 10.17.1 + '@motionone/utils': 10.18.0 + tslib: 2.6.3 + + '@motionone/dom@10.12.0': + dependencies: + '@motionone/animation': 10.18.0 + '@motionone/generators': 10.18.0 + '@motionone/types': 10.17.1 + '@motionone/utils': 10.18.0 + hey-listen: 1.0.8 + tslib: 2.6.3 + + '@motionone/easing@10.18.0': + dependencies: + '@motionone/utils': 10.18.0 + tslib: 2.6.3 + + '@motionone/generators@10.18.0': + dependencies: + '@motionone/types': 10.17.1 + '@motionone/utils': 10.18.0 + tslib: 2.6.3 + + '@motionone/types@10.17.1': {} + + '@motionone/utils@10.18.0': + dependencies: + '@motionone/types': 10.17.1 + hey-listen: 1.0.8 + tslib: 2.6.3 + + '@mui/core-downloads-tracker@5.16.4': {} + + '@mui/material@5.16.4(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.8 + '@mui/core-downloads-tracker': 5.16.4 + '@mui/system': 5.16.4(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@mui/types': 7.2.15(@types/react@18.3.3) + '@mui/utils': 5.16.4(@types/react@18.3.3)(react@18.3.1) + '@popperjs/core': 2.11.8 + '@types/react-transition-group': 4.4.10 + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@types/react': 18.3.3 + + '@mui/private-theming@5.16.4(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.8 + '@mui/utils': 5.16.4(@types/react@18.3.3)(react@18.3.1) + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@mui/styled-engine@5.16.4(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.8 + '@emotion/cache': 11.11.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + + '@mui/system@5.16.4(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.8 + '@mui/private-theming': 5.16.4(@types/react@18.3.3)(react@18.3.1) + '@mui/styled-engine': 5.16.4(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1) + '@mui/types': 7.2.15(@types/react@18.3.3) + '@mui/utils': 5.16.4(@types/react@18.3.3)(react@18.3.1) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@types/react': 18.3.3 + + '@mui/types@7.2.15(@types/react@18.3.3)': + optionalDependencies: + '@types/react': 18.3.3 + + '@mui/utils@5.16.4(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.8 + '@types/prop-types': 15.7.12 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@popperjs/core@2.11.8': {} + + '@radix-ui/primitive@1.1.0': {} + + '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-collapsible@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-context@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-hover-card@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-id@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/rect': 1.1.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-portal@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-slot@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.0 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-use-size@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/rect@1.1.0': {} + + '@rollup/rollup-android-arm-eabi@4.18.1': + optional: true + + '@rollup/rollup-android-arm64@4.18.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.18.1': + optional: true + + '@rollup/rollup-darwin-x64@4.18.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.18.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.18.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.18.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.18.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.18.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.18.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.18.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.18.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.18.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.18.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.18.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.18.1': + optional: true + + '@socket.io/component-emitter@3.1.2': {} + + '@swc/core-darwin-arm64@1.6.13': + optional: true + + '@swc/core-darwin-x64@1.6.13': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.6.13': + optional: true + + '@swc/core-linux-arm64-gnu@1.6.13': + optional: true + + '@swc/core-linux-arm64-musl@1.6.13': + optional: true + + '@swc/core-linux-x64-gnu@1.6.13': + optional: true + + '@swc/core-linux-x64-musl@1.6.13': + optional: true + + '@swc/core-win32-arm64-msvc@1.6.13': + optional: true + + '@swc/core-win32-ia32-msvc@1.6.13': + optional: true + + '@swc/core-win32-x64-msvc@1.6.13': + optional: true + + '@swc/core@1.6.13': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.9 + optionalDependencies: + '@swc/core-darwin-arm64': 1.6.13 + '@swc/core-darwin-x64': 1.6.13 + '@swc/core-linux-arm-gnueabihf': 1.6.13 + '@swc/core-linux-arm64-gnu': 1.6.13 + '@swc/core-linux-arm64-musl': 1.6.13 + '@swc/core-linux-x64-gnu': 1.6.13 + '@swc/core-linux-x64-musl': 1.6.13 + '@swc/core-win32-arm64-msvc': 1.6.13 + '@swc/core-win32-ia32-msvc': 1.6.13 + '@swc/core-win32-x64-msvc': 1.6.13 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.9': + dependencies: + '@swc/counter': 0.1.3 + + '@tanstack/eslint-plugin-query@5.51.1(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@typescript-eslint/utils': 8.0.0-alpha.30(eslint@8.57.0)(typescript@5.5.3) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@tanstack/history@1.45.3': {} + + '@tanstack/query-core@5.51.5': {} + + '@tanstack/react-query@5.51.5(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.51.5 + react: 18.3.1 + + '@tanstack/react-router@1.45.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/history': 1.45.3 + '@tanstack/react-store': 0.5.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + '@tanstack/react-store@0.5.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/store': 0.5.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-sync-external-store: 1.2.2(react@18.3.1) + + '@tanstack/router-cli@1.45.2': + dependencies: + '@tanstack/router-generator': 1.45.2 + chokidar: 3.6.0 + yargs: 17.7.2 + + '@tanstack/router-devtools@1.45.4(@tanstack/react-router@1.45.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/react-router': 1.45.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + clsx: 2.1.1 + goober: 2.1.14(csstype@3.1.3) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - csstype + + '@tanstack/router-generator@1.45.2': + dependencies: + prettier: 3.3.3 + zod: 3.23.8 + + '@tanstack/router-plugin@1.45.3(vite@5.3.4(sass@1.77.8))': + dependencies: + '@babel/core': 7.24.9 + '@babel/generator': 7.24.10 + '@babel/parser': 7.24.8 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.9) + '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.9) + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.8 + '@babel/types': 7.24.9 + '@tanstack/router-generator': 1.45.2 + '@types/babel__core': 7.20.5 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + babel-dead-code-elimination: 1.0.6 + chokidar: 3.6.0 + unplugin: 1.11.0 + zod: 3.23.8 + optionalDependencies: + vite: 5.3.4(sass@1.77.8) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-vite-plugin@1.45.3(vite@5.3.4(sass@1.77.8))': + dependencies: + '@tanstack/router-plugin': 1.45.3(vite@5.3.4(sass@1.77.8)) + transitivePeerDependencies: + - '@rsbuild/core' + - supports-color + - vite + - webpack + + '@tanstack/store@0.5.5': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.24.8 + '@babel/types': 7.24.9 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.24.9 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.24.8 + '@babel/types': 7.24.9 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.24.9 + + '@types/estree@1.0.5': {} + + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.12': {} + + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 18.3.3 + + '@types/react-transition-group@4.4.10': + dependencies: + '@types/react': 18.3.3 + + '@types/react-window@1.8.8': + dependencies: + '@types/react': 18.3.3 + + '@types/react@18.3.3': + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + + '@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 7.16.1(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/scope-manager': 7.16.1 + '@typescript-eslint/type-utils': 7.16.1(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 7.16.1 + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.5.3) + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@typescript-eslint/scope-manager': 7.16.1 + '@typescript-eslint/types': 7.16.1 + '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) + '@typescript-eslint/visitor-keys': 7.16.1 + debug: 4.3.5 + eslint: 8.57.0 + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@7.16.1': + dependencies: + '@typescript-eslint/types': 7.16.1 + '@typescript-eslint/visitor-keys': 7.16.1 + + '@typescript-eslint/scope-manager@8.0.0-alpha.30': + dependencies: + '@typescript-eslint/types': 8.0.0-alpha.30 + '@typescript-eslint/visitor-keys': 8.0.0-alpha.30 + + '@typescript-eslint/type-utils@7.16.1(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) + '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.5.3) + debug: 4.3.5 + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@5.5.3) + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@7.16.1': {} + + '@typescript-eslint/types@8.0.0-alpha.30': {} + + '@typescript-eslint/typescript-estree@7.16.1(typescript@5.5.3)': + dependencies: + '@typescript-eslint/types': 7.16.1 + '@typescript-eslint/visitor-keys': 7.16.1 + debug: 4.3.5 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.5.3) + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.0.0-alpha.30(typescript@5.5.3)': + dependencies: + '@typescript-eslint/types': 8.0.0-alpha.30 + '@typescript-eslint/visitor-keys': 8.0.0-alpha.30 + debug: 4.3.5 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.5.3) + optionalDependencies: + typescript: 5.5.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@7.16.1(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@typescript-eslint/scope-manager': 7.16.1 + '@typescript-eslint/types': 7.16.1 + '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.3) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@8.0.0-alpha.30(eslint@8.57.0)(typescript@5.5.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@typescript-eslint/scope-manager': 8.0.0-alpha.30 + '@typescript-eslint/types': 8.0.0-alpha.30 + '@typescript-eslint/typescript-estree': 8.0.0-alpha.30(typescript@5.5.3) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@7.16.1': + dependencies: + '@typescript-eslint/types': 7.16.1 + eslint-visitor-keys: 3.4.3 + + '@typescript-eslint/visitor-keys@8.0.0-alpha.30': + dependencies: + '@typescript-eslint/types': 8.0.0-alpha.30 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.2.0': {} + + '@vitejs/plugin-react-swc@3.7.0(vite@5.3.4(sass@1.77.8))': + dependencies: + '@swc/core': 1.6.13 + vite: 5.3.4(sass@1.77.8) + transitivePeerDependencies: + - '@swc/helpers' + + '@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/xterm@5.5.0': {} + + acorn-jsx@5.3.2(acorn@8.12.1): + dependencies: + acorn: 8.12.1 + + acorn@8.12.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + anser@2.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-hidden@1.2.4: + dependencies: + tslib: 2.6.3 + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + is-string: 1.0.7 + + array-union@2.1.0: {} + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.flat@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.flatmap@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.toreversed@1.1.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 + + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + + autoprefixer@10.4.19(postcss@8.4.39): + dependencies: + browserslist: 4.23.2 + caniuse-lite: 1.0.30001642 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.0.1 + postcss: 8.4.39 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 + + babel-dead-code-elimination@1.0.6: + dependencies: + '@babel/core': 7.24.9 + '@babel/parser': 7.24.8 + '@babel/traverse': 7.24.8 + '@babel/types': 7.24.9 + transitivePeerDependencies: + - supports-color + + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.24.8 + cosmiconfig: 7.1.0 + resolve: 1.22.8 + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.23.2: + dependencies: + caniuse-lite: 1.0.30001642 + electron-to-chromium: 1.4.829 + node-releases: 2.0.17 + update-browserslist-db: 1.1.0(browserslist@4.23.2) + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001642: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + classnames@2.3.2: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + debug@4.3.5: + dependencies: + ms: 2.1.2 + + deep-is@0.1.4: {} + + deepmerge@4.2.2: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + didyoumean@1.2.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dlv@1.1.3: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.24.8 + csstype: 3.1.3 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.4.829: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + engine.io-client@6.5.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.5 + engine.io-parser: 5.2.3 + ws: 8.17.1 + xmlhttprequest-ssl: 2.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + engine.io-parser@5.2.3: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 + globalthis: 1.0.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.2 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + + es-iterator-helpers@1.0.19: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-set-tostringtag: 2.0.3 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + iterator.prototype: 1.1.2 + safe-array-concat: 1.1.2 + + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.1.2: {} + + escape-carriage@1.3.1: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-plugin-react-refresh@0.4.8(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-plugin-react@7.34.4(eslint@8.57.0): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.2 + array.prototype.toreversed: 1.1.2 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.0.19 + eslint: 8.57.0 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + object.values: 1.2.0 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.11 + string.prototype.repeat: 1.0.0 + + eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.11.0 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.5 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 3.4.3 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.7 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-root@1.1.0: {} + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.1: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.2.1: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + fraction.js@4.3.7: {} + + framer-motion@6.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@motionone/dom': 10.12.0 + framesync: 6.0.1 + hey-listen: 1.0.8 + popmotion: 11.0.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + style-value-types: 5.0.0 + tslib: 2.6.3 + optionalDependencies: + '@emotion/is-prop-valid': 0.8.8 + + framesync@6.0.1: + dependencies: + tslib: 2.6.3 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.2.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@11.12.0: {} + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.0.1 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + + globrex@0.1.2: {} + + goober@2.1.14(csstype@3.1.3): + dependencies: + csstype: 3.1.3 + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + + graphemer@1.4.0: {} + + has-bigints@1.0.2: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hey-listen@1.0.8: {} + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + ignore@5.3.1: {} + + immutable@4.3.6: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 + + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-arrayish@0.2.1: {} + + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.1.2: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.14.0: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-regex@1.1.4: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-weakmap@2.0.2: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.2: + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + reflect.getprototypeof: 1.0.6 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.6: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@2.5.2: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.2 + object.assign: 4.1.5 + object.values: 1.2.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + + lilconfig@3.1.2: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.379.0(react@18.3.1): + dependencies: + react: 18.3.1 + + material-ripple-effects@2.0.1: {} + + memoize-one@5.2.1: {} + + merge2@1.4.1: {} + + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + ms@2.1.2: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.7: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.17: {} + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.2: {} + + object-keys@1.1.1: {} + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + object.entries@1.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + object.values@1.2.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.24.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@4.0.0: {} + + picocolors@1.0.1: {} + + picomatch@2.3.1: {} + + pify@2.3.0: {} + + pirates@4.0.6: {} + + popmotion@11.0.3: + dependencies: + framesync: 6.0.1 + hey-listen: 1.0.8 + style-value-types: 5.0.0 + tslib: 2.6.3 + + possible-typed-array-names@1.0.0: {} + + postcss-import@15.1.0(postcss@8.4.39): + dependencies: + postcss: 8.4.39 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + postcss-js@4.0.1(postcss@8.4.39): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.39 + + postcss-load-config@4.0.2(postcss@8.4.39): + dependencies: + lilconfig: 3.1.2 + yaml: 2.4.5 + optionalDependencies: + postcss: 8.4.39 + + postcss-nested@6.0.1(postcss@8.4.39): + dependencies: + postcss: 8.4.39 + postcss-selector-parser: 6.1.1 + + postcss-selector-parser@6.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.39: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + + prelude-ls@1.2.1: {} + + prettier@3.3.3: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + radix-themes-tw@0.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.24.8 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-virtualized-auto-sizer@1.0.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-window@1.8.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.24.8 + memoize-one: 5.2.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + reflect.getprototypeof@1.0.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + which-builtin-type: 1.1.3 + + regenerator-runtime@0.14.1: {} + + regexp.prototype.flags@1.5.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + require-directory@2.1.1: {} + + resolve-from@4.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.14.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.14.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.18.1: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.18.1 + '@rollup/rollup-android-arm64': 4.18.1 + '@rollup/rollup-darwin-arm64': 4.18.1 + '@rollup/rollup-darwin-x64': 4.18.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.18.1 + '@rollup/rollup-linux-arm-musleabihf': 4.18.1 + '@rollup/rollup-linux-arm64-gnu': 4.18.1 + '@rollup/rollup-linux-arm64-musl': 4.18.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.18.1 + '@rollup/rollup-linux-riscv64-gnu': 4.18.1 + '@rollup/rollup-linux-s390x-gnu': 4.18.1 + '@rollup/rollup-linux-x64-gnu': 4.18.1 + '@rollup/rollup-linux-x64-musl': 4.18.1 + '@rollup/rollup-win32-arm64-msvc': 4.18.1 + '@rollup/rollup-win32-ia32-msvc': 4.18.1 + '@rollup/rollup-win32-x64-msvc': 4.18.1 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.2: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 + + safe-regex-test@1.0.3: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + + sass@1.77.8: + dependencies: + chokidar: 3.6.0 + immutable: 4.3.6 + source-map-js: 1.2.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + semver@7.6.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + socket.io-client@4.7.5: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.5 + engine.io-client: 6.5.4 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + + source-map-js@1.2.0: {} + + source-map@0.5.7: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string.prototype.matchall@4.0.11: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + regexp.prototype.flags: 1.5.2 + set-function-name: 2.0.2 + side-channel: 1.0.6 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.3 + + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-json-comments@3.1.1: {} + + style-value-types@5.0.0: + dependencies: + hey-listen: 1.0.8 + tslib: 2.6.3 + + stylis@4.2.0: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tabbable@6.2.0: {} + + tailwind-merge@1.8.1: {} + + tailwindcss@3.4.6: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.7 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.1 + postcss: 8.4.39 + postcss-import: 15.1.0(postcss@8.4.39) + postcss-js: 4.0.1(postcss@8.4.39) + postcss-load-config: 4.0.2(postcss@8.4.39) + postcss-nested: 6.0.1(postcss@8.4.39) + postcss-selector-parser: 6.1.1 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tiny-invariant@1.3.3: {} + + tiny-warning@1.0.3: {} + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@1.3.0(typescript@5.5.3): + dependencies: + typescript: 5.5.3 + + ts-interface-checker@0.1.13: {} + + tsconfck@3.1.1(typescript@5.5.3): + optionalDependencies: + typescript: 5.5.3 + + tslib@2.6.3: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + + typescript@5.5.3: {} + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + + unplugin@1.11.0: + dependencies: + acorn: 8.12.1 + chokidar: 3.6.0 + webpack-sources: 3.2.3 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.1.0(browserslist@4.23.2): + dependencies: + browserslist: 4.23.2 + escalade: 3.1.2 + picocolors: 1.0.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-sync-external-store@1.2.2(react@18.3.1): + dependencies: + react: 18.3.1 + + util-deprecate@1.0.2: {} + + vite-tsconfig-paths@4.3.2(typescript@5.5.3)(vite@5.3.4(sass@1.77.8)): + dependencies: + debug: 4.3.5 + globrex: 0.1.2 + tsconfck: 3.1.1(typescript@5.5.3) + optionalDependencies: + vite: 5.3.4(sass@1.77.8) + transitivePeerDependencies: + - supports-color + - typescript + + vite@5.3.4(sass@1.77.8): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.39 + rollup: 4.18.1 + optionalDependencies: + fsevents: 2.3.3 + sass: 1.77.8 + + webpack-sources@3.2.3: {} + + webpack-virtual-modules@0.6.2: {} + + which-boxed-primitive@1.0.2: + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + which-builtin-type@1.1.3: + dependencies: + function.prototype.name: 1.1.6 + has-tostringtag: 1.0.2 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.0.2 + is-generator-function: 1.0.10 + is-regex: 1.1.4 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.15 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.3 + + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + ws@8.17.1: {} + + xmlhttprequest-ssl@2.0.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml@1.10.2: {} + + yaml@2.4.5: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zod@3.23.8: {} diff --git a/frontend/src/components/common/_query.ts b/frontend/src/components/common/_query.ts new file mode 100644 index 0000000..98121bf --- /dev/null +++ b/frontend/src/components/common/_query.ts @@ -0,0 +1,60 @@ +import { QueryClient } from "@tanstack/react-query"; + +// we re-export all query options here, to have easy imports elsewhere +export * from "@/components/inbox/_query"; +export * from "@/components/library/_query"; +export * from "@/components/tags/_query"; + +// Global query client instance +export const queryClient = new QueryClient({}); + +// thin wrapper around fetch so that we can use the vite dev server with our backend +export function customizeFetch() { + const originalFetch = window.fetch; + const devMode = import.meta.env.MODE === "development"; + const apiPrefix = devMode ? "http://0.0.0.0:5001/api_v1" : "/api_v1"; + + window.fetch = async ( + input: RequestInfo | URL, + init?: RequestInit + ): Promise => { + if (input instanceof URL) { + input = input.pathname; + } else if (!(typeof input === "string")) { + input = input.url; + } + + // console.log("fetching", apiPrefix + input); + const response = await originalFetch(apiPrefix + input, init); + if (!response.ok) { + const data = (await response.json()) as ErrorData; + throw new APIError(data); + } + + if (devMode && response.headers.get("Content-Type") == "application/json") { + try { + await response.clone().json(); + } catch (e) { + throw new Error("Failed to parse response as JSON in fetch()"); + } + } + + return response; + }; +} + +interface ErrorData { + error: string; //name + messages: string; + trace?: string; +} + +export class APIError extends Error { + trace?: string; + + constructor(public data: ErrorData) { + super(data.messages); + this.name = data.error; + this.trace = data.trace ?? undefined; + } +} diff --git a/frontend/src/components/common/buttons.tsx b/frontend/src/components/common/buttons.tsx index ea22d84..39190a5 100644 --- a/frontend/src/components/common/buttons.tsx +++ b/frontend/src/components/common/buttons.tsx @@ -1,10 +1,10 @@ -import IconButton from "@mui/material/IconButton"; -import { UseMutationOptions, useMutation } from "@tanstack/react-query"; import { CheckIcon } from "lucide-react"; -import { ConfirmDialog, ErrorDialog } from "./dialogs"; -import { Button, ButtonProps, CircularProgress } from "@mui/material"; import { forwardRef, useState } from "react"; +import { Button, ButtonProps, CircularProgress } from "@mui/material"; +import IconButton from "@mui/material/IconButton"; +import { useMutation,UseMutationOptions } from "@tanstack/react-query"; +import { ConfirmDialog, ErrorDialog } from "./dialogs"; /** @@ -27,8 +27,10 @@ export const IconButtonWithMutation = forwardRef(function IconButtonWithMutation children, ...props }: { - mutationOption?: UseMutationOptions - mutateArgs?: unknown; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mutationOption?: UseMutationOptions; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mutateArgs?: any; } & ButtonProps, ref?: React.Ref ) { @@ -46,8 +48,6 @@ export const IconButtonWithMutation = forwardRef(function IconButtonWithMutation } ); - - return (
- mutateArgs: unknown, - confirmTitle: string; - } & ButtonProps, - ref?: React.Ref -) { - - const { isSuccess, isPending, mutate, isError, error, reset } = useMutation(mutationOption); - const [show, setShow] = useState(false); // show confirm state - +export const IconButtonWithMutationAndFeedback = forwardRef( + function IconButtonWithMutationAndFeedback( + { + mutationOption, + children, + confirmTitle, + mutateArgs, + ...props + }: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mutationOption: UseMutationOptions; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mutateArgs: any; + confirmTitle: string; + } & ButtonProps, + ref?: React.Ref + ) { + const { isSuccess, isPending, mutate, isError, error, reset } = + useMutation(mutationOption); + const [show, setShow] = useState(false); // show confirm state - return ( -
- setShow(true) + return ( +
+ setShow(true)}> + {isSuccess ? : children} + + {isPending ? ( + + ) : null} + {} + { + { + setShow(false); + mutate(mutateArgs); + }} + onCancel={() => { + setShow(false); + }} + /> } - > - {isSuccess ? : children} - - {isPending ? ( - - ) : null} - {} - { { - setShow(false); - mutate(mutateArgs); - }} onCancel={() => { - setShow(false); - }} /> - } -
- ); -}); +
+ ); + } +); diff --git a/frontend/src/components/common/contextMenu.tsx b/frontend/src/components/common/contextMenu.tsx index 798d11e..a3f87ec 100644 --- a/frontend/src/components/common/contextMenu.tsx +++ b/frontend/src/components/common/contextMenu.tsx @@ -1,47 +1,42 @@ -import Menu from "@mui/material/Menu"; -import MenuItem, { MenuItemOwnProps } from "@mui/material/MenuItem"; - -import { - UseMutationOptions, - useMutation, -} from "@tanstack/react-query"; - -import { - useState, - MouseEvent, - TouchEvent, - createContext, - useContext, - useRef, - useEffect, - forwardRef, -} from "react"; -import { queryClient } from "@/main"; -import { TagI } from "@/lib/tag"; import { - Tag, - HardDriveDownload, - Clipboard, - Terminal, - Trash2, ChevronRight, + Clipboard, + HardDriveDownload, LayoutList, ListChecks, Maximize, Minimize2, + Tag, + Terminal, + Trash2, } from "lucide-react"; +import { + createContext, + forwardRef, + MouseEvent, + TouchEvent, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { Typography } from "@mui/material"; +import Menu from "@mui/material/Menu"; +import MenuItem, { MenuItemOwnProps } from "@mui/material/MenuItem"; +import { useMutation,UseMutationOptions } from "@tanstack/react-query"; -import { useTerminalContext } from "@/components/terminal"; +import { queryClient, TagI } from "@/components/common/_query"; import { useSelection, useSelectionLookupQueries, -} from "@/components/context/useSelection"; +} from "@/components/common/useSelection"; +import { useSiblings } from "@/components/common/useSiblings"; +import { useTerminalContext } from "@/components/frontpage/terminal"; +import { ExpandableSib } from "@/components/tags/tagView"; -import styles from "./contextMenu.module.scss"; -import { useSiblings } from "@/components/context/useSiblings"; import { ErrorDialog } from "./dialogs"; -import { Typography } from "@mui/material"; -import { ExpandableSib } from "./tagView"; + +import styles from "./contextMenu.module.scss"; interface ContextMenuContextI { closeMenu: () => void; @@ -54,11 +49,11 @@ interface ContextMenuContextI { } const ContextMenuContext = createContext({ - closeMenu: () => { }, - openMenuMouse: () => { }, - startLongPressTimer: () => { }, - handleTouchMove: () => { }, - cancelLongPressTimer: () => { }, + closeMenu: () => {}, + openMenuMouse: () => {}, + startLongPressTimer: () => {}, + handleTouchMove: () => {}, + cancelLongPressTimer: () => {}, open: false, position: undefined, }); @@ -77,7 +72,6 @@ const ContextMenuContext = createContext({ * */ - interface ContextMenuProps extends Omit, "onContextMenu"> { children: React.ReactNode; @@ -85,7 +79,6 @@ interface ContextMenuProps identifier?: string; } - export const defaultActions = [ , , @@ -121,9 +114,9 @@ export function ContextMenu({ setPosition((prev?: { left: number; top: number }) => prev === undefined ? { - left: event.clientX + 2, - top: event.clientY - 6, - } + left: event.clientX + 2, + top: event.clientY - 6, + } : undefined ); }; @@ -312,7 +305,7 @@ export function ExpandAllAction({ ...props }: Partial) { onClick={() => { callOnSiblings((sib) => { sib.setExpanded(true); - }) + }); closeMenu(); }} text={"Expand All"} @@ -330,7 +323,7 @@ export function CollapseAllAction({ ...props }: Partial) { onClick={() => { callOnSiblings((sib) => { sib.setExpanded(false); - }) + }); closeMenu(); }} text={"Collapse All"} @@ -367,8 +360,22 @@ export function TerminalImportAction(props: Partial) { const { getSelected } = useSelection(); const text = useRef(""); + const escapeForBash = (str: string) => { + return str + .replace(/'/g, "'\\''") + .replace(/\\/g, "\\\\") + .replace(/ /g, "\\ "); + }; + + useEffect(() => { - text.current = "'" + getSelected().join("' '") + "'"; + // text.current = "'" + getSelected().join("' '") + "'"; + const selectedPaths = getSelected().map(escapeForBash); + if (selectedPaths.length > 1) { + text.current = "\\\n " + selectedPaths.join(" \\\n "); + } else { + text.current = selectedPaths.join(" ") + } }, [getSelected, text]); return ( @@ -561,12 +568,10 @@ export function DeleteAction(props: Partial) { /* Base action definitions */ /* ---------------------------------------------------------------------------------- */ - interface ActionWithMutationProps extends ActionProps { mutationOption: UseMutationOptions; } - interface ActionProps extends MenuItemOwnProps { onClick?: () => void; icon?: React.ReactNode; @@ -574,16 +579,7 @@ interface ActionProps extends MenuItemOwnProps { className?: string; } - - - -function Action({ - onClick, - icon, - text, - className, - ...props -}: ActionProps) { +function Action({ onClick, icon, text, className, ...props }: ActionProps) { const { closeMenu } = useContextMenu(); return ( @@ -605,13 +601,7 @@ function Action({ } const ActionWithMutation = forwardRef(function ActionWithMutation( - { - mutationOption, - icon, - text, - className, - ...props - }: ActionWithMutationProps, + { mutationOption, icon, text, className, ...props }: ActionWithMutationProps, ref?: React.Ref ) { const { isSuccess, isPending, mutate, isError, error, reset } = @@ -657,7 +647,11 @@ function Heading({ className?: string; } & MenuItemOwnProps) { return ( - +
{text}
{icon &&
{icon}
}
diff --git a/frontend/src/components/common/itemDetailsTable.tsx b/frontend/src/components/common/itemDetailsTable.tsx deleted file mode 100644 index 3163617..0000000 --- a/frontend/src/components/common/itemDetailsTable.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/** Table Representation of an item (track) */ - -import { Item } from "@/lib/library"; - -import Table from "@mui/material/Table"; -import TableBody from "@mui/material/TableBody"; -import TableCell from "@mui/material/TableCell"; -import TableContainer from "@mui/material/TableContainer"; -import TableRow from "@mui/material/TableRow"; -import { ReactNode } from "react"; - -export default function ItemDetailsTableView({ - item, - keys, -}: { - item: Item; - keys?: string | string[]; -}) { - if (!keys || keys === "basic") { - keys = [ - "name", - "artist", - "albumartist", - "album", - "albumtype", - "comp", - "genre", - "label", - "isrc", - "bpm", - "initialkey", - "year", - "added", - "length", - "size", - "bitrate", - "samplerate", - "path", - ]; - } else if (keys === "all") { - keys = Object.keys(item); - } - - keys = keys as string[]; - - return ( - - - - {keys.map((key, i) => { - return ( - - {key} - - {parse(key, item[key])} - - - ); - })} - -
-
- ); -} - - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function parse(key: string, value: any): ReactNode { - // format n/a - if (value === null || value === undefined || value === "") { - return n/a; - } - if (key === "length") { - // format to 2h 30:12 - const hours = Math.floor(value / 3600); - const minutes = Math.floor((value % 3600) / 60); - const seconds = Math.floor(value % 60); - return `${hours ? `${hours}h ` : ""}${minutes}:${seconds.toString().padStart(2, "0")}`; - } - if (key === "size") { - // format to 2.3 MB - const size = value / 1024 / 1024; - return `${size.toFixed(1)} MB`; - } - if (key === "samplerate") { - // format to 44.1 kHz - return `${value / 1000} kHz`; - } - if (key === "bitrate") { - // format to 320 kbps, no past decimal - return `${Math.round(value / 1000)} kbps`; - } - if (["mtime", "added"].includes(key)) { - // format to 2023-07-14 12:34:56 - return new Date(value * 1000).toLocaleString(); - } - if (key === "comp") { - // format to Yes/No - return value ? "Yes" : "No"; - } - if (key === "bpm" && value === 0) { - // format to n/a - return n/a; - - } - return value as string; -} diff --git a/frontend/src/components/json.tsx b/frontend/src/components/common/json.tsx similarity index 100% rename from frontend/src/components/json.tsx rename to frontend/src/components/common/json.tsx diff --git a/frontend/src/components/common/loadingIndicator.tsx b/frontend/src/components/common/loadingIndicator.tsx new file mode 100644 index 0000000..95682e9 --- /dev/null +++ b/frontend/src/components/common/loadingIndicator.tsx @@ -0,0 +1,22 @@ +import Box from "@mui/material/Box"; +import CircularProgress from "@mui/material/CircularProgress"; +import Typography from "@mui/material/Typography"; + +export default function LoadingIndicator() { + return ( + + + Loading ... + + ); +} diff --git a/frontend/src/components/context/useConfig.tsx b/frontend/src/components/common/useConfig.tsx similarity index 100% rename from frontend/src/components/context/useConfig.tsx rename to frontend/src/components/common/useConfig.tsx index cc50bbb..9bd0786 100644 --- a/frontend/src/components/context/useConfig.tsx +++ b/frontend/src/components/common/useConfig.tsx @@ -1,3 +1,4 @@ +import { useEffect } from "react"; import { QueryClient, QueryClientProvider, @@ -5,7 +6,6 @@ import { useSuspenseQuery, } from "@tanstack/react-query"; import { ReactNode } from "@tanstack/react-router"; -import { useEffect } from "react"; export interface MinimalConfig { gui: { diff --git a/frontend/src/components/common/useDebounce.tsx b/frontend/src/components/common/useDebounce.tsx new file mode 100644 index 0000000..32f9b67 --- /dev/null +++ b/frontend/src/components/common/useDebounce.tsx @@ -0,0 +1,25 @@ +import { useEffect, useState } from "react"; + +export function useDebounce(value: T, delay: number) { + // State and setters for debounced value + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect( + () => { + // Update debounced value after delay + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + // Cancel the timeout if value changes (also on delay change or unmount) + // This is how we prevent debounced value from updating if value is changed ... + // .. within the delay period. Timeout gets cleared and restarted. + return () => { + clearTimeout(handler); + }; + }, + [value, delay] // Only re-call effect if value or delay changes + ); + + return debouncedValue; +} diff --git a/frontend/src/components/common/useSearch.tsx b/frontend/src/components/common/useSearch.tsx new file mode 100644 index 0000000..5367278 --- /dev/null +++ b/frontend/src/components/common/useSearch.tsx @@ -0,0 +1,125 @@ +import { + createContext, + Dispatch, + SetStateAction, + useCallback, + useContext, + useState, +} from "react"; +import { useQuery } from "@tanstack/react-query"; + +import { + MinimalAlbum, + MinimalItem, + queryClient, + searchQueryOptions, +} from "@/components/common/_query"; +import { useDebounce } from "@/components/common/useDebounce"; + +export type SearchType = "item" | "album"; + +interface SearchContextType { + query: string; + setQuery: Dispatch>; + type: SearchType; + setType: Dispatch>; + selectedResult?: number; + setSelectedResult: Dispatch>; + results?: (MinimalItem | MinimalAlbum)[]; + sentQuery: string; + isFetching: boolean; + isError: boolean; + error: Error | null; + cancelSearch: () => void; + resetSearch: () => void; +} + +const SearchContext = createContext({ + query: "", + setQuery: () => {}, + type: "item", + setType: () => {}, + setSelectedResult: () => {}, + results: [], + sentQuery: "", + isFetching: true, + isError: true, + error: null, + cancelSearch: () => {}, + resetSearch: () => {}, +}); + +export function SearchContextProvider({ children }: { children: React.ReactNode }) { + const [query, setQuery] = useState(""); + const [type, setType] = useState("item"); + const [selectedResult, setSelectedResult] = useState(undefined); + + // Debounce search by 500ms + let sentQuery = useDebounce(query, 750); + // deal with trailing escape-characters the same way as in the backend, + // so we correctly reflect frontend-side what we are actually searching for + if ( + sentQuery.endsWith("\\") && + (sentQuery.length - sentQuery.replace(/\\+$/, "").length) % 2 === 1 + ) { + sentQuery = sentQuery.slice(0, -1); + } + + const { + data: data, + isFetching, + isError, + error, + } = useQuery({ + ...searchQueryOptions({ + searchFor: sentQuery, + kind: type, + }), + enabled: sentQuery.length > 0, + }); + + // Cancel a currently running query + // reactquery also does this on demount if abort signals are set + const cancelSearch = useCallback(() => { + queryClient + .cancelQueries({ queryKey: ["search", type, query] }) + .catch(console.error); + setSelectedResult(undefined); + }, [type, query, setSelectedResult]); + + // Reset the search to the default state + const resetSearch = useCallback(() => { + setQuery(""); + setSelectedResult(undefined); + }, []); + + return ( + + {children} + + ); +} + +export function useSearchContext() { + const context = useContext(SearchContext); + if (!context) { + throw new Error("useSeachContext must be used within a SearchContextProvider"); + } + return context; +} diff --git a/frontend/src/components/context/useSelection.tsx b/frontend/src/components/common/useSelection.tsx similarity index 98% rename from frontend/src/components/context/useSelection.tsx rename to frontend/src/components/common/useSelection.tsx index 923ed63..331d152 100644 --- a/frontend/src/components/context/useSelection.tsx +++ b/frontend/src/components/common/useSelection.tsx @@ -1,9 +1,10 @@ // in various places we need to make a selection of tags available to lower components, // e.g. to action multiple tags at the same time via the context menu. -import { TagI } from "@/lib/tag"; -import { useQueries } from "@tanstack/react-query"; import { createContext, useCallback, useContext, useState } from "react"; +import { useQueries } from "@tanstack/react-query"; + +import { TagI } from "@/components/common/_query"; interface SelectionContextType { selection: Map; @@ -157,7 +158,6 @@ export interface SelectionLookupI { is_album_folder: boolean; } - export function useSelectionLookupQueries(fullPaths: string[]) { const queries = fullPaths.map((path) => ({ queryKey: ["lookup", path], @@ -168,5 +168,5 @@ export function useSelectionLookupQueries(fullPaths: string[]) { }, })); - return useQueries( { queries }); + return useQueries({ queries }); } diff --git a/frontend/src/components/context/useSiblings.tsx b/frontend/src/components/common/useSiblings.tsx similarity index 96% rename from frontend/src/components/context/useSiblings.tsx rename to frontend/src/components/common/useSiblings.tsx index e494aea..edf2426 100644 --- a/frontend/src/components/context/useSiblings.tsx +++ b/frontend/src/components/common/useSiblings.tsx @@ -1,7 +1,7 @@ // we want to be able to modify a componenents siblings. // e.g. rightclicking on a tag in the tag list should allow us to expand all tags in the same group. -import React, { Context, MutableRefObject, createContext, useCallback, useContext, useRef } from "react"; +import React, { Context, createContext, MutableRefObject, useCallback, useContext, useRef } from "react"; interface SiblingRefsContextType { diff --git a/frontend/src/lib/socket.tsx b/frontend/src/components/common/useSocket.tsx similarity index 98% rename from frontend/src/lib/socket.tsx rename to frontend/src/components/common/useSocket.tsx index e3aa28c..c6cd798 100644 --- a/frontend/src/lib/socket.tsx +++ b/frontend/src/components/common/useSocket.tsx @@ -1,9 +1,10 @@ // we use a single socket, currently only needed for the terminal connection -import { QueryClient } from "@tanstack/react-query"; import { createContext, useContext, useEffect, useState } from "react"; -import { Socket, io } from "socket.io-client"; -import { TagI } from "./tag"; +import { io,Socket } from "socket.io-client"; +import { QueryClient } from "@tanstack/react-query"; + +import { TagI } from "../tags/_query"; /* ---------------------------------------------------------------------------------- */ /* Terminal */ diff --git a/frontend/src/components/frontpage/addInbox.tsx b/frontend/src/components/frontpage/addInbox.tsx index 06322b0..07b252b 100644 --- a/frontend/src/components/frontpage/addInbox.tsx +++ b/frontend/src/components/frontpage/addInbox.tsx @@ -1,5 +1,7 @@ -import { Card, CardContent, } from "@/components/common/card"; import { Plus } from "lucide-react"; + +import { Card, CardContent, } from "@/components/frontpage/card"; + import { IconButtonWithMutation } from "../common/buttons"; export function AddInbox() { diff --git a/frontend/src/components/common/card/index.tsx b/frontend/src/components/frontpage/card.tsx similarity index 100% rename from frontend/src/components/common/card/index.tsx rename to frontend/src/components/frontpage/card.tsx index b44d6be..030cbe3 100644 --- a/frontend/src/components/common/card/index.tsx +++ b/frontend/src/components/frontpage/card.tsx @@ -1,8 +1,8 @@ +import React from "react"; import { Avatar, Box, styled } from "@mui/material"; import MuiCard from "@mui/material/Card"; -import MuiCardContent from "@mui/material/CardContent"; import MuiCardActions from "@mui/material/CardActions"; -import React from "react"; +import MuiCardContent from "@mui/material/CardContent"; export const Card = styled(MuiCard)(() => ({ borderRadius: "12px", diff --git a/frontend/src/components/frontpage/inboxStats.tsx b/frontend/src/components/frontpage/inboxStats.tsx index 9500e53..fb394d8 100644 --- a/frontend/src/components/frontpage/inboxStats.tsx +++ b/frontend/src/components/frontpage/inboxStats.tsx @@ -1,29 +1,31 @@ -import { FolderSync, FolderSearch, Inbox, Recycle, Trash2 } from "lucide-react"; +import { FolderSearch, FolderSync, Inbox, Recycle, Trash2 } from "lucide-react"; +import Box from "@mui/material/Box"; +import Divider from "@mui/material/Divider"; +import Tooltip from "@mui/material/Tooltip"; +import Grid from "@mui/material/Unstable_Grid2"; +import { useQuery } from "@tanstack/react-query"; + +import { + deleteInboxImportedMutation, + deleteInboxMutation, + InboxStats, + inboxStatsQueryOptions, + retagInboxAllMutation, + retagInboxNewMutation, +} from "@/components/common/_query"; import { Card, - CardContent, CardActions, CardAvatar, + CardContent, CardTopInfo, -} from "@/components/common/card"; -import Divider from "@mui/material/Divider"; -import Tooltip from "@mui/material/Tooltip"; -import Box from "@mui/material/Box"; +} from "@/components/frontpage/card"; + import { IconButtonWithMutation, IconButtonWithMutationAndFeedback, } from "../common/buttons"; -import { useQuery } from "@tanstack/react-query"; import { RelativeTime } from "../common/time"; -import Grid from "@mui/material/Unstable_Grid2"; -import { - InboxStats, - deleteInboxImportedMutation, - deleteInboxMutation, - inboxStatsQueryOptions, - retagInboxAllMutation, - retagInboxNewMutation, -} from "@/lib/inbox"; export function InboxStatsGridItems() { const { data, isLoading, isPending, isError, error } = useQuery( diff --git a/frontend/src/components/frontpage/libraryStats.tsx b/frontend/src/components/frontpage/libraryStats.tsx index fb7fda9..e8bbe6d 100644 --- a/frontend/src/components/frontpage/libraryStats.tsx +++ b/frontend/src/components/frontpage/libraryStats.tsx @@ -1,17 +1,18 @@ +import { Library, RefreshCcw } from "lucide-react"; +import { Box, Divider, Tooltip } from "@mui/material"; +import { useQuery } from "@tanstack/react-query"; + +import { libraryStatsQueryOptions } from "@/components/common/_query"; +import { IconButtonWithMutation } from "@/components/common/buttons"; +import { JSONPretty } from "@/components/common/json"; +import { RelativeTime } from "@/components/common/time"; import { Card, - CardContent, CardActions, CardAvatar, + CardContent, CardTopInfo, -} from "@/components/common/card"; -import { libraryStatsQueryOptions } from "@/lib/stats"; -import { JSONPretty } from "../json"; -import { useQuery } from "@tanstack/react-query"; -import { Box, Divider, Tooltip } from "@mui/material"; -import { Library, RefreshCcw } from "lucide-react"; -import { IconButtonWithMutation } from "../common/buttons"; -import { RelativeTime } from "../common/time"; +} from "@/components/frontpage/card"; export function LibraryStats() { const { data } = useQuery(libraryStatsQueryOptions()); diff --git a/frontend/src/components/common/navigation/tabs.tsx b/frontend/src/components/frontpage/navbar.tsx similarity index 57% rename from frontend/src/components/common/navigation/tabs.tsx rename to frontend/src/components/frontpage/navbar.tsx index 2fad0d6..4ce7b8e 100644 --- a/frontend/src/components/common/navigation/tabs.tsx +++ b/frontend/src/components/frontpage/navbar.tsx @@ -1,21 +1,20 @@ -import Tab, { tabClasses } from "@mui/material/Tab"; -import Tabs, { tabsClasses } from "@mui/material/Tabs"; +import { Home, Inbox, Library, Search, Tag } from "lucide-react"; +import { ReactElement } from "react"; +import { Typography } from "@mui/material"; import { styled } from "@mui/material/styles"; -import { Home, Inbox, Tag, Library } from "lucide-react"; -import { createLink, useRouterState } from "@tanstack/react-router"; +import Tab, { tabClasses, TabProps } from "@mui/material/Tab"; +import Tabs, { tabsClasses } from "@mui/material/Tabs"; +import { createLink, LinkProps, useRouterState } from "@tanstack/react-router"; + +interface StyledTabProps extends Omit, Omit { + label: string | ReactElement; +} -/** - * Custom styled component for a tab item. - * - * I just found we can style mui coponents - * like this. I like it! - * - * @param {object} theme - The theme object. - * @returns {JSX.Element} - The styled TabItem component. - */ -const TabItem = createLink( - styled(Tab)(({ theme }) => ({ +const StyledTab = createLink( + styled(Tab)(({ theme }) => ({ lineHeight: "inherit", + minHeight: 32, + marginTop: 8, minWidth: 0, flexDirection: "row", letterSpacing: "1px", @@ -24,7 +23,6 @@ const TabItem = createLink( textTransform: "uppercase", "& svg": { fontSize: 16, - marginRight: 8, width: 16, height: 16, }, @@ -46,20 +44,35 @@ const TabItem = createLink( })) ); -/** Minimal tabs with dark background - * and a white border at the top which - * indicates the active tab. - */ +const TabLabel = styled(Typography)(({ theme }) => ({ + marginLeft: 8, + lineHeight: "12px", + [theme.breakpoints.down("md")]: { + marginLeft: 0, + display: "none", + }, +})); + +function NavItem({ label, ...props }: StyledTabProps) { + return {label}} disableRipple {...props} />; +} + export default function NavTabs() { - // TODO: const location = useRouterState({ select: (s) => s.location }); - const basePath = location.pathname.split("/")[1]; + let basePath = location.pathname.split("/")[1]; + + // only needed temporarily until search gets an icon in the toolbar! + if (basePath === "library") { + basePath += "/" + location.pathname.split("/")[2]; + } + return ( - } - disableRipple // /> - } - disableRipple + // /> - } - disableRipple + // /> - } - disableRipple + // + /> + } + // /> ); diff --git a/frontend/src/components/terminal.module.scss b/frontend/src/components/frontpage/terminal.module.scss similarity index 97% rename from frontend/src/components/terminal.module.scss rename to frontend/src/components/frontpage/terminal.module.scss index 0786427..cdfa920 100644 --- a/frontend/src/components/terminal.module.scss +++ b/frontend/src/components/frontpage/terminal.module.scss @@ -34,9 +34,6 @@ } .terminalExpandButton { - position: fixed; - bottom: 0; - right: 0; margin: 0.5rem; width: 100px; font-size: 0.8rem; diff --git a/frontend/src/components/terminal.tsx b/frontend/src/components/frontpage/terminal.tsx similarity index 95% rename from frontend/src/components/terminal.tsx rename to frontend/src/components/frontpage/terminal.tsx index fcd1c6d..86c5176 100644 --- a/frontend/src/components/terminal.tsx +++ b/frontend/src/components/frontpage/terminal.tsx @@ -1,20 +1,21 @@ +import { ChevronDown, Terminal as TerminalIcon } from "lucide-react"; import React, { + createContext, Dispatch, SetStateAction, - createContext, useEffect, useRef, useState, } from "react"; -import { Slide, Button, Portal, IconButton } from "@mui/material"; -import { ChevronDown, Terminal as TerminalIcon } from "lucide-react"; +import { Socket } from "socket.io-client"; +import { Button, IconButton,Portal, Slide } from "@mui/material"; +import { FitAddon as xTermFitAddon } from "@xterm/addon-fit"; +import { Terminal as xTerminal } from "@xterm/xterm"; + +import { useTerminalSocket } from "@/components/common/useSocket"; import "node_modules/@xterm/xterm/css/xterm.css"; -import { Terminal as xTerminal } from "@xterm/xterm"; -import { FitAddon as xTermFitAddon } from "@xterm/addon-fit"; import styles from "./terminal.module.scss"; -import { useTerminalSocket } from "@/lib/socket"; -import { Socket } from "socket.io-client"; // match our style - this is somewhat redundant with index.css const xTermTheme = { @@ -66,6 +67,16 @@ const SlideIn = ({ children }: { children: React.ReactNode }) => { }, [open, setOpen]); return ( + <> +
@@ -84,16 +95,9 @@ const SlideIn = ({ children }: { children: React.ReactNode }) => {
- + + ); }; diff --git a/frontend/src/components/frontpage/toolbar.tsx b/frontend/src/components/frontpage/toolbar.tsx new file mode 100644 index 0000000..b819696 --- /dev/null +++ b/frontend/src/components/frontpage/toolbar.tsx @@ -0,0 +1,16 @@ +import Box from "@mui/material/Box"; + +import { Terminal } from "@/components/frontpage/terminal"; + + +export default function ToolBar() { + + return ( + + + + ); +} diff --git a/frontend/src/lib/inbox.ts b/frontend/src/components/inbox/_query.ts similarity index 96% rename from frontend/src/lib/inbox.ts rename to frontend/src/components/inbox/_query.ts index 258b3b2..86dfd22 100644 --- a/frontend/src/lib/inbox.ts +++ b/frontend/src/components/inbox/_query.ts @@ -1,5 +1,6 @@ -import { queryClient } from "@/main"; -import { UseMutationOptions, queryOptions } from "@tanstack/react-query"; +import { queryOptions,UseMutationOptions } from "@tanstack/react-query"; + +import { queryClient } from "@/components/common/_query"; // these guys can be infinetely nested and represent a file path on disk. export interface FsPath { diff --git a/frontend/src/routes/inbox.module.scss b/frontend/src/components/inbox/inbox.module.scss similarity index 97% rename from frontend/src/routes/inbox.module.scss rename to frontend/src/components/inbox/inbox.module.scss index 1dd509b..2552ce7 100644 --- a/frontend/src/routes/inbox.module.scss +++ b/frontend/src/components/inbox/inbox.module.scss @@ -28,7 +28,6 @@ $border-color: #495057; flex-direction: column; height: 100%; width: auto; - overflow: wrap; margin: 0 auto 1rem auto; padding: 1rem 1.5rem; @@ -154,6 +153,7 @@ $border-color: #495057; margin-left: 0.45rem; font-family: monospace; font-size: 0.7rem; - white-space: pre; + white-space: pre-wrap; + overflow-wrap: break-workd; color: #999; } diff --git a/frontend/src/components/library/_query.ts b/frontend/src/components/library/_query.ts new file mode 100644 index 0000000..e294f8d --- /dev/null +++ b/frontend/src/components/library/_query.ts @@ -0,0 +1,336 @@ +import { queryOptions } from "@tanstack/react-query"; + +export const LIB_BROWSE_ROUTE = "/library/browse"; + +export interface MinimalArtist { + name: string; + albums: MinimalAlbum[]; +} + +export interface MinimalAlbum { + id: number; + name: string; // added by us for consistency, not a beets-fields + albumartist: string; + year: number; +} + +export interface Album extends MinimalAlbum { + [key: string]: unknown; // enable indexing item[key] + items?: MinimalItem[]; + + added?: number; // 1707767640.980912 + album?: string; // "Away From the Sun" + albumartist_credit?: string; // "3 Doors Down" + albumartist_sort?: string; // "3 Doors Down" + albumartists?: string[]; // ["3 Doors Down"] + albumartists_credit?: string[]; // ["3 Doors Down"] + albumartists_sort?: string[]; // ["3 Doors Down"] + albumdisambig?: string; // "BMG club edition" + albumstatus?: string; // "Official" + albumtype?: string; // "album" + albumtypes?: string[]; // ["album"] + artpath?: string; // "None" + asin?: string; // "" + barcode?: string; // "" + catalognum?: string; // "440 064 396-2" + comp?: number; // 0 + country?: string; // "US" + day?: number; // 12 + discogs_albumid?: number; // 0 + discogs_artistid?: number; // 0 + discogs_labelid?: number; // 0 + disctotal?: number; // 1 + genre?: string; // "Post-Grunge; Hard Rock; Alternative Rock; Rock" + label?: string; // "" + language?: string; // "eng" + mb_albumartistid?: string; // "2386cd66-e923-4e8e-bf14-2eebe2e9b973" + mb_albumid?: string; // "9a7b7d7c-3e5d-4006-a97f-06e10a12cb16" + mb_releasegroupid?: string; // "8f4d4f05-36ed-3c81-86a7-aae2a03b1520" + month?: number; // 11 + original_day?: number; // 11 + original_month?: number; // 11 + original_year?: number; // 2002 + // r128_album_gain?: null; // null + release_group_title?: string; // "Away From the Sun" + releasegroupdisambig?: string; // "" + // rg_album_gain?: null; // null + // rg_album_peak?: null; // null + script?: string; // "Latn" + style?: string; // "" +} + +export interface MinimalItem { + id: number; + name: string; // Track title, added by us for consistency, not a beets-fields + artist: string; // "Basstripper" + albumartist: string; // "Basstripper" + album: string; // "In the City / Wasted" + album_id: number; // 1 + year: number; // 2023 + isrc: string; // "US39N2308955" +} + +export interface Item extends MinimalItem { + [key: string]: unknown; // enable indexing item[key] + + albumartist_credit?: string; // "Basstripper" + albumartist_sort?: string; // "Basstripper" + albumartists?: string[]; // ["Basstripper"] + albumartists_credit?: string[]; // ["Basstripper"] + albumartists_sort?: string[]; // ["Basstripper"] + albumdisambig?: string; // "" + albumstatus?: string; // "Official" + albumtype?: string; // "single" + albumtypes?: string[]; // ["single"] + acoustid_fingerprint?: string; // "" + acoustid_id?: string; // "" + added?: number; // 1715716057.413927 + arranger?: string; // "" + artist_credit?: string; // "Basstripper" + artist_sort?: string; // "Basstripper" + artists?: string[]; // ["Basstripper"] + artists_credit?: string[]; // ["Basstripper"] + artists_ids?: number[]; // [] + artists_sort?: string[]; // ["Basstripper"] + asin?: string; // "" + barcode?: string; // "197338612422" + bitrate?: number; // 1033095 + bitrate_mode?: string; // "" + bpm?: number; // 0 + catalognum?: string; // "" + channels?: number; // 2 + comments?: string; // "" + comp?: number; // 0 + composer?: string; // "" + composer_sort?: string; // "" + country?: string; // "" + data_source?: string; // "MusicBrainz" + day?: number; // 14 + disc?: number; // 1 + discogs_albumid?: number; // 0 + discogs_artistid?: number; // 0 + discogs_labelid?: number; // 0 + disctitle?: string; // "" + disctotal?: number; // 1 + encoder?: string; // "" + encoder_info?: string; // "" + encoder_settings?: string; // "" + format?: string; // "FLAC" + genre?: string; // "" + grouping?: string; // "" + initial_key?: string; // null + label?: string; // "DnB Allstars Records" + language?: string; // "" + length?: number; // 156.34643990929706 + lyricist?: string; // "" + lyrics?: string; // "" + mb_albumartistid?: string; // "82687fdf-84d6-49ac-bff2-de88cb42e5a2" + mb_albumartistids?: string[]; // ["82687fdf-84d6-49ac-bff2-de88cb42e5a2"] + mb_albumid?: string; // "3a76ece6-89ec-43c6-920f-ff955d2e4f9e" + mb_artistid?: string; // "82687fdf-84d6-49ac-bff2-de88cb42e5a2" + mb_artistids?: string[]; // ["82687fdf-84d6-49ac-bff2-de88cb42e5a2"] + mb_releasegroupid?: string; // "6e46e8cc-2546-45d1-a24c-23982cf36980" + mb_releasetrackid?: string; // "6886cc80-ccbe-4c92-a8b5-b066b39a666f" + mb_trackid?: string; // "ee846065-2f7d-4e87-ae3f-b925c75359c1" + mb_workid?: string; // "" + media?: string; // "Digital Media" + month?: number; // 7 + mtime?: number; // 1715716092 + original_day?: number; // 14 + original_month?: number; // 7 + original_year?: number; // 2023 + path?: string; // "/music/imported/Basstripper/In the City - Wasted/01 In the City [1033kbps].flac" + // r128_album_gain?: null; // null + // r128_track_gain?: null; // null + release_group_title?: string; // "In the City / Wasted" + releasegroupdisambig?: string; // "" + remixer?: string; // "" + // rg_album_gain?: null; // null + // rg_album_peak?: null; // null + // rg_track_gain?: null; // null + // rg_track_peak?: null; // null + samplerate?: number; // 44100 + script?: string; // "Latn" + size?: number; // 20487451 + style?: string; // "" + title?: string; // "In the City" + track?: number; // 1 + track_alt?: string; // "1" + trackdisambig?: string; // "" + tracktotal?: number; // 2 + work?: string; // "" + work_disambig?: string; // "" +} + +function _url_parse_minimal_expand( + url: string, + { minimal, expand }: { minimal: boolean; expand: boolean } +) { + const params = []; + if (minimal) { + params.push("minimal"); + } + if (expand) { + params.push("expand"); + } + return params.length ? `${url}?${params.join("&")}` : url; +} + +export const artistsQueryOptions = () => + queryOptions({ + queryKey: ["artists"], + queryFn: async () => { + const response = await fetch(`/library/artist/`); + return (await response.json()) as MinimalArtist[]; + }, + }); + +export const artistQueryOptions = ({ + name, + expand = false, + minimal = true, +}: { + name: string; + expand?: boolean; + minimal?: boolean; +}) => + queryOptions({ + queryKey: ["artist", name, expand, minimal], + queryFn: async (): Promise => { + const url = _url_parse_minimal_expand(`/library/artist/${name}`, { + expand, + minimal, + }); + const response = await fetch(url); + return (await response.json()) as MinimalArtist; + }, + }); + +export const albumQueryOptions = ({ + id, + expand = true, + minimal = true, +}: { + id?: number; + expand?: boolean; + minimal?: boolean; +}) => + queryOptions({ + queryKey: ["album", id, expand, minimal], + queryFn: async (): Promise => { + if (id === undefined || id === null) { + return null; + } + const url = _url_parse_minimal_expand(`/library/album/${id}`, { + expand, + minimal, + }); + const response = await fetch(url); + return (await response.json()) as MinimalAlbum | Album; + }, + }); + +export const itemQueryOptions = ({ + id, + expand = false, + minimal = true, +}: { + id?: number; + expand?: boolean; + minimal?: boolean; +}) => + queryOptions({ + queryKey: ["item", id, expand, minimal], + queryFn: async () => { + if (id === undefined || id === null) { + return null; + } + const url = _url_parse_minimal_expand(`/library/item/${id}`, { + expand, + minimal, + }); + const response = await fetch(url); + return (await response.json()) as Item; + }, + }); + +export const artQueryOptions = ({ type, id }: { type?: string; id?: number }) => + queryOptions({ + queryKey: ["art", type, id], + queryFn: async () => { + if (id === undefined || id === null) { + return undefined; + } + console.log("artQueryOptions", type, id); + const url = `/library/${type}/${id}/art`; + const response = await fetch(url); + const blob = await response.blob(); + const objectUrl = URL.createObjectURL(blob); + return objectUrl; + }, + }); + +/* ---------------------------------------------------------------------------------- */ +/* Search */ +/* ---------------------------------------------------------------------------------- */ + +export interface SearchResult { + results: T[]; +} + +export const searchQueryOptions = ({ + searchFor, + kind, +}: { + searchFor: string; + kind: "item" | "album"; +}) => + queryOptions({ + queryKey: ["search", kind, searchFor], + queryFn: async ({ signal }) => { + const expand = false; + const minimal = true; + const url = _url_parse_minimal_expand( + `/library/${kind}/query/${encodeURIComponent(searchFor)}`, + { + expand, + minimal, + } + ); + const response = await fetch(url, { signal }); + return (await response.json()) as SearchResult; + }, + }); + +/* ---------------------------------------------------------------------------------- */ +/* Stats */ +/* ---------------------------------------------------------------------------------- */ + +export interface LibraryStats { + libraryPath: string; + items: number; + albums: number; + artists: number; + genres: number; + labels: number; + size: number; + lastItemAdded?: Date; + lastItemModified?: Date; +} + +export const libraryStatsQueryOptions = () => { + return queryOptions({ + queryKey: ["libraryStats"], + queryFn: async () => { + const response = await fetch(`/library/stats`); + const dat = (await response.json()) as LibraryStats; + + // convert lastItemAdded to Date + if (dat.lastItemAdded) dat.lastItemAdded = new Date(dat.lastItemAdded); + if (dat.lastItemModified) + dat.lastItemModified = new Date(dat.lastItemModified); + + return dat; + }, + }); +}; diff --git a/frontend/src/components/library/browserHeader.tsx b/frontend/src/components/library/browserHeader.tsx new file mode 100644 index 0000000..bdfde25 --- /dev/null +++ b/frontend/src/components/library/browserHeader.tsx @@ -0,0 +1,144 @@ +// Show an info box where the user is currently navigating + +import { Box, Typography } from "@mui/material"; +import { SxProps } from "@mui/system"; +import { useQuery } from "@tanstack/react-query"; +import { Link, useParams } from "@tanstack/react-router"; + +import { + albumQueryOptions, + itemQueryOptions, +} from "@/components/common/_query"; + +import CoverArt from "./coverArt"; + +export function BrowserHeader({ ...props }: React.HTMLAttributes) { + const params: RouteParams = useParams({ strict: false }); + const artist = params.artist ?? "Artists"; + + // although we do not need all the details, we use the same query options as for the browser to use the cache + // if the parsed id is undefined, the queries return null without server communication + const { data: albumData } = useQuery( + albumQueryOptions({ + id: params.albumId, + expand: true, + minimal: true, + }) + ); + const album = albumData?.name; + + const { data: itemData } = useQuery( + itemQueryOptions({ + id: params.itemId, + minimal: false, + expand: true, + }) + ); + const track = itemData?.name; + + let coverType: string | undefined = undefined; + if (params.itemId) { + coverType = "item"; + } else if (params.albumId) { + coverType = "album"; + } + + console.log("browserheader ", coverType, album, track); + + return ( + + + + + + + {(album ?? track) && ( + + )} + + ); +} + +interface RouteParams { + artist?: string; + albumId?: number; + itemId?: number; +} + +interface LinkTypographyProps { + target?: "artist" | "album" | "library"; + label?: string; + params?: RouteParams; + sx?: SxProps; +} + +function LinkTypography({ + target = "library", + label, + params, + sx, +}: LinkTypographyProps) { + if (!label) { + return <>; + } + if (!params) { + return {label}; + } + + let to = `/library/browse`; + if (target == "artist") { + to = `/library/browse/${params.artist}`; + } else if (target == "album") { + to = `/library/browse/${params.artist}/${params.albumId}`; + } + + return ( + + {label} + + ); +} diff --git a/frontend/src/components/library/coverArt.tsx b/frontend/src/components/library/coverArt.tsx new file mode 100644 index 0000000..aa5be79 --- /dev/null +++ b/frontend/src/components/library/coverArt.tsx @@ -0,0 +1,88 @@ +import Box, { BoxProps } from "@mui/material/Box"; +import Skeleton from "@mui/material/Skeleton"; +import { SxProps } from "@mui/system"; +import { useQuery } from "@tanstack/react-query"; + +import { artQueryOptions } from "@/components/common/_query"; + +interface CoverArtProps { + type?: string; + albumId?: number; + itemId?: number; + sx?: SxProps; + showPlaceholder?: boolean; +} + +export default function CoverArt({ + type, + albumId, + itemId, + sx, + showPlaceholder = true, + ...props +}: CoverArtProps & Partial) { + // in the library browse view, we can assume the album cover should is requested first and cached, and we only get the item-level cover second. + const { data: albumArt } = useQuery({ + ...artQueryOptions({ type: "album", id: albumId }), + enabled: albumId !== undefined && (type === undefined || type === "album"), + }); + + const { data: itemArt, isFetching: isFetchingItem } = useQuery({ + ...artQueryOptions({ type: "item", id: itemId }), + enabled: itemId !== undefined && (type === undefined || type === "item"), + }); + + const coverSx = { + height: 100, + width: 100, + marginRight: "0.1rem", + marginLeft: "0.1rem", + ...sx, + } as SxProps; + + if (type === "album" && albumArt) { + return ; + } else if (type === "item") { + if (isFetchingItem && albumArt) { + return ; + } + if (itemArt) { + return ; + } + } + + // default case, nothing is loading and no cover found. + if (showPlaceholder) { + return ; + } else { + return null; + } +} + +function CoverArtPlaceholder({ + animation, + ...props +}: { + animation: false | "pulse" | "wave" | undefined; +} & Partial) { + + return ( + + + + ); +} + +function CoverArtContent({ + src, + ...props +}: {src:string} & Partial +) { + return ; +} diff --git a/frontend/src/components/library/itemAlbumDetails.tsx b/frontend/src/components/library/itemAlbumDetails.tsx new file mode 100644 index 0000000..3b2490a --- /dev/null +++ b/frontend/src/components/library/itemAlbumDetails.tsx @@ -0,0 +1,255 @@ +import { Bug as BugOn, BugOff } from "lucide-react"; +import { useState } from "react"; +import { ReactNode, useMemo } from "react"; +import { Box, CircularProgress } from "@mui/material"; +import IconButton from "@mui/material/IconButton"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableRow from "@mui/material/TableRow"; +import Tooltip from "@mui/material/Tooltip"; +import { useQuery } from "@tanstack/react-query"; + +import { Album, albumQueryOptions, Item, itemQueryOptions } from "@/components/common/_query"; +import { JSONPretty } from "@/components/common/json"; + +export function ItemView({ itemId }: { itemId?: number }) { + const [detailed, setDetailed] = useState(false); + const { + data: item, + isFetching, + isError, + error, + isSuccess, + } = useQuery(itemQueryOptions({ id: itemId, minimal: false, expand: true })); + + return ( + <> + + {isSuccess && ( + <> + + setDetailed(!detailed)} + > + {detailed && } + {!detailed && } + + + + + )} + {!isSuccess && isFetching && ( + + + + )} + {isError && ( + <> + Error: + + + )} + + + ); +} + +// for now this is the same as ItemView. +export function AlbumView({ albumId }: { albumId?: number }) { + const [detailed, setDetailed] = useState(false); + const { + data, + isFetching, + isError, + error, + isSuccess, + } = useQuery(albumQueryOptions({ id: albumId, minimal: false, expand: false })); + const album = data as Album; + + return ( + <> + + {isSuccess && ( + <> + + setDetailed(!detailed)} + > + {detailed && } + {!detailed && } + + + + + )} + {!isSuccess && isFetching && ( + + + + )} + {isError && ( + <> + Error: + + + )} + + + ); +} + +const basicItemKeys = [ + "title", + "artist", + "albumartist", + "album", + "albumtype", + "comp", + "genre", + "label", + "isrc", + "bpm", + "initial_key", + "year", + "added", + "length", + "size", + "bitrate", + "samplerate", + "path", +]; + +const basicAlbumKeys = [ + "album", + "albumartist", + "albumtype", + "genre", + "comp", + "label", + "year", + "added", +]; + +export function DetailsTable({ + obj, + keys, +}: { + obj: Item | Album; + keys?: string | string[]; +}) { + // albums only have an albumartist (not artist), so we can use this to distinguish + const isItem = (item: unknown): item is Item => { + return (item as Item).artist !== undefined; + }; + + if (!keys || keys === "basic") { + keys = isItem(obj) ? basicItemKeys : basicAlbumKeys; + } else if (keys === "all") { + keys = Object.keys(obj); + // we only added name for backend-frontend consistency, its not a beets-field + // note: this could cause problems if a user adds a custom field "name" + keys = keys.filter((key) => key !== "name"); + } + + keys = keys as string[]; + + const maxKeyWidth = useMemo(() => { + const keyWidths = keys.map((key) => key.length); + return Math.max(...keyWidths) * 10; + }, [keys]); + + return ( + + + + {keys.map((key, i) => { + return ( + + + {key} + + + {parse(key, obj[key])} + + + ); + })} + +
+
+ ); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function parse(key: string, value: any): ReactNode { + // format n/a + if (value === null || value === undefined || value === "") { + return n/a; + } + if (key === "length") { + // format to 2h 30:12 + const hours = Math.floor(value / 3600); + const minutes = Math.floor((value % 3600) / 60); + const seconds = Math.floor(value % 60); + return `${hours ? `${hours}h ` : ""}${minutes}:${seconds.toString().padStart(2, "0")}`; + } + if (key === "size") { + // format to 2.3 MB + const size = value / 1024 / 1024; + return `${size.toFixed(1)} MB`; + } + if (key === "samplerate") { + // format to 44.1 kHz + return `${value / 1000} kHz`; + } + if (key === "bitrate") { + // format to 320 kbps, no past decimal + return `${Math.round(value / 1000)} kbps`; + } + if (["mtime", "added"].includes(key)) { + // format to 2023-07-14 12:34:56 + return new Date(value * 1000).toLocaleString(); + } + if (key === "comp") { + // format to Yes/No + return value ? "Yes" : "No"; + } + if (key === "bpm" && value === 0) { + // format to n/a + return n/a; + } + return value as string; +} diff --git a/frontend/src/components/library/library.module.scss b/frontend/src/components/library/library.module.scss new file mode 100644 index 0000000..3038661 --- /dev/null +++ b/frontend/src/components/library/library.module.scss @@ -0,0 +1,280 @@ +/* ---------------------------------------------------------------------------------- */ +/* Column Browser */ +/* ---------------------------------------------------------------------------------- */ + +$column_gap: 0.5rem; + +.columnBrowser { + display: flex; + height: 100%; + column-gap: $column_gap; + padding-bottom: 0; +} + +.column { + margin: 0; + gap: 0; + padding: 0; + overflow: hidden; + + // on mobile only one column + width: 100%; + display: flex; + flex-direction: column; + &.isSecondary { + display: none; + } + // on large screens multiple columns + @media (min-width: 768px) { + // 4 columns, 3 gaps => x0.75 + width: calc(25% - 0.75 * $column_gap); + &.isSecondary { + display: flex; + width: calc(25% - 0.75 * $column_gap); + } + } +} + +.columnLabel { + text-align: center; + // font-weight: bold; + text-transform: uppercase; + padding-top: 0.5rem; + position: relative; + opacity: 0.6; + display: none; + @media (min-width: 768px) { + display: block; + } + &::after { + // divider line with full opactiy + content: ""; + width: 100%; + display: block; + height: 0.5rem; // this is effectively padding bottom + position: aboslute; + border-bottom: #292e31 solid 1px; + } +} + +.browserHeader { + border-bottom: #292e31 solid 1px; + padding: 0.5rem 0.5rem; + @media (min-width: 768px) { + &:not(.alwaysShow) { + display: none; + } + } +} + +.listBox { + width: 100%; + height: 100%; + + position: relative; + display: flex; + align-items: start; + + // mui adds uls + & > ul { + padding: 0; + width: 100%; + } +} + +.listItem { + --default-bg: var(--mui-palette-background-paper); + --hover-bg: #353a3f; + --selected-bg: #292e31; + cursor: pointer; + padding: 0rem 0.5rem; + width: 100%; + &[data-selected="true"] { + background-color: var(--selected-bg); + } + &:hover { + background-color: var(--hover-bg); + } + &::after { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 15px; + pointer-events: none; + background: linear-gradient(to right, transparent, var(--current-bg)); + } +} +// Dynamically set the --current-bg variable based on state +.listItem { + &[data-selected="true"]::after { + --current-bg: var(--selected-bg); + } + &:hover::after { + --current-bg: var(--hover-bg); + } + &:not(:hover):not([data-selected="true"])::after { + --current-bg: var(--default-bg); + } +} + +/* ---------------------------------------------------------------------------------- */ +/* Search */ +/* ---------------------------------------------------------------------------------- */ + +.SearchPageOuter { + height: 100%; + display: flex; + flex-grow: 1; + flex-direction: column; + + code { + background-color: #212529; + padding: 2px 4px; + border-radius: 4px; + font-family: "Courier New", Courier, monospace; + font-size: 0.9em; + white-space: nowrap; + } +} + +.SearchBarOuter { + display: flex; + flex-direction: row; + margin-top: 0.5rem; + flex-shrink: 0; +} + +.SearchBarTextField { + width: 100%; + margin-right: 0.5rem; + & :global(.MuiOutlinedInput-root) { + & fieldset { + border-color: #a2a2a355; + border-width: 1px; + } + } +} + +.SearchResultsWrapper { + margin-top: 0.5rem; + display: flex; + flex-direction: row; + justify-content: flex-start; + flex-grow: 1; + overflow: hidden; + column-gap: $column_gap; +} + +.SearchResultsLoading { + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; + margin: auto; +} + +.SearchResults { + height: 100%; + width: 33%; + overflow: hidden; + + .ItemResultArtist, .AlbumResultArtist { + display: none; + + @media (min-width: 768px) { + display: inline-block; + } + + & svg { + opacity: 0.6; + display: inline-block; + margin: 0 0.5rem 0.1rem 0.5rem; + } + } + // .ItemResultName, .AlbumResultName { + // } + + +} + +.SearchResultInfoOuter { + width: calc(67% - #{$column_gap}); +} + +.SearchResultInfo { + display: flex; + flex-direction: column; + justify-content: flex-start; + flex-grow: 1; + height: 100%; + + .SearchResultCover { + margin: 0.5rem auto 0 auto; + } +} + +.h100w100 { + height: 100%; + width: 100%; +} + +.BeetsSearchHelpOuter { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + flex-grow: 1; + flex-shrink: 0; + overflow: hidden; + // for fade-out pseudo-elements + position: relative; +} + +.BeetsSearchHelp { + position: relative; + padding-left: 1.5rem; + padding-right: 1.5rem; + padding-bottom: 1.5rem; + height: 100%; + overflow-y: auto; + + h1 { + font-size: 1.2em; + margin-bottom: 0.5em; + margin-top: 1.5rem; + } + + ul { + list-style-type: disc; + padding-left: 2rem; + } + + li { + margin-bottom: 8px; + } +} + +// fade-out of explanations +.BeetsSearchHelpOuter::before, +.BeetsSearchHelpOuter::after { + content: ""; + position: absolute; + left: 0; + right: 0; + height: 2.5rem; + pointer-events: none; // Ensure the overlays don't interfere with interactions + z-index: 1; +} + +.BeetsSearchHelpOuter::before { + top: 0; + background: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%); +} + +.BeetsSearchHelpOuter::after { + bottom: 0; + background: linear-gradient(to top, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%); +} diff --git a/frontend/src/components/common/list.module.scss b/frontend/src/components/library/list.module.scss similarity index 82% rename from frontend/src/components/common/list.module.scss rename to frontend/src/components/library/list.module.scss index 536a0f5..6f1e9d4 100644 --- a/frontend/src/components/common/list.module.scss +++ b/frontend/src/components/library/list.module.scss @@ -1,8 +1,13 @@ +.List { + overflow-x: hidden !important; +} + .ListItemText { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; display: block; + line-height: 2.0; // setting this large line height prevents vertical jitter &.overflowing { &:hover { animation: MoveText 3s ease-in-out infinite; @@ -19,11 +24,9 @@ } 40% { transform: translateX(var(--translate-distance, 0)); - } 60% { transform: translateX(var(--translate-distance, 0)); - } 95% { transform: translateX(0); diff --git a/frontend/src/components/common/list.tsx b/frontend/src/components/library/list.tsx similarity index 61% rename from frontend/src/components/common/list.tsx rename to frontend/src/components/library/list.tsx index 3ae8737..a2b6dcf 100644 --- a/frontend/src/components/common/list.tsx +++ b/frontend/src/components/library/list.tsx @@ -1,11 +1,10 @@ +import { ComponentType, ReactNode, useEffect, useRef, useState } from "react"; +import AutoSizer from "react-virtualized-auto-sizer"; +import { FixedSizeList, ListChildComponentProps } from "react-window"; +import { Box } from "@mui/material"; +import ListItem, { ListItemOwnProps } from "@mui/material/ListItem"; import { Link } from "@tanstack/react-router"; -import { Typography } from "@mui/material"; -import ListItem, { ListItemOwnProps, ListItemProps } from "@mui/material/ListItem"; - -import { ReactNode, ComponentType, useRef, useState, useEffect } from "react"; -import { FixedSizeList, ListChildComponentProps } from "react-window"; -import AutoSizer from "react-virtualized-auto-sizer"; import styles from "./list.module.scss"; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -20,7 +19,7 @@ function List({ children, data, ...props }: WrapperProps) { {({ height, width }) => ( ({ children, data, ...props }: WrapperProps) { } interface ListItemData extends ListItemOwnProps { - label: string; + label: string | ReactNode; to?: string; params?: Record; icon?: ReactNode; + animateOverflow?: boolean; } function Item({ index, data, style }: ListChildComponentProps) { - const { label, to, params, icon, ...props } = data[index]; - const textRef = useRef(null); + const { label, to, params, icon, animateOverflow = true, ...props } = data[index]; + const labelRef = useRef(null); const [isOverflowing, setIsOverflowing] = useState(false); useEffect(() => { - const textEl = textRef.current; - if (textEl) { - const parent = textEl.parentElement?.parentElement; - if (parent && textEl.scrollWidth > parent.clientWidth) { + const el = labelRef.current; + if (el) { + const parent = el.parentElement?.parentElement; + const diff = parent ? parent.clientWidth - el.scrollWidth - 15 : 0; + if (animateOverflow && parent && diff < 0) { setIsOverflowing(true); - const d = parent.clientWidth - textEl.scrollWidth - 15; - textEl.style.setProperty("--translate-distance", `${d}px`); + el.style.setProperty("--translate-distance", `${diff}px`); } else { setIsOverflowing(false); - textEl.style.removeProperty("--translate-distance"); + el.style.removeProperty("--translate-distance"); } } - }, [label]); + }, [labelRef, label, animateOverflow]); const it = ( {icon}
- - {label} - + { + + {label} + + }
); @@ -92,12 +94,6 @@ function Item({ index, data, style }: ListChildComponentProps) { return it; } -export interface ItemProps extends ListItemProps { - label: string; - icon?: React.ReactNode; - to?: string; -} - List.Item = Item; export default List; diff --git a/frontend/src/lib/tag.ts b/frontend/src/components/tags/_query.ts similarity index 100% rename from frontend/src/lib/tag.ts rename to frontend/src/components/tags/_query.ts diff --git a/frontend/src/components/common/similarityBadge.module.scss b/frontend/src/components/tags/similarityBadge.module.scss similarity index 100% rename from frontend/src/components/common/similarityBadge.module.scss rename to frontend/src/components/tags/similarityBadge.module.scss diff --git a/frontend/src/components/common/similarityBadge.tsx b/frontend/src/components/tags/similarityBadge.tsx similarity index 95% rename from frontend/src/components/common/similarityBadge.tsx rename to frontend/src/components/tags/similarityBadge.tsx index 3d39a01..851923d 100644 --- a/frontend/src/components/common/similarityBadge.tsx +++ b/frontend/src/components/tags/similarityBadge.tsx @@ -1,13 +1,12 @@ - - +import * as HoverCard from "@radix-ui/react-hover-card"; import { useQuery } from "@tanstack/react-query"; -import styles from "./similarityBadge.module.scss"; -import { tagQueryOptions } from "@/lib/tag"; -import * as HoverCard from "@radix-ui/react-hover-card"; +import { tagQueryOptions } from "@/components/common/_query"; +import { useConfig } from "@/components/common/useConfig" import { TagPreview } from "./tagView"; -import { useConfig } from "@/components/context/useConfig" + +import styles from "./similarityBadge.module.scss"; export function SimilarityBadgeWithHover({ tagId, diff --git a/frontend/src/components/common/statusIcon.module.scss b/frontend/src/components/tags/statusIcon.module.scss similarity index 100% rename from frontend/src/components/common/statusIcon.module.scss rename to frontend/src/components/tags/statusIcon.module.scss diff --git a/frontend/src/components/common/statusIcon.tsx b/frontend/src/components/tags/statusIcon.tsx similarity index 97% rename from frontend/src/components/common/statusIcon.tsx rename to frontend/src/components/tags/statusIcon.tsx index 93243c1..54eef91 100644 --- a/frontend/src/components/common/statusIcon.tsx +++ b/frontend/src/components/tags/statusIcon.tsx @@ -1,14 +1,16 @@ import { - Tag, CircleCheckBig, CircleDashed, - RectangleEllipsis, Copy, + RectangleEllipsis, + Tag, TriangleAlert, } from "lucide-react"; import { Tooltip } from "@mui/material"; -import { tagQueryOptions } from "@/lib/tag"; import { useQuery } from "@tanstack/react-query"; + +import { tagQueryOptions } from "@/components/common/_query"; + import styles from "./statusIcon.module.scss"; export function TagStatusIcon({ diff --git a/frontend/src/components/common/tagGroupView.tsx b/frontend/src/components/tags/tagGroupView.tsx similarity index 97% rename from frontend/src/components/common/tagGroupView.tsx rename to frontend/src/components/tags/tagGroupView.tsx index 2788856..b899cb4 100644 --- a/frontend/src/components/common/tagGroupView.tsx +++ b/frontend/src/components/tags/tagGroupView.tsx @@ -1,12 +1,12 @@ +import { ChevronDown } from "lucide-react"; import React, { ComponentProps } from "react"; import Accordion from "@mui/material/Accordion"; -import AccordionSummary from "@mui/material/AccordionSummary"; import AccordionDetails from "@mui/material/AccordionDetails"; -import Typography from "@mui/material/Typography"; +import AccordionSummary from "@mui/material/AccordionSummary"; import { styled } from "@mui/material/styles"; -import { ChevronDown } from "lucide-react"; +import Typography from "@mui/material/Typography"; -import { SelectionProvider } from "@/components/context/useSelection"; +import { SelectionProvider } from "@/components/common/useSelection"; const StyledGroupAccordion = styled(Accordion)(({ theme }) => ({ // border: `1px solid ${theme.palette.divider}`, diff --git a/frontend/src/components/common/tagView.module.scss b/frontend/src/components/tags/tagView.module.scss similarity index 91% rename from frontend/src/components/common/tagView.module.scss rename to frontend/src/components/tags/tagView.module.scss index 9731b80..4acc527 100644 --- a/frontend/src/components/common/tagView.module.scss +++ b/frontend/src/components/tags/tagView.module.scss @@ -23,7 +23,8 @@ // to get the color codes working, you will need the ansi css classes form our main.css .tagPreview { - white-space: pre; + white-space: pre-wrap; + overflow-wrap: break-workd; // this allows wrapping e.g. spotify urls font-family: monospace; font-size: 0.7rem; } diff --git a/frontend/src/components/common/tagView.tsx b/frontend/src/components/tags/tagView.tsx similarity index 92% rename from frontend/src/components/common/tagView.tsx rename to frontend/src/components/tags/tagView.tsx index d8c15aa..f5cd24d 100644 --- a/frontend/src/components/common/tagView.tsx +++ b/frontend/src/components/tags/tagView.tsx @@ -1,31 +1,29 @@ -import { useQuery } from "@tanstack/react-query"; +import { Ellipsis } from "lucide-react"; +import { useEffect, useState } from "react"; import Ansi from "@curvenote/ansi-to-react"; +import { Typography } from "@mui/material"; import Accordion from "@mui/material/Accordion"; -import AccordionSummary from "@mui/material/AccordionSummary"; import AccordionDetails from "@mui/material/AccordionDetails"; +import AccordionSummary from "@mui/material/AccordionSummary"; import { styled } from "@mui/material/styles"; -import styles from "./tagView.module.scss"; +import { useQuery } from "@tanstack/react-query"; + +import { APIError, tagQueryOptions } from "@/components/common/_query"; +import { useConfig } from "@/components/common/useConfig"; +import { useSelection } from "@/components/common/useSelection"; +import { useSiblings } from "@/components/common/useSiblings"; -import { tagQueryOptions } from "@/lib/tag"; -import { APIError } from "@/lib/fetch"; -import { SimilarityBadge } from "./similarityBadge"; -import { Typography } from "@mui/material"; -import { Ellipsis } from "lucide-react"; -import { - useEffect, - useState, -} from "react"; -import { useSelection } from "@/components/context/useSelection"; import { CollapseAllAction, ContextMenu, - ExpandAllAction, defaultActions, -} from "./contextMenu"; -import { useSiblings } from "@/components/context/useSiblings"; -import { useConfig } from "@/components/context/useConfig"; + ExpandAllAction, +} from "../common/contextMenu"; +import { SimilarityBadge } from "./similarityBadge"; import { TagStatusIcon } from "./statusIcon"; +import styles from "./tagView.module.scss"; + const StyledTagAccordion = styled(Accordion)(({ theme }) => ({ // borderTop: `1px solid ${theme.palette.divider}`, borderRadius: 0, @@ -63,9 +61,8 @@ const StyledTagAccordion = styled(Accordion)(({ theme }) => ({ }, })); - export interface ExpandableSib { - setExpanded: (state: boolean) => void + setExpanded: (state: boolean) => void; } export function TagView({ tagId, tagPath }: { tagId?: string; tagPath?: string }) { @@ -77,7 +74,8 @@ export function TagView({ tagId, tagPath }: { tagId?: string; tagPath?: string } tagQueryOptions(tagId, tagPath) ); const { isSelected, toggleSelection, markSelectable } = useSelection(); - const { register: registerSibling, unregister: unregisterSibling } = useSiblings(); + const { register: registerSibling, unregister: unregisterSibling } = + useSiblings(); const config = useConfig(); const [expanded, setExpanded] = useState(config.gui.tags.expand_tags); const handleSelect = (event: React.MouseEvent) => { @@ -95,23 +93,20 @@ export function TagView({ tagId, tagPath }: { tagId?: string; tagPath?: string } } }; - // Self register as sibling useEffect(() => { registerSibling(identifier, { - setExpanded + setExpanded, }); return () => { unregisterSibling(identifier); - } + }; }, [identifier, registerSibling, unregisterSibling]); - useEffect(() => { if (data?.album_folder) markSelectable(data?.album_folder); }, [markSelectable, data?.album_folder]); - if (isLoading || isPending || isError) { let inner = ""; if (isLoading) inner = "Loading..."; @@ -167,7 +162,6 @@ export function TagView({ tagId, tagPath }: { tagId?: string; tagPath?: string } ); } - export const TagPreview = ({ tagId, tagPath, diff --git a/frontend/src/lib/fetch.ts b/frontend/src/lib/fetch.ts deleted file mode 100644 index 8895c41..0000000 --- a/frontend/src/lib/fetch.ts +++ /dev/null @@ -1,49 +0,0 @@ -// thin wrapper around fetch so that we can use the vite dev server with our backend - -const originalFetch = window.fetch; -const devMode = import.meta.env.MODE === "development"; -const apiPrefix = devMode ? "http://0.0.0.0:5001/api_v1" : "/api_v1"; - -window.fetch = async ( - input: RequestInfo | URL, - init?: RequestInit -): Promise => { - if (input instanceof URL) { - input = input.pathname; - } else if (!(typeof input === "string")) { - input = input.url; - } - - // console.log("fetching", apiPrefix + input); - const response = await originalFetch(apiPrefix + input, init); - if (!response.ok) { - const data = (await response.json()) as ErrorData; - throw new APIError(data); - } - - if (devMode && response.headers.get("Content-Type") == "application/json") { - try { - await response.clone().json(); - } catch (e) { - throw new Error("Failed to parse response as JSON in fetch()"); - } - } - - return response; -}; - -interface ErrorData { - error: string; //name - messages: string; - trace?: string; -} - -export class APIError extends Error { - trace?: string; - - constructor(public data: ErrorData) { - super(data.messages); - this.name = data.error; - this.trace = data.trace ?? undefined; - } -} \ No newline at end of file diff --git a/frontend/src/lib/library.ts b/frontend/src/lib/library.ts deleted file mode 100644 index 4e84754..0000000 --- a/frontend/src/lib/library.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { queryOptions } from "@tanstack/react-query"; - -export interface MinimalArtist { - name: string; - albums: MinimalAlbum[]; -} - -export interface MinimalAlbum { - id: number; - name: string; -} - -export interface Album extends MinimalAlbum { - items: MinimalItem[]; -} - -export interface MinimalItem { - id: number; - name: string; -} - -export interface Item extends MinimalItem { - [key: string]: unknown; // enable indexing item[key] - - album?: string; // "In the City / Wasted" - album_id?: number; // 1 - albumartist?: string; // "Basstripper" - albumartist_credit?: string; // "Basstripper" - albumartist_sort?: string; // "Basstripper" - albumartists?: string[]; // ["Basstripper"] - albumartists_credit?: string[]; // ["Basstripper"] - albumartists_sort?: string[]; // ["Basstripper"] - albumdisambig?: string; // "" - albumstatus?: string; // "Official" - albumtype?: string; // "single" - albumtypes?: string[]; // ["single"] - acoustid_fingerprint?: string; // "" - acoustid_id?: string; // "" - added?: number; // 1715716057.413927 - arranger?: string; // "" - artist?: string; // "Basstripper" - artist_credit?: string; // "Basstripper" - artist_sort?: string; // "Basstripper" - artists?: string[]; // ["Basstripper"] - artists_credit?: string[]; // ["Basstripper"] - artists_ids?: number[]; // [] - artists_sort?: string[]; // ["Basstripper"] - asin?: string; // "" - barcode?: string; // "197338612422" - bitrate?: number; // 1033095 - bitrate_mode?: string; // "" - bpm?: number; // 0 - catalognum?: string; // "" - channels?: number; // 2 - comments?: string; // "" - comp?: number; // 0 - composer?: string; // "" - composer_sort?: string; // "" - country?: string; // "" - data_source?: string; // "MusicBrainz" - day?: number; // 14 - disc?: number; // 1 - discogs_albumid?: number; // 0 - discogs_artistid?: number; // 0 - discogs_labelid?: number; // 0 - disctitle?: string; // "" - disctotal?: number; // 1 - encoder?: string; // "" - encoder_info?: string; // "" - encoder_settings?: string; // "" - format?: string; // "FLAC" - genre?: string; // "" - grouping?: string; // "" - initial_key?: string; // null - isrc?: string; // "US39N2308955" - label?: string; // "DnB Allstars Records" - language?: string; // "" - length?: number; // 156.34643990929706 - lyricist?: string; // "" - lyrics?: string; // "" - mb_albumartistid?: string; // "82687fdf-84d6-49ac-bff2-de88cb42e5a2" - mb_albumartistids?: string[]; // ["82687fdf-84d6-49ac-bff2-de88cb42e5a2"] - mb_albumid?: string; // "3a76ece6-89ec-43c6-920f-ff955d2e4f9e" - mb_artistid?: string; // "82687fdf-84d6-49ac-bff2-de88cb42e5a2" - mb_artistids?: string[]; // ["82687fdf-84d6-49ac-bff2-de88cb42e5a2"] - mb_releasegroupid?: string; // "6e46e8cc-2546-45d1-a24c-23982cf36980" - mb_releasetrackid?: string; // "6886cc80-ccbe-4c92-a8b5-b066b39a666f" - mb_trackid?: string; // "ee846065-2f7d-4e87-ae3f-b925c75359c1" - mb_workid?: string; // "" - media?: string; // "Digital Media" - month?: number; // 7 - mtime?: number; // 1715716092 - original_day?: number; // 14 - original_month?: number; // 7 - original_year?: number; // 2023 - path?: string; // "/music/imported/Basstripper/In the City - Wasted/01 In the City [1033kbps].flac" - // r128_album_gain?: null; // null - // r128_track_gain?: null; // null - release_group_title?: string; // "In the City / Wasted" - releasegroupdisambig?: string; // "" - remixer?: string; // "" - // rg_album_gain?: null; // null - // rg_album_peak?: null; // null - // rg_track_gain?: null; // null - // rg_track_peak?: null; // null - samplerate?: number; // 44100 - script?: string; // "Latn" - size?: number; // 20487451 - style?: string; // "" - title?: string; // "In the City" - track?: number; // 1 - track_alt?: string; // "1" - trackdisambig?: string; // "" - tracktotal?: number; // 2 - work?: string; // "" - work_disambig?: string; // "" - year?: number; // 2023 -} - -function _url_parse_minimal_expand( - url: string, - { minimal, expand }: { minimal: boolean; expand: boolean } -) { - const params = []; - if (minimal) { - params.push("minimal"); - } - if (expand) { - params.push("expand"); - } - return params.length ? `${url}?${params.join("&")}` : url; -} - -export const artistsQueryOptions = () => - queryOptions({ - queryKey: ["artists"], - queryFn: () => fetchArtists(), - }); - -export async function fetchArtists(): Promise { - const response = await fetch(`/library/artist/`); - return (await response.json()) as MinimalArtist[]; -} - -export const artistQueryOptions = ({ - name, - expand = false, - minimal = true, -}: { - name: string; - expand?: boolean; - minimal?: boolean; -}) => - queryOptions({ - queryKey: ["artist", name, expand, minimal], - queryFn: () => fetchArtist({ name, expand, minimal }), - }); - -export async function fetchArtist({ - name, - expand = false, - minimal = true, -}: { - name: string; - expand?: boolean; - minimal?: boolean; -}): Promise { - const url = _url_parse_minimal_expand(`/library/artist/${name}`, { - expand, - minimal, - }); - const response = await fetch(url); - return (await response.json()) as MinimalArtist; -} - -export const albumQueryOptions = ({ - id, - expand = true, - minimal = true, -}: { - id: number; - expand?: boolean; - minimal?: boolean; -}) => - queryOptions({ - queryKey: ["album", id, expand, minimal], - queryFn: () => fetchAlbum({ id, expand, minimal }), - }); - -export async function fetchAlbum({ - id, - expand = false, - minimal = true, -}: { - id: number; - expand?: boolean; - minimal?: boolean; -}): Promise { - const url = _url_parse_minimal_expand(`/library/album/${id}`, { expand, minimal }); - console.log(url); - const response = await fetch(url); - return (await response.json()) as MinimalAlbum; -} - -export const itemQueryOptions = ({ - id, - expand = false, - minimal = true, -}: { - id: number; - expand?: boolean; - minimal?: boolean; -}) => - queryOptions({ - queryKey: ["item", id, expand, minimal], - queryFn: () => fetchItem({ id, expand, minimal }), - }); - -export async function fetchItem({ - id, - expand = false, - minimal = true, -}: { - id: number; - expand?: boolean; - minimal?: boolean; -}): Promise { - const url = _url_parse_minimal_expand(`/library/item/${id}`, { expand, minimal }); - const response = await fetch(url); - return (await response.json()) as Item; -} diff --git a/frontend/src/lib/stats.ts b/frontend/src/lib/stats.ts deleted file mode 100644 index 6a9c2eb..0000000 --- a/frontend/src/lib/stats.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** You can find all fetch and query functions - * for the general stats here. - * - * I.e. inbox, tags, library, etc. - */ - -import { queryOptions } from "@tanstack/react-query"; - -export interface LibraryStats { - libraryPath: string; - items: number; - albums: number; - artists: number; - genres: number; - labels: number; - size: number; - lastItemAdded?: Date; - lastItemModified?: Date; -} - -export const libraryStatsQueryOptions = () => { - return queryOptions({ - queryKey: ["libraryStats"], - queryFn: async () => { - const response = await fetch(`/library/stats`); - const dat = (await response.json()) as LibraryStats; - - // convert lastItemAdded to Date - if (dat.lastItemAdded) dat.lastItemAdded = new Date(dat.lastItemAdded); - if (dat.lastItemModified) - dat.lastItemModified = new Date(dat.lastItemModified); - - return dat; - }, - }); -}; diff --git a/frontend/src/index.css b/frontend/src/main.css similarity index 94% rename from frontend/src/index.css rename to frontend/src/main.css index 8eae7e6..4378c5d 100644 --- a/frontend/src/index.css +++ b/frontend/src/main.css @@ -130,3 +130,11 @@ .ansi-bright-white-fg { color: #FFFFFF; } + + +main { + height: 100dvh; + width: 100dvw; + display: flex; + flex-direction: column; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 86e4995..ac355d2 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,18 +1,18 @@ import { StrictMode } from "react"; import ReactDOM from "react-dom/client"; -import { RouterProvider, createRouter } from "@tanstack/react-router"; -import { QueryClient } from "@tanstack/react-query"; +import Box from "@mui/material/Box"; +import { createRouter, RouterProvider } from "@tanstack/react-router"; + +import { customizeFetch, queryClient } from "@/components/common/_query"; +import LoadingIndicator from "@/components/common/loadingIndicator"; +import { PrefetchConfigQueryClientProvider } from "@/components/common/useConfig"; +import { StatusContextProvider } from "@/components/common/useSocket"; -// Import the generated route tree import { routeTree } from "./routeTree.gen"; import ThemeProvider from "./theme"; -import CircularProgress from "@mui/material/CircularProgress"; -import "@/lib/fetch"; -import { StatusContextProvider } from "./lib/socket"; -import { PrefetchConfigQueryClientProvider } from "./components/context/useConfig"; -// Create a new query client instance -export const queryClient = new QueryClient({}); +// we tweak the backend-route on the dev server +customizeFetch(); // Create a new router instance const router = createRouter({ @@ -22,14 +22,19 @@ const router = createRouter({ }, defaultPreload: "intent", defaultPendingComponent: () => ( -
-
- -

- Hang tight! We're tuning our server to tag your tunes. -

-
-
+ + + ), // Since we're using React Query, we don't want loader calls to ever be stale diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 71fe8f8..8bd3bb4 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -11,10 +11,10 @@ // Import Routes import { Route as rootRoute } from './routes/__root' -import { Route as InboxImport } from './routes/inbox' -import { Route as FrontpageImport } from './routes/_frontpage' import { Route as TagsIndexImport } from './routes/tags/index' +import { Route as InboxIndexImport } from './routes/inbox/index' import { Route as FrontpageIndexImport } from './routes/_frontpage/index' +import { Route as LibrarySearchImport } from './routes/library/search' import { Route as LibraryBrowseImport } from './routes/library/browse' import { Route as FrontpageModalImport } from './routes/_frontpage/_modal' import { Route as LibraryBrowseArtistImport } from './routes/library/browse.$artist' @@ -24,24 +24,24 @@ import { Route as LibraryBrowseArtistAlbumIdItemIdImport } from './routes/librar // Create/Update Routes -const InboxRoute = InboxImport.update({ - path: '/inbox', +const TagsIndexRoute = TagsIndexImport.update({ + path: '/tags/', getParentRoute: () => rootRoute, } as any) -const FrontpageRoute = FrontpageImport.update({ - id: '/_frontpage', +const InboxIndexRoute = InboxIndexImport.update({ + path: '/inbox/', getParentRoute: () => rootRoute, } as any) -const TagsIndexRoute = TagsIndexImport.update({ - path: '/tags/', +const FrontpageIndexRoute = FrontpageIndexImport.update({ + path: '/', getParentRoute: () => rootRoute, } as any) -const FrontpageIndexRoute = FrontpageIndexImport.update({ - path: '/', - getParentRoute: () => FrontpageRoute, +const LibrarySearchRoute = LibrarySearchImport.update({ + path: '/library/search', + getParentRoute: () => rootRoute, } as any) const LibraryBrowseRoute = LibraryBrowseImport.update({ @@ -50,8 +50,8 @@ const LibraryBrowseRoute = LibraryBrowseImport.update({ } as any) const FrontpageModalRoute = FrontpageModalImport.update({ - id: '/_modal', - getParentRoute: () => FrontpageRoute, + id: '/_frontpage/_modal', + getParentRoute: () => rootRoute, } as any) const LibraryBrowseArtistRoute = LibraryBrowseArtistImport.update({ @@ -81,26 +81,12 @@ const LibraryBrowseArtistAlbumIdItemIdRoute = declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_frontpage': { - id: '/_frontpage' - path: '' - fullPath: '' - preLoaderRoute: typeof FrontpageImport - parentRoute: typeof rootRoute - } - '/inbox': { - id: '/inbox' - path: '/inbox' - fullPath: '/inbox' - preLoaderRoute: typeof InboxImport - parentRoute: typeof rootRoute - } '/_frontpage/_modal': { id: '/_frontpage/_modal' path: '' fullPath: '' preLoaderRoute: typeof FrontpageModalImport - parentRoute: typeof FrontpageImport + parentRoute: typeof rootRoute } '/library/browse': { id: '/library/browse' @@ -109,12 +95,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LibraryBrowseImport parentRoute: typeof rootRoute } + '/library/search': { + id: '/library/search' + path: '/library/search' + fullPath: '/library/search' + preLoaderRoute: typeof LibrarySearchImport + parentRoute: typeof rootRoute + } '/_frontpage/': { id: '/_frontpage/' path: '/' fullPath: '/' preLoaderRoute: typeof FrontpageIndexImport - parentRoute: typeof FrontpageImport + parentRoute: typeof rootRoute + } + '/inbox/': { + id: '/inbox/' + path: '/inbox' + fullPath: '/inbox' + preLoaderRoute: typeof InboxIndexImport + parentRoute: typeof rootRoute } '/tags/': { id: '/tags/' @@ -157,13 +157,9 @@ declare module '@tanstack/react-router' { // Create and export the route tree export const routeTree = rootRoute.addChildren({ - FrontpageRoute: FrontpageRoute.addChildren({ - FrontpageModalRoute: FrontpageModalRoute.addChildren({ - FrontpageModalScheduleRoute, - }), - FrontpageIndexRoute, + FrontpageModalRoute: FrontpageModalRoute.addChildren({ + FrontpageModalScheduleRoute, }), - InboxRoute, LibraryBrowseRoute: LibraryBrowseRoute.addChildren({ LibraryBrowseArtistRoute: LibraryBrowseArtistRoute.addChildren({ LibraryBrowseArtistAlbumIdRoute: @@ -172,6 +168,9 @@ export const routeTree = rootRoute.addChildren({ }), }), }), + LibrarySearchRoute, + FrontpageIndexRoute, + InboxIndexRoute, TagsIndexRoute, }) @@ -183,25 +182,16 @@ export const routeTree = rootRoute.addChildren({ "__root__": { "filePath": "__root.tsx", "children": [ - "/_frontpage", - "/inbox", + "/_frontpage/_modal", "/library/browse", + "/library/search", + "/_frontpage/", + "/inbox/", "/tags/" ] }, - "/_frontpage": { - "filePath": "_frontpage.tsx", - "children": [ - "/_frontpage/_modal", - "/_frontpage/" - ] - }, - "/inbox": { - "filePath": "inbox.tsx" - }, "/_frontpage/_modal": { "filePath": "_frontpage/_modal.tsx", - "parent": "/_frontpage", "children": [ "/_frontpage/_modal/schedule" ] @@ -212,9 +202,14 @@ export const routeTree = rootRoute.addChildren({ "/library/browse/$artist" ] }, + "/library/search": { + "filePath": "library/search.tsx" + }, "/_frontpage/": { - "filePath": "_frontpage/index.tsx", - "parent": "/_frontpage" + "filePath": "_frontpage/index.tsx" + }, + "/inbox/": { + "filePath": "inbox/index.tsx" }, "/tags/": { "filePath": "tags/index.tsx" diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index e19a8e0..a772e37 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -1,10 +1,10 @@ -import { createRootRouteWithContext, Outlet } from "@tanstack/react-router"; +import Container from "@mui/material/Container"; import { QueryClient } from "@tanstack/react-query"; +import { createRootRouteWithContext, Outlet } from "@tanstack/react-router"; -import "../index.css"; -import NavTabs from "@/components/common/navigation/tabs"; -import Container from "@mui/material/Container"; -import { Terminal, TerminalContextProvider } from "@/components/terminal"; +import NavTabs from "@/components/frontpage/navbar"; +import { TerminalContextProvider } from "@/components/frontpage/terminal"; +import ToolBar from "@/components/frontpage/toolbar"; export const Route = createRootRouteWithContext<{ queryClient: QueryClient; @@ -14,14 +14,23 @@ export const Route = createRootRouteWithContext<{ function RootComponent() { return ( -
+
- - + - +
); diff --git a/frontend/src/routes/_frontpage.tsx b/frontend/src/routes/_frontpage.tsx deleted file mode 100644 index 6d2cf55..0000000 --- a/frontend/src/routes/_frontpage.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Outlet, createFileRoute } from "@tanstack/react-router"; -import { InboxStatsGridItems } from "@/components/frontpage/inboxStats"; - -import Grid from "@mui/material/Unstable_Grid2"; -import { LibraryStats } from "@/components/frontpage/libraryStats"; - -export const Route = createFileRoute("/_frontpage")({ - component: Index, -}); - -/** The frontpage is layout which adds an overview - * of the current inbox and database. - * - * It also gives an outlet to render other relevant content - * underneath. This may also be used to render a modal. - */ -function Index() { - return ( -
- - - - - - - -
- ); -} diff --git a/frontend/src/routes/_frontpage/_modal.tsx b/frontend/src/routes/_frontpage/_modal.tsx index 7674887..7c30dd7 100644 --- a/frontend/src/routes/_frontpage/_modal.tsx +++ b/frontend/src/routes/_frontpage/_modal.tsx @@ -1,10 +1,9 @@ -import { Outlet, createFileRoute, useNavigate } from "@tanstack/react-router"; - -import Dialog from "@mui/material/Dialog"; import { forwardRef, useState } from "react"; -import { TransitionProps } from "@mui/material/transitions"; -import Slide from "@mui/material/Slide"; import { Button, DialogActions, DialogContent } from "@mui/material"; +import Dialog from "@mui/material/Dialog"; +import Slide from "@mui/material/Slide"; +import { TransitionProps } from "@mui/material/transitions"; +import { createFileRoute, Outlet, useNavigate } from "@tanstack/react-router"; export const Route = createFileRoute("/_frontpage/_modal")({ component: DialogWrapper, diff --git a/frontend/src/routes/_frontpage/index.tsx b/frontend/src/routes/_frontpage/index.tsx index d10862e..fed47ab 100644 --- a/frontend/src/routes/_frontpage/index.tsx +++ b/frontend/src/routes/_frontpage/index.tsx @@ -1,6 +1,29 @@ -import { createFileRoute } from "@tanstack/react-router"; +import Grid from "@mui/material/Unstable_Grid2"; +import { createFileRoute,Outlet } from "@tanstack/react-router"; + +import { InboxStatsGridItems } from "@/components/frontpage/inboxStats"; +import { LibraryStats } from "@/components/frontpage/libraryStats"; -// We just want to show the layout here export const Route = createFileRoute("/_frontpage/")({ - component: () => <>, + component: Index, }); + +/** The frontpage is layout which adds an overview + * of the current inbox and database. + * + * It also gives an outlet to render other relevant content + * underneath. This may also be used to render a modal. + */ +function Index() { + return ( +
+ + + + + + + +
+ ); +} diff --git a/frontend/src/routes/inbox.tsx b/frontend/src/routes/inbox/index.tsx similarity index 93% rename from frontend/src/routes/inbox.tsx rename to frontend/src/routes/inbox/index.tsx index 3658b38..50abefa 100644 --- a/frontend/src/routes/inbox.tsx +++ b/frontend/src/routes/inbox/index.tsx @@ -1,24 +1,24 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { ChevronRight } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Box, Card, Typography } from "@mui/material"; +import * as Collapsible from "@radix-ui/react-collapsible"; import { useQuery } from "@tanstack/react-query"; -import { FsPath, inboxQueryByPathOptions } from "@/lib/inbox"; -import { TagStatusIcon } from "@/components/common/statusIcon"; -import { SimilarityBadgeWithHover } from "@/components/common/similarityBadge"; -import { SelectionProvider, useSelection } from "@/components/context/useSelection"; +import { createFileRoute } from "@tanstack/react-router"; + +import { FsPath, inboxQueryByPathOptions } from "@/components/common/_query"; import { ContextMenu, - SelectionSummary, defaultActions, + SelectionSummary, } from "@/components/common/contextMenu"; +import { MinimalConfig, useConfig } from "@/components/common/useConfig"; +import { SelectionProvider, useSelection } from "@/components/common/useSelection"; +import { SimilarityBadgeWithHover } from "@/components/tags/similarityBadge"; +import { TagStatusIcon } from "@/components/tags/statusIcon"; -import styles from "./inbox.module.scss"; -import { ChevronRight } from "lucide-react"; +import styles from "@/components/inbox/inbox.module.scss"; -import * as Collapsible from "@radix-ui/react-collapsible"; -import { useEffect, useState } from "react"; -import { MinimalConfig, useConfig } from "@/components/context/useConfig"; -import { Card, Typography } from "@mui/material"; - -export const Route = createFileRoute("/inbox")({ +export const Route = createFileRoute("/inbox/")({ component: () => , }); @@ -31,11 +31,11 @@ function Inboxes() { } return ( - <> + {Object.values(inboxes).map((inbox, i) => { return ; })} - + ); } diff --git a/frontend/src/routes/library/browse.$artist.$albumId.$itemId.tsx b/frontend/src/routes/library/browse.$artist.$albumId.$itemId.tsx index 563de4a..501f5d4 100644 --- a/frontend/src/routes/library/browse.$artist.$albumId.$itemId.tsx +++ b/frontend/src/routes/library/browse.$artist.$albumId.$itemId.tsx @@ -1,18 +1,19 @@ -import { itemQueryOptions } from "@/lib/library"; +import z from "zod"; import Box from "@mui/material/Box"; +import Paper from "@mui/material/Paper"; import { createFileRoute } from "@tanstack/react-router"; -import z from "zod"; -import styles from "./browse.module.scss"; -import ItemDetailsTableView from "@/components/common/itemDetailsTable"; -import { useState } from "react"; -import { Bug as BugOn, BugOff } from "lucide-react"; -import IconButton from "@mui/material/IconButton"; -import Tooltip from "@mui/material/Tooltip"; + +import { itemQueryOptions } from "@/components/library/_query"; +import { BrowserHeader } from "@/components/library/browserHeader"; +import { ItemView } from "@/components/library/itemAlbumDetails"; + +import styles from "@/components/library/library.module.scss"; export const Route = createFileRoute("/library/browse/$artist/$albumId/$itemId")({ parseParams: (params) => ({ itemId: z.number().int().parse(parseInt(params.itemId)), }), + // PS 24-07-26: I kept the loader, although the new TrackView does query on its own. because it uses the same querykeys, i suppose pre-loading should still work. loader: (opts) => opts.context.queryClient.ensureQueryData( itemQueryOptions({ @@ -21,25 +22,26 @@ export const Route = createFileRoute("/library/browse/$artist/$albumId/$itemId") expand: true, }) ), - component: TrackView, + component: TrackPage, }); -function TrackView() { - const item = Route.useLoaderData(); - const [detailed, setDetailed] = useState(false); +interface RouteParams { + artist: string; + albumId: number; + itemId: number; +} +function TrackPage() { + const params = Route.useParams(); return ( <> - - - setDetailed(!detailed)}> - {detailed && } - {!detailed && } - - - - - + + Info + + + ); } diff --git a/frontend/src/routes/library/browse.$artist.$albumId.tsx b/frontend/src/routes/library/browse.$artist.$albumId.tsx index 6df26c4..30aa529 100644 --- a/frontend/src/routes/library/browse.$artist.$albumId.tsx +++ b/frontend/src/routes/library/browse.$artist.$albumId.tsx @@ -1,13 +1,16 @@ -import List from "@/components/common/list"; -import { Album, albumQueryOptions } from "@/lib/library"; -import Box from "@mui/material/Box"; -import { Outlet, createFileRoute } from "@tanstack/react-router"; -import z from "zod"; -import styles from "./browse.module.scss"; -import { BASE_ROUTE } from "./browse"; import { useMemo } from "react"; +import z from "zod"; +import { Box, Paper } from "@mui/material"; +import { createFileRoute,Outlet } from "@tanstack/react-router"; + +import { Album, albumQueryOptions,LIB_BROWSE_ROUTE } from "@/components/common/_query"; +import LoadingIndicator from "@/components/common/loadingIndicator"; +import { BrowserHeader } from "@/components/library/browserHeader"; +import List from "@/components/library/list"; -export const Route = createFileRoute(`${BASE_ROUTE}/$artist/$albumId`)({ +import styles from "@/components/library/library.module.scss"; + +export const Route = createFileRoute(`${LIB_BROWSE_ROUTE}/$artist/$albumId`)({ parseParams: (params) => ({ albumId: z.number().int().parse(parseInt(params.albumId)), }), @@ -33,20 +36,33 @@ function AlbumOverview() { const params = Route.useParams(); const data = useMemo(() => { - return (album as Album).items.map((item) => ({ - to: `${BASE_ROUTE}/$artist/$albumId/$itemId`, + return (album as Album).items?.map((item) => ({ + to: `${LIB_BROWSE_ROUTE}/$artist/$albumId/$itemId`, params: { artist: params.artist, albumId: params.albumId, itemId: item.id }, label: item.name, className: styles.listItem, - "data-selected": params.itemId && params.itemId == item.id + "data-selected": params.itemId && params.itemId == item.id, })); }, [album, params]); + // for mobile, we only want to show one central column. + const isSecondary = Boolean(params.itemId); + return ( <> - - {List.Item} - + + Item + + {album && data ? ( + + {List.Item} + + ) : ( + + )} + ); diff --git a/frontend/src/routes/library/browse.$artist.tsx b/frontend/src/routes/library/browse.$artist.tsx index 9cc7d99..db80c96 100644 --- a/frontend/src/routes/library/browse.$artist.tsx +++ b/frontend/src/routes/library/browse.$artist.tsx @@ -1,13 +1,16 @@ -import List from "@/components/common/list"; -import { artistQueryOptions } from "@/lib/library"; -import Box from "@mui/material/Box"; -import { Outlet, createFileRoute } from "@tanstack/react-router"; -import z from "zod"; -import styles from "./browse.module.scss"; -import { BASE_ROUTE } from "./browse"; import { useMemo } from "react"; +import z from "zod"; +import { Box, Paper } from "@mui/material"; +import { createFileRoute, Outlet } from "@tanstack/react-router"; + +import { artistQueryOptions, LIB_BROWSE_ROUTE } from "@/components/common/_query"; +import LoadingIndicator from "@/components/common/loadingIndicator"; +import { BrowserHeader } from "@/components/library/browserHeader"; +import List from "@/components/library/list"; -export const Route = createFileRoute(`${BASE_ROUTE}/$artist`)({ +import styles from "@/components/library/library.module.scss"; + +export const Route = createFileRoute(`${LIB_BROWSE_ROUTE}/$artist`)({ parseParams: (params) => ({ artist: z.string().parse(params.artist), }), @@ -33,7 +36,7 @@ function ArtistOverview() { const data = useMemo(() => { return artist.albums.map((album) => ({ - to: `${BASE_ROUTE}/$artist/$albumId`, + to: `${LIB_BROWSE_ROUTE}/$artist/$albumId`, params: { artist: params.artist, albumId: album.id }, label: album.name, className: styles.listItem, @@ -41,11 +44,26 @@ function ArtistOverview() { })); }, [artist, params]); + console.log("browse.$artist ", artist, data); + + // for mobile, we only want to show one central column. + const isSecondary = Boolean(params.albumId); + return ( <> - - {List.Item} - + + Album + + {artist && data ? ( + + {List.Item} + + ) : ( + + )} + ); diff --git a/frontend/src/routes/library/browse.module.scss b/frontend/src/routes/library/browse.module.scss deleted file mode 100644 index e1badd3..0000000 --- a/frontend/src/routes/library/browse.module.scss +++ /dev/null @@ -1,41 +0,0 @@ -.columnBrowser { - display: flex; - gap: 4px; -} - -.listBox { - // TODO: get navbar height and substract. - height: calc(100vh - 110px); - width: calc(25% - 4px); - display: flex; - align-items: start; - margin: 0; - gap: 0; - padding: 0; - border: 1px solid #495057; - border-radius: 0.3rem; - overflow-y: auto; - overflow-x: hidden; - // mui adds uls - & > ul { - padding: 0; - width: 100%; - } -} - -.listItem { - padding: 0rem 0.5rem; - width: 100%; - &[data-selected="true"] { - background-color: #6F7B8533; - } - &:hover { - background-color: #6F7B8555; - } -} - -.trackViewBox { - display: flex; - flex-direction: column; - padding: 0 0.5rem; -} diff --git a/frontend/src/routes/library/browse.tsx b/frontend/src/routes/library/browse.tsx index b3a3afe..ca88a2f 100644 --- a/frontend/src/routes/library/browse.tsx +++ b/frontend/src/routes/library/browse.tsx @@ -1,13 +1,15 @@ -import { artistsQueryOptions } from "@/lib/library"; -import { Outlet, createFileRoute } from "@tanstack/react-router"; - -import List from "@/components/common/list"; -import Box from "@mui/material/Box"; -import styles from "./browse.module.scss"; import { useMemo } from "react"; +import { Paper } from "@mui/material"; +import Box from "@mui/material/Box"; +import { createFileRoute, Outlet } from "@tanstack/react-router"; + +import { artistsQueryOptions, LIB_BROWSE_ROUTE } from "@/components/common/_query"; +import { BrowserHeader } from "@/components/library/browserHeader"; +import List from "@/components/library/list"; -export const BASE_ROUTE = "/library/browse"; -export const Route = createFileRoute(BASE_ROUTE)({ +import styles from "@/components/library/library.module.scss"; + +export const Route = createFileRoute(LIB_BROWSE_ROUTE)({ loader: (opts) => opts.context.queryClient.ensureQueryData(artistsQueryOptions()), component: () => , }); @@ -18,11 +20,11 @@ interface RouteParams { function AllArtists() { const artists = Route.useLoaderData(); - const params = Route.useParams < RouteParams >(); + const params = Route.useParams(); const data = useMemo(() => { return artists.map((artist) => ({ - to: `${BASE_ROUTE}/$artist`, + to: `${LIB_BROWSE_ROUTE}/$artist`, params: { artist: artist.name }, label: artist.name, className: styles.listItem, @@ -30,12 +32,23 @@ function AllArtists() { })); }, [artists, params]); + console.log("browse ", artists, data); + + // for mobile, we only want to show one central column. + const isSecondary = Boolean(params.artist); + return ( <> - - {List.Item} - + + Artist + + + {List.Item} + + diff --git a/frontend/src/routes/library/search.tsx b/frontend/src/routes/library/search.tsx new file mode 100644 index 0000000..6aef776 --- /dev/null +++ b/frontend/src/routes/library/search.tsx @@ -0,0 +1,383 @@ +import { ChevronRight, OctagonX, X } from "lucide-react"; +import { useEffect, useMemo, useRef } from "react"; +import { IconButton, InputAdornment, Paper, Tooltip } from "@mui/material"; +import Box from "@mui/material/Box"; +import CircularProgress from "@mui/material/CircularProgress"; +import TextField from "@mui/material/TextField"; +import ToggleButton from "@mui/material/ToggleButton"; +import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; +import { createFileRoute } from "@tanstack/react-router"; + +import { MinimalAlbum, MinimalItem } from "@/components/common/_query"; +import { JSONPretty } from "@/components/common/json"; +import { + SearchContextProvider, + SearchType, + useSearchContext, +} from "@/components/common/useSearch"; +import CoverArt from "@/components/library/coverArt"; +import { AlbumView, ItemView } from "@/components/library/itemAlbumDetails"; +import List from "@/components/library/list"; + +import styles from "@/components/library/library.module.scss"; + +export const Route = createFileRoute("/library/search")({ + component: SearchPage, +}); + +function SearchPage() { + return ( + + + + + + + + + + ); +} + +function SearchBar() { + const searchFieldRef = useRef(null); + const { query, setQuery, type, setType, setSelectedResult } = useSearchContext(); + + useEffect(() => { + if (searchFieldRef.current) { + searchFieldRef.current.focus(); + } + }, [searchFieldRef]); + + function handleTypeChange( + _e: React.MouseEvent, + newType: SearchType | null + ) { + if (newType !== null && newType !== type) { + setType(newType); + setSelectedResult(undefined); + } + } + + function handleInput(e: React.ChangeEvent) { + setQuery(e.target.value); + } + + return ( + { + e.preventDefault(); + }} + > + + ), + }} + /> + + {/* Type selector */} + + Item + Album + + + ); +} + +function CancelSearchButton({ + searchFieldRef, +}: { + searchFieldRef: React.RefObject; +}) { + const { cancelSearch, resetSearch, isFetching, query } = useSearchContext(); + + return ( + + + { + e.preventDefault(); + cancelSearch(); + resetSearch(); + if (searchFieldRef.current) { + searchFieldRef.current.focus(); + } + }} + > + {isFetching ? ( + + ) : ( + 0 ? 1 : 0.5, + }} + /> + )} + + + + ); +} + +function SearchResults() { + const { isError, error, isFetching, type, sentQuery, results } = useSearchContext(); + + if (isError) { + console.error("Error loading search results", error); + return ( + + Error loading results: + + + ); + } + + if (isFetching) { + return ( + + + + Searching {type}s with {sentQuery} ... + + + ); + } + + if (results === undefined) { + return ; + } + + if (results.length === 0) { + return ( + + + No {type}s found with {sentQuery} + + + ); + } + + // Show results! + return ( + <> + {type === "item" && } + {type === "album" && } + + ); +} + +export interface RouteParams { + type?: SearchType; + id?: number; +} + +function ItemResultsBox() { + const { results } = useSearchContext() as { results: MinimalItem[] }; + const { selectedResult, setSelectedResult } = useSearchContext(); + + const data = useMemo(() => { + return results.map((item) => ({ + className: styles.listItem, + "data-selected": selectedResult !== undefined && selectedResult === item.id, + onClick: () => setSelectedResult(item.id), + label: ( + + + {item.artist} + + + {item.name} + + ), + })); + }, [results, selectedResult, setSelectedResult]); + + return ( + + + {List.Item} + + + ); +} + +function AlbumResultsBox() { + const { results } = useSearchContext() as { results: MinimalAlbum[] }; + const { selectedResult, setSelectedResult } = useSearchContext(); + + const data = useMemo(() => { + return results.map((album) => ({ + className: styles.listItem, + "data-selected": selectedResult !== undefined && selectedResult == album.id, + onClick: () => setSelectedResult(album.id), + label: ( + + + {album.albumartist} + + + {album.name} + + ), + })); + }, [results, selectedResult, setSelectedResult]); + + return ( + + + {List.Item} + + + ); +} + +function SearchResultDetails() { + const { type, selectedResult } = useSearchContext(); + + if (selectedResult === undefined) { + return null; + } + + console.log(type); + let art = <>; + if (type === "item") { + console.log("1"); + art = ( + + ); + } else if (type === "album") { + console.log("2"); + art = ( + + ); + } + + return ( + <> + + + {art} + {type === "item" && } + {type === "album" && } + + + + ); +} + +function BeetsSearchHelp() { + return ( + + +

Search uses beets' query syntax

+
    +
  • + combine keywords with a space (AND):{" "} + magnetic tomorrow +
  • +
  • + combine keywords with a comma (OR):{" "} + magnetic tomorrow , beatles yesterday +
  • +
  • + search specific fields: artist:dream +
  • +
  • + escape phrases: "the rebel" or{" "} + the\ rebel +
  • +
  • + use - or ^ to exclude a term:{" "} + ^difficult +
  • +
+ +

Exact matches

+
    +
  • + artist:air substring match, default +
  • +
  • + artist:=~air exact match, ignore case +
  • +
  • + artist:=AIR exact match, case sensitive +
  • +
  • + work on phrases: artist:=~"dave matthews" +
  • +
  • + can be used across all fields: =~crash +
  • +
+ +

+ To use Regexp, add an extra : +

+
    +
  • + "artist::Ann(a|ie)" finds artists Anna + Calvi and Annie but not Annuals +
  • +
  • + ":Ho[pm]eless" to search all fields +
  • +
+ +

Common fields

+
    +
  • + title album genre{" "} + label isrc +
  • +
  • + artist (only for items, not albums) +
  • +
  • + albumartist albumartist_sort{" "} + albumtype +
  • +
  • + year added comment{" "} + data_source +
  • +
  • + path (searches recursively in sub directories) +
  • +
+
+
+ ); +} diff --git a/frontend/src/routes/tags/index.tsx b/frontend/src/routes/tags/index.tsx index ac725f3..2f59e80 100644 --- a/frontend/src/routes/tags/index.tsx +++ b/frontend/src/routes/tags/index.tsx @@ -1,11 +1,15 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { ComponentProps } from "react"; import { useSuspenseQuery } from "@tanstack/react-query"; -import { tagGroupAllQueryOptions, tagGroupIdQueryOptions } from "@/lib/tag"; -import TagGroupView from "@/components/common/tagGroupView"; +import { createFileRoute } from "@tanstack/react-router"; + +import { + tagGroupAllQueryOptions, + tagGroupIdQueryOptions, +} from "@/components/common/_query"; +import { SiblingRefsProvider } from "@/components/common/useSiblings"; +import TagGroupView from "@/components/tags/tagGroupView"; +import { TagView } from "@/components/tags/tagView"; -import { TagView } from "@/components/common/tagView"; -import { SiblingRefsProvider } from "@/components/context/useSiblings"; -import { ComponentProps } from "react"; export const Route = createFileRoute("/tags/")({ loader: (opts) => diff --git a/frontend/src/routes/tags/tags.module.scss b/frontend/src/routes/tags/tags.module.scss deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/theme.tsx b/frontend/src/theme.tsx index 6ac1a47..6c9b027 100644 --- a/frontend/src/theme.tsx +++ b/frontend/src/theme.tsx @@ -1,14 +1,14 @@ +import CssBaseline from "@mui/material/CssBaseline"; import { StyledEngineProvider } from "@mui/material/styles"; import { - ThemeProvider as MatThemeProvider, createTheme, Experimental_CssVarsProvider as CssVarsProvider, experimental_extendTheme as extendTheme, + ThemeProvider as MatThemeProvider, } from "@mui/material/styles"; -import CssBaseline from "@mui/material/CssBaseline"; // Global styles -import "./index.css"; +import "./main.css"; /** Relative basic theme for now * using a mint green and a orange @@ -31,6 +31,10 @@ const darkTheme = extendTheme({ action: { hover: "#212529", }, + background: { + default: "#000000", + paper: "#181A1C", + }, }, components: { MuiTooltip: { @@ -54,17 +58,11 @@ const darkTheme = extendTheme({ }, }, }, - MuiCard: { + MuiPaper: { styleOverrides: { root: { - backgroundColor: "#0B0C0D", - }, - }, - }, - MuiAccordion: { - styleOverrides: { - root: { - backgroundColor: "#0B0C0D", + // Ensuring consistent dark mode color independent of paper elevation + backgroundImage: "none", }, }, }, diff --git a/requirements.txt b/requirements.txt index 6dd67f1..80177eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ gunicorn==22.0.0 gevent==24.2.1 gevent-websocket==0.10.1 pytest==8.2.2 +pillow==10.4.0 # beets dependencies jellyfish==0.10 # current version does not compile on arm64 beets==2.0.0