WebUI: prefer arrow function in callbacks
[qBittorrent.git] / src / webui / www / private / scripts / mocha-init.js
blob0919aca98f805db133a4d084eb87cb6c22106d90
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2008  Christophe Dumez <chris@qbittorrent.org>
4  *
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.
9  *
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.
14  *
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.
18  *
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.
27  */
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 const LocalPreferences = new window.qBittorrent.LocalPreferences.LocalPreferencesClass();
43 let saveWindowSize = function() {};
44 let loadWindowWidth = function() {};
45 let loadWindowHeight = function() {};
46 let showDownloadPage = function() {};
47 let globalUploadLimitFN = function() {};
48 let uploadLimitFN = function() {};
49 let shareRatioFN = function() {};
50 let toggleSequentialDownloadFN = function() {};
51 let toggleFirstLastPiecePrioFN = function() {};
52 let setSuperSeedingFN = function() {};
53 let setForceStartFN = function() {};
54 let globalDownloadLimitFN = function() {};
55 let StatisticsLinkFN = function() {};
56 let downloadLimitFN = function() {};
57 let deleteFN = function() {};
58 let stopFN = function() {};
59 let startFN = function() {};
60 let autoTorrentManagementFN = function() {};
61 let recheckFN = function() {};
62 let reannounceFN = function() {};
63 let setLocationFN = function() {};
64 let renameFN = function() {};
65 let renameFilesFN = function() {};
66 let torrentNewCategoryFN = function() {};
67 let torrentSetCategoryFN = function() {};
68 let createCategoryFN = function() {};
69 let createSubcategoryFN = function() {};
70 let editCategoryFN = function() {};
71 let removeCategoryFN = function() {};
72 let deleteUnusedCategoriesFN = function() {};
73 let startTorrentsByCategoryFN = function() {};
74 let stopTorrentsByCategoryFN = function() {};
75 let deleteTorrentsByCategoryFN = function() {};
76 let torrentAddTagsFN = function() {};
77 let torrentSetTagsFN = function() {};
78 let torrentRemoveAllTagsFN = function() {};
79 let createTagFN = function() {};
80 let removeTagFN = function() {};
81 let deleteUnusedTagsFN = function() {};
82 let startTorrentsByTagFN = function() {};
83 let stopTorrentsByTagFN = function() {};
84 let deleteTorrentsByTagFN = function() {};
85 let startTorrentsByTrackerFN = function() {};
86 let stopTorrentsByTrackerFN = function() {};
87 let deleteTorrentsByTrackerFN = function() {};
88 let copyNameFN = function() {};
89 let copyInfohashFN = function(policy) {};
90 let copyMagnetLinkFN = function() {};
91 let copyIdFN = function() {};
92 let copyCommentFN = function() {};
93 let setQueuePositionFN = function() {};
94 let exportTorrentFN = function() {};
96 const initializeWindows = function() {
97     saveWindowSize = function(windowId) {
98         const size = $(windowId).getSize();
99         LocalPreferences.set("window_" + windowId + "_width", size.x);
100         LocalPreferences.set("window_" + windowId + "_height", size.y);
101     };
103     loadWindowWidth = function(windowId, defaultValue) {
104         return LocalPreferences.get("window_" + windowId + "_width", defaultValue);
105     };
107     loadWindowHeight = function(windowId, defaultValue) {
108         return LocalPreferences.get("window_" + windowId + "_height", defaultValue);
109     };
111     function addClickEvent(el, fn) {
112         ["Link", "Button"].each((item) => {
113             if ($(el + item)) {
114                 $(el + item).addEvent("click", fn);
115             }
116         });
117     }
119     addClickEvent("download", (e) => {
120         new Event(e).stop();
121         showDownloadPage();
122     });
124     showDownloadPage = function(urls) {
125         const id = "downloadPage";
126         const contentUri = new URI("download.html");
128         if (urls && (urls.length > 0)) {
129             contentUri.setData("urls", urls.map(encodeURIComponent).join("|"));
130         }
132         new MochaUI.Window({
133             id: id,
134             title: "QBT_TR(Download from URLs)QBT_TR[CONTEXT=downloadFromURL]",
135             loadMethod: "iframe",
136             contentURL: contentUri.toString(),
137             addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
138             scrollbars: true,
139             maximizable: false,
140             closable: true,
141             paddingVertical: 0,
142             paddingHorizontal: 0,
143             width: loadWindowWidth(id, 500),
144             height: loadWindowHeight(id, 600),
145             onResize: function() {
146                 saveWindowSize(id);
147             }
148         });
149         updateMainData();
150     };
152     addClickEvent("preferences", (e) => {
153         new Event(e).stop();
154         const id = "preferencesPage";
155         new MochaUI.Window({
156             id: id,
157             title: "QBT_TR(Options)QBT_TR[CONTEXT=OptionsDialog]",
158             loadMethod: "xhr",
159             toolbar: true,
160             contentURL: new URI("views/preferences.html").toString(),
161             require: {
162                 css: ["css/Tabs.css"]
163             },
164             toolbarURL: "views/preferencesToolbar.html",
165             maximizable: false,
166             closable: true,
167             paddingVertical: 0,
168             paddingHorizontal: 0,
169             width: loadWindowWidth(id, 700),
170             height: loadWindowHeight(id, 600),
171             onResize: function() {
172                 saveWindowSize(id);
173             }
174         });
175     });
177     addClickEvent("upload", (e) => {
178         new Event(e).stop();
179         const id = "uploadPage";
180         new MochaUI.Window({
181             id: id,
182             title: "QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]",
183             loadMethod: "iframe",
184             contentURL: new URI("upload.html").toString(),
185             addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
186             scrollbars: true,
187             maximizable: false,
188             paddingVertical: 0,
189             paddingHorizontal: 0,
190             width: loadWindowWidth(id, 500),
191             height: loadWindowHeight(id, 460),
192             onResize: function() {
193                 saveWindowSize(id);
194             }
195         });
196         updateMainData();
197     });
199     globalUploadLimitFN = function() {
200         new MochaUI.Window({
201             id: "uploadLimitPage",
202             title: "QBT_TR(Global Upload Speed Limit)QBT_TR[CONTEXT=MainWindow]",
203             loadMethod: "iframe",
204             contentURL: new URI("uploadlimit.html").setData("hashes", "global").toString(),
205             scrollbars: false,
206             resizable: false,
207             maximizable: false,
208             paddingVertical: 0,
209             paddingHorizontal: 0,
210             width: 424,
211             height: 80
212         });
213     };
215     uploadLimitFN = function() {
216         const hashes = torrentsTable.selectedRowsIds();
217         if (hashes.length) {
218             new MochaUI.Window({
219                 id: "uploadLimitPage",
220                 title: "QBT_TR(Torrent Upload Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
221                 loadMethod: "iframe",
222                 contentURL: new URI("uploadlimit.html").setData("hashes", hashes.join("|")).toString(),
223                 scrollbars: false,
224                 resizable: false,
225                 maximizable: false,
226                 paddingVertical: 0,
227                 paddingHorizontal: 0,
228                 width: 424,
229                 height: 80
230             });
231         }
232     };
234     shareRatioFN = function() {
235         const hashes = torrentsTable.selectedRowsIds();
236         if (hashes.length) {
237             let shareRatio = null;
238             let torrentsHaveSameShareRatio = true;
240             // check if all selected torrents have same share ratio
241             for (let i = 0; i < hashes.length; ++i) {
242                 const hash = hashes[i];
243                 const row = torrentsTable.rows[hash].full_data;
244                 const origValues = row.ratio_limit + "|" + row.seeding_time_limit + "|" + row.inactive_seeding_time_limit + "|"
245                     + row.max_ratio + "|" + row.max_seeding_time + "|" + row.max_inactive_seeding_time;
247                 // initialize value
248                 if (shareRatio === null)
249                     shareRatio = origValues;
251                 if (origValues !== shareRatio) {
252                     torrentsHaveSameShareRatio = false;
253                     break;
254                 }
255             }
257             // if all torrents have same share ratio, display that share ratio. else use the default
258             const orig = torrentsHaveSameShareRatio ? shareRatio : "";
259             new MochaUI.Window({
260                 id: "shareRatioPage",
261                 title: "QBT_TR(Torrent Upload/Download Ratio Limiting)QBT_TR[CONTEXT=UpDownRatioDialog]",
262                 loadMethod: "iframe",
263                 contentURL: new URI("shareratio.html").setData("hashes", hashes.join("|")).setData("orig", orig).toString(),
264                 scrollbars: false,
265                 maximizable: false,
266                 paddingVertical: 0,
267                 paddingHorizontal: 0,
268                 width: 424,
269                 height: 175
270             });
271         }
272     };
274     toggleSequentialDownloadFN = function() {
275         const hashes = torrentsTable.selectedRowsIds();
276         if (hashes.length) {
277             new Request({
278                 url: "api/v2/torrents/toggleSequentialDownload",
279                 method: "post",
280                 data: {
281                     hashes: hashes.join("|")
282                 }
283             }).send();
284             updateMainData();
285         }
286     };
288     toggleFirstLastPiecePrioFN = function() {
289         const hashes = torrentsTable.selectedRowsIds();
290         if (hashes.length) {
291             new Request({
292                 url: "api/v2/torrents/toggleFirstLastPiecePrio",
293                 method: "post",
294                 data: {
295                     hashes: hashes.join("|")
296                 }
297             }).send();
298             updateMainData();
299         }
300     };
302     setSuperSeedingFN = function(val) {
303         const hashes = torrentsTable.selectedRowsIds();
304         if (hashes.length) {
305             new Request({
306                 url: "api/v2/torrents/setSuperSeeding",
307                 method: "post",
308                 data: {
309                     value: val,
310                     hashes: hashes.join("|")
311                 }
312             }).send();
313             updateMainData();
314         }
315     };
317     setForceStartFN = function() {
318         const hashes = torrentsTable.selectedRowsIds();
319         if (hashes.length) {
320             new Request({
321                 url: "api/v2/torrents/setForceStart",
322                 method: "post",
323                 data: {
324                     value: "true",
325                     hashes: hashes.join("|")
326                 }
327             }).send();
328             updateMainData();
329         }
330     };
332     globalDownloadLimitFN = function() {
333         new MochaUI.Window({
334             id: "downloadLimitPage",
335             title: "QBT_TR(Global Download Speed Limit)QBT_TR[CONTEXT=MainWindow]",
336             loadMethod: "iframe",
337             contentURL: new URI("downloadlimit.html").setData("hashes", "global").toString(),
338             scrollbars: false,
339             resizable: false,
340             maximizable: false,
341             paddingVertical: 0,
342             paddingHorizontal: 0,
343             width: 424,
344             height: 80
345         });
346     };
348     StatisticsLinkFN = function() {
349         const id = "statisticspage";
350         new MochaUI.Window({
351             id: id,
352             title: "QBT_TR(Statistics)QBT_TR[CONTEXT=StatsDialog]",
353             loadMethod: "xhr",
354             contentURL: new URI("views/statistics.html").toString(),
355             maximizable: false,
356             padding: 10,
357             width: loadWindowWidth(id, 275),
358             height: loadWindowHeight(id, 370),
359             onResize: function() {
360                 saveWindowSize(id);
361             }
362         });
363     };
365     downloadLimitFN = function() {
366         const hashes = torrentsTable.selectedRowsIds();
367         if (hashes.length) {
368             new MochaUI.Window({
369                 id: "downloadLimitPage",
370                 title: "QBT_TR(Torrent Download Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
371                 loadMethod: "iframe",
372                 contentURL: new URI("downloadlimit.html").setData("hashes", hashes.join("|")).toString(),
373                 scrollbars: false,
374                 resizable: false,
375                 maximizable: false,
376                 paddingVertical: 0,
377                 paddingHorizontal: 0,
378                 width: 424,
379                 height: 80
380             });
381         }
382     };
384     deleteFN = function(deleteFiles = false) {
385         const hashes = torrentsTable.selectedRowsIds();
386         if (hashes.length) {
387             new MochaUI.Window({
388                 id: "confirmDeletionPage",
389                 title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
390                 loadMethod: "iframe",
391                 contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).setData("deleteFiles", deleteFiles).toString(),
392                 scrollbars: false,
393                 resizable: true,
394                 maximizable: false,
395                 padding: 10,
396                 width: 424,
397                 height: 160
398             });
399             updateMainData();
400         }
401     };
403     addClickEvent("delete", (e) => {
404         new Event(e).stop();
405         deleteFN();
406     });
408     stopFN = function() {
409         const hashes = torrentsTable.selectedRowsIds();
410         if (hashes.length) {
411             new Request({
412                 url: "api/v2/torrents/stop",
413                 method: "post",
414                 data: {
415                     hashes: hashes.join("|")
416                 }
417             }).send();
418             updateMainData();
419         }
420     };
422     startFN = function() {
423         const hashes = torrentsTable.selectedRowsIds();
424         if (hashes.length) {
425             new Request({
426                 url: "api/v2/torrents/start",
427                 method: "post",
428                 data: {
429                     hashes: hashes.join("|")
430                 }
431             }).send();
432             updateMainData();
433         }
434     };
436     autoTorrentManagementFN = function() {
437         const hashes = torrentsTable.selectedRowsIds();
438         if (hashes.length) {
439             let enable = false;
440             hashes.each((hash, index) => {
441                 const row = torrentsTable.rows[hash];
442                 if (!row.full_data.auto_tmm)
443                     enable = true;
444             });
445             new Request({
446                 url: "api/v2/torrents/setAutoManagement",
447                 method: "post",
448                 data: {
449                     hashes: hashes.join("|"),
450                     enable: enable
451                 }
452             }).send();
453             updateMainData();
454         }
455     };
457     recheckFN = function() {
458         const hashes = torrentsTable.selectedRowsIds();
459         if (hashes.length) {
460             new Request({
461                 url: "api/v2/torrents/recheck",
462                 method: "post",
463                 data: {
464                     hashes: hashes.join("|"),
465                 }
466             }).send();
467             updateMainData();
468         }
469     };
471     reannounceFN = function() {
472         const hashes = torrentsTable.selectedRowsIds();
473         if (hashes.length) {
474             new Request({
475                 url: "api/v2/torrents/reannounce",
476                 method: "post",
477                 data: {
478                     hashes: hashes.join("|"),
479                 }
480             }).send();
481             updateMainData();
482         }
483     };
485     setLocationFN = function() {
486         const hashes = torrentsTable.selectedRowsIds();
487         if (hashes.length) {
488             const hash = hashes[0];
489             const row = torrentsTable.rows[hash];
491             new MochaUI.Window({
492                 id: "setLocationPage",
493                 title: "QBT_TR(Set location)QBT_TR[CONTEXT=TransferListWidget]",
494                 loadMethod: "iframe",
495                 contentURL: new URI("setlocation.html").setData("hashes", hashes.join("|")).setData("path", encodeURIComponent(row.full_data.save_path)).toString(),
496                 scrollbars: false,
497                 resizable: true,
498                 maximizable: false,
499                 paddingVertical: 0,
500                 paddingHorizontal: 0,
501                 width: 400,
502                 height: 130
503             });
504         }
505     };
507     renameFN = function() {
508         const hashes = torrentsTable.selectedRowsIds();
509         if (hashes.length === 1) {
510             const hash = hashes[0];
511             const row = torrentsTable.rows[hash];
512             if (row) {
513                 new MochaUI.Window({
514                     id: "renamePage",
515                     title: "QBT_TR(Rename)QBT_TR[CONTEXT=TransferListWidget]",
516                     loadMethod: "iframe",
517                     contentURL: new URI("rename.html").setData("hash", hash).setData("name", row.full_data.name).toString(),
518                     scrollbars: false,
519                     resizable: true,
520                     maximizable: false,
521                     paddingVertical: 0,
522                     paddingHorizontal: 0,
523                     width: 400,
524                     height: 100
525                 });
526             }
527         }
528     };
530     renameFilesFN = function() {
531         const hashes = torrentsTable.selectedRowsIds();
532         if (hashes.length === 1) {
533             const hash = hashes[0];
534             const row = torrentsTable.rows[hash];
535             if (row) {
536                 new MochaUI.Window({
537                     id: "multiRenamePage",
538                     title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TransferListWidget]",
539                     data: { hash: hash, selectedRows: [] },
540                     loadMethod: "xhr",
541                     contentURL: "rename_files.html",
542                     scrollbars: false,
543                     resizable: true,
544                     maximizable: false,
545                     paddingVertical: 0,
546                     paddingHorizontal: 0,
547                     width: 800,
548                     height: 420,
549                     resizeLimit: { "x": [800], "y": [420] }
550                 });
551             }
552         }
553     };
555     torrentNewCategoryFN = function() {
556         const action = "set";
557         const hashes = torrentsTable.selectedRowsIds();
558         if (hashes.length) {
559             new MochaUI.Window({
560                 id: "newCategoryPage",
561                 title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
562                 loadMethod: "iframe",
563                 contentURL: new URI("newcategory.html").setData("action", action).setData("hashes", hashes.join("|")).toString(),
564                 scrollbars: false,
565                 resizable: true,
566                 maximizable: false,
567                 paddingVertical: 0,
568                 paddingHorizontal: 0,
569                 width: 400,
570                 height: 150
571             });
572         }
573     };
575     torrentSetCategoryFN = function(categoryHash) {
576         const hashes = torrentsTable.selectedRowsIds();
577         if (hashes.length <= 0)
578             return;
580         const categoryName = category_list.has(categoryHash)
581             ? category_list.get(categoryHash).name
582             : "";
583         new Request({
584             url: "api/v2/torrents/setCategory",
585             method: "post",
586             data: {
587                 hashes: hashes.join("|"),
588                 category: categoryName
589             }
590         }).send();
591     };
593     createCategoryFN = function() {
594         const action = "create";
595         new MochaUI.Window({
596             id: "newCategoryPage",
597             title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
598             loadMethod: "iframe",
599             contentURL: new URI("newcategory.html").setData("action", action).toString(),
600             scrollbars: false,
601             resizable: true,
602             maximizable: false,
603             paddingVertical: 0,
604             paddingHorizontal: 0,
605             width: 400,
606             height: 150
607         });
608         updateMainData();
609     };
611     createSubcategoryFN = function(categoryHash) {
612         const action = "createSubcategory";
613         const categoryName = category_list.get(categoryHash).name + "/";
614         new MochaUI.Window({
615             id: "newSubcategoryPage",
616             title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
617             loadMethod: "iframe",
618             contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", categoryName).toString(),
619             scrollbars: false,
620             resizable: true,
621             maximizable: false,
622             paddingVertical: 0,
623             paddingHorizontal: 0,
624             width: 400,
625             height: 150
626         });
627         updateMainData();
628     };
630     editCategoryFN = function(categoryHash) {
631         const action = "edit";
632         const category = category_list.get(categoryHash);
633         new MochaUI.Window({
634             id: "editCategoryPage",
635             title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
636             loadMethod: "iframe",
637             contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", category.name).setData("savePath", category.savePath).toString(),
638             scrollbars: false,
639             resizable: true,
640             maximizable: false,
641             paddingVertical: 0,
642             paddingHorizontal: 0,
643             width: 400,
644             height: 150
645         });
646         updateMainData();
647     };
649     removeCategoryFN = function(categoryHash) {
650         const categoryName = category_list.get(categoryHash).name;
651         new Request({
652             url: "api/v2/torrents/removeCategories",
653             method: "post",
654             data: {
655                 categories: categoryName
656             }
657         }).send();
658         setCategoryFilter(CATEGORIES_ALL);
659     };
661     deleteUnusedCategoriesFN = function() {
662         const categories = [];
663         category_list.forEach((category, hash) => {
664             if (torrentsTable.getFilteredTorrentsNumber("all", hash, TAGS_ALL, TRACKERS_ALL) === 0)
665                 categories.push(category.name);
666         });
668         new Request({
669             url: "api/v2/torrents/removeCategories",
670             method: "post",
671             data: {
672                 categories: categories.join("\n")
673             }
674         }).send();
675         setCategoryFilter(CATEGORIES_ALL);
676     };
678     startTorrentsByCategoryFN = function(categoryHash) {
679         const hashes = torrentsTable.getFilteredTorrentsHashes("all", categoryHash, TAGS_ALL, TRACKERS_ALL);
680         if (hashes.length) {
681             new Request({
682                 url: "api/v2/torrents/start",
683                 method: "post",
684                 data: {
685                     hashes: hashes.join("|")
686                 }
687             }).send();
688             updateMainData();
689         }
690     };
692     stopTorrentsByCategoryFN = function(categoryHash) {
693         const hashes = torrentsTable.getFilteredTorrentsHashes("all", categoryHash, TAGS_ALL, TRACKERS_ALL);
694         if (hashes.length) {
695             new Request({
696                 url: "api/v2/torrents/stop",
697                 method: "post",
698                 data: {
699                     hashes: hashes.join("|")
700                 }
701             }).send();
702             updateMainData();
703         }
704     };
706     deleteTorrentsByCategoryFN = function(categoryHash) {
707         const hashes = torrentsTable.getFilteredTorrentsHashes("all", categoryHash, TAGS_ALL, TRACKERS_ALL);
708         if (hashes.length) {
709             new MochaUI.Window({
710                 id: "confirmDeletionPage",
711                 title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
712                 loadMethod: "iframe",
713                 contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
714                 scrollbars: false,
715                 resizable: true,
716                 maximizable: false,
717                 padding: 10,
718                 width: 424,
719                 height: 160
720             });
721             updateMainData();
722         }
723     };
725     torrentAddTagsFN = function() {
726         const action = "set";
727         const hashes = torrentsTable.selectedRowsIds();
728         if (hashes.length) {
729             new MochaUI.Window({
730                 id: "newTagPage",
731                 title: "QBT_TR(Add Tags)QBT_TR[CONTEXT=TransferListWidget]",
732                 loadMethod: "iframe",
733                 contentURL: new URI("newtag.html").setData("action", action).setData("hashes", hashes.join("|")).toString(),
734                 scrollbars: false,
735                 resizable: true,
736                 maximizable: false,
737                 paddingVertical: 0,
738                 paddingHorizontal: 0,
739                 width: 250,
740                 height: 100
741             });
742         }
743     };
745     torrentSetTagsFN = function(tagHash, isSet) {
746         const hashes = torrentsTable.selectedRowsIds();
747         if (hashes.length <= 0)
748             return;
750         const tagName = tagList.has(tagHash) ? tagList.get(tagHash).name : "";
751         new Request({
752             url: (isSet ? "api/v2/torrents/addTags" : "api/v2/torrents/removeTags"),
753             method: "post",
754             data: {
755                 hashes: hashes.join("|"),
756                 tags: tagName,
757             }
758         }).send();
759     };
761     torrentRemoveAllTagsFN = function() {
762         const hashes = torrentsTable.selectedRowsIds();
763         if (hashes.length) {
764             new Request({
765                 url: ("api/v2/torrents/removeTags"),
766                 method: "post",
767                 data: {
768                     hashes: hashes.join("|"),
769                 }
770             }).send();
771         }
772     };
774     createTagFN = function() {
775         const action = "create";
776         new MochaUI.Window({
777             id: "newTagPage",
778             title: "QBT_TR(New Tag)QBT_TR[CONTEXT=TagFilterWidget]",
779             loadMethod: "iframe",
780             contentURL: new URI("newtag.html").setData("action", action).toString(),
781             scrollbars: false,
782             resizable: true,
783             maximizable: false,
784             paddingVertical: 0,
785             paddingHorizontal: 0,
786             width: 250,
787             height: 100
788         });
789         updateMainData();
790     };
792     removeTagFN = function(tagHash) {
793         const tagName = tagList.get(tagHash).name;
794         new Request({
795             url: "api/v2/torrents/deleteTags",
796             method: "post",
797             data: {
798                 tags: tagName
799             }
800         }).send();
801         setTagFilter(TAGS_ALL);
802     };
804     deleteUnusedTagsFN = function() {
805         const tags = [];
806         tagList.forEach((tag, hash) => {
807             if (torrentsTable.getFilteredTorrentsNumber("all", CATEGORIES_ALL, hash, TRACKERS_ALL) === 0)
808                 tags.push(tag.name);
809         });
810         new Request({
811             url: "api/v2/torrents/deleteTags",
812             method: "post",
813             data: {
814                 tags: tags.join(",")
815             }
816         }).send();
817         setTagFilter(TAGS_ALL);
818     };
820     startTorrentsByTagFN = function(tagHash) {
821         const hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, tagHash, TRACKERS_ALL);
822         if (hashes.length) {
823             new Request({
824                 url: "api/v2/torrents/start",
825                 method: "post",
826                 data: {
827                     hashes: hashes.join("|")
828                 }
829             }).send();
830             updateMainData();
831         }
832     };
834     stopTorrentsByTagFN = function(tagHash) {
835         const hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, tagHash, TRACKERS_ALL);
836         if (hashes.length) {
837             new Request({
838                 url: "api/v2/torrents/stop",
839                 method: "post",
840                 data: {
841                     hashes: hashes.join("|")
842                 }
843             }).send();
844             updateMainData();
845         }
846     };
848     deleteTorrentsByTagFN = function(tagHash) {
849         const hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, tagHash, TRACKERS_ALL);
850         if (hashes.length) {
851             new MochaUI.Window({
852                 id: "confirmDeletionPage",
853                 title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
854                 loadMethod: "iframe",
855                 contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
856                 scrollbars: false,
857                 resizable: true,
858                 maximizable: false,
859                 padding: 10,
860                 width: 424,
861                 height: 160
862             });
863             updateMainData();
864         }
865     };
867     startTorrentsByTrackerFN = function(trackerHash) {
868         const trackerHashInt = Number.parseInt(trackerHash, 10);
869         let hashes = [];
870         switch (trackerHashInt) {
871             case TRACKERS_ALL:
872                 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
873                 break;
874             case TRACKERS_TRACKERLESS:
875                 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
876                 break;
877             default: {
878                 const uniqueTorrents = new Set();
879                 for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
880                     for (const torrent of torrents) {
881                         uniqueTorrents.add(torrent);
882                     }
883                 }
884                 hashes = [...uniqueTorrents];
885                 break;
886             }
887         }
889         if (hashes.length > 0) {
890             new Request({
891                 url: "api/v2/torrents/start",
892                 method: "post",
893                 data: {
894                     hashes: hashes.join("|")
895                 }
896             }).send();
897             updateMainData();
898         }
899     };
901     stopTorrentsByTrackerFN = function(trackerHash) {
902         const trackerHashInt = Number.parseInt(trackerHash, 10);
903         let hashes = [];
904         switch (trackerHashInt) {
905             case TRACKERS_ALL:
906                 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
907                 break;
908             case TRACKERS_TRACKERLESS:
909                 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
910                 break;
911             default: {
912                 const uniqueTorrents = new Set();
913                 for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
914                     for (const torrent of torrents) {
915                         uniqueTorrents.add(torrent);
916                     }
917                 }
918                 hashes = [...uniqueTorrents];
919                 break;
920             }
921         }
923         if (hashes.length) {
924             new Request({
925                 url: "api/v2/torrents/stop",
926                 method: "post",
927                 data: {
928                     hashes: hashes.join("|")
929                 }
930             }).send();
931             updateMainData();
932         }
933     };
935     deleteTorrentsByTrackerFN = function(trackerHash) {
936         const trackerHashInt = Number.parseInt(trackerHash, 10);
937         let hashes = [];
938         switch (trackerHashInt) {
939             case TRACKERS_ALL:
940                 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
941                 break;
942             case TRACKERS_TRACKERLESS:
943                 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
944                 break;
945             default: {
946                 const uniqueTorrents = new Set();
947                 for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
948                     for (const torrent of torrents) {
949                         uniqueTorrents.add(torrent);
950                     }
951                 }
952                 hashes = [...uniqueTorrents];
953                 break;
954             }
955         }
957         if (hashes.length) {
958             new MochaUI.Window({
959                 id: "confirmDeletionPage",
960                 title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
961                 loadMethod: "iframe",
962                 contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
963                 scrollbars: false,
964                 resizable: true,
965                 maximizable: false,
966                 padding: 10,
967                 width: 424,
968                 height: 160,
969                 onCloseComplete: function() {
970                     updateMainData();
971                     setTrackerFilter(TRACKERS_ALL);
972                 }
973             });
974         }
975     };
977     copyNameFN = function() {
978         const selectedRows = torrentsTable.selectedRowsIds();
979         const names = [];
980         if (selectedRows.length > 0) {
981             const rows = torrentsTable.getFilteredAndSortedRows();
982             for (let i = 0; i < selectedRows.length; ++i) {
983                 const hash = selectedRows[i];
984                 names.push(rows[hash].full_data.name);
985             }
986         }
987         return names.join("\n");
988     };
990     copyInfohashFN = function(policy) {
991         const selectedRows = torrentsTable.selectedRowsIds();
992         const infohashes = [];
993         if (selectedRows.length > 0) {
994             const rows = torrentsTable.getFilteredAndSortedRows();
995             switch (policy) {
996                 case 1:
997                     for (const id of selectedRows) {
998                         const infohash = rows[id].full_data.infohash_v1;
999                         if (infohash !== "")
1000                             infohashes.push(infohash);
1001                     }
1002                     break;
1003                 case 2:
1004                     for (const id of selectedRows) {
1005                         const infohash = rows[id].full_data.infohash_v2;
1006                         if (infohash !== "")
1007                             infohashes.push(infohash);
1008                     }
1009                     break;
1010             }
1011         }
1012         return infohashes.join("\n");
1013     };
1015     copyMagnetLinkFN = function() {
1016         const selectedRows = torrentsTable.selectedRowsIds();
1017         const magnets = [];
1018         if (selectedRows.length > 0) {
1019             const rows = torrentsTable.getFilteredAndSortedRows();
1020             for (let i = 0; i < selectedRows.length; ++i) {
1021                 const hash = selectedRows[i];
1022                 magnets.push(rows[hash].full_data.magnet_uri);
1023             }
1024         }
1025         return magnets.join("\n");
1026     };
1028     copyIdFN = function() {
1029         return torrentsTable.selectedRowsIds().join("\n");
1030     };
1032     copyCommentFN = function() {
1033         const selectedRows = torrentsTable.selectedRowsIds();
1034         const comments = [];
1035         if (selectedRows.length > 0) {
1036             const rows = torrentsTable.getFilteredAndSortedRows();
1037             for (let i = 0; i < selectedRows.length; ++i) {
1038                 const hash = selectedRows[i];
1039                 const comment = rows[hash].full_data.comment;
1040                 if (comment && (comment !== ""))
1041                     comments.push(comment);
1042             }
1043         }
1044         return comments.join("\n---------\n");
1045     };
1047     exportTorrentFN = async function() {
1048         const hashes = torrentsTable.selectedRowsIds();
1049         for (const hash of hashes) {
1050             const row = torrentsTable.rows.get(hash);
1051             if (!row)
1052                 continue;
1054             const name = row.full_data.name;
1055             const url = new URI("api/v2/torrents/export");
1056             url.setData("hash", hash);
1058             // download response to file
1059             const element = document.createElement("a");
1060             element.setAttribute("href", url);
1061             element.setAttribute("download", (name + ".torrent"));
1062             document.body.appendChild(element);
1063             element.click();
1064             document.body.removeChild(element);
1066             // https://stackoverflow.com/questions/53560991/automatic-file-downloads-limited-to-10-files-on-chrome-browser
1067             await window.qBittorrent.Misc.sleep(200);
1068         }
1069     };
1071     addClickEvent("stopAll", (e) => {
1072         new Event(e).stop();
1074         if (confirm("QBT_TR(Would you like to stop all torrents?)QBT_TR[CONTEXT=MainWindow]")) {
1075             new Request({
1076                 url: "api/v2/torrents/stop",
1077                 method: "post",
1078                 data: {
1079                     hashes: "all"
1080                 }
1081             }).send();
1082             updateMainData();
1083         }
1084     });
1086     addClickEvent("startAll", (e) => {
1087         new Event(e).stop();
1089         if (confirm("QBT_TR(Would you like to start all torrents?)QBT_TR[CONTEXT=MainWindow]")) {
1090             new Request({
1091                 url: "api/v2/torrents/start",
1092                 method: "post",
1093                 data: {
1094                     hashes: "all"
1095                 }
1096             }).send();
1097             updateMainData();
1098         }
1099     });
1101     ["stop", "start", "recheck"].each((item) => {
1102         addClickEvent(item, (e) => {
1103             new Event(e).stop();
1104             const hashes = torrentsTable.selectedRowsIds();
1105             if (hashes.length) {
1106                 hashes.each((hash, index) => {
1107                     new Request({
1108                         url: "api/v2/torrents/" + item,
1109                         method: "post",
1110                         data: {
1111                             hashes: hash
1112                         }
1113                     }).send();
1114                 });
1115                 updateMainData();
1116             }
1117         });
1118     });
1120     ["decreasePrio", "increasePrio", "topPrio", "bottomPrio"].each((item) => {
1121         addClickEvent(item, (e) => {
1122             new Event(e).stop();
1123             setQueuePositionFN(item);
1124         });
1125     });
1127     setQueuePositionFN = function(cmd) {
1128         const hashes = torrentsTable.selectedRowsIds();
1129         if (hashes.length) {
1130             new Request({
1131                 url: "api/v2/torrents/" + cmd,
1132                 method: "post",
1133                 data: {
1134                     hashes: hashes.join("|")
1135                 }
1136             }).send();
1137             updateMainData();
1138         }
1139     };
1141     addClickEvent("about", (e) => {
1142         new Event(e).stop();
1143         const id = "aboutpage";
1144         new MochaUI.Window({
1145             id: id,
1146             title: "QBT_TR(About qBittorrent)QBT_TR[CONTEXT=AboutDialog]",
1147             loadMethod: "xhr",
1148             contentURL: new URI("views/about.html").toString(),
1149             require: {
1150                 css: ["css/Tabs.css"]
1151             },
1152             toolbar: true,
1153             toolbarURL: "views/aboutToolbar.html",
1154             padding: 10,
1155             width: loadWindowWidth(id, 550),
1156             height: loadWindowHeight(id, 360),
1157             onResize: function() {
1158                 saveWindowSize(id);
1159             }
1160         });
1161     });
1163     addClickEvent("logout", (e) => {
1164         new Event(e).stop();
1165         new Request({
1166             url: "api/v2/auth/logout",
1167             method: "post",
1168             onSuccess: function() {
1169                 window.location.reload(true);
1170             }
1171         }).send();
1172     });
1174     addClickEvent("shutdown", (e) => {
1175         new Event(e).stop();
1176         if (confirm("QBT_TR(Are you sure you want to quit qBittorrent?)QBT_TR[CONTEXT=MainWindow]")) {
1177             new Request({
1178                 url: "api/v2/app/shutdown",
1179                 method: "post",
1180                 onSuccess: function() {
1181                     const shutdownMessage = "QBT_TR(%1 has been shutdown)QBT_TR[CONTEXT=HttpServer]".replace("%1", window.qBittorrent.Client.mainTitle());
1182                     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>`);
1183                     document.close();
1184                     window.stop();
1185                     window.qBittorrent.Client.stop();
1186                 }
1187             }).send();
1188         }
1189     });
1191     // Deactivate menu header links
1192     $$("a.returnFalse").each((el) => {
1193         el.addEvent("click", (e) => {
1194             new Event(e).stop();
1195         });
1196     });