Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Folder watching monitoring and downloading. #729

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions client/src/javascript/actions/WatchActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import axios from 'axios';

import ConfigStore from '@client/stores/ConfigStore';
import WatchStore from '@client/stores/WatchStore';

import type {AddWatchOptions, ModifyWatchOptions} from '@shared/types/api/watch-monitor';
import {WatchedDirectory} from '@shared/types/Watch';

const {baseURI} = ConfigStore;

const WatchActions = {
addWatch: (options: AddWatchOptions) =>
axios.put(`${baseURI}api/watch-monitor`, options).then(() => WatchActions.fetchWatchMonitors()),

modifyWatch: (id: string, options: ModifyWatchOptions) =>
axios.patch(`${baseURI}api/watch-monitor/${id}`, options).then(() => WatchActions.fetchWatchMonitors()),

fetchWatchMonitors: () =>
axios.get<Array<WatchedDirectory>>(`${baseURI}api/watch-monitor`).then(
({data}) => {
WatchStore.handleWatchedDirectoryFetchSuccess(data);
},
() => {
// do nothing.
},
),

removeWatchMonitors: (id: string) =>
axios.delete(`${baseURI}api/watch-monitor/${id}`).then(
() => WatchActions.fetchWatchMonitors(),
() => {
// do nothing.
},
),
} as const;

export default WatchActions;
3 changes: 3 additions & 0 deletions client/src/javascript/components/modals/Modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import TorrentDetailsModal from './torrent-details-modal/TorrentDetailsModal';
import UIStore from '../../stores/UIStore';

import type {Modal} from '../../stores/UIStore';
import WatchesModal from '@client/components/modals/fwatch-modal/WatchesModal';

const createModal = (id: Modal['id']): React.ReactNode => {
switch (id) {
Expand All @@ -25,6 +26,8 @@ const createModal = (id: Modal['id']): React.ReactNode => {
return <ConfirmModal />;
case 'feeds':
return <FeedsModal />;
case 'watches':
return <WatchesModal />;
case 'generate-magnet':
return <GenerateMagnetModal />;
case 'move-torrents':
Expand Down
98 changes: 98 additions & 0 deletions client/src/javascript/components/modals/fwatch-modal/WatchList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {FC} from 'react';
import {observer} from 'mobx-react';
import {Trans} from '@lingui/react';

import {Close, Edit} from '@client/ui/icons';

import {WatchedDirectory} from '@shared/types/Watch';
import WatchStore from '@client/stores/WatchStore';

interface FeedListProps {
currentWatch: WatchedDirectory | null;
onSelect: (watcher: WatchedDirectory) => void;
onRemove: (watcher: WatchedDirectory) => void;
}

const WatchList: FC<FeedListProps> = observer(
({currentWatch, onSelect, onRemove}: FeedListProps) => {
const {watchedDirectories} = WatchStore;

if (watchedDirectories.length === 0) {
return (
<ul className="interactive-list">
<li className="interactive-list__item">
<Trans id="watches.no.watches.defined" />
</li>
</ul>
);
}

return (
<ul className="interactive-list feed-list">
{watchedDirectories.map((watcher) => {
const matchedCount = watcher.count || 0;

return (
<li
className="interactive-list__item interactive-list__item--stacked-content feed-list__feed"
key={watcher._id}
>
<div className="interactive-list__label">
<ul className="interactive-list__detail-list">
<li
className="interactive-list__detail-list__item
interactive-list__detail--primary"
>
{watcher.label}
</li>
<li
className="interactive-list__detail-list__item
interactive-list__detail-list__item--overflow
interactive-list__detail interactive-list__detail--secondary"
>
<Trans id="feeds.match.count" values={{count: matchedCount}} />
</li>
{watcher === currentWatch && (
<li
className="interactive-list__detail-list__item
interactive-list__detail--primary"
>
Modifying
</li>
)}
</ul>
<ul className="interactive-list__detail-list">
<li
className="interactive-list__detail-list__item
interactive-list__detail-list__item--overflow
interactive-list__detail interactive-list__detail--tertiary"
>
<a href={watcher.dir} rel="noopener noreferrer" target="_blank">
{watcher.dir}
</a>
</li>
</ul>
</div>
<button
className="interactive-list__icon interactive-list__icon--action"
type="button"
onClick={() => onSelect(watcher)}
>
<Edit />
</button>
<button
className="interactive-list__icon interactive-list__icon--action interactive-list__icon--action--warning"
type="button"
onClick={() => onRemove(watcher)}
>
<Close />
</button>
</li>
);
})}
</ul>
);
},
);

export default WatchList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {FC, useRef} from 'react';
import {Trans, useLingui} from '@lingui/react';

import {Button, FormRow, FormRowGroup, Textbox} from '@client/ui';

import {WatchedDirectory} from '@shared/types/Watch';
import TagSelect from '@client/components/general/form-elements/TagSelect';
import SettingStore from '@client/stores/SettingStore';
import FilesystemBrowserTextbox from '@client/components/general/form-elements/FilesystemBrowserTextbox';

interface WatchFormProps {
currentWatch: WatchedDirectory | null;
defaultWatch: Pick<WatchedDirectory,'label' | 'dir' | 'destination' | 'tags'>;
isSubmitting: boolean;
onCancel: () => void;
}

const WatchesForm: FC<WatchFormProps> = ({
currentWatch,
defaultWatch,
isSubmitting,
onCancel
}: WatchFormProps) => {
const {i18n} = useLingui();

const dirTextboxRef = useRef<HTMLInputElement>(null);
const destTextboxRef = useRef<HTMLInputElement>(null);
const tagsTextboxRef = useRef<HTMLInputElement>(null);

return (
<FormRowGroup>
<FormRow>
<Textbox
id="label"
label={i18n._('feeds.label')}
placeholder={i18n._('feeds.label')}
defaultValue={currentWatch?.label ?? defaultWatch.label}
/>
</FormRow>
<FormRow>
<FilesystemBrowserTextbox
id="dir"
label={i18n._('watches.dir')}
ref={dirTextboxRef}
selectable="directories"
suggested={currentWatch?.dir ?? defaultWatch?.dir}
/>
</FormRow>
<FormRow>
<FilesystemBrowserTextbox
id="destination"
label={i18n._('torrents.add.destination.label')}
ref={destTextboxRef}
selectable="directories"
suggested={currentWatch?.destination ?? defaultWatch?.destination}
/>
</FormRow>
<FormRow>
<TagSelect
id="tags"
label={i18n._('torrents.add.tags')}
defaultValue={currentWatch?.tags ?? defaultWatch?.tags}
onTagSelected={(tags) => {
if (tagsTextboxRef.current != null) {
const suggestedPath = SettingStore.floodSettings.torrentDestinations?.[tags[0]];
if (typeof suggestedPath === 'string' && tagsTextboxRef.current != null) {
tagsTextboxRef.current.value = suggestedPath;
tagsTextboxRef.current.dispatchEvent(new Event('input', {bubbles: true}));
}
}
}}
/>
</FormRow>
<FormRow>
<Button labelOffset onClick={onCancel}>
<Trans id="button.cancel" />
</Button>
<Button labelOffset type="submit" isLoading={isSubmitting}>
<Trans id="button.save.feed" />
</Button>
</FormRow>
</FormRowGroup>
);
};

export default WatchesForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {FC, useEffect} from 'react';
import {useLingui} from '@lingui/react';

import WatchesTab from './WatchesTab';
import Modal from '../Modal';
import WatchActions from '@client/actions/WatchActions';

const WatchesModal: FC = () => {
const {i18n} = useLingui();

useEffect(() => {
WatchActions.fetchWatchMonitors();
}, []);

const tabs = {
watches: {
content: WatchesTab,
label: i18n._('watches.tabs.watches'),
}
};

return <Modal heading={i18n._('watches.tabs.heading')} orientation="horizontal" size="large" tabs={tabs} />;
};

export default WatchesModal;
Loading
Loading