diff --git a/.dockerignore b/.dockerignore
index e58ec7117..7fd7123ad 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -11,4 +11,4 @@ docker/
frontend/node_modules/
frontend/build/
-webui/dist
+frontend/dist
diff --git a/Cargo.toml b/Cargo.toml
index b6474de40..44e0abdd3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[workspace]
-members = ["backend", "webui", "shared"]
+members = ["backend", "frontend", "shared"]
resolver = "2"
[profile.release]
diff --git a/bin/build_aarch64.sh b/bin/build_aarch64.sh
index 8029b0565..7fc58de88 100755
--- a/bin/build_aarch64.sh
+++ b/bin/build_aarch64.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
echo "building binary for aarch64"
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target aarch64-unknown-linux-musl
-cd ./webui || exit 1
+cd ./frontend || exit 1
env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
diff --git a/bin/build_armv7.sh b/bin/build_armv7.sh
index 9d16e3bf8..76d6a18d4 100755
--- a/bin/build_armv7.sh
+++ b/bin/build_armv7.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
echo "building binary for armv7"
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target armv7-unknown-linux-musleabihf
-cd ./webui || exit 1
+cd ./frontend || exit 1
env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
\ No newline at end of file
diff --git a/bin/build_darwin.sh b/bin/build_darwin.sh
index 0db303bfe..0fc62e75b 100755
--- a/bin/build_darwin.sh
+++ b/bin/build_darwin.sh
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target x86_64-apple-darwin
-cd ./webui || exit 1
+cd ./frontend || exit 1
env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
\ No newline at end of file
diff --git a/bin/build_fe.sh b/bin/build_fe.sh
index 37f519626..5b71d4291 100755
--- a/bin/build_fe.sh
+++ b/bin/build_fe.sh
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-cd ./webui && rm -rf dist && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
+cd ./frontend && rm -rf dist && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
diff --git a/bin/build_github_docker.sh b/bin/build_github_docker.sh
index e9a93fec1..0e27a11eb 100755
--- a/bin/build_github_docker.sh
+++ b/bin/build_github_docker.sh
@@ -7,7 +7,7 @@ BIN_DIR="${WORKING_DIR}/bin"
RESOURCES_DIR="${WORKING_DIR}/resources"
DOCKER_DIR="${WORKING_DIR}/docker"
BACKEND_DIR="${WORKING_DIR}/backend"
-FRONTEND_DIR="${WORKING_DIR}/webui"
+FRONTEND_DIR="${WORKING_DIR}/frontend"
FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist"
declare -A ARCHITECTURES=(
[LINUX]=x86_64-unknown-linux-musl
diff --git a/bin/build_github_docker_aarch64.sh b/bin/build_github_docker_aarch64.sh
index d4c418820..96ee731ae 100755
--- a/bin/build_github_docker_aarch64.sh
+++ b/bin/build_github_docker_aarch64.sh
@@ -28,7 +28,7 @@ BIN_DIR="${WORKING_DIR}/bin"
RESOURCES_DIR="${WORKING_DIR}/resources"
DOCKER_DIR="${WORKING_DIR}/docker"
BACKEND_DIR="${WORKING_DIR}/backend"
-FRONTEND_DIR="${WORKING_DIR}/webui"
+FRONTEND_DIR="${WORKING_DIR}/frontend"
FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist"
TARGET=aarch64-unknown-linux-musl
VERSION=$(grep -Po '^version\s*=\s*"\K[0-9\.]+' "${BACKEND_DIR}/Cargo.toml" || true)
diff --git a/bin/build_github_docker_beta.sh b/bin/build_github_docker_beta.sh
index d44622a61..cae303531 100755
--- a/bin/build_github_docker_beta.sh
+++ b/bin/build_github_docker_beta.sh
@@ -7,7 +7,7 @@ BIN_DIR="${WORKING_DIR}/bin"
RESOURCES_DIR="${WORKING_DIR}/resources"
DOCKER_DIR="${WORKING_DIR}/docker"
BACKEND_DIR="${WORKING_DIR}/backend"
-FRONTEND_DIR="${WORKING_DIR}/webui"
+FRONTEND_DIR="${WORKING_DIR}/frontend"
FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist"
TARGET=x86_64-unknown-linux-musl
VERSION=$(grep -Po '^version\s*=\s*"\K[0-9\.]+' "${BACKEND_DIR}/Cargo.toml" || true)
diff --git a/bin/build_lin.sh b/bin/build_lin.sh
index d6184d81e..c32b029c7 100755
--- a/bin/build_lin.sh
+++ b/bin/build_lin.sh
@@ -5,4 +5,4 @@ if [ "$(uname)" != "Linux" ]; then
else
cargo build -p tuliprox --release
fi
-cd "./webui" && trunk build --release
\ No newline at end of file
+cd "./frontend" && trunk build --release
\ No newline at end of file
diff --git a/bin/build_lin_static.sh b/bin/build_lin_static.sh
index 2bcf7c2a6..fca88dcca 100755
--- a/bin/build_lin_static.sh
+++ b/bin/build_lin_static.sh
@@ -2,4 +2,4 @@
DEFAULT_TARGET="x86_64-unknown-linux-musl"
TARGET="${1:-$DEFAULT_TARGET}"
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target "$TARGET"
-cd "./webui" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
\ No newline at end of file
+cd "./frontend" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
\ No newline at end of file
diff --git a/bin/build_win.sh b/bin/build_win.sh
index 5e6e3fbc2..bff62549e 100755
--- a/bin/build_win.sh
+++ b/bin/build_win.sh
@@ -3,4 +3,4 @@ env RUSTFLAGS="--remap-path-prefix $HOME=~" cargo build -p tuliprox --release --
# cross build, is only working with cargo clean
# env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target x86_64-pc-windows-gnu
-cd "./webui" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
+cd "./frontend" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release
diff --git a/bin/release.sh b/bin/release.sh
index a0e8919ec..a1df08166 100755
--- a/bin/release.sh
+++ b/bin/release.sh
@@ -5,7 +5,7 @@ set -o pipefail
WORKING_DIR=$(pwd)
RESOURCES_DIR="$WORKING_DIR/resources"
RELEASE_DIR="$WORKING_DIR/release"
-FRONTEND_DIR="${WORKING_DIR}/webui"
+FRONTEND_DIR="${WORKING_DIR}/frontend"
FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist"
if ! command -v cargo-set-version &> /dev/null
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 51e058c74..4254aba6a 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -58,7 +58,7 @@ RUN cargo install --locked trunk wasm-bindgen-cli
# Now copy the actual source code and build the project
COPY . .
-WORKDIR /src/webui
+WORKDIR /src/frontend
# Run Trunk build
RUN trunk build --release
@@ -135,7 +135,7 @@ RUN rustup update && rustup target add $RUST_TARGET
# Create the final application layout to mimic production
WORKDIR /app
-COPY --from=trunk-build /src/webui/dist ./web
+COPY --from=trunk-build /src/frontend/dist ./web
COPY --from=resource-build /src/resources ./resources
# Create a separate directory for the source code
@@ -170,7 +170,7 @@ COPY --from=rust-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# App
WORKDIR /app
COPY --from=rust-build /src/target/${RUST_TARGET}/release/tuliprox ./tuliprox
-COPY --from=trunk-build /src/webui/dist ./web
+COPY --from=trunk-build /src/frontend/dist ./web
COPY --from=resource-build /src/resources ./resources
ENTRYPOINT ["/app/tuliprox"]
@@ -195,7 +195,7 @@ RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime
WORKDIR /app
COPY --from=rust-build /src/target/${RUST_TARGET}/release/tuliprox ./tuliprox
-COPY --from=trunk-build /src/webui/dist ./web
+COPY --from=trunk-build /src/frontend/dist ./web
COPY --from=resource-build /src/resources ./resources
ENTRYPOINT ["/sbin/tini", "--", "/app/tuliprox"]
diff --git a/webui/Cargo.toml b/frontend/Cargo.toml
similarity index 100%
rename from webui/Cargo.toml
rename to frontend/Cargo.toml
diff --git a/webui/Trunk.toml b/frontend/Trunk.toml
similarity index 100%
rename from webui/Trunk.toml
rename to frontend/Trunk.toml
diff --git a/frontend/eslintrc.json b/frontend/eslintrc.json
deleted file mode 100644
index 3bd6d3171..000000000
--- a/frontend/eslintrc.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
- "env": {
- "jest/globals": true
- },
- "parser": "@typescript-eslint/parser",
- "extends": [
- "react-app",
- "react-app/jest",
- "eslint-config-prettier",
- "plugin:react/recommended"
- ],
- "settings": {
- "react": {
- "createClass": "createReactClass",
- "pragma": "React",
- "fragment": "Fragment",
- "version": "detect",
- "flowVersion": "detect"
- }
- },
- "parserOptions": {
- "ecmaVersion": 2018,
- "sourceType": "module",
- "ecmaFeatures": {
- "jsx": true
- }
- },
- "plugins": [
- "import",
- "node",
- "react"
- ],
- "rules": {
- "consistent-this": [
- "error",
- "self"
- ],
- "@typescript-eslint/no-this-alias": [
- "error",
- {
- "allowDestructuring": false,
- "allowedNames": [
- "self"
- ]
- }
- ],
- "@typescript-eslint/no-explicit-any": "off",
- "@typescript-eslint/ban-ts-comment": "off",
- "@typescript-eslint/ban-ts-ignore": "off",
- "react/display-name": "off",
- "react/prop-types": "off"
- },
- "ignorePatterns": [ "*.test.tsc", "*.scss", "*.css" ]
-}
diff --git a/webui/index.html b/frontend/index.html
similarity index 100%
rename from webui/index.html
rename to frontend/index.html
diff --git a/frontend/package.json b/frontend/package.json
deleted file mode 100755
index d128c6de5..000000000
--- a/frontend/package.json
+++ /dev/null
@@ -1,83 +0,0 @@
-{
- "name": "tuliprox",
- "description": "tuliprox",
- "author": "euzu",
- "version": "3.1.4",
- "private": true,
- "homepage": "",
- "license": "MIT",
- "scripts": {
- "react-start": "react-scripts start",
- "react-build": "react-scripts build",
- "react-test": "react-scripts test --env=jsdom",
- "react-eject": "react-scripts eject",
- "build": "cross-env GENERATE_SOURCEMAP=false REACT_APP_STAGE=production PUBLIC_URL= yarn react-build",
- "start": "yarn react-start",
- "lint": "eslint --config eslintrc.json src/** --no-error-on-unmatched-pattern --quiet",
- "upgr": "yarn upgrade-interactive --latest",
- "analyze": "source-map-explorer 'build/static/js/*.js'"
- },
- "dependencies": {
- "file-saver": "2.0.5",
- "i18next": "25.0.1",
- "jwt-decode": "4.0.0",
- "mpegts.js": "1.8.0",
- "notistack": "3.0.2",
- "react": "19.1.0",
- "react-datepicker": "8.3.0",
- "react-dom": "19.1.0",
- "react-i18next": "15.4.1",
- "react-tag-input-component": "2.0.2",
- "rxjs": "7.8.2",
- "video.js": "8.22.0",
- "videojs-mpegtsjs": "1.0.0"
- },
- "devDependencies": {
- "@babel/plugin-proposal-private-property-in-object": "7.21.11",
- "@babel/runtime": "7.27.0",
- "@types/file-saver": "2.0.7",
- "@types/react": "19.1.2",
- "@types/react-dom": "19.1.2",
- "@types/video.js": "7.3.58",
- "@typescript-eslint/eslint-plugin": "8.30.1",
- "@typescript-eslint/parser": "8.30.1",
- "babel-eslint": "10.1.0",
- "concurrently": "9.1.2",
- "cross-env": "7.0.3",
- "eslint": "9.25.0",
- "eslint-config-prettier": "10.0.1",
- "eslint-config-react-app": "7.0.1",
- "eslint-plugin-flowtype": "8.0.3",
- "eslint-plugin-import": "2.31.0",
- "eslint-plugin-jest": "28.11.0",
- "eslint-plugin-jsx-a11y": "6.10.2",
- "eslint-plugin-node": "11.1.0",
- "eslint-plugin-react": "7.37.4",
- "eslint-plugin-react-hooks": "5.2.0",
- "prettier": "3.5.3",
- "react-scripts": "5.0.1",
- "sass": "1.86.3",
- "source-map-explorer": "2.5.3",
- "typescript": "5.8.3",
- "wait-on": "8.0.3"
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
- },
- "proxy": "http://localhost:8901"
-}
diff --git a/frontend/public/assets/fonts.css b/frontend/public/assets/fonts.css
index fd07a74a0..01d5d09a4 100644
--- a/frontend/public/assets/fonts.css
+++ b/frontend/public/assets/fonts.css
@@ -56,4 +56,4 @@
font-weight: 400;
src: url('fonts/rubik-latin-400-normal.woff2') format('woff2'), url('fonts/rubik-latin-400-normal.woff') format('woff');
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
-}
\ No newline at end of file
+}
diff --git a/frontend/public/assets/i18n/de_common.json b/frontend/public/assets/i18n/de_common.json
deleted file mode 100644
index 134908033..000000000
--- a/frontend/public/assets/i18n/de_common.json
+++ /dev/null
@@ -1,123 +0,0 @@
-{
- "LABEL": {
- "LIVE": "Live",
- "VOD": "VOD",
- "SERIES": "Serien",
- "SAVE": "Speichern",
- "SUBMIT": "Übernehmen",
- "OK": "Ok",
- "CANCEL": "Abbrechen",
- "SHOW_SELECTED": "Ausgewählte anzeigen",
- "SELECT_ALL": "Alle auswählen",
- "DESELECT_ALL": "Alle abwählen",
- "SEARCH": "Suche",
- "CLEAR": "Löschen",
- "REGEXP": "RegExp",
- "LOGOUT": "Abmelden",
- "CONFIGURATION": "Konfiguration",
- "HOST": "Host",
- "PORT": "Port",
- "WEB_ROOT": "Web-Root",
- "THREADS": "Threads",
- "WORKING_DIR": "Arbeitsverzeichnis",
- "BACKUP_DIR": "Backup-Verzeichnis",
- "UPDATE_ON_BOOT": "Update beim Start",
- "WEB_UI": "Web-UI",
- "SCHEDULES": "Zeitpläne",
- "SANITIZE_SENSITIVE_INFO": "Sensiblen Inhalt bereinigen",
- "ACTIVE_CLIENTS": "Aktive Clients",
- "RESOURCE_REWRITE_DISABLE": "Ressourcenumschreibung deaktivieren",
- "RECONNECT": "Erneut verbinden",
- "BUFFER_ENABLED": "Puffer aktiviert",
- "BUFFER_SIZE": "Puffergröße",
- "ENABLED": "Aktiviert",
- "SIZE": "Größe",
- "CACHE_DIR": "Cache-Verzeichnis",
- "NOTIFY_ON": "Benachrichtigen bei",
- "BOT_TOKEN": "Bot-Token",
- "CHAT_IDS": "Chat-IDs",
- "URL": "URL",
- "USER": "Benutzer",
- "USERS": "Benutzer",
- "TOKEN": "Token",
- "WEB_SEARCH_URL": "Web-Such-URL",
- "EXTENSIONS": "Erweiterungen",
- "HEADERS": "Header",
- "DIRECTORY": "Verzeichnis",
- "ORGANIZE_INTO_DIRECTORIES": "In Verzeichnisse organisieren",
- "EPISODE_PATTERN": "Episoden-Muster",
- "API": "API",
- "MAIN": "Haupt",
- "REVERSE_PROXY": "ReverseProxy",
- "MESSAGING": "Nachrichten",
- "VIDEO": "Video",
- "EXAMPLES": "Beispiele",
- "UPDATE": "Aktualisieren",
- "START": "Starten",
- "CACHE": "Cache",
- "STREAM": "Stream",
- "LOG": "Protokoll",
- "CONFIG": "Konfiguration",
- "TELEGRAM": "Telegram",
- "REST": "Rest",
- "PUSHOVER": "Pushover",
- "PROXY": "Proxy",
- "REFRESH": "Aktualisieren",
- "STATUS": "Status",
- "MAX_CON": "MaxCon",
- "EXP_DATE": "Ablaufdatum",
- "USERNAME": "Benutzername",
- "PASSWORD": "Passwort",
- "SERVER": "Server",
- "TIMEZONE": "Zeitzone",
- "PATH": "Pfad",
- "MESSAGE": "Nachricht",
- "PROTOCOL": "Protokoll",
- "NAME": "Name"
- },
- "TITLE": {
- "USER_BOUQUET_EDITOR": "Benutzer-Gruppen-Editor"
- },
- "MESSAGES": {
- "INVALID_REGEXP": "Ungültiger regulärer Ausdruck",
- "INVALID_FILETYPE": "Ungültiger Dateityp",
- "DOWNLOAD": {
- "SUCCESS": "Erfolgreich heruntergeladen",
- "FAIL": "Fehler beim Herunterladen!",
- "PLAYLIST": {
- "SUCCESS": "Playlist erfolgreich heruntergeladen",
- "FAIL": "Fehler beim Herunterladen der Playlist!"
- },
- "SERVER_CONFIG": {
- "FAIL": "Fehler beim Herunterladen der Serverkonfiguration!"
- }
- },
- "SAVE": {
- "MAIN_CONFIG": {
- "SUCCESS": "Hauptkonfiguration gespeichert!",
- "FAIL": "Fehler beim Speichern der Hauptkonfiguration!"
- },
- "BOUQUET": {
- "SUCCESS": "Bouquet erfolgreich gespeichert",
- "FAIL": "Fehler beim Speichern des Bouquets!"
- },
- "USER": {
- "SUCCESS": "Benutzer erfolgreich gespeichert",
- "FAIL": "Fehler beim Speichern des Benutzers!"
- },
- "PLAYLIST_UPDATE": {
- "SUCCESS": "Playlist Aktualisierung erfolgreich gestartet!",
- "FAIL": "Playlist Aktualisierung fehlgeschlagen!"
- },
- "SOURCE_SELECTOR": {
- "MISSING_SELECTION": "Kein Eintrag ausgewählt"
- }
- }
- },
- "INFO": {
- "RESTART_TO_APPLY_CHANGES": "Sie müssen neu starten, um Änderungen anzuwenden.",
- "SCHEDULE_EXAMPLE": "Beispiel für Zeitplan",
- "THREADS": "Threads: Lassen Sie es auf 0, wenn keine Probleme bestehen. Erhöhen Sie es, wenn Sie mehrere Anbieter haben. Der parallele Zugriff auf Ihren Anbieter kann zu einer Sperrung führen.",
- "WEB_ROOT": "Web-Root: Enthält die Web-UI-Dateien. Normalerweise befinden sie sich im gleichen Verzeichnis unter ./web"
- }
-}
diff --git a/webui/public/assets/i18n/en.json b/frontend/public/assets/i18n/en.json
similarity index 100%
rename from webui/public/assets/i18n/en.json
rename to frontend/public/assets/i18n/en.json
diff --git a/frontend/public/assets/i18n/en_common.json b/frontend/public/assets/i18n/en_common.json
deleted file mode 100644
index 09dde894e..000000000
--- a/frontend/public/assets/i18n/en_common.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
- "LABEL": {
- "LIVE": "Live",
- "VOD": "VOD",
- "SERIES": "Series",
- "SAVE": "Save",
- "SUBMIT": "Submit",
- "OK": "Ok",
- "CANCEL": "Cancel",
- "CLOSE": "Close",
- "SHOW_SELECTED": "Show selected",
- "SELECT_ALL": "Select All",
- "DESELECT_ALL": "Deselect all",
- "SEARCH": "Search",
- "CLEAR": "Clear",
- "REGEXP": "Regexp",
- "LOGOUT": "Logout",
- "CONFIGURATION": "Configuration",
- "HOST": "Host",
- "PORT": "Port",
- "WEB_ROOT": "Web-Root",
- "THREADS": "Threads",
- "WORKING_DIR": "Working dir",
- "BACKUP_DIR": "Backup dir",
- "UPDATE_ON_BOOT": "Update on boot",
- "WEB_UI": "Web UI",
- "USER_UI": "User UI",
- "WEB_UI_PATH": "Path prefix",
- "SCHEDULES": "Schedules",
- "SANITIZE_SENSITIVE_INFO": "Sanitize sensitive info",
- "ACTIVE_USER": "Active User",
- "RESOURCE_REWRITE_DISABLE": "Resource Rewrite disable",
- "DISABLE_REFERER_HEADER": "Referer header disable",
- "RECONNECT": "Reconnect",
- "THROTTLE": "Throttle",
- "GRACE_PERIOD": "Grace period ms",
- "GRACE_PERIOD_TIMEOUT": "Grace period timeout sec",
- "BUFFER_ENABLED": "Buffer Enabled",
- "BUFFER_SIZE": "Buffer Size",
- "ENABLED": "Enabled",
- "SIZE": "Size",
- "CACHE_DIR": "Cache dir",
- "NOTIFY_ON": "Notify on",
- "BOT_TOKEN": "Bot Token",
- "CHAT_IDS": "Chat Ids",
- "URL": "Url",
- "USER": "User",
- "USERS": "Users",
- "TOKEN": "Token",
- "WEB_SEARCH_URL": "Web Search Url",
- "EXTENSIONS": "Extensions",
- "HEADERS": "Headers",
- "DIRECTORY": "Directory",
- "ORGANIZE_INTO_DIRECTORIES": "Organize Into Directorie",
- "EPISODE_PATTERN": "Episode Pattern",
- "API": "Api",
- "MAIN": "Main",
- "REVERSE_PROXY": "ReverseProxy",
- "MESSAGING": "Messaging",
- "VIDEO": "Video",
- "EXAMPLES": "Examples",
- "UPDATE": "Update",
- "START": "Start",
- "CACHE": "Cache",
- "STREAM": "Stream",
- "LOG": "Log",
- "LOG_LEVEL": "Log level",
- "CONFIG": "Config",
- "TELEGRAM": "Telegram",
- "REST": "Rest",
- "PUSHOVER": "Pushover",
- "PROXY": "Proxy",
- "REFRESH": "Refresh",
- "STATUS": "Status",
- "MAX_CON": "MaxCon",
- "EXP_DATE": "ExpDate",
- "USERNAME": "Username",
- "PASSWORD": "Password",
- "SERVER": "Server",
- "TIMEZONE": "Timezone",
- "PATH": "Path",
- "MESSAGE": "Message",
- "PROTOCOL": "Protocol",
- "NAME": "Name",
- "API_PROXY": "Api-Proxy",
- "INFINITE": "Infinite",
- "ONE_YEAR": "One year",
- "SELF_HOSTED": "Self Hosted",
- "PROVIDER": "Provider",
- "CUSTOM": "Custom",
- "XTREAM_CODES": "xtream codes",
- "XC": "xc",
- "M3U": "m3u",
- "PLAYLIST_BROWSER": "Playlist Browser",
- "RATE_LIMIT": "Rate Limit",
- "PERIOD_MILLIS": "Period Millis",
- "BURST_SIZE": "Burst Size",
- "WEB_AUTH": "Auth",
- "ISSUER": "Issuer",
- "SECRET": "Secret",
- "USERFILE": "Userfile",
- "UI_ENABLED": "UI",
- "PLAYER_SERVER": "Player Server",
- "VERSION": "Version",
- "SERVER_TIME": "Server time",
- "BUILD_TIME": "Build time",
- "MEMORY": "Memory",
- "ACTIVE_USERS": "Active users",
- "ACTIVE_USER_CONNECTIONS": "Active user conn...",
- "ACTIVE_PROVIDER_CONNECTIONS": "Active provider conn...",
- "IP_CHECK": "IP Check",
- "URL_IPV4": "Url IPv4",
- "URL_IPV6": "Url IPv6",
- "PATTERN_IPV4": "Pattern IPv4",
- "PATTERN_IPV6": "Pattern IPv6",
- "IPv4": "IPv4",
- "IPv6": "IPv6",
- "IP": "IP",
- "TARGET": "Target",
- "TARGETS": "Targets",
- "NOTES": "Notes"
- },
- "TITLE": {
- "USER_BOUQUET_EDITOR": "User group editor"
- },
- "MESSAGES": {
- "NO_CONTENT": "No content",
- "INVALID_REGEXP": "Invalid regexp",
- "INVALID_FILETYPE": "Invalid filetype",
- "TARGET_NOT_EXISTS": "Target does not exist",
- "DOWNLOAD": {
- "SUCCESS": "Successfully downloaded",
- "FAIL": "Failed to download!",
- "PLAYLIST": {
- "SUCCESS": "Successfully downloaded playlist",
- "FAIL": "Failed to download playlist!"
- },
- "SERVER_CONFIG": {
- "FAIL": "Failed to download server config!"
- }
- },
- "SAVE": {
- "MAIN_CONFIG": {
- "SUCCESS": "Main config saved!",
- "FAIL": "Failed to save main config!"
- },
- "BOUQUET": {
- "SUCCESS": "Successfully saved bouquet",
- "FAIL": "Failed to save bouquet!"
- },
- "USER": {
- "SUCCESS": "Successfully saved user",
- "FAIL": "Failed to save user!",
- "USERNAME_REQUIRED": "Username required",
- "DUPLICATE_USERNAME": "Duplicate username!",
- "DUPLICATE_TOKEN": "Duplicate token!",
- "TARGET_NOT_FOUND": "Target not found!",
- "MAX_CONNECTIONS_INVALID": "MaxConnections invalid"
- }
- },
- "PLAYLIST_UPDATE": {
- "SUCCESS": "Successfully started playlist update!",
- "FAIL": "Playlist update failed!"
- },
- "SOURCE_SELECTOR": {
- "MISSING_SELECTION": "No entry selected"
- }
- },
- "INFO": {
- "RESTART_TO_APPLY_CHANGES": "You need to restart to apply changes.",
- "SCHEDULE_EXAMPLE": "Schedule example",
- "THREADS": "Threads: keep it 0 if you have no problems. Increase it if you have multiple providers. Accessing you provider parallel can cause a ban.",
- "WEB_ROOT": "Web-Root: Contains the web-ui files. They are normally placed in the same directory under ./web"
- },
- "HINT": {
- "CONFIG": {
- "API_PROXY": {
- "PATH": "URL path prefix"
- },
- "USER": {
- "TARGET_FORCE_REDIRECT": "Force Redirect is set for target"
- },
- "LOG": {
- "ACTIVE_USER": "Activates active user info log"
- },
- "PROXY": {
- "RESOURCE_REWRITE_DISABLE": "If activated, the URL rewrite of resources in reverse proxy mode is disabled",
- "DISABLE_REFERER_HEADER": "If activated, the referer header is not sent to the provider",
- "THROTTLE": "Supported units are KB/s,MB/s,KiB/s,MiB/s,kbps,mbps,Mibps"
- },
- "WEB_UI": {
- "PATH": "Configuration of a path prefix, allowing it to be accessed behind a reverse proxy under a custom subpath. like https://yourdomain.com/tuliprox"
- }
- }
- },
- "SETUP":{
- "LABEL": {
- "NEXT": "Next",
- "BACK": "Back",
- "FINISH_SETUP": "Finish Setup"
- },
- "MSG": {
- "WELCOME": "Welcome to tuliprox setup. This wizard will guide you through the initial setup process."
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/public/assets/i18n/es_common.json b/frontend/public/assets/i18n/es_common.json
deleted file mode 100644
index 1b336e84c..000000000
--- a/frontend/public/assets/i18n/es_common.json
+++ /dev/null
@@ -1,112 +0,0 @@
-{
- "LABEL": {
- "LIVE": "En vivo",
- "VOD": "VOD",
- "SERIES": "Series",
- "SAVE": "Guardar",
- "SHOW_SELECTED": "Mostrar seleccionados",
- "SELECT_ALL": "Seleccionar todo",
- "DESELECT_ALL": "Deseleccionar todo",
- "SEARCH": "Buscar",
- "CLEAR": "Borrar",
- "REGEXP": "Expresión regular",
- "LOGOUT": "Cerrar sesión",
- "CONFIGURATION": "Configuración",
- "HOST": "Host",
- "PORT": "Puerto",
- "WEB_ROOT": "Raíz web",
- "THREADS": "Hilos",
- "WORKING_DIR": "Directorio de trabajo",
- "BACKUP_DIR": "Directorio de respaldo",
- "UPDATE_ON_BOOT": "Actualizar al arrancar",
- "WEB_UI": "Interfaz web",
- "SCHEDULES": "Horarios",
- "SANITIZE_SENSITIVE_INFO": "Sanitizar información sensible",
- "ACTIVE_CLIENTS": "Clientes activos",
- "RESOURCE_REWRITE_DISABLE": "Desactivar reescritura de recursos",
- "RECONNECT": "Reconectar",
- "BUFFER_ENABLED": "Búfer activado",
- "BUFFER_SIZE": "Tamaño del búfer",
- "ENABLED": "Habilitado",
- "SIZE": "Tamaño",
- "CACHE_DIR": "Directorio de caché",
- "NOTIFY_ON": "Notificar en",
- "BOT_TOKEN": "Token del bot",
- "CHAT_IDS": "IDs de chat",
- "URL": "URL",
- "USER": "Usuario",
- "TOKEN": "Token",
- "WEB_SEARCH_URL": "URL de búsqueda web",
- "EXTENSIONS": "Extensiones",
- "HEADERS": "Encabezados",
- "DIRECTORY": "Directorio",
- "ORGANIZE_INTO_DIRECTORIES": "Organizar en directorios",
- "EPISODE_PATTERN": "Patrón de episodio",
- "API": "API",
- "MAIN": "Principal",
- "REVERSE_PROXY": "Proxy inverso",
- "MESSAGING": "Mensajería",
- "VIDEO": "Video",
- "EXAMPLES": "Ejemplos",
- "UPDATE": "Actualizar",
- "START": "Iniciar",
- "CACHE": "Caché",
- "STREAM": "Flujo",
- "LOG": "Registro",
- "CONFIG": "Configuración",
- "TELEGRAM": "Telegram",
- "REST": "Rest",
- "PUSHOVER": "Pushover",
- "PROXY": "Proxy",
- "REFRESH": "Actualizar",
- "STATUS": "Estado",
- "MAX_CON": "MaxCon",
- "EXP_DATE": "Fecha de expiración",
- "USERNAME": "Nombre de usuario",
- "PASSWORD": "Contraseña",
- "SERVER": "Servidor",
- "TIMEZONE": "Zona horaria",
- "PATH": "Ruta",
- "MESSAGE": "Mensaje",
- "PROTOCOL": "Protocolo",
- "NAME": "Nombre"
- },
- "TITLE": {
- "USER_BOUQUET_EDITOR": "Editor de bouquet de usuario"
- },
- "MESSAGES": {
- "INVALID_REGEXP": "Expresión regular no válida",
- "INVALID_FILETYPE": "Tipo de archivo no válido",
- "DOWNLOAD": {
- "SUCCESS": "Descargado con éxito",
- "FAIL": "¡Error al descargar!",
- "PLAYLIST": {
- "SUCCESS": "Lista de reproducción descargada con éxito",
- "FAIL": "¡Error al descargar la lista de reproducción!"
- },
- "SERVER_CONFIG": {
- "FAIL": "¡Error al descargar la configuración del servidor!"
- }
- },
- "SAVE": {
- "MAIN_CONFIG": {
- "SUCCESS": "¡Configuración principal guardada!",
- "FAIL": "¡Error al guardar la configuración principal!"
- },
- "BOUQUET": {
- "SUCCESS": "Bouquet guardado con éxito",
- "FAIL": "¡Error al guardar el bouquet!"
- },
- "USER": {
- "SUCCESS": "Usuario guardado con éxito",
- "FAIL": "¡Error al guardar el usuario!"
- }
- }
- },
- "INFO": {
- "RESTART_TO_APPLY_CHANGES": "Necesitas reiniciar para aplicar los cambios.",
- "SCHEDULE_EXAMPLE": "Ejemplo de horario",
- "THREADS": "Hilos: mantén en 0 si no tienes problemas. Aumenta si tienes varios proveedores. Acceder a tu proveedor en paralelo puede causar un baneo.",
- "WEB_ROOT": "Raíz web: contiene los archivos de la interfaz web. Normalmente se colocan en el mismo directorio bajo ./web"
- }
-}
diff --git a/frontend/public/assets/i18n/fr_common.json b/frontend/public/assets/i18n/fr_common.json
deleted file mode 100644
index f1c5c33dc..000000000
--- a/frontend/public/assets/i18n/fr_common.json
+++ /dev/null
@@ -1,112 +0,0 @@
-{
- "LABEL": {
- "LIVE": "En direct",
- "VOD": "VOD",
- "SERIES": "Séries",
- "SAVE": "Enregistrer",
- "SHOW_SELECTED": "Afficher la sélection",
- "SELECT_ALL": "Tout sélectionner",
- "DESELECT_ALL": "Tout désélectionner",
- "SEARCH": "Rechercher",
- "CLEAR": "Effacer",
- "REGEXP": "Expression régulière",
- "LOGOUT": "Se déconnecter",
- "CONFIGURATION": "Configuration",
- "HOST": "Hôte",
- "PORT": "Port",
- "WEB_ROOT": "Racine Web",
- "THREADS": "Threads",
- "WORKING_DIR": "Répertoire de travail",
- "BACKUP_DIR": "Répertoire de sauvegarde",
- "UPDATE_ON_BOOT": "Mettre à jour au démarrage",
- "WEB_UI": "Interface Web",
- "SCHEDULES": "Programmes",
- "SANITIZE_SENSITIVE_INFO": "Assainir les informations sensibles",
- "ACTIVE_CLIENTS": "Clients actifs",
- "RESOURCE_REWRITE_DISABLE": "Désactiver la réécriture des ressources",
- "RECONNECT": "Se reconnecter",
- "BUFFER_ENABLED": "Mise en mémoire tampon activée",
- "BUFFER_SIZE": "Taille du tampon",
- "ENABLED": "Activé",
- "SIZE": "Taille",
- "CACHE_DIR": "Répertoire du cache",
- "NOTIFY_ON": "Notifier lorsqu'il y a",
- "BOT_TOKEN": "Jeton du bot",
- "CHAT_IDS": "Identifiants de chat",
- "URL": "URL",
- "USER": "Utilisateur",
- "TOKEN": "Jeton",
- "WEB_SEARCH_URL": "URL de recherche Web",
- "EXTENSIONS": "Extensions",
- "HEADERS": "En-têtes",
- "DIRECTORY": "Répertoire",
- "ORGANIZE_INTO_DIRECTORIES": "Organiser dans des répertoires",
- "EPISODE_PATTERN": "Modèle d'épisode",
- "API": "API",
- "MAIN": "Principal",
- "REVERSE_PROXY": "Proxy inverse",
- "MESSAGING": "Messagerie",
- "VIDEO": "Vidéo",
- "EXAMPLES": "Exemples",
- "UPDATE": "Mettre à jour",
- "START": "Démarrer",
- "CACHE": "Cache",
- "STREAM": "Diffusion",
- "LOG": "Journal",
- "CONFIG": "Configuration",
- "TELEGRAM": "Telegram",
- "REST": "Rest",
- "PUSHOVER": "Pushover",
- "PROXY": "Proxy",
- "REFRESH": "Rafraîchir",
- "STATUS": "Statut",
- "MAX_CON": "MaxCon",
- "EXP_DATE": "Date d'expiration",
- "USERNAME": "Nom d'utilisateur",
- "PASSWORD": "Mot de passe",
- "SERVER": "Serveur",
- "TIMEZONE": "Fuseau horaire",
- "PATH": "Chemin",
- "MESSAGE": "Message",
- "PROTOCOL": "Protocole",
- "NAME": "Nom"
- },
- "TITLE": {
- "USER_BOUQUET_EDITOR": "Éditeur de bouquet utilisateur"
- },
- "MESSAGES": {
- "INVALID_REGEXP": "Expression régulière invalide",
- "INVALID_FILETYPE": "Type de fichier invalide",
- "DOWNLOAD": {
- "SUCCESS": "Téléchargement réussi",
- "FAIL": "Échec du téléchargement!",
- "PLAYLIST": {
- "SUCCESS": "Liste de lecture téléchargée avec succès",
- "FAIL": "Échec du téléchargement de la liste de lecture!"
- },
- "SERVER_CONFIG": {
- "FAIL": "Échec du téléchargement de la configuration du serveur!"
- }
- },
- "SAVE": {
- "MAIN_CONFIG": {
- "SUCCESS": "Configuration principale enregistrée!",
- "FAIL": "Échec de l'enregistrement de la configuration principale!"
- },
- "BOUQUET": {
- "SUCCESS": "Bouquet enregistré avec succès",
- "FAIL": "Échec de l'enregistrement du bouquet!"
- },
- "USER": {
- "SUCCESS": "Utilisateur enregistré avec succès",
- "FAIL": "Échec de l'enregistrement de l'utilisateur!"
- }
- }
- },
- "INFO": {
- "RESTART_TO_APPLY_CHANGES": "Vous devez redémarrer pour appliquer les modifications.",
- "SCHEDULE_EXAMPLE": "Exemple de programme",
- "THREADS": "Threads: laissez-le à 0 si vous n'avez aucun problème. Augmentez-le si vous avez plusieurs fournisseurs. L'accès parallèle à votre fournisseur peut entraîner un ban.",
- "WEB_ROOT": "Racine Web: Contient les fichiers de l'interface web. Ils sont normalement placés dans le même répertoire sous ./web"
- }
-}
diff --git a/frontend/public/assets/i18n/it_common.json b/frontend/public/assets/i18n/it_common.json
deleted file mode 100644
index 76a2a788c..000000000
--- a/frontend/public/assets/i18n/it_common.json
+++ /dev/null
@@ -1,112 +0,0 @@
-{
- "LABEL": {
- "LIVE": "In diretta",
- "VOD": "VOD",
- "SERIES": "Serie",
- "SAVE": "Salva",
- "SHOW_SELECTED": "Mostra selezionati",
- "SELECT_ALL": "Seleziona tutto",
- "DESELECT_ALL": "Deseleziona tutto",
- "SEARCH": "Cerca",
- "CLEAR": "Cancella",
- "REGEXP": "Espressione regolare",
- "LOGOUT": "Esci",
- "CONFIGURATION": "Configurazione",
- "HOST": "Host",
- "PORT": "Porta",
- "WEB_ROOT": "Root web",
- "THREADS": "Thread",
- "WORKING_DIR": "Cartella di lavoro",
- "BACKUP_DIR": "Cartella di backup",
- "UPDATE_ON_BOOT": "Aggiorna all'avvio",
- "WEB_UI": "Interfaccia web",
- "SCHEDULES": "Orari",
- "SANITIZE_SENSITIVE_INFO": "Sanitizza informazioni sensibili",
- "ACTIVE_CLIENTS": "Client attivi",
- "RESOURCE_REWRITE_DISABLE": "Disabilita riscrittura risorse",
- "RECONNECT": "Riconnetti",
- "BUFFER_ENABLED": "Buffer attivato",
- "BUFFER_SIZE": "Dimensione buffer",
- "ENABLED": "Abilitato",
- "SIZE": "Dimensione",
- "CACHE_DIR": "Cartella cache",
- "NOTIFY_ON": "Notifica su",
- "BOT_TOKEN": "Token bot",
- "CHAT_IDS": "ID chat",
- "URL": "URL",
- "USER": "Utente",
- "TOKEN": "Token",
- "WEB_SEARCH_URL": "URL ricerca web",
- "EXTENSIONS": "Estensioni",
- "HEADERS": "Intestazioni",
- "DIRECTORY": "Cartella",
- "ORGANIZE_INTO_DIRECTORIES": "Organizza in cartelle",
- "EPISODE_PATTERN": "Pattern episodio",
- "API": "API",
- "MAIN": "Principale",
- "REVERSE_PROXY": "Proxy inverso",
- "MESSAGING": "Messaggistica",
- "VIDEO": "Video",
- "EXAMPLES": "Esempi",
- "UPDATE": "Aggiorna",
- "START": "Avvia",
- "CACHE": "Cache",
- "STREAM": "Flusso",
- "LOG": "Registro",
- "CONFIG": "Configurazione",
- "TELEGRAM": "Telegram",
- "REST": "Rest",
- "PUSHOVER": "Pushover",
- "PROXY": "Proxy",
- "REFRESH": "Ricarica",
- "STATUS": "Stato",
- "MAX_CON": "MaxCon",
- "EXP_DATE": "Data di scadenza",
- "USERNAME": "Nome utente",
- "PASSWORD": "Password",
- "SERVER": "Server",
- "TIMEZONE": "Fuso orario",
- "PATH": "Percorso",
- "MESSAGE": "Messaggio",
- "PROTOCOL": "Protocollo",
- "NAME": "Nome"
- },
- "TITLE": {
- "USER_BOUQUET_EDITOR": "Editor bouquet utente"
- },
- "MESSAGES": {
- "INVALID_REGEXP": "Espressione regolare non valida",
- "INVALID_FILETYPE": "Tipo di file non valido",
- "DOWNLOAD": {
- "SUCCESS": "Scaricato con successo",
- "FAIL": "Download fallito!",
- "PLAYLIST": {
- "SUCCESS": "Playlist scaricata con successo",
- "FAIL": "Download playlist fallito!"
- },
- "SERVER_CONFIG": {
- "FAIL": "Download configurazione server fallito!"
- }
- },
- "SAVE": {
- "MAIN_CONFIG": {
- "SUCCESS": "Configurazione principale salvata!",
- "FAIL": "Salvataggio configurazione principale fallito!"
- },
- "BOUQUET": {
- "SUCCESS": "Bouquet salvato con successo",
- "FAIL": "Salvataggio bouquet fallito!"
- },
- "USER": {
- "SUCCESS": "Utente salvato con successo",
- "FAIL": "Salvataggio utente fallito!"
- }
- }
- },
- "INFO": {
- "RESTART_TO_APPLY_CHANGES": "È necessario riavviare per applicare le modifiche.",
- "SCHEDULE_EXAMPLE": "Esempio di programma",
- "THREADS": "Thread: lascia 0 se non hai problemi. Aumenta se hai più provider. L'accesso parallelo ai tuoi provider potrebbe causare un ban.",
- "WEB_ROOT": "Root Web: Contiene i file dell'interfaccia web. Di solito vengono posizionati nella stessa cartella sotto ./web"
- }
-}
diff --git a/frontend/public/assets/i18n/ru_common.json b/frontend/public/assets/i18n/ru_common.json
deleted file mode 100644
index f321fe183..000000000
--- a/frontend/public/assets/i18n/ru_common.json
+++ /dev/null
@@ -1,112 +0,0 @@
-{
- "LABEL": {
- "LIVE": "Прямой эфир",
- "VOD": "VOD",
- "SERIES": "Сериалы",
- "SAVE": "Сохранить",
- "SHOW_SELECTED": "Показать выбранное",
- "SELECT_ALL": "Выбрать все",
- "DESELECT_ALL": "Отменить выбор всех",
- "SEARCH": "Поиск",
- "CLEAR": "Очистить",
- "REGEXP": "Регулярное выражение",
- "LOGOUT": "Выйти",
- "CONFIGURATION": "Конфигурация",
- "HOST": "Хост",
- "PORT": "Порт",
- "WEB_ROOT": "Корень веба",
- "THREADS": "Потоки",
- "WORKING_DIR": "Рабочий каталог",
- "BACKUP_DIR": "Каталог резервных копий",
- "UPDATE_ON_BOOT": "Обновить при загрузке",
- "WEB_UI": "Веб-интерфейс",
- "SCHEDULES": "Расписания",
- "SANITIZE_SENSITIVE_INFO": "Очистить чувствительную информацию",
- "ACTIVE_CLIENTS": "Активные клиенты",
- "RESOURCE_REWRITE_DISABLE": "Отключить переписывание ресурсов",
- "RECONNECT": "Переподключить",
- "BUFFER_ENABLED": "Буфер включен",
- "BUFFER_SIZE": "Размер буфера",
- "ENABLED": "Включено",
- "SIZE": "Размер",
- "CACHE_DIR": "Каталог кэша",
- "NOTIFY_ON": "Уведомить о",
- "BOT_TOKEN": "Токен бота",
- "CHAT_IDS": "ID чатов",
- "URL": "URL",
- "USER": "Пользователь",
- "TOKEN": "Токен",
- "WEB_SEARCH_URL": "URL веб-поиска",
- "EXTENSIONS": "Расширения",
- "HEADERS": "Заголовки",
- "DIRECTORY": "Каталог",
- "ORGANIZE_INTO_DIRECTORIES": "Организовать в каталоги",
- "EPISODE_PATTERN": "Шаблон эпизодов",
- "API": "API",
- "MAIN": "Основное",
- "REVERSE_PROXY": "Обратный прокси",
- "MESSAGING": "Сообщения",
- "VIDEO": "Видео",
- "EXAMPLES": "Примеры",
- "UPDATE": "Обновить",
- "START": "Старт",
- "CACHE": "Кэш",
- "STREAM": "Поток",
- "LOG": "Лог",
- "CONFIG": "Конфигурация",
- "TELEGRAM": "Телеграм",
- "REST": "Rest",
- "PUSHOVER": "Pushover",
- "PROXY": "Прокси",
- "REFRESH": "Обновить",
- "STATUS": "Статус",
- "MAX_CON": "Макс. соединений",
- "EXP_DATE": "Дата истечения",
- "USERNAME": "Имя пользователя",
- "PASSWORD": "Пароль",
- "SERVER": "Сервер",
- "TIMEZONE": "Часовой пояс",
- "PATH": "Путь",
- "MESSAGE": "Сообщение",
- "PROTOCOL": "Протокол",
- "NAME": "Имя"
- },
- "TITLE": {
- "USER_BOUQUET_EDITOR": "Редактор букета пользователя"
- },
- "MESSAGES": {
- "INVALID_REGEXP": "Неверное регулярное выражение",
- "INVALID_FILETYPE": "Неверный тип файла",
- "DOWNLOAD": {
- "SUCCESS": "Загрузка успешна",
- "FAIL": "Ошибка загрузки!",
- "PLAYLIST": {
- "SUCCESS": "Плейлист успешно загружен",
- "FAIL": "Ошибка загрузки плейлиста!"
- },
- "SERVER_CONFIG": {
- "FAIL": "Не удалось загрузить конфигурацию сервера!"
- }
- },
- "SAVE": {
- "MAIN_CONFIG": {
- "SUCCESS": "Основная конфигурация сохранена!",
- "FAIL": "Не удалось сохранить основную конфигурацию!"
- },
- "BOUQUET": {
- "SUCCESS": "Букет успешно сохранен",
- "FAIL": "Не удалось сохранить букет!"
- },
- "USER": {
- "SUCCESS": "Пользователь успешно сохранен",
- "FAIL": "Не удалось сохранить пользователя!"
- }
- }
- },
- "INFO": {
- "RESTART_TO_APPLY_CHANGES": "Необходимо перезагрузить систему, чтобы применить изменения.",
- "SCHEDULE_EXAMPLE": "Пример расписания",
- "THREADS": "Потоки: оставьте 0, если нет проблем. Увеличьте, если у вас несколько провайдеров. Одновременный доступ к нескольким провайдерам может привести к блокировке.",
- "WEB_ROOT": "Корень веба: содержит файлы веб-интерфейса. Обычно они размещаются в той же директории ./web"
- }
-}
diff --git a/frontend/public/assets/i18n/tr_common.json b/frontend/public/assets/i18n/tr_common.json
deleted file mode 100644
index 0033ccda2..000000000
--- a/frontend/public/assets/i18n/tr_common.json
+++ /dev/null
@@ -1,115 +0,0 @@
-{
- "LABEL": {
- "LIVE": "Canlı",
- "VOD": "Video",
- "SERIES": "Diziler",
- "SAVE": "Kaydet",
- "SUBMIT": "Onayla",
- "OK": "Tamam",
- "CANCEL": "İptal",
- "SHOW_SELECTED": "Seçilenleri Göster",
- "SELECT_ALL": "Tümünü Seç",
- "DESELECT_ALL": "Tümünü Seçimden Çıkar",
- "SEARCH": "Ara",
- "CLEAR": "Sil",
- "REGEXP": "RegExp",
- "LOGOUT": "Çıkış Yap",
- "CONFIGURATION": "Yapılandırma",
- "HOST": "Sunucu",
- "PORT": "Port",
- "WEB_ROOT": "Web Klasörü",
- "THREADS": "İşlemciler",
- "WORKING_DIR": "Çalışma Klasörü",
- "BACKUP_DIR": "Yedekleme Klasörü",
- "UPDATE_ON_BOOT": "Başlangıçta Güncelle",
- "WEB_UI": "Web UI",
- "SCHEDULES": "Programlar",
- "SANITIZE_SENSITIVE_INFO": "Hassas Bilgileri Temizle",
- "ACTIVE_CLIENTS": "Aktif Kullanıcılar",
- "RESOURCE_REWRITE_DISABLE": "Url Yönlendirme Devre Dışı",
- "RECONNECT": "Yeniden Bağlan",
- "BUFFER_ENABLED": "Arabellek Aktif",
- "BUFFER_SIZE": "Arabellek Boyutu",
- "ENABLED": "Aktif",
- "SIZE": "Boyut",
- "CACHE_DIR": "Önbellek Dizin",
- "NOTIFY_ON": "Bildirimler",
- "BOT_TOKEN": "Bot Token'i",
- "CHAT_IDS": "Chat ID'leri",
- "URL": "URL",
- "USER": "Kullanıcı",
- "TOKEN": "Token",
- "WEB_SEARCH_URL": "Web Arama URL'si",
- "EXTENSIONS": "Eklentiler",
- "HEADERS": "Başlıklar",
- "DIRECTORY": "Klasör",
- "ORGANIZE_INTO_DIRECTORIES": "Klasörlere ayır",
- "EPISODE_PATTERN": "Bölüm Pattern'i",
- "API": "API",
- "MAIN": "Ana",
- "REVERSE_PROXY": "Ters Proxy",
- "MESSAGING": "Mesajlaşma",
- "VIDEO": "Video",
- "EXAMPLES": "Örnekler",
- "UPDATE": "Güncelle",
- "START": "Başlat",
- "CACHE": "Önbellek",
- "STREAM": "Yayın",
- "LOG": "Kayıt",
- "CONFIG": "Yapılandırma",
- "TELEGRAM": "Telegram",
- "REST": "Rest",
- "PUSHOVER": "Pushover",
- "PROXY": "Proxy",
- "REFRESH": "Yenile",
- "STATUS": "Durum",
- "MAX_CON": "Max. Bağlantı",
- "EXP_DATE": "Son Kullanma Tarihi",
- "USERNAME": "Kullanıcı Adı",
- "PASSWORD": "Şifre",
- "SERVER": "Sunucu",
- "TIMEZONE": "Zaman Dilimi",
- "PATH": "Yol",
- "MESSAGE": "Mesaj",
- "PROTOCOL": "Protokol",
- "NAME": "Ad"
- },
- "TITLE": {
- "USER_BOUQUET_EDITOR": "Kullanıcı Buket Editörü"
- },
- "MESSAGES": {
- "INVALID_REGEXP": "Geçersiz regexp",
- "INVALID_FILETYPE": "Geçersiz dosya türü",
- "DOWNLOAD": {
- "SUCCESS": "Başarıyla indirildi",
- "FAIL": "İndirme başarısız!",
- "PLAYLIST": {
- "SUCCESS": "Liste başarıyla indirildi",
- "FAIL": "Liste indirilemedi!"
- },
- "SERVER_CONFIG": {
- "FAIL": "Sunucu yapılandırması indirilemedi!"
- }
- },
- "SAVE": {
- "MAIN_CONFIG": {
- "SUCCESS": "Ana yapılandırma kaydedildi!",
- "FAIL": "Ana yapılandırma kaydedilemedi!"
- },
- "BOUQUET": {
- "SUCCESS": "Buket başarıyla kaydedildi",
- "FAIL": "Buket kaydedilemedi!"
- },
- "USER": {
- "SUCCESS": "Kullanıcı başarıyla kaydedildi",
- "FAIL": "Kullanıcı kaydedilemedi!"
- }
- }
- },
- "INFO": {
- "RESTART_TO_APPLY_CHANGES": "Değişiklikleri uygulamak için yeniden başlatmanız gerekmektedir.",
- "SCHEDULE_EXAMPLE": "Program örneği",
- "THREADS": "İşlemciler: Eğer bir sorununuz yoksa 0 olarak bırakın. Birden fazla sağlayıcı kullanıyorsanız artırabilirsiniz. Sağlayıcınıza paralel erişim, yasaklanmanıza neden olabilir.",
- "WEB_ROOT": "Web klassörü: Web-UI dosyalarını içerir. Genellikle ./web altında aynı dizine yerleştirilir."
- }
-}
diff --git a/webui/public/assets/icons.json b/frontend/public/assets/icons.json
similarity index 100%
rename from webui/public/assets/icons.json
rename to frontend/public/assets/icons.json
diff --git a/frontend/public/config.json b/frontend/public/config.json
index 390115357..138d5b0b7 100644
--- a/frontend/public/config.json
+++ b/frontend/public/config.json
@@ -1,9 +1,12 @@
{
- "tab_title": "tuliprox",
- "app_title": "tuliprox",
- "app_logo": "assets/tuliprox-logo.svg",
+ "tabTitle": "tuliprox",
+ "appLogo": "/assets/tuliprox-logo.svg",
"api": {
"apiUrl": "/api/v1/",
"authUrl": "/auth/"
- }
+ },
+ "discord": "https://discord.gg/gkzCmWw9Tf",
+ "documentation": "https://github.com/euzu/tuliprox/blob/master/README.md",
+ "wsUrl" : "/ws",
+ "protocolVersion": 1
}
\ No newline at end of file
diff --git a/frontend/public/index.html b/frontend/public/index.html
deleted file mode 100644
index fcc64ac04..000000000
--- a/frontend/public/index.html
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- tuliprox
-
-
- You need to enable JavaScript to run this app.
-
-
-
-
diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json
deleted file mode 100644
index 665f6df82..000000000
--- a/frontend/public/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "short_name": "tuliprox",
- "name": "tuliprox",
- "icons": [
- {
- "src": "assets/favicon.ico",
- "sizes": "64x64 32x32 24x24 16x16",
- "type": "image/x-icon"
- }
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
-}
diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt
deleted file mode 100644
index e9e57dc4d..000000000
--- a/frontend/public/robots.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# https://www.robotstxt.org/robotstxt.html
-User-agent: *
-Disallow:
diff --git a/webui/scss/_default.scss b/frontend/scss/_default.scss
similarity index 100%
rename from webui/scss/_default.scss
rename to frontend/scss/_default.scss
diff --git a/webui/scss/_size.scss b/frontend/scss/_size.scss
similarity index 100%
rename from webui/scss/_size.scss
rename to frontend/scss/_size.scss
diff --git a/webui/scss/_theme.scss b/frontend/scss/_theme.scss
similarity index 100%
rename from webui/scss/_theme.scss
rename to frontend/scss/_theme.scss
diff --git a/webui/scss/app/_app.scss b/frontend/scss/app/_app.scss
similarity index 100%
rename from webui/scss/app/_app.scss
rename to frontend/scss/app/_app.scss
diff --git a/webui/scss/app/_app_header.scss b/frontend/scss/app/_app_header.scss
similarity index 100%
rename from webui/scss/app/_app_header.scss
rename to frontend/scss/app/_app_header.scss
diff --git a/webui/scss/app/_component.scss b/frontend/scss/app/_component.scss
similarity index 100%
rename from webui/scss/app/_component.scss
rename to frontend/scss/app/_component.scss
diff --git a/webui/scss/app/components/_breadcrumbs.scss b/frontend/scss/app/components/_breadcrumbs.scss
similarity index 100%
rename from webui/scss/app/components/_breadcrumbs.scss
rename to frontend/scss/app/components/_breadcrumbs.scss
diff --git a/webui/scss/app/components/_card.scss b/frontend/scss/app/components/_card.scss
similarity index 100%
rename from webui/scss/app/components/_card.scss
rename to frontend/scss/app/components/_card.scss
diff --git a/webui/scss/app/components/_chip.scss b/frontend/scss/app/components/_chip.scss
similarity index 100%
rename from webui/scss/app/components/_chip.scss
rename to frontend/scss/app/components/_chip.scss
diff --git a/webui/scss/app/components/_collapse_panel.scss b/frontend/scss/app/components/_collapse_panel.scss
similarity index 100%
rename from webui/scss/app/components/_collapse_panel.scss
rename to frontend/scss/app/components/_collapse_panel.scss
diff --git a/webui/scss/app/components/_custom_dialog.scss b/frontend/scss/app/components/_custom_dialog.scss
similarity index 97%
rename from webui/scss/app/components/_custom_dialog.scss
rename to frontend/scss/app/components/_custom_dialog.scss
index 4c8cb8c93..b852b66e1 100644
--- a/webui/scss/app/components/_custom_dialog.scss
+++ b/frontend/scss/app/components/_custom_dialog.scss
@@ -73,6 +73,7 @@
display: flex;
justify-content: flex-end;
border-top: 1px solid var(--border-color);
+ padding-top: var(--padding-small);
gap: var(--gap-default);
&-left {
diff --git a/webui/scss/app/components/_dialog.scss b/frontend/scss/app/components/_dialog.scss
similarity index 100%
rename from webui/scss/app/components/_dialog.scss
rename to frontend/scss/app/components/_dialog.scss
diff --git a/webui/scss/app/components/_drop_down_icon_button.scss b/frontend/scss/app/components/_drop_down_icon_button.scss
similarity index 100%
rename from webui/scss/app/components/_drop_down_icon_button.scss
rename to frontend/scss/app/components/_drop_down_icon_button.scss
diff --git a/webui/scss/app/components/_filter.scss b/frontend/scss/app/components/_filter.scss
similarity index 100%
rename from webui/scss/app/components/_filter.scss
rename to frontend/scss/app/components/_filter.scss
diff --git a/webui/scss/app/components/_hide_content.scss b/frontend/scss/app/components/_hide_content.scss
similarity index 100%
rename from webui/scss/app/components/_hide_content.scss
rename to frontend/scss/app/components/_hide_content.scss
diff --git a/webui/scss/app/components/_icon_button.scss b/frontend/scss/app/components/_icon_button.scss
similarity index 100%
rename from webui/scss/app/components/_icon_button.scss
rename to frontend/scss/app/components/_icon_button.scss
diff --git a/webui/scss/app/components/_input.scss b/frontend/scss/app/components/_input.scss
similarity index 100%
rename from webui/scss/app/components/_input.scss
rename to frontend/scss/app/components/_input.scss
diff --git a/webui/scss/app/components/_list_view.scss b/frontend/scss/app/components/_list_view.scss
similarity index 100%
rename from webui/scss/app/components/_list_view.scss
rename to frontend/scss/app/components/_list_view.scss
diff --git a/webui/scss/app/components/_loading_indicator.scss b/frontend/scss/app/components/_loading_indicator.scss
similarity index 100%
rename from webui/scss/app/components/_loading_indicator.scss
rename to frontend/scss/app/components/_loading_indicator.scss
diff --git a/webui/scss/app/components/_login.scss b/frontend/scss/app/components/_login.scss
similarity index 100%
rename from webui/scss/app/components/_login.scss
rename to frontend/scss/app/components/_login.scss
diff --git a/webui/scss/app/components/_mapper_script.scss b/frontend/scss/app/components/_mapper_script.scss
similarity index 97%
rename from webui/scss/app/components/_mapper_script.scss
rename to frontend/scss/app/components/_mapper_script.scss
index 02ec7d246..f6153b803 100644
--- a/webui/scss/app/components/_mapper_script.scss
+++ b/frontend/scss/app/components/_mapper_script.scss
@@ -47,6 +47,10 @@
}
+ .reserved {
+ color: #da2ee0;
+ }
+
//.binary_op-wrapper{
// white-space: pre;
//}
diff --git a/webui/scss/app/components/_menu_item.scss b/frontend/scss/app/components/_menu_item.scss
similarity index 100%
rename from webui/scss/app/components/_menu_item.scss
rename to frontend/scss/app/components/_menu_item.scss
diff --git a/webui/scss/app/components/_no_content.scss b/frontend/scss/app/components/_no_content.scss
similarity index 100%
rename from webui/scss/app/components/_no_content.scss
rename to frontend/scss/app/components/_no_content.scss
diff --git a/webui/scss/app/components/_panel.scss b/frontend/scss/app/components/_panel.scss
similarity index 100%
rename from webui/scss/app/components/_panel.scss
rename to frontend/scss/app/components/_panel.scss
diff --git a/webui/scss/app/components/_popup_menu.scss b/frontend/scss/app/components/_popup_menu.scss
similarity index 100%
rename from webui/scss/app/components/_popup_menu.scss
rename to frontend/scss/app/components/_popup_menu.scss
diff --git a/webui/scss/app/components/_preferences.scss b/frontend/scss/app/components/_preferences.scss
similarity index 100%
rename from webui/scss/app/components/_preferences.scss
rename to frontend/scss/app/components/_preferences.scss
diff --git a/webui/scss/app/components/_radio_button_group.scss b/frontend/scss/app/components/_radio_button_group.scss
similarity index 100%
rename from webui/scss/app/components/_radio_button_group.scss
rename to frontend/scss/app/components/_radio_button_group.scss
diff --git a/webui/scss/app/components/_reveal_content.scss b/frontend/scss/app/components/_reveal_content.scss
similarity index 100%
rename from webui/scss/app/components/_reveal_content.scss
rename to frontend/scss/app/components/_reveal_content.scss
diff --git a/webui/scss/app/components/_search.scss b/frontend/scss/app/components/_search.scss
similarity index 100%
rename from webui/scss/app/components/_search.scss
rename to frontend/scss/app/components/_search.scss
diff --git a/webui/scss/app/components/_sidebar.scss b/frontend/scss/app/components/_sidebar.scss
similarity index 100%
rename from webui/scss/app/components/_sidebar.scss
rename to frontend/scss/app/components/_sidebar.scss
diff --git a/webui/scss/app/components/_table.scss b/frontend/scss/app/components/_table.scss
similarity index 100%
rename from webui/scss/app/components/_table.scss
rename to frontend/scss/app/components/_table.scss
diff --git a/webui/scss/app/components/_tag_list.scss b/frontend/scss/app/components/_tag_list.scss
similarity index 100%
rename from webui/scss/app/components/_tag_list.scss
rename to frontend/scss/app/components/_tag_list.scss
diff --git a/webui/scss/app/components/_text_button.scss b/frontend/scss/app/components/_text_button.scss
similarity index 100%
rename from webui/scss/app/components/_text_button.scss
rename to frontend/scss/app/components/_text_button.scss
diff --git a/webui/scss/app/components/_toastr.scss b/frontend/scss/app/components/_toastr.scss
similarity index 100%
rename from webui/scss/app/components/_toastr.scss
rename to frontend/scss/app/components/_toastr.scss
diff --git a/webui/scss/app/components/_toggle_switch.scss b/frontend/scss/app/components/_toggle_switch.scss
similarity index 100%
rename from webui/scss/app/components/_toggle_switch.scss
rename to frontend/scss/app/components/_toggle_switch.scss
diff --git a/webui/scss/app/components/dashboard/_action_card.scss b/frontend/scss/app/components/dashboard/_action_card.scss
similarity index 100%
rename from webui/scss/app/components/dashboard/_action_card.scss
rename to frontend/scss/app/components/dashboard/_action_card.scss
diff --git a/webui/scss/app/components/dashboard/_dashboard_view.scss b/frontend/scss/app/components/dashboard/_dashboard_view.scss
similarity index 100%
rename from webui/scss/app/components/dashboard/_dashboard_view.scss
rename to frontend/scss/app/components/dashboard/_dashboard_view.scss
diff --git a/webui/scss/app/components/dashboard/_stats_view.scss b/frontend/scss/app/components/dashboard/_stats_view.scss
similarity index 100%
rename from webui/scss/app/components/dashboard/_stats_view.scss
rename to frontend/scss/app/components/dashboard/_stats_view.scss
diff --git a/webui/scss/app/components/dashboard/_status_card.scss b/frontend/scss/app/components/dashboard/_status_card.scss
similarity index 100%
rename from webui/scss/app/components/dashboard/_status_card.scss
rename to frontend/scss/app/components/dashboard/_status_card.scss
diff --git a/webui/scss/app/components/playlist/_playlist_editor_view.scss b/frontend/scss/app/components/playlist/_playlist_editor_view.scss
similarity index 100%
rename from webui/scss/app/components/playlist/_playlist_editor_view.scss
rename to frontend/scss/app/components/playlist/_playlist_editor_view.scss
diff --git a/webui/scss/app/components/playlist/_playlist_explorer.scss b/frontend/scss/app/components/playlist/_playlist_explorer.scss
similarity index 100%
rename from webui/scss/app/components/playlist/_playlist_explorer.scss
rename to frontend/scss/app/components/playlist/_playlist_explorer.scss
diff --git a/webui/scss/app/components/playlist/_playlist_explorer_view.scss b/frontend/scss/app/components/playlist/_playlist_explorer_view.scss
similarity index 100%
rename from webui/scss/app/components/playlist/_playlist_explorer_view.scss
rename to frontend/scss/app/components/playlist/_playlist_explorer_view.scss
diff --git a/frontend/scss/app/components/playlist/_playlist_mappings.scss b/frontend/scss/app/components/playlist/_playlist_mappings.scss
new file mode 100644
index 000000000..c351f2126
--- /dev/null
+++ b/frontend/scss/app/components/playlist/_playlist_mappings.scss
@@ -0,0 +1,59 @@
+@use '../../../../scss/size';
+
+.tp__playlist-mappings {
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ align-items: flex-start;
+ box-sizing: border-box;
+ overflow: hidden;
+
+ &__mapping {
+ display: flex;
+ flex-flow: column;
+ gap: var(--gap-default);
+ justify-content: center;
+ box-sizing: border-box;
+ overflow: hidden;
+
+ &-section {
+ display: flex;
+ flex-flow: row ;
+ align-items: center;
+ box-sizing: border-box;
+ gap: var(--gap-small);
+ overflow: hidden;
+ }
+
+ &-mapper {
+ display: flex;
+ grid-template-columns: 1fr 1fr;
+
+ @media (max-width: size.$dashboard-mobile-breakpoint) {
+ grid-template-columns: 1fr;
+ }
+
+ gap: var(--gap-default);
+ box-sizing: border-box;
+ overflow: auto;
+
+ &-content {
+ display: flex;
+ flex-flow: column;
+ gap: var(--gap-default);
+ justify-content: center;
+ align-items: flex-start;
+ box-sizing: border-box;
+ }
+ }
+ }
+
+ label {
+ font-weight: bold;
+ color: var(--modest-text-color);
+
+ &:after {
+ content: ':'
+ }
+ }
+}
\ No newline at end of file
diff --git a/webui/scss/app/components/playlist/_playlist_processing.scss b/frontend/scss/app/components/playlist/_playlist_processing.scss
similarity index 100%
rename from webui/scss/app/components/playlist/_playlist_processing.scss
rename to frontend/scss/app/components/playlist/_playlist_processing.scss
diff --git a/webui/scss/app/components/playlist/_playlist_update_view.scss b/frontend/scss/app/components/playlist/_playlist_update_view.scss
similarity index 97%
rename from webui/scss/app/components/playlist/_playlist_update_view.scss
rename to frontend/scss/app/components/playlist/_playlist_update_view.scss
index 1920451fa..1d363f055 100644
--- a/webui/scss/app/components/playlist/_playlist_update_view.scss
+++ b/frontend/scss/app/components/playlist/_playlist_update_view.scss
@@ -1,7 +1,7 @@
.tp__playlist-update-view {
display: flex;
flex-flow: column;
- width: 100%;
+ width: 80vw;
gap: var(--gap-default);
padding: var(--padding-default) 0;
box-sizing: border-box;
@@ -17,6 +17,7 @@
&__body {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+
gap: var(--gap-default);
box-sizing: border-box;
overflow-y: auto;
diff --git a/webui/scss/app/components/playlist/_source_selector.scss b/frontend/scss/app/components/playlist/_source_selector.scss
similarity index 100%
rename from webui/scss/app/components/playlist/_source_selector.scss
rename to frontend/scss/app/components/playlist/_source_selector.scss
diff --git a/webui/scss/app/components/playlist/assistant/_playlist_assistant.scss b/frontend/scss/app/components/playlist/assistant/_playlist_assistant.scss
similarity index 100%
rename from webui/scss/app/components/playlist/assistant/_playlist_assistant.scss
rename to frontend/scss/app/components/playlist/assistant/_playlist_assistant.scss
diff --git a/webui/scss/app/components/playlist/input/_input_table.scss b/frontend/scss/app/components/playlist/input/_input_table.scss
similarity index 100%
rename from webui/scss/app/components/playlist/input/_input_table.scss
rename to frontend/scss/app/components/playlist/input/_input_table.scss
diff --git a/webui/scss/app/components/playlist/input/_input_type.scss b/frontend/scss/app/components/playlist/input/_input_type.scss
similarity index 100%
rename from webui/scss/app/components/playlist/input/_input_type.scss
rename to frontend/scss/app/components/playlist/input/_input_type.scss
diff --git a/webui/scss/app/components/playlist/target/_hdhomerun_output.scss b/frontend/scss/app/components/playlist/target/_hdhomerun_output.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_hdhomerun_output.scss
rename to frontend/scss/app/components/playlist/target/_hdhomerun_output.scss
diff --git a/webui/scss/app/components/playlist/target/_m3u_output.scss b/frontend/scss/app/components/playlist/target/_m3u_output.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_m3u_output.scss
rename to frontend/scss/app/components/playlist/target/_m3u_output.scss
diff --git a/webui/scss/app/components/playlist/target/_strm_output.scss b/frontend/scss/app/components/playlist/target/_strm_output.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_strm_output.scss
rename to frontend/scss/app/components/playlist/target/_strm_output.scss
diff --git a/webui/scss/app/components/playlist/target/_target_common.scss b/frontend/scss/app/components/playlist/target/_target_common.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_target_common.scss
rename to frontend/scss/app/components/playlist/target/_target_common.scss
diff --git a/webui/scss/app/components/playlist/target/_target_options.scss b/frontend/scss/app/components/playlist/target/_target_options.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_target_options.scss
rename to frontend/scss/app/components/playlist/target/_target_options.scss
diff --git a/webui/scss/app/components/playlist/target/_target_output.scss b/frontend/scss/app/components/playlist/target/_target_output.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_target_output.scss
rename to frontend/scss/app/components/playlist/target/_target_output.scss
diff --git a/webui/scss/app/components/playlist/target/_target_rename.scss b/frontend/scss/app/components/playlist/target/_target_rename.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_target_rename.scss
rename to frontend/scss/app/components/playlist/target/_target_rename.scss
diff --git a/webui/scss/app/components/playlist/target/_target_sort.scss b/frontend/scss/app/components/playlist/target/_target_sort.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_target_sort.scss
rename to frontend/scss/app/components/playlist/target/_target_sort.scss
diff --git a/webui/scss/app/components/playlist/target/_target_table.scss b/frontend/scss/app/components/playlist/target/_target_table.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_target_table.scss
rename to frontend/scss/app/components/playlist/target/_target_table.scss
diff --git a/webui/scss/app/components/playlist/target/_target_watch.scss b/frontend/scss/app/components/playlist/target/_target_watch.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_target_watch.scss
rename to frontend/scss/app/components/playlist/target/_target_watch.scss
diff --git a/webui/scss/app/components/playlist/target/_xtream_output.scss b/frontend/scss/app/components/playlist/target/_xtream_output.scss
similarity index 100%
rename from webui/scss/app/components/playlist/target/_xtream_output.scss
rename to frontend/scss/app/components/playlist/target/_xtream_output.scss
diff --git a/webui/scss/app/components/userlist/_max_connections.scss b/frontend/scss/app/components/userlist/_max_connections.scss
similarity index 100%
rename from webui/scss/app/components/userlist/_max_connections.scss
rename to frontend/scss/app/components/userlist/_max_connections.scss
diff --git a/webui/scss/app/components/userlist/_proxy_type.scss b/frontend/scss/app/components/userlist/_proxy_type.scss
similarity index 100%
rename from webui/scss/app/components/userlist/_proxy_type.scss
rename to frontend/scss/app/components/userlist/_proxy_type.scss
diff --git a/webui/scss/app/components/userlist/_user_state.scss b/frontend/scss/app/components/userlist/_user_state.scss
similarity index 100%
rename from webui/scss/app/components/userlist/_user_state.scss
rename to frontend/scss/app/components/userlist/_user_state.scss
diff --git a/webui/scss/app/components/userlist/_userlist_view.scss b/frontend/scss/app/components/userlist/_userlist_view.scss
similarity index 100%
rename from webui/scss/app/components/userlist/_userlist_view.scss
rename to frontend/scss/app/components/userlist/_userlist_view.scss
diff --git a/webui/scss/main.scss b/frontend/scss/main.scss
similarity index 100%
rename from webui/scss/main.scss
rename to frontend/scss/main.scss
diff --git a/frontend/src/api/api-service.ts b/frontend/src/api/api-service.ts
deleted file mode 100644
index 736764d4c..000000000
--- a/frontend/src/api/api-service.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-import {Observable} from "rxjs";
-import axios from "axios";
-import ServiceContext from "../service/service-context";
-
-const HEADER_CONTENT_TYPE = 'Content-Type';
-const HEADER_LANGUAGE = 'X-Language';
-const HEADER_ACCEPT = 'Accept';
-const HEADER_AUTHORIZATION = 'Authorization';
-
-export default interface ApiService {
- get(query: string, url?: string): Observable;
-
- post(query: string, payload: any, url?: string): Observable;
-
- put(query: string, payload: any, url?: string): Observable;
-
- delete(query: string, url?: string): Observable;
-
- postFile(query: string, fileName: string, file: any, url?: string): Observable;
- downloadFile(query: string, payload: any, url?: string): Observable;
-}
-
-export class DefaultApiService implements ApiService {
-
- private readonly DEFAULT_ERROR = {'origin': 'server', 'message': 'Server error'};
-
- private static getLanguage(): string {
- return "en";
- }
-
- private prepareError(err: any): any {
- return err || this.DEFAULT_ERROR;
- }
-
- private static getOption(options: any, key: any, defaultValue: any): string {
- if (options) {
- if (Object.prototype.hasOwnProperty.call(options, key)) {
- return options[key];
- }
- }
- return defaultValue;
- }
-
- protected getHeaders(options?: unknown): any {
- const headers: any = {};
- const language = DefaultApiService.getLanguage();
- if (language) {
- headers[HEADER_LANGUAGE] = language;
- }
- const value = DefaultApiService.getOption(options, HEADER_CONTENT_TYPE, 'application/json; charset=utf-8');
- if (value) {
- headers[HEADER_CONTENT_TYPE] = value;
- }
- headers[HEADER_ACCEPT] = 'application/json';
- const token = ServiceContext.auth().getToken();
- if (token) {
- headers[HEADER_AUTHORIZATION] = 'Bearer ' + token;
- }
- return headers;
- }
-
- protected getUrl(query: string, url?: string) {
- const apiBaseUrl = ServiceContext.config().getUiConfig().api.apiUrl;
- return (url ? url : apiBaseUrl) + query;
- }
-
- get(query: string, url?: string): Observable {
- return new Observable((observer) => {
- axios.get(this.getUrl(query, url), {headers: this.getHeaders()})
- .then((response) => {
- observer.next(response.data);
- observer.complete();
- })
- .catch((error) => observer.error(this.prepareError(error)));
- });
- }
-
- post(query: string, payload: any, url?: string): Observable {
- return new Observable((observer) => {
- axios.post(this.getUrl(query, url), payload, {headers: this.getHeaders()})
- .then((response) => {
- observer.next(response.data);
- observer.complete();
- })
- .catch((error) => observer.error(this.prepareError(error)));
- });
- }
-
- put(query: string, payload: any, url?: string): Observable {
- return new Observable((observer) => {
- axios.put(this.getUrl(query, url), payload, {headers: this.getHeaders()})
- .then((response) => {
- observer.next(response.data);
- observer.complete();
- })
- .catch((error) => observer.error(this.prepareError(error)));
- });
- }
-
- delete(query: string, url?: string): Observable {
- return new Observable((observer) => {
- axios.delete(this.getUrl(query, url), {headers: this.getHeaders()})
- .then((response) => {
- observer.next(response.data);
- observer.complete();
- })
- .catch((error) => observer.error(this.prepareError(error)));
- });
- }
-
- postFile(query: string, fileName: string, file: any, url?: string): Observable {
- const fd = new FormData();
- fd.append("fileName", fileName);
- fd.append("file", file, fileName);
-
- return new Observable((observer) => {
- axios.post(this.getUrl(query, url), fd, {headers: this.getHeaders({[HEADER_CONTENT_TYPE]: undefined})})
- .then((response) => {
- observer.next(response.data);
- observer.complete();
- })
- .catch((error) => observer.error(this.prepareError(error)));
- });
- }
-
- downloadFile(query: string, payload: any, url?: string): Observable {
- return this.post(query, payload, url);
- // return new Observable((observer) => {
- //
- // axios.post(this.getUrl(query, url), payload, {responseType: 'blob', headers: this.getHeaders()})
- // .then((response) => {
- // const disposition = response.headers['content-disposition'];
- // let filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1];
- // if (filename.toLowerCase().startsWith("utf-8''"))
- // filename = decodeURIComponent(filename.replace("utf-8''", ''));
- // else
- // filename = filename.replace(/['"]/g, '');
- // return {filename, data: response.data};
- // }).then((params: { filename: string, data: any }) => {
- // const url = window.URL.createObjectURL(params.data);
- // const a = document.createElement('a');
- // a.href = url;
- // a.download = params.filename;
- // document.body.appendChild(a); // append the element to the dom
- // a.click();
- // a.remove(); // afterwards, remove the element
- // observer.next(true as any);
- // observer.complete();
- // }).catch((error) => observer.error(this.prepareError(error)));
- // });
- }
-
-}
diff --git a/frontend/src/api/auth-api-service.ts b/frontend/src/api/auth-api-service.ts
deleted file mode 100644
index 6e2b286f2..000000000
--- a/frontend/src/api/auth-api-service.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import ApiService, {DefaultApiService} from "./api-service";
-import {Observable} from "rxjs";
-import ServiceContext from "../service/service-context";
-
-type TokenResponse = { token: string };
-
-export default interface AuthApiService extends ApiService {
- authenticate(username: string, password: string): Observable;
-
- refresh(): Observable
-}
-
-export class DefaultAuthApiService extends DefaultApiService implements AuthApiService {
- constructor() {
- super();
- }
-
- private getAuthUrl() : string {
- let authUrl = ServiceContext.config().getUiConfig().api.authUrl;
- if (authUrl.endsWith("/")) {
- authUrl = authUrl.slice(0, -1);
- }
- return authUrl;
- }
-
- authenticate(username: string, password: string): Observable<{ token: string }> {
- const authBaseUrl = this.getAuthUrl();
- return this.post('/token', {username, password}, authBaseUrl);
- }
-
- refresh(): Observable {
- const authBaseUrl = this.getAuthUrl();
- return this.post('/refresh', {}, authBaseUrl);
- }
-}
diff --git a/frontend/src/api/config-api-service.ts b/frontend/src/api/config-api-service.ts
deleted file mode 100644
index 585315843..000000000
--- a/frontend/src/api/config-api-service.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import ApiService, {DefaultApiService} from "./api-service";
-import {Observable} from "rxjs";
-import ServerConfig, {ApiProxyServerInfo, ServerMainConfig, TargetUser} from "../model/server-config";
-
-const SERVER_CONFIG_API_PATH = 'config';
-const SERVER_CONFIG_MAIN_API_PATH = 'config/main';
-const SERVER_CONFIG_TARGET_USER_API_PATH = 'config/user';
-const SERVER_CONFIG_SERVER_INFO_API_PATH = 'config/apiproxy';
-
-export default interface ConfigApiService extends ApiService {
- getServerConfig(): Observable;
-
- saveMainConfig(config: ServerMainConfig): Observable;
-
- saveTargetUser(targetUser: TargetUser[]): Observable;
-
- saveApiProxyConfig(serverInfo: ApiProxyServerInfo[]): Observable;
-}
-
-export class DefaultConfigApiService extends DefaultApiService implements ConfigApiService {
- getServerConfig(): Observable {
- return this.get(SERVER_CONFIG_API_PATH);
- }
-
- saveMainConfig(config: ServerMainConfig): Observable {
- return this.post(SERVER_CONFIG_MAIN_API_PATH, config);
- }
-
- saveTargetUser(targetUser: TargetUser[]): Observable {
- return this.post(SERVER_CONFIG_TARGET_USER_API_PATH, targetUser);
- }
-
- saveApiProxyConfig(serverInfo: ApiProxyServerInfo[]): Observable {
- return this.post(SERVER_CONFIG_SERVER_INFO_API_PATH, serverInfo);
- }
-
-}
diff --git a/frontend/src/api/file-api-service.ts b/frontend/src/api/file-api-service.ts
deleted file mode 100644
index f7fc91148..000000000
--- a/frontend/src/api/file-api-service.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import ApiService, {DefaultApiService} from "./api-service";
-import {Observable, throwError} from "rxjs";
-import {DownloadInfo, FileDownloadInfo, FileDownloadRequest} from "../model/file-download";
-import {first} from "rxjs/operators";
-
-//const FILE_API_PATH = 'file';
-const FILE_DOWNLOAD_API_PATH = 'file/download';
-const FILE_DOWNLOAD_INFO_API_PATH = FILE_DOWNLOAD_API_PATH + '/info';
-
-export default interface FileApiService extends ApiService {
- download(req: FileDownloadRequest): Observable;
-
- getDownloadInfo(): Observable;
-}
-
-export class DefaultFileApiService extends DefaultApiService implements FileApiService {
- download(req: FileDownloadRequest): Observable {
- if (req.url) {
- return this.post(FILE_DOWNLOAD_API_PATH, req);
- }
- return throwError(() => new Error('Invalid arguments'));
- }
-
- getDownloadInfo(): Observable {
- return new Observable((observer) => {
- const fetch_info = () => {
- this.get(FILE_DOWNLOAD_INFO_API_PATH).pipe(first()).subscribe({
- next: (info: DownloadInfo) => {
- if (info.completed) {
- observer.next(info);
- observer.complete();
- } else {
- observer.next(info);
- setTimeout(() => fetch_info(), 1000);
- }
- },
- error: (err) => observer.error(err),
- });
- }
- fetch_info();
- });
- }
-}
diff --git a/frontend/src/api/playlist-api-service.ts b/frontend/src/api/playlist-api-service.ts
deleted file mode 100644
index 2fc7363e3..000000000
--- a/frontend/src/api/playlist-api-service.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import ApiService, {DefaultApiService} from "./api-service";
-import {Observable, throwError} from "rxjs";
-import {PlaylistRequest} from "../model/playlist-request";
-import {PlaylistCategories, PlaylistItem} from "../model/playlist";
-
-const PLAYLIST_API_PATH = 'playlist';
-const TARGET_UPDATE_API_PATH = 'playlist/update';
-const WEBPLAYER_URL_API_PATH = 'playlist/webplayer';
-
-export default interface PlaylistApiService extends ApiService {
- getPlaylist(req: PlaylistRequest): Observable;
-
- updateTargets(targets: string[]): Observable;
-
- getWebPlayerUrl(item: PlaylistItem, req: PlaylistRequest): Observable;
-}
-
-export class DefaultPlaylistApiService extends DefaultApiService implements PlaylistApiService {
- getPlaylist(req: PlaylistRequest): Observable {
- // eslint-disable-next-line eqeqeq
- if (req != undefined) {
- return this.post(PLAYLIST_API_PATH, req);
- }
- return throwError(() => new Error('Invalid arguments'));
- }
-
- updateTargets(targets: string[]): Observable {
- return this.post(TARGET_UPDATE_API_PATH, targets);
- }
-
- getWebPlayerUrl(item: PlaylistItem, req: PlaylistRequest): Observable {
- return this.post(WEBPLAYER_URL_API_PATH + '/' + req.sourceId, item);
- }
-
-}
diff --git a/frontend/src/api/server-status-api-service.ts b/frontend/src/api/server-status-api-service.ts
deleted file mode 100644
index bd6f799a8..000000000
--- a/frontend/src/api/server-status-api-service.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import {Observable} from "rxjs";
-import ApiService, {DefaultApiService} from "./api-service";
-import {ServerIpCheck, ServerStatus} from "../model/server-status";
-
-const STATUS_PATH = "status";
-const IPCHECK_PATH = "ipinfo";
-
-export default interface ServerStatusApiService extends ApiService {
- getServerStatus(): Observable;
-
- getServerIpCheck(): Observable;
-}
-
-export class DefaultServerStatusApiService extends DefaultApiService implements ServerStatusApiService {
-
- getServerStatus(): Observable {
- return this.get(STATUS_PATH);
- }
-
- getServerIpCheck(): Observable {
- return this.get(IPCHECK_PATH);
- }
-}
diff --git a/frontend/src/api/user-config-api-service.ts b/frontend/src/api/user-config-api-service.ts
deleted file mode 100644
index 1cd8cf826..000000000
--- a/frontend/src/api/user-config-api-service.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import {Observable} from "rxjs";
-import ApiService, {DefaultApiService} from "./api-service";
-import {UserPlaylistCategories} from "../model/playlist";
-
-const PLAYLIST_CATEGORIES_PATH = "user/playlist/categories";
-const PLAYLIST_BOUQUET_PATH = "user/playlist/bouquet";
-
-export default interface UserConfigApiService extends ApiService {
- getPlaylistBouquet(): Observable;
- getPlaylistCategories(): Observable;
- savePlaylistBouquet(bouquet: UserPlaylistCategories): Observable;
-}
-
-export class DefaultUserConfigApiService extends DefaultApiService implements UserConfigApiService {
-
- getPlaylistBouquet(): Observable {
- return this.get(PLAYLIST_BOUQUET_PATH);
- }
- getPlaylistCategories(): Observable {
- return this.get(PLAYLIST_CATEGORIES_PATH);
- }
-
- savePlaylistBouquet(bouquet: UserPlaylistCategories): Observable {
- return this.post(PLAYLIST_BOUQUET_PATH, bouquet);
- }
-}
diff --git a/frontend/src/app-user/app-user.scss b/frontend/src/app-user/app-user.scss
deleted file mode 100755
index e85a82e65..000000000
--- a/frontend/src/app-user/app-user.scss
+++ /dev/null
@@ -1,73 +0,0 @@
-@use '../scss/common';
-
-.user-app {
- display: flex;
- flex-flow: column;
- justify-content: center;
- overflow: hidden;
- flex: 1 1 auto;
- box-sizing: border-box;
- max-width: 100%;
- color: var(--text-color);
-
- &__header {
- display: flex;
- flex-flow: row;
- font-size: 2rem;
- font-weight: bold;
- color: var(--app-header-color);
- align-content: center;
- padding: 10px;
- gap: 8px;
- @media (max-width: common.$media-size-mobile) {
- padding: 10px 0;
- }
-
- &__logo {
- display: flex;
- justify-content: center;
- align-items: center;
- svg, img {
- width: 2em;
- aspect-ratio: 1 / 1;
- transform: translate(-10px, 0);
- }
- }
-
- &__caption {
- display: flex;
- flex-flow: row nowrap;
- flex: 1 1 auto;
- justify-content: center;
- align-items: center;
- }
-
- &__toolbar {
- display: flex;
- align-items: center;
- flex: 0 0 auto;
- button {
- @include common.iconButton();
- }
- padding-right: 4px;
- }
- }
-
- &__main {
- display: flex;
- flex-flow: column;
- justify-content: center;
- flex: 1 1 auto;
- overflow: hidden;
- box-sizing: border-box;
- max-width: 100%;
- }
- &__content {
- display: flex;
- flex-flow: column;
- flex: 1 1 auto;
- box-sizing: border-box;
- overflow: hidden;
- max-width: 100%;
- }
-}
\ No newline at end of file
diff --git a/frontend/src/app-user/app-user.tsx b/frontend/src/app-user/app-user.tsx
deleted file mode 100755
index 6a9060a0b..000000000
--- a/frontend/src/app-user/app-user.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import React, {useCallback, useMemo} from 'react';
-import './app-user.scss';
-import {getIconByName} from "../icons/icons";
-import {useServices} from "../provider/service-provider";
-import UserPlaylist from "../component/user-playlist/user-playlist";
-import {LANGUAGE} from "../hook/use-translator";
-import i18next from "i18next";
-
-/* eslint-disable @typescript-eslint/no-empty-interface */
-interface AppUserProps {
-
-}
-
-export default function AppUser(props: AppUserProps) {
- const services = useServices();
- const appTitle = useMemo(() => services.config().getUiConfig().app_title ?? 'tuliprox', [services]);
- const appLogo = useMemo(() => {
- let logo = services.config().getUiConfig().app_logo;
- if (logo) {
- return ;
- } else {
- return getIconByName('Logo');
- }
- }, [services]);
- const handleLogout = useCallback(() => {
- services.auth().logout();
- }, [services]);
-
- const handleLanguage = useCallback((event: any) => {
- const language = event.target.value;
- LANGUAGE.next(language);
- }, []);
-
-
- return (
-
-
-
{appLogo} {appTitle}
-
{services.config().getUiConfig().languages.map(l => {l} )}
-
{getIconByName('Logout')}
-
-
-
- );
-}
diff --git a/frontend/src/app/app.scss b/frontend/src/app/app.scss
deleted file mode 100755
index 9035d7e89..000000000
--- a/frontend/src/app/app.scss
+++ /dev/null
@@ -1,76 +0,0 @@
-@use '../scss/common';
-
-.app {
- display: flex;
- flex-flow: column;
- justify-content: center;
- overflow: hidden;
- flex: 1 1 auto;
- box-sizing: border-box;
- max-width: 100%;
- color: var(--text-color);
- background-color: inherit;
-
- .app-header {
- display: flex;
- flex-flow: row;
- font-size: 2rem;
- font-weight: bold;
- color: var(--app-header-color);
- align-content: center;
- padding: 10px;
- @media (max-width: common.$media-size-mobile) {
- padding: 10px 0;
- }
-
- &__logo {
- display: flex;
- justify-content: center;
- align-items: center;
-
- svg, img {
- width: 2em;
- aspect-ratio: 1 / 1;
- transform: translate(-10px, 0);
- }
- }
-
- &__caption {
- display: flex;
- flex-flow: row nowrap;
- flex: 1 1 auto;
- justify-content: center;
- align-items: center;
- }
-
- &__toolbar {
- display: flex;
- align-items: center;
- button {
- @include common.iconButton();
- }
- padding-right: 4px;
- }
- }
-
- .app-main {
- display: flex;
- flex-flow: column;
- justify-content: center;
- flex: 1 1 auto;
- overflow: hidden;
- box-sizing: border-box;
- max-width: 100%;
- background-color: inherit;
-
- .app-content {
- display: flex;
- flex-flow: column;
- flex: 1 1 auto;
- box-sizing: border-box;
- overflow: hidden;
- max-width: 100%;
- background-color: inherit;
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/app/app.tsx b/frontend/src/app/app.tsx
deleted file mode 100755
index 5bc491897..000000000
--- a/frontend/src/app/app.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import './app.scss';
-import {useSnackbar} from 'notistack';
-import {useServices} from "../provider/service-provider";
-import {first} from "rxjs/operators";
-import {noop} from "rxjs";
-import ServerConfig from "../model/server-config";
-import {getIconByName} from "../icons/icons";
-import Preferences from "../component/preferences/preferences";
-import useTranslator, {LANGUAGE} from "../hook/use-translator";
-import i18next from "i18next";
-import PlaylistBrowser from '../component/playlist-browser/playlist-browser';
-import SetupWizard from "../component/setup-wizard/setup-wizard";
-
-/* eslint-disable @typescript-eslint/no-empty-interface */
-interface AppProps {
-
-}
-
-export default function App(props: AppProps) {
- const services = useServices();
- const [serverConfig, setServerConfig] = useState(undefined);
- const serverConfigLoaded = useRef(false);
- const [preferencesVisible, setPreferencesVisible] = useState(true);
- const appTitle = useMemo(() => services.config().getUiConfig().app_title ?? 'tuliprox', [services]);
- const translate = useTranslator();
- const appLogo = useMemo(() => {
- let logo = services.config().getUiConfig().app_logo;
- if (logo) {
- return ;
- } else {
- return getIconByName('Logo');
- }
- }, [services]);
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
-
- useEffect(() => {
- if (!serverConfigLoaded.current) {
- serverConfigLoaded.current = true;
- services.config().getServerConfig().pipe(first()).subscribe({
- next: (cfg: ServerConfig) => {
- setServerConfig(cfg);
- },
- error: (err) => {
- enqueueSnackbar(translate("MESSAGES.DOWNLOAD.SERVER_CONFIG.FAIL"), {variant: 'error'});
- },
- complete: noop,
- });
- }
- return noop
- }, [enqueueSnackbar, services, translate]);
-
- const handlePreferences = useCallback(() => {
- setPreferencesVisible((value:boolean) => !value);
- }, []);
-
- const handleLogout = useCallback(() => {
- services.auth().logout();
- }, [services]);
-
- const handleLanguage = useCallback((event: any) => {
- const language = event.target.value;
- LANGUAGE.next(language);
- }, []);
-
- return (
-
-
-
{appLogo} {appTitle}
-
{services.config().getUiConfig().languages.map(l => {l} )}
-
{getIconByName(preferencesVisible ? 'Live' : 'Config')}
-
{getIconByName('Logout')}
-
- {/*
*/}
- {/*
*/}
- {/* */}
- {/*
*/}
- {/*
*/}
-
-
-
-
- );
-}
diff --git a/webui/src/app/components/authentication.rs b/frontend/src/app/components/authentication.rs
similarity index 100%
rename from webui/src/app/components/authentication.rs
rename to frontend/src/app/components/authentication.rs
diff --git a/webui/src/app/components/breadcrumbs.rs b/frontend/src/app/components/breadcrumbs.rs
similarity index 100%
rename from webui/src/app/components/breadcrumbs.rs
rename to frontend/src/app/components/breadcrumbs.rs
diff --git a/webui/src/app/components/card.rs b/frontend/src/app/components/card.rs
similarity index 100%
rename from webui/src/app/components/card.rs
rename to frontend/src/app/components/card.rs
diff --git a/webui/src/app/components/chip.rs b/frontend/src/app/components/chip.rs
similarity index 100%
rename from webui/src/app/components/chip.rs
rename to frontend/src/app/components/chip.rs
diff --git a/webui/src/app/components/collapse_panel.rs b/frontend/src/app/components/collapse_panel.rs
similarity index 100%
rename from webui/src/app/components/collapse_panel.rs
rename to frontend/src/app/components/collapse_panel.rs
diff --git a/webui/src/app/components/confirm_dialog.rs b/frontend/src/app/components/confirm_dialog.rs
similarity index 100%
rename from webui/src/app/components/confirm_dialog.rs
rename to frontend/src/app/components/confirm_dialog.rs
diff --git a/webui/src/app/components/content_dialog.rs b/frontend/src/app/components/content_dialog.rs
similarity index 100%
rename from webui/src/app/components/content_dialog.rs
rename to frontend/src/app/components/content_dialog.rs
diff --git a/webui/src/app/components/custom_dialog.rs b/frontend/src/app/components/custom_dialog.rs
similarity index 100%
rename from webui/src/app/components/custom_dialog.rs
rename to frontend/src/app/components/custom_dialog.rs
diff --git a/webui/src/app/components/dashboard/action_card.rs b/frontend/src/app/components/dashboard/action_card.rs
similarity index 100%
rename from webui/src/app/components/dashboard/action_card.rs
rename to frontend/src/app/components/dashboard/action_card.rs
diff --git a/webui/src/app/components/dashboard/dashboard_view.rs b/frontend/src/app/components/dashboard/dashboard_view.rs
similarity index 100%
rename from webui/src/app/components/dashboard/dashboard_view.rs
rename to frontend/src/app/components/dashboard/dashboard_view.rs
diff --git a/webui/src/app/components/dashboard/discord_action_card.rs b/frontend/src/app/components/dashboard/discord_action_card.rs
similarity index 100%
rename from webui/src/app/components/dashboard/discord_action_card.rs
rename to frontend/src/app/components/dashboard/discord_action_card.rs
diff --git a/webui/src/app/components/dashboard/documentation_action_card.rs b/frontend/src/app/components/dashboard/documentation_action_card.rs
similarity index 100%
rename from webui/src/app/components/dashboard/documentation_action_card.rs
rename to frontend/src/app/components/dashboard/documentation_action_card.rs
diff --git a/webui/src/app/components/dashboard/github_action_card.rs b/frontend/src/app/components/dashboard/github_action_card.rs
similarity index 100%
rename from webui/src/app/components/dashboard/github_action_card.rs
rename to frontend/src/app/components/dashboard/github_action_card.rs
diff --git a/webui/src/app/components/dashboard/ipinfo_action_card.rs b/frontend/src/app/components/dashboard/ipinfo_action_card.rs
similarity index 100%
rename from webui/src/app/components/dashboard/ipinfo_action_card.rs
rename to frontend/src/app/components/dashboard/ipinfo_action_card.rs
diff --git a/webui/src/app/components/dashboard/mod.rs b/frontend/src/app/components/dashboard/mod.rs
similarity index 100%
rename from webui/src/app/components/dashboard/mod.rs
rename to frontend/src/app/components/dashboard/mod.rs
diff --git a/webui/src/app/components/dashboard/playlist_progress_status_card.rs b/frontend/src/app/components/dashboard/playlist_progress_status_card.rs
similarity index 100%
rename from webui/src/app/components/dashboard/playlist_progress_status_card.rs
rename to frontend/src/app/components/dashboard/playlist_progress_status_card.rs
diff --git a/webui/src/app/components/dashboard/stats_view.rs b/frontend/src/app/components/dashboard/stats_view.rs
similarity index 100%
rename from webui/src/app/components/dashboard/stats_view.rs
rename to frontend/src/app/components/dashboard/stats_view.rs
diff --git a/webui/src/app/components/dashboard/status_card.rs b/frontend/src/app/components/dashboard/status_card.rs
similarity index 100%
rename from webui/src/app/components/dashboard/status_card.rs
rename to frontend/src/app/components/dashboard/status_card.rs
diff --git a/webui/src/app/components/dashboard/user_action_card.rs b/frontend/src/app/components/dashboard/user_action_card.rs
similarity index 100%
rename from webui/src/app/components/dashboard/user_action_card.rs
rename to frontend/src/app/components/dashboard/user_action_card.rs
diff --git a/webui/src/app/components/dashboard/version_action_card.rs b/frontend/src/app/components/dashboard/version_action_card.rs
similarity index 100%
rename from webui/src/app/components/dashboard/version_action_card.rs
rename to frontend/src/app/components/dashboard/version_action_card.rs
diff --git a/webui/src/app/components/drop_down_icon_button.rs b/frontend/src/app/components/drop_down_icon_button.rs
similarity index 100%
rename from webui/src/app/components/drop_down_icon_button.rs
rename to frontend/src/app/components/drop_down_icon_button.rs
diff --git a/webui/src/app/components/hide_content.rs b/frontend/src/app/components/hide_content.rs
similarity index 100%
rename from webui/src/app/components/hide_content.rs
rename to frontend/src/app/components/hide_content.rs
diff --git a/webui/src/app/components/home.rs b/frontend/src/app/components/home.rs
similarity index 100%
rename from webui/src/app/components/home.rs
rename to frontend/src/app/components/home.rs
diff --git a/webui/src/app/components/icon_button.rs b/frontend/src/app/components/icon_button.rs
similarity index 100%
rename from webui/src/app/components/icon_button.rs
rename to frontend/src/app/components/icon_button.rs
diff --git a/webui/src/app/components/input.rs b/frontend/src/app/components/input.rs
similarity index 100%
rename from webui/src/app/components/input.rs
rename to frontend/src/app/components/input.rs
diff --git a/webui/src/app/components/loading_indicator.rs b/frontend/src/app/components/loading_indicator.rs
similarity index 100%
rename from webui/src/app/components/loading_indicator.rs
rename to frontend/src/app/components/loading_indicator.rs
diff --git a/webui/src/app/components/login.rs b/frontend/src/app/components/login.rs
similarity index 100%
rename from webui/src/app/components/login.rs
rename to frontend/src/app/components/login.rs
diff --git a/webui/src/app/components/menu_item.rs b/frontend/src/app/components/menu_item.rs
similarity index 100%
rename from webui/src/app/components/menu_item.rs
rename to frontend/src/app/components/menu_item.rs
diff --git a/webui/src/app/components/mod.rs b/frontend/src/app/components/mod.rs
similarity index 100%
rename from webui/src/app/components/mod.rs
rename to frontend/src/app/components/mod.rs
diff --git a/webui/src/app/components/no_content.rs b/frontend/src/app/components/no_content.rs
similarity index 100%
rename from webui/src/app/components/no_content.rs
rename to frontend/src/app/components/no_content.rs
diff --git a/webui/src/app/components/panel.rs b/frontend/src/app/components/panel.rs
similarity index 100%
rename from webui/src/app/components/panel.rs
rename to frontend/src/app/components/panel.rs
diff --git a/webui/src/app/components/playlist/assistant/mod.rs b/frontend/src/app/components/playlist/assistant/mod.rs
similarity index 100%
rename from webui/src/app/components/playlist/assistant/mod.rs
rename to frontend/src/app/components/playlist/assistant/mod.rs
diff --git a/webui/src/app/components/playlist/assistant/playlist.rs b/frontend/src/app/components/playlist/assistant/playlist.rs
similarity index 100%
rename from webui/src/app/components/playlist/assistant/playlist.rs
rename to frontend/src/app/components/playlist/assistant/playlist.rs
diff --git a/webui/src/app/components/playlist/assistant/playlist_assistant_context.rs b/frontend/src/app/components/playlist/assistant/playlist_assistant_context.rs
similarity index 100%
rename from webui/src/app/components/playlist/assistant/playlist_assistant_context.rs
rename to frontend/src/app/components/playlist/assistant/playlist_assistant_context.rs
diff --git a/webui/src/app/components/playlist/assistant/step_name.rs b/frontend/src/app/components/playlist/assistant/step_name.rs
similarity index 100%
rename from webui/src/app/components/playlist/assistant/step_name.rs
rename to frontend/src/app/components/playlist/assistant/step_name.rs
diff --git a/webui/src/app/components/playlist/assistant/step_schedule.rs b/frontend/src/app/components/playlist/assistant/step_schedule.rs
similarity index 100%
rename from webui/src/app/components/playlist/assistant/step_schedule.rs
rename to frontend/src/app/components/playlist/assistant/step_schedule.rs
diff --git a/webui/src/app/components/playlist/assistant/step_type.rs b/frontend/src/app/components/playlist/assistant/step_type.rs
similarity index 100%
rename from webui/src/app/components/playlist/assistant/step_type.rs
rename to frontend/src/app/components/playlist/assistant/step_type.rs
diff --git a/webui/src/app/components/playlist/create.rs b/frontend/src/app/components/playlist/create.rs
similarity index 100%
rename from webui/src/app/components/playlist/create.rs
rename to frontend/src/app/components/playlist/create.rs
diff --git a/webui/src/app/components/playlist/filter_view.rs b/frontend/src/app/components/playlist/filter_view.rs
similarity index 100%
rename from webui/src/app/components/playlist/filter_view.rs
rename to frontend/src/app/components/playlist/filter_view.rs
diff --git a/webui/src/app/components/playlist/input/input_headers.rs b/frontend/src/app/components/playlist/input/input_headers.rs
similarity index 100%
rename from webui/src/app/components/playlist/input/input_headers.rs
rename to frontend/src/app/components/playlist/input/input_headers.rs
diff --git a/webui/src/app/components/playlist/input/input_options.rs b/frontend/src/app/components/playlist/input/input_options.rs
similarity index 100%
rename from webui/src/app/components/playlist/input/input_options.rs
rename to frontend/src/app/components/playlist/input/input_options.rs
diff --git a/webui/src/app/components/playlist/input/input_type_view.rs b/frontend/src/app/components/playlist/input/input_type_view.rs
similarity index 100%
rename from webui/src/app/components/playlist/input/input_type_view.rs
rename to frontend/src/app/components/playlist/input/input_type_view.rs
diff --git a/webui/src/app/components/playlist/input/mod.rs b/frontend/src/app/components/playlist/input/mod.rs
similarity index 100%
rename from webui/src/app/components/playlist/input/mod.rs
rename to frontend/src/app/components/playlist/input/mod.rs
diff --git a/webui/src/app/components/playlist/input_table.rs b/frontend/src/app/components/playlist/input_table.rs
similarity index 100%
rename from webui/src/app/components/playlist/input_table.rs
rename to frontend/src/app/components/playlist/input_table.rs
diff --git a/webui/src/app/components/playlist/list.rs b/frontend/src/app/components/playlist/list.rs
similarity index 100%
rename from webui/src/app/components/playlist/list.rs
rename to frontend/src/app/components/playlist/list.rs
diff --git a/webui/src/app/components/playlist/mapper_script_view.rs b/frontend/src/app/components/playlist/mapper_script_view.rs
similarity index 86%
rename from webui/src/app/components/playlist/mapper_script_view.rs
rename to frontend/src/app/components/playlist/mapper_script_view.rs
index d8c7c07f5..2e221945a 100644
--- a/webui/src/app/components/playlist/mapper_script_view.rs
+++ b/frontend/src/app/components/playlist/mapper_script_view.rs
@@ -37,6 +37,17 @@ struct FormatParams {
pub p_count: usize,
}
+impl FormatParams {
+ fn inc_level(&mut self, inc: usize) -> &mut Self {
+ self.level += inc;
+ self
+ }
+ fn dec_level(&mut self, dec: usize) -> &mut Self {
+ self.level -= dec;
+ self
+ }
+}
+
// Indents with spaces for pretty printing
fn indent(level: usize, do_indent: bool) -> Html {
if do_indent {
@@ -47,7 +58,7 @@ fn indent(level: usize, do_indent: bool) -> Html {
}
}
-fn newline(format_params: &FormatParams) -> Html {
+fn newline(format_params: &mut FormatParams) -> Html {
if format_params.pretty {
html! { }
} else {
@@ -55,7 +66,7 @@ fn newline(format_params: &FormatParams) -> Html {
}
}
-fn render_args(args: &[ExprId], script: &MapperScript, format_params: &FormatParams) -> Html {
+fn render_args(args: &[ExprId], script: &MapperScript, format_params: &mut FormatParams) -> Html {
html! {
<>
{
@@ -87,7 +98,7 @@ fn render_map_key(key: &MapKey) -> Html {
}
}
-fn render_function_call(name: &BuiltInFunction, args: &[ExprId], script: &MapperScript, format_params: &FormatParams) -> Html {
+fn render_function_call(name: &BuiltInFunction, args: &[ExprId], script: &MapperScript, format_params: &mut FormatParams) -> Html {
html! {
{name.to_string()}{"("}{render_args(args, script, format_params)}{")"}
}
@@ -105,7 +116,7 @@ fn render_null_value() -> Html {
html! { { "null" } }
}
-fn render_map_case(case: &MapCase, script: &MapperScript, format_params: &FormatParams) -> Html {
+fn render_map_case(case: &MapCase, script: &MapperScript, format_params: &mut FormatParams) -> Html {
let keys_html = html! {
<>
{
@@ -131,6 +142,7 @@ fn render_map_case(case: &MapCase, script: &MapperScript, format_params: &Format
let has_bracket = case.keys.len() > 1;
html! {
<>
+ {indent(format_params.level, true)}
{if has_bracket {"("} else {""}}
{keys_html}
{if has_bracket {")"} else {""}}
@@ -141,7 +153,7 @@ fn render_map_case(case: &MapCase, script: &MapperScript, format_params: &Format
}
-fn render_map_cases(cases: &[MapCase], script: &MapperScript, format_params: &FormatParams) -> Html {
+fn render_map_cases(cases: &[MapCase], script: &MapperScript, format_params: &mut FormatParams) -> Html {
html!{
<>
{
@@ -151,22 +163,24 @@ fn render_map_cases(cases: &[MapCase], script: &MapperScript, format_params: &Fo
}
}
-fn render_map_block(map_key: &MapKey, cases: &[MapCase], script: &MapperScript, format_params: &FormatParams) -> Html {
+fn render_map_block(map_key: &MapKey, cases: &[MapCase], script: &MapperScript, format_params: &mut FormatParams) -> Html {
html! {
- <> {"map "}
+ <>
+ {indent(format_params.level, true)}
+ {"map "}
{render_map_key(map_key)}
{" {"}
{newline(format_params)}
- {render_map_cases(cases, script, format_params)}
- {newline(format_params)}
+ {render_map_cases(cases, script, format_params.inc_level(1))}
+ {newline(format_params.dec_level(1))}
+ {indent(format_params.level, true)}
{"}"}
- {newline(format_params)}
>
}
}
-fn render_block(expr_ids: &[ExprId], script: &MapperScript, format_params: &FormatParams) -> Html {
+fn render_block(expr_ids: &[ExprId], script: &MapperScript, format_params: &mut FormatParams) -> Html {
html! {
<>
{"{"}
@@ -188,7 +202,7 @@ fn render_block(expr_ids: &[ExprId], script: &MapperScript, format_params: &Form
}
}
-fn render_assignment(target: &AssignmentTarget, expr_id: &ExprId, script: &MapperScript, format_params: &FormatParams) -> Html {
+fn render_assignment(target: &AssignmentTarget, expr_id: &ExprId, script: &MapperScript, format_params: &mut FormatParams) -> Html {
let target_html = match target {
AssignmentTarget::Identifier(ident) => render_identifier(ident),
AssignmentTarget::Field(field) => render_field(field),
@@ -203,7 +217,7 @@ fn render_assignment(target: &AssignmentTarget, expr_id: &ExprId, script: &Mappe
}
}
-fn render_match_block(match_cases: &[MatchCase], script: &MapperScript, format_params: &FormatParams) -> Html {
+fn render_match_block(match_cases: &[MatchCase], script: &MapperScript, format_params: &mut FormatParams) -> Html {
html! {
<>
{"!! TODO MATCH BLOCK !!!"}
@@ -224,7 +238,7 @@ fn render_regexp(field: &RegexSource, pattern: &String, regex: &Regex) -> Html {
html! { <> {render_regex_source(field)} {"~ '"}{ pattern } {"'"} > }
}
-fn render_expression(expr_id: &ExprId, script: &MapperScript, format_params: &FormatParams) -> Html {
+fn render_expression(expr_id: &ExprId, script: &MapperScript, format_params: &mut FormatParams) -> Html {
script.get_expr_by_id(*expr_id.deref()).map(|expression| {
match expression {
Expression::Identifier(ident) => render_identifier(ident),
@@ -244,15 +258,15 @@ fn render_expression(expr_id: &ExprId, script: &MapperScript, format_params: &Fo
}
fn render_script(script: &MapperScript, pretty: bool, level: usize, do_indent: bool, p_count: usize) -> Html {
- let format_params = FormatParams {
+ let mut format_params = FormatParams {
pretty, level, do_indent, p_count
};
let items = script.statements.iter().map(|stmt| {
match stmt {
Statement::Expression(expr_id) => html! {
<>
- {render_expression(expr_id, script, &format_params)}
- {newline(&format_params)}
+ {render_expression(expr_id, script, &mut format_params)}
+ {newline(&mut format_params)}
>
},
Statement::Comment(comment) => html!{ { comment } },
diff --git a/webui/src/app/components/playlist/mappings.rs b/frontend/src/app/components/playlist/mappings.rs
similarity index 92%
rename from webui/src/app/components/playlist/mappings.rs
rename to frontend/src/app/components/playlist/mappings.rs
index 6c1d00628..7e134e104 100644
--- a/webui/src/app/components/playlist/mappings.rs
+++ b/frontend/src/app/components/playlist/mappings.rs
@@ -31,8 +31,7 @@ pub fn PlaylistMappings(props: &PlaylistMappingsProps) -> Html {
let render_mapper = |mapper: &MapperDto| {
html! {
-
-
{translate.t("LABEL.MAPPER")}
+
@@ -59,14 +58,14 @@ pub fn PlaylistMappings(props: &PlaylistMappingsProps) -> Html {
{translate.t("LABEL.MATCH_AS_ASCII")}
-
+
{
for mapping.mapper.iter().flatten().map(|mapper| {
html! { render_mapper(mapper) }
})
}
-
+
{
for mapping.counter.iter().flatten().map(|counter| {
html! { render_counter(counter) }
diff --git a/webui/src/app/components/playlist/mod.rs b/frontend/src/app/components/playlist/mod.rs
similarity index 100%
rename from webui/src/app/components/playlist/mod.rs
rename to frontend/src/app/components/playlist/mod.rs
diff --git a/webui/src/app/components/playlist/playlist_editor_page.rs b/frontend/src/app/components/playlist/playlist_editor_page.rs
similarity index 100%
rename from webui/src/app/components/playlist/playlist_editor_page.rs
rename to frontend/src/app/components/playlist/playlist_editor_page.rs
diff --git a/webui/src/app/components/playlist/playlist_editor_view.rs b/frontend/src/app/components/playlist/playlist_editor_view.rs
similarity index 100%
rename from webui/src/app/components/playlist/playlist_editor_view.rs
rename to frontend/src/app/components/playlist/playlist_editor_view.rs
diff --git a/webui/src/app/components/playlist/playlist_explorer.rs b/frontend/src/app/components/playlist/playlist_explorer.rs
similarity index 100%
rename from webui/src/app/components/playlist/playlist_explorer.rs
rename to frontend/src/app/components/playlist/playlist_explorer.rs
diff --git a/webui/src/app/components/playlist/playlist_explorer_page.rs b/frontend/src/app/components/playlist/playlist_explorer_page.rs
similarity index 100%
rename from webui/src/app/components/playlist/playlist_explorer_page.rs
rename to frontend/src/app/components/playlist/playlist_explorer_page.rs
diff --git a/webui/src/app/components/playlist/playlist_explorer_view.rs b/frontend/src/app/components/playlist/playlist_explorer_view.rs
similarity index 100%
rename from webui/src/app/components/playlist/playlist_explorer_view.rs
rename to frontend/src/app/components/playlist/playlist_explorer_view.rs
diff --git a/webui/src/app/components/playlist/playlist_update_view.rs b/frontend/src/app/components/playlist/playlist_update_view.rs
similarity index 100%
rename from webui/src/app/components/playlist/playlist_update_view.rs
rename to frontend/src/app/components/playlist/playlist_update_view.rs
diff --git a/webui/src/app/components/playlist/processing.rs b/frontend/src/app/components/playlist/processing.rs
similarity index 100%
rename from webui/src/app/components/playlist/processing.rs
rename to frontend/src/app/components/playlist/processing.rs
diff --git a/webui/src/app/components/playlist/source_selector.rs b/frontend/src/app/components/playlist/source_selector.rs
similarity index 100%
rename from webui/src/app/components/playlist/source_selector.rs
rename to frontend/src/app/components/playlist/source_selector.rs
diff --git a/webui/src/app/components/playlist/target/hdhomerun_output.rs b/frontend/src/app/components/playlist/target/hdhomerun_output.rs
similarity index 100%
rename from webui/src/app/components/playlist/target/hdhomerun_output.rs
rename to frontend/src/app/components/playlist/target/hdhomerun_output.rs
diff --git a/webui/src/app/components/playlist/target/m3u_output.rs b/frontend/src/app/components/playlist/target/m3u_output.rs
similarity index 100%
rename from webui/src/app/components/playlist/target/m3u_output.rs
rename to frontend/src/app/components/playlist/target/m3u_output.rs
diff --git a/webui/src/app/components/playlist/target/mod.rs b/frontend/src/app/components/playlist/target/mod.rs
similarity index 100%
rename from webui/src/app/components/playlist/target/mod.rs
rename to frontend/src/app/components/playlist/target/mod.rs
diff --git a/webui/src/app/components/playlist/target/strm_output.rs b/frontend/src/app/components/playlist/target/strm_output.rs
similarity index 100%
rename from webui/src/app/components/playlist/target/strm_output.rs
rename to frontend/src/app/components/playlist/target/strm_output.rs
diff --git a/webui/src/app/components/playlist/target/target_options.rs b/frontend/src/app/components/playlist/target/target_options.rs
similarity index 100%
rename from webui/src/app/components/playlist/target/target_options.rs
rename to frontend/src/app/components/playlist/target/target_options.rs
diff --git a/webui/src/app/components/playlist/target/target_output.rs b/frontend/src/app/components/playlist/target/target_output.rs
similarity index 100%
rename from webui/src/app/components/playlist/target/target_output.rs
rename to frontend/src/app/components/playlist/target/target_output.rs
diff --git a/webui/src/app/components/playlist/target/target_rename.rs b/frontend/src/app/components/playlist/target/target_rename.rs
similarity index 100%
rename from webui/src/app/components/playlist/target/target_rename.rs
rename to frontend/src/app/components/playlist/target/target_rename.rs
diff --git a/webui/src/app/components/playlist/target/target_sort.rs b/frontend/src/app/components/playlist/target/target_sort.rs
similarity index 100%
rename from webui/src/app/components/playlist/target/target_sort.rs
rename to frontend/src/app/components/playlist/target/target_sort.rs
diff --git a/webui/src/app/components/playlist/target/target_watch.rs b/frontend/src/app/components/playlist/target/target_watch.rs
similarity index 100%
rename from webui/src/app/components/playlist/target/target_watch.rs
rename to frontend/src/app/components/playlist/target/target_watch.rs
diff --git a/webui/src/app/components/playlist/target/xtream_output.rs b/frontend/src/app/components/playlist/target/xtream_output.rs
similarity index 100%
rename from webui/src/app/components/playlist/target/xtream_output.rs
rename to frontend/src/app/components/playlist/target/xtream_output.rs
diff --git a/webui/src/app/components/playlist/target_table.rs b/frontend/src/app/components/playlist/target_table.rs
similarity index 100%
rename from webui/src/app/components/playlist/target_table.rs
rename to frontend/src/app/components/playlist/target_table.rs
diff --git a/webui/src/app/components/popup_menu.rs b/frontend/src/app/components/popup_menu.rs
similarity index 100%
rename from webui/src/app/components/popup_menu.rs
rename to frontend/src/app/components/popup_menu.rs
diff --git a/webui/src/app/components/radio_button_group.rs b/frontend/src/app/components/radio_button_group.rs
similarity index 100%
rename from webui/src/app/components/radio_button_group.rs
rename to frontend/src/app/components/radio_button_group.rs
diff --git a/webui/src/app/components/reveal_content.rs b/frontend/src/app/components/reveal_content.rs
similarity index 100%
rename from webui/src/app/components/reveal_content.rs
rename to frontend/src/app/components/reveal_content.rs
diff --git a/webui/src/app/components/search.rs b/frontend/src/app/components/search.rs
similarity index 100%
rename from webui/src/app/components/search.rs
rename to frontend/src/app/components/search.rs
diff --git a/webui/src/app/components/sidebar.rs b/frontend/src/app/components/sidebar.rs
similarity index 100%
rename from webui/src/app/components/sidebar.rs
rename to frontend/src/app/components/sidebar.rs
diff --git a/webui/src/app/components/svg_icon.rs b/frontend/src/app/components/svg_icon.rs
similarity index 100%
rename from webui/src/app/components/svg_icon.rs
rename to frontend/src/app/components/svg_icon.rs
diff --git a/webui/src/app/components/table.rs b/frontend/src/app/components/table.rs
similarity index 100%
rename from webui/src/app/components/table.rs
rename to frontend/src/app/components/table.rs
diff --git a/webui/src/app/components/tag_list.rs b/frontend/src/app/components/tag_list.rs
similarity index 100%
rename from webui/src/app/components/tag_list.rs
rename to frontend/src/app/components/tag_list.rs
diff --git a/webui/src/app/components/text_button.rs b/frontend/src/app/components/text_button.rs
similarity index 100%
rename from webui/src/app/components/text_button.rs
rename to frontend/src/app/components/text_button.rs
diff --git a/webui/src/app/components/toastr.rs b/frontend/src/app/components/toastr.rs
similarity index 100%
rename from webui/src/app/components/toastr.rs
rename to frontend/src/app/components/toastr.rs
diff --git a/webui/src/app/components/toggle_switch.rs b/frontend/src/app/components/toggle_switch.rs
similarity index 100%
rename from webui/src/app/components/toggle_switch.rs
rename to frontend/src/app/components/toggle_switch.rs
diff --git a/webui/src/app/components/userlist/edit.rs b/frontend/src/app/components/userlist/edit.rs
similarity index 100%
rename from webui/src/app/components/userlist/edit.rs
rename to frontend/src/app/components/userlist/edit.rs
diff --git a/webui/src/app/components/userlist/list.rs b/frontend/src/app/components/userlist/list.rs
similarity index 100%
rename from webui/src/app/components/userlist/list.rs
rename to frontend/src/app/components/userlist/list.rs
diff --git a/webui/src/app/components/userlist/max_connections.rs b/frontend/src/app/components/userlist/max_connections.rs
similarity index 100%
rename from webui/src/app/components/userlist/max_connections.rs
rename to frontend/src/app/components/userlist/max_connections.rs
diff --git a/webui/src/app/components/userlist/mod.rs b/frontend/src/app/components/userlist/mod.rs
similarity index 100%
rename from webui/src/app/components/userlist/mod.rs
rename to frontend/src/app/components/userlist/mod.rs
diff --git a/webui/src/app/components/userlist/page.rs b/frontend/src/app/components/userlist/page.rs
similarity index 100%
rename from webui/src/app/components/userlist/page.rs
rename to frontend/src/app/components/userlist/page.rs
diff --git a/webui/src/app/components/userlist/proxy_type_view.rs b/frontend/src/app/components/userlist/proxy_type_view.rs
similarity index 100%
rename from webui/src/app/components/userlist/proxy_type_view.rs
rename to frontend/src/app/components/userlist/proxy_type_view.rs
diff --git a/webui/src/app/components/userlist/user_status.rs b/frontend/src/app/components/userlist/user_status.rs
similarity index 100%
rename from webui/src/app/components/userlist/user_status.rs
rename to frontend/src/app/components/userlist/user_status.rs
diff --git a/webui/src/app/components/userlist/user_table.rs b/frontend/src/app/components/userlist/user_table.rs
similarity index 100%
rename from webui/src/app/components/userlist/user_table.rs
rename to frontend/src/app/components/userlist/user_table.rs
diff --git a/webui/src/app/components/userlist/userlist_view.rs b/frontend/src/app/components/userlist/userlist_view.rs
similarity index 100%
rename from webui/src/app/components/userlist/userlist_view.rs
rename to frontend/src/app/components/userlist/userlist_view.rs
diff --git a/webui/src/app/context.rs b/frontend/src/app/context.rs
similarity index 100%
rename from webui/src/app/context.rs
rename to frontend/src/app/context.rs
diff --git a/webui/src/app/mod.rs b/frontend/src/app/mod.rs
similarity index 100%
rename from webui/src/app/mod.rs
rename to frontend/src/app/mod.rs
diff --git a/frontend/src/component/api-proxy-view/api-proxy-view.scss b/frontend/src/component/api-proxy-view/api-proxy-view.scss
deleted file mode 100644
index e7d8f9255..000000000
--- a/frontend/src/component/api-proxy-view/api-proxy-view.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-@use '../../scss/common';
-@use '../../scss/preferences';
-
-
-.api-proxy {
-
- @include preferences.prefsRoot();
- @include preferences.prefsToolbar();
- @include preferences.prefsForm();
-
- &__content {
- display: flex;
- flex-flow: column;
- gap: 8px;
- overflow: auto;
- &-area {
- display: flex;
- flex-flow: row wrap;
- gap: 12px;
- padding: 12px;
- }
- }
-
- &__content-help {
- display: flex;
- flex-flow: row;
- justify-content: center;
- gap: 4px;
- align-items: center;
- &-warn-icon {
- color: var(--warn-color);
- }
- }
-
-}
-
diff --git a/frontend/src/component/api-proxy-view/api-proxy-view.tsx b/frontend/src/component/api-proxy-view/api-proxy-view.tsx
deleted file mode 100644
index 8eb9cf0fe..000000000
--- a/frontend/src/component/api-proxy-view/api-proxy-view.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import React, {useCallback, useMemo} from "react";
-import './api-proxy-view.scss';
-import ServerConfig, {ApiProxyServerInfo} from "../../model/server-config";
-import {useSnackbar} from "notistack";
-import {useServices} from "../../provider/service-provider";
-import FormView, {FormFieldType} from "../form-view/from-view";
-import {getIconByName} from "../../icons/icons";
-import useTranslator from "../../hook/use-translator";
-
-const isNumber = (value: string): boolean => {
- return !isNaN(value as any);
-}
-
-const SERVER_INFO_FIELDS = [
- {name: 'name', label: 'LABEL.NAME', fieldType: FormFieldType.READONLY},
- {name: 'protocol', label: 'LABEL.PROTOCOL', fieldType: FormFieldType.SINGLE_SELECT,
- options:[{value: 'http', label:'http'}, {value: 'https', label:'https'}]},
- {name: 'host', label: 'LABEL.HOST', fieldType: FormFieldType.TEXT},
- {name: 'port', label: 'LABEL.PORT', fieldType: FormFieldType.NUMBER, validator: isNumber},
- {name: 'timezone', label: 'LABEL.TIMEZONE', fieldType: FormFieldType.TEXT},
- {name: 'message', label: 'LABEL.MESSAGE', fieldType: FormFieldType.TEXT},
- {name: 'path', label: 'LABEL.PATH', hint: 'HINT.CONFIG.API_PROXY.PATH', fieldType: FormFieldType.TEXT},
-];
-
-interface ApiProxyViewProps {
- config: ServerConfig;
-}
-
-export default function ApiProxyView(props: ApiProxyViewProps) {
- const {config} = props;
- const services = useServices();
- const translate = useTranslator();
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
- const serverInfo = useMemo
(() => config?.api_proxy?.server, [config]);
-
- const handleSave = useCallback(() => {
- if (serverInfo) {
- // @TODO validations
- services.config().saveApiProxyConfig(serverInfo).subscribe({
- next: () => enqueueSnackbar("Api proxy config saved!", {variant: 'success'}),
- error: (err) => enqueueSnackbar("Failed to save api proxy config!", {variant: 'error'})
- });
- }
- }, [services, serverInfo, enqueueSnackbar]);
-
- return
-
{translate('LABEL.API_PROXY')}
- {translate('LABEL.SAVE')}
-
-
-
- {serverInfo?.map((server, idx) =>
-
-
)
- }
-
-
-
- {getIconByName('Warn')}
- {translate('INFO.RESTART_TO_APPLY_CHANGES')}
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/authentication/authentication.tsx b/frontend/src/component/authentication/authentication.tsx
deleted file mode 100644
index 65b32096c..000000000
--- a/frontend/src/component/authentication/authentication.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import React, {JSX, useEffect, useState} from 'react';
-import App from "../../app/app";
-import Login from "../login/login";
-import {useServices} from "../../provider/service-provider";
-import {first} from "rxjs/operators";
-import {noop, tap} from "rxjs";
-import {UserRole} from "../../service/auth-service";
-import AppUser from "../../app-user/app-user";
-
-export default function Authentication(): JSX.Element {
-
- const services = useServices();
- const [loading, setLoading] = useState(true);
- const [authenticated, setAuthenticated] = useState(UserRole.NONE);
-
- useEffect(() => {
- const sub = services.auth().authChannel().subscribe({
- next: (auth) => {
- setLoading(false);
- setAuthenticated(auth);
- },
- error: () => setAuthenticated(UserRole.NONE),
- })
-
- const noAuthCheck = () => services.auth().authenticate('test', 'test')
- .pipe(tap(() => setLoading(false)), first()).subscribe(noop);
-
- services.auth().refresh().pipe(first()).subscribe({
- next: (authenticated) =>!authenticated && noAuthCheck(),
- error: () => noAuthCheck(),
- });
-
- return () => sub.unsubscribe();
- }, [services]);
-
- if (loading) {
- return <>>
- }
-
- if (authenticated === UserRole.ADMIN) {
- return ;
- }
-
- if (authenticated === UserRole.USER) {
- return ;
- }
-
- return
-}
\ No newline at end of file
diff --git a/frontend/src/component/checkbox/checkbox.scss b/frontend/src/component/checkbox/checkbox.scss
deleted file mode 100644
index 99c71565e..000000000
--- a/frontend/src/component/checkbox/checkbox.scss
+++ /dev/null
@@ -1,60 +0,0 @@
-$checkbox-size:18px;
-.checkbox-container {
- display: block;
- position: relative;
- padding-left: 24px;
- cursor: pointer;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
-
- input {
- position: relative;
- opacity: 0;
- cursor: pointer;
- height: 0;
- width: 0;
- }
-
- .checkmark {
- position: absolute;
- top: 0;
- left: 0;
- height: $checkbox-size;
- width: $checkbox-size;
- background-color: var(--checkbox-background-color);
- border-radius: var(--checkbox-border-radius);
-
- &:after {
- content: "";
- position: absolute;
- display: none;
- }
- }
-
- &:hover input ~ .checkmark {
- background-color: var(--checkbox-hover-background-color);
- }
-
- input:checked ~ .checkmark {
- background-color: var(--checkbox-checked-background-color);
- }
-
- input:checked ~ .checkmark:after {
- display: block;
- }
-
- .checkmark:after {
- left: 5px;
- top: 1px;
- width: 5px;
- height: 10px;
- border: solid white;
- border-width: 0 3px 3px 0;
- -webkit-transform: rotate(45deg);
- -ms-transform: rotate(45deg);
- transform: rotate(45deg);
- }
-
-}
diff --git a/frontend/src/component/checkbox/checkbox.tsx b/frontend/src/component/checkbox/checkbox.tsx
deleted file mode 100644
index 43c7e541d..000000000
--- a/frontend/src/component/checkbox/checkbox.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React, {useCallback, useLayoutEffect, useRef} from "react";
-import './checkbox.scss';
-
-interface CheckboxProps {
- label?: string;
- value: any;
- checked: boolean;
- onSelect: (value: any, checked: boolean, evt?: any) => void;
-}
-
-export default function Checkbox(props: CheckboxProps) {
-
- const {label, value, onSelect, checked} = props;
- const inputRef = useRef(undefined);
-
- const handleSelect = useCallback((evt: any) => {
- evt.preventDefault();
- evt.stopPropagation();
- inputRef.current.checked = !inputRef.current.checked;
- onSelect?.(value, inputRef.current.checked, evt);
- }, [value, onSelect]);
-
- useLayoutEffect(() => {
- if (inputRef.current) {
- inputRef.current.checked = checked;
- }
- }, [checked]);
-
- if (label) {
- return
- {label}
-
-
- ;
- } else {
- return
-
-
-
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/clipboard-viewer/clipboard-viewer.scss b/frontend/src/component/clipboard-viewer/clipboard-viewer.scss
deleted file mode 100644
index dfd3328ba..000000000
--- a/frontend/src/component/clipboard-viewer/clipboard-viewer.scss
+++ /dev/null
@@ -1,43 +0,0 @@
-@use '../../scss/theme';
-@use '../../scss/common';
-
-$viewer-height: 200px;
-
-.clipboard-viewer {
- display: flex;
- flex: 1 1 auto;
- flex-flow: column;
-
- .clipboard-viewer-toolbar {
- display: flex;
- justify-content: flex-end;
- flex-flow: row nowrap;
- color: var(--text-color);
- background-color: var(--toolbar-background-color);
- padding: 8px 0 8px 0;
-
- .toolbar-btn {
- cursor: pointer;
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- background-color: transparent;
- color: var(--toolbar-button-hover-color);
- margin-right: 8px;
- &:hover {
- background-color: var(--toolbar-button-hover-background-color);
- }
- }
- }
-
- .clipboard-viewer-content {
- flex-grow: 1;
- max-height: $viewer-height;
- color: var(--text-color);
- overflow: auto;
- white-space: nowrap;
- padding: 8px;
-
- @include common.list();
-
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/clipboard-viewer/clipboard-viewer.tsx b/frontend/src/component/clipboard-viewer/clipboard-viewer.tsx
deleted file mode 100644
index 3dd1164c2..000000000
--- a/frontend/src/component/clipboard-viewer/clipboard-viewer.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React, {JSX, useCallback, useEffect, useState} from 'react';
-import './clipboard-viewer.scss';
-import {noop, Observable} from "rxjs";
-import copyToClipboard from "../../utils/clipboard";
-import {first} from "rxjs/operators";
-import {useSnackbar} from "notistack";
-import {getIconByName} from "../../icons/icons";
-
-interface ClipboardViewerProps {
- channel: Observable;
-}
-
-export default function ClipboardViewer(props: ClipboardViewerProps): JSX.Element {
-
- const {channel} = props;
- const [data, setData] = useState([]);
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
-
- useEffect(() => {
- const sub = channel.subscribe({
- next: (value: string) => setData(d => [...d, value]),
- });
- return () => sub.unsubscribe();
- }, [channel]);
-
- const handleClear = useCallback(() => {
- setData([]);
- }, []);
-
- const handleCopyToClipboard = useCallback(() => {
- if (data?.length) {
- copyToClipboard(data.join('\n')).pipe(first()).subscribe({
- next: value => enqueueSnackbar(value ? "Copied to clipboard" : "Copy to clipboard failed!",
- {variant: value ? 'success' : 'error'}),
- error: err => enqueueSnackbar("Copy to clipboard failed!", {variant: 'error'}),
- complete: noop,
- });
- }
- }, [data, enqueueSnackbar]);
-
- return
-
- {getIconByName('DeleteSweep')}
- {getIconByName('ContentCopy')}
-
-
-
- {data.map((t, i) => {t} )}
-
-
-
;
-}
\ No newline at end of file
diff --git a/frontend/src/component/date-picker/date-picker.scss b/frontend/src/component/date-picker/date-picker.scss
deleted file mode 100644
index 14f21c40c..000000000
--- a/frontend/src/component/date-picker/date-picker.scss
+++ /dev/null
@@ -1,75 +0,0 @@
-@use '../../scss/common';
-@import "react-datepicker/dist/react-datepicker.css";
-
-.date-picker-container {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
- gap: 4px;
-
- &__toolbar {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
- gap: 4px;
- button {
- @include common.iconButton();
- }
- svg {
- fill: var(--icon-button-color);
- }
- }
-}
-
-.react-datepicker-wrapper {
- box-sizing: border-box;
- overflow: hidden;
-}
-
-.react-datepicker__header {
- h2 {
- display: none;
- }
-}
-
-.react-datepicker__navigation--years-upcoming {
- width: 0;
- height: 0;
- border-left: 6px solid transparent;
- border-right: 6px solid transparent;
- border-bottom: 12px solid var(--border-color);
- transform: translateY(4px);
-}
-.react-datepicker__navigation--years-previous {
- width: 0;
- height: 0;
- border-left: 6px solid transparent;
- border-right: 6px solid transparent;
- border-top: 12px solid var(--border-color);
- transform: translateY(-4px);
-}
-
-.react-datepicker__input-container {
- display: flex;
- flex-flow: row-reverse nowrap;
- padding: 0 4px;
- align-items: center;
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- user-select: none;
- cursor: pointer;
- box-sizing: border-box;
-
- input {
- user-select: none;
- pointer-events: none;
- border: none !important;
- caret: transparent;
- &:focus {
- background-color: transparent !important;
- }
- }
- svg {
- fill: var(--icon-button-color);
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/date-picker/date-picker.tsx b/frontend/src/component/date-picker/date-picker.tsx
deleted file mode 100644
index 176ad5932..000000000
--- a/frontend/src/component/date-picker/date-picker.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import React, {useCallback, useEffect} from "react";
-import "./date-picker.scss";
-import ReactDatePicker from "react-datepicker";
-import {getIconByName} from "../../icons/icons";
-
-
-interface DatePickerProps {
- name: string;
- value: any;
- onChange: (name: string, values: string[]) => void;
-}
-
-export default function DatePicker(props: DatePickerProps) {
- const {name, value, onChange} = props;
- const [selected, setSelected] = React.useState(value);
- const datePickerRef = React.useRef(null);
-
- useEffect(() => {
- setSelected(value);
- }, [value]);
-
- const handleDateChange = useCallback((date: any) => {
- setSelected(date);
- datePickerRef.current.setOpen(false);
- onChange(name, date);
- }, [name, onChange]);
-
- const handleClick = useCallback(() => {
- if (!datePickerRef.current.isCalendarOpen()) {
- datePickerRef.current.setOpen(true);
- }
- }, []);
-
- const handleInfiniteDate = useCallback(() => {
- handleDateChange(new Date(0));
- }, [handleDateChange]);
-
- const handleNowDate = useCallback(() => {
- const date = new Date();
- date.setFullYear(date.getFullYear() + 1);
- handleDateChange(date);
- }, [handleDateChange]);
-
- return (
-
-
-
-
-
-
- {getIconByName('Unlimited')}
- {getIconByName('Today')}
-
-
- );
-}
diff --git a/frontend/src/component/file-download/file-download.scss b/frontend/src/component/file-download/file-download.scss
deleted file mode 100644
index 3de12c389..000000000
--- a/frontend/src/component/file-download/file-download.scss
+++ /dev/null
@@ -1,76 +0,0 @@
-@use '../../scss/common';
-
-.file-download {
- display: flex;
- flex-flow: column;
- flex: 1 0 auto;
- margin: 4px 10px;
- overflow: hidden;
- max-height: 12rem;
-
- .download-info {
- display: flex;
- flex-flow: column;
- flex: 1 1 auto;
- background-color: var(--popup-background-color);
- color: var(--popup-color);
- filter: drop-shadow(2px 2px 3px var(--popup-shadow-color));
- border: 1px solid var(--popup-border-color);
- overflow: hidden;
-
- &__toolbar {
- flex: 0 0 auto;
- padding: 4px;
- background-color: var(--background-color);
- button {
- @include common.iconButton();
- }
- }
-
- &__content {
- overflow: auto;
- }
-
- @include common.list();
-
- ul li {
- display: flex;
- flex-flow: row;
- gap: 4px;
- align-items: flex-start;
- }
-
- &__content {
- display: flex;
- flex-flow: row wrap;
- gap: 4px;
- }
-
- &__filesize {
- white-space: nowrap;
- }
-
- &__error {
- margin-left: 8px;
- color: var(--attention-color);
- }
-
- &__remove_btn {
- @include common.iconButton();
- max-height: 0.8rem;
- padding: 0 4px;
- svg {
- transform: scale(0.7);
- }
- }
-
- &__status {
- max-height: 0.8rem;
- padding: 0 4px;
- svg {
- transform: scale(0.7);
- }
- }
- }
-}
-
diff --git a/frontend/src/component/file-download/file-download.tsx b/frontend/src/component/file-download/file-download.tsx
deleted file mode 100644
index 94456cec1..000000000
--- a/frontend/src/component/file-download/file-download.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import React, {useCallback, useEffect, useRef, useState} from "react";
-import './file-download.scss';
-import {Subscription} from "rxjs";
-import {DownloadInfo, FileDownloadInfo} from "../../model/file-download";
-import {useServices} from "../../provider/service-provider";
-import {useSnackbar} from "notistack";
-import {getIconByName} from "../../icons/icons";
-
-interface FileDownloadProps {
-}
-
-export default function FileDownload(props: FileDownloadProps) {
- const services = useServices();
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
- const [downloads, setDownloads] = useState>({})
- const downloading = useRef(false);
-
- const startPoll = useCallback(() => {
- if (!downloading.current) {
- downloading.current = true;
- let subs: Subscription = services.file().getDownloadInfo().subscribe({
- next: (info: DownloadInfo) => {
- if (info.completed === true) {
- downloading.current = false;
- }
- const new_downloads = info.downloads || [];
- if (info.active) {
- new_downloads.push(info.active);
- }
- if (new_downloads.length) {
- setDownloads(downloads => {
- new_downloads.forEach(d => downloads[d.uuid] = d);
- return {...downloads};
- });
- }
- },
- error: (err) => {
- enqueueSnackbar("Download file failed!", {variant: 'error'});
- downloading.current = false;
- },
- complete: () => {
- subs.unsubscribe();
- downloading.current = false;
- }
- });
- }
- }, [enqueueSnackbar, services]);
-
- const handleDeleteClick = useCallback((event: any) => {
- const key = event.target.dataset.uuid;
- setDownloads(downloads => {
- delete downloads[key];
- return {...downloads};
- });
- }, []);
-
- const handleClearAll = useCallback(() => {
- setDownloads({});
- }, []);
-
- useEffect(() => {
- const sub = services.file().subscribeDownloadNotification((info: FileDownloadInfo) => {
- setDownloads((downloads: any) => {
- if (!downloads[info.uuid]) {
- info.ts_created = Date.now();
- info.ts_modified = info.ts_created;
- downloads[info.uuid] = info;
- return {...downloads};
- }
- return downloads;
- });
- startPoll();
- });
- startPoll();
- return () => sub.unsubscribe();
- }, [services, startPoll])
-
- const renderDownloads = useCallback((): React.ReactNode => {
- const info_list = Object.keys(downloads).map(key => downloads[key]);
- if (info_list.length) {
- info_list.sort((a, b) => {
- const ats = a.finished ? a.ts_created : a.ts_modified;
- const bts = b.finished ? b.ts_created : b.ts_modified;
- return bts - ats;
- })
- return
-
-
- {getIconByName('DeleteSweep')}
-
-
-
- {info_list.map(download => {
- return
- {getIconByName('Delete')}
-
- {getIconByName(download.error ? 'Error' : (download.finished ? 'CheckMark' : 'Hourglass'))}
- {download.filename}:
- {download.filesize ? (download.filesize / 1_048_576).toFixed(2) : 0} MB
-
-
- {download.error && {download.error} }
- ;
- })
- }
-
-
-
-
;
- }
- return <>>
- }, [downloads, handleDeleteClick, handleClearAll]);
-
- return renderDownloads();
-}
\ No newline at end of file
diff --git a/frontend/src/component/form-view/form-view.scss b/frontend/src/component/form-view/form-view.scss
deleted file mode 100644
index 4e695e6d9..000000000
--- a/frontend/src/component/form-view/form-view.scss
+++ /dev/null
@@ -1,88 +0,0 @@
-@use '../../scss/common';
-@use '../../scss/preferences';
-
-.form-view {
- @include common.container_vert_no_padding();
-
- .label-hint {
- cursor: pointer;
- svg {
- fill: var(--text-color);
- height: 0.7em;
- }
- }
-
- &__map-editor {
- @include common.container_horz_nowrap();
- flex: 1 1 auto;
- overflow: hidden;
- padding:0;
- }
-
- &__table {
- @include common.container_vert_no_padding();
- }
-
- &__row {
- flex-flow: row nowrap;
- @include common.container_common(flex-start, 0);
- @media (max-width: common.$media-size-mobile) {
- flex-flow: column;
- &:nth-child(even) {
- background-color: var(--card-background-color-darker);
- border-radius: var(--border-radius);
- }
- }
- }
-
- &__col {
- display: flex;
- flex-flow: row;
- padding: 6px;
-
- &-label {
- display: flex;
- min-width: 10em;
- width: 10em;
- justify-content: flex-start;
- align-items: center;
- @media (max-width: common.$media-size-mobile) {
- justify-content: flex-start;
- }
- }
-
- &-value {
- display: flex;
- flex: 1 1 auto;
- align-items: center;
- overflow: hidden;
- gap: 4px;
-
- button {
- border: 1px solid var(--text-button-border-color);
- font-size: 1.2rem;
- height: 100%;
- border-radius: var(--text-button-border-radius);
- background-color: var(--text-button-background-color);
- color: var(--text-button-color);
- }
- }
-
- label {
- font-weight: bold;
- color: var(--label-color);
-
- &:after {
- content: ':'
- }
- }
- //
- //input {
- // @include preferences.prefsInput();
- // flex: 1 1 0;
- // min-width: 0;
- // width: 100%;
- //}
- }
-}
-
diff --git a/frontend/src/component/form-view/from-view.tsx b/frontend/src/component/form-view/from-view.tsx
deleted file mode 100644
index 6402f57aa..000000000
--- a/frontend/src/component/form-view/from-view.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import React, {useCallback, useEffect, useMemo, useRef, useState} from "react";
-import './form-view.scss';
-import Checkbox from "../checkbox/checkbox";
-import TagSelect from "../tag-select/tags-select";
-import MapEditor from "../map-editor/map-editor";
-import TagInput from "../tag-input/tag-input";
-import ScheduleEditor from "../schedule-editor/schedule-editor";
-import DatePicker from "../date-picker/date-picker";
-import {genUuid} from "../../utils/uuid";
-import {getIconByName} from "../../icons/icons";
-import useTranslator from "../../hook/use-translator";
-import ProxySelect from "../proxy-select/proxy-select";
-// export const isNumber = (value: string): boolean => {
-// return !isNaN(value as any);
-// }
-
-export enum FormFieldType {
- READONLY= 'readonly',
- TEXT = 'text',
- NUMBER = 'number',
- MULTI_SELECT = 'multi_select',
- SINGLE_SELECT = 'single_select',
- CHECK = 'checkbox',
- TAGS = 'tags',
- MAP = 'map',
- SCHEDULE = 'schedule',
- DATE = 'date',
- PROXY_TYPE = 'proxy',
-}
-
-export type FormField = {
- name: string,
- label: string,
- hint?: string,
- validator?: (value: any) => boolean,
- options?: { value: string, label: string }[],
- fieldType: FormFieldType
- action?: {icon: string, handler: () => any}
-};
-
-interface FormViewProps {
- data: any;
- fields: FormField[]
-}
-
-export default function FormView(props: FormViewProps) {
- const {data, fields} = props;
- const uuid = useMemo(() => genUuid(), []);
- const translate = useTranslator();
- const inputIds= useRef([]);
- const [formData, setFormData] = useState(data || {});
-
- useEffect(() => {
- if (data) {
- setFormData(data);
- inputIds.current.forEach(([id, fieldName])=> {
- let elem: any = document.getElementById(id);
- if (elem) {
- const field = elem.dataset.field;
- if (field) {
- elem.value = data[field] ?? '';
- }
- }
- });
- }
- }, [data])
-
- const handleFieldAction = useCallback((evt: any) => {
- const field = evt.target.dataset.field;
- for (const fieldDef of fields) {
- if (fieldDef.name === field) {
- const value = fieldDef.action.handler();
- if (data) {
- data[field] = value;
- }
- setFormData((prevData: any) => ({...prevData, [field]: value}));
- for (const [id, fieldName] of inputIds.current) {
- if (fieldName === field) {
- let elem: any = document.getElementById(id);
- if (elem) {
- elem.value = value ?? '';
- }
- break;
- }
- }
- return;
- }
- }
- }, [data, fields]);
-
- const handleInputValueChange = useCallback((evt: any) => {
- const field = evt.target.dataset.field;
- let value = evt.target.value;
- for (const fieldDef of fields) {
- if (fieldDef.name === field) {
- if (fieldDef.fieldType === FormFieldType.NUMBER) {
- if (value == null || value.trim() == '') {
- value = undefined;
- } else {
- value = parseInt(value);
- }
- }
- }
- }
-
- if (data) {
- data[field] = value;
- }
- setFormData((prevData: any) => ({...prevData, [field]: value}));
- }, [data, fields]);
-
- const handleChange = useCallback((field: string, value: any) => {
- if (data) {
- data[field] = value;
- }
- setTimeout(()=> {
- setFormData((prevData: any) => ({...prevData, [field]: value}));
- }, 0);
- }, [data]);
-
- const getFieldInput = useCallback((field: FormField) => {
- switch (field.fieldType) {
- case FormFieldType.READONLY:
- return {formData?.[field.name]} ;
- case FormFieldType.CHECK:
- return
- case FormFieldType.MULTI_SELECT:
- return
- case FormFieldType.SINGLE_SELECT:
- return
- case FormFieldType.PROXY_TYPE: {
- return
- }
- case FormFieldType.MAP:
- return
- case FormFieldType.TAGS:
- return
- case FormFieldType.SCHEDULE:
- return
- case FormFieldType.DATE:
- return
- case FormFieldType.NUMBER:
- case FormFieldType.TEXT:
- default: {
- const input_id = uuid + field.name;
- inputIds.current.push([input_id, field.name]);
- return ;
- }
- }
- }, [uuid, formData, data, inputIds, handleChange, handleInputValueChange]);
-
- return
-
- {
- fields.map(field =>
-
-
- {translate(field.label)}
- {field.hint && {getIconByName('Help')} }
-
-
- {getFieldInput(field)}
- {field.action ? {getIconByName(field.action.icon)} : null}
-
-
- )
- }
-
-
-}
diff --git a/frontend/src/component/input-field/input-field.scss b/frontend/src/component/input-field/input-field.scss
deleted file mode 100644
index 8900d7beb..000000000
--- a/frontend/src/component/input-field/input-field.scss
+++ /dev/null
@@ -1,68 +0,0 @@
-@use "../../scss/common";
-
-.input-field {
- display: flex;
- position: relative;
- flex-flow: row nowrap;
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- font-size: inherit;
- min-width: 0;
- max-width: 100%;
- width: auto;
- box-sizing: border-box;
- background-color: inherit;
-
- &__content {
- display: flex;
- flex-flow: row nowrap;
- flex: 1 1 0;
- overflow: hidden;
- border-radius: var(--border-radius);
- font-size: inherit;
- user-select: none;
- box-sizing: border-box;
- min-width: 0;
- }
-
- &:focus-within {
- border: 1px solid var(--hover-border-color);
- }
-
- label {
- position: absolute;
- color: var(--label-color);
- transform: translate(6px, -0.8rem);
- background-color: inherit;
- border-radius: var(--border-radius);
- padding: 0 6px;
- font-size: 1.1rem !important;
- }
-
- //input {
- // color: var(--text-color);
- // border: none !important;
- // flex: 1 1 0;
- // height: 100%;
- // width: 100%;
- // min-width: 0;
- // font-size: 1.1rem !important;
- // outline: none !important;
- // padding: 0 4px 0 12px;
- // box-sizing: border-box;
- //}
-
- button {
- @include common.iconButton();
- border: none !important;
- border-radius: 0 !important;
- flex-shrink: 0;
- &:last-child {
- border-bottom-right-radius: var(--border-radius);
- border-top-right-radius: var(--border-radius);
- }
- &:not(:last-child) {
- border-right: 1px solid var(--card-background-color) !important;
- }
- }
-}
diff --git a/frontend/src/component/input-field/input-field.tsx b/frontend/src/component/input-field/input-field.tsx
deleted file mode 100644
index 9ae365b82..000000000
--- a/frontend/src/component/input-field/input-field.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React, {ReactNode} from "react";
-import './input-field.scss';
-
-interface InputFieldProps {
- label: string;
- children: ReactNode;
-}
-
-export default function InputField(props: InputFieldProps) {
- const {label, children} = props;
- return
-
{label}
-
- {children}
-
-
;
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/loading-indicator/loading-indicator.scss b/frontend/src/component/loading-indicator/loading-indicator.scss
deleted file mode 100644
index 301d60f5c..000000000
--- a/frontend/src/component/loading-indicator/loading-indicator.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-.loading-bar-placeholder, .loading-bar-container {
- width: 100%; // Full width of the container
- height: 5px; // Thin height for the container
- min-height: 5px;
- max-height: 5px;
-}
-
-// Container for the loading bar
-.loading-bar-container {
- background-color: var(--spinner-color); // Light gray background for the container
- border-radius: 10px; // Optional: rounded corners for the container
- overflow: hidden; // Prevents overflow content from showing outside the container
-}
-
-// The actual loading bar
-.loading-bar {
- height: 100%; // Full height of the loading bar (matches the container's height)
- width: 100%; // Start the width of the loading bar at 100%
- background-color: var(--spinner-highlight-color);
- position: relative;
- animation: loading 2s infinite ease-in-out; // Animation runs infinitely with easing
- transform-origin: center center; // The animation will scale from the center of the element
-}
-
-// The keyframes for the loading animation
-@keyframes loading {
- 0% {
- transform: scaleX(0); // At the start, the bar is not visible (scale from the center)
- }
- 50% {
- transform: scaleX(1); // The bar reaches full width, expanding to the edges
- }
- 100% {
- transform: scaleX(0); // The bar contracts back to the center
- }
-}
-
diff --git a/frontend/src/component/loading-indicator/loading-indicator.tsx b/frontend/src/component/loading-indicator/loading-indicator.tsx
deleted file mode 100644
index f082d2abc..000000000
--- a/frontend/src/component/loading-indicator/loading-indicator.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React, {ReactNode} from "react";
-import './loading-indicator.scss';
-
-interface LoadingIndicatorProps {
- loading: boolean;
-}
-
-export default function LoadingIndicator(props: LoadingIndicatorProps): ReactNode {
- if (!props.loading) {
- return
;
- }
- return ;
-}
\ No newline at end of file
diff --git a/frontend/src/component/login/login.scss b/frontend/src/component/login/login.scss
deleted file mode 100644
index a13d81621..000000000
--- a/frontend/src/component/login/login.scss
+++ /dev/null
@@ -1,78 +0,0 @@
-@use '../../scss/theme';
-@use '../../scss/common';
-
-.login-view {
- display: flex;
- flex-flow: column;
- gap: 8px;
- margin: 10% auto auto auto;
- border: 1px solid var(--border-color);
- border-top-left-radius: 6px;
- border-top-right-radius: 6px;
- overflow: hidden;
- position: relative;
- z-index: 1;
-
- &__header {
- display: flex;
- flex-flow: row;
- align-items: center;
- background-color: var(--card-background-color);
- padding: 0 8px;
- &-title {
- color: var(--card-color);
- padding: 8px;
- font-size: 1.5rem;
- }
- &-logo {
- overflow: hidden;
- img, svg {
- height: 1.5rem;
- width: 1.5rem;
- }
- }
-
- }
-
- &__logo {
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- svg {
- height: 100%;
- width: 100%;
- color: color-mix(in srgb, var(--background-color) 80%, black 10%);
- }
- }
-
- &__form {
- display: flex;
- flex-flow: column;
- gap: 20px;
- padding: 8px;
- width: 300px;
-
- //input {
- // height: 2rem;
- // border: none;
- // border-radius: 6px;
- // font-size: 1.4rem;
- // background-color: var(--card-background-color);
- // padding: 4px 8px;
- //}
- input[type="password"] {
- caret-color: var(--card-color);
- }
-
- button {
- @include common.textButton();
- }
-
- .error-text {
- color: var(--attention-color);
- font-size: 1rem;
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/login/login.tsx b/frontend/src/component/login/login.tsx
deleted file mode 100644
index 8dd5badcf..000000000
--- a/frontend/src/component/login/login.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import React, {JSX, useCallback, useMemo, useRef, useState} from 'react';
-import './login.scss';
-import {useServices} from "../../provider/service-provider";
-import {first} from "rxjs/operators";
-import {getIconByName} from "../../icons/icons";
-
-const checkUserPwd = (username: string, password: string) => username.trim().length > 0 && password.trim().length > 0;
-
-export default function Login(): JSX.Element {
-
- const usernameRef = useRef(undefined);
- const passwordRef = useRef(undefined);
- const services = useServices();
- const appTitle = useMemo(() => services.config().getUiConfig().app_title ?? 'tuliprox', [services]);
- const appLogo = useMemo(() => {
- let logo = services.config().getUiConfig().app_logo;
- if (logo) {
- return ;
- } else {
- return getIconByName('Logo');
- }
- }, [services]);
- const [authorized, setAuthorized] = useState(true);
-
- const handleLogin = useCallback(() => {
- const username = usernameRef.current.value;
- const password = passwordRef.current.value;
- services.auth().authenticate(username, password).pipe(first()).subscribe({
- next: (auth) => setAuthorized(auth),
- error: () => setAuthorized(false)
- });
- }, [services]);
-
- const handleKeyDown = useCallback((event: any) => {
- if (event.code === 'Enter') {
- if (checkUserPwd(usernameRef.current.value, passwordRef.current.value)) {
- handleLogin();
- }
- }
- }, [handleLogin]);
-
-
- return <>
- {getIconByName('Logo')}
-
-
-
{appLogo}
-
Login to {appTitle}
-
-
-
- >
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/main-config-view/main-config-view.scss b/frontend/src/component/main-config-view/main-config-view.scss
deleted file mode 100644
index f9781341c..000000000
--- a/frontend/src/component/main-config-view/main-config-view.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-@use '../../scss/common';
-@use '../../scss/preferences';
-
-@media (min-width: common.$media-size-mobile) {
- .main-config__form, .reverse-proxy__form {
- .form-view__col-label {
- min-width: 200px !important;
- max-width: 200px !important;
- width: 200px !important;
- }
- }
-}
-
-.main-config {
-
- @include preferences.prefsRoot();
- @include preferences.prefsToolbar();
- @include preferences.prefsForm();
-
- &__content-form {
- @include common.container_vert_no_padding();
- }
-
- &__content {
- @include common.container_vert_no_padding(flex-start);
- align-items: stretch;
- overflow-x: hidden;
- overflow-y: auto;
-
- &-form {
- &__section-title {
- color: var(--title-label-color);
- font-weight: bold;
- border-bottom: 1px solid var(--border-color);
- padding-bottom: 4px;
- margin: 8px 0;
- }
- }
-
- &-help {
- color: var(--title-label-color);
- &-img {
- max-width: 360px;
- height: auto;
- fill: currentColor;
- }
-
- pre {
- display: inline;
- }
- }
- }
-
- &__restart {
- @include common.container_horz_no_padding();
- border-top: 1px solid var(--border-color);
- padding-top: 12px;
- margin-top: 12px;
- justify-content: center;
- align-items: center;
- color: var(--label-color);
-
- &-warn-icon {
- color: var(--warn-color);
- }
- }
-
-}
-
diff --git a/frontend/src/component/main-config-view/main-config-view.tsx b/frontend/src/component/main-config-view/main-config-view.tsx
deleted file mode 100644
index b91fe782b..000000000
--- a/frontend/src/component/main-config-view/main-config-view.tsx
+++ /dev/null
@@ -1,455 +0,0 @@
-import React, {useCallback, useMemo, useState} from "react";
-import './main-config-view.scss';
-import ServerConfig, {
- CacheConfig, IpCheckConfig,
- LogConfig,
- MessagingConfig, ProxyConfig,
- PushoverConfig,
- RateLimitConfig,
- RestConfig,
- ReverseProxyConfig,
- ServerApiConfig,
- ServerMainConfig,
- StreamBufferConfig,
- StreamConfig,
- TelegramConfig,
- VideoConfig,
- VideoDownloadConfig,
- WebAuthConfig,
- WebUiConfig
-} from "../../model/server-config";
-import {useSnackbar} from "notistack";
-import FormView, {FormFieldType} from "../form-view/from-view";
-import TabSet from "../tab-set/tab-set";
-import {getIconByName} from "../../icons/icons";
-import {useServices} from "../../provider/service-provider";
-import {MessageKind} from "../../model/message-kind";
-import useTranslator from "../../hook/use-translator";
-
-const SCHEDULE_IMG_PATH = 'm 54.272427,0.5747185 0.02871,0.7255 -0.645988,-0.2615234 -0.108992,0.3271567 0.677887,0.1787243 -0.457242,0.6053405 0.287638,0.1762001 0.377489,-0.6255353 0.363135,0.6300793 L 55.0827,2.1549654 54.635028,1.5445761 55.3177,1.3658518 55.208707,1.0437439 l -0.638542,0.251426 0.03137,-0.7204514 z m 8.921531,0 0.02871,0.7255 -0.645987,-0.2615234 -0.108992,0.3271567 0.677886,0.1787243 -0.457243,0.6053405 0.287639,0.1762001 0.377488,-0.6255353 0.363137,0.6300793 0.287634,-0.1756953 -0.447668,-0.6103893 0.682673,-0.1787243 -0.108994,-0.3221079 -0.638545,0.251426 0.03138,-0.7204514 z m 8.921526,0 0.02872,0.7255 L 71.498218,1.0386951 71.389224,1.3658518 72.06711,1.5445761 71.609873,2.1499166 71.897507,2.3261167 72.274999,1.7005814 72.63813,2.3306607 72.92577,2.1549654 72.478097,1.5445761 73.16077,1.3658518 73.051775,1.0437439 72.41323,1.2951699 72.44461,0.5747185 Z m 8.921531,0 0.02872,0.7255 -0.645986,-0.2615234 -0.108994,0.3271567 0.677886,0.1787243 -0.457243,0.6053405 0.28764,0.1762001 0.377488,-0.6255353 0.363137,0.6300793 0.287634,-0.1756953 -0.447667,-0.6103893 0.682672,-0.1787243 -0.108995,-0.3221079 -0.638544,0.251426 0.03138,-0.7204514 z M 18.714978,0.85088306 C 18.323122,0.85941045 18.03431,1.0005694 17.848345,1.2734603 17.65638,1.555154 17.560176,1.9799877 17.560176,2.5482601 c 0,0.5536178 0.09827,0.975372 0.29508,1.2652073 0.196808,0.2882067 0.491652,0.4321698 0.883646,0.4321698 0.391995,0 0.687869,-0.1448595 0.8879,-0.4346943 0.201643,-0.2914634 0.302523,-0.7123218 0.302523,-1.2626828 0,-0.5633875 -0.0962,-0.986934 -0.288168,-1.2702559 -0.190351,-0.28495037 -0.486225,-0.42712114 -0.887899,-0.42712114 -0.01286,0 -0.02563,-2.74e-4 -0.03828,0 z m 8.921527,0 c -0.391847,0.008527 -0.680667,0.14968634 -0.866631,0.42257724 -0.191965,0.2816937 -0.288169,0.7065274 -0.288169,1.2747998 0,0.5536178 0.09827,0.975372 0.29508,1.2652073 0.196804,0.2882067 0.491663,0.4321698 0.883645,0.4321698 0.391994,0 0.687871,-0.1448595 0.8879,-0.4346943 0.201646,-0.2914634 0.302524,-0.7123218 0.302524,-1.2626828 0,-0.5633875 -0.0962,-0.986934 -0.288166,-1.2702559 -0.190352,-0.28495037 -0.486226,-0.42712114 -0.8879,-0.42712114 -0.01286,0 -0.02564,-2.74e-4 -0.03828,0 z m 8.903452,0 c -0.195279,0.004198 -0.369359,0.0403707 -0.522105,0.10753754 -0.162928,0.0716446 -0.28928,0.1721549 -0.379618,0.3024179 -0.08872,0.130263 -0.13345,0.2756268 -0.13345,0.435199 0,0.182368 0.05512,0.3440251 0.16482,0.4856861 0.109689,0.1416614 0.254769,0.2347258 0.435442,0.2786889 v 0.00958 c -0.198415,0.034194 -0.36117,0.1254658 -0.488609,0.2736402 -0.127439,0.1465454 -0.191405,0.3246105 -0.191405,0.5346584 0,0.3028615 0.102439,0.5398887 0.30731,0.7108587 0.206481,0.1709703 0.491959,0.2564749 0.85653,0.2564749 0.372636,0 0.659161,-0.087297 0.859187,-0.2615236 0.201644,-0.1742269 0.302526,-0.4107488 0.302526,-0.7103537 0,-0.2165608 -0.06292,-0.3964181 -0.188745,-0.5397074 C 37.437626,2.589133 37.270711,2.4991484 37.061002,2.4649546 v -0.00958 c 0.180671,-0.035823 0.326262,-0.124407 0.437568,-0.2660671 0.111314,-0.1416614 0.166946,-0.3047191 0.166946,-0.4887153 0,-0.1644571 -0.04315,-0.3111082 -0.130261,-0.4397429 -0.08712,-0.1286347 -0.212948,-0.2287537 -0.377488,-0.30039832 -0.164542,-0.0732729 -0.357463,-0.109557 -0.578464,-0.109557 -0.01321,0 -0.02632,-2.7917e-4 -0.03934,-1.2e-7 z m 5.989336,0 c -0.31295,0 -0.565145,0.081024 -0.757108,0.24385284 -0.191962,0.1612004 -0.304285,0.3805334 -0.336552,0.6573422 l 0.445546,0.0414 c 0.02096,-0.1839965 0.08753,-0.3266721 0.200442,-0.427626 0.114538,-0.1025819 0.263772,-0.1534808 0.447672,-0.1534808 0.206482,0 0.365606,0.048715 0.476912,0.1464126 0.111302,0.097698 0.166947,0.2367886 0.166947,0.4175286 0,0.1416615 -0.05667,0.2892082 -0.169606,0.4422671 C 42.890633,2.3716389 42.70603,2.5514947 42.449541,2.7582871 42.162399,2.989504 41.935168,3.1955095 41.7674,3.3762495 41.601245,3.555361 41.478524,3.7334248 41.399481,3.910908 v 0.2857572 h 2.274511 V 3.8402253 h -1.749748 c 0.05323,-0.1188645 0.143707,-0.2411582 0.271156,-0.3665363 0.129051,-0.1270066 0.359933,-0.3236602 0.692241,-0.5906993 0.243587,-0.1953945 0.426623,-0.3858895 0.549222,-0.5715142 0.1226,-0.1872532 0.18396,-0.3746686 0.18396,-0.5619218 0,-0.2865785 -0.0962,-0.5080948 -0.288168,-0.6644104 C 43.140689,0.9288277 42.872892,0.85088306 42.529293,0.85088306 Z m 2.950272,0 c -0.391855,0.008527 -0.680669,0.14968634 -0.866634,0.42257724 -0.191963,0.2816937 -0.288168,0.7065274 -0.288168,1.2747998 0,0.5536178 0.09827,0.975372 0.295079,1.2652073 0.196833,0.2882067 0.491653,0.4321698 0.883648,0.4321698 0.391994,0 0.687868,-0.1448595 0.887899,-0.4346943 0.201644,-0.2914634 0.302524,-0.7123218 0.302524,-1.2626828 0,-0.5633875 -0.0962,-0.986934 -0.288169,-1.2702559 -0.190351,-0.28495037 -0.486226,-0.42712114 -0.8879,-0.42712114 -0.01285,0 -0.02563,-2.74e-4 -0.03828,0 z M 36.579303,1.1780397 c 0.204869,0 0.361389,0.046531 0.469467,0.1393443 0.109726,0.091183 0.16429,0.2271962 0.16429,0.407936 0,0.3793908 -0.209553,0.5689898 -0.628974,0.5689898 -0.200029,0 -0.355503,-0.049611 -0.46681,-0.1489365 -0.111314,-0.099325 -0.166947,-0.239313 -0.166947,-0.4200528 0,-0.1791116 0.05459,-0.3151236 0.164287,-0.407936 0.111301,-0.092812 0.26627,-0.1393444 0.464687,-0.1393448 z m -17.826045,0.029282 c 0.26133,0 0.44853,0.1058869 0.56145,0.3175639 0.114538,0.210049 0.171732,0.5511707 0.171732,1.0233739 0,0.4526641 -0.05979,0.7889138 -0.179175,1.0087327 -0.119374,0.219819 -0.307087,0.3296811 -0.563578,0.3296811 -0.258104,0 -0.446861,-0.1067821 -0.566236,-0.3200883 -0.117759,-0.2149342 -0.176515,-0.5542635 -0.176515,-1.0183255 0,-0.4770882 0.05771,-0.8200019 0.173858,-1.0284225 0.11776,-0.2084209 0.310681,-0.3125153 0.578464,-0.3125153 z m 8.92153,0 c 0.261329,0 0.448527,0.1058869 0.56145,0.3175639 0.114527,0.210049 0.171731,0.5511707 0.171731,1.0233739 0,0.4526641 -0.05981,0.7889138 -0.179177,1.0087327 -0.119373,0.219819 -0.307083,0.3296811 -0.563576,0.3296811 -0.258098,0 -0.446862,-0.1067821 -0.566234,-0.3200883 -0.117761,-0.2149342 -0.176516,-0.5542635 -0.176516,-1.0183255 0,-0.4770882 0.05771,-0.8200019 0.173856,-1.0284225 0.117761,-0.2084209 0.310677,-0.3125153 0.578466,-0.3125153 z m 17.843056,0 c 0.26133,0 0.448532,0.1058869 0.56145,0.3175639 0.114527,0.210049 0.171732,0.5511707 0.171732,1.0233739 0,0.4526641 -0.05979,0.7889138 -0.179174,1.0087327 -0.119373,0.219819 -0.307087,0.3296811 -0.563576,0.3296811 -0.258104,0 -0.446863,-0.1067821 -0.566236,-0.3200883 -0.117759,-0.214934 -0.177048,-0.5542633 -0.177048,-1.0183253 0,-0.4770884 0.05824,-0.820002 0.174388,-1.0284227 0.117762,-0.2084207 0.310683,-0.3125152 0.578464,-0.3125153 z M 18.460305,2.2332205 v 0.6078649 h 0.568893 V 2.2332205 Z m 8.921528,0 v 0.6078649 h 0.568894 V 2.2332205 Z m 17.843058,0 v 0.6078649 h 0.568893 V 2.2332205 Z m -8.645588,0.3907702 c 0.227452,0 0.404254,0.052691 0.53008,0.1585296 0.125824,0.104211 0.188745,0.2539408 0.188745,0.4493354 0,0.2312165 -0.05824,0.4031214 -0.174389,0.5154733 -0.116147,0.1123486 -0.29295,0.1686271 -0.530082,0.1686271 -0.23552,0 -0.414396,-0.057171 -0.536994,-0.1711513 -0.120985,-0.1156078 -0.1813,-0.2830327 -0.1813,-0.5028519 0,-0.1856235 0.06188,-0.3349638 0.186086,-0.4473157 0.125825,-0.1139805 0.298466,-0.1706465 0.517854,-0.1706465 z m 2.533438,0.8426299 -0.40886,1.6165977 h 0.297737 l 0.752321,-1.6165977 z m -20.202639,2.1199544 -0.271155,0.00404 0.03615,2.9327967 H 15.74875 L 16.305947,7.9948129 13.886288,8.6511457 16.305947,9.307478 15.750876,8.7808974 h 3.199097 z m 61.925937,0 -0.04199,3.1943203 h 3.201222 L 83.440733,9.3074758 85.85986,8.6511434 83.440733,7.9948107 83.997399,8.523411 h -2.92581 l 0.03613,-2.9327972 z m -53.488233,0.00201 0.04042,8.831209 h -11.63734 l 0.555071,-0.526581 -2.419659,0.656332 2.419659,0.656333 -0.555071,-0.526581 H 27.6615 L 27.6211,5.5885835 Z m 12.523104,0 0.04041,14.643285 H 15.750881 l 0.555071,-0.526581 -2.419659,0.656332 2.419659,0.656334 -0.555071,-0.526581 h 24.43159 L 40.142057,5.588588 Z m 14.231381,0 -0.04042,21.443903 h 29.879153 l -0.556661,0.5286 2.419128,-0.656332 -2.419128,-0.656334 0.554539,0.52658 H 54.335166 l 0.03828,-21.1864125 z m 9.311777,0 -0.04042,14.902789 h 20.621599 l -0.554537,0.526581 2.419127,-0.656334 -2.419127,-0.656332 0.554537,0.526581 H 63.646927 L 63.68521,5.5885805 Z m 8.710985,0 -0.03984,9.090712 h 11.91008 l -0.554537,0.526581 2.419127,-0.656333 -2.419127,-0.656333 0.554537,0.526581 H 72.358492 l 0.03776,-8.831208 z M 1.4642066,7.0077877 c -0.3505704,0 -0.6212685,0.066373 -0.81186986,0.1989192 -0.19060142,0.1325459 -0.28604189,0.3204256 -0.28604189,0.5639411 0,0.2003604 0.0647252,0.3584814 0.19406186,0.4740741 C 0.68969335,8.358773 0.89991743,8.4436925 1.190925,8.4991769 l 0.1632245,0.03433 c 0.2910076,0.057027 0.4795501,0.1165706 0.5646403,0.1782194 0.086791,0.06165 0.1297293,0.14997 0.1297291,0.2655622 0,0.1248403 -0.059517,0.2219575 -0.1786433,0.2913107 -0.1191259,0.067815 -0.2891753,0.1019835 -0.5104093,0.1019835 -0.1650744,0 -0.3318295,-0.020007 -0.50030745,-0.060079 -0.16847802,-0.041612 -0.3394843,-0.103601 -0.5130677,-0.1852862 v 0.4397426 c 0.1837942,0.053945 0.35809515,0.09396 0.52316955,0.120159 0.1667762,0.027738 0.3269419,0.0414 0.4801038,0.0414 0.3675883,0 0.6557162,-0.070278 0.8650373,-0.2105313 C 2.425425,9.375735 2.531281,9.1854112 2.531281,8.9449783 2.531281,8.7338293 2.4603914,8.5698634 2.3191422,8.452729 2.1795945,8.334056 1.9434381,8.2423062 1.6098858,8.177574 L 1.448788,8.1452622 C 1.1969218,8.0944014 1.0292102,8.0412075 0.9458221,7.9857231 0.86243399,7.9286973 0.82087814,7.8511115 0.82087814,7.7524725 c 0,-0.1294635 0.0581358,-0.2265789 0.17385815,-0.2913107 0.11742401,-0.064731 0.29268161,-0.09744 0.52582791,-0.09744 0.1531619,0 0.3009987,0.015623 0.4439498,0.046448 0.142951,0.030825 0.2809036,0.07719 0.4136441,0.1388399 V 7.146627 C 2.2488212,7.1003892 2.1075739,7.0657419 1.9544121,7.0426234 1.8012502,7.0195053 1.6377898,7.0077877 1.4642066,7.0077877 Z m 3.0119464,0 c -0.428853,0 -0.7699091,0.1249335 -1.023477,0.3746145 -0.2518659,0.2481362 -0.3774901,0.583883 -0.3774901,1.0077226 0,0.4099686 0.1322134,0.7354814 0.3976937,0.9759161 0.2671823,0.240433 0.6290696,0.3604783 1.0851515,0.3604783 0.1820925,0 0.3616012,-0.017085 0.5385882,-0.050992 C 5.273606,9.641621 5.4465257,9.5923326 5.6150037,9.5276 V 9.1252175 c -0.1667761,0.080144 -0.3354446,0.1406714 -0.5056242,0.1807437 -0.168478,0.040073 -0.3427789,0.06008 -0.5231696,0.06008 -0.311429,0 -0.5547064,-0.076629 -0.7299916,-0.2292116 C 3.6826348,8.9827058 3.585281,8.7591954 3.5648599,8.4663605 h 2.159669 v -0.208007 c 0,-0.3853089 -0.1124446,-0.6893281 -0.3370828,-0.9128073 C 5.1645109,7.1205259 4.8607594,7.0077877 4.476153,7.0077877 Z m 3.2522641,0 c -0.445871,0 -0.7973428,0.1210289 -1.0543142,0.3630024 C 6.4188333,7.612764 6.291296,7.9451073 6.291296,8.3674055 c 0,0.4161339 0.1265809,0.7465106 0.3801488,0.9915672 0.2535678,0.2450565 0.5961119,0.3675465 1.0266667,0.3675465 0.1582705,0 0.3070578,-0.013665 0.4466082,-0.0414 C 8.2859688,9.6573783 8.4220055,9.6153957 8.5530473,9.559911 V 9.1671212 C 8.4203038,9.2333942 8.286605,9.2836652 8.1521633,9.317573 8.0194228,9.3499383 7.8852996,9.366041 7.7491522,9.366041 7.4445309,9.366041 7.2078427,9.2786786 7.0393649,9.1045179 6.8708865,8.9288169 6.7862866,8.6833589 6.7862866,8.3674055 c 0,-0.315953 0.0846,-0.5614377 0.2530783,-0.7355973 0.1684778,-0.1757005 0.405166,-0.2635427 0.7097873,-0.2635427 0.1361474,0 0.2702706,0.017085 0.4030111,0.050993 0.1344417,0.032366 0.2681405,0.081654 0.400884,0.1479268 V 7.1698512 C 8.4237077,7.1159083 8.2900085,7.0753881 8.1521633,7.0491868 8.0143177,7.0214452 7.8730697,7.0077877 7.7284171,7.0077877 Z m 84.1574529,0 c -0.40487,0 -0.726666,0.1322864 -0.966055,0.3968286 -0.237782,0.262909 -0.356758,0.6187343 -0.356758,1.0678024 0,0.4343718 0.125264,0.7792322 0.375899,1.0339763 0.252241,0.2547445 0.59396,0.3821877 1.02454,0.3821877 0.171908,0 0.341191,-0.018096 0.508284,-0.054021 0.167087,-0.035925 0.330083,-0.088429 0.489142,-0.1570148 V 9.251435 c -0.157452,0.084915 -0.316255,0.14889 -0.476916,0.1913465 -0.159059,0.042458 -0.324156,0.063614 -0.494456,0.063614 -0.294019,0 -0.523572,-0.081179 -0.689057,-0.2428431 C 91.13662,9.100255 91.044899,8.8634641 91.025619,8.5531987 h 2.038979 V 8.3330745 c 0,-0.4082436 -0.105874,-0.7310568 -0.317945,-0.9678383 C 92.536183,7.126822 92.248967,7.0077877 91.88587,7.0077877 Z m 2.853507,0 c -0.144594,0 -0.292912,0.016314 -0.445544,0.048973 -0.152633,0.031026 -0.310387,0.078183 -0.47266,0.1418685 v 0.4165187 c 0.134955,-0.075117 0.275413,-0.1315739 0.421619,-0.1691318 0.1462,-0.037559 0.296623,-0.056042 0.450864,-0.056042 0.242602,0 0.430754,0.056845 0.564107,0.1711518 0.134954,0.1126755 0.202571,0.2727444 0.202571,0.4801326 v 0.044429 h -0.622067 c -0.403263,0 -0.706719,0.078504 -0.910756,0.2352698 -0.202438,0.1567656 -0.30359,0.3890998 -0.30359,0.6977322 0,0.2645417 0.08071,0.4762245 0.242973,0.6346232 0.163879,0.1567653 0.384001,0.2352699 0.660342,0.2352699 0.218504,0 0.403518,-0.039252 0.554537,-0.1176344 0.151027,-0.080016 0.277339,-0.2035072 0.378561,-0.3700707 v 0.4165187 h 0.443417 V 8.252295 c 0,-0.4180416 -0.09644,-0.7302766 -0.289236,-0.9360312 -0.192797,-0.205755 -0.484723,-0.3084761 -0.875138,-0.3084761 z m 3.414958,0 c -0.213683,0 -0.39502,0.041035 -0.54444,0.1226835 -0.149413,0.080016 -0.271011,0.2030025 -0.364193,0.3695657 V 7.0739256 h -0.445551 v 2.7434701 h 0.445551 V 8.3719493 c 0,-0.3102626 0.06708,-0.5474426 0.200442,-0.712373 0.134955,-0.1665636 0.327294,-0.2499114 0.577929,-0.2499114 0.07071,0 0.135164,0.00702 0.193001,0.021708 0.05945,0.013065 0.113958,0.034725 0.163752,0.064119 l -0.0023,-0.4629673 c -0.045,-0.00816 -0.08587,-0.01389 -0.122816,-0.017166 -0.03694,-0.0049 -0.07103,-0.00758 -0.101558,-0.00758 z m -10.637247,0.066138 1.091532,2.6995466 -0.07976,0.2054822 c -0.08192,0.2188185 -0.15635,0.3577325 -0.223828,0.4165185 -0.06748,0.05878 -0.162341,0.08835 -0.28445,0.08835 h -0.260518 v 0.37714 h 0.35463 c 0.199218,0 0.358,-0.04983 0.476911,-0.149443 0.118889,-0.09961 0.241007,-0.313076 0.366328,-0.639671 L 90.143569,7.0739237 H 89.673567 L 88.830328,9.2196281 87.986561,7.0739256 Z M 4.4814697,7.3682655 c 0.2314446,0 0.4171176,0.069295 0.5566652,0.208007 0.1412491,0.1387081 0.2135204,0.3246508 0.2169239,0.5573779 l -1.6747805,0.00252 C 3.604103,7.8926553 3.6943361,7.7037924 3.8509016,7.569705 4.0091688,7.4356174 4.2193929,7.3682614 4.4814697,7.3682614 Z m 87.4097183,0.021708 c 0.218504,0 0.393553,0.07366 0.525295,0.2206287 0.133382,0.1469685 0.201515,0.3436152 0.204699,0.5901945 l -1.581208,0.00252 c 0.0225,-0.2580102 0.107927,-0.4582234 0.255734,-0.6002923 0.149419,-0.1420694 0.348058,-0.2130557 0.59548,-0.2130557 z m 3.127853,1.0481129 h 0.441293 v 0.1004701 c 0,0.2939352 -0.07496,0.5298348 -0.224372,0.7078292 -0.147809,0.1763617 -0.344875,0.2645527 -0.59069,0.2645524 -0.178338,6e-7 -0.319313,-0.046265 -0.423748,-0.139344 -0.102823,-0.094713 -0.154187,-0.2221561 -0.154187,-0.3821874 0,-0.200856 0.06862,-0.3428298 0.206827,-0.4261113 0.138162,-0.083282 0.386594,-0.125208 0.744877,-0.125209 z M 5.6479675,11.980768 v 0.538699 h 0.4700018 v -0.538699 z m 83.9633915,0.295856 v 1.484321 c -0.09319,-0.163298 -0.211636,-0.284116 -0.354629,-0.362497 -0.141381,-0.08002 -0.311716,-0.120159 -0.51094,-0.120159 -0.326146,0 -0.591867,0.132285 -0.797518,0.396829 -0.204037,0.264538 -0.306245,0.612463 -0.306245,1.043567 0,0.431106 0.102208,0.779027 0.306245,1.043569 0.205651,0.264542 0.471372,0.39683 0.797518,0.39683 0.199224,0 0.369559,-0.03926 0.51094,-0.11763 0.142993,-0.08001 0.261446,-0.201723 0.354629,-0.365022 v 0.411471 h 0.44342 v -3.811279 z m 12.837321,0 c -0.30365,0 -0.52534,0.07009 -0.66513,0.210531 -0.13977,0.138801 -0.20948,0.361063 -0.20948,0.66643 v 0.19084 h -0.42428 v 0.350382 h 0.42428 v 2.39309 h 0.44608 v -2.39309 h 0.72999 v -0.350382 h -0.72999 v -0.242337 c 0,-0.169826 0.0303,-0.287582 0.0914,-0.352906 0.0627,-0.06532 0.17325,-0.09794 0.3323,-0.09794 h 0.42428 v -0.374614 z m 12.4524,0 v 3.811273 h 0.44608 v -1.33993 l 1.37119,1.33993 h 0.57846 l -1.49189,-1.459583 1.43127,-1.283889 h -0.56622 l -1.32281,1.18342 V 12.276624 Z M 1.8906111,12.926393 c -0.1897572,0.0044 -0.3551242,0.04229 -0.4955225,0.11309 -0.1497583,0.07552 -0.278252,0.192644 -0.3854653,0.351392 V 12.988491 H 0.53749465 v 2.589485 H 1.0096233 v -1.463621 c 0,-0.251222 0.07089,-0.449836 0.2121388,-0.596253 0.1412493,-0.147959 0.3315986,-0.222144 0.5715522,-0.222144 0.2025141,0 0.352264,0.06002 0.4492665,0.180239 0.097002,0.118676 0.1456793,0.303154 0.1456793,0.552834 v 1.548945 h 0.4721285 v -1.463621 c 0,-0.252763 0.07089,-0.452359 0.2121389,-0.598777 0.1412491,-0.146417 0.3335118,-0.21962 0.5768689,-0.21962 0.1991102,0 0.3469472,0.06002 0.4439495,0.180239 0.097002,0.120216 0.1456793,0.304695 0.1456793,0.552834 v 1.548945 h 0.4721287 v -1.56308 c 0,-0.348321 -0.080773,-0.61623 -0.2424445,-0.80426 -0.1616707,-0.189571 -0.3912379,-0.284243 -0.6890525,-0.284243 -0.2212337,0 -0.4134963,0.04538 -0.5768686,0.136316 -0.1633738,0.09093 -0.3041954,0.231968 -0.4216194,0.42308 C 2.7113953,13.303925 2.6037326,13.164826 2.457378,13.069272 2.3127252,12.973707 2.1365109,12.926393 1.9288917,12.926393 c -0.012871,0 -0.025631,-2.85e-4 -0.038281,0 z m 6.5513156,0 c -0.1839736,0.0045 -0.3476798,0.04287 -0.4912687,0.115112 -0.1514601,0.07706 -0.2832488,0.193704 -0.3955675,0.34937 V 12.988491 H 7.0824304 v 2.589485 h 0.4726601 v -1.463621 c 0,-0.252763 0.073227,-0.452359 0.2195822,-0.598777 0.1463578,-0.146417 0.3461603,-0.21962 0.5997311,-0.21962 0.2110231,0 0.3692756,0.06102 0.4747868,0.182763 0.1055124,0.121759 0.1584397,0.305253 0.1584397,0.55031 v 1.548945 H 9.477101 v -1.56308 c 0,-0.357567 -0.084069,-0.627923 -0.2525466,-0.811328 -0.1684778,-0.18495 -0.4169632,-0.277175 -0.7454102,-0.277175 -0.012447,0 -0.024952,-3e-4 -0.037219,0 z m -2.7939592,0.0621 v 2.589484 h 0.4700018 v -2.589485 z m 86.2671455,0.289796 c -0.1446,0 -0.293445,0.01631 -0.446077,0.04897 -0.152633,0.03103 -0.309855,0.07869 -0.472127,0.142375 v 0.416014 c 0.134954,-0.07513 0.275413,-0.131067 0.42162,-0.168628 0.1462,-0.03756 0.296623,-0.05654 0.450856,-0.05654 0.242609,0 0.430233,0.05735 0.563581,0.171656 0.134955,0.112675 0.202565,0.272743 0.202565,0.480131 v 0.04392 h -0.621528 c -0.403268,0 -0.707246,0.0785 -0.91129,0.23527 -0.202437,0.156765 -0.30359,0.389099 -0.30359,0.697731 0,0.264541 0.08124,0.476225 0.243507,0.634622 0.16388,0.156768 0.384001,0.235272 0.660342,0.235272 0.218503,0 0.403518,-0.03926 0.554538,-0.11763 0.151027,-0.08001 0.276805,-0.203507 0.378021,-0.37007 v 0.416519 h 0.443422 v -1.565108 c 0,-0.418041 -0.09644,-0.729772 -0.289235,-0.935525 -0.192792,-0.205755 -0.484196,-0.308982 -0.874605,-0.308982 z m 7.613074,0 c -0.387196,0 -0.691185,0.127443 -0.911295,0.382188 -0.218503,0.254744 -0.327533,0.607509 -0.327509,1.058209 -2.9e-5,0.449068 0.109006,0.801834 0.327509,1.058211 0.22011,0.254745 0.524099,0.382188 0.911295,0.382188 0.385596,0 0.688523,-0.12744 0.908633,-0.382188 0.22012,-0.256377 0.33018,-0.609143 0.33018,-1.058211 0,-0.4507 -0.11006,-0.803465 -0.33018,-1.058209 -0.22011,-0.254745 -0.523037,-0.382188 -0.908633,-0.382188 z m 10.446913,0 c -0.40487,0 -0.72667,0.132286 -0.96607,0.396829 -0.23777,0.262907 -0.35675,0.61924 -0.35675,1.068307 0,0.434372 0.12527,0.778728 0.3759,1.033472 0.25224,0.254745 0.59343,0.382188 1.024,0.382188 0.17193,0 0.34174,-0.01809 0.50883,-0.05401 0.16708,-0.03592 0.33008,-0.08793 0.48913,-0.156512 v -0.426616 c -0.15744,0.08492 -0.31678,0.148891 -0.47744,0.191347 -0.15906,0.04246 -0.32363,0.06362 -0.49393,0.06362 -0.29401,0 -0.52357,-0.08066 -0.68905,-0.242338 -0.16386,-0.163298 -0.25559,-0.400089 -0.27489,-0.710354 h 2.03899 v -0.220637 c 0,-0.408243 -0.1064,-0.730551 -0.31847,-0.967332 -0.21048,-0.238415 -0.49715,-0.357954 -0.86025,-0.357954 z m 3.03693,0 c -0.40488,0 -0.7272,0.132286 -0.96659,0.396829 -0.23779,0.262907 -0.35675,0.61924 -0.35675,1.068307 0,0.434372 0.12525,0.778728 0.37589,1.033472 0.25225,0.254745 0.59396,0.382188 1.02454,0.382188 0.17191,0 0.3412,-0.01809 0.50828,-0.05401 0.16709,-0.03592 0.33008,-0.08793 0.48915,-0.156512 v -0.426616 c -0.15746,0.08492 -0.31625,0.148891 -0.47692,0.191347 -0.15906,0.04246 -0.32414,0.06362 -0.49445,0.06362 -0.29401,0 -0.52358,-0.08066 -0.68906,-0.242338 -0.16387,-0.163298 -0.25557,-0.400089 -0.27487,-0.710354 h 2.03897 v -0.220637 c 0,-0.408243 -0.10585,-0.730551 -0.31794,-0.967332 -0.21047,-0.238415 -0.49715,-0.357954 -0.86025,-0.357954 z m -19.337603,0.06614 1.091537,2.699547 -0.07923,0.205484 c -0.08193,0.218818 -0.156889,0.357732 -0.224367,0.416518 -0.06749,0.05879 -0.162342,0.08835 -0.284449,0.08835 h -0.259993 v 0.377138 h 0.354101 c 0.199219,0 0.358023,-0.04983 0.476911,-0.149445 0.118894,-0.09962 0.241007,-0.312572 0.366328,-0.639167 l 1.185636,-2.998431 h -0.469469 l -0.843773,2.145704 -0.843239,-2.145704 z m 10.875963,0 0.70607,2.743472 h 0.52317 l 0.58325,-2.248698 0.5806,2.248698 h 0.52317 l 0.70606,-2.743472 h -0.44342 l -0.55188,2.14116 -0.55453,-2.14116 h -0.52264 l -0.55189,2.14116 -0.55453,-2.14116 z M 88.854249,13.65543 c 0.23457,0 0.419584,0.09481 0.554539,0.284241 0.13496,0.187792 0.202571,0.447524 0.202571,0.779017 0,0.331495 -0.06761,0.592116 -0.202571,0.781542 -0.134955,0.187792 -0.319969,0.281718 -0.554539,0.281718 -0.234568,0 -0.419049,-0.09393 -0.554003,-0.281718 -0.133348,-0.189428 -0.19991,-0.450049 -0.19991,-0.781544 0,-0.331493 0.06656,-0.591225 0.19991,-0.779017 0.134954,-0.189424 0.319435,-0.284242 0.554003,-0.284242 z m 10.673938,0.0051 c 0.236182,0 0.423279,0.09571 0.561453,0.286767 0.13817,0.189426 0.20732,0.446481 0.20735,0.771444 0,0.326595 -0.0691,0.584543 -0.20735,0.773966 -0.138174,0.189423 -0.325271,0.284243 -0.561453,0.284243 -0.239389,0 -0.428069,-0.09393 -0.566236,-0.281718 -0.13656,-0.189425 -0.205227,-0.448262 -0.205227,-0.776491 0,-0.328228 0.06918,-0.586177 0.207356,-0.773969 0.138167,-0.189425 0.326325,-0.284242 0.564107,-0.284242 z m 10.451693,0 c 0.2185,0 0.39408,0.07366 0.52583,0.220629 0.13324,0.146967 0.20137,0.343615 0.20469,0.590194 l -1.5812,0.0025 c 0.0225,-0.258009 0.10793,-0.458222 0.25574,-0.600291 0.14943,-0.142069 0.34753,-0.213055 0.59494,-0.213055 z m 3.03693,0 c 0.21851,0 0.39356,0.07366 0.5253,0.220629 0.13328,0.146967 0.20137,0.343615 0.2047,0.590194 l -1.58068,0.0025 c 0.0225,-0.258009 0.1074,-0.458222 0.25521,-0.600291 0.14941,-0.142069 0.34805,-0.213055 0.59547,-0.213055 z m -20.822039,1.048618 h 0.44076 v 0.100467 c 0,0.293935 -0.07441,0.529835 -0.223834,0.70783 -0.147812,0.17636 -0.344873,0.264551 -0.59069,0.264551 -0.178338,0 -0.319846,-0.04676 -0.424281,-0.139844 -0.102822,-0.09471 -0.154181,-0.222157 -0.154181,-0.382187 0,-0.200858 0.06915,-0.34283 0.20735,-0.426112 0.138168,-0.08328 0.386599,-0.124702 0.744876,-0.124702 z M 0.53749465,17.898916 v 3.597209 H 1.0096233 v -1.463164 c 0,-0.252764 0.073228,-0.452361 0.2195824,-0.598778 0.1463546,-0.146415 0.3466943,-0.219619 0.6002624,-0.219619 0.2110231,0 0.3692755,0.0605 0.4747871,0.182259 0.1055113,0.121755 0.1579079,0.305253 0.1579079,0.550309 v 1.548945 H 2.9321649 V 19.9335 c 0,-0.357565 -0.084599,-0.628426 -0.2530778,-0.811833 -0.1684781,-0.184948 -0.416963,-0.277174 -0.7454104,-0.277174 -0.1991104,0 -0.3747928,0.03855 -0.5279546,0.11562 -0.15146,0.07707 -0.2837802,0.193201 -0.3960988,0.348867 V 17.898869 Z M 100.70903,18.547124 v 3.811273 h 0.44555 v -1.55046 c 0,-0.267807 0.0691,-0.478984 0.20736,-0.634119 0.13817,-0.155128 0.32685,-0.232744 0.56623,-0.232744 0.19923,0 0.34913,0.06437 0.44873,0.193366 0.0996,0.128999 0.14941,0.323483 0.14941,0.583126 v 1.640831 h 0.44341 v -1.655473 c 0,-0.378851 -0.0796,-0.665472 -0.23871,-0.859796 -0.15905,-0.195957 -0.39387,-0.293835 -0.70395,-0.293835 -0.18797,0 -0.35412,0.04054 -0.4987,0.122175 -0.143,0.08165 -0.26773,0.20514 -0.37378,0.37007 v -1.494419 z m -1.925719,0.289291 v 0.779016 h -0.325386 v 0.349877 h 0.325386 v 1.489369 c 0,0.342926 0.06499,0.579715 0.195117,0.710353 0.13017,0.129001 0.36652,0.193367 0.708731,0.193367 h 0.455641 v -0.377139 h -0.455641 c -0.184764,0 -0.307933,-0.03174 -0.368984,-0.09541 -0.05944,-0.06369 -0.08879,-0.207442 -0.08879,-0.43116 v -1.489366 h 0.913425 V 19.61544 h -0.913422 v -0.779016 z m -93.8068505,0.0081 c -0.4101334,0 -0.7313147,0.120045 -0.9644609,0.360479 -0.2314446,0.240432 -0.3471848,0.57376 -0.3471848,0.999139 0,0.42384 0.1157402,0.756662 0.3471848,0.998636 0.2331462,0.240433 0.5543275,0.360478 0.9644609,0.360478 0.4084316,0 0.7291879,-0.120045 0.962334,-0.360478 0.2331461,-0.241974 0.3498433,-0.574796 0.3498433,-0.998636 0,-0.425379 -0.1166972,-0.758707 -0.3498433,-0.999139 C 5.7056484,18.964535 5.3848921,18.84449 4.9764605,18.84449 Z m 6.8208755,0 c -0.226339,0 -0.418601,0.03855 -0.576868,0.11562 -0.158269,0.07552 -0.286762,0.191661 -0.385466,0.348867 v -0.401878 h -0.472129 v 2.58898 h 0.472129 V 20.13245 c 0,-0.292836 0.07089,-0.517329 0.212138,-0.672993 0.142952,-0.157206 0.347012,-0.235776 0.612493,-0.235776 0.07488,0 0.142898,0.0069 0.204164,0.0207 0.06296,0.0123 0.121105,0.03234 0.173857,0.06008 l -0.0027,-0.436713 c -0.04765,-0.0078 -0.09111,-0.01305 -0.130261,-0.01616 -0.03913,-0.0046 -0.07506,-0.007 -0.107404,-0.007 z m -4.7787067,0.0626 v 1.567121 c 0,0.356025 0.085556,0.626884 0.2557365,0.811833 0.1701796,0.184949 0.4190896,0.277174 0.7475365,0.277174 0.1991105,0 0.3739434,-0.03757 0.5237012,-0.113085 0.15146,-0.07706 0.2842052,-0.194187 0.3982257,-0.35139 V 21.49611 H 9.4132997 V 18.907131 H 8.9438292 v 1.467662 c 0,0.252762 -0.074184,0.452361 -0.222241,0.598778 -0.1463543,0.146415 -0.3461632,0.219617 -0.5997304,0.219617 -0.2110231,0 -0.3698074,-0.0605 -0.4753188,-0.182257 -0.1055113,-0.1233 -0.1579079,-0.307778 -0.1579079,-0.552834 V 18.907131 Z M 4.9764605,19.20499 c 0.2501643,0 0.4485909,0.09028 0.5949456,0.27061 0.1463577,0.178784 0.2195824,0.421824 0.2195824,0.728529 0,0.30825 -0.073225,0.551766 -0.2195824,0.73055 -0.1463547,0.178783 -0.3447813,0.268086 -0.5949456,0.268086 -0.253568,0 -0.4533763,-0.08883 -0.5997309,-0.266067 -0.1446529,-0.178784 -0.216924,-0.42278 -0.216924,-0.732569 0,-0.309787 0.073227,-0.553306 0.2195822,-0.730548 0.1463546,-0.178783 0.3452066,-0.268591 0.5970727,-0.268591 z m 84.1191735,0.344293 c -0.17915,0.0046 -0.335333,0.04464 -0.467874,0.119655 -0.141387,0.08001 -0.26245,0.204399 -0.363672,0.372594 v -0.426111 h -0.446073 v 2.742966 h 0.446073 v -1.55046 c 0,-0.266175 0.06656,-0.476461 0.199915,-0.631594 0.133376,-0.156765 0.313113,-0.235269 0.539649,-0.235269 0.191191,0 0.3327,0.06347 0.424276,0.19084 0.09159,0.125745 0.137176,0.32111 0.137176,0.585651 v 1.640832 h 0.446078 v -1.55046 c 0,-0.267807 0.06655,-0.478984 0.199908,-0.634119 0.133348,-0.155128 0.315218,-0.232744 0.544968,-0.232744 0.187978,0 0.327387,0.06347 0.418964,0.19084 0.09158,0.127381 0.137704,0.322742 0.137704,0.585651 v 1.640832 h 0.445544 v -1.655473 c 0,-0.369053 -0.07599,-0.653504 -0.228625,-0.852728 -0.152628,-0.200856 -0.369611,-0.300903 -0.650772,-0.300903 -0.208857,0 -0.390728,0.04804 -0.544961,0.14439 -0.15424,0.09633 -0.28684,0.245837 -0.397701,0.448326 -0.06588,-0.192691 -0.167545,-0.340014 -0.305713,-0.441258 -0.13656,-0.101247 -0.302701,-0.151461 -0.498712,-0.151461 -0.01213,0 -0.02418,-3e-4 -0.03613,0 z m 4.556997,0 c -0.37004,0.0076 -0.661375,0.134895 -0.874605,0.381683 -0.218505,0.254745 -0.328049,0.607509 -0.328049,1.058211 0,0.449068 0.109539,0.801832 0.328049,1.05821 0.220109,0.254745 0.52356,0.382188 0.910756,0.382188 0.385596,0 0.6885,-0.127441 0.908639,-0.382188 0.220105,-0.256378 0.330171,-0.609142 0.330171,-1.05821 0,-0.450702 -0.110072,-0.803466 -0.330171,-1.058211 -0.220139,-0.254744 -0.523043,-0.381683 -0.908639,-0.381683 -0.01207,0 -0.02424,-2.49e-4 -0.03614,0 z m 3.276187,0 c -0.17368,0.0048 -0.328059,0.04563 -0.463622,0.122175 -0.142987,0.08165 -0.267728,0.20514 -0.373764,0.37007 v -0.426111 h -0.445544 v 2.742966 h 0.445544 v -1.55046 c 0,-0.267807 0.06916,-0.478985 0.207356,-0.634119 0.138162,-0.155129 0.326841,-0.232745 0.56623,-0.232745 0.199225,0 0.348592,0.06436 0.448207,0.193365 0.09961,0.129 0.149403,0.323484 0.149403,0.583127 v 1.640832 h 0.443416 v -1.655474 c 0,-0.37885 -0.07914,-0.665472 -0.238196,-0.859795 -0.159052,-0.195957 -0.393854,-0.293835 -0.70394,-0.293835 -0.01178,0 -0.02348,-3e-4 -0.03509,0 z m -3.240036,0.381683 c 0.236176,0 0.423284,0.09571 0.561451,0.286767 0.138175,0.189424 0.207357,0.446481 0.207357,0.771444 0,0.326595 -0.06918,0.584541 -0.207357,0.773966 -0.138167,0.189425 -0.325275,0.284244 -0.561451,0.284244 -0.239389,0 -0.428063,-0.09393 -0.566231,-0.281719 -0.136566,-0.189424 -0.205227,-0.448263 -0.205227,-0.776491 0,-0.328229 0.06918,-0.586176 0.20735,-0.773969 0.138174,-0.189424 0.326325,-0.284242 0.564108,-0.284242 z m -4.077423,4.886648 v 1.484828 c -0.09319,-0.163298 -0.211636,-0.28462 -0.354629,-0.363004 -0.141381,-0.08001 -0.311716,-0.119654 -0.51094,-0.119654 -0.326146,0 -0.591867,0.132284 -0.797518,0.39683 -0.204037,0.26454 -0.306245,0.612461 -0.306245,1.043568 0,0.431079 0.102208,0.778521 0.306245,1.043063 0.205651,0.264544 0.471372,0.396831 0.797518,0.396831 0.199224,0 0.369559,-0.03926 0.51094,-0.11763 0.142993,-0.08001 0.261446,-0.201219 0.354629,-0.364517 V 28.6294 h 0.44342 v -3.81178 z m 12.837321,0 c -0.30365,0 -0.52534,0.07061 -0.66513,0.211038 -0.13977,0.138794 -0.20948,0.360558 -0.20948,0.665923 v 0.191346 h -0.42428 v 0.350381 h 0.42428 v 2.393092 h 0.44608 V 26.23631 h 0.72999 v -0.350381 h -0.72999 v -0.242841 c 0,-0.169834 0.0303,-0.287086 0.0914,-0.352402 0.0627,-0.06532 0.17325,-0.09796 0.3323,-0.09796 h 0.42428 v -0.37512 z m 15.23359,0 v 3.81178 h 0.44608 v -1.550461 c 0,-0.267809 0.0686,-0.479488 0.20682,-0.634624 0.13817,-0.155117 0.32738,-0.232745 0.56676,-0.232745 0.19923,0 0.34861,0.06436 0.4482,0.193365 0.0996,0.129 0.14942,0.323485 0.14942,0.583126 v 1.641339 h 0.44341 v -1.655979 c 0,-0.378848 -0.0796,-0.665473 -0.23873,-0.859797 -0.15905,-0.195956 -0.39385,-0.293834 -0.70393,-0.293834 -0.18799,0 -0.35413,0.04054 -0.49872,0.122175 -0.14301,0.08164 -0.26719,0.205139 -0.37323,0.370069 V 24.81761 Z m -1.92572,0.289292 v 0.779015 h -0.32486 v 0.350381 h 0.32486 v 1.488865 c 0,0.342926 0.0655,0.579717 0.19565,0.710356 0.13014,0.128999 0.36597,0.193871 0.7082,0.193871 h 0.45564 v -0.377645 h -0.45564 c -0.18476,0 -0.30739,-0.03174 -0.36846,-0.09542 -0.0595,-0.06369 -0.0892,-0.207442 -0.0892,-0.431161 v -1.488865 h 0.91341 v -0.350381 h -0.91341 v -0.779005 z m -23.841437,0.71288 c -0.1446,0 -0.293445,0.01632 -0.446077,0.04897 -0.152633,0.03104 -0.309855,0.07818 -0.472127,0.14187 v 0.41652 c 0.134954,-0.07512 0.275413,-0.131566 0.42162,-0.169133 0.1462,-0.03756 0.296623,-0.05604 0.450856,-0.05604 0.242609,0 0.430233,0.05684 0.563581,0.17115 0.134955,0.11268 0.202565,0.272746 0.202565,0.480136 v 0.04392 h -0.621528 c -0.403268,0 -0.707246,0.07851 -0.91129,0.235269 -0.202437,0.156767 -0.30359,0.389605 -0.30359,0.698241 0,0.264539 0.08124,0.476223 0.243507,0.634619 0.16388,0.156767 0.384001,0.234768 0.660342,0.234768 0.218503,0 0.403518,-0.03926 0.554538,-0.11763 0.151027,-0.08001 0.276805,-0.203 0.378021,-0.369566 v 0.41652 h 0.443422 v -1.565606 c 0,-0.418043 -0.09644,-0.729773 -0.289235,-0.935527 -0.192792,-0.205756 -0.484196,-0.308477 -0.874605,-0.308478 z m 7.613074,0 c -0.387196,0 -0.691185,0.127439 -0.911295,0.382187 -0.218503,0.254745 -0.327533,0.607506 -0.327509,1.058211 -2.9e-5,0.449043 0.109006,0.801328 0.327509,1.057704 0.22011,0.254747 0.524099,0.38219 0.911295,0.38219 0.385596,0 0.688523,-0.12744 0.908633,-0.38219 0.22012,-0.256376 0.33018,-0.608661 0.33018,-1.057704 0,-0.450705 -0.11006,-0.803466 -0.33018,-1.058211 -0.22011,-0.254742 -0.523037,-0.382187 -0.908633,-0.382187 z m 6.576833,0 c -0.1944,0 -0.36264,0.03965 -0.50402,0.119655 -0.14139,0.08001 -0.26244,0.204398 -0.36368,0.372593 v -0.426111 h -0.44607 v 2.743473 h 0.44607 v -1.550461 c 0,-0.266175 0.0665,-0.476965 0.19992,-0.632099 0.13335,-0.156763 0.31311,-0.23527 0.53965,-0.23527 0.19119,0 0.3327,0.06346 0.42428,0.19084 0.0916,0.125745 0.13769,0.321111 0.13769,0.585651 v 1.641339 h 0.44555 v -1.550461 c 0,-0.267809 0.0665,-0.479488 0.19991,-0.634624 0.13336,-0.155117 0.31522,-0.232745 0.54497,-0.232745 0.18798,0 0.32738,0.06346 0.41896,0.19084 0.0915,0.127365 0.13771,0.322743 0.13771,0.585651 v 1.641339 h 0.44554 v -1.655979 c 0,-0.369052 -0.0759,-0.653001 -0.22862,-0.852222 -0.15263,-0.200856 -0.3696,-0.301409 -0.65077,-0.301409 -0.20886,0 -0.3902,0.04805 -0.54443,0.144389 -0.15425,0.09635 -0.28685,0.245838 -0.3977,0.448328 -0.0659,-0.192693 -0.16808,-0.33951 -0.30625,-0.440754 -0.13656,-0.101247 -0.30269,-0.151966 -0.49871,-0.151966 z m 4.55699,0 c -0.38719,0 -0.69065,0.127439 -0.91076,0.382187 -0.21849,0.254745 -0.32804,0.607506 -0.32804,1.058211 0,0.449043 0.10955,0.801328 0.32804,1.057704 0.22011,0.254747 0.52357,0.38219 0.91076,0.38219 0.3856,0 0.68853,-0.12744 0.90864,-0.38219 0.22011,-0.256376 0.33018,-0.608661 0.33018,-1.057704 0,-0.450705 -0.11008,-0.803466 -0.33018,-1.058211 -0.22011,-0.254742 -0.52304,-0.382187 -0.90864,-0.382187 z m 3.27514,0 c -0.18798,0 -0.35412,0.04053 -0.49871,0.122175 -0.143,0.08164 -0.26774,0.205139 -0.37377,0.370069 v -0.426111 h -0.44555 v 2.743472 h 0.44555 V 27.07893 c 0,-0.267808 0.0692,-0.479487 0.20735,-0.634623 0.13817,-0.155118 0.32684,-0.232746 0.56623,-0.232746 0.19923,0 0.3486,0.06436 0.4482,0.193365 0.0996,0.129001 0.14942,0.323486 0.14942,0.583127 v 1.641338 h 0.4434 v -1.655978 c 0,-0.378848 -0.0791,-0.665474 -0.23818,-0.859797 -0.15906,-0.195956 -0.39386,-0.293834 -0.70394,-0.293834 z m -20.262723,0.06613 1.091537,2.699045 -0.07923,0.205986 c -0.08193,0.218821 -0.156889,0.357732 -0.224367,0.416519 -0.06749,0.05879 -0.162342,0.08786 -0.284449,0.08786 h -0.259993 v 0.37714 h 0.354101 c 0.199219,0 0.358023,-0.04983 0.476911,-0.149446 0.118894,-0.09962 0.241007,-0.312568 0.366328,-0.639168 l 1.185636,-2.997925 h -0.469469 l -0.843773,2.145704 -0.843239,-2.145704 z m -4.820178,0.311001 c 0.23457,0 0.419584,0.09483 0.554539,0.284243 0.13496,0.187792 0.202571,0.44752 0.202571,0.779017 0,0.331467 -0.06761,0.591612 -0.202571,0.781036 -0.134955,0.187794 -0.319969,0.281717 -0.554539,0.281717 -0.234568,0 -0.419049,-0.09393 -0.554003,-0.281717 -0.133348,-0.189419 -0.19991,-0.449564 -0.19991,-0.781031 0,-0.331496 0.06656,-0.591225 0.19991,-0.779017 0.134954,-0.189416 0.319435,-0.284243 0.554003,-0.284243 z m 10.651603,0.0051 c 0.0074,-1.84e-4 0.01491,0 0.02233,0 0.236182,0 0.423279,0.09519 0.561448,0.286263 0.13817,0.189425 0.20732,0.446984 0.20736,0.77195 0,0.326568 -0.0692,0.584539 -0.20736,0.773967 -0.138169,0.189424 -0.325266,0.283737 -0.561448,0.283737 -0.239389,0 -0.428063,-0.09342 -0.566236,-0.281214 -0.13656,-0.189425 -0.205227,-0.44829 -0.205227,-0.77649 0,-0.328232 0.06918,-0.586682 0.207356,-0.774474 0.133852,-0.183503 0.314446,-0.278005 0.541777,-0.283739 z m 11.133828,0 c 0.007,-1.84e-4 0.0149,0 0.0223,0 0.23617,0 0.42329,0.09519 0.56147,0.286263 0.13816,0.189425 0.20735,0.446984 0.20735,0.77195 0,0.326568 -0.0692,0.584539 -0.20735,0.773967 -0.13818,0.189424 -0.3253,0.283737 -0.56147,0.283737 -0.23939,0 -0.42806,-0.09342 -0.56623,-0.281214 -0.13656,-0.189425 -0.20469,-0.44829 -0.20469,-0.77649 0,-0.328232 0.0686,-0.586682 0.20682,-0.774474 0.13385,-0.183503 0.31444,-0.278005 0.54177,-0.283739 z M 92.194771,27.25013 h 0.44076 v 0.100467 c 0,0.293937 -0.07441,0.529836 -0.223834,0.707832 -0.147812,0.17636 -0.344873,0.264551 -0.59069,0.264551 -0.178338,0 -0.319846,-0.04626 -0.424281,-0.13935 -0.102822,-0.09471 -0.154181,-0.222152 -0.154181,-0.382189 0,-0.200828 0.06915,-0.342827 0.20735,-0.426109 0.138168,-0.08328 0.386599,-0.125205 0.744876,-0.125205 z';
-
-function getRandomHex(bytes = 32) {
- const array = new Uint8Array(bytes);
- window.crypto.getRandomValues(array);
- return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
-}
-
-const isNumber = (value: string): boolean => {
- return !isNaN(value as any);
-}
-
-const getObject = (value: any): any => {
- if (!value) {
- return undefined;
- }
- if (Object.keys(value).length === 0) {
- return undefined;
- }
- if (Object.values(value).filter(Boolean).length === 0) {
- return undefined;
- }
- return value;
-}
-
-const CONFIG_API_FIELDS = [
- {name: 'host', label: 'LABEL.HOST', fieldType: FormFieldType.TEXT, validator: undefined},
- {name: 'port', label: 'LABEL.PORT', fieldType: FormFieldType.NUMBER, validator: isNumber},
- {name: 'web_root', label: 'LABEL.WEB_ROOT', fieldType: FormFieldType.TEXT, validator: undefined},
-
-];
-
-const CONFIG_WEB_UI_FIELDS = [
- {name: 'enabled', label: 'LABEL.WEB_UI', fieldType: FormFieldType.CHECK},
- {name: 'path', label: 'LABEL.WEB_UI_PATH', fieldType: FormFieldType.TEXT, hint: 'HINT.CONFIG.WEB_UI.PATH'},
- {name: 'user_ui_enabled', label: 'LABEL.USER_UI', fieldType: FormFieldType.CHECK},
- {name: 'player_server', label: 'LABEL.PLAYER_SERVER', fieldType: FormFieldType.SINGLE_SELECT},
-];
-
-const CONFIG_WEB_AUTH_FIELDS = [
- {name: 'enabled', label: 'LABEL.WEB_AUTH', fieldType: FormFieldType.CHECK},
- {name: 'issuer', label: 'LABEL.ISSUER', fieldType: FormFieldType.TEXT},
- {
- name: 'secret',
- label: 'LABEL.SECRET',
- fieldType: FormFieldType.TEXT,
- action: {icon: 'Refresh', handler: getRandomHex}
- },
- {name: 'userfile', label: 'LABEL.USERFILE', fieldType: FormFieldType.TEXT},
-];
-
-const CONFIG_FIELDS = [
- {name: 'threads', label: 'LABEL.THREADS', fieldType: FormFieldType.NUMBER, validator: isNumber},
- {name: 'working_dir', label: 'LABEL.WORKING_DIR', fieldType: FormFieldType.TEXT, validator: undefined},
- {name: 'backup_dir', label: 'LABEL.BACKUP_DIR', fieldType: FormFieldType.TEXT, validator: undefined},
- {name: 'update_on_boot', label: 'LABEL.UPDATE_ON_BOOT', fieldType: FormFieldType.CHECK, validator: undefined},
-];
-
-const CONFIG_SCHEDULES_FIELDS = [
- {name: 'schedules', label: 'LABEL.SCHEDULES', fieldType: FormFieldType.SCHEDULE},
-];
-
-const CONFIG_LOG_FIELDS = [
- {name: 'sanitize_sensitive_info', label: 'LABEL.SANITIZE_SENSITIVE_INFO', fieldType: FormFieldType.CHECK},
- {name: 'log_active_user', label: 'LABEL.ACTIVE_USER', hint: 'HINT.CONFIG.LOG.ACTIVE_USER', fieldType: FormFieldType.CHECK},
- {name: 'log_level', label: 'LABEL.LOG_LEVEL', fieldType: FormFieldType.SINGLE_SELECT,
- options:[
- {label: 'info', value: 'info'},
- {label: 'debug', value: 'debug'},
- {label: 'warn', value: 'warn'},
- {label: 'error', value: 'error'},
- {label: 'trace', value: 'trace'},
- ]},
-]
-
-const CONFIG_REVERSE_PROXY_FIELDS = [
- {
- name: 'resource_rewrite_disabled',
- label: 'LABEL.RESOURCE_REWRITE_DISABLE',
- hint: 'HINT.CONFIG.PROXY.RESOURCE_REWRITE_DISABLE',
- fieldType: FormFieldType.CHECK
- },
- {
- name: 'disable_referer_header',
- label: 'LABEL.DISABLE_REFERER_HEADER',
- hint: 'HINT.CONFIG.PROXY.DISABLE_REFERER_HEADER',
- fieldType: FormFieldType.CHECK
- },
-];
-
-const CONFIG_REVERSE_PROXY_STREAM_FIELDS = [
- {name: 'retry', label: 'LABEL.RECONNECT', fieldType: FormFieldType.CHECK},
- {name: 'throttle', label: 'LABEL.THROTTLE', hint: 'HINT.CONFIG.PROXY.THROTTLE', fieldType: FormFieldType.TEXT},
- {name: 'grace_period_millis', label: 'LABEL.GRACE_PERIOD', fieldType: FormFieldType.NUMBER},
- {name: 'grace_period_timeout_secs', label: 'LABEL.GRACE_PERIOD_TIMEOUT', fieldType: FormFieldType.NUMBER},
-
-];
-
-const CONFIG_REVERSE_PROXY_STREAM_BUFFER_FIELDS = [
- {name: 'enabled', label: 'LABEL.BUFFER_ENABLED', fieldType: FormFieldType.CHECK},
- {name: 'size', label: 'LABEL.BUFFER_SIZE', fieldType: FormFieldType.NUMBER},
-];
-
-const CONFIG_REVERSE_PROXY_CACHE_FIELDS = [
- {name: 'enabled', label: 'LABEL.ENABLED', fieldType: FormFieldType.CHECK},
- {name: 'size', label: 'LABEL.SIZE', fieldType: FormFieldType.TEXT},
- {name: 'dir', label: 'LABEL.CACHE_DIR', fieldType: FormFieldType.TEXT},
-];
-
-const CONFIG_REVERSE_PROXY_RATE_LIMIT_FIELDS = [
- {name: 'enabled', label: 'LABEL.ENABLED', fieldType: FormFieldType.CHECK},
- {name: 'period_millis', label: 'LABEL.PERIOD_MILLIS', fieldType: FormFieldType.NUMBER},
- {name: 'burst_size', label: 'LABEL.BURST_SIZE', fieldType: FormFieldType.NUMBER},
-];
-
-const CONFIG_MESSAGING_FIELDS = [
- {
- name: 'notify_on', label: 'LABEL.NOTIFY_ON', fieldType: FormFieldType.MULTI_SELECT, options:
- Object.keys(MessageKind).map((key) => ({value: (MessageKind as any)[key], label: key}))
- },
-];
-
-const CONFIG_TELEGRAM_FIELDS = [
- {name: 'bot_token', label: 'LABEL.BOT_TOKEN', fieldType: FormFieldType.TEXT},
- {name: 'chat_ids', label: 'LABEL.CHAT_IDS', fieldType: FormFieldType.TAGS},
-];
-
-const CONFIG_REST_FIELDS = [
- {name: 'url', label: 'LABEL.URL', fieldType: FormFieldType.TEXT},
-];
-const CONFIG_PUSHOVER_FIELDS = [
- {name: 'url', label: 'LABEL.URL', fieldType: FormFieldType.TEXT},
- {name: 'user', label: 'LABEL.USER', fieldType: FormFieldType.TEXT},
- {name: 'token', label: 'LABEL.TOKEN', fieldType: FormFieldType.TEXT},
-];
-
-const CONFIG_VIDEO_FIELDS = [
- {name: 'web_search', label: 'LABEL.WEB_SEARCH_URL', fieldType: FormFieldType.TEXT},
- {name: 'extensions', label: 'LABEL.EXTENSIONS', fieldType: FormFieldType.TEXT},
-];
-
-const CONFIG_VIDEO_DOWNLOAD_FIELDS = [
- {name: 'headers', label: 'LABEL.HEADERS', fieldType: FormFieldType.MAP},
- {name: 'directory', label: 'LABEL.DIRECTORY', fieldType: FormFieldType.TEXT},
- {name: 'organize_into_directories', label: 'LABEL.ORGANIZE_INTO_DIRECTORIES', fieldType: FormFieldType.CHECK},
- {name: 'episode_pattern', label: 'LABEL.EPISODE_PATTERN', fieldType: FormFieldType.TEXT},
-];
-
-const TABS = [
- {label: 'LABEL.API', key: 'api'},
- {label: 'LABEL.MAIN', key: 'main'},
- {label: 'LABEL.WEB_UI', key: 'web_ui'},
- {label: 'LABEL.PROXY', key: 'proxy'},
- {label: 'LABEL.SCHEDULES', key: 'schedules'},
- {label: 'LABEL.REVERSE_PROXY', key: 'reverseProxy'},
- {label: 'LABEL.MESSAGING', key: 'messaging'},
- {label: 'LABEL.VIDEO', key: 'video'}
-];
-
-const CONFIG_PROXY_FIELDS = [
- {name: 'url', label: 'LABEL.URL', fieldType: FormFieldType.TEXT},
- {name: 'username', label: 'LABEL.USERNAME', fieldType: FormFieldType.TEXT},
- {name: 'password', label: 'LABEL.PASSWORD', fieldType: FormFieldType.TEXT},
-];
-
-const CONFIG_IP_CHECK_FIELDS = [
- {name: 'url', label: 'LABEL.URL', fieldType: FormFieldType.TEXT},
- {name: 'url_ipv4', label: 'LABEL.URL_IPV4', fieldType: FormFieldType.TEXT},
- {name: 'pattern_ipv4', label: 'LABEL.PATTERN_IPV4', fieldType: FormFieldType.TEXT},
- {name: 'url_ipv6', label: 'LABEL.URL_IPV6', fieldType: FormFieldType.TEXT},
- {name: 'pattern_ipv6', label: 'LABEL.PATTERN_IPV6', fieldType: FormFieldType.TEXT},
-];
-
-
-// const translateLabels = (config: any, translateFunc: any) => config.map((c: any) => ({
-// ...c,
-// label: translateFunc(c.label)
-// }));
-
-const translateLabels = (config: any, translateFunc: any) => config;
-
-interface MainConfigViewProps {
- config: ServerConfig;
-}
-
-export default function MainConfigView(props: MainConfigViewProps) {
- const {config} = props;
- const services = useServices();
- const translate = useTranslator();
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
- const [activeTab, setActiveTab] = useState('api');
- const apiConfig = useMemo(() => config?.api || {} as any, [config]);
- const mainConfig = useMemo(() => config, [config]);
- const webUiConfig = useMemo(() => config?.web_ui || {} as any, [config]);
- const webAuthConfig = useMemo(() => config?.web_ui?.auth || {} as any, [config]);
- const messagingConfig = useMemo(() => config?.messaging || {} as any, [config]);
- const telegramConfig = useMemo(() => config?.messaging?.telegram || {} as any, [config]);
- const restConfig = useMemo(() => config?.messaging?.rest || {} as any, [config]);
- const pushoverConfig = useMemo(() => config?.messaging?.pushover || {} as any, [config]);
- const videoConfig = useMemo(() => config?.video || {} as any, [config]);
- const videoDownloadConfig = useMemo(() => config?.video?.download || {} as any, [config]);
- const reverseProxyConfig = useMemo(() => config?.reverse_proxy || {} as any, [config]);
- const reverseProxyStreamConfig = useMemo(() => config?.reverse_proxy?.stream || {} as any, [config]);
- const reverseProxyStreamBufferConfig = useMemo(() => config?.reverse_proxy?.stream?.buffer || {} as any, [config]);
- const reverseProxyCacheConfig = useMemo(() => config?.reverse_proxy?.cache || {} as any, [config]);
- const reverseProxyRateLimitConfig = useMemo(() => config?.reverse_proxy?.rate_limit || {} as any, [config]);
- const logConfig = useMemo(() => config?.log || {} as any, [config]);
- const proxyConfig = useMemo(() => config?.proxy || {} as any, [config]);
- const ipCheckConfig = useMemo(() => config?.ipcheck || {} as any, [config]);
- const configs = useMemo(() => {
- CONFIG_WEB_UI_FIELDS.forEach(def => {
- const serverOptions = config?.api_proxy?.server?.map(serverInfo => ({
- value: serverInfo.name,
- label: serverInfo.name
- }));
- if (def.name === "player_server") {
- (def as any).options = serverOptions;
- }
- });
- const translations: any = {
- api_fields: translateLabels(CONFIG_API_FIELDS, translate),
- fields: translateLabels(CONFIG_FIELDS, translate),
- schedules_fields: translateLabels(CONFIG_SCHEDULES_FIELDS, translate),
- log_fields: translateLabels(CONFIG_LOG_FIELDS, translate),
- web_ui_config_fields: translateLabels(CONFIG_WEB_UI_FIELDS, translate),
- web_auth_config_fields: translateLabels(CONFIG_WEB_AUTH_FIELDS, translate),
- reverse_proxy_fields: translateLabels(CONFIG_REVERSE_PROXY_FIELDS, translate),
- reverse_proxy_stream_fields: translateLabels(CONFIG_REVERSE_PROXY_STREAM_FIELDS, translate),
- reverse_proxy_stream_buffer_fields: translateLabels(CONFIG_REVERSE_PROXY_STREAM_BUFFER_FIELDS, translate),
- reverse_proxy_cache_fields: translateLabels(CONFIG_REVERSE_PROXY_CACHE_FIELDS, translate),
- reverse_proxy_rate_limit_fields: translateLabels(CONFIG_REVERSE_PROXY_RATE_LIMIT_FIELDS, translate),
- messaging_fields: translateLabels(CONFIG_MESSAGING_FIELDS, translate),
- telegram_fields: translateLabels(CONFIG_TELEGRAM_FIELDS, translate),
- rest_fields: translateLabels(CONFIG_REST_FIELDS, translate),
- pushover_fields: translateLabels(CONFIG_PUSHOVER_FIELDS, translate),
- video_fields: translateLabels(CONFIG_VIDEO_FIELDS, translate),
- video_download_fields: translateLabels(CONFIG_VIDEO_DOWNLOAD_FIELDS, translate),
- proxy_config_fields: translateLabels(CONFIG_PROXY_FIELDS, translate),
- ipcheck_config_fields: translateLabels(CONFIG_IP_CHECK_FIELDS, translate),
- tabs: translateLabels(TABS, translate),
- }
- return translations;
- }, [translate, config?.api_proxy?.server]);
-
- const scheduleImage = useMemo(() =>
-
- , []);
-
- const handleSave = useCallback(() => {
- if (mainConfig && apiConfig) {
- let telegramConfigAvailable = telegramConfig?.bot_token?.trim().length && telegramConfig?.chat_ids?.length;
- let restConfigAvailable = restConfig?.url?.trim().length;
- let pushoverConfigAvailable = pushoverConfig?.user?.trim().length && pushoverConfig?.token?.length;
- const cfgMessaging: MessagingConfig = {
- notify_on: messagingConfig.notify_on,
- telegram: telegramConfigAvailable ? getObject(telegramConfig) : undefined,
- rest: restConfigAvailable ? getObject(restConfig) : undefined,
- pushover: pushoverConfigAvailable ? getObject(pushoverConfig) : undefined,
- };
-
-
- let reverseProxyStreamConfigAvailable = getObject(reverseProxyStreamConfig) != undefined;
- let cfgReverseProxyStreamBuffer: StreamConfig = reverseProxyStreamConfigAvailable ? ({
- retry: reverseProxyStreamConfig.retry,
- throttle: reverseProxyStreamConfig.throttle?.trim().length ? reverseProxyStreamConfig.throttle : undefined,
- grace_period_millis: reverseProxyStreamConfig.grace_period_millis,
- grace_period_timeout_secs: reverseProxyStreamConfig.grace_period_timeout_secs,
- buffer: {
- enabled: reverseProxyStreamBufferConfig.enabled,
- size: reverseProxyStreamBufferConfig.size ?? undefined,
- }
- }): undefined;
-
- const reverseProxyConfigAvailable = getObject(reverseProxyConfig) ||
- getObject(cfgReverseProxyStreamBuffer) ||
- getObject(reverseProxyCacheConfig) ||
- getObject(reverseProxyRateLimitConfig);
- const cfgReverseProxy: ReverseProxyConfig = reverseProxyConfigAvailable ? {
- resource_rewrite_disabled: reverseProxyConfig.resource_rewrite_disabled,
- disable_referer_header: reverseProxyConfig.disable_referer_header,
- stream: getObject(cfgReverseProxyStreamBuffer),
- cache: getObject(reverseProxyCacheConfig),
- rate_limit: getObject(reverseProxyRateLimitConfig),
- } : undefined;
-
- const cfgWebUiConfig: WebUiConfig = {
- enabled: webUiConfig.enabled ?? false,
- user_ui_enabled: webUiConfig.user_ui_enabled ?? false,
- path: webUiConfig.path,
- auth: webAuthConfig,
- }
-
- const cfgProxyConfig: ProxyConfig = {
- url: proxyConfig.url,
- username: proxyConfig.username,
- password: proxyConfig.password,
- }
-
- const cfgIpCheckConfig: IpCheckConfig = {
- url: ipCheckConfig.url,
- url_ipv4: ipCheckConfig.url_ipv4,
- url_ipv6: ipCheckConfig.url_ipv6,
- pattern_ipv4: ipCheckConfig.pattern_ipv4,
- pattern_ipv6: ipCheckConfig.pattern_ipv6,
- }
-
- const cfgVideo = getObject(videoConfig) && getObject(videoDownloadConfig) ? {
- ...videoConfig,
- download: getObject(videoDownloadConfig)
- }: undefined;
-
- const cfgLog = {
- ...logConfig
- };
-
- const cfgMain: ServerMainConfig = {
- api: apiConfig,
- working_dir: mainConfig.working_dir,
- backup_dir: mainConfig.backup_dir,
- schedules: mainConfig.schedules?.filter(s => s.schedule?.trim().length),
- threads: mainConfig.threads,
- messaging: getObject(cfgMessaging),
- reverse_proxy: getObject(cfgReverseProxy),
- video: getObject(cfgVideo),
- log: getObject(cfgLog),
- update_on_boot: mainConfig.update_on_boot,
- web_ui: getObject(cfgWebUiConfig),
- proxy: getObject(cfgProxyConfig),
- ipcheck: getObject(cfgIpCheckConfig),
- };
-
- services.config().saveMainConfig(cfgMain).subscribe({
- next: () => enqueueSnackbar(translate('MESSAGES.SAVE.MAIN_CONFIG.SUCCESS'), {variant: 'success'}),
- error: (err: any) => enqueueSnackbar(translate('MESSAGES.SAVE.MAIN_CONFIG.FAIL'), {variant: 'error'})
- });
- }
- }, [mainConfig, apiConfig, videoConfig, messagingConfig, telegramConfig, restConfig, pushoverConfig,
- videoDownloadConfig, reverseProxyConfig, enqueueSnackbar, services, logConfig,
- reverseProxyRateLimitConfig, reverseProxyCacheConfig, reverseProxyStreamConfig, reverseProxyStreamBufferConfig,
- webUiConfig, webAuthConfig, proxyConfig, ipCheckConfig,
- translate]);
-
- const handleTabChange = useCallback((tab: string) => {
- setActiveTab(tab);
- }, []);
-
-
- return
-
{translate('LABEL.CONFIG')}
- {translate('LABEL.SAVE')}
-
-
-
-
-
-
- {translate('INFO.WEB_ROOT')}
-
-
-
-
-
{translate('LABEL.LOG')}
-
-
- {translate('INFO.THREADS')}
-
-
-
-
- {translate('LABEL.WEB_AUTH')}
-
-
-
- {translate('LABEL.PROXY')}
-
- {translate('LABEL.IP_CHECK')}
-
-
-
-
-
- {translate('INFO.SCHEDULE_EXAMPLE')}:
- {scheduleImage}
-
-
-
-
- {translate('LABEL.STREAM')}
-
-
- {translate('LABEL.CACHE')}
-
- {translate('LABEL.RATE_LIMIT')}
-
-
-
-
- {translate('LABEL.TELEGRAM')}
-
- {translate('LABEL.REST')}
-
- {translate('LABEL.PUSHOVER')}
-
-
-
-
-
-
-
{translate('LABEL.EXAMPLES')}:
-
{translate('LABEL.WEB_SEARCH_URL')}: {'https://www.imdb.com/search/title/?title={}'}
-
{translate('LABEL.EPISODE_PATTERN')}: {'.*(?P[Ss]\\d{1,2}(.*?)[Ee]\\d{1,2}).*'}
-
-
-
-
-
-
- {getIconByName('Warn')}
- {translate('INFO.RESTART_TO_APPLY_CHANGES')}
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/map-editor/map-editor.scss b/frontend/src/component/map-editor/map-editor.scss
deleted file mode 100644
index bfa3cdfeb..000000000
--- a/frontend/src/component/map-editor/map-editor.scss
+++ /dev/null
@@ -1,72 +0,0 @@
-@use '../../scss/common';
-
-.map-editor {
- @include common.container_vert();
- flex: 1 1 auto;
- overflow: hidden;
- border: 1px solid var(--border-color);
- border-radius: 8px;
-
- &__toolbar {
- flex: 0 0 auto;
- span {
- @include common.iconButton();
- }
- }
-
- &__table {
- @include common.container_vert_no_padding();
- }
-
- &__row {
- display: flex;
- flex-flow: row nowrap;
- gap: 20px;
- flex: 1 1 auto;
-
- }
-
- &__col {
- display: flex;
- align-items: center;
- }
-
- &__col-key, &__col-value {
- user-select: none;
- box-sizing: border-box;
- min-width: 12rem;
- border: 1px solid var(--border-color);
- padding: 0 4px;
- height: 2rem;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
-
- &__col-value {
- flex: 1 1 auto;
- }
-
- &__input {
- display: flex;
- flex-flow: row wrap;
- gap: 20px;
- align-items: center;
-
- &-key, &-value {
- box-sizing: border-box;
- min-width: 12rem;
- display: flex;
- gap: 10px;
- input {
- padding: 4px 0;
- }
- }
-
- button {
- @include common.textButton();
- }
- }
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/map-editor/map-editor.tsx b/frontend/src/component/map-editor/map-editor.tsx
deleted file mode 100644
index e823dfb82..000000000
--- a/frontend/src/component/map-editor/map-editor.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import './map-editor.scss';
-
-import React, {useCallback, useEffect, useMemo, useRef, useState} from "react";
-import {genUuid} from "../../utils/uuid";
-import {getIconByName} from "../../icons/icons";
-
-interface KeyValue {
- key: string;
- value: string;
-}
-
-const mapToList = (data: Record) => data ? Object.keys(data).map(key => ({key, value: data[key]})) : [];
-const listToMap = (data: KeyValue[]) => data?.reduce((acc: any, curVal) => {acc[curVal.key] = curVal.value; return acc;}, {});
-const containsKey = (key: string, data: KeyValue[]) => data?.find((keyValue: KeyValue) => keyValue.key === key);
-
-interface MapEditorProps {
- name: string;
- values: Record;
- onChange: (name: string, values: Record) => void;
-}
-
-export default function MapEditor(props: MapEditorProps) {
- const {name, values, onChange} = props;
- const uuid = useMemo(() => genUuid(), []);
- const keyRef = useRef(undefined);
- const valRef = useRef(undefined);
- const [data, setData] = useState([]);
-
- useEffect(() => {
- if (values) {
- setData(mapToList(values));
- }
- }, [values]);
-
- const handleHeaderRemove = useCallback((event: any) => {
- const key = event.target.dataset.key;
- if (key) {
- setData(data => {
- const newData = data?.filter((keyValue: KeyValue) => keyValue.key !== key);
- onChange(name, listToMap(newData));
- return newData;
- });
- }
-
- }, [name, onChange]);
-
- const handleAddKeyValue = useCallback((event: any) => {
- let key = keyRef.current?.value?.trim();
- let value = valRef.current?.value?.trim();
- if (key.length> 0 && value.length > 0) {
- setData(data => {
- let result = data;
- if (!containsKey(key, data)) {
- data.push({key, value});
- result = [...data];
- }
- onChange(name, listToMap(result));
- return result;
- });
- }
- }, [name, onChange]);
-
- return
-
-
- {data?.map((keyValue: KeyValue, idx) =>
-
-
-
- {getIconByName('Delete')}
-
-
-
{keyValue.key}
-
{keyValue.value}
-
- )}
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/panel/panel.scss b/frontend/src/component/panel/panel.scss
deleted file mode 100644
index 150e858ff..000000000
--- a/frontend/src/component/panel/panel.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-@use '../../scss/common';
-
-.panel {
- @include common.container_vert_no_padding();
- overflow: hidden;
-}
-.hidden {
- display: none;
-}
\ No newline at end of file
diff --git a/frontend/src/component/panel/panel.tsx b/frontend/src/component/panel/panel.tsx
deleted file mode 100644
index 1e87d2de6..000000000
--- a/frontend/src/component/panel/panel.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React, {ReactNode} from "react";
-import './panel.scss';
-
-interface PanelProps {
- value: any;
- active: any;
- children: ReactNode;
-}
-
-export default function Panel(props: PanelProps) {
- const {value, active, children} = props;
- return {children}
-}
\ No newline at end of file
diff --git a/frontend/src/component/playlist-browser/playlist-browser.scss b/frontend/src/component/playlist-browser/playlist-browser.scss
deleted file mode 100644
index c09ad4c45..000000000
--- a/frontend/src/component/playlist-browser/playlist-browser.scss
+++ /dev/null
@@ -1,30 +0,0 @@
-.playlist-browser {
- &__content {
- display: flex;
- flex-flow: column;
- flex: 1 1 auto;
- box-sizing: border-box;
- overflow: hidden;
- max-width: 100%;
- background-color: inherit;
- padding: 10px;
-
- .playlist-viewer {
- background-color: var(--card-background-color);
- }
- }
-
- &__toolbar {
- display: flex;
- flex-flow: row wrap;
- flex: 0 0 auto;
- box-sizing: border-box;
- overflow: hidden;
- background-color: inherit;
- gap: 12px;
-
- .source-selector {
- flex: 1 1 auto;
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/playlist-browser/playlist-browser.tsx b/frontend/src/component/playlist-browser/playlist-browser.tsx
deleted file mode 100644
index 0de622fab..000000000
--- a/frontend/src/component/playlist-browser/playlist-browser.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import React, {useCallback, useMemo, useState} from 'react';
-import './playlist-browser.scss';
-import SourceSelector from "..//source-selector/source-selector";
-import PlaylistViewer, {SearchRequest} from "../playlist-viewer/playlist-viewer";
-import {useSnackbar} from 'notistack';
-import {useServices} from "../../provider/service-provider";
-import Progress from '..//progress/progress';
-import PlaylistFilter from "../playlist-filter/playlist-filter";
-import {finalize, first, noop, Subject} from "rxjs";
-import ClipboardViewer from "../clipboard-viewer/clipboard-viewer";
-import Sidebar from "../sidebar/sidebar";
-import {PlaylistRequest} from "../../model/playlist-request";
-import ServerConfig from "../../model/server-config";
-import FileDownload from "../file-download/file-download";
-import {FileDownloadInfo} from "../../model/file-download";
-import useTranslator from "../../hook/use-translator";
-import {EmptyPlaylistCategories, PlaylistCategories, PlaylistItem} from "../../model/playlist";
-import LoadingIndicator from "../loading-indicator/loading-indicator";
-
-/* eslint-disable @typescript-eslint/no-empty-interface */
-interface PlaylistBrowserProps {
- config: ServerConfig
-}
-
-export default function PlaylistBrowser(props: PlaylistBrowserProps) {
- const {config} = props;
- const searchChannel = useMemo>(() => new Subject(), []);
- const services = useServices();
- const [progress, setProgress] = useState(false);
- const [playlist, setPlaylist] = useState(EmptyPlaylistCategories);
- const [playlistRequest, setPlaylistRequest] = useState(undefined);
- const clipboardChannel = useMemo>(() => new Subject(), []);
- const translate = useTranslator();
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
- const videoChannel = useMemo(() => new Subject<[PlaylistItem, PlaylistRequest]>(), []);
- const handleSourceDownload = useCallback((req: PlaylistRequest) => {
- setProgress(true);
- services.playlist().getPlaylistCategories(req).pipe(first(), finalize(() => setProgress(false))).subscribe({
- next: (pl: PlaylistCategories) => {
- enqueueSnackbar(translate("MESSAGES.DOWNLOAD.PLAYLIST.SUCCESS"), {variant: 'success'})
- setPlaylist(pl);
- setPlaylistRequest(req);
- },
- error: (err) => {
- enqueueSnackbar(translate("MESSAGES.DOWNLOAD.PLAYLIST.FAIL"), {variant: 'error'});
- },
- });
- }, [enqueueSnackbar, services, translate]);
-
- const handleFilter = useCallback((filter: string, regexp: boolean): void => {
- if (!regexp) {
- filter = filter.toLowerCase();
- }
- searchChannel.next({filter, regexp});
- }, [searchChannel]);
-
- const handleProgress = useCallback((value: boolean) => {
- setProgress(value);
- }, []);
-
- const handleOnPlay = useCallback((playlistItem: PlaylistItem): void => {
- videoChannel.next([playlistItem, playlistRequest]);
- }, [videoChannel, playlistRequest]);
-
- const handleOnCopy = useCallback((playlistItem: PlaylistItem): void => {
- clipboardChannel.next(playlistItem.url);
- }, [clipboardChannel]);
-
- const handleOnDownload = useCallback((playlistItem: PlaylistItem): void => {
- let filename = playlistItem.title;
- const parts = playlistItem.url.split('.');
- let ext = undefined;
- if (parts.length > 1) {
- ext = parts[parts.length - 1];
- }
-
- if (config.video?.extensions?.includes(ext)) {
- services.file().download({
- url: playlistItem.url,
- filename: filename + '.' + ext
- }).pipe(first()).subscribe({
- next: (_: FileDownloadInfo) => {
- },
- error: _ => enqueueSnackbar(translate("MESSAGES.DOWNLOAD.FAIL"), {variant: 'error'}),
- complete: noop,
- });
- } else {
- enqueueSnackbar(translate("MESSAGES.INVALID_FILETYPE"), {variant: 'error'})
- }
- }, [config, enqueueSnackbar, services, translate]);
-
- const handleOnWebSearch = useCallback((playlistItem: PlaylistItem): void => {
- if (playlistItem) {
- let title = playlistItem.title;
- let pattern = config.video.download?.episode_pattern;
- if (pattern) {
- pattern = pattern.replace('?P', '?');
- const matches = title.match(pattern);
- if (matches?.groups?.episode) {
- const idx = title.indexOf(matches?.groups?.episode);
- title = title.substring(0, idx).trim();
- }
- }
- const dateSuffixMatch = title.match(/(.*?).\(\d+\)/);
- if (dateSuffixMatch?.length > 1) {
- title = dateSuffixMatch[1];
- }
-
- const url = config.video.web_search.replace("{}", title);
- window.open(url, "imdb");
- }
- }, [config]);
-
-
- return (<>
-
-
-
-
- >);
-}
diff --git a/frontend/src/component/playlist-filter/playlist-filter.scss b/frontend/src/component/playlist-filter/playlist-filter.scss
deleted file mode 100755
index 301d60d70..000000000
--- a/frontend/src/component/playlist-filter/playlist-filter.scss
+++ /dev/null
@@ -1,30 +0,0 @@
-@use "../../scss/common";
-
-.playlist-filter {
- overflow: hidden;
- display: flex;
- flex-flow: row nowrap;
- padding: 10px 0 10px 0;
- max-height: 2.5rem;
- font-size: 0.5rem;
- flex: 1 1 auto;
- max-width: 400px;
- background-color: inherit;
-
- .input-field {
- flex: 1 1 auto;
- }
-
- &__option-active {
- @include common.activeOptionButton();
- }
-
- &__error-message {
- display: flex;
- flex-flow: row;
- color: var(--attention-color);
- white-space: nowrap;
- align-items: center;
- margin-left: 8px;
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/playlist-filter/playlist-filter.tsx b/frontend/src/component/playlist-filter/playlist-filter.tsx
deleted file mode 100755
index d44028874..000000000
--- a/frontend/src/component/playlist-filter/playlist-filter.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React, {KeyboardEvent, useCallback, useEffect, useRef, useState} from "react";
-import './playlist-filter.scss';
-import {getIconByName} from "../../icons/icons";
-import InputField from "../input-field/input-field";
-import useTranslator from "../../hook/use-translator";
-
-function isValidRegExp(pattern: string): boolean {
- try {
- new RegExp(pattern);
- return true;
- } catch (e) {
- return false;
- }
-}
-
-interface PlaylistFilterProps {
- options?: { filter: string, regexp: boolean }
- onFilter: (filter: string, regexp: boolean) => void;
-}
-
-export default function PlaylistFilter(props: PlaylistFilterProps) {
- const {onFilter, options} = props;
- const translate = useTranslator();
- const textField = useRef(undefined);
- const [useRegexp, setUseRegexp] = useState(false);
- const [errorMsg, setErrorMsg] = useState(undefined);
-
- useEffect(()=> {
- setUseRegexp(options?.regexp ?? false);
- if (textField.current) {
- textField.current.value = options?.filter ?? '';
- }
- }, [options?.regexp, options?.filter]);
-
- const handleSearch = useCallback(() => {
- const value = textField.current.value;
- setErrorMsg(undefined);
- if (useRegexp && value?.trim() !== '') {
- if (isValidRegExp(value)) {
- onFilter(value, useRegexp);
- } else {
- setErrorMsg(translate('MESSAGES.INVALID_REGEXP'));
- }
- } else {
- onFilter(value, useRegexp);
- }
- }, [onFilter, useRegexp, translate]);
-
- const handleClear = useCallback(() => {
- textField.current.value = '';
- handleSearch();
- }, [handleSearch]);
-
- const handleKeyPress = useCallback((event: KeyboardEvent) => {
- if (event.key === 'Enter') {
- handleSearch();
- }
- }, [handleSearch]);
-
- const handleRegexp = useCallback(() => {
- setUseRegexp(value => !value);
- }, []);
-
- return
-
-
- {getIconByName('Regexp')}
- {getIconByName('ClearSearch')}
- {getIconByName('Search')}
-
- {errorMsg &&
{errorMsg}
}
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/playlist-gallery/playlist-gallery.scss b/frontend/src/component/playlist-gallery/playlist-gallery.scss
deleted file mode 100644
index 64806f689..000000000
--- a/frontend/src/component/playlist-gallery/playlist-gallery.scss
+++ /dev/null
@@ -1,222 +0,0 @@
-@use '../../scss/common';
-
-$card-width: 180px;
-$card-height: 200px;
-
-$category-size: 96px;
-
-.playlist-gallery {
- display: flex;
- flex-flow: column;
- overflow: hidden;
- flex: 1 1 auto;
- gap: 8px;
-
- &__breadcrumbs {
- flex-flow: row;
- align-items: center;
- //height: 2em;
- //min-height: 2em;
- display: flex;
- //overflow: hidden;
- gap: 4px;
- border-radius: 5px;
- background-color: var(--breadcrumbs-background-color);
-
- button {
- @include common.iconButton();
- }
-
- span {
- cursor: pointer;
- background-color: var(--breadcrumb-background-color);
- color: var(--breadcrumb-color);
- line-height: 1.8em;
- text-align: center;
- padding: 0 4px 0 20px;
- position: relative;
-
- &:after {
- background: var(--breadcrumb-background-color);
- bottom: 0;
- clip-path: polygon(50% 50%, -50% -50%, 0 100%);
- content: '';
- left: 100%;
- position: absolute;
- top: 0;
- width: 2em;
- z-index: 1;
- }
-
- &:before {
- background: var(--breadcrumbs-background-color);
- bottom: 0;
- clip-path: polygon(-50% -50%, 50% 50%, 0 100%);
- content: '';
- left: 0;
- position: absolute;
- top: 0;
- width: 2em;
- }
-
- &:last-child {
- border-right: none;
-
- &:after {
- background: var(--breadcrumb-active-background-color);
- }
-
- background: var(--breadcrumb-active-background-color);
- color: var(--breadcrumb-active-color);
- }
-
- &:not(:last-child) {
- &:hover {
- &:after {
- background: var(--text-button-hover-background-color);
- }
-
- background: var(--text-button-hover-background-color);
- color: var(--text-button-hover-color);
- }
- }
- }
- }
-
- &__categories {
- display: flex;
- flex-flow: row;
- justify-content: center;
- align-items: center;
- flex: 1 1 auto;
- gap: 12px;
- }
-
- &__category {
- display: flex;
- flex-flow: row;
- justify-content: center;
- align-items: center;
- min-height: $category-size;
- min-width: $category-size;
- height: $category-size;
- width: $category-size;
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- padding: 10px;
- cursor: pointer;
-
- svg {
- width: 100%;
- height: 100%;
- }
-
- &:hover {
- background-color: var(--icon-button-hover-background-color);
- color: var(--icon-button-hover-color);
- }
- }
-
- &__content {
- display: flex;
- flex-flow: column;
- width: 100%;
- border-radius: var(--border-radius);
- overflow: hidden;
- }
-
- .channel-Live {
- }
-
- .channel-Video {
- }
-
- .channel-Series {
- }
-
- $grid-cell-width: 300px;
-
- &__channels,
- &__groups {
- display: grid;
- grid-gap: 10px;
- grid-template-columns: repeat(auto-fill, minmax($grid-cell-width, 1fr));
- overflow-x: hidden;
- overflow-y: auto;
- gap: 8px;
- padding: 4px;
- background-color: var(--background-color);
- width: 100%;
- box-sizing: border-box;
- border-radius: var(--border-radius);
-
- &-count {
- margin-left: 10px;
- }
-
- &-channel,
- &-group {
- border: 1px solid var(--border-color);
- padding: 12px 8px;
- border-radius: var(--border-radius);
- background-color: var(--card-background-color);
- color: var(--card-color);
- box-sizing: border-box;
- gap: 8px;
- cursor: pointer;
- overflow: hidden;
- text-overflow: ellipsis;
- min-height: 3em;
-
- &:hover {
- color: var(--icon-button-hover-color);
- background-color: var(--icon-button-hover-background-color);
- }
- }
- }
-
- &__channels {
- &-channel {
- position: relative;
- height: 240px;
- width: $grid-cell-width;
-
- &-logo {
- display: flex;
- justify-content: center;
- align-content: center;
- position: absolute;
- left: 12px;
- top: 12px;
- bottom: 12px;
- right: 12px;
- img {
- fill: white;
- aspect-ratio: 1 / 1;
- }
- }
-
- &-name {
- position: absolute;
- left: 0;
- bottom: 0;
- width: $grid-cell-width;
- height: 4em;
- padding-top: 10px;
- text-align: center;
- background-color: #00000099;
- font-weight: bold;
- font-size: 1.2em;
- }
- }
- }
-
- &__groups {
- &-group {
- display: flex;
- flex-flow: row nowrap;
- height: 48px;
- white-space: nowrap;
- }
- }
-}
diff --git a/frontend/src/component/playlist-gallery/playlist-gallery.tsx b/frontend/src/component/playlist-gallery/playlist-gallery.tsx
deleted file mode 100644
index 7df16d778..000000000
--- a/frontend/src/component/playlist-gallery/playlist-gallery.tsx
+++ /dev/null
@@ -1,371 +0,0 @@
-import ServerConfig from "../../model/server-config";
-import {PlaylistCategories, PlaylistCategory, PlaylistGroup, PlaylistItem, XtreamCluster} from "../../model/playlist";
-import './playlist-gallery.scss';
-import React, {ReactNode, useCallback, useEffect, useState} from "react";
-import {getIconByName} from "../../icons/icons";
-import useTranslator from "../../hook/use-translator";
-
-const getLogo = (item: PlaylistItem): string | undefined => {
- if (item.logo_small && item.logo_small.length > 0) {
- return item.logo_small;
- }
- if (item.logo && item.logo.length > 0) {
- return item.logo;
- }
- return undefined;
-}
-
-interface PlaylistGalleryProps {
- serverConfig: ServerConfig;
- data: PlaylistCategories;
- onCopy: (playlistItem: PlaylistItem) => void;
- onPlay?: (playlistItem: PlaylistItem) => void;
- onDownload?: (playlistItem: PlaylistItem) => void;
- onWebSearch?: (playlistItem: PlaylistItem) => void;
-
-}
-
-export default function PlaylistGallery(props: PlaylistGalleryProps) {
-
- const {data, onCopy, onPlay, onWebSearch, onDownload, serverConfig} = props;
- // const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
- const translate = useTranslator();
- const [selectedItem, setSelectedItem] = useState();
- const [selectedGroup, setSelectedGroup] = useState();
- const [selectedCategory, setSelectedCategory] = useState();
-
- // useEffect(() => {
- // if (category) {
- // setGroup((data as any)?.[category])
- // }
- // }, [data, category]);
- //
- // const getPlaylistItemById = useCallback((itemId: string): PlaylistItem => {
- // // const id = parseInt(itemId);
- // // if (data && !isNaN(id)) {
- // // for (let i = 0, len = data.length; i < len; i++) {
- // // const group = data[i];
- // // for (let j = 0, clen = group.channels.length; j < clen; j++) {
- // // const plitem = group.channels[j];
- // // // eslint-disable-next-line eqeqeq
- // // if (plitem.id == id) {
- // // return plitem;
- // // }
- // // }
- // // }
- // // }
- // return undefined;
- // }, [data]);
- //
- // const handleClipboardUrl = useCallback((e: any) => {
- // const item = getPlaylistItemById(e.target.dataset.item);
- // if (item) {
- // onCopy(item);
- // copyToClipboard(item.header.url).pipe(first()).subscribe({
- // next: value => enqueueSnackbar(value ? "URL copied to clipboard" : "Copy to clipboard failed!", {variant: value ? 'success' : 'error'}),
- // error: _ => enqueueSnackbar("Copy to clipboard failed!", {variant: 'error'}),
- // complete: noop,
- // });
- // }
- // }, [enqueueSnackbar, getPlaylistItemById, onCopy]);
-
- // const handleWebSearch = useCallback((e: any) => {
- // if (onWebSearch) {
- // const item = getPlaylistItemById(e.target.dataset.item);
- // if (item) {
- // onWebSearch(item);
- // }
- // }
- // }, [getPlaylistItemById, onWebSearch]);
-
- // const handleDownloadUrl = useCallback((e: any) => {
- // if (onDownload) {
- // if (!serverConfig.video.download?.directory) {
- // enqueueSnackbar("Please updated the server configuration and add video.download directory and headers!", {variant: 'error'})
- // } else {
- // const item = getPlaylistItemById(e.target.dataset.item);
- // if (item) {
- // onDownload(item);
- // }
- // }
- // }
- // }, [serverConfig, enqueueSnackbar, getPlaylistItemById, onDownload]);
-
- // const handlePlayUrl = useCallback((e: any) => {
- // if (onPlay) {
- // const item = getPlaylistItemById(e.target.dataset.item);
- // if (item) {
- // onPlay(item);
- // }
- // }
- // }, [onPlay, getPlaylistItemById]);
- //
- // const isVideoFile = useCallback((entry: PlaylistItem): boolean => {
- // if (serverConfig?.video?.extensions && entry.header.url) {
- // for (const ext of serverConfig.video.extensions) {
- // if (entry.header.url.endsWith(ext)) {
- // return true;
- // }
- // }
- // }
- // return false;
- // }, [serverConfig]);
-
- // const handleGroupClick = useCallback((event: any) => {
- // // const key = parseInt(event.target.dataset.group);
- // // const group = data.find(plg => plg.id === key);
- // // setShowcaseGroup(group);
- // }, [data]);
- //
- // const renderGroup = useCallback((group: PlaylistGroup): React.ReactNode => {
- // return
- // {group.name}
- //
- // }, [handleGroupClick, group]);
- //
- // const renderPlaylistItem = useCallback((playlistItem: PlaylistItem) => {
- // return
- //
- // {playlistItem.header.name}
- //
- //
- //
- //
- //
- //
- // {getIconByName('LinkRounded')}
- //
- //
- // {getIconByName('PlayArrow')}
- //
- // {isVideoFile(playlistItem) &&
- // <>
- //
- // {getIconByName('Download')}
- //
- // {serverConfig.video?.web_search &&
- //
- // {getIconByName('WebSearch')}
- //
- // }
- // >
- // }
- //
- //
- // }, [handleClipboardUrl, handleDownloadUrl, handlePlayUrl, handleWebSearch, isVideoFile, serverConfig]);
-
- // const renderShowcase = useCallback(() => {
- // if (!group) {
- // return
- // }
- // return group.channels?.map(renderPlaylistItem);
- // }, [group, renderPlaylistItem]);
-
- // useEffect(() => {
- // if (selectedItem) {
- //
- // }
- // }, [selectedItem]);
-
- useEffect(() => {
- if (selectedCategory) {
- let groups = (data as any)[selectedCategory];
- if (!groups?.length) {
- setSelectedItem(undefined);
- setSelectedGroup(undefined);
- setSelectedCategory(undefined);
- }
- if (selectedGroup && groups?.length) {
- let group = groups.find((g: PlaylistGroup) => g.id === selectedGroup.id);
- if (!group) {
- setSelectedItem(undefined);
- setSelectedGroup(undefined);
- }
- if (group && selectedItem) {
- let channel = group.channels.find((c: PlaylistItem) => c.id === selectedItem.id);
- if (!channel) {
- setSelectedItem(undefined);
- }
- }
- }
- }
- }, [data, selectedCategory, selectedGroup, selectedItem])
-
- const handleCategorySelect = useCallback((evt: any) => {
- let category = evt.target.dataset.category;
- setSelectedCategory(category);
- }, []);
-
- const handleGroupSelect = useCallback((evt: any) => {
- let category = evt.target.dataset.category;
- let groupId = evt.target.dataset.group;
- let groups: PlaylistGroup[] = (data as any)[category];
- if (groups) {
- // eslint-disable-next-line eqeqeq
- let group = groups.find(grp => grp.id == groupId);
- setSelectedGroup(group)
- }
- }, [data]);
-
- const handleChannelSelect = useCallback((evt: any) => {
- let category = evt.target.dataset.category;
- if (category !== PlaylistCategory.LIVE) {
- let groupId = evt.target.dataset.group;
- let channelId = evt.target.dataset.channel;
- let groups: PlaylistGroup[] = (data as any)[category];
- if (groups) {
- // eslint-disable-next-line eqeqeq
- let group = groups.find(grp => grp.id == groupId);
- if (group) {
- // eslint-disable-next-line eqeqeq
- let channel = group.channels.find(channel => channel.id == channelId);
- setSelectedItem(channel)
- }
- }
- }
- }, [data]);
-
- const handleBack = useCallback(() => {
- if (selectedItem) {
- setSelectedItem(undefined);
- return;
- }
- if (selectedGroup) {
- setSelectedGroup(undefined);
- return;
- }
- if (selectedCategory) {
- setSelectedCategory(undefined);
- return;
- }
- }, [selectedCategory, selectedGroup, selectedItem]);
-
-
- const renderItem = useCallback((item: PlaylistItem): ReactNode => {
- if (item.xtream_cluster === XtreamCluster.Video) {
- return onPlay?.(item)} className="playlist-gallery">
- {JSON.stringify(item)}
-
{item.title}
-
-
;
-
- }
- return {JSON.stringify(item)}
;
- }, [onPlay]);
-
- const renderItems = useCallback((category: PlaylistCategory, group: PlaylistGroup): ReactNode => {
- return
- {group.channels.map(channel => (
-
-
onPlay?.(channel)}>
- {getLogo(channel) &&
(e.target.onerror=null) || (e.target.src="assets/missing-logo.svg")}/>}
-
-
- {channel.title}
-
-
- ))}
-
;
- }, [handleChannelSelect, onPlay]);
-
- const renderGroups = useCallback((category: PlaylistCategory, groups: PlaylistGroup[]): ReactNode => {
- return
- {groups.map(group => (
-
- {group.name}
- {(group.channels?.length ?? 0)}
-
- ))}
-
;
- }, [handleGroupSelect]);
-
- const renderCategories = useCallback((categories: PlaylistCategories) => {
- const newNode = (tooltip: string, cat: string, icon: string) => {
- return {getIconByName(icon)}
;
- };
- let nodes = [];
- if (categories?.live?.length) {
- nodes.push(newNode('LABEL.LIVE', 'live', 'Live'));
- }
- if (categories?.vod?.length) {
- nodes.push(newNode('LABEL.VOD', 'vod', 'VOD'));
- }
- if (categories?.series?.length) {
- nodes.push(newNode('LABEL.SERIES', 'series', 'Series'));
- }
- if (nodes.length) {
- return {nodes}
- }
- return {translate("MESSAGES.NO_CONTENT")}
- }, [translate, handleCategorySelect]);
-
- const renderSelectionContent = useCallback((): ReactNode => {
- if (selectedItem) {
- return renderItem(selectedItem);
- }
- if (selectedGroup) {
- return renderItems(selectedCategory, selectedGroup);
- }
- if (selectedCategory) {
- return renderGroups(selectedCategory, data[selectedCategory]);
- }
- return {translate("MESSAGES.NO_CONTENT")}
- }, [data, selectedCategory, selectedGroup, selectedItem,
- renderGroups, renderItems, renderItem, translate])
-
- const renderContent = useCallback((): ReactNode => {
- if (data) {
- if (selectedItem || selectedGroup || selectedCategory) {
- return
- {renderSelectionContent()}
-
;
- }
- if (data) {
- return renderCategories(data);
- }
- }
- return {translate("MESSAGES.NO_CONTENT")}
;
- }, [data, selectedCategory, selectedGroup, selectedItem, renderCategories, renderSelectionContent, translate]);
-
- const handleBreadcrumb = useCallback((evt: any) => {
- const index = evt.target.dataset.index;
- // eslint-disable-next-line eqeqeq
- if (index != undefined) {
- switch (index) {
- case '0': {
- setSelectedGroup(undefined);
- setSelectedItem(undefined);
- break;
- }
- case '1': {
- setSelectedItem(undefined);
- break;
- }
- }
- }
- }, []);
-
- const renderBreadcrumbs = useCallback((): ReactNode => {
- const crumbs = [selectedCategory, selectedGroup?.name, selectedItem?.title].filter(Boolean);
- if (crumbs.length) {
- return
- {getIconByName('Back')}
- {crumbs.map((b, index) => {b} )}
-
- }
- return <>>
- }, [selectedCategory, selectedGroup, selectedItem, handleBack, handleBreadcrumb])
-
- return
- {renderBreadcrumbs()}
- {renderContent()}
-
-}
diff --git a/frontend/src/component/playlist-tree/playlist-tree.scss b/frontend/src/component/playlist-tree/playlist-tree.scss
deleted file mode 100644
index 71e83d7a1..000000000
--- a/frontend/src/component/playlist-tree/playlist-tree.scss
+++ /dev/null
@@ -1,100 +0,0 @@
-@use '../../scss/common';
-
-.playlist-tree {
-
- font-size: 1rem;
-
- .tree-expander {
- margin-right: 6px;
-
- &:hover {
- color: var(--tree-expander-hover-color);
- }
- }
-
- .tree-expander, .tool-button {
- cursor: pointer;
-
- * {
- pointer-events: none;
- }
- }
-
- .tree-group {
- &__header {
- display: flex;
- flex-flow: row;
- align-items: center;
- height: 1.3rem;
- white-space: nowrap;
-
- &-content {
- display: flex;
- flex-flow: row;
- align-items: center;
- }
-
- input[type='checkbox'] {
- cursor: pointer;
- accent-color: var(--checkbox-checked-background-color);
- }
-
- }
-
- &__count {
- margin-left: 10px;
- color: var(--tree-count-color);
- }
-
- &__childs {
- .tree-group__channel {
- display: flex;
- flex-flow: row;
- align-items: center;
- margin-left: 50px;
- height: 1.3rem;
- gap: 3px;
-
- &-tools {
- display: flex;
- flex-flow: row;
- align-items: center;
- gap: 3px;
-
- }
-
- &-content {
- display: flex;
- flex-flow: row;
- align-items: center;
- white-space: nowrap;
- }
-
- &-nr {
- min-width: 2rem;
- font-size: 1rem;
- margin-right: 8px;
- text-align: right;
- color: var(--tree-count-color);
-
- &:after {
- content: ')';
- }
- }
- }
- }
-
- &__header-content, &__channel-content {
- cursor: pointer;
-
- &:hover {
- background-color: var(--tree-hover-background-color);
- color: var(--tree-hover-color);
-
- .tree-group__count {
- color: var(--tree-hover-color);
- }
- }
- }
- }
-}
diff --git a/frontend/src/component/playlist-tree/playlist-tree.tsx b/frontend/src/component/playlist-tree/playlist-tree.tsx
deleted file mode 100644
index f9c1eb6bd..000000000
--- a/frontend/src/component/playlist-tree/playlist-tree.tsx
+++ /dev/null
@@ -1,203 +0,0 @@
-import React, {useCallback, useState, useRef, useEffect} from 'react';
-import './playlist-tree.scss';
-import {PlaylistCategories, PlaylistGroup, PlaylistItem} from "../../model/playlist";
-import copyToClipboard from "../../utils/clipboard";
-import {first} from "rxjs/operators";
-import {noop} from "rxjs";
-import {useSnackbar} from "notistack";
-import {getIconByName} from "../../icons/icons";
-import ServerConfig from "../../model/server-config";
-import {PlaylistRequest, PlaylistRequestType} from "../../model/playlist-request";
-
-export type PlaylistTreeState = { [key: number]: boolean };
-
-interface PlaylistTreeProps {
- serverConfig: ServerConfig;
- data: PlaylistCategories;
- playlistRequest: PlaylistRequest;
- state: PlaylistTreeState;
- onCopy: (playlistItem: PlaylistItem) => void;
- onPlay?: (playlistItem: PlaylistItem) => void;
- onDownload?: (playlistItem: PlaylistItem) => void;
- onWebSearch?: (playlistItem: PlaylistItem) => void;
-}
-
-export default function PlaylistTree(props: PlaylistTreeProps) {
- const {serverConfig, data, playlistRequest, onCopy, onPlay, onDownload, onWebSearch} = props;
-
- const [, setForceUpdate] = useState(undefined);
- const expanded = useRef({});
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
- const [videoExtensions, setVideoExtensions] = useState([]);
-
- useEffect(() => {
- if (serverConfig) {
- setVideoExtensions(serverConfig.video?.extensions);
- }
- return noop;
- }, [serverConfig]);
-
- const getPlaylistItemById = useCallback((itemId: string): PlaylistItem => {
- const id = parseInt(itemId);
- if (data && !isNaN(id)) {
- const groups = [data?.live, data?.vod, data?.series].filter(Boolean).flat();
- for (let i = 0, len = groups.length; i < len; i++) {
- const group = groups[i];
- for (let j = 0, clen = group.channels?.length ?? 0; j < clen; j++) {
- const plitem = group.channels[j];
- // eslint-disable-next-line eqeqeq
- if (plitem.id == id) {
- return plitem;
- }
- }
- }
- }
- return undefined;
- }, [data]);
-
- const handleExpand = useCallback((event: any) => {
- const key = event.target.dataset.group;
- expanded.current[key] = !expanded.current[key];
- setForceUpdate({});
- }, []);
-
- const handleClipboardRealUrl = useCallback((e: any) => {
- const item = getPlaylistItemById(e.target.dataset.item);
- if (item) {
- let virtual_id = undefined;
- switch (playlistRequest.rtype) {
- case PlaylistRequestType.TARGET:
- virtual_id = item.virtual_id;
- break;
- case PlaylistRequestType.INPUT:
- case PlaylistRequestType.XTREAM:
- case PlaylistRequestType.M3U:
- break;
- }
- if (virtual_id) {
- onCopy(item);
- copyToClipboard(''+virtual_id).pipe(first()).subscribe({
- next: value => enqueueSnackbar(value ? "URL copied to clipboard" : "Copy to clipboard failed!", {variant: value ? 'success' : 'error'}),
- error: _ => enqueueSnackbar("Copy to clipboard failed!", {variant: 'error'}),
- complete: noop,
- });
- }
- }
- }, [enqueueSnackbar, getPlaylistItemById, onCopy, playlistRequest]);
-
- const handleClipboardUrl = useCallback((e: any) => {
- const item = getPlaylistItemById(e.target.dataset.item);
- if (item) {
-
-
- onCopy(item);
- copyToClipboard(item.url).pipe(first()).subscribe({
- next: value => enqueueSnackbar(value ? "URL copied to clipboard" : "Copy to clipboard failed!", {variant: value ? 'success' : 'error'}),
- error: _ => enqueueSnackbar("Copy to clipboard failed!", {variant: 'error'}),
- complete: noop,
- });
- }
- }, [enqueueSnackbar, getPlaylistItemById, onCopy]);
-
- const handleWebSearch = useCallback((e: any) => {
- if (onWebSearch) {
- const item = getPlaylistItemById(e.target.dataset.item);
- if (item) {
- onWebSearch(item);
- }
- }
- }, [getPlaylistItemById, onWebSearch]);
-
- const handleDownloadUrl = useCallback((e: any) => {
- if (onDownload) {
- if (!serverConfig.video.download?.directory) {
- enqueueSnackbar("Please updated the server configuration and add video.download directory and headers!", {variant: 'error'})
- } else {
- const item = getPlaylistItemById(e.target.dataset.item);
- if (item) {
- onDownload(item);
- }
- }
- }
- }, [serverConfig, enqueueSnackbar, getPlaylistItemById, onDownload]);
-
- const handlePlayUrl = useCallback((e: any) => {
- if (onPlay) {
- const item = getPlaylistItemById(e.target.dataset.item);
- if (item) {
- onPlay(item);
- }
- }
- }, [onPlay, getPlaylistItemById]);
-
- const isVideoFile = useCallback((entry: PlaylistItem): boolean => {
- if (videoExtensions && entry.url) {
- for (const ext of videoExtensions) {
- if (entry.url.endsWith(ext)) {
- return true;
- }
- }
- }
- return false;
- }, [videoExtensions]);
-
- const renderEntry = useCallback((entry: PlaylistItem, index: number): React.ReactNode => {
- return
-
-
- {getIconByName('LinkRounded')}
-
-
- {getIconByName('LinkRounded')}
-
- {onPlay &&
- {getIconByName('PlayArrow')}
-
}
- {isVideoFile(entry) &&
- <>
-
- {getIconByName('Download')}
-
- {serverConfig.video?.web_search &&
-
- {getIconByName('WebSearch')}
-
- }
- >
- }
-
-
-
{index + 1}
- {entry.name}
-
- }, [onPlay, handleClipboardUrl, handlePlayUrl, handleDownloadUrl, isVideoFile, handleWebSearch, serverConfig, handleClipboardRealUrl]);
-
- const renderGroup = useCallback((group: PlaylistGroup): React.ReactNode => {
- return
-
-
{getIconByName(expanded.current[group.id] ?
- 'ExpandMore' : 'ChevronRight')}
-
- {group.name}
-
({group.channels?.length})
-
-
- {expanded.current[group.id] && (
-
- {group.channels?.map(renderEntry)}
-
)}
-
;
- }, [handleExpand, renderEntry]);
-
- const renderPlaylist = useCallback((): React.ReactNode => {
- if (!data) {
- return ;
- }
- return
- {[data?.live, data?.vod, data?.series].filter(Boolean).flat().map(renderGroup)}
- ;
- }, [data, renderGroup]);
-
- return {renderPlaylist()}
;
-}
\ No newline at end of file
diff --git a/frontend/src/component/playlist-video/channel-view.scss b/frontend/src/component/playlist-video/channel-view.scss
deleted file mode 100644
index 193537000..000000000
--- a/frontend/src/component/playlist-video/channel-view.scss
+++ /dev/null
@@ -1,206 +0,0 @@
-@use '../../scss/common';
-
-$menu-width: 300px;
-$menu-width-closed: -288px;
-$item-height: 48px;
-
-.playlist-video {
- display: block;
- flex: 1 1 auto;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
- position: relative;
- overflow: hidden;
-}
-
-.channel-view {
- display: flex;
- flex-flow: column;
- box-sizing: border-box;
- z-index: 2;
- font-size: 1rem;
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- overflow: hidden;
- gap: 4px;
- transition: left 0.4s ease;
- width: $menu-width;
- height: 100%;
- background-color: transparent;
- border: 1px solid var(--border-color);
-
- &__closed {
- left: $menu-width-closed;
- border: none;
- cursor:pointer;
- &:hover {
- background-color: var(--card-background-color);
- }
- .channel-view__header,
- .channel-view__content {
- display: none;
- }
- }
-
- //&:hover {
- // background-color: #00000088;
- // border: 1px solid var(--border-color);
- // width: $menu-width;
- // .channel-view__content,
- // .channel-view__header {
- // display: flex;
- // }
- //}
-
- &__header {
- display: flex;
- flex-flow: row;
- align-items: center;
- box-sizing: border-box;
- height: $item-height;
- }
-
- &__menu {
- display: flex;
- flex-flow: row;
- flex: 1 1 auto;
- box-sizing: border-box;
- align-items: center;
- overflow: hidden;
- cursor: pointer;
-
- &-title {
- overflow: hidden;
- white-space: nowrap;
- }
-
- &-back,
- &-title {
- &:hover {
- cursor: pointer;
- color: var(--icon-button-hover-color);
- background-color: var(--icon-button-hover-background-color);
- }
- }
-
-
- &-disabled {
- pointer-events: none;
- }
-
- &-back {
- &-disabled {
- color: var(--card-background-color);
- }
- }
- }
-
- &__categories {
- display: flex;
- flex-flow: row;
- box-sizing: border-box;
- gap: 4px;
-
- &-category {
- border-radius: var(--border-radius);
- padding: 2px 4px;
- cursor: pointer;
- &:hover {
- color: var(--icon-button-hover-color);
- background-color: var(--icon-button-hover-background-color);
- }
- }
- }
-
- &__content {
- display: flex;
- flex-flow: column;
- box-sizing: border-box;
- overflow-x: hidden;
- overflow-y: auto;
- }
-
- &__groups {
- display: flex;
- flex-flow: column;
- font-size: 1.2rem;
- gap: 4px;
- flex-flow: column;
- box-sizing: border-box;
- padding: 0 4px;
-
- &-group {
- display: flex;
- flex-flow: row;
- cursor: pointer;
- border: 1px solid var(--border-color);
- background-color: var(--card-background-color);
- flex-flow: row nowrap;
- align-items: center;
- align-content: center;
- box-sizing: border-box;
- gap: 8px;
- height: $item-height;
- overflow: hidden;
- padding: 0 4px;
-
- &-count {
- color: var(--hint-color);
- }
- &:hover {
- color: var(--icon-button-hover-color);
- background-color: var(--icon-button-hover-background-color);
- }
- }
- }
-
- &__channels {
- display: flex;
- flex-flow: column;
- gap: 4px;
- box-sizing: border-box;
- padding: 0 4px;
- &-channel {
- display: flex;
- border: 1px solid var(--border-color);
- background-color: var(--card-background-color);
- flex-flow: row nowrap;
- align-items: center;
- align-content: center;
- box-sizing: border-box;
- gap: 8px;
- height: $item-height;
- cursor: pointer;
- overflow: hidden;
-
- &:hover {
- color: var(--icon-button-hover-color);
- background-color: var(--icon-button-hover-background-color);
- }
- &-name {
- display: flex;
- flex-flow: row nowrap;
- white-space: nowrap;
- box-sizing: border-box;
- }
- &-logo {
- box-sizing: border-box;
- display: flex;
- align-items: center;
- align-content: center;
- width: 42px;
- height: 42px;
- min-width: 42px;
- min-height: 42px;
- img {
- box-sizing: border-box;
- width: 100%;
- aspect-ratio: auto;
- }
- }
- }
- }
-}
diff --git a/frontend/src/component/playlist-video/channel-view.tsx b/frontend/src/component/playlist-video/channel-view.tsx
deleted file mode 100644
index f49a3aa42..000000000
--- a/frontend/src/component/playlist-video/channel-view.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-import {PlaylistCategories, PlaylistCategory, PlaylistGroup, PlaylistItem} from "../../model/playlist";
-import './channel-view.scss';
-import React, {ReactNode, useCallback, useState} from "react";
-import {getIconByName} from "../../icons/icons";
-import useTranslator from "../../hook/use-translator";
-import {set} from "react-datepicker/dist/date_utils";
-
-const getLogo = (item: PlaylistItem): string | undefined => {
- if (item.logo_small && item.logo_small.length > 0) {
- return item.logo_small;
- }
- if (item.logo && item.logo.length > 0) {
- return item.logo;
- }
- return undefined;
-}
-
-interface ChannelViewProps {
- data: PlaylistCategories;
- onPlay?: (playlistItem: PlaylistItem) => void;
-}
-
-export default function ChannelView(props: ChannelViewProps) {
-
- const {data, onPlay} = props;
- const translate = useTranslator();
- const [selectedItem, setSelectedItem] = useState();
- const [selectedGroup, setSelectedGroup] = useState();
- const [selectedCategory, setSelectedCategory] = useState(PlaylistCategory.LIVE);
- const [openChannelList, setOpenChannelList] = useState(true);
-
- const handleCategorySelect = useCallback((evt: any) => {
- let category = evt.target.dataset.category;
- if ((data as any)?.[category]) {
- setSelectedCategory(category);
- }
- }, [data]);
-
- const handleGroupSelect = useCallback((evt: any) => {
- let category = evt.target.dataset.category;
- let groupId = evt.target.dataset.group;
- let groups: PlaylistGroup[] = (data as any)[category];
- if (groups) {
- // eslint-disable-next-line eqeqeq
- let group = groups.find(grp => grp.id == groupId);
- setSelectedGroup(group)
- }
- }, [data]);
-
- const handleChannelSelect = useCallback((channel: PlaylistItem) => {
- if (channel) {
- setSelectedItem(channel)
- onPlay?.(channel);
- }
- }, [onPlay]);
-
- const handleCloseChannelViewState = useCallback(() => {
- setOpenChannelList(false);
- }, []);
-
-
- const handleOpenChannelViewState = useCallback(() => {
- if (!openChannelList) {
- setOpenChannelList(true);
- }
- }, [openChannelList]);
-
- const handleBack = useCallback(() => {
- if (selectedGroup) {
- setSelectedGroup(undefined);
- return;
- }
- }, [selectedGroup]);
-
- const renderItems = useCallback((category: PlaylistCategory, group: PlaylistGroup): ReactNode => {
- return
- {group.channels.map(channel => (
-
handleChannelSelect(channel)}
- className={'channel-view__channels-channel channel-' + channel.xtream_cluster}>
-
- {getLogo(channel) &&
(e.target.onerror = null) || (e.target.src = "assets/missing-logo.svg")}/>}
-
-
- {channel.title}
-
-
- ))}
-
;
- }, [handleChannelSelect]);
-
- const renderGroups = useCallback((category: PlaylistCategory, groups: PlaylistGroup[]): ReactNode => {
- return
- {groups.map(group => (
-
- {group.name}
- ({(group.channels?.length ?? 0)})
-
- ))}
-
;
- }, [handleGroupSelect]);
-
- const renderCategories = useCallback(() => {
- return
- {[['live', 'Live'], ['vod', 'VOD'], ['series', 'Series']].map(cat =>
{getIconByName(cat[1])}
)}
-
;
- }, [data, handleCategorySelect]);
-
- const renderSelectionContent = useCallback((): ReactNode => {
- // if (selectedItem) {
- // return renderItem(selectedItem);
- // }
- if (selectedGroup) {
- return renderItems(selectedCategory, selectedGroup);
- }
- if (selectedCategory) {
- return renderGroups(selectedCategory, data[selectedCategory]);
- }
- return {translate("MESSAGES.NO_CONTENT")}
- }, [data, selectedCategory, selectedGroup,
- renderGroups, renderItems, translate])
-
- const renderContent = useCallback((): ReactNode => {
- if (data) {
- if (selectedItem || selectedGroup || selectedCategory) {
- return
- {renderSelectionContent()}
-
;
- }
- }
- return {translate("MESSAGES.NO_CONTENT")}
;
- }, [data, selectedCategory, selectedGroup, selectedItem, renderSelectionContent, translate]);
-
- const renderMenu = useCallback((): ReactNode => {
- return
-
{getIconByName('Clear')}
- {selectedGroup &&
-
{getIconByName('Back')}
- }
-
{selectedGroup?.name ?? ''}
-
- }, [selectedGroup, handleBack, handleCloseChannelViewState])
-
- return
-
- {renderMenu()}
- {renderCategories()}
-
- {renderContent()}
-
-}
diff --git a/frontend/src/component/playlist-video/playlist-video.tsx b/frontend/src/component/playlist-video/playlist-video.tsx
deleted file mode 100644
index 61b759785..000000000
--- a/frontend/src/component/playlist-video/playlist-video.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React, {JSX, useRef} from "react";
-import VideoPlayer from "../video-player/video-player";
-import {PlaylistCategories, PlaylistItem} from "../../model/playlist";
-import {Observable} from "rxjs";
-import {PlaylistRequest} from "../../model/playlist-request";
-import ChannelView from "./channel-view";
-
-interface PlaylistVideoProps {
- data: PlaylistCategories;
- channel: Observable<[PlaylistItem, PlaylistRequest]>;
- onPlay?: (playlistItem: PlaylistItem) => void;
-}
-
-export default function PlaylistVideo(props: PlaylistVideoProps): JSX.Element {
- const {data, channel, onPlay} = props;
- const playerRef = useRef(undefined);
- const handlePlayerReady = (player: any) => {
- playerRef.current = player;
- };
-
- return
-
-
-
;
-}
\ No newline at end of file
diff --git a/frontend/src/component/playlist-viewer/playlist-viewer.scss b/frontend/src/component/playlist-viewer/playlist-viewer.scss
deleted file mode 100755
index 843f11a3f..000000000
--- a/frontend/src/component/playlist-viewer/playlist-viewer.scss
+++ /dev/null
@@ -1,39 +0,0 @@
-.playlist-viewer {
- display: flex;
- flex-flow: column;
- flex: 1 1 auto;
- overflow: hidden;
- padding: 10px;
- height: 100%;
- color: var(--playlist-color);
- box-sizing: border-box;
-
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
-
- &__header {
- display: flex;
- flex-flow: row;
- align-items: center;
- justify-content: flex-end;
- padding-bottom: 4px;
- }
-
- &__content {
- display: flex;
- flex-flow: column;
- flex: 1 1 auto;
- overflow: auto;
- box-sizing: border-box;
- }
-
- .group-label {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
-
- .group-label-text {
- width: 400px;
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/playlist-viewer/playlist-viewer.tsx b/frontend/src/component/playlist-viewer/playlist-viewer.tsx
deleted file mode 100755
index 2cbf883ad..000000000
--- a/frontend/src/component/playlist-viewer/playlist-viewer.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import React, {useMemo, useEffect, useState, useCallback, ReactNode} from "react";
-import './playlist-viewer.scss';
-import PlaylistTree, {PlaylistTreeState} from "../playlist-tree/playlist-tree";
-import {Observable, noop, finalize} from "rxjs";
-import ServerConfig from "../../model/server-config";
-import PlaylistGallery from "../playlist-gallery/playlist-gallery";
-import {getIconByName} from "../../icons/icons";
-import {
- EmptyPlaylistCategories,
- PlaylistCategories,
- PlaylistGroup,
- PlaylistItem
-} from "../../model/playlist";
-import {useSnackbar} from "notistack";
-import {first} from "rxjs/operators";
-import PlaylistVideo from "../playlist-video/playlist-video";
-import {PlaylistRequest} from "../../model/playlist-request";
-
-function textMatch(text: string, searchRequest: SearchRequest): boolean {
- if (searchRequest.regexp) {
- // eslint-disable-next-line eqeqeq
- return text.match(searchRequest.filter) != undefined;
- } else {
- return (text.toLowerCase().indexOf(searchRequest.filter) > -1);
- }
-}
-
-function filterMatchingChannels(grp: PlaylistGroup, searchRequest: SearchRequest): PlaylistGroup {
- let channels: PlaylistItem[] = [];
- for (const c of grp.channels) {
- if (textMatch(c.name, searchRequest)) {
- channels.push(c);
- }
- }
- if (channels.length) {
- return {
- id: grp.id,
- name: grp.name,
- channels
- } as PlaylistGroup;
- }
- return undefined;
-}
-
-function filterMatchingGroups(playlistCategories: PlaylistCategories, searchRequest: SearchRequest): Observable {
- return new Observable((observer) => {
- searchRequest.filter = searchRequest.regexp ? searchRequest.filter : searchRequest.filter.toLowerCase();
- const result: PlaylistCategories = {
- live:[],
- vod: [],
- series: [],
- };
- for (const category of ['live', 'vod', 'series']) {
- const groups = (playlistCategories as any)[category] ?? [];
- const currentResult = (result as any)[category];
- for (const g of groups) {
- if (textMatch(g.name, searchRequest)) {
- currentResult.push(g);
- } else {
- const matches = filterMatchingChannels(g, searchRequest);
- if (matches) {
- currentResult.push(matches);
- }
- }
- }
- }
- if (result.live.length || result.vod.length || result.series.length) {
- observer.next(result);
- } else {
- observer.next(undefined);
- }
- observer.complete();
- })
-}
-
-type PlaylistViewType = 'tree' | 'gallery' | 'player';
-
-const getToggleIcon = (view: PlaylistViewType): ReactNode => {
- switch (view) {
- case 'gallery': return getIconByName('Live');
- case 'player': return getIconByName('TreeView');
- default: return getIconByName('Gallery');
- }
-}
-
-
-export interface SearchRequest {
- filter: string,
- regexp: boolean
-}
-
-
-
-export interface IPlaylistViewer {
- getFilteredPlaylist: () => PlaylistCategories;
-}
-
-interface PlaylistViewerProps {
- serverConfig: ServerConfig;
- playlist: PlaylistCategories;
- searchChannel: Observable;
- videoChannel: Observable<[PlaylistItem, PlaylistRequest]>;
- playlistRequest: PlaylistRequest;
- onProgress: (value: boolean) => void;
- onCopy: (playlistItem: PlaylistItem) => void;
- onPlay?: (playlistItem: PlaylistItem) => void;
- onDownload?: (playlistItem: PlaylistItem) => void;
- onWebSearch?: (playlistItem: PlaylistItem) => void;
-}
-
-export default function PlaylistViewer(props: PlaylistViewerProps) {
- const {serverConfig, playlist, playlistRequest, searchChannel, videoChannel,
- onProgress, onCopy, onPlay, onDownload, onWebSearch} = props;
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
- const [data, setData] = useState(EmptyPlaylistCategories);
- const [galleryView, setGalleryView] = useState((localStorage.getItem("galleryView") as any) ?? 'tree');
- const checked = useMemo((): PlaylistTreeState => ({}), []);
-
- useEffect(() => {
- setData(playlist);
- return noop;
- }, [playlist]);
-
- useEffect(() => {
- const sub = searchChannel.subscribe((searchRequest: SearchRequest) => {
- let criteria = searchRequest.filter;
- // eslint-disable-next-line eqeqeq
- if (criteria == undefined || !criteria.length || !criteria.trim().length) {
- setData(playlist);
- } else {
- const trimmedCrit = criteria.trim();
- if (trimmedCrit.length < 2) {
- enqueueSnackbar("Minimum search criteria length is 2", {variant: 'info'});
- } else {
- searchRequest.filter = trimmedCrit;
- onProgress && onProgress(true)
- filterMatchingGroups(playlist, searchRequest).pipe(
- finalize(() => onProgress && onProgress(false)),
- first())
- .subscribe((matches: PlaylistCategories) => {
- if (matches) {
- const foundCount = matches.live.length + matches.vod.length + matches.series.length;
- enqueueSnackbar("Found Entries: " + foundCount, {variant: 'success'});
- setData(matches);
- } else {
- enqueueSnackbar("Nothing found!", {variant: 'info'});
- }
- });
- }
- }
- });
- return () => sub.unsubscribe();
- }, [searchChannel, playlist, enqueueSnackbar, onProgress]);
-
- const renderContent = () => {
- switch (galleryView) {
- case 'gallery' :
- return
- case 'player' :
- return
- default:
- return
- }
- }
-
- const toggleView = useCallback(() => {
- const getNext = (data: string) => {
- switch (data) {
- case 'tree': return 'gallery';
- case 'gallery': return 'player';
- case 'player': return 'tree';
- }
- return 'gallery';
- };
-
- setGalleryView((data: any) => {
- let view: any = getNext(data);
- localStorage.setItem("galleryView", view);
- return view;
- });
- }, []);
-
- return
-
-
- {getToggleIcon(galleryView)}
-
-
-
- {renderContent()}
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/popup-menu/popup-menu.scss b/frontend/src/component/popup-menu/popup-menu.scss
deleted file mode 100644
index 6fbb4c374..000000000
--- a/frontend/src/component/popup-menu/popup-menu.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-@use '../../scss/common';
-
-.popup-menu {
- position: fixed;
- z-index: 9999;
- color: var(--popup-color);
- background-color: var(--popup-background-color);
- border: 1px solid var(--popup-border-color);
- border-radius: var(--border-radius);
- padding: 4px;
- filter: drop-shadow(8px 9px 7px var(--popup-shadow-color));
-
- @include common.list-hover();
-}
\ No newline at end of file
diff --git a/frontend/src/component/popup-menu/popup-menu.tsx b/frontend/src/component/popup-menu/popup-menu.tsx
deleted file mode 100644
index fed6d3753..000000000
--- a/frontend/src/component/popup-menu/popup-menu.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React, {ReactNode, useEffect, useRef, useState} from "react";
-
-import './popup-menu.scss';
-import ClickAwayListener from "../../utils/click-away-listener";
-
-interface PopupMenuProps {
- position: { x: number, y: number },
- children: ReactNode,
- onHide: () => void;
-}
-
-export default function PopupMenu(props: PopupMenuProps) {
- const {position, onHide} = props;
- const popupRef = useRef(undefined);
- const [popupPosition, setPopupPosition] = useState({top: 0, left: -2000})
-
- useEffect(() => {
- if (position && popupRef.current) {
- const ww = window.innerWidth;
- const {offsetWidth} = popupRef.current;
- const style: any =
- (position.x + offsetWidth > ww)
- ? {right: 12, top: position.y}
- : {left: position.x, top: position.y};
- setPopupPosition(style);
- }
- }, [position])
-
- return <>{props.position &&
-
- {props.children}
-
- }>
-}
\ No newline at end of file
diff --git a/frontend/src/component/preferences/preferences.scss b/frontend/src/component/preferences/preferences.scss
deleted file mode 100644
index e514d7b45..000000000
--- a/frontend/src/component/preferences/preferences.scss
+++ /dev/null
@@ -1,102 +0,0 @@
-@use '../../scss/common';
-
-.preferences {
- @include common.container_horz_no_padding();
- overflow: hidden;
- flex: 1 1 auto;
-
- &__content {
- @include common.container_horz_nowrap(flex-start, 0);
- flex: 1 1 auto;
- overflow: hidden;
- gap: 0;
- }
-
- &__panels {
- @include common.container_vert_no_padding(flex-start);
- overflow: hidden;
- flex: 1 1 auto;
-
- .panel {
- @include common.container_vert();
- flex: 1 1 auto;
- overflow: hidden;
- .card {
- @include common.container_vert();
- flex: 1 1 auto;
- overflow: hidden;
- }
- }
- }
-
- &__sidebar {
- display: flex;
- flex-flow: column;
- box-sizing: border-box;
- @media (max-width: common.$media-size-mobile) {
- flex-flow: row;
- }
- //max-width: 48px;
- //min-width: 48px;
- //width: 48px;
- @media (max-width: common.$media-size-mobile) {
- max-width: 32px;
- min-width: 32px;
- }
- gap: 12px;
- padding: 8px 4px;
-
- button {
- @include common.iconButton();
- border: 1px solid transparent;
- }
-
- button.selected {
- color: var(--icon-button-hover-color);
- background-color: var(--icon-button-hover-background-color);
- border: 1px solid var(--icon-button-hover-border-color);
- }
-
- &-menu-action {
- display: flex;
- flex-flow: row nowrap;
- gap: 8px;
- align-items: center;
- cursor: pointer;
- color: var(--menu-button-color);
- background-color: var(--menu-button-background-color);
- border: 1px solid transparent;
- border-radius: var(--border-radius);
- padding: 8px 16px;
- user-select: none;
- white-space: nowrap;
-
- &_update { svg { fill: var(--menu-icon-color-update); } }
- &_user { svg { fill: var(--menu-icon-color-user); } }
- &_api_server { svg { fill: var(--menu-icon-color-api_server); } }
- &_main_config { svg { fill: var(--menu-icon-color-main_config); } }
- &_status { svg { fill: var(--menu-icon-color-status); } }
-
-
- * {
- pointer-events: none;
- }
-
- @media (max-width: common.$media-size-mobile) {
- label {
- display: none;
- }
- }
-
- &:hover {
- border: 1px solid var(--icon-button-hover-border-color);
- }
- }
-
- &-menu-action.selected {
- color: var(--menu-button-hover-color);
- background-color: var(--menu-button-hover-background-color);
- }
- }
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/preferences/preferences.tsx b/frontend/src/component/preferences/preferences.tsx
deleted file mode 100644
index 3112739b8..000000000
--- a/frontend/src/component/preferences/preferences.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React, {useCallback, useMemo, useState} from "react";
-import './preferences.scss';
-import ServerConfig from "../../model/server-config";
-import UserView from "../user-view/user-view";
-import TargetUpdateView from "../target-update-view/target-update-view";
-import ApiProxyView from "../api-proxy-view/api-proxy-view";
-import {getIconByName} from "../../icons/icons";
-import Panel from "../panel/panel";
-import MainConfigView from "../main-config-view/main-config-view";
-import useTranslator from "../../hook/use-translator";
-import StatusView from "../status-view/status-view";
-
-enum SidebarAction {
- Update = 'update',
- User = 'user',
- ApiServer = 'api_server',
- MainConfig = 'main_config',
- Status = 'status',
-}
-
-const SIDEBAR_ACTIONS: { action: SidebarAction, icon: string, label: string }[] = [
- {action: SidebarAction.Update, icon: 'Refresh', label: 'LABEL.REFRESH'},
- {action: SidebarAction.User, icon: 'User', label: 'LABEL.USERS'},
- {action: SidebarAction.ApiServer, icon: 'ApiServer', label: 'LABEL.PROXY'},
- {action: SidebarAction.MainConfig, icon: 'Config', label: 'LABEL.CONFIG'},
- {action: SidebarAction.Status, icon: 'Status', label: 'LABEL.STATUS'},
-];
-
-interface PreferencesProps {
- config: ServerConfig
-}
-
-export default function Preferences(props: PreferencesProps) {
- const {config} = props;
- const translate = useTranslator();
- const [activePage, setActivePage] = useState(SidebarAction.Update);
- const sidebar_actions = useMemo(() => SIDEBAR_ACTIONS.map(a => ({...a, label: translate(a.label)})), [translate])
-
- const handleSidebarAction = useCallback((event: any) => {
- const action = event.target.dataset.action;
- if (action) {
- setActivePage(action);
- }
- }, []);
-
- return
-
-
- {sidebar_actions.map(action =>
-
{getIconByName(action.icon)}{action.label}
)}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/progress/progress.scss b/frontend/src/component/progress/progress.scss
deleted file mode 100644
index be758a9bc..000000000
--- a/frontend/src/component/progress/progress.scss
+++ /dev/null
@@ -1,59 +0,0 @@
-.progress {
- display: none;
- position: fixed;
- width: 100%;
- justify-content: center;
- color: aqua;
- z-index: 100;
-}
-
-.progress-visible {
- display: flex;
-}
-
-.spinner {
- display: inline-block;
- position: relative;
- width: 80px;
- height: 40px;
-}
-
-.spinner div {
- display: inline-block;
- position: absolute;
- left: 8px;
- width: 16px;
- min-width: 16px;
- max-width: 16px;
- min-height: 16px;
- max-height: 16px;
- border-radius: 50%;
- background: var(--spinner-color);
- opacity: 0.4;
- animation: spinner 1s infinite alternate;
-}
-
-.spinner div:nth-child(1) {
- left: 8px;
- animation-delay: 0s;
-}
-
-.spinner div:nth-child(2) {
- left: 32px;
- animation-delay: -0.3s;
-}
-
-.spinner div:nth-child(3) {
- left: 56px;
- animation-delay: -0.6s;
-}
-
-@keyframes spinner {
- 0% {
- transform: translateY(0);
- background-color: var(--spinner-highlight-color);
- }
- 100% {
- transform: translateY(20px);
- }
-}
diff --git a/frontend/src/component/progress/progress.tsx b/frontend/src/component/progress/progress.tsx
deleted file mode 100644
index df21c88b5..000000000
--- a/frontend/src/component/progress/progress.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import './progress.scss';
-
-interface ProgressProps {
- visible: boolean;
-}
-
-export default function Progress(props: ProgressProps) {
-
- return ;
-}
\ No newline at end of file
diff --git a/frontend/src/component/proxy-select/proxy-select.scss b/frontend/src/component/proxy-select/proxy-select.scss
deleted file mode 100644
index 6fff0c224..000000000
--- a/frontend/src/component/proxy-select/proxy-select.scss
+++ /dev/null
@@ -1,52 +0,0 @@
-.proxy-select {
- display: flex;
- flex-flow: row wrap;
- gap: 4px;
- padding: 2px;
-
- &__tag {
- display: flex;
- flex-flow: column;
- justify-content: center;
- align-items: center;
- gap: 5px;
- background-color: transparent;
- color: var(--text-color);
- padding: 3px 6px;
- border-radius: 4px;
- cursor: pointer;
- border: 1px solid var(--border-light-color);
-
- div {
- display: flex;
- flex-flow: row nowrap;
- justify-content: center;
- align-items: center;
- gap: 5px;
- }
-
- &-sub {
- border-radius: 2px;
- border: 1px solid transparent;
- padding: 2px 4px;
- &:hover {
- border: 1px solid var(--option-active-background-color);
- }
-
- }
-
- &-sub.selected {
- color: var(--option-active-color);
- background-color: var(--option-active-background-color);
- }
-
- &:hover {
- border: 1px solid var(--checkbox-checked-background-color);
- }
-
- &-selected {
- background-color: var(--checkbox-checked-background-color);
- color: var(--text-color);
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/proxy-select/proxy-select.tsx b/frontend/src/component/proxy-select/proxy-select.tsx
deleted file mode 100644
index 4f8383f90..000000000
--- a/frontend/src/component/proxy-select/proxy-select.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import './proxy-select.scss';
-import {useEffect, useState} from "react";
-import {noop} from "rxjs";
-
-const getSubsStr = (subs: any): string => {
- let result = Object.keys(subs).filter(key => subs[key]);
- if (result.length > 0) {// && result.length < 3) {
- return '[' + result.join(',') + ']';
- }
- return '';
-};
-
-const getSubs = (value: string) => {
- if (value?.startsWith('reverse')) {
- let result = {live:true, vod:true, series: true};
- if (!value.includes('live')) {
- result.live = false;
- }
- if (!value.includes('vod')) {
- result.vod = false;
- }
- if (!value.includes('series')) {
- result.series = false;
- }
- if (result.live === false && result.vod === false && result.series === false) {
- return {live:true, vod:true, series: true};
- }
- return result;
- }
- return {live: false, vod:false, series: false};
-}
-
-interface ProxySelectProps {
- name: string;
- value: string;
- onSelect: (name: string, values: any) => void;
-}
-
-export default function ProxySelect(props: ProxySelectProps) {
- const {name, value, onSelect} = props;
- const [proxyType, setProxyType] = useState(() => value == undefined ? 0 : (value.startsWith('redirect')) ? 2 : 1);
- const [subs, setSubs] = useState(() => getSubs(value));
-
- useEffect(() => {
- if (value) {
- setProxyType(value.startsWith('redirect') ? 2 : 1);
- setSubs(getSubs(value));
- }
- return noop;
- }, [value]);
-
- const changeProxyType = (pt: number) => {
- setProxyType(pt);
- onSelect(name, pt === 1 ? ('reverse' + getSubsStr(subs)) : 'redirect');
- };
-
- const toggle = (evt: any, sub: string)=> {
- evt.preventDefault();
- evt.stopPropagation();
- setSubs((subs: any) => {
- subs[sub] = !subs[sub];
- onSelect(name, proxyType === 1 ? ('reverse' + getSubsStr(subs)) : 'redirect');
- return {...subs};
- });
- };
-
- return
-
changeProxyType(1)}>
-
Reverse
-
- toggle(evt, 'live')}>Live
- toggle(evt, 'vod')}>Vod
- toggle(evt, 'series')}>Series
-
-
-
changeProxyType(2)}>
- Redirect
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/schedule-editor/schedule-editor.scss b/frontend/src/component/schedule-editor/schedule-editor.scss
deleted file mode 100644
index 7ff04fcc3..000000000
--- a/frontend/src/component/schedule-editor/schedule-editor.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-@use '../../scss/common';
-@use '../../scss/preferences';
-
-$input-width: 150px;
-
-.schedule-editor {
- display: flex;
- flex-flow: column;
- box-sizing: border-box;
- gap: 2px;
-
- &__schedule {
- display: flex;
- flex-flow: row;
- align-items: center;
- padding: 6px;
- height: 1.5rem;
-
- input {
- width: $input-width;
- min-width: $input-width;
- max-width: $input-width;
- }
-
-
- &-toolbar {
- display: flex;
- flex-flow: row nowrap;
- justify-content: flex-start;
- gap: 2px;
- }
- }
- button {
- @include common.iconButton();
- }
-
-}
diff --git a/frontend/src/component/schedule-editor/schedule-editor.tsx b/frontend/src/component/schedule-editor/schedule-editor.tsx
deleted file mode 100644
index 5551d958d..000000000
--- a/frontend/src/component/schedule-editor/schedule-editor.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import React, {JSX, useCallback, useEffect, useMemo, useState} from "react";
-import "./schedule-editor.scss";
-import {Schedule, SourceConfig} from "../../model/server-config";
-import TagSelect from "../tag-select/tags-select";
-import {getIconByName} from "../../icons/icons";
-
-interface ScheduleEditorProps {
- name: string;
- values: Schedule[],
- sources: SourceConfig[];
- onChange: (name: string, values: Schedule[]) => void;
-}
-
-export default function ScheduleEditor(props: ScheduleEditorProps) {
- const { name, values, sources, onChange} = props;
- const targets = useMemo(() => sources.flatMap(s => s.targets)
- .filter(Boolean).flatMap(t => ({value: t.name, label: t.name})), [sources]);
- const [schedules, setSchedules] = useState([]);
-
- useEffect(() => {
- const schedulesCopy = values?.map(s => ({...s})) || [];
- setSchedules(schedulesCopy);
- }, [values])
-
- const handleTargetChange = useCallback((field: string, selected: any):void => {
- setSchedules(data => {
- data[field as any].targets = selected;
- onChange(name, data);
- return data;
- });
- }, [name, onChange]);
-
- const handleScheduleChange = useCallback((evt: any) => {
- const field = evt.target.dataset.field;
- setSchedules(data => {
- data[field as any].schedule = evt.target.value;
- onChange(name, data);
- return data;
- });
-
- }, [name, onChange]);
-
- const handleScheduleRemove = useCallback((evt: any) => {
- const index = evt.target.dataset.index;
- setSchedules(data => {
- data.splice(index, 1);
- let newData = [...data];
- onChange(name, newData);
- return newData;
- });
- }, [name, onChange]);
-
- const handleScheduleAdd = useCallback((evt: any) => {
- setSchedules(data => {
- let newData = [...data, {schedule: '', targets: undefined}];
- onChange(name, newData);
- return newData;
- });
- }, [name, onChange]);
-
- let lastIndex = (schedules?.length ?? 0) -1;
-
- const renderSchedule = useCallback((schedule: Schedule, index: number): JSX.Element => {
- return
-
-
-
- {getIconByName('ScheduleRemove')}
- {index === lastIndex &&
- {getIconByName('ScheduleAdd')} }
-
-
- }, [targets, handleScheduleChange, handleTargetChange, handleScheduleRemove, handleScheduleAdd, lastIndex]);
-
- return (
-
- {schedules?.map(renderSchedule)}
- {lastIndex < 0 &&
- {getIconByName('ScheduleAdd')} }
-
- );
-}
diff --git a/frontend/src/component/setup-wizard/_setup-common.scss b/frontend/src/component/setup-wizard/_setup-common.scss
deleted file mode 100644
index daec94e52..000000000
--- a/frontend/src/component/setup-wizard/_setup-common.scss
+++ /dev/null
@@ -1,45 +0,0 @@
-@use '../../scss/common' as common;
-
-.setup-label {
-
-}
-
-.wizard-page {
- display: flex;
- flex-flow: column;
- overflow: hidden;
-
- &__header-title {
- display: flex;
- color: var(--title-label-color);
- justify-content: center;
- }
-
- &__content {
- display: flex;
- flex-flow: column;
- overflow: auto;
- }
-
- &__toolbar {
- display: flex;
- flex-flow: row;
- box-sizing: border-box;
- align-items: center;
- justify-content: center;
- gap: 4px;
-
- svg {
- fill: var(--icon-button-color);
- }
-
- button {
- @include common.textButton();
- }
-
- .finish-button {
- color: var(--option-active-color);
- background-color: var(--option-active-background-color);
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/setup-wizard/progress-bar/progress-bar.scss b/frontend/src/component/setup-wizard/progress-bar/progress-bar.scss
deleted file mode 100644
index aa436fcd4..000000000
--- a/frontend/src/component/setup-wizard/progress-bar/progress-bar.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.progress-bar {
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/setup-wizard/progress-bar/progress-bar.tsx b/frontend/src/component/setup-wizard/progress-bar/progress-bar.tsx
deleted file mode 100644
index cafcdd364..000000000
--- a/frontend/src/component/setup-wizard/progress-bar/progress-bar.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import './progress-bar.scss';
-import {JSX} from "react";
-interface ProgressBarProps {
- step: number;
- total: number;
-}
-export default function ProgressBar(props: ProgressBarProps): JSX.Element{
- const {step, total} = props;
-
- return ;
-}
diff --git a/frontend/src/component/setup-wizard/setup-wizard.scss b/frontend/src/component/setup-wizard/setup-wizard.scss
deleted file mode 100644
index e69de29bb..000000000
diff --git a/frontend/src/component/setup-wizard/setup-wizard.tsx b/frontend/src/component/setup-wizard/setup-wizard.tsx
deleted file mode 100644
index dfc8decb8..000000000
--- a/frontend/src/component/setup-wizard/setup-wizard.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { useState } from "react";
-import "./setup-wizard.scss";
-import "./step-welcome/step-welcome";
-import StepWelcome from "./step-welcome/step-welcome";
-import StepSummary from "./step-summary/step-summary";
-import ProgressBar from "./progress-bar/progress-bar";
-
-interface SetupWizardProps {
-}
-
-export default function SetupWizard(props: SetupWizardProps) {
- const [step, setStep] = useState(0);
-
- const steps = [
- setStep(step + 1)} />,
- // setStep(step + 1)} onBack={() => setStep(step - 1)} />,
- // setStep(step + 1)} onBack={() => setStep(step - 1)} />,
- console.log('Finished!')} onBack={() => setStep(step - 1)} />,
- ];
-
- return (
-
- );
-}
\ No newline at end of file
diff --git a/frontend/src/component/setup-wizard/step-summary/step-summary.scss b/frontend/src/component/setup-wizard/step-summary/step-summary.scss
deleted file mode 100644
index 75d3cba9f..000000000
--- a/frontend/src/component/setup-wizard/step-summary/step-summary.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-@use '../setup-common';
-
-.setup-summary {
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/setup-wizard/step-summary/step-summary.tsx b/frontend/src/component/setup-wizard/step-summary/step-summary.tsx
deleted file mode 100644
index a6230a63f..000000000
--- a/frontend/src/component/setup-wizard/step-summary/step-summary.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import "./step-summary.scss";
-import useTranslator from "../../../hook/use-translator";
-
-interface StepSummaryProps {
- onFinish: () => void;
- onBack: () => void;
-}
-
-export default function StepSummary(props: StepSummaryProps) {
-
- const { onFinish, onBack } = props;
-
-
- const translate = useTranslator();
-
- return
-
{translate("SETUP.MSG.WELCOME")}
-
- {translate("SETUP.LABEL.BACK")}
- {translate("SETUP.LABEL.FINISH_SETUP")}
-
-
;
-}
diff --git a/frontend/src/component/setup-wizard/step-welcome/step-welcome.scss b/frontend/src/component/setup-wizard/step-welcome/step-welcome.scss
deleted file mode 100644
index 255a79559..000000000
--- a/frontend/src/component/setup-wizard/step-welcome/step-welcome.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-@use '../setup-common';
-
-.step-welcome {
-
- img {
- max-height: 500px;
- }
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/setup-wizard/step-welcome/step-welcome.tsx b/frontend/src/component/setup-wizard/step-welcome/step-welcome.tsx
deleted file mode 100644
index 46abcfc8c..000000000
--- a/frontend/src/component/setup-wizard/step-welcome/step-welcome.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import "./step-welcome.scss";
-import useTranslator from "../../../hook/use-translator";
-
-interface StepWelcomeProps {
- onNext: () => void;
-}
-
-export default function StepWelcome(props: StepWelcomeProps) {
- const {onNext} = props;
-
- const translate = useTranslator();
-
- return
-
{translate("SETUP.MSG.WELCOME")}
-
-
-
-
- {translate("SETUP.LABEL.NEXT")}
-
-
;
-}
diff --git a/frontend/src/component/sidebar/sidebar.scss b/frontend/src/component/sidebar/sidebar.scss
deleted file mode 100644
index ff9b33030..000000000
--- a/frontend/src/component/sidebar/sidebar.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-@use '../../scss/theme';
-$toggle-size: 24px;
-
-.sidebar {
- display: flex;
- flex-flow: column;
- justify-content: center;
- flex: 0 1 auto;
-
- .sidebar-content {
- display: flex;
- flex-flow: column;
- justify-content: center;
- flex: 1 1 auto;
- overflow: hidden;
- }
- .sidebar-collapsed {
- max-height: 0;
- height: 0;
- overflow: hidden;
- }
-
- .sidebar-toggle {
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--sidebar-toggle-color);
- cursor: pointer;
-
- &:hover {
- background-color: var(--sidebar-toggle-hover-color);
- }
- }
-}
diff --git a/frontend/src/component/sidebar/sidebar.tsx b/frontend/src/component/sidebar/sidebar.tsx
deleted file mode 100644
index fc0bb85ba..000000000
--- a/frontend/src/component/sidebar/sidebar.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React, {JSX, useCallback, useState} from "react";
-import './sidebar.scss';
-import {getIconByName} from "../../icons/icons";
-/* eslint-disable @typescript-eslint/no-empty-interface */
-interface SidebarProps {
- children?: any;
-}
-
-export default function Sidebar(props: SidebarProps): JSX.Element {
-
- const {children} = props;
- const [collapsed, setCollapsed] = useState(true);
-
- const handleToggle = useCallback(() => {
- setCollapsed(value => !value);
- }, []);
-
- return
-
- {children}
-
-
- {getIconByName(collapsed ? 'ArrowDown' : 'ArrowUp')}
-
-
;
-
-}
diff --git a/frontend/src/component/source-selector-editor/source-selector-editor.scss b/frontend/src/component/source-selector-editor/source-selector-editor.scss
deleted file mode 100644
index 947d7d881..000000000
--- a/frontend/src/component/source-selector-editor/source-selector-editor.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-@use '../../scss/common';
-@use '../../scss/preferences';
-
-.source-selector-editor {
- & {
- display: flex;
- flex-flow: column;
- gap: 12px;
- min-height: 400px;
- @include preferences.prefsToolbar();
- }
-
- &__content {
- display: flex;
- padding: 12px;
- flex-flow: column;
- flex: 1 1 auto;
- min-width: 340px;
- .form-view__col-label {
- justify-content: flex-start;
- min-width: 80px;
- width: fit-content;
- }
-
- .tab-set {
- margin-bottom: 8px;
- }
-
- &-tags, &-form {
- display: flex;
- flex-flow: column;
- flex: 1 1 auto;
- gap: 12px;
- }
- }
-}
-
diff --git a/frontend/src/component/source-selector-editor/source-selector-editor.tsx b/frontend/src/component/source-selector-editor/source-selector-editor.tsx
deleted file mode 100644
index 35812a4e0..000000000
--- a/frontend/src/component/source-selector-editor/source-selector-editor.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
-import './source-selector-editor.scss';
-import useTranslator from "../../hook/use-translator";
-import FormView, {FormFieldType} from "../form-view/from-view";
-import ServerConfig from "../../model/server-config";
-import {noop} from "rxjs";
-import TagSelect from "../tag-select/tags-select";
-import TabSet from "../tab-set/tab-set";
-import {enqueueSnackbar} from "notistack";
-import {PlaylistRequest, PlaylistRequestType} from "../../model/playlist-request";
-
-const SOURCE_SELF_HOSTED = "self_hosted"
-const SOURCE_PROVIDER = "provider"
-const SOURCE_CUSTOM = "custom"
-
-const SOURCE_TYPE_XC = "xtream";
-const SOURCE_TYPE_M3U = "m3u";
-
-const SOURCE_TABS = [
- {label: "LABEL.SELF_HOSTED", key: SOURCE_SELF_HOSTED},
- {label: "LABEL.PROVIDER", key: SOURCE_PROVIDER},
- {label: "LABEL.CUSTOM", key: SOURCE_CUSTOM},
-];
-
-const SOURCE_TYPES = [
- {label: "LABEL.XTREAM_CODES", value: SOURCE_TYPE_XC},
- {label: "LABEL.M3U", value: SOURCE_TYPE_M3U},
-];
-
-const XC_COLUMNS = [
- {name: 'username', label: 'LABEL.USERNAME', fieldType: FormFieldType.TEXT},
- {name: 'password', label: 'LABEL.PASSWORD', fieldType: FormFieldType.TEXT},
- {name: 'url', label: 'LABEL.HOST', fieldType: FormFieldType.TEXT},
-];
-
-const M3U_COLUMNS = [
- {name: 'url', label: 'LABEL.URL', fieldType: FormFieldType.TEXT},
-];
-
-interface SourceSelectorProps {
- serverConfig: ServerConfig;
- onSubmit: (req: PlaylistRequest) => boolean;
-}
-
-export interface ISourceSelectorEditor {
- open: () => void;
- close: () => void;
-}
-
-const SourceSelectorEditor = forwardRef((props: SourceSelectorProps, ref: any) => {
- const {serverConfig, onSubmit} = props;
- const dialogRef = useRef(null);
- const dataRef = useRef({});
- const [inputs, setInputs] = useState([]);
- const [targets, setTargets] = useState([]);
- const [activeTab, setActiveTab] = useState(SOURCE_SELF_HOSTED);
- const [sourceType, setSourceType] = useState(SOURCE_TYPE_XC);
- const sourceRef = useRef({input: undefined, target: undefined});
- const translate = useTranslator();
-
- const open = useCallback(() => {
- dialogRef.current?.showModal();
- }, []);
-
- const close = useCallback(() => {
- dialogRef.current?.close();
- }, []);
-
- const reference = useMemo(() => ({open, close}), [open, close]);
-
- useImperativeHandle(ref, () => reference);
-
- const handleSubmit = useCallback(() => {
- let result = undefined;
- if (activeTab === SOURCE_SELF_HOSTED) {
- // eslint-disable-next-line eqeqeq
- if (sourceRef.current.target != null) {
- result = {rtype: PlaylistRequestType.TARGET, sourceId: sourceRef.current.target, sourceName: targets.find(i => i.id === sourceRef.current.target)?.name};
- }
- } else if (activeTab === SOURCE_PROVIDER) {
- // eslint-disable-next-line eqeqeq
- if (sourceRef.current.input != null) {
- result = {rtype: PlaylistRequestType.INPUT, sourceId: sourceRef.current.input, sourceName: inputs.find(i => i.id === sourceRef.current.input)?.name};
- }
- } else {
- let record : any = dataRef.current;
- if (sourceType === SOURCE_TYPE_XC) {
- let username = record.username?.trim()
- let password = record.password?.trim()
- let url = record.url?.trim()
- if (username && password && url) {
- result = {rtype: PlaylistRequestType.XTREAM, username, password, url};
- }
- } else {
- let url = record.url?.trim()
- if (url) {
- result = {rtype: PlaylistRequestType.M3U, url};
- }
- }
- }
- if (!result) {
- enqueueSnackbar(translate('MESSAGES.SOURCE_SELECTOR.MISSING_SELECTION'), {variant: 'error'});
- } else {
- if (onSubmit(result)) {
- dialogRef.current.close();
- }
- }
- }, [onSubmit, activeTab, inputs, targets, sourceType, translate]);
-
- useEffect(() => {
- if (serverConfig) {
- setInputs(serverConfig.sources?.flatMap(source => source.inputs) ?? []);
- setTargets(serverConfig.sources?.flatMap(source => source.targets) ?? []);
- }
- return noop;
- }, [serverConfig]);
-
-
- const handleSourceSelect = useCallback((field: string, value: any) => {
- setTimeout(() => {
- if (field === 'source_type') {
- setSourceType(value)
- } else {
- sourceRef.current = {...sourceRef.current, [field]: value};
- }
- }, 0);
- }, []);
-
- return
-
-
-
-
- ({value: target.id, label: target.name}))}>
-
-
- ({value: input.id, label: input.name}))}>
-
-
-
-
-
-
-
- dialogRef.current?.close()}>{translate('LABEL.CANCEL')}
- {translate('LABEL.OK')}
-
-
- ;
-});
-
-export default SourceSelectorEditor;
\ No newline at end of file
diff --git a/frontend/src/component/source-selector/source-selector.scss b/frontend/src/component/source-selector/source-selector.scss
deleted file mode 100755
index 37d671aa2..000000000
--- a/frontend/src/component/source-selector/source-selector.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-.source-selector {
- display: flex;
- flex-flow: row nowrap;
- padding: 10px 0 10px 0;
- background-color: inherit;
- .input-field {
- flex: 1 1 auto;
- }
-}
-
diff --git a/frontend/src/component/source-selector/source-selector.tsx b/frontend/src/component/source-selector/source-selector.tsx
deleted file mode 100755
index af2dea299..000000000
--- a/frontend/src/component/source-selector/source-selector.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import React, {KeyboardEvent, useCallback, useRef} from "react";
-
-import './source-selector.scss';
-import {getIconByName} from "../../icons/icons";
-import ServerConfig from "../../model/server-config";
-import {PlaylistRequest, PlaylistRequestType} from "../../model/playlist-request";
-import InputField from "../input-field/input-field";
-import SourceSelectorEditor, {ISourceSelectorEditor,} from "../source-selector-editor/source-selector-editor";
-
-const formatSourceSelection = (req: PlaylistRequest): string => {
- switch (req?.rtype) {
- case PlaylistRequestType.TARGET: {
- return `Target: ${req.sourceName}`;
- }
- case PlaylistRequestType.INPUT: {
- return `Provider: ${req.sourceName}`;
- }
- case PlaylistRequestType.XTREAM: {
- return `${req.url}/player_api.php?username=${req.username}&password=${req.password}`;
- }
- case PlaylistRequestType.M3U: {
- return req.url;
- }
- default:
- return '';
- }
-};
-
-interface SourceSelectorProps {
- serverConfig: ServerConfig;
- onDownload: (req: PlaylistRequest) => void;
-}
-
-export default function SourceSelector(props: SourceSelectorProps) {
- const editorRef = useRef(undefined);
- const textFieldRef = useRef(undefined);
- const playlistRequestRef = useRef(undefined);
-
- const {serverConfig, onDownload} = props;
-
- const handleDownload = useCallback(() => {
- if (playlistRequestRef.current) {
- onDownload(playlistRequestRef.current);
- }
- }, [onDownload]);
-
- const handleKeyPress = useCallback((event: KeyboardEvent) => {
- if (event.key === 'Enter') {
- handleDownload();
- }
- }, [handleDownload]);
-
- const openPopup = useCallback((evt: any) => {
- editorRef.current.open();
- }, []);
-
-
- const handleEditorSubmit = useCallback((req: PlaylistRequest): boolean => {
- textFieldRef.current.value = formatSourceSelection(req);
- playlistRequestRef.current = req;
- return true;
- }, []);
-
- return
-
-
- {getIconByName('CloudDownload')}
- {getIconByName('ArrowDown')}
-
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/split-panel/split-panel.scss b/frontend/src/component/split-panel/split-panel.scss
deleted file mode 100644
index ea66e848d..000000000
--- a/frontend/src/component/split-panel/split-panel.scss
+++ /dev/null
@@ -1,30 +0,0 @@
-.resizable-container {
- display: flex;
- flex-direction: column;
- height: 100vh;
- width: 100%;
- overflow: hidden;
-}
-
-.top-pane,
-.bottom-pane {
- overflow: auto;
-}
-
-.top-pane {
-}
-
-.bottom-pane {
- flex: 1;
-}
-
-.divider {
- height: 6px;
- background-color: #4e545d;
- cursor: row-resize;
- transition: background-color 0.2s;
-}
-
-.divider:hover {
- background-color: #aaa;
-}
diff --git a/frontend/src/component/split-panel/split-panel.tsx b/frontend/src/component/split-panel/split-panel.tsx
deleted file mode 100644
index f86a75d05..000000000
--- a/frontend/src/component/split-panel/split-panel.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import React, {useState, useRef, useEffect, JSX} from 'react';
-import './split-panel.scss';
-
-interface SplitPanelProps {
- top: JSX.Element;
- bottom: JSX.Element;
-}
-
-export const SplitPanel = (props: SplitPanelProps) => {
- const containerRef = useRef(null);
- const [topHeight, setTopHeight] = useState(300);
- const [isDragging, setIsDragging] = useState(false);
-
- useEffect(() => {
- const handleMouseMove = (e: MouseEvent) => {
- if (!isDragging || !containerRef.current) return;
- const containerTop = containerRef.current.getBoundingClientRect().top;
- const newHeight = e.clientY - containerTop;
- setTopHeight(newHeight);
- };
-
- const stopDragging = () => setIsDragging(false);
-
- window.addEventListener('mousemove', handleMouseMove);
- window.addEventListener('mouseup', stopDragging);
-
- return () => {
- window.removeEventListener('mousemove', handleMouseMove);
- window.removeEventListener('mouseup', stopDragging);
- };
- }, [isDragging]);
-
- return (
-
-
- {props.top}
-
-
setIsDragging(true)}/>
-
- {props.bottom}
-
-
- );
-};
diff --git a/frontend/src/component/status-view/status-view.scss b/frontend/src/component/status-view/status-view.scss
deleted file mode 100644
index f1e4ea31c..000000000
--- a/frontend/src/component/status-view/status-view.scss
+++ /dev/null
@@ -1,38 +0,0 @@
-@use '../../scss/common';
-
-.status-view {
- display: flex;
- flex-flow: column;
- gap: 12px;
- padding: 4px;
-
- &__content {
- display: grid;
- grid-template-columns: auto 1fr;
- gap: 10px;
- }
-
- &__section-title {
- display: flex;
- flex-flow: row;
- align-items: center;
- gap: 20px;
- color: var(--title-label-color);
- font-weight: bold;
- font-size: 1.5rem;
- border-bottom: 1px solid var(--border-color);
- padding-bottom: 4px;
- margin: 8px 0;
-
- button {
- @include common.iconButton();
- }
- }
-
- &__col-label {
- color: var(--label-color);
- }
- &__col-value {
-
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/status-view/status-view.tsx b/frontend/src/component/status-view/status-view.tsx
deleted file mode 100644
index a8030894a..000000000
--- a/frontend/src/component/status-view/status-view.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import {useServices} from "../../provider/service-provider";
-import useTranslator from "../../hook/use-translator";
-import React, {useCallback, useEffect, useState} from "react";
-import {ServerIpCheck, ServerStatus} from "../../model/server-status";
-import {interval} from "rxjs";
-import {first} from "rxjs/operators";
-import './status-view.scss';
-import {getIconByName} from "../../icons/icons";
-
-const REQUEST_INTERVAL = 5000;
-const STATUS_COLUMNS = [
- "status",
- "version",
- "build_time",
- "server_time",
- "memory",
- "cache",
- "active_users",
- "active_user_connections",
- "active_provider_connections"];
-
-export default function StatusView() {
- const services = useServices();
- const translate = useTranslator();
- const [status, setStatus] = useState
();
- const [ipCheck, setIpCheck] = useState();
-
- const checkIp = useCallback(() => {
- services.status().getIpCheck().pipe(first()).subscribe({
- next: (data: ServerIpCheck) => setIpCheck(data),
- error: (err) => {
- setIpCheck(undefined);
- },
- });
- }, [services])
-
- useEffect(() => {
- const fetchData = () => services.status().getServerStatus().pipe(first()).subscribe({
- next: (data: any) => setStatus(data),
- error: () => {
- }
- });
- fetchData();
- const sub = interval(REQUEST_INTERVAL).subscribe(fetchData);
- checkIp();
- return () => sub.unsubscribe();
- }, [services, checkIp]);
-
- return
- {ipCheck && <>
-
{translate("LABEL.IP")} {getIconByName('Refresh')}
-
-
{translate("LABEL.IPv4")}
-
{ipCheck?.ipv4 ?? '?'}
-
{translate("LABEL.IPv6")}
-
{ipCheck?.ipv6 ?? '?'}
-
- >}
-
{translate("LABEL.STATUS")}
-
- {status && STATUS_COLUMNS.map(col =>
- {translate('LABEL.' + col.toUpperCase())}
- {
- (col === "active_provider_connections") ? JSON.stringify((status as any)?.[col]) : (status as any)?.[col]
- }
-
- )}
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/svg-icon/svg-icon.scss b/frontend/src/component/svg-icon/svg-icon.scss
deleted file mode 100644
index fbd460870..000000000
--- a/frontend/src/component/svg-icon/svg-icon.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-.svg-icon {
- fill: currentColor;
- width: 1em;
- aspect-ratio: 1 / 1;
- display: inline-block;
- font-size: 1.5rem;
- transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
- flex-shrink: 0;
- user-select: none;
- pointer-events: none;
-}
diff --git a/frontend/src/component/svg-icon/svg-icon.tsx b/frontend/src/component/svg-icon/svg-icon.tsx
deleted file mode 100644
index dd411186b..000000000
--- a/frontend/src/component/svg-icon/svg-icon.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import './svg-icon.scss';
-import * as React from "react";
-
-interface SvgIconProps {
- name: string;
- path: string;
-}
-
-function SvgIcon(props: SvgIconProps): React.JSX.Element {
- return
-
- ;
-}
-
-SvgIcon.displayName = 'SvgIcon';
-export default SvgIcon;
diff --git a/frontend/src/component/tab-set/tab-set.scss b/frontend/src/component/tab-set/tab-set.scss
deleted file mode 100644
index 1e75cec8e..000000000
--- a/frontend/src/component/tab-set/tab-set.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-
-.tab-set {
- display: flex;
- flex-flow: row wrap;
- gap: 2px;
- background-color: var(--tabset-background-color);
- border-bottom: 1px solid var(--tabset-border-color);
- border-top-left-radius: var(--border-radius);
- border-top-right-radius: var(--border-radius);
-
- list-style-type: none;
- padding: 0;
- margin: 0;
-
- &__tab {
- cursor: pointer;
- user-select: none;
- box-sizing: border-box;
- background-color: var(--tab-background-color);
- color: var(--tab-color);
- padding: 4px 8px;
- border-top-left-radius: var(--border-radius);
- //border-top-right-radius: var(--border-radius);
-
- &:active {
- transform: scale(0.9);
- }
-
- &:focus, &:hover {
- background-color: var(--tab-hover-background-color);
- color: var(--tab-hover-color);
- }
-
- &-active {
- border-bottom: 3px solid var(--tab-avtive-indicator-color);
- background-color: var(--tab-active-background-color);
- color: var(--tab-active-color);
- }
-
- &:focus-visible {
- outline: none;
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/tab-set/tab-set.tsx b/frontend/src/component/tab-set/tab-set.tsx
deleted file mode 100644
index 223e4dfd1..000000000
--- a/frontend/src/component/tab-set/tab-set.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import React, {useCallback, useMemo} from "react";
-
-import './tab-set.scss';
-import {genUuid} from "../../utils/uuid";
-import useTranslator from "../../hook/use-translator";
-
-export interface TabSetTab {
- label: string;
- key: string;
-}
-
-interface TabSetProps {
- tabs: TabSetTab[];
- active: string;
- onTabChange: (key: string) => void;
-}
-
-export default function TabSet(props: TabSetProps) {
- const {tabs, active, onTabChange} = props;
- const uuid = useMemo(() => genUuid(), []);
- const translate = useTranslator();
-
- const handleTabClick = useCallback((evt: any) => {
- const key = evt.target.dataset.key;
- onTabChange(key);
- }, [onTabChange]);
-
- const handleTabPress = useCallback((evt: any) => {
- let key = (evt as any).key;
- if (key === 'Enter' || key === ' ') {
- handleTabClick(evt)
- }
- const rightArrow = key === 'ArrowRight';
- const leftArrow = key === 'ArrowLeft';
- if (rightArrow || leftArrow) {
- const key = evt.target.dataset.key;
- let idx = tabs?.findIndex( t => t.key === key);
- if (idx >= 0) {
- if (leftArrow) {
- idx -= 1;
- } else if (rightArrow) {
- idx += 1;
- }
- if (idx > tabs.length-1) {
- idx = 0;
- } else if (idx < 0) {
- idx = tabs.length-1;
- }
- }
- let elem = document.getElementById(uuid + '-tab-' + idx)
- elem?.focus();
- }
- }, [uuid, tabs, handleTabClick]);
-
- return
- {tabs?.map((tab, idx) =>
- {translate(tab.label)} )}
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/tag-input/tag-input.scss b/frontend/src/component/tag-input/tag-input.scss
deleted file mode 100644
index 346c8212d..000000000
--- a/frontend/src/component/tag-input/tag-input.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.rti--container {
- --rti-bg: inherit !important;
- --rti-border: none !important;
- --rti-tag: var(--checkbox-checked-background-color) !important;
- --rti-tag-color: var(--text-color) !important;
- --rti-tag-border: var(--border-light-color) !important;
- --rti-tag-remove: var(--text-color) !important;
- --rti-tag-remove-hover: var(--tab-active-color) !important;
- max-width: 660px;
- padding: 0;
-}
-
diff --git a/frontend/src/component/tag-input/tag-input.tsx b/frontend/src/component/tag-input/tag-input.tsx
deleted file mode 100644
index 57d0ab649..000000000
--- a/frontend/src/component/tag-input/tag-input.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from "react";
-import { TagsInput } from "react-tag-input-component";
-import "./tag-input.scss";
-
-interface TagInputProps {
- name: string;
- values: string[];
- onChange: (name: string, values: string[]) => void;
- placeHolder?: string;
-}
-
-export default function TagInput(props: TagInputProps) {
- const { name, values, onChange, placeHolder } = props;
-
- const handleTagsChange = (newTags: string[]) => {
- onChange(name, newTags);
- };
-
- return (
-
-
-
- );
-}
diff --git a/frontend/src/component/tag-select/tag-select.scss b/frontend/src/component/tag-select/tag-select.scss
deleted file mode 100644
index 5f823fd7c..000000000
--- a/frontend/src/component/tag-select/tag-select.scss
+++ /dev/null
@@ -1,24 +0,0 @@
-.tag-select {
- display: flex;
- flex-flow: row wrap;
- gap: 4px;
- padding: 2px;
-
- &__tag {
- background-color: transparent;
- color: var(--text-color);
- padding: 3px 6px;
- border-radius: 4px;
- cursor: pointer;
- border: 1px solid var(--border-light-color);
-
- &:hover {
- border: 1px solid var(--checkbox-checked-background-color);
- }
-
- &-selected {
- background-color: var(--checkbox-checked-background-color);
- color: var(--text-color);
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/tag-select/tags-select.tsx b/frontend/src/component/tag-select/tags-select.tsx
deleted file mode 100644
index 8f95c22a0..000000000
--- a/frontend/src/component/tag-select/tags-select.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import './tag-select.scss';
-import {useCallback, useEffect, useMemo, useState} from "react";
-import {noop} from "rxjs";
-import {genUuid} from "../../utils/uuid";
-import useTranslator from "../../hook/use-translator";
-
-interface TagSelectProps {
- name: string;
- options: { value: any, label: string }[]
- defaultValues?: any[];
- onSelect: (name: string, values: any) => void;
- multi?: boolean;
- radio?: boolean;
-}
-
-export default function TagSelect(props: TagSelectProps) {
- const {name, multi, options, defaultValues, onSelect, radio} = props;
- const translate = useTranslator();
- const [selected, setSelected] = useState>({});
- const uuid = useMemo(() => genUuid(), []);
-
- useEffect(() => {
- if (defaultValues && options) {
- const valueIsArray = Array.isArray(defaultValues);
- const selections: Record = {}
- for (let i = 0; i < options.length; i++) {
- const optionValue = options[i].value;
- // eslint-disable-next-line eqeqeq
- selections[i] = valueIsArray ? defaultValues.includes(optionValue) : defaultValues == optionValue;
- }
- setSelected(selections);
- }
- return noop;
- }, [defaultValues, options]);
-
- const handleTagClick = useCallback((evt: any) => {
- const idx = evt.target.dataset.idx;
- setSelected((selections) => {
- const getSelections = () => {
- if (multi) {
- selections[idx] = !!!selections[idx];
- return {...selections};
- }
- if (radio) {
- if (!!selections[idx]) {
- return selections;
- } else {
- return {[idx]: !!!selections[idx]} as any;
- }
- }
- return {[idx]: !!!selections[idx]} as any;
- };
- const result = getSelections();
- const newSelections = Object.keys(result)?.map((key: any) => result[key] ? options[key].value : undefined).filter(Boolean);
- onSelect(name, multi ? newSelections : (newSelections.length ? newSelections[0] : undefined));
- return result;
- });
- }, [multi, radio, onSelect, name, options]);
-
- return
- {options?.map((o, idx) =>
- {translate(o.label)}
- )}
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/target-update-view/target-update-view.scss b/frontend/src/component/target-update-view/target-update-view.scss
deleted file mode 100644
index 5f79e0132..000000000
--- a/frontend/src/component/target-update-view/target-update-view.scss
+++ /dev/null
@@ -1,20 +0,0 @@
-@use '../../scss/common';
-@use '../../scss/preferences';
-
-.target-update {
-
- @include preferences.prefsRoot();
-
- @include common.list();
-
-
- @include preferences.prefsToolbar();
-
-
- &__content {
- overflow: auto;
- display: flex;
- flex-flow: column;
- padding: 12px;
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/target-update-view/target-update-view.tsx b/frontend/src/component/target-update-view/target-update-view.tsx
deleted file mode 100644
index e140a9135..000000000
--- a/frontend/src/component/target-update-view/target-update-view.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React, {useCallback, useMemo, useRef} from "react";
-import './target-update-view.scss';
-import ServerConfig from "../../model/server-config";
-import ConfigUtils from "../../utils/config-utils";
-import Checkbox from "../checkbox/checkbox";
-import {useServices} from "../../provider/service-provider";
-import {useSnackbar} from "notistack";
-import useTranslator from "../../hook/use-translator";
-
-interface TargetUpdateViewProps {
- config: ServerConfig
-}
-
-export default function TargetUpdateView(props: TargetUpdateViewProps) {
- const {config} = props;
-
- const services = useServices();
- const translate = useTranslator();
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
- const targets = useMemo(() => ConfigUtils.getTargetNames(config), [config]);
- const selected = useRef([]);
-
- const handleSelect = useCallback((target: string,checked: boolean) => {
- if (checked) {
- selected.current.push(target);
- } else {
- const idx = selected.current.indexOf(target);
- selected.current.splice(idx, 1);
- }
- }, []);
-
- const handleUpdate = useCallback((evt: any) => {
- services.playlist().update(selected.current).subscribe({
- next: () => enqueueSnackbar(translate('MESSAGES.PLAYLIST_UPDATE.SUCCESS'), {variant: 'success'}),
- error: (err) => enqueueSnackbar(translate('MESSAGES.PLAYLIST_UPDATE.FAILED') + err, {variant: 'error'}),
- });
- }, [services, enqueueSnackbar, translate]);
-
- return
-
{translate('LABEL.UPDATE')}
- {translate('LABEL.START')}
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/tooltip/tooltip.scss b/frontend/src/component/tooltip/tooltip.scss
deleted file mode 100644
index 482fbedf5..000000000
--- a/frontend/src/component/tooltip/tooltip.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-
-.tooltip {
- position: absolute;
- display: inline-block;
- cursor: pointer;
- background: black;
- color: white;
- padding: 5px 10px;
- border-radius: 5px;
- opacity: 0;
- transition: opacity 2s ease-in-out;
- z-index: 9999;
- pointer-events: none;
-}
diff --git a/frontend/src/component/tooltip/tooltip.tsx b/frontend/src/component/tooltip/tooltip.tsx
deleted file mode 100644
index 244fff058..000000000
--- a/frontend/src/component/tooltip/tooltip.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import {useLayoutEffect, useRef} from "react";
-import './tooltip.scss';
-import useTranslator from "../../hook/use-translator";
-
-export default function Tooltip() {
-
- const ref = useRef(null);
- const translate = useTranslator();
-
- useLayoutEffect(() => {
- const handler = (event: any) => {
- const tooltip = event.target.dataset.tooltip;
- if (tooltip) {
- ref.current.innerText = translate(tooltip);
- setTimeout(() => {
- const el = event.target;
- const rect = el.getBoundingClientRect();
- const screenWidth = window.innerWidth;
- const screenHeight = window.innerHeight;
-
- let tooltipX = rect.left;
- let tooltipY = rect.top + rect.height + 10;
-
- ref.current.style.left = tooltipX + 'px';
- ref.current.style.top = tooltipY + 'px';
-
- if (tooltipX + ref.current.offsetWidth > screenWidth) {
- ref.current.style.left = (screenWidth - ref.current.offsetWidth) + 'px';
- }
-
- if (tooltipX < 5) {
- ref.current.style.left = '5px';
- }
-
- if (tooltipY < 5) {
- ref.current.style.top = '5px';
- }
-
- if (tooltipY + ref.current.offsetHeight > screenHeight) {
- ref.current.style.top = (rect.top - ref.current.offsetHeight - 5) + 'px';
- }
-
- ref.current.style.opacity = '1';
- }, 0);
- }
- };
- const handlerLeave = (event: any) => {
- ref.current.innerText = '';
- ref.current.style.top = '-1000px';
- ref.current.style.opacity = '0';
- };
- document.addEventListener("mouseover", handler);
- document.addEventListener("mouseout", handlerLeave);
- return () => {
- document.removeEventListener("mouseover", handler);
- document.removeEventListener("mouseout", handlerLeave);
- }
- }, [translate])
-
- return
-}
diff --git a/frontend/src/component/user-editor/user-editor.scss b/frontend/src/component/user-editor/user-editor.scss
deleted file mode 100644
index 81b23e425..000000000
--- a/frontend/src/component/user-editor/user-editor.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-@use '../../scss/common';
-@use '../../scss/preferences';
-
-.tag-select {
- min-width: 160px;
-}
-
-.user-editor {
- & {
- min-height: 400px;
- @include preferences.prefsToolbar();
-
- }
-
- &__content {
- @media (max-width: common.$media-size-mobile) {
- .form-view__col-label {
- justify-content: flex-start;
- }
- }
- }
-}
-
diff --git a/frontend/src/component/user-editor/user-editor.tsx b/frontend/src/component/user-editor/user-editor.tsx
deleted file mode 100644
index 92b9fb762..000000000
--- a/frontend/src/component/user-editor/user-editor.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import React, {forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from "react";
-import './user-editor.scss';
-import {Credentials} from "../../model/server-config";
-import useTranslator from "../../hook/use-translator";
-import FormView, {FormFieldType} from "../form-view/from-view";
-
-export const STATUS_OPTIONS = [
- {value: 'Active', label: 'Active'},
- {value: 'Expired', label: 'Expired'},
- {value: 'Banned', label: 'Banned'},
- {value: 'Trial', label: 'Trial'},
- {value: 'Disabled', label: 'Disabled'},
- {value: 'Pending', label: 'Pending'},
-];
-
-const COLUMNS = [
- {name: 'username', label: 'LABEL.USERNAME', fieldType: FormFieldType.TEXT},
- {name: 'password', label: 'LABEL.PASSWORD', fieldType: FormFieldType.TEXT},
- {name: 'token', label: 'LABEL.TOKEN', fieldType: FormFieldType.TEXT},
- {name: 'server', label: 'LABEL.SERVER', fieldType: FormFieldType.SINGLE_SELECT},
- {name: 'proxy', label: 'LABEL.PROXY', fieldType: FormFieldType.PROXY_TYPE},
- {name: 'max_connections', label: 'LABEL.MAX_CON', fieldType: FormFieldType.NUMBER},
- {name: 'status', label: 'LABEL.STATUS', fieldType: FormFieldType.SINGLE_SELECT, options: STATUS_OPTIONS},
- {name: 'exp_date', label: 'LABEL.EXP_DATE', fieldType: FormFieldType.DATE},
- {name: 'ui_enabled', label: 'LABEL.UI_ENABLED', fieldType: FormFieldType.CHECK},
- {name: 'comment', label: 'LABEL.NOTES', fieldType: FormFieldType.TEXT}
-];
-
-interface UserViewProps {
- serverOptions: { value: string, label: string }[];
- onSubmit: (user: Credentials, target: string) => boolean;
-}
-
-export interface IUserEditor {
- edit: (user: Credentials, target: string) => void;
- close: () => void;
-}
-
-const UserEditor = forwardRef((props: UserViewProps, ref: any) => {
- const {serverOptions, onSubmit} = props;
- const dialogRef = useRef(null);
- const translate = useTranslator();
- const formFields = useMemo(() => COLUMNS
- .map((c) => {
- let options = c.options;
- if (c.name === 'server') {
- options = serverOptions;
- }
- return ({...c, options});
- }), [serverOptions]);
-
- const [user, setUser] = useState(undefined);
- const [target, setTarget] = useState(undefined);
-
- const edit = useCallback((user: Credentials, target_name: string) => {
- setUser(user);
- setTarget(target_name);
- dialogRef.current?.showModal();
- }, []);
-
- const close = useCallback(() => {
- dialogRef.current?.close();
- }, []);
-
- const reference = useMemo(() => ({ edit, close }), [edit, close]);
-
- useImperativeHandle(ref, () => reference);
-
- const handleSubmit = useCallback(() => {
- if (onSubmit(user, target)) {
- dialogRef.current.close();
- }
- }, [onSubmit, user, target]);
-
- return
-
-
-
-
-
- dialogRef.current?.close()}>{translate('LABEL.CANCEL')}
- {translate('LABEL.OK')}
-
-
- ;
-});
-
-export default UserEditor;
\ No newline at end of file
diff --git a/frontend/src/component/user-playlist/user-playlist.scss b/frontend/src/component/user-playlist/user-playlist.scss
deleted file mode 100755
index a3d1d7db8..000000000
--- a/frontend/src/component/user-playlist/user-playlist.scss
+++ /dev/null
@@ -1,32 +0,0 @@
-@use '../../scss/common';
-@use '../../scss/preferences';
-
-.user-playlist {
- @include common.container_vert();
- overflow: hidden;
-
- @include preferences.prefsToolbar();
-
- &__content {
- @include common.container_horz_no_padding();
- overflow: hidden;
-
- &-toolbar {
- @include common.container_vert_no_padding();
- button {
- @include common.iconButton();
- height: 48px;
- }
-
- .button-active {
- @include common.activeOptionButton();
- }
- }
-
- &-panels {
- @include common.container_vert_no_padding();
- flex: 1 1 auto;
- overflow: hidden;
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/user-playlist/user-playlist.tsx b/frontend/src/component/user-playlist/user-playlist.tsx
deleted file mode 100755
index 69a8bfa8a..000000000
--- a/frontend/src/component/user-playlist/user-playlist.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import React, {useCallback, useEffect, useRef, useState} from 'react';
-import './user-playlist.scss';
-import {useServices} from "../../provider/service-provider";
-import {finalize, first, zip} from 'rxjs';
-import {UserPlaylistCategories, UserPlaylistTargetCategories} from "../../model/playlist";
-import LoadingIndicator from '../loading-indicator/loading-indicator';
-import useTranslator from "../../hook/use-translator";
-import UserTargetPlaylist, {BouquetSelection} from "../user-target-playlist/user-target-playlist";
-import {useSnackbar} from "notistack";
-
-const TARGET_TABS = [
- {label: "LABEL.XC", key: "xtream"},
- {label: "LABEL.M3U", key: "m3u"},
-];
-
-function isEmpty(value: any): boolean {
- if (value == null) return true;
- if (typeof value === "string") return value.trim() === "";
- if (Array.isArray(value)) return value.length === 0;
- if (typeof value === "object") return Object.keys(value).length === 0;
- return false;
-}
-
-/* eslint-disable @typescript-eslint/no-empty-interface */
-interface UserPlaylistProps {
-
-}
-
-export default function UserPlaylist(props: UserPlaylistProps) {
- const services = useServices();
- const translate = useTranslator();
- const [loading, setLoading] = useState(false);
- const [categories, setCategories] = useState(undefined);
- const [bouquets, setBouquets] = useState(undefined);
- const [activeTab, setActiveTab] = useState(TARGET_TABS[0].key);
- const selectionRef = useRef<{ xtream: BouquetSelection, m3u: BouquetSelection }>({} as any);
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
-
- useEffect(() => {
- setLoading(true);
- zip(services.userConfig().getPlaylistBouquet().pipe(first()),
- services.userConfig().getPlaylistCategories().pipe(first())).pipe(finalize(() => setLoading(false)))
- .subscribe(([bouquet, categories]: [UserPlaylistCategories, UserPlaylistCategories]) => {
- if (isEmpty(bouquet)) {
- bouquet = undefined;
- }
- if (isEmpty(categories)) {
- categories = undefined;
- }
- setCategories(categories);
- setBouquets(bouquet);
-
- const mapToSelection = (userBouquet: string[]) => {
- if (userBouquet) {
- return userBouquet.reduce((acc: any, e: string) => {
- acc[e] = true;
- return acc;
- }, {})
- }
- return undefined;
- }
- const mapTargetToSelection = (targetBouquet: UserPlaylistTargetCategories) => {
- return {
- live: mapToSelection(targetBouquet?.live),
- vod: mapToSelection(targetBouquet?.vod),
- series: mapToSelection(targetBouquet?.series),
- }
- }
-
- selectionRef.current = {
- xtream: mapTargetToSelection(bouquet?.xtream),
- m3u: mapTargetToSelection(bouquet?.m3u),
- };
- });
- }, [services]);
-
- const handleActiveTabChange = useCallback((event: any) => {
- const tab = event.target.dataset.tab;
- setActiveTab(tab);
- }, []);
-
- const handleSave = useCallback(() => {
- setLoading(true);
-
- const toClusterCategories = (clusterBouquet: any, clusterCategories: any): string[] => {
- const result = clusterBouquet ? Object.keys(clusterBouquet).filter(key => clusterBouquet[key]) : undefined;
- if (result?.length === clusterCategories?.length) {
- return undefined;
- }
- return result;
- }
- const toTargetCategories = (bs: BouquetSelection, targetCategories: UserPlaylistTargetCategories): UserPlaylistTargetCategories => ({
- live: toClusterCategories(bs?.live, targetCategories?.live),
- vod: toClusterCategories(bs?.vod, targetCategories?.vod),
- series: toClusterCategories(bs?.series, targetCategories?.series)
- });
-
- const bouquet: UserPlaylistCategories = {
- xtream: toTargetCategories(selectionRef.current.xtream, categories.xtream),
- m3u: toTargetCategories(selectionRef.current.m3u, categories.m3u)
- }
-
- services.userConfig().savePlaylistBouquet(bouquet).pipe(first(), finalize(() => setLoading(false))).subscribe({
- next: () => {
- enqueueSnackbar(translate('MESSAGES.SAVE.BOUQUET.SUCCESS'), {variant: 'success'})
- },
- error: () => {
- enqueueSnackbar(translate('MESSAGES.SAVE.BOUQUET.FAIL'), {variant: 'error'})
- }
- })
- }, [services, translate, enqueueSnackbar, categories?.m3u, categories?.xtream]);
-
- const handleSelectionChange = (selections: BouquetSelection) => {
- (selectionRef.current as any)[activeTab] = selections;
- }
-
- return <>
-
-
-
- {translate('TITLE.USER_BOUQUET_EDITOR')}
- {translate('LABEL.SAVE')}
-
-
-
- {TARGET_TABS.map((t) =>
- {translate(t.label)} )}
-
-
- {TARGET_TABS.map((t) =>
- )}
-
-
-
- >;
-}
diff --git a/frontend/src/component/user-target-playlist/user-target-playlist.scss b/frontend/src/component/user-target-playlist/user-target-playlist.scss
deleted file mode 100755
index ecf9b31aa..000000000
--- a/frontend/src/component/user-target-playlist/user-target-playlist.scss
+++ /dev/null
@@ -1,91 +0,0 @@
-@use '../../scss/common';
-@use '../../scss/preferences';
-
-.user-target-playlist {
- @include common.container_vert();
- overflow: hidden;
-
- @include preferences.prefsToolbar();
-
- &__header {
- font-size: 2rem;
- color: var(--label-color);
- }
- &__categories-panel {
- @include common.container_vert_no_padding();
- overflow: hidden;
- }
-
- &__categories-toolbar {
- @include common.container_horz_no_padding();
- }
-
- &__categories {
- display: grid;
- grid-gap: 10px;
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
- overflow-x: hidden;
- overflow-y: auto;
- gap: 8px;
- padding: 4px;
-
- &__toolbar {
- display: flex;
- flex-flow: row nowrap;
- gap: 4px;
- flex: 0 0 auto;
- padding: 4px;
- background-color: var(--background-color);
- align-items: center;
-
- @media (max-width: common.$media-size-mobile) {
- flex-wrap: wrap-reverse;
- gap: 8px;
- }
-
- &-filter {
- display: flex;
- flex: 1 1 auto;
- background-color: inherit;
- .playlist-filter {
- input {
- font-size: 1em;
- }
- }
- }
-
- button {
- @include common.iconButton();
- height: 48px;
- }
-
- .button-active {
- @include common.activeOptionButton();
- text-orientation: upright;
- }
- }
-
- &__category {
- border:1px solid var(--border-color);
- padding: 12px 8px;
- border-radius: var(--border-radius);
- background-color: var(--card-background-color);
- box-sizing: border-box;
- display: flex;
- flex-flow: row nowrap;
- gap: 8px;
- white-space: nowrap;
-
- .checkbox-container {
- width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .checkbox-container:hover {
- cursor: pointer;
- color: var(--label-color);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/user-target-playlist/user-target-playlist.tsx b/frontend/src/component/user-target-playlist/user-target-playlist.tsx
deleted file mode 100755
index 1e55dc459..000000000
--- a/frontend/src/component/user-target-playlist/user-target-playlist.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import React, {useCallback, useEffect, useState} from 'react';
-import './user-target-playlist.scss';
-import {UserPlaylistCategories, UserPlaylistTargetCategories} from "../../model/playlist";
-import TabSet from "../tab-set/tab-set";
-import Checkbox from "../checkbox/checkbox";
-import {getIconByName} from "../../icons/icons";
-import PlaylistFilter from "../playlist-filter/playlist-filter";
-import useTranslator from "../../hook/use-translator";
-
-const CATEGORY_TABS = [
- {label: "LABEL.LIVE", key: "live"},
- {label: "LABEL.VOD", key: "vod"},
- {label: "LABEL.SERIES", key: "series"}
-];
-
-export interface BouquetSelection {
- live: Record,
- vod: Record,
- series: Record,
-}
-
-const selectEntries = (selected: boolean, list: string[]): Record => {
- return list?.reduce((acc: Record, category: string) => {
- acc[category] = selected;
- return acc;
- }, {});
-}
-
-interface UserTargetPlaylistProps {
- visible: boolean;
- categories: UserPlaylistTargetCategories;
- bouquet: UserPlaylistTargetCategories;
- onSelectionChange: (selections: BouquetSelection) => void,
-}
-
-export default function UserTargetPlaylist(props: UserTargetPlaylistProps) {
- const {categories, bouquet, visible, onSelectionChange} = props;
- const translate = useTranslator();
- const [filteredCategories, setFilteredCategories] = useState({} as any);
- const [selections, setSelections] = useState({} as any);
- const [activeTab, setActiveTab] = useState(CATEGORY_TABS[0].key);
- const [showSelected, setShowSelected] = useState(false);
-
- useEffect(() => {
- if (categories) {
- Object.values(categories).forEach((list: string[]) => {
- list.sort((a, b) => a.localeCompare(b, {sensitivity: 'base'} as any))
- });
- const mapToSelection = (userBouquet: any, cluster: string) => {
- let clusterBouquet = userBouquet?.[cluster];
- return (categories as any)[cluster].reduce((acc: any, e: string) => {
- acc[e] = clusterBouquet ? clusterBouquet?.indexOf(e) >= 0 : true
- return acc;
- }, {})
- }
- const user_bouquets = {
- live: mapToSelection(bouquet, 'live'),
- vod: mapToSelection(bouquet, 'vod'),
- series: mapToSelection(bouquet,'series'),
- };
- setSelections(user_bouquets);
- }
- }, [bouquet, categories])
-
- const getActiveCategories = useCallback((key: string) => {
- let active = ((filteredCategories as any)?.[key] ?? (categories as any)?.[key]) as any;
- if (showSelected) {
- return active.filter((c: string) => (selections as any)[key]?.[c])
- }
- return active;
- },
- [categories, filteredCategories, showSelected, selections]);
-
-
- const handleCheckboxChange = useCallback((value: string, checked: boolean) => {
- let clusterSelections = (selections as any)[activeTab] as any;
- if (!clusterSelections) {
- clusterSelections = {};
- (selections as any)[activeTab] = clusterSelections as any;
- }
- clusterSelections[value] = checked;
- let newSelections = ({...selections, [activeTab]: clusterSelections});
- onSelectionChange(newSelections)
- setSelections(newSelections);
- }, [activeTab, selections, onSelectionChange]);
-
- const renderCat = useCallback((cat: string) => {
- const clusterSelections = (selections as any)[activeTab];
- // eslint-disable-next-line eqeqeq
- const selected = clusterSelections?.[cat] === true;
- return
-
-
;
- }, [handleCheckboxChange, selections, activeTab]);
-
- const toggleSelection = (selected: boolean) => {
- let activeCategories = getActiveCategories(activeTab);
- if (activeCategories?.length) {
- const newSelections: any = {...selections, [activeTab]: selectEntries(selected, activeCategories)};
- onSelectionChange(newSelections);
- setSelections(newSelections);
- }
- };
-
- const handleFilter = useCallback((filter: string, regexp: boolean): void => {
- let filter_value = regexp ? filter : filter.toLowerCase();
- if (filter_value?.length) {
- const filtered = (categories as any)?.[activeTab]?.filter((cat: string) => {
- if (regexp) {
- // eslint-disable-next-line eqeqeq
- return cat.trim().match(filter_value) != undefined;
- } else {
- return (cat.trim().toLowerCase().indexOf(filter_value) > -1);
- }
- }) ?? [];
- setFilteredCategories(filteredCategories => ({...filteredCategories, [activeTab]: filtered}));
- } else {
- setFilteredCategories(filteredCategories => ({
- ...filteredCategories,
- [activeTab]: (categories as any)?.[activeTab]
- }));
- }
- }, [activeTab, categories]);
-
- const handleShowSelected = useCallback((event: any) => {
- event.target.blur();
- setShowSelected(!showSelected);
- }, [showSelected]);
-
- const tabs = CATEGORY_TABS.filter(tab => (categories as any)?.[tab.key]?.length);
- if (tabs.length === 0) {
- return
- {translate("MESSAGES.NO_CONTENT")}
-
- }
- return <>
-
-
- {tabs.map(tab =>
-
-
-
{getIconByName('Checked')}
-
toggleSelection(true)}>{getIconByName('SelectAll')}
-
toggleSelection(false)}>{getIconByName('DeselectAll')}
-
-
- {getActiveCategories(tab.key)?.map(renderCat)}
-
-
)
- }
-
- >;
-}
diff --git a/frontend/src/component/user-view/user-view.scss b/frontend/src/component/user-view/user-view.scss
deleted file mode 100644
index a04e40623..000000000
--- a/frontend/src/component/user-view/user-view.scss
+++ /dev/null
@@ -1,280 +0,0 @@
-@use '../../scss/common';
-@use '../../scss/preferences';
-
-dialog:not([open]) {
- display: none;
-}
-
-.comment-dialog {
- display: flex;
- flex-flow: column;
- color: var(--text-color);
- justify-content: space-between;
-
- button {
- @include common.textButton();
- }
- min-height: 200px;
- min-width: 280px;
-}
-
-.label {
- color: var(--label-color);
-}
-
-.proxy-type {
- &-reverse {
- }
- &-redirect {
- color: #fffa00;
- }
-}
-
-.status {
- &-active, .status-trial {
- color: var(--ok-color);
- }
- &-banned {
- color: var(--attention-color);
- }
- &-expired, &-disabled, &-pending {
- color: var(--warn-color);
- }
-}
-
-.tag-select {
- min-width: 160px;
-}
-
-.checkbox-checked {
- color: var(--ok-color);
-}
-
-.checkbox-unchecked {
- color: var(--text-color);
-}
-
-
-.user {
- & {
- min-height: 400px;
- background-color: inherit;
- @include preferences.prefsRoot();
- @include preferences.prefsToolbar();
- }
-
- &__content {
- & {
- @include common.container_vert_no_padding(flex-start);
- overflow: hidden;
- }
- label {
- font-weight: bold;
- color: var(--label-color);
- span.target-not-exists {
- color: var(--attention-color);
- }
- }
-
- &-targets {
- display: flex;
- flex-flow: column;
- flex: 1 1 auto;
- gap: 20px;
- justify-content: flex-start;
- padding-right: 6px;
- overflow: hidden;
- }
- }
-
- &__target {
- & {
- display: flex;
- flex-flow: column;
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- overflow: hidden;
- background-color: var(--background-color);
- gap: 12px;
- }
-
- &-target {
- & {
- font-size: 1.2rem;
- align-items: center;
- display: flex;
- flex-flow: row;
- padding: 8px;
- gap: 12px;
- span {
- color: var(--label-color);
- }
- }
-
- label {
- flex: 1 1 0;
- display: flex;
- flex-flow: row;
- align-items: center;
- align-content: center;
- gap: 8px;
- }
-
- &-options {
- font-size: 0.9rem;
- color: var(--warn-color);
- }
-
- &-toolbar {
- & {
- flex: 0 0 auto;
- padding-right: 8px;
- }
- button {
- @include common.iconButton();
- }
- }
- }
-
- &-user {
- &-table-container {
- flex: 1 1 auto;
- overflow-y: scroll;
- background-color: var(--card-background-color);
- padding: 8px;
- margin: 10px;
- box-sizing: border-box;
- }
- &-table {
- display: table;
- table-layout: auto;
- width: 100%;
- height: auto;
- box-sizing: border-box;
- border-collapse: collapse;
- }
-
- &-row {
- display: table-row;
- width: auto;
- height: 3rem;
- clear: both;
- border: 1px solid var(--table-border-color);
- }
-
- &-col-header {
- padding: 8px;
- background-color: var(--background-color);
- &-tools {
- width: 64px !important;
- }
- }
-
- &-col {
- & {
- display: table-cell;
- width: auto;
- vertical-align: middle;
- padding-left: 4px;
- padding-right: 4px;
- white-space: nowrap;
-
- }
-
- &-value {
- display: flex;
- flex-flow: row nowrap;
- align-items: center;
- gap: 4px;
- }
-
- &-action {
- cursor: pointer;
- span {
- pointer-events: none;
- }
- &:hover {
- color: var(--icon-button-hover-background-color);
- }
- }
-
- &-comment {
- max-width: 200px;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .visibility {
- opacity: 0.5;
- cursor: pointer;
- &:hover {
- color: var(--icon-button-hover-background-color);
- }
- }
-
- &-toolbar {
- & {
- display: flex;
- flex-flow: row;
- align-items: flex-end;
- justify-content: flex-start;
- padding-left: 4px;
- }
- span {
- //padding: 4px;
- //transform: scale(0.9) translateY(5px);
- padding: 4px;
- @include common.iconButton();
- &:first-child {
- border-radius: var(--icon-button-border-radius) 0 0 var(--icon-button-border-radius) !important;
- }
-
- &:last-child {
- border-radius: 0 var(--icon-button-border-radius) var(--icon-button-border-radius) 0 !important;
- }
- }
- }
- }
-
- &-col-label {
- display: none;
- }
-
- @media (max-width: 980px) {
- &-table {
- display: flex;
- flex-flow: column;
- gap: 20px;
-
- &-header {
- display: none !important;
- }
-
- }
-
- &-row {
- display: flex;
- flex-flow: column;
- gap: 4px;
- height: auto;
- }
-
- &-col-label {
- display: block;
- padding: 4px;
- }
-
- &-col {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 4px;
-
- &-toolbar {
- padding-bottom: 4px;
- }
- }
- }
- }
- }
-}
-
diff --git a/frontend/src/component/user-view/user-view.tsx b/frontend/src/component/user-view/user-view.tsx
deleted file mode 100644
index 6267117f0..000000000
--- a/frontend/src/component/user-view/user-view.tsx
+++ /dev/null
@@ -1,504 +0,0 @@
-import React, {JSX, useCallback, useEffect, useRef, useState} from "react";
-import './user-view.scss';
-import ServerConfig, {Credentials, TargetUser} from "../../model/server-config";
-import {getIconByName} from "../../icons/icons";
-import {useSnackbar} from "notistack";
-import {useServices} from "../../provider/service-provider";
-import ConfigUtils from "../../utils/config-utils";
-import TabSet, {TabSetTab} from "../tab-set/tab-set";
-import useTranslator from "../../hook/use-translator";
-import DateUtils from "../../utils/date-utils";
-import PlaylistFilter from "../playlist-filter/playlist-filter";
-import UserEditor, {STATUS_OPTIONS} from "../user-editor/user-editor";
-import TextGenerator from "../../utils/text-generator";
-
-const renderExpDate = (value: any, hidden?: boolean) => {
- if (!value) {
- return getIconByName('Unlimited');
- }
- return DateUtils.formatDate(value)
-};
-
-const renderBool = (value: any, hidden?: boolean) => {
- return {getIconByName(value ? 'CheckMark' : 'Clear')} ;
-};
-
-const renderComment = (value: any, hidden?: boolean) => {
- if (value?.length) {
- return {value} ;
- }
- return undefined;
-};
-
-const renderMaxCon = (value: any, hidden?: boolean) => {
- if (!value) {
- return getIconByName('Unlimited');
- }
- return value;
-};
-
-const renderStatus = (value: any, hidden?: boolean) => {
- if (value) {
- return {value}
- }
- return value;
-};
-
-const renderProxyType = (value: any, hidden?: boolean) => {
- if (value) {
- return {value}
- }
- return value;
-};
-
-const COLUMNS = [
- {field: 'username', label: 'LABEL.USERNAME'},
- {field: 'password', label: 'LABEL.PASSWORD', hidden: true},
- {field: 'token', label: 'LABEL.TOKEN', hidden: true},
- {field: 'server', label: 'LABEL.SERVER'},
- {field: 'proxy', label: 'LABEL.PROXY', render: renderProxyType},
- {field: 'max_connections', label: 'LABEL.MAX_CON', render: renderMaxCon},
- {field: 'status', label: 'LABEL.STATUS', render: renderStatus},
- {field: 'exp_date', label: 'LABEL.EXP_DATE', render: renderExpDate},
- {field: 'ui_enabled', label: 'LABEL.UI_ENABLED', render: renderBool},
- {field: 'comment', label: 'LABEL.NOTES', render: renderComment, action: true},
-]
-
-COLUMNS.forEach(col => {
- if (!col.render) {
- col.render = (value: any, hidden?: boolean) => value ? (hidden ? '***' : '' + value) : '';
- }
-})
-
-const prepareCredentials = (targetUser: TargetUser[]) => {
- targetUser.forEach((user) => {
- user.credentials.forEach((credential) => {
- if (credential.exp_date) {
- credential.exp_date = DateUtils.unixSecondsToDate(credential.exp_date);
- }
- if (credential.created_at) {
- credential.created_at = DateUtils.unixSecondsToDate(credential.created_at);
- }
- })
- });
-}
-
-const prepareTargetUserForSave = (targetUser: TargetUser[]): TargetUser[] => {
- return targetUser.map((user) => {
- let storeUser = {...user, credentials: user.credentials.map(c => ({...c}))};
- storeUser.credentials.forEach((credential) => {
- credential.exp_date = DateUtils.toUnixSeconds(credential.exp_date);
- credential.created_at = DateUtils.toUnixSeconds(credential.created_at);
- });
- return storeUser;
- });
-}
-
-const usernameExists = (uname: string, targets: TargetUser[]): boolean => {
- for (const target of targets) {
- if (target.credentials.find(c => c.username === uname)) {
- return true;
- }
- }
- return false;
-};
-
-const checkuser = (user: Credentials): string | undefined => {
- if (!user.username?.trim().length) {
- return "MESSAGES.SAVE.USER.USERNAME_REQUIRED";
- }
- // eslint-disable-next-line eqeqeq
- if (user.max_connections != undefined) {
- const max_con = parseInt(user.max_connections as any);
- // eslint-disable-next-line eqeqeq
- if (isNaN(max_con) || max_con < 0 || (('' + max_con) != user.max_connections as any)) {
- return 'MESSAGES.SAVE.USER.MAX_CONNECTIONS_INVALID';
- } else {
- if (max_con < 1) {
- user.max_connections = undefined;
- } else {
- user.max_connections = max_con;
- }
- }
- }
- return undefined;
-}
-
-
-const createNewUser = (targets: TargetUser[]): Credentials => {
- let cnt = 0;
- let username = TextGenerator.generateUsername().toLowerCase();
- while (usernameExists(username, targets)) {
- username = TextGenerator.generateUsername().toLowerCase();
- cnt++;
- if (cnt > 1000) {
- username = "";
- break;
- }
- }
- const created_at = Date.now();
- const exp_date = new Date();
- exp_date.setFullYear(exp_date.getFullYear() + 1);
- return {
- username,
- password: TextGenerator.generatePassword(),
- token: TextGenerator.generatePassword(),
- proxy: 'reverse',
- created_at,
- exp_date: exp_date.getTime(),
- max_connections: 1,
- status: "Active",
- ui_enabled: true,
- comment: undefined,
- // @ts-ignore
- _ref: undefined, // an indicator for new user
- };
-}
-
-interface UserViewProps {
- config: ServerConfig;
-}
-
-export default function UserView(props: UserViewProps) {
- const {config} = props;
- const services = useServices();
- const translate = useTranslator();
- const userEditorRef = useRef(null);
- const {enqueueSnackbar/*, closeSnackbar*/} = useSnackbar();
- const [serverOptions, setServerOptions] = useState<{ value: string, label: string }[]>([]);
- const [targets, setTargets] = useState([]);
- const [targetOptions, setTargetOptions] = useState>({});
- const [activeTarget, setActiveTarget] = useState(undefined);
- const [tabs, setTabs] = useState([]);
- const [showHiddenFields, setShowHiddenFields] = useState>({});
- const [filteredUser, setFilteredUser] = useState>({});
-
- useEffect(() => {
- if (config) {
- const serverOptions = config?.api_proxy?.server?.map(serverInfo => ({
- value: serverInfo.name,
- label: serverInfo.name
- }));
- setServerOptions(serverOptions || []);
- const target_names = ConfigUtils.getTargetNames(config);
- const missing = config?.api_proxy?.user.filter(target => !target_names.includes(target.target));
- const targetOptions = config.sources
- ?.flatMap(s => s.targets || [])
- ?.reduce((acc: any, t: any) => {
- acc[t.name] = t.options || {};
- return acc;
- }, {});
- setTargetOptions(targetOptions);
- const result: TargetUser[] = target_names?.map(name => ({
- src: true,
- target: name,
- credentials: config.api_proxy?.user.find(t => t.target === name)?.credentials || []
- } as any));
- prepareCredentials(result);
- missing?.forEach(target => {
- result.push({src: false, target: target.target, credentials: target.credentials} as any);
- });
- const targets: TargetUser [] = result || [];
- setTargets(targets);
- if (targets?.length) {
- setActiveTarget(targets[0].target);
- const hiddenFields = COLUMNS.filter(c => c.hidden);
- const hidden: any = {};
- targets.forEach(target => target.credentials.forEach(c => {
- if (!c.server) {
- c.server = serverOptions?.[0]?.value;
- }
- hiddenFields.forEach(h => {
- hidden[c.username + h.field] = true
- });
- }));
- setShowHiddenFields(hidden);
- }
- setTabs(targets.map(target => ({key: target.target, label: target.target})));
- }
- }, [config])
-
- const handleUserAdd = useCallback((evt: any) => {
- const target_name = evt.target.dataset.target;
- const target = targets.find(target => target.target === target_name);
- if (target) {
- const user = createNewUser(targets);
- userEditorRef.current.edit(user, target_name);
- }
- }, [targets]);
-
- const handleUserRemove = useCallback((evt: any) => {
- const username = evt.target.dataset.user;
- const target_name = evt.target.dataset.target;
- const target = targets.find(target => target.target === target_name);
- if (target) {
- let idx = target.credentials.findIndex(c => c.username === username)
- if (idx >= 0) {
- target.credentials.splice(idx, 1);
- setTargets([...targets]);
- }
- }
- }, [targets]);
-
- const handleUserEdit = useCallback((evt: any) => {
- const username = evt.target.dataset.user;
- const target_name = evt.target.dataset.target;
- const target = targets.find(target => target.target === target_name);
- if (target) {
- const user = target.credentials.find(c => c.username === username);
- if (user) {
- userEditorRef.current.edit({...user, _ref: user.username}, target_name);
- }
- }
- }, [targets]);
-
- const handleSave = useCallback(() => {
- const usernames: any = {};
- const tokens: any = {};
- for (const target of targets) {
- for (const user of target.credentials) {
- const err = checkuser(user);
- if (err) {
- enqueueSnackbar(translate(err), {variant: 'error'});
- return;
- }
- if (usernames[user.username]) {
- enqueueSnackbar(translate("MESSAGES.USER.DUPLICATE_USERNAME") + user.username, {variant: 'error'});
- return;
- }
- usernames[user.username] = true;
- if (user.token) {
- if (tokens[user.token]) {
- enqueueSnackbar(translate("MESSAGES.USER.DUPLICATE_TOKEN") + user.token, {variant: 'error'});
- return;
- }
- tokens[user.token] = true;
- }
- }
- }
- const targetUser = targets.map(t => {
- t.credentials.forEach(c => {
- c.username = c.username.trim();
- c.password = c.password.trim();
- c.token = c.token?.trim();
- })
- return {target: t.target, credentials: t.credentials}
- });
- const toSaveTargetUser = prepareTargetUserForSave(targetUser);
- services.config().saveTargetUser(toSaveTargetUser).subscribe({
- next: () => enqueueSnackbar(translate('MESSAGES.SAVE.USER.SUCCESS'), {variant: 'success'}),
- error: (err) => enqueueSnackbar(translate('MESSAGES.SAVE.USER.FAIL'), {variant: 'error'})
- });
- }, [targets, services, enqueueSnackbar, translate]);
-
- const handleVisibility = useCallback((evt: any) => {
- let key = evt.target.dataset.hiddenkey;
- setShowHiddenFields((hiddenFields: any) => ({...hiddenFields, [key]: !hiddenFields[key]}));
- }, []);
-
- const filterTarget = useCallback((target: TargetUser, filter: string, regexp: boolean, filterForStatus: string): void => {
- if (target) {
- if (filter?.length) {
- let filtered = target.credentials.filter((credential: Credentials) => {
- if (regexp) {
- // eslint-disable-next-line eqeqeq
- return credential.username.trim().match(filter) != undefined;
- } else {
- return (credential.username.trim().toLowerCase().indexOf(filter) > -1);
- }
- }) ?? [];
- if (filterForStatus) {
- filtered = filtered.filter((c: Credentials) => c.status === filterForStatus);
- }
- setFilteredUser((filteredUser: any) => ({
- ...filteredUser,
- [target.target]: {filter, regexp, status: filterForStatus, user: filtered}
- }));
- } else {
- let filtered = undefined;
- if (filterForStatus) {
- filtered = {
- filter: undefined,
- regexp: false,
- status: filterForStatus,
- user: target.credentials.filter((c: Credentials) => c.status === filterForStatus)
- };
- }
- setFilteredUser((filteredUser: any) => ({
- ...filteredUser, [target.target]: filtered
- }));
- }
- }
- }, []);
-
- const handleFilter = useCallback((filter: string, regexp: boolean): void => {
- let filter_value = regexp ? filter : filter?.toLowerCase();
- const target = targets.find(t => t.target === activeTarget);
- if (target) {
- let currentFilter = filteredUser[target.target];
- filterTarget(target, filter_value, regexp, currentFilter?.status);
- }
- }, [activeTarget, targets, filterTarget, filteredUser]);
-
- const handleUserEditorSubmit = useCallback((user: Credentials, target_name: string): boolean => {
- const userRef = (user as any)._ref;
- const err = checkuser(user);
- if (err) {
- enqueueSnackbar(translate(err), {variant: 'error'});
- return false;
- }
- if ((userRef && user.username === userRef) || !usernameExists(user.username, targets)) {
- const target = targets.find(target => target.target === target_name);
- if (target) {
- delete (user as any)._ref;
- if (userRef) {
- const index = target.credentials.findIndex(c => c.username === userRef);
- target.credentials.splice(index, 1, user);
- } else {
- target.credentials.push(user);
- }
- setTargets(targets.slice());
- return true;
- } else {
- enqueueSnackbar(translate("MESSAGES.USER.TARGET_NOT_FOUND") + target_name, {variant: 'error'});
- }
- } else {
- enqueueSnackbar(translate("MESSAGES.USER.DUPLICATE_USERNAME") + user.username, {variant: 'error'});
- }
- return false;
- }, [targets, translate, enqueueSnackbar]);
-
- const renderTargetOptions = useCallback((target: string): JSX.Element => {
- let options = targetOptions?.[target];
- if (options?.force_redirect?.length > 2) {
- return {translate('HINT.CONFIG.USER.TARGET_FORCE_REDIRECT')} {options.force_redirect}
;
- }
- return <>>;
- }, [targetOptions, translate]);
-
- const handleStatusFilter = useCallback((evt: any) => {
- const target_name = evt.target.dataset.target;
- let target = targets.find((t) => t.target === target_name);
- if (target) {
- let value = evt.target.value;
- value = value?.length ? value : undefined;
- let currentFilter = filteredUser[target_name];
- filterTarget(target, currentFilter?.filter, !!currentFilter?.regexp, value)
- }
- }, [filteredUser, filterTarget, targets]);
-
- const handleColumnAction = useCallback((evt: any) => {
- const field = evt.target.dataset.field;
- if (field === 'comment') {
- const target_name = evt.target.dataset.target;
- let target = targets.find((t) => t.target === target_name);
- if (target) {
- const username = evt.target.dataset.username;
- const user = target.credentials.find(usr => usr.username === username);
- if (user) {
- const dialog: any = document.getElementById("comment-dialog");
- const comment = document.getElementById("comment-text");
- comment.textContent = user.comment;
- dialog.showModal();
- }
- }
- }
- }, [targets]);
-
- return
-
{translate('LABEL.TARGETS')} / {translate('LABEL.USERS')}
-
-
{translate('LABEL.SAVE')}
-
-
-
-
- {
- targets?.map(target =>
-
-
- {!(target as any).src &&
- {translate('MESSAGES.TARGET_NOT_EXISTS')} }
- {renderTargetOptions(target.target)}
-
-
-
{translate("LABEL.STATUS")}
-
-
- {STATUS_OPTIONS.map(l => {l.label} )}
-
-
-
- {getIconByName('PersonAdd')}
-
-
-
-
-
-
-
- {COLUMNS.map(col =>
- {translate(col.label)}
)}
-
-
- {(filteredUser[target.target]?.user ?? target.credentials).map((usr, idx) =>
-
-
-
-
- {getIconByName('PersonRemove')}
-
-
- {getIconByName('Edit')}
-
-
-
- {COLUMNS.map(c =>
-
- {translate(c.label)}
-
-
{c.hidden &&
-
- {getIconByName('Visibility')} }
- {c.render((usr as any)[c.field], (showHiddenFields[usr.username + c.field] ?? c.hidden))}
-
-
)
- }
-
- )}
-
-
-
)}
-
-
-
-
-
-}
\ No newline at end of file
diff --git a/frontend/src/component/video-player/video-player.scss b/frontend/src/component/video-player/video-player.scss
deleted file mode 100644
index 2bd02c65b..000000000
--- a/frontend/src/component/video-player/video-player.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.video-player {
- display: flex;
- justify-content: center;
- height: 100%;
- width: auto;
- background-color: black;
-
- .video-js {
- background-color: black;
- height: 100%;
- width: 100%;
- }
-}
\ No newline at end of file
diff --git a/frontend/src/component/video-player/video-player.tsx b/frontend/src/component/video-player/video-player.tsx
deleted file mode 100644
index eac131d6a..000000000
--- a/frontend/src/component/video-player/video-player.tsx
+++ /dev/null
@@ -1,194 +0,0 @@
-import React, {useCallback, useEffect, useRef} from 'react';
-import './video-player.scss';
-import {first, Observable, Subscription} from "rxjs";
-import {PlaylistItem, PlaylistItemType} from "../../model/playlist";
-import videojs from "video.js";
-import 'videojs-mpegtsjs';
-import "video.js/dist/video-js.css";
-import {useServices} from '../../provider/service-provider';
-import {PlaylistRequest, PlaylistRequestType} from "../../model/playlist-request";
-
-const VIDEOJS_OPTIONS: any = {
- autoplay: true,
- controls: true,
- responsive: false,
- fluid: true,
- preload: "none",
-}
-
-const DEFAULT_OPTIONS: any = {
- ...VIDEOJS_OPTIONS,
- sources: [],
- poster: undefined,
-};
-
-const MPEGTS_OPTIONS: any = {
- mediaDataSource: {
- type: 'mpegts',
- isLive: true,
- cors: true,
- withCredentials: false,
- },
- config: {
- enableWorker: true,
- enableWorkerForMSE: true,
- enableStashBuffer: true,
- }
-};
-
-const HLS_OPTIONS: any = {
- html5: {
- vhs: {
- cors: true,
- overrideNative: true
- },
- nativeAudioTracks: false,
- nativeVideoTracks: false
- }
-};
-
-// @ts-ignore
-type Player = videojs.Player | null;
-
-const getVideoOptions = (playlistItem: PlaylistItem, url: string): { mimeType: string, options: any } => {
- switch (playlistItem.item_type) {
- case PlaylistItemType.Video:
- case PlaylistItemType.Series:
- return {mimeType: 'video/mp4', options: {}};
- case PlaylistItemType.SeriesInfo:
- return {mimeType: 'application/json', options: {}};
- case PlaylistItemType.Live:
- return {
- mimeType: 'video/mp2t', options: {
- mpegtsjs: {
- ...MPEGTS_OPTIONS,
- mediaDataSource: {...MPEGTS_OPTIONS.mediaDataSource, url}
- }
- }
- };
- case PlaylistItemType.Catchup:
- case PlaylistItemType.LiveUnknown:
- case PlaylistItemType.LiveHls:
- return {mimeType: 'application/x-mpegURL', options: HLS_OPTIONS};
- }
- return {mimeType: 'application/octet-stream', options: {}};
-}
-
-interface VideoPlayerProps {
- channel: Observable<[PlaylistItem, PlaylistRequest]>;
- onReady?: (player: any) => void;
-}
-
-export const VideoPlayer = ({channel, onReady}: VideoPlayerProps) => {
- const services = useServices();
- const videoRef = useRef(null);
- const playerRef = useRef(null);
-
- const playVideo = useCallback((playlistItem: PlaylistItem, url: string) => {
- let options = getVideoOptions(playlistItem, url);
- // TODO mimetype
- const playerOptions = {
- ...DEFAULT_OPTIONS,
- sources: [{
- src: url,
- type: options.mimeType,
- }],
- poster: playlistItem.logo ?? playlistItem.logo_small,
- ...options.options
- };
-
- try {
-
- if (playerRef.current && playerRef.current.currentSrc() !== playerOptions.sources[0].src) {
- const player = playerRef.current;
- player.options(playerOptions);
- player.poster(playerOptions.poster);
- player.src(playerOptions.sources);
- }
- } catch (e) {
- console.error(e);
- }
- }, [])
-
- const handlePlayVideo = useCallback(([playlistItem, playlistRequest]: [playlistItem: PlaylistItem, playlistRequest: PlaylistRequest]) => {
- switch (playlistRequest.rtype) {
- case PlaylistRequestType.TARGET:
- if (playlistItem.item_type === PlaylistItemType.LiveHls) {
- playVideo(playlistItem, playlistItem.url);
- } else {
- services.playlist().getWebPlayerUrl(playlistItem, playlistRequest).pipe(first()).subscribe({
- next: (url: string) => {
- playVideo(playlistItem, url);
- },
- error: (error: any) => {
- playVideo(playlistItem, playlistItem.url);
- },
- });
- }
- break;
- case PlaylistRequestType.INPUT:
- case PlaylistRequestType.XTREAM:
- case PlaylistRequestType.M3U:
- playVideo(playlistItem, playlistItem.url);
- break;
- }
- }, [services, playVideo]);
-
- useEffect(() => {
- const sub: Subscription = channel.subscribe({
- next: handlePlayVideo,
- });
- return () => sub.unsubscribe();
- }, [channel, handlePlayVideo]);
-
- useEffect(() => {
- if (!playerRef.current && videoRef.current) {
- const videoElement = document.createElement("video-js");
- videoElement.classList.add("vjs-big-play-centered");
- videoElement.setAttribute('controls', VIDEOJS_OPTIONS.controls ?? 'false');
- videoElement.setAttribute('autoplay', VIDEOJS_OPTIONS.autoplay ?? 'false');
- videoElement.setAttribute('responsive', VIDEOJS_OPTIONS.responsive ?? 'false');
- //videoElement.setAttribute('fluid', VIDEOJS_OPTIONS.fluid ?? 'false');
- videoRef.current.appendChild(videoElement);
-
- const player = videojs(videoElement, {}, () => {
- videojs.log("player is ready");
- onReady && onReady(player);
- });
-
- playerRef.current = player;
- // Error handling
- player.on('error', () => {
- const error = player.error();
- console.error('Video.js Error:', error);
-
- if (error && error.code === 4) {
- console.warn('Not found.');
- }
- });
-
- // const mpegtsPlayer = (player.tech(true) as any).flvPlayer;
- // if (mpegtsPlayer) {
- // mpegtsPlayer.on(mpegts.Events.ERROR, (type, details) => {
- // console.error("mpegts.js ERROR", type, details);
- // });
- // }
- }
- }, [onReady]);
-
- useEffect(() => {
- return () => {
- if (playerRef.current && !playerRef.current.isDisposed()) {
- playerRef.current.dispose();
- playerRef.current = null;
- }
- };
- }, []);
-
- return (
-
-
- );
-}
-
-export default VideoPlayer;
diff --git a/frontend/src/config.ts b/frontend/src/config.ts
deleted file mode 100644
index d7f6e1690..000000000
--- a/frontend/src/config.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-const dev = {
- app: {
- version: process.env.REACT_APP_VERSION,
- },
-};
-
-const prod = {
- app: {
- version: process.env.REACT_APP_VERSION,
- },
-};
-
-const config = process.env.REACT_APP_STAGE === 'production' ? prod : dev;
-
-const DefaultConfig = {
- // Add common config values here
- max_attachment_size: 5000000,
- ...config
-};
-
-export default DefaultConfig;
diff --git a/webui/src/error.rs b/frontend/src/error.rs
similarity index 100%
rename from webui/src/error.rs
rename to frontend/src/error.rs
diff --git a/frontend/src/hook/use-const.ts b/frontend/src/hook/use-const.ts
deleted file mode 100644
index 21a2486c8..000000000
--- a/frontend/src/hook/use-const.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import {useRef} from "react";
-
-type ResultBox = { v: T }
-
-type ConstantFunction = () => T;
-
-export default function useConst(fnOrValue: T | ConstantFunction): T {
- const ref = useRef>(undefined)
- if (!ref.current) {
- // @ts-ignore
- const value: T = (typeof fnOrValue == 'function') ? fnOrValue() : fnOrValue;
- ref.current = { v: value }
- }
- return ref.current.v
-}
diff --git a/frontend/src/hook/use-translator.ts b/frontend/src/hook/use-translator.ts
deleted file mode 100644
index 775be2fca..000000000
--- a/frontend/src/hook/use-translator.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import {useTranslation} from "react-i18next";
-import {useEffect, useMemo} from "react";
-import {ReplaySubject, Subject} from "rxjs";
-
-export type TranslateFunc = (key: string, variables?: Record) => string;
-
-export const LANGUAGE_LOCAL_STORAGE_KEY: string = "language"
-
-export const LANGUAGE: Subject = new ReplaySubject(1);
-export default function useTranslator(): (key: string, variables?: any) => string {
- const [t, i18n] = useTranslation('common');
- useEffect(() => {
- LANGUAGE.subscribe(lang => {
- localStorage.setItem(LANGUAGE_LOCAL_STORAGE_KEY, lang);
- i18n.changeLanguage(lang).then(() => {});
- });
- }, [i18n]);
-
- return useMemo((): TranslateFunc => t, [t]);
-}
diff --git a/webui/src/hooks/mod.rs b/frontend/src/hooks/mod.rs
similarity index 100%
rename from webui/src/hooks/mod.rs
rename to frontend/src/hooks/mod.rs
diff --git a/webui/src/hooks/use_icon_context.rs b/frontend/src/hooks/use_icon_context.rs
similarity index 100%
rename from webui/src/hooks/use_icon_context.rs
rename to frontend/src/hooks/use_icon_context.rs
diff --git a/webui/src/hooks/use_server_status.rs b/frontend/src/hooks/use_server_status.rs
similarity index 100%
rename from webui/src/hooks/use_server_status.rs
rename to frontend/src/hooks/use_server_status.rs
diff --git a/webui/src/hooks/use_service_context.rs b/frontend/src/hooks/use_service_context.rs
similarity index 100%
rename from webui/src/hooks/use_service_context.rs
rename to frontend/src/hooks/use_service_context.rs
diff --git a/frontend/src/icons/icons.tsx b/frontend/src/icons/icons.tsx
deleted file mode 100644
index 667c36a05..000000000
--- a/frontend/src/icons/icons.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import createIcon from "../utils/icon-utils";
-import * as React from "react";
-
-const ICON: Record = {};
-
-export const getIconByName = (name: string): any => {
- if (name) {
- // @ts-ignore
- return ICON[name];
- }
- return undefined;
-}
-
-const DEFAULT_ICONS = {
- Logo: 'M 14.09194,7.6882684e-4 C 13.973764,9.5800672e-5 13.856738,1.8569152e-4 13.740338,0.00128559 10.134203,0.03533121 7.3722672,0.9073449 5.3120566,2.2058124 2.6199728,3.9025243 1.1166249,6.3549771 0.44196532,8.8193928 4.4918101,7.9934421 7.5738185,8.392102 10.393515,9.4901562 V 3.0538267 c 0,-1.0502871 0.717633,-1.3329492 1.494308,-0.8443969 l 5.939123,3.7362233 C 17.883676,5.8672459 17.940027,5.7886156 17.993602,5.7100075 18.773785,4.4409406 19.517515,2.4795189 20.024457,0.67721647 17.837484,0.22159145 15.864573,0.01086418 14.09194,7.6882684e-4 Z M 12.283121,4.6015429 c -0.352571,-0.00358 -0.610222,0.2693437 -0.610222,0.817525 V 18.544943 c 0,0.716278 0.702783,1.00662 1.134576,0.772566 L 23.366199,12.42229 c 0.321466,-0.209944 0.293977,-0.733308 0,-1.002527 L 12.807475,4.7777605 C 12.623215,4.6618552 12.443381,4.6031688 12.283121,4.6015429 Z M 4.6820187,10.631177 c -1.3670233,0.0065 -2.8875164,0.207303 -4.66075611,0.63924 -0.22262,3.841376 1.29470801,7.532859 4.53424051,9.96688 3.3072703,2.484918 8.4898149,3.647573 15.5090939,1.986968 -0.453335,-1.879096 -1.379185,-3.757389 -2.654289,-5.468942 l -5.522485,3.606515 c -0.568764,0.308297 -1.494308,-0.07453 -1.494308,-1.018031 V 12.036783 C 8.6043863,11.2147 6.6723813,10.706689 4.6820187,10.631177 Z',
- ContentCopy: 'M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z',
- DeleteSweep: 'M15 16h4v2h-4zm0-8h7v2h-7zm0 4h6v2h-6zM3 18c0 1.1.9 2 2 2h6c1.1 0 2-.9 2-2V8H3v10zM14 5h-3l-1-1H6L5 5H2v2h12z',
- Delete: 'M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z',
- Search: 'M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z',
- Clear: 'M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z',
- ClearSearch: 'M 10.82534,3 C 7.3479487,3 4.5022347,5.716944 4.2480387,9.1748729 H 6.3014596 C 6.5556707,6.859298 8.4664087,5.0582909 10.82534,5.0582909 c 2.531786,0 4.575514,2.068585 4.575514,4.6311538 0,2.5625733 -2.043728,4.6311543 -4.575514,4.6311543 -0.172856,0 -0.335527,-0.03153 -0.508391,-0.05226 v 2.080402 c 0.172857,0.02062 0.335527,0.03015 0.508391,0.03015 1.637017,0 3.14233,-0.60752 4.30146,-1.616081 l 0.274054,0.289447 v 0.812061 L 20.484758,21 22,19.466331 16.926025,14.320604 h -0.802303 l -0.28597,-0.277387 c 0.996446,-1.173226 1.596663,-2.696845 1.596663,-4.3537672 0,-3.6946315 -2.958832,-6.6894447 -6.609075,-6.6894447 z M 2.7228671,11.048238 2,11.777886 4.5121637,14.320599 2,16.863312 l 0.7228671,0.729648 2.5101775,-2.540703 2.5121641,2.540703 0.720882,-0.729648 -2.510178,-2.542713 2.510178,-2.542713 -0.720882,-0.729648 -2.5121641,2.542713 z',
- ExpandMore: 'M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6z',
- ExpandLess: 'm12 8-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z',
- ChevronRight: 'M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z',
- LinkRounded: 'M17 7h-3c-.55 0-1 .45-1 1s.45 1 1 1h3c1.65 0 3 1.35 3 3s-1.35 3-3 3h-3c-.55 0-1 .45-1 1s.45 1 1 1h3c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-9 5c0 .55.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1H9c-.55 0-1 .45-1 1zm2 3H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h3c.55 0 1-.45 1-1s-.45-1-1-1H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h3c.55 0 1-.45 1-1s-.45-1-1-1z',
- PlayArrow: 'M8 5v14l11-7z',
- ArrowLeft: 'm14 7-5 5 5 5V7z',
- ArrowRight: 'm10 17 5-5-5-5v10z',
- ArrowDown: 'm7 10 5 5 5-5z',
- ArrowUp: 'm7 14 5-5 5 5z',
- CloudDownload: 'M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z',
- Download: 'M5 20h14v-2H5v2zM19 9h-4V3H9v6H5l7 7 7-7z',
- Config: 'M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z',
- PersonAdd: 'M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z',
- PersonRemove: 'M14 8c0-2.21-1.79-4-4-4S6 5.79 6 8s1.79 4 4 4 4-1.79 4-4zm3 2v2h6v-2h-6zM2 18v2h16v-2c0-2.66-5.33-4-8-4s-8 1.34-8 4z',
- WebSearch: 'M19.3 16.9c.4-.7.7-1.5.7-2.4 0-2.5-2-4.5-4.5-4.5S11 12 11 14.5s2 4.5 4.5 4.5c.9 0 1.7-.3 2.4-.7l3.2 3.2 1.4-1.4-3.2-3.2zm-3.8.1c-1.4 0-2.5-1.1-2.5-2.5s1.1-2.5 2.5-2.5 2.5 1.1 2.5 2.5-1.1 2.5-2.5 2.5zM12 20v2C6.48 22 2 17.52 2 12S6.48 2 12 2c4.84 0 8.87 3.44 9.8 8h-2.07c-.64-2.46-2.4-4.47-4.73-5.41V5c0 1.1-.9 2-2 2h-2v2c0 .55-.45 1-1 1H8v2h2v3H9l-4.79-4.79C4.08 10.79 4 11.38 4 12c0 4.41 3.59 8 8 8z',
- CheckMark: 'M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',
- Checked: 'M 4.2222224,2 C 3,2 2,3.000021 2,4.2222362 V 19.777764 C 2,20.999979 3,22 4.2222224,22 H 19.777778 C 20.999989,22 22,20.999979 22,19.777764 V 4.2222362 C 22,3.000021 20.999989,2 19.777778,2 Z m 0,2.2222362 H 19.777778 V 19.777764 H 4.2222224 Z M 17.288622,7.1063517 10.441833,13.953134 6.7113668,10.233533 5.2465223,11.698352 10.441833,16.89369 18.753467,8.5820472 Z',
- Hourglass: 'M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6zm10 14.5V20H8v-3.5l4-4 4 4zm-4-5-4-4V4h8v3.5l-4 4z',
- Error: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z',
- Regexp: 'M 14.219989,2 V 6.6779027 L 10.218308,4.3991767 8.9526346,6.5626756 13.00229,8.8690562 8.8806751,11.216304 l 1.2656739,2.16227 4.07364,-2.3199 v 4.62413 h 2.532593 v -4.677903 l 3.98081,2.26705 L 22,11.108144 18.066228,8.8681345 21.985982,6.6358063 20.720308,4.4735364 16.752582,6.7332118 V 2 Z M 4.8198059,16.438384 C 3.2775726,16.438384 1.9999995,17.698564 2,19.219808 1.9999995,20.741043 3.2775726,22 4.8198059,22 c 1.542234,0 2.8185603,-1.258957 2.8185597,-2.780192 6e-7,-1.521244 -1.2763257,-2.781424 -2.8185597,-2.781424 z',
- User: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z',
- Refresh: 'M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z',
- ApiServer: 'm20.2 5.9.8-.8C19.6 3.7 17.8 3 16 3s-3.6.7-5 2.1l.8.8C13 4.8 14.5 4.2 16 4.2s3 .6 4.2 1.7zm-.9.8c-.9-.9-2.1-1.4-3.3-1.4s-2.4.5-3.3 1.4l.8.8c.7-.7 1.6-1 2.5-1 .9 0 1.8.3 2.5 1l.8-.8zM19 13h-2V9h-2v4H5c-1.1 0-2 .9-2 2v4c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-4c0-1.1-.9-2-2-2zM8 18H6v-2h2v2zm3.5 0h-2v-2h2v2zm3.5 0h-2v-2h2v2z',
- Warn: 'M 11.999765,2 2,22 h 20 z m 0,4.1999619 6.845654,13.6948891 H 5.1545815 Z m -0.909027,4.2211761 v 5.263416 h 1.818524 v -5.263416 z m 0,6.31599 v 2.105149 h 1.818524 v -2.105149 z',
- Gallery: 'M 11.999742,2 C 6.5002947,2 1.9999999,6.5002944 2,11.999742 2,17.499188 6.5002947,22 11.999742,22 17.499187,22 22,17.499188 22,11.999742 22,6.5002944 17.499187,1.9999999 11.999742,2 Z M 7.4796784,5.5010206 H 9.2940081 C 10.454244,5.5013381 11.3944,6.4424302 11.393587,7.6026664 v 1.8143297 c -3.18e-4,1.1594289 -0.94015,2.0992499 -2.0995789,2.0995789 H 7.4796784 C 6.3194422,11.517395 5.3783618,10.577232 5.3780327,9.4169961 V 7.6026664 C 5.3772125,6.4416227 6.3186347,5.5002073 7.4796784,5.5010206 Z m 7.2263136,0 h 1.816396 c 1.160237,3.175e-4 2.100392,0.9414098 2.099579,2.1016458 v 1.8143297 c -3.17e-4,1.1594289 -0.94015,2.0992499 -2.099579,2.0995789 h -1.816396 c -1.159429,-3.18e-4 -2.09925,-0.94015 -2.099579,-2.0995789 V 7.6026664 c -8.2e-4,-1.1602362 0.939342,-2.1013166 2.099579,-2.1016458 z M 7.4796784,12.483425 h 1.8143297 c 1.1594289,3.18e-4 2.0992499,0.94015 2.0995789,2.099579 v 1.816397 c -3.18e-4,1.159428 -0.94015,2.099249 -2.0995789,2.099578 H 7.4796784 C 6.3194422,18.4998 5.3783618,17.559637 5.3780327,16.399401 v -1.816397 c 3.175e-4,-1.160236 0.9414095,-2.100391 2.1016457,-2.099579 z m 7.2263136,0 h 1.816396 c 1.159429,3.18e-4 2.09925,0.94015 2.099579,2.099579 v 1.816397 c -3.17e-4,1.159428 -0.94015,2.099249 -2.099579,2.099578 h -1.816396 c -1.159429,-3.17e-4 -2.09925,-0.94015 -2.099579,-2.099578 v -1.816397 c 3.17e-4,-1.159429 0.94015,-2.09925 2.099579,-2.099579 z',
- Editor: 'M8 5v14l11-7z',
- ScheduleAdd: 'm 15.427906,2 0.0026,4.3103469 -4.402514,-0.00424 0.0016,2.4026963 4.402514,0.00475 0.0026,4.2565168 2.402815,0.0026 -0.0021,-4.2565172 4.164457,0.00517 -0.0016,-2.403213 -4.164974,-0.00465 -0.0026,-4.3103469 z M 9.2342365,7.5143019 c -3.9841557,5e-7 -7.2343587,3.2535111 -7.2343586,7.2428491 0,3.989335 3.2502029,7.242849 7.2343586,7.242849 3.9841545,0 7.2322915,-3.253514 7.2322915,-7.242849 0,-0.06325 -0.0014,-0.126255 -0.0031,-0.18912 H 14.68171 c 0.0022,0.06278 0.0041,0.125793 0.0041,0.18912 0,3.025716 -2.429816,5.460169 -5.4516058,5.460169 -3.0217902,0 -5.4531561,-2.434453 -5.4531561,-5.460169 0,-3.025718 2.4313659,-5.4586186 5.4531561,-5.4586186 0.1480969,0 0.2937091,0.00764 0.4387121,0.019119 C 9.6657863,8.738416 9.6404593,8.1019918 9.6781163,7.5292876 9.531058,7.5202214 9.3835267,7.5143032 9.2342349,7.5143032 Z M 8.078806,10.651303 v 5.534575 h 5.462975 V 14.71943 H 9.5432468 v -4.068127 z',
- ScheduleRemove: 'm 14.051424,1.9998779 -1.742213,1.697054 3.122965,3.0458089 -3.191235,3.1104043 1.741155,1.6975709 3.191765,-3.1098876 3.084332,3.0080846 L 21.999878,9.7518594 18.916075,6.7432575 21.935312,3.8023519 20.194157,2.1042643 17.17439,5.0462036 Z M 9.4085202,7.51427 c -4.0802372,6e-7 -7.4086424,3.253631 -7.4086423,7.242969 0,3.989334 3.3284051,7.242968 7.4086423,7.242968 4.0802358,0 7.4070548,-3.253634 7.4070548,-7.242968 0,-0.06325 -0.0014,-0.126271 -0.0032,-0.189136 h -1.824773 c 0.0023,0.06278 0.0042,0.125808 0.0042,0.189136 0,3.025716 -2.488677,5.460131 -5.5833398,5.460131 -3.0946634,0 -5.5843983,-2.434415 -5.5843983,-5.460131 0,-3.025718 2.4897349,-5.458582 5.5843983,-5.458582 0.1516683,0 0.3008134,0.00764 0.4493133,0.01912 C 9.8504755,8.7385417 9.8245025,8.10196 9.8630655,7.5292556 9.712521,7.5201898 9.5614122,7.51427 9.4085202,7.51427 Z m -1.1833506,3.137276 v 5.534546 H 13.820153 V 14.719515 H 9.7249975 v -4.067969 z',
- Logout: 'm17 7-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4z',
- SelectAll: 'M 2,9.2105263 H 13.578947 V 11.315789 H 2 Z M 2,5 H 13.578947 V 7.105263 H 2 Z m 0,8.421054 h 7.368421 v 2.105263 H 2 Z m 18.515789,-2.178949 -4.473684,4.46316 -2.231579,-2.23158 -1.48421,1.484211 3.715789,3.726316 L 22,12.726317 Z',
- DeselectAll: 'M 2,5 V 7.1538458 H 13.612371 V 5 Z M 2,9.3076922 V 11.461539 H 13.612371 V 9.3076922 Z m 14.082473,1.7899618 -1.265978,1.392431 2.327832,2.559791 -2.327832,2.557693 L 16.082473,19 18.408247,16.442307 20.734022,19 22,17.607569 19.674225,15.049876 22,12.490085 20.734022,11.097654 18.408247,13.655347 Z M 2,13.615385 v 2.153847 h 7.3896896 v -2.153847 z',
- Visibility: 'M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5M12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5m0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3',
- Edit: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.996.996 0 0 0-1.41 0l-1.83 1.83 3.75 3.75z',
- Calendar: 'M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2m0 16H5V10h14zM9 14H7v-2h2zm4 0h-2v-2h2zm4 0h-2v-2h2zm-8 4H7v-2h2zm4 0h-2v-2h2zm4 0h-2v-2h2z',
- Unlimited: 'M18.6 6.62c-1.44 0-2.8.56-3.77 1.53L12 10.66 10.48 12h.01L7.8 14.39c-.64.64-1.49.99-2.4.99-1.87 0-3.39-1.51-3.39-3.38S3.53 8.62 5.4 8.62c.91 0 1.76.35 2.44 1.03l1.13 1 1.51-1.34L9.22 8.2C8.2 7.18 6.84 6.62 5.4 6.62 2.42 6.62 0 9.04 0 12s2.42 5.38 5.4 5.38c1.44 0 2.8-.56 3.77-1.53l2.83-2.5.01.01L13.52 12h-.01l2.69-2.39c.64-.64 1.49-.99 2.4-.99 1.87 0 3.39 1.51 3.39 3.38s-1.52 3.38-3.39 3.38c-.9 0-1.76-.35-2.44-1.03l-1.14-1.01-1.51 1.34 1.27 1.12c1.02 1.01 2.37 1.57 3.82 1.57 2.98 0 5.4-2.41 5.4-5.38s-2.42-5.37-5.4-5.37',
- Today: 'M16.53 11.06 15.47 10l-4.88 4.88-2.12-2.12-1.06 1.06L10.59 17zM19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m0 16H5V8h14z',
- Help: 'M11 18h2v-2h-2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8m0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4',
- Live: 'M21 6h-7.59l3.29-3.29L16 2l-4 4-4-4-.71.71L10.59 6H3c-1.1 0-2 .89-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.11-.9-2-2-2m0 14H3V8h18zM9 10v8l7-4z',
- VOD: 'm 19.815223,2.0000416 a 0.58850091,0.49877638 0 0 0 -0.152559,0.017775 L 2.4317328,6.0126382 A 0.58850091,0.49877638 0 0 0 2.0210941,6.6257352 L 2.9224652,9.4191448 A 1.173617,0.82462271 0 0 0 2.09356,10.207336 1.173617,0.82462271 0 0 0 2.9599692,11.002528 V 22 h 1.176613 V 21.97845 H 20.824021 V 22 h 1.175977 V 10.712682 A 0.58850091,0.49877638 0 0 0 21.411374,10.212723 H 4.4410652 a 1.173617,0.82462271 0 0 0 0,-0.0054 1.173617,0.82462271 0 0 0 -0.069287,-0.2785336 l 16.0358828,-3.7173633 0.002,0.00646 1.134023,-0.2629113 -1.157542,-3.5869872 A 0.58850091,0.49877638 0 0 0 19.815315,2.0000062 Z M 19.407126,3.1109398 20.112078,5.2966424 4.0158064,9.0280154 3.3108555,6.8423128 Z m -2.292202,0.8544558 a 0.88775572,0.75240594 0 0 0 -0.19642,0.025866 0.88775572,0.75240594 0 0 0 -0.622314,0.9244907 0.88775572,0.75240594 0 0 0 1.090798,0.5268975 0.88775572,0.75240594 0 0 0 0.621679,-0.9239538 0.88775572,0.75240594 0 0 0 -0.893743,-0.5532946 z m -3.63663,0.8426032 A 0.88775572,0.75240594 0 0 0 13.281873,4.834396 0.88775572,0.75240594 0 0 0 12.660194,5.7588867 0.88775572,0.75240594 0 0 0 13.750358,6.2857842 0.88775572,0.75240594 0 0 0 14.372672,5.3618304 0.88775572,0.75240594 0 0 0 13.478294,4.8079988 Z M 9.7698324,5.6678394 a 0.88775572,0.75240594 0 0 0 -0.19642,0.02586 0.88775572,0.75240594 0 0 0 -0.621679,0.924494 A 0.88775572,0.75240594 0 0 0 10.041896,7.1450878 0.88775572,0.75240594 0 0 0 10.664211,6.2211341 0.88775572,0.75240594 0 0 0 9.7698324,5.6678394 Z M 5.8490599,6.5767097 A 0.88775572,0.75240594 0 0 0 5.6526401,6.6031068 0.88775572,0.75240594 0 0 0 5.0303252,7.5275985 0.88775572,0.75240594 0 0 0 6.1211239,8.054495 0.88775572,0.75240594 0 0 0 6.7434384,7.1305423 0.88775572,0.75240594 0 0 0 5.8490599,6.5767097 Z M 4.1365822,11.210483 H 20.824021 V 13.47646 H 4.1365822 Z m 2.3335209,0.292541 a 0.88775572,0.75240594 0 0 0 -0.8880218,0.752092 0.88775572,0.75240594 0 0 0 0.8880218,0.752633 0.88775572,0.75240594 0 0 0 0.888022,-0.752633 0.88775572,0.75240594 0 0 0 -0.888022,-0.752092 z m 4.0650679,0 a 0.88775572,0.75240594 0 0 0 -0.887386,0.752092 0.88775572,0.75240594 0 0 0 0.887386,0.752633 0.88775572,0.75240594 0 0 0 0.888022,-0.752633 0.88775572,0.75240594 0 0 0 -0.888022,-0.752092 z m 3.844493,0 a 0.88775572,0.75240594 0 0 0 -0.887387,0.752092 0.88775572,0.75240594 0 0 0 0.887387,0.752633 0.88775572,0.75240594 0 0 0 0.888022,-0.752633 0.88775572,0.75240594 0 0 0 -0.888022,-0.752092 z m 3.770121,0 a 0.88775572,0.75240594 0 0 0 -0.888022,0.752092 0.88775572,0.75240594 0 0 0 0.888022,0.752633 0.88775572,0.75240594 0 0 0 0.887386,-0.752633 0.88775572,0.75240594 0 0 0 -0.887386,-0.752092 z M 4.1365822,14.424658 H 20.824021 V 21.03025 H 4.1365822 Z m 5.7317781,0.759096 v 0.700912 4.107958 l 5.2219757,-2.554745 z m 1.0138837,1.402902 2.083069,0.898093 -2.083069,1.018233 z',
- Series: 'M 5.6142482,2 A 0.52258163,0.5109162 0 0 0 5.0926858,2.5115754 V 6.8905936 H 2.521564 A 0.52258163,0.5109162 0 0 0 2.0000014,7.4027207 V 21.488425 A 0.52258163,0.5109162 0 0 0 2.521564,22 h 15.863059 a 0.52258163,0.5109162 0 0 0 0.522692,-0.511575 v -4.37957 h 2.569993 A 0.52258163,0.5109162 0 0 0 22,16.597831 V 2.5115754 A 0.52258163,0.5109162 0 0 0 21.477308,2 Z M 6.137504,3.0214951 H 20.955746 V 5.3426232 H 6.137504 Z M 8.2096417,3.3211558 A 0.78831624,0.7707189 0 0 0 7.4210893,4.0921057 0.78831624,0.7707189 0 0 0 8.2096417,4.8625039 0.78831624,0.7707189 0 0 0 8.9981944,4.0921057 0.78831624,0.7707189 0 0 0 8.2096417,3.3211558 Z m 3.6097313,0 a 0.78831624,0.7707189 0 0 0 -0.787989,0.7709499 0.78831624,0.7707189 0 0 0 0.787989,0.7703982 0.78831624,0.7707189 0 0 0 0.788552,-0.7703982 0.78831624,0.7707189 0 0 0 -0.788552,-0.7709499 z m 3.413862,0 a 0.78831624,0.7707189 0 0 0 -0.787988,0.7709499 0.78831624,0.7707189 0 0 0 0.787988,0.7703982 0.78831624,0.7707189 0 0 0 0.788553,-0.7703982 0.78831624,0.7707189 0 0 0 -0.788553,-0.7709499 z m 3.347821,0 A 0.78831624,0.7707189 0 0 0 17.792504,4.0921057 0.78831624,0.7707189 0 0 0 18.581056,4.8625039 0.78831624,0.7707189 0 0 0 19.369044,4.0921057 0.78831624,0.7707189 0 0 0 18.581056,3.3211558 Z M 6.137504,6.3138988 H 20.955746 V 13.080269 H 18.907315 V 7.4027207 A 0.52258163,0.5109162 0 0 0 18.384623,6.8905936 H 6.137504 Z M 3.0448198,7.9126405 H 17.863061 V 10.233768 H 3.0448198 Z M 5.1169577,8.211749 A 0.78831624,0.7707189 0 0 0 4.3284051,8.9826991 0.78831624,0.7707189 0 0 0 5.1169577,9.7536488 0.78831624,0.7707189 0 0 0 5.9055102,8.9826991 0.78831624,0.7707189 0 0 0 5.1169577,8.211749 Z m 3.6097308,0 A 0.78831624,0.7707189 0 0 0 7.9387002,8.9826991 0.78831624,0.7707189 0 0 0 8.7266885,9.7536488 0.78831624,0.7707189 0 0 0 9.5152413,8.9826991 0.78831624,0.7707189 0 0 0 8.7266885,8.211749 Z m 3.4138625,0 A 0.78831624,0.7707189 0 0 0 11.352563,8.9826991 0.78831624,0.7707189 0 0 0 12.140551,9.7536488 0.78831624,0.7707189 0 0 0 12.929104,8.9826991 0.78831624,0.7707189 0 0 0 12.140551,8.211749 Z m 3.34782,0 A 0.78831624,0.7707189 0 0 0 14.69982,8.9826991 0.78831624,0.7707189 0 0 0 15.488371,9.7536488 0.78831624,0.7707189 0 0 0 16.276361,8.9826991 0.78831624,0.7707189 0 0 0 15.488371,8.211749 Z M 3.0448198,11.205044 H 17.863061 v 6.765818 H 3.0448198 Z m 5.0897487,0.77702 v 0.718524 4.207942 l 4.6370505,-2.616926 z m 0.9003159,1.4376 1.8497406,0.919952 -1.8497406,1.043017 z m 9.8724306,0.632432 h 2.048431 v 2.035816 h -2.048431 v -0.327806 a 0.78831624,0.7707189 0 0 0 0.461729,-0.701967 0.78831624,0.7707189 0 0 0 -0.461729,-0.701416 z M 3.0448198,18.94269 H 17.863061 v 2.035815 H 3.0448198 Z m 2.0721379,0.235644 a 0.78831624,0.7707189 0 0 0 -0.7885526,0.770398 0.78831624,0.7707189 0 0 0 0.7885526,0.77095 0.78831624,0.7707189 0 0 0 0.7885525,-0.77095 0.78831624,0.7707189 0 0 0 -0.7885525,-0.770398 z m 3.6097308,0 a 0.78831624,0.7707189 0 0 0 -0.7879883,0.770398 0.78831624,0.7707189 0 0 0 0.7879883,0.77095 0.78831624,0.7707189 0 0 0 0.7885528,-0.77095 0.78831624,0.7707189 0 0 0 -0.7885528,-0.770398 z m 3.4138625,0 a 0.78831624,0.7707189 0 0 0 -0.787988,0.770398 0.78831624,0.7707189 0 0 0 0.787988,0.77095 0.78831624,0.7707189 0 0 0 0.788553,-0.77095 0.78831624,0.7707189 0 0 0 -0.788553,-0.770398 z m 3.34782,0 a 0.78831624,0.7707189 0 0 0 -0.788551,0.770398 0.78831624,0.7707189 0 0 0 0.788551,0.77095 0.78831624,0.7707189 0 0 0 0.78799,-0.77095 0.78831624,0.7707189 0 0 0 -0.78799,-0.770398 z',
- Back: 'M10 9V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11',
- TreeView: 'M17 12h2L12 2 5.05 12H7l-3.9 6h6.92v4h3.96v-4H21z',
- Status: 'm 4,3.627 c -1.1,0 -2,0.8999999 -2,2 V 8.6269999 H 4 V 5.627 h 16 v 2.9999999 h 2 V 5.627 c 0,-1.1000001 -0.9,-2 -2,-2 z m 9.994135,3.041016 c -0.362495,0 -0.724532,0.1697839 -0.894533,0.5097642 L 10,13.386764 8.8906064,11.177778 A 0.98799998,0.98799998 0 0 0 8,10.627 H 2 v 2 h 5.3789124 l 1.7206895,3.451167 C 9.2796039,16.418167 9.6199993,16.627 10,16.627 c 0.380001,0 0.720606,-0.208833 0.890606,-0.548833 L 14,9.8672329 15.109393,12.078171 C 15.279391,12.418171 15.619999,12.627 16,12.627 h 6 v -2 H 16.619142 L 14.890606,7.1777802 C 14.720608,6.8377802 14.356611,6.668016 13.994135,6.668016 Z M 2,14.627 v 3 c 0,1.1 0.9,2 2,2 h 16 c 1.1,0 2,-0.9 2,-2 v -3 h -2 v 3 H 4 v -3 z'
-}
-
-
-Object.entries(DEFAULT_ICONS).forEach(icon_def => {
- ICON[icon_def[0]] = createIcon(icon_def[0], icon_def[1]);
-});
-
-
-export default ICON;
diff --git a/frontend/src/index.scss b/frontend/src/index.scss
deleted file mode 100755
index 5a54153c9..000000000
--- a/frontend/src/index.scss
+++ /dev/null
@@ -1,52 +0,0 @@
-@use 'scss/theme';
-@use 'scss/default';
-
-html {
- height: 100%;
-}
-body {
- margin: 0;
- padding: 0;
- font-family: "Rubik", sans-serif;
- background-color: var(--background-color);
- height: 100%;
- max-width: 100vw;
- overflow: hidden;
- box-sizing: border-box;
- display: flex;
- align-content: stretch;
- align-items: stretch;
-
- input, textarea, select, button {
- font-family: inherit;
- }
-
- input {
- height: 2rem;
- border-radius: 6px;
- }
-
- select {
- border-color: var(--border-color);
- }
-}
-
-#root {
- display: flex;
- flex-flow: column;
- flex: 1 1 auto;
- box-sizing: border-box;
- overflow: hidden;
- max-width: 100%;
- background-color: inherit;
-}
-
-dialog {
- border: none;
- border-radius: var(--border-radius);
- background-color: var(--card-background-color);
- filter: drop-shadow(6px 6px 6px var(--popup-shadow-color));
- &::backdrop {
- background: rgba(0, 0, 0, 0.3); /* Schwarzer, halbtransparenter Hintergrund */
- }
-}
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
deleted file mode 100755
index d3d9390e0..000000000
--- a/frontend/src/index.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import React from 'react';
-import {createRoot} from 'react-dom/client';
-import './index.scss';
-import {SnackbarProvider} from 'notistack';
-import {ServiceProvider} from "./provider/service-provider";
-import Authentication from "./component/authentication/authentication";
-import Fetcher from "./utils/fetcher";
-import ServiceContext from "./service/service-context";
-import {UiConfig} from './model/ui-config';
-import i18n_init from "./utils/i18n";
-
-import {catchError, map, switchMap} from 'rxjs/operators';
-import {EMPTY} from 'rxjs';
-import Tooltip from "./component/tooltip/tooltip";
-
-const initUI = () => {
- const container = document.getElementById('root');
- const root = createRoot(container);
- root.render(
-
-
-
-
-
-
- );
-}
-
-Fetcher.fetchJson("config.json").pipe(
- switchMap((config: UiConfig) => {
- ServiceContext.config().setUiConfig(config);
- const document_title = config.tab_title || config.app_title;
- if (document_title) {
- document.title = document_title;
- }
- return i18n_init(ServiceContext.config().getUiConfig().languages).pipe(
- map(() => {
- initUI();
- })
- )
- }
- ),
- catchError((error: Error) => {
- initUI();
- return EMPTY;
- })
-).subscribe();
-
-// Fetcher.fetchJson("config.json").subscribe({
-// next: (config: UiConfig) => {
-// i18n_init(config.languages).subscribe(
-// ServiceContext.config().setUiConfig(config);
-// initUI();
-// },
-// error: (error: Error) => {initUI()}
-// });
-
diff --git a/webui/src/lib.rs b/frontend/src/lib.rs
similarity index 100%
rename from webui/src/lib.rs
rename to frontend/src/lib.rs
diff --git a/webui/src/main.rs b/frontend/src/main.rs
similarity index 100%
rename from webui/src/main.rs
rename to frontend/src/main.rs
diff --git a/webui/src/model/busy_status.rs b/frontend/src/model/busy_status.rs
similarity index 100%
rename from webui/src/model/busy_status.rs
rename to frontend/src/model/busy_status.rs
diff --git a/webui/src/model/dialog.rs b/frontend/src/model/dialog.rs
similarity index 100%
rename from webui/src/model/dialog.rs
rename to frontend/src/model/dialog.rs
diff --git a/webui/src/model/event_message.rs b/frontend/src/model/event_message.rs
similarity index 100%
rename from webui/src/model/event_message.rs
rename to frontend/src/model/event_message.rs
diff --git a/webui/src/model/explorer_source_type.rs b/frontend/src/model/explorer_source_type.rs
similarity index 100%
rename from webui/src/model/explorer_source_type.rs
rename to frontend/src/model/explorer_source_type.rs
diff --git a/frontend/src/model/file-download.ts b/frontend/src/model/file-download.ts
deleted file mode 100644
index d148e5121..000000000
--- a/frontend/src/model/file-download.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-export interface FileDownloadRequest {
- url: string;
- filename: string;
-}
-
-export interface FileDownloadInfo {
- uuid: string;
- ts_created?: number;
- ts_modified?: number;
- filename?: string;
- finished?: boolean;
- filesize?: number;
- error?: string;
-}
-
-export interface DownloadInfo {
- completed: boolean;
- active?: FileDownloadInfo;
- downloads?: FileDownloadInfo[];
-}
\ No newline at end of file
diff --git a/frontend/src/model/message-kind.ts b/frontend/src/model/message-kind.ts
deleted file mode 100644
index 2a0509b56..000000000
--- a/frontend/src/model/message-kind.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export enum MessageKind {
- Info = "info",
- Stats = "stats",
- Error = "error",
- Watch = "watch",
-}
\ No newline at end of file
diff --git a/webui/src/model/mod.rs b/frontend/src/model/mod.rs
similarity index 100%
rename from webui/src/model/mod.rs
rename to frontend/src/model/mod.rs
diff --git a/frontend/src/model/playlist-request.ts b/frontend/src/model/playlist-request.ts
deleted file mode 100644
index ace54e614..000000000
--- a/frontend/src/model/playlist-request.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-export enum PlaylistRequestType {
- INPUT = 1,
- TARGET = 2,
- XTREAM= 3,
- M3U= 4
-}
-
-export interface PlaylistRequest {
- rtype: PlaylistRequestType;
- sourceName?: string;
- username?: string;
- password?: string;
- url?: string;
- sourceId?: number,
-}
\ No newline at end of file
diff --git a/frontend/src/model/playlist.ts b/frontend/src/model/playlist.ts
deleted file mode 100755
index ce30fd708..000000000
--- a/frontend/src/model/playlist.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-export interface PlaylistItemHeader {
- id: number;
- name: string;
- logo: string;
- logo_small: string;
- group: string;
- title: string;
- parent_code: string;
- audio_track: string;
- time_shift: string;
- rec: string;
- source: string;
- url: string;
-}
-
-export enum XtreamCluster {
- Live = 'Live',
- Video = 'Video',
- Series = 'Series',
-}
-
-export enum PlaylistItemType {
- Live = 'Live',
- Video = 'Video',
- Series = 'Series', // xtream series description
- SeriesInfo = 'SeriesInfo', // xtream series info fetched for series description
- Catchup = 'Catchup',
- LiveUnknown = 'LiveUnknown', // No Provider id
- LiveHls = 'LiveHls', // m3u8 entry
-}
-
-export interface PlaylistItem {
- id: number;
- category_id: number,
- provider_id: number,
- virtual_id: number,
- url: string,
- name: string,
- title: string,
- channel_no: number,
- epg_channel_id: string,
- group: string,
- input_name: string,
- item_type: PlaylistItemType,
- logo: string,
- logo_small: string,
- xtream_cluster: XtreamCluster,
- additional_properties: string,
-}
-
-export interface PlaylistGroup {
- id: number,
- name: string,
- channels?: PlaylistItem[];
-}
-
-export interface PlaylistCategories {
- live: PlaylistGroup[],
- vod: PlaylistGroup[],
- series: PlaylistGroup[],
-}
-
-export enum PlaylistCategory {
- LIVE = 'live',
- VOD = 'vod',
- SERIES = 'series'
-}
-
-export const EmptyPlaylistCategories: PlaylistCategories = {live: [], vod: [], series: []}
-
-
-export interface UserPlaylistTargetCategories {
- live: string[],
- vod: string[],
- series: string[],
-}
-
-export interface UserPlaylistCategories {
- xtream: UserPlaylistTargetCategories,
- m3u: UserPlaylistTargetCategories,
-}
\ No newline at end of file
diff --git a/frontend/src/model/server-config.ts b/frontend/src/model/server-config.ts
deleted file mode 100644
index 02a618bf9..000000000
--- a/frontend/src/model/server-config.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-export enum InputType {
- m3u = "m3u",
- xtream = "xtream"
-}
-
-export enum SortOrder {
- asc = "asc",
- desc = "desc"
-}
-
-export enum ExportStyle {
- kodi = "kodi",
- plex = "plex",
- emby = "emby",
- jellyfin = "jellyfin"
-}
-
-export enum TargetType {
- m3u = "m3u",
- xtream = "xtream",
- strm = "strm",
- hdhomerun = "hdhomerun"
-}
-
-export enum ProcessingOrder {
- frm = "frm",
- fmr = "fmr",
- rfm = "rfm",
- rmf = "rmf",
- mfr = "mfr",
- mrf = "mrf",
-}
-
-export interface InputConfig {
- id: number,
- input_type: InputType,
- url: string,
- username: string,
- password: string,
- persist: string,
- name: string,
- enabled: boolean
- options: {
- xtream_skip_live: boolean,
- xtream_skip_vod: boolean,
- xtream_skip_series: boolean,
- },
-}
-
-export interface TargetConfig {
- enabled: boolean,
- name: string,
- options: {
- ignore_logo: boolean,
- share_live_streams: boolean,
- remove_duplicates: boolean,
- force_redirect?: string,
- },
- sort: {
- match_as_ascii: boolean,
- groups: {
- order: SortOrder
- },
- channels:
- {
- field: string,
- group_pattern: string,
- order: SortOrder
- }[]
- },
- filter: string,
- output: [
- {
- type: TargetType,
- // m3u
- filename: string
- include_type_in_url: boolean,
- mask_redirect_url: boolean,
- // xtream
- skip_live_direct_source: boolean,
- skip_video_direct_source: boolean,
- skip_series_direct_source: boolean,
- resolve_series: boolean,
- resolve_series_delay: number,
- resolve_vod: boolean,
- resolve_vod_delay: number,
- // strm
- style: ExportStyle,
- flat?: boolean,
- cleanup: boolean,
- directory: string,
- username?: string, // hdhomerun & strm
- underscore_whitespace: boolean,
- strm_props?: string[],
- /** @deprecated Use style="kodi" instead */
- kodi_style?: boolean,
- // hdhomerun
- device: String,
- use_output?: TargetType,
- }
- ],
- rename: [
- {
- field: string,
- pattern: string,
- new_name: string
- }
- ],
- mapping: string[],
- processing_order: ProcessingOrder,
- watch: string[]
-}
-
-export interface SourceConfig {
- inputs: InputConfig[];
- targets: TargetConfig[];
-}
-
-export interface VideoDownloadConfig {
- headers: Record,
- directory: string;
- organize_into_directories: boolean;
- episode_pattern: string;
-}
-
-export interface VideoConfig {
- extensions: string[];
- download?: VideoDownloadConfig,
- web_search?: string;
-}
-
-export interface TelegramConfig {
- bot_token: string;
- chat_ids: string[];
-}
-
-export interface RestConfig {
- url: string;
-}
-
-export interface PushoverConfig {
- url?: string;
- token: string;
- user: string;
-}
-
-export interface MessagingConfig {
- notify_on: string[];
- telegram?: TelegramConfig;
- rest?: RestConfig;
- pushover?: PushoverConfig;
-}
-
-export interface LogConfig {
- sanitize_sensitive_info: boolean,
- log_active_user: boolean,
- log_level?: string,
-}
-
-export interface Credentials {
- username: string;
- password: string;
- token: string;
- server: string;
- proxy: 'redirect' | 'reverse';
- created_at: number,
- exp_date: number,
- max_connections: number,
- status: string,
- ui_enabled: boolean,
- comment: string,
-}
-
-export interface TargetUser {
- target: string;
- credentials: Credentials[];
-}
-
-export interface ApiProxyServerInfo {
- name: string;
- protocol: string;
- host: string;
- port: string;
- timezone: string;
- message: string;
- path: string;
-}
-
-export interface ApiProxyConfig {
- server: ApiProxyServerInfo[];
- user: TargetUser[];
-}
-
-export interface ServerApiConfig {
- host: string;
- port: number;
- web_root: string
-}
-
-export interface Schedule {
- schedule: string;
- targets: string[];
-}
-
-export interface WebAuthConfig {
- enabled: boolean,
- issuer: string,
- secret: string,
- userfile?: string,
-}
-
-export interface StreamBufferConfig {
- enabled: boolean,
- size: number,
-}
-
-export interface StreamConfig {
- retry: boolean,
- buffer?: StreamBufferConfig,
- throttle?: string,
- grace_period_millis?: number,
- grace_period_timeout_secs?: number,
-}
-
-export interface CacheConfig {
- enabled: boolean,
- size?: string,
- dir?: string,
-}
-
-export interface RateLimitConfig {
- enabled: boolean,
- period_millis: number,
- burst_size: number,
-}
-export interface ReverseProxyConfig {
- stream?: StreamConfig,
- cache?: CacheConfig,
- rate_limit?: RateLimitConfig,
- resource_rewrite_disabled: boolean,
- disable_referer_header: boolean,
-}
-
-export interface WebUiConfig {
- enabled: boolean,
- user_ui_enabled: boolean,
- path?: string,
- auth?: WebAuthConfig,
- player_server?: string,
-}
-
-export interface ProxyConfig {
- url: string,
- username?: string,
- password?: string,
-}
-
-export interface IpCheckConfig {
- url?: string,
- url_ipv4?: string,
- url_ipv6?: string,
- pattern_ipv4?: string,
- pattern_ipv6?: string,
-}
-
-export interface ServerMainConfig {
- threads: number;
- api: ServerApiConfig;
- working_dir: string;
- backup_dir: string;
- schedules?: Schedule[];
- messaging?: MessagingConfig;
- video?: VideoConfig;
- log?: LogConfig,
- update_on_boot: boolean,
- web_ui?: WebUiConfig,
- reverse_proxy?: ReverseProxyConfig,
- proxy?: ProxyConfig,
- ipcheck?: IpCheckConfig,
-}
-
-export default interface ServerConfig extends ServerMainConfig {
- sources: SourceConfig[];
- api_proxy?: ApiProxyConfig;
-}
\ No newline at end of file
diff --git a/frontend/src/model/server-status.tsx b/frontend/src/model/server-status.tsx
deleted file mode 100644
index 664ae1e3a..000000000
--- a/frontend/src/model/server-status.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-export interface ServerStatus {
- status: string,
- version: string,
- build_time: string,
- server_time: string,
- memory: string,
- cache: string,
- active_user: number,
- active_user_connections: number,
- active_provider_connections: Record,
-}
-
-export interface ServerIpCheck {
- ipv4?: string,
- ipv6?: string,
-}
\ No newline at end of file
diff --git a/frontend/src/model/tree-selection.ts b/frontend/src/model/tree-selection.ts
deleted file mode 100755
index 6c1c2bfd4..000000000
--- a/frontend/src/model/tree-selection.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export default interface TreeSelection {
- id?: number;
- text: string;
- type: 'group' | 'entry';
-}
\ No newline at end of file
diff --git a/frontend/src/model/ui-config.ts b/frontend/src/model/ui-config.ts
deleted file mode 100644
index aa5439234..000000000
--- a/frontend/src/model/ui-config.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export interface UiConfig {
- languages?: string[],
- tab_title?: string,
- app_title?: string,
- app_logo?: string,
- api?: {
- apiUrl?: string
- authUrl?: string
- }
-}
-
-export const DefaultUiConfig: UiConfig = {
- languages: ["en", "de", "fr", "tr", "es", "it", "ru"],
- app_title: "tuliprox",
- api: {
- apiUrl: "/api/v1/",
- authUrl: "/auth/"
- }
-}
\ No newline at end of file
diff --git a/webui/src/model/view_type.rs b/frontend/src/model/view_type.rs
similarity index 100%
rename from webui/src/model/view_type.rs
rename to frontend/src/model/view_type.rs
diff --git a/webui/src/model/web_config.rs b/frontend/src/model/web_config.rs
similarity index 100%
rename from webui/src/model/web_config.rs
rename to frontend/src/model/web_config.rs
diff --git a/webui/src/provider/dialog_provider.rs b/frontend/src/provider/dialog_provider.rs
similarity index 100%
rename from webui/src/provider/dialog_provider.rs
rename to frontend/src/provider/dialog_provider.rs
diff --git a/webui/src/provider/icon_context_provider.rs b/frontend/src/provider/icon_context_provider.rs
similarity index 100%
rename from webui/src/provider/icon_context_provider.rs
rename to frontend/src/provider/icon_context_provider.rs
diff --git a/webui/src/provider/mod.rs b/frontend/src/provider/mod.rs
similarity index 100%
rename from webui/src/provider/mod.rs
rename to frontend/src/provider/mod.rs
diff --git a/frontend/src/provider/service-provider.tsx b/frontend/src/provider/service-provider.tsx
deleted file mode 100644
index 3b5e5fea4..000000000
--- a/frontend/src/provider/service-provider.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React, {useContext} from "react";
-import ServiceContext, {Services} from "../service/service-context";
-
-const ServiceCtx = React.createContext(ServiceContext);
-const ServiceCtxProvider = ServiceCtx.Provider;
-
-export function ServiceProvider(props: any) {
- return {props.children}
-}
-
-export function useServices(): Services {
- return useContext(ServiceCtx);
-}
\ No newline at end of file
diff --git a/webui/src/provider/service_context_provider.rs b/frontend/src/provider/service_context_provider.rs
similarity index 100%
rename from webui/src/provider/service_context_provider.rs
rename to frontend/src/provider/service_context_provider.rs
diff --git a/frontend/src/react-app-env.d.ts b/frontend/src/react-app-env.d.ts
deleted file mode 100644
index 6431bc5fc..000000000
--- a/frontend/src/react-app-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/frontend/src/scss/_common.scss b/frontend/src/scss/_common.scss
deleted file mode 100644
index d44830f99..000000000
--- a/frontend/src/scss/_common.scss
+++ /dev/null
@@ -1,134 +0,0 @@
-$media-size-mobile: 600px;
-
-.hidden {
- display: none;
-}
-
-@mixin container_common($justify-content: flex-start, $padding: 12px) {
- display: flex;
- justify-content: $justify-content;
- gap: 8px;
- box-sizing: border-box;
- @media (max-width: $media-size-mobile) {
- padding: $padding;
- }
- @media (min-width: $media-size-mobile) {
- padding: 2px;
- }
-}
-
-@mixin container_vert() {
- flex-flow: column;
- @include container_common();
-}
-
-@mixin container_horz() {
- flex-flow: row;
- @include container_common();
-}
-
-@mixin container_horz_nowrap($justify-content: flex-start, $padding: 12px) {
- flex-flow: row nowrap;
- @media (max-width: $media-size-mobile) {
- flex-flow: column;
- }
- @include container_common($justify-content, $padding);
-}
-
-@mixin container_horz_no_padding() {
- flex-flow: row;
- @include container_common($padding: 0);
-}
-
-@mixin container_vert_no_padding($justify-content: flex-start) {
- flex-flow: column;
- @include container_common($justify-content, $padding: 0);
-}
-
-@mixin activeOptionButton {
- box-sizing: border-box !important;
- background-color: var(--option-active-background-color) !important;
- color: var(--option-active-color) !important;
- font-weight: bold;
- svg {
- color: var(--option-active-color) !important;
- }
-}
-
-@mixin iconButton {
- display: flex;
- justify-content: center;
- align-items: center;
- color: var(--icon-button-color);
- background-color: var(--icon-button-background-color);
- border: none;
- border-radius: var(--icon-button-border-radius);
- aspect-ratio: 1 / 1;
- cursor: pointer;
- box-sizing: border-box;
- max-height: 100%;
- width: min-content;
-
- svg {
- color: var(--text-color);
- }
- &:hover {
- svg {
- color: var(--text-button-hover-color);
- }
- }
-
- &:focus,
- &:hover {
- color: var(--icon-button-hover-color);
- background-color: var(--icon-button-hover-background-color);
- outline: none;
- }
-}
-
-@mixin textButton {
- border: 1px solid var(--text-button-border-color);
- font-size: 1.2rem;
- border-radius: var(--text-button-border-radius);
- padding: 8px 16px;
- background-color: var(--text-button-background-color);
- color: var(--text-button-color);
- &:focus,
- &:hover {
- background-color: var(--text-button-hover-background-color);
- color: var(--text-button-hover-color);
- border: 1px solid transparent;
- outline: none;
- }
-}
-
-@mixin list {
- ul {
- list-style-type: none; /* Remove bullets */
- padding: 0; /* Remove padding */
- margin: 0; /* Remove margins */
- li {
- padding: 8px;
- border-radius: var(--border-radius);
- }
- }
-}
-
-@mixin list-hover {
- ul {
- list-style-type: none; /* Remove bullets */
- padding: 0; /* Remove padding */
- margin: 0; /* Remove margins */
- li {
- padding: 8px;
- border-radius: var(--border-radius);
- user-select: none;
- cursor: pointer;
-
- &:hover {
- color: var(--popup-hover-color);
- background-color: var(--popup-hover-background-color);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/scss/_default.scss b/frontend/src/scss/_default.scss
deleted file mode 100644
index 74fe1d26e..000000000
--- a/frontend/src/scss/_default.scss
+++ /dev/null
@@ -1,123 +0,0 @@
-@use './common';
-
-::-webkit-scrollbar-track {
- -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
- border-radius: 10px;
- background-color: var(--scrollbar-track-color);
-}
-
-::-webkit-scrollbar {
- width: 12px;
- background-color: transparent;
-}
-
-::-webkit-scrollbar-thumb {
- border-radius: 10px;
- //-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
- background-color: var(--scrollbar-handle-color);
-}
-
-
-button {
- cursor: pointer;
- //&:active {
- // transform: scale(0.90); /* Scaling button to 0.98 to its original size */
- //}
-}
-
-.tp__hidden {
- display: none !important;
-}
-
-.card {
- display: flex;
- flex-flow: column;
- flex: 0 1 auto;
- align-content: flex-start;
- overflow: hidden;
- padding: 20px;
- border: 1px solid var(--card-border-color);
- background-color: var(--card-background-color);
- color: var(--card-color);
- filter: drop-shadow(3px 3px 4px var(--popup-shadow-color));
- min-width: 200px;
-
- border-radius:var(--border-radius);
-
- @media (min-width: common.$media-size-mobile) {
- padding: 2px;
- }
-}
-
-.vert-group {
- display: flex;
- flex-flow: column;
- gap: 20px;
- overflow: hidden;
- padding: 0 4px 4px 0;
-}
-
-select,
-input {
- color: var(--text-color);
- &:focus {
- background-color: var(--background-color) !important;
- }
-}
-
-select {
- padding: 2px 4px;
-}
-
-.tool-button {
- display: flex;
- align-items: center;
- justify-content: center;
- height: 1.1rem;
- width: 1.1rem;
- border-radius: var(--border-radius);
- background-color: var(--tool-button-background-color);
- color: var(--tool-button-color);
- cursor: pointer;
-
- svg {
- width: 1rem;
- height: 1rem;
- }
-
- &:hover {
- color: var(--tool-button-hover-color);
- background-color: var(--tool-button-hover-background-color);
- }
-
- &:active {
- transform: scale(0.9);
- }
-}
-
-:not(input[type="text"]) {
- caret-color: transparent;
-}
-input[type="text"] {
- caret-color: auto;
-}
-
-select {
- height: 2rem;
- border-radius: var(--border-radius);
- font-size: 1.2rem;
- padding: 0 8px;
- cursor: pointer;
- &:hover {
- color: var(--text-button-hover-color);
- background-color: var(--text-button-hover-background-color);
- }
-}
-
-option {
- cursor: pointer;
-}
-
-dialog {
- padding: 0 !important;
-}
\ No newline at end of file
diff --git a/frontend/src/scss/_panel.scss b/frontend/src/scss/_panel.scss
deleted file mode 100644
index 1c2408b4b..000000000
--- a/frontend/src/scss/_panel.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-@use "./common";
-
-.panel {
- @include common.container_vert_no_padding();
- overflow: hidden;
-}
diff --git a/frontend/src/scss/_preferences.scss b/frontend/src/scss/_preferences.scss
deleted file mode 100644
index 15b5b8d2f..000000000
--- a/frontend/src/scss/_preferences.scss
+++ /dev/null
@@ -1,53 +0,0 @@
-@use './common';
-
-@mixin prefsRoot {
- @include common.container_vert_no_padding(flex-start);
- align-content: flex-start;
- overflow: hidden;
-}
-
-@mixin prefsToolbar() {
- &__toolbar {
- display: flex;
- flex-flow: row wrap;
- flex: 0 0 auto;
- justify-content: flex-end;
- align-items: center;
- gap: 12px;
- background-color: var(--toolbar-bg-color);
- padding: 8px 16px;
-
- label {
- flex: 1 1 auto;
- font-size: 2rem;
- font-weight: bold;
- color: var(--title-label-color);
- }
-
- button {
- @include common.textButton();
- }
- }
-}
-
-@mixin prefsForm() {
- &__content {
- display: flex;
- flex-flow: column;
-
- &-help {
- display: flex;
- flex-flow: column;
- gap:12px;
- border-top: 1px solid var(--border-light-color);
- padding-top: 12px;
- color: var(--label-color);
-
- pre {
- margin: 0;
- }
- }
-
- }
-
-}
\ No newline at end of file
diff --git a/frontend/src/scss/_theme.scss b/frontend/src/scss/_theme.scss
deleted file mode 100644
index 9089edeb6..000000000
--- a/frontend/src/scss/_theme.scss
+++ /dev/null
@@ -1,110 +0,0 @@
-:root {
- --media-size-mobile: 720px;
- --text-color: #ffffff;
- --hint-color: #888888;
- --label-color: #73d6fe;
- --background-color: #303946; //#121212;
- --app-header-color: #73d6fe; //#6495b1;
- --sidebar-toggle-color: #ffffff;
- --sidebar-toggle-hover-color: #47495d;
- --playlist-color: #aeb0b2;
- --border-color: #47495d;
- --border-light-color: #68686c;
- --hover-border-color: #aaaab0;
- --spinner-color: #ffffff66;
- --spinner-highlight-color: #5599f0;
- --tree-count-color: #7b7b7e;
- --tree-hover-background-color: #004db4;
- --tree-hover-color: white;
- --tree-expander-hover-color: white;
- --tool-button-background-color: #434343;
- --tool-button-color: #fff;
- --tool-button-hover-color: #ffffff;
- --tool-button-hover-background-color: #0c8a13;
- --default-button-background-color: #124a9b;
-
- --toolbar-background-color: #282A36;
- --toolbar-button-hover-color: #ffffff;
- --toolbar-button-hover-background-color: #004db4;
-
- //--popup-background-color: #282A36;
- --popup-background-color: #353535;
- --popup-shadow-color: #06070a;
- --popup-border-color: #646464;
- --popup-color: #fff;
- --popup-hover-background-color: #5599f0;
- --popup-hover-color: #fff;
-
- --attention-color: red;
- --warn-color: orange;
- --ok-color: #21e821;
-
- --card-background-color: #202733; //#282828;
- --card-background-color-darker: #1c2026;
- --card-color: #ffffff;
- --card-border-color: #47495d;
-
- --title-label-color: #6495b1;
-
- --border-radius: 8px;
-
- --icon-button-border-radius: 4px;
- --icon-button-background-color: var(--default-button-background-color);
- --icon-button-color: #ffffff;
- --icon-button-hover-background-color: #0d6efd;
- --icon-button-hover-color: #ffffff;
- --icon-button-hover-border-color: #ced9fb;
-
- --menu-button-color:#ffffff;
- --menu-button-background-color: #20273344;
-
- --menu-button-hover-color: #ffffff;
- --menu-button-hover-background-color: #202733;
-
- --text-button-border-radius: 4px;
- --text-button-border-color: transparent;
- --text-button-background-color: var(--default-button-background-color);
- --text-button-color: #ffffff;
- --text-button-hover-background-color: #0d6efd;
- --text-button-hover-color: #ffffff;
-
- --scrollbar-track-color: #838383;
- --scrollbar-handle-color: #505050;
-
- --tabset-background-color: #44475a47; // #282A36;
- --tabset-border-color: #535353;
- --tab-background-color: #44475A;
- --tab-color: #fff;
- --tab-hover-background-color: #6272A4;
- --tab-hover-color: #50FA7B;
- --tab-avtive-indicator-color: #50FA7B;
- --tab-active-background-color: #54547a;
- --tab-active-color: #50FA7B;
-
- --table-border-color: #44475a;
-
- --checkbox-background-color: #44475A;
- --checkbox-hover-background-color: #006fee;
- --checkbox-checked-background-color: #006fee;
- --checkbox-border-radius: 4px;
-
- --option-active-background-color: #50FA7B;
- --option-active-color: #000;
-
- --shocase-card-background: #4b4b4b;
-
- --breadcrumbs-background-color: #202733;
- --breadcrumb-background-color: #4e545d;
- --breadcrumb-color: #c4c4c4;
- --breadcrumb-active-background-color: #4ad96e;
- --breadcrumb-active-color: #000;
-
- --menu-icon-color-update: #A3D8FF;
- --menu-icon-color-user: #0ABAB5;
- --menu-icon-color-api_server: #FF9D23;
- --menu-icon-color-main_config: #FF2DF1;
- --menu-icon-color-status : #F9E400;
-
- --toolbar-bg-color: #161b25;
-
-}
diff --git a/frontend/src/service/auth-service.ts b/frontend/src/service/auth-service.ts
deleted file mode 100644
index be9872a5f..000000000
--- a/frontend/src/service/auth-service.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import {
- catchError,
- concatWith, EMPTY,
- interval,
- map,
- Observable,
- of,
- ReplaySubject,
- takeWhile,
- tap,
- throwError
-} from "rxjs";
-import AuthApiService, {DefaultAuthApiService} from "../api/auth-api-service";
-import {first} from "rxjs/operators";
-import {jwtDecode} from "jwt-decode";
-
-export enum UserRole {
- NONE = 0,
- ADMIN = 1,
- USER= 2,
-}
-
-const AUTH_TOKEN_KEY = "auth-token";
-const NO_AUTHORIZATION_TOKEN = "authorized";
-
-const REFRESH_INTERVAL = 1000 * 60 * 15; // 15 mins
-
-export default class AuthService {
-
- private token: string;
- private subject = new ReplaySubject(UserRole.NONE);
-
- constructor(private authApiService: AuthApiService = new DefaultAuthApiService()) {
- this.token = localStorage.getItem(AUTH_TOKEN_KEY);
- interval(REFRESH_INTERVAL).pipe(
- takeWhile(() => this.token !== NO_AUTHORIZATION_TOKEN),
- concatWith(EMPTY)).subscribe(() => this.refresh().pipe(first()).subscribe({
- next: (success: boolean) => {
- if (success) {
- this.subject.next(this.getRole());
- } else {
- this.clearToken();
- }
- },
- error: () => this.clearToken(),
- }));
- }
-
- private clearToken() {
- localStorage.removeItem(AUTH_TOKEN_KEY);
- this.subject.next(UserRole.NONE);
- }
-
- private getRole(): UserRole {
- if (this.token) {
- try {
- if (this.token === NO_AUTHORIZATION_TOKEN) {
- return UserRole.ADMIN;
- }
- const claims: any = jwtDecode(this.token);
- if (claims?.roles?.includes("ADMIN")) {
- return UserRole.ADMIN;
- }
- if (claims?.roles?.includes("USER")) {
- return UserRole.USER;
- }
- } catch(err) {
- this.clearToken();
- }
- }
- return UserRole.NONE;
- }
-
- authChannel(): Observable {
- return this.subject;
- }
-
- refresh(): Observable {
- if (this.token) {
- return this.authApiService.refresh().pipe(map(auth => {
- this.token = auth.token;
- localStorage.setItem(AUTH_TOKEN_KEY, auth.token);
- // eslint-disable-next-line eqeqeq
- return auth.token != undefined
- }), tap(data => {
- this.subject.next(this.getRole());
- }), catchError((error: any) => {
- this.subject.next(UserRole.NONE);
- return throwError(() => error)
- }));
- }
- return of(false);
- }
-
- authenticate(username: string, password: string): Observable {
- return this.authApiService.authenticate(username, password).pipe(map(auth => {
- this.token = auth.token;
- localStorage.setItem(AUTH_TOKEN_KEY, auth.token);
- // eslint-disable-next-line eqeqeq
- return auth.token != undefined
- }), tap(data => {
- this.subject.next(this.getRole());
- }), catchError(() => {
- this.clearToken();
- return of(false);
- }));
- }
-
- isAuthenticated(): boolean {
- // eslint-disable-next-line eqeqeq
- return this.token != undefined;
- }
-
- getToken(): string {
- return this.token;
- }
-
- logout() {
- this.token = null;
- localStorage.removeItem(AUTH_TOKEN_KEY);
- this.subject.next(UserRole.NONE);
- }
-}
\ No newline at end of file
diff --git a/frontend/src/service/config-service.ts b/frontend/src/service/config-service.ts
deleted file mode 100644
index 77dca1212..000000000
--- a/frontend/src/service/config-service.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import ConfigApiService, {DefaultConfigApiService} from "../api/config-api-service";
-import {Observable} from "rxjs";
-import ServerConfig, {ApiProxyServerInfo, ServerMainConfig, TargetUser} from "../model/server-config";
-import {DefaultUiConfig, UiConfig} from "../model/ui-config";
-
-export default class ConfigService {
-
- private uiConfig: UiConfig = DefaultUiConfig;
-
- constructor(private configApiService: ConfigApiService = new DefaultConfigApiService()) {
- }
-
- setUiConfig(uiConfig: UiConfig): void {
- if (uiConfig) {
- this.uiConfig = {...DefaultUiConfig, ...uiConfig};
- }
- }
-
- getUiConfig(): UiConfig {
- return this.uiConfig;
- }
-
- getServerConfig(): Observable {
- return this.configApiService.getServerConfig();
- }
-
- saveMainConfig(config: ServerMainConfig) {
- return this.configApiService.saveMainConfig(config);
- }
-
- saveTargetUser(targetUser: TargetUser[]) {
- return this.configApiService.saveTargetUser(targetUser);
- }
-
- saveApiProxyConfig(serverInfo: ApiProxyServerInfo[]) {
- return this.configApiService.saveApiProxyConfig(serverInfo);
- }
-}
diff --git a/frontend/src/service/file-service.ts b/frontend/src/service/file-service.ts
deleted file mode 100755
index 2efc85cff..000000000
--- a/frontend/src/service/file-service.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import {Observable, Subject, Subscription, tap} from "rxjs";
-import {DownloadInfo, FileDownloadInfo, FileDownloadRequest} from "../model/file-download";
-import FileApiService, { DefaultFileApiService } from "../api/file-api-service";
-
-export default class FileService {
-
- private downloadNotification = new Subject();
- constructor(private fileApiService: FileApiService = new DefaultFileApiService()) {
- }
-
- subscribeDownloadNotification(observer: (value: T) => void): Subscription {
- return this.downloadNotification.subscribe(observer as any);
- }
-
- private notifyDownload(info: FileDownloadInfo) {
- this.downloadNotification.next(info);
- }
-
- download(req: FileDownloadRequest): Observable {
- return this.fileApiService.download(req).pipe(tap((result) => this.notifyDownload(result) ));
- }
-
- getDownloadInfo(): Observable {
- return this.fileApiService.getDownloadInfo();
- }
-}
-
diff --git a/frontend/src/service/playlist-service.ts b/frontend/src/service/playlist-service.ts
deleted file mode 100644
index 8ed8014eb..000000000
--- a/frontend/src/service/playlist-service.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import {Observable} from "rxjs";
-import PlaylistApiService, {DefaultPlaylistApiService} from "../api/playlist-api-service";
-import {first} from "rxjs/operators";
-import {PlaylistRequest} from "../model/playlist-request";
-import {PlaylistCategories, PlaylistGroup, PlaylistItem} from "../model/playlist";
-
-// const mergeCategory = (groups: PlaylistGroup[], channels: PlaylistItem[]) => {
-// if (groups?.length && channels?.length) {
-// let unknown: PlaylistGroup = {
-// id: 0,
-// name: "Unknown",
-// channels: [],
-// };
-// const groupMap = groups.reduce((acc: any, group: PlaylistGroup) => {
-// acc[group.name] = group;
-// group.channels = group.channels ?? [];
-// return acc;
-// }, {});
-// channels.forEach(channel => {
-// let group = groupMap[channel.group] ?? unknown;
-// group.channels.push(channel);
-// });
-// }
-// }
-
-export default class PlaylistService {
-
- constructor(private playlistApiService: PlaylistApiService = new DefaultPlaylistApiService()) {
- }
-
- getPlaylistCategories(req: PlaylistRequest): Observable {
- return new Observable((obs) =>
- this.playlistApiService.getPlaylist(req).pipe(first()).subscribe({
- next: (pl: PlaylistCategories) => {
- if (pl) {
- let cnt = 0;
- [pl?.live, pl?.vod, pl?.series].filter(Boolean).flat().forEach((g: PlaylistGroup) => {
- g.id = ++cnt;
- g.name = g.name ?? (g as any).title;
- g?.channels?.forEach(c => {
- c.id = ++cnt;
- });
- });
- obs.next(pl);
- } else {
- obs.error("Could not download playlist");
- }
- },
- error: (e) => obs.error(e),
- complete: () => obs.complete(),
- }));
- }
-
- update(targets: string[]): Observable {
- return this.playlistApiService.updateTargets(targets);
- }
-
- getWebPlayerUrl(item: PlaylistItem, req: PlaylistRequest): Observable {
- return this.playlistApiService.getWebPlayerUrl(item, req);
- }
-
-}
diff --git a/frontend/src/service/server-status-service.ts b/frontend/src/service/server-status-service.ts
deleted file mode 100644
index cbbfd1e4f..000000000
--- a/frontend/src/service/server-status-service.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import {Observable} from "rxjs";
-import {ServerIpCheck, ServerStatus} from "../model/server-status";
-import ServerStatusApiService, {DefaultServerStatusApiService} from "../api/server-status-api-service";
-
-export default class ServerStatusService {
-
- constructor(private serverStatusApiService: ServerStatusApiService = new DefaultServerStatusApiService()) {
- }
-
- getServerStatus(): Observable {
- return this.serverStatusApiService.getServerStatus();
- }
- getIpCheck(): Observable {
- return this.serverStatusApiService.getServerIpCheck();
- }
-}
diff --git a/frontend/src/service/service-context.ts b/frontend/src/service/service-context.ts
deleted file mode 100644
index 35c35642c..000000000
--- a/frontend/src/service/service-context.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import ConfigService from "./config-service";
-import PlaylistService from "./playlist-service";
-import FileService from "./file-service";
-import AuthService from "./auth-service";
-import UserConfigService from "./user-config-service";
-import ServerStatusService from "./server-status-service";
-
-export interface Services {
- config(): ConfigService;
-
- playlist(): PlaylistService;
-
- file(): FileService;
-
- auth(): AuthService;
-
- userConfig(): UserConfigService;
-
- status(): ServerStatusService;
-}
-
-class ServiceContextImpl implements Services {
-
- private readonly _configService: ConfigService = new ConfigService();
- private readonly _playlistService: PlaylistService = new PlaylistService();
- private readonly _fileService: FileService = new FileService();
- private readonly _authService: AuthService = new AuthService();
- private readonly _userConfigService: UserConfigService = new UserConfigService();
- private readonly _statusService: ServerStatusService = new ServerStatusService();
-
- config() {
- return this._configService;
- }
-
- playlist() {
- return this._playlistService;
- }
-
- file() {
- return this._fileService;
- }
-
- auth() {
- return this._authService;
- }
-
- userConfig(): UserConfigService {
- return this._userConfigService;
- }
-
- status(): ServerStatusService {
- return this._statusService;
- }
-}
-
-const ServiceContext = new ServiceContextImpl();
-export default ServiceContext;
-
diff --git a/frontend/src/service/user-config-service.ts b/frontend/src/service/user-config-service.ts
deleted file mode 100644
index 2e2ddca94..000000000
--- a/frontend/src/service/user-config-service.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Observable } from "rxjs";
-import UserConfigApiService, {DefaultUserConfigApiService} from "../api/user-config-api-service";
-import {UserPlaylistCategories} from "../model/playlist";
-
-export default class UserConfigService {
- constructor(private userConfigApiService: UserConfigApiService = new DefaultUserConfigApiService()) {
- }
-
- getPlaylistBouquet(): Observable {
- return this.userConfigApiService.getPlaylistBouquet();
- }
- getPlaylistCategories(): Observable {
- return this.userConfigApiService.getPlaylistCategories();
- }
-
- savePlaylistBouquet(bouquet: UserPlaylistCategories): Observable {
- return this.userConfigApiService.savePlaylistBouquet(bouquet);
- }
-}
diff --git a/webui/src/services/auth_service.rs b/frontend/src/services/auth_service.rs
similarity index 100%
rename from webui/src/services/auth_service.rs
rename to frontend/src/services/auth_service.rs
diff --git a/webui/src/services/config_service.rs b/frontend/src/services/config_service.rs
similarity index 100%
rename from webui/src/services/config_service.rs
rename to frontend/src/services/config_service.rs
diff --git a/webui/src/services/dialog_service.rs b/frontend/src/services/dialog_service.rs
similarity index 100%
rename from webui/src/services/dialog_service.rs
rename to frontend/src/services/dialog_service.rs
diff --git a/webui/src/services/event_service.rs b/frontend/src/services/event_service.rs
similarity index 100%
rename from webui/src/services/event_service.rs
rename to frontend/src/services/event_service.rs
diff --git a/webui/src/services/mod.rs b/frontend/src/services/mod.rs
similarity index 100%
rename from webui/src/services/mod.rs
rename to frontend/src/services/mod.rs
diff --git a/webui/src/services/playlist_service.rs b/frontend/src/services/playlist_service.rs
similarity index 100%
rename from webui/src/services/playlist_service.rs
rename to frontend/src/services/playlist_service.rs
diff --git a/webui/src/services/requests.rs b/frontend/src/services/requests.rs
similarity index 100%
rename from webui/src/services/requests.rs
rename to frontend/src/services/requests.rs
diff --git a/webui/src/services/status_service.rs b/frontend/src/services/status_service.rs
similarity index 100%
rename from webui/src/services/status_service.rs
rename to frontend/src/services/status_service.rs
diff --git a/webui/src/services/toastr_service.rs b/frontend/src/services/toastr_service.rs
similarity index 100%
rename from webui/src/services/toastr_service.rs
rename to frontend/src/services/toastr_service.rs
diff --git a/webui/src/services/websocket_service.rs b/frontend/src/services/websocket_service.rs
similarity index 100%
rename from webui/src/services/websocket_service.rs
rename to frontend/src/services/websocket_service.rs
diff --git a/frontend/src/utils/click-away-listener.tsx b/frontend/src/utils/click-away-listener.tsx
deleted file mode 100644
index 357492420..000000000
--- a/frontend/src/utils/click-away-listener.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import * as React from 'react';
-import {
- useRef,
- useEffect,
- RefCallback,
- cloneElement,
- ReactElement,
- HTMLAttributes,
- MutableRefObject,
- FunctionComponent
-} from 'react';
-
-type MouseEvents = 'click' | 'mousedown' | 'mouseup';
-type TouchEvents = 'touchstart' | 'touchend';
-type Events = MouseEvent | TouchEvent | KeyboardEvent;
-
-interface Props extends HTMLAttributes {
- onClickAway: (event: Events) => void;
- mouseEvent?: MouseEvents;
- touchEvent?: TouchEvents;
- children: ReactElement;
-}
-
-const eventTypeMapping = {
- click: 'onClick',
- mousedown: 'onMouseDown',
- mouseup: 'onMouseUp',
- touchstart: 'onTouchStart',
- touchend: 'onTouchEnd'
-};
-
-const ClickAwayListener: FunctionComponent = ({
- children,
- onClickAway,
- mouseEvent = 'click',
- touchEvent = 'touchend'
- }) => {
- const node = useRef(undefined);
- const bubbledEventTarget = useRef(undefined);
- const mountedRef = useRef(false);
-
- /**
- * Prevents the bubbled event from getting triggered immediately
- * https://github.com/facebook/react/issues/20074
- */
- useEffect(() => {
- setTimeout(() => {
- mountedRef.current = true;
- }, 0);
-
- return () => {
- mountedRef.current = false;
- };
- }, []);
-
- const handleBubbledEvents =
- (type: string) =>
- (event: Events): void => {
- bubbledEventTarget.current = event.target;
-
- const handler = children?.props[type];
-
- if (handler) {
- handler(event);
- }
- };
-
- const handleChildRef = (childRef: HTMLElement) => {
- node.current = childRef;
-
- let {ref} = children as typeof children & {
- ref: RefCallback | MutableRefObject;
- };
-
- if (typeof ref === 'function') {
- ref(childRef);
- } else if (ref) {
- ref.current = childRef;
- }
- };
-
- useEffect(() => {
- const handleEvents = (event: Events): void => {
- if (!mountedRef.current) return;
-
- if (
- (node.current && node.current.contains(event.target as Node)) ||
- bubbledEventTarget.current === event.target ||
- !document.contains(event.target as Node)
- ) {
- return;
- }
-
- onClickAway(event);
- };
-
- const handleKeayboardEvent = (evt: KeyboardEvent) => {
- if (!mountedRef.current) return;
- if (evt.key === 'Escape') {
- onClickAway(evt);
- }
- }
-
- document.addEventListener(mouseEvent, handleEvents);
- document.addEventListener(touchEvent, handleEvents);
- document.addEventListener('keydown', handleKeayboardEvent, {passive: true});
-
- return () => {
- document.removeEventListener(mouseEvent, handleEvents);
- document.removeEventListener(touchEvent, handleEvents);
- document.removeEventListener('keydown', handleKeayboardEvent);
- };
- }, [mouseEvent, onClickAway, touchEvent]);
-
- const mappedMouseEvent = eventTypeMapping[mouseEvent];
- const mappedTouchEvent = eventTypeMapping[touchEvent];
-
- return React.Children.only(
- // @ts-ignore
- cloneElement(children as ReactElement, {
- ref: handleChildRef,
- [mappedMouseEvent]: handleBubbledEvents(mappedMouseEvent),
- [mappedTouchEvent]: handleBubbledEvents(mappedTouchEvent)
- })
- );
-};
-
-ClickAwayListener.displayName = 'ClickAwayListener';
-
-export default ClickAwayListener;
diff --git a/frontend/src/utils/clipboard.ts b/frontend/src/utils/clipboard.ts
deleted file mode 100644
index b540d0256..000000000
--- a/frontend/src/utils/clipboard.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import {Observable, of, from} from "rxjs";
-function copyToClipboardOld(text: string): boolean {
- const el: HTMLElement = document.createElement('span');
- el.innerText = text;
- el.style.position = 'absolute';
- el.style.border = '0';
- el.style.padding = '0';
- el.style.margin = '0';
- // Move element out of screen horizontally
- el.style.position = 'absolute';
- const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
- el.style[ isRTL ? 'right' : 'left' ] = '-9999px';
- // Move element to the same position vertically
- const yPosition = window.pageYOffset || document.documentElement.scrollTop;
- el.style.top = `${yPosition}px`;
- document.body.appendChild(el);
-
- const selection = window.getSelection();
- const range = document.createRange();
- range.selectNode(el);
- selection.removeAllRanges();
- selection.addRange(range);
-
- let result;
- try {
- result = document.execCommand('copy');
- selection.removeAllRanges();
-
- } catch(err) {
- result = false;
- }
- document.body.removeChild(el)
- return result;
-}
-
-export default function copyToClipboard(text: string): Observable {
- let result: Observable;
- if (copyToClipboardOld(text)) {
- result = of(true);
- } else {
- const promise = navigator.clipboard.writeText(text);
- result = new Observable(observer => {
- from(promise).subscribe({
- next: () => observer.next(true),
- error: (err) => observer.next(false),
- complete: () => observer.complete()
- })
- });
- }
- return result;
-}
diff --git a/frontend/src/utils/config-utils.ts b/frontend/src/utils/config-utils.ts
deleted file mode 100644
index 20a52033f..000000000
--- a/frontend/src/utils/config-utils.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import ServerConfig from "../model/server-config";
-
-const getTargetNames = (config: ServerConfig): string[] => {
- return config?.sources.flatMap(s => s.targets)
- .map(t => t.name).filter(n => "default" !== n) || [];
-}
-
-
-const ConfigUtils = {
- getTargetNames
-}
-
-export default ConfigUtils;
\ No newline at end of file
diff --git a/frontend/src/utils/date-utils.ts b/frontend/src/utils/date-utils.ts
deleted file mode 100644
index 26ca53d72..000000000
--- a/frontend/src/utils/date-utils.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-function formatDate(date: any): string {
- if (typeof date === 'string') {
- return date;
- }
- if (typeof date === 'number') {
- date = new Date(date);
-
- }
- if (typeof date?.getTime === 'function') {
- const day = String(date.getDate()).padStart(2, '0');
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const year = date.getFullYear();
-
- return `${year}-${month}-${day}`;
- }
- return '';
-}
-
-const DateUtils = {
- formatDate: (date: any) => {
- if (date) {
- return formatDate(date);
- }
- return '';
- },
- toUnixSeconds: (date: any) => {
- if (date && typeof date.getTime == 'function') {
- if (date.getTime() > 0) {
- return Math.floor(date.getTime() / 1000);
- }
- return 0;
- }
- if (date && typeof date == 'number') {
- if ( date > 0) {
- return Math.floor(date / 1000);
- }
- return 0;
- }
- return date;
- },
- unixSecondsToDate: (date: any) => {
- if (date && typeof date == 'number') {
- return new Date(date * 1000);
- }
- if (date && typeof date.getTime == 'function') {
- return date;
- }
- return date;
- }
-}
-
-export default DateUtils;
\ No newline at end of file
diff --git a/frontend/src/utils/fetcher.ts b/frontend/src/utils/fetcher.ts
deleted file mode 100644
index 022e3f8de..000000000
--- a/frontend/src/utils/fetcher.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import {first, Observable} from "rxjs";
-
-const fetchJson = (fileName: string): Observable => {
- return new Observable((observer) => {
- fetch(process.env.PUBLIC_URL + fileName, {method: 'GET'})
- .then(res => res.json())
- .then(data => {
- observer.next(data)
- observer.complete();
- })
- .catch((e) => observer.error(e));
- }).pipe(first());
-}
-
-const Fetcher = {
- fetchJson,
-}
-
-export default Fetcher;
diff --git a/frontend/src/utils/i18n.ts b/frontend/src/utils/i18n.ts
deleted file mode 100644
index ee962c202..000000000
--- a/frontend/src/utils/i18n.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import {Observable, zip} from "rxjs";
-import i18next from "i18next";
-import {initReactI18next} from "react-i18next";
-import Fetcher from "./fetcher";
-import {LANGUAGE, LANGUAGE_LOCAL_STORAGE_KEY} from "../hook/use-translator";
-
-export default function i18n_init(languages: string[]): Observable {
- return new Observable(observer => {
- const failed = (err: any) => {
- console.error('failed to load i18n');
- observer.error(err);
- }
-
- i18next.use(initReactI18next)
- zip(languages.map(lang => Fetcher.fetchJson('assets/i18n/' + lang + '_common.json')))
- .subscribe({
- next: (responses) => {
- let savedLanguage = localStorage.getItem(LANGUAGE_LOCAL_STORAGE_KEY);
- if (savedLanguage?.length ) {
- LANGUAGE.next(savedLanguage);
- } else {
- LANGUAGE.next(languages[0]);
- }
- // 'common' is our custom namespace
- const resources = languages.reduce((acc: any, lang, idx) => {
- acc[lang] = {common: responses[idx]};
- return acc;
- }, {});
- i18next.init({
- interpolation: {escapeValue: false}, // React already does escaping
- lng: savedLanguage, // language to use
- fallbackLng: {default: ['en']},
- resources,
- }).then(() => observer.next(true))
- .catch(failed);
- },
- error: failed
- });
- });
-}
\ No newline at end of file
diff --git a/frontend/src/utils/icon-utils.tsx b/frontend/src/utils/icon-utils.tsx
deleted file mode 100644
index 7446eb461..000000000
--- a/frontend/src/utils/icon-utils.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import * as React from "react";
-import SvgIcon from "../component/svg-icon/svg-icon";
-
-export default function createIcon(name: string, path: string): React.JSX.Element {
- return ;
-}
diff --git a/webui/src/utils/mod.rs b/frontend/src/utils/mod.rs
similarity index 100%
rename from webui/src/utils/mod.rs
rename to frontend/src/utils/mod.rs
diff --git a/webui/src/utils/storage.rs b/frontend/src/utils/storage.rs
similarity index 100%
rename from webui/src/utils/storage.rs
rename to frontend/src/utils/storage.rs
diff --git a/frontend/src/utils/text-generator.ts b/frontend/src/utils/text-generator.ts
deleted file mode 100644
index 37ec4c411..000000000
--- a/frontend/src/utils/text-generator.ts
+++ /dev/null
@@ -1,1162 +0,0 @@
-const NAMES = [
- 'Aaron',
- 'Adam',
- 'Adrian',
- 'Adriano',
- 'Ahmad',
- 'Ahmed',
- 'Ahmet',
- 'Alan',
- 'Albert',
- 'Alessandro',
- 'Alessio',
- 'Alex',
- 'Alexander',
- 'Alfred',
- 'Ali',
- 'Amar',
- 'Amir',
- 'Amon',
- 'Andre',
- 'Andreas',
- 'Andrew',
- 'Angelo',
- 'Ansgar',
- 'Anthony',
- 'Anton',
- 'Antonio',
- 'Arda',
- 'Arian',
- 'Armin',
- 'Arne',
- 'Arno',
- 'Arthur',
- 'Artur',
- 'Arved',
- 'Arvid',
- 'Ayman',
- 'Baran',
- 'Baris',
- 'Bastian',
- 'Batuhan',
- 'Bela',
- 'Ben',
- 'Benedikt',
- 'Benjamin',
- 'Bennet',
- 'Bennett',
- 'Benno',
- 'Bent',
- 'Berat',
- 'Berkay',
- 'Bernd',
- 'Bilal',
- 'Bjarne',
- 'Björn',
- 'Bo',
- 'Boris',
- 'Brandon',
- 'Brian',
- 'Bruno',
- 'Bryan',
- 'Burak',
- 'Calvin',
- 'Can',
- 'Carl',
- 'Carlo',
- 'Carlos',
- 'Caspar',
- 'Cedric',
- 'Cedrik',
- 'Cem',
- 'Charlie',
- 'Chris',
- 'Christian',
- 'Christiano',
- 'Christoph',
- 'Christopher',
- 'Claas',
- 'Clemens',
- 'Colin',
- 'Collin',
- 'Conner',
- 'Connor',
- 'Constantin',
- 'Corvin',
- 'Curt',
- 'Damian',
- 'Damien',
- 'Daniel',
- 'Danilo',
- 'Danny',
- 'Darian',
- 'Dario',
- 'Darius',
- 'Darren',
- 'David',
- 'Davide',
- 'Davin',
- 'Dean',
- 'Deniz',
- 'Dennis',
- 'Denny',
- 'Devin',
- 'Diego',
- 'Dion',
- 'Domenic',
- 'Domenik',
- 'Dominic',
- 'Dominik',
- 'Dorian',
- 'Dustin',
- 'Dylan',
- 'Ecrin',
- 'Eddi',
- 'Eddy',
- 'Edgar',
- 'Edwin',
- 'Efe',
- 'Ege',
- 'Elia',
- 'Eliah',
- 'Elias',
- 'Elijah',
- 'Emanuel',
- 'Emil',
- 'Emilian',
- 'Emilio',
- 'Emir',
- 'Emirhan',
- 'Emre',
- 'Enes',
- 'Enno',
- 'Enrico',
- 'Eren',
- 'Eric',
- 'Erik',
- 'Etienne',
- 'Fabian',
- 'Fabien',
- 'Fabio',
- 'Fabrice',
- 'Falk',
- 'Felix',
- 'Ferdinand',
- 'Fiete',
- 'Filip',
- 'Finlay',
- 'Finley',
- 'Finn',
- 'Finnley',
- 'Florian',
- 'Francesco',
- 'Franz',
- 'Frederic',
- 'Frederick',
- 'Frederik',
- 'Friedrich',
- 'Fritz',
- 'Furkan',
- 'Fynn',
- 'Gabriel',
- 'Georg',
- 'Gerrit',
- 'Gian',
- 'Gianluca',
- 'Gino',
- 'Giuliano',
- 'Giuseppe',
- 'Gregor',
- 'Gustav',
- 'Hagen',
- 'Hamza',
- 'Hannes',
- 'Hanno',
- 'Hans',
- 'Hasan',
- 'Hassan',
- 'Hauke',
- 'Hendrik',
- 'Hennes',
- 'Henning',
- 'Henri',
- 'Henrick',
- 'Henrik',
- 'Henry',
- 'Hugo',
- 'Hussein',
- 'Ian',
- 'Ibrahim',
- 'Ilias',
- 'Ilja',
- 'Ilyas',
- 'Immanuel',
- 'Ismael',
- 'Ismail',
- 'Ivan',
- 'Iven',
- 'Jack',
- 'Jacob',
- 'Jaden',
- 'Jakob',
- 'Jamal',
- 'James',
- 'Jamie',
- 'Jan',
- 'Janek',
- 'Janis',
- 'Janne',
- 'Jannek',
- 'Jannes',
- 'Jannik',
- 'Jannis',
- 'Jano',
- 'Janosch',
- 'Jared',
- 'Jari',
- 'Jarne',
- 'Jarno',
- 'Jaron',
- 'Jason',
- 'Jasper',
- 'Jay',
- 'Jayden',
- 'Jayson',
- 'Jean',
- 'Jens',
- 'Jeremias',
- 'Jeremie',
- 'Jeremy',
- 'Jermaine',
- 'Jerome',
- 'Jesper',
- 'Jesse',
- 'Jim',
- 'Jimmy',
- 'Joe',
- 'Joel',
- 'Joey',
- 'Johann',
- 'Johannes',
- 'John',
- 'Johnny',
- 'Jon',
- 'Jona',
- 'Jonah',
- 'Jonas',
- 'Jonathan',
- 'Jonte',
- 'Joost',
- 'Jordan',
- 'Joris',
- 'Joscha',
- 'Joschua',
- 'Josef',
- 'Joseph',
- 'Josh',
- 'Joshua',
- 'Josua',
- 'Juan',
- 'Julian',
- 'Julien',
- 'Julius',
- 'Juri',
- 'Justin',
- 'Justus',
- 'Kaan',
- 'Kai',
- 'Kalle',
- 'Karim',
- 'Karl',
- 'Karlo',
- 'Kay',
- 'Keanu',
- 'Kenan',
- 'Kenny',
- 'Keno',
- 'Kerem',
- 'Kerim',
- 'Kevin',
- 'Kian',
- 'Kilian',
- 'Kim',
- 'Kimi',
- 'Kjell',
- 'Klaas',
- 'Klemens',
- 'Konrad',
- 'Konstantin',
- 'Koray',
- 'Korbinian',
- 'Kurt',
- 'Lars',
- 'Lasse',
- 'Laurence',
- 'Laurens',
- 'Laurenz',
- 'Laurin',
- 'Lean',
- 'Leander',
- 'Leandro',
- 'Leif',
- 'Len',
- 'Lenn',
- 'Lennard',
- 'Lennart',
- 'Lennert',
- 'Lennie',
- 'Lennox',
- 'Lenny',
- 'Leo',
- 'Leon',
- 'Leonard',
- 'Leonardo',
- 'Leonhard',
- 'Leonidas',
- 'Leopold',
- 'Leroy',
- 'Levent',
- 'Levi',
- 'Levin',
- 'Lewin',
- 'Lewis',
- 'Liam',
- 'Lian',
- 'Lias',
- 'Lino',
- 'Linus',
- 'Lio',
- 'Lion',
- 'Lionel',
- 'Logan',
- 'Lorenz',
- 'Lorenzo',
- 'Loris',
- 'Louis',
- 'Luan',
- 'Luc',
- 'Luca',
- 'Lucas',
- 'Lucian',
- 'Lucien',
- 'Ludwig',
- 'Luis',
- 'Luiz',
- 'Luk',
- 'Luka',
- 'Lukas',
- 'Luke',
- 'Lutz',
- 'Maddox',
- 'Mads',
- 'Magnus',
- 'Maik',
- 'Maksim',
- 'Malik',
- 'Malte',
- 'Manuel',
- 'Marc',
- 'Marcel',
- 'Marco',
- 'Marcus',
- 'Marek',
- 'Marian',
- 'Mario',
- 'Marius',
- 'Mark',
- 'Marko',
- 'Markus',
- 'Marlo',
- 'Marlon',
- 'Marten',
- 'Martin',
- 'Marvin',
- 'Marwin',
- 'Mateo',
- 'Mathis',
- 'Matis',
- 'Mats',
- 'Matteo',
- 'Mattes',
- 'Matthias',
- 'Matthis',
- 'Matti',
- 'Mattis',
- 'Maurice',
- 'Max',
- 'Maxim',
- 'Maximilian',
- 'Mehmet',
- 'Meik',
- 'Melvin',
- 'Merlin',
- 'Mert',
- 'Michael',
- 'Michel',
- 'Mick',
- 'Miguel',
- 'Mika',
- 'Mikail',
- 'Mike',
- 'Milan',
- 'Milo',
- 'Mio',
- 'Mirac',
- 'Mirco',
- 'Mirko',
- 'Mohamed',
- 'Mohammad',
- 'Mohammed',
- 'Moritz',
- 'Morten',
- 'Muhammed',
- 'Murat',
- 'Mustafa',
- 'Nathan',
- 'Nathanael',
- 'Nelson',
- 'Neo',
- 'Nevio',
- 'Nick',
- 'Niclas',
- 'Nico',
- 'Nicolai',
- 'Nicolas',
- 'Niels',
- 'Nikita',
- 'Niklas',
- 'Niko',
- 'Nikolai',
- 'Nikolas',
- 'Nils',
- 'Nino',
- 'Noah',
- 'Noel',
- 'Norman',
- 'Odin',
- 'Oke',
- 'Ole',
- 'Oliver',
- 'Omar',
- 'Onur',
- 'Oscar',
- 'Oskar',
- 'Pascal',
- 'Patrice',
- 'Patrick',
- 'Paul',
- 'Peer',
- 'Pepe',
- 'Peter',
- 'Phil',
- 'Philip',
- 'Philipp',
- 'Pierre',
- 'Piet',
- 'Pit',
- 'Pius',
- 'Quentin',
- 'Quirin',
- 'Rafael',
- 'Raik',
- 'Ramon',
- 'Raphael',
- 'Rasmus',
- 'Raul',
- 'Rayan',
- 'René',
- 'Ricardo',
- 'Riccardo',
- 'Richard',
- 'Rick',
- 'Rico',
- 'Robert',
- 'Robin',
- 'Rocco',
- 'Roman',
- 'Romeo',
- 'Ron',
- 'Ruben',
- 'Ryan',
- 'Said',
- 'Salih',
- 'Sam',
- 'Sami',
- 'Sammy',
- 'Samuel',
- 'Sandro',
- 'Santino',
- 'Sascha',
- 'Sean',
- 'Sebastian',
- 'Selim',
- 'Semih',
- 'Shawn',
- 'Silas',
- 'Simeon',
- 'Simon',
- 'Sinan',
- 'Sky',
- 'Stefan',
- 'Steffen',
- 'Stephan',
- 'Steve',
- 'Steven',
- 'Sven',
- 'Sönke',
- 'Sören',
- 'Taha',
- 'Tamino',
- 'Tammo',
- 'Tarik',
- 'Tayler',
- 'Taylor',
- 'Teo',
- 'Theo',
- 'Theodor',
- 'Thies',
- 'Thilo',
- 'Thomas',
- 'Thorben',
- 'Thore',
- 'Thorge',
- 'Tiago',
- 'Til',
- 'Till',
- 'Tillmann',
- 'Tim',
- 'Timm',
- 'Timo',
- 'Timon',
- 'Timothy',
- 'Tino',
- 'Titus',
- 'Tizian',
- 'Tjark',
- 'Tobias',
- 'Tom',
- 'Tommy',
- 'Toni',
- 'Tony',
- 'Torben',
- 'Tore',
- 'Tristan',
- 'Tyler',
- 'Tyron',
- 'Umut',
- 'Valentin',
- 'Valentino',
- 'Veit',
- 'Victor',
- 'Viktor',
- 'Vin',
- 'Vincent',
- 'Vito',
- 'Vitus',
- 'Wilhelm',
- 'Willi',
- 'William',
- 'Willy',
- 'Xaver',
- 'Yannic',
- 'Yannick',
- 'Yannik',
- 'Yannis',
- 'Yasin',
- 'Youssef',
- 'Yunus',
- 'Yusuf',
- 'Yven',
- 'Yves',
- 'Ömer',
- 'Aaliyah',
- 'Abby',
- 'Abigail',
- 'Ada',
- 'Adelina',
- 'Adriana',
- 'Aileen',
- 'Aimee',
- 'Alana',
- 'Alea',
- 'Alena',
- 'Alessa',
- 'Alessia',
- 'Alexa',
- 'Alexandra',
- 'Alexia',
- 'Alexis',
- 'Aleyna',
- 'Alia',
- 'Alica',
- 'Alice',
- 'Alicia',
- 'Alina',
- 'Alisa',
- 'Alisha',
- 'Alissa',
- 'Aliya',
- 'Aliyah',
- 'Allegra',
- 'Alma',
- 'Alyssa',
- 'Amalia',
- 'Amanda',
- 'Amelia',
- 'Amelie',
- 'Amina',
- 'Amira',
- 'Amy',
- 'Ana',
- 'Anabel',
- 'Anastasia',
- 'Andrea',
- 'Angela',
- 'Angelina',
- 'Angelique',
- 'Anja',
- 'Ann',
- 'Anna',
- 'Annabel',
- 'Annabell',
- 'Annabelle',
- 'Annalena',
- 'Anne',
- 'Anneke',
- 'Annelie',
- 'Annemarie',
- 'Anni',
- 'Annie',
- 'Annika',
- 'Anny',
- 'Anouk',
- 'Antonia',
- 'Ariana',
- 'Ariane',
- 'Arwen',
- 'Ashley',
- 'Asya',
- 'Aurelia',
- 'Aurora',
- 'Ava',
- 'Ayleen',
- 'Aylin',
- 'Ayse',
- 'Azra',
- 'Betty',
- 'Bianca',
- 'Bianka',
- 'Caitlin',
- 'Cara',
- 'Carina',
- 'Carla',
- 'Carlotta',
- 'Carmen',
- 'Carolin',
- 'Carolina',
- 'Caroline',
- 'Cassandra',
- 'Catharina',
- 'Catrin',
- 'Cecile',
- 'Cecilia',
- 'Celia',
- 'Celina',
- 'Celine',
- 'Ceyda',
- 'Ceylin',
- 'Chantal',
- 'Charleen',
- 'Charlotta',
- 'Charlotte',
- 'Chayenne',
- 'Cheyenne',
- 'Chiara',
- 'Christin',
- 'Christina',
- 'Cindy',
- 'Claire',
- 'Clara',
- 'Clarissa',
- 'Colleen',
- 'Collien',
- 'Cora',
- 'Corinna',
- 'Cosima',
- 'Dana',
- 'Daniela',
- 'Daria',
- 'Darleen',
- 'Defne',
- 'Delia',
- 'Denise',
- 'Diana',
- 'Dilara',
- 'Dina',
- 'Dorothea',
- 'Eda',
- 'Eileen',
- 'Ela',
- 'Elaine',
- 'Elanur',
- 'Elea',
- 'Elena',
- 'Eleni',
- 'Eleonora',
- 'Eliana',
- 'Elif',
- 'Elina',
- 'Elisa',
- 'Elisabeth',
- 'Ella',
- 'Ellen',
- 'Elli',
- 'Elly',
- 'Elsa',
- 'Emelie',
- 'Emely',
- 'Emilia',
- 'Emilie',
- 'Emily',
- 'Emma',
- 'Emmely',
- 'Emmi',
- 'Emmy',
- 'Enie',
- 'Enna',
- 'Enya',
- 'Esma',
- 'Estelle',
- 'Esther',
- 'Eva',
- 'Evelin',
- 'Evelina',
- 'Eveline',
- 'Evelyn',
- 'Fabienne',
- 'Fatima',
- 'Fatma',
- 'Felicia',
- 'Felicitas',
- 'Felina',
- 'Femke',
- 'Fenja',
- 'Fine',
- 'Finia',
- 'Finja',
- 'Finnja',
- 'Fiona',
- 'Flora',
- 'Florentine',
- 'Francesca',
- 'Franka',
- 'Franziska',
- 'Frederike',
- 'Freya',
- 'Frida',
- 'Frieda',
- 'Friederike',
- 'Giada',
- 'Gina',
- 'Giulia',
- 'Giuliana',
- 'Greta',
- 'Hailey',
- 'Hana',
- 'Hanna',
- 'Hannah',
- 'Heidi',
- 'Helen',
- 'Helena',
- 'Helene',
- 'Helin',
- 'Henriette',
- 'Henrike',
- 'Hermine',
- 'Ida',
- 'Ilayda',
- 'Imke',
- 'Ina',
- 'Ines',
- 'Inga',
- 'Inka',
- 'Irem',
- 'Isa',
- 'Isabel',
- 'Isabell',
- 'Isabella',
- 'Isabelle',
- 'Ivonne',
- 'Jacqueline',
- 'Jamila',
- 'Jana',
- 'Jane',
- 'Janin',
- 'Janina',
- 'Janine',
- 'Janna',
- 'Jara',
- 'Jasmin',
- 'Jasmina',
- 'Jasmine',
- 'Jella',
- 'Jenna',
- 'Jennifer',
- 'Jenny',
- 'Jessica',
- 'Jessy',
- 'Jette',
- 'Jil',
- 'Jill',
- 'Joana',
- 'Joanna',
- 'Joelina',
- 'Joeline',
- 'Joelle',
- 'Johanna',
- 'Joleen',
- 'Jolie',
- 'Jolien',
- 'Jolin',
- 'Jolina',
- 'Joline',
- 'Jonna',
- 'Josefin',
- 'Josefine',
- 'Josephin',
- 'Josephine',
- 'Josie',
- 'Josy',
- 'Joy',
- 'Joyce',
- 'Judith',
- 'Judy',
- 'Jule',
- 'Julia',
- 'Juliana',
- 'Juliane',
- 'Julie',
- 'Julienne',
- 'Julika',
- 'Julina',
- 'Juna',
- 'Justine',
- 'Kaja',
- 'Karina',
- 'Karla',
- 'Karlotta',
- 'Karolina',
- 'Karoline',
- 'Kassandra',
- 'Katarina',
- 'Katharina',
- 'Kathrin',
- 'Katja',
- 'Katrin',
- 'Kaya',
- 'Kayra',
- 'Kiana',
- 'Kiara',
- 'Kimberley',
- 'Kimberly',
- 'Kira',
- 'Klara',
- 'Korinna',
- 'Kristin',
- 'Kyra',
- 'Laila',
- 'Lana',
- 'Lara',
- 'Larissa',
- 'Laura',
- 'Laureen',
- 'Lavinia',
- 'Lea',
- 'Leah',
- 'Leana',
- 'Leandra',
- 'Leann',
- 'Lee',
- 'Leila',
- 'Lena',
- 'Lene',
- 'Leni',
- 'Lenia',
- 'Lenja',
- 'Lenya',
- 'Leona',
- 'Leoni',
- 'Leonie',
- 'Leonora',
- 'Leticia',
- 'Letizia',
- 'Levke',
- 'Leyla',
- 'Lia',
- 'Liah',
- 'Liana',
- 'Lili',
- 'Lilia',
- 'Lilian',
- 'Liliana',
- 'Lilith',
- 'Lilli',
- 'Lillian',
- 'Lilly',
- 'Lily',
- 'Lina',
- 'Linda',
- 'Lindsay',
- 'Line',
- 'Linn',
- 'Linnea',
- 'Lisa',
- 'Lisann',
- 'Lisanne',
- 'Liv',
- 'Livia',
- 'Liz',
- 'Lola',
- 'Loreen',
- 'Lorena',
- 'Lotta',
- 'Lotte',
- 'Louisa',
- 'Louise',
- 'Luana',
- 'Lucia',
- 'Lucie',
- 'Lucienne',
- 'Lucy',
- 'Luisa',
- 'Luise',
- 'Luna',
- 'Luzie',
- 'Lya',
- 'Lydia',
- 'Lyn',
- 'Lynn',
- 'Madeleine',
- 'Madita',
- 'Madleen',
- 'Madlen',
- 'Magdalena',
- 'Maike',
- 'Mailin',
- 'Maira',
- 'Maja',
- 'Malena',
- 'Malia',
- 'Malin',
- 'Malina',
- 'Mandy',
- 'Mara',
- 'Marah',
- 'Mareike',
- 'Maren',
- 'Maria',
- 'Mariam',
- 'Marie',
- 'Marieke',
- 'Mariella',
- 'Marika',
- 'Marina',
- 'Marisa',
- 'Marissa',
- 'Marit',
- 'Marla',
- 'Marleen',
- 'Marlen',
- 'Marlena',
- 'Marlene',
- 'Marta',
- 'Martha',
- 'Mary',
- 'Maryam',
- 'Mathilda',
- 'Mathilde',
- 'Matilda',
- 'Maxi',
- 'Maxima',
- 'Maxine',
- 'Maya',
- 'Mayra',
- 'Medina',
- 'Medine',
- 'Meike',
- 'Melanie',
- 'Melek',
- 'Melike',
- 'Melina',
- 'Melinda',
- 'Melis',
- 'Melisa',
- 'Melissa',
- 'Merle',
- 'Merve',
- 'Meryem',
- 'Mette',
- 'Mia',
- 'Michaela',
- 'Michelle',
- 'Mieke',
- 'Mila',
- 'Milana',
- 'Milena',
- 'Milla',
- 'Mina',
- 'Mira',
- 'Miray',
- 'Miriam',
- 'Mirja',
- 'Mona',
- 'Monique',
- 'Nadine',
- 'Nadja',
- 'Naemi',
- 'Nancy',
- 'Naomi',
- 'Natalia',
- 'Natalie',
- 'Nathalie',
- 'Neele',
- 'Nela',
- 'Nele',
- 'Nelli',
- 'Nelly',
- 'Nia',
- 'Nicole',
- 'Nika',
- 'Nike',
- 'Nila',
- 'Nina',
- 'Nisa',
- 'Noemi',
- 'Nora',
- 'Olivia',
- 'Patricia',
- 'Patrizia',
- 'Paula',
- 'Paulina',
- 'Pauline',
- 'Penelope',
- 'Philine',
- 'Phoebe',
- 'Pia',
- 'Rahel',
- 'Rania',
- 'Rebecca',
- 'Rebekka',
- 'Riana',
- 'Rieke',
- 'Rike',
- 'Romina',
- 'Romy',
- 'Ronja',
- 'Rosa',
- 'Rosalie',
- 'Ruby',
- 'Sabrina',
- 'Sahra',
- 'Sally',
- 'Salome',
- 'Samantha',
- 'Samia',
- 'Samira',
- 'Sandra',
- 'Sandy',
- 'Sanja',
- 'Saphira',
- 'Sara',
- 'Sarah',
- 'Saskia',
- 'Selin',
- 'Selina',
- 'Selma',
- 'Sena',
- 'Sidney',
- 'Sienna',
- 'Silja',
- 'Sina',
- 'Sinja',
- 'Smilla',
- 'Sofia',
- 'Sofie',
- 'Sonja',
- 'Sophia',
- 'Sophie',
- 'Soraya',
- 'Stefanie',
- 'Stella',
- 'Stephanie',
- 'Stina',
- 'Sude',
- 'Summer',
- 'Susanne',
- 'Svea',
- 'Svenja',
- 'Sydney',
- 'Tabea',
- 'Talea',
- 'Talia',
- 'Tamara',
- 'Tamia',
- 'Tamina',
- 'Tanja',
- 'Tara',
- 'Tarja',
- 'Teresa',
- 'Tessa',
- 'Thalea',
- 'Thalia',
- 'Thea',
- 'Theresa',
- 'Tia',
- 'Tina',
- 'Tomke',
- 'Tuana',
- 'Valentina',
- 'Valeria',
- 'Valerie',
- 'Vanessa',
- 'Vera',
- 'Veronika',
- 'Victoria',
- 'Viktoria',
- 'Viola',
- 'Vivian',
- 'Vivien',
- 'Vivienne',
- 'Wibke',
- 'Wiebke',
- 'Xenia',
- 'Yara',
- 'Yaren',
- 'Yasmin',
- 'Yvonne',
- 'Zara',
- 'Zehra',
- 'Zeynep',
- 'Zoe',
- 'Zoey',
-];
-
-
-const generateUsername = (): string => {
- const idx = Math.floor(Math.random() * NAMES.length);
- return NAMES[idx] + Math.floor(Math.random() * 100);
-}
-
-
-
-const generatePassword = (length: number = 12): string => {
- const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- let retVal = "";
- for (let i = 0, n = charset.length; i < length; i++) {
- retVal += charset.charAt(Math.floor(Math.random() * n));
- }
- return retVal;
-}
-
-const TextGenerator = {generatePassword, generateUsername};
-export default TextGenerator;
\ No newline at end of file
diff --git a/frontend/src/utils/uuid.ts b/frontend/src/utils/uuid.ts
deleted file mode 100644
index 637619e27..000000000
--- a/frontend/src/utils/uuid.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-// yes, i know this is a fake this is not a real uuid
-export const genUuid = () => {
- let d = new Date().getTime();//Timestamp
- // Time in microseconds since page-load or 0 if unsupported
- let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now()*1000)) || 0;
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
- let r = Math.random() * 16;//random number between 0 and 16
- if(d > 0){//Use timestamp until depleted
- r = (d + r)%16 | 0;
- d = Math.floor(d/16);
- } else {//Use microseconds since page-load if supported
- r = (d2 + r)%16 | 0;
- d2 = Math.floor(d2/16);
- }
- return (c === 'x' ? r : ((r & 0x3) | 0x8)).toString(16);
- });
-}
\ No newline at end of file
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
deleted file mode 100644
index 5d8956e36..000000000
--- a/frontend/tsconfig.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "compilerOptions": {
- "target": "es6",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
- "noFallthroughCasesInSwitch": true,
- "allowJs": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "strictNullChecks": false,
- "forceConsistentCasingInFileNames": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx"
- },
- "include": [
- "src"
- ]
-}
diff --git a/webui/public/assets/favicon.ico b/webui/public/assets/favicon.ico
deleted file mode 100644
index 8d861c4c8..000000000
Binary files a/webui/public/assets/favicon.ico and /dev/null differ
diff --git a/webui/public/assets/fonts.css b/webui/public/assets/fonts.css
deleted file mode 100644
index 01d5d09a4..000000000
--- a/webui/public/assets/fonts.css
+++ /dev/null
@@ -1,59 +0,0 @@
-/* rubik-arabic-400-normal */
-@font-face {
- font-family: 'Rubik';
- font-style: normal;
- font-display: swap;
- font-weight: 400;
- src: url('fonts/rubik-arabic-400-normal.woff2') format('woff2'), url('fonts/rubik-arabic-400-normal.woff') format('woff');
- unicode-range: U+0600-06FF,U+0750-077F,U+0870-088E,U+0890-0891,U+0897-08E1,U+08E3-08FF,U+200C-200E,U+2010-2011,U+204F,U+2E41,U+FB50-FDFF,U+FE70-FE74,U+FE76-FEFC,U+102E0-102FB,U+10E60-10E7E,U+10EC2-10EC4,U+10EFC-10EFF,U+1EE00-1EE03,U+1EE05-1EE1F,U+1EE21-1EE22,U+1EE24,U+1EE27,U+1EE29-1EE32,U+1EE34-1EE37,U+1EE39,U+1EE3B,U+1EE42,U+1EE47,U+1EE49,U+1EE4B,U+1EE4D-1EE4F,U+1EE51-1EE52,U+1EE54,U+1EE57,U+1EE59,U+1EE5B,U+1EE5D,U+1EE5F,U+1EE61-1EE62,U+1EE64,U+1EE67-1EE6A,U+1EE6C-1EE72,U+1EE74-1EE77,U+1EE79-1EE7C,U+1EE7E,U+1EE80-1EE89,U+1EE8B-1EE9B,U+1EEA1-1EEA3,U+1EEA5-1EEA9,U+1EEAB-1EEBB,U+1EEF0-1EEF1;
-}
-
-/* rubik-cyrillic-ext-400-normal */
-@font-face {
- font-family: 'Rubik';
- font-style: normal;
- font-display: swap;
- font-weight: 400;
- src: url('fonts/rubik-cyrillic-ext-400-normal.woff2') format('woff2'), url('fonts/rubik-cyrillic-ext-400-normal.woff') format('woff');
- unicode-range: U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F;
-}
-
-/* rubik-cyrillic-400-normal */
-@font-face {
- font-family: 'Rubik';
- font-style: normal;
- font-display: swap;
- font-weight: 400;
- src: url('fonts/rubik-cyrillic-400-normal.woff2') format('woff2'), url('fonts/rubik-cyrillic-400-normal.woff') format('woff');
- unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
-}
-
-/* rubik-hebrew-400-normal */
-@font-face {
- font-family: 'Rubik';
- font-style: normal;
- font-display: swap;
- font-weight: 400;
- src: url('fonts/rubik-hebrew-400-normal.woff2') format('woff2'), url('fonts/rubik-hebrew-400-normal.woff') format('woff');
- unicode-range: U+0307-0308,U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F;
-}
-
-/* rubik-latin-ext-400-normal */
-@font-face {
- font-family: 'Rubik';
- font-style: normal;
- font-display: swap;
- font-weight: 400;
- src: url('fonts/rubik-latin-ext-400-normal.woff2') format('woff2'), url('fonts/rubik-latin-ext-400-normal.woff') format('woff');
- unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
-}
-
-/* rubik-latin-400-normal */
-@font-face {
- font-family: 'Rubik';
- font-style: normal;
- font-display: swap;
- font-weight: 400;
- src: url('fonts/rubik-latin-400-normal.woff2') format('woff2'), url('fonts/rubik-latin-400-normal.woff') format('woff');
- unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
-}
diff --git a/webui/public/assets/fonts/rubik-arabic-400-italic.woff b/webui/public/assets/fonts/rubik-arabic-400-italic.woff
deleted file mode 100644
index fca1c231b..000000000
Binary files a/webui/public/assets/fonts/rubik-arabic-400-italic.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-arabic-400-italic.woff2 b/webui/public/assets/fonts/rubik-arabic-400-italic.woff2
deleted file mode 100644
index b42c25f4f..000000000
Binary files a/webui/public/assets/fonts/rubik-arabic-400-italic.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-arabic-400-normal.woff b/webui/public/assets/fonts/rubik-arabic-400-normal.woff
deleted file mode 100644
index 3b48a3e5e..000000000
Binary files a/webui/public/assets/fonts/rubik-arabic-400-normal.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-arabic-400-normal.woff2 b/webui/public/assets/fonts/rubik-arabic-400-normal.woff2
deleted file mode 100644
index 673f79567..000000000
Binary files a/webui/public/assets/fonts/rubik-arabic-400-normal.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-cyrillic-400-italic.woff b/webui/public/assets/fonts/rubik-cyrillic-400-italic.woff
deleted file mode 100644
index b11f2dc6d..000000000
Binary files a/webui/public/assets/fonts/rubik-cyrillic-400-italic.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-cyrillic-400-italic.woff2 b/webui/public/assets/fonts/rubik-cyrillic-400-italic.woff2
deleted file mode 100644
index d6d59de0a..000000000
Binary files a/webui/public/assets/fonts/rubik-cyrillic-400-italic.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-cyrillic-400-normal.woff b/webui/public/assets/fonts/rubik-cyrillic-400-normal.woff
deleted file mode 100644
index 0506d064a..000000000
Binary files a/webui/public/assets/fonts/rubik-cyrillic-400-normal.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-cyrillic-400-normal.woff2 b/webui/public/assets/fonts/rubik-cyrillic-400-normal.woff2
deleted file mode 100644
index 8a166d4df..000000000
Binary files a/webui/public/assets/fonts/rubik-cyrillic-400-normal.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-cyrillic-ext-400-italic.woff b/webui/public/assets/fonts/rubik-cyrillic-ext-400-italic.woff
deleted file mode 100644
index 42098f8a8..000000000
Binary files a/webui/public/assets/fonts/rubik-cyrillic-ext-400-italic.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-cyrillic-ext-400-italic.woff2 b/webui/public/assets/fonts/rubik-cyrillic-ext-400-italic.woff2
deleted file mode 100644
index 109a7ca52..000000000
Binary files a/webui/public/assets/fonts/rubik-cyrillic-ext-400-italic.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-cyrillic-ext-400-normal.woff b/webui/public/assets/fonts/rubik-cyrillic-ext-400-normal.woff
deleted file mode 100644
index d47ad615c..000000000
Binary files a/webui/public/assets/fonts/rubik-cyrillic-ext-400-normal.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-cyrillic-ext-400-normal.woff2 b/webui/public/assets/fonts/rubik-cyrillic-ext-400-normal.woff2
deleted file mode 100644
index cca969e32..000000000
Binary files a/webui/public/assets/fonts/rubik-cyrillic-ext-400-normal.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-hebrew-400-italic.woff b/webui/public/assets/fonts/rubik-hebrew-400-italic.woff
deleted file mode 100644
index cb34551ba..000000000
Binary files a/webui/public/assets/fonts/rubik-hebrew-400-italic.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-hebrew-400-italic.woff2 b/webui/public/assets/fonts/rubik-hebrew-400-italic.woff2
deleted file mode 100644
index 798e805e4..000000000
Binary files a/webui/public/assets/fonts/rubik-hebrew-400-italic.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-hebrew-400-normal.woff b/webui/public/assets/fonts/rubik-hebrew-400-normal.woff
deleted file mode 100644
index df2536016..000000000
Binary files a/webui/public/assets/fonts/rubik-hebrew-400-normal.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-hebrew-400-normal.woff2 b/webui/public/assets/fonts/rubik-hebrew-400-normal.woff2
deleted file mode 100644
index 658d71941..000000000
Binary files a/webui/public/assets/fonts/rubik-hebrew-400-normal.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-latin-400-italic.woff b/webui/public/assets/fonts/rubik-latin-400-italic.woff
deleted file mode 100644
index 458d6cd9f..000000000
Binary files a/webui/public/assets/fonts/rubik-latin-400-italic.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-latin-400-italic.woff2 b/webui/public/assets/fonts/rubik-latin-400-italic.woff2
deleted file mode 100644
index af50274a4..000000000
Binary files a/webui/public/assets/fonts/rubik-latin-400-italic.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-latin-400-normal.woff b/webui/public/assets/fonts/rubik-latin-400-normal.woff
deleted file mode 100644
index 25a12df47..000000000
Binary files a/webui/public/assets/fonts/rubik-latin-400-normal.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-latin-400-normal.woff2 b/webui/public/assets/fonts/rubik-latin-400-normal.woff2
deleted file mode 100644
index ac6ff6829..000000000
Binary files a/webui/public/assets/fonts/rubik-latin-400-normal.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-latin-ext-400-italic.woff b/webui/public/assets/fonts/rubik-latin-ext-400-italic.woff
deleted file mode 100644
index c7530972a..000000000
Binary files a/webui/public/assets/fonts/rubik-latin-ext-400-italic.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-latin-ext-400-italic.woff2 b/webui/public/assets/fonts/rubik-latin-ext-400-italic.woff2
deleted file mode 100644
index 0d4e484ff..000000000
Binary files a/webui/public/assets/fonts/rubik-latin-ext-400-italic.woff2 and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-latin-ext-400-normal.woff b/webui/public/assets/fonts/rubik-latin-ext-400-normal.woff
deleted file mode 100644
index a97a610d3..000000000
Binary files a/webui/public/assets/fonts/rubik-latin-ext-400-normal.woff and /dev/null differ
diff --git a/webui/public/assets/fonts/rubik-latin-ext-400-normal.woff2 b/webui/public/assets/fonts/rubik-latin-ext-400-normal.woff2
deleted file mode 100644
index f8a84d783..000000000
Binary files a/webui/public/assets/fonts/rubik-latin-ext-400-normal.woff2 and /dev/null differ
diff --git a/webui/public/assets/missing-logo.svg b/webui/public/assets/missing-logo.svg
deleted file mode 100644
index 1006b8346..000000000
--- a/webui/public/assets/missing-logo.svg
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/webui/public/assets/placeholder.png b/webui/public/assets/placeholder.png
deleted file mode 100644
index d157b8f4e..000000000
Binary files a/webui/public/assets/placeholder.png and /dev/null differ
diff --git a/webui/public/assets/schedule.svg b/webui/public/assets/schedule.svg
deleted file mode 100644
index 87bff106d..000000000
--- a/webui/public/assets/schedule.svg
+++ /dev/null
@@ -1,204 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0 0 8,20 * * * *
- sec min hour
-
- year day of week month day of month
-
-
-
-
-
-
-
-
-
diff --git a/webui/public/assets/tuliprox-logo.svg b/webui/public/assets/tuliprox-logo.svg
deleted file mode 100644
index 7f5c089eb..000000000
--- a/webui/public/assets/tuliprox-logo.svg
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/webui/public/config.json b/webui/public/config.json
deleted file mode 100644
index 138d5b0b7..000000000
--- a/webui/public/config.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "tabTitle": "tuliprox",
- "appLogo": "/assets/tuliprox-logo.svg",
- "api": {
- "apiUrl": "/api/v1/",
- "authUrl": "/auth/"
- },
- "discord": "https://discord.gg/gkzCmWw9Tf",
- "documentation": "https://github.com/euzu/tuliprox/blob/master/README.md",
- "wsUrl" : "/ws",
- "protocolVersion": 1
-}
\ No newline at end of file
diff --git a/webui/scss/app/components/playlist/_playlist_mappings.scss b/webui/scss/app/components/playlist/_playlist_mappings.scss
deleted file mode 100644
index 60fc2ca16..000000000
--- a/webui/scss/app/components/playlist/_playlist_mappings.scss
+++ /dev/null
@@ -1,42 +0,0 @@
-.tp__playlist-mappings {
-
- //li {
- // display: flex;
- // flex-flow: row nowrap;
- // gap: var(--gap-small);
- // align-items: center;
- // background: transparent;
- // white-space: nowrap;
- // padding: var(--padding-micro) var(--padding-mini);
- // border-radius: var(--border-radius);
- //
- // &:hover {
- // cursor: pointer;
- // background-color: var(--menu-item-hover-background-color);
- // color: var(--menu-item-hover-color)
- // }
- //}
-
- &__mapping {
- display: flex;
- flex-flow: column;
- gap: var(--gap-default);
-
- &-section {
- display: flex;
- flex-flow: row;
- align-items: center;
- gap: var(--gap-small);
-
- }
- }
-
- label {
- font-weight: bold;
- color: var(--modest-text-color);
-
- &:after {
- content: ':'
- }
- }
-}
\ No newline at end of file