diff --git a/client/src/javascript/components/modals/Modals.tsx b/client/src/javascript/components/modals/Modals.tsx
index 7a1d62df..1425477e 100644
--- a/client/src/javascript/components/modals/Modals.tsx
+++ b/client/src/javascript/components/modals/Modals.tsx
@@ -6,6 +6,7 @@ import {useKeyPressEvent} from 'react-use';
import AddTorrentsModal from './add-torrents-modal/AddTorrentsModal';
import ConfirmModal from './confirm-modal/ConfirmModal';
import FeedsModal from './feeds-modal/FeedsModal';
+import GenerateMagnetModal from './generate-magnet-modal/GenerateMagnetModal';
import MoveTorrentsModal from './move-torrents-modal/MoveTorrentsModal';
import RemoveTorrentsModal from './remove-torrents-modal/RemoveTorrentsModal';
import SetTagsModal from './set-tags-modal/SetTagsModal';
@@ -25,6 +26,8 @@ const createModal = (id: Modal['id']): React.ReactNode => {
return ;
case 'feeds':
return ;
+ case 'generate-magnet':
+ return ;
case 'move-torrents':
return ;
case 'remove-torrents':
diff --git a/client/src/javascript/components/modals/generate-magnet-modal/GenerateMagnetModal.tsx b/client/src/javascript/components/modals/generate-magnet-modal/GenerateMagnetModal.tsx
new file mode 100644
index 00000000..3a8f5f84
--- /dev/null
+++ b/client/src/javascript/components/modals/generate-magnet-modal/GenerateMagnetModal.tsx
@@ -0,0 +1,145 @@
+import {FC, useEffect, useRef, useState} from 'react';
+import {useIntl} from 'react-intl';
+
+import {TorrentTrackerType} from '@shared/types/TorrentTracker';
+
+import Checkmark from '../../icons/Checkmark';
+import ClipboardIcon from '../../icons/ClipboardIcon';
+import {Form, FormElementAddon, FormError, FormRow, Textbox} from '../../../ui';
+import Modal from '../Modal';
+import TorrentActions from '../../../actions/TorrentActions';
+import TorrentStore from '../../../stores/TorrentStore';
+
+const generateMagnet = (hash: string, trackers?: Array): string => {
+ let result = `magnet:?xt=urn:btih:${hash}`;
+
+ if (trackers?.length) {
+ trackers.forEach((tracker) => {
+ result = `${result}&tr=${encodeURI(tracker)}`;
+ });
+ }
+
+ return result;
+};
+
+const GenerateMagnetModal: FC = () => {
+ const magnetTextboxRef = useRef(null);
+ const magnetTrackersTextboxRef = useRef(null);
+ const intl = useIntl();
+
+ const [isMagnetCopied, setIsMagnetCopied] = useState(false);
+ const [isMagnetTrackersCopied, setIsMagnetTrackersCopied] = useState(false);
+ const [trackerState, setTrackerState] = useState<{
+ isLoadingTrackers: boolean;
+ magnetTrackersLink: string;
+ }>({
+ isLoadingTrackers: true,
+ magnetTrackersLink: '',
+ });
+
+ useEffect(() => {
+ TorrentActions.fetchTorrentTrackers(TorrentStore.selectedTorrents[0]).then((trackers) => {
+ if (trackers != null) {
+ setTrackerState({
+ isLoadingTrackers: false,
+ magnetTrackersLink: generateMagnet(
+ TorrentStore.selectedTorrents[0],
+ trackers.filter((tracker) => tracker.type !== TorrentTrackerType.DHT).map((tracker) => tracker.url),
+ ),
+ });
+ }
+ });
+ }, []);
+
+ const magnetLink = generateMagnet(TorrentStore.selectedTorrents[0]);
+
+ return (
+
+
+
+ }
+ actions={[
+ {
+ clickHandler: null,
+ content: intl.formatMessage({
+ id: 'button.close',
+ }),
+ triggerDismiss: true,
+ type: 'tertiary',
+ },
+ ]}
+ />
+ );
+};
+
+export default GenerateMagnetModal;
diff --git a/client/src/javascript/components/modals/settings-modal/lists/TorrentContextMenuActionsList.tsx b/client/src/javascript/components/modals/settings-modal/lists/TorrentContextMenuActionsList.tsx
index b3544ae0..e76fff18 100644
--- a/client/src/javascript/components/modals/settings-modal/lists/TorrentContextMenuActionsList.tsx
+++ b/client/src/javascript/components/modals/settings-modal/lists/TorrentContextMenuActionsList.tsx
@@ -30,8 +30,13 @@ class TorrentContextMenuActionsList extends Component<
constructor(props: TorrentContextMenuActionsListProps) {
super(props);
+ const {torrentContextMenuActions} = SettingStore.floodSettings;
+
this.state = {
- torrentContextMenuActions: SettingStore.floodSettings.torrentContextMenuActions,
+ torrentContextMenuActions: Object.keys(TorrentContextMenuActions).map((key) => ({
+ id: key,
+ visible: torrentContextMenuActions.some((setting) => setting.id === key && setting.visible),
+ })) as FloodSettings['torrentContextMenuActions'],
};
}
@@ -92,16 +97,11 @@ class TorrentContextMenuActionsList extends Component<
};
render() {
- const torrentContextMenuActions = Object.keys(TorrentContextMenuActions).map((key) => ({
- id: key,
- visible: this.state.torrentContextMenuActions.some((setting) => setting.id === key && setting.visible),
- }));
-
return (