Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
9 changes: 9 additions & 0 deletions client/src/javascript/actions/TorrentActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
AddTorrentByFileOptions,
AddTorrentByURLOptions,
ReannounceTorrentsOptions,
SetTorrentsCategoryOptions,
SetTorrentsTagsOptions,
} from '@shared/schema/api/torrents';
import {
Expand Down Expand Up @@ -249,6 +250,14 @@ const TorrentActions = {
},
),

setCategory: (options: SetTorrentsCategoryOptions): Promise<void> =>
axios.patch(`${baseURI}api/torrents/category`, options).then(
() => UIStore.handleSetCategorySuccess(),
() => {
// do nothing.
},
),

setTags: (options: SetTorrentsTagsOptions): Promise<void> =>
axios.patch(`${baseURI}api/torrents/tags`, options).then(
() => UIStore.handleSetTaxonomySuccess(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import classnames from 'classnames';
import {FC, ReactElement, ReactNode, useEffect, useRef, useState} from 'react';
import {sort} from 'fast-sort';
import {Trans} from '@lingui/react';
import {useKeyPressEvent} from 'react-use';

import {ContextMenu, FormElementAddon, FormRowItem, Portal, SelectItem, Textbox} from '@client/ui';
import {Chevron} from '@client/ui/icons';
import TorrentFilterStore from '@client/stores/TorrentFilterStore';

import type {TorrentProperties} from '@shared/types/Torrent';

interface CategorySelectProps {
id?: string;
label?: ReactNode;
defaultValue?: TorrentProperties['category'];
placeholder?: string;
onCategorySelected?: (categories: TorrentProperties['category']) => void;
}

const CategorySelect: FC<CategorySelectProps> = ({
defaultValue,
placeholder,
id,
label,
onCategorySelected,
}: CategorySelectProps) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [selectedCategories, setSelectedCategories] = useState<string>(defaultValue ?? '');
const formRowRef = useRef<HTMLDivElement>(null);
const menuRef = useRef<HTMLDivElement>(null);
const textboxRef = useRef<HTMLInputElement>(null);

const classes = classnames('select form__element', {
'select--is-open': isOpen,
});

useKeyPressEvent('Escape', (e) => {
e.preventDefault();
setIsOpen(false);
});

useEffect(() => {
const handleDocumentClick = (e: Event) => {
if (!formRowRef.current?.contains(e.target as unknown as Node)) {
setIsOpen(false);
}
};

document.addEventListener('click', handleDocumentClick);

return () => {
document.removeEventListener('click', handleDocumentClick);
};
}, []);

useEffect(() => {
if (textboxRef.current != null) {
textboxRef.current.value = selectedCategories;
}
if (onCategorySelected) {
onCategorySelected(selectedCategories);
}
}, [selectedCategories, onCategorySelected]);

return (
<FormRowItem ref={formRowRef}>
<div className={classes}>
<Textbox
autoComplete={isOpen ? 'off' : undefined}
id={id || 'categories'}
addonPlacement="after"
defaultValue={defaultValue}
label={label}
onChange={() => {
if (textboxRef.current != null) {
setSelectedCategories(textboxRef.current.value.trim());
}
}}
placeholder={placeholder}
ref={textboxRef}
>
<FormElementAddon
onClick={() => {
setIsOpen(!isOpen);
}}
className="select__indicator"
>
<Chevron />
</FormElementAddon>
<Portal>
<ContextMenu isIn={isOpen} overlayProps={{isInteractive: false}} ref={menuRef} triggerRef={textboxRef}>
{[
...new Set(['uncategorized', ...sort(Object.keys(TorrentFilterStore.taxonomy.categoriesCounts)).asc()]),
].reduce((accumulator: Array<ReactElement>, category) => {
if (category === '') {
return accumulator;
}

accumulator.push(
<SelectItem
id={category}
key={category}
isSelected={selectedCategories === category}
onClick={() => {
if (category === 'uncategorized') {
setSelectedCategories('');
} else {
setSelectedCategories(category);
}
}}
>
{category === 'uncategorized' ? <Trans id="filter.uncategorized" /> : category}
</SelectItem>,
);
return accumulator;
}, [])}
</ContextMenu>
</Portal>
</Textbox>
</div>
</FormRowItem>
);
};

CategorySelect.defaultProps = {
id: 'categories',
label: undefined,
defaultValue: undefined,
placeholder: undefined,
onCategorySelected: undefined,
};

export default CategorySelect;
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 @@ -9,6 +9,7 @@ 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 SetCategoryModal from './set-category-modal/SetCategoryModal';
import SetTagsModal from './set-tags-modal/SetTagsModal';
import SetTrackersModal from './set-trackers-modal/SetTrackersModal';
import SettingsModal from './settings-modal/SettingsModal';
Expand All @@ -31,6 +32,8 @@ const createModal = (id: Modal['id']): React.ReactNode => {
return <MoveTorrentsModal />;
case 'remove-torrents':
return <RemoveTorrentsModal />;
case 'set-category':
return <SetCategoryModal />;
case 'set-taxonomy':
return <SetTagsModal />;
case 'set-trackers':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import UIStore from '@client/stores/UIStore';
import AddTorrentsActions from './AddTorrentsActions';
import FilesystemBrowserTextbox from '../../general/form-elements/FilesystemBrowserTextbox';
import TagSelect from '../../general/form-elements/TagSelect';
import CategorySelect from '../../general/form-elements/CategorySelect';
import TextboxRepeater, {getTextArray} from '../../general/form-elements/TextboxRepeater';

type AddTorrentsByCreationFormData = {
Expand All @@ -20,6 +21,7 @@ type AddTorrentsByCreationFormData = {
infoSource: string;
isPrivate: boolean;
start: boolean;
category: string;
tags: string;
};

Expand Down Expand Up @@ -63,6 +65,13 @@ const AddTorrentsByCreation: FC = () => {
{i18n._('torrents.create.is.private.label')}
</Checkbox>
</FormRow>
<FormRow>
<CategorySelect
id="category"
label={i18n._('torrents.add.category')}
placeholder={i18n._('torrents.create.category.input.placeholder')}
/>
</FormRow>
<FormRow>
<TagSelect
id="tags"
Expand Down Expand Up @@ -91,6 +100,7 @@ const AddTorrentsByCreation: FC = () => {
infoSource: formData.infoSource,
isPrivate: formData.isPrivate || false,
start: formData.start || false,
category: formData.category || '',
tags: formData.tags != null ? formData.tags.split(',') : undefined,
}).then(() => {
UIStore.setActiveModal(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import UIStore from '@client/stores/UIStore';
import AddTorrentsActions from './AddTorrentsActions';
import FileDropzone from '../../general/form-elements/FileDropzone';
import FilesystemBrowserTextbox from '../../general/form-elements/FilesystemBrowserTextbox';
import CategorySelect from '../../general/form-elements/CategorySelect';
import TagSelect from '../../general/form-elements/TagSelect';

import type {ProcessedFiles} from '../../general/form-elements/FileDropzone';

interface AddTorrentsByFileFormData {
destination: string;
start: boolean;
category: string;
tags: string;
isBasePath: boolean;
isCompleted: boolean;
Expand Down Expand Up @@ -46,6 +48,21 @@ const AddTorrentsByFile: FC = () => {
}}
/>
</FormRow>
<FormRow>
<CategorySelect
label={i18n._('torrents.add.category')}
id="category"
onCategorySelected={(category) => {
if (textboxRef.current != null) {
const suggestedPath = SettingStore.floodSettings.torrentCategoryDestinations?.[category];
if (typeof suggestedPath === 'string' && textboxRef.current != null) {
textboxRef.current.value = suggestedPath;
textboxRef.current.dispatchEvent(new Event('input', {bubbles: true}));
}
}
}}
/>
</FormRow>
<FormRow>
<TagSelect
label={i18n._('torrents.add.tags')}
Expand Down Expand Up @@ -79,7 +96,7 @@ const AddTorrentsByFile: FC = () => {
const formData = formRef.current?.getFormData();
setIsAddingTorrents(true);

const {destination, start, tags, isBasePath, isCompleted, isSequential} =
const {destination, start, category, tags, isBasePath, isCompleted, isSequential} =
formData as Partial<AddTorrentsByFileFormData>;

const filesData: Array<string> = [];
Expand All @@ -97,6 +114,7 @@ const AddTorrentsByFile: FC = () => {
TorrentActions.addTorrentsByFiles({
files: filesData as [string, ...string[]],
destination,
category,
tags: tagsArray,
isBasePath,
isCompleted,
Expand All @@ -109,6 +127,7 @@ const AddTorrentsByFile: FC = () => {
saveAddTorrentsUserPreferences({
start,
destination,
categories: category,
tags: tagsArray,
tab: 'by-file',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import UIStore from '@client/stores/UIStore';

import AddTorrentsActions from './AddTorrentsActions';
import FilesystemBrowserTextbox from '../../general/form-elements/FilesystemBrowserTextbox';
import CategorySelect from '../../general/form-elements/CategorySelect';
import TagSelect from '../../general/form-elements/TagSelect';
import TextboxRepeater, {getTextArray} from '../../general/form-elements/TextboxRepeater';

Expand All @@ -23,6 +24,7 @@ type AddTorrentsByURLFormData = {
isCompleted: boolean;
isSequential: boolean;
start: boolean;
category: string;
tags: string;
};

Expand Down Expand Up @@ -76,6 +78,21 @@ const AddTorrentsByURL: FC = () => {
label={i18n._('torrents.add.cookies.label')}
placeholder={i18n._('torrents.add.cookies.input.placeholder')}
/>
<FormRow>
<CategorySelect
label={i18n._('torrents.add.category')}
id="category"
onCategorySelected={(category) => {
if (textboxRef.current != null) {
const suggestedPath = SettingStore.floodSettings.torrentCategoryDestinations?.[category];
if (typeof suggestedPath === 'string' && textboxRef.current != null) {
textboxRef.current.value = suggestedPath;
textboxRef.current.dispatchEvent(new Event('input', {bubbles: true}));
}
}
}}
/>
</FormRow>
<FormRow>
<TagSelect
id="tags"
Expand Down Expand Up @@ -136,6 +153,7 @@ const AddTorrentsByURL: FC = () => {
isCompleted: formData.isCompleted,
isSequential: formData.isSequential,
start: formData.start,
category: formData.category,
tags,
}).then(() => {
UIStore.setActiveModal(null);
Expand All @@ -144,6 +162,7 @@ const AddTorrentsByURL: FC = () => {
saveAddTorrentsUserPreferences({
start: formData.start,
destination: formData.destination,
categories: formData.category,
tags,
tab: 'by-url',
});
Expand Down
Loading