From 776481c513e9e50e4cf45f76e5060308c9139f2e Mon Sep 17 00:00:00 2001 From: MerlinScheurer Date: Sat, 10 May 2025 14:09:59 +0200 Subject: [PATCH] Add Video details page to settings --- backend/video/src/constants.py | 2 + frontend/src/api/actions/updateUserConfig.ts | 3 +- .../src/api/loader/loadVideoListByPage.ts | 40 +++- frontend/src/components/Filterbar.tsx | 21 +- .../src/components/SettingsNavigation.tsx | 3 + frontend/src/components/SortArrow.tsx | 23 ++ .../src/configuration/routes/RouteList.ts | 1 + frontend/src/main.tsx | 5 + frontend/src/pages/Home.tsx | 2 - frontend/src/pages/SettingsVideos.tsx | 214 ++++++++++++++++++ frontend/src/style.css | 5 + 11 files changed, 305 insertions(+), 14 deletions(-) create mode 100644 frontend/src/components/SortArrow.tsx create mode 100644 frontend/src/pages/SettingsVideos.tsx diff --git a/backend/video/src/constants.py b/backend/video/src/constants.py index 12fc4b23..11b7de74 100644 --- a/backend/video/src/constants.py +++ b/backend/video/src/constants.py @@ -31,6 +31,8 @@ class SortEnum(enum.Enum): LIKES = "stats.like_count" DURATION = "player.duration" MEDIASIZE = "media_size" + WIDTH = "streams.width" + HEIGHT = "streams.height" @classmethod def values(cls) -> list[str]: diff --git a/frontend/src/api/actions/updateUserConfig.ts b/frontend/src/api/actions/updateUserConfig.ts index 84c5ce3d..4557f287 100644 --- a/frontend/src/api/actions/updateUserConfig.ts +++ b/frontend/src/api/actions/updateUserConfig.ts @@ -1,5 +1,6 @@ -import { SortByType, SortOrderType, ViewLayoutType } from '../../pages/Home'; +import { ViewLayoutType } from '../../pages/Home'; import APIClient from '../../functions/APIClient'; +import { SortByType, SortOrderType } from '../loader/loadVideoListByPage'; export type ColourVariants = | 'dark.css' diff --git a/frontend/src/api/loader/loadVideoListByPage.ts b/frontend/src/api/loader/loadVideoListByPage.ts index 06066f77..b598df87 100644 --- a/frontend/src/api/loader/loadVideoListByPage.ts +++ b/frontend/src/api/loader/loadVideoListByPage.ts @@ -1,4 +1,4 @@ -import { ConfigType, SortByType, SortOrderType, VideoType } from '../../pages/Home'; +import { ConfigType, VideoType } from '../../pages/Home'; import { PaginationType } from '../../components/Pagination'; import APIClient from '../../functions/APIClient'; @@ -8,9 +8,45 @@ export type VideoListByFilterResponseType = { paginate?: PaginationType; }; -type WatchTypes = 'watched' | 'unwatched' | 'continue'; +export type SortByType = + | 'published' + | 'downloaded' + | 'views' + | 'likes' + | 'duration' + | 'mediasize' + | 'width' + | 'height'; +export const SortByEnum = { + Published: 'published', + Downloaded: 'downloaded', + Views: 'views', + Likes: 'likes', + Duration: 'duration', + 'Media Size': 'mediasize', +}; + +export const SortByExpandedEnum = { + ...SortByEnum, + Width: 'width', + Height: 'height', +}; + +export type SortOrderType = 'asc' | 'desc'; +export const SortOrderEnum = { + Asc: 'asc', + Desc: 'desc', +}; + export type VideoTypes = 'videos' | 'streams' | 'shorts'; +export type WatchTypes = 'watched' | 'unwatched' | 'continue'; +export const WatchTypesEnum = { + Watched: 'watched', + Unwatched: 'unwatched', + Continue: 'continue', +}; + type FilterType = { page?: number; playlist?: string; diff --git a/frontend/src/components/Filterbar.tsx b/frontend/src/components/Filterbar.tsx index 57394b19..32a91b1d 100644 --- a/frontend/src/components/Filterbar.tsx +++ b/frontend/src/components/Filterbar.tsx @@ -4,10 +4,15 @@ import iconAdd from '/img/icon-add.svg'; import iconSubstract from '/img/icon-substract.svg'; import iconGridView from '/img/icon-gridview.svg'; import iconListView from '/img/icon-listview.svg'; -import { SortByType, SortOrderType } from '../pages/Home'; import { useUserConfigStore } from '../stores/UserConfigStore'; import { ViewStyles } from '../configuration/constants/ViewStyle'; import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig'; +import { + SortByEnum, + SortByType, + SortOrderEnum, + SortOrderType, +} from '../api/loader/loadVideoListByPage'; type FilterbarProps = { hideToggleText: string; @@ -67,12 +72,9 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar handleUserConfigUpdate({ sort_by: event.target.value as SortByType }); }} > - - - - - - + {Object.entries(SortByEnum).map(([key, value]) => { + return ; + })} diff --git a/frontend/src/components/SettingsNavigation.tsx b/frontend/src/components/SettingsNavigation.tsx index 5475614b..970f16b7 100644 --- a/frontend/src/components/SettingsNavigation.tsx +++ b/frontend/src/components/SettingsNavigation.tsx @@ -26,6 +26,9 @@ const SettingsNavigation = () => {

Actions

+ +

Videos

+ )} diff --git a/frontend/src/components/SortArrow.tsx b/frontend/src/components/SortArrow.tsx new file mode 100644 index 00000000..06c60e3d --- /dev/null +++ b/frontend/src/components/SortArrow.tsx @@ -0,0 +1,23 @@ +import { SortOrderType } from '../api/loader/loadVideoListByPage'; + +const ARROW_UP = '↑'; +const ARROW_DOWN = '↓'; + +type SortArrowProps = { + visible: boolean; + sortOrder: SortOrderType; +}; + +const SortArrow = ({ visible, sortOrder }: SortArrowProps) => { + if (!visible) { + return null; + } + + if (sortOrder === 'asc') { + return ARROW_UP; + } + + return ARROW_DOWN; +}; + +export default SortArrow; diff --git a/frontend/src/configuration/routes/RouteList.ts b/frontend/src/configuration/routes/RouteList.ts index 41f36558..203fff12 100644 --- a/frontend/src/configuration/routes/RouteList.ts +++ b/frontend/src/configuration/routes/RouteList.ts @@ -17,6 +17,7 @@ const Routes = { SettingsApplication: '/settings/application/', SettingsScheduling: '/settings/scheduling/', SettingsActions: '/settings/actions/', + SettingsVideos: '/settings/videos/', Login: '/login/', Video: (id: string) => `/video/${id}`, VideoAtTimestamp: (id: string, timestamp: string) => `/video/${id}/?t=${timestamp}`, diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 53868ba0..f2afcf5a 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -28,6 +28,7 @@ import Download from './pages/Download'; import loadUserAccount from './api/loader/loadUserAccount'; import loadAppsettingsConfig from './api/loader/loadAppsettingsConfig'; import NotFound from './pages/NotFound'; +import SettingsVideos from './pages/SettingsVideos'; const router = createBrowserRouter( [ @@ -135,6 +136,10 @@ const router = createBrowserRouter( path: Routes.SettingsUser, element: , }, + { + path: Routes.SettingsVideos, + element: , + }, { path: Routes.About, element: , diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 45a5b49b..b36b2b7b 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -99,8 +99,6 @@ export type ConfigType = { downloads: DownloadsType; }; -export type SortByType = 'published' | 'downloaded' | 'views' | 'likes' | 'duration' | 'mediasize'; -export type SortOrderType = 'asc' | 'desc'; export type ViewLayoutType = 'grid' | 'list'; const Home = () => { diff --git a/frontend/src/pages/SettingsVideos.tsx b/frontend/src/pages/SettingsVideos.tsx new file mode 100644 index 00000000..ab654fef --- /dev/null +++ b/frontend/src/pages/SettingsVideos.tsx @@ -0,0 +1,214 @@ +import { useEffect, useState } from 'react'; +import SettingsNavigation from '../components/SettingsNavigation'; +import { ApiResponseType } from '../functions/APIClient'; +import loadVideoListByFilter, { + SortByExpandedEnum, + SortByType, + SortOrderEnum, + SortOrderType, + VideoListByFilterResponseType, + WatchTypes, + WatchTypesEnum, +} from '../api/loader/loadVideoListByPage'; +import { Link, useOutletContext } from 'react-router-dom'; +import { OutletContextType } from './Base'; +import Pagination from '../components/Pagination'; +import humanFileSize from '../functions/humanFileSize'; +import { useUserConfigStore } from '../stores/UserConfigStore'; +import { FileSizeUnits } from '../api/actions/updateUserConfig'; +import Routes from '../configuration/routes/RouteList'; +import SortArrow from '../components/SortArrow'; + +const SettingsVideos = () => { + const { userConfig } = useUserConfigStore(); + const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType; + + const [refreshVideoList, setRefreshVideoList] = useState(false); + const [watchedState, setWatchedState] = useState('unwatched'); + const [sortBy, setSortBy] = useState('mediasize'); + const [sortOrder, setSortOrder] = useState('desc'); + + const [videoResponse, setVideoReponse] = + useState>(); + + const { data: videoResponseData } = videoResponse ?? {}; + + useEffect(() => { + (async () => { + const videos = await loadVideoListByFilter({ + page: currentPage, + watch: watchedState, + sort: sortBy, + order: sortOrder, + }); + + setVideoReponse(videos); + + setRefreshVideoList(false); + })(); + }, [refreshVideoList, currentPage, watchedState, sortBy, sortOrder]); + + const videoList = videoResponseData?.data; + const pagination = videoResponseData?.paginate; + const hasVideos = videoResponseData?.data?.length !== 0; + + const useSiUnits = userConfig.file_size_unit === FileSizeUnits.Metric; + + const toggleSortOrder = () => { + if (sortOrder === 'asc') { + setSortOrder('desc'); + } else { + setSortOrder('asc'); + } + }; + + const onHeaderClicked = (header: SortByType) => { + if (sortBy === header) { + toggleSortOrder(); + } else { + setSortBy(header); + } + }; + + return ( + <> + TA | Videos +
+ + +
+

Video details

+
+ +
+

+ Show watched:{' '} + +
+ Sort by: + + +

+ {!hasVideos &&

No videos found

} + + {hasVideos && ( + + + {videoList?.map(({ youtube_id, title, channel, vid_type, media_size, streams }) => { + const [videoStream, audioStream] = streams; + + return ( + + + + + + + + + + + + + ); + })} + + + + + + + + + + + + + + + + +
+ {channel.channel_name} + + {title} + {vid_type}{videoStream.width}{videoStream.height}{humanFileSize(media_size, useSiUnits)}{videoStream.codec}{humanFileSize(videoStream.bitrate, useSiUnits)}{audioStream.codec}{humanFileSize(audioStream.bitrate, useSiUnits)}
Channel nameVideo titleType + { + onHeaderClicked('width'); + }} + > +
+ + Width +
+
+
+ { + onHeaderClicked('height'); + }} + > + + Height + + + { + onHeaderClicked('mediasize'); + }} + > + + Media size + + Video codecVideo bitrateAudio codecAudio bitrate
+ )} +
+ + {pagination && ( +
+ +
+ )} +
+ + ); +}; + +export default SettingsVideos; diff --git a/frontend/src/style.css b/frontend/src/style.css index f5301d5b..5e5a4ac6 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -133,6 +133,11 @@ button:hover { color: var(--main-bg); } +.video-details table td, +.video-details table th { + padding: 5px; +} + .button-box { padding: 5px 2px; display: inline-flex;