Channel can be created with Tag.

This commit is contained in:
Roland 2024-10-04 16:32:48 -06:00
parent 84a173677c
commit 11dc370ed9
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
4 changed files with 99 additions and 63 deletions

View file

@ -100,7 +100,7 @@ function ChannelPage({create, header}) {
const {channel, changeValue, fetchChannel} = useChannel(channelId);
const {SingleTag} = React.useContext(TagsContext);
const [tagModalOpen, setTagModalOpen] = useState(false);
const [tagEditModalOpen, setTagEditModalOpen] = useState(false);
const [newTagName, setNewTagName] = useState(channel ? channel.tag_name : null);
const [moveToTagDirectory, setMoveToTagDirectory] = useState(true);
const [newTagDirectory, setNewTagDirectory] = useState('');
@ -147,6 +147,7 @@ function ChannelPage({create, header}) {
name: channel.name,
directory: channel.directory,
url: channel.url,
tag_name: channel.tag_name,
};
setDisabled(true);
@ -252,13 +253,13 @@ function ChannelPage({create, header}) {
if (e) {
e.preventDefault();
}
setTagModalOpen(true);
setTagEditModalOpen(true);
}
const handleTagChannel = async () => {
const handleTagEditChannel = async () => {
try {
await tagChannel(channelId, newTagName, moveToTagDirectory ? newTagDirectory : null);
setTagModalOpen(false);
setTagEditModalOpen(false);
} catch (e) {
console.error('Failed to tag channel', e);
} finally {
@ -269,9 +270,10 @@ function ChannelPage({create, header}) {
}
}
const handleTagSelect = async (newTagName_) => {
const handleTagSelectMoveSuggestion = async (newTagName_) => {
setNewTagName(newTagName_);
try {
// Get suggested Tag directory for this Tag and Channel.
const videosDestination = await tagChannelInfo(channelId, newTagName_);
setNewTagDirectory(videosDestination);
} catch (e) {
@ -279,53 +281,57 @@ function ChannelPage({create, header}) {
}
}
const tagModal = <Modal
open={tagModalOpen}
onClose={() => setTagModalOpen(false)}
closeIcon
>
<ModalHeader>{channel.tag_name ? 'Modify Tag' : 'Add Tag'}</ModalHeader>
<ModalContent>
<Grid columns={1}>
<Grid.Row>
<Grid.Column>
<TagsSelector
limit={1}
selectedTagNames={newTagName ? [newTagName] : []}
onAdd={handleTagSelect}
onRemove={() => handleTagSelect(null)}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column>
<Toggle
label='Move to directory: '
checked={moveToTagDirectory}
onChange={setMoveToTagDirectory}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column>
<Input fluid
value={newTagDirectory}
onChange={(e, {value}) => setNewTagDirectory(value)}
disabled={!moveToTagDirectory}
/>
</Grid.Column>
</Grid.Row>
</Grid>
</ModalContent>
<ModalActions>
<Button onClick={() => setTagModalOpen(false)}>Cancel</Button>
<APIButton
color='violet'
onClick={handleTagChannel}
obeyWROLMode={true}
>Save</APIButton>
</ModalActions>
</Modal>;
let tagEditModal;
if (!create) {
// User is editing the Tag of the Channel.
tagEditModal = <Modal
open={tagEditModalOpen}
onClose={() => setTagEditModalOpen(false)}
closeIcon
>
<ModalHeader>{channel.tag_name ? 'Modify Tag' : 'Add Tag'}</ModalHeader>
<ModalContent>
<Grid columns={1}>
<Grid.Row>
<Grid.Column>
<TagsSelector
limit={1}
selectedTagNames={newTagName ? [newTagName] : []}
onAdd={handleTagSelectMoveSuggestion}
onRemove={() => handleTagSelectMoveSuggestion(null)}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column>
<Toggle
label='Move to directory: '
checked={moveToTagDirectory}
onChange={setMoveToTagDirectory}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column>
<Input fluid
value={newTagDirectory}
onChange={(e, {value}) => setNewTagDirectory(value)}
disabled={!moveToTagDirectory}
/>
</Grid.Column>
</Grid.Row>
</Grid>
</ModalContent>
<ModalActions>
<Button onClick={() => setTagEditModalOpen(false)}>Cancel</Button>
<APIButton
color='violet'
onClick={handleTagEditChannel}
obeyWROLMode={true}
>Save</APIButton>
</ModalActions>
</Modal>;
}
return <>
<BackButton/>
@ -383,7 +389,7 @@ function ChannelPage({create, header}) {
</FormField>
</FormGroup>
{channel.tag_name && <SingleTag name={channel.tag_name}/>}
{channel.tag_name && !create && <SingleTag name={channel.tag_name}/>}
<Message error
header={messageHeader}
@ -395,9 +401,9 @@ function ChannelPage({create, header}) {
/>
<Grid stackable columns={2}>
<Grid.Row>
<Grid.Column>
{!create &&
<Grid.Row style={{marginTop: '1em'}}>
<Grid.Column width={8}>
{!create ?
<>
<APIButton
color='red'
@ -415,14 +421,22 @@ function ChannelPage({create, header}) {
obeyWROLMode={true}
style={{marginTop: '1em'}}
>Refresh</APIButton>
<Button
size='small'
onClick={handleTagModalOpen}
color='green'
>Tag</Button>
{tagEditModal}
</>
: <>
<TagsSelector
limit={1}
selectedTagNames={channel.tag_name ? [channel.tag_name] : []}
onAdd={i => changeValue('tag_name', i)}
onRemove={() => changeValue('tag_name', null)}
/>
</>
}
<Button
size='small'
onClick={handleTagModalOpen}
color='green'
>Tag</Button>
{tagModal}
</Grid.Column>
<Grid.Column>
<APIButton

View file

@ -426,6 +426,7 @@ export const useChannel = (channel_id) => {
url: '',
download_frequency: '',
match_regex: '',
tag_name: null,
};
const [fetched, setFetched] = useState(false);
const [channel, setChannel] = useState(emptyChannel);

View file

@ -549,6 +549,26 @@ async def test_tag_channel(async_client, test_session, test_directory, channel_f
assert [i.path for i in test_session.query(Directory)] == [channel.directory, ]
@pytest.mark.asyncio
async def test_create_channel_with_tag(async_client, test_session, test_directory, tag_factory):
"""A Channel can be created with a single Tag."""
tag = await tag_factory()
assert not (test_directory / 'some/deep/new/directory').exists()
body = dict(
name='Channel Name',
directory='some/deep/new/directory',
tag_name=tag.name,
)
request, response = await async_client.post('/api/videos/channels', json=body)
assert response.status_code == HTTPStatus.CREATED, response.content.decode()
channel = test_session.query(Channel).one()
assert channel.tag_name == tag.name
assert channel.directory == (test_directory / 'some/deep/new/directory')
assert (test_directory / 'some/deep/new/directory').is_dir()
@pytest.mark.asyncio
async def test_move_channel(async_client, test_session, test_directory, channel_factory, tag_factory,
video_factory, test_channels_config, test_download_manager, test_downloader):

View file

@ -11,8 +11,9 @@ class ChannelPostRequest:
directory: str
calculate_duration: Optional[bool] = None
generate_posters: Optional[bool] = None
url: Optional[str] = None
source_id: Optional[str] = None
tag_name: Optional[str] = None
url: Optional[str] = None
def __post_init__(self):
self.name = self.name.strip() or None