add playlist subscribed filter, align filter UIUX
This commit is contained in:
parent
41c9415b3f
commit
d9a3d7f12f
|
|
@ -64,7 +64,9 @@ class ChannelListSerializer(serializers.Serializer):
|
|||
class ChannelListQuerySerializer(serializers.Serializer):
|
||||
"""serialize list query"""
|
||||
|
||||
filter = serializers.ChoiceField(choices=["subscribed"], required=False)
|
||||
filter = serializers.ChoiceField(
|
||||
choices=["subscribed", "unsubscribed"], required=False
|
||||
)
|
||||
page = serializers.IntegerField(required=False)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,11 @@ class ChannelApiListView(ApiBaseView):
|
|||
|
||||
must_list = []
|
||||
query_filter = validated_data.get("filter")
|
||||
if query_filter:
|
||||
must_list.append({"term": {"channel_subscribed": {"value": True}}})
|
||||
if query_filter is not None:
|
||||
channel_subscribed = query_filter == "subscribed"
|
||||
must_list.append(
|
||||
{"term": {"channel_subscribed": {"value": channel_subscribed}}}
|
||||
)
|
||||
|
||||
self.data["query"] = {"bool": {"must": must_list}}
|
||||
self.get_document_list(request)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class PlaylistListQuerySerializer(serializers.Serializer):
|
|||
"""serialize playlist list query params"""
|
||||
|
||||
channel = serializers.CharField(required=False)
|
||||
subscribed = serializers.BooleanField(required=False)
|
||||
subscribed = serializers.BooleanField(required=False, allow_null=True)
|
||||
type = serializers.ChoiceField(
|
||||
choices=["regular", "custom"], required=False
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class QueryBuilder:
|
|||
must_list.append({"match": {"playlist_channel_id": channel}})
|
||||
|
||||
subscribed = self.request_params.get("subscribed")
|
||||
if subscribed:
|
||||
if subscribed is not None:
|
||||
must_list.append({"match": {"playlist_subscribed": subscribed}})
|
||||
|
||||
playlist_type = self.request_params.get("type")
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ class UserMeConfigSerializer(serializers.Serializer):
|
|||
hide_watched = serializers.BooleanField(allow_null=True)
|
||||
file_size_unit = serializers.ChoiceField(choices=["binary", "metric"])
|
||||
show_ignored_only = serializers.BooleanField()
|
||||
show_subed_only = serializers.BooleanField()
|
||||
show_subed_only = serializers.BooleanField(allow_null=True)
|
||||
show_subed_only_playlists = serializers.BooleanField(allow_null=True)
|
||||
show_help_text = serializers.BooleanField()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ class UserConfigType(TypedDict, total=False):
|
|||
hide_watched: bool | None
|
||||
file_size_unit: str
|
||||
show_ignored_only: bool
|
||||
show_subed_only: bool
|
||||
show_subed_only: bool | None
|
||||
show_subed_only_playlists: bool | None
|
||||
show_help_text: bool
|
||||
|
||||
|
||||
|
|
@ -49,7 +50,8 @@ class UserConfig:
|
|||
hide_watched=False,
|
||||
file_size_unit="binary",
|
||||
show_ignored_only=False,
|
||||
show_subed_only=False,
|
||||
show_subed_only=None,
|
||||
show_subed_only_playlists=None,
|
||||
show_help_text=True,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ export type UserConfigType = {
|
|||
hide_watched: boolean | null;
|
||||
file_size_unit: 'binary' | 'metric';
|
||||
show_ignored_only: boolean;
|
||||
show_subed_only: boolean;
|
||||
show_subed_only: boolean | null;
|
||||
show_subed_only_playlists: boolean | null;
|
||||
show_help_text: boolean;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ export type ChannelsListResponse = {
|
|||
config?: ConfigType;
|
||||
};
|
||||
|
||||
const loadChannelList = async (page: number, showSubscribed: boolean) => {
|
||||
const loadChannelList = async (page: number, showSubscribed: boolean | null) => {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (page) searchParams.append('page', page.toString());
|
||||
if (showSubscribed) searchParams.append('filter', 'subscribed');
|
||||
if (showSubscribed !== null) {
|
||||
searchParams.append('filter', showSubscribed ? 'subscribed' : 'unsubscribed');
|
||||
}
|
||||
|
||||
const endpoint = `/api/channel/${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ type PlaylistCategoryType = 'regular' | 'custom';
|
|||
type LoadPlaylistListProps = {
|
||||
channel?: string;
|
||||
page?: number | undefined;
|
||||
subscribed?: boolean;
|
||||
subscribed?: boolean | null;
|
||||
type?: PlaylistCategoryType;
|
||||
};
|
||||
|
||||
|
|
@ -21,7 +21,8 @@ const loadPlaylistList = async ({ channel, page, subscribed, type }: LoadPlaylis
|
|||
|
||||
if (channel) searchParams.append('channel', channel);
|
||||
if (page) searchParams.append('page', page.toString());
|
||||
if (subscribed) searchParams.append('subscribed', subscribed.toString());
|
||||
if (subscribed !== undefined && subscribed !== null)
|
||||
searchParams.append('subscribed', subscribed.toString());
|
||||
if (type) searchParams.append('type', type);
|
||||
|
||||
const endpoint = `/api/playlist/${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import ScrollToTopOnNavigate from '../components/ScrollToTop';
|
|||
import loadPlaylistList, { PlaylistsResponseType } from '../api/loader/loadPlaylistList';
|
||||
import iconGridView from '/img/icon-gridview.svg';
|
||||
import iconListView from '/img/icon-listview.svg';
|
||||
import iconFilter from '/img/icon-filter.svg';
|
||||
import { useUserConfigStore } from '../stores/UserConfigStore';
|
||||
import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig';
|
||||
import { ApiResponseType } from '../functions/APIClient';
|
||||
|
|
@ -18,6 +19,7 @@ const ChannelPlaylist = () => {
|
|||
const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType;
|
||||
|
||||
const [refreshPlaylists, setRefreshPlaylists] = useState(false);
|
||||
const [showFilterItems, setShowFilterItems] = useState(false);
|
||||
|
||||
const [playlistsResponse, setPlaylistsResponse] =
|
||||
useState<ApiResponseType<PlaylistsResponseType>>();
|
||||
|
|
@ -28,7 +30,7 @@ const ChannelPlaylist = () => {
|
|||
const pagination = playlistsResponseData?.paginate;
|
||||
|
||||
const viewStyle = userConfig.view_style_playlist;
|
||||
const showSubedOnly = userConfig.show_subed_only;
|
||||
const showSubedOnly = userConfig.show_subed_only_playlists;
|
||||
|
||||
const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => {
|
||||
const updatedUserConfig = await updateUserConfig(config);
|
||||
|
|
@ -58,30 +60,35 @@ const ChannelPlaylist = () => {
|
|||
<ScrollToTopOnNavigate />
|
||||
<div className="boxed-content">
|
||||
<div className="view-controls">
|
||||
<div className="toggle">
|
||||
<span>Show subscribed only:</span>
|
||||
<div className="toggleBox">
|
||||
<input
|
||||
checked={showSubedOnly}
|
||||
onChange={() => {
|
||||
handleUserConfigUpdate({ show_subed_only: !showSubedOnly });
|
||||
setRefreshPlaylists(true);
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
{!showSubedOnly && (
|
||||
<label htmlFor="" className="ofbtn">
|
||||
Off
|
||||
</label>
|
||||
)}
|
||||
{showSubedOnly && (
|
||||
<label htmlFor="" className="onbtn">
|
||||
On
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="view-icons">
|
||||
{showFilterItems && (
|
||||
<div>
|
||||
<span>Filter:</span>
|
||||
<select
|
||||
value={
|
||||
userConfig.show_subed_only_playlists === null
|
||||
? ''
|
||||
: userConfig.show_subed_only_playlists.toString()
|
||||
}
|
||||
onChange={event => {
|
||||
handleUserConfigUpdate({
|
||||
show_subed_only_playlists:
|
||||
event.target.value === '' ? null : event.target.value === 'true',
|
||||
});
|
||||
setRefreshPlaylists(true);
|
||||
}}
|
||||
>
|
||||
<option value="">All subscribe state</option>
|
||||
<option value="true">Subscribed only</option>
|
||||
<option value="false">Unsubscribed only</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<img
|
||||
src={iconFilter}
|
||||
alt="icon filter"
|
||||
onClick={() => setShowFilterItems(!showFilterItems)}
|
||||
/>
|
||||
<img
|
||||
src={iconGridView}
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import loadChannelList, { ChannelsListResponse } from '../api/loader/loadChannel
|
|||
import iconGridView from '/img/icon-gridview.svg';
|
||||
import iconListView from '/img/icon-listview.svg';
|
||||
import iconAdd from '/img/icon-add.svg';
|
||||
import iconFilter from '/img/icon-filter.svg';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Pagination from '../components/Pagination';
|
||||
import { OutletContextType } from './Base';
|
||||
|
|
@ -52,6 +53,7 @@ const Channels = () => {
|
|||
useState<ApiResponseType<ChannelsListResponse>>();
|
||||
const [showAddForm, setShowAddForm] = useState(false);
|
||||
const [refresh, setRefresh] = useState(true);
|
||||
const [showFilterItems, setShowFilterItems] = useState(false);
|
||||
const [showNotification, setShowNotification] = useState(false);
|
||||
const [channelsToSubscribeTo, setChannelsToSubscribeTo] = useState('');
|
||||
|
||||
|
|
@ -144,31 +146,34 @@ const Channels = () => {
|
|||
/>
|
||||
|
||||
<div className="view-controls">
|
||||
<div className="toggle">
|
||||
<span>Show subscribed only:</span>
|
||||
<div className="toggleBox">
|
||||
<input
|
||||
id="show_subed_only"
|
||||
onChange={async () => {
|
||||
handleUserConfigUpdate({ show_subed_only: !userConfig.show_subed_only });
|
||||
setRefresh(true);
|
||||
}}
|
||||
type="checkbox"
|
||||
checked={userConfig.show_subed_only}
|
||||
/>
|
||||
{!userConfig.show_subed_only && (
|
||||
<label htmlFor="" className="ofbtn">
|
||||
Off
|
||||
</label>
|
||||
)}
|
||||
{userConfig.show_subed_only && (
|
||||
<label htmlFor="" className="onbtn">
|
||||
On
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="view-icons">
|
||||
{showFilterItems && (
|
||||
<div>
|
||||
<span>Filter:</span>
|
||||
<select
|
||||
value={
|
||||
userConfig.show_subed_only === null ? '' : userConfig.show_subed_only.toString()
|
||||
}
|
||||
onChange={event => {
|
||||
handleUserConfigUpdate({
|
||||
show_subed_only:
|
||||
event.target.value === '' ? null : event.target.value === 'true',
|
||||
});
|
||||
setRefresh(true);
|
||||
}}
|
||||
>
|
||||
<option value="">All subscribe state</option>
|
||||
<option value="true">Subscribed only</option>
|
||||
<option value="false">Unsubscribed only</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<img
|
||||
src={iconFilter}
|
||||
alt="icon filter"
|
||||
onClick={() => setShowFilterItems(!showFilterItems)}
|
||||
/>
|
||||
|
||||
<img
|
||||
src={iconGridView}
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useOutletContext } from 'react-router-dom';
|
|||
import iconAdd from '/img/icon-add.svg';
|
||||
import iconGridView from '/img/icon-gridview.svg';
|
||||
import iconListView from '/img/icon-listview.svg';
|
||||
import iconFilter from '/img/icon-filter.svg';
|
||||
|
||||
import { OutletContextType } from './Base';
|
||||
import loadPlaylistList, { PlaylistsResponseType } from '../api/loader/loadPlaylistList';
|
||||
|
|
@ -28,6 +29,7 @@ const Playlists = () => {
|
|||
const [showAddForm, setShowAddForm] = useState(false);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [showNotification, setShowNotification] = useState(false);
|
||||
const [showFilterItems, setShowFilterItems] = useState(false);
|
||||
const [playlistsToAddText, setPlaylistsToAddText] = useState('');
|
||||
const [customPlaylistsToAddText, setCustomPlaylistsToAddText] = useState('');
|
||||
|
||||
|
|
@ -39,7 +41,7 @@ const Playlists = () => {
|
|||
const pagination = playlistResponseData?.paginate;
|
||||
|
||||
const viewStyle = userConfig.view_style_playlist;
|
||||
const showSubedOnly = userConfig.show_subed_only;
|
||||
const showSubedOnly = userConfig.show_subed_only_playlists;
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
|
|
@ -53,7 +55,7 @@ const Playlists = () => {
|
|||
setShowNotification(false);
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [refresh, userConfig.show_subed_only, currentPage, pagination?.current_page]);
|
||||
}, [refresh, userConfig.show_subed_only_playlists, currentPage, pagination?.current_page]);
|
||||
|
||||
const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => {
|
||||
const updatedUserConfig = await updateUserConfig(config);
|
||||
|
|
@ -150,29 +152,35 @@ const Playlists = () => {
|
|||
/>
|
||||
|
||||
<div className="view-controls">
|
||||
<div className="toggle">
|
||||
<span>Show subscribed only:</span>
|
||||
<div className="toggleBox">
|
||||
<input
|
||||
checked={showSubedOnly}
|
||||
onChange={() => {
|
||||
handleUserConfigUpdate({ show_subed_only: !showSubedOnly });
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
{!showSubedOnly && (
|
||||
<label htmlFor="" className="ofbtn">
|
||||
Off
|
||||
</label>
|
||||
)}
|
||||
{showSubedOnly && (
|
||||
<label htmlFor="" className="onbtn">
|
||||
On
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="view-icons">
|
||||
{showFilterItems && (
|
||||
<div>
|
||||
<span>Filter:</span>
|
||||
<select
|
||||
value={
|
||||
userConfig.show_subed_only_playlists === null
|
||||
? ''
|
||||
: userConfig.show_subed_only_playlists.toString()
|
||||
}
|
||||
onChange={event => {
|
||||
handleUserConfigUpdate({
|
||||
show_subed_only_playlists:
|
||||
event.target.value === '' ? null : event.target.value === 'true',
|
||||
});
|
||||
setRefresh(true);
|
||||
}}
|
||||
>
|
||||
<option value="">All subscribe state</option>
|
||||
<option value="true">Subscribed only</option>
|
||||
<option value="false">Unsubscribed only</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<img
|
||||
src={iconFilter}
|
||||
alt="icon filter"
|
||||
onClick={() => setShowFilterItems(!showFilterItems)}
|
||||
/>
|
||||
<img
|
||||
src={iconGridView}
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ export const useUserConfigStore = create<UserConfigState>(set => ({
|
|||
hide_watched: false,
|
||||
file_size_unit: 'binary',
|
||||
show_ignored_only: false,
|
||||
show_subed_only: false,
|
||||
show_subed_only: null,
|
||||
show_subed_only_playlists: null,
|
||||
show_help_text: true,
|
||||
},
|
||||
setUserConfig: userConfig => set({ userConfig }),
|
||||
|
|
|
|||
Loading…
Reference in New Issue