Initial commit - realms platform

This commit is contained in:
doomtube 2026-01-05 22:54:27 -05:00
parent c590ab6d18
commit c717c3751c
234 changed files with 74103 additions and 15231 deletions

View file

@ -0,0 +1,123 @@
/**
* Shared formatting utilities for the frontend
*/
/**
* Format seconds to duration string (e.g., "1:23" or "1:23:45")
* @param {number} seconds - Duration in seconds
* @param {boolean} includeHours - Always include hours for long durations (auto-detected if false)
* @returns {string} Formatted duration
*/
export function formatDuration(seconds, includeHours = false) {
if (!seconds) return '0:00';
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (includeHours || hrs > 0) {
return `${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
/**
* Format view/play count with K/M suffix
* @param {number} count - The count to format
* @param {boolean} includeSuffix - Append " views" to the output
* @returns {string} Formatted count (e.g., "1.2K" or "1.2K views")
*/
export function formatViews(count, includeSuffix = false) {
if (count === null || count === undefined) count = 0;
const suffix = includeSuffix ? ' views' : '';
if (count >= 1000000) return (count / 1000000).toFixed(1) + 'M' + suffix;
if (count >= 1000) return (count / 1000).toFixed(1) + 'K' + suffix;
return count.toString() + suffix;
}
/**
* Format date as relative time (e.g., "5 min ago", "2 days ago")
* @param {string|Date} dateStr - Date string or Date object
* @param {boolean} short - Use abbreviated format (m, h, d instead of full text)
* @returns {string} Relative time string
*/
export function timeAgo(dateStr, short = false) {
const date = new Date(dateStr);
const now = new Date();
const seconds = Math.floor((now - date) / 1000);
if (short) {
if (seconds < 60) return 'now';
if (seconds < 3600) return Math.floor(seconds / 60) + 'm';
if (seconds < 86400) return Math.floor(seconds / 3600) + 'h';
if (seconds < 604800) return Math.floor(seconds / 86400) + 'd';
return date.toLocaleDateString();
}
if (seconds < 60) return 'just now';
if (seconds < 3600) return Math.floor(seconds / 60) + ' min ago';
if (seconds < 86400) return Math.floor(seconds / 3600) + ' hours ago';
if (seconds < 604800) return Math.floor(seconds / 86400) + ' days ago';
return date.toLocaleDateString();
}
/**
* Format bytes to human-readable size
* @param {number} bytes - Size in bytes
* @returns {string} Formatted size (e.g., "1.5 GB")
*/
export function formatBytes(bytes) {
if (!bytes) return '0 bytes';
if (bytes >= 1073741824) return (bytes / 1073741824).toFixed(2) + ' GB';
if (bytes >= 1048576) return (bytes / 1048576).toFixed(2) + ' MB';
if (bytes >= 1024) return (bytes / 1024).toFixed(2) + ' KB';
return bytes + ' bytes';
}
/**
* Format bitrate to human-readable format
* @param {number} bps - Bitrate in bits per second
* @returns {string} Formatted bitrate (e.g., "2.5 Mbps")
*/
export function formatBitrate(bps) {
if (!bps) return '';
const kbps = Math.round(bps / 1000);
if (kbps >= 1000) return (kbps / 1000).toFixed(1) + ' Mbps';
return kbps + ' kbps';
}
/**
* Format codec name to human-readable format
* @param {string} codec - Raw codec name
* @returns {string} Human-readable codec name
*/
export function formatCodec(codec) {
if (!codec) return '';
const codecMap = {
'h264': 'H.264 (AVC)',
'hevc': 'H.265 (HEVC)',
'h265': 'H.265 (HEVC)',
'vp9': 'VP9',
'vp8': 'VP8',
'av1': 'AV1',
'aac': 'AAC',
'mp3': 'MP3',
'opus': 'Opus',
'vorbis': 'Vorbis',
'flac': 'FLAC',
'ac3': 'AC-3',
'eac3': 'E-AC-3'
};
return codecMap[codec.toLowerCase()] || codec.toUpperCase();
}
/**
* Format date to localized string
* @param {string|Date} dateStr - Date string or Date object
* @returns {string} Formatted date (e.g., "January 1, 2024")
*/
export function formatDate(dateStr) {
return new Date(dateStr).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}