image-tagger/module/apps/Image.mjs

197 lines
4.8 KiB
JavaScript

import { __ID__, filePath } from "../consts.mjs";
import { determineFileExtension, getFile, hashFile, lastModifiedAt, uploadFile, uploadJson } from "../utils/fs.mjs";
import { DBConnectorMixin } from "./mixins/DBConnector.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export class ImageApp extends
DBConnectorMixin(
HandlebarsApplicationMixin(
ApplicationV2
)) {
// #region Options
static dbType = `Image`;
static dbPath = `storage/db/images.json`;
static DEFAULT_OPTIONS = {
tag: `form`,
classes: [
__ID__,
`ImageApp`,
],
position: {
width: 600,
},
window: {
contentClasses: [
`standard-form`,
],
},
form: {
handler: this.#onSubmit,
submitOnChange: false,
closeOnSubmit: true,
},
actions: {},
};
static PARTS = {
header: { template: filePath(`templates/ImageApp/header.hbs`) },
preview: { template: filePath(`templates/ImageApp/preview.hbs`) },
form: { template: filePath(`templates/ImageApp/form.hbs`) },
footer: { template: filePath(`templates/partials/footer.hbs`) },
};
// #endregion Options
// #region Instance Data
/** The artist that is being edited, or the default artist values */
_doc = { path: ``, tags: [], artists: [] };
get title() {
if (this._docID) {
if (this._doc.name) {
return _loc(
`TB.apps.ImageApp.title.edit-named`,
{ name: this._doc.name },
);
} else {
return _loc(`TB.apps.ImageApp.title.edit-generic`);
}
};
return _loc(`TB.apps.ImageApp.title.upload`);
};
// #endregion Instance Data
// #region Lifecycle
_onFirstRender() {
if (this._doc?.name) {
this._updateFrame({ window: { title: this.title } });
};
this.element.querySelector(`input[type="file"]`)?.addEventListener(
`change`,
this.#changeImage.bind(this),
);
};
/** @this {ImageApp} */
static async #onSubmit(event, element, formData) {
const data = formData.object;
// Validate the DB hasn't been updated since opening
if (!(await this.isAbleToSave())) { return };
// Upload new image to server
if (!this._docID) {
const hash = this._doc.hash;
const extension = determineFileExtension(this.#file);
const path = `storage/tokens/${hash}.${extension}`;
if (!this.#file) {
// TODO: ERROR
return;
}
const file = new File(
[this.#file],
`${hash}.${extension}`,
{ type: this.#file.type },
);
await uploadFile(`tokens`, file);
const images = await getFile(this.constructor.dbPath);
images[hash] = {
name: data.name,
tags: data.tags,
artists: data.artists,
path: this.#file ? path : ``,
};
await uploadJson(`db`, `images.json`, images);
}
else {
const images = await getFile(this.constructor.dbPath);
Object.assign(images[this._docID], {
name: data.name,
tage: data.tags,
artists: data.artists,
});
await uploadJson(`db`, `images.json`, images);
};
};
// #endregion Lifecycle
// #region Data Prep
#lastArtistEdit = null;
#artistCache = [];
async _prepareContext() {
// Get all of the available artists for the dropdown
const lastModified = await lastModifiedAt(`storage/db/artists.json`);
if (
this.#lastArtistEdit == null
|| lastModified !== this.#lastArtistEdit
) {
this.#artistCache = [];
const artistDB = await getFile(`storage/db/artists.json`);
for (const [id, artist] of Object.entries(artistDB)) {
this.#artistCache.push({ value: id, label: artist.name });
};
};
const ctx = {
meta: {
idp: this.id,
},
imageTypes: Object.values(CONST.IMAGE_FILE_EXTENSIONS).join(`,`),
imageURL: this._doc.path.startsWith(`blob`)
? this._doc.path
: filePath(this._doc.path),
image: this._doc,
artists: this.#artistCache,
};
return ctx;
};
// #endregion Data Prep
// #region Actions
/** @type {File | null} */
#file = null;
async #changeImage(event) {
/** @type {File} */
const file = this.#file = event.target.files[0];
// Prevent memory leaks
URL.revokeObjectURL(this._doc.path);
if (!file) {
this._docID = null;
this._doc = { path: ``, artists: [], tags: [] };
this._updateFrame({ window: { title: this.title } });
await this.render({ parts: [`preview`, `form`] });
return;
};
// Ensure we don't already have the file uploaded
const extension = determineFileExtension(file);
const hash = await hashFile(file);
const path = `storage/tokens/${hash}.${extension}`;
const lastModified = await lastModifiedAt(path);
if (lastModified) {
this._docID = hash;
this._doc.hash = hash;
await this._fetchDocument();
this._updateFrame({ window: { title: this.title } });
await this.render({ parts: [`preview`, `form`] });
return;
};
// Temporarily blob the image so it can be displayed in-app
const url = URL.createObjectURL(file);
this._doc.path = url;
this._doc.hash = hash;
await this.render({ parts: [`preview`] });
};
// #endregion Actions
};