Compare commits

..

3 Commits

Author SHA1 Message Date
6c9118f67e added dockerignore 2024-11-12 11:39:21 +01:00
34c78c7e2d ready for 3.2.2 2024-11-12 11:32:50 +01:00
Marco Piovanello
01c6edef74 jotai migration (#221) 2024-11-12 11:31:25 +01:00
39 changed files with 318 additions and 411 deletions

28
.dockerignore Normal file
View File

@@ -0,0 +1,28 @@
.pre-commit-config.yaml
.direnv/
result/
result
dist
.pnpm-debug.log
node_modules
.env
*.mp4
*.ytdl
*.part
*.db
downloads
.DS_Store
build/
yt-dlp-webui
session.dat
config.yml
cookies.txt
__debug*
ui/
.idea
frontend/.pnp.cjs
frontend/.pnp.loader.mjs
frontend/.yarn/install-state.gz
.db.lock
livestreams.dat
.git

View File

@@ -1,13 +1,13 @@
{ {
"name": "yt-dlp-webui", "name": "yt-dlp-webui",
"version": "3.2.1", "version": "3.2.2",
"description": "Frontend compontent of yt-dlp-webui", "description": "Frontend compontent of yt-dlp-webui",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build" "build": "vite build"
}, },
"author": "marcopeocchi", "author": "marcopeocchi",
"license": "MPL-2.0", "license": "GPL-3.0-only",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
@@ -21,7 +21,7 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^6.23.1", "react-router-dom": "^6.23.1",
"react-virtuoso": "^4.7.11", "react-virtuoso": "^4.7.11",
"recoil": "^0.7.7", "jotai": "^2.10.2",
"rxjs": "^7.8.1" "rxjs": "^7.8.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -29,6 +29,9 @@ importers:
fp-ts: fp-ts:
specifier: ^2.16.5 specifier: ^2.16.5
version: 2.16.5 version: 2.16.5
jotai:
specifier: ^2.10.2
version: 2.10.2(@types/react@18.3.3)(react@18.3.1)
react: react:
specifier: ^18.3.1 specifier: ^18.3.1
version: 18.3.1 version: 18.3.1
@@ -41,9 +44,6 @@ importers:
react-virtuoso: react-virtuoso:
specifier: ^4.7.11 specifier: ^4.7.11
version: 4.7.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 4.7.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
recoil:
specifier: ^0.7.7
version: 0.7.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
rxjs: rxjs:
specifier: ^7.8.1 specifier: ^7.8.1
version: 7.8.1 version: 7.8.1
@@ -854,9 +854,6 @@ packages:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'} engines: {node: '>=4'}
hamt_plus@1.0.2:
resolution: {integrity: sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==}
has-flag@3.0.0: has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -894,6 +891,18 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'} engines: {node: '>=0.12.0'}
jotai@2.10.2:
resolution: {integrity: sha512-DqsBTlRglIBviuJLfK6JxZzpd6vKfbuJ4IqRCz70RFEDeZf46Fcteb/FXxNr1UnoxR5oUy3oq7IE8BrEq0G5DQ==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=17.0.0'
react: '>=17.0.0'
peerDependenciesMeta:
'@types/react':
optional: true
react:
optional: true
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -1025,18 +1034,6 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'} engines: {node: '>=8.10.0'}
recoil@0.7.7:
resolution: {integrity: sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==}
peerDependencies:
react: '>=16.13.1'
react-dom: '*'
react-native: '*'
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
regenerator-runtime@0.14.1: regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
@@ -1894,8 +1891,6 @@ snapshots:
globals@11.12.0: {} globals@11.12.0: {}
hamt_plus@1.0.2: {}
has-flag@3.0.0: {} has-flag@3.0.0: {}
has@1.0.3: has@1.0.3:
@@ -1929,6 +1924,11 @@ snapshots:
is-number@7.0.0: {} is-number@7.0.0: {}
jotai@2.10.2(@types/react@18.3.3)(react@18.3.1):
optionalDependencies:
'@types/react': 18.3.3
react: 18.3.1
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-yaml@4.1.0: js-yaml@4.1.0:
@@ -2050,13 +2050,6 @@ snapshots:
dependencies: dependencies:
picomatch: 2.3.1 picomatch: 2.3.1
recoil@0.7.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
hamt_plus: 1.0.2
react: 18.3.1
optionalDependencies:
react-dom: 18.3.1(react@18.3.1)
regenerator-runtime@0.14.1: {} regenerator-runtime@0.14.1: {}
resolve-from@4.0.0: {} resolve-from@4.0.0: {}

View File

@@ -1,11 +1,11 @@
import { RouterProvider } from 'react-router-dom' import { RouterProvider } from 'react-router-dom'
import { RecoilRoot } from 'recoil' import { Provider } from 'jotai'
import { router } from './router' import { router } from './router'
export function App() { export function App() {
return ( return (
<RecoilRoot> <Provider>
<RouterProvider router={router} /> <RouterProvider router={router} />
</RecoilRoot> </Provider>
) )
} }

View File

@@ -19,7 +19,6 @@ import Typography from '@mui/material/Typography'
import { grey } from '@mui/material/colors' import { grey } 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 { useRecoilValue } from 'recoil'
import { settingsState } from './atoms/settings' import { settingsState } from './atoms/settings'
import AppBar from './components/AppBar' import AppBar from './components/AppBar'
import Drawer from './components/Drawer' import Drawer from './components/Drawer'
@@ -29,11 +28,12 @@ import SocketSubscriber from './components/SocketSubscriber'
import ThemeToggler from './components/ThemeToggler' 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'
export default function Layout() { export default function Layout() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const settings = useRecoilValue(settingsState) const settings = useAtomValue(settingsState)
const mode = settings.theme const mode = settings.theme
const theme = useMemo(() => const theme = useMemo(() =>

View File

@@ -1,44 +1,34 @@
import { getOrElse } from 'fp-ts/lib/Either' import { getOrElse } from 'fp-ts/lib/Either'
import { pipe } from 'fp-ts/lib/function' import { pipe } from 'fp-ts/lib/function'
import { atom, selector } from 'recoil'
import { ffetch } from '../lib/httpClient' import { ffetch } from '../lib/httpClient'
import { CustomTemplate } from '../types' import { CustomTemplate } from '../types'
import { serverSideCookiesState, serverURL } from './settings' import { serverSideCookiesState, serverURL } from './settings'
import { atom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
export const cookiesTemplateState = selector({ export const cookiesTemplateState = atom<Promise<string>>(async (get) =>
key: 'cookiesTemplateState', await get(serverSideCookiesState)
get: ({ get }) => get(serverSideCookiesState)
? '--cookies=cookies.txt' ? '--cookies=cookies.txt'
: '' : ''
}) )
export const customArgsState = atom({ export const customArgsState = atomWithStorage(
key: 'customArgsState', 'customArgs',
default: localStorage.getItem('customArgs') ?? '', localStorage.getItem('customArgs') ?? ''
effects: [ )
({ onSet }) => onSet(e => localStorage.setItem('customArgs', e))
]
})
export const filenameTemplateState = atom({ export const filenameTemplateState = atomWithStorage(
key: 'filenameTemplateState', 'lastFilenameTemplate',
default: localStorage.getItem('lastFilenameTemplate') ?? '', localStorage.getItem('lastFilenameTemplate') ?? ''
effects: [ )
({ onSet }) => onSet(e => localStorage.setItem('lastFilenameTemplate', e))
]
})
export const downloadTemplateState = selector({ export const downloadTemplateState = atom<string>((get) =>
key: 'downloadTemplateState',
get: ({ get }) =>
`${get(customArgsState)} ${get(cookiesTemplateState)}` `${get(customArgsState)} ${get(cookiesTemplateState)}`
.replace(/ +/g, ' ') .replace(/ +/g, ' ')
.trim() .trim()
}) )
export const savedTemplatesState = selector<CustomTemplate[]>({ export const savedTemplatesState = atom<Promise<CustomTemplate[]>>(async (get) => {
key: 'savedTemplatesState',
get: async ({ get }) => {
const task = ffetch<CustomTemplate[]>(`${get(serverURL)}/api/v1/template/all`) const task = ffetch<CustomTemplate[]>(`${get(serverURL)}/api/v1/template/all`)
const either = await task() const either = await task()
@@ -46,5 +36,5 @@ export const savedTemplatesState = selector<CustomTemplate[]>({
either, either,
getOrElse(() => new Array<CustomTemplate>()) getOrElse(() => new Array<CustomTemplate>())
) )
} }
}) )

View File

@@ -1,22 +1,13 @@
import * as O from 'fp-ts/Option' import * as O from 'fp-ts/Option'
import { pipe } from 'fp-ts/lib/function' import { pipe } from 'fp-ts/lib/function'
import { atom, selector } from 'recoil'
import { RPCResult } from '../types' import { RPCResult } from '../types'
import { atom } from 'jotai'
export const downloadsState = atom<O.Option<RPCResult[]>>({ export const downloadsState = atom<O.Option<RPCResult[]>>(O.none)
key: 'downloadsState',
default: O.none
})
export const loadingDownloadsState = selector<boolean>({ export const loadingDownloadsState = atom<boolean>((get) => O.isNone(get(downloadsState)))
key: 'loadingDownloadsState',
get: ({ get }) => O.isNone(get(downloadsState))
})
export const activeDownloadsState = selector<RPCResult[]>({ export const activeDownloadsState = atom<RPCResult[]>((get) => pipe(
key: 'activeDownloadsState',
get: ({ get }) => pipe(
get(downloadsState), get(downloadsState),
O.getOrElse(() => new Array<RPCResult>()) O.getOrElse(() => new Array<RPCResult>())
) ))
})

View File

@@ -1,9 +1,5 @@
import { selector } from 'recoil' import { atom } from 'jotai'
import I18nBuilder from '../lib/intl' import I18nBuilder from '../lib/intl'
import { languageState } from './settings' import { languageState } from './settings'
export const i18nBuilderState = selector({ export const i18nBuilderState = atom((get) => new I18nBuilder(get(languageState)))
key: 'i18nBuilderState',
get: ({ get }) => new I18nBuilder(get(languageState)),
dangerouslyAllowMutability: true,
})

View File

@@ -1,23 +1,17 @@
import { atom, selector } from 'recoil' import { atom } from 'jotai'
import { RPCClient } from '../lib/rpcClient' import { RPCClient } from '../lib/rpcClient'
import { rpcHTTPEndpoint, rpcWebSocketEndpoint } from './settings' import { rpcHTTPEndpoint, rpcWebSocketEndpoint } from './settings'
import { atomWithStorage } from 'jotai/utils'
export const rpcClientState = selector({ export const rpcClientState = atom((get) =>
key: 'rpcClientState',
get: ({ get }) =>
new RPCClient( new RPCClient(
get(rpcHTTPEndpoint), get(rpcHTTPEndpoint),
get(rpcWebSocketEndpoint), get(rpcWebSocketEndpoint),
localStorage.getItem('token') ?? '' localStorage.getItem('token') ?? ''
), ),
dangerouslyAllowMutability: true, )
})
export const rpcPollingTimeState = atom({ export const rpcPollingTimeState = atomWithStorage(
key: 'rpcPollingTimeState', 'rpc-polling-time',
default: Number(localStorage.getItem('rpc-polling-time')) || 1000, Number(localStorage.getItem('rpc-polling-time')) || 1000
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('rpc-polling-time', a.toString()))
]
})

View File

@@ -1,8 +1,9 @@
import { pipe } from 'fp-ts/lib/function' import { pipe } from 'fp-ts/lib/function'
import { matchW } from 'fp-ts/lib/TaskEither' import { matchW } from 'fp-ts/lib/TaskEither'
import { atom, selector } from 'recoil'
import { ffetch } from '../lib/httpClient' import { ffetch } from '../lib/httpClient'
import { prefersDarkMode } from '../utils' import { prefersDarkMode } from '../utils'
import { atomWithStorage } from 'jotai/utils'
import { atom } from 'jotai'
export const languages = [ export const languages = [
'english', 'english',
@@ -40,126 +41,72 @@ export interface SettingsState {
appTitle: string appTitle: string
} }
export const languageState = atom<Language>({ export const languageState = atomWithStorage<Language>(
key: 'languageState', 'language',
default: localStorage.getItem('language') as Language || 'english', localStorage.getItem('language') as Language || 'english'
effects: [ )
({ onSet }) =>
onSet(l => localStorage.setItem('language', l.toString()))
]
})
export const themeState = atom<Theme>({ export const themeState = atomWithStorage<Theme>(
key: 'themeStateState', 'theme',
default: localStorage.getItem('theme') as Theme || 'system', localStorage.getItem('theme') as Theme || 'system'
effects: [ )
({ onSet }) =>
onSet(l => localStorage.setItem('theme', l.toString()))
]
})
export const serverAddressState = atom<string>({ export const serverAddressState = atomWithStorage<string>(
key: 'serverAddressState', 'server-addr',
default: localStorage.getItem('server-addr') || window.location.hostname, localStorage.getItem('server-addr') || window.location.hostname
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('server-addr', a.toString()))
]
})
export const serverPortState = atom<number>({ export const serverPortState = atomWithStorage<number>(
key: 'serverPortState', 'server-port',
default: Number(localStorage.getItem('server-port')) || Number(window.location.port), Number(localStorage.getItem('server-port')) || Number(window.location.port)
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('server-port', a.toString()))
]
})
export const latestCliArgumentsState = atom<string>({ export const latestCliArgumentsState = atomWithStorage<string>(
key: 'latestCliArgumentsState', 'cli-args',
default: localStorage.getItem('cli-args') || '--no-mtime', localStorage.getItem('cli-args') || '--no-mtime'
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('cli-args', a.toString()))
]
})
export const formatSelectionState = atom({ export const formatSelectionState = atomWithStorage(
key: 'formatSelectionState', 'format-selection',
default: localStorage.getItem('format-selection') === "true", localStorage.getItem('format-selection') === 'true'
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('format-selection', a.toString()))
]
})
export const fileRenamingState = atom({ export const fileRenamingState = atomWithStorage(
key: 'fileRenamingState', 'file-renaming',
default: localStorage.getItem('file-renaming') === "true", localStorage.getItem('file-renaming') === 'true'
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('file-renaming', a.toString()))
]
})
export const pathOverridingState = atom({ export const pathOverridingState = atomWithStorage(
key: 'pathOverridingState', 'path-overriding',
default: localStorage.getItem('path-overriding') === "true", localStorage.getItem('path-overriding') === 'true'
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('path-overriding', a.toString()))
]
})
export const enableCustomArgsState = atom({ export const enableCustomArgsState = atomWithStorage(
key: 'enableCustomArgsState', 'enable-custom-args',
default: localStorage.getItem('enable-custom-args') === "true", localStorage.getItem('enable-custom-args') === 'true'
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('enable-custom-args', a.toString()))
]
})
export const listViewState = atom({ export const listViewState = atomWithStorage(
key: 'listViewState', 'listview',
default: localStorage.getItem('listview') === "true", localStorage.getItem('listview') === 'true'
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('listview', a.toString()))
]
})
export const servedFromReverseProxyState = atom({ export const servedFromReverseProxyState = atomWithStorage(
key: 'servedFromReverseProxyState', 'reverseProxy',
default: localStorage.getItem('reverseProxy') === "true" || window.location.port == "", localStorage.getItem('reverseProxy') === 'true' || window.location.port == ''
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('reverseProxy', a.toString()))
]
})
export const servedFromReverseProxySubDirState = atom<string>({ export const servedFromReverseProxySubDirState = atomWithStorage<string>(
key: 'servedFromReverseProxySubDirState', 'reverseProxySubDir',
default: localStorage.getItem('reverseProxySubDir') ?? '', localStorage.getItem('reverseProxySubDir') ?? ''
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('reverseProxySubDir', a))
]
})
export const appTitleState = atom({ export const appTitleState = atomWithStorage(
key: 'appTitleState', 'appTitle',
default: localStorage.getItem('appTitle') ?? 'yt-dlp Web UI', localStorage.getItem('appTitle') ?? 'yt-dlp Web UI'
effects: [ )
({ onSet }) =>
onSet(a => localStorage.setItem('appTitle', a.toString()))
]
})
export const serverAddressAndPortState = selector({ export const serverAddressAndPortState = atom((get) => {
key: 'serverAddressAndPortState',
get: ({ get }) => {
if (get(servedFromReverseProxySubDirState)) { if (get(servedFromReverseProxySubDirState)) {
return `${get(serverAddressState)}/${get(servedFromReverseProxySubDirState)}/` return `${get(serverAddressState)}/${get(servedFromReverseProxySubDirState)}/`
} }
@@ -167,56 +114,42 @@ export const serverAddressAndPortState = selector({
return `${get(serverAddressState)}` return `${get(serverAddressState)}`
} }
return `${get(serverAddressState)}:${get(serverPortState)}` return `${get(serverAddressState)}:${get(serverPortState)}`
}
}) })
export const serverURL = selector({ export const serverURL = atom((get) =>
key: 'serverURL',
get: ({ get }) =>
`${window.location.protocol}//${get(serverAddressAndPortState)}` `${window.location.protocol}//${get(serverAddressAndPortState)}`
}) )
export const rpcWebSocketEndpoint = selector({ export const rpcWebSocketEndpoint = atom((get) => {
key: 'rpcWebSocketEndpoint',
get: ({ get }) => {
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:' const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
return `${proto}//${get(serverAddressAndPortState)}/rpc/ws` return `${proto}//${get(serverAddressAndPortState)}/rpc/ws`
} }
}) )
export const rpcHTTPEndpoint = selector({ export const rpcHTTPEndpoint = atom((get) => {
key: 'rpcHTTPEndpoint',
get: ({ get }) => {
const proto = window.location.protocol const proto = window.location.protocol
return `${proto}//${get(serverAddressAndPortState)}/rpc/http` return `${proto}//${get(serverAddressAndPortState)}/rpc/http`
} }
}) )
export const serverSideCookiesState = selector<string>({ export const serverSideCookiesState = atom<Promise<string>>(async (get) => await pipe(
key: 'serverSideCookiesState',
get: async ({ get }) => await pipe(
ffetch<Readonly<{ cookies: string }>>(`${get(serverURL)}/api/v1/cookies`), ffetch<Readonly<{ cookies: string }>>(`${get(serverURL)}/api/v1/cookies`),
matchW( matchW(
() => '', () => '',
(r) => r.cookies (r) => r.cookies
) )
)() )())
})
const themeSelector = selector<ThemeNarrowed>({ const themeSelector = atom<ThemeNarrowed>((get) => {
key: 'themeSelector',
get: ({ get }) => {
const theme = get(themeState) const theme = get(themeState)
if ((theme === 'system' && prefersDarkMode()) || theme === 'dark') { if ((theme === 'system' && prefersDarkMode()) || theme === 'dark') {
return 'dark' return 'dark'
} }
return 'light' return 'light'
} }
}) )
export const settingsState = selector<SettingsState>({ export const settingsState = atom<SettingsState>((get) => ({
key: 'settingsState',
get: ({ get }) => ({
serverAddr: get(serverAddressState), serverAddr: get(serverAddressState),
serverPort: get(serverPortState), serverPort: get(serverPortState),
language: get(languageState), language: get(languageState),
@@ -229,5 +162,5 @@ export const settingsState = selector<SettingsState>({
listView: get(listViewState), listView: get(listViewState),
servedFromReverseProxy: get(servedFromReverseProxyState), servedFromReverseProxy: get(servedFromReverseProxyState),
appTitle: get(appTitleState) appTitle: get(appTitleState)
})
}) })
)

View File

@@ -1,38 +1,28 @@
import { pipe } from 'fp-ts/lib/function' import { pipe } from 'fp-ts/lib/function'
import { of } from 'fp-ts/lib/Task' import { of } from 'fp-ts/lib/Task'
import { getOrElse } from 'fp-ts/lib/TaskEither' import { getOrElse } from 'fp-ts/lib/TaskEither'
import { atom, selector } from 'recoil'
import { ffetch } from '../lib/httpClient' import { ffetch } from '../lib/httpClient'
import { RPCVersion } from '../types' import { RPCVersion } from '../types'
import { rpcClientState } from './rpc' import { rpcClientState } from './rpc'
import { serverURL } from './settings' import { serverURL } from './settings'
import { atom } from 'jotai'
export const connectedState = atom({ export const connectedState = atom(false)
key: 'connectedState',
default: false
})
export const freeSpaceBytesState = selector({ export const freeSpaceBytesState = atom(async (get) => {
key: 'freeSpaceBytesState', const res = await get(rpcClientState)
get: async ({ get }) => { .freeSpace()
const res = await get(rpcClientState).freeSpace()
.catch(() => ({ result: 0 })) .catch(() => ({ result: 0 }))
return res.result return res.result
}
}) })
export const availableDownloadPathsState = selector({ export const availableDownloadPathsState = atom(async (get) => {
key: 'availableDownloadPathsState',
get: async ({ get }) => {
const res = await get(rpcClientState).directoryTree() const res = await get(rpcClientState).directoryTree()
.catch(() => ({ result: [] })) .catch(() => ({ result: [] }))
return res.result return res.result
}
}) })
export const ytdlpRpcVersionState = selector<RPCVersion>({ export const ytdlpRpcVersionState = atom<Promise<RPCVersion>>(async (get) => await pipe(
key: 'ytdlpRpcVersionState',
get: async ({ get }) => await pipe(
ffetch<RPCVersion>(`${get(serverURL)}/api/v1/version`), ffetch<RPCVersion>(`${get(serverURL)}/api/v1/version`),
getOrElse(() => pipe( getOrElse(() => pipe(
{ {
@@ -41,5 +31,4 @@ export const ytdlpRpcVersionState = selector<RPCVersion>({
}, },
of of
)), )),
)() )())
})

View File

@@ -1,5 +1,5 @@
import { AlertColor } from '@mui/material' import { AlertColor } from '@mui/material'
import { atom } from 'recoil' import { atom } from 'jotai'
export type Toast = { export type Toast = {
open: boolean, open: boolean,
@@ -9,7 +9,4 @@ export type Toast = {
severity?: AlertColor severity?: AlertColor
} }
export const toastListState = atom<Toast[]>({ export const toastListState = atom<Toast[]>([])
key: 'toastListState',
default: [],
})

View File

@@ -1,14 +1,10 @@
import { atom, selector } from 'recoil' import { atom } from 'jotai'
import { activeDownloadsState } from './downloads' import { activeDownloadsState } from './downloads'
export const loadingAtom = atom({ export const loadingAtom = atom(true)
key: 'loadingAtom',
default: true
})
export const totalDownloadSpeedState = selector<number>({ export const totalDownloadSpeedState = atom<number>((get) =>
key: 'totalDownloadSpeedState', get(activeDownloadsState)
get: ({ get }) => get(activeDownloadsState)
.map(d => d.progress.speed) .map(d => d.progress.speed)
.reduce((curr, next) => curr + next, 0) .reduce((curr, next) => curr + next, 0)
}) )

View File

@@ -5,12 +5,12 @@ import * as O from 'fp-ts/Option'
import { matchW } from 'fp-ts/lib/TaskEither' import { matchW } from 'fp-ts/lib/TaskEither'
import { pipe } from 'fp-ts/lib/function' import { pipe } from 'fp-ts/lib/function'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useRecoilValue } from 'recoil'
import { Subject, debounceTime, distinctUntilChanged } from 'rxjs' import { Subject, debounceTime, distinctUntilChanged } from 'rxjs'
import { serverSideCookiesState, serverURL } from '../atoms/settings' import { serverSideCookiesState, serverURL } from '../atoms/settings'
import { useSubscription } from '../hooks/observable' import { useSubscription } from '../hooks/observable'
import { useToast } from '../hooks/toast' import { useToast } from '../hooks/toast'
import { ffetch } from '../lib/httpClient' import { ffetch } from '../lib/httpClient'
import { useAtomValue } from 'jotai'
const validateCookie = (cookie: string) => pipe( const validateCookie = (cookie: string) => pipe(
cookie, cookie,
@@ -75,8 +75,8 @@ const noopValidator = (s: string): E.Either<string, string[]> => pipe(
const isCommentOrNewLine = (s: string) => s === '' || s.startsWith('\n') || s.startsWith('#') const isCommentOrNewLine = (s: string) => s === '' || s.startsWith('\n') || s.startsWith('#')
const CookiesTextField: React.FC = () => { const CookiesTextField: React.FC = () => {
const serverAddr = useRecoilValue(serverURL) const serverAddr = useAtomValue(serverURL)
const savedCookies = useRecoilValue(serverSideCookiesState) const savedCookies = useAtomValue(serverSideCookiesState)
const { pushMessage } = useToast() const { pushMessage } = useToast()

View File

@@ -16,10 +16,10 @@ import {
Typography Typography
} from '@mui/material' } from '@mui/material'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useRecoilValue } from 'recoil'
import { serverURL } from '../atoms/settings' import { serverURL } from '../atoms/settings'
import { RPCResult } from '../types' import { RPCResult } from '../types'
import { base64URLEncode, ellipsis, formatSize, formatSpeedMiB, mapProcessStatus } from '../utils' import { base64URLEncode, ellipsis, formatSize, formatSpeedMiB, mapProcessStatus } from '../utils'
import { useAtomValue } from 'jotai'
type Props = { type Props = {
download: RPCResult download: RPCResult
@@ -37,7 +37,7 @@ const Resolution: React.FC<{ resolution?: string }> = ({ resolution }) => {
} }
const DownloadCard: React.FC<Props> = ({ download, onStop, onCopy }) => { const DownloadCard: React.FC<Props> = ({ download, onStop, onCopy }) => {
const serverAddr = useRecoilValue(serverURL) const serverAddr = useAtomValue(serverURL)
const isCompleted = useCallback( const isCompleted = useCallback(
() => download.progress.percentage === '-1', () => download.progress.percentage === '-1',

View File

@@ -30,7 +30,6 @@ import {
useState, useState,
useTransition useTransition
} from 'react' } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
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'
@@ -42,6 +41,7 @@ import { toFormatArgs } from '../utils'
import ExtraDownloadOptions from './ExtraDownloadOptions' import ExtraDownloadOptions from './ExtraDownloadOptions'
import { useToast } from '../hooks/toast' 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 & {
@@ -59,11 +59,11 @@ type Props = {
} }
const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => { const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {
const settings = useRecoilValue(settingsState) const settings = useAtomValue(settingsState)
const isConnected = useRecoilValue(connectedState) const isConnected = useAtomValue(connectedState)
const availableDownloadPaths = useRecoilValue(availableDownloadPathsState) const availableDownloadPaths = useAtomValue(availableDownloadPathsState)
const downloadTemplate = useRecoilValue(downloadTemplateState) const downloadTemplate = useAtomValue(downloadTemplateState)
const savedTemplates = useRecoilValue(savedTemplatesState) const savedTemplates = useAtomValue(savedTemplatesState)
const [downloadFormats, setDownloadFormats] = useState<DLMetadata>() const [downloadFormats, setDownloadFormats] = useState<DLMetadata>()
const [pickedVideoFormat, setPickedVideoFormat] = useState('') const [pickedVideoFormat, setPickedVideoFormat] = useState('')
@@ -71,11 +71,11 @@ const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {
const [pickedBestFormat, setPickedBestFormat] = useState('') const [pickedBestFormat, setPickedBestFormat] = useState('')
const [isFormatsLoading, setIsFormatsLoading] = useState(false) const [isFormatsLoading, setIsFormatsLoading] = useState(false)
const [customArgs, setCustomArgs] = useRecoilState(customArgsState) const [customArgs, setCustomArgs] = useAtom(customArgsState)
const [downloadPath, setDownloadPath] = useState('') const [downloadPath, setDownloadPath] = useState('')
const [filenameTemplate, setFilenameTemplate] = useRecoilState( const [filenameTemplate, setFilenameTemplate] = useAtom(
filenameTemplateState filenameTemplateState
) )

View File

@@ -1,5 +1,5 @@
import { useAtom, useAtomValue } from 'jotai'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { loadingDownloadsState } from '../atoms/downloads' import { loadingDownloadsState } from '../atoms/downloads'
import { listViewState } from '../atoms/settings' import { listViewState } from '../atoms/settings'
import { loadingAtom } from '../atoms/ui' import { loadingAtom } from '../atoms/ui'
@@ -7,10 +7,10 @@ import DownloadsGridView from './DownloadsGridView'
import DownloadsTableView from './DownloadsTableView' import DownloadsTableView from './DownloadsTableView'
const Downloads: React.FC = () => { const Downloads: React.FC = () => {
const tableView = useRecoilValue(listViewState) const tableView = useAtomValue(listViewState)
const loadingDownloads = useRecoilValue(loadingDownloadsState) const loadingDownloads = useAtomValue(loadingDownloadsState)
const [isLoading, setIsLoading] = useRecoilState(loadingAtom) const [isLoading, setIsLoading] = useAtom(loadingAtom)
useEffect(() => { useEffect(() => {
if (loadingDownloads) { if (loadingDownloads) {

View File

@@ -1,5 +1,5 @@
import { Grid } from '@mui/material' import { Grid } from '@mui/material'
import { useRecoilValue } from 'recoil' import { useAtomValue } from 'jotai'
import { activeDownloadsState } from '../atoms/downloads' import { activeDownloadsState } from '../atoms/downloads'
import { useToast } from '../hooks/toast' import { useToast } from '../hooks/toast'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
@@ -8,7 +8,7 @@ import { ProcessStatus, RPCResult } from '../types'
import DownloadCard from './DownloadCard' import DownloadCard from './DownloadCard'
const DownloadsGridView: React.FC = () => { const DownloadsGridView: React.FC = () => {
const downloads = useRecoilValue(activeDownloadsState) const downloads = useAtomValue(activeDownloadsState)
const { i18n } = useI18n() const { i18n } = useI18n()
const { client } = useRPC() const { client } = useRPC()

View File

@@ -20,12 +20,12 @@ import {
} from "@mui/material" } from "@mui/material"
import { forwardRef } from 'react' import { forwardRef } from 'react'
import { TableComponents, TableVirtuoso } from 'react-virtuoso' import { TableComponents, TableVirtuoso } from 'react-virtuoso'
import { useRecoilValue } from 'recoil'
import { activeDownloadsState } from '../atoms/downloads' import { activeDownloadsState } from '../atoms/downloads'
import { serverURL } from '../atoms/settings' import { serverURL } from '../atoms/settings'
import { useRPC } from '../hooks/useRPC' import { useRPC } from '../hooks/useRPC'
import { ProcessStatus, RPCResult } from '../types' import { ProcessStatus, RPCResult } from '../types'
import { base64URLEncode, formatSize, formatSpeedMiB } from "../utils" import { base64URLEncode, formatSize, formatSpeedMiB } from "../utils"
import { useAtomValue } from 'jotai'
const columns = [ const columns = [
{ {
@@ -119,8 +119,8 @@ function fixedHeaderContent() {
} }
const DownloadsTableView: React.FC = () => { const DownloadsTableView: React.FC = () => {
const downloads = useRecoilValue(activeDownloadsState) const downloads = useAtomValue(activeDownloadsState)
const serverAddr = useRecoilValue(serverURL) const serverAddr = useAtomValue(serverURL)
const { client } = useRPC() const { client } = useRPC()
const viewFile = (path: string) => { const viewFile = (path: string) => {

View File

@@ -1,13 +1,13 @@
import { Autocomplete, Box, TextField, Typography } from '@mui/material' import { Autocomplete, Box, TextField, Typography } from '@mui/material'
import { useRecoilState, useRecoilValue } from 'recoil'
import { customArgsState, savedTemplatesState } from '../atoms/downloadTemplate' import { customArgsState, savedTemplatesState } from '../atoms/downloadTemplate'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import { useAtom, useAtomValue } from 'jotai'
const ExtraDownloadOptions: React.FC = () => { const ExtraDownloadOptions: React.FC = () => {
const { i18n } = useI18n() const { i18n } = useI18n()
const customTemplates = useRecoilValue(savedTemplatesState) const customTemplates = useAtomValue(savedTemplatesState)
const [, setCustomArgs] = useRecoilState(customArgsState) const [, setCustomArgs] = useAtom(customArgsState)
return ( return (
<> <>

View File

@@ -2,7 +2,6 @@ import DownloadIcon from '@mui/icons-material/Download'
import SettingsEthernet from '@mui/icons-material/SettingsEthernet' import SettingsEthernet from '@mui/icons-material/SettingsEthernet'
import { AppBar, CircularProgress, Divider, Toolbar } from '@mui/material' import { AppBar, CircularProgress, Divider, Toolbar } from '@mui/material'
import { Suspense } from 'react' import { Suspense } from 'react'
import { useRecoilValue } from 'recoil'
import { settingsState } from '../atoms/settings' import { settingsState } from '../atoms/settings'
import { connectedState } from '../atoms/status' import { connectedState } from '../atoms/status'
import { totalDownloadSpeedState } from '../atoms/ui' import { totalDownloadSpeedState } from '../atoms/ui'
@@ -10,11 +9,12 @@ import { useI18n } from '../hooks/useI18n'
import { formatSpeedMiB } from '../utils' import { formatSpeedMiB } from '../utils'
import FreeSpaceIndicator from './FreeSpaceIndicator' import FreeSpaceIndicator from './FreeSpaceIndicator'
import VersionIndicator from './VersionIndicator' import VersionIndicator from './VersionIndicator'
import { useAtomValue } from 'jotai'
const Footer: React.FC = () => { const Footer: React.FC = () => {
const settings = useRecoilValue(settingsState) const settings = useAtomValue(settingsState)
const isConnected = useRecoilValue(connectedState) const isConnected = useAtomValue(connectedState)
const totalDownloadSpeed = useRecoilValue(totalDownloadSpeedState) const totalDownloadSpeed = useAtomValue(totalDownloadSpeedState)
const mode = settings.theme const mode = settings.theme
const { i18n } = useI18n() const { i18n } = useI18n()

View File

@@ -1,10 +1,10 @@
import StorageIcon from '@mui/icons-material/Storage' import StorageIcon from '@mui/icons-material/Storage'
import { useRecoilValue } from 'recoil'
import { freeSpaceBytesState } from '../atoms/status' import { freeSpaceBytesState } from '../atoms/status'
import { formatSize } from '../utils' import { formatSize } from '../utils'
import { useAtomValue } from 'jotai'
const FreeSpaceIndicator = () => { const FreeSpaceIndicator = () => {
const freeSpace = useRecoilValue(freeSpaceBytesState) const freeSpace = useAtomValue(freeSpaceBytesState)
return ( return (
<div style={{ <div style={{

View File

@@ -1,5 +1,5 @@
import { useSetAtom } from 'jotai'
import { Suspense, useState } from 'react' import { Suspense, useState } from 'react'
import { useRecoilState } from 'recoil'
import { loadingAtom } from '../atoms/ui' import { loadingAtom } from '../atoms/ui'
import { useToast } from '../hooks/toast' import { useToast } from '../hooks/toast'
import DownloadDialog from './DownloadDialog' import DownloadDialog from './DownloadDialog'
@@ -7,7 +7,7 @@ import HomeSpeedDial from './HomeSpeedDial'
import TemplatesEditor from './TemplatesEditor' import TemplatesEditor from './TemplatesEditor'
const HomeActions: React.FC = () => { const HomeActions: React.FC = () => {
const [, setIsLoading] = useRecoilState(loadingAtom) const setIsLoading = useSetAtom(loadingAtom)
const [openDownload, setOpenDownload] = useState(false) const [openDownload, setOpenDownload] = useState(false)
const [openEditor, setOpenEditor] = useState(false) const [openEditor, setOpenEditor] = useState(false)

View File

@@ -9,7 +9,7 @@ import {
SpeedDialAction, SpeedDialAction,
SpeedDialIcon SpeedDialIcon
} from '@mui/material' } from '@mui/material'
import { useRecoilState, useRecoilValue } from 'recoil' import { useAtom, useAtomValue } from 'jotai'
import { listViewState, serverURL } from '../atoms/settings' import { listViewState, serverURL } from '../atoms/settings'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC' import { useRPC } from '../hooks/useRPC'
@@ -20,8 +20,8 @@ type Props = {
} }
const HomeSpeedDial: React.FC<Props> = ({ onDownloadOpen, onEditorOpen }) => { const HomeSpeedDial: React.FC<Props> = ({ onDownloadOpen, onEditorOpen }) => {
const serverAddr = useRecoilValue(serverURL) const serverAddr = useAtomValue(serverURL)
const [listView, setListView] = useRecoilState(listViewState) const [listView, setListView] = useAtom(listViewState)
const { i18n } = useI18n() const { i18n } = useI18n()
const { client } = useRPC() const { client } = useRPC()

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { useRecoilValue } from 'recoil'
import { serverURL } from '../atoms/settings' import { serverURL } from '../atoms/settings'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import { useAtomValue } from 'jotai'
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
@@ -11,7 +11,7 @@ const LogTerminal: React.FC = () => {
const boxRef = useRef<HTMLDivElement>(null) const boxRef = useRef<HTMLDivElement>(null)
const serverAddr = useRecoilValue(serverURL) const serverAddr = useAtomValue(serverURL)
const { i18n } = useI18n() const { i18n } = useI18n()

View File

@@ -1,7 +1,6 @@
import * as O from 'fp-ts/Option' import * as O from 'fp-ts/Option'
import { useEffect, useMemo } from 'react' import { useEffect, useMemo } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useRecoilState, useRecoilValue } from 'recoil'
import { take, timer } from 'rxjs' import { take, timer } from 'rxjs'
import { downloadsState } from '../atoms/downloads' import { downloadsState } from '../atoms/downloads'
import { rpcPollingTimeState } from '../atoms/rpc' import { rpcPollingTimeState } from '../atoms/rpc'
@@ -12,15 +11,16 @@ 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 { datetimeCompareFunc, isRPCResponse } from '../utils' import { datetimeCompareFunc, isRPCResponse } from '../utils'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
interface Props extends React.HTMLAttributes<HTMLBaseElement> { } interface Props extends React.HTMLAttributes<HTMLBaseElement> { }
const SocketSubscriber: React.FC<Props> = () => { const SocketSubscriber: React.FC<Props> = () => {
const [connected, setIsConnected] = useRecoilState(connectedState) const [connected, setIsConnected] = useAtom(connectedState)
const [, setDownloads] = useRecoilState(downloadsState) const setDownloads = useSetAtom(downloadsState)
const serverAddressAndPort = useRecoilValue(serverAddressAndPortState) const serverAddressAndPort = useAtomValue(serverAddressAndPortState)
const rpcPollingTime = useRecoilValue(rpcPollingTimeState) const rpcPollingTime = useAtomValue(rpcPollingTimeState)
const { i18n } = useI18n() const { i18n } = useI18n()
const { client } = useRPC() const { client } = useRPC()

View File

@@ -1,8 +1,8 @@
import CloudDownloadIcon from '@mui/icons-material/CloudDownload' import CloudDownloadIcon from '@mui/icons-material/CloudDownload'
import { Container, SvgIcon, Typography, styled } from '@mui/material' import { Container, SvgIcon, Typography, styled } from '@mui/material'
import { useRecoilValue } from 'recoil'
import { activeDownloadsState } from '../atoms/downloads' import { activeDownloadsState } from '../atoms/downloads'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import { useAtomValue } from 'jotai'
const FlexContainer = styled(Container)({ const FlexContainer = styled(Container)({
display: 'flex', display: 'flex',
@@ -23,7 +23,7 @@ const Title = styled(Typography)({
export default function Splash() { export default function Splash() {
const { i18n } = useI18n() const { i18n } = useI18n()
const activeDownloads = useRecoilValue(activeDownloadsState) const activeDownloads = useAtomValue(activeDownloadsState)
if (activeDownloads.length !== 0) { if (activeDownloads.length !== 0) {
return null return null

View File

@@ -20,12 +20,12 @@ import { TransitionProps } from '@mui/material/transitions'
import { matchW } from 'fp-ts/lib/Either' import { matchW } from 'fp-ts/lib/Either'
import { pipe } from 'fp-ts/lib/function' import { pipe } from 'fp-ts/lib/function'
import { forwardRef, useEffect, useState, useTransition } from 'react' import { forwardRef, useEffect, useState, useTransition } from 'react'
import { useRecoilValue } from 'recoil'
import { serverURL } from '../atoms/settings' import { serverURL } from '../atoms/settings'
import { useToast } from '../hooks/toast' import { useToast } from '../hooks/toast'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import { ffetch } from '../lib/httpClient' import { ffetch } from '../lib/httpClient'
import { CustomTemplate } from '../types' import { CustomTemplate } from '../types'
import { useAtomValue } from 'jotai'
const Transition = forwardRef(function Transition( const Transition = forwardRef(function Transition(
props: TransitionProps & { props: TransitionProps & {
@@ -45,7 +45,7 @@ const TemplatesEditor: React.FC<Props> = ({ open, onClose }) => {
const [templateName, setTemplateName] = useState('') const [templateName, setTemplateName] = useState('')
const [templateContent, setTemplateContent] = useState('') const [templateContent, setTemplateContent] = useState('')
const serverAddr = useRecoilValue(serverURL) const serverAddr = useAtomValue(serverURL)
const [isPending, startTransition] = useTransition() const [isPending, startTransition] = useTransition()
const [templates, setTemplates] = useState<CustomTemplate[]>([]) const [templates, setTemplates] = useState<CustomTemplate[]>([])

View File

@@ -2,12 +2,12 @@ import Brightness4 from '@mui/icons-material/Brightness4'
import Brightness5 from '@mui/icons-material/Brightness5' import Brightness5 from '@mui/icons-material/Brightness5'
import BrightnessAuto from '@mui/icons-material/BrightnessAuto' import BrightnessAuto from '@mui/icons-material/BrightnessAuto'
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material' import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
import { useRecoilState } from 'recoil'
import { Theme, themeState } from '../atoms/settings' import { Theme, themeState } from '../atoms/settings'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import { useAtom } from 'jotai'
const ThemeToggler: React.FC = () => { const ThemeToggler: React.FC = () => {
const [theme, setTheme] = useRecoilState(themeState) const [theme, setTheme] = useAtom(themeState)
const actions: Record<Theme, React.ReactNode> = { const actions: Record<Theme, React.ReactNode> = {
system: <BrightnessAuto />, system: <BrightnessAuto />,

View File

@@ -1,9 +1,9 @@
import { Chip } from '@mui/material' import { Chip } from '@mui/material'
import { useRecoilValue } from 'recoil'
import { ytdlpRpcVersionState } from '../atoms/status' import { ytdlpRpcVersionState } from '../atoms/status'
import { useAtomValue } from 'jotai'
const VersionIndicator: React.FC = () => { const VersionIndicator: React.FC = () => {
const version = useRecoilValue(ytdlpRpcVersionState) const version = useAtomValue(ytdlpRpcVersionState)
return ( return (
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}> <div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>

View File

@@ -1,9 +1,9 @@
import { AlertColor } from '@mui/material' import { AlertColor } from '@mui/material'
import { useRecoilState } from 'recoil'
import { toastListState } from '../atoms/toast' import { toastListState } from '../atoms/toast'
import { useSetAtom } from 'jotai'
export const useToast = () => { export const useToast = () => {
const [, setToasts] = useRecoilState(toastListState) const setToasts = useSetAtom(toastListState)
return { return {
pushMessage: (message: string, severity?: AlertColor) => { pushMessage: (message: string, severity?: AlertColor) => {

View File

@@ -1,8 +1,8 @@
import { useRecoilValue } from 'recoil' import { useAtomValue } from 'jotai'
import { i18nBuilderState } from '../atoms/i18n' import { i18nBuilderState } from '../atoms/i18n'
export const useI18n = () => { export const useI18n = () => {
const instance = useRecoilValue(i18nBuilderState) const instance = useAtomValue(i18nBuilderState)
return { return {
i18n: instance i18n: instance

View File

@@ -1,8 +1,8 @@
import { useRecoilValue } from 'recoil' import { useAtomValue } from 'jotai'
import { rpcClientState } from '../atoms/rpc' import { rpcClientState } from '../atoms/rpc'
export const useRPC = () => { export const useRPC = () => {
const client = useRecoilValue(rpcClientState) const client = useAtomValue(rpcClientState)
return { return {
client client

View File

@@ -1,10 +1,10 @@
import { Alert, Snackbar } from "@mui/material" import { Alert, Snackbar } from "@mui/material"
import { useRecoilState } from 'recoil'
import { Toast, toastListState } from '../atoms/toast' import { Toast, toastListState } from '../atoms/toast'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useAtom } from 'jotai'
const Toaster: React.FC = () => { const Toaster: React.FC = () => {
const [toasts, setToasts] = useRecoilState(toastListState) const [toasts, setToasts] = useAtom(toastListState)
const deletePredicate = (t: Toast) => (Date.now() - t.createdAt) < 2000 const deletePredicate = (t: Toast) => (Date.now() - t.createdAt) < 2000

View File

@@ -33,7 +33,6 @@ import { matchW } from 'fp-ts/lib/TaskEither'
import { pipe } from 'fp-ts/lib/function' import { pipe } from 'fp-ts/lib/function'
import { useEffect, useMemo, useState, useTransition } from 'react' import { useEffect, useMemo, useState, useTransition } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useRecoilValue } from 'recoil'
import { BehaviorSubject, Subject, combineLatestWith, map, share } from 'rxjs' import { BehaviorSubject, Subject, combineLatestWith, map, share } from 'rxjs'
import { serverURL } from '../atoms/settings' import { serverURL } from '../atoms/settings'
import { useObservable } from '../hooks/observable' import { useObservable } from '../hooks/observable'
@@ -42,13 +41,14 @@ import { useI18n } from '../hooks/useI18n'
import { ffetch } from '../lib/httpClient' import { ffetch } from '../lib/httpClient'
import { DirectoryEntry } from '../types' import { DirectoryEntry } from '../types'
import { base64URLEncode, formatSize } from '../utils' import { base64URLEncode, formatSize } from '../utils'
import { useAtomValue } from 'jotai'
export default function Downloaded() { export default function Downloaded() {
const [menuPos, setMenuPos] = useState({ x: 0, y: 0 }) const [menuPos, setMenuPos] = useState({ x: 0, y: 0 })
const [showMenu, setShowMenu] = useState(false) const [showMenu, setShowMenu] = useState(false)
const [currentFile, setCurrentFile] = useState<DirectoryEntry>() const [currentFile, setCurrentFile] = useState<DirectoryEntry>()
const serverAddr = useRecoilValue(serverURL) const serverAddr = useAtomValue(serverURL)
const navigate = useNavigate() const navigate = useNavigate()
const { i18n } = useI18n() const { i18n } = useI18n()

View File

@@ -1,15 +1,15 @@
import { import {
Container Container
} from '@mui/material' } from '@mui/material'
import { useRecoilValue } from 'recoil'
import { loadingAtom } from '../atoms/ui' import { loadingAtom } from '../atoms/ui'
import Downloads from '../components/Downloads' import Downloads from '../components/Downloads'
import HomeActions from '../components/HomeActions' import HomeActions from '../components/HomeActions'
import LoadingBackdrop from '../components/LoadingBackdrop' import LoadingBackdrop from '../components/LoadingBackdrop'
import Splash from '../components/Splash' import Splash from '../components/Splash'
import { useAtomValue } from 'jotai'
export default function Home() { export default function Home() {
const isLoading = useRecoilValue(loadingAtom) const isLoading = useAtomValue(loadingAtom)
return ( return (
<Container maxWidth="xl" sx={{ mt: 2, mb: 8 }}> <Container maxWidth="xl" sx={{ mt: 2, mb: 8 }}>

View File

@@ -16,10 +16,10 @@ import { matchW } from 'fp-ts/lib/TaskEither'
import { pipe } from 'fp-ts/lib/function' import { pipe } from 'fp-ts/lib/function'
import { useState } from 'react' import { useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useRecoilValue } from 'recoil'
import { serverURL } from '../atoms/settings' import { serverURL } from '../atoms/settings'
import { useToast } from '../hooks/toast' import { useToast } from '../hooks/toast'
import { ffetch } from '../lib/httpClient' import { ffetch } from '../lib/httpClient'
import { useAtomValue } from 'jotai'
const LoginContainer = styled(Container)({ const LoginContainer = styled(Container)({
display: 'flex', display: 'flex',
@@ -43,7 +43,7 @@ export default function Login() {
const [formHasError, setFormHasError] = useState(false) const [formHasError, setFormHasError] = useState(false)
const url = useRecoilValue(serverURL) const url = useAtomValue(serverURL)
const navigate = useNavigate() const navigate = useNavigate()

View File

@@ -19,7 +19,6 @@ import {
capitalize capitalize
} from '@mui/material' } from '@mui/material'
import { Suspense, useEffect, useMemo, useState } from 'react' import { Suspense, useEffect, useMemo, useState } from 'react'
import { useRecoilState } from 'recoil'
import { import {
Subject, Subject,
debounceTime, debounceTime,
@@ -49,25 +48,26 @@ 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 { 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() {
const [reverseProxy, setReverseProxy] = useRecoilState(servedFromReverseProxyState) const [reverseProxy, setReverseProxy] = useAtom(servedFromReverseProxyState)
const [baseURL, setBaseURL] = useRecoilState(servedFromReverseProxySubDirState) const [baseURL, setBaseURL] = useAtom(servedFromReverseProxySubDirState)
const [formatSelection, setFormatSelection] = useRecoilState(formatSelectionState) const [formatSelection, setFormatSelection] = useAtom(formatSelectionState)
const [pathOverriding, setPathOverriding] = useRecoilState(pathOverridingState) const [pathOverriding, setPathOverriding] = useAtom(pathOverridingState)
const [fileRenaming, setFileRenaming] = useRecoilState(fileRenamingState) const [fileRenaming, setFileRenaming] = useAtom(fileRenamingState)
const [enableArgs, setEnableArgs] = useRecoilState(enableCustomArgsState) const [enableArgs, setEnableArgs] = useAtom(enableCustomArgsState)
const [serverAddr, setServerAddr] = useRecoilState(serverAddressState) const [serverAddr, setServerAddr] = useAtom(serverAddressState)
const [serverPort, setServerPort] = useRecoilState(serverPortState) const [serverPort, setServerPort] = useAtom(serverPortState)
const [pollingTime, setPollingTime] = useRecoilState(rpcPollingTimeState) const [pollingTime, setPollingTime] = useAtom(rpcPollingTimeState)
const [language, setLanguage] = useRecoilState(languageState) const [language, setLanguage] = useAtom(languageState)
const [appTitle, setApptitle] = useRecoilState(appTitleState) const [appTitle, setApptitle] = useAtom(appTitleState)
const [theme, setTheme] = useRecoilState(themeState) const [theme, setTheme] = useAtom(themeState)
const [invalidIP, setInvalidIP] = useState(false) const [invalidIP, setInvalidIP] = useState(false)

View File

@@ -148,7 +148,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.1" const CURRENT_RPC_VERSION = "3.2.2"
result := make(chan string, 1) result := make(chan string, 1)