// Provides the sorting UI and logic for the Image Drawer list, including sort-type selection and shuffle. // Supports multiple sort strategies (filename, path, date, favourites, etc.) with auto-shuffle mode. import { $el } from "/scripts/ui.js"; import * as SortTypes from "../common/SortTypes.js" import { utilitiesInstance } from "../common/Utilities.js"; import { ImageDrawerComponent, ClassInstanceFactory } from "./Core/ImageDrawerModule.js"; class ImageDrawerListSorting extends ImageDrawerComponent { constructor(args) { super(args); this.sortingWidget; this.sortSelectionWidget; this.sortShuffleButton; this.sortTypes = {}; this.lastShuffleInterval = 1000; this.baseShuffleButtonTooltipText = "Shuffle sorting again (long press to enable auto shuffle mode)"; this.autoModehuffleButtonTooltipText = "Cancel auto shuffle mode"; } initializeSortTypes() { this.addSortType("filenameAscending", SortTypes.SortTypeFilename, true); this.addSortType("filenameDescending", SortTypes.SortTypeFilename, false); this.addSortType("pathAscending", SortTypes.SortTypePath, true); this.addSortType("pathDescending", SortTypes.SortTypePath, false); this.addSortType("friendlyNameAscending", SortTypes.SortTypeFriendlyName, true); this.addSortType("friendlyNameDescending", SortTypes.SortTypeFriendlyName, false); this.addSortType("dateAscending", SortTypes.SortTypeDate, true); this.addSortType("dateDescending", SortTypes.SortTypeDate, false); this.addSortType("fileSizeAscending", SortTypes.SortTypeFileSize, true); this.addSortType("fileSizeDescending", SortTypes.SortTypeFileSize, false); this.addSortType("imageWidthAscending", SortTypes.SortTypeImageWidth, true); this.addSortType("imageWidthDescending", SortTypes.SortTypeImageWidth, false); this.addSortType("imageHeightAscending", SortTypes.SortTypeImageHeight, true); this.addSortType("imageHeightDescending", SortTypes.SortTypeImageHeight, false); this.addSortType("imageAspectRatioAscending", SortTypes.SortTypeImageAspectRatio, true); this.addSortType("imageAspectRatioDescending", SortTypes.SortTypeImageAspectRatio, false); this.addSortType("fileTypeAscending", SortTypes.SortTypeFileType, true); this.addSortType("fileTypeDescending", SortTypes.SortTypeFileType, false); this.addSortType("shuffle", SortTypes.SortTypeShuffle); } addSortType(internalName, type, bIsAscending) { this.sortTypes[internalName] = new type(bIsAscending, this.imageDrawerInstance); } getSortTypes() { return this.sortTypes; } getCurrentSortTypeName() { if (this.sortSelectionWidget) { return this.sortSelectionWidget.value; } else { return ""; } } getCurrentSortTypeObject() { return this.getSortTypeObjectFromName(this.getCurrentSortTypeName()); } sortWithCurrentType() { this.getCurrentSortTypeObject()?.sortImageList(); } getSortTypeObjectFromName(sortName, bIsAscending = undefined) { const sortValues = Object.values(this.sortTypes); let foundSortType; for (const value of sortValues) { // returns true if the name at least partially matched and bIsAscending is not specified, // or is specified and matches the SortType instance if (value.name.includes(sortName) && (bIsAscending == undefined || value.bIsAscending == bIsAscending)) { foundSortType = value; break; } } if (foundSortType) { return foundSortType; } else { console.error(`SortType with name '${sortName}' not found.`); return null; } } getSortTypeObjectFromClassName(className, bIsAscending = undefined) { const sortValues = Object.values(this.sortTypes); let foundSortType; for (const value of sortValues) { // returns true if the name at least partially matched and bIsAscending is not specified, // or is specified and matches the SortType instance const valueType = value.constructor.name; if (valueType == className && (value.bIsAscending == undefined || value.bIsAscending == bIsAscending)) { foundSortType = value; break; } } if (foundSortType) { return foundSortType; } else { console.error(`SortType with class name '${className}' not found.`); return null; } } getSortTypeObjectFromClassType(classType, bIsAscending = undefined) { const sortValues = Object.values(this.sortTypes); let foundSortType; for (const value of sortValues) { // returns true if the name at least partially matched and bIsAscending is not specified, // or is specified and matches the SortType instance const valueType = value.constructor.name; if (valueType == classType.name && (value.bIsAscending == undefined || value.bIsAscending == bIsAscending)) { foundSortType = value; break; } } if (foundSortType) { return foundSortType; } else { console.error(`SortType with name '${classType.name}' not found.`); return null; } } // Selector setOptionSelectedFromOptionName(option) { this.sortSelectionWidget.value = option; this.onOptionSelected(option); } setOptionSelectedFromSortType(type, bIsAscending) { const foundType = this.getSortTypeObjectFromClassType(type, bIsAscending); this.setOptionSelectedFromOptionName(foundType.name); } onOptionSelected(option) { const newType = typeof option === 'object' && option !== null ? this.getSortTypeObjectFromClassName(option.name, option.bIsAscending) : this.getSortTypeObjectFromName(option); newType.sortImageList(); if (newType instanceof SortTypes.SortTypeShuffle) { this.sortShuffleButton.style.display = "unset"; } else { this.sortShuffleButton.style.display = "none"; this.stopAutomaticShuffle(); } const imageDrawerContextSelectorInstance = this.imageDrawerInstance.getComponentByName("ImageDrawerContextSelector"); imageDrawerContextSelectorInstance.getCurrentContextObject().setLastSelectedSorting(newType); } _addSortingOption(optionName) { const option = document.createElement("option"); option.value = optionName; option.textContent = optionName; // Find the correct position to insert the new option let index = 0; while (index < this.sortSelectionWidget.children.length && optionName.localeCompare(this.sortSelectionWidget.children[index].value) > 0) { index++; } // Insert the new option at the correct position this.sortSelectionWidget.insertBefore(option, this.sortSelectionWidget.children[index]); } _addUniqueSortingOption(optionName) { if (!Array.from(this.sortSelectionWidget.children).find(op => op.value === optionName)) { this._addSortingOption(optionName); } } setSortingOptionsFromSortTypeArray(inOptionArray) { this.sortSelectionWidget.replaceChildren(); for (const sortType of inOptionArray) { for (let bool = 1; bool > -1; bool--) { // Ascending and descending const sortObject = this.getSortTypeObjectFromClassType(sortType, bool); if (sortObject) { this._addUniqueSortingOption(sortObject.name); } } } } setSortingOptionsFromStringArray(inOptionArray) { this.sortSelectionWidget.replaceChildren(); for (const optionKey in inOptionArray) { this._addUniqueSortingOption(optionKey); } } _createSortShuffleButton() { this.sortShuffleButton = utilitiesInstance.createLongPressableButton( { textContent: "🔀", title: this.baseShuffleButtonTooltipText, style: { display: "none" } }, async () => { // Regular click if (!this.stopAutomaticShuffle()) { this.sortWithCurrentType(); } }, async () => { // Long press const value = Math.abs(+prompt("Set automatic shuffle interval in milliseconds:", this.lastShuffleInterval)); if (!isNaN(value) && value > 1) { this.stopAutomaticShuffle(); // Stop existing auto mode this.lastShuffleInterval = value; this.sortShuffleButton.style.backgroundColor = "red"; this.sortShuffleButton.title = `${this.autoModehuffleButtonTooltipText} (currently ${value} ms)`; this.sortShuffleButton.timer = setInterval(() => { this.sortWithCurrentType(); }, value); } }, ["JNodes-sort-shuffle-btn"]); return this.sortShuffleButton; } // If the Shuffle sort type is set to automatically work at an interval, // stop it with this function. Returns true if it was in auto mode and was stopped. stopAutomaticShuffle() { if (this.sortShuffleButton?.timer) { clearInterval(this.sortShuffleButton.timer); this.sortShuffleButton.timer = 0; this.sortShuffleButton.style.backgroundColor = ""; this.sortShuffleButton.title = this.baseShuffleButtonTooltipText; return true; } return false; } makeSortingWidget() { this.sortSelectionWidget = $el("select", { style: { width: '100%', } }); this.sortingWidget = $el("div", { style: { width: '100%', display: 'flex', flexDirection: 'row', } }, [ this.sortSelectionWidget, this._createSortShuffleButton() ]); // Add an event listener for the "change" event this.sortSelectionWidget.addEventListener("change", async () => { this.onOptionSelected(this.sortSelectionWidget.value); }); this.initializeSortTypes(); return this.sortingWidget; } } const factoryInstance = new ClassInstanceFactory(ImageDrawerListSorting);