From a2bb1220a99ea63d2904d0226b4424bde94b6210 Mon Sep 17 00:00:00 2001 From: Igor Katson Date: Sat, 2 Dec 2023 14:49:44 +0000 Subject: [PATCH] Strict typescript --- crates/librqbit/webui/src/api-types.ts | 2 +- .../webui/src/{api.ts => http-api.ts} | 2 +- crates/librqbit/webui/src/main.tsx | 4 +- crates/librqbit/webui/src/rqbit-web.tsx | 116 +++++++++++------- crates/librqbit/webui/tsconfig.json | 2 +- 5 files changed, 78 insertions(+), 48 deletions(-) rename crates/librqbit/webui/src/{api.ts => http-api.ts} (98%) diff --git a/crates/librqbit/webui/src/api-types.ts b/crates/librqbit/webui/src/api-types.ts index 5715a5d..bb61424 100644 --- a/crates/librqbit/webui/src/api-types.ts +++ b/crates/librqbit/webui/src/api-types.ts @@ -112,7 +112,7 @@ export interface RqbitAPI { listOnly?: boolean, selectedFiles?: Array, unpopularTorrent?: boolean, - initialPeers?: Array, + initialPeers?: Array | null, }) => Promise; pause: (index: number) => Promise; diff --git a/crates/librqbit/webui/src/api.ts b/crates/librqbit/webui/src/http-api.ts similarity index 98% rename from crates/librqbit/webui/src/api.ts rename to crates/librqbit/webui/src/http-api.ts index 56b89ce..a52fc87 100644 --- a/crates/librqbit/webui/src/api.ts +++ b/crates/librqbit/webui/src/http-api.ts @@ -59,7 +59,7 @@ export const API: RqbitAPI = { listOnly?: boolean, selectedFiles?: Array, unpopularTorrent?: boolean, - initialPeers?: Array, + initialPeers?: Array | null, }): Promise => { opts = opts || {}; let url = '/torrents?&overwrite=true'; diff --git a/crates/librqbit/webui/src/main.tsx b/crates/librqbit/webui/src/main.tsx index e7eb463..9ca117d 100644 --- a/crates/librqbit/webui/src/main.tsx +++ b/crates/librqbit/webui/src/main.tsx @@ -1,9 +1,9 @@ import { StrictMode } from "react"; import ReactDOM from 'react-dom/client'; import { RqbitWebUI } from "./rqbit-web"; -import { API } from "./api"; +import { API } from "./http-api"; globalThis.API = API; -const torrentsContainer = document.getElementById('app'); +const torrentsContainer = document.getElementById('app') as HTMLInputElement; ReactDOM.createRoot(torrentsContainer).render(); diff --git a/crates/librqbit/webui/src/rqbit-web.tsx b/crates/librqbit/webui/src/rqbit-web.tsx index 867ebd9..7658016 100644 --- a/crates/librqbit/webui/src/rqbit-web.tsx +++ b/crates/librqbit/webui/src/rqbit-web.tsx @@ -1,6 +1,6 @@ -import { createContext, useContext, useEffect, useRef, useState } from 'react'; +import { MouseEventHandler, RefObject, createContext, useContext, useEffect, useRef, useState } from 'react'; import { ProgressBar, Button, Container, Row, Col, Alert, Modal, Form, Spinner } from 'react-bootstrap'; -import { AddTorrentResponse, TorrentDetails, TorrentId, TorrentStats, ErrorDetails as ApiErrorDetails, STATE_INITIALIZING, STATE_LIVE, STATE_PAUSED, STATE_ERROR, RqbitAPI } from './api-types'; +import { AddTorrentResponse, TorrentDetails, TorrentId, TorrentStats, ErrorDetails as ApiErrorDetails, STATE_INITIALIZING, STATE_LIVE, STATE_PAUSED, STATE_ERROR, RqbitAPI, ErrorDetails } from './api-types'; declare const API: RqbitAPI; @@ -10,12 +10,15 @@ interface Error { } interface ContextType { - setCloseableError: (error: Error) => void, + setCloseableError: (error: Error | null) => void, refreshTorrents: () => void, } -const AppContext = createContext(null); -const RefreshTorrentStatsContext = createContext<{ refresh: () => void }>(null); +const AppContext = createContext({ + setCloseableError: (_) => { }, + refreshTorrents: () => { }, +}); +const RefreshTorrentStatsContext = createContext({ refresh: () => { } }); const IconButton: React.FC<{ className: string, @@ -23,7 +26,7 @@ const IconButton: React.FC<{ disabled?: boolean, color?: string, }> = ({ className, onClick, disabled, color }) => { - const onClickStopPropagation = (e) => { + const onClickStopPropagation: MouseEventHandler = (e) => { e.stopPropagation(); if (disabled) { return; @@ -33,12 +36,16 @@ const IconButton: React.FC<{ return } -const DeleteTorrentModal = ({ id, show, onHide }) => { +const DeleteTorrentModal: React.FC<{ + id: number, + show: boolean, + onHide: () => void +}> = ({ id, show, onHide }) => { if (!show) { return null; } const [deleteFiles, setDeleteFiles] = useState(false); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const [deleting, setDeleting] = useState(false); const ctx = useContext(AppContext); @@ -152,7 +159,9 @@ const TorrentActions: React.FC<{ } const TorrentRow: React.FC<{ - id: number, detailsResponse: TorrentDetails, statsResponse: TorrentStats + id: number, + detailsResponse: TorrentDetails | null, + statsResponse: TorrentStats | null }> = ({ id, detailsResponse, statsResponse }) => { const state = statsResponse?.state ?? ""; const error = statsResponse?.error; @@ -182,7 +191,7 @@ const TorrentRow: React.FC<{ case STATE_ERROR: return 'Error'; } - return statsResponse.live?.download_speed.human_readable ?? "N/A"; + return statsResponse?.live?.download_speed.human_readable ?? "N/A"; } let classNames = []; @@ -242,9 +251,12 @@ const Column: React.FC<{ ); -const Torrent = ({ id, torrent }) => { - const [detailsResponse, updateDetailsResponse] = useState(null); - const [statsResponse, updateStatsResponse] = useState(null); +const Torrent: React.FC<{ + id: number, + torrent: TorrentId +}> = ({ id, torrent }) => { + const [detailsResponse, updateDetailsResponse] = useState(null); + const [statsResponse, updateStatsResponse] = useState(null); const [forceStatsRefresh, setForceStatsRefresh] = useState(0); const forceStatsRefreshCallback = () => { @@ -288,7 +300,7 @@ const Torrent = ({ id, torrent }) => { } -const TorrentsList = (props: { torrents: Array, loading: boolean }) => { +const TorrentsList = (props: { torrents: Array | null, loading: boolean }) => { if (props.torrents === null && props.loading) { return } @@ -310,10 +322,10 @@ const TorrentsList = (props: { torrents: Array, loading: boolean }) = }; export const RqbitWebUI = () => { - const [closeableError, setCloseableError] = useState(null); - const [otherError, setOtherError] = useState(null); + const [closeableError, setCloseableError] = useState(null); + const [otherError, setOtherError] = useState(null); - const [torrents, setTorrents] = useState>(null); + const [torrents, setTorrents] = useState | null>(null); const [torrentsLoading, setTorrentsLoading] = useState(false); const refreshTorrents = async () => { @@ -351,7 +363,7 @@ export const RqbitWebUI = () => { } -const ErrorDetails = (props: { details: ApiErrorDetails }) => { +const ErrorDetails = (props: { details: ApiErrorDetails | null | undefined }) => { let { details } = props; if (!details) { return null; @@ -363,7 +375,7 @@ const ErrorDetails = (props: { details: ApiErrorDetails }) => { } -const ErrorComponent = (props: { error: Error, remove?: () => void }) => { +const ErrorComponent = (props: { error: Error | null, remove?: () => void }) => { let { error, remove } = props; if (error == null) { @@ -377,13 +389,16 @@ const ErrorComponent = (props: { error: Error, remove?: () => void }) => { ); }; -const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => { +const UploadButton: React.FC<{ + buttonText: string, + onClick: () => void, + data: string | File | null, + resetData: () => void, + variant: string, +}> = ({ buttonText, onClick, data, resetData, variant }) => { const [loading, setLoading] = useState(false); - const [listTorrentResponse, setListTorrentResponse] = useState(null); - const [listTorrentError, setListTorrentError] = useState(null); - const ctx = useContext(AppContext); - - const showModal = data !== null || listTorrentError !== null; + const [listTorrentResponse, setListTorrentResponse] = useState(null); + const [listTorrentError, setListTorrentError] = useState(null); // Get the torrent file list if there's data. useEffect(() => { @@ -397,7 +412,7 @@ const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => { const response = await API.uploadTorrent(data, { listOnly: true }); setListTorrentResponse(response); } catch (e) { - setListTorrentError({ text: 'Error listing torrent files', details: e }); + setListTorrentError({ text: 'Error listing torrent files', details: e as ErrorDetails }); } finally { setLoading(false); } @@ -418,14 +433,13 @@ const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => { {buttonText} - + />} ); }; @@ -463,9 +477,9 @@ const UrlPromptModal: React.FC<{ } const MagnetInput = () => { - let [magnet, setMagnet] = useState(null); + let [magnet, setMagnet] = useState(null); - let [showModal, setShowModal] = useState(null); + let [showModal, setShowModal] = useState(false); const onClick = () => { const m = prompt('Enter magnet link or HTTP(s) URL'); @@ -487,11 +501,11 @@ const MagnetInput = () => { { - setShowModal(null); + setShowModal(false); setMagnet(url); }} cancel={() => { - setShowModal(null); + setShowModal(false); setMagnet(null); }} /> @@ -499,20 +513,29 @@ const MagnetInput = () => { }; const FileInput = () => { - const inputRef = useRef(); - const [file, setFile] = useState(null); + const inputRef = useRef() as RefObject; + const [file, setFile] = useState(null); const onFileChange = async () => { + if (!inputRef?.current?.files) { + return; + } const file = inputRef.current.files[0]; setFile(file); }; const reset = () => { + if (!inputRef?.current) { + return; + } inputRef.current.value = ''; setFile(null); } const onClick = () => { + if (!inputRef?.current) { + return; + } inputRef.current.click(); } @@ -531,18 +554,17 @@ const FileInput = () => { }; const FileSelectionModal = (props: { - show: boolean, onHide: () => void, - listTorrentResponse: AddTorrentResponse, - listTorrentError: Error, + listTorrentResponse: AddTorrentResponse | null, + listTorrentError: Error | null, listTorrentLoading: boolean, data: string | File, }) => { - let { show, onHide, listTorrentResponse, listTorrentError, listTorrentLoading, data } = props; + let { onHide, listTorrentResponse, listTorrentError, listTorrentLoading, data } = props; - const [selectedFiles, setSelectedFiles] = useState([]); + const [selectedFiles, setSelectedFiles] = useState([]); const [uploading, setUploading] = useState(false); - const [uploadError, setUploadError] = useState(null); + const [uploadError, setUploadError] = useState(null); const [unpopularTorrent, setUnpopularTorrent] = useState(false); const ctx = useContext(AppContext); @@ -566,6 +588,9 @@ const FileSelectionModal = (props: { }; const handleUpload = async () => { + if (!listTorrentResponse) { + return; + } setUploading(true); let initialPeers = listTorrentResponse.seen_peers ? listTorrentResponse.seen_peers.slice(0, 32) : null; API.uploadTorrent(data, { selectedFiles, unpopularTorrent, initialPeers }).then(() => { @@ -616,7 +641,7 @@ const FileSelectionModal = (props: { }; return ( - + Add torrent @@ -646,7 +671,12 @@ const Buttons = () => { ); }; -const RootContent = (props: { closeableError: ApiErrorDetails, otherError: ApiErrorDetails, torrents: Array, torrentsLoading: boolean }) => { +const RootContent = (props: { + closeableError: ApiErrorDetails | null, + otherError: ApiErrorDetails | null, + torrents: Array | null, + torrentsLoading: boolean +}) => { let ctx = useContext(AppContext); return ctx.setCloseableError(null)} /> diff --git a/crates/librqbit/webui/tsconfig.json b/crates/librqbit/webui/tsconfig.json index aab5593..ec0c6a1 100644 --- a/crates/librqbit/webui/tsconfig.json +++ b/crates/librqbit/webui/tsconfig.json @@ -6,9 +6,9 @@ "noEmit": true, "allowJs": true, "checkJs": true, - /* Preact Config */ "jsx": "react-jsx", "skipLibCheck": true, + "strict": true, }, "include": [ "node_modules/vite/client.d.ts",