mirror of
https://github.com/KevinMidboe/seasoned.git
synced 2026-03-11 03:49:07 +00:00
Feat/settings page redesign (#104)
* include credentials on login fetch requests, allows set header response * Add theme composable and utility improvements - Create useTheme composable for centralized theme management - Update main.ts to use useTheme for initialization - Generalize getCookie utility in user module - Add utility functions for data formatting * Add Plex integration composables and icons - Create usePlexAuth composable for Plex OAuth flow - Create usePlexApi composable for Plex API interactions - Create useRandomWords composable for password generation - Add Plex-related icons (IconPlex, IconServer, IconSync) - Add Plex helper utilities - Update API with Plex-related endpoints * Add storage management components for data & privacy section - Create StorageManager component for browser storage overview - Create StorageSectionBrowser for localStorage/sessionStorage/cookies - Create StorageSectionServer for server-side data (mock) - Create ExportSection for data export functionality - Refactor DataExport component with modular sections - Add storage icons (IconCookie, IconDatabase, IconTimer) - Implement collapsible sections with visual indicators - Add colored borders per storage type - Display item counts and total size in headers * Add theme, password, and security settings components - Create ThemePreferences with visual theme selector - Create PasswordGenerator with passphrase and random modes - Create SecuritySettings wrapper for password management - Update ChangePassword to work with new layout - Implement improved slider UX with visual feedback - Add theme preview cards with gradients - Standardize component styling and typography * Add Plex settings and authentication components - Create PlexSettings component for Plex account management - Create PlexAuthButton with improved OAuth flow - Create PlexServerInfo for server details display - Use icon components instead of inline SVGs - Add sync and unlink functionality - Implement user-friendly authentication flow * Redesign settings page with two-column layout and ProfileHero - Create ProfileHero component with avatar and user info - Create RequestHistory component for Plex requests (placeholder) - Redesign SettingsPage with modern two-column grid layout - Add shared-settings.scss for consistent styling - Organize sections: Appearance, Security, Integrations, Data & Privacy - Implement responsive mobile layout - Standardize typography (h2: 1.5rem, 700 weight) - Add compact modifier for tighter sections
This commit is contained in:
176
src/utils/plexHelpers.ts
Normal file
176
src/utils/plexHelpers.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
export function getLibraryIcon(type: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
movies: "🎬",
|
||||
"tv shows": "📺",
|
||||
music: "🎵"
|
||||
};
|
||||
return icons[type] || "📁";
|
||||
}
|
||||
|
||||
export function getLibraryIconComponent(type: string): string {
|
||||
const components: Record<string, string> = {
|
||||
movies: "IconMovie",
|
||||
"tv shows": "IconShow",
|
||||
music: "IconMusic"
|
||||
};
|
||||
return components[type] || "IconMovie";
|
||||
}
|
||||
|
||||
export function getLibraryTitle(type: string): string {
|
||||
const titles: Record<string, string> = {
|
||||
movies: "Movies",
|
||||
"tv shows": "TV Shows",
|
||||
music: "Music"
|
||||
};
|
||||
return titles[type] || type;
|
||||
}
|
||||
|
||||
export function formatDate(dateString: string): string {
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric"
|
||||
});
|
||||
} catch {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatMemberSince(dateString: string): string {
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const years = now.getFullYear() - date.getFullYear();
|
||||
|
||||
if (years === 0) return "New Member";
|
||||
if (years === 1) return "1 Year";
|
||||
return `${years} Years`;
|
||||
} catch {
|
||||
return "Member";
|
||||
}
|
||||
}
|
||||
|
||||
export function processLibraryItem(
|
||||
item: any,
|
||||
libraryType: string,
|
||||
authToken: string,
|
||||
serverUrl: string,
|
||||
machineIdentifier: string
|
||||
) {
|
||||
// Get poster/thumbnail URL
|
||||
let posterUrl = null;
|
||||
|
||||
// For TV tv shows, prefer grandparentThumb (show poster) over thumb (episode thumbnail)
|
||||
if (libraryType === "tv shows") {
|
||||
if (item.grandparentThumb) {
|
||||
posterUrl = `${serverUrl}${item.grandparentThumb}?X-Plex-Token=${authToken}`;
|
||||
} else if (item.thumb) {
|
||||
posterUrl = `${serverUrl}${item.thumb}?X-Plex-Token=${authToken}`;
|
||||
}
|
||||
}
|
||||
// For music, prefer grandparentThumb (artist/album) over thumb
|
||||
else if (libraryType === "music") {
|
||||
if (item.grandparentThumb) {
|
||||
posterUrl = `${serverUrl}${item.grandparentThumb}?X-Plex-Token=${authToken}`;
|
||||
} else if (item.thumb) {
|
||||
posterUrl = `${serverUrl}${item.thumb}?X-Plex-Token=${authToken}`;
|
||||
}
|
||||
}
|
||||
// For movies and other types, use thumb
|
||||
else if (item.thumb) {
|
||||
posterUrl = `${serverUrl}${item.thumb}?X-Plex-Token=${authToken}`;
|
||||
}
|
||||
|
||||
// Build Plex Web App URL
|
||||
// Format: https://app.plex.tv/desktop/#!/server/{machineId}/details?key=%2Flibrary%2Fmetadata%2F{ratingKey}
|
||||
const ratingKey = item.ratingKey || item.key;
|
||||
let plexUrl = null;
|
||||
if (ratingKey && machineIdentifier) {
|
||||
const encodedKey = encodeURIComponent(`/library/metadata/${ratingKey}`);
|
||||
plexUrl = `https://app.plex.tv/desktop/#!/server/${machineIdentifier}/details?key=${encodedKey}`;
|
||||
}
|
||||
|
||||
// For tv shows, use grandparent data (show info) instead of episode info
|
||||
const title =
|
||||
libraryType === "tv shows" && item.grandparentTitle
|
||||
? item.grandparentTitle
|
||||
: item.title;
|
||||
|
||||
const year =
|
||||
libraryType === "tv shows" && item.grandparentYear
|
||||
? item.grandparentYear
|
||||
: item.year || item.parentYear || new Date().getFullYear();
|
||||
|
||||
const baseItem = {
|
||||
title,
|
||||
year,
|
||||
poster: posterUrl,
|
||||
fallbackIcon: getLibraryIcon(libraryType),
|
||||
rating: item.rating ? Math.round(item.rating * 10) / 10 : null,
|
||||
type: libraryType,
|
||||
ratingKey,
|
||||
plexUrl
|
||||
};
|
||||
|
||||
if (libraryType === "tv shows") {
|
||||
return {
|
||||
...baseItem,
|
||||
episodes: item.leafCount || 0
|
||||
};
|
||||
}
|
||||
if (libraryType === "music") {
|
||||
return {
|
||||
...baseItem,
|
||||
artist: item.parentTitle || "Unknown Artist",
|
||||
tracks: item.leafCount || 0
|
||||
};
|
||||
}
|
||||
|
||||
return baseItem;
|
||||
}
|
||||
|
||||
export function calculateGenreStats(metadata: any[]) {
|
||||
const genreMap = new Map<string, number>();
|
||||
|
||||
metadata.forEach((item: any) => {
|
||||
if (item.Genre) {
|
||||
item.Genre.forEach((genre: any) => {
|
||||
genreMap.set(genre.tag, (genreMap.get(genre.tag) || 0) + 1);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(genreMap.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 5)
|
||||
.map(([name, count]) => ({ name, count }));
|
||||
}
|
||||
|
||||
export function calculateDuration(metadata: any[], libraryType: string) {
|
||||
let totalDuration = 0;
|
||||
let totalEpisodes = 0;
|
||||
let totalTracks = 0;
|
||||
|
||||
metadata.forEach((item: any) => {
|
||||
if (item.duration) {
|
||||
totalDuration += item.duration;
|
||||
}
|
||||
|
||||
if (libraryType === "tv shows" && item.leafCount) {
|
||||
totalEpisodes += item.leafCount;
|
||||
} else if (libraryType === "music" && item.leafCount) {
|
||||
totalTracks += item.leafCount;
|
||||
}
|
||||
});
|
||||
|
||||
const hours = Math.round(totalDuration / (1000 * 60 * 60));
|
||||
const formattedDuration = `${hours.toLocaleString()} hours`;
|
||||
|
||||
return {
|
||||
totalDuration: formattedDuration,
|
||||
totalEpisodes,
|
||||
totalTracks
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user