Skip to content

Commit

Permalink
refactor: overhaul metadata settings (#52)
Browse files Browse the repository at this point in the history
- Overhaul the metadata settings section in the plugin settings. By default we let the plugin decide what to do for the main title, alternate title, and description, but each of the three behaviors can be overridden by enabling a checkbox to reveal the new-and-shiny (for the titles at least) advanced selectors. The description selector has also been moved under an override checkbox in this commit, while previously it was always visible.

- Cleaned up the rest of the metadata settings section to be more tidy by renaming and moving around the other settings.

- Overhauled the internals to support the new settings. In practice you shouldn't see much difference in behavior compared to the previous options.

**Warning**: **The title and description settings has been reset to the new default values**, meaning any people that had altered their title/description settings previously should edit them if they don't want to use the new defaults. This is a side-effect of us not having settings migrations. Maybe in the future we will have it, but not now. 🙂
  • Loading branch information
fearnlj01 authored May 2, 2024
1 parent 4258c05 commit 1fb54de
Show file tree
Hide file tree
Showing 10 changed files with 483 additions and 259 deletions.
79 changes: 65 additions & 14 deletions Shokofin/Configuration/PluginConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
using MediaBrowser.Model.Plugins;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Xml.Serialization;
using MediaBrowser.Model.Plugins;
using Shokofin.API.Models;

using CollectionCreationType = Shokofin.Utils.Ordering.CollectionCreationType;
using DisplayLanguageType = Shokofin.Utils.Text.DisplayLanguageType;
using DescriptionProvider = Shokofin.Utils.Text.DescriptionProvider;
using LibraryFilteringMode = Shokofin.Utils.Ordering.LibraryFilteringMode;
using OrderType = Shokofin.Utils.Ordering.OrderType;
using SpecialOrderType = Shokofin.Utils.Ordering.SpecialOrderType;
using TextSourceType = Shokofin.Utils.Text.TextSourceType;
using TitleProvider = Shokofin.Utils.Text.TitleProvider;

namespace Shokofin.Configuration;

Expand Down Expand Up @@ -75,15 +76,35 @@ public virtual string PrettyUrl

#region Metadata

/// <summary>
/// Determines if we use the overridden settings for how the main title is fetched for entries.
/// </summary>
public bool TitleMainOverride { get; set; }

/// <summary>
/// Determines how we'll be selecting our main title for entries.
/// </summary>
public DisplayLanguageType TitleMainType { get; set; }
public TitleProvider[] TitleMainList { get; set; }

/// <summary>
/// The order of which we will be selecting our main title for entries.
/// </summary>
public TitleProvider[] TitleMainOrder { get; set; }

/// <summary>
/// Determines how we'll be selecting the alternate title for our entries.
/// Determines if we use the overridden settings for how the alternate title is fetched for entries.
/// </summary>
public DisplayLanguageType TitleAlternateType { get; set; }
public bool TitleAlternateOverride { get; set; }

/// <summary>
/// Determines how we'll be selecting our alternate title for entries.
/// </summary>
public TitleProvider[] TitleAlternateList { get; set; }

/// <summary>
/// The order of which we will be selecting our alternate title for entries.
/// </summary>
public TitleProvider[] TitleAlternateOrder { get; set; }

/// <summary>
/// Allow choosing any title in the selected language if no official
Expand All @@ -98,20 +119,25 @@ public virtual string PrettyUrl
public bool TitleAddForMultipleEpisodes { get; set; }

/// <summary>
/// Mark any episode that is not considered a normal season epiode with a
/// Mark any episode that is not considered a normal season episode with a
/// prefix and number.
/// </summary>
public bool MarkSpecialsWhenGrouped { get; set; }

/// <summary>
/// Determines if we use the overridden settings for how descriptions are fetched for entries.
/// </summary>
public bool DescriptionSourceOverride { get; set; }

/// <summary>
/// The collection of providers for descriptions. Replaces the former `DescriptionSource`.
/// </summary>
public TextSourceType[] DescriptionSourceList { get; set; }
public DescriptionProvider[] DescriptionSourceList { get; set; }

/// <summary>
/// The prioritisation order of source providers for description sources.
/// </summary>
public TextSourceType[] DescriptionSourceOrder { get; set; }
public DescriptionProvider[] DescriptionSourceOrder { get; set; }

/// <summary>
/// Clean up links within the AniDB description for entries.
Expand Down Expand Up @@ -302,11 +328,36 @@ public PluginConfiguration()
SynopsisCleanMultiEmptyLines = true;
AddAniDBId = true;
AddTMDBId = true;
TitleMainType = DisplayLanguageType.Default;
TitleAlternateType = DisplayLanguageType.Origin;
TitleAllowAny = false;
DescriptionSourceList = new[] { TextSourceType.AniDb, TextSourceType.TvDb, TextSourceType.TMDB };
DescriptionSourceOrder = new[] { TextSourceType.AniDb, TextSourceType.TvDb, TextSourceType.TMDB };
TitleMainOverride = false;
TitleMainList = new[] {
TitleProvider.Shoko_Default,
};
TitleMainOrder = new[] {
TitleProvider.Shoko_Default,
TitleProvider.AniDB_Default,
TitleProvider.AniDB_LibraryLanguage,
TitleProvider.AniDB_CountryOfOrigin,
TitleProvider.TMDB_Default,
TitleProvider.TMDB_LibraryLanguage,
TitleProvider.TMDB_CountryOfOrigin,
};
TitleAlternateOverride = false;
TitleAlternateList = new[] {
TitleProvider.AniDB_CountryOfOrigin
};
TitleAlternateOrder = TitleMainOrder.ToArray();
TitleAllowAny = true;
DescriptionSourceOverride = false;
DescriptionSourceList = new[] {
DescriptionProvider.AniDB,
DescriptionProvider.TvDB,
DescriptionProvider.TMDB,
};
DescriptionSourceOrder = new[] {
DescriptionProvider.AniDB,
DescriptionProvider.TvDB,
DescriptionProvider.TMDB,
};
VirtualFileSystem = CanCreateSymbolicLinks;
VirtualFileSystemThreads = 4;
UseGroupsForShows = false;
Expand Down
111 changes: 94 additions & 17 deletions Shokofin/Configuration/configController.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function filterReconnectIntervals(value) {

// Convert it back into an array.
return Array.from(filteredSet).sort((a, b) => a - b);
}
}

function adjustSortableListElement(element) {
const btnSortable = element.querySelector(".btnSortable");
Expand Down Expand Up @@ -274,16 +274,15 @@ async function defaultSubmit(form) {
const ignoredFolders = filterIgnoredFolders(form.querySelector("#IgnoredFolders").value);

// Metadata settings
config.TitleMainType = form.querySelector("#TitleMainType").value;
config.TitleAlternateType = form.querySelector("#TitleAlternateType").value;
["Main", "Alternate"].forEach((type) => setTitleIntoConfig(form, type, config));
config.TitleAllowAny = form.querySelector("#TitleAllowAny").checked;
config.TitleAddForMultipleEpisodes = form.querySelector("#TitleAddForMultipleEpisodes").checked;
config.MarkSpecialsWhenGrouped = form.querySelector("#MarkSpecialsWhenGrouped").checked;
setDescriptionSourcesIntoConfig(form, config);
config.SynopsisCleanLinks = form.querySelector("#CleanupAniDBDescriptions").checked;
config.SynopsisCleanMultiEmptyLines = form.querySelector("#CleanupAniDBDescriptions").checked;
config.SynopsisCleanMiscLines = form.querySelector("#MinimalAniDBDescriptions").checked;
config.SynopsisRemoveSummary = form.querySelector("#MinimalAniDBDescriptions").checked;
config.SynopsisCleanMiscLines = form.querySelector("#CleanupAniDBDescriptions").checked;
config.SynopsisRemoveSummary = form.querySelector("#CleanupAniDBDescriptions").checked;

// Provider settings
config.AddAniDBId = form.querySelector("#AddAniDBId").checked;
Expand Down Expand Up @@ -464,16 +463,15 @@ async function syncSettings(form) {
const ignoredFolders = filterIgnoredFolders(form.querySelector("#IgnoredFolders").value);

// Metadata settings
config.TitleMainType = form.querySelector("#TitleMainType").value;
config.TitleAlternateType = form.querySelector("#TitleAlternateType").value;
["Main", "Alternate"].forEach((type) => { setTitleIntoConfig(form, type, config) });
config.TitleAllowAny = form.querySelector("#TitleAllowAny").checked;
config.TitleAddForMultipleEpisodes = form.querySelector("#TitleAddForMultipleEpisodes").checked;
config.MarkSpecialsWhenGrouped = form.querySelector("#MarkSpecialsWhenGrouped").checked;
setDescriptionSourcesIntoConfig(form, config);
config.SynopsisCleanLinks = form.querySelector("#CleanupAniDBDescriptions").checked;
config.SynopsisCleanMultiEmptyLines = form.querySelector("#CleanupAniDBDescriptions").checked;
config.SynopsisCleanMiscLines = form.querySelector("#MinimalAniDBDescriptions").checked;
config.SynopsisRemoveSummary = form.querySelector("#MinimalAniDBDescriptions").checked;
config.SynopsisCleanMiscLines = form.querySelector("#CleanupAniDBDescriptions").checked;
config.SynopsisRemoveSummary = form.querySelector("#CleanupAniDBDescriptions").checked;

// Provider settings
config.AddAniDBId = form.querySelector("#AddAniDBId").checked;
Expand Down Expand Up @@ -744,7 +742,23 @@ export default function (page) {
}
});

form.querySelector("#descriptionSourceList").addEventListener("click", onSortableContainerClick);
Array.prototype.forEach.call(
form.querySelectorAll("#descriptionSourceList, #TitleMainList, #TitleAlternateList"),
(el) => el.addEventListener("click", onSortableContainerClick)
);

["Main", "Alternate"].forEach((type) => {
const settingsList = form.querySelector(`#Title${type}List`);

form.querySelector(`#Title${type}Override`).addEventListener("change", ({ target: { checked } }) => {
checked ? settingsList.removeAttribute("hidden") : settingsList.setAttribute("hidden", "");
});
});

form.querySelector("#DescriptionSourceOverride").addEventListener("change", ({ target: { checked } }) => {
const root = form.querySelector("#descriptionSourceList");
checked ? root.removeAttribute("hidden") : root.setAttribute("hidden", "");
});

page.addEventListener("viewshow", async function () {
Dashboard.showLoadingMsg();
Expand All @@ -759,14 +773,12 @@ export default function (page) {
form.querySelector("#Password").value = "";

// Metadata settings
form.querySelector("#TitleMainType").value = config.TitleMainType;
form.querySelector("#TitleAlternateType").value = config.TitleAlternateType;
["Main", "Alternate"].forEach((t) => { setTitleFromConfig(form, t, config) });
form.querySelector("#TitleAllowAny").checked = config.TitleAllowAny;
form.querySelector("#TitleAddForMultipleEpisodes").checked = config.TitleAddForMultipleEpisodes != null ? config.TitleAddForMultipleEpisodes : true;
form.querySelector("#MarkSpecialsWhenGrouped").checked = config.MarkSpecialsWhenGrouped;
await setDescriptionSourcesFromConfig(form, config);
form.querySelector("#CleanupAniDBDescriptions").checked = config.SynopsisCleanMultiEmptyLines || config.SynopsisCleanLinks;
form.querySelector("#MinimalAniDBDescriptions").checked = config.SynopsisRemoveSummary || config.SynopsisCleanMiscLines;
setDescriptionSourcesFromConfig(form, config);
form.querySelector("#CleanupAniDBDescriptions").checked = config.SynopsisCleanMultiEmptyLines || config.SynopsisCleanLinks || config.SynopsisRemoveSummary || config.SynopsisCleanMiscLines;

// Provider settings
form.querySelector("#AddAniDBId").checked = config.AddAniDBId;
Expand Down Expand Up @@ -879,6 +891,7 @@ export default function (page) {
}

function setDescriptionSourcesIntoConfig(form, config) {
const override = form.querySelector("#DescriptionSourceOverride");
const descriptionElements = form.querySelectorAll(`#descriptionSourceList .chkDescriptionSource`);
config.DescriptionSourceList = Array.prototype.filter.call(descriptionElements,
(el) => el.checked)
Expand All @@ -887,12 +900,19 @@ function setDescriptionSourcesIntoConfig(form, config) {
config.DescriptionSourceOrder = Array.prototype.map.call(descriptionElements,
(el) => el.dataset.descriptionsource
);

config.DescriptionSourceOverride = override.checked;
}

async function setDescriptionSourcesFromConfig(form, config) {
const list = form.querySelector("#descriptionSourceList .checkboxList");
function setDescriptionSourcesFromConfig(form, config) {
const root = form.querySelector("#descriptionSourceList");
const override = form.querySelector("#DescriptionSourceOverride");
const list = root.querySelector(".checkboxList");
const listItems = list.querySelectorAll('.listItem');

override.checked = config.DescriptionSourceOverride;
override.checked ? root.removeAttribute("hidden") : root.setAttribute("hidden", "");

for (const item of listItems) {
const source = item.dataset.descriptionsource;
if (config.DescriptionSourceList.includes(source)) {
Expand All @@ -909,7 +929,64 @@ async function setDescriptionSourcesFromConfig(form, config) {
list.append(targetElement);
}
}

for (const option of list.querySelectorAll(".sortableOption")) {
adjustSortableListElement(option)
};
}

/**
* This function **must** be called for each type of title separately, lest the config be incomplete.
* @param {"Main"|"Alternate"} type
*/
function setTitleIntoConfig(form, type, config) {
const titleElements = form.querySelectorAll(`#Title${type}List .chkTitleSource`);
const getSettingName = (el) => `${el.dataset.titleprovider}_${el.dataset.titlestyle}`;

config[`Title${type}List`] = Array.prototype.filter.call(titleElements,
(el) => el.checked)
.map((el) => getSettingName(el));

config[`Title${type}Order`] = Array.prototype.map.call(titleElements,
(el) => getSettingName(el)
);

config[`Title${type}Override`] = form.querySelector(`#Title${type}Override`).checked
}

/**
* This function **must** be called for each type of title separately, lest the config be incomplete.
* @param {"Main"|"Alternate"} type
*/
function setTitleFromConfig(form, type, config) {
const root = form.querySelector(`#Title${type}List`);
const override = form.querySelector(`#Title${type}Override`);
const list = root.querySelector(`.checkboxList`);
const listItems = list.querySelectorAll('.listItem');

override.checked = config[`Title${type}Override`];
override.checked ? root.removeAttribute("hidden") : root.setAttribute("hidden", "");

const getSettingName = (el) => `${el.dataset.titleprovider}_${el.dataset.titlestyle}`;

for (const item of listItems) {
const setting = getSettingName(item);
if (config[`Title${type}List`].includes(setting))
item.querySelector(".chkTitleSource").checked = true;
if (config[`Title${type}Order`].includes(setting))
list.removeChild(item);
}

for (const setting of config[`Title${type}Order`]) {
const targetElement = Array.prototype.find.call(
listItems,
(el) => getSettingName(el) === setting
);
if (targetElement)
list.append(targetElement);
}

for (const option of list.querySelectorAll(".sortableOption")) {
adjustSortableListElement(option)
}
}
Loading

0 comments on commit 1fb54de

Please sign in to comment.