serias categories favs
This commit is contained in:
parent
b8df5d0997
commit
28c5cb8c0c
@ -24,6 +24,8 @@
|
|||||||
favoriteLiveCategoryStreamsById: {},
|
favoriteLiveCategoryStreamsById: {},
|
||||||
expandedFavoriteVodCategoryById: {},
|
expandedFavoriteVodCategoryById: {},
|
||||||
favoriteVodCategoryStreamsById: {},
|
favoriteVodCategoryStreamsById: {},
|
||||||
|
expandedFavoriteSeriesCategoryById: {},
|
||||||
|
favoriteSeriesCategoryItemsById: {},
|
||||||
currentLiveEpgStreamId: null,
|
currentLiveEpgStreamId: null,
|
||||||
currentStreamInfo: null
|
currentStreamInfo: null
|
||||||
};
|
};
|
||||||
@ -60,6 +62,7 @@
|
|||||||
seriesCategory: document.getElementById("series-category"),
|
seriesCategory: document.getElementById("series-category"),
|
||||||
seriesSearch: document.getElementById("series-search"),
|
seriesSearch: document.getElementById("series-search"),
|
||||||
seriesRefresh: document.getElementById("series-refresh"),
|
seriesRefresh: document.getElementById("series-refresh"),
|
||||||
|
seriesFavoriteCategory: document.getElementById("series-favorite-category"),
|
||||||
seriesList: document.getElementById("series-list"),
|
seriesList: document.getElementById("series-list"),
|
||||||
|
|
||||||
customForm: document.getElementById("custom-form"),
|
customForm: document.getElementById("custom-form"),
|
||||||
@ -283,9 +286,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function bindSeriesTab() {
|
function bindSeriesTab() {
|
||||||
el.seriesCategory.addEventListener("change", () => loadSeriesList().catch(showError));
|
el.seriesCategory.addEventListener("change", () => {
|
||||||
|
loadSeriesList().catch(showError);
|
||||||
|
updateSeriesCategoryFavoriteButton();
|
||||||
|
});
|
||||||
el.seriesSearch.addEventListener("input", renderSeriesList);
|
el.seriesSearch.addEventListener("input", renderSeriesList);
|
||||||
el.seriesRefresh.addEventListener("click", () => loadSeriesData().catch(showError));
|
el.seriesRefresh.addEventListener("click", () => loadSeriesData().catch(showError));
|
||||||
|
el.seriesFavoriteCategory.addEventListener("click", () => {
|
||||||
|
const favorite = selectedSeriesCategoryFavorite();
|
||||||
|
if (!favorite) {
|
||||||
|
setSettingsMessage("Select a series category first.", "err");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toggleFavorite(favorite).catch(showError);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindCustomTab() {
|
function bindCustomTab() {
|
||||||
@ -410,6 +424,8 @@
|
|||||||
state.favoriteLiveCategoryStreamsById = {};
|
state.favoriteLiveCategoryStreamsById = {};
|
||||||
state.expandedFavoriteVodCategoryById = {};
|
state.expandedFavoriteVodCategoryById = {};
|
||||||
state.favoriteVodCategoryStreamsById = {};
|
state.favoriteVodCategoryStreamsById = {};
|
||||||
|
state.expandedFavoriteSeriesCategoryById = {};
|
||||||
|
state.favoriteSeriesCategoryItemsById = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadLiveData() {
|
async function loadLiveData() {
|
||||||
@ -599,9 +615,39 @@
|
|||||||
const categoriesPayload = await apiJson("/api/library/categories?type=series");
|
const categoriesPayload = await apiJson("/api/library/categories?type=series");
|
||||||
state.seriesCategories = sanitizeCategories(categoriesPayload.items);
|
state.seriesCategories = sanitizeCategories(categoriesPayload.items);
|
||||||
fillCategorySelect(el.seriesCategory, state.seriesCategories, "All series");
|
fillCategorySelect(el.seriesCategory, state.seriesCategories, "All series");
|
||||||
|
updateSeriesCategoryFavoriteButton();
|
||||||
await loadSeriesList();
|
await loadSeriesList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectedSeriesCategoryFavorite() {
|
||||||
|
const categoryId = String(el.seriesCategory.value || "").trim();
|
||||||
|
if (!categoryId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const category = state.seriesCategories.find((item) => String(item?.category_id || "") === categoryId);
|
||||||
|
if (!category) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return makeFavoriteSeriesCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSeriesCategoryFavoriteButton() {
|
||||||
|
const favorite = selectedSeriesCategoryFavorite();
|
||||||
|
if (!favorite) {
|
||||||
|
el.seriesFavoriteCategory.disabled = true;
|
||||||
|
el.seriesFavoriteCategory.textContent = "☆";
|
||||||
|
el.seriesFavoriteCategory.title = "Select category";
|
||||||
|
el.seriesFavoriteCategory.setAttribute("aria-label", "Select category");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.seriesFavoriteCategory.disabled = false;
|
||||||
|
const active = isFavorite(favorite.key);
|
||||||
|
el.seriesFavoriteCategory.textContent = favoriteIcon(active);
|
||||||
|
const label = active ? "Remove category from favorites" : "Add category to favorites";
|
||||||
|
el.seriesFavoriteCategory.title = label;
|
||||||
|
el.seriesFavoriteCategory.setAttribute("aria-label", label);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadSeriesList() {
|
async function loadSeriesList() {
|
||||||
ensureLibraryReady();
|
ensureLibraryReady();
|
||||||
const query = new URLSearchParams({type: "series"});
|
const query = new URLSearchParams({type: "series"});
|
||||||
@ -826,6 +872,7 @@
|
|||||||
renderFavorites();
|
renderFavorites();
|
||||||
updateLiveCategoryFavoriteButton();
|
updateLiveCategoryFavoriteButton();
|
||||||
updateVodCategoryFavoriteButton();
|
updateVodCategoryFavoriteButton();
|
||||||
|
updateSeriesCategoryFavoriteButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFavorites() {
|
function renderFavorites() {
|
||||||
@ -846,19 +893,23 @@
|
|||||||
filtered.forEach((favorite) => {
|
filtered.forEach((favorite) => {
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
li.className = "stream-item";
|
li.className = "stream-item";
|
||||||
const isSeriesCategory = favorite?.mode === "series_item";
|
const isSeriesItem = favorite?.mode === "series_item";
|
||||||
const isLiveCategory = favorite?.mode === "live_category";
|
const isLiveCategory = favorite?.mode === "live_category";
|
||||||
const isVodCategory = favorite?.mode === "vod_category";
|
const isVodCategory = favorite?.mode === "vod_category";
|
||||||
|
const isSeriesCategory = favorite?.mode === "series_category";
|
||||||
const seriesId = String(favorite?.id || "");
|
const seriesId = String(favorite?.id || "");
|
||||||
const isExpanded = isSeriesCategory && Boolean(state.expandedFavoriteSeriesById[seriesId]);
|
const isSeriesItemExpanded = isSeriesItem && Boolean(state.expandedFavoriteSeriesById[seriesId]);
|
||||||
const liveCategoryId = String(favorite?.id || "");
|
const liveCategoryId = String(favorite?.id || "");
|
||||||
const isLiveCategoryExpanded = isLiveCategory && Boolean(state.expandedFavoriteLiveCategoryById[liveCategoryId]);
|
const isLiveCategoryExpanded = isLiveCategory && Boolean(state.expandedFavoriteLiveCategoryById[liveCategoryId]);
|
||||||
const vodCategoryId = String(favorite?.id || "");
|
const vodCategoryId = String(favorite?.id || "");
|
||||||
const isVodCategoryExpanded = isVodCategory && Boolean(state.expandedFavoriteVodCategoryById[vodCategoryId]);
|
const isVodCategoryExpanded = isVodCategory && Boolean(state.expandedFavoriteVodCategoryById[vodCategoryId]);
|
||||||
li.innerHTML = isSeriesCategory
|
const seriesCategoryId = String(favorite?.id || "");
|
||||||
|
const isSeriesCategoryExpanded = isSeriesCategory
|
||||||
|
&& Boolean(state.expandedFavoriteSeriesCategoryById[seriesCategoryId]);
|
||||||
|
li.innerHTML = isSeriesItem
|
||||||
? `
|
? `
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="stream-title stream-link" data-action="toggle-favorite-series">${isExpanded ? "Hide" : "Show"} ${esc(favorite.title || "Untitled")}</button>
|
<button type="button" class="stream-title stream-link" data-action="toggle-favorite-series">${isSeriesItemExpanded ? "Hide" : "Show"} ${esc(favorite.title || "Untitled")}</button>
|
||||||
<div class="stream-meta">${esc(favoriteSummary(favorite))}</div>
|
<div class="stream-meta">${esc(favoriteSummary(favorite))}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stream-actions">
|
<div class="stream-actions">
|
||||||
@ -885,6 +936,16 @@
|
|||||||
<button type="button" data-action="remove-favorite" class="danger">Remove</button>
|
<button type="button" data-action="remove-favorite" class="danger">Remove</button>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
: isSeriesCategory
|
||||||
|
? `
|
||||||
|
<div>
|
||||||
|
<button type="button" class="stream-title stream-link" data-action="toggle-favorite-series-category">${isSeriesCategoryExpanded ? "Hide" : "Show"} ${esc(favorite.title || "Untitled")}</button>
|
||||||
|
<div class="stream-meta">${esc(favoriteSummary(favorite))}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stream-actions">
|
||||||
|
<button type="button" data-action="remove-favorite" class="danger">Remove</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
: `
|
: `
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="stream-title stream-link" data-action="open-favorite">${esc(favorite.title || "Untitled")}</button>
|
<button type="button" class="stream-title stream-link" data-action="open-favorite">${esc(favorite.title || "Untitled")}</button>
|
||||||
@ -894,7 +955,7 @@
|
|||||||
<button type="button" data-action="remove-favorite" class="danger">Remove</button>
|
<button type="button" data-action="remove-favorite" class="danger">Remove</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
if (isSeriesCategory) {
|
if (isSeriesItem) {
|
||||||
li.querySelector("button[data-action='toggle-favorite-series']").addEventListener("click", () => {
|
li.querySelector("button[data-action='toggle-favorite-series']").addEventListener("click", () => {
|
||||||
toggleFavoriteSeriesItem(favorite).catch(showError);
|
toggleFavoriteSeriesItem(favorite).catch(showError);
|
||||||
});
|
});
|
||||||
@ -906,6 +967,10 @@
|
|||||||
li.querySelector("button[data-action='toggle-favorite-vod-category']").addEventListener("click", () => {
|
li.querySelector("button[data-action='toggle-favorite-vod-category']").addEventListener("click", () => {
|
||||||
toggleFavoriteVodCategory(favorite).catch(showError);
|
toggleFavoriteVodCategory(favorite).catch(showError);
|
||||||
});
|
});
|
||||||
|
} else if (isSeriesCategory) {
|
||||||
|
li.querySelector("button[data-action='toggle-favorite-series-category']").addEventListener("click", () => {
|
||||||
|
toggleFavoriteSeriesCategory(favorite).catch(showError);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
li.querySelector("button[data-action='open-favorite']").addEventListener("click", () => {
|
li.querySelector("button[data-action='open-favorite']").addEventListener("click", () => {
|
||||||
openFavorite(favorite).catch(showError);
|
openFavorite(favorite).catch(showError);
|
||||||
@ -926,6 +991,10 @@
|
|||||||
delete state.expandedFavoriteVodCategoryById[vodCategoryId];
|
delete state.expandedFavoriteVodCategoryById[vodCategoryId];
|
||||||
delete state.favoriteVodCategoryStreamsById[vodCategoryId];
|
delete state.favoriteVodCategoryStreamsById[vodCategoryId];
|
||||||
}
|
}
|
||||||
|
if (favorite?.mode === "series_category") {
|
||||||
|
delete state.expandedFavoriteSeriesCategoryById[seriesCategoryId];
|
||||||
|
delete state.favoriteSeriesCategoryItemsById[seriesCategoryId];
|
||||||
|
}
|
||||||
renderFavorites();
|
renderFavorites();
|
||||||
renderLiveStreams();
|
renderLiveStreams();
|
||||||
renderVodStreams();
|
renderVodStreams();
|
||||||
@ -935,7 +1004,7 @@
|
|||||||
});
|
});
|
||||||
el.favoritesList.appendChild(li);
|
el.favoritesList.appendChild(li);
|
||||||
|
|
||||||
if (!isSeriesCategory && !isLiveCategory && !isVodCategory) {
|
if (!isSeriesItem && !isLiveCategory && !isVodCategory && !isSeriesCategory) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -945,7 +1014,10 @@
|
|||||||
if (isVodCategory && !isVodCategoryExpanded) {
|
if (isVodCategory && !isVodCategoryExpanded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isSeriesCategory && !isExpanded) {
|
if (isSeriesItem && !isSeriesItemExpanded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isSeriesCategory && !isSeriesCategoryExpanded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -953,6 +1025,8 @@
|
|||||||
? state.favoriteLiveCategoryStreamsById[liveCategoryId]
|
? state.favoriteLiveCategoryStreamsById[liveCategoryId]
|
||||||
: isVodCategory
|
: isVodCategory
|
||||||
? state.favoriteVodCategoryStreamsById[vodCategoryId]
|
? state.favoriteVodCategoryStreamsById[vodCategoryId]
|
||||||
|
: isSeriesCategory
|
||||||
|
? state.favoriteSeriesCategoryItemsById[seriesCategoryId]
|
||||||
: state.favoriteSeriesEpisodesById[seriesId];
|
: state.favoriteSeriesEpisodesById[seriesId];
|
||||||
const episodesLi = document.createElement("li");
|
const episodesLi = document.createElement("li");
|
||||||
episodesLi.className = "card episodes series-inline-episodes";
|
episodesLi.className = "card episodes series-inline-episodes";
|
||||||
@ -1033,6 +1107,106 @@
|
|||||||
el.favoritesList.appendChild(episodesLi);
|
el.favoritesList.appendChild(episodesLi);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isSeriesCategory) {
|
||||||
|
const seriesItems = Array.isArray(episodesEntry.episodes) ? episodesEntry.episodes : [];
|
||||||
|
seriesItems.forEach((seriesItem) => {
|
||||||
|
const seriesItemFavorite = makeFavoriteSeriesItem(seriesItem);
|
||||||
|
const localSeriesId = String(seriesItem?.series_id || "");
|
||||||
|
const isLocalExpanded = Boolean(state.expandedFavoriteSeriesById[localSeriesId]);
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "stream-item";
|
||||||
|
row.innerHTML = `
|
||||||
|
<div>
|
||||||
|
<button type="button" class="stream-title stream-link" data-action="toggle-favorite-series">${isLocalExpanded ? "Hide episodes" : "Episodes"} ${esc(seriesItem?.name || "Untitled")}</button>
|
||||||
|
<div class="stream-meta">Series ID: ${esc(localSeriesId)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stream-actions">
|
||||||
|
<button type="button" class="favorite-toggle" data-action="toggle-favorite" aria-label="${isFavorite(seriesItemFavorite.key) ? "Remove from favorites" : "Add to favorites"}" title="${isFavorite(seriesItemFavorite.key) ? "Remove from favorites" : "Add to favorites"}">${favoriteIcon(isFavorite(seriesItemFavorite.key))}</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
row.querySelector("button[data-action='toggle-favorite-series']").addEventListener("click", () => {
|
||||||
|
toggleFavoriteSeriesItem(seriesItemFavorite).catch(showError);
|
||||||
|
});
|
||||||
|
row.querySelector("button[data-action='toggle-favorite']").addEventListener("click", () => {
|
||||||
|
toggleFavorite(seriesItemFavorite).then(() => renderFavorites()).catch(showError);
|
||||||
|
});
|
||||||
|
wrap.appendChild(row);
|
||||||
|
|
||||||
|
if (!isLocalExpanded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const seriesEntry = state.favoriteSeriesEpisodesById[localSeriesId];
|
||||||
|
const episodesBlock = document.createElement("div");
|
||||||
|
episodesBlock.className = "season-list";
|
||||||
|
if (!seriesEntry || seriesEntry.loading) {
|
||||||
|
episodesBlock.innerHTML = `<div class="muted">Loading episodes...</div>`;
|
||||||
|
wrap.appendChild(episodesBlock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (seriesEntry.error) {
|
||||||
|
episodesBlock.innerHTML = `<div class="danger">Unable to load episodes: ${esc(seriesEntry.error)}</div>`;
|
||||||
|
wrap.appendChild(episodesBlock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const groupedBySeason = groupEpisodesBySeason(seriesEntry.episodes);
|
||||||
|
groupedBySeason.forEach((group) => {
|
||||||
|
const isSeasonExpanded = Boolean(state.expandedSeasonByFavoriteSeries?.[localSeriesId]?.[group.season]);
|
||||||
|
const seasonBlock = document.createElement("div");
|
||||||
|
seasonBlock.className = "season-block";
|
||||||
|
seasonBlock.innerHTML = `
|
||||||
|
<button type="button" class="season-title season-toggle" data-action="toggle-favorite-season">
|
||||||
|
${isSeasonExpanded ? "Hide" : "Show"} Season ${esc(group.season)} (${group.episodes.length})
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
seasonBlock.querySelector("button[data-action='toggle-favorite-season']").addEventListener("click", () => {
|
||||||
|
toggleFavoriteSeasonGroup(localSeriesId, group.season);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isSeasonExpanded) {
|
||||||
|
episodesBlock.appendChild(seasonBlock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const seasonList = document.createElement("div");
|
||||||
|
seasonList.className = "season-list";
|
||||||
|
group.episodes.forEach((episode) => {
|
||||||
|
const episodeFavorite = makeFavoriteSeriesEpisode(
|
||||||
|
{name: seriesItem?.name || "Series", series_id: localSeriesId},
|
||||||
|
episode
|
||||||
|
);
|
||||||
|
const episodeRow = document.createElement("div");
|
||||||
|
episodeRow.className = "stream-item";
|
||||||
|
episodeRow.innerHTML = `
|
||||||
|
<div>
|
||||||
|
<button type="button" class="stream-title stream-link" data-action="play-title">S${esc(episode.season)}E${esc(episode.episodeNum)} - ${esc(episode.title)}</button>
|
||||||
|
<div class="stream-meta">Episode ID: ${esc(episode.id)} | Ext: ${esc(episode.ext)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stream-actions">
|
||||||
|
<button type="button" class="favorite-toggle" data-action="toggle-favorite" aria-label="${isFavorite(episodeFavorite.key) ? "Remove from favorites" : "Add to favorites"}" title="${isFavorite(episodeFavorite.key) ? "Remove from favorites" : "Add to favorites"}">${favoriteIcon(isFavorite(episodeFavorite.key))}</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
episodeRow.querySelector("button[data-action='play-title']").addEventListener("click", () => {
|
||||||
|
playXtream("series", episode.id, episode.ext, `${seriesItem?.name || "Series"} - ${episode.title}`, null, {
|
||||||
|
seriesId: localSeriesId,
|
||||||
|
season: episode.season,
|
||||||
|
episode: episode.episodeNum
|
||||||
|
}).catch(showError);
|
||||||
|
});
|
||||||
|
episodeRow.querySelector("button[data-action='toggle-favorite']").addEventListener("click", () => {
|
||||||
|
toggleFavorite(episodeFavorite).then(() => renderFavorites()).catch(showError);
|
||||||
|
});
|
||||||
|
seasonList.appendChild(episodeRow);
|
||||||
|
});
|
||||||
|
seasonBlock.appendChild(seasonList);
|
||||||
|
episodesBlock.appendChild(seasonBlock);
|
||||||
|
});
|
||||||
|
wrap.appendChild(episodesBlock);
|
||||||
|
});
|
||||||
|
episodesLi.appendChild(wrap);
|
||||||
|
el.favoritesList.appendChild(episodesLi);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const groupedBySeason = groupEpisodesBySeason(episodesEntry.episodes);
|
const groupedBySeason = groupEpisodesBySeason(episodesEntry.episodes);
|
||||||
groupedBySeason.forEach((group) => {
|
groupedBySeason.forEach((group) => {
|
||||||
@ -1138,6 +1312,9 @@
|
|||||||
case "vod_category":
|
case "vod_category":
|
||||||
await toggleFavoriteVodCategory(favorite);
|
await toggleFavoriteVodCategory(favorite);
|
||||||
break;
|
break;
|
||||||
|
case "series_category":
|
||||||
|
await toggleFavoriteSeriesCategory(favorite);
|
||||||
|
break;
|
||||||
case "custom":
|
case "custom":
|
||||||
playCustom(favorite.title || "Custom stream", String(favorite.url || ""));
|
playCustom(favorite.title || "Custom stream", String(favorite.url || ""));
|
||||||
break;
|
break;
|
||||||
@ -1279,6 +1456,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleFavoriteSeriesCategory(favorite) {
|
||||||
|
const categoryId = String(favorite?.id || "");
|
||||||
|
if (!categoryId) {
|
||||||
|
throw new Error("Missing series category id.");
|
||||||
|
}
|
||||||
|
if (state.expandedFavoriteSeriesCategoryById[categoryId]) {
|
||||||
|
delete state.expandedFavoriteSeriesCategoryById[categoryId];
|
||||||
|
renderFavorites();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.expandedFavoriteSeriesCategoryById[categoryId] = true;
|
||||||
|
renderFavorites();
|
||||||
|
await ensureFavoriteSeriesCategoryItemsLoaded(categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureFavoriteSeriesCategoryItemsLoaded(categoryId) {
|
||||||
|
ensureLibraryReady();
|
||||||
|
if (state.favoriteSeriesCategoryItemsById[categoryId]?.loaded
|
||||||
|
|| state.favoriteSeriesCategoryItemsById[categoryId]?.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.favoriteSeriesCategoryItemsById[categoryId] = {loading: true, loaded: false, episodes: []};
|
||||||
|
renderFavorites();
|
||||||
|
try {
|
||||||
|
const query = new URLSearchParams({type: "series", category_id: categoryId});
|
||||||
|
const payload = await apiJson(`/api/library/items?${query.toString()}`);
|
||||||
|
state.favoriteSeriesCategoryItemsById[categoryId] = {
|
||||||
|
loading: false,
|
||||||
|
loaded: true,
|
||||||
|
episodes: sanitizeSeriesItems(payload.items)
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
state.favoriteSeriesCategoryItemsById[categoryId] = {
|
||||||
|
loading: false,
|
||||||
|
loaded: false,
|
||||||
|
error: error.message || String(error),
|
||||||
|
episodes: []
|
||||||
|
};
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
renderFavorites();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleFavoriteSeasonGroup(seriesId, season) {
|
function toggleFavoriteSeasonGroup(seriesId, season) {
|
||||||
const current = state.expandedSeasonByFavoriteSeries[seriesId] || {};
|
const current = state.expandedSeasonByFavoriteSeries[seriesId] || {};
|
||||||
const seasonKey = String(season || "?");
|
const seasonKey = String(season || "?");
|
||||||
@ -1301,6 +1522,9 @@
|
|||||||
if (mode === "vod_category") {
|
if (mode === "vod_category") {
|
||||||
return `VOD category | ID: ${favorite.id || "-"}`;
|
return `VOD category | ID: ${favorite.id || "-"}`;
|
||||||
}
|
}
|
||||||
|
if (mode === "series_category") {
|
||||||
|
return `Series category | ID: ${favorite.id || "-"}`;
|
||||||
|
}
|
||||||
if (mode === "series_episode") {
|
if (mode === "series_episode") {
|
||||||
return `Series episode | ID: ${favorite.id || "-"} | Ext: ${favorite.ext || "mp4"}`;
|
return `Series episode | ID: ${favorite.id || "-"} | Ext: ${favorite.ext || "mp4"}`;
|
||||||
}
|
}
|
||||||
@ -1357,6 +1581,16 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeFavoriteSeriesCategory(category) {
|
||||||
|
const categoryId = String(category?.category_id || "");
|
||||||
|
return {
|
||||||
|
key: `series-category:${categoryId}`,
|
||||||
|
mode: "series_category",
|
||||||
|
id: categoryId,
|
||||||
|
title: String(category?.category_name || `Category ${categoryId}`)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function makeFavoriteSeriesItem(item) {
|
function makeFavoriteSeriesItem(item) {
|
||||||
const seriesId = String(item?.series_id || "");
|
const seriesId = String(item?.series_id || "");
|
||||||
return {
|
return {
|
||||||
@ -1407,6 +1641,7 @@
|
|||||||
state.favorites = state.favorites.filter((item) => item?.key !== key);
|
state.favorites = state.favorites.filter((item) => item?.key !== key);
|
||||||
updateLiveCategoryFavoriteButton();
|
updateLiveCategoryFavoriteButton();
|
||||||
updateVodCategoryFavoriteButton();
|
updateVodCategoryFavoriteButton();
|
||||||
|
updateSeriesCategoryFavoriteButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCustomStreams() {
|
function renderCustomStreams() {
|
||||||
@ -1993,6 +2228,7 @@
|
|||||||
fillCategorySelect(el.seriesCategory, state.seriesCategories, "All series");
|
fillCategorySelect(el.seriesCategory, state.seriesCategories, "All series");
|
||||||
updateLiveCategoryFavoriteButton();
|
updateLiveCategoryFavoriteButton();
|
||||||
updateVodCategoryFavoriteButton();
|
updateVodCategoryFavoriteButton();
|
||||||
|
updateSeriesCategoryFavoriteButton();
|
||||||
renderLiveStreams();
|
renderLiveStreams();
|
||||||
renderVodStreams();
|
renderVodStreams();
|
||||||
renderSeriesList();
|
renderSeriesList();
|
||||||
|
|||||||
@ -112,7 +112,10 @@
|
|||||||
Search
|
Search
|
||||||
<input id="series-search" type="search" placeholder="Series title">
|
<input id="series-search" type="search" placeholder="Series title">
|
||||||
</label>
|
</label>
|
||||||
|
<div class="actions">
|
||||||
<button id="series-refresh">Reload</button>
|
<button id="series-refresh">Reload</button>
|
||||||
|
<button id="series-favorite-category" class="favorite-toggle" type="button" title="Add selected category to favorites" aria-label="Add selected category to favorites">☆</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul id="series-list" class="stream-list"></ul>
|
<ul id="series-list" class="stream-list"></ul>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user