add video filter by height

This commit is contained in:
Simon 2025-08-12 13:58:14 +07:00
parent 445ef8ad05
commit f2da730aa2
No known key found for this signature in database
GPG Key ID: 2C15AA5E89985DD4
9 changed files with 47 additions and 0 deletions

View File

@ -120,6 +120,7 @@ class VideoListQuerySerializer(serializers.Serializer):
choices=VideoTypeEnum.values_known(), required=False
)
page = serializers.IntegerField(required=False)
height = serializers.IntegerField(required=False)
class CommentThreadItemSerializer(serializers.Serializer):

View File

@ -44,6 +44,11 @@ class QueryBuilder:
type_list_list = self.parse_type(video_type)
must_list.append(type_list_list)
height = self.request_params.get("height")
if height:
height_must = self.parse_height(height)
must_list.append(height_must)
query = {"bool": {"must": must_list}}
return query
@ -83,6 +88,11 @@ class QueryBuilder:
return {"match": {"vid_type": vid_type}}
def parse_height(self, height: str):
"""parse height to int"""
return {"term": {"streams.height": {"value": height}}}
def parse_sort(self) -> dict | None:
"""build sort key"""
playlist = self.request_params.get("playlist")

View File

@ -31,6 +31,7 @@ class VideoApiListView(ApiBaseView):
- sort:enum=published|downloaded|views|likes|duration|filesize
- order:enum=asc|desc
- type:enum=videos|streams|shorts
- height:int=px
"""
search_base = "ta_video/_search/"

View File

@ -51,6 +51,7 @@ type FilterType = {
sort?: SortByType;
order?: SortOrderType;
type?: VideoTypes;
height?: string;
};
const loadVideoListByFilter = async (filter: FilterType) => {
@ -67,6 +68,7 @@ const loadVideoListByFilter = async (filter: FilterType) => {
if (filter.sort) searchParams.append('sort', filter.sort);
if (filter.order) searchParams.append('order', filter.order);
if (filter.type) searchParams.append('type', filter.type);
if (filter.height) searchParams.append('height', filter.height.toString());
const endpoint = `/api/video/${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
return APIClient<VideoListByFilterResponseType>(endpoint);

View File

@ -15,6 +15,7 @@ import {
SortOrderType,
VideoTypes,
} from '../api/loader/loadVideoListByPage';
import { useFilterBarTempConf } from '../stores/FilterbarTempConf';
type FilterbarProps = {
viewStyle: ViewStyleNamesType;
@ -26,6 +27,7 @@ const Filterbar = ({ viewStyle, showSort = true, showTypeFilter = false }: Filte
const { userConfig, setUserConfig } = useUserConfigStore();
const [showHidden, setShowHidden] = useState(false);
const { filterHeight, setFilterHeight } = useFilterBarTempConf();
const currentViewStyle = userConfig[viewStyle];
const isGridView = currentViewStyle === ViewStylesEnum.Grid;
@ -82,6 +84,11 @@ const Filterbar = ({ viewStyle, showSort = true, showTypeFilter = false }: Filte
<option value="shorts">Shorts</option>
</select>
)}
<input
placeholder="height in px"
value={filterHeight}
onChange={e => setFilterHeight(e.target.value)}
/>
</div>
{showHidden && (

View File

@ -27,6 +27,7 @@ import humanFileSize from '../functions/humanFileSize';
import { useUserConfigStore } from '../stores/UserConfigStore';
import { FileSizeUnits } from '../api/actions/updateUserConfig';
import { ApiResponseType } from '../functions/APIClient';
import { useFilterBarTempConf } from '../stores/FilterbarTempConf';
type ChannelParams = {
channelId: string;
@ -44,6 +45,7 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
const videoId = searchParams.get('videoId');
const [refresh, setRefresh] = useState(false);
const { filterHeight } = useFilterBarTempConf();
const [channelResponse, setChannelResponse] = useState<ApiResponseType<ChannelResponseType>>();
const [videoResponse, setVideoReponse] =
@ -82,6 +84,7 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
sort: userConfig.sort_by,
order: userConfig.sort_order,
type: videoType,
height: filterHeight,
});
const channelAggs = await loadChannelAggs(channelId);
@ -95,6 +98,7 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
userConfig.sort_by,
userConfig.sort_order,
userConfig.hide_watched,
filterHeight,
currentPage,
channelId,
pagination?.current_page,

View File

@ -22,6 +22,7 @@ import EmbeddableVideoPlayer from '../components/EmbeddableVideoPlayer';
import { SponsorBlockType } from './Video';
import { useUserConfigStore } from '../stores/UserConfigStore';
import { ApiResponseType } from '../functions/APIClient';
import { useFilterBarTempConf } from '../stores/FilterbarTempConf';
export type PlayerType = {
watched: boolean;
@ -118,6 +119,8 @@ const Home = () => {
const [continueVideoResponse, setContinueVideoResponse] =
useState<ApiResponseType<VideoListByFilterResponseType>>();
const { filterHeight } = useFilterBarTempConf();
const { data: videoResponseData } = videoResponse ?? {};
const { data: continueVideoResponseData } = continueVideoResponse ?? {};
@ -145,6 +148,7 @@ const Home = () => {
type: userConfig.vid_type_filter as VideoTypes,
sort: userConfig.sort_by,
order: userConfig.sort_order,
height: filterHeight,
});
try {
@ -165,6 +169,7 @@ const Home = () => {
userConfig.sort_order,
userConfig.hide_watched,
userConfig.vid_type_filter,
filterHeight,
currentPage,
pagination?.current_page,
videoId,

View File

@ -33,6 +33,7 @@ import { useUserConfigStore } from '../stores/UserConfigStore';
import { ApiResponseType } from '../functions/APIClient';
import NotFound from './NotFound';
import updatePlaylistSortOrder from '../api/actions/updatePlaylistSortOrder';
import { useFilterBarTempConf } from '../stores/FilterbarTempConf';
export type VideoResponseType = {
data?: VideoType[];
@ -46,6 +47,7 @@ const Playlist = () => {
const videoId = searchParams.get('videoId');
const { userConfig } = useUserConfigStore();
const { filterHeight } = useFilterBarTempConf();
const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType;
const isAdmin = useIsAdmin();
@ -90,6 +92,7 @@ const Playlist = () => {
? WatchTypesEnum.Watched
: WatchTypesEnum.Unwatched) as WatchTypes),
type: userConfig.vid_type_filter as VideoTypes,
height: filterHeight,
});
setPlaylistResponse(playlist);
@ -109,6 +112,7 @@ const Playlist = () => {
playlistId,
userConfig.hide_watched,
userConfig.vid_type_filter,
filterHeight,
refresh,
currentPage,
pagination?.current_page,

View File

@ -0,0 +1,13 @@
// temporary values for filtering, not stored in backend
import { create } from 'zustand';
interface FilterbarTempConfInterface {
filterHeight: string;
setFilterHeight: (filterHeight: string) => void;
}
export const useFilterBarTempConf = create<FilterbarTempConfInterface>(set => ({
filterHeight: '',
setFilterHeight: (filterHeight: string) => set({ filterHeight }),
}));