Compare commits

..

9 Commits

Author SHA1 Message Date
d9cb018132 ready for v3.2.3 2024-12-12 10:20:57 +01:00
Marco Piovanello
ac077ea1e1 upgraded react to v19 (#232) 2024-12-12 09:49:20 +01:00
f29d719df0 fixed never awaited cookies template promise 2024-12-12 09:33:09 +01:00
Marco Piovanello
6adfa71fde custom path based frontend (#231) 2024-12-05 10:00:15 +01:00
0946d374e3 more examples 2024-11-23 09:53:44 +01:00
Marco Piovanello
f68c29f838 Update README.md 2024-11-19 11:43:42 +01:00
Marco Piovanello
c46e39e736 code refactoring (#227) 2024-11-19 11:36:36 +01:00
Marco Piovanello
2885d6b5d8 Update README.md 2024-11-17 08:58:08 +01:00
Marco Piovanello
ab7932ae92 Editable templates (#225)
* editable templates

* removed unused import
2024-11-15 14:24:44 +01:00
18 changed files with 820 additions and 1132 deletions

View File

@@ -1,3 +1,7 @@
> [!NOTE]
> A poll is up to decide the future of yt-dlp-web-ui frontend! If you're interested you can take part.
> https://github.com/marcopeocchi/yt-dlp-web-ui/discussions/223
# yt-dlp Web UI # yt-dlp Web UI
A not so terrible web ui for yt-dlp. A not so terrible web ui for yt-dlp.
@@ -151,6 +155,8 @@ Usage yt-dlp-webui:
session file path (default ".") session file path (default ".")
-user string -user string
Username required for auth Username required for auth
-web string
frontend web resources path
``` ```
### Config file ### Config file
@@ -180,7 +186,7 @@ password: my_random_secret
queue_size: 4 # min. 2 queue_size: 4 # min. 2
# [optional] Full path to the yt-dlp (default: "yt-dlp") # [optional] Full path to the yt-dlp (default: "yt-dlp")
downloaderPath: /usr/local/bin/yt-dlp #downloaderPath: /usr/local/bin/yt-dlp
# [optional] Enable file based logging with rotation (default: false) # [optional] Enable file based logging with rotation (default: false)
#enable_file_logging: false #enable_file_logging: false
@@ -193,6 +199,9 @@ downloaderPath: /usr/local/bin/yt-dlp
# [optional] Path where the sqlite database will be created/opened (default: "./local.db") # [optional] Path where the sqlite database will be created/opened (default: "./local.db")
#local_database_path #local_database_path
# [optional] Path where a custom frontend will be loaded (instead of the embedded one)
#frontend_path: ./web/solid-frontend
``` ```
### Systemd integration ### Systemd integration
@@ -258,6 +267,22 @@ It is **planned** to also expose a **gRPC** server.
For more information open an issue on GitHub and I will provide more info ASAP. For more information open an issue on GitHub and I will provide more info ASAP.
## Custom frontend
To load a custom frontend you need to specify its path either in the config file ([see config file](#config-file)) or via flags.
The frontend needs to follow this structure:
```
path/to/my/frontend
├── assets
│ ├── js-chunk-1.js (example)
│ ├── js-chunk-2.js (example)
│ ├── style.css (example)
└── index.html
```
`assets` is where the resources will be loaded.
`index.html` is the entrypoint.
## Nix ## Nix
This repo adds support for Nix(OS) in various ways through a `flake-parts` flake. This repo adds support for Nix(OS) in various ways through a `flake-parts` flake.
For more info, please refer to the [official documentation](https://nixos.org/learn/). For more info, please refer to the [official documentation](https://nixos.org/learn/).

View File

@@ -0,0 +1,27 @@
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://app:3033;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
client_max_body_size 20000m;
proxy_connect_timeout 5000;
proxy_send_timeout 5000;
proxy_read_timeout 5000;
send_timeout 5000;
}
}

View File

@@ -0,0 +1,15 @@
services:
app:
image: marcobaobao/yt-dlp-webui
volumes:
- ./downloads:/downloads
restart: unless-stopped
nginx:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./app.conf:/etc/nginx/conf.d/app.conf
depends_on:
- app
ports:
- 80:80

View File

@@ -1,6 +1,6 @@
{ {
"name": "yt-dlp-webui", "name": "yt-dlp-webui",
"version": "3.2.2", "version": "3.2.3",
"description": "Frontend compontent of yt-dlp-webui", "description": "Frontend compontent of yt-dlp-webui",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -10,30 +10,29 @@
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.4", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.11.5", "@emotion/styled": "^11.14.0",
"@fontsource/roboto": "^5.0.13", "@fontsource/roboto": "^5.0.13",
"@fontsource/roboto-mono": "^5.0.18", "@fontsource/roboto-mono": "^5.0.18",
"@mui/icons-material": "^5.15.16", "@mui/icons-material": "^6.2.0",
"@mui/material": "^5.15.16", "@mui/material": "^6.2.0",
"fp-ts": "^2.16.5", "fp-ts": "^2.16.5",
"react": "^18.3.1", "react": "^19.0.0",
"react-dom": "^18.3.1", "react-dom": "^19.0.0",
"react-router-dom": "^6.23.1", "react-router-dom": "^6.23.1",
"react-virtuoso": "^4.7.11", "react-virtuoso": "^4.7.11",
"jotai": "^2.10.2", "jotai": "^2.10.3",
"rxjs": "^7.8.1" "rxjs": "^7.8.1"
}, },
"devDependencies": { "devDependencies": {
"@modyfi/vite-plugin-yaml": "^1.1.0", "@modyfi/vite-plugin-yaml": "^1.1.0",
"@types/node": "^20.14.2", "@types/node": "^20.14.2",
"@types/react": "^18.3.3", "@types/react": "^19.0.1",
"@types/react-dom": "^18.2.18", "@types/react-dom": "^19.0.2",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react-swc": "^3.7.0", "@vitejs/plugin-react-swc": "^3.7.2",
"million": "^3.1.11", "typescript": "^5.7.2",
"typescript": "^5.4.5", "vite": "^6.0.3"
"vite": "^5.2.11"
} }
} }

1668
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ import ListItemIcon from '@mui/material/ListItemIcon'
import ListItemText from '@mui/material/ListItemText' import ListItemText from '@mui/material/ListItemText'
import Toolbar from '@mui/material/Toolbar' import Toolbar from '@mui/material/Toolbar'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { grey } from '@mui/material/colors' import { grey, red } from '@mui/material/colors'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { Link, Outlet } from 'react-router-dom' import { Link, Outlet } from 'react-router-dom'
import { settingsState } from './atoms/settings' import { settingsState } from './atoms/settings'
@@ -29,6 +29,7 @@ import ThemeToggler from './components/ThemeToggler'
import { useI18n } from './hooks/useI18n' import { useI18n } from './hooks/useI18n'
import Toaster from './providers/ToasterProvider' import Toaster from './providers/ToasterProvider'
import { useAtomValue } from 'jotai' import { useAtomValue } from 'jotai'
import { getAccentValue } from './utils'
export default function Layout() { export default function Layout() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@@ -40,11 +41,14 @@ export default function Layout() {
createTheme({ createTheme({
palette: { palette: {
mode: settings.theme, mode: settings.theme,
primary: {
main: getAccentValue(settings.accent)
},
background: { background: {
default: settings.theme === 'light' ? grey[50] : '#121212' default: settings.theme === 'light' ? grey[50] : '#121212'
}, },
}, },
}), [settings.theme] }), [settings.theme, settings.accent]
) )
const toggleDrawer = () => setOpen(state => !state) const toggleDrawer = () => setOpen(state => !state)

View File

@@ -65,6 +65,7 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
german: german:
urlInput: Video URL urlInput: Video URL
statusTitle: Status statusTitle: Status
@@ -126,6 +127,7 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
french: french:
urlInput: URL vidéo de YouTube ou d'un autre service pris en charge urlInput: URL vidéo de YouTube ou d'un autre service pris en charge
statusTitle: Statut statusTitle: Statut
@@ -189,6 +191,7 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
italian: italian:
urlInput: URL Video (uno per linea) urlInput: URL Video (uno per linea)
statusTitle: Stato statusTitle: Stato
@@ -249,6 +252,7 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
chinese: chinese:
urlInput: 视频 URL urlInput: 视频 URL
statusTitle: 状态 statusTitle: 状态
@@ -310,6 +314,7 @@ languages:
如果直播已经开始,那么依然可以下载,但是不会记录下载进度。 如果直播已经开始,那么依然可以下载,但是不会记录下载进度。
直播开始后,将会转移到下载页面 直播开始后,将会转移到下载页面
livestreamExperimentalWarning: 实验性功能可能存在未知Bug请谨慎使用 livestreamExperimentalWarning: 实验性功能可能存在未知Bug请谨慎使用
accentSelect: 'Accent'
spanish: spanish:
urlInput: URL de YouTube u otro servicio compatible urlInput: URL de YouTube u otro servicio compatible
statusTitle: Estado statusTitle: Estado
@@ -369,6 +374,7 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
russian: russian:
urlInput: URL-адрес YouTube или любого другого поддерживаемого сервиса urlInput: URL-адрес YouTube или любого другого поддерживаемого сервиса
statusTitle: Статус statusTitle: Статус
@@ -428,6 +434,7 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
korean: korean:
urlInput: YouTube나 다른 지원되는 사이트의 URL urlInput: YouTube나 다른 지원되는 사이트의 URL
statusTitle: 상태 statusTitle: 상태
@@ -487,6 +494,7 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
japanese: japanese:
urlInput: YouTubeまたはサポート済み動画のURL urlInput: YouTubeまたはサポート済み動画のURL
statusTitle: 状態 statusTitle: 状態
@@ -547,6 +555,7 @@ languages:
すでに開始されているライブストリームが提供された場合、ダウンロードは継続されますが進行状況は追跡されません。 すでに開始されているライブストリームが提供された場合、ダウンロードは継続されますが進行状況は追跡されません。
ライブストリームが開始されると、ダウンロードページに移動されます。 ライブストリームが開始されると、ダウンロードページに移動されます。
livestreamExperimentalWarning: この機能は実験的なものです。何かが壊れるかもしれません! livestreamExperimentalWarning: この機能は実験的なものです。何かが壊れるかもしれません!
accentSelect: 'Accent'
catalan: catalan:
urlInput: URL de YouTube o d'un altre servei compatible urlInput: URL de YouTube o d'un altre servei compatible
statusTitle: Estat statusTitle: Estat
@@ -606,6 +615,7 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
ukrainian: ukrainian:
urlInput: URL-адреса YouTube або будь-якого іншого підтримуваного сервісу urlInput: URL-адреса YouTube або будь-якого іншого підтримуваного сервісу
statusTitle: Статус statusTitle: Статус
@@ -665,6 +675,7 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
polish: polish:
urlInput: Adres URL YouTube lub innej obsługiwanej usługi urlInput: Adres URL YouTube lub innej obsługiwanej usługi
statusTitle: Status statusTitle: Status
@@ -724,6 +735,7 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'
swedish: swedish:
urlInput: Videolänk (en per rad) urlInput: Videolänk (en per rad)
statusTitle: Status statusTitle: Status
@@ -789,3 +801,4 @@ languages:
If an already started livestream is provided it will be still downloaded but its progress will not be tracked. If an already started livestream is provided it will be still downloaded but its progress will not be tracked.
Once started the livestream will be migrated to the downloads page. Once started the livestream will be migrated to the downloads page.
livestreamExperimentalWarning: This feature is still experimental. Something might break! livestreamExperimentalWarning: This feature is still experimental. Something might break!
accentSelect: 'Accent'

View File

@@ -22,8 +22,8 @@ export const filenameTemplateState = atomWithStorage(
localStorage.getItem('lastFilenameTemplate') ?? '' localStorage.getItem('lastFilenameTemplate') ?? ''
) )
export const downloadTemplateState = atom<string>((get) => export const downloadTemplateState = atom<Promise<string>>(async (get) =>
`${get(customArgsState)} ${get(cookiesTemplateState)}` `${get(customArgsState)} ${await get(cookiesTemplateState)}`
.replace(/ +/g, ' ') .replace(/ +/g, ' ')
.trim() .trim()
) )

View File

@@ -26,11 +26,15 @@ export type Language = (typeof languages)[number]
export type Theme = 'light' | 'dark' | 'system' export type Theme = 'light' | 'dark' | 'system'
export type ThemeNarrowed = 'light' | 'dark' export type ThemeNarrowed = 'light' | 'dark'
export const accents = ['default', 'red'] as const
export type Accent = (typeof accents)[number]
export interface SettingsState { export interface SettingsState {
serverAddr: string serverAddr: string
serverPort: number serverPort: number
language: Language language: Language
theme: ThemeNarrowed theme: ThemeNarrowed
accent: Accent
cliArgs: string cliArgs: string
formatSelection: boolean formatSelection: boolean
fileRenaming: boolean fileRenaming: boolean
@@ -146,7 +150,11 @@ const themeSelector = atom<ThemeNarrowed>((get) => {
return 'dark' return 'dark'
} }
return 'light' return 'light'
} })
export const accentState = atomWithStorage<Accent>(
'accent-color',
localStorage.getItem('accent-color') as Accent ?? 'default',
) )
export const settingsState = atom<SettingsState>((get) => ({ export const settingsState = atom<SettingsState>((get) => ({
@@ -154,6 +162,7 @@ export const settingsState = atom<SettingsState>((get) => ({
serverPort: get(serverPortState), serverPort: get(serverPortState),
language: get(languageState), language: get(languageState),
theme: get(themeSelector), theme: get(themeSelector),
accent: get(accentState),
cliArgs: get(latestCliArgumentsState), cliArgs: get(latestCliArgumentsState),
formatSelection: get(formatSelectionState), formatSelection: get(formatSelectionState),
fileRenaming: get(fileRenamingState), fileRenaming: get(fileRenamingState),

View File

@@ -2,7 +2,6 @@ import { FileUpload } from '@mui/icons-material'
import CloseIcon from '@mui/icons-material/Close' import CloseIcon from '@mui/icons-material/Close'
import { import {
Autocomplete, Autocomplete,
Backdrop,
Box, Box,
Button, Button,
Checkbox, Checkbox,
@@ -21,6 +20,7 @@ import Slide from '@mui/material/Slide'
import Toolbar from '@mui/material/Toolbar' import Toolbar from '@mui/material/Toolbar'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { TransitionProps } from '@mui/material/transitions' import { TransitionProps } from '@mui/material/transitions'
import { useAtom, useAtomValue } from 'jotai'
import { import {
FC, FC,
Suspense, Suspense,
@@ -30,18 +30,22 @@ import {
useState, useState,
useTransition useTransition
} from 'react' } from 'react'
import { customArgsState, downloadTemplateState, filenameTemplateState, savedTemplatesState } from '../atoms/downloadTemplate' import {
customArgsState,
downloadTemplateState,
filenameTemplateState,
savedTemplatesState
} from '../atoms/downloadTemplate'
import { settingsState } from '../atoms/settings' import { settingsState } from '../atoms/settings'
import { availableDownloadPathsState, connectedState } from '../atoms/status' import { availableDownloadPathsState, connectedState } from '../atoms/status'
import FormatsGrid from '../components/FormatsGrid' import FormatsGrid from '../components/FormatsGrid'
import { useToast } from '../hooks/toast'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC' import { useRPC } from '../hooks/useRPC'
import type { DLMetadata } from '../types' import type { DLMetadata } from '../types'
import { toFormatArgs } from '../utils' import { toFormatArgs } from '../utils'
import ExtraDownloadOptions from './ExtraDownloadOptions' import ExtraDownloadOptions from './ExtraDownloadOptions'
import { useToast } from '../hooks/toast'
import LoadingBackdrop from './LoadingBackdrop' import LoadingBackdrop from './LoadingBackdrop'
import { useAtom, useAtomValue } from 'jotai'
const Transition = forwardRef(function Transition( const Transition = forwardRef(function Transition(
props: TransitionProps & { props: TransitionProps & {

View File

@@ -0,0 +1,33 @@
import { Button, CircularProgress } from '@mui/material'
import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC'
import { useState } from 'react'
import { useToast } from '../hooks/toast'
const UpdateBinaryButton: React.FC = () => {
const { i18n } = useI18n()
const { client } = useRPC()
const { pushMessage } = useToast()
const [isLoading, setIsLoading] = useState(false)
const updateBinary = () => {
setIsLoading(true)
client
.updateExecutable()
.then(() => pushMessage(i18n.t('toastUpdated'), 'success'))
.then(() => setIsLoading(false))
}
return (
<Button
variant="contained"
endIcon={isLoading ? <CircularProgress size={16} color='secondary' /> : <></>}
onClick={updateBinary}
>
{i18n.t('updateBinButton')}
</Button>
)
}
export default UpdateBinaryButton

View File

@@ -191,4 +191,11 @@ export class RPCClient {
params: [] params: []
}) })
} }
public updateExecutable() {
return this.sendHTTP({
method: 'Service.UpdateExecutable',
params: []
})
}
} }

View File

@@ -1,4 +1,6 @@
import { blue, red } from '@mui/material/colors'
import { pipe } from 'fp-ts/lib/function' import { pipe } from 'fp-ts/lib/function'
import { Accent } from './atoms/settings'
import type { RPCResponse } from "./types" import type { RPCResponse } from "./types"
import { ProcessStatus } from './types' import { ProcessStatus } from './types'
@@ -80,3 +82,14 @@ export const base64URLEncode = (s: string) => pipe(
btoa, btoa,
encodeURIComponent encodeURIComponent
) )
export const getAccentValue = (accent: Accent) => {
switch (accent) {
case 'default':
return blue[700]
case 'red':
return red[600]
default:
return blue[700]
}
}

View File

@@ -1,5 +1,4 @@
import { import {
Button,
Checkbox, Checkbox,
Container, Container,
FormControl, FormControl,
@@ -18,6 +17,7 @@ import {
Typography, Typography,
capitalize capitalize
} from '@mui/material' } from '@mui/material'
import { useAtom } from 'jotai'
import { Suspense, useEffect, useMemo, useState } from 'react' import { Suspense, useEffect, useMemo, useState } from 'react'
import { import {
Subject, Subject,
@@ -28,8 +28,11 @@ import {
} from 'rxjs' } from 'rxjs'
import { rpcPollingTimeState } from '../atoms/rpc' import { rpcPollingTimeState } from '../atoms/rpc'
import { import {
Accent,
Language, Language,
Theme, Theme,
accentState,
accents,
appTitleState, appTitleState,
enableCustomArgsState, enableCustomArgsState,
fileRenamingState, fileRenamingState,
@@ -44,11 +47,10 @@ import {
themeState themeState
} from '../atoms/settings' } from '../atoms/settings'
import CookiesTextField from '../components/CookiesTextField' import CookiesTextField from '../components/CookiesTextField'
import UpdateBinaryButton from '../components/UpdateBinaryButton'
import { useToast } from '../hooks/toast' import { useToast } from '../hooks/toast'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC'
import { validateDomain, validateIP } from '../utils' import { validateDomain, validateIP } from '../utils'
import { useAtom } from 'jotai'
// NEED ABSOLUTELY TO BE SPLIT IN MULTIPLE COMPONENTS // NEED ABSOLUTELY TO BE SPLIT IN MULTIPLE COMPONENTS
export default function Settings() { export default function Settings() {
@@ -66,13 +68,13 @@ export default function Settings() {
const [pollingTime, setPollingTime] = useAtom(rpcPollingTimeState) const [pollingTime, setPollingTime] = useAtom(rpcPollingTimeState)
const [language, setLanguage] = useAtom(languageState) const [language, setLanguage] = useAtom(languageState)
const [appTitle, setApptitle] = useAtom(appTitleState) const [appTitle, setApptitle] = useAtom(appTitleState)
const [accent, setAccent] = useAtom(accentState)
const [theme, setTheme] = useAtom(themeState) const [theme, setTheme] = useAtom(themeState)
const [invalidIP, setInvalidIP] = useState(false) const [invalidIP, setInvalidIP] = useState(false)
const { i18n } = useI18n() const { i18n } = useI18n()
const { client } = useRPC()
const { pushMessage } = useToast() const { pushMessage } = useToast()
@@ -140,13 +142,6 @@ export default function Settings() {
setTheme(event.target.value as Theme) setTheme(event.target.value as Theme)
} }
/**
* Updates yt-dlp binary via RPC
*/
const updateBinary = () => {
client.updateExecutable().then(() => pushMessage(i18n.t('toastUpdated'), 'success'))
}
return ( return (
<Container maxWidth="xl" sx={{ mt: 4, mb: 8 }}> <Container maxWidth="xl" sx={{ mt: 4, mb: 8 }}>
<Paper <Paper
@@ -257,7 +252,7 @@ export default function Settings() {
Appearance Appearance
</Typography> </Typography>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12} md={6}> <Grid item xs={12}>
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel>{i18n.t('languageSelect')}</InputLabel> <InputLabel>{i18n.t('languageSelect')}</InputLabel>
<Select <Select
@@ -287,6 +282,22 @@ export default function Settings() {
</Select> </Select>
</FormControl> </FormControl>
</Grid> </Grid>
<Grid item xs={12} md={6}>
<FormControl fullWidth>
<InputLabel>{i18n.t('accentSelect')}</InputLabel>
<Select
defaultValue={accent}
label={i18n.t('accentSelect')}
onChange={(e) => setAccent(e.target.value as Accent)}
>
{accents.map((accent) => (
<MenuItem key={accent} value={accent}>
{capitalize(accent)}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid> </Grid>
<Typography variant="h6" color="primary" sx={{ mt: 2, mb: 0.5 }}> <Typography variant="h6" color="primary" sx={{ mt: 2, mb: 0.5 }}>
General download settings General download settings
@@ -352,14 +363,8 @@ export default function Settings() {
</Suspense> </Suspense>
</Grid> </Grid>
<Grid> <Grid>
<Stack direction="row"> <Stack direction="row" sx={{ pt: 2 }}>
<Button <UpdateBinaryButton />
sx={{ mr: 1, mt: 3 }}
variant="contained"
onClick={() => updateBinary()}
>
{i18n.t('updateBinButton')}
</Button>
</Stack> </Stack>
</Grid> </Grid>
</Paper> </Paper>

View File

@@ -1,12 +1,10 @@
import react from '@vitejs/plugin-react-swc' import react from '@vitejs/plugin-react-swc'
import million from 'million/compiler'
import ViteYaml from '@modyfi/vite-plugin-yaml' import ViteYaml from '@modyfi/vite-plugin-yaml'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
export default defineConfig(() => { export default defineConfig(() => {
return { return {
plugins: [ plugins: [
million.vite({ auto: true }),
react(), react(),
ViteYaml(), ViteYaml(),
], ],

View File

@@ -23,6 +23,7 @@ var (
downloaderPath string downloaderPath string
sessionFilePath string sessionFilePath string
localDatabasePath string localDatabasePath string
frontendPath string
requireAuth bool requireAuth bool
username string username string
@@ -52,6 +53,7 @@ func init() {
flag.StringVar(&downloaderPath, "driver", "yt-dlp", "yt-dlp executable path") flag.StringVar(&downloaderPath, "driver", "yt-dlp", "yt-dlp executable path")
flag.StringVar(&sessionFilePath, "session", ".", "session file path") flag.StringVar(&sessionFilePath, "session", ".", "session file path")
flag.StringVar(&localDatabasePath, "db", "local.db", "local database path") flag.StringVar(&localDatabasePath, "db", "local.db", "local database path")
flag.StringVar(&frontendPath, "web", "", "frontend web resources path")
flag.BoolVar(&enableFileLogging, "fl", false, "enable outputting logs to a file") flag.BoolVar(&enableFileLogging, "fl", false, "enable outputting logs to a file")
flag.StringVar(&logFile, "lf", "yt-dlp-webui.log", "set log file location") flag.StringVar(&logFile, "lf", "yt-dlp-webui.log", "set log file location")
@@ -69,6 +71,10 @@ func main() {
log.Fatalln(err) log.Fatalln(err)
} }
if frontendPath != "" {
frontend = os.DirFS(frontendPath)
}
c := config.Instance() c := config.Instance()
{ {

View File

@@ -22,13 +22,13 @@ type Config struct {
QueueSize int `yaml:"queue_size"` QueueSize int `yaml:"queue_size"`
LocalDatabasePath string `yaml:"local_database_path"` LocalDatabasePath string `yaml:"local_database_path"`
SessionFilePath string `yaml:"session_file_path"` SessionFilePath string `yaml:"session_file_path"`
path string path string // private
UseOpenId bool `yaml:"use_openid"` UseOpenId bool `yaml:"use_openid"`
OpenIdProviderURL string `yaml:"openid_provider_url"` OpenIdProviderURL string `yaml:"openid_provider_url"`
OpenIdClientId string `yaml:"openid_client_id"` OpenIdClientId string `yaml:"openid_client_id"`
OpenIdClientSecret string `yaml:"openid_client_secret"` OpenIdClientSecret string `yaml:"openid_client_secret"`
OpenIdRedirectURL string `yaml:"openid_redirect_url"` OpenIdRedirectURL string `yaml:"openid_redirect_url"`
FrontendPath string `yaml:"frontend_path"`
} }
var ( var (

View File

@@ -164,7 +164,7 @@ func (s *Service) DeleteTemplate(ctx context.Context, id string) error {
func (s *Service) GetVersion(ctx context.Context) (string, string, error) { func (s *Service) GetVersion(ctx context.Context) (string, string, error) {
//TODO: load from realease properties file, or anything else outside code //TODO: load from realease properties file, or anything else outside code
const CURRENT_RPC_VERSION = "3.2.2" const CURRENT_RPC_VERSION = "3.2.3"
result := make(chan string, 1) result := make(chan string, 1)