mirror of
https://github.com/shreshth-mehra/Obsidian-TV-Tracker.git
synced 2026-07-22 09:20:31 +00:00
v1.3
This commit is contained in:
parent
1e4e1be49e
commit
6c965a539d
11 changed files with 1825 additions and 173 deletions
|
|
@ -21,7 +21,7 @@ const MovieGrid = ({ movies, selectedProperties, numberOfColumns, toggleFittedIm
|
|||
};
|
||||
|
||||
function openFile(filePath) {
|
||||
console.log("Path is " + filePath);
|
||||
// console.log("Path is " + filePath);
|
||||
plugin.app.workspace.openLinkText(filePath, '/', true);
|
||||
}
|
||||
|
||||
|
|
|
|||
38
Components/expandableSection.jsx
Normal file
38
Components/expandableSection.jsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import { Box, Typography, Collapse, Grid, IconButton } from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
|
||||
const ExpandableSection = ({ title, items, itemKey, itemLabel, itemValue, themeMode, expanded, onExpand, headingColor }) => {
|
||||
return (
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex">
|
||||
{/* <IconButton style={{ padding: 0 }}> */}
|
||||
<ExpandMoreIcon
|
||||
onClick={onExpand}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit',
|
||||
transform: expanded ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s',
|
||||
}}
|
||||
/>
|
||||
{/* </IconButton> */}
|
||||
<Typography variant="subtitle1" style={{ color: headingColor, cursor: 'pointer' }} onClick={onExpand}>
|
||||
{title}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{items.map((item, index) => (
|
||||
<Grid item key={item[itemKey]}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {item[itemLabel]}: {item[itemValue]}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExpandableSection;
|
||||
|
|
@ -14,11 +14,11 @@ import SortFilter from './sortFilter';
|
|||
import AddNew from './addNew';
|
||||
import MovieTypeFilter from './typeFilter';
|
||||
import MovieSelectPopUp from './movieSelectPopUp';
|
||||
import { Platform, requestUrl } from "obsidian";
|
||||
import { Platform, requestUrl, Notice } from "obsidian";
|
||||
|
||||
|
||||
|
||||
const Header = ({ movieProperties, selectedProperties, handlePropertyChange, selectedRating, handleRatingChange, genres, selectedGenres, handleGenreChange, selectedTypes, handleTypeChange, createMarkdownFile, handleSortChange, sortOption, sortOrder, toggleSortOrder, themeMode, plugin }) => {
|
||||
const Header = ({ showTrailerAndPosterLinks, movieProperties, handleClearAllFilters, selectedProperties, handlePropertyChange, selectedRating, handleRatingChange, genres, selectedGenres, handleGenreChange, selectedTypes, handleTypeChange, createMarkdownFile, handleSortChange, sortOption, sortOrder, toggleSortOrder, themeMode, plugin }) => {
|
||||
|
||||
const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false);
|
||||
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
||||
|
|
@ -73,7 +73,7 @@ const Header = ({ movieProperties, selectedProperties, handlePropertyChange, sel
|
|||
|
||||
try {
|
||||
// Fetch detailed movie information for the selected movie
|
||||
const detailsUrl = `https://api.themoviedb.org/3/${detailsType}/${selectedItem.id}?api_key=${plugin.settings.apiKey}`;
|
||||
const detailsUrl = `https://api.themoviedb.org/3/${detailsType}/${selectedItem.id}?api_key=${plugin.settings.apiKey}&append_to_response=videos`;
|
||||
const detailsResponse = await requestUrl({
|
||||
url: detailsUrl,
|
||||
method: 'GET',
|
||||
|
|
@ -89,7 +89,35 @@ const Header = ({ movieProperties, selectedProperties, handlePropertyChange, sel
|
|||
const creditsData = await creditsResponse.json;
|
||||
const directors = creditsData.crew.filter(member => member.job === 'Director').map(director => director.name).join(', ');
|
||||
|
||||
const originalLanguage = detailsData.original_language;
|
||||
const overview = detailsData.overview;
|
||||
|
||||
let productionCompanies = '';
|
||||
if (detailsData.production_companies && detailsData.production_companies.length > 0) {
|
||||
productionCompanies = detailsData.production_companies.slice(0, 2).map(company => company.name).join(', ');
|
||||
}
|
||||
|
||||
let trailer = '';
|
||||
if (detailsData.videos && detailsData.videos.results.length > 0) {
|
||||
const trailerData = detailsData.videos.results.find(video => video.type === 'Trailer');
|
||||
if (trailerData) {
|
||||
trailer = `https://www.youtube.com/watch?v=${trailerData.key}`;
|
||||
}
|
||||
}
|
||||
|
||||
const posterLink = `https://image.tmdb.org/t/p/original${detailsData.poster_path}`;
|
||||
|
||||
let budget = null;
|
||||
let revenue = null;
|
||||
let belongsToCollection = null;
|
||||
|
||||
if (!isTvShow) {
|
||||
budget = detailsData.budget;
|
||||
revenue = detailsData.revenue;
|
||||
belongsToCollection = detailsData.belongs_to_collection ? detailsData.belongs_to_collection.name : null;
|
||||
}
|
||||
|
||||
const escapeDoubleQuotes = (str) => str.replace(/"/g, '\\"');
|
||||
const movieYAML = `---
|
||||
Title: "${detailsData.title || detailsData.name}"
|
||||
Rating: ${selectedMovieState.rating}
|
||||
|
|
@ -104,13 +132,32 @@ Cast: "${creditsData.cast.slice(0, 10).map(member => member.name).join(', ')}"
|
|||
TMDB ID: ${selectedItem.id}
|
||||
Director: "${directors}"
|
||||
tags: "tvtracker, ${selectedMovieState.type}"
|
||||
original_language: "${originalLanguage}"
|
||||
overview: "${escapeDoubleQuotes(overview)}"
|
||||
trailer: "${trailer}"
|
||||
budget: ${budget}
|
||||
revenue: ${revenue}
|
||||
belongs_to_collection: ${belongsToCollection ? `"${belongsToCollection}"` : '""'}
|
||||
production_company: "${productionCompanies}"
|
||||
---`;
|
||||
|
||||
let content = movieYAML;
|
||||
|
||||
if (showTrailerAndPosterLinks) {
|
||||
if (detailsData.poster_path) {
|
||||
content += `\n`;
|
||||
}
|
||||
if (trailer != '') {
|
||||
content += `\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const title = detailsData.title || detailsData.name; // TV shows use 'name' instead of 'title'
|
||||
const fileName = `${title.replace(/[\/\\:]/g, '_')}`; // Sanitize title for filename
|
||||
await createMarkdownFile(fileName, movieYAML);
|
||||
|
||||
await createMarkdownFile(fileName, content);
|
||||
new Notice('Successfully added. Please restart plugin to view it in the library');
|
||||
} catch (error) {
|
||||
new Notice('Error: Could not add movie');
|
||||
console.error('Error processing the selected movie:', error);
|
||||
}
|
||||
|
||||
|
|
@ -168,6 +215,15 @@ tags: "tvtracker, ${selectedMovieState.type}"
|
|||
handleTypeChange={handleTypeChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<Button variant="contained" onClick={handleClearAllFilters} style={{
|
||||
color: 'inherit', maxWidth: '100px', height: 'auto',
|
||||
whiteSpace: 'wrap',
|
||||
overflow: 'hidden',
|
||||
padding: '8px 8px',
|
||||
fontSize: '10px'
|
||||
}}>Clear All filters</Button>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
|
|
@ -184,7 +240,13 @@ tags: "tvtracker, ${selectedMovieState.type}"
|
|||
<Button onClick={handleErrorClose}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Button sx={{ color: 'inherit' }} onClick={handleOpenAddDialog}>Add New</Button>
|
||||
<Button sx={{
|
||||
color: 'inherit', maxWidth: '120px', height: 'auto',
|
||||
whiteSpace: 'wrap',
|
||||
overflow: 'hidden',
|
||||
padding: '8px 8px',
|
||||
fontSize: '12px'
|
||||
}} onClick={handleOpenAddDialog}>Add New</Button>
|
||||
<AddNew
|
||||
open={addDialogOpen}
|
||||
handleClose={handleCloseAddDialog}
|
||||
|
|
|
|||
507
Components/metricsView backup half budget metrics.jsx
Normal file
507
Components/metricsView backup half budget metrics.jsx
Normal file
|
|
@ -0,0 +1,507 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Typography, Grid, Collapse, Switch, FormControlLabel, IconButton, Checkbox, Select, MenuItem, FormControl, InputLabel } from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
|
||||
const Metrics = ({
|
||||
movies,
|
||||
topActorsNumber,
|
||||
topGenresNumber,
|
||||
topDirectorsNumber,
|
||||
topProductionCompaniesNumber,
|
||||
topCollectionsNumber,
|
||||
topPerformersNumber,
|
||||
minMoviesForMetrics,
|
||||
minMoviesForMetricsCollections,
|
||||
minMoviesForMetricsDirectors,
|
||||
movieMetricsHeadingColor,
|
||||
movieMetricsSubheadingColor,
|
||||
themeMode,
|
||||
metricsHeading
|
||||
}) => {
|
||||
const [topGenres, setTopGenres] = useState([]);
|
||||
const [topActors, setTopActors] = useState([]);
|
||||
const [totalDuration, setTotalDuration] = useState(0);
|
||||
const [topDirectors, setTopDirectors] = useState([]);
|
||||
const [topProductionCompanies, setTopProductionCompanies] = useState([]);
|
||||
const [topCollections, setTopCollections] = useState([]);
|
||||
const [underperformers, setUnderperformers] = useState([]);
|
||||
const [overperformers, setOverperformers] = useState([]);
|
||||
const [expandedMain, setExpandedMain] = useState(true);
|
||||
const [expandedGenres, setExpandedGenres] = useState(false);
|
||||
const [expandedActors, setExpandedActors] = useState(false);
|
||||
const [expandedDirectors, setExpandedDirectors] = useState(false);
|
||||
const [expandedTasteIndex, setExpandedTasteIndex] = useState(false);
|
||||
const [expandedProductionCompanies, setExpandedProductionCompanies] = useState(false);
|
||||
const [expandedCollections, setExpandedCollections] = useState(false);
|
||||
const [expandedUnderperformers, setExpandedUnderperformers] = useState(false);
|
||||
const [expandedOverperformers, setExpandedOverperformers] = useState(false);
|
||||
const [ratingMode, setRatingMode] = useState('Count');
|
||||
const [genreTasteIndexAvg, setGenreTasteIndexAvg] = useState({});
|
||||
const [includeWatchlist, setIncludeWatchlist] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
calculateMetrics();
|
||||
}, [movies, ratingMode, includeWatchlist]);
|
||||
|
||||
const parseDuration = (durationStr) => {
|
||||
const match = durationStr.match(/(\d+)\s*minutes/);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
};
|
||||
|
||||
const formatDuration = (totalMinutes) => {
|
||||
const days = Math.floor(totalMinutes / (60 * 24));
|
||||
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
const daysDisplay = days > 0 ? `${days}d ` : '';
|
||||
const hoursDisplay = hours > 0 ? `${hours}h ` : '';
|
||||
const minutesDisplay = minutes > 0 ? `${minutes}m` : '';
|
||||
return `${daysDisplay}${hoursDisplay}${minutesDisplay}`.trim();
|
||||
};
|
||||
|
||||
const calculateMetrics = () => {
|
||||
let genreCounts = {};
|
||||
let genreTasteIndexSum = {};
|
||||
let actorCounts = {};
|
||||
let genreRatingSum = {};
|
||||
let genreMovieCount = {};
|
||||
let actorRatingSum = {};
|
||||
let actorMovieCount = {};
|
||||
let durationSum = 0;
|
||||
let directorCounts = {};
|
||||
let directorRatingSum = {};
|
||||
let directorMovieCount = {};
|
||||
let productionCompanyCounts = {};
|
||||
let productionCompanyRatingSum = {};
|
||||
let productionCompanyMovieCount = {};
|
||||
let collectionCounts = {};
|
||||
let collectionRatingSum = {};
|
||||
let collectionMovieCount = {};
|
||||
let underperformersList = [];
|
||||
let overperformersList = [];
|
||||
|
||||
movies.forEach(movie => {
|
||||
if (includeWatchlist || movie.Status !== 'Watchlist') {
|
||||
const movieRating = parseFloat(movie.Rating) || 0;
|
||||
const genres = movie.Genre ? movie.Genre.split(', ') : [];
|
||||
const actors = movie.Cast ? movie.Cast.split(', ') : [];
|
||||
const director = movie.Director;
|
||||
const productionCompanies = movie['production_company'] ? movie['production_company'].split(', ') : [];
|
||||
const collection = movie['belongs_to_collection'];
|
||||
|
||||
const userRatingScaled = parseFloat(movie.Rating) * 2; // Scale to 1-10
|
||||
const publicRating = parseFloat(movie['Avg vote']);
|
||||
|
||||
const tasteIndex = userRatingScaled > 0 ? userRatingScaled / publicRating : 0;
|
||||
|
||||
genres.forEach(genre => {
|
||||
if (ratingMode === 'Count') {
|
||||
genreCounts[genre] = (genreCounts[genre] || 0) + 1;
|
||||
} else {
|
||||
genreRatingSum[genre] = (genreRatingSum[genre] || 0) + movieRating;
|
||||
}
|
||||
genreMovieCount[genre] = (genreMovieCount[genre] || 0) + 1;
|
||||
genreTasteIndexSum[genre] = (genreTasteIndexSum[genre] || 0) + tasteIndex;
|
||||
});
|
||||
|
||||
actors.forEach((actor, index) => {
|
||||
if (ratingMode === 'Count') {
|
||||
actorCounts[actor] = (actorCounts[actor] || 0) + 1;
|
||||
} else {
|
||||
let ratingMultiplier = movieRating;
|
||||
if (ratingMode === 'Balanced Rating' && index < 4) {
|
||||
ratingMultiplier *= 2;
|
||||
}
|
||||
actorRatingSum[actor] = (actorRatingSum[actor] || 0) + ratingMultiplier;
|
||||
}
|
||||
actorMovieCount[actor] = (actorMovieCount[actor] || 0) + 1;
|
||||
});
|
||||
|
||||
if (director) {
|
||||
if (ratingMode === 'Count') {
|
||||
directorCounts[director] = (directorCounts[director] || 0) + 1;
|
||||
} else {
|
||||
let ratingMultiplier = parseFloat(movie.Rating) || 0;
|
||||
if (ratingMode === 'Balanced Rating') {
|
||||
ratingMultiplier *= 1; // or any other logic for Balanced Rating
|
||||
}
|
||||
directorRatingSum[director] = (directorRatingSum[director] || 0) + ratingMultiplier;
|
||||
}
|
||||
directorMovieCount[director] = (directorMovieCount[director] || 0) + 1;
|
||||
}
|
||||
|
||||
productionCompanies.forEach((company) => {
|
||||
if (ratingMode === 'Count') {
|
||||
productionCompanyCounts[company] = (productionCompanyCounts[company] || 0) + 1;
|
||||
} else {
|
||||
productionCompanyRatingSum[company] = (productionCompanyRatingSum[company] || 0) + movieRating;
|
||||
}
|
||||
productionCompanyMovieCount[company] = (productionCompanyMovieCount[company] || 0) + 1;
|
||||
});
|
||||
|
||||
if (collection) {
|
||||
if (ratingMode === 'Count') {
|
||||
collectionCounts[collection] = (collectionCounts[collection] || 0) + 1;
|
||||
} else {
|
||||
collectionRatingSum[collection] = (collectionRatingSum[collection] || 0) + movieRating;
|
||||
}
|
||||
collectionMovieCount[collection] = (collectionMovieCount[collection] || 0) + 1;
|
||||
}
|
||||
|
||||
if (movie.Duration) {
|
||||
durationSum += parseDuration(movie.Duration);
|
||||
}
|
||||
|
||||
if (movie.budget && movie.revenue) {
|
||||
// const budget = parseFloat(movie.budget.replace(/[^0-9.-]+/g, ""));
|
||||
// const revenue = parseFloat(movie.revenue.replace(/[^0-9.-]+/g, ""));
|
||||
const budget = parseFloat(movie.budget);
|
||||
const revenue = parseFloat(movie.revenue);
|
||||
if (budget > 0) {
|
||||
const ratio = revenue / budget;
|
||||
if (ratio < 1) {
|
||||
underperformersList.push({ title: movie.Title, ratio });
|
||||
} else {
|
||||
overperformersList.push({ title: movie.Title, ratio });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (ratingMode === 'Count') {
|
||||
setTopGenres(Object.entries(genreCounts).sort((a, b) => b[1] - a[1]).slice(0, topGenresNumber));
|
||||
setTopActors(Object.entries(actorCounts).sort((a, b) => b[1] - a[1]).slice(0, topActorsNumber));
|
||||
setTopDirectors(Object.entries(directorCounts).sort((a, b) => b[1] - a[1]).slice(0, topDirectorsNumber));
|
||||
setTopProductionCompanies(Object.entries(productionCompanyCounts).sort((a, b) => b[1] - a[1]).slice(0, topProductionCompaniesNumber));
|
||||
setTopCollections(Object.entries(collectionCounts).sort((a, b) => b[1] - a[1]).slice(0, topCollectionsNumber));
|
||||
} else if (ratingMode === 'Simple Rating' || ratingMode === 'Balanced Rating') {
|
||||
setTopGenres(Object.entries(genreRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topGenresNumber));
|
||||
setTopActors(Object.entries(actorRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topActorsNumber));
|
||||
setTopDirectors(Object.entries(directorRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topDirectorsNumber));
|
||||
setTopProductionCompanies(Object.entries(productionCompanyRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topProductionCompaniesNumber));
|
||||
setTopCollections(Object.entries(collectionRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topCollectionsNumber));
|
||||
} else if (ratingMode === 'Avg Rating') {
|
||||
const avgGenreRatings = {};
|
||||
for (const genre in genreRatingSum) {
|
||||
avgGenreRatings[genre] = (genreRatingSum[genre] / genreMovieCount[genre]).toFixed(2);
|
||||
}
|
||||
setTopGenres(Object.entries(avgGenreRatings).sort((a, b) => b[1] - a[1]).slice(0, topGenresNumber));
|
||||
|
||||
const avgDirectorRatings = {};
|
||||
for (const director in directorRatingSum) {
|
||||
if (directorMovieCount[director] >= minMoviesForMetricsDirectors) {
|
||||
avgDirectorRatings[director] = (directorRatingSum[director] / directorMovieCount[director]).toFixed(2);
|
||||
}
|
||||
}
|
||||
setTopDirectors(Object.entries(avgDirectorRatings).sort((a, b) => b[1] - a[1]).slice(0, topDirectorsNumber));
|
||||
|
||||
const avgActorRatings = {};
|
||||
for (const actor in actorRatingSum) {
|
||||
if (actorMovieCount[actor] >= minMoviesForMetrics) {
|
||||
avgActorRatings[actor] = (actorRatingSum[actor] / actorMovieCount[actor]).toFixed(2);
|
||||
}
|
||||
}
|
||||
setTopActors(Object.entries(avgActorRatings).sort((a, b) => b[1] - a[1]).slice(0, topActorsNumber));
|
||||
|
||||
const avgProductionCompanyRatings = {};
|
||||
for (const company in productionCompanyRatingSum) {
|
||||
if (productionCompanyMovieCount[company] >= minMoviesForMetricsDirectors) {
|
||||
avgProductionCompanyRatings[company] = (productionCompanyRatingSum[company] / productionCompanyMovieCount[company]).toFixed(2);
|
||||
}
|
||||
}
|
||||
setTopProductionCompanies(Object.entries(avgProductionCompanyRatings).sort((a, b) => b[1] - a[1]).slice(0, topProductionCompaniesNumber));
|
||||
|
||||
const avgCollectionRatings = {};
|
||||
for (const collection in collectionRatingSum) {
|
||||
if (collectionMovieCount[collection] >= minMoviesForMetricsCollections) {
|
||||
avgCollectionRatings[collection] = (collectionRatingSum[collection] / collectionMovieCount[collection]).toFixed(2);
|
||||
}
|
||||
}
|
||||
setTopCollections(Object.entries(avgCollectionRatings).sort((a, b) => b[1] - a[1]).slice(0, topCollectionsNumber));
|
||||
}
|
||||
setTotalDuration(formatDuration(durationSum));
|
||||
|
||||
setUnderperformers(underperformersList.sort((a, b) => a.ratio - b.ratio).slice(0, topPerformersNumber));
|
||||
setOverperformers(overperformersList.sort((a, b) => b.ratio - a.ratio).slice(0, topPerformersNumber));
|
||||
|
||||
const genreTasteIndexAvg = {};
|
||||
Object.keys(genreTasteIndexSum).forEach(genre => {
|
||||
genreTasteIndexAvg[genre] = (genreTasteIndexSum[genre] / genreMovieCount[genre]).toFixed(3);
|
||||
});
|
||||
setGenreTasteIndexAvg(Object.entries(genreTasteIndexAvg)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.reduce((acc, [genre, avg]) => ({ ...acc, [genre]: avg }), {}));
|
||||
};
|
||||
|
||||
const handleRatingModeChange = (event) => {
|
||||
setRatingMode(event.target.value);
|
||||
};
|
||||
|
||||
const handleExpandClickMain = () => {
|
||||
setExpandedMain(!expandedMain);
|
||||
};
|
||||
|
||||
const handleExpandClickGenres = () => {
|
||||
setExpandedGenres(!expandedGenres);
|
||||
};
|
||||
|
||||
const handleExpandClickTasteIndex = () => {
|
||||
setExpandedTasteIndex(!expandedTasteIndex);
|
||||
};
|
||||
|
||||
const handleExpandClickActors = () => {
|
||||
setExpandedActors(!expandedActors);
|
||||
};
|
||||
|
||||
const handleExpandClickDirectors = () => {
|
||||
setExpandedDirectors(!expandedDirectors);
|
||||
};
|
||||
|
||||
const handleExpandClickProductionCompanies = () => {
|
||||
setExpandedProductionCompanies(!expandedProductionCompanies);
|
||||
};
|
||||
|
||||
const handleExpandClickCollections = () => {
|
||||
setExpandedCollections(!expandedCollections);
|
||||
};
|
||||
|
||||
const handleExpandClickUnderperformers = () => {
|
||||
setExpandedUnderperformers(!expandedUnderperformers);
|
||||
};
|
||||
|
||||
const handleExpandClickOverperformers = () => {
|
||||
setExpandedOverperformers(!expandedOverperformers);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ my: 4, borderRadius: 1, position: 'relative' }}>
|
||||
<Box display="flex" justifyContent="flex-start" alignItems="center">
|
||||
<ExpandMoreIcon onClick={handleExpandClickMain}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedMain ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography onClick={handleExpandClickMain} variant="h6" style={{ color: movieMetricsHeadingColor }}>{metricsHeading}</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedMain} timeout="auto" unmountOnExit>
|
||||
<Box display="flex" justifyContent="flex-start" alignItems="center" style={{ margin: '10px 0' }}>
|
||||
<FormControl style={{ color: 'inherit', margin: '10px 0', width: '100px' }}>
|
||||
<InputLabel id="rating-mode-label" style={{ color: 'inherit' }}>Rating Mode</InputLabel>
|
||||
<Select
|
||||
labelId="rating-mode-label"
|
||||
id="rating-mode-select"
|
||||
value={ratingMode}
|
||||
label="Rating Mode"
|
||||
onChange={handleRatingModeChange}
|
||||
sx={{
|
||||
color: 'inherit',
|
||||
'& .MuiSelect-select': { paddingLeft: '14px' },
|
||||
'& .MuiOutlinedInput-notchedOutline': { borderColor: 'inherit' },
|
||||
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: 'inherit' },
|
||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: 'inherit' },
|
||||
'& .MuiSvgIcon-root': { color: 'inherit' },
|
||||
}} // For the dropdown items
|
||||
>
|
||||
<MenuItem value="Count">Count</MenuItem>
|
||||
<MenuItem value="Simple Rating">Simple Rating</MenuItem>
|
||||
<MenuItem value="Balanced Rating">Balanced Rating</MenuItem>
|
||||
<MenuItem value="Avg Rating">Avg Rating</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={includeWatchlist}
|
||||
onChange={(event) => setIncludeWatchlist(event.target.checked)}
|
||||
name="includeWatchlist"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Include Watchlist"
|
||||
style={{ margin: '10px' }} // Adjust styling as needed
|
||||
/>
|
||||
</Box>
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickGenres}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedGenres ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickGenres}>Top {topGenresNumber} Genres:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedGenres} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topGenres.map(([genre, count], index) => (
|
||||
<Grid item key={genre}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {genre}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickActors}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedActors ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickActors}>Top {topActorsNumber} Actors:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedActors} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topActors.map(([actor, count], index) => (
|
||||
<Grid item key={actor}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {actor}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickDirectors}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedDirectors ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickDirectors}>Top {topDirectorsNumber} Directors:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedDirectors} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topDirectors.map(([director, count], index) => (
|
||||
<Grid item key={director}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {director}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex">
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickProductionCompanies}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedProductionCompanies ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickProductionCompanies}>Top {topProductionCompaniesNumber} Production Companies:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedProductionCompanies} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topProductionCompanies.map(([company, count], index) => (
|
||||
<Grid item key={company}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {company}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickCollections}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedCollections ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickCollections}>Top {topCollectionsNumber} Collections:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedCollections} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topCollections.map(([collection, count], index) => (
|
||||
<Grid item key={collection}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {collection}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickUnderperformers}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedUnderperformers ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickUnderperformers}>Top {topPerformersNumber} Underperformers:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedUnderperformers} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{underperformers.map(({ title, ratio }, index) => (
|
||||
<Grid item key={title}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {title}: {ratio.toFixed(2)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickOverperformers}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedOverperformers ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickOverperformers}>Top {topPerformersNumber} Overperformers:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedOverperformers} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{overperformers.map(({ title, ratio }, index) => (
|
||||
<Grid item key={title}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {title}: {ratio.toFixed(2)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickTasteIndex}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedTasteIndex ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickTasteIndex}>Genre Taste Index:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedTasteIndex} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{Object.entries(genreTasteIndexAvg).map(([genre, avgIndex], index) => (
|
||||
<Grid item key={genre}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {genre}: {avgIndex}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor }}>Total Movie Watching Time: {totalDuration}</Typography>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Metrics;
|
||||
394
Components/metricsView backup pre budget metrics.jsx
Normal file
394
Components/metricsView backup pre budget metrics.jsx
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
// MoviesBox.jsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Typography, Grid, Collapse, Switch, FormControlLabel, IconButton, Checkbox, Select, MenuItem, FormControl, InputLabel } from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
|
||||
|
||||
const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber, topProductionCompaniesNumber, minMoviesForMetrics, movieMetricsHeadingColor, movieMetricsSubheadingColor, themeMode, metricsHeading }) => {
|
||||
const [topGenres, setTopGenres] = useState([]);
|
||||
const [topActors, setTopActors] = useState([]);
|
||||
const [totalDuration, setTotalDuration] = useState(0);
|
||||
const [topDirectors, setTopDirectors] = useState([]);
|
||||
const [topProductionCompanies, setTopProductionCompanies] = useState([]);
|
||||
const [expandedMain, setExpandedMain] = useState(true);
|
||||
const [expandedGenres, setExpandedGenres] = useState(false);
|
||||
const [expandedActors, setExpandedActors] = useState(false);
|
||||
const [expandedDirectors, setExpandedDirectors] = useState(false);
|
||||
const [expandedTasteIndex, setExpandedTasteIndex] = useState(false);
|
||||
const [expandedProductionCompanies, setExpandedProductionCompanies] = useState(false);
|
||||
const [ratingMode, setRatingMode] = useState('Count');
|
||||
const [genreTasteIndexAvg, setGenreTasteIndexAvg] = useState({});
|
||||
const [includeWatchlist, setIncludeWatchlist] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
calculateMetrics();
|
||||
}, [movies, ratingMode, includeWatchlist]);
|
||||
|
||||
|
||||
const parseDuration = (durationStr) => {
|
||||
const match = durationStr.match(/(\d+)\s*minutes/);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
};
|
||||
|
||||
const formatDuration = (totalMinutes) => {
|
||||
const days = Math.floor(totalMinutes / (60 * 24));
|
||||
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
const daysDisplay = days > 0 ? `${days}d ` : '';
|
||||
const hoursDisplay = hours > 0 ? `${hours}h ` : '';
|
||||
const minutesDisplay = minutes > 0 ? `${minutes}m` : '';
|
||||
return `${daysDisplay}${hoursDisplay}${minutesDisplay}`.trim();
|
||||
};
|
||||
|
||||
|
||||
const calculateMetrics = () => {
|
||||
let genreCounts = {};
|
||||
let genreTasteIndexSum = {};
|
||||
let actorCounts = {};
|
||||
let genreRatingSum = {};
|
||||
let genreMovieCount = {};
|
||||
let actorRatingSum = {};
|
||||
let actorMovieCount = {};
|
||||
let durationSum = 0;
|
||||
let directorCounts = {};
|
||||
let directorRatingSum = {};
|
||||
let directorMovieCount = {};
|
||||
let productionCompanyCounts = {};
|
||||
let productionCompanyRatingSum = {};
|
||||
let productionCompanyMovieCount = {};
|
||||
|
||||
movies.forEach(movie => {
|
||||
if (includeWatchlist || movie.Status !== 'Watchlist') {
|
||||
const movieRating = parseFloat(movie.Rating) || 0;
|
||||
const genres = movie.Genre ? movie.Genre.split(', ') : [];
|
||||
const actors = movie.Cast ? movie.Cast.split(', ') : [];
|
||||
const director = movie.Director;
|
||||
const productionCompanies = movie['production_company'] ? movie['production_company'].split(', ') : [];
|
||||
|
||||
const userRatingScaled = parseFloat(movie.Rating) * 2; // Scale to 1-10
|
||||
const publicRating = parseFloat(movie['Avg vote']);
|
||||
|
||||
const tasteIndex = userRatingScaled > 0 ? userRatingScaled / publicRating : 0;
|
||||
|
||||
|
||||
genres.forEach(genre => {
|
||||
if (ratingMode === 'Count') {
|
||||
genreCounts[genre] = (genreCounts[genre] || 0) + 1;
|
||||
} else {
|
||||
genreRatingSum[genre] = (genreRatingSum[genre] || 0) + movieRating;
|
||||
}
|
||||
genreMovieCount[genre] = (genreMovieCount[genre] || 0) + 1;
|
||||
genreTasteIndexSum[genre] = (genreTasteIndexSum[genre] || 0) + tasteIndex;
|
||||
});
|
||||
|
||||
actors.forEach((actor, index) => {
|
||||
if (ratingMode === 'Count') {
|
||||
actorCounts[actor] = (actorCounts[actor] || 0) + 1;
|
||||
} else {
|
||||
let ratingMultiplier = movieRating;
|
||||
if (ratingMode === 'Balanced Rating' && index < 4) {
|
||||
ratingMultiplier *= 2;
|
||||
}
|
||||
actorRatingSum[actor] = (actorRatingSum[actor] || 0) + ratingMultiplier;
|
||||
}
|
||||
actorMovieCount[actor] = (actorMovieCount[actor] || 0) + 1;
|
||||
});
|
||||
|
||||
|
||||
if (director) {
|
||||
if (ratingMode === 'Count') {
|
||||
directorCounts[director] = (directorCounts[director] || 0) + 1;
|
||||
} else {
|
||||
let ratingMultiplier = parseFloat(movie.Rating) || 0;
|
||||
if (ratingMode === 'Balanced Rating') {
|
||||
ratingMultiplier *= 1; // or any other logic for Balanced Rating
|
||||
}
|
||||
directorRatingSum[director] = (directorRatingSum[director] || 0) + ratingMultiplier;
|
||||
}
|
||||
directorMovieCount[director] = (directorMovieCount[director] || 0) + 1;
|
||||
}
|
||||
|
||||
productionCompanies.forEach((company) => {
|
||||
if (ratingMode === 'Count') {
|
||||
productionCompanyCounts[company] = (productionCompanyCounts[company] || 0) + 1;
|
||||
} else {
|
||||
productionCompanyRatingSum[company] = (productionCompanyRatingSum[company] || 0) + movieRating;
|
||||
}
|
||||
productionCompanyMovieCount[company] = (productionCompanyMovieCount[company] || 0) + 1;
|
||||
});
|
||||
|
||||
if (movie.Duration) {
|
||||
durationSum += parseDuration(movie.Duration);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (ratingMode === 'Count') {
|
||||
setTopGenres(Object.entries(genreCounts).sort((a, b) => b[1] - a[1]).slice(0, topGenresNumber));
|
||||
setTopActors(Object.entries(actorCounts).sort((a, b) => b[1] - a[1]).slice(0, topActorsNumber));
|
||||
setTopDirectors(Object.entries(directorCounts).sort((a, b) => b[1] - a[1]).slice(0, topDirectorsNumber));
|
||||
setTopProductionCompanies(Object.entries(productionCompanyCounts).sort((a, b) => b[1] - a[1]).slice(0, topProductionCompaniesNumber));
|
||||
} else if (ratingMode === 'Simple Rating' || ratingMode === 'Balanced Rating') {
|
||||
setTopGenres(Object.entries(genreRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topGenresNumber));
|
||||
setTopActors(Object.entries(actorRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topActorsNumber));
|
||||
setTopDirectors(Object.entries(directorRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topDirectorsNumber));
|
||||
setTopProductionCompanies(Object.entries(productionCompanyRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topProductionCompaniesNumber));
|
||||
} else if (ratingMode === 'Avg Rating') {
|
||||
const avgGenreRatings = {};
|
||||
for (const genre in genreRatingSum) {
|
||||
avgGenreRatings[genre] = (genreRatingSum[genre] / genreMovieCount[genre]).toFixed(2);
|
||||
|
||||
}
|
||||
setTopGenres(Object.entries(avgGenreRatings).sort((a, b) => b[1] - a[1]).slice(0, topGenresNumber));
|
||||
|
||||
|
||||
const avgDirectorRatings = {};
|
||||
for (const director in directorRatingSum) {
|
||||
if (directorMovieCount[director] >= 3) {
|
||||
avgDirectorRatings[director] = (directorRatingSum[director] / directorMovieCount[director]).toFixed(2);
|
||||
}
|
||||
}
|
||||
setTopDirectors(Object.entries(avgDirectorRatings).sort((a, b) => b[1] - a[1]).slice(0, topDirectorsNumber));
|
||||
|
||||
|
||||
const avgActorRatings = {};
|
||||
for (const actor in actorRatingSum) {
|
||||
if (actorMovieCount[actor] >= minMoviesForMetrics) {
|
||||
avgActorRatings[actor] = (actorRatingSum[actor] / actorMovieCount[actor]).toFixed(2);
|
||||
|
||||
}
|
||||
}
|
||||
setTopActors(Object.entries(avgActorRatings).sort((a, b) => b[1] - a[1]).slice(0, topActorsNumber));
|
||||
|
||||
const avgProductionCompanyRatings = {};
|
||||
for (const company in productionCompanyRatingSum) {
|
||||
if (productionCompanyMovieCount[company] >= minMoviesForMetrics) {
|
||||
avgProductionCompanyRatings[company] = (productionCompanyRatingSum[company] / productionCompanyMovieCount[company]).toFixed(2);
|
||||
}
|
||||
}
|
||||
setTopProductionCompanies(Object.entries(avgProductionCompanyRatings).sort((a, b) => b[1] - a[1]).slice(0, topProductionCompaniesNumber));
|
||||
}
|
||||
setTotalDuration(formatDuration(durationSum));
|
||||
|
||||
const genreTasteIndexAvg = {};
|
||||
|
||||
Object.keys(genreTasteIndexSum).forEach(genre => {
|
||||
genreTasteIndexAvg[genre] = (genreTasteIndexSum[genre] / genreMovieCount[genre]).toFixed(3);
|
||||
});
|
||||
setGenreTasteIndexAvg(Object.entries(genreTasteIndexAvg)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.reduce((acc, [genre, avg]) => ({ ...acc, [genre]: avg }), {}));
|
||||
// setGenreTasteIndexAvg(genreTasteIndexAvg);
|
||||
|
||||
|
||||
};
|
||||
|
||||
const handleRatingModeChange = (event) => {
|
||||
setRatingMode(event.target.value);
|
||||
};
|
||||
|
||||
|
||||
const handleExpandClickMain = () => {
|
||||
setExpandedMain(!expandedMain);
|
||||
};
|
||||
|
||||
const handleExpandClickGenres = () => {
|
||||
setExpandedGenres(!expandedGenres);
|
||||
};
|
||||
|
||||
const handleExpandClickTasteIndex = () => {
|
||||
setExpandedTasteIndex(!expandedTasteIndex);
|
||||
};
|
||||
|
||||
const handleExpandClickActors = () => {
|
||||
setExpandedActors(!expandedActors);
|
||||
};
|
||||
|
||||
const handleExpandClickDirectors = () => {
|
||||
setExpandedDirectors(!expandedDirectors);
|
||||
};
|
||||
|
||||
const handleExpandClickProductionCompanies = () => {
|
||||
setExpandedProductionCompanies(!expandedProductionCompanies);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ my: 4, borderRadius: 1, position: 'relative' }}>
|
||||
<Box display="flex" justifyContent="flex-start" alignItems="center">
|
||||
<ExpandMoreIcon onClick={handleExpandClickMain}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedMain ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
|
||||
<Typography onClick={handleExpandClickMain} variant="h6" style={{ color: movieMetricsHeadingColor }}>{metricsHeading}</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedMain} timeout="auto" unmountOnExit>
|
||||
<Box display="flex" jjustifyContent="flex-start" alignItems="center" style={{ margin: '10px 0' }}>
|
||||
|
||||
<FormControl style={{ color: 'inherit', margin: '10px 0', width: '100px' }}>
|
||||
<InputLabel id="rating-mode-label" style={{ color: 'inherit' }}>Rating Mode</InputLabel>
|
||||
<Select
|
||||
labelId="rating-mode-label"
|
||||
id="rating-mode-select"
|
||||
value={ratingMode}
|
||||
label="Rating Mode"
|
||||
onChange={handleRatingModeChange}
|
||||
sx={{
|
||||
color: 'inherit',
|
||||
'& .MuiSelect-select': { paddingLeft: '14px' },
|
||||
'& .MuiOutlinedInput-notchedOutline': { borderColor: 'inherit' },
|
||||
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: 'inherit' },
|
||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: 'inherit' },
|
||||
'& .MuiSvgIcon-root': { color: 'inherit' },
|
||||
}} // For the dropdown items
|
||||
|
||||
>
|
||||
<MenuItem value="Count">Count</MenuItem>
|
||||
<MenuItem value="Simple Rating">Simple Rating</MenuItem>
|
||||
<MenuItem value="Balanced Rating">Balanced Rating</MenuItem>
|
||||
<MenuItem value="Avg Rating">Avg Rating</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={includeWatchlist}
|
||||
onChange={(event) => setIncludeWatchlist(event.target.checked)}
|
||||
name="includeWatchlist"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Include Watchlist"
|
||||
style={{ margin: '10px' }} // Adjust styling as needed
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickGenres}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedGenres ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickGenres}>Top {topGenresNumber} Genres:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedGenres} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topGenres.map(([genre, count], index) => (
|
||||
<Grid item key={genre}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {genre}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
|
||||
{/* Top 5 Actors */}
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickActors}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedActors ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickActors}>Top {topActorsNumber} Actors:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedActors} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topActors.map(([actor, count], index) => (
|
||||
<Grid item key={actor}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {actor}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickDirectors}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedDirectors ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickDirectors}>Top {topDirectorsNumber} Directors:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedDirectors} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topDirectors.map(([director, count], index) => (
|
||||
<Grid item key={director}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {director}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex">
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickProductionCompanies}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedProductionCompanies ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickProductionCompanies}>Top {topProductionCompaniesNumber} Production Companies:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedProductionCompanies} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topProductionCompanies.map(([company, count], index) => (
|
||||
<Grid item key={company}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {company}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickTasteIndex}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedTasteIndex ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickTasteIndex}>Genre Taste Index:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedTasteIndex} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{Object.entries(genreTasteIndexAvg).map(([genre, avgIndex], index) => (
|
||||
<Grid item key={genre}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {genre}: {avgIndex}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
|
||||
{/* Total Duration */}
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor }}>Total Movie Watching Time: {totalDuration}</Typography>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Metrics;
|
||||
|
|
@ -1,29 +1,61 @@
|
|||
// MoviesBox.jsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Typography, Grid, Collapse, Switch, FormControlLabel, IconButton, Checkbox, Select, MenuItem, FormControl, InputLabel } from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandableSection from './expandableSection';
|
||||
|
||||
|
||||
const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber, minMoviesForMetrics, movieMetricsHeadingColor, movieMetricsSubheadingColor, themeMode, metricsHeading }) => {
|
||||
const Metrics = ({
|
||||
movies,
|
||||
topActorsNumber,
|
||||
topGenresNumber,
|
||||
topDirectorsNumber,
|
||||
topProductionCompaniesNumber,
|
||||
topCollectionsNumber,
|
||||
topPerformersNumber,
|
||||
minMoviesForMetrics,
|
||||
minMoviesForMetricsCollections,
|
||||
minMoviesForMetricsDirectors,
|
||||
movieMetricsHeadingColor,
|
||||
movieMetricsSubheadingColor,
|
||||
themeMode,
|
||||
hideBudgetMetrics,
|
||||
hideGenreTasteIndexMetrics,
|
||||
budgetMetricsSubheadingColor,
|
||||
metricsHeading
|
||||
}) => {
|
||||
const [topGenres, setTopGenres] = useState([]);
|
||||
const [topActors, setTopActors] = useState([]);
|
||||
const [totalDuration, setTotalDuration] = useState(0);
|
||||
const [topDirectors, setTopDirectors] = useState([]);
|
||||
const [topProductionCompanies, setTopProductionCompanies] = useState([]);
|
||||
const [topCollections, setTopCollections] = useState([]);
|
||||
const [underperformers, setUnderperformers] = useState([]);
|
||||
const [overperformers, setOverperformers] = useState([]);
|
||||
const [highestBudgets, setHighestBudgets] = useState([]);
|
||||
const [lowestBudgets, setLowestBudgets] = useState([]);
|
||||
const [mostRevenue, setMostRevenue] = useState([]);
|
||||
const [leastRevenue, setLeastRevenue] = useState([]);
|
||||
const [expandedMain, setExpandedMain] = useState(true);
|
||||
const [expandedGenres, setExpandedGenres] = useState(false);
|
||||
const [expandedActors, setExpandedActors] = useState(false);
|
||||
const [expandedDirectors, setExpandedDirectors] = useState(false);
|
||||
const [expandedTasteIndex, setExpandedTasteIndex] = useState(false);
|
||||
const [expandedProductionCompanies, setExpandedProductionCompanies] = useState(false);
|
||||
const [expandedCollections, setExpandedCollections] = useState(false);
|
||||
const [expandedBudgetMetrics, setExpandedBudgetMetrics] = useState(false);
|
||||
const [expandedUnderperformers, setExpandedUnderperformers] = useState(false);
|
||||
const [expandedOverperformers, setExpandedOverperformers] = useState(false);
|
||||
const [expandedHighestBudgets, setExpandedHighestBudgets] = useState(false);
|
||||
const [expandedLowestBudgets, setExpandedLowestBudgets] = useState(false);
|
||||
const [expandedMostRevenue, setExpandedMostRevenue] = useState(false);
|
||||
const [expandedLeastRevenue, setExpandedLeastRevenue] = useState(false);
|
||||
const [ratingMode, setRatingMode] = useState('Count');
|
||||
const [genreTasteIndexAvg, setGenreTasteIndexAvg] = useState({});
|
||||
const [includeWatchlist, setIncludeWatchlist] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
calculateMetrics();
|
||||
}, [movies, ratingMode, includeWatchlist]);
|
||||
|
||||
|
||||
const parseDuration = (durationStr) => {
|
||||
const match = durationStr.match(/(\d+)\s*minutes/);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
|
|
@ -39,7 +71,6 @@ const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber,
|
|||
return `${daysDisplay}${hoursDisplay}${minutesDisplay}`.trim();
|
||||
};
|
||||
|
||||
|
||||
const calculateMetrics = () => {
|
||||
let genreCounts = {};
|
||||
let genreTasteIndexSum = {};
|
||||
|
|
@ -52,6 +83,18 @@ const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber,
|
|||
let directorCounts = {};
|
||||
let directorRatingSum = {};
|
||||
let directorMovieCount = {};
|
||||
let productionCompanyCounts = {};
|
||||
let productionCompanyRatingSum = {};
|
||||
let productionCompanyMovieCount = {};
|
||||
let collectionCounts = {};
|
||||
let collectionRatingSum = {};
|
||||
let collectionMovieCount = {};
|
||||
let underperformersList = [];
|
||||
let overperformersList = [];
|
||||
let highestBudgetsList = [];
|
||||
let lowestBudgetsList = [];
|
||||
let mostRevenueList = [];
|
||||
let leastRevenueList = [];
|
||||
|
||||
movies.forEach(movie => {
|
||||
if (includeWatchlist || movie.Status !== 'Watchlist') {
|
||||
|
|
@ -59,13 +102,14 @@ const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber,
|
|||
const genres = movie.Genre ? movie.Genre.split(', ') : [];
|
||||
const actors = movie.Cast ? movie.Cast.split(', ') : [];
|
||||
const director = movie.Director;
|
||||
const productionCompanies = movie['production_company'] ? movie['production_company'].split(', ') : [];
|
||||
const collection = movie['belongs_to_collection'];
|
||||
|
||||
const userRatingScaled = parseFloat(movie.Rating) * 2; // Scale to 1-10
|
||||
const publicRating = parseFloat(movie['Avg vote']);
|
||||
|
||||
const tasteIndex = userRatingScaled > 0 ? userRatingScaled / publicRating : 0;
|
||||
|
||||
|
||||
genres.forEach(genre => {
|
||||
if (ratingMode === 'Count') {
|
||||
genreCounts[genre] = (genreCounts[genre] || 0) + 1;
|
||||
|
|
@ -89,7 +133,6 @@ const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber,
|
|||
actorMovieCount[actor] = (actorMovieCount[actor] || 0) + 1;
|
||||
});
|
||||
|
||||
|
||||
if (director) {
|
||||
if (ratingMode === 'Count') {
|
||||
directorCounts[director] = (directorCounts[director] || 0) + 1;
|
||||
|
|
@ -103,68 +146,133 @@ const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber,
|
|||
directorMovieCount[director] = (directorMovieCount[director] || 0) + 1;
|
||||
}
|
||||
|
||||
productionCompanies.forEach((company) => {
|
||||
if (ratingMode === 'Count') {
|
||||
productionCompanyCounts[company] = (productionCompanyCounts[company] || 0) + 1;
|
||||
} else {
|
||||
productionCompanyRatingSum[company] = (productionCompanyRatingSum[company] || 0) + movieRating;
|
||||
}
|
||||
productionCompanyMovieCount[company] = (productionCompanyMovieCount[company] || 0) + 1;
|
||||
});
|
||||
|
||||
if (collection) {
|
||||
if (ratingMode === 'Count') {
|
||||
collectionCounts[collection] = (collectionCounts[collection] || 0) + 1;
|
||||
} else {
|
||||
collectionRatingSum[collection] = (collectionRatingSum[collection] || 0) + movieRating;
|
||||
}
|
||||
collectionMovieCount[collection] = (collectionMovieCount[collection] || 0) + 1;
|
||||
}
|
||||
|
||||
if (movie.Duration) {
|
||||
durationSum += parseDuration(movie.Duration);
|
||||
}
|
||||
}
|
||||
|
||||
if (movie.budget && movie.revenue) {
|
||||
const budget = parseFloat(movie.budget);
|
||||
const revenue = parseFloat(movie.revenue);
|
||||
if (budget > 0) {
|
||||
const ratio = revenue / budget;
|
||||
if (ratio < 1) {
|
||||
underperformersList.push({ title: movie.Title, ratio });
|
||||
} else {
|
||||
overperformersList.push({ title: movie.Title, ratio });
|
||||
}
|
||||
highestBudgetsList.push({ title: movie.Title, budget });
|
||||
lowestBudgetsList.push({ title: movie.Title, budget });
|
||||
mostRevenueList.push({ title: movie.Title, revenue });
|
||||
leastRevenueList.push({ title: movie.Title, revenue });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (ratingMode === 'Count') {
|
||||
setTopGenres(Object.entries(genreCounts).sort((a, b) => b[1] - a[1]).slice(0, topGenresNumber));
|
||||
setTopActors(Object.entries(actorCounts).sort((a, b) => b[1] - a[1]).slice(0, topActorsNumber));
|
||||
setTopDirectors(Object.entries(directorCounts).sort((a, b) => b[1] - a[1]).slice(0, topDirectorsNumber));
|
||||
setTopProductionCompanies(Object.entries(productionCompanyCounts).sort((a, b) => b[1] - a[1]).slice(0, topProductionCompaniesNumber));
|
||||
setTopCollections(Object.entries(collectionCounts).sort((a, b) => b[1] - a[1]).slice(0, topCollectionsNumber));
|
||||
} else if (ratingMode === 'Simple Rating' || ratingMode === 'Balanced Rating') {
|
||||
setTopGenres(Object.entries(genreRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topGenresNumber));
|
||||
setTopActors(Object.entries(actorRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topActorsNumber));
|
||||
setTopDirectors(Object.entries(directorRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topDirectorsNumber));
|
||||
setTopProductionCompanies(Object.entries(productionCompanyRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topProductionCompaniesNumber));
|
||||
setTopCollections(Object.entries(collectionRatingSum).sort((a, b) => b[1] - a[1]).slice(0, topCollectionsNumber));
|
||||
} else if (ratingMode === 'Avg Rating') {
|
||||
const avgGenreRatings = {};
|
||||
for (const genre in genreRatingSum) {
|
||||
avgGenreRatings[genre] = (genreRatingSum[genre] / genreMovieCount[genre]).toFixed(2);
|
||||
|
||||
}
|
||||
setTopGenres(Object.entries(avgGenreRatings).sort((a, b) => b[1] - a[1]).slice(0, topGenresNumber));
|
||||
|
||||
|
||||
const avgDirectorRatings = {};
|
||||
for (const director in directorRatingSum) {
|
||||
if (directorMovieCount[director] >= 3) {
|
||||
if (directorMovieCount[director] >= minMoviesForMetricsDirectors) {
|
||||
avgDirectorRatings[director] = (directorRatingSum[director] / directorMovieCount[director]).toFixed(2);
|
||||
}
|
||||
}
|
||||
setTopDirectors(Object.entries(avgDirectorRatings).sort((a, b) => b[1] - a[1]).slice(0, topDirectorsNumber));
|
||||
|
||||
|
||||
const avgActorRatings = {};
|
||||
for (const actor in actorRatingSum) {
|
||||
if (actorMovieCount[actor] >= minMoviesForMetrics) {
|
||||
avgActorRatings[actor] = (actorRatingSum[actor] / actorMovieCount[actor]).toFixed(2);
|
||||
|
||||
}
|
||||
}
|
||||
setTopActors(Object.entries(avgActorRatings).sort((a, b) => b[1] - a[1]).slice(0, topActorsNumber));
|
||||
|
||||
const avgProductionCompanyRatings = {};
|
||||
for (const company in productionCompanyRatingSum) {
|
||||
if (productionCompanyMovieCount[company] >= minMoviesForMetricsDirectors) {
|
||||
avgProductionCompanyRatings[company] = (productionCompanyRatingSum[company] / productionCompanyMovieCount[company]).toFixed(2);
|
||||
}
|
||||
}
|
||||
setTopProductionCompanies(Object.entries(avgProductionCompanyRatings).sort((a, b) => b[1] - a[1]).slice(0, topProductionCompaniesNumber));
|
||||
|
||||
const avgCollectionRatings = {};
|
||||
for (const collection in collectionRatingSum) {
|
||||
if (collectionMovieCount[collection] >= minMoviesForMetricsCollections) {
|
||||
avgCollectionRatings[collection] = (collectionRatingSum[collection] / collectionMovieCount[collection]).toFixed(2);
|
||||
}
|
||||
}
|
||||
setTopCollections(Object.entries(avgCollectionRatings).sort((a, b) => b[1] - a[1]).slice(0, topCollectionsNumber));
|
||||
}
|
||||
setTotalDuration(formatDuration(durationSum));
|
||||
|
||||
const genreTasteIndexAvg = {};
|
||||
setUnderperformers(underperformersList.sort((a, b) => a.ratio - b.ratio).slice(0, topPerformersNumber));
|
||||
setOverperformers(overperformersList.sort((a, b) => b.ratio - a.ratio).slice(0, topPerformersNumber));
|
||||
setHighestBudgets(highestBudgetsList.sort((a, b) => b.budget - a.budget).slice(0, topPerformersNumber));
|
||||
setLowestBudgets(lowestBudgetsList.sort((a, b) => a.budget - b.budget).slice(0, topPerformersNumber));
|
||||
setMostRevenue(mostRevenueList.sort((a, b) => b.revenue - a.revenue).slice(0, topPerformersNumber));
|
||||
setLeastRevenue(leastRevenueList.sort((a, b) => a.revenue - b.revenue).slice(0, topPerformersNumber));
|
||||
|
||||
const genreTasteIndexAvg = {};
|
||||
Object.keys(genreTasteIndexSum).forEach(genre => {
|
||||
genreTasteIndexAvg[genre] = (genreTasteIndexSum[genre] / genreMovieCount[genre]).toFixed(3);
|
||||
});
|
||||
setGenreTasteIndexAvg(Object.entries(genreTasteIndexAvg)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.reduce((acc, [genre, avg]) => ({ ...acc, [genre]: avg }), {}));
|
||||
// setGenreTasteIndexAvg(genreTasteIndexAvg);
|
||||
|
||||
|
||||
};
|
||||
|
||||
function formatMoney(revenue) {
|
||||
if (revenue >= 1e9) {
|
||||
return (revenue / 1e9).toFixed(1) + 'Billion USD';
|
||||
} else if (revenue >= 1e6) {
|
||||
return (revenue / 1e6).toFixed(1) + 'Million USD';
|
||||
} else if (revenue >= 1e3) {
|
||||
return (revenue / 1e3).toFixed(0) + 'K USD';
|
||||
} else {
|
||||
return revenue.toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleRatingModeChange = (event) => {
|
||||
setRatingMode(event.target.value);
|
||||
};
|
||||
|
||||
|
||||
const handleExpandClickMain = () => {
|
||||
setExpandedMain(!expandedMain);
|
||||
};
|
||||
|
|
@ -185,6 +293,43 @@ const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber,
|
|||
setExpandedDirectors(!expandedDirectors);
|
||||
};
|
||||
|
||||
const handleExpandClickProductionCompanies = () => {
|
||||
setExpandedProductionCompanies(!expandedProductionCompanies);
|
||||
};
|
||||
|
||||
const handleExpandClickCollections = () => {
|
||||
setExpandedCollections(!expandedCollections);
|
||||
};
|
||||
|
||||
const handleExpandClickBudgetMetrics = () => {
|
||||
setExpandedBudgetMetrics(!expandedBudgetMetrics);
|
||||
};
|
||||
|
||||
const handleExpandClickUnderperformers = () => {
|
||||
setExpandedUnderperformers(!expandedUnderperformers);
|
||||
};
|
||||
|
||||
const handleExpandClickOverperformers = () => {
|
||||
setExpandedOverperformers(!expandedOverperformers);
|
||||
};
|
||||
|
||||
const handleExpandClickHighestBudgets = () => {
|
||||
setExpandedHighestBudgets(!expandedHighestBudgets);
|
||||
};
|
||||
|
||||
const handleExpandClickLowestBudgets = () => {
|
||||
setExpandedLowestBudgets(!expandedLowestBudgets);
|
||||
};
|
||||
|
||||
const handleExpandClickMostRevenue = () => {
|
||||
setExpandedMostRevenue(!expandedMostRevenue);
|
||||
};
|
||||
|
||||
const handleExpandClickLeastRevenue = () => {
|
||||
setExpandedLeastRevenue(!expandedLeastRevenue);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box sx={{ my: 4, borderRadius: 1, position: 'relative' }}>
|
||||
<Box display="flex" justifyContent="flex-start" alignItems="center">
|
||||
|
|
@ -193,12 +338,10 @@ const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber,
|
|||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedMain ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
|
||||
<Typography onClick={handleExpandClickMain} variant="h6" style={{ color: movieMetricsHeadingColor }}>{metricsHeading}</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedMain} timeout="auto" unmountOnExit>
|
||||
<Box display="flex" jjustifyContent="flex-start" alignItems="center" style={{ margin: '10px 0' }}>
|
||||
|
||||
<Box display="flex" justifyContent="flex-start" alignItems="center" style={{ margin: '10px 0' }}>
|
||||
<FormControl style={{ color: 'inherit', margin: '10px 0', width: '100px' }}>
|
||||
<InputLabel id="rating-mode-label" style={{ color: 'inherit' }}>Rating Mode</InputLabel>
|
||||
<Select
|
||||
|
|
@ -215,7 +358,6 @@ const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber,
|
|||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: 'inherit' },
|
||||
'& .MuiSvgIcon-root': { color: 'inherit' },
|
||||
}} // For the dropdown items
|
||||
|
||||
>
|
||||
<MenuItem value="Count">Count</MenuItem>
|
||||
<MenuItem value="Simple Rating">Simple Rating</MenuItem>
|
||||
|
|
@ -223,7 +365,6 @@ const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber,
|
|||
<MenuItem value="Avg Rating">Avg Rating</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
|
|
@ -238,101 +379,159 @@ const Metrics = ({ movies, topActorsNumber, topGenresNumber, topDirectorsNumber,
|
|||
/>
|
||||
</Box>
|
||||
|
||||
<ExpandableSection
|
||||
title={`Top ${topGenresNumber} Genres:`}
|
||||
items={topGenres.map(([genre, count]) => ({ genre, count }))}
|
||||
itemKey="genre"
|
||||
itemLabel="genre"
|
||||
itemValue="count"
|
||||
headingColor={movieMetricsSubheadingColor}
|
||||
themeMode={themeMode}
|
||||
expanded={expandedGenres}
|
||||
onExpand={handleExpandClickGenres}
|
||||
/>
|
||||
<ExpandableSection
|
||||
title={`Top ${topActorsNumber} Actors:`}
|
||||
items={topActors.map(([actor, count]) => ({ actor, count }))}
|
||||
itemKey="actor"
|
||||
itemLabel="actor"
|
||||
itemValue="count"
|
||||
headingColor={movieMetricsSubheadingColor}
|
||||
themeMode={themeMode}
|
||||
expanded={expandedActors}
|
||||
onExpand={handleExpandClickActors}
|
||||
/>
|
||||
<ExpandableSection
|
||||
title={`Top ${topDirectorsNumber} Directors:`}
|
||||
items={topDirectors.map(([director, count]) => ({ director, count }))}
|
||||
itemKey="director"
|
||||
itemLabel="director"
|
||||
itemValue="count"
|
||||
headingColor={movieMetricsSubheadingColor}
|
||||
themeMode={themeMode}
|
||||
expanded={expandedDirectors}
|
||||
onExpand={handleExpandClickDirectors}
|
||||
/>
|
||||
<ExpandableSection
|
||||
title={`Top ${topProductionCompaniesNumber} Production Companies:`}
|
||||
items={topProductionCompanies.map(([company, count]) => ({ company, count }))}
|
||||
itemKey="company"
|
||||
itemLabel="company"
|
||||
itemValue="count"
|
||||
headingColor={movieMetricsSubheadingColor}
|
||||
themeMode={themeMode}
|
||||
expanded={expandedProductionCompanies}
|
||||
onExpand={handleExpandClickProductionCompanies}
|
||||
/>
|
||||
<ExpandableSection
|
||||
title={`Top ${topCollectionsNumber} Collections:`}
|
||||
items={topCollections.map(([collection, count]) => ({ collection, count }))}
|
||||
itemKey="collection"
|
||||
itemLabel="collection"
|
||||
itemValue="count"
|
||||
headingColor={movieMetricsSubheadingColor}
|
||||
themeMode={themeMode}
|
||||
expanded={expandedCollections}
|
||||
onExpand={handleExpandClickCollections}
|
||||
/>
|
||||
|
||||
{!hideGenreTasteIndexMetrics && (
|
||||
<ExpandableSection
|
||||
title={`Genre Taste Index:`}
|
||||
items={Object.entries(genreTasteIndexAvg).map(([genre, avgIndex]) => ({ genre, avgIndex }))}
|
||||
itemKey="genre"
|
||||
itemLabel="genre"
|
||||
itemValue="avgIndex"
|
||||
headingColor={movieMetricsSubheadingColor}
|
||||
themeMode={themeMode}
|
||||
expanded={expandedTasteIndex}
|
||||
onExpand={handleExpandClickTasteIndex}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickGenres}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedGenres ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickGenres}>Top {topGenresNumber} Genres:</Typography>
|
||||
{!hideBudgetMetrics && (
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickBudgetMetrics}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedBudgetMetrics ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickBudgetMetrics}>Budget Metrics:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedBudgetMetrics} timeout="auto" unmountOnExit>
|
||||
<Box style={{ paddingLeft: '15px' }}>
|
||||
<ExpandableSection
|
||||
title={`Top ${topPerformersNumber} Underperformers:`}
|
||||
items={underperformers.map(({ title, ratio }) => ({ title, value: ratio.toFixed(2) }))}
|
||||
itemKey="title"
|
||||
itemLabel="title"
|
||||
itemValue="value"
|
||||
themeMode={themeMode}
|
||||
headingColor={budgetMetricsSubheadingColor}
|
||||
expanded={expandedUnderperformers}
|
||||
onExpand={handleExpandClickUnderperformers}
|
||||
/>
|
||||
<ExpandableSection
|
||||
title={`Top ${topPerformersNumber} Overperformers:`}
|
||||
items={overperformers.map(({ title, ratio }) => ({ title, value: ratio.toFixed(2) }))}
|
||||
itemKey="title"
|
||||
itemLabel="title"
|
||||
itemValue="value"
|
||||
themeMode={themeMode}
|
||||
headingColor={budgetMetricsSubheadingColor}
|
||||
expanded={expandedOverperformers}
|
||||
onExpand={handleExpandClickOverperformers}
|
||||
/>
|
||||
<ExpandableSection
|
||||
title={`Top ${topPerformersNumber} Highest Budgets:`}
|
||||
items={highestBudgets.map(({ title, budget }) => ({ title, value: formatMoney(budget) }))}
|
||||
itemKey="title"
|
||||
itemLabel="title"
|
||||
itemValue="value"
|
||||
themeMode={themeMode}
|
||||
headingColor={budgetMetricsSubheadingColor}
|
||||
expanded={expandedHighestBudgets}
|
||||
onExpand={handleExpandClickHighestBudgets}
|
||||
/>
|
||||
<ExpandableSection
|
||||
title={`Top ${topPerformersNumber} Lowest Budgets:`}
|
||||
items={lowestBudgets.map(({ title, budget }) => ({ title, value: formatMoney(budget) }))}
|
||||
itemKey="title"
|
||||
itemLabel="title"
|
||||
itemValue="value"
|
||||
themeMode={themeMode}
|
||||
headingColor={budgetMetricsSubheadingColor}
|
||||
expanded={expandedLowestBudgets}
|
||||
onExpand={handleExpandClickLowestBudgets}
|
||||
/>
|
||||
<ExpandableSection
|
||||
title={`Top ${topPerformersNumber} Most Revenue:`}
|
||||
items={mostRevenue.map(({ title, revenue }) => ({ title, value: formatMoney(revenue) }))}
|
||||
itemKey="title"
|
||||
itemLabel="title"
|
||||
itemValue="value"
|
||||
themeMode={themeMode}
|
||||
headingColor={budgetMetricsSubheadingColor}
|
||||
expanded={expandedMostRevenue}
|
||||
onExpand={handleExpandClickMostRevenue}
|
||||
/>
|
||||
<ExpandableSection
|
||||
title={`Top ${topPerformersNumber} Least Revenue:`}
|
||||
items={leastRevenue.map(({ title, revenue }) => ({ title, value: formatMoney(revenue) }))}
|
||||
itemKey="title"
|
||||
itemLabel="title"
|
||||
itemValue="value"
|
||||
themeMode={themeMode}
|
||||
headingColor={budgetMetricsSubheadingColor}
|
||||
expanded={expandedLeastRevenue}
|
||||
onExpand={handleExpandClickLeastRevenue}
|
||||
/>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
<Collapse in={expandedGenres} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topGenres.map(([genre, count], index) => (
|
||||
<Grid item key={genre}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {genre}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
|
||||
{/* Top 5 Actors */}
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickActors}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedActors ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickActors}>Top {topActorsNumber} Actors:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedActors} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topActors.map(([actor, count], index) => (
|
||||
<Grid item key={actor}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {actor}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickDirectors}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedDirectors ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickDirectors}>Top {topDirectorsNumber} Directors:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedDirectors} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{topDirectors.map(([director, count], index) => (
|
||||
<Grid item key={director}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {director}: {count}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
|
||||
<Box style={{ paddingBottom: '15px' }}>
|
||||
<Box display="flex" >
|
||||
<ExpandMoreIcon
|
||||
onClick={handleExpandClickTasteIndex}
|
||||
style={{
|
||||
color: themeMode === 'dark' ? 'white' : 'inherit', transform: expandedTasteIndex ? 'rotate(0deg)' : 'rotate(270deg)',
|
||||
transition: 'transform 0.3s'
|
||||
}} />
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor, cursor: 'pointer' }} onClick={handleExpandClickTasteIndex}>Genre Taste Index:</Typography>
|
||||
</Box>
|
||||
<Collapse in={expandedTasteIndex} timeout="auto" unmountOnExit>
|
||||
<Grid container spacing={2}>
|
||||
{Object.entries(genreTasteIndexAvg).map(([genre, avgIndex], index) => (
|
||||
<Grid item key={genre}>
|
||||
<Typography variant="body1" style={{ color: 'inherit' }}>
|
||||
{index + 1}. {genre}: {avgIndex}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</Box>
|
||||
|
||||
{/* Total Duration */}
|
||||
)}
|
||||
<Typography variant="subtitle1" style={{ color: movieMetricsSubheadingColor }}>Total Movie Watching Time: {totalDuration}</Typography>
|
||||
</Collapse>
|
||||
</Box>
|
||||
|
|
|
|||
56
README.md
56
README.md
|
|
@ -2,12 +2,37 @@
|
|||
|
||||
Movie and TV show tracker plugin for Obsidian
|
||||
|
||||
I work on this in my spare time. If you enjoy using this, please say Thank you or
|
||||
# New in v1.3.0
|
||||
|
||||
- Added more properties
|
||||
|
||||
1. Overview
|
||||
2. Trailer link
|
||||
3. Original Language
|
||||
4. Production Company
|
||||
5. Belongs to Collection
|
||||
6. Budget
|
||||
7. Revenue
|
||||
|
||||
- More Metrics
|
||||
|
||||
1. Top Production Companies
|
||||
2. Top Collections
|
||||
3. Budget Metrics
|
||||
a. Highest and Lowest Revenue
|
||||
b. Highest and Lowest Budget
|
||||
c. Under and Over Performer (Ratio of Revenue to Budget)
|
||||
|
||||
- Added Clear all filters button
|
||||
- Can search using Production companies now
|
||||
- Improved Error handling and Notices
|
||||
|
||||
I work on this in my spare time. If you enjoy using this, please say Thank you or
|
||||
[](https://www.buymeacoffee.com/shreshthmehra)
|
||||
|
||||
Happy to implement any feature requests you have or better yet, if you wish you can create pull requests.
|
||||
|
||||
## Showcase
|
||||
## Showcase as of v1.1.0 (More features in v1.3.0)
|
||||
|
||||

|
||||
|
||||
|
|
@ -15,11 +40,9 @@ Happy to implement any feature requests you have or better yet, if you wish you
|
|||
|
||||

|
||||
|
||||
|
||||
|
||||
# Description
|
||||
|
||||
A simple Movie and TV show library to keep track of favourite movies and shows. The discover feature helps you find new movies and shows based on your library and ratings. Better and more algorithms for discover new coming soon.
|
||||
A simple Movie and TV show library to keep track of favourite movies and shows. The discover feature helps you find new movies and shows based on your library and ratings. Better and more algorithms for discover new coming soon.
|
||||
|
||||
Each Movie and TV show is saved as a markdown file in a folder of your choice (Specified in the settings). Each file has YAML content which is used to Display, filter and sort content. To be able to add new Movies and TV shows you will need to get your personal API key from TMDB. Simply create an account at https://www.themoviedb.org/signup and get the API key. You will then have to enter your API key in the settings for this plugin.
|
||||
|
||||
|
|
@ -52,31 +75,38 @@ If you wish to batch add titles from a csv, I can provide python scripts that I
|
|||
|
||||
## Discover
|
||||
|
||||
After selecting the genre you will be shown titles from your own library in that genre. You can then select the type of movies/show you are in a mood for and then the algorithm recommends new movies/shows you haven't seen.
|
||||
After selecting the genre you will be shown titles from your own library in that genre. You can then select the type of movies/show you are in a mood for and then the algorithm recommends new movies/shows you haven't seen.
|
||||
|
||||
## Metrics
|
||||
|
||||
The Movie Metrics shows top Genres, Actors, Directors and total viewing time from your library. There are 4 options to choose from for deciding the metric for Top in each category. The total viewing time only displays the viewing time of Movies and does not include Series
|
||||
The Movie Metrics shows top Genres, Actors, Directors, Production Companies and total viewing time from your library. It also shows Budget metrics and Genre taste index.
|
||||
|
||||
There are 4 options to choose from for deciding the metric for Top in each category. The total viewing time only displays the viewing time of Movies and does not include Series.
|
||||
|
||||
1. Count - Based on how many titles are there in your library for the category
|
||||
2. Simple Rating - Based on sum of Rating for each category. For example, when evaluating Top Actors the points for say Sandra Bullock will be calculated as sum of the ratings for each of her movies and shows in your library
|
||||
3. Balance Rating - Similar to Simple rating but an Actor gets more points if they are in the 4 Cast members for a title. For Top Genre and Directors acts the same way as Simple Rating
|
||||
2. Simple Rating - Based on sum of Rating for each category. For example, when evaluating Top Actors the points for Sandra Bullock will be calculated as the sum of the ratings for each of her movies and tv shows in your library.
|
||||
3. Balanced Rating - Similar to Simple rating but an Actor gets more points if they are in the first 4 Cast members for a title. For Top Genre, Directors, Production Companies act the same way as Simple Rating
|
||||
4. Avg Rating - Avg rating for the person/genre if they have a minimum number of titles (This number can be changed from settings)
|
||||
|
||||
Genre taste index is the ration of User rating to public rating for each of the title in that genre. So, if your genre taste index for Documentries is 1.2 that means that you rate documentary movies 1.2 times higher on average than public ratings. Keep in mind that the public ratings are fetched from TMDB and are on a scale of 1 to 10. The user rating is multiplied by 2 for this calculation to be on the same scale. Additionally, the rating mode does not affect this metric.
|
||||
|
||||
## Settings
|
||||
|
||||
There are 13 configurable settings. The two most improtant ones are the TMDB API key and the Folder path. The folder path is relative to the root of your obsidian vault (where the .obsidian folder is). So if the content is in a folder called Movies which is in the root, then the path would simply be 'Movies'
|
||||
The plugin was designed for high customization and hence has multiple configurable settings. The two most improtant ones are the TMDB API key and the Folder path. The folder path is relative to the root of your obsidian vault (where the .obsidian folder is). So if the content is in a folder called Movies which is in the root, then the path would simply be 'Movies'
|
||||
|
||||
### Updating Files
|
||||
|
||||
As of version 1.3.0 options to update previously existing files has been added. I have tested the plugin on multiple test cases but please keep a backup of your library before proceeding. If any errors occur, please create an issue on Github and I will work towards solving it as soon as possible.
|
||||
|
||||
# Key things to note
|
||||
|
||||
1. When entering a movie in the watchlist. Please enter the rating as 1 or higher otherwise the movie will not be displayed
|
||||
2. After adding a title, the TV tracker will ahve to be reloaded for the title to show up.
|
||||
2. After adding a title, the TV tracker will have to be reloaded for the title to show up.
|
||||
3. If the movie has a special character in it's name such as '!' , ':' , '?' or '&' . Please remove the special character from the markdown file name after creating it through Add new.
|
||||
4. If there are no results to show for a search then all titles will be displayed but the Showing results will read 0
|
||||
5. If the entire library is large and cache was recently cleared or does not exist yet then incorrect Poster paths might be displayed for a while. Please be patient at this point as it will eventually resolve itself
|
||||
5. If the entire library is large and cache was recently cleared or does not exist yet then incorrect Poster paths might be displayed for a while. Please be patient as it will eventually resolve itself.
|
||||
6. If you change the theme in your vault from Light ot Dark or the other way around. Please turn the plugin off and then turn it on again for necessary changes to reflect.
|
||||
|
||||
|
||||
# Attribution
|
||||
|
||||
This product uses the TMDB API but is not endorsed or certified by TMDB. You will have to obtain your own personal TMDB API to use this plugin.The TMDB API is used for fetching all necessary details about each title including but not limited to Official title, poster, Cast members, Genre, Avg rating, Popularity etc.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import SearchIcon from '@mui/icons-material/Search';
|
|||
import Checkbox from '@mui/material/Checkbox';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Metrics from './Components/metricsView';
|
||||
import { Platform } from "obsidian";
|
||||
import { Platform, Notice } from "obsidian";
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import DiscoverPopup from './Components/discoverView'
|
||||
|
||||
|
|
@ -78,7 +78,13 @@ export const ReactView = ({ moviesData, createMarkdownFile, themeMode, plugin })
|
|||
// Handler for closing the Discover popup
|
||||
const closeDiscoverPopup = () => setShowDiscoverPopup(false);
|
||||
|
||||
|
||||
const handleClearFilters = () => {
|
||||
setSearchTerm('');
|
||||
setSelectedGenres([]);
|
||||
setSelectedTypes([]);
|
||||
setSelectedRating(1);
|
||||
setShowWatchlist(false);
|
||||
};
|
||||
|
||||
const handleGenreChange = (event) => {
|
||||
const {
|
||||
|
|
@ -86,6 +92,7 @@ export const ReactView = ({ moviesData, createMarkdownFile, themeMode, plugin })
|
|||
} = event;
|
||||
|
||||
setSelectedGenres(typeof value === 'string' ? value.split(',') : value);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -168,24 +175,34 @@ export const ReactView = ({ moviesData, createMarkdownFile, themeMode, plugin })
|
|||
|
||||
|
||||
const applyFilters = () => {
|
||||
|
||||
const lowerCaseSearchTerm = debouncedSearchTerm.toLowerCase();
|
||||
|
||||
|
||||
const filtered = movies.filter(movie => {
|
||||
try {
|
||||
const matchesTitle = movie.Title && movie.Title.toString().toLowerCase().includes(lowerCaseSearchTerm);
|
||||
const matchesDirector = movie.Director ? movie.Director.toLowerCase().includes(lowerCaseSearchTerm) : false;
|
||||
|
||||
const matchesTitle = movie.Title && movie.Title.toLowerCase().includes(lowerCaseSearchTerm);
|
||||
const matchesDirector = movie.Director ? movie.Director.toLowerCase().includes(lowerCaseSearchTerm) : false;
|
||||
const matchesCast = movie.Cast && movie.Cast.split(', ').some(castMember =>
|
||||
castMember.toLowerCase().includes(lowerCaseSearchTerm)
|
||||
);
|
||||
const matchesProductionComapny = movie.production_company && movie.production_company.split(', ').some(producer =>
|
||||
producer.toLowerCase().includes(lowerCaseSearchTerm)
|
||||
);
|
||||
|
||||
const matchesCast = movie.Cast && movie.Cast.split(', ').some(castMember =>
|
||||
castMember.toLowerCase().includes(lowerCaseSearchTerm)
|
||||
);
|
||||
const matchesGenre = selectedGenres.length === 0 || selectedGenres.some(Genre => movie.Genre.split(', ').includes(Genre));
|
||||
|
||||
const matchesGenre = selectedGenres.length === 0 || selectedGenres.some(Genre => movie.Genre.split(', ').includes(Genre));
|
||||
const matchesType = selectedTypes.length === 0 || selectedTypes.includes(movie.Type);
|
||||
const matchesRating = movie.Rating >= selectedRating;
|
||||
const matchesWatchlist = !showWatchlist || movie.Status === 'Watchlist';
|
||||
const matchesType = selectedTypes.length === 0 || selectedTypes.includes(movie.Type);
|
||||
const matchesRating = movie.Rating >= selectedRating;
|
||||
const matchesWatchlist = !showWatchlist || movie.Status === 'Watchlist';
|
||||
|
||||
return matchesGenre && matchesType && matchesRating && (matchesTitle || matchesCast || matchesDirector) && matchesWatchlist;
|
||||
return matchesGenre && matchesType && matchesRating && (matchesTitle || matchesCast || matchesDirector || matchesProductionComapny) && matchesWatchlist;
|
||||
} catch (error) {
|
||||
new Notice(`Error processing movie ${movie.Title}. Showing rest of the results. Please console for detailed error`);
|
||||
console.error(`Error processing movie ${movie.Title}: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const sortedMovies = sortMovies(filtered);
|
||||
|
|
@ -204,6 +221,7 @@ export const ReactView = ({ moviesData, createMarkdownFile, themeMode, plugin })
|
|||
TV Tracker
|
||||
</Typography>
|
||||
<Header
|
||||
showTrailerAndPosterLinks={plugin.settings.showTrailerAndPosterLinks}
|
||||
movieProperties={movieProperties}
|
||||
selectedProperties={selectedProperties}
|
||||
handlePropertyChange={handlePropertyChange}
|
||||
|
|
@ -221,6 +239,7 @@ export const ReactView = ({ moviesData, createMarkdownFile, themeMode, plugin })
|
|||
handleSortChange={handleSortChange}
|
||||
themeMode={themeMode}
|
||||
plugin={plugin}
|
||||
handleClearAllFilters={handleClearFilters}
|
||||
/>
|
||||
<Box style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<TextField
|
||||
|
|
@ -313,7 +332,7 @@ export const ReactView = ({ moviesData, createMarkdownFile, themeMode, plugin })
|
|||
</Box>
|
||||
)}
|
||||
{!plugin.settings.hideMetrics && (
|
||||
<Metrics movies={movies} topActorsNumber={plugin.settings.topActorsNumber} topGenresNumber={plugin.settings.topGenresNumber} topDirectorsNumber={plugin.settings.topDirectorsNumber} minMoviesForMetrics={plugin.settings.minMoviesForMetrics} movieMetricsHeadingColor={plugin.settings.movieMetricsHeadingColor} movieMetricsSubheadingColor={plugin.settings.movieMetricsSubheadingColor} themeMode={themeMode} metricsHeading={plugin.settings.metricsHeading} />
|
||||
<Metrics movies={movies} budgetMetricsSubheadingColor={plugin.settings.budgetMetricsSubheadingColor} hideBudgetMetrics={plugin.settings.hideBudgetMetrics} hideGenreTasteIndexMetrics={plugin.settings.hideGenreTasteIndexMetrics} topActorsNumber={plugin.settings.topActorsNumber} topCollectionsNumber={plugin.settings.topCollectionsNumber} topPerformersNumber={plugin.settings.topPerformersNumber} topGenresNumber={plugin.settings.topGenresNumber} topDirectorsNumber={plugin.settings.topDirectorsNumber} topProductionCompaniesNumber={plugin.settings.topProductionCompaniesNumber} minMoviesForMetrics={plugin.settings.minMoviesForMetrics} minMoviesForMetricsCollections={plugin.settings.minMoviesForMetricsCollections} minMoviesForMetricsDirectors={plugin.settings.minMoviesForMetricsDirectors} movieMetricsHeadingColor={plugin.settings.movieMetricsHeadingColor} movieMetricsSubheadingColor={plugin.settings.movieMetricsSubheadingColor} themeMode={themeMode} metricsHeading={plugin.settings.metricsHeading} />
|
||||
)}
|
||||
<DiscoverPopup
|
||||
open={showDiscoverPopup}
|
||||
|
|
|
|||
447
main.ts
447
main.ts
|
|
@ -1,4 +1,4 @@
|
|||
import { App, Plugin, PluginSettingTab, Setting} from 'obsidian';
|
||||
import { App, Plugin, PluginSettingTab, Setting, Notice, requestUrl} from 'obsidian';
|
||||
import { TVTracker,VIEW_TV } from 'view';
|
||||
|
||||
|
||||
|
|
@ -9,17 +9,27 @@ interface TVTrackerSettings {
|
|||
numberOfResults: number;
|
||||
toggleFittedImages: boolean;
|
||||
hideLegend: boolean;
|
||||
hideMetrics: boolean;
|
||||
hideMetrics: boolean;
|
||||
hideBudgetMetrics: boolean;
|
||||
hideGenreTasteIndexMetrics: boolean;
|
||||
imageFolderPath: string;
|
||||
apiKey: string;
|
||||
topGenresNumber: number;
|
||||
topActorsNumber: number;
|
||||
topDirectorsNumber: number;
|
||||
topProductionCompaniesNumber: number;
|
||||
topCollectionsNumber :number;
|
||||
showTrailerAndPosterLinks: boolean;
|
||||
topPerformersNumber:number;
|
||||
minMoviesForMetrics: number;
|
||||
minMoviesForMetricsDirectors: number;
|
||||
minMoviesForMetricsCollections: number;
|
||||
movieMetricsHeadingColor: string;
|
||||
movieMetricsSubheadingColor: string;
|
||||
budgetMetricsSubheadingColor: string;
|
||||
movieCardColor: string;
|
||||
metricsHeading: string;
|
||||
|
||||
}
|
||||
|
||||
const DEFAULT_TV_SETTINGS: TVTrackerSettings = {
|
||||
|
|
@ -29,18 +39,26 @@ const DEFAULT_TV_SETTINGS: TVTrackerSettings = {
|
|||
numberOfResults: 3,
|
||||
toggleFittedImages: true,
|
||||
hideLegend: false,
|
||||
hideMetrics: false,
|
||||
hideMetrics: false,
|
||||
hideBudgetMetrics: false,
|
||||
hideGenreTasteIndexMetrics: false,
|
||||
imageFolderPath: 'Movies/Images',
|
||||
apiKey :'' ,
|
||||
topActorsNumber:5,
|
||||
topDirectorsNumber:5,
|
||||
topProductionCompaniesNumber:5,
|
||||
topGenresNumber:5,
|
||||
topCollectionsNumber:5,
|
||||
topPerformersNumber:5,
|
||||
minMoviesForMetrics:7,
|
||||
minMoviesForMetricsDirectors:5,
|
||||
minMoviesForMetricsCollections: 3,
|
||||
movieMetricsHeadingColor: 'lightblue',
|
||||
movieMetricsSubheadingColor: 'orange',
|
||||
budgetMetricsSubheadingColor: '#DB6FFC',
|
||||
movieCardColor: 'inherit',
|
||||
metricsHeading: 'For Number geeks'
|
||||
|
||||
metricsHeading: 'For Number geeks',
|
||||
showTrailerAndPosterLinks: true
|
||||
}
|
||||
|
||||
export default class TVTrackerPlugin extends Plugin {
|
||||
|
|
@ -99,6 +117,255 @@ export default class TVTrackerPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
async removeTrailerAndPosterLinks() {
|
||||
const movieFolder = this.app.vault.getAbstractFileByPath(this.settings.movieFolderPath);
|
||||
if (!movieFolder || !(movieFolder as any).children) return;
|
||||
|
||||
const files = (movieFolder as any).children.filter((file: any) => file.extension === 'md');
|
||||
console.log(`Number of Markdown files found: ${files.length}`);
|
||||
|
||||
let iteration = 0;
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
iteration += 1;
|
||||
console.log(`\nProcessing file ${file.path}`);
|
||||
console.log(`Number is ${iteration}`);
|
||||
|
||||
const cache = this.app.metadataCache.getFileCache(file);
|
||||
const yaml = cache?.frontmatter;
|
||||
|
||||
// console.log("YAML match is ", yaml);
|
||||
if (!yaml) {
|
||||
errorCount++;
|
||||
new Notice(`Error with reading YAML: ${file.path}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const poster = yaml.Poster;
|
||||
const trailer = yaml.trailer;
|
||||
// console.log("Poster is ", poster);
|
||||
// console.log("Trailer is ", trailer);
|
||||
|
||||
let fileContent = await this.app.vault.read(file);
|
||||
|
||||
if (poster) {
|
||||
const posterLink = ``;
|
||||
console.log("PosterLink is ", posterLink);
|
||||
fileContent = fileContent.replace(`${posterLink}`, '\n');
|
||||
}
|
||||
if (trailer) {
|
||||
const trailerLink = ``;
|
||||
fileContent = fileContent.replace(`${trailerLink}`, '\n');
|
||||
}
|
||||
|
||||
console.log("Updated content is ", fileContent);
|
||||
|
||||
try {
|
||||
await this.app.vault.modify(file, fileContent);
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
console.error(`Failed to update file ${file.path}`, error);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
new Notice(`Files processed: ${iteration}, Success: ${successCount}, Errors: ${errorCount}`);
|
||||
}
|
||||
|
||||
|
||||
async addTrailerAndPoster() {
|
||||
const movieFolder = this.app.vault.getAbstractFileByPath(this.settings.movieFolderPath);
|
||||
if (!movieFolder || !(movieFolder as any).children) return;
|
||||
|
||||
const files = (movieFolder as any).children.filter((file: any) => file.extension === 'md');
|
||||
console.log(`Number of Markdown files found: ${files.length}`);
|
||||
|
||||
let iteration = 0;
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
iteration = iteration + 1;
|
||||
// console.log("");
|
||||
// console.log("");
|
||||
// console.log("Doing file ", file.path);
|
||||
// console.log("Number is ", iteration);
|
||||
|
||||
|
||||
const cache = this.app.metadataCache.getFileCache(file);
|
||||
const yaml = cache?.frontmatter;
|
||||
|
||||
//console.log("YAML match is ", yaml);
|
||||
if (!yaml) {
|
||||
errorCount++;
|
||||
new Notice(`Error with reading YAML: ${file.path}`);
|
||||
console.log(`Error with reading YAML: ${file.path}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const poster = yaml.Poster;
|
||||
const trailer = yaml.trailer;
|
||||
// console.log("Poster is ", poster);
|
||||
// console.log('trailer is ', trailer);
|
||||
let newContent = await this.app.vault.read(file);
|
||||
if (poster) {
|
||||
const posterLink = ``;
|
||||
newContent = `${newContent}\n${posterLink}`;
|
||||
}
|
||||
if (trailer) {
|
||||
const trailerLink = ``;
|
||||
newContent = `${newContent}\n${trailerLink}`;
|
||||
} else if (poster) {
|
||||
new Notice(`No trailer found for file: ${file.path}`);
|
||||
console.log(`No trailer found for file: ${file.path}`);
|
||||
}
|
||||
// console.log("New content is ", newContent);
|
||||
if (poster || trailer) {
|
||||
await this.app.vault.modify(file, newContent);
|
||||
successCount++;
|
||||
}
|
||||
else {
|
||||
new Notice(`Error with reading Poster or Trailer: ${file.path}`);
|
||||
console.log(`Error with reading Poster or Trailer: ${file.path}`);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
new Notice(`Files processed: ${iteration}, Success: ${successCount}, Errors: ${errorCount}`);
|
||||
}
|
||||
|
||||
|
||||
async updateNewProperties() {
|
||||
// Get all movie files
|
||||
const movieFolder = this.app.vault.getAbstractFileByPath(this.settings.movieFolderPath);
|
||||
if (!movieFolder || !(movieFolder as any).children) return;
|
||||
|
||||
const files = (movieFolder as any).children.filter((file: any) => file.extension === 'md');
|
||||
//console.log(`Number of Markdown files found: ${files.length}`);
|
||||
let iteration = 0;
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
for (const file of files) {
|
||||
iteration = iteration +1;
|
||||
// console.log("");
|
||||
// console.log("");
|
||||
// if(iteration>=100){
|
||||
// break;
|
||||
// }
|
||||
const filePath = file.path;
|
||||
// console.log("Doing file ", file.path);
|
||||
// console.log("Number is ", iteration)
|
||||
const cache = this.app.metadataCache.getFileCache(file);
|
||||
const yaml = cache?.frontmatter;
|
||||
|
||||
//console.log("YAML match is ", yaml);
|
||||
if (!yaml) {
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const type = yaml.Type;
|
||||
const tmdbId = yaml["TMDB ID"];
|
||||
// console.log("Type match is ", type);
|
||||
// console.log("TMDB Id match is ", tmdbId);
|
||||
if (!type || !tmdbId) {
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const endpoint = type === 'Movie' ? `movie` : `tv`;
|
||||
|
||||
// Fetch original language from TMDB API
|
||||
const response = await requestUrl({
|
||||
url: `https://api.themoviedb.org/3/${endpoint}/${tmdbId}?api_key=${this.settings.apiKey}&append_to_response=videos`,
|
||||
});
|
||||
|
||||
//console.log(`Status for ${filePath}: ${response.status}`);
|
||||
|
||||
if (response.status !== 200) {
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = response.json;
|
||||
|
||||
const originalLanguage = data.original_language;
|
||||
const overview = data.overview;
|
||||
|
||||
let productionCompanies = '';
|
||||
if (data.production_companies && data.production_companies.length > 0) {
|
||||
productionCompanies = data.production_companies.slice(0, 2).map((company: any) => company.name).join(', ');
|
||||
}
|
||||
|
||||
let trailer = '';
|
||||
if (data.videos && data.videos.results.length > 0) {
|
||||
const trailerData = data.videos.results.find((video: any) => video.type === 'Trailer');
|
||||
if (trailerData) {
|
||||
trailer = `https://www.youtube.com/watch?v=${trailerData.key}`;
|
||||
}
|
||||
}
|
||||
|
||||
let budget = null;
|
||||
let revenue = null;
|
||||
let belongsToCollection = null;
|
||||
|
||||
if (type === 'Movie') {
|
||||
budget = data.budget;
|
||||
revenue = data.revenue;
|
||||
belongsToCollection = data.belongs_to_collection ? data.belongs_to_collection.name : null;
|
||||
}
|
||||
const escapeDoubleQuotes = (str: string) => str.replace(/"/g, '\\"');
|
||||
|
||||
if (yaml.Title) {
|
||||
const title = yaml.Title;
|
||||
if (!title.startsWith('"') || !title.endsWith('"')) {
|
||||
yaml.Title = `"${title}"`;
|
||||
} else {
|
||||
// Escape internal double quotes if the title is already quoted
|
||||
yaml.Title = `${title}`;
|
||||
}
|
||||
}
|
||||
const updatedYaml = {
|
||||
...yaml,
|
||||
original_language: `"${originalLanguage}"`,
|
||||
overview: `"${escapeDoubleQuotes(overview)}"`,
|
||||
trailer: `"${trailer}"`,
|
||||
budget: budget,
|
||||
revenue: revenue,
|
||||
belongs_to_collection: belongsToCollection ? `"${belongsToCollection}"` : '""',
|
||||
production_company: `"${productionCompanies}"`,
|
||||
};
|
||||
|
||||
//console.log("updatedYaml is ", updatedYaml);
|
||||
const updatedYamlContent = `---\n${Object.entries(updatedYaml).map(([key, value]) => `${key}: ${typeof value === 'string' ? value : JSON.stringify(value)}`).join('\n')}\n---`;
|
||||
// console.log("updatedYamlContent is ", updatedYamlContent);
|
||||
|
||||
const fileContent = await this.app.vault.read(file);
|
||||
// console.log("fiel content is ", fileContent);
|
||||
// console.log("file content (raw) is ", JSON.stringify(fileContent));
|
||||
|
||||
const yamlRegex = /^---[\r\n]+[\s\S]*?[\r\n]+---/m;
|
||||
|
||||
if (yamlRegex.test(fileContent)) {
|
||||
const updatedFileContent = fileContent.replace(yamlRegex, updatedYamlContent);
|
||||
// console.log("Updated file content is ", updatedFileContent);
|
||||
// Save the updated content back to the file
|
||||
await this.app.vault.modify(file, updatedFileContent);
|
||||
successCount++;
|
||||
} else {
|
||||
console.error("YAML front matter not found in file:", file.path);
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
new Notice(`Original language updated for ${successCount} files. ${errorCount} files encountered errors.`);
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_TV_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
|
@ -122,6 +389,9 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
|
||||
containerEl.empty();
|
||||
|
||||
|
||||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Folder path')
|
||||
.setDesc('Path to the folder where all content is stored.')
|
||||
|
|
@ -143,6 +413,18 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Show Trailer and Poster Links')
|
||||
.setDesc('Enable this to display trailer and poster links in new files. Only affects the new files. To update existing files see Update Files section below.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.showTrailerAndPosterLinks)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.showTrailerAndPosterLinks = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Number of results to show')
|
||||
.setDesc('Number of results to display for add new')
|
||||
|
|
@ -174,7 +456,9 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
|
||||
|
||||
|
||||
let fileUpdateSettingsContainer = this.addSectionHeader(containerEl, 'Update Files (Please keep a backup of the folder before proceeding)', 'updateFiles-settings');
|
||||
this.addFileUpdateSettings(fileUpdateSettingsContainer);
|
||||
|
||||
let styleSettingsContainer = this.addSectionHeader(containerEl, 'Style', 'style-settings');
|
||||
this.addStyleSettings(styleSettingsContainer);
|
||||
|
|
@ -196,6 +480,39 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
return contentContainer;
|
||||
}
|
||||
|
||||
addFileUpdateSettings(containerEl: HTMLElement){
|
||||
new Setting(containerEl)
|
||||
.setName('Update Files with new data')
|
||||
.setDesc('Fetches overview, trailer link, original language, production company, budget, revenue for all movies/shows and updates the YAML. These new properties were added in v1.3.0')
|
||||
.addButton(button => button
|
||||
.setButtonText('Update')
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.plugin.updateNewProperties();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Add trailer and poster link to existing Files')
|
||||
.setDesc('Click this button to update all existing files to include trailer and poster links.')
|
||||
.addButton(button => button
|
||||
.setButtonText('Add')
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
await this.plugin.addTrailerAndPoster();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Remove trailer and poster links from existing Files')
|
||||
.setDesc('Click this button to update all existing files to remove trailer and poster links. The rest of the contents should remain unchanged')
|
||||
.addButton(button => button
|
||||
.setButtonText('Remove')
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
await this.plugin.removeTrailerAndPosterLinks();
|
||||
}));
|
||||
|
||||
|
||||
}
|
||||
|
||||
addStyleSettings(containerEl: HTMLElement) {
|
||||
|
||||
|
|
@ -209,9 +526,18 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Color for movie metrics heading (H1)')
|
||||
.setDesc('Enter as a hex code. Choose a color for the movie metrics heading.')
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.movieMetricsHeadingColor)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.movieMetricsHeadingColor = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Color for movie metrics subheadings')
|
||||
.setName('Color for metrics subheadings (H2)')
|
||||
.setDesc('Enter as a hex code. Choose a color for the movie metrics subheadings.')
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.movieMetricsSubheadingColor)
|
||||
|
|
@ -220,16 +546,19 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Color for metrics subheadings (H3)')
|
||||
.setDesc('Enter as a hex code. Choose a color for the metrics H3 subheadings such as under budget metrics.')
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.budgetMetricsSubheadingColor)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.budgetMetricsSubheadingColor = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Color for movie metrics heading')
|
||||
.setDesc('Enter as a hex code. Choose a color for the movie metrics heading.')
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.movieMetricsHeadingColor)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.movieMetricsHeadingColor = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Toggle fitted images')
|
||||
|
|
@ -274,7 +603,7 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
|
||||
new Setting(containerEl)
|
||||
.setName('Hide Metrics')
|
||||
.setDesc('Hide Metrics from the view')
|
||||
.setDesc('Hides all Metrics from the view')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.hideMetrics)
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -282,6 +611,25 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Hide Budget Metrics')
|
||||
.setDesc('Hides only budget Metrics from the view')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.hideBudgetMetrics)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.hideBudgetMetrics = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Hide Genre taste index Metrics')
|
||||
.setDesc('Hides only Genre taste index Metrics from the view')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.hideGenreTasteIndexMetrics)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.hideGenreTasteIndexMetrics = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Name of Metrics')
|
||||
|
|
@ -304,6 +652,17 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Number of top actors to show')
|
||||
.setDesc('Number of top actors to show in metrics')
|
||||
.addText(text => text
|
||||
.setValue(String(this.plugin.settings.topActorsNumber))
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.topActorsNumber = Number(value);
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Number of top directors to show')
|
||||
.setDesc('Number of top directors to show in metrics')
|
||||
|
|
@ -314,8 +673,41 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Number of top production companies to show')
|
||||
.setDesc('Number of top production companies to show in metrics')
|
||||
.addText(text => text
|
||||
.setValue(String(this.plugin.settings.topProductionCompaniesNumber))
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.topProductionCompaniesNumber = Number(value);
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Number of top Collections/Franchises to show')
|
||||
.setDesc('Number of top Collections/Franchises to show in metrics')
|
||||
.addText(text => text
|
||||
.setValue(String(this.plugin.settings.topCollectionsNumber))
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.topCollectionsNumber = Number(value);
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Number of top results to show in Budget metrics')
|
||||
.setDesc('Number of top results to show in Budget metrics')
|
||||
.addText(text => text
|
||||
.setValue(String(this.plugin.settings.topPerformersNumber))
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.topPerformersNumber = Number(value);
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
|
||||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Minimum number of movies for metric ')
|
||||
.setName('Minimum number of movies for metric - Actor ')
|
||||
.setDesc('Minimum number of movie for an actor for avg rating based metrics')
|
||||
.addText(text => text
|
||||
.setValue(String(this.plugin.settings.minMoviesForMetrics))
|
||||
|
|
@ -325,14 +717,25 @@ class TVTrackerSettingsTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Number of top actors to show')
|
||||
.setDesc('Number of top actors to show in metrics')
|
||||
.setName('Minimum number of movies for metric - Director and Production company ')
|
||||
.setDesc('Minimum number of movie for Director and Production company for avg rating based metrics')
|
||||
.addText(text => text
|
||||
.setValue(String(this.plugin.settings.topActorsNumber))
|
||||
.setValue(String(this.plugin.settings.minMoviesForMetricsDirectors))
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.topActorsNumber = Number(value);
|
||||
this.plugin.settings.minMoviesForMetricsDirectors = Number(value);
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Minimum number of movies for metric - Collections/Franchise ')
|
||||
.setDesc('Minimum number of movie for a Collections/Franchise for avg rating based metrics')
|
||||
.addText(text => text
|
||||
.setValue(String(this.plugin.settings.minMoviesForMetricsCollections))
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.minMoviesForMetricsCollections = Number(value);
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "tv-tracker",
|
||||
"name": "Movie and TV show tracker",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "A Movie and TV show tracker.",
|
||||
"author": "Shreshth Mehra",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "tv-tracker-plugin",
|
||||
"version": "1.0.0",
|
||||
"version": "1.3.0",
|
||||
"description": "This is a movie and tv show tracker plugin for obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue