242 lines
5.7 KiB
JavaScript
242 lines
5.7 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: [`list`] });
|
|
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);
|
|
|
|
/*
|
|
This collates all of the image data into the required summary data for the
|
|
display of the artists. Because this does the collation all in one iteration
|
|
it is a more performant way of collecting all of the information once the
|
|
databases get larger
|
|
*/
|
|
const summary = {};
|
|
for (const image of allImages) {
|
|
for (const artistID of image.artists) {
|
|
summary[artistID] ??= { count: 0, tags: {} };
|
|
summary[artistID].count++;
|
|
for (const tag of image.tags) {
|
|
summary[artistID].tags[tag] ??= { name: tag, count: 0 };
|
|
summary[artistID].tags[tag].count++;
|
|
};
|
|
};
|
|
};
|
|
|
|
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;
|
|
|
|
artist.imageCount = summary[id].count;
|
|
artist.commonTags = Object.values(summary[id].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.#page = 1;
|
|
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;
|
|
};
|