Add the ability to register external images into the module using the ImageApp

This commit is contained in:
Oliver 2026-02-07 18:29:20 -07:00
parent 16e61a4855
commit 3fdbdf842c
5 changed files with 144 additions and 43 deletions

View file

@ -1,8 +1,10 @@
import { __ID__, filePath } from "../consts.mjs";
import { convertToWebp, determineFileExtension, getFile, hashFile, lastModifiedAt, uploadFile, uploadJson } from "../utils/fs.mjs";
import { imagePath } from "../utils/imagePath.mjs";
import { DBConnectorMixin } from "./mixins/DBConnector.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
const { fetchWithTimeout } = foundry.utils;
export class ImageApp extends
DBConnectorMixin(
@ -27,6 +29,13 @@ export class ImageApp extends
contentClasses: [
`standard-form`,
],
controls: [
{
icon: `fa-solid fa-arrow-up-right-from-square`,
label: `IT.apps.ImageApp.toggle-upload-mode`,
action: `toggleUploadMode`,
}
],
},
form: {
handler: this.#onSubmit,
@ -34,6 +43,7 @@ export class ImageApp extends
closeOnSubmit: true,
},
actions: {
toggleUploadMode: this.#toggleUploadMode,
removeEditingImage: this.#removeEditingImage,
},
};
@ -47,6 +57,8 @@ export class ImageApp extends
// #endregion Options
// #region Instance Data
#isExternal = false;
/** The artist that is being edited, or the default artist values */
_doc = { name: ``, path: ``, tags: [], artists: [] };
@ -61,6 +73,9 @@ export class ImageApp extends
return _loc(`IT.apps.ImageApp.title.edit-generic`);
}
};
if (this.#isExternal) {
return _loc(`IT.apps.ImageApp.title.register`);
}
return _loc(`IT.apps.ImageApp.title.upload`);
};
// #endregion Instance Data
@ -76,7 +91,11 @@ export class ImageApp extends
if (options.parts?.includes(`header`)) {
this.element.querySelector(`input[type="file"]`)?.addEventListener(
`change`,
this.#changeImage.bind(this),
this.#changeImageInput.bind(this),
);
this.element.querySelector(`file-picker`)?.addEventListener(
`change`,
this.#changeFilePickerInput.bind(this),
);
};
};
@ -88,11 +107,38 @@ export class ImageApp extends
// Validate the DB hasn't been updated since opening
if (!(await this.isAbleToSave())) { return };
// Add the external image into the DB
if (this.#isExternal) {
const path = data.path;
const response = await fetchWithTimeout(path);
const type = response.headers.get(`Content-Type`)
if (!Object.values(CONST.IMAGE_FILE_EXTENSIONS).includes(type)) {
// TODO: error
return;
};
const blob = await response.blob();
const hash = await hashFile(blob);
const images = await getFile(`storage/db/images.json`);
images[hash] = {
name: data.name,
tags: data.tags,
artists: data.artists,
external: true,
path,
};
await uploadJson(`db`, `images.json`, images);
Hooks.callAll(`${__ID__}.imageUploaded`, hash, images[hash]);
}
// Upload new image to server
if (!this._docID) {
else if (!this._docID) {
const hash = this._doc.hash;
const extension = determineFileExtension(this.#file);
const path = `storage/tokens/${hash}.${extension}`;
if (!this.#file) {
// TODO: ERROR
@ -105,12 +151,12 @@ export class ImageApp extends
);
await uploadFile(`tokens`, file);
const images = await getFile(this.constructor.dbPath);
const images = await getFile(`storage/db/images.json`);
images[hash] = {
name: data.name,
tags: data.tags,
artists: data.artists,
path: this.#file ? path : ``,
path: `storage/tokens/${hash}.${extension}`,
};
await uploadJson(`db`, `images.json`, images);
@ -148,17 +194,21 @@ export class ImageApp extends
};
};
let path = imagePath(this._doc);
if (this.#isExternal || this._doc.path.startsWith(`blob`)) {
path = this._doc.path;
};
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),
imageURL: path,
docID: this._docID,
image: this._doc,
artists: this.#artistCache,
external: this.#isExternal,
};
return ctx;
@ -168,7 +218,7 @@ export class ImageApp extends
// #region Actions
/** @type {File | null} */
#file = null;
async #changeImage(event) {
async #changeImageInput(event) {
/** @type {File} */
let file = this.#file = event.target.files[0];
@ -214,6 +264,38 @@ export class ImageApp extends
await this.render({ parts: [`preview`, `form`] });
};
async #changeFilePickerInput(event) {
const picker = event.currentTarget;
const path = picker.value;
if (!path) {
this._docID = null;
this._doc = { path, name: ``, artists: [], tags: [] };
this.render({ parts: [ `preview`, `form` ] });
return;
};
let hash;
try {
const response = await fetchWithTimeout(path);
const blob = await response.blob();
hash = await hashFile(blob);
} catch {
ui.notifications.error(_loc(`IT.notifs.error.cant-find-image`, { url: path }));
picker.value = ``;
return;
};
this._docID = hash;
await this._fetchDocument(true);
if (this._doc.path !== path) {
this._docID = null;
this._doc = { path, name: ``, artists: [], tags: [] };
};
this.render({ parts: [ `preview`, `form` ] });
};
/** @this {ImageApp} */
static async #removeEditingImage() {
this.#file = null;
@ -222,5 +304,11 @@ export class ImageApp extends
this._updateFrame({ window: { title: this.title } });
await this.render({ parts: [`header`, `preview`, `form`] });
};
/** @this {ImageApp} */
static async #toggleUploadMode() {
this.#isExternal = !this.#isExternal;
ImageApp.#removeEditingImage.call(this);
};
// #endregion Actions
};