image-tagger/module/apps/ArtistBrowser.mjs

235 lines
5.4 KiB
JavaScript

import { __ID__, filePath } from "../consts.mjs";
import { getFile, lastModifiedAt } from "../utils/fs.mjs";
import { paginate } from "../utils/pagination.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
const { FormDataExtended } = foundry.applications.ux;
const { deepClone } = foundry.utils;
const PAGE_SIZE = 8;
export class ArtistBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
// #region Options
static DEFAULT_OPTIONS = {
classes: [
__ID__,
`ArtistBrowser`,
`data-browser`,
],
position: {},
window: {},
actions: {
nextPage: this.#nextPage,
prevPage: this.#prevPage,
},
};
static PARTS = {
sidebar: {
template: filePath(`templates/ArtistBrowser/sidebar.hbs`),
},
list: {
template: filePath(`templates/ArtistBrowser/list.hbs`),
templates: [
filePath(`templates/ArtistBrowser/artist.hbs`),
],
},
};
// #endregion Options
// #region Instance Data
#page = 1;
filters = {
name: ``,
sortBy: `name-asc`,
};
constructor({ selectCount = 0, onSubmit = null, ...opts } = {}) {
super(opts);
};
get page() {
return this.#page;
};
async setPage(page) {
if (this.#page == page) { return };
this.#page = page;
if (this.rendered) {
await this.render({ parts: [`images`] });
return;
};
return;
};
// #endregion Instance Data
// #region Lifecycle
#imagesDB = {
lastModified: null,
data: undefined,
};
async #getImages() {
const newLastModified = await lastModifiedAt(`storage/db/images.json`);
if (this.#imagesDB.lastModified && newLastModified === this.#imagesDB.lastModified) {
return;
};
this.#imagesDB.lastModified = newLastModified;
this.#imagesDB.data = await getFile(`storage/db/images.json`);
};
#artistDB = {
lastModified: null,
data: undefined,
};
async #getArtists() {
const newLastModified = await lastModifiedAt(`storage/db/artists.json`);
if (this.#artistDB.lastModified && newLastModified === this.#artistDB.lastModified) {
return;
};
this.#artistDB.lastModified = newLastModified;
this.#artistDB.data = await getFile(`storage/db/artists.json`);
};
async render(...args) {
await this.#getArtists();
await this.#getImages();
return super.render(...args);
};
async _onRender(ctx, options) {
if (options.parts?.includes(`sidebar`)) {
this.element.querySelector(`form.filters`)?.addEventListener(
`change`,
this.#onFilterSubmit.bind(this),
);
};
if (options.parts?.includes(`images`)) {
this._updateSelectedCount();
};
};
// #endregion Lifecycle
// #region Data Prep
_prepareContext() {
return {
meta: {
idp: this.id,
},
can: {
upload: game.user.can(`FILES_UPLOAD`),
},
};
};
_preparePartContext(partID, ctx) {
switch (partID) {
case `sidebar`: {
this._prepareSidebarContext(ctx);
break;
};
case `list`: {
this._prepareListContext(ctx);
break;
};
};
return ctx;
};
_prepareSidebarContext(ctx) {
ctx.name = this.filters.name;
ctx.sortBy = this.filters.sortBy;
ctx.sortOptions = [
{ value: `name-asc`, label: `IT.apps.ArtistBrowser.sort-options.name-asc` },
{ value: `name-desc`, label: `IT.apps.ArtistBrowser.sort-options.name-desc` },
{ value: `image-count-asc`, label: `IT.apps.ArtistBrowser.sort-options.image-count-asc` },
{ value: `image-count-desc`, label: `IT.apps.ArtistBrowser.sort-options.image-count-desc` },
];
};
_prepareListContext(ctx) {
const allArtists = Object.entries(deepClone(this.#artistDB.data));
const allImages = Object.values(this.#imagesDB.data);
const artists = [];
for (const [id, artist] of allArtists) {
// Check if it matches the required filters
if (this.filters.name && !artist.name.includes(this.filters.name)) {
continue;
};
// Populate ephemeral data for rendering
artist.id = id;
let imageCount = 0;
let tags = {};
for (const image of allImages) {
if (!image.artists.includes(id)) continue;
imageCount++;
for (const tag of image.tags) {
tags[tag] ??= { name: tag, count: 0 };
tags[tag].count++;
};
};
artist.imageCount = imageCount;
artist.commonTags = Object.values(tags)
.sort((a, b) => b.count - a.count)
.slice(0, 5);
artists.push(artist);
};
// Paginate after filtering and sorting to give page continuity
artists.sort(compareArtists.bind(undefined, this.filters.sortBy));
const paginated = paginate(artists, this.#page, PAGE_SIZE);
ctx.artists = paginated.page;
ctx.page = this.#page;
ctx.pages = paginated.total;
ctx.has = {
prev: paginated.prev,
next: paginated.next
};
};
// #endregion Data Prep
// #region Actions
async #onFilterSubmit(event) {
event.preventDefault();
event.stopPropagation();
const data = (new FormDataExtended(event.currentTarget)).object;
this.filters = data;
this.render({ parts: [`list`] });
};
/** @this {ArtistBrowser} */
static async #nextPage() {
this.setPage(this.#page + 1);
};
/** @this {ArtistBrowser} */
static async #prevPage() {
this.setPage(this.#page - 1);
};
// #endregion Actions
};
function compareArtists(mode, a, b) {
switch (mode) {
case `name-asc`: {
return a.name.localeCompare(b.name);
};
case `name-desc`: {
return b.name.localeCompare(a.name);
};
case `image-count-asc`: {
return a.imageCount - b.imageCount;
};
case `image-count-desc`: {
return b.imageCount - a.imageCount;
};
};
return 0;
};