add playlist subscribed filter, align filter UIUX

This commit is contained in:
Simon
2025-08-14 17:13:15 +07:00
parent 41c9415b3f
commit d9a3d7f12f
13 changed files with 119 additions and 86 deletions

View File

@@ -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;
};

View File

@@ -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()}` : ''}`;

View File

@@ -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()}` : ''}`;

View File

@@ -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={() => {

View File

@@ -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={() => {

View File

@@ -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={() => {

View File

@@ -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 }),