Refac use file_size_unit from user config in frontend #886

This commit is contained in:
MerlinScheurer
2025-03-15 15:15:22 +01:00
parent 87d4f16456
commit c5f549967b
10 changed files with 83 additions and 40 deletions

View File

@@ -3,6 +3,11 @@ import APIClient from '../../functions/APIClient';
export type ColourVariants = 'dark.css' | 'light.css' | 'matrix.css' | 'midnight.css'; export type ColourVariants = 'dark.css' | 'light.css' | 'matrix.css' | 'midnight.css';
export const FileSizeUnits = {
Binary: 'binary',
Metric: 'metric',
};
export type UserConfigType = { export type UserConfigType = {
stylesheet: ColourVariants; stylesheet: ColourVariants;
page_size: number; page_size: number;
@@ -14,6 +19,7 @@ export type UserConfigType = {
view_style_playlist: ViewLayoutType; view_style_playlist: ViewLayoutType;
grid_items: number; grid_items: number;
hide_watched: boolean; hide_watched: boolean;
file_size_unit: 'binary' | 'metric';
show_ignored_only: boolean; show_ignored_only: boolean;
show_subed_only: boolean; show_subed_only: boolean;
show_help_text: boolean; show_help_text: boolean;

View File

@@ -8,14 +8,14 @@ type BiggestChannelsStatsProps = {
biggestChannelsStatsByCount?: BiggestChannelsStatsType; biggestChannelsStatsByCount?: BiggestChannelsStatsType;
biggestChannelsStatsByDuration?: BiggestChannelsStatsType; biggestChannelsStatsByDuration?: BiggestChannelsStatsType;
biggestChannelsStatsByMediaSize?: BiggestChannelsStatsType; biggestChannelsStatsByMediaSize?: BiggestChannelsStatsType;
useSI: boolean; useSIUnits: boolean;
}; };
const BiggestChannelsStats = ({ const BiggestChannelsStats = ({
biggestChannelsStatsByCount, biggestChannelsStatsByCount,
biggestChannelsStatsByDuration, biggestChannelsStatsByDuration,
biggestChannelsStatsByMediaSize, biggestChannelsStatsByMediaSize,
useSI, useSIUnits,
}: BiggestChannelsStatsProps) => { }: BiggestChannelsStatsProps) => {
if ( if (
!biggestChannelsStatsByCount && !biggestChannelsStatsByCount &&
@@ -94,7 +94,9 @@ const BiggestChannelsStats = ({
<td className="agg-channel-name"> <td className="agg-channel-name">
<Link to={Routes.Channel(id)}>{name}</Link> <Link to={Routes.Channel(id)}>{name}</Link>
</td> </td>
<td className="agg-channel-right-align">{humanFileSize(media_size, useSI)}</td> <td className="agg-channel-right-align">
{humanFileSize(media_size, useSIUnits)}
</td>
</tr> </tr>
); );
})} })}

View File

@@ -5,10 +5,10 @@ import { DownloadHistoryStatsType } from '../pages/SettingsDashboard';
type DownloadHistoryStatsProps = { type DownloadHistoryStatsProps = {
downloadHistoryStats?: DownloadHistoryStatsType; downloadHistoryStats?: DownloadHistoryStatsType;
useSI: boolean; useSIUnits: boolean;
}; };
const DownloadHistoryStats = ({ downloadHistoryStats, useSI }: DownloadHistoryStatsProps) => { const DownloadHistoryStats = ({ downloadHistoryStats, useSIUnits }: DownloadHistoryStatsProps) => {
if (!downloadHistoryStats) { if (!downloadHistoryStats) {
return <p id="loading">Loading...</p>; return <p id="loading">Loading...</p>;
} }
@@ -31,7 +31,7 @@ const DownloadHistoryStats = ({ downloadHistoryStats, useSI }: DownloadHistorySt
<p> <p>
+{formatNumbers(count)} {videoText} +{formatNumbers(count)} {videoText}
<br /> <br />
{humanFileSize(media_size, useSI)} {humanFileSize(media_size, useSIUnits)}
</p> </p>
</div> </div>
); );

View File

@@ -6,10 +6,10 @@ import { VideoStatsType } from '../pages/SettingsDashboard';
type OverviewStatsProps = { type OverviewStatsProps = {
videoStats?: VideoStatsType; videoStats?: VideoStatsType;
useSI: boolean; useSIUnits: boolean;
}; };
const OverviewStats = ({ videoStats, useSI }: OverviewStatsProps) => { const OverviewStats = ({ videoStats, useSIUnits }: OverviewStatsProps) => {
if (!videoStats) { if (!videoStats) {
return <p id="loading">Loading...</p>; return <p id="loading">Loading...</p>;
} }
@@ -19,7 +19,7 @@ const OverviewStats = ({ videoStats, useSI }: OverviewStatsProps) => {
title: 'All: ', title: 'All: ',
data: { data: {
Videos: formatNumbers(videoStats?.doc_count || 0), Videos: formatNumbers(videoStats?.doc_count || 0),
['Media Size']: humanFileSize(videoStats?.media_size || 0, useSI), ['Media Size']: humanFileSize(videoStats?.media_size || 0, useSIUnits),
Duration: videoStats?.duration_str, Duration: videoStats?.duration_str,
}, },
}, },
@@ -27,7 +27,7 @@ const OverviewStats = ({ videoStats, useSI }: OverviewStatsProps) => {
title: 'Active: ', title: 'Active: ',
data: { data: {
Videos: formatNumbers(videoStats?.active_true?.doc_count || 0), Videos: formatNumbers(videoStats?.active_true?.doc_count || 0),
['Media Size']: humanFileSize(videoStats?.active_true?.media_size || 0, useSI), ['Media Size']: humanFileSize(videoStats?.active_true?.media_size || 0, useSIUnits),
Duration: videoStats?.active_true?.duration_str || 'NA', Duration: videoStats?.active_true?.duration_str || 'NA',
}, },
}, },
@@ -35,7 +35,7 @@ const OverviewStats = ({ videoStats, useSI }: OverviewStatsProps) => {
title: 'Inactive: ', title: 'Inactive: ',
data: { data: {
Videos: formatNumbers(videoStats?.active_false?.doc_count || 0), Videos: formatNumbers(videoStats?.active_false?.doc_count || 0),
['Media Size']: humanFileSize(videoStats?.active_false?.media_size || 0, useSI), ['Media Size']: humanFileSize(videoStats?.active_false?.media_size || 0, useSIUnits),
Duration: videoStats?.active_false?.duration_str || 'NA', Duration: videoStats?.active_false?.duration_str || 'NA',
}, },
}, },

View File

@@ -6,10 +6,10 @@ import { VideoStatsType } from '../pages/SettingsDashboard';
type VideoTypeStatsProps = { type VideoTypeStatsProps = {
videoStats?: VideoStatsType; videoStats?: VideoStatsType;
useSI: boolean; useSIUnits: boolean;
}; };
const VideoTypeStats = ({ videoStats, useSI }: VideoTypeStatsProps) => { const VideoTypeStats = ({ videoStats, useSIUnits }: VideoTypeStatsProps) => {
if (!videoStats) { if (!videoStats) {
return <p id="loading">Loading...</p>; return <p id="loading">Loading...</p>;
} }
@@ -19,7 +19,7 @@ const VideoTypeStats = ({ videoStats, useSI }: VideoTypeStatsProps) => {
title: 'Regular Videos: ', title: 'Regular Videos: ',
data: { data: {
Videos: formatNumbers(videoStats?.type_videos?.doc_count || 0), Videos: formatNumbers(videoStats?.type_videos?.doc_count || 0),
['Media Size']: humanFileSize(videoStats?.type_videos?.media_size || 0, useSI), ['Media Size']: humanFileSize(videoStats?.type_videos?.media_size || 0, useSIUnits),
Duration: videoStats?.type_videos?.duration_str || 'NA', Duration: videoStats?.type_videos?.duration_str || 'NA',
}, },
}, },
@@ -27,7 +27,7 @@ const VideoTypeStats = ({ videoStats, useSI }: VideoTypeStatsProps) => {
title: 'Shorts: ', title: 'Shorts: ',
data: { data: {
Videos: formatNumbers(videoStats?.type_shorts?.doc_count || 0), Videos: formatNumbers(videoStats?.type_shorts?.doc_count || 0),
['Media Size']: humanFileSize(videoStats?.type_shorts?.media_size || 0, useSI), ['Media Size']: humanFileSize(videoStats?.type_shorts?.media_size || 0, useSIUnits),
Duration: videoStats?.type_shorts?.duration_str || 'NA', Duration: videoStats?.type_shorts?.duration_str || 'NA',
}, },
}, },
@@ -35,7 +35,7 @@ const VideoTypeStats = ({ videoStats, useSI }: VideoTypeStatsProps) => {
title: 'Streams: ', title: 'Streams: ',
data: { data: {
Videos: formatNumbers(videoStats?.type_streams?.doc_count || 0), Videos: formatNumbers(videoStats?.type_streams?.doc_count || 0),
['Media Size']: humanFileSize(videoStats?.type_streams?.media_size || 0, useSI), ['Media Size']: humanFileSize(videoStats?.type_streams?.media_size || 0, useSIUnits),
Duration: videoStats?.type_streams?.duration_str || 'NA', Duration: videoStats?.type_streams?.duration_str || 'NA',
}, },
}, },

View File

@@ -20,6 +20,7 @@ import loadVideoListByFilter, {
import loadChannelAggs, { ChannelAggsType } from '../api/loader/loadChannelAggs'; import loadChannelAggs, { ChannelAggsType } from '../api/loader/loadChannelAggs';
import humanFileSize from '../functions/humanFileSize'; import humanFileSize from '../functions/humanFileSize';
import { useUserConfigStore } from '../stores/UserConfigStore'; import { useUserConfigStore } from '../stores/UserConfigStore';
import { FileSizeUnits } from '../api/actions/updateUserConfig';
type ChannelParams = { type ChannelParams = {
channelId: string; channelId: string;
@@ -47,6 +48,7 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
const pagination = videoResponse?.paginate; const pagination = videoResponse?.paginate;
const hasVideos = videoResponse?.data?.length !== 0; const hasVideos = videoResponse?.data?.length !== 0;
const useSiUnits = userConfig.file_size_unit === FileSizeUnits.Metric;
const view = userConfig.view_style_home; const view = userConfig.view_style_home;
const isGridView = view === ViewStyles.grid; const isGridView = view === ViewStyles.grid;
@@ -114,7 +116,7 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
<span className="space-carrot">|</span>{' '} <span className="space-carrot">|</span>{' '}
{videoAggsResponse.total_duration.value_str} playback{' '} {videoAggsResponse.total_duration.value_str} playback{' '}
<span className="space-carrot">|</span> Total size{' '} <span className="space-carrot">|</span> Total size{' '}
{humanFileSize(videoAggsResponse.total_size.value, true)} {humanFileSize(videoAggsResponse.total_size.value, useSiUnits)}
</p> </p>
<div className="button-box"> <div className="button-box">
<Button <Button

View File

@@ -15,6 +15,8 @@ import DownloadHistoryStats from '../components/DownloadHistoryStats';
import BiggestChannelsStats from '../components/BiggestChannelsStats'; import BiggestChannelsStats from '../components/BiggestChannelsStats';
import Notifications from '../components/Notifications'; import Notifications from '../components/Notifications';
import PaginationDummy from '../components/PaginationDummy'; import PaginationDummy from '../components/PaginationDummy';
import { useUserConfigStore } from '../stores/UserConfigStore';
import { FileSizeUnits } from '../api/actions/updateUserConfig';
export type VideoStatsType = { export type VideoStatsType = {
doc_count: number; doc_count: number;
@@ -125,7 +127,7 @@ type DashboardStatsReponses = {
}; };
const SettingsDashboard = () => { const SettingsDashboard = () => {
const [useSi, setUseSi] = useState(false); const { userConfig } = useUserConfigStore();
const [response, setResponse] = useState<DashboardStatsReponses>({ const [response, setResponse] = useState<DashboardStatsReponses>({
videoStats: undefined, videoStats: undefined,
@@ -181,6 +183,8 @@ const SettingsDashboard = () => {
})(); })();
}, []); }, []);
const useSiUnits = userConfig.file_size_unit === FileSizeUnits.Metric;
return ( return (
<> <>
<title>TA | Settings Dashboard</title> <title>TA | Settings Dashboard</title>
@@ -190,31 +194,17 @@ const SettingsDashboard = () => {
<div className="title-bar"> <div className="title-bar">
<h1>Your Archive</h1> <h1>Your Archive</h1>
</div> </div>
<p>
File Sizes in:
<select
value={useSi ? 'true' : 'false'}
onChange={event => {
const value = event.target.value;
console.log(value);
setUseSi(value === 'true');
}}
>
<option value="true">SI units</option>
<option value="false">Binary units</option>
</select>
</p>
<div className="settings-item"> <div className="settings-item">
<h2>Overview</h2> <h2>Overview</h2>
<div className="info-box info-box-3"> <div className="info-box info-box-3">
<OverviewStats videoStats={videoStats} useSI={useSi} /> <OverviewStats videoStats={videoStats} useSIUnits={useSiUnits} />
</div> </div>
</div> </div>
<div className="settings-item"> <div className="settings-item">
<h2>Video Type</h2> <h2>Video Type</h2>
<div className="info-box info-box-3"> <div className="info-box info-box-3">
<VideoTypeStats videoStats={videoStats} useSI={useSi} /> <VideoTypeStats videoStats={videoStats} useSIUnits={useSiUnits} />
</div> </div>
</div> </div>
<div className="settings-item"> <div className="settings-item">
@@ -236,7 +226,7 @@ const SettingsDashboard = () => {
<div className="settings-item"> <div className="settings-item">
<h2>Download History</h2> <h2>Download History</h2>
<div className="info-box info-box-4"> <div className="info-box info-box-4">
<DownloadHistoryStats downloadHistoryStats={downloadHistoryStats} useSI={false} /> <DownloadHistoryStats downloadHistoryStats={downloadHistoryStats} useSIUnits={false} />
</div> </div>
</div> </div>
<div className="settings-item"> <div className="settings-item">
@@ -246,7 +236,7 @@ const SettingsDashboard = () => {
biggestChannelsStatsByCount={biggestChannelsStatsByCount} biggestChannelsStatsByCount={biggestChannelsStatsByCount}
biggestChannelsStatsByDuration={biggestChannelsStatsByDuration} biggestChannelsStatsByDuration={biggestChannelsStatsByDuration}
biggestChannelsStatsByMediaSize={biggestChannelsStatsByMediaSize} biggestChannelsStatsByMediaSize={biggestChannelsStatsByMediaSize}
useSI={useSi} useSIUnits={useSiUnits}
/> />
</div> </div>
</div> </div>

View File

@@ -1,5 +1,9 @@
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import updateUserConfig, { ColourVariants, UserConfigType } from '../api/actions/updateUserConfig'; import updateUserConfig, {
ColourVariants,
FileSizeUnits,
UserConfigType,
} from '../api/actions/updateUserConfig';
import { ColourConstant } from '../configuration/colours/useColours'; import { ColourConstant } from '../configuration/colours/useColours';
import SettingsNavigation from '../components/SettingsNavigation'; import SettingsNavigation from '../components/SettingsNavigation';
import Notifications from '../components/Notifications'; import Notifications from '../components/Notifications';
@@ -18,14 +22,21 @@ const SettingsUser = () => {
const [styleSheetRefresh, setStyleSheetRefresh] = useState(false); const [styleSheetRefresh, setStyleSheetRefresh] = useState(false);
const [pageSize, setPageSize] = useState<number>(userConfig.page_size); const [pageSize, setPageSize] = useState<number>(userConfig.page_size);
const [showHelpText, setShowHelpText] = useState(userConfig.show_help_text); const [showHelpText, setShowHelpText] = useState(userConfig.show_help_text);
const [selectedFileSizeUnit, setSelectedFileSizeUnit] = useState(FileSizeUnits.Binary);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
setStyleSheet(userConfig.stylesheet); setStyleSheet(userConfig.stylesheet);
setPageSize(userConfig.page_size); setPageSize(userConfig.page_size);
setShowHelpText(userConfig.show_help_text); setShowHelpText(userConfig.show_help_text);
setSelectedFileSizeUnit(userConfig.file_size_unit);
})(); })();
}, [userConfig.page_size, userConfig.stylesheet, userConfig.show_help_text]); }, [
userConfig.page_size,
userConfig.stylesheet,
userConfig.show_help_text,
userConfig.file_size_unit,
]);
const handleStyleSheetChange = async (selectedStyleSheet: ColourVariants) => { const handleStyleSheetChange = async (selectedStyleSheet: ColourVariants) => {
handleUserConfigUpdate({ stylesheet: selectedStyleSheet }); handleUserConfigUpdate({ stylesheet: selectedStyleSheet });
@@ -41,6 +52,10 @@ const SettingsUser = () => {
handleUserConfigUpdate({ [configKey]: configValue }); handleUserConfigUpdate({ [configKey]: configValue });
}; };
const handleFileSizeUnitChange = async (configKey: string, configValue: string) => {
handleUserConfigUpdate({ [configKey]: configValue });
};
const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => { const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config); const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig); setUserConfig(updatedUserConfig);
@@ -88,6 +103,7 @@ const SettingsUser = () => {
{styleSheetRefresh && <button onClick={handlePageRefresh}>Refresh</button>} {styleSheetRefresh && <button onClick={handlePageRefresh}>Refresh</button>}
</div> </div>
</div> </div>
<div className="settings-box-wrapper"> <div className="settings-box-wrapper">
<div> <div>
<p>Archive view page size</p> <p>Archive view page size</p>
@@ -113,18 +129,40 @@ const SettingsUser = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="settings-box-wrapper"> <div className="settings-box-wrapper">
<div> <div>
<p>Show help text</p> <p>Show help text</p>
</div> </div>
<ToggleConfig <ToggleConfig
name="show_help_text" name="show_help_text"
value={showHelpText} value={showHelpText}
updateCallback={handleShowHelpTextChange} updateCallback={handleShowHelpTextChange}
/> />
</div> </div>
<div
className="settings-box-wrapper"
title="Metric (SI) units, aka powers of 1000. Binary (IEC), aka powers of 1024."
>
<div>
<p>File size units:</p>
</div>
<select
value={selectedFileSizeUnit}
onChange={event => {
handleFileSizeUnitChange('file_size_unit', event.currentTarget.value);
}}
>
<option value={FileSizeUnits.Metric}>SI units</option>
<option value={FileSizeUnits.Binary}>Binary units</option>
</select>
</div> </div>
</div> </div>
</div>
{isAdmin && ( {isAdmin && (
<> <>
<div className="settings-group"> <div className="settings-group">

View File

@@ -41,6 +41,8 @@ import ToggleConfig from '../components/ToggleConfig';
import { PlaylistType } from '../api/loader/loadPlaylistById'; import { PlaylistType } from '../api/loader/loadPlaylistById';
import { useAppSettingsStore } from '../stores/AppSettingsStore'; import { useAppSettingsStore } from '../stores/AppSettingsStore';
import updateDownloadQueueStatusById from '../api/actions/updateDownloadQueueStatusById'; import updateDownloadQueueStatusById from '../api/actions/updateDownloadQueueStatusById';
import { FileSizeUnits } from '../api/actions/updateUserConfig';
import { useUserConfigStore } from '../stores/UserConfigStore';
const isInPlaylist = (videoId: string, playlist: PlaylistType) => { const isInPlaylist = (videoId: string, playlist: PlaylistType) => {
return playlist.playlist_entries.some(entry => { return playlist.playlist_entries.some(entry => {
@@ -112,6 +114,7 @@ const Video = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const isAdmin = useIsAdmin(); const isAdmin = useIsAdmin();
const { appSettingsConfig } = useAppSettingsStore(); const { appSettingsConfig } = useAppSettingsStore();
const { userConfig } = useUserConfigStore();
const [videoEnded, setVideoEnded] = useState(false); const [videoEnded, setVideoEnded] = useState(false);
const [playlistAutoplay, setPlaylistAutoplay] = useState( const [playlistAutoplay, setPlaylistAutoplay] = useState(
@@ -199,6 +202,7 @@ const Video = () => {
const customPlaylists = customPlaylistsResponse?.data; const customPlaylists = customPlaylistsResponse?.data;
const starRating = convertStarRating(video?.stats?.average_rating); const starRating = convertStarRating(video?.stats?.average_rating);
const comments = commentsResponse; const comments = commentsResponse;
const useSiUnits = userConfig.file_size_unit === FileSizeUnits.Metric;
console.log('playlistNav', playlistNav); console.log('playlistNav', playlistNav);
@@ -439,14 +443,14 @@ const Video = () => {
</div> </div>
</div> </div>
<div className="info-box-item"> <div className="info-box-item">
{video.media_size && <p>File size: {humanFileSize(video.media_size)}</p>} {video.media_size && <p>File size: {humanFileSize(video.media_size, useSiUnits)}</p>}
{video.streams && {video.streams &&
video.streams.map(stream => { video.streams.map(stream => {
return ( return (
<p key={stream.index}> <p key={stream.index}>
{capitalizeFirstLetter(stream.type)}: {stream.codec}{' '} {capitalizeFirstLetter(stream.type)}: {stream.codec}{' '}
{humanFileSize(stream.bitrate)}/s {humanFileSize(stream.bitrate, useSiUnits)}/s
{stream.width && ( {stream.width && (
<> <>
<span className="space-carrot">|</span> {stream.width}x{stream.height} <span className="space-carrot">|</span> {stream.width}x{stream.height}

View File

@@ -18,6 +18,7 @@ export const useUserConfigStore = create<UserConfigState>(set => ({
view_style_playlist: 'grid', view_style_playlist: 'grid',
grid_items: 3, grid_items: 3,
hide_watched: false, hide_watched: false,
file_size_unit: 'binary',
show_ignored_only: false, show_ignored_only: false,
show_subed_only: false, show_subed_only: false,
show_help_text: true, show_help_text: true,