diff --git a/src/components/modals/add.tsx b/src/components/modals/add.tsx
index c6d0f67..a8d4f5e 100644
--- a/src/components/modals/add.tsx
+++ b/src/components/modals/add.tsx
@@ -19,7 +19,7 @@
import { Box, Button, Checkbox, Divider, Flex, Group, Menu, SegmentedControl, Text, TextInput } from "@mantine/core";
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import type { ModalState, LocationData } from "./common";
-import { HkModal, TorrentLabels, TorrentLocation, limitTorrentNames, useTorrentLocation } from "./common";
+import { HkModal, LimitedNamesList, TorrentLabels, TorrentLocation, useTorrentLocation } from "./common";
import type { PriorityNumberType } from "rpc/transmission";
import { PriorityColors, PriorityStrings } from "rpc/transmission";
import type { Torrent } from "rpc/torrent";
@@ -558,9 +558,7 @@ export function AddTorrent(props: AddCommonModalProps) {
const names = useMemo(() => {
if (torrentData === undefined) return [];
- const names = torrentData.map((td) => td.name);
-
- return limitTorrentNames(names, 1);
+ return torrentData.map((td) => td.name);
}, [torrentData]);
const torrentExists = existingTorrent !== undefined;
@@ -578,7 +576,7 @@ export function AddTorrent(props: AddCommonModalProps) {
{torrentExists
? Torrent already exists
- : names.map((name, i) => {name})}
+ : }
{(torrentData.length > 1 || torrentData[0].files == null)
diff --git a/src/components/modals/common.tsx b/src/components/modals/common.tsx
index c5b008b..1013698 100644
--- a/src/components/modals/common.tsx
+++ b/src/components/modals/common.tsx
@@ -68,12 +68,21 @@ export function SaveCancelModal({ onSave, onClose, children, saveLoading, ...oth
);
}
-export function limitTorrentNames(allNames: string[], limit: number = 5) {
- const names: string[] = allNames.slice(0, limit);
+export function LimitedNamesList({ names, limit }: { names: string[], limit?: number }) {
+ limit = limit ?? 5;
+ const t = names.slice(0, limit);
- if (allNames.length > limit) names.push(`... and ${allNames.length - limit} more`);
-
- return names;
+ return <>
+ {t.map((s, i) =>
+ {s}
+ )}
+ {names.length > limit && {`... and ${names.length - limit} more`}}
+ >;
}
export function TorrentsNames() {
@@ -84,20 +93,11 @@ export function TorrentsNames() {
if (serverData.current == null || serverSelected.size === 0) {
return ["No torrent selected"];
}
-
- const selected = serverData.torrents.filter(
- (t) => serverSelected.has(t.id));
-
- const allNames: string[] = [];
- selected.forEach((t) => allNames.push(t.name));
- return allNames;
+ return serverData.torrents.filter(
+ (t) => serverSelected.has(t.id)).map((t) => t.name);
}, [serverData, serverSelected]);
- const names = limitTorrentNames(allNames);
-
- return <>
- {names.map((s, i) => {s})}
- >;
+ return ;
}
export interface LocationData {
diff --git a/src/components/modals/edittorrent.tsx b/src/components/modals/edittorrent.tsx
index e32aaac..53c092a 100644
--- a/src/components/modals/edittorrent.tsx
+++ b/src/components/modals/edittorrent.tsx
@@ -16,16 +16,14 @@
* along with this program. If not, see .
*/
-import React, { useCallback, useContext, useEffect } from "react";
+import React, { useCallback, useEffect, useMemo } from "react";
import type { ModalState } from "./common";
-import { SaveCancelModal } from "./common";
+import { SaveCancelModal, TorrentsNames } from "./common";
import { useForm } from "@mantine/form";
import { useMutateTorrent, useTorrentDetails } from "queries";
import { notifications } from "@mantine/notifications";
-import { Button, Checkbox, Grid, LoadingOverlay, NumberInput, Text, Textarea } from "@mantine/core";
-import { ConfigContext } from "config";
-import type { TrackerStats } from "rpc/torrent";
-import { useServerRpcVersion, useServerTorrentData } from "rpc/torrent";
+import { Checkbox, Grid, LoadingOverlay, NumberInput } from "@mantine/core";
+import { useServerSelectedTorrents, useServerTorrentData } from "rpc/torrent";
interface FormValues {
downloadLimited?: boolean,
@@ -37,16 +35,20 @@ interface FormValues {
seedRatioLimit: number,
seedIdleMode: number,
seedIdleLimit: number,
- trackerList: string,
honorsSessionLimits: boolean,
sequentialDownload: boolean,
}
export function EditTorrent(props: ModalState) {
- const config = useContext(ConfigContext);
const serverData = useServerTorrentData();
- const torrentId = serverData.current;
- const rpcVersion = useServerRpcVersion();
+ const selected = useServerSelectedTorrents();
+
+ const torrentId = useMemo(() => {
+ if (serverData.current === undefined || !selected.has(serverData.current)) {
+ return [...selected][0];
+ }
+ return serverData.current;
+ }, [selected, serverData]);
const { data: torrent, isLoading } = useTorrentDetails(
torrentId ?? -1, torrentId !== undefined && props.opened, false, true);
@@ -66,40 +68,22 @@ export function EditTorrent(props: ModalState) {
seedRatioLimit: torrent.seedRatioLimit,
seedIdleMode: torrent.seedIdleMode,
seedIdleLimit: torrent.seedIdleLimit,
- trackerList: rpcVersion >= 17
- ? torrent.trackerList
- : torrent.trackerStats.map((s: TrackerStats) => s.announce).join("\n"),
honorsSessionLimits: torrent.honorsSessionLimits,
sequentialDownload: torrent.sequentialDownload,
});
- }, [rpcVersion, setValues, torrent]);
+ }, [setValues, torrent]);
const mutation = useMutateTorrent();
const onSave = useCallback(() => {
if (torrentId === undefined || torrent === undefined) return;
- let toAdd;
- let toRemove;
- if (rpcVersion < 17) {
- const trackers = form.values.trackerList.split("\n").filter((s) => s !== "");
- const currentTrackers = Object.fromEntries(
- torrent.trackerStats.map((s: TrackerStats) => [s.announce, s.id]));
- toAdd = trackers.filter((t) => !Object.hasOwn(currentTrackers, t));
- toRemove = (torrent.trackerStats as TrackerStats[])
- .filter((s: TrackerStats) => !trackers.includes(s.announce))
- .map((s: TrackerStats) => s.id as number);
- if (toAdd.length === 0) toAdd = undefined;
- if (toRemove.length === 0) toRemove = undefined;
- }
mutation.mutate(
{
- torrentIds: [torrentId],
+ torrentIds: [...selected],
fields: {
...form.values,
"peer-limit": form.values.peerLimit,
- trackerAdd: toAdd,
- trackerRemove: toRemove,
},
},
{
@@ -113,14 +97,7 @@ export function EditTorrent(props: ModalState) {
},
);
props.close();
- }, [form.values, mutation, torrent, props, rpcVersion, torrentId]);
-
- const addDefaultTrackers = useCallback(() => {
- let list = form.values.trackerList;
- if (!list.endsWith("\n")) list += "\n";
- list += config.values.interface.defaultTrackers.join("\n");
- form.setFieldValue("trackerList", list);
- }, [config, form]);
+ }, [torrentId, torrent, mutation, selected, form.values, props]);
return <>{props.opened &&
- Torrent: {torrent?.name}
+
minutes
-
- Tracker list, one per line, empty line between tiers
-
-
-
-
-
-
-
}
>;
diff --git a/src/components/modals/edittrackers.tsx b/src/components/modals/edittrackers.tsx
new file mode 100644
index 0000000..938c83c
--- /dev/null
+++ b/src/components/modals/edittrackers.tsx
@@ -0,0 +1,137 @@
+/**
+ * TrguiNG - next gen remote GUI for transmission torrent daemon
+ * Copyright (C) 2023 qu1ck (mail at qu1ck.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import React, { useCallback, useContext, useEffect, useMemo } from "react";
+import type { ModalState } from "./common";
+import { SaveCancelModal, TorrentsNames } from "./common";
+import { useForm } from "@mantine/form";
+import { useMutateTorrent, useTorrentDetails } from "queries";
+import { notifications } from "@mantine/notifications";
+import { Button, Grid, LoadingOverlay, Text, Textarea } from "@mantine/core";
+import { ConfigContext } from "config";
+import type { TrackerStats } from "rpc/torrent";
+import { useServerRpcVersion, useServerSelectedTorrents, useServerTorrentData } from "rpc/torrent";
+
+interface FormValues {
+ trackerList: string,
+}
+
+export function EditTrackers(props: ModalState) {
+ const rpcVersion = useServerRpcVersion();
+ const config = useContext(ConfigContext);
+ const serverData = useServerTorrentData();
+ const selected = useServerSelectedTorrents();
+
+ const torrentId = useMemo(() => {
+ if (serverData.current === undefined || !selected.has(serverData.current)) {
+ return [...selected][0];
+ }
+ return serverData.current;
+ }, [selected, serverData]);
+
+ const { data: torrent, isLoading } = useTorrentDetails(
+ torrentId ?? -1, torrentId !== undefined && props.opened, false, true);
+
+ const form = useForm({});
+
+ const { setValues } = form;
+ useEffect(() => {
+ if (torrent === undefined) return;
+ setValues({
+ trackerList: rpcVersion >= 17
+ ? torrent.trackerList
+ : torrent.trackerStats.map((s: TrackerStats) => s.announce).join("\n"),
+ });
+ }, [rpcVersion, setValues, torrent]);
+
+ const mutation = useMutateTorrent();
+
+ const onSave = useCallback(() => {
+ if (torrentId === undefined || torrent === undefined) return;
+ let toAdd;
+ let toRemove;
+ if (rpcVersion < 17) {
+ const trackers = form.values.trackerList.split("\n").filter((s) => s !== "");
+ const currentTrackers = Object.fromEntries(
+ torrent.trackerStats.map((s: TrackerStats) => [s.announce, s.id]));
+
+ toAdd = trackers.filter((t) => !Object.prototype.hasOwnProperty.call(currentTrackers, t));
+ toRemove = (torrent.trackerStats as TrackerStats[])
+ .filter((s: TrackerStats) => !trackers.includes(s.announce))
+ .map((s: TrackerStats) => s.id as number);
+ if (toAdd.length === 0) toAdd = undefined;
+ if (toRemove.length === 0) toRemove = undefined;
+ }
+ mutation.mutate(
+ {
+ torrentIds: [...selected],
+ fields: {
+ ...form.values,
+ trackerAdd: toAdd,
+ trackerRemove: toRemove,
+ },
+ },
+ {
+ onError: (e) => {
+ console.error("Failed to update torrent properties", e);
+ notifications.show({
+ message: "Error updating torrent",
+ color: "red",
+ });
+ },
+ },
+ );
+ props.close();
+ }, [torrentId, torrent, rpcVersion, mutation, selected, form.values, props]);
+
+ const addDefaultTrackers = useCallback(() => {
+ let list = form.values.trackerList;
+ if (!list.endsWith("\n")) list += "\n";
+ list += config.values.interface.defaultTrackers.join("\n");
+ form.setFieldValue("trackerList", list);
+ }, [config, form]);
+
+ return <>{props.opened &&
+
+
+
+
+
+
+
+ Tracker list, one per line, empty line between tiers
+
+
+
+
+
+
+
+
+ }
+ >;
+}
diff --git a/src/components/modals/servermodals.tsx b/src/components/modals/servermodals.tsx
index a8d5365..5ffbdda 100644
--- a/src/components/modals/servermodals.tsx
+++ b/src/components/modals/servermodals.tsx
@@ -25,6 +25,7 @@ import { AddMagnet, AddTorrent } from "./add";
import { DaemonSettingsModal } from "./daemon";
import { EditTorrent } from "./edittorrent";
import type { ServerTabsRef } from "components/servertabs";
+import { EditTrackers } from "./edittrackers";
const { TAURI, appWindow } = await import(/* webpackChunkName: "taurishim" */"taurishim");
export interface ModalCallbacks {
@@ -34,6 +35,7 @@ export interface ModalCallbacks {
addMagnet: () => void,
addTorrent: () => void,
daemonSettings: () => void,
+ editTrackers: () => void,
editTorrent: () => void,
}
@@ -67,6 +69,7 @@ const ServerModals = React.forwardRef(functio
const [showRemoveModal, openRemoveModal, closeRemoveModal] = usePausingModalState(props.runUpdates);
const [showMoveModal, openMoveModal, closeMoveModal] = usePausingModalState(props.runUpdates);
const [showDaemonSettingsModal, openDaemonSettingsModal, closeDaemonSettingsModal] = usePausingModalState(props.runUpdates);
+ const [showEditTrackersModal, openEditTrackersModal, closeEditTrackersModal] = usePausingModalState(props.runUpdates);
const [showEditTorrentModal, openEditTorrentModal, closeEditTorrentModal] = usePausingModalState(props.runUpdates);
useImperativeHandle(ref, () => ({
@@ -76,6 +79,7 @@ const ServerModals = React.forwardRef(functio
addMagnet: openAddMagnetModal,
addTorrent: openAddTorrentModal,
daemonSettings: openDaemonSettingsModal,
+ editTrackers: openEditTrackersModal,
editTorrent: openEditTorrentModal,
}));
@@ -189,6 +193,8 @@ const ServerModals = React.forwardRef(functio
opened={showAddTorrentModal} close={closeAddTorrentModalAndPop} />
+
>;
diff --git a/src/components/tables/torrenttable.tsx b/src/components/tables/torrenttable.tsx
index aa682e2..8889595 100644
--- a/src/components/tables/torrenttable.tsx
+++ b/src/components/tables/torrenttable.tsx
@@ -458,6 +458,7 @@ function TorrentContextMenu(props: {
}) {
const serverData = useServerTorrentData();
const serverSelected = useServerSelectedTorrents();
+ const rpcVersion = useServerRpcVersion();
const { onRowDoubleClick } = props;
const onOpen = useCallback((reveal: boolean) => {
@@ -663,11 +664,18 @@ function TorrentContextMenu(props: {
Remove...
+ props.modals.current?.editTrackers()}
+ icon={}
+ onMouseEnter={closeQueueSubmenu}
+ disabled={serverSelected.size === 0 || (serverSelected.size > 1 && rpcVersion < 17)}>
+ Trackers...
+
props.modals.current?.editTorrent()}
icon={}
onMouseEnter={closeQueueSubmenu}
- disabled={serverSelected.size !== 1}>
+ disabled={serverSelected.size === 0}>
Properties...