Naively sort channel table without pagination handling
This commit is contained in:
parent
a31dc44606
commit
14a893741d
|
|
@ -81,6 +81,24 @@ class ChannelListQuerySerializer(serializers.Serializer):
|
|||
choices=["subscribed", "unsubscribed"], required=False
|
||||
)
|
||||
page = serializers.IntegerField(required=False)
|
||||
view = serializers.ChoiceField(
|
||||
choices=["grid", "list", "table"], required=False
|
||||
)
|
||||
sort = serializers.ChoiceField(
|
||||
choices=[
|
||||
"name",
|
||||
"subscribers",
|
||||
"video_count",
|
||||
"duration",
|
||||
"media_size",
|
||||
"last_refresh",
|
||||
],
|
||||
required=False,
|
||||
default="name",
|
||||
)
|
||||
order = serializers.ChoiceField(
|
||||
choices=["asc", "desc"], required=False, default="asc"
|
||||
)
|
||||
|
||||
|
||||
class ChannelUpdateSerializer(serializers.Serializer):
|
||||
|
|
|
|||
|
|
@ -85,16 +85,21 @@ class ChannelApiListView(ApiBaseView):
|
|||
)
|
||||
def get(self, request):
|
||||
"""get request"""
|
||||
self.data.update(
|
||||
{"sort": [{"channel_name.keyword": {"order": "asc"}}]}
|
||||
)
|
||||
|
||||
serializer = ChannelListQuerySerializer(data=request.query_params)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
validated_data = serializer.validated_data
|
||||
|
||||
must_list = []
|
||||
view = validated_data.get("view")
|
||||
sort = validated_data.get("sort", "name")
|
||||
order = validated_data.get("order", "asc")
|
||||
query_filter = validated_data.get("filter")
|
||||
|
||||
# Aggregation-based sort fields
|
||||
agg_sorts = ["video_count", "duration", "media_size"]
|
||||
needs_aggregations = view == "table" or sort in agg_sorts
|
||||
|
||||
# Build filter
|
||||
must_list = []
|
||||
if query_filter is not None:
|
||||
channel_subscribed = query_filter == "subscribed"
|
||||
must_list.append(
|
||||
|
|
@ -102,8 +107,23 @@ class ChannelApiListView(ApiBaseView):
|
|||
)
|
||||
|
||||
self.data["query"] = {"bool": {"must": must_list}}
|
||||
|
||||
# Apply ES-level sort for non-aggregation fields
|
||||
sort_field_map = {
|
||||
"name": "channel_name.keyword",
|
||||
"subscribers": "channel_subs",
|
||||
"last_refresh": "channel_last_refresh",
|
||||
}
|
||||
if sort in sort_field_map:
|
||||
self.data["sort"] = [{sort_field_map[sort]: {"order": order}}]
|
||||
else:
|
||||
# For aggregation-based sorts, use default ES sort first
|
||||
self.data["sort"] = [{"channel_name.keyword": {"order": "asc"}}]
|
||||
|
||||
self.get_document_list(request)
|
||||
if self.response.get("data"):
|
||||
|
||||
# Conditionally compute aggregations
|
||||
if self.response.get("data") and needs_aggregations:
|
||||
channel_ids = [
|
||||
channel["channel_id"] for channel in self.response["data"]
|
||||
]
|
||||
|
|
@ -120,10 +140,30 @@ class ChannelApiListView(ApiBaseView):
|
|||
},
|
||||
)
|
||||
)
|
||||
|
||||
# Sort by aggregated field in Python
|
||||
if sort in agg_sorts:
|
||||
self._sort_by_aggregation(sort, order)
|
||||
|
||||
serializer = ChannelListSerializer(self.response)
|
||||
|
||||
return Response(serializer.data)
|
||||
|
||||
def _sort_by_aggregation(self, sort: str, order: str):
|
||||
"""Sort response data by aggregated field"""
|
||||
sort_key_map = {
|
||||
"video_count": "channel_video_count",
|
||||
"duration": "channel_video_duration",
|
||||
"media_size": "channel_video_media_size",
|
||||
}
|
||||
key = sort_key_map.get(sort)
|
||||
if key:
|
||||
self.response["data"] = sorted(
|
||||
self.response["data"],
|
||||
key=lambda x: x.get(key, 0),
|
||||
reverse=(order == "desc"),
|
||||
)
|
||||
|
||||
def post(self, request):
|
||||
"""subscribe/unsubscribe to list of channels"""
|
||||
data = request.data
|
||||
|
|
|
|||
|
|
@ -1,21 +1,40 @@
|
|||
import { PaginationType } from '../../components/Pagination';
|
||||
import { ViewStylesType } from '../../configuration/constants/ViewStyle';
|
||||
import APIClient from '../../functions/APIClient';
|
||||
import { ChannelType } from '../../pages/Channels';
|
||||
import { ConfigType } from '../../pages/Home';
|
||||
|
||||
export type ChannelSortOptions =
|
||||
| 'name'
|
||||
| 'subscribers'
|
||||
| 'video_count'
|
||||
| 'duration'
|
||||
| 'media_size'
|
||||
| 'last_refresh';
|
||||
export type SortOrder = 'asc' | 'desc';
|
||||
|
||||
export type ChannelsListResponse = {
|
||||
data: ChannelType[];
|
||||
paginate: PaginationType;
|
||||
config?: ConfigType;
|
||||
};
|
||||
|
||||
const loadChannelList = async (page: number, showSubscribed: boolean | null) => {
|
||||
const loadChannelList = async (
|
||||
page: number,
|
||||
showSubscribed: boolean | null,
|
||||
viewStyle?: ViewStylesType,
|
||||
sort?: ChannelSortOptions,
|
||||
order?: SortOrder,
|
||||
) => {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (page) searchParams.append('page', page.toString());
|
||||
if (showSubscribed !== null) {
|
||||
searchParams.append('filter', showSubscribed ? 'subscribed' : 'unsubscribed');
|
||||
}
|
||||
if (viewStyle) searchParams.append('view', viewStyle);
|
||||
if (sort) searchParams.append('sort', sort);
|
||||
if (order) searchParams.append('order', order);
|
||||
|
||||
const endpoint = `/api/channel/${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,17 +12,43 @@ import ChannelBanner from './ChannelBanner';
|
|||
import LoadingIndicator from './LoadingIndicator';
|
||||
import { useUserConfigStore } from '../stores/UserConfigStore';
|
||||
import { ViewStylesEnum } from '../configuration/constants/ViewStyle';
|
||||
import { ChannelSortOptions, SortOrder } from '../api/loader/loadChannelList';
|
||||
|
||||
type ChannelListProps = {
|
||||
channelList: ChannelType[] | undefined;
|
||||
refreshChannelList: (refresh: boolean) => void;
|
||||
sortBy?: ChannelSortOptions;
|
||||
sortOrder?: SortOrder;
|
||||
onSort?: (column: ChannelSortOptions) => void;
|
||||
};
|
||||
|
||||
const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => {
|
||||
const ChannelList = ({
|
||||
channelList,
|
||||
refreshChannelList,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
onSort,
|
||||
}: ChannelListProps) => {
|
||||
const { userConfig } = useUserConfigStore();
|
||||
const viewStyle = userConfig.view_style_channel;
|
||||
const useSiUnits = userConfig.file_size_unit === FileSizeUnits.Metric;
|
||||
|
||||
const getSortIndicator = (column: ChannelSortOptions) => {
|
||||
if (sortBy !== column) return '';
|
||||
return sortOrder === 'asc' ? ' \u25B2' : ' \u25BC';
|
||||
};
|
||||
|
||||
const renderSortableHeader = (column: ChannelSortOptions, label: string) => (
|
||||
<th
|
||||
onClick={() => onSort?.(column)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
className={sortBy === column ? 'sort-active' : ''}
|
||||
>
|
||||
{label}
|
||||
{getSortIndicator(column)}
|
||||
</th>
|
||||
);
|
||||
|
||||
if (!channelList) {
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
|
|
@ -36,12 +62,12 @@ const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => {
|
|||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Channel</th>
|
||||
<th>Subscribers</th>
|
||||
<th>Videos</th>
|
||||
<th>Duration</th>
|
||||
<th>Media size</th>
|
||||
<th>Last refreshed</th>
|
||||
{renderSortableHeader('name', 'Channel')}
|
||||
{renderSortableHeader('subscribers', 'Subscribers')}
|
||||
{renderSortableHeader('video_count', 'Videos')}
|
||||
{renderSortableHeader('duration', 'Duration')}
|
||||
{renderSortableHeader('media_size', 'Media size')}
|
||||
{renderSortableHeader('last_refresh', 'Last refreshed')}
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { useOutletContext } from 'react-router-dom';
|
||||
import loadChannelList, { ChannelsListResponse } from '../api/loader/loadChannelList';
|
||||
import loadChannelList, {
|
||||
ChannelSortOptions,
|
||||
ChannelsListResponse,
|
||||
SortOrder,
|
||||
} from '../api/loader/loadChannelList';
|
||||
import iconGridView from '/img/icon-gridview.svg';
|
||||
import iconListView from '/img/icon-listview.svg';
|
||||
import iconTableView from '/img/icon-tableview.svg';
|
||||
|
|
@ -60,6 +64,10 @@ const Channels = () => {
|
|||
const [showFilterItems, setShowFilterItems] = useState(false);
|
||||
const [showNotification, setShowNotification] = useState(false);
|
||||
const [channelsToSubscribeTo, setChannelsToSubscribeTo] = useState('');
|
||||
const [sortBy, setSortBy] = useState<ChannelSortOptions>('name');
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>('asc');
|
||||
|
||||
const viewStyle = userConfig.view_style_channel;
|
||||
|
||||
const { data: channelListResponseData } = channelListResponse ?? {};
|
||||
|
||||
|
|
@ -75,15 +83,38 @@ const Channels = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleSort = (column: ChannelSortOptions) => {
|
||||
if (sortBy === column) {
|
||||
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
|
||||
} else {
|
||||
setSortBy(column);
|
||||
setSortOrder('asc');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const channelListResponse = await loadChannelList(currentPage, userConfig.show_subed_only);
|
||||
const channelListResponse = await loadChannelList(
|
||||
currentPage,
|
||||
userConfig.show_subed_only,
|
||||
viewStyle,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
);
|
||||
|
||||
setChannelListResponse(channelListResponse);
|
||||
setShowNotification(false);
|
||||
setRefresh(false);
|
||||
})();
|
||||
}, [refresh, userConfig.show_subed_only, currentPage, pagination?.current_page]);
|
||||
}, [
|
||||
refresh,
|
||||
userConfig.show_subed_only,
|
||||
currentPage,
|
||||
pagination?.current_page,
|
||||
viewStyle,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -213,8 +244,14 @@ const Channels = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`channel-list ${userConfig.view_style_channel}`}>
|
||||
<ChannelList channelList={channels} refreshChannelList={setRefresh} />
|
||||
<div className={`channel-list ${viewStyle}`}>
|
||||
<ChannelList
|
||||
channelList={channels}
|
||||
refreshChannelList={setRefresh}
|
||||
sortBy={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{pagination && (
|
||||
|
|
|
|||
Loading…
Reference in New Issue