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; };