WebUI: migrate to fetch API
[qBittorrent.git] / src / webui / www / private / scripts / mocha-init.js
blob03368ecd464e52e315f79c63ac21651f294b4a39
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2008 Christophe Dumez <chris@qbittorrent.org>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * In addition, as a special exception, the copyright holders give permission to
20 * link this program with the OpenSSL project's "OpenSSL" library (or with
21 * modified versions of it that use the same license as the "OpenSSL" library),
22 * and distribute the linked executables. You must obey the GNU General Public
23 * License in all respects for all of the code used other than "OpenSSL". If you
24 * modify file(s), you may extend this exception to your version of the file(s),
25 * but you are not obligated to do so. If you do not wish to do so, delete this
26 * exception statement from your version.
29 /* -----------------------------------------------------------------
31 ATTACH MOCHA LINK EVENTS
32 Notes: Here is where you define your windows and the events that open them.
33 If you are not using links to run Mocha methods you can remove this function.
35 If you need to add link events to links within windows you are creating, do
36 it in the onContentLoaded function of the new window.
38 ----------------------------------------------------------------- */
39 "use strict";
41 window.qBittorrent ??= {};
42 window.qBittorrent.Dialog ??= (() => {
43 const exports = () => {
44 return {
45 baseModalOptions: baseModalOptions
49 const deepFreeze = (obj) => {
50 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#examples
51 // accounts for circular refs
52 const frozen = new WeakSet();
53 const deepFreezeSafe = (obj) => {
54 if (frozen.has(obj))
55 return;
57 frozen.add(obj);
59 const keys = Reflect.ownKeys(obj);
60 for (const key of keys) {
61 const value = obj[key];
62 if ((value && (typeof value === "object")) || (typeof value === "function"))
63 deepFreezeSafe(value);
65 Object.freeze(obj);
68 deepFreezeSafe(obj);
71 const baseModalOptions = Object.assign(Object.create(null), {
72 addClass: "modalDialog",
73 collapsible: false,
74 cornerRadius: 5,
75 draggable: true,
76 footerHeight: 20,
77 icon: "images/qbittorrent-tray.svg",
78 loadMethod: "xhr",
79 maximizable: false,
80 method: "post",
81 minimizable: false,
82 padding: {
83 top: 15,
84 right: 10,
85 bottom: 15,
86 left: 5
88 resizable: true,
89 width: 480,
90 onCloseComplete: () => {
91 // make sure overlay is properly hidden upon modal closing
92 document.getElementById("modalOverlay").style.display = "none";
94 });
96 deepFreeze(baseModalOptions);
98 return exports();
99 })();
100 Object.freeze(window.qBittorrent.Dialog);
102 const LocalPreferences = new window.qBittorrent.LocalPreferences.LocalPreferences();
104 let saveWindowSize = () => {};
105 let loadWindowWidth = () => {};
106 let loadWindowHeight = () => {};
107 let showDownloadPage = () => {};
108 let globalUploadLimitFN = () => {};
109 let uploadLimitFN = () => {};
110 let shareRatioFN = () => {};
111 let toggleSequentialDownloadFN = () => {};
112 let toggleFirstLastPiecePrioFN = () => {};
113 let setSuperSeedingFN = () => {};
114 let setForceStartFN = () => {};
115 let globalDownloadLimitFN = () => {};
116 let StatisticsLinkFN = () => {};
117 let downloadLimitFN = () => {};
118 let deleteSelectedTorrentsFN = () => {};
119 let stopFN = () => {};
120 let startFN = () => {};
121 let autoTorrentManagementFN = () => {};
122 let recheckFN = () => {};
123 let reannounceFN = () => {};
124 let setLocationFN = () => {};
125 let renameFN = () => {};
126 let renameFilesFN = () => {};
127 let startVisibleTorrentsFN = () => {};
128 let stopVisibleTorrentsFN = () => {};
129 let deleteVisibleTorrentsFN = () => {};
130 let torrentNewCategoryFN = () => {};
131 let torrentSetCategoryFN = () => {};
132 let createCategoryFN = () => {};
133 let createSubcategoryFN = () => {};
134 let editCategoryFN = () => {};
135 let removeCategoryFN = () => {};
136 let deleteUnusedCategoriesFN = () => {};
137 let torrentAddTagsFN = () => {};
138 let torrentSetTagsFN = () => {};
139 let torrentRemoveAllTagsFN = () => {};
140 let createTagFN = () => {};
141 let removeTagFN = () => {};
142 let deleteUnusedTagsFN = () => {};
143 let deleteTrackerFN = () => {};
144 let copyNameFN = () => {};
145 let copyInfohashFN = (policy) => {};
146 let copyMagnetLinkFN = () => {};
147 let copyIdFN = () => {};
148 let copyCommentFN = () => {};
149 let setQueuePositionFN = () => {};
150 let exportTorrentFN = () => {};
152 const initializeWindows = () => {
153 saveWindowSize = (windowId) => {
154 const size = $(windowId).getSize();
155 LocalPreferences.set("window_" + windowId + "_width", size.x);
156 LocalPreferences.set("window_" + windowId + "_height", size.y);
159 loadWindowWidth = (windowId, defaultValue) => {
160 return LocalPreferences.get("window_" + windowId + "_width", defaultValue);
163 loadWindowHeight = (windowId, defaultValue) => {
164 return LocalPreferences.get("window_" + windowId + "_height", defaultValue);
167 const addClickEvent = (el, fn) => {
168 ["Link", "Button"].each((item) => {
169 if ($(el + item))
170 $(el + item).addEventListener("click", fn);
174 addClickEvent("download", (e) => {
175 e.preventDefault();
176 e.stopPropagation();
177 showDownloadPage();
180 showDownloadPage = (urls) => {
181 const id = "downloadPage";
182 const contentUri = new URI("download.html");
184 if (urls && (urls.length > 0))
185 contentUri.setData("urls", urls.map(encodeURIComponent).join("|"));
187 new MochaUI.Window({
188 id: id,
189 icon: "images/qbittorrent-tray.svg",
190 title: "QBT_TR(Download from URLs)QBT_TR[CONTEXT=downloadFromURL]",
191 loadMethod: "iframe",
192 contentURL: contentUri.toString(),
193 addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
194 scrollbars: true,
195 maximizable: false,
196 closable: true,
197 paddingVertical: 0,
198 paddingHorizontal: 0,
199 width: loadWindowWidth(id, 500),
200 height: loadWindowHeight(id, 600),
201 onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
202 saveWindowSize(id);
205 updateMainData();
208 addClickEvent("preferences", (e) => {
209 e.preventDefault();
210 e.stopPropagation();
212 const id = "preferencesPage";
213 new MochaUI.Window({
214 id: id,
215 icon: "images/qbittorrent-tray.svg",
216 title: "QBT_TR(Options)QBT_TR[CONTEXT=OptionsDialog]",
217 loadMethod: "xhr",
218 toolbar: true,
219 contentURL: new URI("views/preferences.html").toString(),
220 require: {
221 css: ["css/Tabs.css"]
223 toolbarURL: "views/preferencesToolbar.html",
224 maximizable: false,
225 closable: true,
226 paddingVertical: 0,
227 paddingHorizontal: 0,
228 width: loadWindowWidth(id, 730),
229 height: loadWindowHeight(id, 600),
230 onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
231 saveWindowSize(id);
236 addClickEvent("manageCookies", (e) => {
237 e.preventDefault();
238 e.stopPropagation();
240 const id = "cookiesPage";
241 new MochaUI.Window({
242 id: id,
243 title: "QBT_TR(Manage Cookies)QBT_TR[CONTEXT=CookiesDialog]",
244 loadMethod: "xhr",
245 contentURL: new URI("views/cookies.html").toString(),
246 maximizable: false,
247 paddingVertical: 0,
248 paddingHorizontal: 0,
249 width: loadWindowWidth(id, 900),
250 height: loadWindowHeight(id, 400),
251 onResize: () => {
252 saveWindowSize(id);
257 addClickEvent("upload", (e) => {
258 e.preventDefault();
259 e.stopPropagation();
261 const id = "uploadPage";
262 new MochaUI.Window({
263 id: id,
264 icon: "images/qbittorrent-tray.svg",
265 title: "QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]",
266 loadMethod: "iframe",
267 contentURL: new URI("upload.html").toString(),
268 addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
269 scrollbars: true,
270 maximizable: false,
271 paddingVertical: 0,
272 paddingHorizontal: 0,
273 width: loadWindowWidth(id, 500),
274 height: loadWindowHeight(id, 460),
275 onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
276 saveWindowSize(id);
279 updateMainData();
282 globalUploadLimitFN = () => {
283 new MochaUI.Window({
284 id: "uploadLimitPage",
285 icon: "images/qbittorrent-tray.svg",
286 title: "QBT_TR(Global Upload Speed Limit)QBT_TR[CONTEXT=MainWindow]",
287 loadMethod: "iframe",
288 contentURL: new URI("uploadlimit.html").setData("hashes", "global").toString(),
289 scrollbars: false,
290 resizable: false,
291 maximizable: false,
292 paddingVertical: 0,
293 paddingHorizontal: 0,
294 width: 424,
295 height: 100
299 uploadLimitFN = () => {
300 const hashes = torrentsTable.selectedRowsIds();
301 if (hashes.length) {
302 new MochaUI.Window({
303 id: "uploadLimitPage",
304 icon: "images/qbittorrent-tray.svg",
305 title: "QBT_TR(Torrent Upload Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
306 loadMethod: "iframe",
307 contentURL: new URI("uploadlimit.html").setData("hashes", hashes.join("|")).toString(),
308 scrollbars: false,
309 resizable: false,
310 maximizable: false,
311 paddingVertical: 0,
312 paddingHorizontal: 0,
313 width: 424,
314 height: 100
319 shareRatioFN = () => {
320 const hashes = torrentsTable.selectedRowsIds();
321 if (hashes.length) {
322 let shareRatio = null;
323 let torrentsHaveSameShareRatio = true;
325 // check if all selected torrents have same share ratio
326 for (let i = 0; i < hashes.length; ++i) {
327 const hash = hashes[i];
328 const row = torrentsTable.getRow(hash).full_data;
329 const origValues = row.ratio_limit + "|" + row.seeding_time_limit + "|" + row.inactive_seeding_time_limit + "|"
330 + row.max_ratio + "|" + row.max_seeding_time + "|" + row.max_inactive_seeding_time;
332 // initialize value
333 if (shareRatio === null)
334 shareRatio = origValues;
336 if (origValues !== shareRatio) {
337 torrentsHaveSameShareRatio = false;
338 break;
342 // if all torrents have same share ratio, display that share ratio. else use the default
343 const orig = torrentsHaveSameShareRatio ? shareRatio : "";
344 new MochaUI.Window({
345 id: "shareRatioPage",
346 icon: "images/qbittorrent-tray.svg",
347 title: "QBT_TR(Torrent Upload/Download Ratio Limiting)QBT_TR[CONTEXT=UpDownRatioDialog]",
348 loadMethod: "iframe",
349 contentURL: new URI("shareratio.html").setData("hashes", hashes.join("|")).setData("orig", orig).toString(),
350 scrollbars: false,
351 maximizable: false,
352 paddingVertical: 0,
353 paddingHorizontal: 0,
354 width: 424,
355 height: 220
360 toggleSequentialDownloadFN = () => {
361 const hashes = torrentsTable.selectedRowsIds();
362 if (hashes.length) {
363 fetch("api/v2/torrents/toggleSequentialDownload", {
364 method: "POST",
365 body: new URLSearchParams({
366 hashes: hashes.join("|")
369 updateMainData();
373 toggleFirstLastPiecePrioFN = () => {
374 const hashes = torrentsTable.selectedRowsIds();
375 if (hashes.length) {
376 fetch("api/v2/torrents/toggleFirstLastPiecePrio", {
377 method: "POST",
378 body: new URLSearchParams({
379 hashes: hashes.join("|")
382 updateMainData();
386 setSuperSeedingFN = (val) => {
387 const hashes = torrentsTable.selectedRowsIds();
388 if (hashes.length) {
389 fetch("api/v2/torrents/setSuperSeeding", {
390 method: "POST",
391 body: new URLSearchParams({
392 hashes: hashes.join("|"),
393 value: val
396 updateMainData();
400 setForceStartFN = () => {
401 const hashes = torrentsTable.selectedRowsIds();
402 if (hashes.length) {
403 fetch("api/v2/torrents/setForceStart", {
404 method: "POST",
405 body: new URLSearchParams({
406 hashes: hashes.join("|"),
407 value: "true"
410 updateMainData();
414 globalDownloadLimitFN = () => {
415 new MochaUI.Window({
416 id: "downloadLimitPage",
417 icon: "images/qbittorrent-tray.svg",
418 title: "QBT_TR(Global Download Speed Limit)QBT_TR[CONTEXT=MainWindow]",
419 loadMethod: "iframe",
420 contentURL: new URI("downloadlimit.html").setData("hashes", "global").toString(),
421 scrollbars: false,
422 resizable: false,
423 maximizable: false,
424 paddingVertical: 0,
425 paddingHorizontal: 0,
426 width: 424,
427 height: 100
431 StatisticsLinkFN = () => {
432 const id = "statisticspage";
433 new MochaUI.Window({
434 id: id,
435 icon: "images/qbittorrent-tray.svg",
436 title: "QBT_TR(Statistics)QBT_TR[CONTEXT=StatsDialog]",
437 loadMethod: "xhr",
438 contentURL: new URI("views/statistics.html").toString(),
439 maximizable: false,
440 padding: 10,
441 width: loadWindowWidth(id, 285),
442 height: loadWindowHeight(id, 415),
443 onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
444 saveWindowSize(id);
449 downloadLimitFN = () => {
450 const hashes = torrentsTable.selectedRowsIds();
451 if (hashes.length) {
452 new MochaUI.Window({
453 id: "downloadLimitPage",
454 icon: "images/qbittorrent-tray.svg",
455 title: "QBT_TR(Torrent Download Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
456 loadMethod: "iframe",
457 contentURL: new URI("downloadlimit.html").setData("hashes", hashes.join("|")).toString(),
458 scrollbars: false,
459 resizable: false,
460 maximizable: false,
461 paddingVertical: 0,
462 paddingHorizontal: 0,
463 width: 424,
464 height: 100
469 deleteSelectedTorrentsFN = (forceDeleteFiles = false) => {
470 const hashes = torrentsTable.selectedRowsIds();
471 if (hashes.length > 0) {
472 if (window.qBittorrent.Cache.preferences.get().confirm_torrent_deletion) {
473 new MochaUI.Modal({
474 ...window.qBittorrent.Dialog.baseModalOptions,
475 id: "confirmDeletionPage",
476 title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
477 data: {
478 hashes: hashes,
479 forceDeleteFiles: forceDeleteFiles
481 contentURL: "views/confirmdeletion.html",
482 onContentLoaded: (w) => {
483 MochaUI.resizeWindow(w, { centered: true });
484 MochaUI.centerWindow(w);
486 onCloseComplete: () => {
487 // make sure overlay is properly hidden upon modal closing
488 document.getElementById("modalOverlay").style.display = "none";
492 else {
493 fetch("api/v2/torrents/delete", {
494 method: "POST",
495 body: new URLSearchParams({
496 hashes: hashes.join("|"),
497 deleteFiles: forceDeleteFiles
500 .then((response) => {
501 if (!response.ok) {
502 alert("QBT_TR(Unable to delete torrents.)QBT_TR[CONTEXT=HttpServer]");
503 return;
506 torrentsTable.deselectAll();
507 updateMainData();
508 updatePropertiesPanel();
514 addClickEvent("delete", (e) => {
515 e.preventDefault();
516 e.stopPropagation();
517 deleteSelectedTorrentsFN();
520 stopFN = () => {
521 const hashes = torrentsTable.selectedRowsIds();
522 if (hashes.length) {
523 fetch("api/v2/torrents/stop", {
524 method: "POST",
525 body: new URLSearchParams({
526 hashes: hashes.join("|")
529 updateMainData();
533 startFN = () => {
534 const hashes = torrentsTable.selectedRowsIds();
535 if (hashes.length) {
536 fetch("api/v2/torrents/start", {
537 method: "POST",
538 body: new URLSearchParams({
539 hashes: hashes.join("|")
542 updateMainData();
546 autoTorrentManagementFN = () => {
547 const hashes = torrentsTable.selectedRowsIds();
548 if (hashes.length > 0) {
549 const enableAutoTMM = hashes.some((hash) => !(torrentsTable.getRow(hash).full_data.auto_tmm));
550 if (enableAutoTMM) {
551 new MochaUI.Modal({
552 ...window.qBittorrent.Dialog.baseModalOptions,
553 id: "confirmAutoTMMDialog",
554 title: "QBT_TR(Enable automatic torrent management)QBT_TR[CONTEXT=confirmAutoTMMDialog]",
555 data: {
556 hashes: hashes,
557 enable: enableAutoTMM
559 contentURL: "views/confirmAutoTMM.html"
562 else {
563 fetch("api/v2/torrents/setAutoManagement", {
564 method: "POST",
565 body: new URLSearchParams({
566 hashes: hashes.join("|"),
567 enable: enableAutoTMM
570 .then((response) => {
571 if (!response.ok) {
572 alert("QBT_TR(Unable to set Auto Torrent Management for the selected torrents.)QBT_TR[CONTEXT=HttpServer]");
573 return;
576 updateMainData();
582 recheckFN = () => {
583 const hashes = torrentsTable.selectedRowsIds();
584 if (hashes.length > 0) {
585 if (window.qBittorrent.Cache.preferences.get().confirm_torrent_recheck) {
586 new MochaUI.Modal({
587 ...window.qBittorrent.Dialog.baseModalOptions,
588 id: "confirmRecheckDialog",
589 title: "QBT_TR(Recheck confirmation)QBT_TR[CONTEXT=confirmRecheckDialog]",
590 data: { hashes: hashes },
591 contentURL: "views/confirmRecheck.html"
594 else {
595 fetch("api/v2/torrents/recheck", {
596 method: "POST",
597 body: new URLSearchParams({
598 hashes: hashes.join("|"),
601 .then((response) => {
602 if (!response.ok) {
603 alert("QBT_TR(Unable to recheck torrents.)QBT_TR[CONTEXT=HttpServer]");
604 return;
607 updateMainData();
613 reannounceFN = () => {
614 const hashes = torrentsTable.selectedRowsIds();
615 if (hashes.length) {
616 fetch("api/v2/torrents/reannounce", {
617 method: "POST",
618 body: new URLSearchParams({
619 hashes: hashes.join("|")
622 updateMainData();
626 setLocationFN = () => {
627 const hashes = torrentsTable.selectedRowsIds();
628 if (hashes.length) {
629 const hash = hashes[0];
630 const row = torrentsTable.getRow(hash);
632 new MochaUI.Window({
633 id: "setLocationPage",
634 icon: "images/qbittorrent-tray.svg",
635 title: "QBT_TR(Set location)QBT_TR[CONTEXT=TransferListWidget]",
636 loadMethod: "iframe",
637 contentURL: new URI("setlocation.html").setData("hashes", hashes.join("|")).setData("path", encodeURIComponent(row.full_data.save_path)).toString(),
638 scrollbars: false,
639 resizable: true,
640 maximizable: false,
641 paddingVertical: 0,
642 paddingHorizontal: 0,
643 width: 400,
644 height: 130
649 renameFN = () => {
650 const hashes = torrentsTable.selectedRowsIds();
651 if (hashes.length === 1) {
652 const hash = hashes[0];
653 const row = torrentsTable.getRow(hash);
654 if (row) {
655 new MochaUI.Window({
656 id: "renamePage",
657 icon: "images/qbittorrent-tray.svg",
658 title: "QBT_TR(Rename)QBT_TR[CONTEXT=TransferListWidget]",
659 loadMethod: "iframe",
660 contentURL: new URI("rename.html").setData("hash", hash).setData("name", row.full_data.name).toString(),
661 scrollbars: false,
662 resizable: true,
663 maximizable: false,
664 paddingVertical: 0,
665 paddingHorizontal: 0,
666 width: 400,
667 height: 100
673 renameFilesFN = () => {
674 const hashes = torrentsTable.selectedRowsIds();
675 if (hashes.length === 1) {
676 const hash = hashes[0];
677 const row = torrentsTable.getRow(hash);
678 if (row) {
679 new MochaUI.Window({
680 id: "multiRenamePage",
681 icon: "images/qbittorrent-tray.svg",
682 title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TransferListWidget]",
683 data: { hash: hash, selectedRows: [] },
684 loadMethod: "xhr",
685 contentURL: "rename_files.html",
686 scrollbars: false,
687 resizable: true,
688 maximizable: false,
689 paddingVertical: 0,
690 paddingHorizontal: 0,
691 width: 800,
692 height: 420,
693 resizeLimit: { x: [800], y: [420] }
699 startVisibleTorrentsFN = () => {
700 const hashes = torrentsTable.getFilteredTorrentsHashes(selectedStatus, selectedCategory, selectedTag, selectedTracker);
701 if (hashes.length > 0) {
702 fetch("api/v2/torrents/start", {
703 method: "POST",
704 body: new URLSearchParams({
705 hashes: hashes.join("|")
708 .then((response) => {
709 if (!response.ok) {
710 alert("QBT_TR(Unable to start torrents.)QBT_TR[CONTEXT=HttpServer]");
711 return;
714 updateMainData();
715 updatePropertiesPanel();
720 stopVisibleTorrentsFN = () => {
721 const hashes = torrentsTable.getFilteredTorrentsHashes(selectedStatus, selectedCategory, selectedTag, selectedTracker);
722 if (hashes.length > 0) {
723 fetch("api/v2/torrents/stop", {
724 method: "POST",
725 body: new URLSearchParams({
726 hashes: hashes.join("|")
729 .then((response) => {
730 if (!response.ok) {
731 alert("QBT_TR(Unable to stop torrents.)QBT_TR[CONTEXT=HttpServer]");
732 return;
735 updateMainData();
736 updatePropertiesPanel();
741 deleteVisibleTorrentsFN = () => {
742 const hashes = torrentsTable.getFilteredTorrentsHashes(selectedStatus, selectedCategory, selectedTag, selectedTracker);
743 if (hashes.length > 0) {
744 if (window.qBittorrent.Cache.preferences.get().confirm_torrent_deletion) {
745 new MochaUI.Modal({
746 ...window.qBittorrent.Dialog.baseModalOptions,
747 id: "confirmDeletionPage",
748 title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
749 data: {
750 hashes: hashes,
751 isDeletingVisibleTorrents: true
753 contentURL: "views/confirmdeletion.html",
754 onContentLoaded: (w) => {
755 MochaUI.resizeWindow(w, { centered: true });
756 MochaUI.centerWindow(w);
760 else {
761 fetch("api/v2/torrents/delete", {
762 method: "POST",
763 body: new URLSearchParams({
764 hashes: hashes.join("|"),
765 deleteFiles: false,
768 .then((response) => {
769 if (!response.ok) {
770 alert("QBT_TR(Unable to delete torrents.)QBT_TR[CONTEXT=HttpServer]");
771 return;
774 torrentsTable.deselectAll();
775 updateMainData();
776 updatePropertiesPanel();
782 torrentNewCategoryFN = () => {
783 const action = "set";
784 const hashes = torrentsTable.selectedRowsIds();
785 if (hashes.length) {
786 new MochaUI.Window({
787 id: "newCategoryPage",
788 icon: "images/qbittorrent-tray.svg",
789 title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
790 loadMethod: "iframe",
791 contentURL: new URI("newcategory.html").setData("action", action).setData("hashes", hashes.join("|")).toString(),
792 scrollbars: false,
793 resizable: true,
794 maximizable: false,
795 paddingVertical: 0,
796 paddingHorizontal: 0,
797 width: 400,
798 height: 150
803 torrentSetCategoryFN = (categoryHash) => {
804 const hashes = torrentsTable.selectedRowsIds();
805 if (hashes.length <= 0)
806 return;
808 const categoryName = category_list.has(categoryHash)
809 ? category_list.get(categoryHash).name
810 : "";
811 fetch("api/v2/torrents/setCategory", {
812 method: "POST",
813 body: new URLSearchParams({
814 hashes: hashes.join("|"),
815 category: categoryName
818 .then((response) => {
819 if (!response.ok)
820 return;
822 updateMainData();
826 createCategoryFN = () => {
827 const action = "create";
828 new MochaUI.Window({
829 id: "newCategoryPage",
830 icon: "images/qbittorrent-tray.svg",
831 title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
832 loadMethod: "iframe",
833 contentURL: new URI("newcategory.html").setData("action", action).toString(),
834 scrollbars: false,
835 resizable: true,
836 maximizable: false,
837 paddingVertical: 0,
838 paddingHorizontal: 0,
839 width: 400,
840 height: 150
844 createSubcategoryFN = (categoryHash) => {
845 const action = "createSubcategory";
846 const categoryName = category_list.get(categoryHash).name + "/";
847 new MochaUI.Window({
848 id: "newSubcategoryPage",
849 icon: "images/qbittorrent-tray.svg",
850 title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
851 loadMethod: "iframe",
852 contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", categoryName).toString(),
853 scrollbars: false,
854 resizable: true,
855 maximizable: false,
856 paddingVertical: 0,
857 paddingHorizontal: 0,
858 width: 400,
859 height: 150
863 editCategoryFN = (categoryHash) => {
864 const action = "edit";
865 const category = category_list.get(categoryHash);
866 new MochaUI.Window({
867 id: "editCategoryPage",
868 icon: "images/qbittorrent-tray.svg",
869 title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
870 loadMethod: "iframe",
871 contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", category.name).setData("savePath", category.savePath).toString(),
872 scrollbars: false,
873 resizable: true,
874 maximizable: false,
875 paddingVertical: 0,
876 paddingHorizontal: 0,
877 width: 400,
878 height: 150
882 removeCategoryFN = (categoryHash) => {
883 fetch("api/v2/torrents/removeCategories", {
884 method: "POST",
885 body: new URLSearchParams({
886 categories: category_list.get(categoryHash).name
889 .then((response) => {
890 if (!response.ok)
891 return;
893 setCategoryFilter(CATEGORIES_ALL);
894 updateMainData();
898 deleteUnusedCategoriesFN = () => {
899 const categories = [];
900 category_list.forEach((category, hash) => {
901 if (torrentsTable.getFilteredTorrentsNumber("all", hash, TAGS_ALL, TRACKERS_ALL) === 0)
902 categories.push(category.name);
904 fetch("api/v2/torrents/removeCategories", {
905 method: "POST",
906 body: new URLSearchParams({
907 categories: categories.join("\n")
910 .then((response) => {
911 if (!response.ok)
912 return;
914 setCategoryFilter(CATEGORIES_ALL);
915 updateMainData();
919 torrentAddTagsFN = () => {
920 const action = "set";
921 const hashes = torrentsTable.selectedRowsIds();
922 if (hashes.length) {
923 new MochaUI.Window({
924 id: "newTagPage",
925 icon: "images/qbittorrent-tray.svg",
926 title: "QBT_TR(Add tags)QBT_TR[CONTEXT=TransferListWidget]",
927 loadMethod: "iframe",
928 contentURL: new URI("newtag.html").setData("action", action).setData("hashes", hashes.join("|")).toString(),
929 scrollbars: false,
930 resizable: true,
931 maximizable: false,
932 paddingVertical: 0,
933 paddingHorizontal: 0,
934 width: 250,
935 height: 100
940 torrentSetTagsFN = (tagHash, isSet) => {
941 const hashes = torrentsTable.selectedRowsIds();
942 if (hashes.length <= 0)
943 return;
945 fetch((isSet ? "api/v2/torrents/addTags" : "api/v2/torrents/removeTags"), {
946 method: "POST",
947 body: new URLSearchParams({
948 hashes: hashes.join("|"),
949 tags: (tagList.get(tagHash)?.name || "")
954 torrentRemoveAllTagsFN = () => {
955 const hashes = torrentsTable.selectedRowsIds();
956 if (hashes.length) {
957 fetch("api/v2/torrents/removeTags", {
958 method: "POST",
959 body: new URLSearchParams({
960 hashes: hashes.join("|")
966 createTagFN = () => {
967 const action = "create";
968 new MochaUI.Window({
969 id: "newTagPage",
970 icon: "images/qbittorrent-tray.svg",
971 title: "QBT_TR(New Tag)QBT_TR[CONTEXT=TagFilterWidget]",
972 loadMethod: "iframe",
973 contentURL: new URI("newtag.html").setData("action", action).toString(),
974 scrollbars: false,
975 resizable: true,
976 maximizable: false,
977 paddingVertical: 0,
978 paddingHorizontal: 0,
979 width: 250,
980 height: 100
982 updateMainData();
985 removeTagFN = (tagHash) => {
986 fetch("api/v2/torrents/deleteTags", {
987 method: "POST",
988 body: new URLSearchParams({
989 tags: tagList.get(tagHash).name
992 setTagFilter(TAGS_ALL);
995 deleteUnusedTagsFN = () => {
996 const tags = [];
997 tagList.forEach((tag, hash) => {
998 if (torrentsTable.getFilteredTorrentsNumber("all", CATEGORIES_ALL, hash, TRACKERS_ALL) === 0)
999 tags.push(tag.name);
1001 fetch("api/v2/torrents/deleteTags", {
1002 method: "POST",
1003 body: new URLSearchParams({
1004 tags: tags.join(",")
1007 setTagFilter(TAGS_ALL);
1010 deleteTrackerFN = (trackerHash) => {
1011 const trackerHashInt = Number(trackerHash);
1012 if ((trackerHashInt === TRACKERS_ALL) || (trackerHashInt === TRACKERS_TRACKERLESS))
1013 return;
1015 const tracker = trackerList.get(trackerHashInt);
1016 const host = tracker.host;
1017 const urls = [...tracker.trackerTorrentMap.keys()];
1019 new MochaUI.Window({
1020 id: "confirmDeletionPage",
1021 title: "QBT_TR(Remove tracker)QBT_TR[CONTEXT=confirmDeletionDlg]",
1022 loadMethod: "iframe",
1023 contentURL: new URI("confirmtrackerdeletion.html").setData("host", host).setData("urls", urls.map(encodeURIComponent).join("|")).toString(),
1024 scrollbars: false,
1025 resizable: true,
1026 maximizable: false,
1027 padding: 10,
1028 width: 424,
1029 height: 100,
1030 onCloseComplete: () => {
1031 updateMainData();
1032 setTrackerFilter(TRACKERS_ALL);
1037 copyNameFN = () => {
1038 const selectedRows = torrentsTable.selectedRowsIds();
1039 const names = [];
1040 if (selectedRows.length > 0) {
1041 const rows = torrentsTable.getFilteredAndSortedRows();
1042 for (let i = 0; i < selectedRows.length; ++i) {
1043 const hash = selectedRows[i];
1044 names.push(rows[hash].full_data.name);
1047 return names.join("\n");
1050 copyInfohashFN = (policy) => {
1051 const selectedRows = torrentsTable.selectedRowsIds();
1052 const infohashes = [];
1053 if (selectedRows.length > 0) {
1054 const rows = torrentsTable.getFilteredAndSortedRows();
1055 switch (policy) {
1056 case 1:
1057 for (const id of selectedRows) {
1058 const infohash = rows[id].full_data.infohash_v1;
1059 if (infohash !== "")
1060 infohashes.push(infohash);
1062 break;
1063 case 2:
1064 for (const id of selectedRows) {
1065 const infohash = rows[id].full_data.infohash_v2;
1066 if (infohash !== "")
1067 infohashes.push(infohash);
1069 break;
1072 return infohashes.join("\n");
1075 copyMagnetLinkFN = () => {
1076 const selectedRows = torrentsTable.selectedRowsIds();
1077 const magnets = [];
1078 if (selectedRows.length > 0) {
1079 const rows = torrentsTable.getFilteredAndSortedRows();
1080 for (let i = 0; i < selectedRows.length; ++i) {
1081 const hash = selectedRows[i];
1082 magnets.push(rows[hash].full_data.magnet_uri);
1085 return magnets.join("\n");
1088 copyIdFN = () => {
1089 return torrentsTable.selectedRowsIds().join("\n");
1092 copyCommentFN = () => {
1093 const selectedRows = torrentsTable.selectedRowsIds();
1094 const comments = [];
1095 if (selectedRows.length > 0) {
1096 const rows = torrentsTable.getFilteredAndSortedRows();
1097 for (let i = 0; i < selectedRows.length; ++i) {
1098 const hash = selectedRows[i];
1099 const comment = rows[hash].full_data.comment;
1100 if (comment && (comment !== ""))
1101 comments.push(comment);
1104 return comments.join("\n---------\n");
1107 exportTorrentFN = async () => {
1108 const hashes = torrentsTable.selectedRowsIds();
1109 for (const hash of hashes) {
1110 const row = torrentsTable.getRow(hash);
1111 if (!row)
1112 continue;
1114 const name = row.full_data.name;
1115 const url = new URI("api/v2/torrents/export").setData("hash", hash).toString();
1117 // download response to file
1118 await window.qBittorrent.Misc.downloadFile(url, `${name}.torrent`, "QBT_TR(Unable to export torrent file)QBT_TR[CONTEXT=MainWindow]");
1120 // https://stackoverflow.com/questions/53560991/automatic-file-downloads-limited-to-10-files-on-chrome-browser
1121 await window.qBittorrent.Misc.sleep(200);
1125 addClickEvent("stopAll", (e) => {
1126 e.preventDefault();
1127 e.stopPropagation();
1129 if (confirm("QBT_TR(Would you like to stop all torrents?)QBT_TR[CONTEXT=MainWindow]")) {
1130 fetch("api/v2/torrents/stop", {
1131 method: "POST",
1132 body: new URLSearchParams({
1133 hashes: "all"
1136 updateMainData();
1140 addClickEvent("startAll", (e) => {
1141 e.preventDefault();
1142 e.stopPropagation();
1144 if (confirm("QBT_TR(Would you like to start all torrents?)QBT_TR[CONTEXT=MainWindow]")) {
1145 fetch("api/v2/torrents/start", {
1146 method: "POST",
1147 body: new URLSearchParams({
1148 hashes: "all"
1151 updateMainData();
1155 ["stop", "start", "recheck"].each((item) => {
1156 addClickEvent(item, (e) => {
1157 e.preventDefault();
1158 e.stopPropagation();
1160 const hashes = torrentsTable.selectedRowsIds();
1161 if (hashes.length) {
1162 hashes.each((hash, index) => {
1163 fetch(`api/v2/torrents/${item}`, {
1164 method: "POST",
1165 body: new URLSearchParams({
1166 hashes: hash
1170 updateMainData();
1175 ["decreasePrio", "increasePrio", "topPrio", "bottomPrio"].each((item) => {
1176 addClickEvent(item, (e) => {
1177 e.preventDefault();
1178 e.stopPropagation();
1179 setQueuePositionFN(item);
1183 setQueuePositionFN = (cmd) => {
1184 const hashes = torrentsTable.selectedRowsIds();
1185 if (hashes.length) {
1186 fetch(`api/v2/torrents/${cmd}`, {
1187 method: "POST",
1188 body: new URLSearchParams({
1189 hashes: hashes.join("|")
1192 updateMainData();
1196 addClickEvent("about", (e) => {
1197 e.preventDefault();
1198 e.stopPropagation();
1200 const id = "aboutpage";
1201 new MochaUI.Window({
1202 id: id,
1203 icon: "images/qbittorrent-tray.svg",
1204 title: "QBT_TR(About qBittorrent)QBT_TR[CONTEXT=AboutDialog]",
1205 loadMethod: "xhr",
1206 contentURL: new URI("views/about.html").toString(),
1207 require: {
1208 css: ["css/Tabs.css"]
1210 toolbar: true,
1211 toolbarURL: "views/aboutToolbar.html",
1212 padding: 10,
1213 width: loadWindowWidth(id, 570),
1214 height: loadWindowHeight(id, 360),
1215 onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
1216 saveWindowSize(id);
1221 addClickEvent("logout", (e) => {
1222 e.preventDefault();
1223 e.stopPropagation();
1225 fetch("api/v2/auth/logout", {
1226 method: "POST"
1228 .then((response) => {
1229 if (!response.ok)
1230 return;
1232 window.location.reload(true);
1236 addClickEvent("shutdown", (e) => {
1237 e.preventDefault();
1238 e.stopPropagation();
1240 if (confirm("QBT_TR(Are you sure you want to quit qBittorrent?)QBT_TR[CONTEXT=MainWindow]")) {
1241 fetch("api/v2/app/shutdown", {
1242 method: "POST"
1244 .then((response) => {
1245 if (!response.ok)
1246 return;
1248 const shutdownMessage = "QBT_TR(%1 has been shutdown)QBT_TR[CONTEXT=HttpServer]".replace("%1", window.qBittorrent.Client.mainTitle());
1249 document.write(`<!doctype html><html lang="${LANG}"><head> <meta charset="UTF-8"> <meta name="color-scheme" content="light dark"> <title>${shutdownMessage}</title> <style>* {font-family: Arial, Helvetica, sans-serif;}</style></head><body> <h1 style="text-align: center;">${shutdownMessage}</h1></body></html>`);
1250 document.close();
1251 window.stop();
1252 window.qBittorrent.Client.stop();
1257 // Deactivate menu header links
1258 $$("a.returnFalse").each((el) => {
1259 el.addEventListener("click", (e) => {
1260 e.preventDefault();
1261 e.stopPropagation();