import { __ID__, filePath } from "../consts.mjs"; import { getFile, lastModifiedAt, uploadJson } from "../utils/fs.mjs"; import { promptViaTemplate } from "../utils/dialogs.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { randomID } = foundry.utils; export class ArtistApp extends HandlebarsApplicationMixin(ApplicationV2) { // #region Options static DEFAULT_OPTIONS = { tag: `form`, classes: [ __ID__, `ArtistApp`, ], position: { width: 350, }, window: { contentClasses: [ `standard-form`, ], }, form: { handler: this.#onSubmit, submitOnChange: false, closeOnSubmit: true, }, actions: { addNewLink: this.#addNewLink, deleteLink: this.#deleteLink, }, }; static PARTS = { form: { template: filePath(`templates/ArtistApp/form.hbs`), }, linkList: { template: filePath(`templates/ArtistApp/linkList.hbs`), }, footer: { template: filePath(`templates/ArtistApp/footer.hbs`), }, }; // #endregion Options // #region Instance Data /** @type { null | string } */ #artistID = null; /** The artist that is being edited, or the default artist values */ #artist = { name: ``, links: [] }; /** @type { null | string } */ #lastModified = null; constructor({ artistID = null, ...opts } = {}) { super(opts); this.#artistID = artistID; }; get title() { if (this.#artistID && this.#artist.name) { return game.i18n.format( `TB.apps.ArtistApp.title.edit`, { name: this.#artist.name }, ); }; return game.i18n.localize(`TB.apps.ArtistApp.title.create`); }; // #endregion Instance Data // #region Lifecycle /** Whether or not the database values have been initialized */ #connected = false; /** * This fetches the DB data that we care about for the purposes of being able * to create/edit entries in the Artists DB */ async #connectToDB() { if (this.#connected) { return true }; this.#lastModified ??= await lastModifiedAt(`storage/db/artists.json`); if (this.#artistID) { const artists = await getFile(`storage/db/artists.json`); if (artists[this.#artistID] == null) { ui.notifications.error(_loc( `TB.notifs.error.artist-ID-404`, { id: this.#artistID }, )); return false; }; Object.assign(this.#artist, artists[this.#artistID]); }; this.#connected = true; return true; }; /** * Ensures that the app is in a state where it is allowed to render * before actually letting it render at all. */ async render(options, ...args) { const allowed = await this.#connectToDB(); if (!allowed) { return this }; return super.render(options, ...args); }; /** * Makes it so that we update the frame's title to include the Artist name if * we're editing an existing artist instead of creating a new one. */ _onFirstRender() { if (this.#artist.name) { this._updateFrame({ window: { title: this.title } }); }; }; /** @this {ArtistApp} */ static async #onSubmit(event, element, formData) { const artist = formData.object; if (artist.name.length === 0) { return }; artist.links = this.#artist.links; artist.links.forEach(link => { delete link.isNew; }); // Validate the DB hasn't been updated since if (this.#artistID) { const newLastModified = await lastModifiedAt(`storage/db/artists.json`); if (newLastModified !== this.#lastModified) { ui.notifications.error( `TB.notifs.error.db-out-of-date`, { localize: true }, ); return; }; }; const artists = getFile(`storage/db/artists.json`); let id = this.#artistID; if (!this.#artistID) { do { id = randomID(); } while (artists[id] != null); }; artists[id] = artist; await uploadJson(`db`, `artists.json`, artists); }; // #endregion Lifecycle // #region Data Prep async _prepareContext() { const ctx = { meta: { idp: this.id, }, artist: this.#artist, }; return ctx; }; // #endregion Data Prep // #region Actions /** @this {ArtistApp} */ static async #addNewLink() { const link = await promptViaTemplate( `TB.dialogs.Link.title`, `templates/Dialogs/Link.hbs`, ); link.isNew = true; this.#artist.links.push(link); this.render({ parts: [ `linkList` ] }); }; /** @this {ArtistApp} */ static async #deleteLink(event, element) { const index = element.closest(`[data-index]`)?.dataset.index; if (index == null) { return }; this.#artist.links.splice(index, 1); this.render({ parts: [ `linkList` ] }); }; // #endregion Actions };