From ee996012afb068c3f8b900a6495bade039b1f77d Mon Sep 17 00:00:00 2001 From: Igor Katson Date: Wed, 6 Dec 2023 15:29:11 +0000 Subject: [PATCH] rqbit desktop configuration before startup works --- crates/librqbit/webui/src/rqbit-web.tsx | 4 +- desktop/src/api.tsx | 2 +- desktop/src/configuration.tsx | 45 ++++ desktop/src/configure.tsx | 266 ++++++++++++++++++++++++ desktop/src/main.tsx | 31 ++- 5 files changed, 340 insertions(+), 8 deletions(-) create mode 100644 desktop/src/configuration.tsx create mode 100644 desktop/src/configure.tsx diff --git a/crates/librqbit/webui/src/rqbit-web.tsx b/crates/librqbit/webui/src/rqbit-web.tsx index ac87ac0..6e077e7 100644 --- a/crates/librqbit/webui/src/rqbit-web.tsx +++ b/crates/librqbit/webui/src/rqbit-web.tsx @@ -2,7 +2,7 @@ import { MouseEventHandler, RefObject, createContext, useContext, useEffect, use 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, AddTorrentOptions } from './api-types'; -interface Error { +export interface Error { text: string, details?: ApiErrorDetails, } @@ -409,7 +409,7 @@ const ErrorDetails = (props: { details: ApiErrorDetails | null | undefined }) => } -const ErrorComponent = (props: { error: Error | null, remove?: () => void }) => { +export const ErrorComponent = (props: { error: Error | null, remove?: () => void }) => { let { error, remove } = props; if (error == null) { diff --git a/desktop/src/api.tsx b/desktop/src/api.tsx index e64531c..8bcb7e0 100644 --- a/desktop/src/api.tsx +++ b/desktop/src/api.tsx @@ -23,7 +23,7 @@ function errorToUIError(path: string): (e: InvokeErrorResponse) => Promise(name: string, params?: InvokeArgs): Promise { +export async function invokeAPI(name: string, params?: InvokeArgs): Promise { console.log("invoking", name, params); const result = await invoke(name, params).catch(errorToUIError(name)); console.log(result); diff --git a/desktop/src/configuration.tsx b/desktop/src/configuration.tsx new file mode 100644 index 0000000..a21552f --- /dev/null +++ b/desktop/src/configuration.tsx @@ -0,0 +1,45 @@ +type PathLike = string; +type Duration = string; +type SocketAddr = string; + +interface RqbitDesktopConfigDht { + disable: boolean; + disable_persistence: boolean; + persistence_filename: PathLike; +} + +interface RqbitDesktopConfigTcpListen { + disable: boolean; + min_port: number; + max_port: number; +} + +interface RqbitDesktopConfigPersistence { + disable: boolean; + filename: PathLike; +} + +interface RqbitDesktopConfigPeerOpts { + connect_timeout: Duration; + read_write_timeout: Duration; +} + +interface RqbitDesktopConfigHttpApi { + disable: boolean; + listen_addr: SocketAddr; + read_only: boolean; +} + +interface RqbitDesktopConfigUpnp { + disable: boolean; +} + +export interface RqbitDesktopConfig { + default_download_location: PathLike; + dht: RqbitDesktopConfigDht; + tcp_listen: RqbitDesktopConfigTcpListen; + upnp: RqbitDesktopConfigUpnp; + persistence: RqbitDesktopConfigPersistence; + peer_opts: RqbitDesktopConfigPeerOpts; + http_api: RqbitDesktopConfigHttpApi; +} diff --git a/desktop/src/configure.tsx b/desktop/src/configure.tsx new file mode 100644 index 0000000..2452a7e --- /dev/null +++ b/desktop/src/configure.tsx @@ -0,0 +1,266 @@ +import React, { useState } from "react"; +import { RqbitDesktopConfig } from "./configuration"; +import { Button, Form, Modal } from "react-bootstrap"; +import { ErrorComponent } from "./rqbit-webui-src/rqbit-web"; +import { invokeAPI } from "./api"; +import { ErrorDetails } from "./rqbit-webui-src/api-types"; + +export const ConfigModal: React.FC<{ + handleOk: (config: RqbitDesktopConfig) => void, + initialConfig: RqbitDesktopConfig, +}> = ({ handleOk, initialConfig }) => { + const [config, setConfig] = useState(initialConfig); + const [error, setError] = useState(null); + + const handleInputChange = (e: any) => { + const name: string = e.target.name; + const value: any = e.target.value; + const [mainField, subField] = name.split('.', 2); + + if (subField) { + setConfig((prevConfig: any) => ({ + ...prevConfig, + [mainField]: { + ...prevConfig[mainField], + [subField]: value, + }, + })); + } else { + setConfig((prevConfig) => ({ + ...prevConfig, + [name]: value, + })); + } + }; + + const handleToggleChange = (e: any) => { + const name: string = e.target.name; + const [mainField, subField] = name.split('.', 2); + + if (subField) { + setConfig((prevConfig: any) => ({ + ...prevConfig, + [mainField]: { + ...prevConfig[mainField], + [subField]: !prevConfig[mainField][subField], + }, + })); + } else { + setConfig((prevConfig: any) => ({ + ...prevConfig, + [name]: !prevConfig[name], + })); + } + }; + + const handleOkClick = () => { + setError(null); + invokeAPI<{}>("config_change", { config }).then( + () => handleOk(config), + (e: ErrorDetails) => { + setError({ + text: "Error saving configuration", + details: e, + }); + } + ) + }; + + return ( + + + Configure Rqbit desktop + + + +
+
+ + Default Download Location + + +
+ +
+ DHT config + + + + + + + + + + + Persistence Filename + + +
+ +
+ + TCP Listener config + + + + + + + TCP Listen Min Port + + + + + TCP Listen Max Port + + + + + + + +
+ + +
+ Session persistence + + + + + + + Persistence Filename + + + +
+ +
+ Peer connection options + + + Peer Options Connect Timeout + + + + + Peer Options Read Write Timeout + + +
+ +
+ HTTP API config + + + + + + + HTTP API Listen Address + + + + + + + +
+ +
+
+ + + + +
+ ); +}; diff --git a/desktop/src/main.tsx b/desktop/src/main.tsx index a67f5ac..b74c247 100644 --- a/desktop/src/main.tsx +++ b/desktop/src/main.tsx @@ -1,19 +1,40 @@ -import { StrictMode } from "react"; +import { StrictMode, useState } from "react"; import ReactDOM from 'react-dom/client'; import { APIContext, RqbitWebUI } from "./rqbit-webui-src/rqbit-web"; import { API } from "./api"; import { invoke } from "@tauri-apps/api"; +import { RqbitDesktopConfig } from "./configuration"; +import { ConfigModal } from "./configure"; -let version = invoke("get_version").then((version) => { +async function get_version(): Promise { + return invoke("get_version"); +} + +async function get_default_config(): Promise { + return invoke("config_default"); +} + +const RqbitDesktop: React.FC<{ + version: string, + defaultConfig: RqbitDesktopConfig, +}> = ({ version, defaultConfig }) => { + let [configured, setConfigured] = useState(false); + + if (configured) { + return + } + return setConfigured(true)} initialConfig={defaultConfig}>; +} + +Promise.all([get_version(), get_default_config()]).then(([version, config]) => { ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - + ); -}); - +})