Add video details view (#956)
* Add Video details page to settings * Add option A * Refac remove option A * Refac viewStyleType and viewStyleEnum * Add viewStyle Table * Refac remove unused code * Refac show resolution in one cell
This commit is contained in:
parent
436a641746
commit
aa08701049
@ -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"])
|
||||
|
@ -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]:
|
||||
|
7
frontend/public/img/icon-tableview.svg
Normal file
7
frontend/public/img/icon-tableview.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" width="960pt" height="960pt" viewBox="0 0 960 960" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(0,960)scale(.075,.075)">
|
||||
<path id="path1" d="M 740 -10481 c -77 24 -152 94 -170 160 -6 23 -10 376 -10 946 l 0 910 24 50 c 28 59 66 97 121 122 38 17 268 18 5620 21 3923 1 5595 -1 5632 -8 70 -15 124 -56 157 -121 l 27 -54 0 -920 c 0 -637 -4 -932 -11 -958 -17 -58 -84 -122 -150 -141 -49 -15 -544 -16 -5635 -15 -3069 0 -5591 4 -5605 8 z M 9615 -7400 c -11 4 -31 20 -45 35 l -25 27 -3 889 c -3 978 -5 935 57 972 27 16 117 17 1253 17 1321 0 1252 3 1293 -54 20 -27 20 -43 20 -919 0 -860 -1 -893 -19 -920 -41 -60 37 -57 -1293 -56 -670 0 -1227 4 -1238 9 z M 3658 -7384 c -61 32 -58 -10 -58 959 0 973 -3 928 60 960 25 13 178 15 1250 15 1072 0 1225 -2 1250 -15 63 -32 60 13 60 -960 0 -973 3 -928 -60 -960 -25 -13 -178 -15 -1252 -15 -1062 1 -1227 3 -1250 16 z M 6551 -7374 c -17 14 -35 42 -41 62 -6 24 -10 339 -10 892 0 826 1 856 20 898 35 78 -60 73 1315 70 l 1225 -3 32 -33 33 -32 3 -895 c 2 -788 0 -899 -13 -925 -33 -64 48 -60 -1304 -60 l -1229 0 -31 26 z M 616 -7369 c -59 46 -56 0 -56 951 0 976 -4 924 70 960 33 17 113 18 1250 18 1187 0 1216 -1 1247 -20 66 -40 63 12 63 -955 0 -791 -2 -880 -16 -911 -33 -68 54 -64 -1302 -64 l -1229 0 -27 21 z M 9615 -4530 c -11 4 -31 20 -45 35 l -25 27 -3 901 c -2 891 -2 902 18 935 40 65 -32 62 1296 62 1335 0 1255 4 1292 -64 16 -29 17 -101 17 -921 0 -832 -1 -892 -18 -922 -36 -67 50 -63 -1294 -62 -670 0 -1227 4 -1238 9 z M 3664 -4514 c -68 33 -64 -26 -64 973 l 0 901 34 38 34 37 1242 0 1242 0 34 -37 34 -38 0 -900 c 0 -989 3 -942 -60 -975 -25 -13 -178 -15 -1247 -15 -1064 0 -1222 2 -1249 16 z M 6551 -4504 c -17 14 -35 42 -41 62 -14 51 -14 1733 0 1784 6 20 24 48 41 62 l 31 26 1237 0 c 1360 0 1263 5 1296 -60 23 -44 23 -1796 0 -1840 -33 -64 47 -60 -1304 -60 l -1229 0 -31 26 z M 615 -4498 c -57 44 -55 13 -55 963 0 844 1 877 19 913 11 21 31 43 46 50 20 9 323 12 1260 12 l 1233 0 31 -30 c 17 -17 33 -45 36 -63 3 -18 4 -429 3 -915 l -3 -884 -37 -34 -38 -34 -1234 0 -1233 0 -28 22 z " />
|
||||
</g>
|
||||
</svg>
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -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 <p>No channels found.</p>;
|
||||
@ -26,8 +26,8 @@ const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => {
|
||||
<>
|
||||
{channelList.map(channel => {
|
||||
return (
|
||||
<div key={channel.channel_id} className={`channel-item ${viewLayout}`}>
|
||||
<div className={`channel-banner ${viewLayout}`}>
|
||||
<div key={channel.channel_id} className={`channel-item ${viewStyle}`}>
|
||||
<div className={`channel-banner ${viewStyle}`}>
|
||||
<Link to={Routes.Channel(channel.channel_id)}>
|
||||
<ChannelBanner
|
||||
channelId={channel.channel_id}
|
||||
@ -35,7 +35,7 @@ const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => {
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={`info-box info-box-2 ${viewLayout}`}>
|
||||
<div className={`info-box info-box-2 ${viewStyle}`}>
|
||||
<div className="info-box-item">
|
||||
<div className="round-img">
|
||||
<Link to={Routes.Channel(channel.channel_id)}>
|
||||
|
@ -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 (
|
||||
<div className={`video-item ${view}`} id={`dl-${download.youtube_id}`}>
|
||||
<div className={`video-thumb-wrap ${view}`}>
|
||||
<div className={`video-item ${viewStyle}`} id={`dl-${download.youtube_id}`}>
|
||||
<div className={`video-thumb-wrap ${viewStyle}`}>
|
||||
<div className="video-thumb">
|
||||
<VideoThumbnail videoThumbUrl={download.vid_thumb_url} />
|
||||
|
||||
@ -39,7 +39,7 @@ const DownloadListItem = ({ download, setRefresh }: DownloadListItemProps) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`video-desc ${view}`}>
|
||||
<div className={`video-desc ${viewStyle}`}>
|
||||
<div>
|
||||
{download.channel_indexed && (
|
||||
<Link to={Routes.Channel(download.channel_id)}>{download.channel_name}</Link>
|
||||
|
@ -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<UserConfigType>) => {
|
||||
const updatedUserConfig = await updateUserConfig(config);
|
||||
@ -55,7 +76,7 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showHidden && showSort && (
|
||||
{showHidden && (
|
||||
<div className="sort">
|
||||
<div id="form">
|
||||
<span>Sort by:</span>
|
||||
@ -67,12 +88,9 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
|
||||
handleUserConfigUpdate({ sort_by: event.target.value as SortByType });
|
||||
}}
|
||||
>
|
||||
<option value="published">date published</option>
|
||||
<option value="downloaded">date downloaded</option>
|
||||
<option value="views">views</option>
|
||||
<option value="likes">likes</option>
|
||||
<option value="duration">duration</option>
|
||||
<option value="mediasize">media size</option>
|
||||
{Object.entries(SortByEnum).map(([key, value]) => {
|
||||
return <option value={value}>{key}</option>;
|
||||
})}
|
||||
</select>
|
||||
<select
|
||||
name="sort_order"
|
||||
@ -82,19 +100,20 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
|
||||
handleUserConfigUpdate({ sort_order: event.target.value as SortOrderType });
|
||||
}}
|
||||
>
|
||||
<option value="asc">asc</option>
|
||||
<option value="desc">desc</option>
|
||||
{Object.entries(SortOrderEnum).map(([key, value]) => {
|
||||
return <option value={value}>{key}</option>;
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="view-icons">
|
||||
{setShowHidden && showSort && (
|
||||
{showSort && (
|
||||
<img
|
||||
src={iconSort}
|
||||
alt="sort-icon"
|
||||
onClick={() => {
|
||||
setShowHidden?.(!showHidden);
|
||||
setShowHidden(!showHidden);
|
||||
}}
|
||||
id="animate-icon"
|
||||
/>
|
||||
@ -125,17 +144,24 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
|
||||
<img
|
||||
src={iconGridView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ [viewStyleName]: 'grid' });
|
||||
handleUserConfigUpdate({ [viewStyle]: ViewStylesEnum.Grid });
|
||||
}}
|
||||
alt="grid view"
|
||||
/>
|
||||
<img
|
||||
src={iconListView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ [viewStyleName]: 'list' });
|
||||
handleUserConfigUpdate({ [viewStyle]: ViewStylesEnum.List });
|
||||
}}
|
||||
alt="list view"
|
||||
/>
|
||||
<img
|
||||
src={iconTableView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ [viewStyle]: ViewStylesEnum.Table });
|
||||
}}
|
||||
alt="table view"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -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 <p>No playlists found.</p>;
|
||||
@ -24,7 +24,7 @@ const PlaylistList = ({ playlistList, setRefresh }: PlaylistListProps) => {
|
||||
<>
|
||||
{playlistList.map((playlist: PlaylistType) => {
|
||||
return (
|
||||
<div key={playlist.playlist_id} className={`playlist-item ${viewLayout}`}>
|
||||
<div key={playlist.playlist_id} className={`playlist-item ${viewStyle}`}>
|
||||
<div className="playlist-thumbnail">
|
||||
<Link to={Routes.Playlist(playlist.playlist_id)}>
|
||||
<PlaylistThumbnail
|
||||
@ -33,7 +33,7 @@ const PlaylistList = ({ playlistList, setRefresh }: PlaylistListProps) => {
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={`playlist-desc ${viewLayout}`}>
|
||||
<div className={`playlist-desc ${viewStyle}`}>
|
||||
{playlist.playlist_type != 'custom' && (
|
||||
<Link to={Routes.Channel(playlist.playlist_channel_id)}>
|
||||
<h3>{playlist.playlist_channel}</h3>
|
||||
|
@ -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 <p>No videos found.</p>;
|
||||
}
|
||||
|
||||
if (viewStyle === ViewStylesEnum.Table) {
|
||||
return <VideoListItemTable videoList={videoList} viewStyle={viewStyle} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{videoList.map(video => {
|
||||
@ -27,7 +33,7 @@ const VideoList = ({
|
||||
<VideoListItem
|
||||
key={video.youtube_id}
|
||||
video={video}
|
||||
viewLayout={viewLayout}
|
||||
viewStyle={viewStyle}
|
||||
playlistId={playlistId}
|
||||
showReorderButton={showReorderButton}
|
||||
refreshVideoList={refreshVideoList}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import Routes from '../configuration/routes/RouteList';
|
||||
import { VideoType, ViewLayoutType } from '../pages/Home';
|
||||
import { VideoType } from '../pages/Home';
|
||||
import iconPlay from '/img/icon-play.svg';
|
||||
import iconDotMenu from '/img/icon-dot-menu.svg';
|
||||
import iconClose from '/img/icon-close.svg';
|
||||
@ -11,10 +11,11 @@ import MoveVideoMenu from './MoveVideoMenu';
|
||||
import { useState } from 'react';
|
||||
import deleteVideoProgressById from '../api/actions/deleteVideoProgressById';
|
||||
import VideoThumbnail from './VideoThumbail';
|
||||
import { ViewStylesType } from '../configuration/constants/ViewStyle';
|
||||
|
||||
type VideoListItemProps = {
|
||||
video: VideoType;
|
||||
viewLayout: ViewLayoutType;
|
||||
viewStyle: ViewStylesType;
|
||||
playlistId?: string;
|
||||
showReorderButton?: boolean;
|
||||
refreshVideoList: (refresh: boolean) => void;
|
||||
@ -22,7 +23,7 @@ type VideoListItemProps = {
|
||||
|
||||
const VideoListItem = ({
|
||||
video,
|
||||
viewLayout,
|
||||
viewStyle,
|
||||
playlistId,
|
||||
showReorderButton = false,
|
||||
refreshVideoList,
|
||||
@ -36,7 +37,7 @@ const VideoListItem = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`video-item ${viewLayout}`}>
|
||||
<div className={`video-item ${viewStyle}`}>
|
||||
<a
|
||||
onClick={() => {
|
||||
setSearchParams(params => {
|
||||
@ -46,7 +47,7 @@ const VideoListItem = ({
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className={`video-thumb-wrap ${viewLayout}`}>
|
||||
<div className={`video-thumb-wrap ${viewStyle}`}>
|
||||
<div className="video-thumb">
|
||||
<VideoThumbnail videoThumbUrl={video.vid_thumb_url} />
|
||||
|
||||
@ -72,7 +73,7 @@ const VideoListItem = ({
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div className={`video-desc ${viewLayout}`}>
|
||||
<div className={`video-desc ${viewStyle}`}>
|
||||
<div className="video-desc-player" id={`video-info-${video.youtube_id}`}>
|
||||
<WatchedCheckBox
|
||||
watched={video.player.watched}
|
||||
|
64
frontend/src/components/VideoListItemTable.tsx
Normal file
64
frontend/src/components/VideoListItemTable.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import Routes from '../configuration/routes/RouteList';
|
||||
import { VideoType } from '../pages/Home';
|
||||
import { ViewStylesType } from '../configuration/constants/ViewStyle';
|
||||
import humanFileSize from '../functions/humanFileSize';
|
||||
import { FileSizeUnits } from '../api/actions/updateUserConfig';
|
||||
import { useUserConfigStore } from '../stores/UserConfigStore';
|
||||
|
||||
type VideoListItemProps = {
|
||||
videoList: VideoType[] | undefined;
|
||||
viewStyle: ViewStylesType;
|
||||
};
|
||||
|
||||
const VideoListItemTable = ({ videoList, viewStyle }: VideoListItemProps) => {
|
||||
const { userConfig } = useUserConfigStore();
|
||||
|
||||
const useSiUnits = userConfig.file_size_unit === FileSizeUnits.Metric;
|
||||
|
||||
return (
|
||||
<div className={`video-item ${viewStyle}`}>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Channel</th>
|
||||
<th>Title</th>
|
||||
<th>Type</th>
|
||||
<th>Resolution</th>
|
||||
<th>Media size</th>
|
||||
<th>Video codec</th>
|
||||
<th>Video bitrate</th>
|
||||
<th>Audio codec</th>
|
||||
<th>Audio bitrate</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{videoList?.map(({ youtube_id, title, channel, vid_type, media_size, streams }) => {
|
||||
const [videoStream, audioStream] = streams;
|
||||
|
||||
return (
|
||||
<tr key={youtube_id}>
|
||||
<td className="no-nowrap">
|
||||
<Link to={Routes.Channel(channel.channel_id)}>{channel.channel_name}</Link>
|
||||
</td>
|
||||
<td className="no-nowrap title">
|
||||
<Link to={Routes.Video(youtube_id)}>{title}</Link>
|
||||
</td>
|
||||
<td>{vid_type}</td>
|
||||
<td>{`${videoStream.width}x${videoStream.height}`}</td>
|
||||
<td>{humanFileSize(media_size, useSiUnits)}</td>
|
||||
<td>{videoStream.codec}</td>
|
||||
<td>{humanFileSize(videoStream.bitrate, useSiUnits)}</td>
|
||||
<td>{audioStream.codec}</td>
|
||||
<td>{humanFileSize(audioStream.bitrate, useSiUnits)}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoListItemTable;
|
@ -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',
|
||||
};
|
||||
|
@ -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<UserConfigType>) => {
|
||||
@ -87,14 +88,18 @@ const ChannelPlaylist = () => {
|
||||
<img
|
||||
src={iconGridView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ view_style_playlist: 'grid' });
|
||||
handleUserConfigUpdate({
|
||||
view_style_playlist: ViewStylesEnum.Grid as ViewStylesType,
|
||||
});
|
||||
}}
|
||||
alt="grid view"
|
||||
/>
|
||||
<img
|
||||
src={iconListView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ view_style_playlist: 'list' });
|
||||
handleUserConfigUpdate({
|
||||
view_style_playlist: ViewStylesEnum.List as ViewStylesType,
|
||||
});
|
||||
}}
|
||||
alt="list view"
|
||||
/>
|
||||
@ -103,7 +108,7 @@ const ChannelPlaylist = () => {
|
||||
</div>
|
||||
|
||||
<div className={`boxed-content`}>
|
||||
<div className={`playlist-list ${view}`}>
|
||||
<div className={`playlist-list ${viewStyle}`}>
|
||||
<PlaylistList playlistList={playlistList} setRefresh={setRefreshPlaylists} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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) => {
|
||||
</div>
|
||||
|
||||
<div className={`boxed-content ${gridView}`}>
|
||||
<Filterbar hideToggleText={'Hide watched videos:'} viewStyleName={ViewStyleNames.home} />
|
||||
<Filterbar
|
||||
hideToggleText={'Hide watched videos:'}
|
||||
viewStyle={ViewStyleNames.Home as ViewStyleNamesType}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<EmbeddableVideoPlayer videoId={videoId} />
|
||||
|
||||
<div className={`boxed-content ${gridView}`}>
|
||||
<div className={`video-list ${view} ${gridViewGrid}`}>
|
||||
<div className={`video-list ${viewStyle} ${gridViewGrid}`}>
|
||||
{!hasVideos && (
|
||||
<>
|
||||
<h2>No videos found...</h2>
|
||||
@ -177,7 +186,7 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<VideoList videoList={videoList} viewLayout={view} refreshVideoList={setRefresh} />
|
||||
<VideoList videoList={videoList} viewStyle={viewStyle} refreshVideoList={setRefresh} />
|
||||
</div>
|
||||
</div>
|
||||
{pagination && (
|
||||
|
@ -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 = () => {
|
||||
<img
|
||||
src={iconGridView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ view_style_channel: 'grid' });
|
||||
handleUserConfigUpdate({
|
||||
view_style_channel: ViewStylesEnum.Grid as ViewStylesType,
|
||||
});
|
||||
}}
|
||||
data-origin="channel"
|
||||
data-value="grid"
|
||||
alt="grid view"
|
||||
/>
|
||||
<img
|
||||
src={iconListView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ view_style_channel: 'list' });
|
||||
handleUserConfigUpdate({
|
||||
view_style_channel: ViewStylesEnum.List as ViewStylesType,
|
||||
});
|
||||
}}
|
||||
data-origin="channel"
|
||||
data-value="list"
|
||||
|
@ -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 = () => {
|
||||
<img
|
||||
src={iconGridView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ view_style_downloads: 'grid' });
|
||||
handleUserConfigUpdate({
|
||||
view_style_downloads: ViewStylesEnum.Grid as ViewStylesType,
|
||||
});
|
||||
}}
|
||||
alt="grid view"
|
||||
/>
|
||||
<img
|
||||
src={iconListView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ view_style_downloads: 'list' });
|
||||
handleUserConfigUpdate({
|
||||
view_style_downloads: ViewStylesEnum.List as ViewStylesType,
|
||||
});
|
||||
}}
|
||||
alt="list view"
|
||||
/>
|
||||
@ -341,7 +345,7 @@ const Download = () => {
|
||||
</div>
|
||||
|
||||
<div className={`boxed-content ${gridView}`}>
|
||||
<div className={`video-list ${view} ${gridViewGrid}`}>
|
||||
<div className={`video-list ${viewStyle} ${gridViewGrid}`}>
|
||||
{downloadList &&
|
||||
downloadList?.map(download => {
|
||||
return (
|
||||
|
@ -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 = () => {
|
||||
<EmbeddableVideoPlayer videoId={videoId} />
|
||||
|
||||
<div className={`boxed-content ${gridView}`}>
|
||||
{continueVideos && continueVideos.length > 0 && (
|
||||
{continueVideos && continueVideos.length > 0 && !isTableView && (
|
||||
<>
|
||||
<div className="title-bar">
|
||||
<h1>Continue Watching</h1>
|
||||
@ -176,7 +179,7 @@ const Home = () => {
|
||||
<div className={`video-list ${userConfig.view_style_home} ${gridViewGrid}`}>
|
||||
<VideoList
|
||||
videoList={continueVideos}
|
||||
viewLayout={userConfig.view_style_home}
|
||||
viewStyle={userConfig.view_style_home}
|
||||
refreshVideoList={setRefreshVideoList}
|
||||
/>
|
||||
</div>
|
||||
@ -187,7 +190,10 @@ const Home = () => {
|
||||
<h1>Recent Videos</h1>
|
||||
</div>
|
||||
|
||||
<Filterbar hideToggleText="Show unwatched only:" viewStyleName={ViewStyleNames.home} />
|
||||
<Filterbar
|
||||
hideToggleText="Show unwatched only:"
|
||||
viewStyle={ViewStyleNames.Home as ViewStyleNamesType}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={`boxed-content ${gridView}`}>
|
||||
@ -206,7 +212,7 @@ const Home = () => {
|
||||
{hasVideos && (
|
||||
<VideoList
|
||||
videoList={videoList}
|
||||
viewLayout={userConfig.view_style_home}
|
||||
viewStyle={userConfig.view_style_home}
|
||||
refreshVideoList={setRefreshVideoList}
|
||||
/>
|
||||
)}
|
||||
|
@ -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 = () => {
|
||||
<div className={`boxed-content ${gridView}`}>
|
||||
<Filterbar
|
||||
hideToggleText="Hide watched videos:"
|
||||
viewStyleName={ViewStyleNames.home}
|
||||
viewStyle={ViewStyleNames.Home as ViewStyleNamesType} // its a list of videos, so ViewStyleNames.Home
|
||||
showSort={false}
|
||||
/>
|
||||
</div>
|
||||
@ -312,7 +319,7 @@ const Playlist = () => {
|
||||
<EmbeddableVideoPlayer videoId={videoId} />
|
||||
|
||||
<div className={`boxed-content ${gridView}`}>
|
||||
<div className={`video-list ${view} ${gridViewGrid}`}>
|
||||
<div className={`video-list ${viewStyle} ${gridViewGrid}`}>
|
||||
{videoInPlaylistCount === 0 && (
|
||||
<>
|
||||
<h2>No videos found...</h2>
|
||||
@ -334,7 +341,7 @@ const Playlist = () => {
|
||||
{videoInPlaylistCount !== 0 && (
|
||||
<VideoList
|
||||
videoList={videos}
|
||||
viewLayout={view}
|
||||
viewStyle={viewStyle}
|
||||
playlistId={playlistId}
|
||||
showReorderButton={isCustomPlaylist}
|
||||
refreshVideoList={setRefresh}
|
||||
|
@ -18,6 +18,7 @@ import { useUserConfigStore } from '../stores/UserConfigStore';
|
||||
import Notifications from '../components/Notifications';
|
||||
import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig';
|
||||
import { ApiResponseType } from '../functions/APIClient';
|
||||
import { ViewStylesEnum, ViewStylesType } from '../configuration/constants/ViewStyle';
|
||||
|
||||
const Playlists = () => {
|
||||
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 = () => {
|
||||
<img
|
||||
src={iconGridView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ view_style_playlist: 'grid' });
|
||||
handleUserConfigUpdate({
|
||||
view_style_playlist: ViewStylesEnum.Grid as ViewStylesType,
|
||||
});
|
||||
}}
|
||||
alt="grid view"
|
||||
/>
|
||||
<img
|
||||
src={iconListView}
|
||||
onClick={() => {
|
||||
handleUserConfigUpdate({ view_style_playlist: 'list' });
|
||||
handleUserConfigUpdate({
|
||||
view_style_playlist: ViewStylesEnum.List as ViewStylesType,
|
||||
});
|
||||
}}
|
||||
alt="list view"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`playlist-list ${view}`}>
|
||||
<div className={`playlist-list ${viewStyle}`}>
|
||||
{!hasPlaylists && <h2>No playlists found...</h2>}
|
||||
|
||||
{hasPlaylists && <PlaylistList playlistList={playlistList} setRefresh={setRefresh} />}
|
||||
|
@ -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 = () => {
|
||||
<div id="video-results" className={`video-list ${viewVideos} ${gridViewGrid}`}>
|
||||
<VideoList
|
||||
videoList={videoList}
|
||||
viewLayout={viewVideos}
|
||||
viewStyle={viewVideos}
|
||||
refreshVideoList={setRefresh}
|
||||
/>
|
||||
</div>
|
||||
|
@ -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 = () => {
|
||||
<div className="video-list grid grid-3" id="similar-videos">
|
||||
<VideoList
|
||||
videoList={similarVideosResponseData}
|
||||
viewLayout="grid"
|
||||
viewStyle={ViewStylesEnum.Grid as ViewStylesType}
|
||||
refreshVideoList={setRefreshVideoList}
|
||||
/>
|
||||
</div>
|
||||
|
@ -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<UserConfigState>(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',
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user