diff --git a/crates/librqbit/src/http_api.rs b/crates/librqbit/src/http_api.rs index be3bee4..1608c40 100644 --- a/crates/librqbit/src/http_api.rs +++ b/crates/librqbit/src/http_api.rs @@ -11,6 +11,7 @@ use librqbit_core::id20::Id20; use librqbit_core::torrent_metainfo::TorrentMetaV1Info; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc::UnboundedSender; @@ -281,6 +282,7 @@ pub struct TorrentDetailsResponse { pub struct ApiAddTorrentResponse { pub id: Option, pub details: TorrentDetailsResponse, + pub seen_peers: Option>, } pub struct OnlyFiles(Vec); @@ -324,6 +326,36 @@ impl<'de> Deserialize<'de> for OnlyFiles { } } +pub struct InitialPeers(pub Vec); + +impl<'de> Deserialize<'de> for InitialPeers { + fn deserialize(deserializer: D) -> std::prelude::v1::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let string = String::deserialize(deserializer)?; + let mut addrs = Vec::new(); + for addr_str in string.split(',') { + addrs.push(SocketAddr::from_str(addr_str).map_err(D::Error::custom)?); + } + Ok(InitialPeers(addrs)) + } +} + +impl Serialize for InitialPeers { + fn serialize(&self, serializer: S) -> std::prelude::v1::Result + where + S: serde::Serializer, + { + self.0 + .iter() + .map(|s| s.to_string()) + .join(",") + .serialize(serializer) + } +} + #[derive(Serialize, Deserialize, Default)] pub struct TorrentAddQueryParams { pub overwrite: Option, @@ -333,6 +365,7 @@ pub struct TorrentAddQueryParams { pub only_files: Option, pub peer_connect_timeout: Option, pub peer_read_write_timeout: Option, + pub initial_peers: Option, pub list_only: Option, } @@ -345,6 +378,7 @@ impl TorrentAddQueryParams { output_folder: self.output_folder, sub_folder: self.sub_folder, list_only: self.list_only.unwrap_or(false), + initial_peers: self.initial_peers.map(|i| i.0), peer_opts: Some(PeerConnectionOptions { connect_timeout: self.peer_connect_timeout.map(Duration::from_secs), read_write_timeout: self.peer_read_write_timeout.map(Duration::from_secs), @@ -471,8 +505,10 @@ impl ApiInternal { info_hash, info, only_files, + seen_peers, }) => ApiAddTorrentResponse { id: None, + seen_peers: Some(seen_peers), details: make_torrent_details(&info_hash, &info, only_files.as_deref()) .context("error making torrent details")?, }, @@ -486,6 +522,7 @@ impl ApiInternal { ApiAddTorrentResponse { id: Some(id), details, + seen_peers: None, } } }; diff --git a/crates/librqbit/src/session.rs b/crates/librqbit/src/session.rs index 91fe07e..2f29302 100644 --- a/crates/librqbit/src/session.rs +++ b/crates/librqbit/src/session.rs @@ -192,7 +192,7 @@ pub struct AddTorrentOptions { pub sub_folder: Option, pub peer_opts: Option, pub force_tracker_interval: Option, - + pub initial_peers: Option>, // This is used to restore the session. pub preferred_id: Option, } @@ -201,6 +201,7 @@ pub struct ListOnlyResponse { pub info_hash: Id20, pub info: TorrentMetaV1Info, pub only_files: Option>, + pub seen_peers: Vec, } pub enum AddTorrentResponse { @@ -642,6 +643,7 @@ impl Session { info_hash, info, only_files, + seen_peers: initial_peers, })); } diff --git a/crates/librqbit/src/torrent_state/mod.rs b/crates/librqbit/src/torrent_state/mod.rs index 28035c4..554514c 100644 --- a/crates/librqbit/src/torrent_state/mod.rs +++ b/crates/librqbit/src/torrent_state/mod.rs @@ -29,6 +29,7 @@ use tracing::debug; use tracing::error; use tracing::error_span; use tracing::warn; +use tracing::trace; use url::Url; use crate::chunk_tracker::ChunkTracker; @@ -207,6 +208,7 @@ impl ManagedTorrent { { let live: Arc = live.upgrade().context("no longer live")?; + trace!("adding {} initial peers", initial_peers.len()); for peer in initial_peers { live.add_peer_if_not_seen(peer).context("torrent closed")?; } diff --git a/crates/librqbit/webui/dist/assets/index.js b/crates/librqbit/webui/dist/assets/index.js index ec39cab..afc5913 100644 --- a/crates/librqbit/webui/dist/assets/index.js +++ b/crates/librqbit/webui/dist/assets/index.js @@ -41,4 +41,4 @@ Error generating stack: `+o.message+` Copyright (c) 2018 Jed Watson. Licensed under the MIT License (MIT), see http://jedwatson.github.io/classnames -*/(function(e){(function(){var t={}.hasOwnProperty;function n(){for(var r=[],l=0;l=0)&&(n[l]=e[l]);return n}function ia(e){return"default"+e.charAt(0).toUpperCase()+e.substr(1)}function mh(e){var t=hh(e,"string");return typeof t=="symbol"?t:String(t)}function hh(e,t){if(typeof e!="object"||e===null)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||"default");if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function vh(e,t,n){var r=y.useRef(e!==void 0),l=y.useState(t),o=l[0],i=l[1],u=e!==void 0,s=r.current;return r.current=u,!u&&s&&o!==t&&i(t),[u?e:o,y.useCallback(function(a){for(var f=arguments.length,h=new Array(f>1?f-1:0),d=1;d{o.target===e&&(l(),t(o))},n+r)}function Uh(e){e.offsetHeight}const aa=e=>!e||typeof e=="function"?e:t=>{e.current=t};function Bh(e,t){const n=aa(e),r=aa(t);return l=>{n&&n(l),r&&r(l)}}function mo(e,t){return y.useMemo(()=>Bh(e,t),[e,t])}function Hh(e){return e&&"setState"in e?Nn.findDOMNode(e):e??null}const Wh=Vt.forwardRef(({onEnter:e,onEntering:t,onEntered:n,onExit:r,onExiting:l,onExited:o,addEndListener:i,children:u,childRef:s,...a},f)=>{const h=y.useRef(null),d=mo(h,s),g=N=>{d(Hh(N))},S=N=>T=>{N&&h.current&&N(h.current,T)},k=y.useCallback(S(e),[e]),R=y.useCallback(S(t),[t]),p=y.useCallback(S(n),[n]),c=y.useCallback(S(r),[r]),m=y.useCallback(S(l),[l]),w=y.useCallback(S(o),[o]),C=y.useCallback(S(i),[i]);return v.jsx(zh,{ref:f,...a,onEnter:k,onEntered:p,onEntering:R,onExit:c,onExited:w,onExiting:m,addEndListener:C,nodeRef:h,children:typeof u=="function"?(N,T)=>u(N,{...T,ref:g}):Vt.cloneElement(u,{ref:g})})}),Vh=Wh;function Qh(e){const t=y.useRef(e);return y.useEffect(()=>{t.current=e},[e]),t}function Pe(e){const t=Qh(e);return y.useCallback(function(...n){return t.current&&t.current(...n)},[t])}const Qf=e=>y.forwardRef((t,n)=>v.jsx("div",{...t,ref:n,className:M(t.className,e)})),Kf=Qf("h4");Kf.displayName="DivStyledAsH4";const Gf=y.forwardRef(({className:e,bsPrefix:t,as:n=Kf,...r},l)=>(t=H(t,"alert-heading"),v.jsx(n,{ref:l,className:M(e,t),...r})));Gf.displayName="AlertHeading";const Kh=Gf;function Gh(){return y.useState(null)}function Yh(){const e=y.useRef(!0),t=y.useRef(()=>e.current);return y.useEffect(()=>(e.current=!0,()=>{e.current=!1}),[]),t.current}function Xh(e){const t=y.useRef(null);return y.useEffect(()=>{t.current=e}),t.current}const Zh=typeof global<"u"&&global.navigator&&global.navigator.product==="ReactNative",Jh=typeof document<"u",ca=Jh||Zh?y.useLayoutEffect:y.useEffect,qh=["as","disabled"];function bh(e,t){if(e==null)return{};var n={},r=Object.keys(e),l,o;for(o=0;o=0)&&(n[l]=e[l]);return n}function ev(e){return!e||e.trim()==="#"}function Hu({tagName:e,disabled:t,href:n,target:r,rel:l,role:o,onClick:i,tabIndex:u=0,type:s}){e||(n!=null||r!=null||l!=null?e="a":e="button");const a={tagName:e};if(e==="button")return[{type:s||"button",disabled:t},a];const f=d=>{if((t||e==="a"&&ev(n))&&d.preventDefault(),t){d.stopPropagation();return}i==null||i(d)},h=d=>{d.key===" "&&(d.preventDefault(),f(d))};return e==="a"&&(n||(n="#"),t&&(n=void 0)),[{role:o??"button",disabled:void 0,tabIndex:t?void 0:u,href:n,target:e==="a"?r:void 0,"aria-disabled":t||void 0,rel:e==="a"?l:void 0,onClick:f,onKeyDown:h},a]}const tv=y.forwardRef((e,t)=>{let{as:n,disabled:r}=e,l=bh(e,qh);const[o,{tagName:i}]=Hu(Object.assign({tagName:n,disabled:r},l));return v.jsx(i,Object.assign({},l,o,{ref:t}))});tv.displayName="Button";const nv=["onKeyDown"];function rv(e,t){if(e==null)return{};var n={},r=Object.keys(e),l,o;for(o=0;o=0)&&(n[l]=e[l]);return n}function lv(e){return!e||e.trim()==="#"}const Yf=y.forwardRef((e,t)=>{let{onKeyDown:n}=e,r=rv(e,nv);const[l]=Hu(Object.assign({tagName:"a"},r)),o=Pe(i=>{l.onKeyDown(i),n==null||n(i)});return lv(r.href)||r.role==="button"?v.jsx("a",Object.assign({ref:t},r,l,{onKeyDown:o})):v.jsx("a",Object.assign({ref:t},r,{onKeyDown:n}))});Yf.displayName="Anchor";const ov=Yf,Xf=y.forwardRef(({className:e,bsPrefix:t,as:n=ov,...r},l)=>(t=H(t,"alert-link"),v.jsx(n,{ref:l,className:M(e,t),...r})));Xf.displayName="AlertLink";const iv=Xf,uv={[St]:"show",[Wt]:"show"},Zf=y.forwardRef(({className:e,children:t,transitionClasses:n={},onEnter:r,...l},o)=>{const i={in:!1,timeout:300,mountOnEnter:!1,unmountOnExit:!1,appear:!1,...l},u=y.useCallback((s,a)=>{Uh(s),r==null||r(s,a)},[r]);return v.jsx(Vh,{ref:o,addEndListener:Ah,...i,onEnter:u,childRef:t.ref,children:(s,a)=>y.cloneElement(t,{...a,className:M("fade",e,t.props.className,uv[s],n[s])})})});Zf.displayName="Fade";const Kl=Zf,sv={"aria-label":ot.string,onClick:ot.func,variant:ot.oneOf(["white"])},Wu=y.forwardRef(({className:e,variant:t,"aria-label":n="Close",...r},l)=>v.jsx("button",{ref:l,type:"button",className:M("btn-close",t&&`btn-close-${t}`,e),"aria-label":n,...r}));Wu.displayName="CloseButton";Wu.propTypes=sv;const Jf=Wu,qf=y.forwardRef((e,t)=>{const{bsPrefix:n,show:r=!0,closeLabel:l="Close alert",closeVariant:o,className:i,children:u,variant:s="primary",onClose:a,dismissible:f,transition:h=Kl,...d}=yh(e,{show:"onClose"}),g=H(n,"alert"),S=Pe(p=>{a&&a(!1,p)}),k=h===!0?Kl:h,R=v.jsxs("div",{role:"alert",...k?void 0:d,ref:t,className:M(i,g,s&&`${g}-${s}`,f&&`${g}-dismissible`),children:[f&&v.jsx(Jf,{onClick:S,"aria-label":l,variant:o}),u]});return k?v.jsx(k,{unmountOnExit:!0,...d,ref:void 0,in:r,children:R}):r?R:null});qf.displayName="Alert";const fa=Object.assign(qf,{Link:iv,Heading:Kh}),bf=y.forwardRef(({as:e,bsPrefix:t,variant:n="primary",size:r,active:l=!1,disabled:o=!1,className:i,...u},s)=>{const a=H(t,"btn"),[f,{tagName:h}]=Hu({tagName:e,disabled:o,...u}),d=h;return v.jsx(d,{...f,...u,ref:s,disabled:o,className:M(i,a,l&&"active",n&&`${a}-${n}`,r&&`${a}-${r}`,u.href&&o&&"disabled")})});bf.displayName="Button";const Mr=bf;function av(e){const t=y.useRef(e);return t.current=e,t}function ed(e){const t=av(e);y.useEffect(()=>()=>t.current(),[])}function cv(e,t){let n=0;return y.Children.map(e,r=>y.isValidElement(r)?t(r,n++):r)}function fv(e,t){return y.Children.toArray(e).some(n=>y.isValidElement(n)&&n.type===t)}function dv({as:e,bsPrefix:t,className:n,...r}){t=H(t,"col");const l=Df(),o=If(),i=[],u=[];return l.forEach(s=>{const a=r[s];delete r[s];let f,h,d;typeof a=="object"&&a!=null?{span:f,offset:h,order:d}=a:f=a;const g=s!==o?`-${s}`:"";f&&i.push(f===!0?`${t}${g}`:`${t}${g}-${f}`),d!=null&&u.push(`order${g}-${d}`),h!=null&&u.push(`offset${g}-${h}`)}),[{...r,className:M(n,...i,...u)},{as:e,bsPrefix:t,spans:i}]}const td=y.forwardRef((e,t)=>{const[{className:n,...r},{as:l="div",bsPrefix:o,spans:i}]=dv(e);return v.jsx(l,{...r,ref:t,className:M(n,!i.length&&o)})});td.displayName="Col";const Vu=td,nd=y.forwardRef(({bsPrefix:e,fluid:t=!1,as:n="div",className:r,...l},o)=>{const i=H(e,"container"),u=typeof t=="string"?`-${t}`:"-fluid";return v.jsx(n,{ref:o,...l,className:M(r,t?`${i}${u}`:i)})});nd.displayName="Container";const pv=nd;var mv=Function.prototype.bind.call(Function.prototype.call,[].slice);function fn(e,t){return mv(e.querySelectorAll(t))}function da(e,t){if(e.contains)return e.contains(t);if(e.compareDocumentPosition)return e===t||!!(e.compareDocumentPosition(t)&16)}const hv="data-rr-ui-";function vv(e){return`${hv}${e}`}const rd=y.createContext(Wn?window:void 0);rd.Provider;function Qu(){return y.useContext(rd)}const yv={type:ot.string,tooltip:ot.bool,as:ot.elementType},Ku=y.forwardRef(({as:e="div",className:t,type:n="valid",tooltip:r=!1,...l},o)=>v.jsx(e,{...l,ref:o,className:M(t,`${n}-${r?"tooltip":"feedback"}`)}));Ku.displayName="Feedback";Ku.propTypes=yv;const ld=Ku,gv=y.createContext({}),ct=gv,od=y.forwardRef(({id:e,bsPrefix:t,className:n,type:r="checkbox",isValid:l=!1,isInvalid:o=!1,as:i="input",...u},s)=>{const{controlId:a}=y.useContext(ct);return t=H(t,"form-check-input"),v.jsx(i,{...u,ref:s,type:r,id:e||a,className:M(n,t,l&&"is-valid",o&&"is-invalid")})});od.displayName="FormCheckInput";const id=od,ud=y.forwardRef(({bsPrefix:e,className:t,htmlFor:n,...r},l)=>{const{controlId:o}=y.useContext(ct);return e=H(e,"form-check-label"),v.jsx("label",{...r,ref:l,htmlFor:n||o,className:M(t,e)})});ud.displayName="FormCheckLabel";const Gi=ud,sd=y.forwardRef(({id:e,bsPrefix:t,bsSwitchPrefix:n,inline:r=!1,reverse:l=!1,disabled:o=!1,isValid:i=!1,isInvalid:u=!1,feedbackTooltip:s=!1,feedback:a,feedbackType:f,className:h,style:d,title:g="",type:S="checkbox",label:k,children:R,as:p="input",...c},m)=>{t=H(t,"form-check"),n=H(n,"form-switch");const{controlId:w}=y.useContext(ct),C=y.useMemo(()=>({controlId:e||w}),[w,e]),N=!R&&k!=null&&k!==!1||fv(R,Gi),T=v.jsx(id,{...c,type:S==="switch"?"checkbox":S,ref:m,isValid:i,isInvalid:u,disabled:o,as:p});return v.jsx(ct.Provider,{value:C,children:v.jsx("div",{style:d,className:M(h,N&&t,r&&`${t}-inline`,l&&`${t}-reverse`,S==="switch"&&n),children:R||v.jsxs(v.Fragment,{children:[T,N&&v.jsx(Gi,{title:g,children:k}),a&&v.jsx(ld,{type:f,tooltip:s,children:a})]})})})});sd.displayName="FormCheck";const Gl=Object.assign(sd,{Input:id,Label:Gi}),ad=y.forwardRef(({bsPrefix:e,type:t,size:n,htmlSize:r,id:l,className:o,isValid:i=!1,isInvalid:u=!1,plaintext:s,readOnly:a,as:f="input",...h},d)=>{const{controlId:g}=y.useContext(ct);return e=H(e,"form-control"),v.jsx(f,{...h,type:t,size:r,ref:d,readOnly:a,id:l||g,className:M(o,s?`${e}-plaintext`:e,n&&`${e}-${n}`,t==="color"&&`${e}-color`,i&&"is-valid",u&&"is-invalid")})});ad.displayName="FormControl";const wv=Object.assign(ad,{Feedback:ld}),cd=y.forwardRef(({className:e,bsPrefix:t,as:n="div",...r},l)=>(t=H(t,"form-floating"),v.jsx(n,{ref:l,className:M(e,t),...r})));cd.displayName="FormFloating";const Sv=cd,fd=y.forwardRef(({controlId:e,as:t="div",...n},r)=>{const l=y.useMemo(()=>({controlId:e}),[e]);return v.jsx(ct.Provider,{value:l,children:v.jsx(t,{...n,ref:r})})});fd.displayName="FormGroup";const dd=fd,pd=y.forwardRef(({as:e="label",bsPrefix:t,column:n=!1,visuallyHidden:r=!1,className:l,htmlFor:o,...i},u)=>{const{controlId:s}=y.useContext(ct);t=H(t,"form-label");let a="col-form-label";typeof n=="string"&&(a=`${a} ${a}-${n}`);const f=M(l,t,r&&"visually-hidden",n&&a);return o=o||s,n?v.jsx(Vu,{ref:u,as:"label",className:f,htmlFor:o,...i}):v.jsx(e,{ref:u,className:f,htmlFor:o,...i})});pd.displayName="FormLabel";const kv=pd,md=y.forwardRef(({bsPrefix:e,className:t,id:n,...r},l)=>{const{controlId:o}=y.useContext(ct);return e=H(e,"form-range"),v.jsx("input",{...r,type:"range",ref:l,className:M(t,e),id:n||o})});md.displayName="FormRange";const xv=md,hd=y.forwardRef(({bsPrefix:e,size:t,htmlSize:n,className:r,isValid:l=!1,isInvalid:o=!1,id:i,...u},s)=>{const{controlId:a}=y.useContext(ct);return e=H(e,"form-select"),v.jsx("select",{...u,size:n,ref:s,className:M(r,e,t&&`${e}-${t}`,l&&"is-valid",o&&"is-invalid"),id:i||a})});hd.displayName="FormSelect";const Ev=hd,vd=y.forwardRef(({bsPrefix:e,className:t,as:n="small",muted:r,...l},o)=>(e=H(e,"form-text"),v.jsx(n,{...l,ref:o,className:M(t,e,r&&"text-muted")})));vd.displayName="FormText";const Cv=vd,yd=y.forwardRef((e,t)=>v.jsx(Gl,{...e,ref:t,type:"switch"}));yd.displayName="Switch";const Nv=Object.assign(yd,{Input:Gl.Input,Label:Gl.Label}),gd=y.forwardRef(({bsPrefix:e,className:t,children:n,controlId:r,label:l,...o},i)=>(e=H(e,"form-floating"),v.jsxs(dd,{ref:i,className:M(t,e),controlId:r,...o,children:[n,v.jsx("label",{htmlFor:r,children:l})]})));gd.displayName="FloatingLabel";const Tv=gd,_v={_ref:ot.any,validated:ot.bool,as:ot.elementType},Gu=y.forwardRef(({className:e,validated:t,as:n="form",...r},l)=>v.jsx(n,{...r,ref:l,className:M(e,t&&"was-validated")}));Gu.displayName="Form";Gu.propTypes=_v;const Et=Object.assign(Gu,{Group:dd,Control:wv,Floating:Sv,Check:Gl,Switch:Nv,Label:kv,Text:Cv,Range:xv,Select:Ev,FloatingLabel:Tv});var ul;function pa(e){if((!ul&&ul!==0||e)&&Wn){var t=document.createElement("div");t.style.position="absolute",t.style.top="-9999px",t.style.width="50px",t.style.height="50px",t.style.overflow="scroll",document.body.appendChild(t),ul=t.offsetWidth-t.clientWidth,document.body.removeChild(t)}return ul}function Wo(e){e===void 0&&(e=po());try{var t=e.activeElement;return!t||!t.nodeName?null:t}catch{return e.body}}function jv(e=document){const t=e.defaultView;return Math.abs(t.innerWidth-e.documentElement.clientWidth)}const ma=vv("modal-open");class Rv{constructor({ownerDocument:t,handleContainerOverflow:n=!0,isRTL:r=!1}={}){this.handleContainerOverflow=n,this.isRTL=r,this.modals=[],this.ownerDocument=t}getScrollbarWidth(){return jv(this.ownerDocument)}getElement(){return(this.ownerDocument||document).body}setModalAttributes(t){}removeModalAttributes(t){}setContainerStyle(t){const n={overflow:"hidden"},r=this.isRTL?"paddingLeft":"paddingRight",l=this.getElement();t.style={overflow:l.style.overflow,[r]:l.style[r]},t.scrollBarWidth&&(n[r]=`${parseInt(Zt(l,r)||"0",10)+t.scrollBarWidth}px`),l.setAttribute(ma,""),Zt(l,n)}reset(){[...this.modals].forEach(t=>this.remove(t))}removeContainerStyle(t){const n=this.getElement();n.removeAttribute(ma),Object.assign(n.style,t.style)}add(t){let n=this.modals.indexOf(t);return n!==-1||(n=this.modals.length,this.modals.push(t),this.setModalAttributes(t),n!==0)||(this.state={scrollBarWidth:this.getScrollbarWidth(),style:{}},this.handleContainerOverflow&&this.setContainerStyle(this.state)),n}remove(t){const n=this.modals.indexOf(t);n!==-1&&(this.modals.splice(n,1),!this.modals.length&&this.handleContainerOverflow&&this.removeContainerStyle(this.state),this.removeModalAttributes(t))}isTopModal(t){return!!this.modals.length&&this.modals[this.modals.length-1]===t}}const Yu=Rv,Vo=(e,t)=>Wn?e==null?(t||po()).body:(typeof e=="function"&&(e=e()),e&&"current"in e&&(e=e.current),e&&("nodeType"in e||e.getBoundingClientRect)?e:null):null;function Lv(e,t){const n=Qu(),[r,l]=y.useState(()=>Vo(e,n==null?void 0:n.document));if(!r){const o=Vo(e);o&&l(o)}return y.useEffect(()=>{t&&r&&t(r)},[t,r]),y.useEffect(()=>{const o=Vo(e);o!==r&&l(o)},[e,r]),r}function Ov({children:e,in:t,onExited:n,mountOnEnter:r,unmountOnExit:l}){const o=y.useRef(null),i=y.useRef(t),u=Pe(n);y.useEffect(()=>{t?i.current=!0:u(o.current)},[t,u]);const s=mo(o,e.ref),a=y.cloneElement(e,{ref:s});return t?a:l||!i.current&&r?null:a}function Pv({in:e,onTransition:t}){const n=y.useRef(null),r=y.useRef(!0),l=Pe(t);return ca(()=>{if(!n.current)return;let o=!1;return l({in:e,element:n.current,initial:r.current,isStale:()=>o}),()=>{o=!0}},[e,l]),ca(()=>(r.current=!1,()=>{r.current=!0}),[]),n}function Fv({children:e,in:t,onExited:n,onEntered:r,transition:l}){const[o,i]=y.useState(!t);t&&o&&i(!1);const u=Pv({in:!!t,onTransition:a=>{const f=()=>{a.isStale()||(a.in?r==null||r(a.element,a.initial):(i(!0),n==null||n(a.element)))};Promise.resolve(l(a)).then(f,h=>{throw a.in||i(!0),h})}}),s=mo(u,e.ref);return o&&!t?null:y.cloneElement(e,{ref:s})}function ha(e,t,n){return e?v.jsx(e,Object.assign({},n)):t?v.jsx(Fv,Object.assign({},n,{transition:t})):v.jsx(Ov,Object.assign({},n))}function Mv(e){return e.code==="Escape"||e.keyCode===27}const zv=["show","role","className","style","children","backdrop","keyboard","onBackdropClick","onEscapeKeyDown","transition","runTransition","backdropTransition","runBackdropTransition","autoFocus","enforceFocus","restoreFocus","restoreFocusOptions","renderDialog","renderBackdrop","manager","container","onShow","onHide","onExit","onExited","onExiting","onEnter","onEntering","onEntered"];function $v(e,t){if(e==null)return{};var n={},r=Object.keys(e),l,o;for(o=0;o=0)&&(n[l]=e[l]);return n}let Qo;function Dv(e){return Qo||(Qo=new Yu({ownerDocument:e==null?void 0:e.document})),Qo}function Iv(e){const t=Qu(),n=e||Dv(t),r=y.useRef({dialog:null,backdrop:null});return Object.assign(r.current,{add:()=>n.add(r.current),remove:()=>n.remove(r.current),isTopModal:()=>n.isTopModal(r.current),setDialogRef:y.useCallback(l=>{r.current.dialog=l},[]),setBackdropRef:y.useCallback(l=>{r.current.backdrop=l},[])})}const wd=y.forwardRef((e,t)=>{let{show:n=!1,role:r="dialog",className:l,style:o,children:i,backdrop:u=!0,keyboard:s=!0,onBackdropClick:a,onEscapeKeyDown:f,transition:h,runTransition:d,backdropTransition:g,runBackdropTransition:S,autoFocus:k=!0,enforceFocus:R=!0,restoreFocus:p=!0,restoreFocusOptions:c,renderDialog:m,renderBackdrop:w=K=>v.jsx("div",Object.assign({},K)),manager:C,container:N,onShow:T,onHide:j=()=>{},onExit:U,onExited:P,onExiting:ie,onEnter:Ve,onEntering:Qe,onEntered:ln}=e,Qn=$v(e,zv);const _e=Qu(),Ke=Lv(N),E=Iv(C),L=Yh(),O=Xh(n),[D,A]=y.useState(!n),fe=y.useRef(null);y.useImperativeHandle(t,()=>E,[E]),Wn&&!O&&n&&(fe.current=Wo(_e==null?void 0:_e.document)),n&&D&&A(!1);const je=Pe(()=>{if(E.add(),un.current=Ql(document,"keydown",ho),on.current=Ql(document,"focus",()=>setTimeout(Re),!0),T&&T(),k){var K,Hr;const Yn=Wo((K=(Hr=E.dialog)==null?void 0:Hr.ownerDocument)!=null?K:_e==null?void 0:_e.document);E.dialog&&Yn&&!da(E.dialog,Yn)&&(fe.current=Yn,E.dialog.focus())}}),qe=Pe(()=>{if(E.remove(),un.current==null||un.current(),on.current==null||on.current(),p){var K;(K=fe.current)==null||K.focus==null||K.focus(c),fe.current=null}});y.useEffect(()=>{!n||!Ke||je()},[n,Ke,je]),y.useEffect(()=>{D&&qe()},[D,qe]),ed(()=>{qe()});const Re=Pe(()=>{if(!R||!L()||!E.isTopModal())return;const K=Wo(_e==null?void 0:_e.document);E.dialog&&K&&!da(E.dialog,K)&&E.dialog.focus()}),mt=Pe(K=>{K.target===K.currentTarget&&(a==null||a(K),u===!0&&j())}),ho=Pe(K=>{s&&Mv(K)&&E.isTopModal()&&(f==null||f(K),K.defaultPrevented||j())}),on=y.useRef(),un=y.useRef(),Kn=(...K)=>{A(!0),P==null||P(...K)};if(!Ke)return null;const Br=Object.assign({role:r,ref:E.setDialogRef,"aria-modal":r==="dialog"?!0:void 0},Qn,{style:o,className:l,tabIndex:-1});let Gn=m?m(Br):v.jsx("div",Object.assign({},Br,{children:y.cloneElement(i,{role:"document"})}));Gn=ha(h,d,{unmountOnExit:!0,mountOnEnter:!0,appear:!0,in:!!n,onExit:U,onExiting:ie,onExited:Kn,onEnter:Ve,onEntering:Qe,onEntered:ln,children:Gn});let At=null;return u&&(At=w({ref:E.setBackdropRef,onClick:mt}),At=ha(g,S,{in:!!n,appear:!0,mountOnEnter:!0,unmountOnExit:!0,children:At})),v.jsx(v.Fragment,{children:Nn.createPortal(v.jsxs(v.Fragment,{children:[At,Gn]}),Ke)})});wd.displayName="Modal";const Av=Object.assign(wd,{Manager:Yu});function Uv(e,t){return e.classList?!!t&&e.classList.contains(t):(" "+(e.className.baseVal||e.className)+" ").indexOf(" "+t+" ")!==-1}function Bv(e,t){e.classList?e.classList.add(t):Uv(e,t)||(typeof e.className=="string"?e.className=e.className+" "+t:e.setAttribute("class",(e.className&&e.className.baseVal||"")+" "+t))}function va(e,t){return e.replace(new RegExp("(^|\\s)"+t+"(?:\\s|$)","g"),"$1").replace(/\s+/g," ").replace(/^\s*|\s*$/g,"")}function Hv(e,t){e.classList?e.classList.remove(t):typeof e.className=="string"?e.className=va(e.className,t):e.setAttribute("class",va(e.className&&e.className.baseVal||"",t))}const dn={FIXED_CONTENT:".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",STICKY_CONTENT:".sticky-top",NAVBAR_TOGGLER:".navbar-toggler"};class Wv extends Yu{adjustAndStore(t,n,r){const l=n.style[t];n.dataset[t]=l,Zt(n,{[t]:`${parseFloat(Zt(n,t))+r}px`})}restore(t,n){const r=n.dataset[t];r!==void 0&&(delete n.dataset[t],Zt(n,{[t]:r}))}setContainerStyle(t){super.setContainerStyle(t);const n=this.getElement();if(Bv(n,"modal-open"),!t.scrollBarWidth)return;const r=this.isRTL?"paddingLeft":"paddingRight",l=this.isRTL?"marginLeft":"marginRight";fn(n,dn.FIXED_CONTENT).forEach(o=>this.adjustAndStore(r,o,t.scrollBarWidth)),fn(n,dn.STICKY_CONTENT).forEach(o=>this.adjustAndStore(l,o,-t.scrollBarWidth)),fn(n,dn.NAVBAR_TOGGLER).forEach(o=>this.adjustAndStore(l,o,t.scrollBarWidth))}removeContainerStyle(t){super.removeContainerStyle(t);const n=this.getElement();Hv(n,"modal-open");const r=this.isRTL?"paddingLeft":"paddingRight",l=this.isRTL?"marginLeft":"marginRight";fn(n,dn.FIXED_CONTENT).forEach(o=>this.restore(r,o)),fn(n,dn.STICKY_CONTENT).forEach(o=>this.restore(l,o)),fn(n,dn.NAVBAR_TOGGLER).forEach(o=>this.restore(l,o))}}let Ko;function Vv(e){return Ko||(Ko=new Wv(e)),Ko}const Sd=y.forwardRef(({className:e,bsPrefix:t,as:n="div",...r},l)=>(t=H(t,"modal-body"),v.jsx(n,{ref:l,className:M(e,t),...r})));Sd.displayName="ModalBody";const Qv=Sd,Kv=y.createContext({onHide(){}}),kd=Kv,xd=y.forwardRef(({bsPrefix:e,className:t,contentClassName:n,centered:r,size:l,fullscreen:o,children:i,scrollable:u,...s},a)=>{e=H(e,"modal");const f=`${e}-dialog`,h=typeof o=="string"?`${e}-fullscreen-${o}`:`${e}-fullscreen`;return v.jsx("div",{...s,ref:a,className:M(f,t,l&&`${e}-${l}`,r&&`${f}-centered`,u&&`${f}-scrollable`,o&&h),children:v.jsx("div",{className:M(`${e}-content`,n),children:i})})});xd.displayName="ModalDialog";const Ed=xd,Cd=y.forwardRef(({className:e,bsPrefix:t,as:n="div",...r},l)=>(t=H(t,"modal-footer"),v.jsx(n,{ref:l,className:M(e,t),...r})));Cd.displayName="ModalFooter";const Gv=Cd,Yv=y.forwardRef(({closeLabel:e="Close",closeVariant:t,closeButton:n=!1,onHide:r,children:l,...o},i)=>{const u=y.useContext(kd),s=Pe(()=>{u==null||u.onHide(),r==null||r()});return v.jsxs("div",{ref:i,...o,children:[l,n&&v.jsx(Jf,{"aria-label":e,variant:t,onClick:s})]})}),Xv=Yv,Nd=y.forwardRef(({bsPrefix:e,className:t,closeLabel:n="Close",closeButton:r=!1,...l},o)=>(e=H(e,"modal-header"),v.jsx(Xv,{ref:o,...l,className:M(t,e),closeLabel:n,closeButton:r})));Nd.displayName="ModalHeader";const Zv=Nd,Jv=Qf("h4"),Td=y.forwardRef(({className:e,bsPrefix:t,as:n=Jv,...r},l)=>(t=H(t,"modal-title"),v.jsx(n,{ref:l,className:M(e,t),...r})));Td.displayName="ModalTitle";const qv=Td;function bv(e){return v.jsx(Kl,{...e,timeout:null})}function ey(e){return v.jsx(Kl,{...e,timeout:null})}const _d=y.forwardRef(({bsPrefix:e,className:t,style:n,dialogClassName:r,contentClassName:l,children:o,dialogAs:i=Ed,"aria-labelledby":u,"aria-describedby":s,"aria-label":a,show:f=!1,animation:h=!0,backdrop:d=!0,keyboard:g=!0,onEscapeKeyDown:S,onShow:k,onHide:R,container:p,autoFocus:c=!0,enforceFocus:m=!0,restoreFocus:w=!0,restoreFocusOptions:C,onEntered:N,onExit:T,onExiting:j,onEnter:U,onEntering:P,onExited:ie,backdropClassName:Ve,manager:Qe,...ln},Qn)=>{const[_e,Ke]=y.useState({}),[E,L]=y.useState(!1),O=y.useRef(!1),D=y.useRef(!1),A=y.useRef(null),[fe,je]=Gh(),qe=mo(Qn,je),Re=Pe(R),mt=kh();e=H(e,"modal");const ho=y.useMemo(()=>({onHide:Re}),[Re]);function on(){return Qe||Vv({isRTL:mt})}function un($){if(!Wn)return;const sn=on().getScrollbarWidth()>0,Zu=$.scrollHeight>po($).documentElement.clientHeight;Ke({paddingRight:sn&&!Zu?pa():void 0,paddingLeft:!sn&&Zu?pa():void 0})}const Kn=Pe(()=>{fe&&un(fe.dialog)});ed(()=>{Ki(window,"resize",Kn),A.current==null||A.current()});const Br=()=>{O.current=!0},Gn=$=>{O.current&&fe&&$.target===fe.dialog&&(D.current=!0),O.current=!1},At=()=>{L(!0),A.current=Vf(fe.dialog,()=>{L(!1)})},K=$=>{$.target===$.currentTarget&&At()},Hr=$=>{if(d==="static"){K($);return}if(D.current||$.target!==$.currentTarget){D.current=!1;return}R==null||R()},Yn=$=>{g?S==null||S($):($.preventDefault(),d==="static"&&At())},Dd=($,sn)=>{$&&un($),U==null||U($,sn)},Id=$=>{A.current==null||A.current(),T==null||T($)},Ad=($,sn)=>{P==null||P($,sn),Wf(window,"resize",Kn)},Ud=$=>{$&&($.style.display=""),ie==null||ie($),Ki(window,"resize",Kn)},Bd=y.useCallback($=>v.jsx("div",{...$,className:M(`${e}-backdrop`,Ve,!h&&"show")}),[h,Ve,e]),Xu={...n,..._e};Xu.display="block";const Hd=$=>v.jsx("div",{role:"dialog",...$,style:Xu,className:M(t,e,E&&`${e}-static`,!h&&"show"),onClick:d?Hr:void 0,onMouseUp:Gn,"aria-label":a,"aria-labelledby":u,"aria-describedby":s,children:v.jsx(i,{...ln,onMouseDown:Br,className:r,contentClassName:l,children:o})});return v.jsx(kd.Provider,{value:ho,children:v.jsx(Av,{show:f,ref:qe,backdrop:d,container:p,keyboard:!0,autoFocus:c,enforceFocus:m,restoreFocus:w,restoreFocusOptions:C,onEscapeKeyDown:Yn,onShow:k,onHide:R,onEnter:Dd,onEntering:Ad,onEntered:N,onExit:Id,onExiting:j,onExited:Ud,manager:on(),transition:h?bv:void 0,backdropTransition:h?ey:void 0,renderBackdrop:Bd,renderDialog:Hd})})});_d.displayName="Modal";const tt=Object.assign(_d,{Body:Qv,Header:Zv,Title:qv,Footer:Gv,Dialog:Ed,TRANSITION_DURATION:300,BACKDROP_TRANSITION_DURATION:150}),ya=1e3;function ty(e,t,n){const r=(e-t)/(n-t)*100;return Math.round(r*ya)/ya}function ga({min:e,now:t,max:n,label:r,visuallyHidden:l,striped:o,animated:i,className:u,style:s,variant:a,bsPrefix:f,...h},d){return v.jsx("div",{ref:d,...h,role:"progressbar",className:M(u,`${f}-bar`,{[`bg-${a}`]:a,[`${f}-bar-animated`]:i,[`${f}-bar-striped`]:i||o}),style:{width:`${ty(t,e,n)}%`,...s},"aria-valuenow":t,"aria-valuemin":e,"aria-valuemax":n,children:l?v.jsx("span",{className:"visually-hidden",children:r}):r})}const jd=y.forwardRef(({isChild:e=!1,...t},n)=>{const r={min:0,max:100,animated:!1,visuallyHidden:!1,striped:!1,...t};if(r.bsPrefix=H(r.bsPrefix,"progress"),e)return ga(r,n);const{min:l,now:o,max:i,label:u,visuallyHidden:s,striped:a,animated:f,bsPrefix:h,variant:d,className:g,children:S,...k}=r;return v.jsx("div",{ref:n,...k,className:M(g,h),children:S?cv(S,R=>y.cloneElement(R,{isChild:!0})):ga({min:l,now:o,max:i,label:u,visuallyHidden:s,striped:a,animated:f,bsPrefix:h,variant:d},n)})});jd.displayName="ProgressBar";const ny=jd,Rd=y.forwardRef(({bsPrefix:e,className:t,as:n="div",...r},l)=>{const o=H(e,"row"),i=Df(),u=If(),s=`${o}-cols`,a=[];return i.forEach(f=>{const h=r[f];delete r[f];let d;h!=null&&typeof h=="object"?{cols:d}=h:d=h;const g=f!==u?`-${f}`:"";d!=null&&a.push(`${s}${g}-${d}`)}),v.jsx(n,{ref:l,...r,className:M(t,o,...a)})});Rd.displayName="Row";const Ld=Rd,Od=y.forwardRef(({bsPrefix:e,variant:t,animation:n="border",size:r,as:l="div",className:o,...i},u)=>{e=H(e,"spinner");const s=`${e}-${n}`;return v.jsx(l,{ref:u,...i,className:M(o,s,r&&`${s}-${r}`,t&&`text-${t}`)})});Od.displayName="Spinner";const An=Od,ry=window.origin==="null"||window.origin==="http://localhost:3031"?"http://localhost:3030":"",Sl="initializing",wa="paused",Pd="live",ly="error",vt=async(e,t,n)=>{console.log(e,t);const r=ry+t,l={method:e,headers:{Accept:"application/json"},body:n};let o={method:e,path:t,text:""},i;try{i=await fetch(r,l)}catch{return o.text="network error",Promise.reject(o)}if(o.status=i.status,o.statusText=i.statusText,!i.ok){const s=await i.text();try{const a=JSON.parse(s);o.text=a.human_readable!==void 0?a.human_readable:JSON.stringify(a,null,2)}catch{o.text=s}return Promise.reject(o)}return await i.json()},ft={listTorrents:()=>vt("GET","/torrents"),getTorrentDetails:e=>vt("GET",`/torrents/${e}`),getTorrentStats:e=>vt("GET",`/torrents/${e}/stats/v1`),uploadTorrent:(e,t)=>{t=t||{};let n="/torrents?&overwrite=true";return t.listOnly&&(n+="&list_only=true"),t.selectedFiles!=null&&(n+=`&only_files=${t.selectedFiles.join(",")}`),t.unpopularTorrent&&(n+="&peer_connect_timeout=20&peer_read_write_timeout=60"),vt("POST",n,e)},pause:e=>vt("POST",`/torrents/${e}/pause`),start:e=>vt("POST",`/torrents/${e}/start`),forget:e=>vt("POST",`/torrents/${e}/forget`),delete:e=>vt("POST",`/torrents/${e}/delete`)},Vn=y.createContext(null),Fd=y.createContext(null),Go=({className:e,onClick:t,disabled:n,color:r})=>{const l=o=>{o.stopPropagation(),!n&&t()};return v.jsx("a",{className:`bi ${e} p-1`,onClick:l,href:"#"})},oy=({id:e,show:t,onHide:n})=>{if(!t)return null;const[r,l]=y.useState(!1),[o,i]=y.useState(null),[u,s]=y.useState(!1),a=y.useContext(Vn),f=()=>{l(!1),i(null),s(!1),n()},h=()=>{s(!0),(r?ft.delete:ft.forget)(e).then(()=>{a.refreshTorrents(),f()}).catch(g=>{i({text:`Error deleting torrent id=${e}`,details:g}),s(!1)})};return v.jsxs(tt,{show:t,onHide:f,children:[v.jsx(tt.Header,{closeButton:!0,children:"Delete torrent"}),v.jsxs(tt.Body,{children:[v.jsx(Et,{children:v.jsx(Et.Group,{controlId:"delete-torrent",children:v.jsx(Et.Check,{type:"checkbox",label:"Also delete files",checked:r,onChange:()=>l(!r)})})}),o&&v.jsx(zr,{error:o})]}),v.jsxs(tt.Footer,{children:[u&&v.jsx(An,{}),v.jsx(Mr,{variant:"primary",onClick:h,disabled:u,children:"OK"}),v.jsx(Mr,{variant:"secondary",onClick:f,children:"Cancel"})]})]})},iy=({id:e,statsResponse:t})=>{let n=t.state,[r,l]=y.useState(!1),[o,i]=y.useState(!1),u=y.useContext(Fd);const s=n=="live",a=n=="paused"||n=="error",f=y.useContext(Vn),h=()=>{l(!0),ft.start(e).then(()=>{u.refresh()},k=>{f.setCloseableError({text:`Error starting torrent id=${e}`,details:k})}).finally(()=>l(!1))},d=()=>{l(!0),ft.pause(e).then(()=>{u.refresh()},k=>{f.setCloseableError({text:`Error pausing torrent id=${e}`,details:k})}).finally(()=>l(!1))},g=()=>{l(!0),i(!0)},S=()=>{l(!1),i(!1)};return v.jsx(Ld,{children:v.jsxs(Vu,{children:[a&&v.jsx(Go,{className:"bi-play-circle",onClick:h,disabled:r,color:"success"}),s&&v.jsx(Go,{className:"bi-pause-circle",onClick:d,disabled:r}),v.jsx(Go,{className:"bi-x-circle",onClick:g,disabled:r,color:"danger"}),v.jsx(oy,{id:e,show:o,onHide:S})]})})},uy=({id:e,detailsResponse:t,statsResponse:n})=>{const r=(n==null?void 0:n.state)??"",l=n==null?void 0:n.error,o=(n==null?void 0:n.total_bytes)??1,i=(n==null?void 0:n.progress_bytes)??0,u=(n==null?void 0:n.finished)||!1,s=l?100:i/o*100,a=(r==Sl||r==Pd)&&!u,f=l?"Error":`${s.toFixed(2)}%`,h=l?"danger":u?"success":r==Sl?"warning":"primary",d=()=>{var R;let k=(R=n==null?void 0:n.live)==null?void 0:R.snapshot.peer_stats;return k?`${k.live} / ${k.seen}`:""},g=()=>{var k;if(u)return"Completed";switch(r){case wa:return"Paused";case Sl:return"Checking files";case ly:return"Error"}return((k=n.live)==null?void 0:k.download_speed.human_readable)??"N/A"};let S=[];return l?S.push("bg-warning"):e%2==0&&S.push("bg-light"),v.jsxs(Ld,{className:S.join(" "),children:[v.jsx(yt,{size:3,label:"Name",children:t?v.jsxs(v.Fragment,{children:[v.jsx("div",{className:"text-truncate",children:yy(t)}),l&&v.jsxs("p",{className:"text-danger",children:[v.jsx("strong",{children:"Error:"})," ",l]})]}):v.jsx(An,{})}),n?v.jsxs(v.Fragment,{children:[v.jsx(yt,{label:"Size",children:`${zd(o)} `}),v.jsx(yt,{size:2,label:(r==wa,"Progress"),children:v.jsx(ny,{now:s,label:f,animated:a,variant:h})}),v.jsx(yt,{size:2,label:"Down Speed",children:g()}),v.jsx(yt,{label:"ETA",children:gy(n)}),v.jsx(yt,{size:2,label:"Peers",children:d()}),v.jsx(yt,{label:"Actions",children:v.jsx(iy,{id:e,statsResponse:n})})]}):v.jsx(yt,{label:"Loading stats",size:8,children:v.jsx(An,{})})]})},yt=({size:e,label:t,children:n})=>v.jsxs(Vu,{md:e||1,className:"py-3",children:[v.jsx("div",{className:"fw-bold",children:t}),n]}),sy=({id:e,torrent:t})=>{const[n,r]=y.useState(null),[l,o]=y.useState(null),[i,u]=y.useState(0),s=()=>{u(i+1)};return y.useEffect(()=>{if(n===null)return Sy(async()=>{await ft.getTorrentDetails(t.id).then(r)},1e3)},[n]),y.useEffect(()=>$d(async()=>ft.getTorrentStats(t.id).then(g=>(o(g),g)).then(g=>g.finished?1e4:g.state==Sl||g.state==Pd?1e3:1e4,g=>1e4),0),[i]),v.jsx(Fd.Provider,{value:{refresh:s},children:v.jsx(uy,{id:e,detailsResponse:n,statsResponse:l})})},ay=e=>{if(e.torrents===null&&e.loading)return v.jsx(An,{});if(e.torrents!==null)return e.torrents.length===0?v.jsx("div",{className:"text-center",children:v.jsx("p",{children:"No existing torrents found. Add them through buttons below."})}):v.jsx(v.Fragment,{children:e.torrents.map(t=>v.jsx(sy,{id:t.id,torrent:t},t.id))})},cy=()=>{const[e,t]=y.useState(null),[n,r]=y.useState(null),[l,o]=y.useState(null),[i,u]=y.useState(!1),s=async()=>{u(!0);let f=await ft.listTorrents().finally(()=>u(!1));o(f.torrents)};y.useEffect(()=>$d(async()=>s().then(()=>(r(null),5e3),f=>(r({text:"Error refreshing torrents",details:f}),console.error(f),5e3)),0),[]);const a={setCloseableError:t,refreshTorrents:s};return v.jsx(Vn.Provider,{value:a,children:v.jsxs("div",{className:"text-center",children:[v.jsx("h1",{className:"mt-3 mb-4",children:"rqbit web 4.0.0-beta.0"}),v.jsx(vy,{closeableError:e,otherError:n,torrents:l,torrentsLoading:i})]})})},fy=e=>{let{details:t}=e;return t?v.jsxs(v.Fragment,{children:[t.status&&v.jsxs("strong",{children:[t.status," ",t.statusText,": "]}),t.text]}):null},zr=e=>{let{error:t,remove:n}=e;return t==null?null:v.jsxs(fa,{variant:"danger",onClose:n,dismissible:n!=null,children:[v.jsx(fa.Heading,{children:t.text}),v.jsx(fy,{details:t.details})]})},Md=({buttonText:e,onClick:t,data:n,resetData:r,variant:l})=>{const[o,i]=y.useState(!1),[u,s]=y.useState([]),[a,f]=y.useState(null);y.useContext(Vn);const h=n!==null||a!==null;y.useEffect(()=>{if(n===null)return;let g=setTimeout(async()=>{i(!0);try{const S=await ft.uploadTorrent(n,{listOnly:!0});s(S.details.files)}catch(S){f({text:"Error uploading torrent",details:S})}finally{i(!1)}},0);return()=>clearTimeout(g)},[n]);const d=()=>{r(),f(null),s([]),i(!1)};return v.jsxs(v.Fragment,{children:[v.jsx(Mr,{variant:l,onClick:t,className:"m-1",children:e}),v.jsx(my,{show:h,onHide:d,fileListError:a,fileList:u,data:n,fileListLoading:o})]})},dy=()=>{let[e,t]=y.useState(null);const n=()=>{const r=prompt("Enter magnet link or HTTP(s) URL");t(r===""?null:r)};return v.jsx(Md,{variant:"primary",buttonText:"Add Torrent from Magnet / URL",onClick:n,data:e,resetData:()=>t(null)})},py=()=>{const e=y.useRef(),[t,n]=y.useState(null),r=async()=>{const i=e.current.files[0];n(i)},l=()=>{e.current.value="",n(null)},o=()=>{e.current.click()};return v.jsxs(v.Fragment,{children:[v.jsx("input",{type:"file",ref:e,accept:".torrent",onChange:r,className:"d-none"}),v.jsx(Md,{variant:"secondary",buttonText:"Upload .torrent File",onClick:o,data:t,resetData:l})]})},my=e=>{let{show:t,onHide:n,fileList:r,fileListError:l,fileListLoading:o,data:i}=e;const[u,s]=y.useState([]),[a,f]=y.useState(!1),[h,d]=y.useState(null),[g,S]=y.useState(!1),k=y.useContext(Vn);y.useEffect(()=>{s(r.map((m,w)=>w))},[r]);const R=()=>{n(),s([]),d(null),f(!1)},p=m=>{u.includes(m)?s(u.filter(w=>w!==m)):s([...u,m])},c=async()=>{f(!0),ft.uploadTorrent(i,{selectedFiles:u,unpopularTorrent:g}).then(()=>{n(),k.refreshTorrents()},m=>{d({text:"Error starting torrent",details:m})}).finally(()=>f(!1))};return v.jsxs(tt,{show:t,onHide:R,size:"lg",children:[v.jsx(tt.Header,{closeButton:!0,children:!!l||v.jsx(tt.Title,{children:"Add torrent"})}),v.jsxs(tt.Body,{children:[v.jsxs(Et,{children:[v.jsxs("fieldset",{className:"mb-5",children:[v.jsx("legend",{children:"Pick the files to download"}),o?v.jsx(An,{}):l?v.jsx(zr,{error:l}):v.jsx(v.Fragment,{children:r.map((m,w)=>v.jsx(Et.Group,{controlId:`check-${w}`,children:v.jsx(Et.Check,{type:"checkbox",label:`${m.name} (${zd(m.length)})`,checked:u.includes(w),onChange:()=>p(w)})},w))})]}),v.jsxs("fieldset",{children:[v.jsx("legend",{children:"Other options"}),v.jsxs(Et.Group,{controlId:"unpopular-torrent",children:[v.jsx(Et.Check,{type:"checkbox",label:"Increase timeouts",checked:g,onChange:()=>S(!g)}),v.jsx("small",{id:"emailHelp",className:"form-text text-muted",children:"This might be useful for unpopular torrents with few peers."})]})]})]}),v.jsx(zr,{error:h})]}),v.jsxs(tt.Footer,{children:[a&&v.jsx(An,{}),v.jsx(Mr,{variant:"primary",onClick:c,disabled:o||a||u.length==0,children:"OK"}),v.jsx(Mr,{variant:"secondary",onClick:R,children:"Cancel"})]})]})},hy=()=>v.jsxs("div",{id:"buttons-container",className:"mt-3",children:[v.jsx(dy,{}),v.jsx(py,{})]}),vy=e=>{let t=y.useContext(Vn);return v.jsxs(pv,{children:[v.jsx(zr,{error:e.closeableError,remove:()=>t.setCloseableError(null)}),v.jsx(zr,{error:e.otherError}),v.jsx(ay,{torrents:e.torrents,loading:e.torrentsLoading}),v.jsx(hy,{})]})};function zd(e){if(e===0)return"0 Bytes";const t=1024,n=["Bytes","KB","MB","GB"],r=Math.floor(Math.log(e)/Math.log(t));return parseFloat((e/Math.pow(t,r)).toFixed(2))+" "+n[r]}function yy(e){return e.files.filter(n=>n.included).reduce((n,r)=>n.length>r.length?n:r).name}function gy(e){var n,r,l;let t=(l=(r=(n=e==null?void 0:e.live)==null?void 0:n.time_remaining)==null?void 0:r.duration)==null?void 0:l.secs;return t==null?"N/A":wy(t)}function wy(e){const t=Math.floor(e/3600),n=Math.floor(e%3600/60),r=e%60,l=(o,i)=>o>0?`${o}${i}`:"";return t>0?`${l(t,"h")} ${l(n,"m")}`.trim():n>0?`${l(n,"m")} ${l(r,"s")}`.trim():`${l(r,"s")}`.trim()}function $d(e,t){let n,r=t;const l=async()=>{if(r=await e(),r==null)throw"asyncCallback returned null or undefined";o()};let o=()=>{n=setTimeout(l,r)};return o(),()=>{clearTimeout(n)}}function Sy(e,t){let n;const r=async()=>{await e().then(()=>!1,()=>!0)&&l()};let l=o=>{n=setTimeout(r,o!==void 0?o:t)};return l(0),()=>clearTimeout(n)}async function ky(){const e=document.getElementById("app");Yo.createRoot(e).render(v.jsx(y.StrictMode,{children:v.jsx(cy,{})}))}document.addEventListener("DOMContentLoaded",ky); +*/(function(e){(function(){var t={}.hasOwnProperty;function n(){for(var r=[],l=0;l=0)&&(n[l]=e[l]);return n}function ia(e){return"default"+e.charAt(0).toUpperCase()+e.substr(1)}function mh(e){var t=hh(e,"string");return typeof t=="symbol"?t:String(t)}function hh(e,t){if(typeof e!="object"||e===null)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||"default");if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function vh(e,t,n){var r=y.useRef(e!==void 0),l=y.useState(t),o=l[0],i=l[1],u=e!==void 0,s=r.current;return r.current=u,!u&&s&&o!==t&&i(t),[u?e:o,y.useCallback(function(a){for(var f=arguments.length,h=new Array(f>1?f-1:0),d=1;d{o.target===e&&(l(),t(o))},n+r)}function Uh(e){e.offsetHeight}const aa=e=>!e||typeof e=="function"?e:t=>{e.current=t};function Bh(e,t){const n=aa(e),r=aa(t);return l=>{n&&n(l),r&&r(l)}}function mo(e,t){return y.useMemo(()=>Bh(e,t),[e,t])}function Hh(e){return e&&"setState"in e?Nn.findDOMNode(e):e??null}const Wh=Vt.forwardRef(({onEnter:e,onEntering:t,onEntered:n,onExit:r,onExiting:l,onExited:o,addEndListener:i,children:u,childRef:s,...a},f)=>{const h=y.useRef(null),d=mo(h,s),g=N=>{d(Hh(N))},S=N=>T=>{N&&h.current&&N(h.current,T)},k=y.useCallback(S(e),[e]),R=y.useCallback(S(t),[t]),p=y.useCallback(S(n),[n]),c=y.useCallback(S(r),[r]),m=y.useCallback(S(l),[l]),w=y.useCallback(S(o),[o]),C=y.useCallback(S(i),[i]);return v.jsx(zh,{ref:f,...a,onEnter:k,onEntered:p,onEntering:R,onExit:c,onExited:w,onExiting:m,addEndListener:C,nodeRef:h,children:typeof u=="function"?(N,T)=>u(N,{...T,ref:g}):Vt.cloneElement(u,{ref:g})})}),Vh=Wh;function Qh(e){const t=y.useRef(e);return y.useEffect(()=>{t.current=e},[e]),t}function Pe(e){const t=Qh(e);return y.useCallback(function(...n){return t.current&&t.current(...n)},[t])}const Qf=e=>y.forwardRef((t,n)=>v.jsx("div",{...t,ref:n,className:M(t.className,e)})),Kf=Qf("h4");Kf.displayName="DivStyledAsH4";const Gf=y.forwardRef(({className:e,bsPrefix:t,as:n=Kf,...r},l)=>(t=H(t,"alert-heading"),v.jsx(n,{ref:l,className:M(e,t),...r})));Gf.displayName="AlertHeading";const Kh=Gf;function Gh(){return y.useState(null)}function Yh(){const e=y.useRef(!0),t=y.useRef(()=>e.current);return y.useEffect(()=>(e.current=!0,()=>{e.current=!1}),[]),t.current}function Xh(e){const t=y.useRef(null);return y.useEffect(()=>{t.current=e}),t.current}const Zh=typeof global<"u"&&global.navigator&&global.navigator.product==="ReactNative",Jh=typeof document<"u",ca=Jh||Zh?y.useLayoutEffect:y.useEffect,qh=["as","disabled"];function bh(e,t){if(e==null)return{};var n={},r=Object.keys(e),l,o;for(o=0;o=0)&&(n[l]=e[l]);return n}function ev(e){return!e||e.trim()==="#"}function Hu({tagName:e,disabled:t,href:n,target:r,rel:l,role:o,onClick:i,tabIndex:u=0,type:s}){e||(n!=null||r!=null||l!=null?e="a":e="button");const a={tagName:e};if(e==="button")return[{type:s||"button",disabled:t},a];const f=d=>{if((t||e==="a"&&ev(n))&&d.preventDefault(),t){d.stopPropagation();return}i==null||i(d)},h=d=>{d.key===" "&&(d.preventDefault(),f(d))};return e==="a"&&(n||(n="#"),t&&(n=void 0)),[{role:o??"button",disabled:void 0,tabIndex:t?void 0:u,href:n,target:e==="a"?r:void 0,"aria-disabled":t||void 0,rel:e==="a"?l:void 0,onClick:f,onKeyDown:h},a]}const tv=y.forwardRef((e,t)=>{let{as:n,disabled:r}=e,l=bh(e,qh);const[o,{tagName:i}]=Hu(Object.assign({tagName:n,disabled:r},l));return v.jsx(i,Object.assign({},l,o,{ref:t}))});tv.displayName="Button";const nv=["onKeyDown"];function rv(e,t){if(e==null)return{};var n={},r=Object.keys(e),l,o;for(o=0;o=0)&&(n[l]=e[l]);return n}function lv(e){return!e||e.trim()==="#"}const Yf=y.forwardRef((e,t)=>{let{onKeyDown:n}=e,r=rv(e,nv);const[l]=Hu(Object.assign({tagName:"a"},r)),o=Pe(i=>{l.onKeyDown(i),n==null||n(i)});return lv(r.href)||r.role==="button"?v.jsx("a",Object.assign({ref:t},r,l,{onKeyDown:o})):v.jsx("a",Object.assign({ref:t},r,{onKeyDown:n}))});Yf.displayName="Anchor";const ov=Yf,Xf=y.forwardRef(({className:e,bsPrefix:t,as:n=ov,...r},l)=>(t=H(t,"alert-link"),v.jsx(n,{ref:l,className:M(e,t),...r})));Xf.displayName="AlertLink";const iv=Xf,uv={[St]:"show",[Wt]:"show"},Zf=y.forwardRef(({className:e,children:t,transitionClasses:n={},onEnter:r,...l},o)=>{const i={in:!1,timeout:300,mountOnEnter:!1,unmountOnExit:!1,appear:!1,...l},u=y.useCallback((s,a)=>{Uh(s),r==null||r(s,a)},[r]);return v.jsx(Vh,{ref:o,addEndListener:Ah,...i,onEnter:u,childRef:t.ref,children:(s,a)=>y.cloneElement(t,{...a,className:M("fade",e,t.props.className,uv[s],n[s])})})});Zf.displayName="Fade";const Kl=Zf,sv={"aria-label":ot.string,onClick:ot.func,variant:ot.oneOf(["white"])},Wu=y.forwardRef(({className:e,variant:t,"aria-label":n="Close",...r},l)=>v.jsx("button",{ref:l,type:"button",className:M("btn-close",t&&`btn-close-${t}`,e),"aria-label":n,...r}));Wu.displayName="CloseButton";Wu.propTypes=sv;const Jf=Wu,qf=y.forwardRef((e,t)=>{const{bsPrefix:n,show:r=!0,closeLabel:l="Close alert",closeVariant:o,className:i,children:u,variant:s="primary",onClose:a,dismissible:f,transition:h=Kl,...d}=yh(e,{show:"onClose"}),g=H(n,"alert"),S=Pe(p=>{a&&a(!1,p)}),k=h===!0?Kl:h,R=v.jsxs("div",{role:"alert",...k?void 0:d,ref:t,className:M(i,g,s&&`${g}-${s}`,f&&`${g}-dismissible`),children:[f&&v.jsx(Jf,{onClick:S,"aria-label":l,variant:o}),u]});return k?v.jsx(k,{unmountOnExit:!0,...d,ref:void 0,in:r,children:R}):r?R:null});qf.displayName="Alert";const fa=Object.assign(qf,{Link:iv,Heading:Kh}),bf=y.forwardRef(({as:e,bsPrefix:t,variant:n="primary",size:r,active:l=!1,disabled:o=!1,className:i,...u},s)=>{const a=H(t,"btn"),[f,{tagName:h}]=Hu({tagName:e,disabled:o,...u}),d=h;return v.jsx(d,{...f,...u,ref:s,disabled:o,className:M(i,a,l&&"active",n&&`${a}-${n}`,r&&`${a}-${r}`,u.href&&o&&"disabled")})});bf.displayName="Button";const Mr=bf;function av(e){const t=y.useRef(e);return t.current=e,t}function ed(e){const t=av(e);y.useEffect(()=>()=>t.current(),[])}function cv(e,t){let n=0;return y.Children.map(e,r=>y.isValidElement(r)?t(r,n++):r)}function fv(e,t){return y.Children.toArray(e).some(n=>y.isValidElement(n)&&n.type===t)}function dv({as:e,bsPrefix:t,className:n,...r}){t=H(t,"col");const l=Df(),o=If(),i=[],u=[];return l.forEach(s=>{const a=r[s];delete r[s];let f,h,d;typeof a=="object"&&a!=null?{span:f,offset:h,order:d}=a:f=a;const g=s!==o?`-${s}`:"";f&&i.push(f===!0?`${t}${g}`:`${t}${g}-${f}`),d!=null&&u.push(`order${g}-${d}`),h!=null&&u.push(`offset${g}-${h}`)}),[{...r,className:M(n,...i,...u)},{as:e,bsPrefix:t,spans:i}]}const td=y.forwardRef((e,t)=>{const[{className:n,...r},{as:l="div",bsPrefix:o,spans:i}]=dv(e);return v.jsx(l,{...r,ref:t,className:M(n,!i.length&&o)})});td.displayName="Col";const Vu=td,nd=y.forwardRef(({bsPrefix:e,fluid:t=!1,as:n="div",className:r,...l},o)=>{const i=H(e,"container"),u=typeof t=="string"?`-${t}`:"-fluid";return v.jsx(n,{ref:o,...l,className:M(r,t?`${i}${u}`:i)})});nd.displayName="Container";const pv=nd;var mv=Function.prototype.bind.call(Function.prototype.call,[].slice);function fn(e,t){return mv(e.querySelectorAll(t))}function da(e,t){if(e.contains)return e.contains(t);if(e.compareDocumentPosition)return e===t||!!(e.compareDocumentPosition(t)&16)}const hv="data-rr-ui-";function vv(e){return`${hv}${e}`}const rd=y.createContext(Wn?window:void 0);rd.Provider;function Qu(){return y.useContext(rd)}const yv={type:ot.string,tooltip:ot.bool,as:ot.elementType},Ku=y.forwardRef(({as:e="div",className:t,type:n="valid",tooltip:r=!1,...l},o)=>v.jsx(e,{...l,ref:o,className:M(t,`${n}-${r?"tooltip":"feedback"}`)}));Ku.displayName="Feedback";Ku.propTypes=yv;const ld=Ku,gv=y.createContext({}),ct=gv,od=y.forwardRef(({id:e,bsPrefix:t,className:n,type:r="checkbox",isValid:l=!1,isInvalid:o=!1,as:i="input",...u},s)=>{const{controlId:a}=y.useContext(ct);return t=H(t,"form-check-input"),v.jsx(i,{...u,ref:s,type:r,id:e||a,className:M(n,t,l&&"is-valid",o&&"is-invalid")})});od.displayName="FormCheckInput";const id=od,ud=y.forwardRef(({bsPrefix:e,className:t,htmlFor:n,...r},l)=>{const{controlId:o}=y.useContext(ct);return e=H(e,"form-check-label"),v.jsx("label",{...r,ref:l,htmlFor:n||o,className:M(t,e)})});ud.displayName="FormCheckLabel";const Gi=ud,sd=y.forwardRef(({id:e,bsPrefix:t,bsSwitchPrefix:n,inline:r=!1,reverse:l=!1,disabled:o=!1,isValid:i=!1,isInvalid:u=!1,feedbackTooltip:s=!1,feedback:a,feedbackType:f,className:h,style:d,title:g="",type:S="checkbox",label:k,children:R,as:p="input",...c},m)=>{t=H(t,"form-check"),n=H(n,"form-switch");const{controlId:w}=y.useContext(ct),C=y.useMemo(()=>({controlId:e||w}),[w,e]),N=!R&&k!=null&&k!==!1||fv(R,Gi),T=v.jsx(id,{...c,type:S==="switch"?"checkbox":S,ref:m,isValid:i,isInvalid:u,disabled:o,as:p});return v.jsx(ct.Provider,{value:C,children:v.jsx("div",{style:d,className:M(h,N&&t,r&&`${t}-inline`,l&&`${t}-reverse`,S==="switch"&&n),children:R||v.jsxs(v.Fragment,{children:[T,N&&v.jsx(Gi,{title:g,children:k}),a&&v.jsx(ld,{type:f,tooltip:s,children:a})]})})})});sd.displayName="FormCheck";const Gl=Object.assign(sd,{Input:id,Label:Gi}),ad=y.forwardRef(({bsPrefix:e,type:t,size:n,htmlSize:r,id:l,className:o,isValid:i=!1,isInvalid:u=!1,plaintext:s,readOnly:a,as:f="input",...h},d)=>{const{controlId:g}=y.useContext(ct);return e=H(e,"form-control"),v.jsx(f,{...h,type:t,size:r,ref:d,readOnly:a,id:l||g,className:M(o,s?`${e}-plaintext`:e,n&&`${e}-${n}`,t==="color"&&`${e}-color`,i&&"is-valid",u&&"is-invalid")})});ad.displayName="FormControl";const wv=Object.assign(ad,{Feedback:ld}),cd=y.forwardRef(({className:e,bsPrefix:t,as:n="div",...r},l)=>(t=H(t,"form-floating"),v.jsx(n,{ref:l,className:M(e,t),...r})));cd.displayName="FormFloating";const Sv=cd,fd=y.forwardRef(({controlId:e,as:t="div",...n},r)=>{const l=y.useMemo(()=>({controlId:e}),[e]);return v.jsx(ct.Provider,{value:l,children:v.jsx(t,{...n,ref:r})})});fd.displayName="FormGroup";const dd=fd,pd=y.forwardRef(({as:e="label",bsPrefix:t,column:n=!1,visuallyHidden:r=!1,className:l,htmlFor:o,...i},u)=>{const{controlId:s}=y.useContext(ct);t=H(t,"form-label");let a="col-form-label";typeof n=="string"&&(a=`${a} ${a}-${n}`);const f=M(l,t,r&&"visually-hidden",n&&a);return o=o||s,n?v.jsx(Vu,{ref:u,as:"label",className:f,htmlFor:o,...i}):v.jsx(e,{ref:u,className:f,htmlFor:o,...i})});pd.displayName="FormLabel";const kv=pd,md=y.forwardRef(({bsPrefix:e,className:t,id:n,...r},l)=>{const{controlId:o}=y.useContext(ct);return e=H(e,"form-range"),v.jsx("input",{...r,type:"range",ref:l,className:M(t,e),id:n||o})});md.displayName="FormRange";const xv=md,hd=y.forwardRef(({bsPrefix:e,size:t,htmlSize:n,className:r,isValid:l=!1,isInvalid:o=!1,id:i,...u},s)=>{const{controlId:a}=y.useContext(ct);return e=H(e,"form-select"),v.jsx("select",{...u,size:n,ref:s,className:M(r,e,t&&`${e}-${t}`,l&&"is-valid",o&&"is-invalid"),id:i||a})});hd.displayName="FormSelect";const Ev=hd,vd=y.forwardRef(({bsPrefix:e,className:t,as:n="small",muted:r,...l},o)=>(e=H(e,"form-text"),v.jsx(n,{...l,ref:o,className:M(t,e,r&&"text-muted")})));vd.displayName="FormText";const Cv=vd,yd=y.forwardRef((e,t)=>v.jsx(Gl,{...e,ref:t,type:"switch"}));yd.displayName="Switch";const Nv=Object.assign(yd,{Input:Gl.Input,Label:Gl.Label}),gd=y.forwardRef(({bsPrefix:e,className:t,children:n,controlId:r,label:l,...o},i)=>(e=H(e,"form-floating"),v.jsxs(dd,{ref:i,className:M(t,e),controlId:r,...o,children:[n,v.jsx("label",{htmlFor:r,children:l})]})));gd.displayName="FloatingLabel";const Tv=gd,_v={_ref:ot.any,validated:ot.bool,as:ot.elementType},Gu=y.forwardRef(({className:e,validated:t,as:n="form",...r},l)=>v.jsx(n,{...r,ref:l,className:M(e,t&&"was-validated")}));Gu.displayName="Form";Gu.propTypes=_v;const Et=Object.assign(Gu,{Group:dd,Control:wv,Floating:Sv,Check:Gl,Switch:Nv,Label:kv,Text:Cv,Range:xv,Select:Ev,FloatingLabel:Tv});var ul;function pa(e){if((!ul&&ul!==0||e)&&Wn){var t=document.createElement("div");t.style.position="absolute",t.style.top="-9999px",t.style.width="50px",t.style.height="50px",t.style.overflow="scroll",document.body.appendChild(t),ul=t.offsetWidth-t.clientWidth,document.body.removeChild(t)}return ul}function Wo(e){e===void 0&&(e=po());try{var t=e.activeElement;return!t||!t.nodeName?null:t}catch{return e.body}}function jv(e=document){const t=e.defaultView;return Math.abs(t.innerWidth-e.documentElement.clientWidth)}const ma=vv("modal-open");class Rv{constructor({ownerDocument:t,handleContainerOverflow:n=!0,isRTL:r=!1}={}){this.handleContainerOverflow=n,this.isRTL=r,this.modals=[],this.ownerDocument=t}getScrollbarWidth(){return jv(this.ownerDocument)}getElement(){return(this.ownerDocument||document).body}setModalAttributes(t){}removeModalAttributes(t){}setContainerStyle(t){const n={overflow:"hidden"},r=this.isRTL?"paddingLeft":"paddingRight",l=this.getElement();t.style={overflow:l.style.overflow,[r]:l.style[r]},t.scrollBarWidth&&(n[r]=`${parseInt(Zt(l,r)||"0",10)+t.scrollBarWidth}px`),l.setAttribute(ma,""),Zt(l,n)}reset(){[...this.modals].forEach(t=>this.remove(t))}removeContainerStyle(t){const n=this.getElement();n.removeAttribute(ma),Object.assign(n.style,t.style)}add(t){let n=this.modals.indexOf(t);return n!==-1||(n=this.modals.length,this.modals.push(t),this.setModalAttributes(t),n!==0)||(this.state={scrollBarWidth:this.getScrollbarWidth(),style:{}},this.handleContainerOverflow&&this.setContainerStyle(this.state)),n}remove(t){const n=this.modals.indexOf(t);n!==-1&&(this.modals.splice(n,1),!this.modals.length&&this.handleContainerOverflow&&this.removeContainerStyle(this.state),this.removeModalAttributes(t))}isTopModal(t){return!!this.modals.length&&this.modals[this.modals.length-1]===t}}const Yu=Rv,Vo=(e,t)=>Wn?e==null?(t||po()).body:(typeof e=="function"&&(e=e()),e&&"current"in e&&(e=e.current),e&&("nodeType"in e||e.getBoundingClientRect)?e:null):null;function Lv(e,t){const n=Qu(),[r,l]=y.useState(()=>Vo(e,n==null?void 0:n.document));if(!r){const o=Vo(e);o&&l(o)}return y.useEffect(()=>{t&&r&&t(r)},[t,r]),y.useEffect(()=>{const o=Vo(e);o!==r&&l(o)},[e,r]),r}function Ov({children:e,in:t,onExited:n,mountOnEnter:r,unmountOnExit:l}){const o=y.useRef(null),i=y.useRef(t),u=Pe(n);y.useEffect(()=>{t?i.current=!0:u(o.current)},[t,u]);const s=mo(o,e.ref),a=y.cloneElement(e,{ref:s});return t?a:l||!i.current&&r?null:a}function Pv({in:e,onTransition:t}){const n=y.useRef(null),r=y.useRef(!0),l=Pe(t);return ca(()=>{if(!n.current)return;let o=!1;return l({in:e,element:n.current,initial:r.current,isStale:()=>o}),()=>{o=!0}},[e,l]),ca(()=>(r.current=!1,()=>{r.current=!0}),[]),n}function Fv({children:e,in:t,onExited:n,onEntered:r,transition:l}){const[o,i]=y.useState(!t);t&&o&&i(!1);const u=Pv({in:!!t,onTransition:a=>{const f=()=>{a.isStale()||(a.in?r==null||r(a.element,a.initial):(i(!0),n==null||n(a.element)))};Promise.resolve(l(a)).then(f,h=>{throw a.in||i(!0),h})}}),s=mo(u,e.ref);return o&&!t?null:y.cloneElement(e,{ref:s})}function ha(e,t,n){return e?v.jsx(e,Object.assign({},n)):t?v.jsx(Fv,Object.assign({},n,{transition:t})):v.jsx(Ov,Object.assign({},n))}function Mv(e){return e.code==="Escape"||e.keyCode===27}const zv=["show","role","className","style","children","backdrop","keyboard","onBackdropClick","onEscapeKeyDown","transition","runTransition","backdropTransition","runBackdropTransition","autoFocus","enforceFocus","restoreFocus","restoreFocusOptions","renderDialog","renderBackdrop","manager","container","onShow","onHide","onExit","onExited","onExiting","onEnter","onEntering","onEntered"];function $v(e,t){if(e==null)return{};var n={},r=Object.keys(e),l,o;for(o=0;o=0)&&(n[l]=e[l]);return n}let Qo;function Dv(e){return Qo||(Qo=new Yu({ownerDocument:e==null?void 0:e.document})),Qo}function Iv(e){const t=Qu(),n=e||Dv(t),r=y.useRef({dialog:null,backdrop:null});return Object.assign(r.current,{add:()=>n.add(r.current),remove:()=>n.remove(r.current),isTopModal:()=>n.isTopModal(r.current),setDialogRef:y.useCallback(l=>{r.current.dialog=l},[]),setBackdropRef:y.useCallback(l=>{r.current.backdrop=l},[])})}const wd=y.forwardRef((e,t)=>{let{show:n=!1,role:r="dialog",className:l,style:o,children:i,backdrop:u=!0,keyboard:s=!0,onBackdropClick:a,onEscapeKeyDown:f,transition:h,runTransition:d,backdropTransition:g,runBackdropTransition:S,autoFocus:k=!0,enforceFocus:R=!0,restoreFocus:p=!0,restoreFocusOptions:c,renderDialog:m,renderBackdrop:w=K=>v.jsx("div",Object.assign({},K)),manager:C,container:N,onShow:T,onHide:j=()=>{},onExit:U,onExited:P,onExiting:ie,onEnter:Ve,onEntering:Qe,onEntered:ln}=e,Qn=$v(e,zv);const _e=Qu(),Ke=Lv(N),E=Iv(C),L=Yh(),O=Xh(n),[D,A]=y.useState(!n),fe=y.useRef(null);y.useImperativeHandle(t,()=>E,[E]),Wn&&!O&&n&&(fe.current=Wo(_e==null?void 0:_e.document)),n&&D&&A(!1);const je=Pe(()=>{if(E.add(),un.current=Ql(document,"keydown",ho),on.current=Ql(document,"focus",()=>setTimeout(Re),!0),T&&T(),k){var K,Hr;const Yn=Wo((K=(Hr=E.dialog)==null?void 0:Hr.ownerDocument)!=null?K:_e==null?void 0:_e.document);E.dialog&&Yn&&!da(E.dialog,Yn)&&(fe.current=Yn,E.dialog.focus())}}),qe=Pe(()=>{if(E.remove(),un.current==null||un.current(),on.current==null||on.current(),p){var K;(K=fe.current)==null||K.focus==null||K.focus(c),fe.current=null}});y.useEffect(()=>{!n||!Ke||je()},[n,Ke,je]),y.useEffect(()=>{D&&qe()},[D,qe]),ed(()=>{qe()});const Re=Pe(()=>{if(!R||!L()||!E.isTopModal())return;const K=Wo(_e==null?void 0:_e.document);E.dialog&&K&&!da(E.dialog,K)&&E.dialog.focus()}),mt=Pe(K=>{K.target===K.currentTarget&&(a==null||a(K),u===!0&&j())}),ho=Pe(K=>{s&&Mv(K)&&E.isTopModal()&&(f==null||f(K),K.defaultPrevented||j())}),on=y.useRef(),un=y.useRef(),Kn=(...K)=>{A(!0),P==null||P(...K)};if(!Ke)return null;const Br=Object.assign({role:r,ref:E.setDialogRef,"aria-modal":r==="dialog"?!0:void 0},Qn,{style:o,className:l,tabIndex:-1});let Gn=m?m(Br):v.jsx("div",Object.assign({},Br,{children:y.cloneElement(i,{role:"document"})}));Gn=ha(h,d,{unmountOnExit:!0,mountOnEnter:!0,appear:!0,in:!!n,onExit:U,onExiting:ie,onExited:Kn,onEnter:Ve,onEntering:Qe,onEntered:ln,children:Gn});let At=null;return u&&(At=w({ref:E.setBackdropRef,onClick:mt}),At=ha(g,S,{in:!!n,appear:!0,mountOnEnter:!0,unmountOnExit:!0,children:At})),v.jsx(v.Fragment,{children:Nn.createPortal(v.jsxs(v.Fragment,{children:[At,Gn]}),Ke)})});wd.displayName="Modal";const Av=Object.assign(wd,{Manager:Yu});function Uv(e,t){return e.classList?!!t&&e.classList.contains(t):(" "+(e.className.baseVal||e.className)+" ").indexOf(" "+t+" ")!==-1}function Bv(e,t){e.classList?e.classList.add(t):Uv(e,t)||(typeof e.className=="string"?e.className=e.className+" "+t:e.setAttribute("class",(e.className&&e.className.baseVal||"")+" "+t))}function va(e,t){return e.replace(new RegExp("(^|\\s)"+t+"(?:\\s|$)","g"),"$1").replace(/\s+/g," ").replace(/^\s*|\s*$/g,"")}function Hv(e,t){e.classList?e.classList.remove(t):typeof e.className=="string"?e.className=va(e.className,t):e.setAttribute("class",va(e.className&&e.className.baseVal||"",t))}const dn={FIXED_CONTENT:".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",STICKY_CONTENT:".sticky-top",NAVBAR_TOGGLER:".navbar-toggler"};class Wv extends Yu{adjustAndStore(t,n,r){const l=n.style[t];n.dataset[t]=l,Zt(n,{[t]:`${parseFloat(Zt(n,t))+r}px`})}restore(t,n){const r=n.dataset[t];r!==void 0&&(delete n.dataset[t],Zt(n,{[t]:r}))}setContainerStyle(t){super.setContainerStyle(t);const n=this.getElement();if(Bv(n,"modal-open"),!t.scrollBarWidth)return;const r=this.isRTL?"paddingLeft":"paddingRight",l=this.isRTL?"marginLeft":"marginRight";fn(n,dn.FIXED_CONTENT).forEach(o=>this.adjustAndStore(r,o,t.scrollBarWidth)),fn(n,dn.STICKY_CONTENT).forEach(o=>this.adjustAndStore(l,o,-t.scrollBarWidth)),fn(n,dn.NAVBAR_TOGGLER).forEach(o=>this.adjustAndStore(l,o,t.scrollBarWidth))}removeContainerStyle(t){super.removeContainerStyle(t);const n=this.getElement();Hv(n,"modal-open");const r=this.isRTL?"paddingLeft":"paddingRight",l=this.isRTL?"marginLeft":"marginRight";fn(n,dn.FIXED_CONTENT).forEach(o=>this.restore(r,o)),fn(n,dn.STICKY_CONTENT).forEach(o=>this.restore(l,o)),fn(n,dn.NAVBAR_TOGGLER).forEach(o=>this.restore(l,o))}}let Ko;function Vv(e){return Ko||(Ko=new Wv(e)),Ko}const Sd=y.forwardRef(({className:e,bsPrefix:t,as:n="div",...r},l)=>(t=H(t,"modal-body"),v.jsx(n,{ref:l,className:M(e,t),...r})));Sd.displayName="ModalBody";const Qv=Sd,Kv=y.createContext({onHide(){}}),kd=Kv,xd=y.forwardRef(({bsPrefix:e,className:t,contentClassName:n,centered:r,size:l,fullscreen:o,children:i,scrollable:u,...s},a)=>{e=H(e,"modal");const f=`${e}-dialog`,h=typeof o=="string"?`${e}-fullscreen-${o}`:`${e}-fullscreen`;return v.jsx("div",{...s,ref:a,className:M(f,t,l&&`${e}-${l}`,r&&`${f}-centered`,u&&`${f}-scrollable`,o&&h),children:v.jsx("div",{className:M(`${e}-content`,n),children:i})})});xd.displayName="ModalDialog";const Ed=xd,Cd=y.forwardRef(({className:e,bsPrefix:t,as:n="div",...r},l)=>(t=H(t,"modal-footer"),v.jsx(n,{ref:l,className:M(e,t),...r})));Cd.displayName="ModalFooter";const Gv=Cd,Yv=y.forwardRef(({closeLabel:e="Close",closeVariant:t,closeButton:n=!1,onHide:r,children:l,...o},i)=>{const u=y.useContext(kd),s=Pe(()=>{u==null||u.onHide(),r==null||r()});return v.jsxs("div",{ref:i,...o,children:[l,n&&v.jsx(Jf,{"aria-label":e,variant:t,onClick:s})]})}),Xv=Yv,Nd=y.forwardRef(({bsPrefix:e,className:t,closeLabel:n="Close",closeButton:r=!1,...l},o)=>(e=H(e,"modal-header"),v.jsx(Xv,{ref:o,...l,className:M(t,e),closeLabel:n,closeButton:r})));Nd.displayName="ModalHeader";const Zv=Nd,Jv=Qf("h4"),Td=y.forwardRef(({className:e,bsPrefix:t,as:n=Jv,...r},l)=>(t=H(t,"modal-title"),v.jsx(n,{ref:l,className:M(e,t),...r})));Td.displayName="ModalTitle";const qv=Td;function bv(e){return v.jsx(Kl,{...e,timeout:null})}function ey(e){return v.jsx(Kl,{...e,timeout:null})}const _d=y.forwardRef(({bsPrefix:e,className:t,style:n,dialogClassName:r,contentClassName:l,children:o,dialogAs:i=Ed,"aria-labelledby":u,"aria-describedby":s,"aria-label":a,show:f=!1,animation:h=!0,backdrop:d=!0,keyboard:g=!0,onEscapeKeyDown:S,onShow:k,onHide:R,container:p,autoFocus:c=!0,enforceFocus:m=!0,restoreFocus:w=!0,restoreFocusOptions:C,onEntered:N,onExit:T,onExiting:j,onEnter:U,onEntering:P,onExited:ie,backdropClassName:Ve,manager:Qe,...ln},Qn)=>{const[_e,Ke]=y.useState({}),[E,L]=y.useState(!1),O=y.useRef(!1),D=y.useRef(!1),A=y.useRef(null),[fe,je]=Gh(),qe=mo(Qn,je),Re=Pe(R),mt=kh();e=H(e,"modal");const ho=y.useMemo(()=>({onHide:Re}),[Re]);function on(){return Qe||Vv({isRTL:mt})}function un($){if(!Wn)return;const sn=on().getScrollbarWidth()>0,Zu=$.scrollHeight>po($).documentElement.clientHeight;Ke({paddingRight:sn&&!Zu?pa():void 0,paddingLeft:!sn&&Zu?pa():void 0})}const Kn=Pe(()=>{fe&&un(fe.dialog)});ed(()=>{Ki(window,"resize",Kn),A.current==null||A.current()});const Br=()=>{O.current=!0},Gn=$=>{O.current&&fe&&$.target===fe.dialog&&(D.current=!0),O.current=!1},At=()=>{L(!0),A.current=Vf(fe.dialog,()=>{L(!1)})},K=$=>{$.target===$.currentTarget&&At()},Hr=$=>{if(d==="static"){K($);return}if(D.current||$.target!==$.currentTarget){D.current=!1;return}R==null||R()},Yn=$=>{g?S==null||S($):($.preventDefault(),d==="static"&&At())},Dd=($,sn)=>{$&&un($),U==null||U($,sn)},Id=$=>{A.current==null||A.current(),T==null||T($)},Ad=($,sn)=>{P==null||P($,sn),Wf(window,"resize",Kn)},Ud=$=>{$&&($.style.display=""),ie==null||ie($),Ki(window,"resize",Kn)},Bd=y.useCallback($=>v.jsx("div",{...$,className:M(`${e}-backdrop`,Ve,!h&&"show")}),[h,Ve,e]),Xu={...n,..._e};Xu.display="block";const Hd=$=>v.jsx("div",{role:"dialog",...$,style:Xu,className:M(t,e,E&&`${e}-static`,!h&&"show"),onClick:d?Hr:void 0,onMouseUp:Gn,"aria-label":a,"aria-labelledby":u,"aria-describedby":s,children:v.jsx(i,{...ln,onMouseDown:Br,className:r,contentClassName:l,children:o})});return v.jsx(kd.Provider,{value:ho,children:v.jsx(Av,{show:f,ref:qe,backdrop:d,container:p,keyboard:!0,autoFocus:c,enforceFocus:m,restoreFocus:w,restoreFocusOptions:C,onEscapeKeyDown:Yn,onShow:k,onHide:R,onEnter:Dd,onEntering:Ad,onEntered:N,onExit:Id,onExiting:j,onExited:Ud,manager:on(),transition:h?bv:void 0,backdropTransition:h?ey:void 0,renderBackdrop:Bd,renderDialog:Hd})})});_d.displayName="Modal";const tt=Object.assign(_d,{Body:Qv,Header:Zv,Title:qv,Footer:Gv,Dialog:Ed,TRANSITION_DURATION:300,BACKDROP_TRANSITION_DURATION:150}),ya=1e3;function ty(e,t,n){const r=(e-t)/(n-t)*100;return Math.round(r*ya)/ya}function ga({min:e,now:t,max:n,label:r,visuallyHidden:l,striped:o,animated:i,className:u,style:s,variant:a,bsPrefix:f,...h},d){return v.jsx("div",{ref:d,...h,role:"progressbar",className:M(u,`${f}-bar`,{[`bg-${a}`]:a,[`${f}-bar-animated`]:i,[`${f}-bar-striped`]:i||o}),style:{width:`${ty(t,e,n)}%`,...s},"aria-valuenow":t,"aria-valuemin":e,"aria-valuemax":n,children:l?v.jsx("span",{className:"visually-hidden",children:r}):r})}const jd=y.forwardRef(({isChild:e=!1,...t},n)=>{const r={min:0,max:100,animated:!1,visuallyHidden:!1,striped:!1,...t};if(r.bsPrefix=H(r.bsPrefix,"progress"),e)return ga(r,n);const{min:l,now:o,max:i,label:u,visuallyHidden:s,striped:a,animated:f,bsPrefix:h,variant:d,className:g,children:S,...k}=r;return v.jsx("div",{ref:n,...k,className:M(g,h),children:S?cv(S,R=>y.cloneElement(R,{isChild:!0})):ga({min:l,now:o,max:i,label:u,visuallyHidden:s,striped:a,animated:f,bsPrefix:h,variant:d},n)})});jd.displayName="ProgressBar";const ny=jd,Rd=y.forwardRef(({bsPrefix:e,className:t,as:n="div",...r},l)=>{const o=H(e,"row"),i=Df(),u=If(),s=`${o}-cols`,a=[];return i.forEach(f=>{const h=r[f];delete r[f];let d;h!=null&&typeof h=="object"?{cols:d}=h:d=h;const g=f!==u?`-${f}`:"";d!=null&&a.push(`${s}${g}-${d}`)}),v.jsx(n,{ref:l,...r,className:M(t,o,...a)})});Rd.displayName="Row";const Ld=Rd,Od=y.forwardRef(({bsPrefix:e,variant:t,animation:n="border",size:r,as:l="div",className:o,...i},u)=>{e=H(e,"spinner");const s=`${e}-${n}`;return v.jsx(l,{ref:u,...i,className:M(o,s,r&&`${s}-${r}`,t&&`text-${t}`)})});Od.displayName="Spinner";const An=Od,ry=window.origin==="null"||window.origin==="http://localhost:3031"?"http://localhost:3030":"",Sl="initializing",wa="paused",Pd="live",ly="error",vt=async(e,t,n)=>{console.log(e,t);const r=ry+t,l={method:e,headers:{Accept:"application/json"},body:n};let o={method:e,path:t,text:""},i;try{i=await fetch(r,l)}catch{return o.text="network error",Promise.reject(o)}if(o.status=i.status,o.statusText=i.statusText,!i.ok){const s=await i.text();try{const a=JSON.parse(s);o.text=a.human_readable!==void 0?a.human_readable:JSON.stringify(a,null,2)}catch{o.text=s}return Promise.reject(o)}return await i.json()},ft={listTorrents:()=>vt("GET","/torrents"),getTorrentDetails:e=>vt("GET",`/torrents/${e}`),getTorrentStats:e=>vt("GET",`/torrents/${e}/stats/v1`),uploadTorrent:(e,t)=>{t=t||{};let n="/torrents?&overwrite=true";return t.listOnly&&(n+="&list_only=true"),t.selectedFiles!=null&&(n+=`&only_files=${t.selectedFiles.join(",")}`),t.unpopularTorrent&&(n+="&peer_connect_timeout=20&peer_read_write_timeout=60"),t.initialPeers&&(n+=`&initial_peers=${t.initialPeers.join(",")}`),vt("POST",n,e)},pause:e=>vt("POST",`/torrents/${e}/pause`),start:e=>vt("POST",`/torrents/${e}/start`),forget:e=>vt("POST",`/torrents/${e}/forget`),delete:e=>vt("POST",`/torrents/${e}/delete`)},Vn=y.createContext(null),Fd=y.createContext(null),Go=({className:e,onClick:t,disabled:n,color:r})=>{const l=o=>{o.stopPropagation(),!n&&t()};return v.jsx("a",{className:`bi ${e} p-1`,onClick:l,href:"#"})},oy=({id:e,show:t,onHide:n})=>{if(!t)return null;const[r,l]=y.useState(!1),[o,i]=y.useState(null),[u,s]=y.useState(!1),a=y.useContext(Vn),f=()=>{l(!1),i(null),s(!1),n()},h=()=>{s(!0),(r?ft.delete:ft.forget)(e).then(()=>{a.refreshTorrents(),f()}).catch(g=>{i({text:`Error deleting torrent id=${e}`,details:g}),s(!1)})};return v.jsxs(tt,{show:t,onHide:f,children:[v.jsx(tt.Header,{closeButton:!0,children:"Delete torrent"}),v.jsxs(tt.Body,{children:[v.jsx(Et,{children:v.jsx(Et.Group,{controlId:"delete-torrent",children:v.jsx(Et.Check,{type:"checkbox",label:"Also delete files",checked:r,onChange:()=>l(!r)})})}),o&&v.jsx(zr,{error:o})]}),v.jsxs(tt.Footer,{children:[u&&v.jsx(An,{}),v.jsx(Mr,{variant:"primary",onClick:h,disabled:u,children:"OK"}),v.jsx(Mr,{variant:"secondary",onClick:f,children:"Cancel"})]})]})},iy=({id:e,statsResponse:t})=>{let n=t.state,[r,l]=y.useState(!1),[o,i]=y.useState(!1),u=y.useContext(Fd);const s=n=="live",a=n=="paused"||n=="error",f=y.useContext(Vn),h=()=>{l(!0),ft.start(e).then(()=>{u.refresh()},k=>{f.setCloseableError({text:`Error starting torrent id=${e}`,details:k})}).finally(()=>l(!1))},d=()=>{l(!0),ft.pause(e).then(()=>{u.refresh()},k=>{f.setCloseableError({text:`Error pausing torrent id=${e}`,details:k})}).finally(()=>l(!1))},g=()=>{l(!0),i(!0)},S=()=>{l(!1),i(!1)};return v.jsx(Ld,{children:v.jsxs(Vu,{children:[a&&v.jsx(Go,{className:"bi-play-circle",onClick:h,disabled:r,color:"success"}),s&&v.jsx(Go,{className:"bi-pause-circle",onClick:d,disabled:r}),v.jsx(Go,{className:"bi-x-circle",onClick:g,disabled:r,color:"danger"}),v.jsx(oy,{id:e,show:o,onHide:S})]})})},uy=({id:e,detailsResponse:t,statsResponse:n})=>{const r=(n==null?void 0:n.state)??"",l=n==null?void 0:n.error,o=(n==null?void 0:n.total_bytes)??1,i=(n==null?void 0:n.progress_bytes)??0,u=(n==null?void 0:n.finished)||!1,s=l?100:i/o*100,a=(r==Sl||r==Pd)&&!u,f=l?"Error":`${s.toFixed(2)}%`,h=l?"danger":u?"success":r==Sl?"warning":"primary",d=()=>{var R;let k=(R=n==null?void 0:n.live)==null?void 0:R.snapshot.peer_stats;return k?`${k.live} / ${k.seen}`:""},g=()=>{var k;if(u)return"Completed";switch(r){case wa:return"Paused";case Sl:return"Checking files";case ly:return"Error"}return((k=n.live)==null?void 0:k.download_speed.human_readable)??"N/A"};let S=[];return l?S.push("bg-warning"):e%2==0&&S.push("bg-light"),v.jsxs(Ld,{className:S.join(" "),children:[v.jsx(yt,{size:3,label:"Name",children:t?v.jsxs(v.Fragment,{children:[v.jsx("div",{className:"text-truncate",children:yy(t)}),l&&v.jsxs("p",{className:"text-danger",children:[v.jsx("strong",{children:"Error:"})," ",l]})]}):v.jsx(An,{})}),n?v.jsxs(v.Fragment,{children:[v.jsx(yt,{label:"Size",children:`${zd(o)} `}),v.jsx(yt,{size:2,label:(r==wa,"Progress"),children:v.jsx(ny,{now:s,label:f,animated:a,variant:h})}),v.jsx(yt,{size:2,label:"Down Speed",children:g()}),v.jsx(yt,{label:"ETA",children:gy(n)}),v.jsx(yt,{size:2,label:"Peers",children:d()}),v.jsx(yt,{label:"Actions",children:v.jsx(iy,{id:e,statsResponse:n})})]}):v.jsx(yt,{label:"Loading stats",size:8,children:v.jsx(An,{})})]})},yt=({size:e,label:t,children:n})=>v.jsxs(Vu,{md:e||1,className:"py-3",children:[v.jsx("div",{className:"fw-bold",children:t}),n]}),sy=({id:e,torrent:t})=>{const[n,r]=y.useState(null),[l,o]=y.useState(null),[i,u]=y.useState(0),s=()=>{u(i+1)};return y.useEffect(()=>{if(n===null)return Sy(async()=>{await ft.getTorrentDetails(t.id).then(r)},1e3)},[n]),y.useEffect(()=>$d(async()=>ft.getTorrentStats(t.id).then(g=>(o(g),g)).then(g=>g.finished?1e4:g.state==Sl||g.state==Pd?1e3:1e4,g=>1e4),0),[i]),v.jsx(Fd.Provider,{value:{refresh:s},children:v.jsx(uy,{id:e,detailsResponse:n,statsResponse:l})})},ay=e=>{if(e.torrents===null&&e.loading)return v.jsx(An,{});if(e.torrents!==null)return e.torrents.length===0?v.jsx("div",{className:"text-center",children:v.jsx("p",{children:"No existing torrents found. Add them through buttons below."})}):v.jsx(v.Fragment,{children:e.torrents.map(t=>v.jsx(sy,{id:t.id,torrent:t},t.id))})},cy=()=>{const[e,t]=y.useState(null),[n,r]=y.useState(null),[l,o]=y.useState(null),[i,u]=y.useState(!1),s=async()=>{u(!0);let f=await ft.listTorrents().finally(()=>u(!1));o(f.torrents)};y.useEffect(()=>$d(async()=>s().then(()=>(r(null),5e3),f=>(r({text:"Error refreshing torrents",details:f}),console.error(f),5e3)),0),[]);const a={setCloseableError:t,refreshTorrents:s};return v.jsx(Vn.Provider,{value:a,children:v.jsxs("div",{className:"text-center",children:[v.jsx("h1",{className:"mt-3 mb-4",children:"rqbit web 4.0.0-beta.0"}),v.jsx(vy,{closeableError:e,otherError:n,torrents:l,torrentsLoading:i})]})})},fy=e=>{let{details:t}=e;return t?v.jsxs(v.Fragment,{children:[t.status&&v.jsxs("strong",{children:[t.status," ",t.statusText,": "]}),t.text]}):null},zr=e=>{let{error:t,remove:n}=e;return t==null?null:v.jsxs(fa,{variant:"danger",onClose:n,dismissible:n!=null,children:[v.jsx(fa.Heading,{children:t.text}),v.jsx(fy,{details:t.details})]})},Md=({buttonText:e,onClick:t,data:n,resetData:r,variant:l})=>{const[o,i]=y.useState(!1),[u,s]=y.useState(null),[a,f]=y.useState(null);y.useContext(Vn);const h=n!==null||a!==null;y.useEffect(()=>{if(n===null)return;let g=setTimeout(async()=>{i(!0);try{const S=await ft.uploadTorrent(n,{listOnly:!0});s(S)}catch(S){f({text:"Error listing torrent files",details:S})}finally{i(!1)}},0);return()=>clearTimeout(g)},[n]);const d=()=>{r(),f(null),s(null),i(!1)};return v.jsxs(v.Fragment,{children:[v.jsx(Mr,{variant:l,onClick:t,className:"m-1",children:e}),v.jsx(my,{show:h,onHide:d,listTorrentError:a,listTorrentResponse:u,data:n,listTorrentLoading:o})]})},dy=()=>{let[e,t]=y.useState(null);const n=()=>{const r=prompt("Enter magnet link or HTTP(s) URL");t(r===""?null:r)};return v.jsx(Md,{variant:"primary",buttonText:"Add Torrent from Magnet / URL",onClick:n,data:e,resetData:()=>t(null)})},py=()=>{const e=y.useRef(),[t,n]=y.useState(null),r=async()=>{const i=e.current.files[0];n(i)},l=()=>{e.current.value="",n(null)},o=()=>{e.current.click()};return v.jsxs(v.Fragment,{children:[v.jsx("input",{type:"file",ref:e,accept:".torrent",onChange:r,className:"d-none"}),v.jsx(Md,{variant:"secondary",buttonText:"Upload .torrent File",onClick:o,data:t,resetData:l})]})},my=e=>{let{show:t,onHide:n,listTorrentResponse:r,listTorrentError:l,listTorrentLoading:o,data:i}=e;const[u,s]=y.useState([]),[a,f]=y.useState(!1),[h,d]=y.useState(null),[g,S]=y.useState(!1),k=y.useContext(Vn);y.useEffect(()=>{s(r?r.details.files.map((m,w)=>w):[])},[r]);const R=()=>{n(),s([]),d(null),f(!1)},p=m=>{u.includes(m)?s(u.filter(w=>w!==m)):s([...u,m])},c=async()=>{f(!0);let m=r.seen_peers?r.seen_peers.slice(0,32):null;ft.uploadTorrent(i,{selectedFiles:u,unpopularTorrent:g,initialPeers:m}).then(()=>{n(),k.refreshTorrents()},w=>{d({text:"Error starting torrent",details:w})}).finally(()=>f(!1))};return v.jsxs(tt,{show:t,onHide:R,size:"lg",children:[v.jsx(tt.Header,{closeButton:!0,children:v.jsx(tt.Title,{children:"Add torrent"})}),v.jsxs(tt.Body,{children:[v.jsxs(Et,{children:[v.jsxs("fieldset",{className:"mb-5",children:[v.jsx("legend",{children:"Pick the files to download"}),o?v.jsx(An,{}):l?v.jsx(zr,{error:l}):v.jsx(v.Fragment,{children:r==null?void 0:r.details.files.map((m,w)=>v.jsx(Et.Group,{controlId:`check-${w}`,children:v.jsx(Et.Check,{type:"checkbox",label:`${m.name} (${zd(m.length)})`,checked:u.includes(w),onChange:()=>p(w)})},w))})]}),v.jsxs("fieldset",{children:[v.jsx("legend",{children:"Other options"}),v.jsxs(Et.Group,{controlId:"unpopular-torrent",children:[v.jsx(Et.Check,{type:"checkbox",label:"Increase timeouts",checked:g,onChange:()=>S(!g)}),v.jsx("small",{id:"emailHelp",className:"form-text text-muted",children:"This might be useful for unpopular torrents with few peers."})]})]})]}),v.jsx(zr,{error:h})]}),v.jsxs(tt.Footer,{children:[a&&v.jsx(An,{}),v.jsx(Mr,{variant:"primary",onClick:c,disabled:o||a||u.length==0,children:"OK"}),v.jsx(Mr,{variant:"secondary",onClick:R,children:"Cancel"})]})]})},hy=()=>v.jsxs("div",{id:"buttons-container",className:"mt-3",children:[v.jsx(dy,{}),v.jsx(py,{})]}),vy=e=>{let t=y.useContext(Vn);return v.jsxs(pv,{children:[v.jsx(zr,{error:e.closeableError,remove:()=>t.setCloseableError(null)}),v.jsx(zr,{error:e.otherError}),v.jsx(ay,{torrents:e.torrents,loading:e.torrentsLoading}),v.jsx(hy,{})]})};function zd(e){if(e===0)return"0 Bytes";const t=1024,n=["Bytes","KB","MB","GB"],r=Math.floor(Math.log(e)/Math.log(t));return parseFloat((e/Math.pow(t,r)).toFixed(2))+" "+n[r]}function yy(e){return e.files.filter(n=>n.included).reduce((n,r)=>n.length>r.length?n:r).name}function gy(e){var n,r,l;let t=(l=(r=(n=e==null?void 0:e.live)==null?void 0:n.time_remaining)==null?void 0:r.duration)==null?void 0:l.secs;return t==null?"N/A":wy(t)}function wy(e){const t=Math.floor(e/3600),n=Math.floor(e%3600/60),r=e%60,l=(o,i)=>o>0?`${o}${i}`:"";return t>0?`${l(t,"h")} ${l(n,"m")}`.trim():n>0?`${l(n,"m")} ${l(r,"s")}`.trim():`${l(r,"s")}`.trim()}function $d(e,t){let n,r=t;const l=async()=>{if(r=await e(),r==null)throw"asyncCallback returned null or undefined";o()};let o=()=>{n=setTimeout(l,r)};return o(),()=>{clearTimeout(n)}}function Sy(e,t){let n;const r=async()=>{await e().then(()=>!1,()=>!0)&&l()};let l=o=>{n=setTimeout(r,o!==void 0?o:t)};return l(0),()=>clearTimeout(n)}async function ky(){const e=document.getElementById("app");Yo.createRoot(e).render(v.jsx(y.StrictMode,{children:v.jsx(cy,{})}))}document.addEventListener("DOMContentLoaded",ky); diff --git a/crates/librqbit/webui/dist/manifest.json b/crates/librqbit/webui/dist/manifest.json index 8d25a85..c3b3044 100644 --- a/crates/librqbit/webui/dist/manifest.json +++ b/crates/librqbit/webui/dist/manifest.json @@ -4,7 +4,7 @@ "src": "assets/logo.svg" }, "index.html": { - "file": "assets/index-b3c336f4.js", + "file": "assets/index-75fed916.js", "isEntry": true, "src": "index.html" } diff --git a/crates/librqbit/webui/src/api.ts b/crates/librqbit/webui/src/api.ts index eb8fe4e..dd8772c 100644 --- a/crates/librqbit/webui/src/api.ts +++ b/crates/librqbit/webui/src/api.ts @@ -22,6 +22,7 @@ export interface TorrentDetails { export interface AddTorrentResponse { id: number | null; details: TorrentDetails; + seen_peers?: Array; } export interface ListTorrentsResponse { @@ -147,7 +148,10 @@ export const API = { }, uploadTorrent: (data: string | File, opts?: { - listOnly?: boolean, selectedFiles?: Array, unpopularTorrent?: boolean, + listOnly?: boolean, + selectedFiles?: Array, + unpopularTorrent?: boolean, + initialPeers?: Array, }): Promise => { opts = opts || {}; let url = '/torrents?&overwrite=true'; @@ -160,6 +164,9 @@ export const API = { if (opts.unpopularTorrent) { url += '&peer_connect_timeout=20&peer_read_write_timeout=60'; } + if (opts.initialPeers) { + url += `&initial_peers=${opts.initialPeers.join(',')}`; + } return makeRequest('POST', url, data) }, diff --git a/crates/librqbit/webui/src/index.tsx b/crates/librqbit/webui/src/index.tsx index 54e6df4..c787629 100644 --- a/crates/librqbit/webui/src/index.tsx +++ b/crates/librqbit/webui/src/index.tsx @@ -378,11 +378,11 @@ const ErrorComponent = (props: { error: Error, remove?: () => void }) => { const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => { const [loading, setLoading] = useState(false); - const [fileList, setFileList] = useState([]); - const [fileListError, setFileListError] = useState(null); + const [listTorrentResponse, setListTorrentResponse] = useState(null); + const [listTorrentError, setListTorrentError] = useState(null); const ctx = useContext(AppContext); - const showModal = data !== null || fileListError !== null; + const showModal = data !== null || listTorrentError !== null; // Get the torrent file list if there's data. useEffect(() => { @@ -394,9 +394,9 @@ const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => { setLoading(true); try { const response = await API.uploadTorrent(data, { listOnly: true }); - setFileList(response.details.files); + setListTorrentResponse(response); } catch (e) { - setFileListError({ text: 'Error uploading torrent', details: e }); + setListTorrentError({ text: 'Error listing torrent files', details: e }); } finally { setLoading(false); } @@ -406,8 +406,8 @@ const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => { const clear = () => { resetData(); - setFileListError(null); - setFileList([]); + setListTorrentError(null); + setListTorrentResponse(null); setLoading(false); } @@ -420,10 +420,10 @@ const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => { ); @@ -471,12 +471,12 @@ const FileInput = () => { const FileSelectionModal = (props: { show: boolean, onHide: () => void, - fileList: Array, - fileListError: Error, - fileListLoading: boolean, + listTorrentResponse: AddTorrentResponse, + listTorrentError: Error, + listTorrentLoading: boolean, data: string | File }) => { - let { show, onHide, fileList, fileListError, fileListLoading, data } = props; + let { show, onHide, listTorrentResponse, listTorrentError, listTorrentLoading, data } = props; const [selectedFiles, setSelectedFiles] = useState([]); const [uploading, setUploading] = useState(false); @@ -485,8 +485,8 @@ const FileSelectionModal = (props: { const ctx = useContext(AppContext); useEffect(() => { - setSelectedFiles(fileList.map((_, id) => id)); - }, [fileList]); + setSelectedFiles(listTorrentResponse ? listTorrentResponse.details.files.map((_, id) => id) : []); + }, [listTorrentResponse]); const clear = () => { onHide(); @@ -505,7 +505,8 @@ const FileSelectionModal = (props: { const handleUpload = async () => { setUploading(true); - API.uploadTorrent(data, { selectedFiles, unpopularTorrent }).then(() => { + let initialPeers = listTorrentResponse.seen_peers ? listTorrentResponse.seen_peers.slice(0, 32) : null; + API.uploadTorrent(data, { selectedFiles, unpopularTorrent, initialPeers }).then(() => { onHide(); ctx.refreshTorrents(); }, @@ -518,16 +519,16 @@ const FileSelectionModal = (props: { return ( - {!!fileListError || Add torrent} + Add torrent
Pick the files to download - {fileListLoading ? - : fileListError ? : + {listTorrentLoading ? + : listTorrentError ? : <> - {fileList.map((file, index) => ( + {listTorrentResponse?.details.files.map((file, index) => ( {uploading && } -