code refactoring
This commit is contained in:
@@ -32,8 +32,10 @@ import AppBar from './components/AppBar'
|
|||||||
import Drawer from './components/Drawer'
|
import Drawer from './components/Drawer'
|
||||||
|
|
||||||
import Logout from './components/Logout'
|
import Logout from './components/Logout'
|
||||||
import { formatGiB } from './utils'
|
|
||||||
import ThemeToggler from './components/ThemeToggler'
|
import ThemeToggler from './components/ThemeToggler'
|
||||||
|
import I18nProvider from './providers/i18nProvider'
|
||||||
|
import RPCCLientProvider from './providers/rpcClientProvider'
|
||||||
|
import { formatGiB } from './utils'
|
||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
@@ -59,6 +61,8 @@ export default function Layout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
|
<I18nProvider>
|
||||||
|
<RPCCLientProvider>
|
||||||
<Box sx={{ display: 'flex' }}>
|
<Box sx={{ display: 'flex' }}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<AppBar position="absolute" open={open}>
|
<AppBar position="absolute" open={open}>
|
||||||
@@ -92,7 +96,9 @@ export default function Layout() {
|
|||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
}}>
|
}}>
|
||||||
<Storage />
|
<Storage />
|
||||||
<span> {formatGiB(status.freeSpace)} </span>
|
<span>
|
||||||
|
{formatGiB(status.freeSpace)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
@@ -102,7 +108,9 @@ export default function Layout() {
|
|||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
}}>
|
}}>
|
||||||
<SettingsEthernet />
|
<SettingsEthernet />
|
||||||
<span> {status.connected ? settings.serverAddr : 'not connected'}</span>
|
<span>
|
||||||
|
{status.connected ? settings.serverAddr : 'not connected'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
@@ -176,6 +184,8 @@ export default function Layout() {
|
|||||||
<Outlet />
|
<Outlet />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
</RPCCLientProvider>
|
||||||
|
</I18nProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,8 @@ languages:
|
|||||||
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
||||||
customArgsInput: Custom yt-dlp arguments
|
customArgsInput: Custom yt-dlp arguments
|
||||||
rpcConnErr: Error while conencting to RPC server
|
rpcConnErr: Error while conencting to RPC server
|
||||||
|
splashText: No active downloads
|
||||||
|
archiveTitle: Archive
|
||||||
italian:
|
italian:
|
||||||
urlInput: URL di YouTube o di qualsiasi altro servizio supportato
|
urlInput: URL di YouTube o di qualsiasi altro servizio supportato
|
||||||
statusTitle: Stato
|
statusTitle: Stato
|
||||||
@@ -57,6 +59,8 @@ languages:
|
|||||||
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
||||||
customArgsInput: Custom yt-dlp arguments
|
customArgsInput: Custom yt-dlp arguments
|
||||||
rpcConnErr: Error nella connessione al server RPC
|
rpcConnErr: Error nella connessione al server RPC
|
||||||
|
splashText: Nessun download attivo
|
||||||
|
archiveTitle: Archivio
|
||||||
chinese:
|
chinese:
|
||||||
urlInput: YouTube 或其他受支持服务的视频网址
|
urlInput: YouTube 或其他受支持服务的视频网址
|
||||||
statusTitle: 状态
|
statusTitle: 状态
|
||||||
@@ -85,6 +89,8 @@ languages:
|
|||||||
customArgs: 启用自定义 yt-dlp 参数(能力越大 = 责任越大)
|
customArgs: 启用自定义 yt-dlp 参数(能力越大 = 责任越大)
|
||||||
customArgsInput: 自定义 yt-dlp 参数
|
customArgsInput: 自定义 yt-dlp 参数
|
||||||
rpcConnErr: Error while conencting to RPC server
|
rpcConnErr: Error while conencting to RPC server
|
||||||
|
splashText: No active downloads
|
||||||
|
archiveTitle: Archive
|
||||||
spanish:
|
spanish:
|
||||||
urlInput: URL de YouTube u otro servicio compatible
|
urlInput: URL de YouTube u otro servicio compatible
|
||||||
statusTitle: Estado
|
statusTitle: Estado
|
||||||
@@ -113,6 +119,8 @@ languages:
|
|||||||
customArgs: Habilitar los argumentos yt-dlp personalizados (un gran poder conlleva una gran responsabilidad)
|
customArgs: Habilitar los argumentos yt-dlp personalizados (un gran poder conlleva una gran responsabilidad)
|
||||||
customArgsInput: Argumentos yt-dlp personalizados
|
customArgsInput: Argumentos yt-dlp personalizados
|
||||||
rpcConnErr: Error al conectarse al servidor RPC
|
rpcConnErr: Error al conectarse al servidor RPC
|
||||||
|
splashText: No active downloads
|
||||||
|
archiveTitle: Archive
|
||||||
russian:
|
russian:
|
||||||
urlInput: YouTube or other supported service video url
|
urlInput: YouTube or other supported service video url
|
||||||
statusTitle: Status
|
statusTitle: Status
|
||||||
@@ -141,6 +149,8 @@ languages:
|
|||||||
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
||||||
customArgsInput: Custom yt-dlp arguments
|
customArgsInput: Custom yt-dlp arguments
|
||||||
rpcConnErr: Error while conencting to RPC server
|
rpcConnErr: Error while conencting to RPC server
|
||||||
|
splashText: No active downloads
|
||||||
|
archiveTitle: Archive
|
||||||
korean:
|
korean:
|
||||||
urlInput: YouTube나 다른 지원되는 사이트의 URL
|
urlInput: YouTube나 다른 지원되는 사이트의 URL
|
||||||
statusTitle: 상태
|
statusTitle: 상태
|
||||||
@@ -169,6 +179,8 @@ languages:
|
|||||||
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
|
||||||
customArgsInput: Custom yt-dlp arguments
|
customArgsInput: Custom yt-dlp arguments
|
||||||
rpcConnErr: Error while conencting to RPC server
|
rpcConnErr: Error while conencting to RPC server
|
||||||
|
splashText: No active downloads
|
||||||
|
archiveTitle: Archive
|
||||||
japanese:
|
japanese:
|
||||||
urlInput: YouTubeまたはサポート済み動画のURL
|
urlInput: YouTubeまたはサポート済み動画のURL
|
||||||
statusTitle: 状態
|
statusTitle: 状態
|
||||||
@@ -198,6 +210,8 @@ languages:
|
|||||||
customArgs: yt-dlpのオプションの有効化 (最適設定にする場合)
|
customArgs: yt-dlpのオプションの有効化 (最適設定にする場合)
|
||||||
customArgsInput: yt-dlpのオプション
|
customArgsInput: yt-dlpのオプション
|
||||||
rpcConnErr: Error while conencting to RPC server
|
rpcConnErr: Error while conencting to RPC server
|
||||||
|
splashText: No active downloads
|
||||||
|
archiveTitle: Archive
|
||||||
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
|
||||||
@@ -226,3 +240,5 @@ languages:
|
|||||||
customArgs: Habilitar els arguments yt-dlp personalitzats (un gran poder comporta una gran responsabilitat)
|
customArgs: Habilitar els arguments yt-dlp personalitzats (un gran poder comporta una gran responsabilitat)
|
||||||
customArgsInput: Arguments yt-dlp personalitzats
|
customArgsInput: Arguments yt-dlp personalitzats
|
||||||
rpcConnErr: Error en connectar-se al servidor RPC
|
rpcConnErr: Error en connectar-se al servidor RPC
|
||||||
|
splashText: No active downloads
|
||||||
|
archiveTitle: Archive
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import Toolbar from '@mui/material/Toolbar'
|
|||||||
import { TransitionProps } from '@mui/material/transitions'
|
import { TransitionProps } from '@mui/material/transitions'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import { Buffer } from 'buffer'
|
import { Buffer } from 'buffer'
|
||||||
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react'
|
import { forwardRef, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import FormatsGrid from '../components/FormatsGrid'
|
import FormatsGrid from '../components/FormatsGrid'
|
||||||
import { CliArguments } from '../lib/argsParser'
|
import { CliArguments } from '../lib/argsParser'
|
||||||
import I18nBuilder from '../lib/intl'
|
import { I18nContext } from '../providers/i18nProvider'
|
||||||
import { RPCClient } from '../lib/rpcClient'
|
import { RPCClientContext } from '../providers/rpcClientProvider'
|
||||||
import { RootState } from '../stores/store'
|
import { RootState } from '../stores/store'
|
||||||
import type { DLMetadata } from '../types'
|
import type { DLMetadata } from '../types'
|
||||||
import { isValidURL, toFormatArgs } from '../utils'
|
import { isValidURL, toFormatArgs } from '../utils'
|
||||||
@@ -67,10 +67,12 @@ export default function DownloadDialog({ open, onClose }: Props) {
|
|||||||
const [workingUrl, setWorkingUrl] = useState('')
|
const [workingUrl, setWorkingUrl] = useState('')
|
||||||
|
|
||||||
// memos
|
// memos
|
||||||
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language])
|
|
||||||
const client = useMemo(() => new RPCClient(), [settings.serverAddr, settings.serverPort])
|
|
||||||
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs])
|
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs])
|
||||||
|
|
||||||
|
// context
|
||||||
|
const { i18n } = useContext(I18nContext)
|
||||||
|
const { client } = useContext(RPCClientContext)
|
||||||
|
|
||||||
// refs
|
// refs
|
||||||
const urlInputRef = useRef<HTMLInputElement>(null)
|
const urlInputRef = useRef<HTMLInputElement>(null)
|
||||||
const customFilenameInputRef = useRef<HTMLInputElement>(null)
|
const customFilenameInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
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 { useContext } from 'react'
|
||||||
|
import { I18nContext } from '../providers/i18nProvider'
|
||||||
|
|
||||||
const FlexContainer = styled(Container)({
|
const FlexContainer = styled(Container)({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -19,6 +21,8 @@ const Title = styled(Typography)({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export default function Splash() {
|
export default function Splash() {
|
||||||
|
const { i18n } = useContext(I18nContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlexContainer>
|
<FlexContainer>
|
||||||
<Title fontWeight={'500'} fontSize={72} color={'gray'}>
|
<Title fontWeight={'500'} fontSize={72} color={'gray'}>
|
||||||
@@ -27,7 +31,7 @@ export default function Splash() {
|
|||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
</Title>
|
</Title>
|
||||||
<Title fontWeight={'500'} fontSize={36} color={'gray'}>
|
<Title fontWeight={'500'} fontSize={36} color={'gray'}>
|
||||||
No active downloads
|
{i18n.t('splashText')}
|
||||||
</Title>
|
</Title>
|
||||||
</FlexContainer>
|
</FlexContainer>
|
||||||
)
|
)
|
||||||
|
|||||||
30
frontend/src/providers/i18nProvider.tsx
Normal file
30
frontend/src/providers/i18nProvider.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { createContext, useMemo } from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import I18nBuilder from '../lib/intl'
|
||||||
|
import { RootState, store } from '../stores/store'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Context {
|
||||||
|
i18n: I18nBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
export const I18nContext = createContext<Context>({
|
||||||
|
i18n: new I18nBuilder(store.getState().settings.language)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function I18nProvider({ children }: Props) {
|
||||||
|
const settings = useSelector((state: RootState) => state.settings)
|
||||||
|
|
||||||
|
const i18n = useMemo(() => new I18nBuilder(
|
||||||
|
settings.language
|
||||||
|
), [settings.language])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<I18nContext.Provider value={{ i18n }}>
|
||||||
|
{children}
|
||||||
|
</I18nContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
31
frontend/src/providers/rpcClientProvider.tsx
Normal file
31
frontend/src/providers/rpcClientProvider.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { createContext, useMemo } from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import { RPCClient } from '../lib/rpcClient'
|
||||||
|
import { RootState } from '../stores/store'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Context {
|
||||||
|
client: RPCClient
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RPCClientContext = createContext<Context>({
|
||||||
|
client: new RPCClient()
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function RPCCLientProvider({ children }: Props) {
|
||||||
|
const settings = useSelector((state: RootState) => state.settings)
|
||||||
|
|
||||||
|
const client = useMemo(() => new RPCClient(), [
|
||||||
|
settings.serverAddr,
|
||||||
|
settings.serverPort,
|
||||||
|
])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RPCClientContext.Provider value={{ client }}>
|
||||||
|
{children}
|
||||||
|
</RPCClientContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'
|
|||||||
import VideoFileIcon from '@mui/icons-material/VideoFile'
|
import VideoFileIcon from '@mui/icons-material/VideoFile'
|
||||||
|
|
||||||
import { Buffer } from 'buffer'
|
import { Buffer } from 'buffer'
|
||||||
import { useEffect, useMemo, useState, useTransition } from 'react'
|
import { useContext, useEffect, useMemo, useState, useTransition } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { BehaviorSubject, Subject, combineLatestWith, map, share } from 'rxjs'
|
import { BehaviorSubject, Subject, combineLatestWith, map, share } from 'rxjs'
|
||||||
import { useObservable } from '../hooks/observable'
|
import { useObservable } from '../hooks/observable'
|
||||||
@@ -36,11 +36,14 @@ import { DeleteRequest, DirectoryEntry } from '../types'
|
|||||||
import { roundMiB } from '../utils'
|
import { roundMiB } from '../utils'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { ffetch } from '../lib/httpClient'
|
import { ffetch } from '../lib/httpClient'
|
||||||
|
import { I18nContext } from '../providers/i18nProvider'
|
||||||
|
|
||||||
export default function Downloaded() {
|
export default function Downloaded() {
|
||||||
const settings = useSelector((state: RootState) => state.settings)
|
const settings = useSelector((state: RootState) => state.settings)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const { i18n } = useContext(I18nContext)
|
||||||
|
|
||||||
const [openDialog, setOpenDialog] = useState(false)
|
const [openDialog, setOpenDialog] = useState(false)
|
||||||
|
|
||||||
const serverAddr =
|
const serverAddr =
|
||||||
@@ -160,7 +163,7 @@ export default function Downloaded() {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
}}>
|
}}>
|
||||||
<Typography py={1} variant="h5" color="primary">
|
<Typography py={1} variant="h5" color="primary">
|
||||||
{'Archive'}
|
{i18n.t('archiveTitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<List sx={{ width: '100%', bgcolor: 'background.paper' }}>
|
<List sx={{ width: '100%', bgcolor: 'background.paper' }}>
|
||||||
{selectable.length === 0 && 'No files found'}
|
{selectable.length === 0 && 'No files found'}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
SpeedDialIcon,
|
SpeedDialIcon,
|
||||||
styled
|
styled
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useContext, useEffect, useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import DownloadDialog from '../components/DownloadDialog'
|
import DownloadDialog from '../components/DownloadDialog'
|
||||||
import { DownloadsCardView } from '../components/DownloadsCardView'
|
import { DownloadsCardView } from '../components/DownloadsCardView'
|
||||||
@@ -20,8 +20,9 @@ import { DownloadsListView } from '../components/DownloadsListView'
|
|||||||
import Splash from '../components/Splash'
|
import Splash from '../components/Splash'
|
||||||
import { toggleListView } from '../features/settings/settingsSlice'
|
import { toggleListView } from '../features/settings/settingsSlice'
|
||||||
import { connected, setFreeSpace } from '../features/status/statusSlice'
|
import { connected, setFreeSpace } from '../features/status/statusSlice'
|
||||||
import I18nBuilder from '../lib/intl'
|
import { socket$ } from '../lib/rpcClient'
|
||||||
import { RPCClient, socket$ } from '../lib/rpcClient'
|
import { I18nContext } from '../providers/i18nProvider'
|
||||||
|
import { RPCClientContext } from '../providers/rpcClientProvider'
|
||||||
import { RootState } from '../stores/store'
|
import { RootState } from '../stores/store'
|
||||||
import type { RPCResponse, RPCResult } from '../types'
|
import type { RPCResponse, RPCResult } from '../types'
|
||||||
import { dateTimeComparatorFunc } from '../utils'
|
import { dateTimeComparatorFunc } from '../utils'
|
||||||
@@ -41,9 +42,9 @@ export default function Home() {
|
|||||||
const [openDialog, setOpenDialog] = useState(false)
|
const [openDialog, setOpenDialog] = useState(false)
|
||||||
const [socketHasError, setSocketHasError] = useState(false)
|
const [socketHasError, setSocketHasError] = useState(false)
|
||||||
|
|
||||||
// memos
|
// context
|
||||||
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language])
|
const { i18n } = useContext(I18nContext)
|
||||||
const client = useMemo(() => new RPCClient(), [settings.serverAddr, settings.serverPort])
|
const { client } = useContext(RPCClientContext)
|
||||||
|
|
||||||
/* -------------------- Effects -------------------- */
|
/* -------------------- Effects -------------------- */
|
||||||
|
|
||||||
@@ -120,7 +121,6 @@ export default function Home() {
|
|||||||
client.killAll()
|
client.killAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -------------------- styled components -------------------- */
|
/* -------------------- styled components -------------------- */
|
||||||
|
|
||||||
const Input = styled('input')({
|
const Input = styled('input')({
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useContext, useEffect, useMemo, useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import {
|
import {
|
||||||
Subject,
|
Subject,
|
||||||
@@ -26,9 +26,6 @@ import {
|
|||||||
map,
|
map,
|
||||||
takeWhile
|
takeWhile
|
||||||
} from 'rxjs'
|
} from 'rxjs'
|
||||||
import { CliArguments } from '../lib/argsParser'
|
|
||||||
import I18nBuilder from '../lib/intl'
|
|
||||||
import { RPCClient } from '../lib/rpcClient'
|
|
||||||
import {
|
import {
|
||||||
LanguageUnion,
|
LanguageUnion,
|
||||||
ThemeUnion,
|
ThemeUnion,
|
||||||
@@ -43,6 +40,9 @@ import {
|
|||||||
setTheme
|
setTheme
|
||||||
} from '../features/settings/settingsSlice'
|
} from '../features/settings/settingsSlice'
|
||||||
import { updated } from '../features/status/statusSlice'
|
import { updated } from '../features/status/statusSlice'
|
||||||
|
import { CliArguments } from '../lib/argsParser'
|
||||||
|
import { I18nContext } from '../providers/i18nProvider'
|
||||||
|
import { RPCClientContext } from '../providers/rpcClientProvider'
|
||||||
import { RootState } from '../stores/store'
|
import { RootState } from '../stores/store'
|
||||||
import { validateDomain, validateIP } from '../utils'
|
import { validateDomain, validateIP } from '../utils'
|
||||||
|
|
||||||
@@ -54,9 +54,9 @@ export default function Settings() {
|
|||||||
|
|
||||||
const [invalidIP, setInvalidIP] = useState(false);
|
const [invalidIP, setInvalidIP] = useState(false);
|
||||||
|
|
||||||
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language])
|
const { i18n } = useContext(I18nContext)
|
||||||
|
const { client } = useContext(RPCClientContext)
|
||||||
|
|
||||||
const client = useMemo(() => new RPCClient(), [])
|
|
||||||
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [])
|
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [])
|
||||||
|
|
||||||
const serverAddr$ = useMemo(() => new Subject<string>(), [])
|
const serverAddr$ = useMemo(() => new Subject<string>(), [])
|
||||||
|
|||||||
Reference in New Issue
Block a user