From 81978e22d5b04ee332ccb3aa368364d0d25e4d4b Mon Sep 17 00:00:00 2001 From: dekzter Date: Mon, 10 Mar 2025 20:54:06 -0400 Subject: [PATCH 01/29] first run at mantine --- docker/docker-compose.dev.yml | 3 +- docker/entrypoint.sh | 1 - docker/uwsgi.dev.ini | 2 +- vite/.gitignore | 24 + vite/README.md | 12 + vite/eslint.config.js | 33 + vite/index.html | 13 + vite/package-lock.json | 4665 +++++++++++++++++ vite/package.json | 52 + vite/prettier.config.js | 10 + vite/public/vite.svg | 1 + vite/src/App.css | 38 + vite/src/App.jsx | 169 + vite/src/WebSocket.jsx | 85 + vite/src/api.js | 783 +++ vite/src/assets/react.svg | 1 + vite/src/components/Alert.jsx | 26 + vite/src/components/FloatingVideo.jsx | 98 + vite/src/components/Sidebar-new.jsx | 186 + vite/src/components/Sidebar.jsx | 189 + vite/src/components/forms/Channel.jsx | 491 ++ vite/src/components/forms/ChannelGroup.jsx | 71 + vite/src/components/forms/EPG.jsx | 143 + vite/src/components/forms/LoginForm.jsx | 93 + vite/src/components/forms/M3U.jsx | 192 + vite/src/components/forms/M3UProfile.jsx | 158 + vite/src/components/forms/M3UProfiles.jsx | 107 + vite/src/components/forms/Stream.jsx | 103 + vite/src/components/forms/StreamProfile.jsx | 111 + vite/src/components/forms/SuperuserForm.jsx | 128 + vite/src/components/forms/UserAgent.jsx | 119 + vite/src/components/tables/ChannelsTable.jsx | 1005 ++++ vite/src/components/tables/EPGsTable.jsx | 168 + vite/src/components/tables/M3UsTable.jsx | 242 + .../components/tables/StreamProfilesTable.jsx | 202 + vite/src/components/tables/StreamsTable.jsx | 608 +++ .../src/components/tables/UserAgentsTable.jsx | 219 + vite/src/helpers/index.jsx | 3 + vite/src/helpers/table.jsx | 61 + vite/src/images/dispatcharr.svg | 23 + vite/src/images/ghost.svg | 66 + vite/src/images/logo.png | Bin 0 -> 44564 bytes vite/src/index.css | 31 + vite/src/logo.svg | 1 + vite/src/main.jsx | 9 + vite/src/mantineTheme.jsx | 58 + vite/src/pages/Channels.jsx | 43 + vite/src/pages/Dashboard.jsx | 27 + vite/src/pages/EPG.jsx | 27 + vite/src/pages/Guide.jsx | 532 ++ vite/src/pages/Home.jsx | 14 + vite/src/pages/Login.jsx | 8 + vite/src/pages/M3U.jsx | 34 + vite/src/pages/Settings.jsx | 146 + vite/src/pages/StreamProfiles.jsx | 8 + vite/src/store/alerts.jsx | 24 + vite/src/store/auth.jsx | 129 + vite/src/store/channels.jsx | 89 + vite/src/store/epgs.jsx | 31 + vite/src/store/playlists.jsx | 65 + vite/src/store/settings.jsx | 34 + vite/src/store/streamProfiles.jsx | 40 + vite/src/store/streams.jsx | 41 + vite/src/store/useVideoStore.jsx | 24 + vite/src/store/userAgents.jsx | 40 + vite/src/theme.jsx | 92 + vite/src/utils.js | 53 + vite/vite.config.js | 26 + 68 files changed, 12327 insertions(+), 3 deletions(-) create mode 100644 vite/.gitignore create mode 100644 vite/README.md create mode 100644 vite/eslint.config.js create mode 100644 vite/index.html create mode 100644 vite/package-lock.json create mode 100644 vite/package.json create mode 100644 vite/prettier.config.js create mode 100644 vite/public/vite.svg create mode 100644 vite/src/App.css create mode 100644 vite/src/App.jsx create mode 100644 vite/src/WebSocket.jsx create mode 100644 vite/src/api.js create mode 100644 vite/src/assets/react.svg create mode 100644 vite/src/components/Alert.jsx create mode 100644 vite/src/components/FloatingVideo.jsx create mode 100644 vite/src/components/Sidebar-new.jsx create mode 100644 vite/src/components/Sidebar.jsx create mode 100644 vite/src/components/forms/Channel.jsx create mode 100644 vite/src/components/forms/ChannelGroup.jsx create mode 100644 vite/src/components/forms/EPG.jsx create mode 100644 vite/src/components/forms/LoginForm.jsx create mode 100644 vite/src/components/forms/M3U.jsx create mode 100644 vite/src/components/forms/M3UProfile.jsx create mode 100644 vite/src/components/forms/M3UProfiles.jsx create mode 100644 vite/src/components/forms/Stream.jsx create mode 100644 vite/src/components/forms/StreamProfile.jsx create mode 100644 vite/src/components/forms/SuperuserForm.jsx create mode 100644 vite/src/components/forms/UserAgent.jsx create mode 100644 vite/src/components/tables/ChannelsTable.jsx create mode 100644 vite/src/components/tables/EPGsTable.jsx create mode 100644 vite/src/components/tables/M3UsTable.jsx create mode 100644 vite/src/components/tables/StreamProfilesTable.jsx create mode 100644 vite/src/components/tables/StreamsTable.jsx create mode 100644 vite/src/components/tables/UserAgentsTable.jsx create mode 100644 vite/src/helpers/index.jsx create mode 100644 vite/src/helpers/table.jsx create mode 100644 vite/src/images/dispatcharr.svg create mode 100644 vite/src/images/ghost.svg create mode 100644 vite/src/images/logo.png create mode 100644 vite/src/index.css create mode 100644 vite/src/logo.svg create mode 100644 vite/src/main.jsx create mode 100644 vite/src/mantineTheme.jsx create mode 100644 vite/src/pages/Channels.jsx create mode 100644 vite/src/pages/Dashboard.jsx create mode 100644 vite/src/pages/EPG.jsx create mode 100644 vite/src/pages/Guide.jsx create mode 100644 vite/src/pages/Home.jsx create mode 100644 vite/src/pages/Login.jsx create mode 100644 vite/src/pages/M3U.jsx create mode 100644 vite/src/pages/Settings.jsx create mode 100644 vite/src/pages/StreamProfiles.jsx create mode 100644 vite/src/store/alerts.jsx create mode 100644 vite/src/store/auth.jsx create mode 100644 vite/src/store/channels.jsx create mode 100644 vite/src/store/epgs.jsx create mode 100644 vite/src/store/playlists.jsx create mode 100644 vite/src/store/settings.jsx create mode 100644 vite/src/store/streamProfiles.jsx create mode 100644 vite/src/store/streams.jsx create mode 100644 vite/src/store/useVideoStore.jsx create mode 100644 vite/src/store/userAgents.jsx create mode 100644 vite/src/theme.jsx create mode 100644 vite/src/utils.js create mode 100644 vite/vite.config.js diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index e1cb976d..35c5087b 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -6,8 +6,9 @@ services: image: dispatcharr/dispatcharr container_name: dispatcharr_dev ports: - - "5656:5656" + - 5656:5656 - 9191:9191 + - 8001:8001 volumes: - ../:/app environment: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 305f061f..fe81a6ac 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -46,7 +46,6 @@ if [[ ! -f /etc/profile.d/dispatcharr.sh ]]; then echo "export POSTGRES_HOST=$POSTGRES_HOST" >> /etc/profile.d/dispatcharr.sh echo "export POSTGRES_PORT=$POSTGRES_PORT" >> /etc/profile.d/dispatcharr.sh echo "export DISPATCHARR_ENV=$DISPATCHARR_ENV" >> /etc/profile.d/dispatcharr.sh - echo "export REACT_APP_ENV_MODE=$DISPATCHARR_ENV" >> /etc/profile.d/dispatcharr.sh fi chmod +x /etc/profile.d/dispatcharr.sh diff --git a/docker/uwsgi.dev.ini b/docker/uwsgi.dev.ini index 9bd0ab3f..e8761385 100644 --- a/docker/uwsgi.dev.ini +++ b/docker/uwsgi.dev.ini @@ -5,7 +5,7 @@ attach-daemon = celery -A dispatcharr worker -l info attach-daemon = redis-server attach-daemon = daphne -b 0.0.0.0 -p 8001 dispatcharr.asgi:application -attach-daemon = cd /app/frontend && npm run start +attach-daemon = cd /app/vite && npm run dev # Core settings chdir = /app diff --git a/vite/.gitignore b/vite/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/vite/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/vite/README.md b/vite/README.md new file mode 100644 index 00000000..fd3b758d --- /dev/null +++ b/vite/README.md @@ -0,0 +1,12 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/vite/eslint.config.js b/vite/eslint.config.js new file mode 100644 index 00000000..ec2b712d --- /dev/null +++ b/vite/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/vite/index.html b/vite/index.html new file mode 100644 index 00000000..18475376 --- /dev/null +++ b/vite/index.html @@ -0,0 +1,13 @@ + + + + + + + Dispatcharr + + +
+ + + diff --git a/vite/package-lock.json b/vite/package-lock.json new file mode 100644 index 00000000..0bb89a2e --- /dev/null +++ b/vite/package-lock.json @@ -0,0 +1,4665 @@ +{ + "name": "vite", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vite", + "version": "0.0.0", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mantine/core": "^7.17.1", + "@mantine/dates": "^7.17.1", + "@mantine/hooks": "^7.17.1", + "@mui/icons-material": "^6.4.7", + "@mui/material": "^6.4.7", + "@mui/x-date-pickers": "^7.27.3", + "@tabler/icons-react": "^3.31.0", + "axios": "^1.8.2", + "clsx": "^2.1.1", + "dayjs": "^1.11.13", + "formik": "^2.4.6", + "hls.js": "^1.5.20", + "lucide-react": "^0.479.0", + "mantine-react-table": "^2.0.0-beta.9", + "material-react-table": "^3.2.1", + "mpegts.js": "^1.8.0", + "prettier": "^3.5.3", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-draggable": "^4.4.6", + "react-pro-sidebar": "^1.1.0", + "react-router-dom": "^7.3.0", + "video.js": "^8.21.0", + "yup": "^1.6.1", + "zustand": "^5.0.3" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react-swc": "^3.8.0", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "vite": "^6.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", + "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.9" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", + "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", + "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", + "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz", + "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", + "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.12.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mantine/core": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.17.1.tgz", + "integrity": "sha512-V8O3Ftq4la4I4wNDkTfH4Slkt/pCEU32pTE/DkO46zua0VFxfOAJeLjaol0s11//T+bXx82DtjMsd9APWPuFhA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.28", + "clsx": "^2.1.1", + "react-number-format": "^5.4.3", + "react-remove-scroll": "^2.6.2", + "react-textarea-autosize": "8.5.6", + "type-fest": "^4.27.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.17.1", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/core/node_modules/type-fest": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz", + "integrity": "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mantine/dates": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-7.17.1.tgz", + "integrity": "sha512-L9MlIDb528RpznUeeW71xS4q3lYGolElz/f7xGRXEu9gHLaNJufbxroTw2N8RC6p/+RN1ZrSXEsjlr2euiofAw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "7.17.1", + "@mantine/hooks": "7.17.1", + "dayjs": ">=1.0.0", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/hooks": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.17.1.tgz", + "integrity": "sha512-mkHLrXMPd5xdI5WD7UOLwNEpdh/i6A7HaRDTXvjDE2/S0N8VmAE+BlvdyvWRMi7ODp2zVqJdP8cF1tgUn+Z0fA==", + "license": "MIT", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.7.tgz", + "integrity": "sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.7.tgz", + "integrity": "sha512-Rk8cs9ufQoLBw582Rdqq7fnSXXZTqhYRbpe1Y5SAz9lJKZP3CIdrj0PfG8HJLGw1hrsHFN/rkkm70IDzhJsG1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.4.7", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.7.tgz", + "integrity": "sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.4.7", + "@mui/system": "^6.4.7", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.6", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.4.7", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.6.tgz", + "integrity": "sha512-T5FxdPzCELuOrhpA2g4Pi6241HAxRwZudzAuL9vBvniuB5YU82HCmrARw32AuCiyTfWzbrYGGpZ4zyeqqp9RvQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.6", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.6.tgz", + "integrity": "sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.7.tgz", + "integrity": "sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.6", + "@mui/styled-engine": "^6.4.6", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.6", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.6.tgz", + "integrity": "sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.27.3.tgz", + "integrity": "sha512-igfKTPC4ZVCmS5j/NXcXBtj/hHseQHzRpCpIB1PMnJGhMdRYXnz8qZz5XhlNBKlzJVXkGu6Uil+obZpCLNj1xg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0", + "@mui/x-internals": "7.26.0", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", + "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2 || ^3.0.0", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.26.0.tgz", + "integrity": "sha512-VxTCYQcZ02d3190pdvys2TDg9pgbvewAVakEopiOgReKAUhLdRlgGJHcOA/eAuGLyK1YIo26A6Ow6ZKlSRLwMg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", + "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", + "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", + "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", + "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", + "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", + "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", + "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", + "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", + "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", + "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", + "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", + "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", + "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", + "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", + "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", + "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", + "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", + "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", + "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.8.tgz", + "integrity": "sha512-UAL+EULxrc0J73flwYHfu29mO8CONpDJiQv1QPDXsyCvDUcEhqAqUROVTgC+wtJCFFqMQdyr4stAA5/s0KSOmA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.19" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.11.8", + "@swc/core-darwin-x64": "1.11.8", + "@swc/core-linux-arm-gnueabihf": "1.11.8", + "@swc/core-linux-arm64-gnu": "1.11.8", + "@swc/core-linux-arm64-musl": "1.11.8", + "@swc/core-linux-x64-gnu": "1.11.8", + "@swc/core-linux-x64-musl": "1.11.8", + "@swc/core-win32-arm64-msvc": "1.11.8", + "@swc/core-win32-ia32-msvc": "1.11.8", + "@swc/core-win32-x64-msvc": "1.11.8" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.8.tgz", + "integrity": "sha512-rrSsunyJWpHN+5V1zumndwSSifmIeFQBK9i2RMQQp15PgbgUNxHK5qoET1n20pcUrmZeT6jmJaEWlQchkV//Og==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.8.tgz", + "integrity": "sha512-44goLqQuuo0HgWnG8qC+ZFw/qnjCVVeqffhzFr9WAXXotogVaxM8ze6egE58VWrfEc8me8yCcxOYL9RbtjhS/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.8.tgz", + "integrity": "sha512-Mzo8umKlhTWwF1v8SLuTM1z2A+P43UVhf4R8RZDhzIRBuB2NkeyE+c0gexIOJBuGSIATryuAF4O4luDu727D1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.8.tgz", + "integrity": "sha512-EyhO6U+QdoGYC1MeHOR0pyaaSaKYyNuT4FQNZ1eZIbnuueXpuICC7iNmLIOfr3LE5bVWcZ7NKGVPlM2StJEcgA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.8.tgz", + "integrity": "sha512-QU6wOkZnS6/QuBN1MHD6G2BgFxB0AclvTVGbqYkRA7MsVkcC29PffESqzTXnypzB252/XkhQjoB2JIt9rPYf6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.8.tgz", + "integrity": "sha512-r72onUEIU1iJi9EUws3R28pztQ/eM3EshNpsPRBfuLwKy+qn3et55vXOyDhIjGCUph5Eg2Yn8H3h6MTxDdLd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.8.tgz", + "integrity": "sha512-294k8cLpO103++f4ZUEDr3vnBeUfPitW6G0a3qeVZuoXFhFgaW7ANZIWknUc14WiLOMfMecphJAEiy9C8OeYSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.8.tgz", + "integrity": "sha512-EbjOzQ+B85rumHyeesBYxZ+hq3ZQn+YAAT1ZNE9xW1/8SuLoBmHy/K9YniRGVDq/2NRmp5kI5+5h5TX0asIS9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.8.tgz", + "integrity": "sha512-Z+FF5kgLHfQWIZ1KPdeInToXLzbY0sMAashjd/igKeP1Lz0qKXVAK+rpn6ASJi85Fn8wTftCGCyQUkRVn0bTDg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.8.tgz", + "integrity": "sha512-j6B6N0hChCeAISS6xp/hh6zR5CSCr037BAjCxNLsT8TGe5D+gYZ57heswUWXRH8eMKiRDGiLCYpPB2pkTqxCSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.19.tgz", + "integrity": "sha512-WkAZaAfj44kh/UFdAQcrMP1I0nwRqpt27u+08LMBYMqmQfwwMofYoMh/48NGkMMRfC4ynpfwRbJuu8ErfNloeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@tabler/icons": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.31.0.tgz", + "integrity": "sha512-dblAdeKY3+GA1U+Q9eziZ0ooVlZMHsE8dqP0RkwvRtEsAULoKOYaCUOcJ4oW1DjWegdxk++UAt2SlQVnmeHv+g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.31.0.tgz", + "integrity": "sha512-2rrCM5y/VnaVKnORpDdAua9SEGuJKVqPtWxeQ/vUVsgaUx30LDgBZph7/lterXxDY1IKR6NO//HDhWiifXTi3w==", + "license": "MIT", + "dependencies": { + "@tabler/icons": "3.31.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": ">= 16" + } + }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz", + "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==", + "license": "MIT", + "dependencies": { + "remove-accents": "0.5.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.20.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz", + "integrity": "sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.2.tgz", + "integrity": "sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.11.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz", + "integrity": "sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", + "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@videojs/http-streaming": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.17.0.tgz", + "integrity": "sha512-Ch1P3tvvIEezeZXyK11UfWgp4cWKX4vIhZ30baN/lRinqdbakZ5hiAI3pGjRy3d+q/Epyc8Csz5xMdKNNGYpcw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^4.1.1", + "aes-decrypter": "^4.0.2", + "global": "^4.4.0", + "m3u8-parser": "^7.2.0", + "mpd-parser": "^1.3.1", + "mux.js": "7.1.0", + "video.js": "^7 || ^8" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "video.js": "^8.19.0" + } + }, + "node_modules/@videojs/vhs-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.1.1.tgz", + "integrity": "sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/@videojs/xhr": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.7.0.tgz", + "integrity": "sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "global": "~4.4.0", + "is-function": "^1.0.1" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.8.0.tgz", + "integrity": "sha512-T4sHPvS+DIqDP51ifPqa9XIRAz/kIvIi8oXcnOZZgHmMotgmmdxe/DD5tMFlt5nuIRzT0/QuiwmKlH0503Aapw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/core": "^1.10.15" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aes-decrypter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.2.tgz", + "integrity": "sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^4.1.1", + "global": "^4.4.0", + "pkcs7": "^1.0.4" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.22.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", + "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.2", + "@eslint/config-helpers": "^0.1.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.22.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.19.tgz", + "integrity": "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formik": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz", + "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==", + "funding": [ + { + "type": "individual", + "url": "https://opencollective.com/formik" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.1", + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "license": "MIT", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight-words": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/highlight-words/-/highlight-words-2.0.0.tgz", + "integrity": "sha512-If5n+IhSBRXTScE7wl16VPmd+44Vy7kof24EdqhjsZsDuHikpv1OCagVcJFpB4fS4UPUniedlWqrjIO8vWOsIQ==", + "license": "MIT", + "engines": { + "node": ">= 20", + "npm": ">= 9" + } + }, + "node_modules/hls.js": { + "version": "1.5.20", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.20.tgz", + "integrity": "sha512-uu0VXUK52JhihhnN/MVVo1lvqNNuhoxkonqgO3IpjvQiGpJBdIXMGkofjQb/j9zvV7a1SW8U9g1FslWx/1HOiQ==", + "license": "Apache-2.0" + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "license": "MIT" + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lucide-react": { + "version": "0.479.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.479.0.tgz", + "integrity": "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/m3u8-parser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.2.0.tgz", + "integrity": "sha512-CRatFqpjVtMiMaKXxNvuI3I++vUumIXVVT/JpCpdU/FynV/ceVw1qpPyyBNindL+JlPMSesx+WX1QJaZEJSaMQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^4.1.1", + "global": "^4.4.0" + } + }, + "node_modules/mantine-react-table": { + "version": "2.0.0-beta.9", + "resolved": "https://registry.npmjs.org/mantine-react-table/-/mantine-react-table-2.0.0-beta.9.tgz", + "integrity": "sha512-ZdfcwebWaPERoDvAuk43VYcBCzamohARVclnbuepT0PHZ0wRcDPMBR+zgaocL+pFy8EXUGwvWTOKNh25ITpjNQ==", + "license": "MIT", + "dependencies": { + "@tanstack/match-sorter-utils": "8.19.4", + "@tanstack/react-table": "8.20.5", + "@tanstack/react-virtual": "3.11.2" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kevinvandy" + }, + "peerDependencies": { + "@mantine/core": "^7.9", + "@mantine/dates": "^7.9", + "@mantine/hooks": "^7.9", + "@tabler/icons-react": ">=2.23.0", + "clsx": ">=2", + "dayjs": ">=1.11", + "react": ">=18.0", + "react-dom": ">=18.0" + } + }, + "node_modules/mantine-react-table/node_modules/@tanstack/react-table": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", + "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/material-react-table": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-3.2.1.tgz", + "integrity": "sha512-sQtTf7bETpkPN2Hm5BVtz89wrfXCVQguz6XlwMChSnfKFO5QCKAJJC5aSIKnUc3S0AvTz/k/ILi00FnnY1Gixw==", + "license": "MIT", + "dependencies": { + "@tanstack/match-sorter-utils": "8.19.4", + "@tanstack/react-table": "8.20.6", + "@tanstack/react-virtual": "3.11.2", + "highlight-words": "2.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kevinvandy" + }, + "peerDependencies": { + "@emotion/react": ">=11.13", + "@emotion/styled": ">=11.13", + "@mui/icons-material": ">=6", + "@mui/material": ">=6", + "@mui/x-date-pickers": ">=7.15", + "react": ">=18.0", + "react-dom": ">=18.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mpd-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.1.tgz", + "integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^4.0.0", + "@xmldom/xmldom": "^0.8.3", + "global": "^4.4.0" + }, + "bin": { + "mpd-to-m3u8-json": "bin/parse.js" + } + }, + "node_modules/mpegts.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mpegts.js/-/mpegts.js-1.8.0.tgz", + "integrity": "sha512-ZtujqtmTjWgcDDkoOnLvrOKUTO/MKgLHM432zGDI8oPaJ0S+ebPxg1nEpDpLw6I7KmV/GZgUIrfbWi3qqEircg==", + "license": "Apache-2.0", + "dependencies": { + "es6-promise": "^4.2.5", + "webworkify-webpack": "github:xqq/webworkify-webpack" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mux.js": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.1.0.tgz", + "integrity": "sha512-NTxawK/BBELJrYsZThEulyUMDVlLizKdxyAsMuzoCD1eFj97BVaA8D/CvKsKu6FOLYkFojN5CbM9h++ZTZtknA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.11.2", + "global": "^4.4.0" + }, + "bin": { + "muxjs-transmux": "bin/transmux.js" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/nanoid": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", + "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pkcs7": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", + "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.5.5" + }, + "bin": { + "pkcs7": "bin/cli.js" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/react-draggable": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "license": "MIT", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-draggable/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==", + "license": "MIT" + }, + "node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, + "node_modules/react-number-format": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.3.tgz", + "integrity": "sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ==", + "license": "MIT", + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-pro-sidebar": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-pro-sidebar/-/react-pro-sidebar-1.1.0.tgz", + "integrity": "sha512-rdRJ4PeMsqWq9n69AmF6et6qCbhCF1KEBgjAH8vIiLxE1k5fMxtRYo0k4asxW8qpIH6sqahiMxrxVVoObv8orQ==", + "license": "MIT", + "dependencies": { + "@emotion/react": "^11.10.5", + "@emotion/styled": "^11.10.5", + "@popperjs/core": "^2.11.6", + "classnames": "^2.3.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", + "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.3.0.tgz", + "integrity": "sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.3.0.tgz", + "integrity": "sha512-z7Q5FTiHGgQfEurX/FBinkOXhWREJIAB2RiU24lvcBa82PxUpwqvs/PAXb9lJyPjTs2jrl6UkLvCZVGJPeNuuQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.3.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.6.tgz", + "integrity": "sha512-aT3ioKXMa8f6zHYGebhbdMD2L00tKeRX1zuVuDx9YQK/JLLRSaSxq3ugECEmUB9z2kvk6bFSIoRHLkkUv0RJiw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz", + "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.35.0", + "@rollup/rollup-android-arm64": "4.35.0", + "@rollup/rollup-darwin-arm64": "4.35.0", + "@rollup/rollup-darwin-x64": "4.35.0", + "@rollup/rollup-freebsd-arm64": "4.35.0", + "@rollup/rollup-freebsd-x64": "4.35.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", + "@rollup/rollup-linux-arm-musleabihf": "4.35.0", + "@rollup/rollup-linux-arm64-gnu": "4.35.0", + "@rollup/rollup-linux-arm64-musl": "4.35.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", + "@rollup/rollup-linux-riscv64-gnu": "4.35.0", + "@rollup/rollup-linux-s390x-gnu": "4.35.0", + "@rollup/rollup-linux-x64-gnu": "4.35.0", + "@rollup/rollup-linux-x64-musl": "4.35.0", + "@rollup/rollup-win32-arm64-msvc": "4.35.0", + "@rollup/rollup-win32-ia32-msvc": "4.35.0", + "@rollup/rollup-win32-x64-msvc": "4.35.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz", + "integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "license": "MIT", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/video.js": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.21.0.tgz", + "integrity": "sha512-zcwerRb257QAuWfi8NH9yEX7vrGKFthjfcONmOQ4lxFRpDAbAi+u5LAjCjMWqhJda6zEmxkgdDpOMW3Y21QpXA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/http-streaming": "^3.16.2", + "@videojs/vhs-utils": "^4.1.1", + "@videojs/xhr": "2.7.0", + "aes-decrypter": "^4.0.2", + "global": "4.4.0", + "m3u8-parser": "^7.2.0", + "mpd-parser": "^1.3.1", + "mux.js": "^7.0.1", + "videojs-contrib-quality-levels": "4.1.0", + "videojs-font": "4.2.0", + "videojs-vtt.js": "0.15.5" + } + }, + "node_modules/videojs-contrib-quality-levels": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.1.0.tgz", + "integrity": "sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==", + "license": "Apache-2.0", + "dependencies": { + "global": "^4.4.0" + }, + "engines": { + "node": ">=16", + "npm": ">=8" + }, + "peerDependencies": { + "video.js": "^8" + } + }, + "node_modules/videojs-font": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.2.0.tgz", + "integrity": "sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==", + "license": "Apache-2.0" + }, + "node_modules/videojs-vtt.js": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", + "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", + "license": "Apache-2.0", + "dependencies": { + "global": "^4.3.1" + } + }, + "node_modules/vite": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", + "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/webworkify-webpack": { + "version": "2.1.5", + "resolved": "git+ssh://git@github.com/xqq/webworkify-webpack.git#24d1e719b4a6cac37a518b2bb10fe124527ef4ef", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yup": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz", + "integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==", + "license": "MIT", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/vite/package.json b/vite/package.json new file mode 100644 index 00000000..2e734af8 --- /dev/null +++ b/vite/package.json @@ -0,0 +1,52 @@ +{ + "name": "vite", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mantine/core": "^7.17.1", + "@mantine/dates": "^7.17.1", + "@mantine/hooks": "^7.17.1", + "@mui/icons-material": "^6.4.7", + "@mui/material": "^6.4.7", + "@mui/x-date-pickers": "^7.27.3", + "@tabler/icons-react": "^3.31.0", + "axios": "^1.8.2", + "clsx": "^2.1.1", + "dayjs": "^1.11.13", + "formik": "^2.4.6", + "hls.js": "^1.5.20", + "lucide-react": "^0.479.0", + "mantine-react-table": "^2.0.0-beta.9", + "material-react-table": "^3.2.1", + "mpegts.js": "^1.8.0", + "prettier": "^3.5.3", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-draggable": "^4.4.6", + "react-pro-sidebar": "^1.1.0", + "react-router-dom": "^7.3.0", + "video.js": "^8.21.0", + "yup": "^1.6.1", + "zustand": "^5.0.3" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react-swc": "^3.8.0", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "vite": "^6.2.0" + } +} diff --git a/vite/prettier.config.js b/vite/prettier.config.js new file mode 100644 index 00000000..d2b86878 --- /dev/null +++ b/vite/prettier.config.js @@ -0,0 +1,10 @@ +// prettier.config.js or .prettierrc.js +export default { + semi: true, // Add semicolons at the end of statements + singleQuote: true, // Use single quotes instead of double + tabWidth: 2, // Set the indentation width + trailingComma: "es5", // Add trailing commas where valid in ES5 + printWidth: 80, // Wrap lines at 80 characters + bracketSpacing: true, // Add spaces inside object braces + arrowParens: "always", // Always include parentheses around arrow function parameters +}; diff --git a/vite/public/vite.svg b/vite/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/vite/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vite/src/App.css b/vite/src/App.css new file mode 100644 index 00000000..74b5e053 --- /dev/null +++ b/vite/src/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/vite/src/App.jsx b/vite/src/App.jsx new file mode 100644 index 00000000..198d1f06 --- /dev/null +++ b/vite/src/App.jsx @@ -0,0 +1,169 @@ +// frontend/src/App.js +import React, { useEffect, useState } from 'react'; +import { + BrowserRouter as Router, + Route, + Routes, + Navigate, +} from 'react-router-dom'; +// import Sidebar from './components/Sidebar'; +import Sidebar from './components/Sidebar-new'; +import Login from './pages/Login'; +import Channels from './pages/Channels'; +import M3U from './pages/M3U'; +import { ThemeProvider } from '@mui/material/styles'; +import { Box, CssBaseline, GlobalStyles } from '@mui/material'; +import theme from './theme'; +import EPG from './pages/EPG'; +import Guide from './pages/Guide'; +import Settings from './pages/Settings'; +import StreamProfiles from './pages/StreamProfiles'; +import useAuthStore from './store/auth'; +import Alert from './components/Alert'; +import FloatingVideo from './components/FloatingVideo'; +import SuperuserForm from './components/forms/SuperuserForm'; +import { WebsocketProvider } from './WebSocket'; +import { AppShell, MantineProvider } from '@mantine/core'; +import '@mantine/core/styles.css'; // Ensure Mantine global styles load +import 'mantine-react-table/styles.css'; +import mantineTheme from './mantineTheme'; + +const drawerWidth = 240; +const miniDrawerWidth = 60; +const defaultRoute = '/channels'; + +const App = () => { + const [open, setOpen] = useState(true); + const [needsSuperuser, setNeedsSuperuser] = useState(false); + const { + isAuthenticated, + setIsAuthenticated, + logout, + initData, + initializeAuth, + } = useAuthStore(); + + const toggleDrawer = () => { + setOpen(!open); + }; + + // Check if a superuser exists on first load. + useEffect(() => { + async function checkSuperuser() { + try { + const response = await fetch('/api/accounts/initialize-superuser/'); + const res = await response.json(); + if (!res.data.superuser_exists) { + setNeedsSuperuser(true); + } + } catch (error) { + console.error('Error checking superuser status:', error); + } + } + checkSuperuser(); + }, []); + + // Authentication check + useEffect(() => { + const checkAuth = async () => { + const loggedIn = await initializeAuth(); + if (loggedIn) { + await initData(); + setIsAuthenticated(true); + } else { + await logout(); + } + }; + checkAuth(); + }, [initializeAuth, initData, setIsAuthenticated, logout]); + + // If no superuser exists, show the initialization form + if (needsSuperuser) { + return setNeedsSuperuser(false)} />; + } + + return ( + + + + + + + + + + + + {isAuthenticated ? ( + <> + } /> + } /> + } /> + } + /> + } /> + } /> + + ) : ( + } /> + )} + + } + /> + + + + + + + + + + + + ); +}; + +export default App; diff --git a/vite/src/WebSocket.jsx b/vite/src/WebSocket.jsx new file mode 100644 index 00000000..a5ae12e7 --- /dev/null +++ b/vite/src/WebSocket.jsx @@ -0,0 +1,85 @@ +import React, { + useState, + useEffect, + useRef, + createContext, + useContext, +} from 'react'; +import useStreamsStore from './store/streams'; +import useAlertStore from './store/alerts'; + +export const WebsocketContext = createContext(false, null, () => {}); + +export const WebsocketProvider = ({ children }) => { + const [isReady, setIsReady] = useState(false); + const [val, setVal] = useState(null); + + const { showAlert } = useAlertStore(); + const { fetchStreams } = useStreamsStore(); + + const ws = useRef(null); + + useEffect(() => { + let wsUrl = `${window.location.host}/ws/`; + if (import.meta.env.DEV) { + wsUrl = `${window.location.hostname}:8001/ws/`; + } + + if (window.location.protocol.match(/https/)) { + wsUrl = `wss://${wsUrl}`; + } else { + wsUrl = `ws://${wsUrl}`; + } + + const socket = new WebSocket(wsUrl); + + socket.onopen = () => { + console.log('websocket connected'); + setIsReady(true); + }; + + // Reconnection logic + socket.onclose = () => { + setIsReady(false); + setTimeout(() => { + const reconnectWs = new WebSocket(wsUrl); + reconnectWs.onopen = () => setIsReady(true); + }, 3000); // Attempt to reconnect every 3 seconds + }; + + socket.onmessage = async (event) => { + event = JSON.parse(event.data); + switch (event.type) { + case 'm3u_refresh': + if (event.message?.success) { + fetchStreams(); + showAlert(event.message.message, 'success'); + } + break; + + default: + console.error(`Unknown websocket event type: ${event.type}`); + break; + } + }; + + ws.current = socket; + + return () => { + socket.close(); + }; + }, []); + + const ret = [isReady, val, ws.current?.send.bind(ws.current)]; + + return ( + + {children} + + ); +}; + +export const useWebSocket = () => { + const socket = useContext(WebsocketContext); + return socket; +}; diff --git a/vite/src/api.js b/vite/src/api.js new file mode 100644 index 00000000..1d436a02 --- /dev/null +++ b/vite/src/api.js @@ -0,0 +1,783 @@ +// src/api.js (updated) +import useAuthStore from './store/auth'; +import useChannelsStore from './store/channels'; +import useUserAgentsStore from './store/userAgents'; +import usePlaylistsStore from './store/playlists'; +import useEPGsStore from './store/epgs'; +import useStreamsStore from './store/streams'; +import useStreamProfilesStore from './store/streamProfiles'; +import useSettingsStore from './store/settings'; + +// If needed, you can set a base host or keep it empty if relative requests +const host = import.meta.env.DEV + ? `http://${window.location.hostname}:5656` + : ''; + +export default class API { + /** + * A static method so we can do: await API.getAuthToken() + */ + static async getAuthToken() { + return await useAuthStore.getState().getToken(); + } + + static async login(username, password) { + const response = await fetch(`${host}/api/accounts/token/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ username, password }), + }); + + return await response.json(); + } + + static async refreshToken(refresh) { + const response = await fetch(`${host}/api/accounts/token/refresh/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ refresh }), + }); + + const retval = await response.json(); + return retval; + } + + static async logout() { + const response = await fetch(`${host}/api/accounts/auth/logout/`, { + method: 'POST', + }); + + return response.data.data; + } + + static async getChannels() { + const response = await fetch(`${host}/api/channels/channels/`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await API.getAuthToken()}`, + }, + }); + + const retval = await response.json(); + return retval; + } + + static async getChannelGroups() { + const response = await fetch(`${host}/api/channels/groups/`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await API.getAuthToken()}`, + }, + }); + + const retval = await response.json(); + return retval; + } + + static async addChannelGroup(values) { + const response = await fetch(`${host}/api/channels/groups/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + }); + + const retval = await response.json(); + if (retval.id) { + useChannelsStore.getState().addChannelGroup(retval); + } + + return retval; + } + + static async updateChannelGroup(values) { + const { id, ...payload } = values; + const response = await fetch(`${host}/api/channels/groups/${id}/`, { + method: 'PUT', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + const retval = await response.json(); + if (retval.id) { + useChannelsStore.getState().updateChannelGroup(retval); + } + + return retval; + } + + static async addChannel(channel) { + let body = null; + if (channel.logo_file) { + // Must send FormData for file upload + body = new FormData(); + for (const prop in channel) { + body.append(prop, channel[prop]); + } + } else { + body = { ...channel }; + delete body.logo_file; + body = JSON.stringify(body); + } + + const response = await fetch(`${host}/api/channels/channels/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + ...(channel.logo_file + ? {} + : { + 'Content-Type': 'application/json', + }), + }, + body: body, + }); + + const retval = await response.json(); + if (retval.id) { + useChannelsStore.getState().addChannel(retval); + } + + return retval; + } + + static async deleteChannel(id) { + const response = await fetch(`${host}/api/channels/channels/${id}/`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + useChannelsStore.getState().removeChannels([id]); + } + + // @TODO: the bulk delete endpoint is currently broken + static async deleteChannels(channel_ids) { + const response = await fetch(`${host}/api/channels/channels/bulk-delete/`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ channel_ids }), + }); + + useChannelsStore.getState().removeChannels(channel_ids); + } + + static async updateChannel(values) { + const { id, ...payload } = values; + const response = await fetch(`${host}/api/channels/channels/${id}/`, { + method: 'PUT', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + const retval = await response.json(); + if (retval.id) { + useChannelsStore.getState().updateChannel(retval); + } + + return retval; + } + + static async assignChannelNumbers(channelIds) { + // Make the request + const response = await fetch(`${host}/api/channels/channels/assign/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ channel_order: channelIds }), + }); + + if (!response.ok) { + const text = await response.text(); + throw new Error(`Assign channels failed: ${response.status} => ${text}`); + } + + const retval = await response.json(); + + // Optionally refresh the channel list in Zustand + await useChannelsStore.getState().fetchChannels(); + + return retval; + } + + static async createChannelFromStream(values) { + const response = await fetch(`${host}/api/channels/channels/from-stream/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + }); + + const retval = await response.json(); + if (retval.id) { + useChannelsStore.getState().addChannel(retval); + } + + return retval; + } + + static async createChannelsFromStreams(values) { + const response = await fetch( + `${host}/api/channels/channels/from-stream/bulk/`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + } + ); + + const retval = await response.json(); + if (retval.created.length > 0) { + useChannelsStore.getState().addChannels(retval.created); + } + + return retval; + } + + static async getStreams() { + const response = await fetch(`${host}/api/channels/streams/`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await API.getAuthToken()}`, + }, + }); + + const retval = await response.json(); + return retval; + } + + static async queryStreams(params) { + const response = await fetch( + `${host}/api/channels/streams/?${params.toString()}`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await API.getAuthToken()}`, + }, + } + ); + + const retval = await response.json(); + return retval; + } + + static async getAllStreamIds(params) { + const response = await fetch( + `${host}/api/channels/streams/ids/?${params.toString()}`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await API.getAuthToken()}`, + }, + } + ); + + const retval = await response.json(); + return retval; + } + + static async getStreamGroups() { + const response = await fetch(`${host}/api/channels/streams/groups/`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await API.getAuthToken()}`, + }, + }); + + const retval = await response.json(); + return retval; + } + + static async addStream(values) { + const response = await fetch(`${host}/api/channels/streams/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + }); + + const retval = await response.json(); + if (retval.id) { + useStreamsStore.getState().addStream(retval); + } + + return retval; + } + + static async updateStream(values) { + const { id, ...payload } = values; + const response = await fetch(`${host}/api/channels/streams/${id}/`, { + method: 'PUT', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + const retval = await response.json(); + if (retval.id) { + useStreamsStore.getState().updateStream(retval); + } + + return retval; + } + + static async deleteStream(id) { + const response = await fetch(`${host}/api/channels/streams/${id}/`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + useStreamsStore.getState().removeStreams([id]); + } + + static async deleteStreams(ids) { + const response = await fetch(`${host}/api/channels/streams/bulk-delete/`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ stream_ids: ids }), + }); + + useStreamsStore.getState().removeStreams(ids); + } + + static async getUserAgents() { + const response = await fetch(`${host}/api/core/useragents/`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await API.getAuthToken()}`, + }, + }); + + const retval = await response.json(); + return retval; + } + + static async addUserAgent(values) { + const response = await fetch(`${host}/api/core/useragents/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + }); + + const retval = await response.json(); + if (retval.id) { + useUserAgentsStore.getState().addUserAgent(retval); + } + + return retval; + } + + static async updateUserAgent(values) { + const { id, ...payload } = values; + const response = await fetch(`${host}/api/core/useragents/${id}/`, { + method: 'PUT', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + const retval = await response.json(); + if (retval.id) { + useUserAgentsStore.getState().updateUserAgent(retval); + } + + return retval; + } + + static async deleteUserAgent(id) { + const response = await fetch(`${host}/api/core/useragents/${id}/`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + useUserAgentsStore.getState().removeUserAgents([id]); + } + + static async getPlaylist(id) { + const response = await fetch(`${host}/api/m3u/accounts/${id}/`, { + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + const retval = await response.json(); + return retval; + } + + static async getPlaylists() { + const response = await fetch(`${host}/api/m3u/accounts/`, { + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + const retval = await response.json(); + return retval; + } + + static async addPlaylist(values) { + const response = await fetch(`${host}/api/m3u/accounts/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + }); + + const retval = await response.json(); + if (retval.id) { + usePlaylistsStore.getState().addPlaylist(retval); + } + + return retval; + } + + static async refreshPlaylist(id) { + const response = await fetch(`${host}/api/m3u/refresh/${id}/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + const retval = await response.json(); + return retval; + } + + static async refreshAllPlaylist() { + const response = await fetch(`${host}/api/m3u/refresh/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + const retval = await response.json(); + return retval; + } + + static async deletePlaylist(id) { + const response = await fetch(`${host}/api/m3u/accounts/${id}/`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + usePlaylistsStore.getState().removePlaylists([id]); + } + + static async updatePlaylist(values) { + const { id, ...payload } = values; + const response = await fetch(`${host}/api/m3u/accounts/${id}/`, { + method: 'PUT', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + const retval = await response.json(); + if (retval.id) { + usePlaylistsStore.getState().updatePlaylist(retval); + } + + return retval; + } + + static async getEPGs() { + const response = await fetch(`${host}/api/epg/sources/`, { + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + const retval = await response.json(); + return retval; + } + + // Notice there's a duplicated "refreshPlaylist" method above; + // you might want to rename or remove one if it's not needed. + + static async addEPG(values) { + let body = null; + if (values.epg_file) { + body = new FormData(); + for (const prop in values) { + body.append(prop, values[prop]); + } + } else { + body = { ...values }; + delete body.epg_file; + body = JSON.stringify(body); + } + + const response = await fetch(`${host}/api/epg/sources/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + ...(values.epg_file + ? {} + : { + 'Content-Type': 'application/json', + }), + }, + body, + }); + + const retval = await response.json(); + if (retval.id) { + useEPGsStore.getState().addEPG(retval); + } + + return retval; + } + + static async deleteEPG(id) { + const response = await fetch(`${host}/api/epg/sources/${id}/`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + useEPGsStore.getState().removeEPGs([id]); + } + + static async refreshEPG(id) { + const response = await fetch(`${host}/api/epg/import/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ id }), + }); + + const retval = await response.json(); + return retval; + } + + static async getStreamProfiles() { + const response = await fetch(`${host}/api/core/streamprofiles/`, { + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + const retval = await response.json(); + return retval; + } + + static async addStreamProfile(values) { + const response = await fetch(`${host}/api/core/streamprofiles/`, { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + }); + + const retval = await response.json(); + if (retval.id) { + useStreamProfilesStore.getState().addStreamProfile(retval); + } + return retval; + } + + static async updateStreamProfile(values) { + const { id, ...payload } = values; + const response = await fetch(`${host}/api/core/streamprofiles/${id}/`, { + method: 'PUT', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + const retval = await response.json(); + if (retval.id) { + useStreamProfilesStore.getState().updateStreamProfile(retval); + } + + return retval; + } + + static async deleteStreamProfile(id) { + const response = await fetch(`${host}/api/core/streamprofiles/${id}/`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + useStreamProfilesStore.getState().removeStreamProfiles([id]); + } + + static async getGrid() { + const response = await fetch(`${host}/api/epg/grid/`, { + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + }); + + const retval = await response.json(); + return retval.data; + } + + static async addM3UProfile(accountId, values) { + const response = await fetch( + `${host}/api/m3u/accounts/${accountId}/profiles/`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + } + ); + + const retval = await response.json(); + if (retval.id) { + // Refresh the playlist + const playlist = await API.getPlaylist(accountId); + usePlaylistsStore + .getState() + .updateProfiles(playlist.id, playlist.profiles); + } + + return retval; + } + + static async deleteM3UProfile(accountId, id) { + const response = await fetch( + `${host}/api/m3u/accounts/${accountId}/profiles/${id}/`, + { + method: 'DELETE', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + } + ); + + const playlist = await API.getPlaylist(accountId); + usePlaylistsStore.getState().updatePlaylist(playlist); + } + + static async updateM3UProfile(accountId, values) { + const { id, ...payload } = values; + const response = await fetch( + `${host}/api/m3u/accounts/${accountId}/profiles/${id}/`, + { + method: 'PUT', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + } + ); + + const playlist = await API.getPlaylist(accountId); + usePlaylistsStore.getState().updateProfiles(playlist.id, playlist.profiles); + } + + static async getSettings() { + const response = await fetch(`${host}/api/core/settings/`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await API.getAuthToken()}`, + }, + }); + + const retval = await response.json(); + return retval; + } + + static async getEnvironmentSettings() { + const response = await fetch(`${host}/api/core/settings/env/`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await API.getAuthToken()}`, + }, + }); + + const retval = await response.json(); + return retval; + } + + static async updateSetting(values) { + const { id, ...payload } = values; + const response = await fetch(`${host}/api/core/settings/${id}/`, { + method: 'PUT', + headers: { + Authorization: `Bearer ${await API.getAuthToken()}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + const retval = await response.json(); + if (retval.id) { + useSettingsStore.getState().updateSetting(retval); + } + + return retval; + } +} diff --git a/vite/src/assets/react.svg b/vite/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/vite/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vite/src/components/Alert.jsx b/vite/src/components/Alert.jsx new file mode 100644 index 00000000..fa843400 --- /dev/null +++ b/vite/src/components/Alert.jsx @@ -0,0 +1,26 @@ +import React, { useState } from 'react'; +import { Snackbar, Alert, Button } from '@mui/material'; +import useAlertStore from '../store/alerts'; + +const AlertPopup = () => { + const { open, message, severity, hideAlert } = useAlertStore(); + + const handleClose = () => { + hideAlert(); + }; + + return ( + + + {message} + + + ); +}; + +export default AlertPopup; diff --git a/vite/src/components/FloatingVideo.jsx b/vite/src/components/FloatingVideo.jsx new file mode 100644 index 00000000..cc162c11 --- /dev/null +++ b/vite/src/components/FloatingVideo.jsx @@ -0,0 +1,98 @@ +// frontend/src/components/FloatingVideo.js +import React, { useEffect, useRef } from 'react'; +import Draggable from 'react-draggable'; +import useVideoStore from '../store/useVideoStore'; +import mpegts from 'mpegts.js'; + +export default function FloatingVideo() { + const { isVisible, streamUrl, hideVideo } = useVideoStore(); + const videoRef = useRef(null); + const playerRef = useRef(null); + + useEffect(() => { + if (!isVisible || !streamUrl) { + return; + } + + // If the browser supports MSE for live playback, initialize mpegts.js + if (mpegts.getFeatureList().mseLivePlayback) { + const player = mpegts.createPlayer({ + type: 'mpegts', + url: streamUrl, + isLive: true, + // You can include other custom MPEGTS.js config fields here, e.g.: + // cors: true, + // withCredentials: false, + }); + + player.attachMediaElement(videoRef.current); + player.load(); + player.play(); + + // Store player instance so we can clean up later + playerRef.current = player; + } + + // Cleanup when component unmounts or streamUrl changes + return () => { + if (playerRef.current) { + playerRef.current.destroy(); + playerRef.current = null; + } + }; + }, [isVisible, streamUrl]); + + // If the floating video is hidden or no URL is selected, do not render + if (!isVisible || !streamUrl) { + return null; + } + + return ( + +
+ {/* Simple header row with a close button */} +
+ +
+ + {/* The
+
+ ); +} diff --git a/vite/src/components/Sidebar-new.jsx b/vite/src/components/Sidebar-new.jsx new file mode 100644 index 00000000..6aede6eb --- /dev/null +++ b/vite/src/components/Sidebar-new.jsx @@ -0,0 +1,186 @@ +import { Link, useLocation } from 'react-router-dom'; +import { + ListOrdered, + Play, + Database, + SlidersHorizontal, + LayoutGrid, + Settings as LucideSettings, +} from 'lucide-react'; +import { + Avatar, + AppShell, + Group, + Stack, + Box, + Text, + UnstyledButton, +} from '@mantine/core'; +import { useState } from 'react'; +import headerLogo from '../images/dispatcharr.svg'; +import logo from '../images/logo.png'; + +// Navigation Items +const navItems = [ + { + label: 'Channels', + icon: , + path: '/channels', + // badge: '(323)', + }, + { label: 'M3U', icon: , path: '/m3u' }, + { label: 'EPG', icon: , path: '/epg' }, + { + label: 'Stream Profiles', + icon: , + path: '/stream-profiles', + }, + { label: 'TV Guide', icon: , path: '/guide' }, + { label: 'Settings', icon: , path: '/settings' }, +]; + +const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => { + const location = useLocation(); + + return ( + + {/* Brand - Click to Toggle */} + + {/* */} + + {!collapsed && ( + + Dispatcharr + + )} + + + {/* Navigation Links */} + + {navItems.map((item) => { + const isActive = location.pathname === item.path; + + return ( + + {item.icon} + {!collapsed && ( + + {item.label} + + )} + {!collapsed && item.badge && ( + + {item.badge} + + )} + + ); + })} + + + {/* Profile Section */} + + + {!collapsed && ( + + + John Doe + + + ••• + + + )} + + + ); +}; + +export default Sidebar; diff --git a/vite/src/components/Sidebar.jsx b/vite/src/components/Sidebar.jsx new file mode 100644 index 00000000..9b4502fa --- /dev/null +++ b/vite/src/components/Sidebar.jsx @@ -0,0 +1,189 @@ +import React from 'react'; +import { Link, useLocation } from 'react-router-dom'; +import { + Drawer, + Toolbar, + Box, + Typography, + Avatar, + List, + ListItemButton, + ListItemIcon, + ListItemText, +} from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import { + ListOrdered, + Play, + Database, + SlidersHorizontal, + LayoutGrid, + Settings as LucideSettings, +} from 'lucide-react'; +import logo from '../images/logo.png'; +import { AppShell } from '@mantine/core'; + +const navItems = [ + { label: 'Channels', icon: , path: '/channels' }, + { label: 'M3U', icon: , path: '/m3u' }, + { label: 'EPG', icon: , path: '/epg' }, + { + label: 'Stream Profiles', + icon: , + path: '/stream-profiles', + }, + { label: 'TV Guide', icon: , path: '/guide' }, + { label: 'Settings', icon: , path: '/settings' }, +]; + +const Sidebar = ({ open, drawerWidth, miniDrawerWidth, toggleDrawer }) => { + const location = useLocation(); + const theme = useTheme(); + + return ( + + + + {open ? ( + + Dispatcharr Logo + + Dispatcharr + + + ) : ( + Dispatcharr Logo + )} + + + + {navItems.map((item) => { + const isActive = location.pathname.startsWith(item.path); + return ( + + + + {item.icon} + + {open && ( + + )} + + + ); + })} + + + + + + {open && ( + + John Doe + + )} + + + + ); +}; + +export default Sidebar; diff --git a/vite/src/components/forms/Channel.jsx b/vite/src/components/forms/Channel.jsx new file mode 100644 index 00000000..2b5d84e2 --- /dev/null +++ b/vite/src/components/forms/Channel.jsx @@ -0,0 +1,491 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { + Box, + Typography, + Stack, + TextField, + Button, + Select, + MenuItem, + Grid2, + InputLabel, + FormControl, + CircularProgress, + IconButton, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + FormHelperText, +} from '@mui/material'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import useChannelsStore from '../../store/channels'; +import API from '../../api'; +import useStreamProfilesStore from '../../store/streamProfiles'; +import { Add as AddIcon, Remove as RemoveIcon } from '@mui/icons-material'; +import useStreamsStore from '../../store/streams'; +import { + MaterialReactTable, + useMaterialReactTable, +} from 'material-react-table'; +import ChannelGroupForm from './ChannelGroup'; +import usePlaylistsStore from '../../store/playlists'; +import logo from '../../images/logo.png'; + +const Channel = ({ channel = null, isOpen, onClose }) => { + const channelGroups = useChannelsStore((state) => state.channelGroups); + const streams = useStreamsStore((state) => state.streams); + const { profiles: streamProfiles } = useStreamProfilesStore(); + const { playlists } = usePlaylistsStore(); + + const [logoFile, setLogoFile] = useState(null); + const [logoPreview, setLogoPreview] = useState(logo); + const [channelStreams, setChannelStreams] = useState([]); + const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false); + + const addStream = (stream) => { + const streamSet = new Set(channelStreams); + streamSet.add(stream); + setChannelStreams(Array.from(streamSet)); + }; + + const removeStream = (stream) => { + const streamSet = new Set(channelStreams); + streamSet.delete(stream); + setChannelStreams(Array.from(streamSet)); + }; + + const handleLogoChange = (e) => { + const file = e.target.files[0]; + if (file) { + setLogoFile(file); + setLogoPreview(URL.createObjectURL(file)); + } + }; + + const formik = useFormik({ + initialValues: { + channel_name: '', + channel_number: '', + channel_group_id: '', + stream_profile_id: '0', + tvg_id: '', + tvg_name: '', + }, + validationSchema: Yup.object({ + channel_name: Yup.string().required('Name is required'), + channel_number: Yup.string().required('Invalid channel number').min(0), + channel_group_id: Yup.string().required('Channel group is required'), + }), + onSubmit: async (values, { setSubmitting, resetForm }) => { + if (values.stream_profile_id == '0') { + values.stream_profile_id = null; + } + + console.log(values); + if (channel?.id) { + await API.updateChannel({ + id: channel.id, + ...values, + logo_file: logoFile, + streams: channelStreams.map((stream) => stream.id), + }); + } else { + await API.addChannel({ + ...values, + logo_file: logoFile, + streams: channelStreams.map((stream) => stream.id), + }); + } + + resetForm(); + setLogoFile(null); + setLogoPreview(logo); + setSubmitting(false); + onClose(); + }, + }); + + useEffect(() => { + if (channel) { + formik.setValues({ + channel_name: channel.channel_name, + channel_number: channel.channel_number, + channel_group_id: channel.channel_group?.id, + stream_profile_id: channel.stream_profile_id || '0', + tvg_id: channel.tvg_id, + tvg_name: channel.tvg_name, + }); + + console.log(channel); + const filteredStreams = streams + .filter((stream) => channel.stream_ids.includes(stream.id)) + .sort( + (a, b) => + channel.stream_ids.indexOf(a.id) - channel.stream_ids.indexOf(b.id) + ); + setChannelStreams(filteredStreams); + } else { + formik.resetForm(); + } + }, [channel]); + + const activeStreamsTable = useMaterialReactTable({ + data: channelStreams, + columns: useMemo( + () => [ + { + header: 'Name', + accessorKey: 'name', + }, + { + header: 'M3U', + accessorKey: 'group_name', + }, + ], + [] + ), + enableSorting: false, + enableBottomToolbar: false, + enableTopToolbar: false, + columnFilterDisplayMode: 'popover', + enablePagination: false, + enableRowVirtualization: true, + enableRowOrdering: true, + rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer + initialState: { + density: 'compact', + }, + enableRowActions: true, + positionActionsColumn: 'last', + renderRowActions: ({ row }) => ( + <> + removeStream(row.original)} + > + {/* Small icon size */} + + + ), + muiTableContainerProps: { + sx: { + height: '200px', + }, + }, + muiRowDragHandleProps: ({ table }) => ({ + onDragEnd: () => { + const { draggingRow, hoveredRow } = table.getState(); + + if (hoveredRow && draggingRow) { + channelStreams.splice( + hoveredRow.index, + 0, + channelStreams.splice(draggingRow.index, 1)[0] + ); + + setChannelStreams([...channelStreams]); + } + }, + }), + }); + + const availableStreamsTable = useMaterialReactTable({ + data: streams, + columns: useMemo( + () => [ + { + header: 'Name', + accessorKey: 'name', + }, + { + header: 'M3U', + accessorFn: (row) => + playlists.find((playlist) => playlist.id === row.m3u_account)?.name, + }, + ], + [] + ), + enableBottomToolbar: false, + enableTopToolbar: false, + columnFilterDisplayMode: 'popover', + enablePagination: false, + enableRowVirtualization: true, + rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer + initialState: { + density: 'compact', + }, + enableRowActions: true, + renderRowActions: ({ row }) => ( + <> + addStream(row.original)} + > + {/* Small icon size */} + + + ), + positionActionsColumn: 'last', + muiTableContainerProps: { + sx: { + height: '200px', + }, + }, + }); + + if (!isOpen) { + return <>; + } + + return ( + <> + + + Channel + + +
+ + + + + + + + + + Channel Group + + + + {formik.touched.channel_group_id && + formik.errors.channel_group_id} + + + + + setChannelGroupModalOpen(true)} + title="Create new group" + size="small" + variant="filled" + > + + + + + + + + Stream Profile + + + + + + + + + + + + + + + + {/* File upload input */} + + Logo + {/* Display selected image */} + + Selected + + handleLogoChange(event)} + style={{ display: 'none' }} + /> + + + + + + + + + Active Streams + + + + + Available Streams + + + + + + + {/* Submit button */} + + +
+
+ setChannelGroupModalOpen(false)} + /> + + ); +}; + +export default Channel; diff --git a/vite/src/components/forms/ChannelGroup.jsx b/vite/src/components/forms/ChannelGroup.jsx new file mode 100644 index 00000000..407695df --- /dev/null +++ b/vite/src/components/forms/ChannelGroup.jsx @@ -0,0 +1,71 @@ +// Modal.js +import React, { useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import API from '../../api'; +import { Flex, TextInput, Button } from '@mantine/core'; + +const ChannelGroup = ({ channelGroup = null, isOpen, onClose }) => { + const formik = useFormik({ + initialValues: { + name: '', + }, + validationSchema: Yup.object({ + name: Yup.string().required('Name is required'), + }), + onSubmit: async (values, { setSubmitting, resetForm }) => { + if (channelGroup?.id) { + await API.updateChannelGroup({ id: channelGroup.id, ...values }); + } else { + await API.addChannelGroup(values); + } + + resetForm(); + setSubmitting(false); + onClose(); + }, + }); + + useEffect(() => { + if (channelGroup) { + formik.setValues({ + name: channelGroup.name, + }); + } else { + formik.resetForm(); + } + }, [channelGroup]); + + if (!isOpen) { + return <>; + } + + return ( + +
+ + + + + + +
+ ); +}; + +export default ChannelGroup; diff --git a/vite/src/components/forms/EPG.jsx b/vite/src/components/forms/EPG.jsx new file mode 100644 index 00000000..93b0ca5f --- /dev/null +++ b/vite/src/components/forms/EPG.jsx @@ -0,0 +1,143 @@ +// Modal.js +import React, { useState, useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import API from '../../api'; +import useEPGsStore from '../../store/epgs'; +import { + LoadingOverlay, + TextInput, + Button, + Checkbox, + Modal, + Flex, + NativeSelect, + FileInput, + Space, +} from '@mantine/core'; + +const EPG = ({ epg = null, isOpen, onClose }) => { + const epgs = useEPGsStore((state) => state.epgs); + const [file, setFile] = useState(null); + + const handleFileChange = (e) => { + const file = e.target.files[0]; + if (file) { + setFile(file); + } + }; + + const formik = useFormik({ + initialValues: { + name: '', + source_type: 'xmltv', + url: '', + api_key: '', + is_active: true, + }, + validationSchema: Yup.object({ + name: Yup.string().required('Name is required'), + source_type: Yup.string().required('Source type is required'), + }), + onSubmit: async (values, { setSubmitting, resetForm }) => { + if (epg?.id) { + await API.updateEPG({ id: epg.id, ...values, epg_file: file }); + } else { + await API.addEPG({ + ...values, + epg_file: file, + }); + } + + resetForm(); + setFile(null); + setSubmitting(false); + onClose(); + }, + }); + + useEffect(() => { + if (epg) { + formik.setValues({ + name: epg.name, + source_type: epg.source_type, + url: epg.url, + api_key: epg.api_key, + is_active: epg.is_active, + }); + } else { + formik.resetForm(); + } + }, [epg]); + + if (!isOpen) { + return <>; + } + + return ( + +
+ + + + + + + + + + + + +
+ ); +}; + +export default EPG; diff --git a/vite/src/components/forms/LoginForm.jsx b/vite/src/components/forms/LoginForm.jsx new file mode 100644 index 00000000..8eb8c183 --- /dev/null +++ b/vite/src/components/forms/LoginForm.jsx @@ -0,0 +1,93 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import useAuthStore from '../../store/auth'; +import { + Paper, + Title, + TextInput, + Button, + Checkbox, + Modal, + Box, + Center, + Stack, +} from '@mantine/core'; + +const LoginForm = () => { + const { login, isAuthenticated, initData } = useAuthStore(); // Get login function from AuthContext + const navigate = useNavigate(); // Hook to navigate to other routes + const [formData, setFormData] = useState({ username: '', password: '' }); + + useEffect(() => { + if (isAuthenticated) { + navigate('/channels'); + } + }, [isAuthenticated, navigate]); + + const handleInputChange = (e) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + await login(formData); + initData(); + navigate('/channels'); // Or any other route you'd like + }; + + // // Handle form submission + // const handleSubmit = async (e) => { + // e.preventDefault(); + // setLoading(true); + // setError(''); // Reset error on each new submission + + // await login(username, password) + // navigate('/channels'); // Or any other route you'd like + // }; + + return ( +
+ + + Login + +
+ + + + + + + +
+
+
+ ); +}; + +export default LoginForm; diff --git a/vite/src/components/forms/M3U.jsx b/vite/src/components/forms/M3U.jsx new file mode 100644 index 00000000..1567bf2a --- /dev/null +++ b/vite/src/components/forms/M3U.jsx @@ -0,0 +1,192 @@ +// Modal.js +import React, { useState, useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import API from '../../api'; +import useUserAgentsStore from '../../store/userAgents'; +import M3UProfiles from './M3UProfiles'; +import { + LoadingOverlay, + TextInput, + Button, + Checkbox, + Modal, + Flex, + NativeSelect, + FileInput, + Select, + Space, +} from '@mantine/core'; + +const M3U = ({ playlist = null, isOpen, onClose }) => { + const userAgents = useUserAgentsStore((state) => state.userAgents); + const [file, setFile] = useState(null); + const [profileModalOpen, setProfileModalOpen] = useState(false); + + const handleFileChange = (file) => { + console.log(file); + if (file) { + setFile(file); + } + }; + + const formik = useFormik({ + initialValues: { + name: '', + server_url: '', + user_agent: '', + is_active: true, + max_streams: 0, + }, + validationSchema: Yup.object({ + name: Yup.string().required('Name is required'), + user_agent: Yup.string().required('User-Agent is required'), + max_streams: Yup.string().required('Max streams is required'), + }), + onSubmit: async (values, { setSubmitting, resetForm }) => { + if (playlist?.id) { + await API.updatePlaylist({ + id: playlist.id, + ...values, + uploaded_file: file, + }); + } else { + await API.addPlaylist({ + ...values, + uploaded_file: file, + }); + } + + resetForm(); + setFile(null); + setSubmitting(false); + onClose(); + }, + }); + + useEffect(() => { + if (playlist) { + formik.setValues({ + name: playlist.name, + server_url: playlist.server_url, + max_streams: playlist.max_streams, + user_agent: playlist.user_agent, + is_active: playlist.is_active, + }); + } else { + formik.resetForm(); + } + }, [playlist]); + + if (!isOpen) { + return <>; + } + + console.log(formik.values); + + return ( + +
+ + +
+ + + + + + + + + ({ + label: ua.user_agent_name, + value: `${ua.id}`, + }))} + /> + + + + + formik.setFieldValue('is_active', e.target.checked) + } + /> + + + + + + + {playlist && ( + setProfileModalOpen(false)} + /> + )} + +
+
+ ); +}; + +export default M3U; diff --git a/vite/src/components/forms/M3UProfile.jsx b/vite/src/components/forms/M3UProfile.jsx new file mode 100644 index 00000000..d01bbd5e --- /dev/null +++ b/vite/src/components/forms/M3UProfile.jsx @@ -0,0 +1,158 @@ +import React, { useState, useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import API from '../../api'; +import { + Flex, + Modal, + TextInput, + Button, + Title, + Text, + Paper, +} from '@mantine/core'; + +const RegexFormAndView = ({ profile = null, m3u, isOpen, onClose }) => { + const [searchPattern, setSearchPattern] = useState(''); + const [replacePattern, setReplacePattern] = useState(''); + + let regex; + try { + regex = new RegExp(searchPattern, 'g'); + } catch (e) { + regex = null; + } + + const highlightedUrl = regex + ? m3u.server_url.replace(regex, (match) => `${match}`) + : m3u.server_url; + + const resultUrl = + regex && replacePattern + ? m3u.server_url.replace(regex, replacePattern) + : m3u.server_url; + + const onSearchPatternUpdate = (e) => { + formik.handleChange(e); + setSearchPattern(e.target.value); + }; + + const onReplacePatternUpdate = (e) => { + formik.handleChange(e); + setReplacePattern(e.target.value); + }; + + const formik = useFormik({ + initialValues: { + name: '', + max_streams: 0, + search_pattern: '', + replace_pattern: '', + }, + validationSchema: Yup.object({ + name: Yup.string().required('Name is required'), + search_pattern: Yup.string().required('Search pattern is required'), + replace_pattern: Yup.string().required('Replace pattern is required'), + }), + onSubmit: async (values, { setSubmitting, resetForm }) => { + console.log('submiting'); + if (profile?.id) { + await API.updateM3UProfile(m3u.id, { + id: profile.id, + ...values, + }); + } else { + await API.addM3UProfile(m3u.id, values); + } + + resetForm(); + setSubmitting(false); + onClose(); + }, + }); + + useEffect(() => { + if (profile) { + setSearchPattern(profile.search_pattern); + setReplacePattern(profile.replace_pattern); + formik.setValues({ + name: profile.name, + max_streams: profile.max_streams, + search_pattern: profile.search_pattern, + replace_pattern: profile.replace_pattern, + }); + } else { + formik.resetForm(); + } + }, [profile]); + + return ( + +
+ + + + + + + + + + + + Search + + + + + Replace + {resultUrl} + +
+ ); +}; + +export default RegexFormAndView; diff --git a/vite/src/components/forms/M3UProfiles.jsx b/vite/src/components/forms/M3UProfiles.jsx new file mode 100644 index 00000000..7579db4f --- /dev/null +++ b/vite/src/components/forms/M3UProfiles.jsx @@ -0,0 +1,107 @@ +import React, { useState, useMemo } from 'react'; +import API from '../../api'; +import M3UProfile from './M3UProfile'; +import { Delete as DeleteIcon, Edit as EditIcon } from '@mui/icons-material'; +import usePlaylistsStore from '../../store/playlists'; +import { + Card, + Checkbox, + Flex, + Modal, + Button, + Box, + ActionIcon, + Text, +} from '@mantine/core'; + +const M3UProfiles = ({ playlist = null, isOpen, onClose }) => { + const profiles = usePlaylistsStore((state) => state.profiles[playlist.id]); + const [profileEditorOpen, setProfileEditorOpen] = useState(false); + const [profile, setProfile] = useState(null); + + const editProfile = (profile = null) => { + if (profile) { + setProfile(profile); + } + + setProfileEditorOpen(true); + }; + + const deleteProfile = async (id) => { + await API.deleteM3UProfile(playlist.id, id); + }; + + const toggleActive = async (values) => { + await API.updateM3UProfile(playlist.id, { + ...values, + is_active: !values.is_active, + }); + }; + + const closeEditor = () => { + setProfile(null); + setProfileEditorOpen(false); + }; + + if (!isOpen || !profiles) { + return <>; + } + + return ( + <> + + {profiles + .filter((playlist) => playlist.is_default == false) + .map((item) => ( + + + Max Streams: {item.max_streams} + toggleActive(item)} + color="primary" + /> + editProfile(item)} color="yellow.5"> + + + deleteProfile(item.id)} + color="error" + > + + + + + ))} + + + + + + + + + ); +}; + +export default M3UProfiles; diff --git a/vite/src/components/forms/Stream.jsx b/vite/src/components/forms/Stream.jsx new file mode 100644 index 00000000..456797ec --- /dev/null +++ b/vite/src/components/forms/Stream.jsx @@ -0,0 +1,103 @@ +// Modal.js +import React, { useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import API from '../../api'; +import useStreamProfilesStore from '../../store/streamProfiles'; +import { TextInput, Select, Button } from '@mantine/core'; + +const Stream = ({ stream = null, isOpen, onClose }) => { + const streamProfiles = useStreamProfilesStore((state) => state.profiles); + + const formik = useFormik({ + initialValues: { + name: '', + url: '', + stream_profile_id: '', + }, + validationSchema: Yup.object({ + name: Yup.string().required('Name is required'), + url: Yup.string().required('URL is required').min(0), + // stream_profile_id: Yup.string().required('Stream profile is required'), + }), + onSubmit: async (values, { setSubmitting, resetForm }) => { + if (stream?.id) { + await API.updateStream({ id: stream.id, ...values }); + } else { + await API.addStream(values); + } + + resetForm(); + setSubmitting(false); + onClose(); + }, + }); + + useEffect(() => { + if (stream) { + formik.setValues({ + name: stream.name, + url: stream.url, + stream_profile_id: stream.stream_profile_id, + }); + } else { + formik.resetForm(); + } + }, [stream]); + + if (!isOpen) { + return <>; + } + + return ( + +
+ + + + + ({ + label: ua.user_agent_name, + value: `${ua.id}`, + }))} + /> + + + +
+ ); +}; + +export default StreamProfile; diff --git a/vite/src/components/forms/SuperuserForm.jsx b/vite/src/components/forms/SuperuserForm.jsx new file mode 100644 index 00000000..32eb5e14 --- /dev/null +++ b/vite/src/components/forms/SuperuserForm.jsx @@ -0,0 +1,128 @@ +// frontend/src/components/forms/SuperuserForm.js +import React, { useState } from 'react'; +import axios from 'axios'; +import { + Box, + Paper, + Typography, + Grid2, + TextField, + Button, +} from '@mui/material'; + +function SuperuserForm({ onSuccess }) { + const [formData, setFormData] = useState({ + username: '', + password: '', + email: '', + }); + const [error, setError] = useState(''); + + const handleChange = (e) => { + setFormData((prev) => ({ + ...prev, + [e.target.name]: e.target.value, + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + const res = await axios.post('/api/accounts/initialize-superuser/', { + username: formData.username, + password: formData.password, + email: formData.email, + }); + if (res.data.superuser_exists) { + onSuccess(); + } + } catch (err) { + let msg = 'Failed to create superuser.'; + if (err.response && err.response.data && err.response.data.error) { + msg += ` ${err.response.data.error}`; + } + setError(msg); + } + }; + + return ( + + + + Create your Super User Account + + {error && ( + + {error} + + )} +
+ + + + + + + + + + + + + + +
+
+
+ ); +} + +export default SuperuserForm; diff --git a/vite/src/components/forms/UserAgent.jsx b/vite/src/components/forms/UserAgent.jsx new file mode 100644 index 00000000..a5644b75 --- /dev/null +++ b/vite/src/components/forms/UserAgent.jsx @@ -0,0 +1,119 @@ +// Modal.js +import React, { useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import API from '../../api'; +import { + LoadingOverlay, + TextInput, + Button, + Checkbox, + Modal, + Flex, + NativeSelect, + FileInput, + Space, +} from '@mantine/core'; + +const UserAgent = ({ userAgent = null, isOpen, onClose }) => { + const formik = useFormik({ + initialValues: { + user_agent_name: '', + user_agent: '', + description: '', + is_active: true, + }, + validationSchema: Yup.object({ + user_agent_name: Yup.string().required('Name is required'), + user_agent: Yup.string().required('User-Agent is required'), + }), + onSubmit: async (values, { setSubmitting, resetForm }) => { + if (userAgent?.id) { + await API.updateUserAgent({ id: userAgent.id, ...values }); + } else { + await API.addUserAgent(values); + } + + resetForm(); + setSubmitting(false); + onClose(); + }, + }); + + useEffect(() => { + if (userAgent) { + formik.setValues({ + user_agent_name: userAgent.user_agent_name, + user_agent: userAgent.user_agent, + description: userAgent.description, + is_active: userAgent.is_active, + }); + } else { + formik.resetForm(); + } + }, [userAgent]); + + if (!isOpen) { + return <>; + } + + return ( + +
+ + + + + + + + + + + + + + +
+ ); +}; + +export default UserAgent; diff --git a/vite/src/components/tables/ChannelsTable.jsx b/vite/src/components/tables/ChannelsTable.jsx new file mode 100644 index 00000000..9a89c36c --- /dev/null +++ b/vite/src/components/tables/ChannelsTable.jsx @@ -0,0 +1,1005 @@ +import { useEffect, useMemo, useRef, useState, useCallback } from 'react'; +import { MantineReactTable, useMantineReactTable } from 'mantine-react-table'; +import useChannelsStore from '../../store/channels'; +import useAlertStore from '../../store/alerts'; +import { + Add as AddIcon, + LiveTv as LiveTvIcon, + ContentCopy, + IndeterminateCheckBox, + CompareArrows, + Code, + AddBox, +} from '@mui/icons-material'; +import API from '../../api'; +import ChannelForm from '../forms/Channel'; +import { TableHelper } from '../../helpers'; +import utils from '../../utils'; +import logo from '../../images/logo.png'; +import useVideoStore from '../../store/useVideoStore'; +import useSettingsStore from '../../store/settings'; +import usePlaylistsStore from '../../store/playlists'; +import { + Tv2, + ScreenShare, + Scroll, + SquareMinus, + Pencil, + ArrowUp, + ArrowDown, + ArrowUpDown, + TvMinimalPlay, +} from 'lucide-react'; +import ghostImage from '../../images/ghost.svg'; +import { + Box, + TextInput, + Popover, + ActionIcon, + Select, + Button, + Paper, + Flex, + Center, + Text, + Tooltip, + Grid, + Group, + useMantineTheme, + UnstyledButton, +} from '@mantine/core'; +import { + IconArrowDown, + IconArrowUp, + IconDeviceDesktopSearch, + IconSelector, + IconSortAscendingNumbers, + IconSquarePlus, +} from '@tabler/icons-react'; // Import custom icons + +const ChannelStreams = ({ channel, isExpanded }) => { + const channelStreams = useChannelsStore( + (state) => state.channels[channel.id]?.streams + ); + const { playlists } = usePlaylistsStore(); + + const removeStream = async (stream) => { + const newStreamList = channelStreams.filter((s) => s.id !== stream.id); + await API.updateChannel({ + ...channel, + stream_ids: newStreamList.map((s) => s.id), + }); + }; + + const channelStreamsTable = useMantineReactTable({ + ...TableHelper.defaultProperties, + data: channelStreams, + columns: useMemo( + () => [ + { + header: 'Name', + accessorKey: 'name', + }, + { + header: 'M3U', + accessorFn: (row) => + playlists.find((playlist) => playlist.id === row.m3u_account)?.name, + }, + ], + [playlists] + ), + enableKeyboardShortcuts: false, + enableColumnFilters: false, + enableBottomToolbar: false, + enableTopToolbar: false, + columnFilterDisplayMode: 'popover', + enablePagination: false, + enableRowVirtualization: true, + enableColumnHeaders: false, + rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer + initialState: { + density: 'compact', + }, + enableRowActions: true, + enableRowOrdering: true, + mantineRowDragHandleProps: ({ table }) => ({ + onDragEnd: async () => { + const { draggingRow, hoveredRow } = table.getState(); + + if (hoveredRow && draggingRow) { + channelStreams.splice( + hoveredRow.index, + 0, + channelStreams.splice(draggingRow.index, 1)[0] + ); + + const { streams: _, ...channelUpdate } = channel; + + API.updateChannel({ + ...channelUpdate, + stream_ids: channelStreams.map((stream) => stream.id), + }); + } + }, + }), + renderRowActions: ({ row }) => ( + + removeStream(row.original)} + > + + + + ), + }); + + if (!isExpanded) { + return <>; + } + + return ( + + + + ); +}; + +// /* ----------------------------------------------------------- +// 2) Custom-styled "chip" buttons for HDHR, M3U, EPG +// ------------------------------------------------------------ */ +// const HDHRButton = styled(Button)(() => ({ +// border: '1px solid #a3d977', +// color: '#a3d977', +// backgroundColor: 'transparent', +// textTransform: 'none', +// fontSize: '0.85rem', +// display: 'flex', +// alignItems: 'center', +// gap: '4px', +// padding: '2px 8px', +// minWidth: 'auto', +// '&:hover': { +// borderColor: '#c2e583', +// color: '#c2e583', +// backgroundColor: 'rgba(163,217,119,0.1)', +// }, +// })); + +// const M3UButton = styled(Button)(() => ({ +// border: '1px solid #5f6dc6', +// color: '#5f6dc6', +// backgroundColor: 'transparent', +// textTransform: 'none', +// fontSize: '0.85rem', +// display: 'flex', +// alignItems: 'center', +// gap: '4px', +// padding: '2px 8px', +// minWidth: 'auto', +// '&:hover': { +// borderColor: '#7f8de6', +// color: '#7f8de6', +// backgroundColor: 'rgba(95,109,198,0.1)', +// }, +// })); + +// const EPGButton = styled(Button)(() => ({ +// border: '1px solid #707070', +// color: '#a0a0a0', +// backgroundColor: 'transparent', +// textTransform: 'none', +// fontSize: '0.85rem', +// display: 'flex', +// alignItems: 'center', +// gap: '4px', +// padding: '2px 8px', +// minWidth: 'auto', +// '&:hover': { +// borderColor: '#a0a0a0', +// color: '#c0c0c0', +// backgroundColor: 'rgba(112,112,112,0.1)', +// }, +// })); + +const ChannelsTable = ({}) => { + const [channel, setChannel] = useState(null); + const [channelModalOpen, setChannelModalOpen] = useState(false); + const [rowSelection, setRowSelection] = useState([]); + const [channelGroupOptions, setChannelGroupOptions] = useState([]); + + const [anchorEl, setAnchorEl] = useState(null); + const [textToCopy, setTextToCopy] = useState(''); + + const [filterValues, setFilterValues] = useState({}); + + // const theme = useTheme(); + const theme = useMantineTheme(); + + const { showVideo } = useVideoStore(); + const { + channels, + isLoading: channelsLoading, + fetchChannels, + setChannelsPageSelection, + } = useChannelsStore(); + const { showAlert } = useAlertStore(); + + useEffect(() => { + setChannelGroupOptions([ + ...new Set( + Object.values(channels).map((channel) => channel.channel_group?.name) + ), + ]); + }, [channels]); + + const handleFilterChange = (columnId, value) => { + console.log(columnId); + console.log(value); + setFilterValues((prev) => ({ + ...prev, + [columnId]: value ? value.toLowerCase() : '', + })); + }; + + const outputUrlRef = useRef(null); + + const { + environment: { env_mode }, + } = useSettingsStore(); + + // Configure columns + const columns = useMemo( + () => [ + { + header: '#', + size: 50, + accessorKey: 'channel_number', + }, + { + header: 'Name', + accessorKey: 'channel_name', + muiTableHeadCellProps: { + sx: { textAlign: 'center' }, + }, + Header: ({ column }) => ( + handleFilterChange(column.id, e.target.value)} + size="xs" + /> + ), + meta: { + filterVariant: null, + }, + }, + { + header: 'Group', + accessorFn: (row) => row.channel_group?.name || '', + Header: ({ column }) => ( + { + setActiveFilterValue(e.target.value); + column.setFilterValue(e.target.value); + }} + displayEmpty + fullWidth + > + All + Active + Inactive + + + ), + filterFn: (row, _columnId, activeFilterValue) => { + if (!activeFilterValue) return true; // Show all if no filter + return String(row.getValue('is_active')) === activeFilterValue; + }, + }, + ], + [] + ); + + //optionally access the underlying virtualizer instance + const rowVirtualizerInstanceRef = useRef(null); + + const [isLoading, setIsLoading] = useState(true); + const [sorting, setSorting] = useState([]); + + const editPlaylist = async (playlist = null) => { + if (playlist) { + setPlaylist(playlist); + } + setPlaylistModalOpen(true); + }; + + const refreshPlaylist = async (id) => { + await API.refreshPlaylist(id); + }; + + const deletePlaylist = async (id) => { + await API.deletePlaylist(id); + }; + + const closeModal = () => { + setPlaylistModalOpen(false); + setPlaylist(null); + }; + + const deletePlaylists = async (ids) => { + const selected = table + .getRowModel() + .rows.filter((row) => row.getIsSelected()); + // await API.deleteStreams(selected.map(stream => stream.original.id)) + }; + + useEffect(() => { + if (typeof window !== 'undefined') { + setIsLoading(false); + } + }, []); + + useEffect(() => { + //scroll to the top of the table when the sorting changes + try { + rowVirtualizerInstanceRef.current?.scrollToIndex?.(0); + } catch (error) { + console.error(error); + } + }, [sorting]); + + const table = useMaterialReactTable({ + ...TableHelper.defaultProperties, + columns, + data: playlists, + enablePagination: false, + enableRowVirtualization: true, + // enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + state: { + isLoading, + sorting, + rowSelection, + }, + rowVirtualizerInstanceRef, //optional + rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer + initialState: { + density: 'compact', + }, + enableRowActions: true, + renderRowActions: ({ row }) => ( + <> + { + editPlaylist(row.original); + }} + sx={{ pt: 0, pb: 0 }} + > + {/* Small icon size */} + + deletePlaylist(row.original.id)} + sx={{ pt: 0, pb: 0 }} + > + {/* Small icon size */} + + refreshPlaylist(row.original.id)} + sx={{ pt: 0, pb: 0 }} + > + {/* Small icon size */} + + + ), + muiTableContainerProps: { + sx: { + height: 'calc(43vh - 0px)', + pr: 1, + pl: 1, + }, + }, + renderTopToolbarCustomActions: ({ table }) => ( + + M3U Accounts + + editPlaylist()} + > + {/* Small icon size */} + + + + ), + }); + + return ( + + + + + ); +}; + +export default Example; diff --git a/vite/src/components/tables/StreamProfilesTable.jsx b/vite/src/components/tables/StreamProfilesTable.jsx new file mode 100644 index 00000000..b2ac3390 --- /dev/null +++ b/vite/src/components/tables/StreamProfilesTable.jsx @@ -0,0 +1,202 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; +import { MantineReactTable, useMantineReactTable } from 'mantine-react-table'; +import API from '../../api'; +import { + Delete as DeleteIcon, + Edit as EditIcon, + Add as AddIcon, + Check as CheckIcon, + Close as CloseIcon, +} from '@mui/icons-material'; +import StreamProfileForm from '../forms/StreamProfile'; +import useStreamProfilesStore from '../../store/streamProfiles'; +import { TableHelper } from '../../helpers'; +import useSettingsStore from '../../store/settings'; +import useAlertStore from '../../store/alerts'; +import { Box, ActionIcon, Tooltip, Text } from '@mantine/core'; + +const StreamProfiles = () => { + const [profile, setProfile] = useState(null); + const [profileModalOpen, setProfileModalOpen] = useState(false); + const [rowSelection, setRowSelection] = useState([]); + const [activeFilterValue, setActiveFilterValue] = useState('all'); + + const streamProfiles = useStreamProfilesStore((state) => state.profiles); + const { settings } = useSettingsStore(); + const { showAlert } = useAlertStore(); + + const columns = useMemo( + //column definitions... + () => [ + { + header: 'Name', + accessorKey: 'profile_name', + }, + { + header: 'Command', + accessorKey: 'command', + }, + { + header: 'Parameters', + accessorKey: 'parameters', + }, + { + header: 'Active', + accessorKey: 'is_active', + size: 100, + sortingFn: 'basic', + muiTableBodyCellProps: { + align: 'left', + }, + Cell: ({ cell }) => ( + + {cell.getValue() ? ( + + ) : ( + + )} + + ), + Filter: ({ column }) => ( + + e.stopPropagation()} + onChange={handleGroupChange} + data={groupOptions} + /> + ), + Cell: ({ cell }) => ( +
+ {cell.getValue()} +
+ ), + }, + { + header: 'M3U', + size: 75, + accessorFn: (row) => + playlists.find((playlist) => playlist.id === row.m3u_account)?.name, + Header: ({ column }) => ( + { + setActiveFilterValue(e.target.value); + column.setFilterValue(e.target.value); + }} + displayEmpty + data={[ + { + value: 'all', + label: 'All', + }, + { + value: 'active', + label: 'Active', + }, + { + value: 'inactive', + label: 'Inactive', + }, + ]} + /> + ), + filterFn: (row, _columnId, activeFilterValue) => { + if (activeFilterValue == 'all') return true; // Show all if no filter + return String(row.getValue('is_active')) === activeFilterValue; + }, + }, + ], + [] + ); + + //optionally access the underlying virtualizer instance + const rowVirtualizerInstanceRef = useRef(null); + + const [isLoading, setIsLoading] = useState(true); + const [sorting, setSorting] = useState([]); + + const editUserAgent = async (userAgent = null) => { + setUserAgent(userAgent); + setUserAgentModalOpen(true); + }; + + const deleteUserAgent = async (ids) => { + if (Array.isArray(ids)) { + if (ids.includes(settings['default-user-agent'].value)) { + showAlert('Cannot delete default user-agent', 'error'); + return; + } + + await API.deleteUserAgents(ids); + } else { + if (ids == settings['default-user-agent'].value) { + showAlert('Cannot delete default user-agent', 'error'); + return; + } + + await API.deleteUserAgent(ids); + } + }; + + const closeUserAgentForm = () => { + setUserAgent(null); + setUserAgentModalOpen(false); + }; + + useEffect(() => { + if (typeof window !== 'undefined') { + setIsLoading(false); + } + }, []); + + useEffect(() => { + //scroll to the top of the table when the sorting changes + try { + rowVirtualizerInstanceRef.current?.scrollToIndex?.(0); + } catch (error) { + console.error(error); + } + }, [sorting]); + + const table = useMantineReactTable({ + ...TableHelper.defaultProperties, + columns, + data: userAgents, + enablePagination: false, + enableRowVirtualization: true, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + state: { + isLoading, + sorting, + rowSelection, + }, + rowVirtualizerInstanceRef, //optional + rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer + initialState: { + density: 'compact', + }, + enableRowActions: true, + renderRowActions: ({ row }) => ( + <> + { + editUserAgent(row.original); + }} + > + {/* Small icon size */} + + deleteUserAgent(row.original.id)} + > + {/* Small icon size */} + + + ), + muiTableContainerProps: { + sx: { + height: 'calc(42vh + 5px)', + }, + }, + renderTopToolbarCustomActions: ({ table }) => ( + + User-Agents + + editUserAgent()} + > + {/* Small icon size */} + + + + ), + }); + + return ( + <> + + + + ); +}; + +export default UserAgentsTable; diff --git a/vite/src/helpers/index.jsx b/vite/src/helpers/index.jsx new file mode 100644 index 00000000..7ec0abb7 --- /dev/null +++ b/vite/src/helpers/index.jsx @@ -0,0 +1,3 @@ +import table from "./table"; + +export const TableHelper = table; diff --git a/vite/src/helpers/table.jsx b/vite/src/helpers/table.jsx new file mode 100644 index 00000000..f8ebe8ab --- /dev/null +++ b/vite/src/helpers/table.jsx @@ -0,0 +1,61 @@ +// frontend/src/helpers/table.js + +export default { + defaultProperties: { + enableBottomToolbar: false, + enableDensityToggle: false, + enableFullScreenToggle: false, + positionToolbarAlertBanner: 'none', + // columnFilterDisplayMode: 'popover', + enableRowNumbers: false, + positionActionsColumn: 'last', + enableColumnActions: false, + enableColumnFilters: false, + enableGlobalFilter: false, + initialState: { + density: 'compact', + }, + mantineSelectAllCheckboxProps: { + size: 'xs', + }, + mantineSelectCheckboxProps: { + size: 'xs', + }, + mantineTableBodyCellProps: { + style: { + // py: 0, + paddingLeft: 10, + paddingRight: 10, + borderColor: '#444', + color: '#E0E0E0', + fontSize: '0.85rem', + }, + }, + mantineTableHeadCellProps: { + style: { + paddingLeft: 10, + paddingRight: 10, + color: '#CFCFCF', + backgroundColor: '#383A3F', + borderColor: '#444', + // fontWeight: 600, + // fontSize: '0.8rem', + }, + }, + mantineTableBodyProps: { + style: { + // Subtle row striping + '& tr:nth-of-type(odd)': { + backgroundColor: '#2F3034', + }, + '& tr:nth-of-type(even)': { + backgroundColor: '#333539', + }, + // Row hover effect + '& tr:hover td': { + backgroundColor: '#3B3D41', + }, + }, + }, + }, +}; diff --git a/vite/src/images/dispatcharr.svg b/vite/src/images/dispatcharr.svg new file mode 100644 index 00000000..3ddd8e2b --- /dev/null +++ b/vite/src/images/dispatcharr.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vite/src/images/ghost.svg b/vite/src/images/ghost.svg new file mode 100644 index 00000000..2206100d --- /dev/null +++ b/vite/src/images/ghost.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + diff --git a/vite/src/images/logo.png b/vite/src/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..99c3c19f3d6ba0647c2883654126d5ea8b529020 GIT binary patch literal 44564 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 \ No newline at end of file diff --git a/vite/src/main.jsx b/vite/src/main.jsx new file mode 100644 index 00000000..2a7b83c0 --- /dev/null +++ b/vite/src/main.jsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +); diff --git a/vite/src/mantineTheme.jsx b/vite/src/mantineTheme.jsx new file mode 100644 index 00000000..e89455d8 --- /dev/null +++ b/vite/src/mantineTheme.jsx @@ -0,0 +1,58 @@ +import { createTheme, MantineProvider, rem } from '@mantine/core'; + +const theme = createTheme({ + palette: { + mode: 'dark', + background: { + default: '#18181b', // Global background color (Tailwind zinc-900) + paper: '#27272a', // Paper background (Tailwind zinc-800) + }, + primary: { + main: '#4A90E2', + contrastText: '#FFFFFF', + }, + secondary: { + main: '#F5A623', + contrastText: '#FFFFFF', + }, + text: { + primary: '#FFFFFF', + secondary: '#d4d4d8', // Updated secondary text color (Tailwind zinc-300) + }, + // Custom colors for components (chip buttons, borders, etc.) + custom: { + // For chip buttons: + greenMain: '#90C43E', + greenHoverBg: 'rgba(144,196,62,0.1)', + + indigoMain: '#4F39F6', + indigoHoverBg: 'rgba(79,57,246,0.1)', + + greyBorder: '#707070', + greyHoverBg: 'rgba(112,112,112,0.1)', + greyText: '#a0a0a0', + + // Common border colors: + borderDefault: '#3f3f46', // Tailwind zinc-700 + borderHover: '#5f5f66', // Approximate Tailwind zinc-600 + + // For the "Add" button: + successBorder: '#00a63e', + successBg: '#0d542b', + successBgHover: '#0a4020', + successIcon: '#05DF72', + }, + }, + + custom: { + sidebar: { + activeBackground: 'rgba(21, 69, 62, 0.67)', + activeBorder: '#14917e', + hoverBackground: '#27272a', + hoverBorder: '#3f3f46', + fontFamily: 'Inter, sans-serif', + }, + }, +}); + +export default theme; diff --git a/vite/src/pages/Channels.jsx b/vite/src/pages/Channels.jsx new file mode 100644 index 00000000..4899c4fb --- /dev/null +++ b/vite/src/pages/Channels.jsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; +import ChannelsTable from '../components/tables/ChannelsTable'; +import StreamsTable from '../components/tables/StreamsTable'; +import { Box, Grid } from '@mantine/core'; + +const ChannelsPage = () => { + return ( + + + + + + + + + + + + + ); +}; + +export default ChannelsPage; diff --git a/vite/src/pages/Dashboard.jsx b/vite/src/pages/Dashboard.jsx new file mode 100644 index 00000000..af2104e1 --- /dev/null +++ b/vite/src/pages/Dashboard.jsx @@ -0,0 +1,27 @@ +// src/components/Dashboard.js +import React, { useState } from "react"; + +const Dashboard = () => { + const [newStream, setNewStream] = useState(""); + + return ( +
+

Dashboard Page

+ setNewStream(e.target.value)} + placeholder="Enter Stream" + /> + +

Streams:

+
    + {state.streams.map((stream, index) => ( +
  • {stream}
  • + ))} +
+
+ ); +}; + +export default Dashboard; diff --git a/vite/src/pages/EPG.jsx b/vite/src/pages/EPG.jsx new file mode 100644 index 00000000..f8b9a7f4 --- /dev/null +++ b/vite/src/pages/EPG.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Box } from '@mantine/core'; +import UserAgentsTable from '../components/tables/UserAgentsTable'; +import EPGsTable from '../components/tables/EPGsTable'; + +const EPGPage = () => { + return ( + + + + + + + + + + ); +}; + +export default EPGPage; diff --git a/vite/src/pages/Guide.jsx b/vite/src/pages/Guide.jsx new file mode 100644 index 00000000..d60ec8ba --- /dev/null +++ b/vite/src/pages/Guide.jsx @@ -0,0 +1,532 @@ +// frontend/src/pages/Guide.js +import React, { useMemo, useState, useEffect, useRef } from 'react'; +import { + Box, + Typography, + Paper, + Stack, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Slide, + CircularProgress, + Backdrop, +} from '@mui/material'; +import dayjs from 'dayjs'; +import API from '../api'; +import useChannelsStore from '../store/channels'; +import logo from '../images/logo.png'; +import useVideoStore from '../store/useVideoStore'; // NEW import +import useAlertStore from '../store/alerts'; +import useSettingsStore from '../store/settings'; + +/** Layout constants */ +const CHANNEL_WIDTH = 120; // Width of the channel/logo column +const PROGRAM_HEIGHT = 90; // Height of each channel row +const HOUR_WIDTH = 300; // The width for a 1-hour block +const MINUTE_INCREMENT = 15; // For positioning programs every 15 min +const MINUTE_BLOCK_WIDTH = HOUR_WIDTH / (60 / MINUTE_INCREMENT); + +// Modal size constants +const MODAL_WIDTH = 600; +const MODAL_HEIGHT = 400; + +// Slide transition for Dialog +const Transition = React.forwardRef(function Transition(props, ref) { + return ; +}); + +export default function TVChannelGuide({ startDate, endDate }) { + const { channels } = useChannelsStore(); + + const [programs, setPrograms] = useState([]); + const [guideChannels, setGuideChannels] = useState([]); + const [now, setNow] = useState(dayjs()); + const [selectedProgram, setSelectedProgram] = useState(null); + const [loading, setLoading] = useState(true); + const { showAlert } = useAlertStore(); + const { + environment: { env_mode }, + } = useSettingsStore(); + + const guideRef = useRef(null); + + // Load program data once + useEffect(() => { + if (!channels || channels.length === 0) { + console.warn('No channels provided or empty channels array'); + showAlert('No channels available', 'error'); + setLoading(false); + return; + } + + const fetchPrograms = async () => { + console.log('Fetching program grid...'); + const fetched = await API.getGrid(); // GETs your EPG grid + console.log(`Received ${fetched.length} programs`); + + // Unique tvg_ids from returned programs + const programIds = [...new Set(fetched.map((p) => p.tvg_id))]; + + // Filter your Redux/Zustand channels by matching tvg_id + const filteredChannels = channels.filter((ch) => + programIds.includes(ch.tvg_id) + ); + console.log( + `found ${filteredChannels.length} channels with matching tvg_ids` + ); + + setGuideChannels(filteredChannels); + setPrograms(fetched); + setLoading(false); + }; + + fetchPrograms(); + }, [channels]); + + // Use start/end from props or default to "today at midnight" +24h + const defaultStart = dayjs(startDate || dayjs().startOf('day')); + const defaultEnd = endDate ? dayjs(endDate) : defaultStart.add(24, 'hour'); + + // Expand timeline if needed based on actual earliest/ latest program + const earliestProgramStart = useMemo(() => { + if (!programs.length) return defaultStart; + return programs.reduce((acc, p) => { + const s = dayjs(p.start_time); + return s.isBefore(acc) ? s : acc; + }, defaultStart); + }, [programs, defaultStart]); + + const latestProgramEnd = useMemo(() => { + if (!programs.length) return defaultEnd; + return programs.reduce((acc, p) => { + const e = dayjs(p.end_time); + return e.isAfter(acc) ? e : acc; + }, defaultEnd); + }, [programs, defaultEnd]); + + const start = earliestProgramStart.isBefore(defaultStart) + ? earliestProgramStart + : defaultStart; + const end = latestProgramEnd.isAfter(defaultEnd) + ? latestProgramEnd + : defaultEnd; + + // Time increments in 15-min steps (for placing programs) + const programTimeline = useMemo(() => { + const times = []; + let current = start; + while (current.isBefore(end)) { + times.push(current); + current = current.add(MINUTE_INCREMENT, 'minute'); + } + return times; + }, [start, end]); + + // Hourly marks + const hourTimeline = useMemo(() => { + const hours = []; + let current = start; + while (current.isBefore(end)) { + hours.push(current); + current = current.add(1, 'hour'); + } + return hours; + }, [start, end]); + + // Scroll to "now" on load + useEffect(() => { + if (guideRef.current) { + const nowOffset = dayjs().diff(start, 'minute'); + const scrollPosition = + (nowOffset / MINUTE_INCREMENT) * MINUTE_BLOCK_WIDTH - + MINUTE_BLOCK_WIDTH; + guideRef.current.scrollLeft = Math.max(scrollPosition, 0); + } + }, [programs, start]); + + // Update “now” every 60s + useEffect(() => { + const interval = setInterval(() => { + setNow(dayjs()); + }, 60000); + return () => clearInterval(interval); + }, []); + + // Pixel offset for the “now” vertical line + const nowPosition = useMemo(() => { + if (now.isBefore(start) || now.isAfter(end)) return -1; + const minutesSinceStart = now.diff(start, 'minute'); + return (minutesSinceStart / MINUTE_INCREMENT) * MINUTE_BLOCK_WIDTH; + }, [now, start, end]); + + // Helper: find channel by tvg_id + function findChannelByTvgId(tvgId) { + return guideChannels.find((ch) => ch.tvg_id === tvgId); + } + + // The “Watch Now” click => show floating video + const { showVideo } = useVideoStore(); // or useVideoStore() + function handleWatchStream(program) { + const matched = findChannelByTvgId(program.tvg_id); + if (!matched) { + console.warn(`No channel found for tvg_id=${program.tvg_id}`); + return; + } + // Build a playable stream URL for that channel + let vidUrl = `/output/stream/${matched.channel_number}/`; + if (env_mode == 'dev') { + vidUrl = `${window.location.protocol}//${window.location.hostname}:5656${vidUrl}`; + } + + showVideo(vidUrl); + + // Optionally close the modal + setSelectedProgram(null); + } + + // On program click, open the details modal + function handleProgramClick(program, event) { + // Optionally scroll that element into view or do something else + event.currentTarget.scrollIntoView({ + behavior: 'smooth', + inline: 'center', + }); + setSelectedProgram(program); + } + + // Close the modal + function handleCloseModal() { + setSelectedProgram(null); + } + + // Renders each program block + function renderProgram(program, channelStart) { + const programKey = `${program.tvg_id}-${program.start_time}`; + const programStart = dayjs(program.start_time); + const programEnd = dayjs(program.end_time); + const startOffsetMinutes = programStart.diff(channelStart, 'minute'); + const durationMinutes = programEnd.diff(programStart, 'minute'); + const leftPx = (startOffsetMinutes / MINUTE_INCREMENT) * MINUTE_BLOCK_WIDTH; + const widthPx = (durationMinutes / MINUTE_INCREMENT) * MINUTE_BLOCK_WIDTH; + + // Highlight if currently live + const isLive = now.isAfter(programStart) && now.isBefore(programEnd); + + return ( + handleProgramClick(program, e)} + > + + + {program.title} + + + {programStart.format('h:mma')} - {programEnd.format('h:mma')} + + + + ); + } + + if (loading) { + return ( + theme.zIndex.drawer + 1, + position: 'fixed', // Ensure it covers the entire page + top: 0, + left: 0, + right: 0, + bottom: 0, + }} + open={loading} + > + + + ); + } + + return ( + + {/* Sticky top bar */} + + + TV Guide + + + {now.format('dddd, MMMM D, YYYY • h:mm A')} + + + + {/* Main layout */} + + {/* Channel Logos Column */} + + + {guideChannels.map((channel) => ( + + + {channel.channel_name} + + + ))} + + + {/* Timeline & Program Blocks */} + + {/* Sticky timeline header */} + + + {hourTimeline.map((time, hourIndex) => ( + + + {time.format('h:mma')} + + + {[0, 1, 2, 3].map((i) => ( + + ))} + + + ))} + + + + {/* Now line */} + + {nowPosition >= 0 && ( + + )} + + {/* Channel rows */} + {guideChannels.map((channel) => { + const channelPrograms = programs.filter( + (p) => p.tvg_id === channel.tvg_id + ); + return ( + + + {channelPrograms.map((prog) => renderProgram(prog, start))} + + + ); + })} + + + + + {/* Modal for program details */} + + {selectedProgram && ( + <> + + {selectedProgram.title} + + + + {dayjs(selectedProgram.start_time).format('h:mma')} -{' '} + {dayjs(selectedProgram.end_time).format('h:mma')} + + + {selectedProgram.description || 'No description available.'} + + + + {/* Only show the Watch button if currently live */} + {now.isAfter(dayjs(selectedProgram.start_time)) && + now.isBefore(dayjs(selectedProgram.end_time)) && ( + + )} + + + + )} + + + ); +} diff --git a/vite/src/pages/Home.jsx b/vite/src/pages/Home.jsx new file mode 100644 index 00000000..ac971332 --- /dev/null +++ b/vite/src/pages/Home.jsx @@ -0,0 +1,14 @@ +// src/components/Home.js +import React, { useState } from "react"; + +const Home = () => { + const [newChannel, setNewChannel] = useState(""); + + return ( +
+

Home Page

+
+ ); +}; + +export default Home; diff --git a/vite/src/pages/Login.jsx b/vite/src/pages/Login.jsx new file mode 100644 index 00000000..7ce24331 --- /dev/null +++ b/vite/src/pages/Login.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import LoginForm from '../components/forms/LoginForm'; + +const Login = () => { + return ; +}; + +export default Login; diff --git a/vite/src/pages/M3U.jsx b/vite/src/pages/M3U.jsx new file mode 100644 index 00000000..71cc729e --- /dev/null +++ b/vite/src/pages/M3U.jsx @@ -0,0 +1,34 @@ +import React, { useState } from 'react'; +import useUserAgentsStore from '../store/userAgents'; +import M3UsTable from '../components/tables/M3UsTable'; +import UserAgentsTable from '../components/tables/UserAgentsTable'; +import { Box } from '@mantine/core'; + +const M3UPage = () => { + const isLoading = useUserAgentsStore((state) => state.isLoading); + const error = useUserAgentsStore((state) => state.error); + + if (isLoading) return
Loading...
; + if (error) return
Error: {error}
; + + return ( + + + + + + + + + + ); +}; + +export default M3UPage; diff --git a/vite/src/pages/Settings.jsx b/vite/src/pages/Settings.jsx new file mode 100644 index 00000000..32655186 --- /dev/null +++ b/vite/src/pages/Settings.jsx @@ -0,0 +1,146 @@ +import React, { useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; + +import API from '../api'; +import useSettingsStore from '../store/settings'; +import useUserAgentsStore from '../store/userAgents'; +import useStreamProfilesStore from '../store/streamProfiles'; +import { Button, Center, Flex, Paper, Select, Title } from '@mantine/core'; + +const SettingsPage = () => { + const { settings } = useSettingsStore(); + const { userAgents } = useUserAgentsStore(); + const { profiles: streamProfiles } = useStreamProfilesStore(); + + // Add your region choices here: + const regionChoices = [ + { value: 'us', label: 'US' }, + { value: 'uk', label: 'UK' }, + { value: 'nl', label: 'NL' }, + { value: 'de', label: 'DE' }, + // Add more if needed + ]; + + const formik = useFormik({ + initialValues: { + 'default-user-agent': '', + 'default-stream-profile': '', + 'preferred-region': '', + }, + validationSchema: Yup.object({ + 'default-user-agent': Yup.string().required('User-Agent is required'), + 'default-stream-profile': Yup.string().required( + 'Stream Profile is required' + ), + // The region is optional or required as you prefer + // 'preferred-region': Yup.string().required('Region is required'), + }), + onSubmit: async (values, { setSubmitting, resetForm }) => { + const changedSettings = {}; + for (const settingKey in values) { + // If the user changed the setting’s value from what’s in the DB: + if (String(values[settingKey]) !== String(settings[settingKey].value)) { + changedSettings[settingKey] = values[settingKey]; + } + } + + // Update each changed setting in the backend + for (const updatedKey in changedSettings) { + await API.updateSetting({ + ...settings[updatedKey], + value: changedSettings[updatedKey], + }); + } + + setSubmitting(false); + // Don’t necessarily resetForm, in case the user wants to see new values + }, + }); + + // Initialize form values once settings / userAgents / profiles are loaded + useEffect(() => { + formik.setValues( + Object.values(settings).reduce((acc, setting) => { + // If the setting’s value is numeric, parse it + // Otherwise, just store as string + const possibleNumber = parseInt(setting.value, 10); + acc[setting.key] = isNaN(possibleNumber) + ? setting.value + : possibleNumber; + return acc; + }, {}) + ); + // eslint-disable-next-line + }, [settings, userAgents, streamProfiles]); + + return ( +
+ + + Settings + +
+ ({ + value: `${option.id}`, + label: option.profile_name, + }))} + /> + {/* - {channelGroups.map((option, index) => ( - - {option.name} - - ))} - - - {formik.touched.channel_group_id && - formik.errors.channel_group_id} - - - - - setChannelGroupModalOpen(true)} - title="Create new group" - size="small" - variant="filled" - > - - - - - - - - Stream Profile - - - - - - - - - - - - - - - - {/* File upload input */} - - Logo - {/* Display selected image */} - - Selected - - handleLogoChange(event)} - style={{ display: 'none' }} - /> - - - - - - - - - Active Streams - - - - - Available Streams - - - - - - - {/* Submit button */} - - - - - setChannelGroupModalOpen(false)} - /> - - ); -}; - -export default Channel; diff --git a/frontend/src/components/forms/Channel.jsx b/frontend/src/components/forms/Channel.jsx new file mode 100644 index 00000000..9e7b72f1 --- /dev/null +++ b/frontend/src/components/forms/Channel.jsx @@ -0,0 +1,416 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import useChannelsStore from '../../store/channels'; +import API from '../../api'; +import useStreamProfilesStore from '../../store/streamProfiles'; +import { Add as AddIcon, Remove as RemoveIcon } from '@mui/icons-material'; +import useStreamsStore from '../../store/streams'; +import { MantineReactTable, useMantineReactTable } from 'mantine-react-table'; +import ChannelGroupForm from './ChannelGroup'; +import usePlaylistsStore from '../../store/playlists'; +import logo from '../../images/logo.png'; +import { + Box, + Button, + Modal, + TextInput, + NativeSelect, + Text, + Group, + ActionIcon, + Center, + Grid, + Flex, +} from '@mantine/core'; + +const Channel = ({ channel = null, isOpen, onClose }) => { + const channelGroups = useChannelsStore((state) => state.channelGroups); + const streams = useStreamsStore((state) => state.streams); + const { profiles: streamProfiles } = useStreamProfilesStore(); + const { playlists } = usePlaylistsStore(); + + const [logoFile, setLogoFile] = useState(null); + const [logoPreview, setLogoPreview] = useState(logo); + const [channelStreams, setChannelStreams] = useState([]); + const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false); + + const addStream = (stream) => { + const streamSet = new Set(channelStreams); + streamSet.add(stream); + setChannelStreams(Array.from(streamSet)); + }; + + const removeStream = (stream) => { + const streamSet = new Set(channelStreams); + streamSet.delete(stream); + setChannelStreams(Array.from(streamSet)); + }; + + const handleLogoChange = (e) => { + const file = e.target.files[0]; + if (file) { + setLogoFile(file); + setLogoPreview(URL.createObjectURL(file)); + } + }; + + const formik = useFormik({ + initialValues: { + channel_name: '', + channel_number: '', + channel_group_id: '', + stream_profile_id: '0', + tvg_id: '', + tvg_name: '', + }, + validationSchema: Yup.object({ + channel_name: Yup.string().required('Name is required'), + channel_number: Yup.string().required('Invalid channel number').min(0), + channel_group_id: Yup.string().required('Channel group is required'), + }), + onSubmit: async (values, { setSubmitting, resetForm }) => { + if (values.stream_profile_id == '0') { + values.stream_profile_id = null; + } + + console.log(values); + if (channel?.id) { + await API.updateChannel({ + id: channel.id, + ...values, + logo_file: logoFile, + stream_ids: channelStreams.map((stream) => stream.id), + }); + } else { + await API.addChannel({ + ...values, + logo_file: logoFile, + stream_ids: channelStreams.map((stream) => stream.id), + }); + } + + resetForm(); + setLogoFile(null); + setLogoPreview(logo); + setSubmitting(false); + onClose(); + }, + }); + + useEffect(() => { + if (channel) { + formik.setValues({ + channel_name: channel.channel_name, + channel_number: channel.channel_number, + channel_group_id: channel.channel_group?.id, + stream_profile_id: channel.stream_profile_id || '0', + tvg_id: channel.tvg_id, + tvg_name: channel.tvg_name, + }); + + console.log(channel); + setChannelStreams(channel.streams); + } else { + formik.resetForm(); + } + }, [channel]); + + // const activeStreamsTable = useMantineReactTable({ + // data: channelStreams, + // columns: useMemo( + // () => [ + // { + // header: 'Name', + // accessorKey: 'name', + // Cell: ({ cell }) => ( + //
+ // {cell.getValue()} + //
+ // ), + // }, + // { + // header: 'M3U', + // accessorKey: 'group_name', + // Cell: ({ cell }) => ( + //
+ // {cell.getValue()} + //
+ // ), + // }, + // ], + // [] + // ), + // enableSorting: false, + // enableBottomToolbar: false, + // enableTopToolbar: false, + // columnFilterDisplayMode: 'popover', + // enablePagination: false, + // enableRowVirtualization: true, + // enableRowOrdering: true, + // rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer + // initialState: { + // density: 'compact', + // }, + // enableRowActions: true, + // positionActionsColumn: 'last', + // renderRowActions: ({ row }) => ( + // <> + // removeStream(row.original)} + // > + // {/* Small icon size */} + // + // + // ), + // mantineTableContainerProps: { + // style: { + // height: '200px', + // }, + // }, + // mantineRowDragHandleProps: ({ table }) => ({ + // onDragEnd: () => { + // const { draggingRow, hoveredRow } = table.getState(); + + // if (hoveredRow && draggingRow) { + // channelStreams.splice( + // hoveredRow.index, + // 0, + // channelStreams.splice(draggingRow.index, 1)[0] + // ); + + // setChannelStreams([...channelStreams]); + // } + // }, + // }), + // }); + + // const availableStreamsTable = useMantineReactTable({ + // data: streams, + // columns: useMemo( + // () => [ + // { + // header: 'Name', + // accessorKey: 'name', + // }, + // { + // header: 'M3U', + // accessorFn: (row) => + // playlists.find((playlist) => playlist.id === row.m3u_account)?.name, + // }, + // ], + // [] + // ), + // enableBottomToolbar: false, + // enableTopToolbar: false, + // columnFilterDisplayMode: 'popover', + // enablePagination: false, + // enableRowVirtualization: true, + // rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer + // initialState: { + // density: 'compact', + // }, + // enableRowActions: true, + // renderRowActions: ({ row }) => ( + // <> + // addStream(row.original)} + // > + // {/* Small icon size */} + // + // + // ), + // positionActionsColumn: 'last', + // mantineTableContainerProps: { + // style: { + // height: '200px', + // }, + // }, + // }); + + if (!isOpen) { + return <>; + } + + return ( + <> + +
+ + + + + + ({ + value: `${option.id}`, + label: option.name, + }))} + /> +
+ setChannelGroupModalOpen(true)} + title="Create new group" + size="small" + variant="filled" + > + + +
+
+ + ({ + value: `${option.id}`, + label: option.profile_name, + }))} + /> + + +
+ + + + + + + + + + Logo + {/* Display selected image */} + + Selected + + handleLogoChange(event)} + style={{ display: 'none' }} + /> + + + +
+ + {/* + + Active Streams + + + + + Available Streams + + + */} + + + + +
+
+ setChannelGroupModalOpen(false)} + /> + + ); +}; + +export default Channel; diff --git a/frontend/src/components/forms/ChannelGroup.js b/frontend/src/components/forms/ChannelGroup.js deleted file mode 100644 index dd4f2ee4..00000000 --- a/frontend/src/components/forms/ChannelGroup.js +++ /dev/null @@ -1,94 +0,0 @@ -// Modal.js -import React, { useEffect } from "react"; -import { - TextField, - Button, - CircularProgress, - Dialog, - DialogTitle, - DialogContent, - DialogActions, -} from "@mui/material"; -import { useFormik } from "formik"; -import * as Yup from "yup"; -import API from "../../api"; - -const ChannelGroup = ({ channelGroup = null, isOpen, onClose }) => { - const formik = useFormik({ - initialValues: { - name: "", - }, - validationSchema: Yup.object({ - name: Yup.string().required("Name is required"), - }), - onSubmit: async (values, { setSubmitting, resetForm }) => { - if (channelGroup?.id) { - await API.updateChannelGroup({ id: channelGroup.id, ...values }); - } else { - await API.addChannelGroup(values); - } - - resetForm(); - setSubmitting(false); - onClose(); - }, - }); - - useEffect(() => { - if (channelGroup) { - formik.setValues({ - name: channelGroup.name, - }); - } else { - formik.resetForm(); - } - }, [channelGroup]); - - if (!isOpen) { - return <>; - } - - return ( - - - Channel Group - - -
- - - - - {/* Submit button */} - - -
-
- ); -}; - -export default ChannelGroup; diff --git a/vite/src/components/forms/ChannelGroup.jsx b/frontend/src/components/forms/ChannelGroup.jsx similarity index 96% rename from vite/src/components/forms/ChannelGroup.jsx rename to frontend/src/components/forms/ChannelGroup.jsx index 407695df..93741ef1 100644 --- a/vite/src/components/forms/ChannelGroup.jsx +++ b/frontend/src/components/forms/ChannelGroup.jsx @@ -3,7 +3,7 @@ import React, { useEffect } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import API from '../../api'; -import { Flex, TextInput, Button } from '@mantine/core'; +import { Flex, TextInput, Button, Modal } from '@mantine/core'; const ChannelGroup = ({ channelGroup = null, isOpen, onClose }) => { const formik = useFormik({ diff --git a/frontend/src/components/forms/EPG.js b/frontend/src/components/forms/EPG.js deleted file mode 100644 index 232a2791..00000000 --- a/frontend/src/components/forms/EPG.js +++ /dev/null @@ -1,175 +0,0 @@ -// Modal.js -import React, { useState, useEffect } from 'react'; -import { - TextField, - Button, - Select, - MenuItem, - InputLabel, - FormControl, - CircularProgress, - Dialog, - DialogTitle, - DialogContent, - DialogActions, -} from '@mui/material'; -import { useFormik } from 'formik'; -import * as Yup from 'yup'; -import API from '../../api'; -import useEPGsStore from '../../store/epgs'; - -const EPG = ({ epg = null, isOpen, onClose }) => { - const epgs = useEPGsStore((state) => state.epgs); - const [file, setFile] = useState(null); - - const handleFileChange = (e) => { - const file = e.target.files[0]; - if (file) { - setFile(file); - } - }; - - const formik = useFormik({ - initialValues: { - name: '', - source_type: '', - url: '', - api_key: '', - is_active: true, - }, - validationSchema: Yup.object({ - name: Yup.string().required('Name is required'), - source_type: Yup.string().required('Source type is required'), - }), - onSubmit: async (values, { setSubmitting, resetForm }) => { - if (epg?.id) { - await API.updateEPG({ id: epg.id, ...values, epg_file: file }); - } else { - await API.addEPG({ - ...values, - epg_file: file, - }); - } - - resetForm(); - setFile(null); - setSubmitting(false); - onClose(); - }, - }); - - useEffect(() => { - if (epg) { - formik.setValues({ - name: epg.name, - source_type: epg.source_type, - url: epg.url, - api_key: epg.api_key, - is_active: epg.is_active, - }); - } else { - formik.resetForm(); - } - }, [epg]); - - if (!isOpen) { - return <>; - } - - return ( - - - EPG Source - -
- - - - - - - - - Source Type - - - - - - {/* Submit button */} - - -
-
- ); -}; - -export default EPG; diff --git a/vite/src/components/forms/EPG.jsx b/frontend/src/components/forms/EPG.jsx similarity index 95% rename from vite/src/components/forms/EPG.jsx rename to frontend/src/components/forms/EPG.jsx index 93b0ca5f..4fd46bcb 100644 --- a/vite/src/components/forms/EPG.jsx +++ b/frontend/src/components/forms/EPG.jsx @@ -116,11 +116,11 @@ const EPG = ({ epg = null, isOpen, onClose }) => { data={[ { label: 'XMLTV', - valeu: 'xmltv', + value: 'xmltv', }, { label: 'Schedules Direct', - valeu: 'schedules_direct', + value: 'schedules_direct', }, ]} /> @@ -132,7 +132,7 @@ const EPG = ({ epg = null, isOpen, onClose }) => { disabled={formik.isSubmitting} size="small" > - {formik.isSubmitting ? : 'Submit'} + Submit diff --git a/frontend/src/components/forms/LoginForm.js b/frontend/src/components/forms/LoginForm.js deleted file mode 100644 index f7b4445e..00000000 --- a/frontend/src/components/forms/LoginForm.js +++ /dev/null @@ -1,111 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import useAuthStore from '../../store/auth'; -import { - Box, - TextField, - Button, - Typography, - Grid2, - Paper, -} from '@mui/material'; - -const LoginForm = () => { - const { login, isAuthenticated, initData } = useAuthStore(); // Get login function from AuthContext - const navigate = useNavigate(); // Hook to navigate to other routes - const [formData, setFormData] = useState({ username: '', password: '' }); - - useEffect(() => { - if (isAuthenticated) { - navigate('/channels'); - } - }, [isAuthenticated, navigate]); - - const handleInputChange = (e) => { - setFormData({ - ...formData, - [e.target.name]: e.target.value, - }); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - await login(formData); - initData(); - navigate('/channels'); // Or any other route you'd like - }; - - // // Handle form submission - // const handleSubmit = async (e) => { - // e.preventDefault(); - // setLoading(true); - // setError(''); // Reset error on each new submission - - // await login(username, password) - // navigate('/channels'); // Or any other route you'd like - // }; - - return ( - - - - Login - -
- - - - - - - - - - - -
-
-
- ); -}; - -export default LoginForm; diff --git a/vite/src/components/forms/LoginForm.jsx b/frontend/src/components/forms/LoginForm.jsx similarity index 100% rename from vite/src/components/forms/LoginForm.jsx rename to frontend/src/components/forms/LoginForm.jsx diff --git a/frontend/src/components/forms/M3U.js b/frontend/src/components/forms/M3U.js deleted file mode 100644 index 37f8bf27..00000000 --- a/frontend/src/components/forms/M3U.js +++ /dev/null @@ -1,246 +0,0 @@ -// Modal.js -import React, { useState, useEffect } from 'react'; -import { - Box, - Typography, - Stack, - TextField, - Button, - Select, - MenuItem, - InputLabel, - FormControl, - CircularProgress, - FormControlLabel, - Checkbox, - Dialog, - DialogTitle, - DialogContent, - DialogActions, -} from '@mui/material'; -import { useFormik } from 'formik'; -import * as Yup from 'yup'; -import API from '../../api'; -import useUserAgentsStore from '../../store/userAgents'; -import M3UProfiles from './M3UProfiles'; - -const M3U = ({ playlist = null, isOpen, onClose }) => { - const userAgents = useUserAgentsStore((state) => state.userAgents); - const [file, setFile] = useState(null); - const [profileModalOpen, setProfileModalOpen] = useState(false); - - const handleFileChange = (e) => { - const file = e.target.files[0]; - if (file) { - setFile(file); - } - }; - - const formik = useFormik({ - initialValues: { - name: '', - server_url: '', - user_agent: '', - is_active: true, - max_streams: 0, - }, - validationSchema: Yup.object({ - name: Yup.string().required('Name is required'), - server_url: Yup.string().required('Server URL is required'), - user_agent: Yup.string().required('User-Agent is required'), - max_streams: Yup.string().required('Max streams is required'), - }), - onSubmit: async (values, { setSubmitting, resetForm }) => { - if (playlist?.id) { - await API.updatePlaylist({ - id: playlist.id, - ...values, - uploaded_file: file, - }); - } else { - await API.addPlaylist({ - ...values, - uploaded_file: file, - }); - } - - resetForm(); - setFile(null); - setSubmitting(false); - onClose(); - }, - }); - - useEffect(() => { - if (playlist) { - formik.setValues({ - name: playlist.name, - server_url: playlist.server_url, - max_streams: playlist.max_streams, - user_agent: playlist.user_agent, - is_active: playlist.is_active, - }); - } else { - formik.resetForm(); - } - }, [playlist]); - - if (!isOpen) { - return <>; - } - - return ( - - - M3U Account - - -
- - - - - - - - File - - handleFileChange(event)} - style={{ display: 'none' }} - /> - - - - - - - - User-Agent - - - - - formik.setFieldValue('is_active', e.target.checked) - } - /> - } - label="Is Active" - /> - - - - - - - - - {playlist && ( - setProfileModalOpen(false)} - /> - )} - -
- ); -}; - -export default M3U; diff --git a/vite/src/components/forms/M3U.jsx b/frontend/src/components/forms/M3U.jsx similarity index 91% rename from vite/src/components/forms/M3U.jsx rename to frontend/src/components/forms/M3U.jsx index 1567bf2a..65c43f98 100644 --- a/vite/src/components/forms/M3U.jsx +++ b/frontend/src/components/forms/M3U.jsx @@ -23,6 +23,8 @@ const M3U = ({ playlist = null, isOpen, onClose }) => { const [file, setFile] = useState(null); const [profileModalOpen, setProfileModalOpen] = useState(false); + console.log(playlist); + const handleFileChange = (file) => { console.log(file); if (file) { @@ -34,7 +36,7 @@ const M3U = ({ playlist = null, isOpen, onClose }) => { initialValues: { name: '', server_url: '', - user_agent: '', + user_agent: `${userAgents[0].id}`, is_active: true, max_streams: 0, }, @@ -82,8 +84,6 @@ const M3U = ({ playlist = null, isOpen, onClose }) => { return <>; } - console.log(formik.values); - return (
@@ -126,7 +126,8 @@ const M3U = ({ playlist = null, isOpen, onClose }) => { fullWidth id="max_streams" name="max_streams" - label="Max Streams (0 = unlimited)" + label="Max Streams" + placeholder="0 = Unlimited" value={formik.values.max_streams} onChange={formik.handleChange} error={formik.errors.max_streams ? formik.touched.max_streams : ''} @@ -136,7 +137,7 @@ const M3U = ({ playlist = null, isOpen, onClose }) => { id="user_agent" name="user_agent" label="User-Agent" - value={formik.values.user_agent.value} + value={formik.values.user_agent} onChange={formik.handleChange} error={formik.errors.user_agent ? formik.touched.user_agent : ''} data={userAgents.map((ua) => ({ @@ -157,15 +158,16 @@ const M3U = ({ playlist = null, isOpen, onClose }) => { /> - - + {playlist && ( + + )} - - - - - - Search - - - - - - - Replace - {resultUrl} - - - - ); -}; - -export default RegexFormAndView; diff --git a/vite/src/components/forms/M3UProfile.jsx b/frontend/src/components/forms/M3UProfile.jsx similarity index 100% rename from vite/src/components/forms/M3UProfile.jsx rename to frontend/src/components/forms/M3UProfile.jsx diff --git a/frontend/src/components/forms/M3UProfiles.js b/frontend/src/components/forms/M3UProfiles.js deleted file mode 100644 index 24694811..00000000 --- a/frontend/src/components/forms/M3UProfiles.js +++ /dev/null @@ -1,133 +0,0 @@ -import React, { useState, useMemo } from 'react'; -import { - Typography, - Dialog, - DialogContent, - DialogTitle, - DialogActions, - Button, - Box, - Switch, - IconButton, - List, - ListItem, - ListItemText, -} from '@mui/material'; -import API from '../../api'; -import M3UProfile from './M3UProfile'; -import { Delete as DeleteIcon, Edit as EditIcon } from '@mui/icons-material'; -import usePlaylistsStore from '../../store/playlists'; - -const M3UProfiles = ({ playlist = null, isOpen, onClose }) => { - const profiles = usePlaylistsStore((state) => state.profiles[playlist.id]); - const [profileEditorOpen, setProfileEditorOpen] = useState(false); - const [profile, setProfile] = useState(null); - - const editProfile = (profile = null) => { - if (profile) { - setProfile(profile); - } - - setProfileEditorOpen(true); - }; - - const deleteProfile = async (id) => { - await API.deleteM3UProfile(playlist.id, id); - }; - - const toggleActive = async (values) => { - await API.updateM3UProfile(playlist.id, { - ...values, - is_active: !values.is_active, - }); - }; - - const closeEditor = () => { - setProfile(null); - setProfileEditorOpen(false); - }; - - if (!isOpen || !profiles) { - return <>; - } - - return ( - <> - - - Profiles - - - - {profiles - .filter((playlist) => playlist.is_default == false) - .map((item) => ( - - - - Max Streams: {item.max_streams} - - toggleActive(item)} - color="primary" - inputProps={{ 'aria-label': 'active switch' }} - /> - editProfile(item)} - color="warning" - > - - - deleteProfile(item.id)} - color="error" - > - - - - } - /> - - ))} - - - - - - - - - - - ); -}; - -export default M3UProfiles; diff --git a/vite/src/components/forms/M3UProfiles.jsx b/frontend/src/components/forms/M3UProfiles.jsx similarity index 100% rename from vite/src/components/forms/M3UProfiles.jsx rename to frontend/src/components/forms/M3UProfiles.jsx diff --git a/frontend/src/components/forms/Stream.js b/frontend/src/components/forms/Stream.js deleted file mode 100644 index 714876fa..00000000 --- a/frontend/src/components/forms/Stream.js +++ /dev/null @@ -1,151 +0,0 @@ -// Modal.js -import React, { useEffect } from 'react'; -import { - TextField, - Button, - Select, - MenuItem, - Grid2, - InputLabel, - FormControl, - CircularProgress, - Dialog, - DialogTitle, - DialogContent, - DialogActions, -} from '@mui/material'; -import { useFormik } from 'formik'; -import * as Yup from 'yup'; -import API from '../../api'; -import useStreamProfilesStore from '../../store/streamProfiles'; - -const Stream = ({ stream = null, isOpen, onClose }) => { - const streamProfiles = useStreamProfilesStore((state) => state.profiles); - - const formik = useFormik({ - initialValues: { - name: '', - url: '', - stream_profile_id: '', - }, - validationSchema: Yup.object({ - name: Yup.string().required('Name is required'), - url: Yup.string().required('URL is required').min(0), - // stream_profile_id: Yup.string().required('Stream profile is required'), - }), - onSubmit: async (values, { setSubmitting, resetForm }) => { - if (stream?.id) { - await API.updateStream({ id: stream.id, ...values }); - } else { - await API.addStream(values); - } - - resetForm(); - setSubmitting(false); - onClose(); - }, - }); - - useEffect(() => { - if (stream) { - formik.setValues({ - name: stream.name, - url: stream.url, - stream_profile_id: stream.stream_profile_id, - }); - } else { - formik.resetForm(); - } - }, [stream]); - - if (!isOpen) { - return <>; - } - - return ( - - - Stream - - -
- - - - - - - - - - Stream Profile - - - - - - - - - - -
-
- ); -}; - -export default Stream; diff --git a/vite/src/components/forms/Stream.jsx b/frontend/src/components/forms/Stream.jsx similarity index 61% rename from vite/src/components/forms/Stream.jsx rename to frontend/src/components/forms/Stream.jsx index 456797ec..a01cbae8 100644 --- a/vite/src/components/forms/Stream.jsx +++ b/frontend/src/components/forms/Stream.jsx @@ -1,18 +1,21 @@ // Modal.js -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import API from '../../api'; import useStreamProfilesStore from '../../store/streamProfiles'; -import { TextInput, Select, Button } from '@mantine/core'; +import { Modal, TextInput, Select, Button, Flex } from '@mantine/core'; const Stream = ({ stream = null, isOpen, onClose }) => { const streamProfiles = useStreamProfilesStore((state) => state.profiles); + const [selectedStreamProfile, setSelectedStreamProfile] = useState(''); + console.log(stream); const formik = useFormik({ initialValues: { name: '', url: '', + group_name: '', stream_profile_id: '', }, validationSchema: Yup.object({ @@ -38,6 +41,7 @@ const Stream = ({ stream = null, isOpen, onClose }) => { formik.setValues({ name: stream.name, url: stream.url, + group_name: stream.group_name, stream_profile_id: stream.stream_profile_id, }); } else { @@ -50,7 +54,7 @@ const Stream = ({ stream = null, isOpen, onClose }) => { } return ( - +
{ label="Stream Name" value={formik.values.name} onChange={formik.handleChange} - error={formik.touched.name && Boolean(formik.errors.name)} + error={formik.errors.name} /> { label="Stream URL" value={formik.values.url} onChange={formik.handleChange} - error={formik.touched.url && Boolean(formik.errors.url)} + error={formik.errors.url} + /> + + - {userAgents.map((option, index) => ( - - {option.user_agent_name} - - ))} - - - - - - - - - - ); -}; - -export default StreamProfile; diff --git a/vite/src/components/forms/StreamProfile.jsx b/frontend/src/components/forms/StreamProfile.jsx similarity index 100% rename from vite/src/components/forms/StreamProfile.jsx rename to frontend/src/components/forms/StreamProfile.jsx diff --git a/frontend/src/components/forms/SuperuserForm.js b/frontend/src/components/forms/SuperuserForm.js deleted file mode 100644 index 32eb5e14..00000000 --- a/frontend/src/components/forms/SuperuserForm.js +++ /dev/null @@ -1,128 +0,0 @@ -// frontend/src/components/forms/SuperuserForm.js -import React, { useState } from 'react'; -import axios from 'axios'; -import { - Box, - Paper, - Typography, - Grid2, - TextField, - Button, -} from '@mui/material'; - -function SuperuserForm({ onSuccess }) { - const [formData, setFormData] = useState({ - username: '', - password: '', - email: '', - }); - const [error, setError] = useState(''); - - const handleChange = (e) => { - setFormData((prev) => ({ - ...prev, - [e.target.name]: e.target.value, - })); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - try { - const res = await axios.post('/api/accounts/initialize-superuser/', { - username: formData.username, - password: formData.password, - email: formData.email, - }); - if (res.data.superuser_exists) { - onSuccess(); - } - } catch (err) { - let msg = 'Failed to create superuser.'; - if (err.response && err.response.data && err.response.data.error) { - msg += ` ${err.response.data.error}`; - } - setError(msg); - } - }; - - return ( - - - - Create your Super User Account - - {error && ( - - {error} - - )} -
- - - - - - - - - - - - - - -
-
-
- ); -} - -export default SuperuserForm; diff --git a/frontend/src/components/forms/SuperuserForm.jsx b/frontend/src/components/forms/SuperuserForm.jsx new file mode 100644 index 00000000..66aa5ced --- /dev/null +++ b/frontend/src/components/forms/SuperuserForm.jsx @@ -0,0 +1,94 @@ +// frontend/src/components/forms/SuperuserForm.js +import React, { useState } from 'react'; +import { TextInput, Center, Button, Paper, Title, Stack } from '@mantine/core'; +import API from '../../api'; +import useAuthStore from '../../store/auth'; + +function SuperuserForm({}) { + const [formData, setFormData] = useState({ + username: '', + password: '', + email: '', + }); + const [error, setError] = useState(''); + const { setSuperuserExists } = useAuthStore(); + + const handleChange = (e) => { + setFormData((prev) => ({ + ...prev, + [e.target.name]: e.target.value, + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + console.log(formData); + const response = await API.createSuperUser({ + username: formData.username, + password: formData.password, + email: formData.email, + }); + if (response.superuser_exists) { + setSuperuserExists(true); + } + } catch (err) { + console.log(err); + // let msg = 'Failed to create superuser.'; + // if (err.response && err.response.data && err.response.data.error) { + // msg += ` ${err.response.data.error}`; + // } + // setError(msg); + } + }; + + return ( +
+ + + Create your Super User Account + +
+ + + + + + + + +
+
+
+ ); +} + +export default SuperuserForm; diff --git a/frontend/src/components/forms/UserAgent.js b/frontend/src/components/forms/UserAgent.js deleted file mode 100644 index db13429f..00000000 --- a/frontend/src/components/forms/UserAgent.js +++ /dev/null @@ -1,144 +0,0 @@ -// Modal.js -import React, { useEffect } from 'react'; -import { - TextField, - Button, - CircularProgress, - Checkbox, - Dialog, - DialogTitle, - DialogContent, - DialogActions, -} from '@mui/material'; -import { useFormik } from 'formik'; -import * as Yup from 'yup'; -import API from '../../api'; -import useSettingsStore from '../../store/settings'; - -const UserAgent = ({ userAgent = null, isOpen, onClose }) => { - const formik = useFormik({ - initialValues: { - user_agent_name: '', - user_agent: '', - description: '', - is_active: true, - }, - validationSchema: Yup.object({ - user_agent_name: Yup.string().required('Name is required'), - user_agent: Yup.string().required('User-Agent is required'), - }), - onSubmit: async (values, { setSubmitting, resetForm }) => { - if (userAgent?.id) { - await API.updateUserAgent({ id: userAgent.id, ...values }); - } else { - await API.addUserAgent(values); - } - - resetForm(); - setSubmitting(false); - onClose(); - }, - }); - - useEffect(() => { - if (userAgent) { - formik.setValues({ - user_agent_name: userAgent.user_agent_name, - user_agent: userAgent.user_agent, - description: userAgent.description, - is_active: userAgent.is_active, - }); - } else { - formik.resetForm(); - } - }, [userAgent]); - - if (!isOpen) { - return <>; - } - - return ( - - - User-Agent - - -
- - - - - - - - - - - - - -
-
- ); -}; - -export default UserAgent; diff --git a/vite/src/components/forms/UserAgent.jsx b/frontend/src/components/forms/UserAgent.jsx similarity index 100% rename from vite/src/components/forms/UserAgent.jsx rename to frontend/src/components/forms/UserAgent.jsx diff --git a/frontend/src/components/sidebar.css b/frontend/src/components/sidebar.css new file mode 100644 index 00000000..fa3756be --- /dev/null +++ b/frontend/src/components/sidebar.css @@ -0,0 +1,41 @@ +.mantine-Stack-root .navlink { + display: flex; + flex-direction: row; /* Ensures horizontal layout */ + flex-wrap: nowrap; + align-items: center; + gap: 12px; + padding: 5px 8px !important; + border-radius: 6px; + color: #D4D4D8; /* Default color when not active */ + background-color: transparent; /* Default background when not active */ + border: 1px solid transparent; + transition: all 0.3s ease; + } + + /* Active state styles */ + .navlink.navlink-active { + color: #FFFFFF; + background-color: #245043; + border: 1px solid #3BA882; + } + + /* Hover effect */ + .navlink:hover { + background-color: #2A2F34; /* Gray hover effect when not active */ + border: 1px solid #3D3D42; + } + + /* Hover effect for active state */ + .navlink.navlink-active:hover { + background-color: #3A3A40; + border: 1px solid #3BA882; + } + + /* Collapse condition for justifyContent */ + .navlink.navlink-collapsed { + justify-content: center; + } + + .navlink:not(.navlink-collapsed) { + justify-content: flex-start; + } diff --git a/frontend/src/components/tables/ChannelsTable.js b/frontend/src/components/tables/ChannelsTable.js deleted file mode 100644 index 255c97f7..00000000 --- a/frontend/src/components/tables/ChannelsTable.js +++ /dev/null @@ -1,1149 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from 'react'; -import { - MaterialReactTable, - useMaterialReactTable, -} from 'material-react-table'; -import { - Box, - Grid2, - Stack, - Typography, - Tooltip, - IconButton, - Button, - ButtonGroup, - Snackbar, - Popover, - TextField, - Autocomplete, - InputAdornment, - Paper, -} from '@mui/material'; -import useChannelsStore from '../../store/channels'; -import { - Delete as DeleteIcon, - Edit as EditIcon, - Add as AddIcon, - SwapVert as SwapVertIcon, - LiveTv as LiveTvIcon, - ContentCopy, - Tv as TvIcon, - Clear as ClearIcon, - IndeterminateCheckBox, - CompareArrows, - Code, - AddBox, - Hd as HdIcon, -} from '@mui/icons-material'; -import API from '../../api'; -import ChannelForm from '../forms/Channel'; -import { TableHelper } from '../../helpers'; -import utils from '../../utils'; -import logo from '../../images/logo.png'; -import useVideoStore from '../../store/useVideoStore'; -import useSettingsStore from '../../store/settings'; -import usePlaylistsStore from '../../store/playlists'; -import { Tv2, ScreenShare, Scroll, SquareMinus, Pencil } from 'lucide-react'; -import { styled, useTheme } from '@mui/material/styles'; -import ghostImage from '../../images/ghost.svg'; - -const ChannelStreams = ({ channel, isExpanded }) => { - const channelStreams = useChannelsStore( - (state) => state.channels[channel.id]?.streams - ); - const { playlists } = usePlaylistsStore(); - - const removeStream = async (stream) => { - const newStreamList = channelStreams.filter((s) => s.id !== stream.id); - await API.updateChannel({ - ...channel, - stream_ids: newStreamList.map((s) => s.id), - }); - }; - - const channelStreamsTable = useMaterialReactTable({ - ...TableHelper.defaultProperties, - data: channelStreams, - columns: useMemo( - () => [ - { - header: 'Name', - accessorKey: 'name', - }, - { - header: 'M3U', - accessorFn: (row) => - playlists.find((playlist) => playlist.id === row.m3u_account)?.name, - }, - ], - [playlists] - ), - enableKeyboardShortcuts: false, - enableColumnFilters: false, - enableSorting: false, - enableBottomToolbar: false, - enableTopToolbar: false, - columnFilterDisplayMode: 'popover', - enablePagination: false, - enableRowVirtualization: true, - enableColumnHeaders: false, - rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer - initialState: { - density: 'compact', - }, - enableRowActions: true, - enableRowOrdering: true, - muiRowDragHandleProps: ({ table }) => ({ - onDragEnd: async () => { - const { draggingRow, hoveredRow } = table.getState(); - - if (hoveredRow && draggingRow) { - channelStreams.splice( - hoveredRow.index, - 0, - channelStreams.splice(draggingRow.index, 1)[0] - ); - - const { streams: oldStreams, ...channelUpdate } = channel; - - API.updateChannel({ - ...channelUpdate, - stream_ids: channelStreams.map((stream) => stream.id), - }); - } - }, - }), - renderRowActions: ({ row }) => ( - <> - removeStream(row.original)} - > - - - - ), - }); - - if (!isExpanded) { - return <>; - } - - return ( - - - - ); -}; - -/* ----------------------------------------------------------- - 2) Custom-styled "chip" buttons for HDHR, M3U, EPG ------------------------------------------------------------- */ -const HDHRButton = styled(Button)(() => ({ - border: '1px solid #a3d977', - color: '#a3d977', - backgroundColor: 'transparent', - textTransform: 'none', - fontSize: '0.85rem', - display: 'flex', - alignItems: 'center', - gap: '4px', - padding: '2px 8px', - minWidth: 'auto', - '&:hover': { - borderColor: '#c2e583', - color: '#c2e583', - backgroundColor: 'rgba(163,217,119,0.1)', - }, -})); - -const M3UButton = styled(Button)(() => ({ - border: '1px solid #5f6dc6', - color: '#5f6dc6', - backgroundColor: 'transparent', - textTransform: 'none', - fontSize: '0.85rem', - display: 'flex', - alignItems: 'center', - gap: '4px', - padding: '2px 8px', - minWidth: 'auto', - '&:hover': { - borderColor: '#7f8de6', - color: '#7f8de6', - backgroundColor: 'rgba(95,109,198,0.1)', - }, -})); - -const EPGButton = styled(Button)(() => ({ - border: '1px solid #707070', - color: '#a0a0a0', - backgroundColor: 'transparent', - textTransform: 'none', - fontSize: '0.85rem', - display: 'flex', - alignItems: 'center', - gap: '4px', - padding: '2px 8px', - minWidth: 'auto', - '&:hover': { - borderColor: '#a0a0a0', - color: '#c0c0c0', - backgroundColor: 'rgba(112,112,112,0.1)', - }, -})); - -const ChannelsTable = ({}) => { - const [channel, setChannel] = useState(null); - const [channelModalOpen, setChannelModalOpen] = useState(false); - const [rowSelection, setRowSelection] = useState([]); - const [channelGroupOptions, setChannelGroupOptions] = useState([]); - - const [anchorEl, setAnchorEl] = useState(null); - const [textToCopy, setTextToCopy] = useState(''); - const [snackbarMessage, setSnackbarMessage] = useState(''); - const [snackbarOpen, setSnackbarOpen] = useState(false); - - const [filterValues, setFilterValues] = useState({}); - - const theme = useTheme(); - - const { showVideo } = useVideoStore(); - const { - channels, - isLoading: channelsLoading, - fetchChannels, - setChannelsPageSelection, - } = useChannelsStore(); - - useEffect(() => { - setChannelGroupOptions([ - ...new Set( - Object.values(channels).map((channel) => channel.channel_group?.name) - ), - ]); - }, [channels]); - - const handleFilterChange = (columnId, value) => { - console.log(columnId); - console.log(value); - setFilterValues((prev) => ({ - ...prev, - [columnId]: value ? value.toLowerCase() : '', - })); - }; - - const outputUrlRef = useRef(null); - - const { - environment: { env_mode }, - } = useSettingsStore(); - - // Configure columns - const columns = useMemo( - () => [ - { - header: '#', - size: 50, - accessorKey: 'channel_number', - }, - { - header: 'Name', - accessorKey: 'channel_name', - muiTableHeadCellProps: { - sx: { textAlign: 'center' }, - }, - Header: ({ column }) => ( - handleFilterChange(column.id, e.target.value)} - size="small" - margin="none" - fullWidth - sx={ - { - // '& .MuiInputBase-root': { fontSize: '0.875rem' }, - // '& .MuiInputLabel-root': { fontSize: '0.75rem' }, - // width: '200px', // Optional: Adjust width - } - } - slotProps={{ - input: { - endAdornment: ( - - handleFilterChange(column.id, '')} - edge="end" - size="small" - > - - - - ), - }, - }} - /> - ), - meta: { - filterVariant: null, - }, - }, - { - header: 'Group', - accessorFn: (row) => row.channel_group?.name || '', - Header: ({ column }) => ( - { - event.stopPropagation(); - handleFilterChange(column.id, newValue); - }} - renderInput={(params) => ( - e.stopPropagation()} - sx={{ - pb: 0.8, - // '& .MuiInputBase-root': { fontSize: '0.875rem' }, - // '& .MuiInputLabel-root': { fontSize: '0.75rem' }, - // width: '200px', // Optional: Adjust width - }} - /> - )} - /> - ), - }, - { - header: 'Logo', - accessorKey: 'logo_url', - enableSorting: false, - size: 55, - Cell: ({ cell }) => ( - - channel logo - - ), - meta: { - filterVariant: null, - }, - }, - ], - [channelGroupOptions, filterValues] - ); - - // Access the row virtualizer instance (optional) - const rowVirtualizerInstanceRef = useRef(null); - - const [isLoading, setIsLoading] = useState(true); - const [sorting, setSorting] = useState([]); - - const closeSnackbar = () => setSnackbarOpen(false); - - const editChannel = async (ch = null) => { - setChannel(ch); - setChannelModalOpen(true); - }; - - const deleteChannel = async (id) => { - await API.deleteChannel(id); - }; - - function handleWatchStream(channelNumber) { - let vidUrl = `/output/stream/${channelNumber}/`; - if (env_mode == 'dev') { - vidUrl = `${window.location.protocol}//${window.location.hostname}:5656${vidUrl}`; - } - showVideo(vidUrl); - } - - // (Optional) bulk delete, but your endpoint is @TODO - const deleteChannels = async () => { - setIsLoading(true); - const selected = table - .getRowModel() - .rows.filter((row) => row.getIsSelected()); - await utils.Limiter( - 4, - selected.map((chan) => () => deleteChannel(chan.original.id)) - ); - // If you have a real bulk-delete endpoint, call it here: - // await API.deleteChannels(selected.map((sel) => sel.id)); - setIsLoading(false); - }; - - // ───────────────────────────────────────────────────────── - // The "Assign Channels" button logic - // ───────────────────────────────────────────────────────── - const assignChannels = async () => { - try { - // Get row order from the table - const rowOrder = table.getRowModel().rows.map((row) => row.original.id); - - // Call our custom API endpoint - setIsLoading(true); - const result = await API.assignChannelNumbers(rowOrder); - setIsLoading(false); - - // We might get { message: "Channels have been auto-assigned!" } - setSnackbarMessage(result.message || 'Channels assigned'); - setSnackbarOpen(true); - - // Refresh the channel list - await fetchChannels(); - } catch (err) { - console.error(err); - setSnackbarMessage('Failed to assign channels'); - setSnackbarOpen(true); - } - }; - - // ───────────────────────────────────────────────────────── - // The new "Match EPG" button logic - // ───────────────────────────────────────────────────────── - const matchEpg = async () => { - try { - // Hit our new endpoint that triggers the fuzzy matching Celery task - const resp = await fetch('/api/channels/channels/match-epg/', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${await API.getAuthToken()}`, - }, - }); - - if (resp.ok) { - setSnackbarMessage('EPG matching task started!'); - } else { - const text = await resp.text(); - setSnackbarMessage(`Failed to start EPG matching: ${text}`); - } - } catch (err) { - setSnackbarMessage(`Error: ${err.message}`); - } - setSnackbarOpen(true); - }; - - const closeChannelForm = () => { - setChannel(null); - setChannelModalOpen(false); - }; - - useEffect(() => { - if (typeof window !== 'undefined') { - setIsLoading(false); - } - }, []); - - useEffect(() => { - // Scroll to the top of the table when sorting changes - try { - rowVirtualizerInstanceRef.current?.scrollToIndex?.(0); - } catch (error) { - console.error(error); - } - }, [sorting]); - - const closePopover = () => { - setAnchorEl(null); - setSnackbarMessage(''); - }; - const openPopover = Boolean(anchorEl); - - const handleCopy = async () => { - try { - await navigator.clipboard.writeText(textToCopy); - setSnackbarMessage('Copied!'); - } catch (err) { - const inputElement = outputUrlRef.current.querySelector('input'); // Get the actual input - - if (inputElement) { - inputElement.focus(); - inputElement.select(); - - // For older browsers - document.execCommand('copy'); - setSnackbarMessage('Copied!'); - } - } - setSnackbarOpen(true); - }; - - // Example copy URLs - const copyM3UUrl = (event) => { - setAnchorEl(event.currentTarget); - setTextToCopy( - `${window.location.protocol}//${window.location.host}/output/m3u` - ); - }; - const copyEPGUrl = (event) => { - setAnchorEl(event.currentTarget); - setTextToCopy( - `${window.location.protocol}//${window.location.host}/output/epg` - ); - }; - const copyHDHRUrl = (event) => { - setAnchorEl(event.currentTarget); - setTextToCopy( - `${window.location.protocol}//${window.location.host}/output/hdhr` - ); - }; - - useEffect(() => { - const selectedRows = table - .getSelectedRowModel() - .rows.map((row) => row.original); - setChannelsPageSelection(selectedRows); - }, [rowSelection]); - - const filteredData = Object.values(channels).filter((row) => - columns.every(({ accessorKey }) => - filterValues[accessorKey] - ? row[accessorKey]?.toLowerCase().includes(filterValues[accessorKey]) - : true - ) - ); - - const table = useMaterialReactTable({ - ...TableHelper.defaultProperties, - columns, - data: filteredData, - enablePagination: false, - enableColumnActions: false, - enableRowVirtualization: true, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - state: { - isLoading: isLoading || channelsLoading, - sorting, - rowSelection, - }, - rowVirtualizerInstanceRef, - rowVirtualizerOptions: { overscan: 5 }, - initialState: { - density: 'compact', - }, - enableRowActions: true, - enableExpandAll: false, - displayColumnDefOptions: { - 'mrt-row-select': { - size: 50, - }, - 'mrt-row-expand': { - size: 10, - header: '', - muiTableHeadCellProps: { - sx: { width: 38, minWidth: 38, maxWidth: 38, height: '100%' }, - }, - muiTableBodyCellProps: { - sx: { width: 38, minWidth: 38, maxWidth: 38 }, - }, - }, - 'mrt-row-actions': { - size: 68, - }, - }, - muiExpandButtonProps: ({ row, table }) => ({ - onClick: () => { - setRowSelection({ [row.index]: true }); - table.setExpanded({ [row.id]: !row.getIsExpanded() }); - }, - sx: { - transform: row.getIsExpanded() ? 'rotate(180deg)' : 'rotate(-90deg)', - transition: 'transform 0.2s', - }, - }), - renderDetailPanel: ({ row }) => ( - - ), - renderRowActions: ({ row }) => ( - - - { - editChannel(row.original); - }} - sx={{ py: 0, px: 0.5 }} - > - - - - - - deleteChannel(row.original.id)} - sx={{ py: 0, px: 0.5 }} - > - - - - - - handleWatchStream(row.original.channel_number)} - sx={{ py: 0, px: 0.5 }} - > - - - - - ), - muiTableContainerProps: { - sx: { - height: 'calc(100vh - 75px)', - overflowY: 'auto', - }, - }, - muiSearchTextFieldProps: { - variant: 'standard', - }, - renderTopToolbarCustomActions: ({ table }) => { - const selectedRowCount = table.getSelectedRowModel().rows.length; - - return ( - - Channels - - editChannel()} - > - - - - - - - - - - - - - - - {/* Our brand-new button for EPG matching */} - - - - - - - - - - - - - ); - }, - }); - - return ( - - {/* Header Row: outside the Paper */} - - - Channels - - - - Links: - - - - {/* HDHR Button */} - - - {/* M3U Button */} - - - {/* EPG Button */} - - - - - {/* Paper container: contains top toolbar and table (or ghost state) */} - - {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */} - - - - - - - - - - - - - - - - - - {/* Table or ghost empty state inside Paper */} - - {filteredData.length === 0 ? ( - - - - - It’s recommended to create channels after adding your M3U or - streams. - - - You can still create channels without streams if you’d like, - and map them later. - - - - - ) : ( - - - - )} - - - - - - - - - - - - - - - - - ); -}; - -export default ChannelsTable; diff --git a/vite/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx similarity index 72% rename from vite/src/components/tables/ChannelsTable.jsx rename to frontend/src/components/tables/ChannelsTable.jsx index 9a89c36c..d0823107 100644 --- a/vite/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useRef, useState, useCallback } from 'react'; import { MantineReactTable, useMantineReactTable } from 'mantine-react-table'; import useChannelsStore from '../../store/channels'; -import useAlertStore from '../../store/alerts'; +import { notifications } from '@mantine/notifications'; import { Add as AddIcon, LiveTv as LiveTvIcon, @@ -47,6 +47,8 @@ import { Group, useMantineTheme, UnstyledButton, + Container, + Space, } from '@mantine/core'; import { IconArrowDown, @@ -232,7 +234,6 @@ const ChannelsTable = ({}) => { fetchChannels, setChannelsPageSelection, } = useChannelsStore(); - const { showAlert } = useAlertStore(); useEffect(() => { setChannelGroupOptions([ @@ -279,9 +280,17 @@ const ChannelsTable = ({}) => { size="xs" /> ), - meta: { - filterVariant: null, - }, + Cell: ({ cell }) => ( +
+ {cell.getValue()} +
+ ), }, { header: 'Group', @@ -292,10 +301,10 @@ const ChannelsTable = ({}) => { searchable size="xs" nothingFound="No options" - // onChange={(e, value) => { - // e.stopPropagation(); - // handleGroupChange(value); - // }} + onChange={(e, value) => { + e.stopPropagation(); + handleGroupChange(value); + }} data={channelGroupOptions} /> ), @@ -377,13 +386,19 @@ const ChannelsTable = ({}) => { setIsLoading(false); // We might get { message: "Channels have been auto-assigned!" } - showAlert(result.message || 'Channels assigned'); + notifications.show({ + title: result.message || 'Channels assigned', + color: 'green.5', + }); // Refresh the channel list await fetchChannels(); } catch (err) { console.error(err); - showAlert('Failed to assign channels'); + notifications.show({ + title: 'Failed to assign channels', + color: 'red.5', + }); } }; @@ -584,7 +599,7 @@ const ChannelsTable = ({}) => { ), mantineTableContainerProps: { style: { - height: 'calc(100vh - 125px)', + height: 'calc(100vh - 127px)', overflowY: 'auto', }, }, @@ -596,7 +611,10 @@ const ChannelsTable = ({}) => { return ( {/* Header Row: outside the Paper */} - + { > Channels - { > Links: - - - - - - - - - - - - - - - - - - - - - - - - - - - - + HDHR + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Paper container: contains top toolbar and table (or ghost state) */} {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */} @@ -836,7 +777,7 @@ const ChannelsTable = ({}) => { - + - - ) : ( - - - +
)} + {filteredData.length > 0 && ( + + + + )} { - const [epg, setEPG] = useState(null); - const [epgModalOpen, setEPGModalOpen] = useState(false); - const [rowSelection, setRowSelection] = useState([]); - const [snackbarMessage, setSnackbarMessage] = useState(''); - const [snackbarOpen, setSnackbarOpen] = useState(false); - - const epgs = useEPGsStore((state) => state.epgs); - - const columns = useMemo( - //column definitions... - () => [ - { - header: 'Name', - accessorKey: 'name', - }, - { - header: 'Source Type', - accessorKey: 'source_type', - }, - { - header: 'URL / API Key', - accessorKey: 'max_streams', - }, - ], - [] - ); - - //optionally access the underlying virtualizer instance - const rowVirtualizerInstanceRef = useRef(null); - - const [isLoading, setIsLoading] = useState(true); - const [sorting, setSorting] = useState([]); - - const closeSnackbar = () => { - setSnackbarOpen(false); - }; - - const editEPG = async (epg = null) => { - setEPG(epg); - setEPGModalOpen(true); - }; - - const deleteEPG = async (id) => { - setIsLoading(true); - await API.deleteEPG(id); - setIsLoading(false); - }; - - const refreshEPG = async (id) => { - await API.refreshEPG(id); - setSnackbarMessage('EPG refresh initiated'); - setSnackbarOpen(true); - }; - - const closeEPGForm = () => { - setEPG(null); - setEPGModalOpen(false); - }; - - useEffect(() => { - if (typeof window !== 'undefined') { - setIsLoading(false); - } - }, []); - - useEffect(() => { - //scroll to the top of the table when the sorting changes - try { - rowVirtualizerInstanceRef.current?.scrollToIndex?.(0); - } catch (error) { - console.error(error); - } - }, [sorting]); - - const table = useMaterialReactTable({ - ...TableHelper.defaultProperties, - columns, - data: epgs, - enablePagination: false, - enableRowVirtualization: true, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - state: { - isLoading, - sorting, - rowSelection, - }, - rowVirtualizerInstanceRef, //optional - rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer - initialState: { - density: 'compact', - }, - enableRowActions: true, - renderRowActions: ({ row }) => ( - <> - editEPG(row.original)} - sx={{ pt: 0, pb: 0 }} - > - {/* Small icon size */} - - deleteEPG(row.original.id)} - sx={{ pt: 0, pb: 0 }} - > - {/* Small icon size */} - - refreshEPG(row.original.id)} - sx={{ pt: 0, pb: 0 }} - > - {/* Small icon size */} - - - ), - muiTableContainerProps: { - sx: { - height: 'calc(43vh - 0px)', - }, - }, - renderTopToolbarCustomActions: ({ table }) => ( - - EPGs - - editEPG()} - > - {/* Small icon size */} - - - - ), - }); - - return ( - - - - - - - - ); -}; - -export default EPGsTable; diff --git a/vite/src/components/tables/EPGsTable.jsx b/frontend/src/components/tables/EPGsTable.jsx similarity index 64% rename from vite/src/components/tables/EPGsTable.jsx rename to frontend/src/components/tables/EPGsTable.jsx index c7d9290e..50fffbc9 100644 --- a/vite/src/components/tables/EPGsTable.jsx +++ b/frontend/src/components/tables/EPGsTable.jsx @@ -10,18 +10,28 @@ import { import useEPGsStore from '../../store/epgs'; import EPGForm from '../forms/EPG'; import { TableHelper } from '../../helpers'; -import { ActionIcon, Text, Tooltip, Box } from '@mantine/core'; -import useAlertStore from '../../store/alerts'; +import { + ActionIcon, + Text, + Tooltip, + Box, + Paper, + Button, + Flex, + useMantineTheme, +} from '@mantine/core'; +import { notifications } from '@mantine/notifications'; +import { IconSquarePlus } from '@tabler/icons-react'; const EPGsTable = () => { const [epg, setEPG] = useState(null); const [epgModalOpen, setEPGModalOpen] = useState(false); const [rowSelection, setRowSelection] = useState([]); - const { showAlert } = useAlertStore(); - const epgs = useEPGsStore((state) => state.epgs); + const theme = useMantineTheme(); + const columns = useMemo( //column definitions... () => [ @@ -60,7 +70,9 @@ const EPGsTable = () => { const refreshEPG = async (id) => { await API.refreshEPG(id); - showAlert('EPG refresh initiated'); + notifications.show({ + title: 'EPG refresh initiated', + }); }; const closeEPGForm = () => { @@ -89,7 +101,8 @@ const EPGsTable = () => { data: epgs, enablePagination: false, enableRowVirtualization: true, - enableRowSelection: true, + enableRowSelection: false, + renderTopToolbar: false, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, state: { @@ -136,29 +149,73 @@ const EPGsTable = () => { height: 'calc(40vh - 0px)', }, }, - renderTopToolbarCustomActions: ({ table }) => ( - <> - EPGs - - editEPG()} - > - {/* Small icon size */} - - - - ), }); return ( - + + + + EPGs + + + + + {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */} + + + + + + + + + diff --git a/frontend/src/components/tables/M3UsTable.js b/frontend/src/components/tables/M3UsTable.js deleted file mode 100644 index eb1ce5e6..00000000 --- a/frontend/src/components/tables/M3UsTable.js +++ /dev/null @@ -1,242 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from 'react'; -import { - MaterialReactTable, - useMaterialReactTable, -} from 'material-react-table'; -import { - Box, - Stack, - Typography, - IconButton, - Tooltip, - Select, - MenuItem, -} from '@mui/material'; -import API from '../../api'; -import { - Delete as DeleteIcon, - Edit as EditIcon, - Add as AddIcon, - SwapVert as SwapVertIcon, - Check as CheckIcon, - Close as CloseIcon, - Refresh as RefreshIcon, -} from '@mui/icons-material'; -import usePlaylistsStore from '../../store/playlists'; -import M3UForm from '../forms/M3U'; -import { TableHelper } from '../../helpers'; - -const Example = () => { - const [playlist, setPlaylist] = useState(null); - const [playlistModalOpen, setPlaylistModalOpen] = useState(false); - const [rowSelection, setRowSelection] = useState([]); - const [activeFilterValue, setActiveFilterValue] = useState('all'); - - const playlists = usePlaylistsStore((state) => state.playlists); - - const columns = useMemo( - //column definitions... - () => [ - { - header: 'Name', - accessorKey: 'name', - }, - { - header: 'URL / File', - accessorKey: 'server_url', - }, - { - header: 'Max Streams', - accessorKey: 'max_streams', - size: 200, - }, - { - header: 'Active', - accessorKey: 'is_active', - size: 100, - sortingFn: 'basic', - muiTableBodyCellProps: { - align: 'left', - }, - Cell: ({ cell }) => ( - - {cell.getValue() ? ( - - ) : ( - - )} - - ), - Filter: ({ column }) => ( - - - - ), - filterFn: (row, _columnId, activeFilterValue) => { - if (!activeFilterValue) return true; // Show all if no filter - return String(row.getValue('is_active')) === activeFilterValue; - }, - }, - ], - [] - ); - - //optionally access the underlying virtualizer instance - const rowVirtualizerInstanceRef = useRef(null); - - const [isLoading, setIsLoading] = useState(true); - const [sorting, setSorting] = useState([]); - - const editPlaylist = async (playlist = null) => { - if (playlist) { - setPlaylist(playlist); - } - setPlaylistModalOpen(true); - }; - - const refreshPlaylist = async (id) => { - await API.refreshPlaylist(id); - }; - - const deletePlaylist = async (id) => { - await API.deletePlaylist(id); - }; - - const closeModal = () => { - setPlaylistModalOpen(false); - setPlaylist(null); - }; - - const deletePlaylists = async (ids) => { - const selected = table - .getRowModel() - .rows.filter((row) => row.getIsSelected()); - // await API.deleteStreams(selected.map(stream => stream.original.id)) - }; - - useEffect(() => { - if (typeof window !== 'undefined') { - setIsLoading(false); - } - }, []); - - useEffect(() => { - //scroll to the top of the table when the sorting changes - try { - rowVirtualizerInstanceRef.current?.scrollToIndex?.(0); - } catch (error) { - console.error(error); - } - }, [sorting]); - - const table = useMaterialReactTable({ - ...TableHelper.defaultProperties, - columns, - data: playlists, - enablePagination: false, - enableRowVirtualization: true, - // enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - state: { - isLoading, - sorting, - rowSelection, - }, - rowVirtualizerInstanceRef, //optional - rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer - initialState: { - density: 'compact', - }, - enableRowActions: true, - renderRowActions: ({ row }) => ( - <> - { - editPlaylist(row.original); - }} - sx={{ pt: 0, pb: 0 }} - > - {/* Small icon size */} - - deletePlaylist(row.original.id)} - sx={{ pt: 0, pb: 0 }} - > - {/* Small icon size */} - - refreshPlaylist(row.original.id)} - sx={{ pt: 0, pb: 0 }} - > - {/* Small icon size */} - - - ), - muiTableContainerProps: { - sx: { - height: 'calc(43vh - 0px)', - pr: 1, - pl: 1, - }, - }, - renderTopToolbarCustomActions: ({ table }) => ( - - M3U Accounts - - editPlaylist()} - > - {/* Small icon size */} - - - - ), - }); - - return ( - - - - - ); -}; - -export default Example; diff --git a/vite/src/components/tables/M3UsTable.jsx b/frontend/src/components/tables/M3UsTable.jsx similarity index 54% rename from vite/src/components/tables/M3UsTable.jsx rename to frontend/src/components/tables/M3UsTable.jsx index eb1ce5e6..e538bb2e 100644 --- a/vite/src/components/tables/M3UsTable.jsx +++ b/frontend/src/components/tables/M3UsTable.jsx @@ -1,17 +1,5 @@ import { useEffect, useMemo, useRef, useState } from 'react'; -import { - MaterialReactTable, - useMaterialReactTable, -} from 'material-react-table'; -import { - Box, - Stack, - Typography, - IconButton, - Tooltip, - Select, - MenuItem, -} from '@mui/material'; +import { MantineReactTable, useMantineReactTable } from 'mantine-react-table'; import API from '../../api'; import { Delete as DeleteIcon, @@ -22,9 +10,50 @@ import { Close as CloseIcon, Refresh as RefreshIcon, } from '@mui/icons-material'; +import { + LiveTv as LiveTvIcon, + ContentCopy, + Tv as TvIcon, + Clear as ClearIcon, + IndeterminateCheckBox, + CompareArrows, + Code, + AddBox, + Hd as HdIcon, +} from '@mui/icons-material'; import usePlaylistsStore from '../../store/playlists'; import M3UForm from '../forms/M3U'; import { TableHelper } from '../../helpers'; +import { + useMantineTheme, + Paper, + Button, + Flex, + Text, + Box, + ActionIcon, + Tooltip, + Select, +} from '@mantine/core'; +import { + Tv2, + ScreenShare, + Scroll, + SquareMinus, + Pencil, + ArrowUp, + ArrowDown, + ArrowUpDown, + TvMinimalPlay, +} from 'lucide-react'; +import { + IconArrowDown, + IconArrowUp, + IconDeviceDesktopSearch, + IconSelector, + IconSortAscendingNumbers, + IconSquarePlus, +} from '@tabler/icons-react'; // Import custom icons const Example = () => { const [playlist, setPlaylist] = useState(null); @@ -34,6 +63,8 @@ const Example = () => { const playlists = usePlaylistsStore((state) => state.playlists); + const theme = useMantineTheme(); + const columns = useMemo( //column definitions... () => [ @@ -44,6 +75,17 @@ const Example = () => { { header: 'URL / File', accessorKey: 'server_url', + Cell: ({ cell }) => ( +
+ {cell.getValue()} +
+ ), }, { header: 'Max Streams', @@ -55,7 +97,7 @@ const Example = () => { accessorKey: 'is_active', size: 100, sortingFn: 'basic', - muiTableBodyCellProps: { + mantineTableBodyCellProps: { align: 'left', }, Cell: ({ cell }) => ( @@ -67,29 +109,6 @@ const Example = () => { )}
), - Filter: ({ column }) => ( - - - - ), - filterFn: (row, _columnId, activeFilterValue) => { - if (!activeFilterValue) return true; // Show all if no filter - return String(row.getValue('is_active')) === activeFilterValue; - }, }, ], [] @@ -143,14 +162,15 @@ const Example = () => { } }, [sorting]); - const table = useMaterialReactTable({ + const table = useMantineReactTable({ ...TableHelper.defaultProperties, columns, data: playlists, enablePagination: false, enableRowVirtualization: true, - // enableRowSelection: true, + enableRowSelection: false, onRowSelectionChange: setRowSelection, + renderTopToolbar: false, onSortingChange: setSorting, state: { isLoading, @@ -165,71 +185,103 @@ const Example = () => { enableRowActions: true, renderRowActions: ({ row }) => ( <> - { editPlaylist(row.original); }} - sx={{ pt: 0, pb: 0 }} > - {/* Small icon size */} - - + + deletePlaylist(row.original.id)} - sx={{ pt: 0, pb: 0 }} > - {/* Small icon size */} - - + + refreshPlaylist(row.original.id)} - sx={{ pt: 0, pb: 0 }} > - {/* Small icon size */} - + + ), - muiTableContainerProps: { - sx: { - height: 'calc(43vh - 0px)', - pr: 1, - pl: 1, + mantineTableContainerProps: { + style: { + height: 'calc(40vh - 0px)', }, }, - renderTopToolbarCustomActions: ({ table }) => ( - - M3U Accounts - - editPlaylist()} - > - {/* Small icon size */} - - - - ), }); return ( - - + + + + M3U Accounts + + + + + {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */} + + + + + + + + + + { - const [profile, setProfile] = useState(null); - const [profileModalOpen, setProfileModalOpen] = useState(false); - const [rowSelection, setRowSelection] = useState([]); - const [snackbarMessage, setSnackbarMessage] = useState(''); - const [snackbarOpen, setSnackbarOpen] = useState(false); - const [activeFilterValue, setActiveFilterValue] = useState('all'); - - const streamProfiles = useStreamProfilesStore((state) => state.profiles); - const { settings } = useSettingsStore(); - const { showAlert } = useAlertStore(); - - const columns = useMemo( - //column definitions... - () => [ - { - header: 'Name', - accessorKey: 'profile_name', - }, - { - header: 'Command', - accessorKey: 'command', - }, - { - header: 'Parameters', - accessorKey: 'parameters', - }, - { - header: 'Active', - accessorKey: 'is_active', - size: 100, - sortingFn: 'basic', - muiTableBodyCellProps: { - align: 'left', - }, - Cell: ({ cell }) => ( - - {cell.getValue() ? ( - - ) : ( - - )} - - ), - Filter: ({ column }) => ( - - - - ), - filterFn: (row, _columnId, filterValue) => { - if (filterValue == 'all') return true; // Show all if no filter - return String(row.getValue('is_active')) === filterValue; - }, - }, - ], - [] - ); - - //optionally access the underlying virtualizer instance - const rowVirtualizerInstanceRef = useRef(null); - - const [isLoading, setIsLoading] = useState(true); - const [sorting, setSorting] = useState([]); - - const editStreamProfile = async (profile = null) => { - setProfile(profile); - setProfileModalOpen(true); - }; - - const deleteStreamProfile = async (id) => { - if (id == settings['default-stream-profile'].value) { - showAlert('Cannot delete default stream-profile', 'error'); - return; - } - - await API.deleteStreamProfile(id); - }; - - const closeStreamProfileForm = () => { - setProfile(null); - setProfileModalOpen(false); - }; - - useEffect(() => { - if (typeof window !== 'undefined') { - setIsLoading(false); - } - }, []); - - useEffect(() => { - //scroll to the top of the table when the sorting changes - try { - rowVirtualizerInstanceRef.current?.scrollToIndex?.(0); - } catch (error) { - console.error(error); - } - }, [sorting]); - - const table = useMaterialReactTable({ - ...TableHelper.defaultProperties, - columns, - data: streamProfiles, - enablePagination: false, - enableRowVirtualization: true, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - state: { - isLoading, - sorting, - rowSelection, - }, - rowVirtualizerInstanceRef, //optional - rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer - initialState: { - density: 'compact', - }, - enableRowActions: true, - renderRowActions: ({ row }) => ( - <> - editStreamProfile(row.original)} - sx={{ pt: 0, pb: 0 }} - > - {/* Small icon size */} - - deleteStreamProfile(row.original.id)} - sx={{ pt: 0, pb: 0 }} - > - {/* Small icon size */} - - - ), - muiTableContainerProps: { - sx: { - height: 'calc(100vh - 73px)', // Subtract padding to avoid cutoff - overflowY: 'auto', // Internal scrolling for the table - }, - }, - renderTopToolbarCustomActions: ({ table }) => ( - - Stream Profiles - - editStreamProfile()} - > - {/* Small icon size */} - - - - ), - }); - - return ( - - - - - - ); -}; - -export default StreamProfiles; diff --git a/vite/src/components/tables/StreamProfilesTable.jsx b/frontend/src/components/tables/StreamProfilesTable.jsx similarity index 69% rename from vite/src/components/tables/StreamProfilesTable.jsx rename to frontend/src/components/tables/StreamProfilesTable.jsx index b2ac3390..4776ce8f 100644 --- a/vite/src/components/tables/StreamProfilesTable.jsx +++ b/frontend/src/components/tables/StreamProfilesTable.jsx @@ -12,8 +12,18 @@ import StreamProfileForm from '../forms/StreamProfile'; import useStreamProfilesStore from '../../store/streamProfiles'; import { TableHelper } from '../../helpers'; import useSettingsStore from '../../store/settings'; -import useAlertStore from '../../store/alerts'; -import { Box, ActionIcon, Tooltip, Text } from '@mantine/core'; +import { notifications } from '@mantine/notifications'; +import { + Box, + ActionIcon, + Tooltip, + Text, + Paper, + Flex, + Button, + useMantineTheme, +} from '@mantine/core'; +import { IconSquarePlus } from '@tabler/icons-react'; const StreamProfiles = () => { const [profile, setProfile] = useState(null); @@ -23,7 +33,8 @@ const StreamProfiles = () => { const streamProfiles = useStreamProfilesStore((state) => state.profiles); const { settings } = useSettingsStore(); - const { showAlert } = useAlertStore(); + + const theme = useMantineTheme(); const columns = useMemo( //column definitions... @@ -93,7 +104,10 @@ const StreamProfiles = () => { const deleteStreamProfile = async (id) => { if (id == settings['default-stream-profile'].value) { - showAlert('Cannot delete default stream-profile', 'error'); + notifications.show({ + title: 'Cannot delete default stream-profile', + color: 'red.5', + }); return; } @@ -127,6 +141,7 @@ const StreamProfiles = () => { enablePagination: false, enableRowVirtualization: true, enableRowSelection: true, + renderTopToolbar: false, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, state: { @@ -161,33 +176,77 @@ const StreamProfiles = () => { ), mantineTableContainerProps: { style: { - height: 'calc(100vh - 90px)', + height: 'calc(100vh - 120px)', overflowY: 'auto', }, }, - renderTopToolbarCustomActions: ({ table }) => ( - <> - Stream Profiles - - editStreamProfile()} - > - {/* Small icon size */} - - - - ), }); return ( - + + + + Stream Profiles + + + + + {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */} + + + + + + + + + { - /** - * useState - */ - const [rowSelection, setRowSelection] = useState([]); - const [stream, setStream] = useState(null); - const [modalOpen, setModalOpen] = useState(false); - const [moreActionsAnchorEl, setMoreActionsAnchorEl] = useState(null); - const [groupOptions, setGroupOptions] = useState([]); - const [m3uOptions, setM3uOptions] = useState([]); - const [actionsOpenRow, setActionsOpenRow] = useState(null); - - const [data, setData] = useState([]); // Holds fetched data - const [rowCount, setRowCount] = useState(0); - const [isLoading, setIsLoading] = useState(true); - const [sorting, setSorting] = useState([]); - const [selectedStreamIds, setSelectedStreamIds] = useState([]); - const [unselectedStreamIds, setUnselectedStreamIds] = useState([]); - // const [allRowsSelected, setAllRowsSelected] = useState(false); - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: 25, - }); - const [filters, setFilters] = useState({ - name: '', - group_name: '', - m3u_account: '', - }); - const debouncedFilters = useDebounce(filters, 500); - - /** - * Stores - */ - const { playlists } = usePlaylistsStore(); - const { channelsPageSelection } = useChannelsStore(); - const channelSelectionStreams = useChannelsStore( - (state) => state.channels[state.channelsPageSelection[0]?.id]?.streams - ); - - const isMoreActionsOpen = Boolean(moreActionsAnchorEl); - - // Access the row virtualizer instance (optional) - const rowVirtualizerInstanceRef = useRef(null); - - /** - * useMemo - */ - const columns = useMemo( - () => [ - { - header: 'Name', - accessorKey: 'name', - muiTableHeadCellProps: { - sx: { textAlign: 'center' }, // Center-align the header - }, - Header: ({ column }) => ( - e.stopPropagation()} - onChange={handleFilterChange} - size="small" - margin="none" - fullWidth - sx={ - { - // '& .MuiInputBase-root': { fontSize: '0.875rem' }, // Text size - // '& .MuiInputLabel-root': { fontSize: '0.75rem' }, // Label size - // width: '200px', // Optional: Adjust width - } - } - // slotProps={{ - // input: { - // endAdornment: ( - // - // handleFilterChange(column.id, '')} // Clear text on click - // edge="end" - // size="small" - // sx={{ p: 0 }} - // > - // - // - // - // ), - // }, - // }} - /> - ), - }, - { - header: 'Group', - accessorKey: 'group_name', - Header: ({ column }) => ( - { - e.stopPropagation(); - handleGroupChange(value); - }} - renderInput={(params) => ( - e.stopPropagation()} - sx={{ - pb: 0.8, - '& .MuiInputBase-root': { fontSize: '0.875rem' }, // Text size - '& .MuiInputLabel-root': { fontSize: '0.75rem' }, // Label size - width: '200px', // Optional: Adjust width - }} - /> - )} - /> - ), - }, - { - header: 'M3U', - size: 100, - accessorFn: (row) => - playlists.find((playlist) => playlist.id === row.m3u_account)?.name, - Header: ({ column }) => ( - ({ - label: playlist.name, - value: playlist.id, - }))} - size="small" - // sx={{ width: 300 }} - clearOnEscape - onChange={(e, value) => { - e.stopPropagation(); - handleM3UChange(value); - }} - renderInput={(params) => ( - e.stopPropagation()} - sx={{ - pb: 0.8, - '& .MuiInputBase-root': { fontSize: '0.875rem' }, // Text size - '& .MuiInputLabel-root': { fontSize: '0.75rem' }, // Label size - width: '200px', // Optional: Adjust width - }} - /> - )} - /> - ), - }, - ], - [playlists, groupOptions, filters] - ); - - /** - * Functions - */ - const handleFilterChange = (e) => { - const { name, value } = e.target; - setFilters((prev) => ({ - ...prev, - [name]: value, - })); - }; - - const handleGroupChange = (value) => { - console.log(value); - setFilters((prev) => ({ - ...prev, - group_name: value ? value.value : '', - })); - }; - - const handleM3UChange = (value) => { - console.log(value); - setFilters((prev) => ({ - ...prev, - m3u_account: value ? value.value : '', - })); - }; - - const fetchData = useCallback(async () => { - setIsLoading(true); - - const params = new URLSearchParams(); - params.append('page', pagination.pageIndex + 1); - params.append('page_size', pagination.pageSize); - - // Apply sorting - if (sorting.length > 0) { - const sortField = sorting[0].id; - const sortDirection = sorting[0].desc ? '-' : ''; - params.append('ordering', `${sortDirection}${sortField}`); - } - - // Apply debounced filters - Object.entries(debouncedFilters).forEach(([key, value]) => { - if (value) params.append(key, value); - }); - - try { - const result = await API.queryStreams(params); - setData(result.results); - setRowCount(result.count); - - const newSelection = {}; - result.results.forEach((item, index) => { - if (selectedStreamIds.includes(item.id)) { - newSelection[index] = true; - } - }); - - // ✅ Only update rowSelection if it's different - if (JSON.stringify(newSelection) !== JSON.stringify(rowSelection)) { - setRowSelection(newSelection); - } - } catch (error) { - console.error('Error fetching data:', error); - } - - const groups = await API.getStreamGroups(); - setGroupOptions(groups); - - setIsLoading(false); - }, [pagination, sorting, debouncedFilters]); - - useEffect(() => { - console.log(pagination); - }, [pagination]); - - // Fallback: Individual creation (optional) - const createChannelFromStream = async (stream) => { - await API.createChannelFromStream({ - channel_name: stream.name, - channel_number: null, - stream_id: stream.id, - }); - }; - - // Bulk creation: create channels from selected streams in one API call - const createChannelsFromStreams = async () => { - setIsLoading(true); - await API.createChannelsFromStreams( - selectedStreamIds.map((stream_id) => ({ - stream_id, - })) - ); - setIsLoading(false); - }; - - const editStream = async (stream = null) => { - setStream(stream); - setModalOpen(true); - }; - - const deleteStream = async (id) => { - await API.deleteStream(id); - }; - - const deleteStreams = async () => { - setIsLoading(true); - await API.deleteStreams(selectedStreamIds); - setIsLoading(false); - }; - - const closeStreamForm = () => { - setStream(null); - setModalOpen(false); - }; - - const addStreamsToChannel = async () => { - const { streams, ...channel } = { ...channelsPageSelection[0] }; - await API.updateChannel({ - ...channel, - stream_ids: [ - ...new Set( - channelSelectionStreams - .map((stream) => stream.id) - .concat(selectedStreamIds) - ), - ], - }); - }; - - const addStreamToChannel = async (streamId) => { - const { streams, ...channel } = { ...channelsPageSelection[0] }; - await API.updateChannel({ - ...channel, - stream_ids: [ - ...new Set( - channelSelectionStreams.map((stream) => stream.id).concat([streamId]) - ), - ], - }); - }; - - const handleMoreActionsClick = (event, rowId) => { - setMoreActionsAnchorEl(event.currentTarget); - setActionsOpenRow(rowId); - }; - - const handleMoreActionsClose = () => { - setMoreActionsAnchorEl(null); - setActionsOpenRow(null); - }; - - const onRowSelectionChange = (updater) => { - setRowSelection((prevRowSelection) => { - const newRowSelection = - typeof updater === 'function' ? updater(prevRowSelection) : updater; - - const updatedSelected = new Set([...selectedStreamIds]); - table.getRowModel().rows.map((row) => { - if (newRowSelection[row.id] === undefined || !newRowSelection[row.id]) { - updatedSelected.delete(row.original.id); - } else { - updatedSelected.add(row.original.id); - } - }); - setSelectedStreamIds([...updatedSelected]); - - return newRowSelection; - }); - }; - - const onSelectAllChange = async (e) => { - const selectAll = e.target.checked; - if (selectAll) { - // Get all stream IDs for current view - const params = new URLSearchParams(); - - // Apply debounced filters - Object.entries(debouncedFilters).forEach(([key, value]) => { - if (value) params.append(key, value); - }); - - const ids = await API.getAllStreamIds(params); - setSelectedStreamIds(ids); - } else { - setSelectedStreamIds([]); - } - - const newSelection = {}; - table.getRowModel().rows.forEach((item, index) => { - newSelection[index] = selectAll; - }); - setRowSelection(newSelection); - }; - - const onPaginationChange = (updater) => { - const newPagination = updater(pagination); - if (JSON.stringify(newPagination) === JSON.stringify(pagination)) { - // Prevent infinite re-render when there are no results - return; - } - - setPagination(updater); - }; - - const table = useMaterialReactTable({ - ...TableHelper.defaultProperties, - columns, - data, - enablePagination: true, - manualPagination: true, - enableRowVirtualization: true, - rowVirtualizerInstanceRef, - rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer - manualSorting: true, - enableBottomToolbar: true, - enableStickyHeader: true, - onPaginationChange: onPaginationChange, - onSortingChange: setSorting, - rowCount: rowCount, - enableRowSelection: true, - muiSelectAllCheckboxProps: { - checked: selectedStreamIds.length == rowCount, - indeterminate: - selectedStreamIds.length > 0 && selectedStreamIds.length != rowCount, - onChange: onSelectAllChange, - }, - onRowSelectionChange: onRowSelectionChange, - initialState: { - density: 'compact', - }, - state: { - isLoading: isLoading, - sorting, - pagination, - rowSelection, - }, - enableRowActions: true, - positionActionsColumn: 'first', - renderRowActions: ({ row }) => ( - <> - - addStreamToChannel(row.original.id)} - sx={{ py: 0, px: 0.5 }} - disabled={ - channelsPageSelection.length != 1 || - (channelSelectionStreams && - channelSelectionStreams - .map((stream) => stream.id) - .includes(row.original.id)) - } - > - - - - - - createChannelFromStream(row.original)} - sx={{ py: 0, px: 0.5 }} - > - - - - - handleMoreActionsClick(event, row.original.id)} - size="small" - sx={{ py: 0, px: 0.5 }} - > - - - - editStream(row.original.id)} - disabled={row.original.m3u_account ? true : false} - > - Edit - - deleteStream(row.original.id)}> - Delete Stream - - - - ), - muiPaginationProps: { - size: 'small', - rowsPerPageOptions: [25, 50, 100, 250, 500, 1000, 10000], - labelRowsPerPage: 'Rows per page', - }, - muiTableContainerProps: { - sx: { - height: 'calc(100vh - 145px)', - overflowY: 'auto', - }, - }, - displayColumnDefOptions: { - 'mrt-row-actions': { - size: 68, - }, - 'mrt-row-select': { - size: 50, - }, - }, - renderTopToolbarCustomActions: ({ table }) => { - const selectedRowCount = table.getSelectedRowModel().rows.length; - - return ( - - Streams - - editStream()} - > - - - - - - - - - - - - ); - }, - }); - - /** - * useEffects - */ - useEffect(() => { - fetchData(); - }, [fetchData]); - - useEffect(() => { - if (typeof window !== 'undefined') { - setIsLoading(false); - } - }, []); - - useEffect(() => { - // Scroll to the top of the table when sorting changes - try { - rowVirtualizerInstanceRef.current?.scrollToIndex?.(0); - } catch (error) { - console.error(error); - } - }, [sorting]); - - return ( - - - - - ); -}; - -export default StreamsTable; diff --git a/vite/src/components/tables/StreamsTable.jsx b/frontend/src/components/tables/StreamsTable.jsx similarity index 73% rename from vite/src/components/tables/StreamsTable.jsx rename to frontend/src/components/tables/StreamsTable.jsx index af2a86d4..2f90fb09 100644 --- a/vite/src/components/tables/StreamsTable.jsx +++ b/frontend/src/components/tables/StreamsTable.jsx @@ -13,7 +13,7 @@ import StreamForm from '../forms/Stream'; import usePlaylistsStore from '../../store/playlists'; import useChannelsStore from '../../store/channels'; import { useDebounce } from '../../utils'; -import { SquarePlus, ListPlus } from 'lucide-react'; +import { SquarePlus, ListPlus, SquareMinus } from 'lucide-react'; import { TextInput, ActionIcon, @@ -25,6 +25,11 @@ import { Text, Paper, Button, + Card, + Stack, + Title, + Divider, + Center, } from '@mantine/core'; import { IconArrowDown, @@ -32,9 +37,9 @@ import { IconDeviceDesktopSearch, IconSelector, IconSortAscendingNumbers, - IconSquareMinus, IconSquarePlus, } from '@tabler/icons-react'; +import { useNavigate } from 'react-router-dom'; const StreamsTable = ({}) => { /** @@ -47,6 +52,7 @@ const StreamsTable = ({}) => { const [groupOptions, setGroupOptions] = useState([]); const [m3uOptions, setM3uOptions] = useState([]); const [actionsOpenRow, setActionsOpenRow] = useState(null); + const [dataFetched, setDataFetched] = useState(false); const [data, setData] = useState([]); // Holds fetched data const [rowCount, setRowCount] = useState(0); @@ -66,6 +72,8 @@ const StreamsTable = ({}) => { }); const debouncedFilters = useDebounce(filters, 500); + const navigate = useNavigate(); + /** * Stores */ @@ -80,6 +88,11 @@ const StreamsTable = ({}) => { // Access the row virtualizer instance (optional) const rowVirtualizerInstanceRef = useRef(null); + const handleSelectClick = (e) => { + e.stopPropagation(); + e.preventDefault(); + }; + /** * useMemo */ @@ -119,15 +132,17 @@ const StreamsTable = ({}) => { accessorKey: 'group_name', size: 100, Header: ({ column }) => ( - + ), Cell: ({ cell }) => (
{ accessorFn: (row) => playlists.find((playlist) => playlist.id === row.m3u_account)?.name, Header: ({ column }) => ( - ({ + label: playlist.name, + value: `${playlist.id}`, + }))} + /> + ), }, ], @@ -179,14 +196,14 @@ const StreamsTable = ({}) => { const handleGroupChange = (value) => { setFilters((prev) => ({ ...prev, - group_name: value ? value.value : '', + group_name: value ? value : '', })); }; const handleM3UChange = (value) => { setFilters((prev) => ({ ...prev, - m3u_account: value ? value.value : '', + m3u_account: value ? value : '', })); }; @@ -233,6 +250,9 @@ const StreamsTable = ({}) => { setGroupOptions(groups); setIsLoading(false); + if (dataFetched === false) { + setDataFetched(true); + } }, [pagination, sorting, debouncedFilters]); // Fallback: Individual creation (optional) @@ -393,6 +413,13 @@ const StreamsTable = ({}) => { onPaginationChange: onPaginationChange, rowCount: rowCount, enableRowSelection: true, + mantineSelectAllCheckboxProps: { + checked: selectedStreamIds.length == rowCount, + indeterminate: + selectedStreamIds.length > 0 && selectedStreamIds.length != rowCount, + onChange: onSelectAllChange, + size: 'xs', + }, muiPaginationProps: { size: 'small', rowsPerPageOptions: [25, 50, 100, 250, 500, 1000, 10000], @@ -457,7 +484,7 @@ const StreamsTable = ({}) => { editStream(row.original.id)} + onClick={() => editStream(row.original)} disabled={row.original.m3u_account ? true : false} > Edit @@ -471,16 +498,16 @@ const StreamsTable = ({}) => { ), mantineTableContainerProps: { style: { - height: 'calc(100vh - 165px)', + height: 'calc(100vh - 180px)', overflowY: 'auto', }, }, displayColumnDefOptions: { 'mrt-row-actions': { - size: 68, + size: 30, }, 'mrt-row-select': { - size: 50, + size: 20, }, }, }); @@ -509,7 +536,10 @@ const StreamsTable = ({}) => { return ( <> - + { fontSize: '20px', lineHeight: 1, letterSpacing: '-0.3px', - color: 'gray.6', // Adjust this to match MUI's theme.palette.text.secondary + // color: 'gray.6', // Adjust this to match MUI's theme.palette.text.secondary marginBottom: 0, }} > @@ -528,16 +558,14 @@ const StreamsTable = ({}) => { {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */} { }} > - - - + - - - + - - - + - - + {!dataFetched && ( +
+ + + + Getting started + + + In order to get started, add your M3U or start
+ adding custom streams. +
+ + + +
+
+
+ )} + {dataFetched && } + { - const [userAgent, setUserAgent] = useState(null); - const [userAgentModalOpen, setUserAgentModalOpen] = useState(false); - const [rowSelection, setRowSelection] = useState([]); - const [activeFilterValue, setActiveFilterValue] = useState('all'); - - const userAgents = useUserAgentsStore((state) => state.userAgents); - const { settings } = useSettingsStore(); - const { showAlert } = useAlertStore(); - - const columns = useMemo( - //column definitions... - () => [ - { - header: 'Name', - accessorKey: 'user_agent_name', - }, - { - header: 'User-Agent', - accessorKey: 'user_agent', - }, - { - header: 'Desecription', - accessorKey: 'description', - }, - { - header: 'Active', - accessorKey: 'is_active', - size: 100, - sortingFn: 'basic', - muiTableBodyCellProps: { - align: 'left', - }, - Cell: ({ cell }) => ( - - {cell.getValue() ? ( - - ) : ( - - )} - - ), - Filter: ({ column }) => ( - - - - ), - filterFn: (row, _columnId, activeFilterValue) => { - if (activeFilterValue == 'all') return true; // Show all if no filter - return String(row.getValue('is_active')) === activeFilterValue; - }, - }, - ], - [] - ); - - //optionally access the underlying virtualizer instance - const rowVirtualizerInstanceRef = useRef(null); - - const [isLoading, setIsLoading] = useState(true); - const [sorting, setSorting] = useState([]); - - const editUserAgent = async (userAgent = null) => { - setUserAgent(userAgent); - setUserAgentModalOpen(true); - }; - - const deleteUserAgent = async (ids) => { - if (Array.isArray(ids)) { - if (ids.includes(settings['default-user-agent'].value)) { - showAlert('Cannot delete default user-agent', 'error'); - return; - } - - await API.deleteUserAgents(ids); - } else { - if (ids == settings['default-user-agent'].value) { - showAlert('Cannot delete default user-agent', 'error'); - return; - } - - await API.deleteUserAgent(ids); - } - }; - - const closeUserAgentForm = () => { - setUserAgent(null); - setUserAgentModalOpen(false); - }; - - useEffect(() => { - if (typeof window !== 'undefined') { - setIsLoading(false); - } - }, []); - - useEffect(() => { - //scroll to the top of the table when the sorting changes - try { - rowVirtualizerInstanceRef.current?.scrollToIndex?.(0); - } catch (error) { - console.error(error); - } - }, [sorting]); - - const table = useMaterialReactTable({ - ...TableHelper.defaultProperties, - columns, - data: userAgents, - enablePagination: false, - enableRowVirtualization: true, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - state: { - isLoading, - sorting, - rowSelection, - }, - rowVirtualizerInstanceRef, //optional - rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer - initialState: { - density: 'compact', - }, - enableRowActions: true, - renderRowActions: ({ row }) => ( - <> - { - editUserAgent(row.original); - }} - sx={{ pt: 0, pb: 0 }} - > - {/* Small icon size */} - - deleteUserAgent(row.original.id)} - sx={{ pt: 0, pb: 0 }} - > - {/* Small icon size */} - - - ), - muiTableContainerProps: { - sx: { - height: 'calc(42vh + 5px)', - }, - }, - renderTopToolbarCustomActions: ({ table }) => ( - - User-Agents - - editUserAgent()} - > - {/* Small icon size */} - - - - ), - }); - - return ( - <> - - - - - - ); -}; - -export default UserAgentsTable; diff --git a/vite/src/components/tables/UserAgentsTable.jsx b/frontend/src/components/tables/UserAgentsTable.jsx similarity index 62% rename from vite/src/components/tables/UserAgentsTable.jsx rename to frontend/src/components/tables/UserAgentsTable.jsx index 6324b3a8..b1107e83 100644 --- a/vite/src/components/tables/UserAgentsTable.jsx +++ b/frontend/src/components/tables/UserAgentsTable.jsx @@ -12,8 +12,26 @@ import useUserAgentsStore from '../../store/userAgents'; import UserAgentForm from '../forms/UserAgent'; import { TableHelper } from '../../helpers'; import useSettingsStore from '../../store/settings'; -import useAlertStore from '../../store/alerts'; -import { ActionIcon, Center, Flex, Select, Tooltip, Text } from '@mantine/core'; +import { notifications } from '@mantine/notifications'; +import { + ActionIcon, + Center, + Flex, + Select, + Tooltip, + Text, + Paper, + Box, + Button, +} from '@mantine/core'; +import { + IconArrowDown, + IconArrowUp, + IconDeviceDesktopSearch, + IconSelector, + IconSortAscendingNumbers, + IconSquarePlus, +} from '@tabler/icons-react'; const UserAgentsTable = () => { const [userAgent, setUserAgent] = useState(null); @@ -23,7 +41,6 @@ const UserAgentsTable = () => { const userAgents = useUserAgentsStore((state) => state.userAgents); const { settings } = useSettingsStore(); - const { showAlert } = useAlertStore(); const columns = useMemo( //column definitions... @@ -35,17 +52,39 @@ const UserAgentsTable = () => { { header: 'User-Agent', accessorKey: 'user_agent', + Cell: ({ cell }) => ( +
+ {cell.getValue()} +
+ ), }, { header: 'Desecription', accessorKey: 'description', + Cell: ({ cell }) => ( +
+ {cell.getValue()} +
+ ), }, { header: 'Active', accessorKey: 'is_active', size: 100, sortingFn: 'basic', - muiTableBodyCellProps: { + mantineTableBodyCellProps: { align: 'left', }, Cell: ({ cell }) => ( @@ -105,14 +144,20 @@ const UserAgentsTable = () => { const deleteUserAgent = async (ids) => { if (Array.isArray(ids)) { if (ids.includes(settings['default-user-agent'].value)) { - showAlert('Cannot delete default user-agent', 'error'); + notifications.show({ + title: 'Cannot delete default user-agent', + color: 'red.5', + }); return; } await API.deleteUserAgents(ids); } else { if (ids == settings['default-user-agent'].value) { - showAlert('Cannot delete default user-agent', 'error'); + notifications.show({ + title: 'Cannot delete default user-agent', + color: 'red.5', + }); return; } @@ -147,6 +192,7 @@ const UserAgentsTable = () => { enablePagination: false, enableRowVirtualization: true, enableRowSelection: true, + renderTopToolbar: false, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, state: { @@ -182,30 +228,85 @@ const UserAgentsTable = () => { ), - muiTableContainerProps: { - sx: { - height: 'calc(42vh + 5px)', + mantineTableContainerProps: { + style: { + height: 'calc(43vh - 55px)', }, }, - renderTopToolbarCustomActions: ({ table }) => ( - - User-Agents - - editUserAgent()} - > - {/* Small icon size */} - - - - ), }); return ( <> + + + User-Agents + + + + + {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */} + + + + + + + + + - - -); diff --git a/vite/src/main.jsx b/frontend/src/main.jsx similarity index 100% rename from vite/src/main.jsx rename to frontend/src/main.jsx diff --git a/vite/src/mantineTheme.jsx b/frontend/src/mantineTheme.jsx similarity index 100% rename from vite/src/mantineTheme.jsx rename to frontend/src/mantineTheme.jsx diff --git a/frontend/src/pages/Channels-test.jsx b/frontend/src/pages/Channels-test.jsx new file mode 100644 index 00000000..14e3319c --- /dev/null +++ b/frontend/src/pages/Channels-test.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Allotment } from 'allotment'; +import { Box, Container } from '@mantine/core'; +import 'allotment/dist/style.css'; + +const ChannelsPage = () => { + return ( + +
Pane 1
+
Pane 1
+
+ ); +}; + +export default ChannelsPage; diff --git a/frontend/src/pages/Channels.js b/frontend/src/pages/Channels.js deleted file mode 100644 index 73eccb9a..00000000 --- a/frontend/src/pages/Channels.js +++ /dev/null @@ -1,43 +0,0 @@ -import React, { useState } from 'react'; -import ChannelsTable from '../components/tables/ChannelsTable'; -import StreamsTable from '../components/tables/StreamsTable'; -import { Grid2, Box } from '@mui/material'; - -const ChannelsPage = () => { - return ( - - - - - - - - - - - - - ); -}; - -export default ChannelsPage; diff --git a/vite/src/pages/Channels.jsx b/frontend/src/pages/Channels.jsx similarity index 85% rename from vite/src/pages/Channels.jsx rename to frontend/src/pages/Channels.jsx index 4899c4fb..c3913d76 100644 --- a/vite/src/pages/Channels.jsx +++ b/frontend/src/pages/Channels.jsx @@ -5,11 +5,11 @@ import { Box, Grid } from '@mantine/core'; const ChannelsPage = () => { return ( - + { { - const [newStream, setNewStream] = useState(""); - - return ( -
-

Dashboard Page

- setNewStream(e.target.value)} - placeholder="Enter Stream" - /> - -

Streams:

-
    - {state.streams.map((stream, index) => ( -
  • {stream}
  • - ))} -
-
- ); -}; - -export default Dashboard; diff --git a/vite/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx similarity index 100% rename from vite/src/pages/Dashboard.jsx rename to frontend/src/pages/Dashboard.jsx diff --git a/frontend/src/pages/EPG.js b/frontend/src/pages/EPG.js deleted file mode 100644 index 24909f05..00000000 --- a/frontend/src/pages/EPG.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; -import { Box } from "@mui/material"; -import UserAgentsTable from "../components/tables/UserAgentsTable"; -import EPGsTable from "../components/tables/EPGsTable"; - -const EPGPage = () => { - return ( - - - - - - - - - - ); -}; - -export default EPGPage; diff --git a/vite/src/pages/EPG.jsx b/frontend/src/pages/EPG.jsx similarity index 92% rename from vite/src/pages/EPG.jsx rename to frontend/src/pages/EPG.jsx index f8b9a7f4..aea3d92d 100644 --- a/vite/src/pages/EPG.jsx +++ b/frontend/src/pages/EPG.jsx @@ -9,8 +9,9 @@ const EPGPage = () => { style={{ display: 'flex', flexDirection: 'column', - height: '95vh', + height: '100vh', overflow: 'hidden', + padding: 16, }} > diff --git a/frontend/src/pages/Guide.js b/frontend/src/pages/Guide.js deleted file mode 100644 index d60ec8ba..00000000 --- a/frontend/src/pages/Guide.js +++ /dev/null @@ -1,532 +0,0 @@ -// frontend/src/pages/Guide.js -import React, { useMemo, useState, useEffect, useRef } from 'react'; -import { - Box, - Typography, - Paper, - Stack, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Button, - Slide, - CircularProgress, - Backdrop, -} from '@mui/material'; -import dayjs from 'dayjs'; -import API from '../api'; -import useChannelsStore from '../store/channels'; -import logo from '../images/logo.png'; -import useVideoStore from '../store/useVideoStore'; // NEW import -import useAlertStore from '../store/alerts'; -import useSettingsStore from '../store/settings'; - -/** Layout constants */ -const CHANNEL_WIDTH = 120; // Width of the channel/logo column -const PROGRAM_HEIGHT = 90; // Height of each channel row -const HOUR_WIDTH = 300; // The width for a 1-hour block -const MINUTE_INCREMENT = 15; // For positioning programs every 15 min -const MINUTE_BLOCK_WIDTH = HOUR_WIDTH / (60 / MINUTE_INCREMENT); - -// Modal size constants -const MODAL_WIDTH = 600; -const MODAL_HEIGHT = 400; - -// Slide transition for Dialog -const Transition = React.forwardRef(function Transition(props, ref) { - return ; -}); - -export default function TVChannelGuide({ startDate, endDate }) { - const { channels } = useChannelsStore(); - - const [programs, setPrograms] = useState([]); - const [guideChannels, setGuideChannels] = useState([]); - const [now, setNow] = useState(dayjs()); - const [selectedProgram, setSelectedProgram] = useState(null); - const [loading, setLoading] = useState(true); - const { showAlert } = useAlertStore(); - const { - environment: { env_mode }, - } = useSettingsStore(); - - const guideRef = useRef(null); - - // Load program data once - useEffect(() => { - if (!channels || channels.length === 0) { - console.warn('No channels provided or empty channels array'); - showAlert('No channels available', 'error'); - setLoading(false); - return; - } - - const fetchPrograms = async () => { - console.log('Fetching program grid...'); - const fetched = await API.getGrid(); // GETs your EPG grid - console.log(`Received ${fetched.length} programs`); - - // Unique tvg_ids from returned programs - const programIds = [...new Set(fetched.map((p) => p.tvg_id))]; - - // Filter your Redux/Zustand channels by matching tvg_id - const filteredChannels = channels.filter((ch) => - programIds.includes(ch.tvg_id) - ); - console.log( - `found ${filteredChannels.length} channels with matching tvg_ids` - ); - - setGuideChannels(filteredChannels); - setPrograms(fetched); - setLoading(false); - }; - - fetchPrograms(); - }, [channels]); - - // Use start/end from props or default to "today at midnight" +24h - const defaultStart = dayjs(startDate || dayjs().startOf('day')); - const defaultEnd = endDate ? dayjs(endDate) : defaultStart.add(24, 'hour'); - - // Expand timeline if needed based on actual earliest/ latest program - const earliestProgramStart = useMemo(() => { - if (!programs.length) return defaultStart; - return programs.reduce((acc, p) => { - const s = dayjs(p.start_time); - return s.isBefore(acc) ? s : acc; - }, defaultStart); - }, [programs, defaultStart]); - - const latestProgramEnd = useMemo(() => { - if (!programs.length) return defaultEnd; - return programs.reduce((acc, p) => { - const e = dayjs(p.end_time); - return e.isAfter(acc) ? e : acc; - }, defaultEnd); - }, [programs, defaultEnd]); - - const start = earliestProgramStart.isBefore(defaultStart) - ? earliestProgramStart - : defaultStart; - const end = latestProgramEnd.isAfter(defaultEnd) - ? latestProgramEnd - : defaultEnd; - - // Time increments in 15-min steps (for placing programs) - const programTimeline = useMemo(() => { - const times = []; - let current = start; - while (current.isBefore(end)) { - times.push(current); - current = current.add(MINUTE_INCREMENT, 'minute'); - } - return times; - }, [start, end]); - - // Hourly marks - const hourTimeline = useMemo(() => { - const hours = []; - let current = start; - while (current.isBefore(end)) { - hours.push(current); - current = current.add(1, 'hour'); - } - return hours; - }, [start, end]); - - // Scroll to "now" on load - useEffect(() => { - if (guideRef.current) { - const nowOffset = dayjs().diff(start, 'minute'); - const scrollPosition = - (nowOffset / MINUTE_INCREMENT) * MINUTE_BLOCK_WIDTH - - MINUTE_BLOCK_WIDTH; - guideRef.current.scrollLeft = Math.max(scrollPosition, 0); - } - }, [programs, start]); - - // Update “now” every 60s - useEffect(() => { - const interval = setInterval(() => { - setNow(dayjs()); - }, 60000); - return () => clearInterval(interval); - }, []); - - // Pixel offset for the “now” vertical line - const nowPosition = useMemo(() => { - if (now.isBefore(start) || now.isAfter(end)) return -1; - const minutesSinceStart = now.diff(start, 'minute'); - return (minutesSinceStart / MINUTE_INCREMENT) * MINUTE_BLOCK_WIDTH; - }, [now, start, end]); - - // Helper: find channel by tvg_id - function findChannelByTvgId(tvgId) { - return guideChannels.find((ch) => ch.tvg_id === tvgId); - } - - // The “Watch Now” click => show floating video - const { showVideo } = useVideoStore(); // or useVideoStore() - function handleWatchStream(program) { - const matched = findChannelByTvgId(program.tvg_id); - if (!matched) { - console.warn(`No channel found for tvg_id=${program.tvg_id}`); - return; - } - // Build a playable stream URL for that channel - let vidUrl = `/output/stream/${matched.channel_number}/`; - if (env_mode == 'dev') { - vidUrl = `${window.location.protocol}//${window.location.hostname}:5656${vidUrl}`; - } - - showVideo(vidUrl); - - // Optionally close the modal - setSelectedProgram(null); - } - - // On program click, open the details modal - function handleProgramClick(program, event) { - // Optionally scroll that element into view or do something else - event.currentTarget.scrollIntoView({ - behavior: 'smooth', - inline: 'center', - }); - setSelectedProgram(program); - } - - // Close the modal - function handleCloseModal() { - setSelectedProgram(null); - } - - // Renders each program block - function renderProgram(program, channelStart) { - const programKey = `${program.tvg_id}-${program.start_time}`; - const programStart = dayjs(program.start_time); - const programEnd = dayjs(program.end_time); - const startOffsetMinutes = programStart.diff(channelStart, 'minute'); - const durationMinutes = programEnd.diff(programStart, 'minute'); - const leftPx = (startOffsetMinutes / MINUTE_INCREMENT) * MINUTE_BLOCK_WIDTH; - const widthPx = (durationMinutes / MINUTE_INCREMENT) * MINUTE_BLOCK_WIDTH; - - // Highlight if currently live - const isLive = now.isAfter(programStart) && now.isBefore(programEnd); - - return ( - handleProgramClick(program, e)} - > - - - {program.title} - - - {programStart.format('h:mma')} - {programEnd.format('h:mma')} - - - - ); - } - - if (loading) { - return ( - theme.zIndex.drawer + 1, - position: 'fixed', // Ensure it covers the entire page - top: 0, - left: 0, - right: 0, - bottom: 0, - }} - open={loading} - > - - - ); - } - - return ( - - {/* Sticky top bar */} - - - TV Guide - - - {now.format('dddd, MMMM D, YYYY • h:mm A')} - - - - {/* Main layout */} - - {/* Channel Logos Column */} - - - {guideChannels.map((channel) => ( - - - {channel.channel_name} - - - ))} - - - {/* Timeline & Program Blocks */} - - {/* Sticky timeline header */} - - - {hourTimeline.map((time, hourIndex) => ( - - - {time.format('h:mma')} - - - {[0, 1, 2, 3].map((i) => ( - - ))} - - - ))} - - - - {/* Now line */} - - {nowPosition >= 0 && ( - - )} - - {/* Channel rows */} - {guideChannels.map((channel) => { - const channelPrograms = programs.filter( - (p) => p.tvg_id === channel.tvg_id - ); - return ( - - - {channelPrograms.map((prog) => renderProgram(prog, start))} - - - ); - })} - - - - - {/* Modal for program details */} - - {selectedProgram && ( - <> - - {selectedProgram.title} - - - - {dayjs(selectedProgram.start_time).format('h:mma')} -{' '} - {dayjs(selectedProgram.end_time).format('h:mma')} - - - {selectedProgram.description || 'No description available.'} - - - - {/* Only show the Watch button if currently live */} - {now.isAfter(dayjs(selectedProgram.start_time)) && - now.isBefore(dayjs(selectedProgram.end_time)) && ( - - )} - - - - )} - - - ); -} diff --git a/vite/src/pages/Guide.jsx b/frontend/src/pages/Guide.jsx similarity index 72% rename from vite/src/pages/Guide.jsx rename to frontend/src/pages/Guide.jsx index d60ec8ba..c8f4ea58 100644 --- a/vite/src/pages/Guide.jsx +++ b/frontend/src/pages/Guide.jsx @@ -1,26 +1,23 @@ // frontend/src/pages/Guide.js import React, { useMemo, useState, useEffect, useRef } from 'react'; -import { - Box, - Typography, - Paper, - Stack, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Button, - Slide, - CircularProgress, - Backdrop, -} from '@mui/material'; import dayjs from 'dayjs'; import API from '../api'; import useChannelsStore from '../store/channels'; import logo from '../images/logo.png'; import useVideoStore from '../store/useVideoStore'; // NEW import -import useAlertStore from '../store/alerts'; +import { notifications } from '@mantine/notifications'; import useSettingsStore from '../store/settings'; +import { + Title, + Box, + Modal, + Flex, + Button, + Text, + Paper, + Grid, +} from '@mantine/core'; +import './guide.css'; /** Layout constants */ const CHANNEL_WIDTH = 120; // Width of the channel/logo column @@ -33,11 +30,6 @@ const MINUTE_BLOCK_WIDTH = HOUR_WIDTH / (60 / MINUTE_INCREMENT); const MODAL_WIDTH = 600; const MODAL_HEIGHT = 400; -// Slide transition for Dialog -const Transition = React.forwardRef(function Transition(props, ref) { - return ; -}); - export default function TVChannelGuide({ startDate, endDate }) { const { channels } = useChannelsStore(); @@ -46,7 +38,6 @@ export default function TVChannelGuide({ startDate, endDate }) { const [now, setNow] = useState(dayjs()); const [selectedProgram, setSelectedProgram] = useState(null); const [loading, setLoading] = useState(true); - const { showAlert } = useAlertStore(); const { environment: { env_mode }, } = useSettingsStore(); @@ -55,9 +46,9 @@ export default function TVChannelGuide({ startDate, endDate }) { // Load program data once useEffect(() => { - if (!channels || channels.length === 0) { + if (!Object.keys(channels).length === 0) { console.warn('No channels provided or empty channels array'); - showAlert('No channels available', 'error'); + notifications.show({ title: 'No channels available', color: 'red.5' }); setLoading(false); return; } @@ -71,7 +62,7 @@ export default function TVChannelGuide({ startDate, endDate }) { const programIds = [...new Set(fetched.map((p) => p.tvg_id))]; // Filter your Redux/Zustand channels by matching tvg_id - const filteredChannels = channels.filter((ch) => + const filteredChannels = Object.values(channels).filter((ch) => programIds.includes(ch.tvg_id) ); console.log( @@ -217,8 +208,9 @@ export default function TVChannelGuide({ startDate, endDate }) { return ( - + {program.title} - - + + {programStart.format('h:mma')} - {programEnd.format('h:mma')} - + ); } - if (loading) { - return ( - theme.zIndex.drawer + 1, - position: 'fixed', // Ensure it covers the entire page - top: 0, - left: 0, - right: 0, - bottom: 0, - }} - open={loading} - > - - - ); - } - return ( {/* Sticky top bar */} - - + TV Guide - </Typography> - <Typography variant="body2"> - {now.format('dddd, MMMM D, YYYY • h:mm A')} - </Typography> - </Box> + + {now.format('dddd, MMMM D, YYYY • h:mm A')} +
{/* Main layout */} - + {/* Channel Logos Column */} - + ( {/* Sticky timeline header */} - + {hourTimeline.map((time, hourIndex) => ( - {time.format('h:mma')} - + ( {/* Now line */} - + {nowPosition >= 0 && ( - + {channelPrograms.map((prog) => renderProgram(prog, start))} @@ -471,62 +440,36 @@ export default function TVChannelGuide({ startDate, endDate }) { })} - + {/* Modal for program details */} - {selectedProgram && ( <> - - {selectedProgram.title} - - - - {dayjs(selectedProgram.start_time).format('h:mma')} -{' '} - {dayjs(selectedProgram.end_time).format('h:mma')} - - - {selectedProgram.description || 'No description available.'} - - - - {/* Only show the Watch button if currently live */} - {now.isAfter(dayjs(selectedProgram.start_time)) && - now.isBefore(dayjs(selectedProgram.end_time)) && ( - - )} - - + + )} )} - + ); } diff --git a/frontend/src/pages/Home.js b/frontend/src/pages/Home.js deleted file mode 100644 index ac971332..00000000 --- a/frontend/src/pages/Home.js +++ /dev/null @@ -1,14 +0,0 @@ -// src/components/Home.js -import React, { useState } from "react"; - -const Home = () => { - const [newChannel, setNewChannel] = useState(""); - - return ( -
-

Home Page

-
- ); -}; - -export default Home; diff --git a/vite/src/pages/Home.jsx b/frontend/src/pages/Home.jsx similarity index 100% rename from vite/src/pages/Home.jsx rename to frontend/src/pages/Home.jsx diff --git a/frontend/src/pages/Login.js b/frontend/src/pages/Login.js deleted file mode 100644 index 5ed4f203..00000000 --- a/frontend/src/pages/Login.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; -import LoginForm from "../components/forms/LoginForm"; - -const Login = () => { - return ; -}; - -export default Login; diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx new file mode 100644 index 00000000..3e9cdecc --- /dev/null +++ b/frontend/src/pages/Login.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import LoginForm from '../components/forms/LoginForm'; +import SuperuserForm from '../components/forms/SuperuserForm'; +import useAuthStore from '../store/auth'; + +const Login = ({}) => { + const { superuserExists } = useAuthStore(); + + if (!superuserExists) { + return ; + } + + return ; +}; + +export default Login; diff --git a/frontend/src/pages/M3U.js b/frontend/src/pages/M3U.js deleted file mode 100644 index 9fd7e2c2..00000000 --- a/frontend/src/pages/M3U.js +++ /dev/null @@ -1,72 +0,0 @@ -import React, { useState } from "react"; -import useUserAgentsStore from "../store/userAgents"; -import { Box } from "@mui/material"; -import M3UsTable from "../components/tables/M3UsTable"; -import UserAgentsTable from "../components/tables/UserAgentsTable"; -import usePlaylistsStore from "../store/playlists"; -import API from "../api"; -import M3UForm from "../components/forms/M3U"; - -const M3UPage = () => { - const isLoading = useUserAgentsStore((state) => state.isLoading); - const error = useUserAgentsStore((state) => state.error); - const playlists = usePlaylistsStore((state) => state.playlists); - - const [playlist, setPlaylist] = useState(null); - const [playlistModalOpen, setPlaylistModalOpen] = useState(false); - - const [userAgent, setUserAgent] = useState(null); - const [userAgentModalOpen, setUserAgentModalOpen] = useState(false); - - const editUserAgent = async (userAgent = null) => { - setUserAgent(userAgent); - setUserAgentModalOpen(true); - }; - - const editPlaylist = async (playlist = null) => { - setPlaylist(playlist); - setPlaylistModalOpen(true); - }; - - const deleteUserAgent = async (ids) => { - if (Array.isArray(ids)) { - await API.deleteUserAgents(ids); - } else { - await API.deleteUserAgent(ids); - } - }; - - const deletePlaylist = async (id) => { - await API.deletePlaylist(id); - }; - - if (isLoading) return
Loading...
; - if (error) return
Error: {error}
; - - return ( - - - - - - - - - - setPlaylistModalOpen(false)} - /> - - ); -}; - -export default M3UPage; diff --git a/vite/src/pages/M3U.jsx b/frontend/src/pages/M3U.jsx similarity index 96% rename from vite/src/pages/M3U.jsx rename to frontend/src/pages/M3U.jsx index 71cc729e..acc883e8 100644 --- a/vite/src/pages/M3U.jsx +++ b/frontend/src/pages/M3U.jsx @@ -13,11 +13,12 @@ const M3UPage = () => { return ( diff --git a/frontend/src/pages/Settings.js b/frontend/src/pages/Settings.js deleted file mode 100644 index cd068750..00000000 --- a/frontend/src/pages/Settings.js +++ /dev/null @@ -1,197 +0,0 @@ -import React, { useEffect } from 'react'; -import { - Grid as Grid2, - Box, - Container, - Typography, - FormControl, - Select, - MenuItem, - CircularProgress, - InputLabel, - Button, -} from '@mui/material'; -import { useFormik } from 'formik'; -import * as Yup from 'yup'; - -import API from '../api'; -import useSettingsStore from '../store/settings'; -import useUserAgentsStore from '../store/userAgents'; -import useStreamProfilesStore from '../store/streamProfiles'; - -const SettingsPage = () => { - const { settings } = useSettingsStore(); - const { userAgents } = useUserAgentsStore(); - const { profiles: streamProfiles } = useStreamProfilesStore(); - - // Add your region choices here: - const regionChoices = [ - { value: 'us', label: 'US' }, - { value: 'uk', label: 'UK' }, - { value: 'nl', label: 'NL' }, - { value: 'de', label: 'DE' }, - // Add more if needed - ]; - - const formik = useFormik({ - initialValues: { - 'default-user-agent': '', - 'default-stream-profile': '', - 'preferred-region': '', - }, - validationSchema: Yup.object({ - 'default-user-agent': Yup.string().required('User-Agent is required'), - 'default-stream-profile': Yup.string().required( - 'Stream Profile is required' - ), - // The region is optional or required as you prefer - // 'preferred-region': Yup.string().required('Region is required'), - }), - onSubmit: async (values, { setSubmitting, resetForm }) => { - const changedSettings = {}; - for (const settingKey in values) { - // If the user changed the setting’s value from what’s in the DB: - if (String(values[settingKey]) !== String(settings[settingKey].value)) { - changedSettings[settingKey] = values[settingKey]; - } - } - - // Update each changed setting in the backend - for (const updatedKey in changedSettings) { - await API.updateSetting({ - ...settings[updatedKey], - value: changedSettings[updatedKey], - }); - } - - setSubmitting(false); - // Don’t necessarily resetForm, in case the user wants to see new values - }, - }); - - // Initialize form values once settings / userAgents / profiles are loaded - useEffect(() => { - formik.setValues( - Object.values(settings).reduce((acc, setting) => { - // If the setting’s value is numeric, parse it - // Otherwise, just store as string - const possibleNumber = parseInt(setting.value, 10); - acc[setting.key] = isNaN(possibleNumber) - ? setting.value - : possibleNumber; - return acc; - }, {}) - ); - // eslint-disable-next-line - }, [settings, userAgents, streamProfiles]); - - return ( - - - - Settings - - -
- - {/* Default User-Agent */} - - - Default User-Agent - - - - - {/* Default Stream Profile */} - - - - Default Stream Profile - - - - - - {/* Preferred Region */} - - {/* Only render if you do indeed have "preferred-region" in the DB */} - {settings['preferred-region'] && ( - - Preferred Region - - - )} - - - - - - -
-
-
- ); -}; - -export default SettingsPage; diff --git a/vite/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx similarity index 100% rename from vite/src/pages/Settings.jsx rename to frontend/src/pages/Settings.jsx diff --git a/frontend/src/pages/StreamProfiles.js b/frontend/src/pages/StreamProfiles.js deleted file mode 100644 index 615bace2..00000000 --- a/frontend/src/pages/StreamProfiles.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; -import StreamProfilesTable from "../components/tables/StreamProfilesTable"; - -const StreamProfilesPage = () => { - return ; -}; - -export default StreamProfilesPage; diff --git a/vite/src/pages/StreamProfiles.jsx b/frontend/src/pages/StreamProfiles.jsx similarity index 58% rename from vite/src/pages/StreamProfiles.jsx rename to frontend/src/pages/StreamProfiles.jsx index 190779ea..42dda005 100644 --- a/vite/src/pages/StreamProfiles.jsx +++ b/frontend/src/pages/StreamProfiles.jsx @@ -1,8 +1,13 @@ import React from 'react'; import StreamProfilesTable from '../components/tables/StreamProfilesTable'; +import { Box } from '@mantine/core'; const StreamProfilesPage = () => { - return ; + return ( + + + + ); }; export default StreamProfilesPage; diff --git a/frontend/src/pages/guide.css b/frontend/src/pages/guide.css new file mode 100644 index 00000000..93161a5e --- /dev/null +++ b/frontend/src/pages/guide.css @@ -0,0 +1,25 @@ +.tv-guide .guide-program-container .guide-program { + position: relative; + left: 2px; + top: 2px; + padding: 10px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + border-radius: 8px; + background: linear-gradient(to right, #2d3748, #2d3748); /* Default background */ + color: #fff; + transition: background 0.3s ease; +} + +.tv-guide .guide-program-container .guide-program.live { + background: linear-gradient(to right, #1e3a8a, #2c5282); +} + +.tv-guide .guide-program-container .guide-program.live:hover { + background: linear-gradient(to right, #1e3a8a, #2a4365); +} + +.tv-guide .guide-program-container .guide-program.not-live:hover { + background: linear-gradient(to right, #2d3748, #1a202c); +} diff --git a/frontend/src/reportWebVitals.js b/frontend/src/reportWebVitals.js deleted file mode 100644 index 5253d3ad..00000000 --- a/frontend/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = onPerfEntry => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/frontend/src/setupTests.js b/frontend/src/setupTests.js deleted file mode 100644 index 8f2609b7..00000000 --- a/frontend/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; diff --git a/frontend/src/store/alerts.js b/frontend/src/store/alerts.js deleted file mode 100644 index b51076a3..00000000 --- a/frontend/src/store/alerts.js +++ /dev/null @@ -1,24 +0,0 @@ -// frontend/src/store/useAlertStore.js -import { create } from 'zustand'; - -/** - * Global store to track whether a floating video is visible and which URL is playing. - */ -const useAlertStore = create((set) => ({ - open: false, - message: '', - severity: 'info', - - showAlert: (message, severity = 'info') => - set({ - open: true, - message, - severity, - }), - - hideAlert: () => { - set({ open: false }); - }, -})); - -export default useAlertStore; diff --git a/frontend/src/store/auth.js b/frontend/src/store/auth.js deleted file mode 100644 index d72f9f0f..00000000 --- a/frontend/src/store/auth.js +++ /dev/null @@ -1,129 +0,0 @@ -import { create } from 'zustand'; -import API from '../api'; -import useChannelsStore from './channels'; -import useUserAgentsStore from './userAgents'; -import usePlaylistsStore from './playlists'; -import useEPGsStore from './epgs'; -import useStreamProfilesStore from './streamProfiles'; -import useSettingsStore from './settings'; - -const decodeToken = (token) => { - if (!token) return null; - const payload = token.split('.')[1]; - const decodedPayload = JSON.parse(atob(payload)); - return decodedPayload.exp; -}; - -const isTokenExpired = (expirationTime) => { - const now = Math.floor(Date.now() / 1000); - return now >= expirationTime; -}; - -const useAuthStore = create((set, get) => ({ - accessToken: localStorage.getItem('accessToken') || null, - refreshToken: localStorage.getItem('refreshToken') || null, - tokenExpiration: localStorage.getItem('tokenExpiration') || null, - isAuthenticated: false, - - setIsAuthenticated: (isAuthenticated) => set({ isAuthenticated }), - - initData: async () => { - await Promise.all([ - useChannelsStore.getState().fetchChannels(), - useChannelsStore.getState().fetchChannelGroups(), - useUserAgentsStore.getState().fetchUserAgents(), - usePlaylistsStore.getState().fetchPlaylists(), - useEPGsStore.getState().fetchEPGs(), - useStreamProfilesStore.getState().fetchProfiles(), - useSettingsStore.getState().fetchSettings(), - ]); - }, - - getToken: async () => { - const tokenExpiration = localStorage.getItem('tokenExpiration'); - let accessToken = null; - if (isTokenExpired(tokenExpiration)) { - accessToken = await get().refreshToken(); - } else { - accessToken = localStorage.getItem('accessToken'); - } - - return accessToken; - }, - - // Action to login - login: async ({ username, password }) => { - try { - const response = await API.login(username, password); - if (response.access) { - const expiration = decodeToken(response.access); - set({ - accessToken: response.access, - refreshToken: response.refresh, - tokenExpiration: expiration, // 1 hour from now - isAuthenticated: true, - }); - // Store in localStorage - localStorage.setItem('accessToken', response.access); - localStorage.setItem('refreshToken', response.refresh); - localStorage.setItem('tokenExpiration', expiration); - } - } catch (error) { - console.error('Login failed:', error); - } - }, - - // Action to refresh the token - refreshToken: async () => { - const refreshToken = localStorage.getItem('refreshToken'); - if (!refreshToken) return; - - try { - const data = await API.refreshToken(refreshToken); - if (data.access) { - set({ - accessToken: data.access, - tokenExpiration: decodeToken(data.access), - isAuthenticated: true, - }); - localStorage.setItem('accessToken', data.access); - localStorage.setItem('tokenExpiration', decodeToken(data.access)); - - return true; - } - } catch (error) { - console.error('Token refresh failed:', error); - get().logout(); - } - - return false; - }, - - // Action to logout - logout: () => { - set({ - accessToken: null, - refreshToken: null, - tokenExpiration: null, - isAuthenticated: false, - }); - localStorage.removeItem('accessToken'); - localStorage.removeItem('refreshToken'); - localStorage.removeItem('tokenExpiration'); - }, - - initializeAuth: async () => { - const refreshToken = localStorage.getItem('refreshToken') || null; - - if (refreshToken) { - const loggedIn = await get().refreshToken(); - if (loggedIn) { - return true; - } - } - - return false; - }, -})); - -export default useAuthStore; diff --git a/vite/src/store/auth.jsx b/frontend/src/store/auth.jsx similarity index 97% rename from vite/src/store/auth.jsx rename to frontend/src/store/auth.jsx index d72f9f0f..23c8596c 100644 --- a/vite/src/store/auth.jsx +++ b/frontend/src/store/auth.jsx @@ -23,10 +23,13 @@ const useAuthStore = create((set, get) => ({ accessToken: localStorage.getItem('accessToken') || null, refreshToken: localStorage.getItem('refreshToken') || null, tokenExpiration: localStorage.getItem('tokenExpiration') || null, + superuserExists: true, isAuthenticated: false, setIsAuthenticated: (isAuthenticated) => set({ isAuthenticated }), + setSuperuserExists: (superuserExists) => set({ superuserExists }), + initData: async () => { await Promise.all([ useChannelsStore.getState().fetchChannels(), diff --git a/frontend/src/store/channels.js b/frontend/src/store/channels.js deleted file mode 100644 index dde00729..00000000 --- a/frontend/src/store/channels.js +++ /dev/null @@ -1,89 +0,0 @@ -import { create } from 'zustand'; -import api from '../api'; - -const useChannelsStore = create((set) => ({ - channels: [], - channelGroups: [], - channelsPageSelection: [], - isLoading: false, - error: null, - - fetchChannels: async () => { - set({ isLoading: true, error: null }); - try { - const channels = await api.getChannels(); - set({ - channels: channels.reduce((acc, channel) => { - acc[channel.id] = channel; - return acc; - }, {}), - isLoading: false, - }); - } catch (error) { - console.error('Failed to fetch channels:', error); - set({ error: 'Failed to load channels.', isLoading: false }); - } - }, - - fetchChannelGroups: async () => { - set({ isLoading: true, error: null }); - try { - const channelGroups = await api.getChannelGroups(); - set({ channelGroups: channelGroups, isLoading: false }); - } catch (error) { - console.error('Failed to fetch channel groups:', error); - set({ error: 'Failed to load channel groups.', isLoading: false }); - } - }, - - addChannel: (newChannel) => - set((state) => ({ - channels: { - ...state.channels, - [newChannel.id]: newChannel, - }, - })), - - addChannels: (newChannels) => - set((state) => ({ - channels: { - ...state.channels, - ...newChannels, - }, - })), - - updateChannel: (channel) => - set((state) => ({ - channels: { - ...state.channels, - [channel.id]: channel, - }, - })), - - removeChannels: (channelIds) => - set((state) => { - const updatedChannels = { ...state.channels }; - for (const id of channelIds) { - delete updatedChannels[id]; - } - - return { channels: updatedChannels }; - }), - - addChannelGroup: (newChannelGroup) => - set((state) => ({ - channelGroups: [...state.channelGroups, newChannelGroup], - })), - - updateChannelGroup: (channelGroup) => - set((state) => ({ - channelGroups: state.channelGroups.map((group) => - group.id === channelGroup.id ? channelGroup : group - ), - })), - - setChannelsPageSelection: (channelsPageSelection) => - set((state) => ({ channelsPageSelection })), -})); - -export default useChannelsStore; diff --git a/vite/src/store/channels.jsx b/frontend/src/store/channels.jsx similarity index 100% rename from vite/src/store/channels.jsx rename to frontend/src/store/channels.jsx diff --git a/frontend/src/store/epgs.js b/frontend/src/store/epgs.js deleted file mode 100644 index 14cfd623..00000000 --- a/frontend/src/store/epgs.js +++ /dev/null @@ -1,31 +0,0 @@ -import { create } from "zustand"; -import api from "../api"; - -const useEPGsStore = create((set) => ({ - epgs: [], - isLoading: false, - error: null, - - fetchEPGs: async () => { - set({ isLoading: true, error: null }); - try { - const epgs = await api.getEPGs(); - set({ epgs: epgs, isLoading: false }); - } catch (error) { - console.error("Failed to fetch epgs:", error); - set({ error: "Failed to load epgs.", isLoading: false }); - } - }, - - addEPG: (newPlaylist) => - set((state) => ({ - epgs: [...state.epgs, newPlaylist], - })), - - removeEPGs: (epgIds) => - set((state) => ({ - epgs: state.epgs.filter((epg) => !epgIds.includes(epg.id)), - })), -})); - -export default useEPGsStore; diff --git a/vite/src/store/epgs.jsx b/frontend/src/store/epgs.jsx similarity index 100% rename from vite/src/store/epgs.jsx rename to frontend/src/store/epgs.jsx diff --git a/frontend/src/store/playlists.js b/frontend/src/store/playlists.js deleted file mode 100644 index 274c943a..00000000 --- a/frontend/src/store/playlists.js +++ /dev/null @@ -1,65 +0,0 @@ -import { create } from 'zustand'; -import api from '../api'; - -const usePlaylistsStore = create((set) => ({ - playlists: [], - profiles: {}, - isLoading: false, - error: null, - - fetchPlaylists: async () => { - set({ isLoading: true, error: null }); - try { - const playlists = await api.getPlaylists(); - set({ - playlists: playlists, - isLoading: false, - profiles: playlists.reduce((acc, playlist) => { - acc[playlist.id] = playlist.profiles; - return acc; - }, {}), - }); - } catch (error) { - console.error('Failed to fetch playlists:', error); - set({ error: 'Failed to load playlists.', isLoading: false }); - } - }, - - addPlaylist: (newPlaylist) => - set((state) => ({ - playlists: [...state.playlists, newPlaylist], - profiles: { - ...state.profiles, - [newPlaylist.id]: newPlaylist.profiles, - }, - })), - - updatePlaylist: (playlist) => - set((state) => ({ - playlists: state.playlists.map((pl) => - pl.id === playlist.id ? playlist : pl - ), - profiles: { - ...state.profiles, - [playlist.id]: playlist.profiles, - }, - })), - - updateProfiles: (playlistId, profiles) => - set((state) => ({ - profiles: { - ...state.profiles, - [playlistId]: profiles, - }, - })), - - removePlaylists: (playlistIds) => - set((state) => ({ - playlists: state.playlists.filter( - (playlist) => !playlistIds.includes(playlist.id) - ), - // @TODO: remove playlist profiles here - })), -})); - -export default usePlaylistsStore; diff --git a/vite/src/store/playlists.jsx b/frontend/src/store/playlists.jsx similarity index 100% rename from vite/src/store/playlists.jsx rename to frontend/src/store/playlists.jsx diff --git a/frontend/src/store/settings.js b/frontend/src/store/settings.js deleted file mode 100644 index 5dffbed6..00000000 --- a/frontend/src/store/settings.js +++ /dev/null @@ -1,34 +0,0 @@ -import { create } from 'zustand'; -import api from '../api'; - -const useSettingsStore = create((set) => ({ - settings: {}, - environment: {}, - isLoading: false, - error: null, - - fetchSettings: async () => { - set({ isLoading: true, error: null }); - try { - const settings = await api.getSettings(); - const env = await api.getEnvironmentSettings(); - set({ - settings: settings.reduce((acc, setting) => { - acc[setting.key] = setting; - return acc; - }, {}), - isLoading: false, - environment: env, - }); - } catch (error) { - set({ error: 'Failed to load settings.', isLoading: false }); - } - }, - - updateSetting: (setting) => - set((state) => ({ - settings: { ...state.settings, [setting.key]: setting }, - })), -})); - -export default useSettingsStore; diff --git a/vite/src/store/settings.jsx b/frontend/src/store/settings.jsx similarity index 100% rename from vite/src/store/settings.jsx rename to frontend/src/store/settings.jsx diff --git a/frontend/src/store/streamProfiles.js b/frontend/src/store/streamProfiles.js deleted file mode 100644 index 1b98c662..00000000 --- a/frontend/src/store/streamProfiles.js +++ /dev/null @@ -1,40 +0,0 @@ -import { create } from 'zustand'; -import api from '../api'; - -const useStreamProfilesStore = create((set) => ({ - profiles: [], - isLoading: false, - error: null, - - fetchProfiles: async () => { - set({ isLoading: true, error: null }); - try { - const profiles = await api.getStreamProfiles(); - set({ profiles: profiles, isLoading: false }); - } catch (error) { - console.error('Failed to fetch profiles:', error); - set({ error: 'Failed to load profiles.', isLoading: false }); - } - }, - - addStreamProfile: (profile) => - set((state) => ({ - profiles: [...state.profiles, profile], - })), - - updateStreamProfile: (profile) => - set((state) => ({ - profiles: state.profiles.map((prof) => - prof.id === profile.id ? profile : prof - ), - })), - - removeStreamProfiles: (propfileIds) => - set((state) => ({ - profiles: state.profiles.filter( - (profile) => !propfileIds.includes(profile.id) - ), - })), -})); - -export default useStreamProfilesStore; diff --git a/vite/src/store/streamProfiles.jsx b/frontend/src/store/streamProfiles.jsx similarity index 100% rename from vite/src/store/streamProfiles.jsx rename to frontend/src/store/streamProfiles.jsx diff --git a/frontend/src/store/streams.js b/frontend/src/store/streams.js deleted file mode 100644 index 9f477c20..00000000 --- a/frontend/src/store/streams.js +++ /dev/null @@ -1,41 +0,0 @@ -import { create } from 'zustand'; -import api from '../api'; - -const useStreamsStore = create((set) => ({ - streams: [], - count: 0, - isLoading: false, - error: null, - - fetchStreams: async () => { - set({ isLoading: true, error: null }); - try { - const response = await api.getStreams(); - set({ - streams: response.results, - count: response.count, - isLoading: false, - }); - } catch (error) { - console.error('Failed to fetch streams:', error); - set({ error: 'Failed to load streams.', isLoading: false }); - } - }, - - addStream: (stream) => - set((state) => ({ - streams: [...state.streams, stream], - })), - - updateStream: (stream) => - set((state) => ({ - streams: state.streams.map((st) => (st.id === stream.id ? stream : st)), - })), - - removeStreams: (streamIds) => - set((state) => ({ - streams: state.streams.filter((stream) => !streamIds.includes(stream.id)), - })), -})); - -export default useStreamsStore; diff --git a/vite/src/store/streams.jsx b/frontend/src/store/streams.jsx similarity index 100% rename from vite/src/store/streams.jsx rename to frontend/src/store/streams.jsx diff --git a/frontend/src/store/useVideoStore.js b/frontend/src/store/useVideoStore.js deleted file mode 100644 index 0229552a..00000000 --- a/frontend/src/store/useVideoStore.js +++ /dev/null @@ -1,24 +0,0 @@ -// frontend/src/store/useVideoStore.js -import { create } from 'zustand'; - -/** - * Global store to track whether a floating video is visible and which URL is playing. - */ -const useVideoStore = create((set) => ({ - isVisible: false, - streamUrl: null, - - showVideo: (url) => - set({ - isVisible: true, - streamUrl: url, - }), - - hideVideo: () => - set({ - isVisible: false, - streamUrl: null, - }), -})); - -export default useVideoStore; diff --git a/vite/src/store/useVideoStore.jsx b/frontend/src/store/useVideoStore.jsx similarity index 100% rename from vite/src/store/useVideoStore.jsx rename to frontend/src/store/useVideoStore.jsx diff --git a/frontend/src/store/userAgents.js b/frontend/src/store/userAgents.js deleted file mode 100644 index 6693e830..00000000 --- a/frontend/src/store/userAgents.js +++ /dev/null @@ -1,40 +0,0 @@ -import { create } from "zustand"; -import api from "../api"; - -const useUserAgentsStore = create((set) => ({ - userAgents: [], - isLoading: false, - error: null, - - fetchUserAgents: async () => { - set({ isLoading: true, error: null }); - try { - const userAgents = await api.getUserAgents(); - set({ userAgents: userAgents, isLoading: false }); - } catch (error) { - console.error("Failed to fetch userAgents:", error); - set({ error: "Failed to load userAgents.", isLoading: false }); - } - }, - - addUserAgent: (userAgent) => - set((state) => ({ - userAgents: [...state.userAgents, userAgent], - })), - - updateUserAgent: (userAgent) => - set((state) => ({ - userAgents: state.userAgents.map((ua) => - ua.id === userAgent.id ? userAgent : ua, - ), - })), - - removeUserAgents: (userAgentIds) => - set((state) => ({ - userAgents: state.userAgents.filter( - (userAgent) => !userAgentIds.includes(userAgent.id), - ), - })), -})); - -export default useUserAgentsStore; diff --git a/vite/src/store/userAgents.jsx b/frontend/src/store/userAgents.jsx similarity index 100% rename from vite/src/store/userAgents.jsx rename to frontend/src/store/userAgents.jsx diff --git a/frontend/src/theme.js b/frontend/src/theme.js deleted file mode 100644 index 2fdb34bb..00000000 --- a/frontend/src/theme.js +++ /dev/null @@ -1,92 +0,0 @@ -// src/theme.js -import { createTheme } from '@mui/material/styles'; - -const theme = createTheme({ - palette: { - mode: 'dark', - background: { - default: '#18181b', // Global background color (Tailwind zinc-900) - paper: '#27272a', // Paper background (Tailwind zinc-800) - }, - primary: { - main: '#4A90E2', - contrastText: '#FFFFFF', - }, - secondary: { - main: '#F5A623', - contrastText: '#FFFFFF', - }, - text: { - primary: '#FFFFFF', - secondary: '#d4d4d8', // Updated secondary text color (Tailwind zinc-300) - }, - // Custom colors for components (chip buttons, borders, etc.) - custom: { - // For chip buttons: - greenMain: '#90C43E', - greenHoverBg: 'rgba(144,196,62,0.1)', - - indigoMain: '#4F39F6', - indigoHoverBg: 'rgba(79,57,246,0.1)', - - greyBorder: '#707070', - greyHoverBg: 'rgba(112,112,112,0.1)', - greyText: '#a0a0a0', - - // Common border colors: - borderDefault: '#3f3f46', // Tailwind zinc-700 - borderHover: '#5f5f66', // Approximate Tailwind zinc-600 - - // For the "Add" button: - successBorder: '#00a63e', - successBg: '#0d542b', - successBgHover: '#0a4020', - successIcon: '#05DF72', - }, - }, - typography: { - // Set Inter as the global font - fontFamily: 'Inter, sans-serif', - h1: { fontSize: '2.5rem', fontWeight: 700 }, - h2: { fontSize: '2rem', fontWeight: 700 }, - body1: { fontSize: '1rem' }, - }, - spacing: 8, - components: { - MuiButton: { - styleOverrides: { - root: { - borderRadius: 4, - textTransform: 'none', - fontWeight: 500, - }, - }, - }, - MuiDrawer: { - styleOverrides: { - paper: { - backgroundColor: '#27272a', // Use the same paper color - color: '#FFFFFF', - }, - }, - }, - MuiAppBar: { - styleOverrides: { - root: { - backgroundColor: '#18181b', - }, - }, - }, - }, - custom: { - sidebar: { - activeBackground: 'rgba(21, 69, 62, 0.67)', - activeBorder: '#14917e', - hoverBackground: '#27272a', - hoverBorder: '#3f3f46', - fontFamily: 'Inter, sans-serif', - }, - }, -}); - -export default theme; diff --git a/vite/src/theme.jsx b/frontend/src/theme.jsx similarity index 100% rename from vite/src/theme.jsx rename to frontend/src/theme.jsx diff --git a/vite/vite.config.js b/frontend/vite.config.js similarity index 100% rename from vite/vite.config.js rename to frontend/vite.config.js diff --git a/vite/.gitignore b/vite/.gitignore deleted file mode 100644 index a547bf36..00000000 --- a/vite/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/vite/README.md b/vite/README.md deleted file mode 100644 index fd3b758d..00000000 --- a/vite/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# React + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/vite/package-lock.json b/vite/package-lock.json deleted file mode 100644 index 0bb89a2e..00000000 --- a/vite/package-lock.json +++ /dev/null @@ -1,4665 +0,0 @@ -{ - "name": "vite", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "vite", - "version": "0.0.0", - "dependencies": { - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.0", - "@mantine/core": "^7.17.1", - "@mantine/dates": "^7.17.1", - "@mantine/hooks": "^7.17.1", - "@mui/icons-material": "^6.4.7", - "@mui/material": "^6.4.7", - "@mui/x-date-pickers": "^7.27.3", - "@tabler/icons-react": "^3.31.0", - "axios": "^1.8.2", - "clsx": "^2.1.1", - "dayjs": "^1.11.13", - "formik": "^2.4.6", - "hls.js": "^1.5.20", - "lucide-react": "^0.479.0", - "mantine-react-table": "^2.0.0-beta.9", - "material-react-table": "^3.2.1", - "mpegts.js": "^1.8.0", - "prettier": "^3.5.3", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-draggable": "^4.4.6", - "react-pro-sidebar": "^1.1.0", - "react-router-dom": "^7.3.0", - "video.js": "^8.21.0", - "yup": "^1.6.1", - "zustand": "^5.0.3" - }, - "devDependencies": { - "@eslint/js": "^9.21.0", - "@types/react": "^19.0.10", - "@types/react-dom": "^19.0.4", - "@vitejs/plugin-react-swc": "^3.8.0", - "eslint": "^9.21.0", - "eslint-plugin-react-hooks": "^5.1.0", - "eslint-plugin-react-refresh": "^0.4.19", - "globals": "^15.15.0", - "vite": "^6.2.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.26.9" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", - "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", - "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emotion/babel-plugin": { - "version": "11.13.5", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", - "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.2", - "@emotion/memoize": "^0.9.0", - "@emotion/serialize": "^1.3.3", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/cache": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", - "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.9.0", - "@emotion/sheet": "^1.4.0", - "@emotion/utils": "^1.4.2", - "@emotion/weak-memoize": "^0.4.0", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", - "license": "MIT" - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", - "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.9.0" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT" - }, - "node_modules/@emotion/react": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", - "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.13.5", - "@emotion/cache": "^11.14.0", - "@emotion/serialize": "^1.3.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", - "@emotion/utils": "^1.4.2", - "@emotion/weak-memoize": "^0.4.0", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", - "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", - "license": "MIT", - "dependencies": { - "@emotion/hash": "^0.9.2", - "@emotion/memoize": "^0.9.0", - "@emotion/unitless": "^0.10.0", - "@emotion/utils": "^1.4.2", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", - "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", - "license": "MIT" - }, - "node_modules/@emotion/styled": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", - "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.13.5", - "@emotion/is-prop-valid": "^1.3.0", - "@emotion/serialize": "^1.3.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", - "@emotion/utils": "^1.4.2" - }, - "peerDependencies": { - "@emotion/react": "^11.0.0-rc.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/unitless": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", - "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", - "license": "MIT" - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", - "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", - "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", - "license": "MIT" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", - "license": "MIT" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", - "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", - "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", - "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", - "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", - "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", - "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", - "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", - "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", - "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", - "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", - "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", - "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", - "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", - "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", - "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", - "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", - "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", - "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", - "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", - "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", - "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", - "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", - "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", - "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", - "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz", - "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", - "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", - "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.12.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.9" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", - "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.9" - } - }, - "node_modules/@floating-ui/react": { - "version": "0.26.28", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", - "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", - "license": "MIT" - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mantine/core": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.17.1.tgz", - "integrity": "sha512-V8O3Ftq4la4I4wNDkTfH4Slkt/pCEU32pTE/DkO46zua0VFxfOAJeLjaol0s11//T+bXx82DtjMsd9APWPuFhA==", - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.26.28", - "clsx": "^2.1.1", - "react-number-format": "^5.4.3", - "react-remove-scroll": "^2.6.2", - "react-textarea-autosize": "8.5.6", - "type-fest": "^4.27.0" - }, - "peerDependencies": { - "@mantine/hooks": "7.17.1", - "react": "^18.x || ^19.x", - "react-dom": "^18.x || ^19.x" - } - }, - "node_modules/@mantine/core/node_modules/type-fest": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz", - "integrity": "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@mantine/dates": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-7.17.1.tgz", - "integrity": "sha512-L9MlIDb528RpznUeeW71xS4q3lYGolElz/f7xGRXEu9gHLaNJufbxroTw2N8RC6p/+RN1ZrSXEsjlr2euiofAw==", - "license": "MIT", - "dependencies": { - "clsx": "^2.1.1" - }, - "peerDependencies": { - "@mantine/core": "7.17.1", - "@mantine/hooks": "7.17.1", - "dayjs": ">=1.0.0", - "react": "^18.x || ^19.x", - "react-dom": "^18.x || ^19.x" - } - }, - "node_modules/@mantine/hooks": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.17.1.tgz", - "integrity": "sha512-mkHLrXMPd5xdI5WD7UOLwNEpdh/i6A7HaRDTXvjDE2/S0N8VmAE+BlvdyvWRMi7ODp2zVqJdP8cF1tgUn+Z0fA==", - "license": "MIT", - "peerDependencies": { - "react": "^18.x || ^19.x" - } - }, - "node_modules/@mui/core-downloads-tracker": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.7.tgz", - "integrity": "sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - } - }, - "node_modules/@mui/icons-material": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.7.tgz", - "integrity": "sha512-Rk8cs9ufQoLBw582Rdqq7fnSXXZTqhYRbpe1Y5SAz9lJKZP3CIdrj0PfG8HJLGw1hrsHFN/rkkm70IDzhJsG1g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@mui/material": "^6.4.7", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/material": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.7.tgz", - "integrity": "sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.4.7", - "@mui/system": "^6.4.7", - "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.6", - "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.12", - "clsx": "^2.1.1", - "csstype": "^3.1.3", - "prop-types": "^15.8.1", - "react-is": "^19.0.0", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.4.7", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@mui/material-pigment-css": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/private-theming": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.6.tgz", - "integrity": "sha512-T5FxdPzCELuOrhpA2g4Pi6241HAxRwZudzAuL9vBvniuB5YU82HCmrARw32AuCiyTfWzbrYGGpZ4zyeqqp9RvQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.6", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/styled-engine": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.6.tgz", - "integrity": "sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@emotion/cache": "^11.13.5", - "@emotion/serialize": "^1.3.3", - "@emotion/sheet": "^1.4.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/system": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.7.tgz", - "integrity": "sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.4.6", - "@mui/styled-engine": "^6.4.6", - "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.6", - "clsx": "^2.1.1", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/types": { - "version": "7.2.21", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", - "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", - "license": "MIT", - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/utils": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.6.tgz", - "integrity": "sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/types": "^7.2.21", - "@types/prop-types": "^15.7.14", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-is": "^19.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/x-date-pickers": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.27.3.tgz", - "integrity": "sha512-igfKTPC4ZVCmS5j/NXcXBtj/hHseQHzRpCpIB1PMnJGhMdRYXnz8qZz5XhlNBKlzJVXkGu6Uil+obZpCLNj1xg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^5.16.6 || ^6.0.0", - "@mui/x-internals": "7.26.0", - "@types/react-transition-group": "^4.4.11", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14 || ^6.0.0", - "@mui/system": "^5.15.14 || ^6.0.0", - "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", - "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", - "dayjs": "^1.10.7", - "luxon": "^3.0.2", - "moment": "^2.29.4", - "moment-hijri": "^2.1.2 || ^3.0.0", - "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "date-fns": { - "optional": true - }, - "date-fns-jalali": { - "optional": true - }, - "dayjs": { - "optional": true - }, - "luxon": { - "optional": true - }, - "moment": { - "optional": true - }, - "moment-hijri": { - "optional": true - }, - "moment-jalaali": { - "optional": true - } - } - }, - "node_modules/@mui/x-internals": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.26.0.tgz", - "integrity": "sha512-VxTCYQcZ02d3190pdvys2TDg9pgbvewAVakEopiOgReKAUhLdRlgGJHcOA/eAuGLyK1YIo26A6Ow6ZKlSRLwMg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^5.16.6 || ^6.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", - "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", - "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", - "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", - "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", - "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", - "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", - "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", - "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", - "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", - "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", - "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", - "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", - "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", - "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", - "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", - "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", - "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", - "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", - "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@swc/core": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.8.tgz", - "integrity": "sha512-UAL+EULxrc0J73flwYHfu29mO8CONpDJiQv1QPDXsyCvDUcEhqAqUROVTgC+wtJCFFqMQdyr4stAA5/s0KSOmA==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.19" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-darwin-arm64": "1.11.8", - "@swc/core-darwin-x64": "1.11.8", - "@swc/core-linux-arm-gnueabihf": "1.11.8", - "@swc/core-linux-arm64-gnu": "1.11.8", - "@swc/core-linux-arm64-musl": "1.11.8", - "@swc/core-linux-x64-gnu": "1.11.8", - "@swc/core-linux-x64-musl": "1.11.8", - "@swc/core-win32-arm64-msvc": "1.11.8", - "@swc/core-win32-ia32-msvc": "1.11.8", - "@swc/core-win32-x64-msvc": "1.11.8" - }, - "peerDependencies": { - "@swc/helpers": "*" - }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } - } - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.8.tgz", - "integrity": "sha512-rrSsunyJWpHN+5V1zumndwSSifmIeFQBK9i2RMQQp15PgbgUNxHK5qoET1n20pcUrmZeT6jmJaEWlQchkV//Og==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.8.tgz", - "integrity": "sha512-44goLqQuuo0HgWnG8qC+ZFw/qnjCVVeqffhzFr9WAXXotogVaxM8ze6egE58VWrfEc8me8yCcxOYL9RbtjhS/Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.8.tgz", - "integrity": "sha512-Mzo8umKlhTWwF1v8SLuTM1z2A+P43UVhf4R8RZDhzIRBuB2NkeyE+c0gexIOJBuGSIATryuAF4O4luDu727D1w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.8.tgz", - "integrity": "sha512-EyhO6U+QdoGYC1MeHOR0pyaaSaKYyNuT4FQNZ1eZIbnuueXpuICC7iNmLIOfr3LE5bVWcZ7NKGVPlM2StJEcgA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.8.tgz", - "integrity": "sha512-QU6wOkZnS6/QuBN1MHD6G2BgFxB0AclvTVGbqYkRA7MsVkcC29PffESqzTXnypzB252/XkhQjoB2JIt9rPYf6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.8.tgz", - "integrity": "sha512-r72onUEIU1iJi9EUws3R28pztQ/eM3EshNpsPRBfuLwKy+qn3et55vXOyDhIjGCUph5Eg2Yn8H3h6MTxDdLd+w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.8.tgz", - "integrity": "sha512-294k8cLpO103++f4ZUEDr3vnBeUfPitW6G0a3qeVZuoXFhFgaW7ANZIWknUc14WiLOMfMecphJAEiy9C8OeYSw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.8.tgz", - "integrity": "sha512-EbjOzQ+B85rumHyeesBYxZ+hq3ZQn+YAAT1ZNE9xW1/8SuLoBmHy/K9YniRGVDq/2NRmp5kI5+5h5TX0asIS9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.8.tgz", - "integrity": "sha512-Z+FF5kgLHfQWIZ1KPdeInToXLzbY0sMAashjd/igKeP1Lz0qKXVAK+rpn6ASJi85Fn8wTftCGCyQUkRVn0bTDg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.8.tgz", - "integrity": "sha512-j6B6N0hChCeAISS6xp/hh6zR5CSCr037BAjCxNLsT8TGe5D+gYZ57heswUWXRH8eMKiRDGiLCYpPB2pkTqxCSw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@swc/types": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.19.tgz", - "integrity": "sha512-WkAZaAfj44kh/UFdAQcrMP1I0nwRqpt27u+08LMBYMqmQfwwMofYoMh/48NGkMMRfC4ynpfwRbJuu8ErfNloeA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3" - } - }, - "node_modules/@tabler/icons": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.31.0.tgz", - "integrity": "sha512-dblAdeKY3+GA1U+Q9eziZ0ooVlZMHsE8dqP0RkwvRtEsAULoKOYaCUOcJ4oW1DjWegdxk++UAt2SlQVnmeHv+g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/codecalm" - } - }, - "node_modules/@tabler/icons-react": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.31.0.tgz", - "integrity": "sha512-2rrCM5y/VnaVKnORpDdAua9SEGuJKVqPtWxeQ/vUVsgaUx30LDgBZph7/lterXxDY1IKR6NO//HDhWiifXTi3w==", - "license": "MIT", - "dependencies": { - "@tabler/icons": "3.31.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/codecalm" - }, - "peerDependencies": { - "react": ">= 16" - } - }, - "node_modules/@tanstack/match-sorter-utils": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz", - "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==", - "license": "MIT", - "dependencies": { - "remove-accents": "0.5.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-table": { - "version": "8.20.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz", - "integrity": "sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ==", - "license": "MIT", - "dependencies": { - "@tanstack/table-core": "8.20.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/@tanstack/react-virtual": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.2.tgz", - "integrity": "sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==", - "license": "MIT", - "dependencies": { - "@tanstack/virtual-core": "3.11.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@tanstack/table-core": { - "version": "8.20.5", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", - "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/virtual-core": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz", - "integrity": "sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", - "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", - "license": "MIT", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.0.10", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", - "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", - "license": "MIT", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", - "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.0.0" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", - "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*" - } - }, - "node_modules/@videojs/http-streaming": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.17.0.tgz", - "integrity": "sha512-Ch1P3tvvIEezeZXyK11UfWgp4cWKX4vIhZ30baN/lRinqdbakZ5hiAI3pGjRy3d+q/Epyc8Csz5xMdKNNGYpcw==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^4.1.1", - "aes-decrypter": "^4.0.2", - "global": "^4.4.0", - "m3u8-parser": "^7.2.0", - "mpd-parser": "^1.3.1", - "mux.js": "7.1.0", - "video.js": "^7 || ^8" - }, - "engines": { - "node": ">=8", - "npm": ">=5" - }, - "peerDependencies": { - "video.js": "^8.19.0" - } - }, - "node_modules/@videojs/vhs-utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.1.1.tgz", - "integrity": "sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "global": "^4.4.0" - }, - "engines": { - "node": ">=8", - "npm": ">=5" - } - }, - "node_modules/@videojs/xhr": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.7.0.tgz", - "integrity": "sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.5.5", - "global": "~4.4.0", - "is-function": "^1.0.1" - } - }, - "node_modules/@vitejs/plugin-react-swc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.8.0.tgz", - "integrity": "sha512-T4sHPvS+DIqDP51ifPqa9XIRAz/kIvIi8oXcnOZZgHmMotgmmdxe/DD5tMFlt5nuIRzT0/QuiwmKlH0503Aapw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@swc/core": "^1.10.15" - }, - "peerDependencies": { - "vite": "^4 || ^5 || ^6" - } - }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", - "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/aes-decrypter": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.2.tgz", - "integrity": "sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^4.1.1", - "global": "^4.4.0", - "pkcs7": "^1.0.4" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", - "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "license": "MIT" - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", - "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", - "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.1", - "@esbuild/android-arm": "0.25.1", - "@esbuild/android-arm64": "0.25.1", - "@esbuild/android-x64": "0.25.1", - "@esbuild/darwin-arm64": "0.25.1", - "@esbuild/darwin-x64": "0.25.1", - "@esbuild/freebsd-arm64": "0.25.1", - "@esbuild/freebsd-x64": "0.25.1", - "@esbuild/linux-arm": "0.25.1", - "@esbuild/linux-arm64": "0.25.1", - "@esbuild/linux-ia32": "0.25.1", - "@esbuild/linux-loong64": "0.25.1", - "@esbuild/linux-mips64el": "0.25.1", - "@esbuild/linux-ppc64": "0.25.1", - "@esbuild/linux-riscv64": "0.25.1", - "@esbuild/linux-s390x": "0.25.1", - "@esbuild/linux-x64": "0.25.1", - "@esbuild/netbsd-arm64": "0.25.1", - "@esbuild/netbsd-x64": "0.25.1", - "@esbuild/openbsd-arm64": "0.25.1", - "@esbuild/openbsd-x64": "0.25.1", - "@esbuild/sunos-x64": "0.25.1", - "@esbuild/win32-arm64": "0.25.1", - "@esbuild/win32-ia32": "0.25.1", - "@esbuild/win32-x64": "0.25.1" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", - "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.1.0", - "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.22.0", - "@eslint/plugin-kit": "^0.2.7", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.19.tgz", - "integrity": "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.40" - } - }, - "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "license": "MIT" - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formik": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz", - "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==", - "funding": [ - { - "type": "individual", - "url": "https://opencollective.com/formik" - } - ], - "license": "Apache-2.0", - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.1", - "deepmerge": "^2.1.1", - "hoist-non-react-statics": "^3.3.0", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "react-fast-compare": "^2.0.1", - "tiny-warning": "^1.0.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "license": "MIT", - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/highlight-words": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/highlight-words/-/highlight-words-2.0.0.tgz", - "integrity": "sha512-If5n+IhSBRXTScE7wl16VPmd+44Vy7kof24EdqhjsZsDuHikpv1OCagVcJFpB4fS4UPUniedlWqrjIO8vWOsIQ==", - "license": "MIT", - "engines": { - "node": ">= 20", - "npm": ">= 9" - } - }, - "node_modules/hls.js": { - "version": "1.5.20", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.20.tgz", - "integrity": "sha512-uu0VXUK52JhihhnN/MVVo1lvqNNuhoxkonqgO3IpjvQiGpJBdIXMGkofjQb/j9zvV7a1SW8U9g1FslWx/1HOiQ==", - "license": "Apache-2.0" - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", - "license": "MIT" - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lucide-react": { - "version": "0.479.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.479.0.tgz", - "integrity": "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/m3u8-parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.2.0.tgz", - "integrity": "sha512-CRatFqpjVtMiMaKXxNvuI3I++vUumIXVVT/JpCpdU/FynV/ceVw1qpPyyBNindL+JlPMSesx+WX1QJaZEJSaMQ==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^4.1.1", - "global": "^4.4.0" - } - }, - "node_modules/mantine-react-table": { - "version": "2.0.0-beta.9", - "resolved": "https://registry.npmjs.org/mantine-react-table/-/mantine-react-table-2.0.0-beta.9.tgz", - "integrity": "sha512-ZdfcwebWaPERoDvAuk43VYcBCzamohARVclnbuepT0PHZ0wRcDPMBR+zgaocL+pFy8EXUGwvWTOKNh25ITpjNQ==", - "license": "MIT", - "dependencies": { - "@tanstack/match-sorter-utils": "8.19.4", - "@tanstack/react-table": "8.20.5", - "@tanstack/react-virtual": "3.11.2" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kevinvandy" - }, - "peerDependencies": { - "@mantine/core": "^7.9", - "@mantine/dates": "^7.9", - "@mantine/hooks": "^7.9", - "@tabler/icons-react": ">=2.23.0", - "clsx": ">=2", - "dayjs": ">=1.11", - "react": ">=18.0", - "react-dom": ">=18.0" - } - }, - "node_modules/mantine-react-table/node_modules/@tanstack/react-table": { - "version": "8.20.5", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", - "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", - "license": "MIT", - "dependencies": { - "@tanstack/table-core": "8.20.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/material-react-table": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-3.2.1.tgz", - "integrity": "sha512-sQtTf7bETpkPN2Hm5BVtz89wrfXCVQguz6XlwMChSnfKFO5QCKAJJC5aSIKnUc3S0AvTz/k/ILi00FnnY1Gixw==", - "license": "MIT", - "dependencies": { - "@tanstack/match-sorter-utils": "8.19.4", - "@tanstack/react-table": "8.20.6", - "@tanstack/react-virtual": "3.11.2", - "highlight-words": "2.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kevinvandy" - }, - "peerDependencies": { - "@emotion/react": ">=11.13", - "@emotion/styled": ">=11.13", - "@mui/icons-material": ">=6", - "@mui/material": ">=6", - "@mui/x-date-pickers": ">=7.15", - "react": ">=18.0", - "react-dom": ">=18.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", - "dependencies": { - "dom-walk": "^0.1.0" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mpd-parser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.1.tgz", - "integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^4.0.0", - "@xmldom/xmldom": "^0.8.3", - "global": "^4.4.0" - }, - "bin": { - "mpd-to-m3u8-json": "bin/parse.js" - } - }, - "node_modules/mpegts.js": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mpegts.js/-/mpegts.js-1.8.0.tgz", - "integrity": "sha512-ZtujqtmTjWgcDDkoOnLvrOKUTO/MKgLHM432zGDI8oPaJ0S+ebPxg1nEpDpLw6I7KmV/GZgUIrfbWi3qqEircg==", - "license": "Apache-2.0", - "dependencies": { - "es6-promise": "^4.2.5", - "webworkify-webpack": "github:xqq/webworkify-webpack" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/mux.js": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.1.0.tgz", - "integrity": "sha512-NTxawK/BBELJrYsZThEulyUMDVlLizKdxyAsMuzoCD1eFj97BVaA8D/CvKsKu6FOLYkFojN5CbM9h++ZTZtknA==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.11.2", - "global": "^4.4.0" - }, - "bin": { - "muxjs-transmux": "bin/transmux.js" - }, - "engines": { - "node": ">=8", - "npm": ">=5" - } - }, - "node_modules/nanoid": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", - "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/pkcs7": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", - "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.5.5" - }, - "bin": { - "pkcs7": "bin/cli.js" - } - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/property-expr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", - "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", - "license": "MIT" - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.25.0" - }, - "peerDependencies": { - "react": "^19.0.0" - } - }, - "node_modules/react-draggable": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", - "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", - "license": "MIT", - "dependencies": { - "clsx": "^1.1.1", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" - } - }, - "node_modules/react-draggable/node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/react-fast-compare": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", - "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==", - "license": "MIT" - }, - "node_modules/react-is": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", - "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", - "license": "MIT" - }, - "node_modules/react-number-format": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.3.tgz", - "integrity": "sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ==", - "license": "MIT", - "peerDependencies": { - "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-pro-sidebar": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/react-pro-sidebar/-/react-pro-sidebar-1.1.0.tgz", - "integrity": "sha512-rdRJ4PeMsqWq9n69AmF6et6qCbhCF1KEBgjAH8vIiLxE1k5fMxtRYo0k4asxW8qpIH6sqahiMxrxVVoObv8orQ==", - "license": "MIT", - "dependencies": { - "@emotion/react": "^11.10.5", - "@emotion/styled": "^11.10.5", - "@popperjs/core": "^2.11.6", - "classnames": "^2.3.2" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/react-remove-scroll": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", - "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.3", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.3" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-router": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.3.0.tgz", - "integrity": "sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw==", - "license": "MIT", - "dependencies": { - "@types/cookie": "^0.6.0", - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0", - "turbo-stream": "2.4.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-router-dom": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.3.0.tgz", - "integrity": "sha512-z7Q5FTiHGgQfEurX/FBinkOXhWREJIAB2RiU24lvcBa82PxUpwqvs/PAXb9lJyPjTs2jrl6UkLvCZVGJPeNuuQ==", - "license": "MIT", - "dependencies": { - "react-router": "7.3.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-textarea-autosize": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.6.tgz", - "integrity": "sha512-aT3ioKXMa8f6zHYGebhbdMD2L00tKeRX1zuVuDx9YQK/JLLRSaSxq3ugECEmUB9z2kvk6bFSIoRHLkkUv0RJiw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.20.13", - "use-composed-ref": "^1.3.0", - "use-latest": "^1.2.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, - "node_modules/remove-accents": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", - "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/rollup": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz", - "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.6" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.35.0", - "@rollup/rollup-android-arm64": "4.35.0", - "@rollup/rollup-darwin-arm64": "4.35.0", - "@rollup/rollup-darwin-x64": "4.35.0", - "@rollup/rollup-freebsd-arm64": "4.35.0", - "@rollup/rollup-freebsd-x64": "4.35.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", - "@rollup/rollup-linux-arm-musleabihf": "4.35.0", - "@rollup/rollup-linux-arm64-gnu": "4.35.0", - "@rollup/rollup-linux-arm64-musl": "4.35.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", - "@rollup/rollup-linux-riscv64-gnu": "4.35.0", - "@rollup/rollup-linux-s390x-gnu": "4.35.0", - "@rollup/rollup-linux-x64-gnu": "4.35.0", - "@rollup/rollup-linux-x64-musl": "4.35.0", - "@rollup/rollup-win32-arm64-msvc": "4.35.0", - "@rollup/rollup-win32-ia32-msvc": "4.35.0", - "@rollup/rollup-win32-x64-msvc": "4.35.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", - "license": "MIT" - }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "license": "MIT" - }, - "node_modules/tiny-case": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", - "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", - "license": "MIT" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "license": "MIT" - }, - "node_modules/toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/turbo-stream": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", - "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", - "license": "ISC" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-composed-ref": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", - "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-isomorphic-layout-effect": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz", - "integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-latest": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", - "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", - "license": "MIT", - "dependencies": { - "use-isomorphic-layout-effect": "^1.1.1" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/video.js": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.21.0.tgz", - "integrity": "sha512-zcwerRb257QAuWfi8NH9yEX7vrGKFthjfcONmOQ4lxFRpDAbAi+u5LAjCjMWqhJda6zEmxkgdDpOMW3Y21QpXA==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/http-streaming": "^3.16.2", - "@videojs/vhs-utils": "^4.1.1", - "@videojs/xhr": "2.7.0", - "aes-decrypter": "^4.0.2", - "global": "4.4.0", - "m3u8-parser": "^7.2.0", - "mpd-parser": "^1.3.1", - "mux.js": "^7.0.1", - "videojs-contrib-quality-levels": "4.1.0", - "videojs-font": "4.2.0", - "videojs-vtt.js": "0.15.5" - } - }, - "node_modules/videojs-contrib-quality-levels": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.1.0.tgz", - "integrity": "sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==", - "license": "Apache-2.0", - "dependencies": { - "global": "^4.4.0" - }, - "engines": { - "node": ">=16", - "npm": ">=8" - }, - "peerDependencies": { - "video.js": "^8" - } - }, - "node_modules/videojs-font": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.2.0.tgz", - "integrity": "sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==", - "license": "Apache-2.0" - }, - "node_modules/videojs-vtt.js": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", - "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", - "license": "Apache-2.0", - "dependencies": { - "global": "^4.3.1" - } - }, - "node_modules/vite": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", - "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "postcss": "^8.5.3", - "rollup": "^4.30.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/webworkify-webpack": { - "version": "2.1.5", - "resolved": "git+ssh://git@github.com/xqq/webworkify-webpack.git#24d1e719b4a6cac37a518b2bb10fe124527ef4ef", - "license": "MIT" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yup": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz", - "integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==", - "license": "MIT", - "dependencies": { - "property-expr": "^2.0.5", - "tiny-case": "^1.0.3", - "toposort": "^2.0.2", - "type-fest": "^2.19.0" - } - }, - "node_modules/zustand": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", - "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - } - } -} diff --git a/vite/package.json b/vite/package.json deleted file mode 100644 index 2e734af8..00000000 --- a/vite/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "vite", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite --host", - "build": "vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.0", - "@mantine/core": "^7.17.1", - "@mantine/dates": "^7.17.1", - "@mantine/hooks": "^7.17.1", - "@mui/icons-material": "^6.4.7", - "@mui/material": "^6.4.7", - "@mui/x-date-pickers": "^7.27.3", - "@tabler/icons-react": "^3.31.0", - "axios": "^1.8.2", - "clsx": "^2.1.1", - "dayjs": "^1.11.13", - "formik": "^2.4.6", - "hls.js": "^1.5.20", - "lucide-react": "^0.479.0", - "mantine-react-table": "^2.0.0-beta.9", - "material-react-table": "^3.2.1", - "mpegts.js": "^1.8.0", - "prettier": "^3.5.3", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-draggable": "^4.4.6", - "react-pro-sidebar": "^1.1.0", - "react-router-dom": "^7.3.0", - "video.js": "^8.21.0", - "yup": "^1.6.1", - "zustand": "^5.0.3" - }, - "devDependencies": { - "@eslint/js": "^9.21.0", - "@types/react": "^19.0.10", - "@types/react-dom": "^19.0.4", - "@vitejs/plugin-react-swc": "^3.8.0", - "eslint": "^9.21.0", - "eslint-plugin-react-hooks": "^5.1.0", - "eslint-plugin-react-refresh": "^0.4.19", - "globals": "^15.15.0", - "vite": "^6.2.0" - } -} diff --git a/vite/prettier.config.js b/vite/prettier.config.js deleted file mode 100644 index d2b86878..00000000 --- a/vite/prettier.config.js +++ /dev/null @@ -1,10 +0,0 @@ -// prettier.config.js or .prettierrc.js -export default { - semi: true, // Add semicolons at the end of statements - singleQuote: true, // Use single quotes instead of double - tabWidth: 2, // Set the indentation width - trailingComma: "es5", // Add trailing commas where valid in ES5 - printWidth: 80, // Wrap lines at 80 characters - bracketSpacing: true, // Add spaces inside object braces - arrowParens: "always", // Always include parentheses around arrow function parameters -}; diff --git a/vite/src/App.css b/vite/src/App.css deleted file mode 100644 index 74b5e053..00000000 --- a/vite/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/vite/src/api.js b/vite/src/api.js deleted file mode 100644 index 1d436a02..00000000 --- a/vite/src/api.js +++ /dev/null @@ -1,783 +0,0 @@ -// src/api.js (updated) -import useAuthStore from './store/auth'; -import useChannelsStore from './store/channels'; -import useUserAgentsStore from './store/userAgents'; -import usePlaylistsStore from './store/playlists'; -import useEPGsStore from './store/epgs'; -import useStreamsStore from './store/streams'; -import useStreamProfilesStore from './store/streamProfiles'; -import useSettingsStore from './store/settings'; - -// If needed, you can set a base host or keep it empty if relative requests -const host = import.meta.env.DEV - ? `http://${window.location.hostname}:5656` - : ''; - -export default class API { - /** - * A static method so we can do: await API.getAuthToken() - */ - static async getAuthToken() { - return await useAuthStore.getState().getToken(); - } - - static async login(username, password) { - const response = await fetch(`${host}/api/accounts/token/`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ username, password }), - }); - - return await response.json(); - } - - static async refreshToken(refresh) { - const response = await fetch(`${host}/api/accounts/token/refresh/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ refresh }), - }); - - const retval = await response.json(); - return retval; - } - - static async logout() { - const response = await fetch(`${host}/api/accounts/auth/logout/`, { - method: 'POST', - }); - - return response.data.data; - } - - static async getChannels() { - const response = await fetch(`${host}/api/channels/channels/`, { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${await API.getAuthToken()}`, - }, - }); - - const retval = await response.json(); - return retval; - } - - static async getChannelGroups() { - const response = await fetch(`${host}/api/channels/groups/`, { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${await API.getAuthToken()}`, - }, - }); - - const retval = await response.json(); - return retval; - } - - static async addChannelGroup(values) { - const response = await fetch(`${host}/api/channels/groups/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(values), - }); - - const retval = await response.json(); - if (retval.id) { - useChannelsStore.getState().addChannelGroup(retval); - } - - return retval; - } - - static async updateChannelGroup(values) { - const { id, ...payload } = values; - const response = await fetch(`${host}/api/channels/groups/${id}/`, { - method: 'PUT', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - const retval = await response.json(); - if (retval.id) { - useChannelsStore.getState().updateChannelGroup(retval); - } - - return retval; - } - - static async addChannel(channel) { - let body = null; - if (channel.logo_file) { - // Must send FormData for file upload - body = new FormData(); - for (const prop in channel) { - body.append(prop, channel[prop]); - } - } else { - body = { ...channel }; - delete body.logo_file; - body = JSON.stringify(body); - } - - const response = await fetch(`${host}/api/channels/channels/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - ...(channel.logo_file - ? {} - : { - 'Content-Type': 'application/json', - }), - }, - body: body, - }); - - const retval = await response.json(); - if (retval.id) { - useChannelsStore.getState().addChannel(retval); - } - - return retval; - } - - static async deleteChannel(id) { - const response = await fetch(`${host}/api/channels/channels/${id}/`, { - method: 'DELETE', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - useChannelsStore.getState().removeChannels([id]); - } - - // @TODO: the bulk delete endpoint is currently broken - static async deleteChannels(channel_ids) { - const response = await fetch(`${host}/api/channels/channels/bulk-delete/`, { - method: 'DELETE', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ channel_ids }), - }); - - useChannelsStore.getState().removeChannels(channel_ids); - } - - static async updateChannel(values) { - const { id, ...payload } = values; - const response = await fetch(`${host}/api/channels/channels/${id}/`, { - method: 'PUT', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - const retval = await response.json(); - if (retval.id) { - useChannelsStore.getState().updateChannel(retval); - } - - return retval; - } - - static async assignChannelNumbers(channelIds) { - // Make the request - const response = await fetch(`${host}/api/channels/channels/assign/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ channel_order: channelIds }), - }); - - if (!response.ok) { - const text = await response.text(); - throw new Error(`Assign channels failed: ${response.status} => ${text}`); - } - - const retval = await response.json(); - - // Optionally refresh the channel list in Zustand - await useChannelsStore.getState().fetchChannels(); - - return retval; - } - - static async createChannelFromStream(values) { - const response = await fetch(`${host}/api/channels/channels/from-stream/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(values), - }); - - const retval = await response.json(); - if (retval.id) { - useChannelsStore.getState().addChannel(retval); - } - - return retval; - } - - static async createChannelsFromStreams(values) { - const response = await fetch( - `${host}/api/channels/channels/from-stream/bulk/`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(values), - } - ); - - const retval = await response.json(); - if (retval.created.length > 0) { - useChannelsStore.getState().addChannels(retval.created); - } - - return retval; - } - - static async getStreams() { - const response = await fetch(`${host}/api/channels/streams/`, { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${await API.getAuthToken()}`, - }, - }); - - const retval = await response.json(); - return retval; - } - - static async queryStreams(params) { - const response = await fetch( - `${host}/api/channels/streams/?${params.toString()}`, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${await API.getAuthToken()}`, - }, - } - ); - - const retval = await response.json(); - return retval; - } - - static async getAllStreamIds(params) { - const response = await fetch( - `${host}/api/channels/streams/ids/?${params.toString()}`, - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${await API.getAuthToken()}`, - }, - } - ); - - const retval = await response.json(); - return retval; - } - - static async getStreamGroups() { - const response = await fetch(`${host}/api/channels/streams/groups/`, { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${await API.getAuthToken()}`, - }, - }); - - const retval = await response.json(); - return retval; - } - - static async addStream(values) { - const response = await fetch(`${host}/api/channels/streams/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(values), - }); - - const retval = await response.json(); - if (retval.id) { - useStreamsStore.getState().addStream(retval); - } - - return retval; - } - - static async updateStream(values) { - const { id, ...payload } = values; - const response = await fetch(`${host}/api/channels/streams/${id}/`, { - method: 'PUT', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - const retval = await response.json(); - if (retval.id) { - useStreamsStore.getState().updateStream(retval); - } - - return retval; - } - - static async deleteStream(id) { - const response = await fetch(`${host}/api/channels/streams/${id}/`, { - method: 'DELETE', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - useStreamsStore.getState().removeStreams([id]); - } - - static async deleteStreams(ids) { - const response = await fetch(`${host}/api/channels/streams/bulk-delete/`, { - method: 'DELETE', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ stream_ids: ids }), - }); - - useStreamsStore.getState().removeStreams(ids); - } - - static async getUserAgents() { - const response = await fetch(`${host}/api/core/useragents/`, { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${await API.getAuthToken()}`, - }, - }); - - const retval = await response.json(); - return retval; - } - - static async addUserAgent(values) { - const response = await fetch(`${host}/api/core/useragents/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(values), - }); - - const retval = await response.json(); - if (retval.id) { - useUserAgentsStore.getState().addUserAgent(retval); - } - - return retval; - } - - static async updateUserAgent(values) { - const { id, ...payload } = values; - const response = await fetch(`${host}/api/core/useragents/${id}/`, { - method: 'PUT', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - const retval = await response.json(); - if (retval.id) { - useUserAgentsStore.getState().updateUserAgent(retval); - } - - return retval; - } - - static async deleteUserAgent(id) { - const response = await fetch(`${host}/api/core/useragents/${id}/`, { - method: 'DELETE', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - useUserAgentsStore.getState().removeUserAgents([id]); - } - - static async getPlaylist(id) { - const response = await fetch(`${host}/api/m3u/accounts/${id}/`, { - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - const retval = await response.json(); - return retval; - } - - static async getPlaylists() { - const response = await fetch(`${host}/api/m3u/accounts/`, { - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - const retval = await response.json(); - return retval; - } - - static async addPlaylist(values) { - const response = await fetch(`${host}/api/m3u/accounts/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(values), - }); - - const retval = await response.json(); - if (retval.id) { - usePlaylistsStore.getState().addPlaylist(retval); - } - - return retval; - } - - static async refreshPlaylist(id) { - const response = await fetch(`${host}/api/m3u/refresh/${id}/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - const retval = await response.json(); - return retval; - } - - static async refreshAllPlaylist() { - const response = await fetch(`${host}/api/m3u/refresh/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - const retval = await response.json(); - return retval; - } - - static async deletePlaylist(id) { - const response = await fetch(`${host}/api/m3u/accounts/${id}/`, { - method: 'DELETE', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - usePlaylistsStore.getState().removePlaylists([id]); - } - - static async updatePlaylist(values) { - const { id, ...payload } = values; - const response = await fetch(`${host}/api/m3u/accounts/${id}/`, { - method: 'PUT', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - const retval = await response.json(); - if (retval.id) { - usePlaylistsStore.getState().updatePlaylist(retval); - } - - return retval; - } - - static async getEPGs() { - const response = await fetch(`${host}/api/epg/sources/`, { - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - const retval = await response.json(); - return retval; - } - - // Notice there's a duplicated "refreshPlaylist" method above; - // you might want to rename or remove one if it's not needed. - - static async addEPG(values) { - let body = null; - if (values.epg_file) { - body = new FormData(); - for (const prop in values) { - body.append(prop, values[prop]); - } - } else { - body = { ...values }; - delete body.epg_file; - body = JSON.stringify(body); - } - - const response = await fetch(`${host}/api/epg/sources/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - ...(values.epg_file - ? {} - : { - 'Content-Type': 'application/json', - }), - }, - body, - }); - - const retval = await response.json(); - if (retval.id) { - useEPGsStore.getState().addEPG(retval); - } - - return retval; - } - - static async deleteEPG(id) { - const response = await fetch(`${host}/api/epg/sources/${id}/`, { - method: 'DELETE', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - useEPGsStore.getState().removeEPGs([id]); - } - - static async refreshEPG(id) { - const response = await fetch(`${host}/api/epg/import/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ id }), - }); - - const retval = await response.json(); - return retval; - } - - static async getStreamProfiles() { - const response = await fetch(`${host}/api/core/streamprofiles/`, { - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - const retval = await response.json(); - return retval; - } - - static async addStreamProfile(values) { - const response = await fetch(`${host}/api/core/streamprofiles/`, { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(values), - }); - - const retval = await response.json(); - if (retval.id) { - useStreamProfilesStore.getState().addStreamProfile(retval); - } - return retval; - } - - static async updateStreamProfile(values) { - const { id, ...payload } = values; - const response = await fetch(`${host}/api/core/streamprofiles/${id}/`, { - method: 'PUT', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - const retval = await response.json(); - if (retval.id) { - useStreamProfilesStore.getState().updateStreamProfile(retval); - } - - return retval; - } - - static async deleteStreamProfile(id) { - const response = await fetch(`${host}/api/core/streamprofiles/${id}/`, { - method: 'DELETE', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - useStreamProfilesStore.getState().removeStreamProfiles([id]); - } - - static async getGrid() { - const response = await fetch(`${host}/api/epg/grid/`, { - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - }); - - const retval = await response.json(); - return retval.data; - } - - static async addM3UProfile(accountId, values) { - const response = await fetch( - `${host}/api/m3u/accounts/${accountId}/profiles/`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(values), - } - ); - - const retval = await response.json(); - if (retval.id) { - // Refresh the playlist - const playlist = await API.getPlaylist(accountId); - usePlaylistsStore - .getState() - .updateProfiles(playlist.id, playlist.profiles); - } - - return retval; - } - - static async deleteM3UProfile(accountId, id) { - const response = await fetch( - `${host}/api/m3u/accounts/${accountId}/profiles/${id}/`, - { - method: 'DELETE', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - } - ); - - const playlist = await API.getPlaylist(accountId); - usePlaylistsStore.getState().updatePlaylist(playlist); - } - - static async updateM3UProfile(accountId, values) { - const { id, ...payload } = values; - const response = await fetch( - `${host}/api/m3u/accounts/${accountId}/profiles/${id}/`, - { - method: 'PUT', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - } - ); - - const playlist = await API.getPlaylist(accountId); - usePlaylistsStore.getState().updateProfiles(playlist.id, playlist.profiles); - } - - static async getSettings() { - const response = await fetch(`${host}/api/core/settings/`, { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${await API.getAuthToken()}`, - }, - }); - - const retval = await response.json(); - return retval; - } - - static async getEnvironmentSettings() { - const response = await fetch(`${host}/api/core/settings/env/`, { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${await API.getAuthToken()}`, - }, - }); - - const retval = await response.json(); - return retval; - } - - static async updateSetting(values) { - const { id, ...payload } = values; - const response = await fetch(`${host}/api/core/settings/${id}/`, { - method: 'PUT', - headers: { - Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - const retval = await response.json(); - if (retval.id) { - useSettingsStore.getState().updateSetting(retval); - } - - return retval; - } -} diff --git a/vite/src/components/Alert.jsx b/vite/src/components/Alert.jsx deleted file mode 100644 index fa843400..00000000 --- a/vite/src/components/Alert.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useState } from 'react'; -import { Snackbar, Alert, Button } from '@mui/material'; -import useAlertStore from '../store/alerts'; - -const AlertPopup = () => { - const { open, message, severity, hideAlert } = useAlertStore(); - - const handleClose = () => { - hideAlert(); - }; - - return ( - - - {message} - - - ); -}; - -export default AlertPopup; diff --git a/vite/src/components/Sidebar.jsx b/vite/src/components/Sidebar.jsx deleted file mode 100644 index 9b4502fa..00000000 --- a/vite/src/components/Sidebar.jsx +++ /dev/null @@ -1,189 +0,0 @@ -import React from 'react'; -import { Link, useLocation } from 'react-router-dom'; -import { - Drawer, - Toolbar, - Box, - Typography, - Avatar, - List, - ListItemButton, - ListItemIcon, - ListItemText, -} from '@mui/material'; -import { useTheme } from '@mui/material/styles'; -import { - ListOrdered, - Play, - Database, - SlidersHorizontal, - LayoutGrid, - Settings as LucideSettings, -} from 'lucide-react'; -import logo from '../images/logo.png'; -import { AppShell } from '@mantine/core'; - -const navItems = [ - { label: 'Channels', icon: , path: '/channels' }, - { label: 'M3U', icon: , path: '/m3u' }, - { label: 'EPG', icon: , path: '/epg' }, - { - label: 'Stream Profiles', - icon: , - path: '/stream-profiles', - }, - { label: 'TV Guide', icon: , path: '/guide' }, - { label: 'Settings', icon: , path: '/settings' }, -]; - -const Sidebar = ({ open, drawerWidth, miniDrawerWidth, toggleDrawer }) => { - const location = useLocation(); - const theme = useTheme(); - - return ( - - - - {open ? ( - - Dispatcharr Logo - - Dispatcharr - - - ) : ( - Dispatcharr Logo - )} - - - - {navItems.map((item) => { - const isActive = location.pathname.startsWith(item.path); - return ( - - - - {item.icon} - - {open && ( - - )} - - - ); - })} - - - - - - {open && ( - - John Doe - - )} - - - - ); -}; - -export default Sidebar; diff --git a/vite/src/components/forms/Channel.jsx b/vite/src/components/forms/Channel.jsx deleted file mode 100644 index 2b5d84e2..00000000 --- a/vite/src/components/forms/Channel.jsx +++ /dev/null @@ -1,491 +0,0 @@ -import React, { useState, useEffect, useMemo } from 'react'; -import { - Box, - Typography, - Stack, - TextField, - Button, - Select, - MenuItem, - Grid2, - InputLabel, - FormControl, - CircularProgress, - IconButton, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - FormHelperText, -} from '@mui/material'; -import { useFormik } from 'formik'; -import * as Yup from 'yup'; -import useChannelsStore from '../../store/channels'; -import API from '../../api'; -import useStreamProfilesStore from '../../store/streamProfiles'; -import { Add as AddIcon, Remove as RemoveIcon } from '@mui/icons-material'; -import useStreamsStore from '../../store/streams'; -import { - MaterialReactTable, - useMaterialReactTable, -} from 'material-react-table'; -import ChannelGroupForm from './ChannelGroup'; -import usePlaylistsStore from '../../store/playlists'; -import logo from '../../images/logo.png'; - -const Channel = ({ channel = null, isOpen, onClose }) => { - const channelGroups = useChannelsStore((state) => state.channelGroups); - const streams = useStreamsStore((state) => state.streams); - const { profiles: streamProfiles } = useStreamProfilesStore(); - const { playlists } = usePlaylistsStore(); - - const [logoFile, setLogoFile] = useState(null); - const [logoPreview, setLogoPreview] = useState(logo); - const [channelStreams, setChannelStreams] = useState([]); - const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false); - - const addStream = (stream) => { - const streamSet = new Set(channelStreams); - streamSet.add(stream); - setChannelStreams(Array.from(streamSet)); - }; - - const removeStream = (stream) => { - const streamSet = new Set(channelStreams); - streamSet.delete(stream); - setChannelStreams(Array.from(streamSet)); - }; - - const handleLogoChange = (e) => { - const file = e.target.files[0]; - if (file) { - setLogoFile(file); - setLogoPreview(URL.createObjectURL(file)); - } - }; - - const formik = useFormik({ - initialValues: { - channel_name: '', - channel_number: '', - channel_group_id: '', - stream_profile_id: '0', - tvg_id: '', - tvg_name: '', - }, - validationSchema: Yup.object({ - channel_name: Yup.string().required('Name is required'), - channel_number: Yup.string().required('Invalid channel number').min(0), - channel_group_id: Yup.string().required('Channel group is required'), - }), - onSubmit: async (values, { setSubmitting, resetForm }) => { - if (values.stream_profile_id == '0') { - values.stream_profile_id = null; - } - - console.log(values); - if (channel?.id) { - await API.updateChannel({ - id: channel.id, - ...values, - logo_file: logoFile, - streams: channelStreams.map((stream) => stream.id), - }); - } else { - await API.addChannel({ - ...values, - logo_file: logoFile, - streams: channelStreams.map((stream) => stream.id), - }); - } - - resetForm(); - setLogoFile(null); - setLogoPreview(logo); - setSubmitting(false); - onClose(); - }, - }); - - useEffect(() => { - if (channel) { - formik.setValues({ - channel_name: channel.channel_name, - channel_number: channel.channel_number, - channel_group_id: channel.channel_group?.id, - stream_profile_id: channel.stream_profile_id || '0', - tvg_id: channel.tvg_id, - tvg_name: channel.tvg_name, - }); - - console.log(channel); - const filteredStreams = streams - .filter((stream) => channel.stream_ids.includes(stream.id)) - .sort( - (a, b) => - channel.stream_ids.indexOf(a.id) - channel.stream_ids.indexOf(b.id) - ); - setChannelStreams(filteredStreams); - } else { - formik.resetForm(); - } - }, [channel]); - - const activeStreamsTable = useMaterialReactTable({ - data: channelStreams, - columns: useMemo( - () => [ - { - header: 'Name', - accessorKey: 'name', - }, - { - header: 'M3U', - accessorKey: 'group_name', - }, - ], - [] - ), - enableSorting: false, - enableBottomToolbar: false, - enableTopToolbar: false, - columnFilterDisplayMode: 'popover', - enablePagination: false, - enableRowVirtualization: true, - enableRowOrdering: true, - rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer - initialState: { - density: 'compact', - }, - enableRowActions: true, - positionActionsColumn: 'last', - renderRowActions: ({ row }) => ( - <> - removeStream(row.original)} - > - {/* Small icon size */} - - - ), - muiTableContainerProps: { - sx: { - height: '200px', - }, - }, - muiRowDragHandleProps: ({ table }) => ({ - onDragEnd: () => { - const { draggingRow, hoveredRow } = table.getState(); - - if (hoveredRow && draggingRow) { - channelStreams.splice( - hoveredRow.index, - 0, - channelStreams.splice(draggingRow.index, 1)[0] - ); - - setChannelStreams([...channelStreams]); - } - }, - }), - }); - - const availableStreamsTable = useMaterialReactTable({ - data: streams, - columns: useMemo( - () => [ - { - header: 'Name', - accessorKey: 'name', - }, - { - header: 'M3U', - accessorFn: (row) => - playlists.find((playlist) => playlist.id === row.m3u_account)?.name, - }, - ], - [] - ), - enableBottomToolbar: false, - enableTopToolbar: false, - columnFilterDisplayMode: 'popover', - enablePagination: false, - enableRowVirtualization: true, - rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer - initialState: { - density: 'compact', - }, - enableRowActions: true, - renderRowActions: ({ row }) => ( - <> - addStream(row.original)} - > - {/* Small icon size */} - - - ), - positionActionsColumn: 'last', - muiTableContainerProps: { - sx: { - height: '200px', - }, - }, - }); - - if (!isOpen) { - return <>; - } - - return ( - <> - - - Channel - - -
- - - - - - - - - - Channel Group - - - - {formik.touched.channel_group_id && - formik.errors.channel_group_id} - - - - - setChannelGroupModalOpen(true)} - title="Create new group" - size="small" - variant="filled" - > - - - - - - - - Stream Profile - - - - - - - - - - - - - - - - {/* File upload input */} - - Logo - {/* Display selected image */} - - Selected - - handleLogoChange(event)} - style={{ display: 'none' }} - /> - - - - - - - - - Active Streams - - - - - Available Streams - - - - - - - {/* Submit button */} - - -
-
- setChannelGroupModalOpen(false)} - /> - - ); -}; - -export default Channel; diff --git a/vite/src/components/forms/SuperuserForm.jsx b/vite/src/components/forms/SuperuserForm.jsx deleted file mode 100644 index 32eb5e14..00000000 --- a/vite/src/components/forms/SuperuserForm.jsx +++ /dev/null @@ -1,128 +0,0 @@ -// frontend/src/components/forms/SuperuserForm.js -import React, { useState } from 'react'; -import axios from 'axios'; -import { - Box, - Paper, - Typography, - Grid2, - TextField, - Button, -} from '@mui/material'; - -function SuperuserForm({ onSuccess }) { - const [formData, setFormData] = useState({ - username: '', - password: '', - email: '', - }); - const [error, setError] = useState(''); - - const handleChange = (e) => { - setFormData((prev) => ({ - ...prev, - [e.target.name]: e.target.value, - })); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - try { - const res = await axios.post('/api/accounts/initialize-superuser/', { - username: formData.username, - password: formData.password, - email: formData.email, - }); - if (res.data.superuser_exists) { - onSuccess(); - } - } catch (err) { - let msg = 'Failed to create superuser.'; - if (err.response && err.response.data && err.response.data.error) { - msg += ` ${err.response.data.error}`; - } - setError(msg); - } - }; - - return ( - - - - Create your Super User Account - - {error && ( - - {error} - - )} -
- - - - - - - - - - - - - - -
-
-
- ); -} - -export default SuperuserForm; diff --git a/vite/src/images/dispatcharr.svg b/vite/src/images/dispatcharr.svg deleted file mode 100644 index 3ddd8e2b..00000000 --- a/vite/src/images/dispatcharr.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vite/src/images/ghost.svg b/vite/src/images/ghost.svg deleted file mode 100644 index 2206100d..00000000 --- a/vite/src/images/ghost.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - diff --git a/vite/src/images/logo.png b/vite/src/images/logo.png deleted file mode 100644 index 99c3c19f3d6ba0647c2883654126d5ea8b529020..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44564 zcmc$`c|4Wr|37}8b8swK+EjK$t2Jx(QelwNqU@1$tS7q?=S&MzqL`3KO($gxsjOu= zO(N4EODe=EB1`tN^Lt(QQ8DlN%=gdVJzvl3y8W_ymk~d&2rq_V z{3gcREisG({>g!HbHV?GiElcMVVw4qeFyyxnwx1kdwVJzKH}}) z?CkF1C*|bgO7YTN-T$~`wG`!u?rJMFb0u@Cq02Ff@d+Q7y(f0 zO2_BOQ7y~uJLevTpLACr^Yf!>DJljA1}X%qD0ut0Dk^W;vPDs8v*PB>@^FW|Z?Ko& z;UIZ0-!9r{d8BahU-#uFF)e^{oPc59}oP^N1PR1 zJX}0oy!?C>l@*i~=il!VMENlrFW)(Hf#DR<6}Vk#!R>yOqyNY4=*s`xPCe%B=k0sU zoBDlH3x@c9`F|b+bN~AzL5HdTcC)$pf8XxuxnLQ-emerdaC1&rFsE+=`0fj)x+q$@ z_1%Kg>OD2YfyM4TmQas?Aucgs8DU%(B>dITx)#W!Uutxyr zX~DCUqruw``@vnCl{Tx%E2+pUsqfpYq@}i5OKA)ITT4j^e=~kJ6j26wQ088O|McxGI*JR1(19tMn`;?UeEqzAg6EC8*X8)aQSn5SlETAF>##HO zitcKvkM|LOXO|=RYGMBDtG?bx{Q?jBxNLQW$kJWC_2^Lw_%B#$x4)AI#rK$tkJM(V z&43_k3m*$S<^r$$zb&dbe?8ykr=$2kD_}2w9-zQ;?B4-U0pclshdlVC>8-d1vvL22 zMPCg)hrbrv#Mpd1u;)YBJ-!*D*A@5M5x?5{nl|5fJ>0k7=0WF3Qm1cmr;bIZ4!O4` zGUio!;ibxtImR#huU}2})pnrnHr({iuXLH6IDfd|zU*jikE4ZMYVP~>M(@2+T2kjwImvX+dBx;1WFe_toFc8A6D=UHxFXT2T1w9~48Z()UZuX`HQL+lg| z@fIoTym>f8lHRsh!RPkWN7Bj@D^5LR1b=;!Bj6e@D_>0N6n**iMp^d3Jw6dlu_i&f z^s+<8iqAa?PIoPt6l*o~GcWGu4H&&)Q}tLl6x%=U+ES>C@j!9op&B3Z#W235=syCM zn6whZq%f21TlWPe^}Y+sZry*XbJBHe+R&hl=gH0UIij`6WfxcQUa8rZ{@nQF&DZop z58`+|Zyxj9x_Ft#mt%Yx3P=AQi8w$xTT5U3IG-VYie~GcuRG;wJYZ|$^Nfh#VOMh#iKJc8Izd*-~N|&RW^fU zd|^*S7se`J4$}ODeL)Um*a?2-b@`Cs6qjV#NJ9+c#nn);fO!YSY(^1DNE zncxfBN%~3mirL4W$hwr{53x&QPc){|$KIct<#-gltE9S^*vvJxd_dp5qV-sk5sYZP zPY%moe>Ncw+eC|3X&jgp8|aO@DtxtHIdRcg`e*%=G}*8_?AN`4JCAv^j!5z(6;HeF zXxe@vDV#oAA;?EB85JO)0UyX=s>hl%rj3T5n$KEfn=`~mma`OT(aMkXnVSt@w8kC6 zm`Kv+&hhAv;n!xKHmiB4{NhQ2y zYEieo_7U~C|9~|-_FoC_PXj;Hi%O^J(i}KK#m59Mw%xr}%r&JvV3gB-FIQj<+~WTW zCrwMSbVQ70%X&>S6fNJQA0n8~GiJ#8YCSY6(upRK_z>IedN$!YpY7kDoJxw|p0Es( z9*6B-)0y~&!*B!R0F1LmI<=GF?dn$?cCYNXs#rAfHEA{{Zt6Dsj$XmbtT8- zS@Cq;Yv*cSzT>t%U|L+_FNAD)^B{J&K&Z|_sc|fYr?*BkN;v)+&D6bOG)G`HT(8vN z!_<~KX^D+_vT~Sx7u${(PgW=EY5=&0R+ib1P3T8X&Ol*tlNqUrO0 zaoQN({rE4U!SKe?Y_TB4Ti2#<>rE&OgzhvV&E((~rnnx$j@h3{_{vd5YT*=nqZy^j zbBz{=Nv6qqdy4Ob=My*aVdl0GUCWt^gwV^MkeJtsBvEvnawgpPg-2N&o3di{HXLGb zW^o0%%LkugFDWVud%68gf<5n)2CEz%6n>dUf5q&79V6+*e(yz2T7XFe#hVpK%X@#K z_QRL`tX}IOPxh?a@(jl6Q1RY@+AAiYa-k6#*6*IUX%&VL%5VGveLw{it?RE=#-s_mz0z41C#BwrMFLTo3%NKc* z0aty8xCc%$CF^4(-Epr^I}x&;pGgQMjT!Wa#ROEGU~OZC1?eLP1^n8IDXx}I&1>s$ z5czq9(xZ~LT)+n06qfp$ge>ke0DC2ZwzXOp7{5$P3>;Dd<>0=7%vSAPL)X8xnC>cnnh6y$c z^wz;>(VO5k7>a?7XNI{A1R;mGyqJ|GjPd2PYcz{92?ubPX4t`) z7U^_noA687$C5bOh3X~^%}m*0UNy*VPz^}IXWOI|&e+i-%G9Ro|1KMrhnxAcCLc|L zN4ZF7Y*Cgs3@KcsNbt^-ZEeA$F(8uD07elY_Hhb$C+~=n=&Yt+aI5IC!Ly)O4m%-L zn$3~FIA}jSIpfZhW<1cZC&pu6pL&1jaIg&g%xO#1S?Q#;skGhof{ckJ zc?;p?ap}|)tT>``M)pg25%Zc`MT!^hApIkRmxr55c03jzD=6C|Lvghw+gh5=+`;jf z<8hwAjlnntn;9;ahIQv9M)I#T4LQ5qki6S3;#wy!nhTCM8i2jdC1IcL3e_Esw14;| z5@LWtoLQXDoqs>BFas@|@$C79M6F|!8M9#!w*GdpRB?#5sWVRB>xmBx8hCNgUw3%dJ5jgfzKEcqJKHr+P zf|b{PpbBM)JO`KK-Vo8^qj?3(4_wGFG_EDu5Sm4tCSC~DoyYxu?yohN_+lrmGc})P z9|FEI&K2|75~jT5#~s)Y$Go#4zkFWn^5PyA<(_O9BhqxSCgp4=TKzEmwtu!PH9W-g z1^^U?CgCNa$>EnT!BeBsCKg|@fmG|E)@%gE^?_cRqD`TLtqvqb_24B|1pFFgkkZ;(=tU|!7?gD+z7Ra_ve7r}0^UwlX z&VvgQ&pf1&4b9@AT2c_0rKQ6QrAqzQX9>K?gXcsNzA$$%9dI3GUV zQ)7BUmB3S>UUnBImnF29*jGN=FH0#tB{-&}WM?{a0?+M?&n;R^r-y!Thx7>$(1tO> zbXle{^Paq2?o(^5c)?iLg)(d!8u=rXohpc`hw)8BL5Q z#lMazd4Tm`QyOq*-Xn#pN3D_PYNF{M9&vqEhTAdV z94AwUM_HbrcX~~e-B(?fhI>Vk?r)2o8cAc(%*XUM>KC)u>-_!6^7ZKJm^{47sjdJ^ z$dvjK2htk0s5abUuF7e@W=+0&=5x8ZJ>Qf7^&n73r`_~CEDw*-?hEg%$t$xQ+{F!? zJ&B>A>#v2;z>U^JDa1{*VfsC_C>ee`%!{bE*va}WZm=jS(NO}S?aV3^@zW9gm|})( zSg!x2aWIKw(9&C?f1f&gL$xHA7{*=rIBx19zLv7LMgBBt>qj2rJu8!692iY(#Hy)jh06GME2kwUbKrOS4SzT&bVt zfMbu?#NsgWl$rea<%X@+Z)c94e@L4p>O4L!Mo}K~W&LJ7G=b+k^D^rf8-LNdn!Y2F^OlXz_Y39kgUv`{vQ=227D}P<7pKY5Qr151RqT|oo%w7}jQIZWU z227Eo!#_O|s`I6{lI&jtS2UgqWW~TMaMEV2BZsYp$PgN+W)^2W{6y{}%$$-Bw&{^N zwb*Hb;;1(85T~Dcv{9@-T~@yld0Rv;8Z)a3`?E6a^;F!o*@86LR&o-S#1-UQpjz^k zq`>)8MAyh?@9s;`BWMU?vBNJiNS}CS9>s+i^$4BXXWDYW)SmPQhXRMX1_aW(!%Tjr zIDlXd;vnZ!pk)#L*Njl8Ng{LM>PwvKiYT|W!e*=LZTaK6uiyJsKz zW4cemyu}?O+$!ANEA9~25e2i+?OP)i+W-Dm?w+z1)9jWxS@VYQ8{K;Rp@}(UJyen_ zu=xm*3m&d4(i*q~)_=xoW7@!La8@z>k%O5zDVRUrA5-EttUn8M-Mzw!6t42AcM4CCDNIw@!L| zY6-OWl<;5<#}!atFbQHDR4}ZVN6ganYttvFPWqLfnun_4m-0kG3t z6IZl#l$bW;k9)gMHm%r&?IJa3M6Ka}cNrO66bhR+Dp&p~oXpI@ud`mqr%Fgb7GXWK zE6lUZjEG7zDw`APn4*J! zno)|Umr{8KYM{x+S?4<-_OZFqHleru1*Vy;^)X?Y{@fQ=I8B(ad|1CiYqWzU!7|Tj zug*m!{?XGln09s3?BkDh8Kh$YIqf+)?R)7qE+QkWAEOVUmJWBw>!r-bURz0xJUP&01&H-is@|9akvA&0NlG z0B1^HV6HPX(>R+Tfcw2tkdId2J5^$%ULRA^q;;mlLOyt}{CJ4ta;k5SPAxN;IOQ49 zr5~P07Pz~V&^5X%lV=NCxXQ(rm#21BdjCn+K-NtQa=Sol_L$gHIxd%0nz7)H#TRzxb ze%y^kWm&Rn(5$t2^HoYm?hD1H&|_r2BycjD$3BOR9>nCF7CPmmT9$h0NS{z$DE{Vx zhggCr9}PM@+Fe=iyRs=fVx;D`TG6VGlf-_feH=-Pore7Xgf~XO8xLZBV7v+X0*9Pb zpz7YbMPPSF0g;u{0=&wfIYXAddF(H&`=mIhz1%u{_b6l~S12igDkV5*I0RqC2vWqiy%TmyblOKOTs+`^w00^uq2_i{|2hRv9 zI{+Ggk|4a?{wk)Q^c}q3rqh-C2vAa^5m3|$9l~@j3f0Zf1XsUn z7ssrXzF`)fYz;WxtHz8sH0$S9{@jFWr=b&JU?l1o5UVYO8=O}2QET5MA%63t^#db9 zbuox3hybCy(>WFvcnG`u3r@dYG2a6}$lO5*72|5gnio5r*n_O~C7F|1H%w&q4OyzI z?Kcf&#OYLis{REpBfT7~Vd>OOhGs7iWTxGd!>$9cy~{b-gDo*&yI^O0!aI}}yG?1n zbbskofcKL*0?;e89>jEX*I|!d5Dk)nNk!LaT)9K&5iP%bu3O4c?UJF{I^*Z*u_=6(#cxf>BLdAEdQ&V%-%R?nGb;hhTSTl%kCtl)2^ZF=r&g!MV*F* zx5hB;I1-e!p*T80Ohu zALC7q;;Q0O2a3)AYC%P0WKv0L=zYGRQGd$;vHWF0*~eulcXyE+&Lt@DD}Tnd>Wo9! z-vEnEpcA{Z;V9Ber6d;({gtbQ5VECor17)&7y#&=Aww#A@K;hmROZ!)yN0Wld(Lhu z_47dM&S7!6;rDUSxNgUVNK%PZk*s2X`n&dvLe`cdqPPVCT=pe7?5Hc@r6-c&t%q{0 zheRA@snS^mXp&V#iABmqx~16`cs_CP(cT`y{I2y_o!*ZJK0gPG*U`PrxK>D)G9(ud+Nv9IDxPJC|GJXF&ovuj=L@h|(wQ_!s#wiz2{iw^8$m%dmBF5?)kr$k2(!uout-S+%0>zyI%z%M3X%d69<8Kb(r zns}53;^#U%`(*OzBGtqjlS$ai-wxoR&;N(xNT{z#>?k@RpJr)N`)$tV6Euqgm zg;#2(G1u?}x!+_>iav0;6`rytzpNX2VtN7y^#c1x2yp2UL!cVfqIEa!5yslU{3)>A zv{OFVR6bZuh_;EAfzrco8PzqSYeJ}Q7$s;|*Fh{YFA#vwI@6(xvXIZ#uVOX|)jc*e zo5Yu{>2;&b7hn3N0H~Rs^5gqh+ocfkeOb(WymC3g6=czDNaWhsLs(=dhr#F}%-9!{ z#^jneLvNX7h;p2mSK(@5ir;S2z8P1T6BnXJ)dZFA7z&djj(m+$zx@$im(Yp;n%;s= zb1Kt;vSK0TALAKFXPySJ_FO_p?j;$uk6dm#`i#fbJQ#)ORw3$E;YuvW0&~$U3Cxeo z_-$r)P|umR96|#W##6#rEyn`9-!re#qzv8%@9?0&_-W~Ze>4&`CI$k^Mf#=L*+BYg zKfH&50ld(_*8!92W2{sr%7)lM|MDM&jPd><`@Ikz9fuaMAWWW9k2^%VbYuvv`{_}F z62i~Ui7Ud!3vIrU1)?-?Q$FNJ{#enwgP^79t-~0cuoon*$vekQx8EZ|d!#F!O2Y5E zz{w2Sg-1p`!0`g!bMq}2F z39oDuafP7)Vh6uM^|pAygQWqVUtn`(<~!+BcSEyq6ztuKVDwz*p6<91r2LH+M0-(O z&3#%Ra2vGQQ?CyJJYLn)2lmlQ{YRBeoL>2^Xb<=?oE>i6Kr07}o1hbq>#^#-3lg#Q zked`{<(Rh7ym5ZB*G<;g-8P=pcxo4BEN*2ABDh{kjdqncLYW&&Y1QEgnTwR=(e^++ zVBA~+)i3>CUz-78lztQ~`y+BDCw`i{5k|ghJ@lEmhslogn_IXK@`Jcsfspf2Ie&dn z3xqEWtose$lWJU!Myo?ZTFJ|55UR@nh1UZOEe>x4$qoY3GWr$XqNz0g0fKb2s=+#v z!y@Gj0cBmJ`&*v;KD5X`Am$;NO1L#isNz>XbltRebq7QyfZMS2PLWH%HzU1N_4yCB zLJOk0mwAQR3DAaA8#jc3{SZQY7{&3QUD=i;WBVey&Iz6RRm}a{S%A7>B-P2wf%Q@3 z!DBa|19huNr(?GCu(hVK0b0P1{U{NXSrp2A=>3xFj{)fTZ5M<*vzNb+H0Bf0r6!#U zTkIw=9q|xSa_n5X{2k%jQ6scvS-wENHoI~$VE;hrdLSOn+0XI%N}Q{&l^5tBB%1wO3=(7miR!F)rX#YjNZ_Y9WN-3pS3d@z63l6;{z%}|469uvGd8qOwE2`Ybnt940+;w6Wb zJ;aS}Vv+Wp)Qq`4QZ<=ht{RU+jErZ!fnbU`3WXRVUh*JkGakruYHZzmG&&p~VB!&) z>0qMGh%3yE3)x6reFtTN%?1|{w1aslyWA@BNJa!#k|^S6M=V$w zk$q%MZP>QRmhry?F9hb(`SdJf3l#JPB~^w@k|gMfI-Q`PSFU>iSO|3mEEHf3qL}rN zY8EjcF&f7xV&5WVJ4vS#_UOjNkiNlW_So?YyWRv9T8?yTV0gk;l)l2irRHMNWa(bh zVnI!UCOhI~zPDo5wO~j21MD1XT_U+!#@zCsR6w8)^g5H_!Y%73FWmt-UD%L^1rMRI=JqZt#a zO9y@ts{0Gg@ouxuMIdOP#28!ehV8$c_H_DFx+&cV^&9OnQsD_Ll1|o0Q{%tL_O~De zM&jKC;QqW1)aE{NkaGkj8rlNNdI&Wr`)J{bz>VhhwNj>ZL81eEPwNx0&_BPCkC;<+(X# z9yUq-);=8_-jua3Zmo+VD3(uz>JCY-O(Drp1f~pqbtD@mp73BM$?P~!so@^R+b17x zDnAw4xBRk0GHDv@ydm?!egCjG-em#bxe57T`5FPnVp?bxcBx(zXO6Tv<~U@%vz*uh z>NnaH?B2)@H1g{#hCTRtL6k3>RG!e?|D#UXwRKPcheN4E14~6VvXkb-w{|5K7b{Ar z?gG?kh63UoU~oB1=8N@Q%j9~sABj7cph}(nUZox}agG^Ee>l@Qt3w zm(%VCYaJmK=c%C26)^|i?^VirJq!&szbOp^E=2-{x{4Dolo+C0j?eaP8qXfbj7Mf}j z>mxh>1tLbMP7f{K7DeS#OMRGU?Mk*1(jw5PKMzwhtWSnT+X+9geyUX zNdnCq1^PPQVO1#KIAmn={lo6uA)8D6_C|CK0I+pU{s>FN>PLy?q&p;B8Tc^2I?cHe zP?E9@hZYUuCi(>$E(>I>8#sXUZE>fe7knm_`N61-lbjN|!r+1OlqOpu$ju}VsO(8R zbDAEFrSK~kAzFU{HW7pLdJ#a&RK{8lrCJXKGdHs05EaoxGNIq8h^}DpjkPUI5v3R) z1|t|Y96%G`4C2Hq=!Zp)plbpTo`zLtYeL5b6%=7Pg$|^}dF1oQE$r^-&5EEEaM~)8 zEy(a+yLJaRI)IwB0UOR~YiKWMI0DEMKd{itYGvljg7p>%s|0YEjM4f%4$c{cNM!Nm-v&N_1(2(p{q&ilhGr+B z(M)qdHg7tQ+dL{AR|1HIM7(@vEgO>>I4e~50yt{DBwpolCw|#3Kd$~AT7*DjfU{k2 zsN)7W`yEKbb5%AC#CNor@&j9*%$EWE!qqifQuyeI&Kw5K#t zn)Ws`MK|%CwSPk90>|nDy()$a*bvY~-T_`*3xMqf=qRktCB%VfKzR>Z9jH~1zpzyZ z>-i2a-H+cbQ2!`1Y+Y*-XhwDf1X4rHJz^B5K$375GwFVOn)V8s?PNGC!x;bAK?$%Y zzCq1Y?dpA|q4{xByLRqi`Xd+w;;2?S5-**)9G0F4et(D*3picsx>u!cGP_EOvUwrS z&>XO0*maxJ0M{a80c&+7+(;fAOuO1;ntC*K6pq@|9;uJ%=ZqqQ76%(zU*7#FUBhvz zLNE*>&p$qK6g$|AZHBeoT!hX&1VfiEN1aEm6wnNZq{Ii4fKV zjQAKHc4$_LX7B*nnuDdRm+=9h82+ejjARH^ZON4=p0u5#*G+e}(JU+~&B8p~j!t9= z(`C$|)WX(^4s5NM!qy75;CzZNdKh_+-`%x$OGinV+AU{7e{$kgJ*qUxBvGH;@Y1ZX z0)L6L&TpzBNDW9P{;a8CfvrZb3D zr$(JvDj*-gz8P#+)WRXEqp0^NIG3Yx^coiQU_WymSW90|O{K`Em}p{sFsD7-y`ulf zJ`UU02-5IvIR!Yd3T2jYrvu^!btD|LfHGTNTK`GeOXz7;HI-8X43df4tp#a${Uq0| z*S#|U-1}hz#6KFeVbqZ#dmBZ;%-_*9l;B`R47jXlU4n_FVNUx=dfTdZKE)uNruExn ze-Hp#in0J#;Ekb7b;WEU1vQ>eSQw0pP1hp7zW2gq(KIoOV(A6*#nm_CSkI zuA+uTbOCy}0z2UmdvSF~MArdUJDjCRf9zhN2%9?7%CKgRv6eHlxhY&N54y}tPFA0p zEshaE`=b5CP;ow2iWM9^^kTuh`ccWRw#<%)81{yopHQ=delI2QYAz$@ zAK|j=@JKEVw9&YZTHbtLni+gEZW`c`C&T;#H)=gZq{ZvmT;M&*R(Wf zQ*d!8g}v!x)z*Mv*NSELRvS`b>QKf@UY9p&BlpYxT4!bbEZFrEteC0>seJobzk7r zHb5T#+ZsAj>La zEhf=#z$%JtSxMaqENm@AXhvMf8juCJkt|T}x}cj?<(Pvz+oy@?aZ53auk#TFGN1ys zCBG4!;BAe|f3&xZVgyF|Sx7dm+`nQicD+}VRR@qMc@0qE4~XGdC^DcPMAx|7fOD;D zzBJ=auN7?VLS}YvV$1!YH+TFF3gc)|B8YjTyDH5lcO9WfEeMWic|z!NP#K{Ijt%TG z_Wh9?mHXq7mrv0y(EVX(6d(Z?O`*CKU#yYu?8K1DFwu*_F21RB>fS<01Eg-?_>LB= zV1enhJYj~@C&A1Xs7N97T~I|HV$B>Ouygdle`T6rvrZ){Oj7~-d|$;AS(66{DM^V} zXU6LP&KA_DH2{%0Q??`)4#wM|h)$k&Q1sE?--|>+_rzk_aeJP2aIYXFk|L-NXk6HA z`9`ejogb4r2|e4)yca2d`7&&uXf-b%Ce#T7L61q^SU057Gg!2Js0@VmxIHU~<%f0w zEK}ym!HmprDS^C{!bj`Z7J{?%8eB;=bKXstZRLi8E<|+D<;&ao3h^V@kC>}$1tPV^ ztYLZ&*s1?|;?>aObD@QJ!Oj&)r?3pL#OO81=3o^n-@WuPhD{$G6?8Q>NR@Okvyn-lAY4 zYY?)0Q8N^(Qos4D5d=VNQK}3a){Q7=})BR;wYaZ`dJGiMF>}CZm9%2&n z5u_YWBDwhOVRePxXWaB{!)rU)C`oOFp*~TmY94|hW&-CKU{B~fg7nVo|2rUodO=VH zjiU0I6O_3^C^eWr6pIfKWjHs7<8)AK=yi0|cb*EEJ9E31&zC<6FH;jw{QoF8_%|e? zICo(@5Dt6PC7|?Q5FoD>o>z+fP=*TRG>d}<3zi@RZLB8ise`RXVbvFaN2|iP-f&Wq_GPj z0CXuYZG$)*0~U~xeno^mc?2WE9C*yz#r3r;a>7|q=zyR-nzD9>BkL)18gdYA^daXi z;QwD{>!x?yuh^3-~v-=))j61O~^cIB_qbIoowPgceao_G0e ztKCn$l^QbUjKhs*)H4_%$}7#cNpDCM546-#zEf)zAMFd0 z4S)`uTZ)P1iECo2($7vyKNBw?nRM^mYb8jST&Z8Glei_S-b$O+PY(4}5}S9D?X}V%kyyfok}>Mtt65g$r!^%o z3+3cHUyz(CxH1tA;5NMBZ{`x?*jz%p&P$C%2tH=>yI1#njB}9^Kts zWjmPSXtiJDSp3X2r{8}%Nq&0S!SYy#9M*vJiLosWs#7o2qF>N9GJ@QV2eao)RSjNR zPL-yH_32nz6k*29b@1F?-^2})NeyiA`Iv|>HtsD zn!k@TibaT{VBA@yl~Gl&*5NrLIf8TYiI-XxSVC!JKcBnuPT{X zK4;19%jzHAMqbEn6W3`n{c`5}lP%0`R1MymsUS zw3zsl>3 zt;F2H!qZuz`TUwoEJv;w*Crk7?!KCu=|!$0cyV0+3p(aootQT{)Kf`}-#2_u$rvkJ zPms_{E|IQRwIzIt_obgSJU@D7*=YNmmEP9B;*Y#Q)Aj+l>Q*EQ@}FDNdiPetTqqpG zj*0BTM(SJ)mRlC8F@mi7lyHx_CO&$^o6p9G@aueH2qPpH89BsiNB#vz6j7S9O*`^B zfQ7w0vrO>ZvcPr_ZjfU*dPx8)m10KSw`5t);LaW&@}9RLRO=LwlTe=K6JLfsrWr=I zu4KKrrMm8WB$V~*hw1prqg&)-QLHW#seo-3rcL5v0Tj(qshf zoLlHmd#zq?)>|~v*Bs1{2o`QLDG1Y88MO8muIid__AV7f}Alb;52vaL0?~7@^l` z>I5U)@cg7N8@r;^{ry+8gxNxdrtnZh`=lDR;K=xR{LXPWQ3YL-^$UB}lS?0x zrJt$Hq2&RvXx!l88-blwYnE@sBmp}VE5x_kz<%~-KHAXumIGjwPe*cLm%^S;bD3rH z741u?mVKe_wStd4u>Am(^ZVX7gOjqnz=(OOwy&EP!Dne1QF`obM#X~y1 zd~^BbpN7R)Pa#l07Mwl?7Q`3VawA>d3af8md%ebcJ6=UpAXo&FNX^_jSGL6Li@8S# zJ)tCK2i80a2hh+Y;Y1tQL^b2T5})^h_*lgg>d3j~t*UUi4i54BT3vm=5l$rTwBy<* z{mjPj{GB;`JC&N2o_-*_=JmI;05m@Oq;KbC-XS#KiDv0~U2w)jY&~?r$cvt3|B?S2 zpP9o$W$=(Z1dI+xRtQQ69?bvn3TnilDCsNIZ-f})p55IFj)GM2fuIg!OP&_>l~Hj% z;kyIpEE5{_Dvp!($ZsuGlQCvsJos#`K3hP2R#C#do}Ap;b*fn0F%&sq#qv1mifDodR;?38u8otEytN|NhDAoy)(6DEL|s<-jr^-CYF&%wjGYmXk$k1wVg_36|KPhMkMEIPL)MfT+L z*&5tkDb6S;Z52qrU;2-1@!9bU+rg}WljTOu=%t<(&3l%>i~S4tG|2rWA?w1 zKsa<}q*8u4_pfU(y}(X^n{eFBtb@Z~F4s0bQ7CIo)Dg36{#W{E^F9v3F{8lnuI_v! z*%$LIvx{vGA)NeP5i_iz<%e6z`w~7#I;@>LRUmz4V`Vb9nH*(ol1j(~fFMSyHCC4EC zB;nQF?ZaAfhR?%BOka+30*BX>vz?mee zdQ`Q6gB_Z+a<<%sx`Eso@Nx4u0LjGeDh80CRoQkvzJnd^x?uCq%q`4|%)LN`?Iw%C z7W@TLHfUu%44k|S9+N8%BWl@t@q#v+SR4sipK5~PqKiLo+RsE ziJiifE=#gq;O4!K({)FLv5&AnfWk01i%+w3>>^2Iq>~NL2cBD2{hST_){rgM zW#ROeWbNqett+tp=i=KO&n#ai7iG3RUM; zwSc&}_4Lpr2Vc>~VNFNpsvyzeLWeD@<1Vp%Pz}l;sckhY3W!CVJ)uw`Fk(k1 zED2Rkw4_a#^}cw*e&fN0OCKn6&!m7IHo|A_Gw_UXi^|HVsgvv1KxQRD7IWgUe!)YV zN*f%wUjaZv z1qG_!M_$^9iz^Xoy5N~*4oUxP-s1NM=Mp3b4468I=q&nHyBPO!aaQK~?@imfF&k#O zPxwI~Q?YLoRlK%7QzzQPQl33uU|ll{qM)c8i)9Mf$kb*iMQKbS6HU@aU5o z{)>kl>QLj}kCmvN)TZ~Wl3W&KZd}77ztG8v4%MWVOWLJ6(B&aO3O=2tE;bvVTlIf% z?aPDp(e*jE0ictbr8}+wfknu|hrxcTlxNX@aITu>E&OBT0>i&SK*!S%&5f7#YxRG0 zT9sFNxSn1f^-^K|@UkXB>SK<$CUY!=worv(7vP$Zv0>&W-|z&ZA6|a5+SKI@rx;N} zU_;-(b=vh|Wa#U?GkW0rCxj}+p7vt(KQF~B5B;C8Jm!+9-gl1({jst)%H7{y35Le0 z{lZDxms$jS;m@=U&%4ewoEcCM<=dVb^j9St75OU#(t_IzQN@^MiO|*_=QP;(nvKWd z+s;4!21Iz#bwWkAXt)xf|7Wl@Y?q*?p!+w0g9?A;#SZ4_vgy?c&v+D0lAKlKOfHER zVn)*+mKlVzAtiL+;U6aOhm~gjOI2Zn27PqY)Y68KeGhYk{`Yk263P-Qcx=mxd<{mB zh4FEVxpa>O{>_9xnXo2MV!5|yn}+FK5^ia9%AgE3H$q;Yw zy(pA+$tgMaC45*~Yc7dQTg!l66kaTax3SWWl2p!tV;!0s>jcVD1U+K}&0~Yvx|WG8 z;Zn!bf7MRP+FGJrqZmUWu6@`rBC_aU644+Q!lp9*8=3ka#iak)7Z)T)ttv1afiHfJ z*!$rR!AXx{%A8VCBWj2;XFii!-UgN96KpR7Jc3mB0C_Xfd-6M3X18KES-wCAvg{K0 z3h%?Wr;NWujS)HxlvoOf=x2UV<%z)4sQ;f>|BXWZC>ClZ_&o1P?eZ3z+c+&zDVD z3>H8LUsP9et4xGYM^wFfM{9;>}w63uKWuGpg}s~!B7n*S?_#-~_S zlNGAPe~X3`06kHOL0AOVA z`45hiEgg1PNi3Mwvn=G8TRFQSKw*}>s)7cduh?q>%F3^{q=cUr%&PwO|B;hpQhMZ(C#}$@H2H*o;*(5l z925)%Sq6{G#IrcaK9$}vx*L~QK(uN_l~k~^>X!d>D^m%=G7CF~Rss|REnn62U-HtM zq`(Dom7irMAsha>4DJmaE@di#lt>ML)&xz>$Lxg;oN$xicmrsJJL8(1`3GJaBjdq; zF&h!P`du|B?mYi)O_e7QFbx@t6hEW zi#4J}LuWS5PWrzn+Enb=w)6kSaUVXH!>k7&Y@z4_RYx}3_cMEkB7gH-wjbMT6-YQq z!}TZY?P0L9F@Clh&1fzRA#68RTxEiQ^uIDRIMB%Bb|)4F^r`j`mfa@Vj89Ab&zPV7 zO>}JKn{r_-Ef|)&a5M159Exq-S36 zO}#L%beOLyQGBC{HV-%u!#KyPueSYt&6VWZDX<5~7oJBcE_gucH3^7eiI+ZrJ{Q$Y zm+{=S*NT5&3Ck|Xz%kyIbTE7_{CF_)4;*lyey;1Zf`%GiFD^7T&D0>y>iQ-HgZwH0 zuV#yr2mvc&Rp>xZpdnHG3%2k(K)o|%1~W^ll3fHqb$?2{A&#^TRLwaJYIPdIV&NVpTS(U4v{0#@xlyUd$^LmvpKwIdKW98~E62e|o{I^gA3*w_Vo z^e2FUWPgHEw=zYLddj$l^nihcsz7=P=agBWoUS8*x@0a-&_v+W!gz>ah&T{=buNQ>6oGmg5j(Y^5}#9O}o!Pp(Ah>c8#yeT9iw?zLJRq&DE!r*qba z$dZ{eyD-cf%$}V)2Z(GX*B(~M8A2K$7d%|{oQD=W7wqU}INY1zJ#Smj?M_;{gG-1e zYn`v_hQBGG+1vC9$v*g&9XS$KaGdc0?A(-*IqjEPX4}S}xRI7VD^SQWHVSWVe;Zk? zQ^SL*1_VJ1*wn4`oWgrwNHI^D!{H z*g2eJFE~YFS!^!fKqnJ2pFlXSgXYuqP!EJ-jC$W3BWX^CKI{%b>I<8`w>`9=4h(Zcn zqbiDPG#~+>hM+4Q<{(M8dJrT_TZ*|abWCN#9Lg1+wAl!eDtUCO)9>=yYem8t$P7q; zKh%ICkMXvNN9;(*ZWe5%y$DW|GPj%7W@#Txa=mt|fb{Egu!3;fhMrB{o3kb0TLel^ zFP9f73i2H`acbjX0xHZvqQhUwE_`x-hLxp3ay?uxe<%gMG1i4|WU{|}1Gp(u$S)CC zoYH_u$E>Ii{7J2Y>_p_lGRl`@CVzEqhU0I~nxJ!msOEraY+~j79mu0pJ^NN2IN`S` zLv3Lc=8|3`F>L||G{z1<5({j0ePGlo3d?@aFsdr4OrMQGC5c%+CdeexPEi z&A~fRixcQjxyLu20Ne1^svyO4wqR2}eRAS+5yLLV*(fj4LQ43iy8o&j+FNki_`=<^ zbVckQN9a1=Uo+tkC&D*#b9l}k|MgO)4NZ5G>5Ex~z?GEC;DvBz9wFsL@@?p!+y`v# ze~Vmaf|SX}@g;4^ZT$|1tY=c@eDE4%<#c)qXYR+Uhde0E*ycq}v+t2flanO163MzVP9EK|Alq5%N?1IUpS~jqf@tAfI^!x0}0U|s~&6(`&HZEm%Ll8=x?NaA-x4ycyO>mUiqHySBW9V*t?4lIcBATrC~etvLD@ zdmRPD8()GlVi_52*Gp8xRu5cc?zrk}2K!EUXh_rW&;a5ebF`m_C7<7^SP2+9!!b6= zY%6$Z_5|(=-PC{x90F%_1UsO4I9}LbuT_aIh%w}`-FVn2E#9v~*W!NMAAU?ddp`&&f1lXI}9VMV*b|0OID= zaM!w5^{?PaLJd$fU)MvY;Vw<}cX%B^dDQzW<0}}UzCoMbS}E=X7={luHyJe4THk7i z+I>)5rXW~CS>?X2Tj<}10)-GqXzP@dGjPbnkf~3odLiDf8FoqAAZ@B48zr|VY!CuJ z-+f8j$RO2lC;ZtLRFx?Ap!_T7g(K#)}dl}N_KQg)*p7( zQpkOk$!!U(-re1TzTxYyw4Tptg98~J<+B;t>QEj;&)VtJ_-Ot?dz)KS##*hqA%VI#j9CguqQWQN?9M{M_#8SYn1Y(19O=@kqW z%WpHJn*0*D<>QDKE2n)6A8P~a@}iPF4M+(%t`M>-`_mBsyX^IPT!l4O6DEJb23sqn z#jJ3U%Q=Tx*xB1sPy}@BE4xbVDyu%n2=0W4h!IiH2l~8G!najN)+GMMg%ivlIQD_;LL{thP zsUeEG)g)=u+hWRKU(m?42caG4FO22iRHrZh%~CEc^aWadFI&s4#gR=Cbt{`!;{h zr1A7Jix%b$U~4<&ssRN*A?pFOVfj<&b>DiIK4mAiblrZJyy?8q@0I70FE~-`XZOmwZbSdOgu&_uWNpF$jby^9I}%?=#$9jU z_Fw#mcldp~XIJI|0R)JHPf^Y$!>2t(C=ACly{T01EY=-D&?DZLHutq6+i+N?{@Eb9*b9>dY zX&FA2jqGl@s0Y7)(+C>u1;A!Yx*JxsBm41nQJ6r9|7we{R=g=z9B`00@<7BTwwUrB zmbOC4pnsmgnx?yPld70HPi2?gpN0sPzRCUawg(zq3-g0FR`&W_CL)s>===`~f~DiH z%rIn6KuX?+h709frBI#BD|>Xil=(D4dWi7G_MTZnL#?h(!YF==c2xtbMVYi~ckN%B zCld(xi@>)bPqYQSZx)P zD4Ok{HQLz{jY`8_Y$>@fzL?S=u;H^jKQ)NNlf&TaD^`*Ez%NG0esSAq?#cy;|yyI;V$|Wlt06aQ`vvWL8w8-m3+a=L2 zIh~e_9Q9DAmhoEX_nEur{XcXM%lEo{n;v?{%)hW#b*m@IH3p!Gbq%1+dX7z<#RqCj zL|RQID=ULvfJRRU*|);)hMW*()&;ZM^_BQiBpEP=ijDAZ=P8R0^l((EhEJv2N1mq# z_*yDFvlHJev~It5;`};%pVdq@(LnSHn^Lnv5r=}XS9BNzwJ{_9LkuT`;Zk2(@h><2 zgCQbj!3m2;a?6vc9AtsfxAY&=g#(~-#Q4D3?XUnt6=DHZ*A{nNWz;C9CYm|Au@y~a zaxFvRaiHGARc=%69`mgEcSdj-!l*R!LQpEP9x$9BH~Y3$dOE$vfjzF@rSDz*hugOo zFTVsNWK2nM7uf?&uOA7kKRIf^WRVi8X9QRDN>{mfJnh)W((4Xlwa~3C!?gls#$0X7 z=xJsb4O(gWX=0@%Q>0cZVtlKA|K{_{35D747_mpo_*Lfgh$01Xl@TIZ`u4pieA|T+ zYNriwEq~;E^)XW4nDMYSsa7WSsF~xjH021Rz)1XdebWl6_4QXZC}H_qX*}3iZcsvi zc;`f3j)rKB!0tpww0I4QUu9)F8-JOSuOQG!xzl}ihC_X-$wLV;FIwggrqyUN@S{-k zy2D@5Gx+4-NyS{1PPIot2C41UL@_rd5NufH1PU^25iO1yLW}?&Fvdou30nP zY1L4;S}bM6sDY7|S8u34qW4@f&$1hpC?vhmC46DYAi`b?RTW>VLC?xjiwA1`(}m-O z<<$%8Pt+c~IY=Qgf}Ohv$K1aux;9TabX1gr(1jbxH>y{us2I`von23KHBg1UIiuUN z-}wuPrmUj;FH!UG#n!i{LBDJt-Dg0JTkxHoJVA=R2)r<&BagB2lkx;I_wZ|~T@`oO z+P8(TJ$f{~Sm*bV1zojh43$iMuEcDdUDK#-G8J%U4bsWuIic_!PHHOnde!M}i`$pg zf>!G26?*RMw7(?XeG=3t766nT_o}5U=W^TbesC($KCkM58xmr#L6Xu*BYt-ghBazJ zVGLL1zsD!f+U27ylw^H%==-ylP8mH22p0yrQZ=gw&J}*GcHQmA6e@yNGk=ZxAKJyc zLO35rcA{Nn*8D44xK^vZ^BM)`eV#c$Jh1+)I^<;^Uig1)p2*fx6k zil2Z$kU||9x-dR#oKb z4f+#4t#_lZx(7)AzwN4PrSQEU+l6n!{%CV-8Ak&3Qv0BA`?kB>T68B`r~m3xS6EcK z`WYRs90*jOmw43iTF5)+PAH5#Skzbc^jEY1S#7)8-RMW*d?ogcb*NM339n39!T+o7 zB9ErdW_~#L#+)M8-!=YHOTUXu#Ex`k2E+EZ^v(eGsg>FAGczz-P&U}<}gaPhf_z&;98N8jja-7t06sVJr{8Np;%LMAh z!>%n5V>XO~0=%aA8=>r>2?3-U)KF4|)ri0eocLl-gq&?bN8SL(eA? zwD(09JEco~uP;6A*i7HWK#sZZ)=`rpDY-rQNP&{1T{|c9a^DnZI?Wqg_OM;8-g|3+w4q?IjpB?eoTy;;d@2}-#!T_wcVVvu@ zG6f*8+MM_UG&CwlMPwx~!$=V|Qw+z8?b1=0@h0LXJ%$o}$L3YGzU6k(eZ6DP^oy)- zGl+dJA6&dTHSG|Q5FCb~FZfXH`oe-t8H!VqzkG}7p`U{ylkpPve zlcQcBQ~oewJ`s38?0HgA3|Cj5*F9$nwOT^gCJ%Aee`*I;aPNA=+cx%;$P==h?PPZ6 z@%hNug38uE;4m6P9*X6E3H7K;+A}aUOC8Zd!9_gc@>6vY`3g2gKruIx@7(?(JFTR`)8H`K%IMnq=R~l3`QSu6f=SnT)e9k^9So zs$CWfg0&4}Yf)hIcnqH)#!d>Gpwn%=a<;!orJyk@>Pjs_Z&n~e`9h)~>CDhn`G-_N z0||*CUN;$c3E=Q1*bGTp>)_iy#DZ5%DD-8B$B1Zx7rCq~NQ{;gZBq1npulF13PROX zVmJ8mW_DeWctgvKk+5N$v?iGlP{$G{+7c3k6{g8n4+Hm9|8m@yV;&;^2_K|MHU|_r zu?_Paw~`oy(HijKQ8s0PuJYJUH}E8eYxfx2)+_X%>6Gw$+E4lTus3j-5ChXNTp$8gq0)7u5Mbd4(Rsn{DkaVx##3z<|U+ z2k=P<$yj-VmQ9u|Y>$>16>8dI&pmO2_))hxS@+&w#$I=t>3~I9E47FNnzFpULbFZv=Dj-F5`oxlE7Y1b!ruE z^^iy*J-U!Y<5FM)rI2G9qJDva_8rbwsd2f}hEo%43;H~@r4y6ozvcR0TV5HF6X4Cd zU8=K)baxVw_D{O923IAJvL&%qrBK7bQGx60O`VTf#anQS(XArgc3c}4nc_*_=58Nk zR2R)t+KxM=zP}CaE{JWD3zNunG<5~l_dIU#K53hr;D#s`89M^+G879W z$=28I`JPf2Qt?vAp@n*&Dfx25IMKE039D5C;?I+9MI=jjot5-;$LOuh(o($cTDm(@ zdu#M$YY*TYq1M;}EFgALT*tUh> z`lMPg^6Fh*kO$3=?G@sM_c1$5>FnTiYdmU;3AKc}LKv-d59SthgB8DLGe{q>_gAU- zkc2|L?$Q~);cJbt7Nz0?#-vqb!CNpijsp5Qv^PP621ICv!t28pF-dREbJz(Mdy9Yi zIvG|1!|)Y4a{V-mnAQ7Ckc8d6b@?zEwSU7jQceFPV>6a$<97T91AfOqfV4lG;{P5fd_pY4cBeu747TT7-Cc%kW0#546d`SyoNbH#YN| z#(H|@&kxqVuo^k+af`j(*pHAHAl>FtZhi@Qp${^_I1q<0s6;kgGiN*20zUbVwRCK4 zi9-hnfU>W0I=5EIjxsIC^D)6mkIe=%(Bv9s49c3w7hJ0)}-9e7SCT z4qwDAm-e$j+;>RjvQ#JhN^LfyRkpMkpO(c$b6-K^x+OYG`s$Rw?jThx|qg2FiG|kOrRbUAoT!6&RR3tEJBn^mJoM-)%{z%Ma+xE zWmJ#L(WAQfYNQVzYU!xbW=QO#{UZ-_^%@DhN%bY!Vqg8S0aqiE3X8H zA;U{dq;LgQZBCd%rBOAqxwTl^Z}>9DCn7RTClgVa^=| zoB?|stOVj~mHNjE_(w>H{HSuekU%imYR60rt2&zN_|~L^@al)-8{6f}yQ}5`*SoMW z>=!hnO8KjgwEDb0Nr4(ra+{PN+XvTYk7YNB(}S7i9|$%|a)_+WHA;gIsy(b-o-g}4 z!7SD|AWBn2^c)96DsG_foY>fOG(Yp09)VX=w)c~Mo^8WY_y96EhT!qB(k~48s00GX`E#oWN z>fg(SU5ZTT>h=k$5jFq0wxg@;0ueiC@gPhDf5vSd+`I2OT#%`p>m1e>m3ce_?!U%U zoEoJ~mWxliY;oPt^kCnF`ezNC5IO;iFV!1(hat$c2GDi@Q1>p0xhx;dlN}#x+a=qH zM}`t7+=PPP;cv}yx6!RdnD?dbIW$5pPLQ-LfLWuIdIVyRi;@-9pb#7l!QkV!<24%K zjd#G0E>;X^X}NunyuKi@+LdSaEgWC(o+oJ{zCky}J$qkJXGD&1Wm;GF_d$_s;7i>6 z=<6AKoOKr{R}ZVsf+%le-iL%ne|>?eApf~CE$D~b)rIQ?lLc8sr{4m`igo%?FsSf> zTO!X#7UZ|aVL<(u#vpVUM@Q{xdr@PDPfhNutWe(W3EKJws=2s#zyN>d&X}?*Qe#hm ze7HE{Hn<|$Z^4bCYi=uX;s~`POp7`?#yZu~2#!-@$*5NBf?FnmU| z{zy}|1RN9$^3_7E19yR#X?M$d5v355^1(UnyNQ^glv~|{j}B(hcyy~Eoou*x2;ZOVB<`83oMCcDQI^L#O#2819 z+nC!R_G%gLtOAHcQ8PuB0=7g z@n#d)=vg=!QUIjS!*{Hr5d2s^iw^kGOc+LGU|@^qK@xi8F=F>v7U<@dmMW<-(*6xq z?J^XKvqQu|;V(lXi#!!d{uq(0$n>e7b1cj*ue>Jf-xpb6ayMt_P)rsqV^nw0^y&O2OX>)`|bc z1-qQmpYZBP(;e%EZVwzA5oEpS(oq-LmgwkNCCn-B5T-0NEHiazT5tH=yimZ%|_Rc_-t||4c6o;eNQ{!KEwr;I~p!^Q3)=_-H ze-j#$Z0r|DX;A4~ig&y0IA}ago7&w#J=@yPnH1VSiW+>DN|kk&)gGCu_sK^qP&xB& zgQl(Jvn&1y`Z3+i1{$ACU`@isO@47bx?|r>^!|)X-&VM?&CHi*u2G@#+Q$w|p|A7S zkI8=G?-soHtZuM4h1J?#V1g%ik|$*#0ws>1V+xfE^*>X)U>^V7>f=p*qCnfg#0@9) zx}{wI?iu>v)`pt!p+sX}{|7wvb4Jhuu_j$O1opjFG!=sb0RVH=l zl`AKHUB%mMkf~y-!Tu{zu6aV0Z%4#@^CiV63c@-{m7cU%P-1h3L^+=Q%SD1=RW5Nl z>X2ByD~@wm8t~@uUuCpYAs*{?rI*p-aePt2#Whfnh4~%Z;-@pKT-rVA4Z|yX2SnOw zM%gERUzTvun}3u4MKZ6mkymQ#KM!YB6K(q9;H(UKl77co`vvqXf2qJ{a*yCeKDQW6TvcQ5zC)kKuu{$NF50OcZA#yv z2M_(?r5Cu7n2r;r(e>B33t9t0i>#rKci6qk_M(d1OhzRY^G7hR+R`RGU3~8IaEAA+&xuU=AN!YdfpL}I<0q1b7Ft|>SCMp6H`PI>4(3{rurr;((Pw4D{A45K*Nuji z)Q=V{J+-a4`@-ij4tHUC7EBz$u1+~rG(qj!Ihv-U)(t08oawc5Rps3B35nk^;}8{` z(1xwik(BL4bsSscLM_{aNjb9E^!tUVQ--opS#?;UnRj<8)|0Ef>d#%yd$*?e zIP60kr^cN*m6((;am^EM2mb<@&@Qw_Hzi+>nq$7SLP%!4CacF1U1ejZI`14GNXt}T zM^K=CTvc7a+v&ynCAv}}!h~3RONKgMG`F2w+j%ppLL{|mxU~MEetR+_D4)quG-44I(J$3kV=BulUlFvhPjwC z%_eav+~-gJSlq9$L81&MQAR$L@eT)QNQ6iv&6&97k{{r32WfAYGPCBc3n`f`YWt2?W2K(E$bxaEk zn%du|J>=_%IV5}*f9-NowHPr1=|HF3pMaUx#Ppy|eg`w2|Iq*BL z1(`EU?TSk#pMtq?G4`3qFc&Kvt50m44u?_{4bPlL5JwMJaZgFPUNc%pkF|s7ncSWM zk4m<~c=!~oR7PEkQfGiq@xoT}HjfBY36#w=CIc36Vp6X6 zZ{M+x7a1Hv&@%N zgpyY=Y&yG!p(6|i@x%4T(bt-I_Yo!56%LtF52C@}Hp;1o&RjIMFRRF(SN9NFIRJS_ z5qXnKPB5AkWJ#GS{e37q)jz^oCq^qbEN@x6SyyL?2hLDO$=2pJWsPl$-wK#g|@IBoxu3w^Ekav&0D z&N@@YOupW~bjLycyrWW_ClDPzt8;z9VNMfwH1P?vZM`5-YeHg{g%l6~(G}a&E+os0 z42&QWXZp3a*Q)7EbOFSPegIAwQZ z?(@3{p}RiJZl6iO^%RM9k?_O!*XB8|1$xk35L{McYR%J`oc{m#v{O%d{q-pB^OA_VaG}dX<=lQ@-*XhnXXEk3csuav+@fLPZ3!E+d6|^i#4{I~U z#ZB<(V&{a5vV0bQx0fr?js{nme{+SawXTky!~GA^8R0XF|Au{)`bi73YiSjKlqxu*;glN7MT4sHOBxO<=?z9=oi1}aj zo15kYY%8u(-Kl?Qv~*TzS~`?9aZM3T!E_>={gH}bve$2`DxH=V**nWfpJ8x#2>`Vp z-WSffZqQ^1Yf&T1AE(homahjf4)MKcmgli9@XIL<5aU;{G~!fx8(MBgoqv`ewS~mI z!y10NX}6`Q{$ezq1NzU0;Wg$C@x5(~%K6@Xepvs`Ls%LD5mf8!I~emGBpTayG8*Q2 zM|zS+jz~q+#kk2UHE`ZZ1*!**4zwqu75SzA9ptvE(gFef!V_((yAGGb!LfLbcy7@1 z4(7^ik+}*uzKtlWDJz{5mUj{;W|le5bRI#(W`5TS*RrM2_7zhx)dBS+4Z zfc;~=tg>gK-&a-MJ-tcaU+wsnBhLjq5T1j2?6LW_(5~_#5L2ohxq%+d_4Y+{H^{7{A3g+RQC{1&3yT; z-VmQtt>ra(3FdeyXHF%nJ4v)+Jq8$6SB!_i`|9Dj3m^gtca^BlP$Ci^o_WWhNus(O z@E)Q%cvB;ip)(v++NINOyn~jw_PqLaQpYj~T%XB_0ymW4L=OVzd;UxQ3H~3*Ei~?O zR2j$SdFRbDUz+%B8ayQwSICj|WgJ*-NH6`nZ^w2PF}rX$K{gVb#BWP~T2uIH5Y4Ld z@wy`*gpbp44ouNNfOV)^@Dna}?|~FJO`O93!h>7RM+VjpVmS+hth){brv#sV^si62 zI0(TpgT7VwPBeiA@;JHt76m&H0<}ZDF*7T zL|h~Yeu0g}y;0XWmG@PKW9$Rc{xc8!_=Au0`oIg)8%{H_TvVuvF~sk1@%`_JFA1)= zjWs%>o<_rmZjz5`sMPS(KJH%!hYTr69g(T?dj3`y3 z{<90areVwKH;>+B6`?{I0IRNFsSAGixdS0bEPoH&A zbbmFwx8!)oqlh5O0v8CC5 zaj?Wg`zEf5;U<2c_~{feSPR@Jx)YKCsdZ>X($l|S*z%=zesS6qZ^TMoqYT=giot93 zIw|i}m4vW6yhBVXhz2Q|6F`e6+;=bnCPZ`R@y|;wws9PJ*O&p-zgQ^Ypl9;zUDaS{ zA7llay*90?d<)E#5a5F70p&YK159w?^kwEzuw=8_%In@Hb(}#+;ej)KPA4|&M)6{i zNvH`Yc&#B8(!(WD*9e|uY!2%Oy&~wXhH1UeD#C5OgoNG>9hM`LbH$+PB%GI5PUc_4 zJ!%90o`+#^a!3x8GNE~GTv!yF*nbD*s2MO zTnHD<>eTa;krZ;;hTy(KS0MG;+A1vjE+#l3c}PweGX(j0y`~t^h;H=ZyvJ`J=i*5N z=XtW#l(rT@(NQZAB5jDhc&bo68+Q5Ze*56#3k@!N%)8SjLO3MJ3}iP8wrIcMEew;% zp$3D1Ls&5Nnw<`4j=|`({|T5eTS3+SI(IqSw#Yv5;T{IRzsFM8R6b%ic4m|0K;~v= z74^lD1m^2(!3Ws1HgP#4Odw!x^Yh)c>N=-iwaIX@bYcI@O2+x6+4f`KEJrCIkEprr zBbAx%<9jY-`EtidT5aUJc`B4M9@ViAxBfc7USp&zzja@1g8m^RCg)y`7L#+eO`W>H zM5)U{A!ppW5yZ$wkEgjn0Ly_xU>kedfF5~VT-z53(n8E%L=+Q3Zva+#K}Zg@AE{Ym zRB4{{evcjd`jZ))Zt{t(x$`iW*aD z*rWfRemjGAhtRE|Nj}YH_erpPjz9M~!TcX-fPS+QmVZYU%MZl$m&C6}=4hLeuvh4U zlj7n`yz{057I~4Ms9EdMY58(9VpI~cH`)(LGq35aQ;j%f&=jx)vwQ)@O-nv`BFY=) z4#X@s1a3ccNw|1~zf^#X(?IWM7)k8LOG69(WY}n!QhtP-j>#FejxmGOk=m%{$o>=p z7F94k#(aypeQbbGi(J&4JKj&hiXMWfg7RkB9nO<=&^$QBk9pG#0vJIAFdhOZ>M*62 zz>gm;`Wb?k=hCa?UjV9*apEa6Jmt$>fhx#^O;2eyglC%u&I_EJIi4*4H{x)ho~W5O+id6TK+hdd#(>{@U$B2U5eK^|dXaD(<67nGTm9a@Pv5~Tm>QD^)mo<< zPwmzhDwP{z0VAB zdR^K~6lSg=aQ)ke7gN%Ew2~_n(yZL zXj8_CBxaik+?K$v6Lu}Xd&{7yfneZOvk3?ZlwgKnnjoG84>!)s6|S{9@Nnm2 zp$W!L@LcgQ(aq1Xvp+mLT?&m16V`Is(O?eaEfsndg^ou3lN@&IIZ;nD{5d#?Vj*S` zNUa2IU+)<1|BTd+CJ(6}CGxIrr;d}1(E=G`jglpmJMYev;IwVOHo)A5=TPfn0^}0p zK8-`QV)ruQ!@o69v&zUUHp75D8M=qIJx;}0Q)B-J7&dq*3ne)SVM6j`k?DB7Bjp&g z?eTF2$DCAi<2iM3wdJ{#yYd#iJe=tI%mdo^(hRCqCU&37aDAQffr`RW;Bbk?VkE~K zU})k17|uIBTh4J`%h=hmZjEk3GisGvv{%$%5>%0cEGP#$YpC}a5vmjDv7<~Lk!|(>(i= zKQ}QYq2=crKi>^Zvxt3BzUrR|W)=$u%P9I|qwD&-FZhJazXJ zp+CQ@P`$(=E-dcC&jQ=zp#?T%H+bJ<_;{>b<+J>zUcMl;E0YD06rIsCMi!tW?@nEo zJxZ#euN^JUMGi(;lJCs&A=t3A=M;Y~M^~YA?747X-*fC4=8Nu|49P@h{w%@ivjVlL zhp!R{?&|<{O#WJEshMHtfHNI#q z-inpA+S&PK!jO1*k)JdLnTP?*3{>o}vEoEn4)6VT>Y2w1a5oE~s$UVOjuY}wz4=B7 zrfFwu=NCAno-nzER8JR-LhMMak7jUo&jHko!F@&#{PUwYE$`+F`Ou%mX}v5l#$#NS zbtD!4*~@y?9=C|Nk`8Ywq}lXrg>q@li)G?OM>7g=N^I`dM+$E_8D)`MPxkD5^54@P znqSjh77s1Gn}rV9!EvbQ4W^zwD_P(ou)uWRMMi36GG5;#_~(2ooD{Sc^=MuvyUr>e zry~98Q3cN?@Xss9J~C^(grZPI;QuFbe&l)j2CCSMAp4e!xtgY0>&B3G{|u83VL`Yj z^w`8TOv;z$LW?G@6?*Xdo3Ys_xy&U1h|K{T=dvL_&KR;GgFin(#mWj(*IuWZs!7P) zE)l{g6l=yq!tcp!~U$$i#gl1ZOl^^+Omb;u$hA2Q()fAg1rcXyT-x^k7Ct zKBR$q;N3O6%_HskWFuH^$X2@~u^InmCoK7F-`=n80z>lh`81@|s!Iscw?QNdAn;1u zxg3AaeN+myEf@9tchCvIP8huYW17uKXrny8vh_2Ni}BVkI!hV*1th*ZbWcXD;2lith54 zX#-fL=&o1|B#k9V%FBbqAKD~bEaVr+MU6Hkn>F{QXJtIjMPbnw1w*{zPX;IH8qvvl z+C1SqtZb$g<1$OK2aXqbn4%Uljo5g?cZZMGM57-j2>P(EKN(58{smp7GYoy@wFwtP zX!u%!hG>ik5NIXbMYv+cR3rgO*zXKrUX^h?00g+EB$^fIic$nI)5Pz`P@y8F6ng*c zKowyf*+OWMZggYpg_!(OY+x2B_g=c4;f<*|<}d2G_;fHa(r3k!I>+ozuL8Dx4@>O8 zr(LskUQ2ekJ5WR!jzlBTcoWV=DcYUD7%YrJ)3!n3? z=}~f_Zd($PuIQk@YqT`iNi?q2revUl=bR(_X1-YX<<>_bCgC{ES3wby3>uiL5P8 z>HZuG?fDQjDxc3hr#pA-sv6DobVwGN>wNkH|uoAz! zJY?TQk}XhqNReLhALjLDSSt3;7ETE+C^h!Z3%07d1D2WnJnfVysLH9^g{GvJ!kgVO z1V1I50YCne-@vK+-X{L2P&FH8VTk2=0|afUP6|)o-7gpxT2%ciA-Le`1NJ$d{Sbh4 z)(E?H+GMJ8$8UU3s!D^rt(b*y6`rVlfyOR6qb`^ea>7>fgB5~Yb>qZsd zw1W{SZDM)*oL^?AkU3%C|y#+}6#>#AtgGQH~&hDjQ)e+Z?y zXZbmF(TMMTFFA?E@;|G0iQ5GVRP$kUB5;l9*2CXTGzeE^;N}e7wp|1)+(DAlAr=dM zB>(znbqCkov>eUX$O^wp&;Y$7|4gyH7$q;-BzSwqP2c;|d@n?+IEmSPDyG?>uD)|( zphdpzqKf{z>DaNg#>P8Rgp(S9jqqBOTb72{YqC6zQ(EDR|>Vmj2Ibn?r>uiW4MM1e}R<>5M}lX}mm^^mv4j z61M)KxzG_d45DdlMWnkiJjXE2b+p} zgCUbrUQUAgJN{S5VZ;3Lah>9H5?dX1FYsji!v(;JDQh_q$B5NyNl&Hs?$r?EkMFPv zsf>v4&1ci1#^156c(#>sl{O!)U?_QScs+IM@2SpfC=`;Xl0Qcb%gHflGO{Cc{!#+* zqqPcmkuh?oiNZP1Vq~N4?Wy zWv9u`lr8lu?q-t+53XuSJ2f13GESt*Ux?RGuVo{~h+d)`IF4W{?_Myq@;HbMRltl0 z*b!q~pD&;KDyakQNe)2NxD49rd<}-ZWG5JCT}G%%`@OmuAq!b-``MT{Y$7@5X zb8UX-DgH|v1+{x-#kGCJAkrmx#zJ-79$|M9|4UZqMkkqusn(vjVx}K8T?+~OFirz| zcy;{lJV67ht!wU?X-57Jdg&iB0WniOusSbhm%OQh*!9;|+#D28yc;clfC{?NW}XR3 z9^u$FF=8;~8vg0=;dFwx-IfUa%T2r;#p=dDflGb9igM|%om*%lg}Q@;)1iAUxJ6w3 zxG|z9{M-EB17kNf)2)=Nsa|lCyMKgBq8xS^x{?0Sgg8No**`WgbPHN`=kg8t1t?9v zmzWvL6U$^E12^Oc;Tz_|+<+Lviuk9`qN>DiAt%y?kf`7BFL+`mNZ#{Us#P{>q}WVr z=Ut8k%c$47bv7~OL(`0w^CnCqf!L@#mbU7iSz}2sKY(V^Jko0c%rBArn?cryS|0QaKYDDhRL7&!2v4 zsusKSl186f#BrpLi4PCqfepdgN)gr>v9wmXqkkS*80c8I77C=SLQ=!1U zPnGGl%Gzd~9kI7P#U8)AFfEmOodoRMdjE51yIp*VNLmF{a}I=fB`afWZ~gc{eVp>d ze{^+1mwATA$AGk+j`!y-hy1k`Gjq^EQs|8>aW~G9Tyw?c)Dw^PmmNU@SAjoY-J|_+ zzFik`KOXj9?Kk \ No newline at end of file diff --git a/vite/src/pages/Login.jsx b/vite/src/pages/Login.jsx deleted file mode 100644 index 7ce24331..00000000 --- a/vite/src/pages/Login.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import LoginForm from '../components/forms/LoginForm'; - -const Login = () => { - return ; -}; - -export default Login; diff --git a/vite/src/store/alerts.jsx b/vite/src/store/alerts.jsx deleted file mode 100644 index b51076a3..00000000 --- a/vite/src/store/alerts.jsx +++ /dev/null @@ -1,24 +0,0 @@ -// frontend/src/store/useAlertStore.js -import { create } from 'zustand'; - -/** - * Global store to track whether a floating video is visible and which URL is playing. - */ -const useAlertStore = create((set) => ({ - open: false, - message: '', - severity: 'info', - - showAlert: (message, severity = 'info') => - set({ - open: true, - message, - severity, - }), - - hideAlert: () => { - set({ open: false }); - }, -})); - -export default useAlertStore; diff --git a/vite/src/utils.js b/vite/src/utils.js deleted file mode 100644 index b765c405..00000000 --- a/vite/src/utils.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState, useEffect } from 'react'; - -export default { - Limiter: (n, list) => { - if (!list || !list.length) { - return; - } - - var tail = list.splice(n); - var head = list; - var resolved = []; - var processed = 0; - - return new Promise(function (resolve) { - head.forEach(function (x) { - var res = x(); - resolved.push(res); - res.then(function (y) { - runNext(); - return y; - }); - }); - function runNext() { - if (processed == tail.length) { - resolve(Promise.all(resolved)); - } else { - resolved.push( - tail[processed]().then(function (x) { - runNext(); - return x; - }) - ); - processed++; - } - } - }); - }, -}; - -// Custom debounce hook -export function useDebounce(value, delay = 500) { - const [debouncedValue, setDebouncedValue] = useState(value); - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - - return () => clearTimeout(handler); // Cleanup timeout on unmount or value change - }, [value, delay]); - - return debouncedValue; -} From 0433352ee21c60a2cb97848cd95039a1bd131e0b Mon Sep 17 00:00:00 2001 From: dekzter Date: Tue, 11 Mar 2025 19:20:17 -0400 Subject: [PATCH 07/29] static file changes --- dispatcharr/settings.py | 2 +- docker/nginx.conf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dispatcharr/settings.py b/dispatcharr/settings.py index b275bf66..e6831b7e 100644 --- a/dispatcharr/settings.py +++ b/dispatcharr/settings.py @@ -139,7 +139,7 @@ STATIC_ROOT = BASE_DIR / 'static' # Directory where static files will be collec # Adjust STATICFILES_DIRS to include the paths to the directories that contain your static files. STATICFILES_DIRS = [ - os.path.join(BASE_DIR, 'frontend/build/assets'), # React build static files + os.path.join(BASE_DIR, 'frontend/dist'), # React build static files ] diff --git a/docker/nginx.conf b/docker/nginx.conf index 847300a6..e0303ffe 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -7,8 +7,8 @@ server { uwsgi_pass unix:/app/uwsgi.sock; } - location /static/ { - root /app; # Base directory for static files + location /assets/ { + root /app/static; # Base directory for static files } # admin disabled when not in dev mode From 34f956ff68a048dc7b94cc741c6bc6ab59bfcd8f Mon Sep 17 00:00:00 2001 From: dekzter Date: Tue, 11 Mar 2025 19:22:07 -0400 Subject: [PATCH 08/29] updated build dir --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index ac90ef1d..54aa75e5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -32,7 +32,7 @@ RUN apt-get update && \ cd /app/frontend && \ npm install && \ npm run build && \ - find . -maxdepth 1 ! -name '.' ! -name 'build' -exec rm -rf '{}' \; + find . -maxdepth 1 ! -name '.' ! -name 'dist' -exec rm -rf '{}' \; FROM python:3.13-slim From 85b8f6194ea1e6848d8d63cf6590cc1821797c79 Mon Sep 17 00:00:00 2001 From: dekzter Date: Tue, 11 Mar 2025 20:34:48 -0400 Subject: [PATCH 09/29] updated uwsgi for dev --- docker/uwsgi.dev.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/uwsgi.dev.ini b/docker/uwsgi.dev.ini index e8761385..535897a5 100644 --- a/docker/uwsgi.dev.ini +++ b/docker/uwsgi.dev.ini @@ -5,7 +5,7 @@ attach-daemon = celery -A dispatcharr worker -l info attach-daemon = redis-server attach-daemon = daphne -b 0.0.0.0 -p 8001 dispatcharr.asgi:application -attach-daemon = cd /app/vite && npm run dev +attach-daemon = cd /app/frontend && npm run dev # Core settings chdir = /app From 9567db516f69941343c2b4d8a735a67717979fbc Mon Sep 17 00:00:00 2001 From: dekzter Date: Tue, 11 Mar 2025 21:52:21 -0400 Subject: [PATCH 10/29] fixed modal --- frontend/src/components/forms/StreamProfile.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/forms/StreamProfile.jsx b/frontend/src/components/forms/StreamProfile.jsx index 76ded98f..4d625a95 100644 --- a/frontend/src/components/forms/StreamProfile.jsx +++ b/frontend/src/components/forms/StreamProfile.jsx @@ -54,7 +54,7 @@ const StreamProfile = ({ profile = null, isOpen, onClose }) => { } return ( - +
Date: Wed, 12 Mar 2025 08:35:13 -0400 Subject: [PATCH 11/29] fixed issue with react 19 --- frontend/src/components/FloatingVideo.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/FloatingVideo.jsx b/frontend/src/components/FloatingVideo.jsx index cc162c11..1c0c1400 100644 --- a/frontend/src/components/FloatingVideo.jsx +++ b/frontend/src/components/FloatingVideo.jsx @@ -8,6 +8,7 @@ export default function FloatingVideo() { const { isVisible, streamUrl, hideVideo } = useVideoStore(); const videoRef = useRef(null); const playerRef = useRef(null); + const videoContainerRef = useRef(null); useEffect(() => { if (!isVisible || !streamUrl) { @@ -48,8 +49,9 @@ export default function FloatingVideo() { } return ( - +
Date: Wed, 12 Mar 2025 09:34:53 -0400 Subject: [PATCH 12/29] fixed settings --- frontend/src/pages/Settings.jsx | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index 32655186..35d965ea 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -6,7 +6,14 @@ import API from '../api'; import useSettingsStore from '../store/settings'; import useUserAgentsStore from '../store/userAgents'; import useStreamProfilesStore from '../store/streamProfiles'; -import { Button, Center, Flex, Paper, Select, Title } from '@mantine/core'; +import { + Button, + Center, + Flex, + Paper, + NativeSelect, + Title, +} from '@mantine/core'; const SettingsPage = () => { const { settings } = useSettingsStore(); @@ -24,9 +31,9 @@ const SettingsPage = () => { const formik = useFormik({ initialValues: { - 'default-user-agent': '', - 'default-stream-profile': '', - 'preferred-region': '', + 'default-user-agent': `${settings['default-user-agent'].id}`, + 'default-stream-profile': `${settings['default-stream-profile'].id}`, + // 'preferred-region': '', }, validationSchema: Yup.object({ 'default-user-agent': Yup.string().required('User-Agent is required'), @@ -37,6 +44,7 @@ const SettingsPage = () => { // 'preferred-region': Yup.string().required('Region is required'), }), onSubmit: async (values, { setSubmitting, resetForm }) => { + console.log(values); const changedSettings = {}; for (const settingKey in values) { // If the user changed the setting’s value from what’s in the DB: @@ -88,26 +96,26 @@ const SettingsPage = () => { Settings - ({ value: `${option.id}`, label: option.profile_name, From fc244044e24eb745eb441ef78bd004dcee417d62 Mon Sep 17 00:00:00 2001 From: dekzter Date: Wed, 12 Mar 2025 13:49:16 -0400 Subject: [PATCH 13/29] fixed m3u file upload --- frontend/src/api.js | 20 ++++++++++++++++++-- frontend/src/components/forms/M3U.jsx | 3 --- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/src/api.js b/frontend/src/api.js index 4ff9348f..61d03d60 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -479,13 +479,29 @@ export default class API { } static async addPlaylist(values) { + let body = null; + if (values.uploaded_file) { + body = new FormData(); + for (const prop in values) { + body.append(prop, values[prop]); + } + } else { + body = { ...values }; + delete body.uploaded_file; + body = JSON.stringify(body); + } + const response = await fetch(`${host}/api/m3u/accounts/`, { method: 'POST', headers: { Authorization: `Bearer ${await API.getAuthToken()}`, - 'Content-Type': 'application/json', + ...(values.uploaded_file + ? {} + : { + 'Content-Type': 'application/json', + }), }, - body: JSON.stringify(values), + body, }); const retval = await response.json(); diff --git a/frontend/src/components/forms/M3U.jsx b/frontend/src/components/forms/M3U.jsx index 65c43f98..051826ac 100644 --- a/frontend/src/components/forms/M3U.jsx +++ b/frontend/src/components/forms/M3U.jsx @@ -23,10 +23,7 @@ const M3U = ({ playlist = null, isOpen, onClose }) => { const [file, setFile] = useState(null); const [profileModalOpen, setProfileModalOpen] = useState(false); - console.log(playlist); - const handleFileChange = (file) => { - console.log(file); if (file) { setFile(file); } From 7cdb6f0d5b1aad6acff05a77396e4ec23071c9cd Mon Sep 17 00:00:00 2001 From: dekzter Date: Wed, 12 Mar 2025 14:00:12 -0400 Subject: [PATCH 14/29] lots of style updates and functionality fixes --- .../src/components/tables/ChannelsTable.jsx | 449 +++++++----------- .../src/components/tables/StreamsTable.jsx | 76 ++- 2 files changed, 239 insertions(+), 286 deletions(-) diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx index d0823107..dbfd7aa2 100644 --- a/frontend/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -24,11 +24,11 @@ import { ScreenShare, Scroll, SquareMinus, - Pencil, - ArrowUp, - ArrowDown, - ArrowUpDown, - TvMinimalPlay, + CirclePlay, + SquarePen, + Binary, + ArrowDown01, + SquarePlus, } from 'lucide-react'; import ghostImage from '../../images/ghost.svg'; import { @@ -40,24 +40,14 @@ import { Button, Paper, Flex, - Center, Text, Tooltip, Grid, Group, useMantineTheme, - UnstyledButton, + Center, Container, - Space, } from '@mantine/core'; -import { - IconArrowDown, - IconArrowUp, - IconDeviceDesktopSearch, - IconSelector, - IconSortAscendingNumbers, - IconSquarePlus, -} from '@tabler/icons-react'; // Import custom icons const ChannelStreams = ({ channel, isExpanded }) => { const channelStreams = useChannelsStore( @@ -142,76 +132,12 @@ const ChannelStreams = ({ channel, isExpanded }) => { return <>; } - return ( - - - - ); + return ; }; -// /* ----------------------------------------------------------- -// 2) Custom-styled "chip" buttons for HDHR, M3U, EPG -// ------------------------------------------------------------ */ -// const HDHRButton = styled(Button)(() => ({ -// border: '1px solid #a3d977', -// color: '#a3d977', -// backgroundColor: 'transparent', -// textTransform: 'none', -// fontSize: '0.85rem', -// display: 'flex', -// alignItems: 'center', -// gap: '4px', -// padding: '2px 8px', -// minWidth: 'auto', -// '&:hover': { -// borderColor: '#c2e583', -// color: '#c2e583', -// backgroundColor: 'rgba(163,217,119,0.1)', -// }, -// })); - -// const M3UButton = styled(Button)(() => ({ -// border: '1px solid #5f6dc6', -// color: '#5f6dc6', -// backgroundColor: 'transparent', -// textTransform: 'none', -// fontSize: '0.85rem', -// display: 'flex', -// alignItems: 'center', -// gap: '4px', -// padding: '2px 8px', -// minWidth: 'auto', -// '&:hover': { -// borderColor: '#7f8de6', -// color: '#7f8de6', -// backgroundColor: 'rgba(95,109,198,0.1)', -// }, -// })); - -// const EPGButton = styled(Button)(() => ({ -// border: '1px solid #707070', -// color: '#a0a0a0', -// backgroundColor: 'transparent', -// textTransform: 'none', -// fontSize: '0.85rem', -// display: 'flex', -// alignItems: 'center', -// gap: '4px', -// padding: '2px 8px', -// minWidth: 'auto', -// '&:hover': { -// borderColor: '#a0a0a0', -// color: '#c0c0c0', -// backgroundColor: 'rgba(112,112,112,0.1)', -// }, -// })); +const m3uUrl = `${window.location.protocol}//${window.location.host}/output/m3u`; +const epgUrl = `${window.location.protocol}//${window.location.host}/output/epg`; +const hdhrUrl = `${window.location.protocol}//${window.location.host}/output/hdhr`; const ChannelsTable = ({}) => { const [channel, setChannel] = useState(null); @@ -219,7 +145,6 @@ const ChannelsTable = ({}) => { const [rowSelection, setRowSelection] = useState([]); const [channelGroupOptions, setChannelGroupOptions] = useState([]); - const [anchorEl, setAnchorEl] = useState(null); const [textToCopy, setTextToCopy] = useState(''); const [filterValues, setFilterValues] = useState({}); @@ -252,7 +177,9 @@ const ChannelsTable = ({}) => { })); }; - const outputUrlRef = useRef(null); + const hdhrUrlRef = useRef(null); + const m3uUrlRef = useRef(null); + const epgUrlRef = useRef(null); const { environment: { env_mode }, @@ -269,14 +196,18 @@ const ChannelsTable = ({}) => { { header: 'Name', accessorKey: 'channel_name', - muiTableHeadCellProps: { + mantineTableHeadCellProps: { sx: { textAlign: 'center' }, }, Header: ({ column }) => ( handleFilterChange(column.id, e.target.value)} + onChange={(e) => { + e.stopPropagation(); + handleFilterChange(column.id, e.target.value); + }} size="xs" /> ), @@ -447,12 +378,16 @@ const ChannelsTable = ({}) => { } }, [sorting]); - const handleCopy = async () => { + const handleCopy = async (textToCopy, ref) => { try { await navigator.clipboard.writeText(textToCopy); - showAlert('Copied!'); + notifications.show({ + title: 'Copied!', + // style: { width: '200px', left: '200px' }, + }); } catch (err) { - const inputElement = outputUrlRef.current.querySelector('input'); // Get the actual input + const inputElement = ref.current; // Get the actual input + console.log(inputElement); if (inputElement) { inputElement.focus(); @@ -460,28 +395,28 @@ const ChannelsTable = ({}) => { // For older browsers document.execCommand('copy'); - showAlert('Copied!'); + notifications.show({ title: 'Copied!' }); } } }; // Example copy URLs - const copyM3UUrl = (event) => { - setAnchorEl(event.currentTarget); - setTextToCopy( - `${window.location.protocol}//${window.location.host}/output/m3u` + const copyM3UUrl = () => { + handleCopy( + `${window.location.protocol}//${window.location.host}/output/m3u`, + m3uUrlRef ); }; - const copyEPGUrl = (event) => { - setAnchorEl(event.currentTarget); - setTextToCopy( - `${window.location.protocol}//${window.location.host}/output/epg` + const copyEPGUrl = () => { + handleCopy( + `${window.location.protocol}//${window.location.host}/output/epg`, + epgUrlRef ); }; - const copyHDHRUrl = (event) => { - setAnchorEl(event.currentTarget); - setTextToCopy( - `${window.location.protocol}//${window.location.host}/output/hdhr` + const copyHDHRUrl = () => { + handleCopy( + `${window.location.protocol}//${window.location.host}/output/hdhr`, + hdhrUrlRef ); }; @@ -509,11 +444,6 @@ const ChannelsTable = ({}) => { enableRowVirtualization: true, enableRowSelection: true, renderTopToolbar: false, - icons: { - IconSortAscending: IconArrowUp, // Upward arrow for ascending sort - IconSortDescending: IconArrowDown, // Downward arrow for descending sort - IconSort: IconSelector, // Default sort icon (unsorted state) - }, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, state: { @@ -535,23 +465,18 @@ const ChannelsTable = ({}) => { 'mrt-row-expand': { size: 10, header: '', - muiTableHeadCellProps: { - sx: { width: 38, minWidth: 38, maxWidth: 38, height: '100%' }, - }, - muiTableBodyCellProps: { - sx: { width: 38, minWidth: 38, maxWidth: 38 }, - }, }, 'mrt-row-actions': { size: 74, }, }, - muiExpandButtonProps: ({ row, table }) => ({ + mantineExpandButtonProps: ({ row, table }) => ({ onClick: () => { setRowSelection({ [row.index]: true }); table.setExpanded({ [row.id]: !row.getIsExpanded() }); }, - sx: { + size: 'xs', + style: { transform: row.getIsExpanded() ? 'rotate(180deg)' : 'rotate(-90deg)', transition: 'transform 0.2s', }, @@ -561,40 +486,42 @@ const ChannelsTable = ({}) => { ), renderRowActions: ({ row }) => ( - - { - editChannel(row.original); - }} - > - - - +
+ + { + editChannel(row.original); + }} + > + + + - - deleteChannel(row.original.id)} - > - - - + + deleteChannel(row.original.id)} + > + + + - - handleWatchStream(row.original.channel_number)} - > - - - + + handleWatchStream(row.original.channel_number)} + > + + + +
), mantineTableContainerProps: { @@ -603,9 +530,6 @@ const ChannelsTable = ({}) => { overflowY: 'auto', }, }, - muiSearchTextFieldProps: { - variant: 'standard', - }, }); return ( @@ -658,12 +582,10 @@ const ChannelsTable = ({}) => { - + - - - + {/* Table or ghost empty state inside Paper */} - + {filteredData.length === 0 && ( - - - - + - It’s recommended to create channels after adding your M3U or - streams. - - + It’s recommended to create channels after adding your M3U or + streams. + + + You can still create channels without streams if you’d like, + and map them later. + + + +
+ +
+ - You can still create channels without streams if you’d like, - and map them later. - - - - + /> +
+
)} - {filteredData.length > 0 && ( - - - - )} + {filteredData.length > 0 && } { const [groupOptions, setGroupOptions] = useState([]); const [m3uOptions, setM3uOptions] = useState([]); const [actionsOpenRow, setActionsOpenRow] = useState(null); - const [dataFetched, setDataFetched] = useState(false); + const [initialDataCount, setInitialDataCount] = useState(null); const [data, setData] = useState([]); // Holds fetched data const [rowCount, setRowCount] = useState(0); + const [pageCount, setPageCount] = useState(0); + const [paginationString, setPaginationString] = useState(''); const [isLoading, setIsLoading] = useState(true); const [sorting, setSorting] = useState([]); const [selectedStreamIds, setSelectedStreamIds] = useState([]); @@ -63,7 +69,7 @@ const StreamsTable = ({}) => { // const [allRowsSelected, setAllRowsSelected] = useState(false); const [pagination, setPagination] = useState({ pageIndex: 0, - pageSize: 25, + pageSize: 250, }); const [filters, setFilters] = useState({ name: '', @@ -112,7 +118,6 @@ const StreamsTable = ({}) => { onClick={(e) => e.stopPropagation()} onChange={handleFilterChange} size="xs" - margin="none" /> ), Cell: ({ cell }) => ( @@ -230,6 +235,21 @@ const StreamsTable = ({}) => { const result = await API.queryStreams(params); setData(result.results); setRowCount(result.count); + setPageCount(Math.ceil(result.count / pagination.pageSize)); + + // Calculate the starting and ending item indexes + const startItem = pagination.pageIndex * pagination.pageSize + 1; // +1 to start from 1, not 0 + const endItem = Math.min( + (pagination.pageIndex + 1) * pagination.pageSize, + result.count + ); + + if (initialDataCount === null) { + setInitialDataCount(result.count); + } + + // Generate the string + setPaginationString(`${startItem} to ${endItem} of ${result.count}`); const newSelection = {}; result.results.forEach((item, index) => { @@ -250,9 +270,6 @@ const StreamsTable = ({}) => { setGroupOptions(groups); setIsLoading(false); - if (dataFetched === false) { - setDataFetched(true); - } }, [pagination, sorting, debouncedFilters]); // Fallback: Individual creation (optional) @@ -374,17 +391,21 @@ const StreamsTable = ({}) => { setRowSelection(newSelection); }; - const onPageSizeChange = (pageSize) => { + const onPageSizeChange = (e) => { setPagination({ ...pagination, - pageSize, + pageSize: e.target.value, }); }; const onPageIndexChange = (pageIndex) => { + if (!pageIndex || pageIndex > pageCount) { + return; + } + setPagination({ ...pagination, - pageIndex, + pageIndex: pageIndex - 1, }); }; @@ -409,8 +430,33 @@ const StreamsTable = ({}) => { rowVirtualizerInstanceRef, rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer enableBottomToolbar: true, + renderBottomToolbar: ({ table }) => ( + + Page Size + + + {paginationString} + + ), enableStickyHeader: true, - onPaginationChange: onPaginationChange, + // onPaginationChange: onPaginationChange, rowCount: rowCount, enableRowSelection: true, mantineSelectAllCheckboxProps: { @@ -433,7 +479,7 @@ const StreamsTable = ({}) => { state: { isLoading: isLoading, sorting, - pagination, + // pagination, rowSelection, }, enableRowActions: true, @@ -498,7 +544,7 @@ const StreamsTable = ({}) => { ), mantineTableContainerProps: { style: { - height: 'calc(100vh - 180px)', + height: 'calc(100vh - 167px)', overflowY: 'auto', }, }, @@ -537,7 +583,7 @@ const StreamsTable = ({}) => { return ( <> { - {!dataFetched && ( + {initialDataCount === 0 && (
{
)} - {dataFetched && } + {initialDataCount > 0 && } Date: Wed, 12 Mar 2025 14:00:56 -0400 Subject: [PATCH 15/29] upadted styles, added back in ip --- frontend/src/components/Sidebar.jsx | 89 +++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index e1bac892..29b1fd4a 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -1,3 +1,4 @@ +import React, { useRef } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { ListOrdered, @@ -15,10 +16,14 @@ import { Box, Text, UnstyledButton, + TextInput, + ActionIcon, } from '@mantine/core'; import logo from '../images/logo.png'; import useChannelsStore from '../store/channels'; import './sidebar.css'; +import useSettingsStore from '../store/settings'; +import { ContentCopy } from '@mui/icons-material'; const NavLink = ({ item, isActive, collapsed }) => { return ( @@ -55,6 +60,8 @@ const NavLink = ({ item, isActive, collapsed }) => { const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => { const location = useLocation(); const { channels } = useChannelsStore(); + const { environment } = useSettingsStore(); + const publicIPRef = useRef(null); // Navigation Items const navItems = [ @@ -79,6 +86,23 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => { }, ]; + const copyPublicIP = async () => { + try { + await navigator.clipboard.writeText(environment.public_ip); + } catch (err) { + const inputElement = publicIPRef.current; // Get the actual input + console.log(inputElement); + + if (inputElement) { + inputElement.focus(); + inputElement.select(); + + // For older browsers + document.execCommand('copy'); + } + } + }; + return ( { {/* Navigation Links */} - + {navItems.map((item) => { const isActive = location.pathname === item.path; @@ -150,23 +174,52 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => { justifyContent: collapsed ? 'center' : 'flex-start', }} > - - {!collapsed && ( - - - John Doe - - - ••• - - - )} + + {!collapsed && ( + + ) + } + rightSection={ + + + + } + /> + )} + + + {!collapsed && ( + + + John Doe + + + ••• + + + )} + ); From 35ab449f86c0f052772232f68cf7d87fdb775863 Mon Sep 17 00:00:00 2001 From: dekzter Date: Wed, 12 Mar 2025 14:01:20 -0400 Subject: [PATCH 16/29] fixed websocket message --- frontend/src/WebSocket.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/WebSocket.jsx b/frontend/src/WebSocket.jsx index 39ed0e49..ffc86673 100644 --- a/frontend/src/WebSocket.jsx +++ b/frontend/src/WebSocket.jsx @@ -53,7 +53,7 @@ export const WebsocketProvider = ({ children }) => { if (event.message?.success) { fetchStreams(); notifications.show({ - title: 'event.message.message', + message: event.message.message, color: 'green.5', }); } From 8bcd2a2a7aceebef16d2746c5c611e53fb89aa37 Mon Sep 17 00:00:00 2001 From: dekzter Date: Wed, 12 Mar 2025 14:01:27 -0400 Subject: [PATCH 17/29] Fixed notifications --- frontend/src/App.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4b30aea2..ec8163ea 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -19,9 +19,11 @@ import FloatingVideo from './components/FloatingVideo'; import { WebsocketProvider } from './WebSocket'; import { Box, AppShell, MantineProvider } from '@mantine/core'; import '@mantine/core/styles.css'; // Ensure Mantine global styles load +import '@mantine/notifications/styles.css'; import 'mantine-react-table/styles.css'; import mantineTheme from './mantineTheme'; import API from './api'; +import { Notifications } from '@mantine/notifications'; const drawerWidth = 240; const miniDrawerWidth = 60; @@ -79,6 +81,7 @@ const App = () => { withNormalizeCSS > + Date: Wed, 12 Mar 2025 14:01:40 -0400 Subject: [PATCH 18/29] form fixes and style fixes --- frontend/src/components/forms/Channel.jsx | 50 ++++++++++--------- frontend/src/components/forms/Stream.jsx | 1 - .../src/components/forms/StreamProfile.jsx | 30 +++++------ 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/frontend/src/components/forms/Channel.jsx b/frontend/src/components/forms/Channel.jsx index 9e7b72f1..aa4a413f 100644 --- a/frontend/src/components/forms/Channel.jsx +++ b/frontend/src/components/forms/Channel.jsx @@ -23,6 +23,7 @@ import { Grid, Flex, } from '@mantine/core'; +import { SquarePlus } from 'lucide-react'; const Channel = ({ channel = null, isOpen, onClose }) => { const channelGroups = useChannelsStore((state) => state.channelGroups); @@ -250,7 +251,7 @@ const Channel = ({ channel = null, isOpen, onClose }) => { return ( <> - + @@ -265,35 +266,38 @@ const Channel = ({ channel = null, isOpen, onClose }) => { } /> - - ({ - value: `${option.id}`, - label: option.name, - }))} - /> -
+ + + ({ + value: `${option.id}`, + label: option.name, + }))} + /> + + setChannelGroupModalOpen(true)} title="Create new group" size="small" - variant="filled" + variant="light" + style={{ marginTop: '175%' }} // @TODO: I don't like this, figure out better placement > - + -
-
+
+
{ const streamProfiles = useStreamProfilesStore((state) => state.profiles); const [selectedStreamProfile, setSelectedStreamProfile] = useState(''); - console.log(stream); const formik = useFormik({ initialValues: { name: '', diff --git a/frontend/src/components/forms/StreamProfile.jsx b/frontend/src/components/forms/StreamProfile.jsx index 4d625a95..eadcca13 100644 --- a/frontend/src/components/forms/StreamProfile.jsx +++ b/frontend/src/components/forms/StreamProfile.jsx @@ -4,7 +4,7 @@ import { useFormik } from 'formik'; import * as Yup from 'yup'; import API from '../../api'; import useUserAgentsStore from '../../store/userAgents'; -import { Modal, TextInput, Select, Button } from '@mantine/core'; +import { Modal, TextInput, Select, Button, Flex } from '@mantine/core'; const StreamProfile = ({ profile = null, isOpen, onClose }) => { const userAgents = useUserAgentsStore((state) => state.userAgents); @@ -62,7 +62,7 @@ const StreamProfile = ({ profile = null, isOpen, onClose }) => { label="Name" value={formik.values.profile_name} onChange={formik.handleChange} - error={formik.touched.profile_name} + error={formik.errors.profile_name} /> { label="Command" value={formik.values.command} onChange={formik.handleChange} - error={formik.touched.command} + error={formik.errors.command} /> { label="Parameters" value={formik.values.parameters} onChange={formik.handleChange} - error={formik.touched.parameters} + error={formik.errors.parameters} />