From f2da730aa2eb9e2a597a02d7da46f086faa47251 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 12 Aug 2025 13:58:14 +0700 Subject: [PATCH] add video filter by height --- backend/video/serializers.py | 1 + backend/video/src/query_building.py | 10 ++++++++++ backend/video/views.py | 1 + frontend/src/api/loader/loadVideoListByPage.ts | 2 ++ frontend/src/components/Filterbar.tsx | 7 +++++++ frontend/src/pages/ChannelVideo.tsx | 4 ++++ frontend/src/pages/Home.tsx | 5 +++++ frontend/src/pages/Playlist.tsx | 4 ++++ frontend/src/stores/FilterbarTempConf.ts | 13 +++++++++++++ 9 files changed, 47 insertions(+) create mode 100644 frontend/src/stores/FilterbarTempConf.ts diff --git a/backend/video/serializers.py b/backend/video/serializers.py index fe17da6c..55d229f0 100644 --- a/backend/video/serializers.py +++ b/backend/video/serializers.py @@ -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): diff --git a/backend/video/src/query_building.py b/backend/video/src/query_building.py index 84a4b955..cb8ee8b9 100644 --- a/backend/video/src/query_building.py +++ b/backend/video/src/query_building.py @@ -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") diff --git a/backend/video/views.py b/backend/video/views.py index ef5c011a..4bb95d87 100644 --- a/backend/video/views.py +++ b/backend/video/views.py @@ -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/" diff --git a/frontend/src/api/loader/loadVideoListByPage.ts b/frontend/src/api/loader/loadVideoListByPage.ts index 1cf67155..726279ae 100644 --- a/frontend/src/api/loader/loadVideoListByPage.ts +++ b/frontend/src/api/loader/loadVideoListByPage.ts @@ -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(endpoint); diff --git a/frontend/src/components/Filterbar.tsx b/frontend/src/components/Filterbar.tsx index 7a27aeac..71b84811 100644 --- a/frontend/src/components/Filterbar.tsx +++ b/frontend/src/components/Filterbar.tsx @@ -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 )} + setFilterHeight(e.target.value)} + /> {showHidden && ( diff --git a/frontend/src/pages/ChannelVideo.tsx b/frontend/src/pages/ChannelVideo.tsx index 522140a1..0c3d9eac 100644 --- a/frontend/src/pages/ChannelVideo.tsx +++ b/frontend/src/pages/ChannelVideo.tsx @@ -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>(); 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, diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 6e05b26f..7ae3abff 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -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>(); + 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, diff --git a/frontend/src/pages/Playlist.tsx b/frontend/src/pages/Playlist.tsx index fc3777d5..c28654f3 100644 --- a/frontend/src/pages/Playlist.tsx +++ b/frontend/src/pages/Playlist.tsx @@ -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, diff --git a/frontend/src/stores/FilterbarTempConf.ts b/frontend/src/stores/FilterbarTempConf.ts new file mode 100644 index 00000000..045f0691 --- /dev/null +++ b/frontend/src/stores/FilterbarTempConf.ts @@ -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(set => ({ + filterHeight: '', + setFilterHeight: (filterHeight: string) => set({ filterHeight }), +}));