diff --git a/backend/user/serializers.py b/backend/user/serializers.py index 3c89d5cb..be617437 100644 --- a/backend/user/serializers.py +++ b/backend/user/serializers.py @@ -31,7 +31,9 @@ class UserMeConfigSerializer(serializers.Serializer): page_size = serializers.IntegerField() sort_by = serializers.ChoiceField(choices=SortEnum.names()) sort_order = serializers.ChoiceField(choices=OrderEnum.values()) - view_style_home = serializers.ChoiceField(choices=["grid", "list"]) + view_style_home = serializers.ChoiceField( + choices=["grid", "list", "table"] + ) view_style_channel = serializers.ChoiceField(choices=["grid", "list"]) view_style_downloads = serializers.ChoiceField(choices=["grid", "list"]) view_style_playlist = serializers.ChoiceField(choices=["grid", "list"]) 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/public/img/icon-tableview.svg b/frontend/public/img/icon-tableview.svg new file mode 100644 index 00000000..fe81095c --- /dev/null +++ b/frontend/public/img/icon-tableview.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/api/actions/updateUserConfig.ts b/frontend/src/api/actions/updateUserConfig.ts index ac806e20..9d2aac7a 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 { ViewStylesType } from '../../configuration/constants/ViewStyle'; import APIClient from '../../functions/APIClient'; +import { SortByType, SortOrderType } from '../loader/loadVideoListByPage'; export type ColourVariants = | 'dark.css' @@ -26,10 +27,10 @@ export type UserConfigType = { page_size: number; sort_by: SortByType; sort_order: SortOrderType; - view_style_home: ViewLayoutType; - view_style_channel: ViewLayoutType; - view_style_downloads: ViewLayoutType; - view_style_playlist: ViewLayoutType; + view_style_home: ViewStylesType; + view_style_channel: ViewStylesType; + view_style_downloads: ViewStylesType; + view_style_playlist: ViewStylesType; grid_items: number; hide_watched: boolean; file_size_unit: 'binary' | 'metric'; diff --git a/frontend/src/api/loader/loadVideoListByPage.ts b/frontend/src/api/loader/loadVideoListByPage.ts index 06066f77..b7a4af93 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,41 @@ 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', + 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/ChannelList.tsx b/frontend/src/components/ChannelList.tsx index 9bb42e50..75319fe5 100644 --- a/frontend/src/components/ChannelList.tsx +++ b/frontend/src/components/ChannelList.tsx @@ -16,7 +16,7 @@ type ChannelListProps = { const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => { const { userConfig } = useUserConfigStore(); - const viewLayout = userConfig.view_style_channel; + const viewStyle = userConfig.view_style_channel; if (!channelList || channelList.length === 0) { return

No channels found.

; @@ -26,8 +26,8 @@ const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => { <> {channelList.map(channel => { return ( -
-
+
+
{ />
-
+
diff --git a/frontend/src/components/DownloadListItem.tsx b/frontend/src/components/DownloadListItem.tsx index cd9ac14a..bb0a82e3 100644 --- a/frontend/src/components/DownloadListItem.tsx +++ b/frontend/src/components/DownloadListItem.tsx @@ -16,14 +16,14 @@ type DownloadListItemProps = { const DownloadListItem = ({ download, setRefresh }: DownloadListItemProps) => { const { userConfig } = useUserConfigStore(); - const view = userConfig.view_style_downloads; + const viewStyle = userConfig.view_style_downloads; const showIgnored = userConfig.show_ignored_only; const [hideDownload, setHideDownload] = useState(false); return ( -
-
+
+
@@ -39,7 +39,7 @@ const DownloadListItem = ({ download, setRefresh }: DownloadListItemProps) => {
-
+
{download.channel_indexed && ( {download.channel_name} diff --git a/frontend/src/components/Filterbar.tsx b/frontend/src/components/Filterbar.tsx index 57394b19..8219bea1 100644 --- a/frontend/src/components/Filterbar.tsx +++ b/frontend/src/components/Filterbar.tsx @@ -1,24 +1,45 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import iconSort from '/img/icon-sort.svg'; 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 iconTableView from '/img/icon-tableview.svg'; import { useUserConfigStore } from '../stores/UserConfigStore'; -import { ViewStyles } from '../configuration/constants/ViewStyle'; +import { ViewStyleNamesType, ViewStylesEnum } from '../configuration/constants/ViewStyle'; import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig'; +import { + SortByEnum, + SortByType, + SortOrderEnum, + SortOrderType, +} from '../api/loader/loadVideoListByPage'; type FilterbarProps = { hideToggleText: string; - viewStyleName: string; + viewStyle: ViewStyleNamesType; showSort?: boolean; }; -const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: FilterbarProps) => { +const Filterbar = ({ hideToggleText, viewStyle, showSort = true }: FilterbarProps) => { const { userConfig, setUserConfig } = useUserConfigStore(); + const [showHidden, setShowHidden] = useState(false); - const isGridView = userConfig.view_style_home === ViewStyles.grid; + + const currentViewStyle = userConfig[viewStyle]; + const isGridView = currentViewStyle === ViewStylesEnum.Grid; + + useEffect(() => { + if (!showSort) { + return; + } + + if (currentViewStyle === ViewStylesEnum.Table) { + setShowHidden(true); + } else { + setShowHidden(false); + } + }, [currentViewStyle, showSort]); const handleUserConfigUpdate = async (config: Partial) => { const updatedUserConfig = await updateUserConfig(config); @@ -55,7 +76,7 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
- {showHidden && showSort && ( + {showHidden && (
Sort by: @@ -67,12 +88,9 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar handleUserConfigUpdate({ sort_by: event.target.value as SortByType }); }} > - - - - - - + {Object.entries(SortByEnum).map(([key, value]) => { + return ; + })}
)}
- {setShowHidden && showSort && ( + {showSort && ( sort-icon { - setShowHidden?.(!showHidden); + setShowHidden(!showHidden); }} id="animate-icon" /> @@ -125,17 +144,24 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar { - handleUserConfigUpdate({ [viewStyleName]: 'grid' }); + handleUserConfigUpdate({ [viewStyle]: ViewStylesEnum.Grid }); }} alt="grid view" /> { - handleUserConfigUpdate({ [viewStyleName]: 'list' }); + handleUserConfigUpdate({ [viewStyle]: ViewStylesEnum.List }); }} alt="list view" /> + { + handleUserConfigUpdate({ [viewStyle]: ViewStylesEnum.Table }); + }} + alt="table view" + />
); diff --git a/frontend/src/components/PlaylistList.tsx b/frontend/src/components/PlaylistList.tsx index 182499db..555d1e83 100644 --- a/frontend/src/components/PlaylistList.tsx +++ b/frontend/src/components/PlaylistList.tsx @@ -14,7 +14,7 @@ type PlaylistListProps = { const PlaylistList = ({ playlistList, setRefresh }: PlaylistListProps) => { const { userConfig } = useUserConfigStore(); - const viewLayout = userConfig.view_style_playlist; + const viewStyle = userConfig.view_style_playlist; if (!playlistList || playlistList.length === 0) { return

No playlists found.

; @@ -24,7 +24,7 @@ const PlaylistList = ({ playlistList, setRefresh }: PlaylistListProps) => { <> {playlistList.map((playlist: PlaylistType) => { return ( -
+
{ />
-
+
{playlist.playlist_type != 'custom' && (

{playlist.playlist_channel}

diff --git a/frontend/src/components/VideoList.tsx b/frontend/src/components/VideoList.tsx index 440ed6bc..bff8217c 100644 --- a/frontend/src/components/VideoList.tsx +++ b/frontend/src/components/VideoList.tsx @@ -1,9 +1,11 @@ -import { VideoType, ViewLayoutType } from '../pages/Home'; +import { ViewStylesEnum, ViewStylesType } from '../configuration/constants/ViewStyle'; +import { VideoType } from '../pages/Home'; import VideoListItem from './VideoListItem'; +import VideoListItemTable from './VideoListItemTable'; type VideoListProps = { videoList: VideoType[] | undefined; - viewLayout: ViewLayoutType; + viewStyle: ViewStylesType; playlistId?: string; showReorderButton?: boolean; refreshVideoList: (refresh: boolean) => void; @@ -11,7 +13,7 @@ type VideoListProps = { const VideoList = ({ videoList, - viewLayout, + viewStyle, playlistId, showReorderButton = false, refreshVideoList, @@ -20,6 +22,10 @@ const VideoList = ({ return

No videos found.

; } + if (viewStyle === ViewStylesEnum.Table) { + return ; + } + return ( <> {videoList.map(video => { @@ -27,7 +33,7 @@ const VideoList = ({ void; @@ -22,7 +23,7 @@ type VideoListItemProps = { const VideoListItem = ({ video, - viewLayout, + viewStyle, playlistId, showReorderButton = false, refreshVideoList, @@ -36,7 +37,7 @@ const VideoListItem = ({ } return ( -
+
{ setSearchParams(params => { @@ -46,7 +47,7 @@ const VideoListItem = ({ }); }} > -
+
@@ -72,7 +73,7 @@ const VideoListItem = ({
-
+
{ + const { userConfig } = useUserConfigStore(); + + const useSiUnits = userConfig.file_size_unit === FileSizeUnits.Metric; + + return ( +
+ + + + + + + + + + + + + + + + + {videoList?.map(({ youtube_id, title, channel, vid_type, media_size, streams }) => { + const [videoStream, audioStream] = streams; + + return ( + + + + + + + + + + + + ); + })} + +
ChannelTitleTypeResolutionMedia sizeVideo codecVideo bitrateAudio codecAudio bitrate
+ {channel.channel_name} + + {title} + {vid_type}{`${videoStream.width}x${videoStream.height}`}{humanFileSize(media_size, useSiUnits)}{videoStream.codec}{humanFileSize(videoStream.bitrate, useSiUnits)}{audioStream.codec}{humanFileSize(audioStream.bitrate, useSiUnits)}
+
+ ); +}; + +export default VideoListItemTable; diff --git a/frontend/src/configuration/constants/ViewStyle.ts b/frontend/src/configuration/constants/ViewStyle.ts index cafe55cc..5390a310 100644 --- a/frontend/src/configuration/constants/ViewStyle.ts +++ b/frontend/src/configuration/constants/ViewStyle.ts @@ -1,11 +1,18 @@ +export type ViewStyleNamesType = + | 'view_style_home' + | 'view_style_channel' + | 'view_style_downloads' + | 'view_style_playlist'; export const ViewStyleNames = { - home: 'view_style_home', - channel: 'view_style_channel', - downloads: 'view_style_downloads', - playlist: 'view_style_playlist', + Home: 'view_style_home', + Channel: 'view_style_channel', + Downloads: 'view_style_downloads', + Playlist: 'view_style_playlist', }; -export const ViewStyles = { - grid: 'grid', - list: 'list', +export type ViewStylesType = 'grid' | 'list' | 'table'; +export const ViewStylesEnum = { + Grid: 'grid', + List: 'list', + Table: 'table', }; diff --git a/frontend/src/pages/ChannelPlaylist.tsx b/frontend/src/pages/ChannelPlaylist.tsx index c3730e56..14b2912e 100644 --- a/frontend/src/pages/ChannelPlaylist.tsx +++ b/frontend/src/pages/ChannelPlaylist.tsx @@ -11,6 +11,7 @@ import iconListView from '/img/icon-listview.svg'; import { useUserConfigStore } from '../stores/UserConfigStore'; import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig'; import { ApiResponseType } from '../functions/APIClient'; +import { ViewStylesType, ViewStylesEnum } from '../configuration/constants/ViewStyle'; const ChannelPlaylist = () => { const { channelId } = useParams(); @@ -27,7 +28,7 @@ const ChannelPlaylist = () => { const playlistList = playlistsResponseData?.data; const pagination = playlistsResponseData?.paginate; - const view = userConfig.view_style_playlist; + const viewStyle = userConfig.view_style_playlist; const showSubedOnly = userConfig.show_subed_only; const handleUserConfigUpdate = async (config: Partial) => { @@ -87,14 +88,18 @@ const ChannelPlaylist = () => { { - handleUserConfigUpdate({ view_style_playlist: 'grid' }); + handleUserConfigUpdate({ + view_style_playlist: ViewStylesEnum.Grid as ViewStylesType, + }); }} alt="grid view" /> { - handleUserConfigUpdate({ view_style_playlist: 'list' }); + handleUserConfigUpdate({ + view_style_playlist: ViewStylesEnum.List as ViewStylesType, + }); }} alt="list view" /> @@ -103,7 +108,7 @@ const ChannelPlaylist = () => {
-
+
diff --git a/frontend/src/pages/ChannelVideo.tsx b/frontend/src/pages/ChannelVideo.tsx index a6f3f4ed..e5a123c5 100644 --- a/frontend/src/pages/ChannelVideo.tsx +++ b/frontend/src/pages/ChannelVideo.tsx @@ -5,7 +5,11 @@ import VideoList from '../components/VideoList'; import Routes from '../configuration/routes/RouteList'; import Pagination from '../components/Pagination'; import Filterbar from '../components/Filterbar'; -import { ViewStyleNames, ViewStyles } from '../configuration/constants/ViewStyle'; +import { + ViewStyleNames, + ViewStyleNamesType, + ViewStylesEnum, +} from '../configuration/constants/ViewStyle'; import ChannelOverview from '../components/ChannelOverview'; import loadChannelById, { ChannelResponseType } from '../api/loader/loadChannelById'; import ScrollToTopOnNavigate from '../components/ScrollToTop'; @@ -15,6 +19,8 @@ import Button from '../components/Button'; import loadVideoListByFilter, { VideoListByFilterResponseType, VideoTypes, + WatchTypes, + WatchTypesEnum, } from '../api/loader/loadVideoListByPage'; import loadChannelAggs, { ChannelAggsType } from '../api/loader/loadChannelAggs'; import humanFileSize from '../functions/humanFileSize'; @@ -56,8 +62,8 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => { const hasVideos = videoResponseData?.data?.length !== 0; const useSiUnits = userConfig.file_size_unit === FileSizeUnits.Metric; - const view = userConfig.view_style_home; - const isGridView = view === ViewStyles.grid; + const viewStyle = userConfig.view_style_home; + const isGridView = viewStyle === ViewStylesEnum.Grid; const gridView = isGridView ? `boxed-${userConfig.grid_items}` : ''; const gridViewGrid = isGridView ? `grid-${userConfig.grid_items}` : ''; @@ -67,7 +73,7 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => { const videos = await loadVideoListByFilter({ channel: channelId, page: currentPage, - watch: userConfig.hide_watched ? 'unwatched' : undefined, + watch: userConfig.hide_watched ? (WatchTypesEnum.Unwatched as WatchTypes) : undefined, sort: userConfig.sort_by, order: userConfig.sort_order, type: videoType, @@ -160,13 +166,16 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
- +
-
+
{!hasVideos && ( <>

No videos found...

@@ -177,7 +186,7 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => { )} - +
{pagination && ( diff --git a/frontend/src/pages/Channels.tsx b/frontend/src/pages/Channels.tsx index d71c2743..40e6dc2a 100644 --- a/frontend/src/pages/Channels.tsx +++ b/frontend/src/pages/Channels.tsx @@ -15,6 +15,7 @@ import useIsAdmin from '../functions/useIsAdmin'; import { useUserConfigStore } from '../stores/UserConfigStore'; import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig'; import { ApiResponseType } from '../functions/APIClient'; +import { ViewStylesEnum, ViewStylesType } from '../configuration/constants/ViewStyle'; type ChannelOverwritesType = { download_format: string | null; @@ -172,16 +173,19 @@ const Channels = () => { { - handleUserConfigUpdate({ view_style_channel: 'grid' }); + handleUserConfigUpdate({ + view_style_channel: ViewStylesEnum.Grid as ViewStylesType, + }); }} data-origin="channel" - data-value="grid" alt="grid view" /> { - handleUserConfigUpdate({ view_style_channel: 'list' }); + handleUserConfigUpdate({ + view_style_channel: ViewStylesEnum.List as ViewStylesType, + }); }} data-origin="channel" data-value="list" diff --git a/frontend/src/pages/Download.tsx b/frontend/src/pages/Download.tsx index 1e31dd2b..f08de4fa 100644 --- a/frontend/src/pages/Download.tsx +++ b/frontend/src/pages/Download.tsx @@ -10,7 +10,7 @@ import { ConfigType } from './Home'; import loadDownloadQueue from '../api/loader/loadDownloadQueue'; import { OutletContextType } from './Base'; import Pagination, { PaginationType } from '../components/Pagination'; -import { ViewStyles } from '../configuration/constants/ViewStyle'; +import { ViewStylesEnum, ViewStylesType } from '../configuration/constants/ViewStyle'; import updateDownloadQueue from '../api/actions/updateDownloadQueue'; import updateTaskByName from '../api/actions/updateTaskByName'; import Notifications from '../components/Notifications'; @@ -81,11 +81,11 @@ const Download = () => { ? downloadResponseData?.data[0].channel_name : ''; - const view = userConfig.view_style_downloads; + const viewStyle = userConfig.view_style_downloads; const gridItems = userConfig.grid_items; const showIgnored = ignoredOnlyParam !== null ? ignoredOnlyParam === 'true' : userConfig.show_ignored_only; - const isGridView = view === ViewStyles.grid; + const isGridView = viewStyle === ViewStylesEnum.Grid; const gridView = isGridView ? `boxed-${gridItems}` : ''; const gridViewGrid = isGridView ? `grid-${gridItems}` : ''; @@ -315,14 +315,18 @@ const Download = () => { { - handleUserConfigUpdate({ view_style_downloads: 'grid' }); + handleUserConfigUpdate({ + view_style_downloads: ViewStylesEnum.Grid as ViewStylesType, + }); }} alt="grid view" /> { - handleUserConfigUpdate({ view_style_downloads: 'list' }); + handleUserConfigUpdate({ + view_style_downloads: ViewStylesEnum.List as ViewStylesType, + }); }} alt="list view" /> @@ -341,7 +345,7 @@ const Download = () => {
-
+
{downloadList && downloadList?.map(download => { return ( diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 45a5b49b..67fb7fac 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -4,12 +4,18 @@ import Routes from '../configuration/routes/RouteList'; import Pagination from '../components/Pagination'; import loadVideoListByFilter, { VideoListByFilterResponseType, + WatchTypes, + WatchTypesEnum, } from '../api/loader/loadVideoListByPage'; import VideoList from '../components/VideoList'; import { ChannelType } from './Channels'; import { OutletContextType } from './Base'; import Filterbar from '../components/Filterbar'; -import { ViewStyleNames, ViewStyles } from '../configuration/constants/ViewStyle'; +import { + ViewStyleNames, + ViewStyleNamesType, + ViewStylesEnum, +} from '../configuration/constants/ViewStyle'; import ScrollToTopOnNavigate from '../components/ScrollToTop'; import EmbeddableVideoPlayer from '../components/EmbeddableVideoPlayer'; import { SponsorBlockType } from './Video'; @@ -99,10 +105,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 = () => { const { userConfig } = useUserConfigStore(); const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType; @@ -125,15 +127,16 @@ const Home = () => { const hasVideos = videoResponseData?.data?.length !== 0; - const isGridView = userConfig.view_style_home === ViewStyles.grid; + const isGridView = userConfig.view_style_home === ViewStylesEnum.Grid; const gridView = isGridView ? `boxed-${userConfig.grid_items}` : ''; const gridViewGrid = isGridView ? `grid-${userConfig.grid_items}` : ''; + const isTableView = userConfig.view_style_home === ViewStylesEnum.Table; useEffect(() => { (async () => { const videos = await loadVideoListByFilter({ page: currentPage, - watch: userConfig.hide_watched ? 'unwatched' : undefined, + watch: userConfig.hide_watched ? (WatchTypesEnum.Unwatched as WatchTypes) : undefined, sort: userConfig.sort_by, order: userConfig.sort_order, }); @@ -168,7 +171,7 @@ const Home = () => {
- {continueVideos && continueVideos.length > 0 && ( + {continueVideos && continueVideos.length > 0 && !isTableView && ( <>

Continue Watching

@@ -176,7 +179,7 @@ const Home = () => {
@@ -187,7 +190,10 @@ const Home = () => {

Recent Videos

- +
@@ -206,7 +212,7 @@ const Home = () => { {hasVideos && ( )} diff --git a/frontend/src/pages/Playlist.tsx b/frontend/src/pages/Playlist.tsx index e30112d6..37657f64 100644 --- a/frontend/src/pages/Playlist.tsx +++ b/frontend/src/pages/Playlist.tsx @@ -9,7 +9,11 @@ import VideoList from '../components/VideoList'; import Pagination, { PaginationType } from '../components/Pagination'; import ChannelOverview from '../components/ChannelOverview'; import Linkify from '../components/Linkify'; -import { ViewStyleNames, ViewStyles } from '../configuration/constants/ViewStyle'; +import { + ViewStyleNames, + ViewStyleNamesType, + ViewStylesEnum, +} from '../configuration/constants/ViewStyle'; import updatePlaylistSubscription from '../api/actions/updatePlaylistSubscription'; import deletePlaylist from '../api/actions/deletePlaylist'; import Routes from '../configuration/routes/RouteList'; @@ -19,7 +23,10 @@ import updateWatchedState from '../api/actions/updateWatchedState'; import ScrollToTopOnNavigate from '../components/ScrollToTop'; import EmbeddableVideoPlayer from '../components/EmbeddableVideoPlayer'; import Button from '../components/Button'; -import loadVideoListByFilter from '../api/loader/loadVideoListByPage'; +import loadVideoListByFilter, { + WatchTypes, + WatchTypesEnum, +} from '../api/loader/loadVideoListByPage'; import useIsAdmin from '../functions/useIsAdmin'; import { useUserConfigStore } from '../stores/UserConfigStore'; import { ApiResponseType } from '../functions/APIClient'; @@ -62,10 +69,10 @@ const Playlist = () => { const videoArchivedCount = Number(palylistEntries?.filter(video => video.downloaded).length); const videoInPlaylistCount = Number(palylistEntries?.length); - const view = userConfig.view_style_home; + const viewStyle = userConfig.view_style_home; // its a list of videos, so view_style_home const gridItems = userConfig.grid_items; const hideWatched = userConfig.hide_watched; - const isGridView = view === ViewStyles.grid; + const isGridView = viewStyle === ViewStylesEnum.Grid; const gridView = isGridView ? `boxed-${gridItems}` : ''; const gridViewGrid = isGridView ? `grid-${gridItems}` : ''; @@ -75,7 +82,7 @@ const Playlist = () => { const video = await loadVideoListByFilter({ playlist: playlistId, page: currentPage, - watch: hideWatched ? 'unwatched' : undefined, + watch: hideWatched ? (WatchTypesEnum.Unwatched as WatchTypes) : undefined, }); setPlaylistResponse(playlist); @@ -304,7 +311,7 @@ const Playlist = () => {
@@ -312,7 +319,7 @@ const Playlist = () => {
-
+
{videoInPlaylistCount === 0 && ( <>

No videos found...

@@ -334,7 +341,7 @@ const Playlist = () => { {videoInPlaylistCount !== 0 && ( { const { userConfig, setUserConfig } = useUserConfigStore(); @@ -39,7 +40,7 @@ const Playlists = () => { const hasPlaylists = playlistResponseData?.data?.length !== 0; - const view = userConfig.view_style_playlist; + const viewStyle = userConfig.view_style_playlist; const showSubedOnly = userConfig.show_subed_only; useEffect(() => { @@ -177,21 +178,25 @@ const Playlists = () => { { - handleUserConfigUpdate({ view_style_playlist: 'grid' }); + handleUserConfigUpdate({ + view_style_playlist: ViewStylesEnum.Grid as ViewStylesType, + }); }} alt="grid view" /> { - handleUserConfigUpdate({ view_style_playlist: 'list' }); + handleUserConfigUpdate({ + view_style_playlist: ViewStylesEnum.List as ViewStylesType, + }); }} alt="list view" />
-
+
{!hasPlaylists &&

No playlists found...

} {hasPlaylists && } diff --git a/frontend/src/pages/Search.tsx b/frontend/src/pages/Search.tsx index b2718d79..6af56508 100644 --- a/frontend/src/pages/Search.tsx +++ b/frontend/src/pages/Search.tsx @@ -5,7 +5,7 @@ import VideoList from '../components/VideoList'; import ChannelList from '../components/ChannelList'; import PlaylistList from '../components/PlaylistList'; import SubtitleList from '../components/SubtitleList'; -import { ViewStyles } from '../configuration/constants/ViewStyle'; +import { ViewStylesEnum } from '../configuration/constants/ViewStyle'; import EmbeddableVideoPlayer from '../components/EmbeddableVideoPlayer'; import SearchExampleQueries from '../components/SearchExampleQueries'; import { useUserConfigStore } from '../stores/UserConfigStore'; @@ -61,7 +61,7 @@ const Search = () => { const isPlaylistQuery = queryType === 'playlist' || isSimpleQuery; const isFullTextQuery = queryType === 'full' || isSimpleQuery; - const isGridView = viewVideos === ViewStyles.grid; + const isGridView = viewVideos === ViewStylesEnum.Grid; const gridView = isGridView ? `boxed-${gridItems}` : ''; const gridViewGrid = isGridView ? `grid-${gridItems}` : ''; @@ -125,7 +125,7 @@ const Search = () => {
diff --git a/frontend/src/pages/Video.tsx b/frontend/src/pages/Video.tsx index 87031962..785a6e23 100644 --- a/frontend/src/pages/Video.tsx +++ b/frontend/src/pages/Video.tsx @@ -44,6 +44,7 @@ import { useUserConfigStore } from '../stores/UserConfigStore'; import NotFound from './NotFound'; import { ApiResponseType } from '../functions/APIClient'; import VideoThumbnail from '../components/VideoThumbail'; +import { ViewStylesEnum, ViewStylesType } from '../configuration/constants/ViewStyle'; const isInPlaylist = (videoId: string, playlist: PlaylistType) => { return playlist.playlist_entries.some(entry => { @@ -581,7 +582,7 @@ const Video = () => {
diff --git a/frontend/src/stores/UserConfigStore.ts b/frontend/src/stores/UserConfigStore.ts index 20b2a417..077715c5 100644 --- a/frontend/src/stores/UserConfigStore.ts +++ b/frontend/src/stores/UserConfigStore.ts @@ -1,5 +1,7 @@ import { create } from 'zustand'; import { UserConfigType } from '../api/actions/updateUserConfig'; +import { ViewStylesEnum, ViewStylesType } from '../configuration/constants/ViewStyle'; +import { SortOrderEnum, SortOrderType } from '../api/loader/loadVideoListByPage'; interface UserConfigState { userConfig: UserConfigType; @@ -11,11 +13,11 @@ export const useUserConfigStore = create(set => ({ stylesheet: 'dark.css', page_size: 12, sort_by: 'published', - sort_order: 'desc', - view_style_home: 'grid', - view_style_channel: 'list', - view_style_downloads: 'list', - view_style_playlist: 'grid', + sort_order: SortOrderEnum.Desc as SortOrderType, + view_style_home: ViewStylesEnum.Grid as ViewStylesType, + view_style_channel: ViewStylesEnum.List as ViewStylesType, + view_style_downloads: ViewStylesEnum.List as ViewStylesType, + view_style_playlist: ViewStylesEnum.Grid as ViewStylesType, grid_items: 3, hide_watched: false, file_size_unit: 'binary', diff --git a/frontend/src/style.css b/frontend/src/style.css index f5301d5b..13a70796 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -133,6 +133,40 @@ button:hover { color: var(--main-bg); } +.video-item.table { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.video-item.table table { + min-width: 800px; + width: 100%; + border-collapse: collapse; + position: relative; +} + +.video-item.table th, +.video-item.table td { + padding: 8px 12px; + text-align: left; + white-space: nowrap; +} + +.video-item.table th { + font-weight: 600; + border-bottom: 1px solid #ddd; +} + +.video-item.table td { + border-bottom: 1px solid #eee; +} + +.video-item.table td.no-nowrap { + white-space: normal; + word-break: keep-all; + min-width: 100px; +} + .button-box { padding: 5px 2px; display: inline-flex;