Skip to content

Commit

Permalink
Remove ResultRow dependency on PlexUI
Browse files Browse the repository at this point in the history
Split out UI section operations into its own class, removing the final circular
dependency in client code.
  • Loading branch information
danrahn committed Mar 10, 2024
1 parent 8119454 commit ce76d8b
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 147 deletions.
142 changes: 16 additions & 126 deletions Client/Script/PlexUI.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { $, $$, buildNode, clearEle, clickOnEnterCallback } from './Common.js';
import { ContextualLog } from '../../Shared/ConsoleLog.js';

import { FilterDialog, FilterSettings, SortConditions, SortOrder } from './FilterDialog.js';
import { MovieResultRow, SectionOptionsResultRow, ShowResultRow } from './ResultRow.js';
import { animateOpacity } from './AnimationHelpers.js';
import { FilterSettings, SortConditions, SortOrder } from './FilterDialog.js';
import { MovieResultRow, ResultRow, SectionOptionsResultRow, ShowResultRow } from './ResultRow.js';
import { UISection, UISections } from './ResultSections.js';
import { ClientSettings } from './ClientSettings.js';
import { CustomEvents } from './CustomEvents.js';
import Overlay from './Overlay.js';
Expand All @@ -18,17 +18,6 @@ import { SectionType } from '../../Shared/PlexTypes.js';

const BaseLog = new ContextualLog('PlexUI');

/**
* The result sections of the application.
* Can be bitwise-or'd and -and'd to pass in multiple
* sections at once to relevant methods.
* @enum */
const UISection = {
MoviesOrShows : 0x1,
Seasons : 0x2,
Episodes : 0x4
};

/**
* OR-able modifier states
* @enum */
Expand Down Expand Up @@ -71,16 +60,6 @@ class PlexUIManager {
* @type {HTMLElement} */
#searchContainer = $('#container');

/**
* The three result sections: shows, seasons, and episodes.
* @type {{[group: number]: HTMLElement}}
* */
#uiSections = {
[UISection.MoviesOrShows] : $('#toplevellist'),
[UISection.Seasons] : $('#seasonlist'),
[UISection.Episodes] : $('#episodelist')
};

/**
* timerID for the search timeout
* @type {number} */
Expand Down Expand Up @@ -209,7 +188,7 @@ class PlexUIManager {
* was changed, requiring the current view to be reset. */
onSettingsApplied(shouldResetView) {
if (shouldResetView) {
this.clearAllSections();
UISections.clearAllSections();
}

if (!this.#searchBox.classList.contains('hidden')) {
Expand All @@ -218,70 +197,6 @@ class PlexUIManager {
}
}

/**
* Add a row to the given UI section.
* @param {UISection} uiSection
* @param {HTMLElement} row */
addRow(uiSection, row) {
this.#uiSections[uiSection].appendChild(row);
}

/** Clears data from the show, season, and episode lists. */
clearAllSections() {
this.clearAndShowSections(UISection.MoviesOrShows | UISection.Seasons | UISection.Episodes);
PlexClientState.clearActiveShow();
}

/**
* Clear out all child elements from the specified UI sections
* @param {UISection} uiSection */
clearSections(uiSection) {
this.#sectionOperation(uiSection, ele => {
clearEle(ele);
});
}

showSections(uiSection) {
const promises = [];
this.#sectionOperation(uiSection, ele => {
const isHidden = ele.classList.contains('hidden');
ele.classList.remove('hidden');
if (isHidden) {
ele.style.opacity = 0;
ele.style.height = 0;
promises.push(animateOpacity(ele, 0, 1, { noReset : true, duration : 100 }, () => {
if (document.activeElement?.id !== 'search') {
$$('.tabbableRow', ele)?.focus();
}
}));
}
});

return Promise.all(promises);
}

hideSections(uiSection) {
/** @type {Promise<void>[]} */
const promises = [];
this.#sectionOperation(uiSection, ele => {
if (ele.classList.contains('hidden')) {
promises.push(Promise.resolve());
} else {
promises.push(animateOpacity(ele, 1, 0, 100, () => { ele.classList.add('hidden'); }));
}
});

return Promise.all(promises);
}

/**
* Clear the given result group of any elements and ensure it's not hidden.
* @param {number} uiSections The group(s) to clear and unhide. */
clearAndShowSections(uiSections) {
this.clearSections(uiSections);
this.showSections(uiSections);
}

async #libraryChanged() {
this.#searchContainer.classList.add('hidden');
this.#lastSearch = null;
Expand All @@ -304,7 +219,7 @@ class PlexUIManager {
}

await PlexClientState.setSection(section, libType);
this.clearAllSections();
UISections.clearAllSections();
if (!isNaN(section) && section !== -1) {
ClientSettings.setLastSection(section);
this.#searchContainer.classList.remove('hidden');
Expand Down Expand Up @@ -349,7 +264,7 @@ class PlexUIManager {
if (this.#lastSearch?.length !== 0 && !FilterSettings.hasFilter()) {
// Previous search was deleted, and we have no filter. Go to default state,
// not loading any results.
this.clearAllSections();
UISections.clearAllSections();
this.#noSearch();
return;
} else if (!FilterSettings.hasFilter()) {
Expand All @@ -375,17 +290,17 @@ class PlexUIManager {
// If we're adjusting the list due to a filter change, we
// don't want to reapply the search itself, just decide what
// items we want to display, unless the sort order has changed.
if (!forFilterReapply || (newSort && !this.#uiSections[UISection.MoviesOrShows].classList.contains('hidden'))) {
if (!forFilterReapply || (newSort && UISections.sectionVisible(UISection.MoviesOrShows))) {

// Remove any existing show/season/marker data
this.clearAllSections();
UISections.clearAllSections();

PlexClientState.search(this.#searchBox.value);

this.clearAndShowSections(UISection.MoviesOrShows);
UISections.clearAndShowSections(UISection.MoviesOrShows);
} else {
// Clear the section, but don't show it
this.clearSections(UISection.MoviesOrShows);
UISections.clearSections(UISection.MoviesOrShows);
}

switch (PlexClientState.activeSectionType()) {
Expand All @@ -409,13 +324,13 @@ class PlexUIManager {
return;
}

const itemList = this.#uiSections[UISection.MoviesOrShows];
const itemList = UISections.getSection(UISection.MoviesOrShows);
itemList.appendChild(new SectionOptionsResultRow().buildRow());
itemList.appendChild(this.noResultsBecauseNoSearchRow());
}

#searchMovies() {
const movieList = this.#uiSections[UISection.MoviesOrShows];
const movieList = UISections.getSection(UISection.MoviesOrShows);
if (ClientSettings.showExtendedMarkerInfo()) {
movieList.appendChild(new SectionOptionsResultRow().buildRow());
}
Expand Down Expand Up @@ -447,7 +362,7 @@ class PlexUIManager {
}

if (nonFiltered === 0) {
movieList.appendChild(this.noResultsBecauseOfFilterRow());
movieList.appendChild(ResultRow.NoResultsBecauseOfFilterRow());
}

if (searchResults.length > nextFilterIndex) {
Expand Down Expand Up @@ -483,7 +398,7 @@ class PlexUIManager {
PlexClientState.clearActiveShow();
}

const showList = this.#uiSections[UISection.MoviesOrShows];
const showList = UISections.getSection(UISection.MoviesOrShows);
if (ClientSettings.showExtendedMarkerInfo()) {
showList.appendChild(new SectionOptionsResultRow().buildRow());
}
Expand All @@ -506,25 +421,12 @@ class PlexUIManager {
}

if (filteredResults.length === 0) {
showList.appendChild(this.noResultsBecauseOfFilterRow());
showList.appendChild(ResultRow.NoResultsBecauseOfFilterRow());
}

PlexClientState.setActiveSearchRows(filteredResults);
}

/**
* Return a row indicating that there are no rows to show because
* the active filter is hiding all of them. Clicking the row displays the filter UI.
* @returns {HTMLElement} */
noResultsBecauseOfFilterRow() {
return buildNode(
'div',
{ class : 'topLevelResult tabbableRow', tabindex : 0 },
'No results with the current filter.',
{ click : () => new FilterDialog(PlexClientState.activeSectionType()).show(),
keydown : clickOnEnterCallback });
}

noResultsBecauseNoSearchRow() {
return buildNode(
'div',
Expand All @@ -535,24 +437,12 @@ class PlexUIManager {
);
}

/**
* Apply the given function to all UI sections specified in uiSections.
* @param {number} uiSections
* @param {(ele: HTMLElement) => void} fn */
#sectionOperation(uiSections, fn) {
for (const group of Object.values(UISection)) {
if (group & uiSections) {
fn(this.#uiSections[group]);
}
}
}

/**
* Callback invoked when a new filter is applied. */
onFilterApplied() {
// Don't start a search if we don't have any existing items, unless
// we're in the "start" page.
const showingStartScreen = $$('.noSearchRow', this.#uiSections[UISection.MoviesOrShows]);
const showingStartScreen = $$('.noSearchRow', UISections.getSection(UISection.MoviesOrShows));
const newSort = FilterSettings.sortBy !== this.#lastSort.by || FilterSettings.sortOrder !== this.#lastSort.order;
this.#lastSort.by = FilterSettings.sortBy;
this.#lastSort.order = FilterSettings.sortOrder;
Expand Down
56 changes: 35 additions & 21 deletions Client/Script/ResultRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ContextualLog } from '../../Shared/ConsoleLog.js';
import { ClientEpisodeData, ClientMovieData } from './ClientDataExtensions.js';
import { errorMessage, errorResponseOverlay, errorToast } from './ErrorHandling.js';
import { FilterDialog, FilterSettings, SortConditions, SortOrder } from './FilterDialog.js';
import { PlexUI, UISection } from './PlexUI.js';
import { UISection, UISections } from './ResultSections.js';
import { BulkActionType } from './BulkActionCommon.js';
import BulkAddOverlay from './BulkAddOverlay.js';
import BulkDeleteOverlay from './BulkDeleteOverlay.js';
Expand Down Expand Up @@ -60,6 +60,20 @@ function filteredListIcon() {

/** Represents a single row of a show/season/episode in the results page. */
class ResultRow {

/**
* Return a row indicating that there are no rows to show because
* the active filter is hiding all of them. Clicking the row displays the filter UI.
* @returns {HTMLElement} */
static NoResultsBecauseOfFilterRow() {
return buildNode(
'div',
{ class : 'topLevelResult tabbableRow', tabindex : 0 },
'No results with the current filter.',
{ click : () => new FilterDialog(PlexClientState.activeSectionType()).show(),
keydown : clickOnEnterCallback });
}

/** The HTML of the row.
* @type {HTMLElement} */
#html;
Expand Down Expand Up @@ -490,9 +504,9 @@ class ShowResultRow extends ResultRow {
const row = this.buildRowColumns(titleNode, customColumn, selected ? null : this.#showClick.bind(this));
if (selected) {
this.addBackButton(row, 'Back to results', async () => {
PlexUI.clearSections(UISection.Seasons | UISection.Episodes);
await PlexUI.hideSections(UISection.Seasons | UISection.Episodes);
PlexUI.showSections(UISection.MoviesOrShows);
UISections.clearSections(UISection.Seasons | UISection.Episodes);
await UISections.hideSections(UISection.Seasons | UISection.Episodes);
UISections.showSections(UISection.MoviesOrShows);
});

row.classList.add('dynamicText');
Expand Down Expand Up @@ -592,10 +606,10 @@ class ShowResultRow extends ResultRow {
* Takes the seasons retrieved for a show and creates and entry for each season.
* @param {SerializedSeasonData[]} seasons List of serialized {@linkcode SeasonData} seasons for a given show. */
async #showSeasons(seasons) {
await PlexUI.hideSections(UISection.MoviesOrShows);
PlexUI.clearAndShowSections(UISection.Seasons);
await UISections.hideSections(UISection.MoviesOrShows);
UISections.clearAndShowSections(UISection.Seasons);

const addRow = row => PlexUI.addRow(UISection.Seasons, row);
const addRow = row => UISections.addRow(UISection.Seasons, row);
if (ClientSettings.showExtendedMarkerInfo()) {
this.#sectionTitle = new SectionOptionsResultRow();
addRow(this.#sectionTitle.buildRow());
Expand Down Expand Up @@ -634,7 +648,7 @@ class ShowResultRow extends ResultRow {


if (!firstRow) {
firstRow = PlexUI.noResultsBecauseOfFilterRow();
firstRow = ResultRow.NoResultsBecauseOfFilterRow();
addRow(firstRow);
}

Expand Down Expand Up @@ -681,8 +695,8 @@ class ShowResultRow extends ResultRow {
/**
* Update what rows are visible based on the new filter. */
onFilterApplied() {
PlexUI.clearSections(UISection.Seasons);
const addRow = row => PlexUI.addRow(UISection.Seasons, row);
UISections.clearSections(UISection.Seasons);
const addRow = row => UISections.addRow(UISection.Seasons, row);
addRow(this.#sectionTitle.html());
addRow(this.#showTitle.html());
addRow(new BulkActionResultRow(this.show()).buildRow());
Expand All @@ -700,7 +714,7 @@ class ShowResultRow extends ResultRow {
}

if (!anyShowing) {
addRow(PlexUI.noResultsBecauseOfFilterRow());
addRow(ResultRow.NoResultsBecauseOfFilterRow());
}

this.#onFilterStatusChanged();
Expand Down Expand Up @@ -798,9 +812,9 @@ class SeasonResultRow extends ResultRow {
const row = this.buildRowColumns(title, null, selected ? null : this.#seasonClick.bind(this));
if (selected) {
this.addBackButton(row, 'Back to seasons', async () => {
await PlexUI.hideSections(UISection.Episodes);
PlexUI.clearSections(UISection.Episodes);
PlexUI.showSections(UISection.Seasons);
await UISections.hideSections(UISection.Episodes);
UISections.clearSections(UISection.Episodes);
UISections.showSections(UISection.Seasons);
});

row.classList.add('dynamicText');
Expand Down Expand Up @@ -958,9 +972,9 @@ class SeasonResultRow extends ResultRow {
* serialized {@linkcode MarkerData} for the episode.
* @param {ChapterMap?} chapterData */
async #showEpisodesAndMarkers(data, chapterData) {
await PlexUI.hideSections(UISection.Seasons);
PlexUI.clearAndShowSections(UISection.Episodes);
const addRow = row => PlexUI.addRow(UISection.Episodes, row);
await UISections.hideSections(UISection.Seasons);
UISections.clearAndShowSections(UISection.Episodes);
const addRow = row => UISections.addRow(UISection.Episodes, row);
if (ClientSettings.showExtendedMarkerInfo()) {
this.#sectionTitle = new SectionOptionsResultRow();
addRow(this.#sectionTitle.buildRow());
Expand Down Expand Up @@ -1002,7 +1016,7 @@ class SeasonResultRow extends ResultRow {
// Episode rows are tabbed a bit differently because of its marker table
$$('.tabbableRow', firstRow)?.focus();
} else {
firstRow = PlexUI.noResultsBecauseOfFilterRow();
firstRow = ResultRow.NoResultsBecauseOfFilterRow();
addRow(firstRow);
firstRow.focus();
}
Expand Down Expand Up @@ -1059,8 +1073,8 @@ class SeasonResultRow extends ResultRow {
return;
}

PlexUI.clearSections(UISection.Episodes);
const addRow = row => PlexUI.addRow(UISection.Episodes, row);
UISections.clearSections(UISection.Episodes);
const addRow = row => UISections.addRow(UISection.Episodes, row);

// Recreate headers
addRow(this.#sectionTitle.html());
Expand All @@ -1082,7 +1096,7 @@ class SeasonResultRow extends ResultRow {
}

if (!anyShowing) {
addRow(PlexUI.noResultsBecauseOfFilterRow());
addRow(ResultRow.NoResultsBecauseOfFilterRow());
}

this.#onFilterStatusChanged();
Expand Down

0 comments on commit ce76d8b

Please sign in to comment.