migrated from redux to recoil
This commit is contained in:
@@ -1,37 +0,0 @@
|
||||
import {
|
||||
Card,
|
||||
CardActionArea,
|
||||
CardContent,
|
||||
CardMedia,
|
||||
Skeleton,
|
||||
Typography
|
||||
} from '@mui/material'
|
||||
import { ellipsis } from '../utils'
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
thumbnail: string,
|
||||
url: string,
|
||||
}
|
||||
|
||||
export function ArchiveResult({ title, thumbnail, url }: Props) {
|
||||
return (
|
||||
<Card>
|
||||
<CardActionArea onClick={() => window.open(url)}>
|
||||
{thumbnail ?
|
||||
<CardMedia
|
||||
component="img"
|
||||
height={180}
|
||||
image={thumbnail}
|
||||
/> :
|
||||
<Skeleton variant="rectangular" height={180} />
|
||||
}
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="body2" component="div">
|
||||
{ellipsis(title, 72)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -26,19 +26,19 @@ import { TransitionProps } from '@mui/material/transitions'
|
||||
import { Buffer } from 'buffer'
|
||||
import {
|
||||
forwardRef,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
useTransition
|
||||
} from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { settingsState } from '../atoms/settings'
|
||||
import { connectedState } from '../atoms/status'
|
||||
import FormatsGrid from '../components/FormatsGrid'
|
||||
import { useI18n } from '../hooks/useI18n'
|
||||
import { useRPC } from '../hooks/useRPC'
|
||||
import { CliArguments } from '../lib/argsParser'
|
||||
import { I18nContext } from '../providers/i18nProvider'
|
||||
import { RPCClientContext } from '../providers/rpcClientProvider'
|
||||
import { RootState } from '../stores/store'
|
||||
import type { DLMetadata } from '../types'
|
||||
import { isValidURL, toFormatArgs } from '../utils'
|
||||
|
||||
@@ -62,9 +62,9 @@ export default function DownloadDialog({
|
||||
onClose,
|
||||
onDownloadStart
|
||||
}: Props) {
|
||||
// redux state
|
||||
const settings = useSelector((state: RootState) => state.settings)
|
||||
const status = useSelector((state: RootState) => state.status)
|
||||
// recoil state
|
||||
const settings = useRecoilValue(settingsState)
|
||||
const [isConnected] = useRecoilState(connectedState)
|
||||
|
||||
// ephemeral state
|
||||
const [downloadFormats, setDownloadFormats] = useState<DLMetadata>()
|
||||
@@ -85,11 +85,12 @@ export default function DownloadDialog({
|
||||
|
||||
// memos
|
||||
const cliArgs = useMemo(() =>
|
||||
new CliArguments().fromString(settings.cliArgs), [settings.cliArgs])
|
||||
new CliArguments().fromString(settings.cliArgs), [settings.cliArgs]
|
||||
)
|
||||
|
||||
// context
|
||||
const { i18n } = useContext(I18nContext)
|
||||
const { client } = useContext(RPCClientContext)
|
||||
const { i18n } = useI18n()
|
||||
const { client } = useRPC()
|
||||
|
||||
// refs
|
||||
const urlInputRef = useRef<HTMLInputElement>(null)
|
||||
@@ -254,7 +255,7 @@ export default function DownloadDialog({
|
||||
variant="outlined"
|
||||
onChange={handleUrlChange}
|
||||
disabled={
|
||||
!status.connected
|
||||
!isConnected
|
||||
|| (settings.formatSelection && downloadFormats != null)
|
||||
}
|
||||
InputProps={{
|
||||
@@ -290,7 +291,10 @@ export default function DownloadDialog({
|
||||
variant="outlined"
|
||||
onChange={handleCustomArgsChange}
|
||||
value={customArgs}
|
||||
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
|
||||
disabled={
|
||||
!isConnected ||
|
||||
(settings.formatSelection && downloadFormats != null)
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
}
|
||||
@@ -304,7 +308,10 @@ export default function DownloadDialog({
|
||||
variant="outlined"
|
||||
value={fileNameOverride}
|
||||
onChange={handleFilenameOverrideChange}
|
||||
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
|
||||
disabled={
|
||||
!isConnected ||
|
||||
(settings.formatSelection && downloadFormats != null)
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
}
|
||||
@@ -338,7 +345,11 @@ export default function DownloadDialog({
|
||||
: sendUrl()
|
||||
}
|
||||
>
|
||||
{settings.formatSelection ? i18n.t('selectFormatButton') : i18n.t('startButton')}
|
||||
{
|
||||
settings.formatSelection
|
||||
? i18n.t('selectFormatButton')
|
||||
: i18n.t('startButton')
|
||||
}
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
|
||||
72
frontend/src/components/Downloads.tsx
Normal file
72
frontend/src/components/Downloads.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { activeDownloadsState } from '../atoms/downloads'
|
||||
import { listViewState } from '../atoms/settings'
|
||||
import { useRPC } from '../hooks/useRPC'
|
||||
import { DownloadsCardView } from './DownloadsCardView'
|
||||
import { DownloadsListView } from './DownloadsListView'
|
||||
import { useEffect } from 'react'
|
||||
import { connectedState, isDownloadingState } from '../atoms/status'
|
||||
import { datetimeCompareFunc, isRPCResponse } from '../utils'
|
||||
import { RPCResponse, RPCResult } from '../types'
|
||||
|
||||
const Downloads: React.FC = () => {
|
||||
const [active, setActive] = useRecoilState(activeDownloadsState)
|
||||
const isConnected = useRecoilValue(connectedState)
|
||||
const listView = useRecoilValue(listViewState)
|
||||
|
||||
const { client, socket$ } = useRPC()
|
||||
|
||||
const abort = (id?: string) => {
|
||||
if (id) {
|
||||
client.kill(id)
|
||||
return
|
||||
}
|
||||
client.killAll()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isConnected) { return }
|
||||
|
||||
const sub = socket$.subscribe((event: RPCResponse<RPCResult[]>) => {
|
||||
if (!isRPCResponse(event)) { return }
|
||||
if (!Array.isArray(event.result)) { return }
|
||||
|
||||
setActive(
|
||||
(event.result || [])
|
||||
.filter(f => !!f.info.url)
|
||||
.sort((a, b) => datetimeCompareFunc(
|
||||
b.info.created_at,
|
||||
a.info.created_at,
|
||||
))
|
||||
)
|
||||
})
|
||||
|
||||
return () => sub.unsubscribe()
|
||||
}, [socket$, isConnected])
|
||||
|
||||
const [, setIsDownloading] = useRecoilState(isDownloadingState)
|
||||
|
||||
useEffect(() => {
|
||||
if (active) {
|
||||
setIsDownloading(true)
|
||||
}
|
||||
}, [active?.length])
|
||||
|
||||
if (listView) {
|
||||
return (
|
||||
<DownloadsListView
|
||||
downloads={active ?? []}
|
||||
onStop={abort}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DownloadsCardView
|
||||
downloads={active ?? []}
|
||||
onStop={abort}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Downloads
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Grid } from "@mui/material"
|
||||
import { Fragment, useContext } from "react"
|
||||
import { Fragment } from "react"
|
||||
import { useToast } from "../hooks/toast"
|
||||
import { I18nContext } from "../providers/i18nProvider"
|
||||
import { useI18n } from '../hooks/useI18n'
|
||||
import type { RPCResult } from "../types"
|
||||
import { StackableResult } from "./StackableResult"
|
||||
|
||||
@@ -11,7 +11,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export function DownloadsCardView({ downloads, onStop }: Props) {
|
||||
const { i18n } = useContext(I18nContext)
|
||||
const { i18n } = useI18n()
|
||||
const { pushMessage } = useToast()
|
||||
|
||||
return (
|
||||
|
||||
51
frontend/src/components/HomeSpeedDial.tsx
Normal file
51
frontend/src/components/HomeSpeedDial.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import AddCircleIcon from '@mui/icons-material/AddCircle'
|
||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever'
|
||||
import FormatListBulleted from '@mui/icons-material/FormatListBulleted'
|
||||
import {
|
||||
SpeedDial,
|
||||
SpeedDialAction,
|
||||
SpeedDialIcon
|
||||
} from '@mui/material'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { listViewState } from '../atoms/settings'
|
||||
import { useI18n } from '../hooks/useI18n'
|
||||
import { useRPC } from '../hooks/useRPC'
|
||||
|
||||
type Props = {
|
||||
onOpen: () => void
|
||||
}
|
||||
|
||||
const HomeSpeedDial: React.FC<Props> = ({ onOpen }) => {
|
||||
const [, setListView] = useRecoilState(listViewState)
|
||||
|
||||
const { i18n } = useI18n()
|
||||
const { client } = useRPC()
|
||||
|
||||
const abort = () => client.killAll()
|
||||
|
||||
return (
|
||||
<SpeedDial
|
||||
ariaLabel="Home speed dial"
|
||||
sx={{ position: 'absolute', bottom: 32, right: 32 }}
|
||||
icon={<SpeedDialIcon />}
|
||||
>
|
||||
<SpeedDialAction
|
||||
icon={<FormatListBulleted />}
|
||||
tooltipTitle={`Table view`}
|
||||
onClick={() => setListView(state => !state)}
|
||||
/>
|
||||
<SpeedDialAction
|
||||
icon={<DeleteForeverIcon />}
|
||||
tooltipTitle={i18n.t('abortAllButton')}
|
||||
onClick={abort}
|
||||
/>
|
||||
<SpeedDialAction
|
||||
icon={<AddCircleIcon />}
|
||||
tooltipTitle={`New download`}
|
||||
onClick={onOpen}
|
||||
/>
|
||||
</SpeedDial>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeSpeedDial
|
||||
46
frontend/src/components/SocketSubscriber.tsx
Normal file
46
frontend/src/components/SocketSubscriber.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { connectedState } from '../atoms/status'
|
||||
import { useRPC } from '../hooks/useRPC'
|
||||
import { useToast } from '../hooks/toast'
|
||||
import { serverAddressAndPortState } from '../atoms/settings'
|
||||
import { useI18n } from '../hooks/useI18n'
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLBaseElement> { }
|
||||
|
||||
const SocketSubscriber: React.FC<Props> = ({ children }) => {
|
||||
const [isConnected, setIsConnected] = useRecoilState(connectedState)
|
||||
const serverAddressAndPort = useRecoilValue(serverAddressAndPortState)
|
||||
|
||||
const { i18n } = useI18n()
|
||||
const { socket$ } = useRPC()
|
||||
const { pushMessage } = useToast()
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected) { return }
|
||||
|
||||
const sub = socket$.subscribe({
|
||||
next: () => {
|
||||
setIsConnected(true)
|
||||
pushMessage(
|
||||
`Connected to (${serverAddressAndPort})`,
|
||||
"success"
|
||||
)
|
||||
},
|
||||
error: (e) => {
|
||||
console.error(e)
|
||||
pushMessage(
|
||||
`${i18n.t('rpcConnErr')} (${serverAddressAndPort})`,
|
||||
"error"
|
||||
)
|
||||
}
|
||||
})
|
||||
return () => sub.unsubscribe()
|
||||
}, [isConnected])
|
||||
|
||||
return (
|
||||
<>{children}</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SocketSubscriber
|
||||
@@ -1,7 +1,8 @@
|
||||
import CloudDownloadIcon from '@mui/icons-material/CloudDownload'
|
||||
import { Container, SvgIcon, Typography, styled } from '@mui/material'
|
||||
import { useContext } from 'react'
|
||||
import { I18nContext } from '../providers/i18nProvider'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { activeDownloadsState } from '../atoms/downloads'
|
||||
import { useI18n } from '../hooks/useI18n'
|
||||
|
||||
const FlexContainer = styled(Container)({
|
||||
display: 'flex',
|
||||
@@ -21,7 +22,12 @@ const Title = styled(Typography)({
|
||||
})
|
||||
|
||||
export default function Splash() {
|
||||
const { i18n } = useContext(I18nContext)
|
||||
const { i18n } = useI18n()
|
||||
const activeDownloads = useRecoilValue(activeDownloadsState)
|
||||
|
||||
if (!activeDownloads || activeDownloads.length !== 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<FlexContainer>
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { setTheme } from '../features/settings/settingsSlice'
|
||||
import { RootState } from '../stores/store'
|
||||
import { Brightness4, Brightness5 } from '@mui/icons-material'
|
||||
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { themeState } from '../atoms/settings'
|
||||
|
||||
export default function ThemeToggler() {
|
||||
const settings = useSelector((state: RootState) => state.settings)
|
||||
const dispatch = useDispatch()
|
||||
const [theme, setTheme] = useRecoilState(themeState)
|
||||
|
||||
return (
|
||||
<ListItemButton onClick={() => {
|
||||
settings.theme === 'light'
|
||||
? dispatch(setTheme('dark'))
|
||||
: dispatch(setTheme('light'))
|
||||
theme === 'light'
|
||||
? setTheme('dark')
|
||||
: setTheme('light')
|
||||
}}>
|
||||
<ListItemIcon>
|
||||
{
|
||||
settings.theme === 'light'
|
||||
theme === 'light'
|
||||
? <Brightness4 />
|
||||
: <Brightness5 />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user