Add the ability to register external images into the module using the ImageApp
This commit is contained in:
parent
16e61a4855
commit
3fdbdf842c
5 changed files with 144 additions and 43 deletions
|
|
@ -48,9 +48,11 @@
|
|||
"ImageApp": {
|
||||
"title": {
|
||||
"upload": "Upload New Image",
|
||||
"register": "Register External Image",
|
||||
"edit-named": "Editing Image: {name}",
|
||||
"edit-generic": "Editing Image"
|
||||
},
|
||||
"toggle-upload-mode": "Toggle Upload Mode",
|
||||
"image-label": "Image",
|
||||
"clear": "Clear",
|
||||
"preview-placeholder": "Select an image to see a preview"
|
||||
|
|
@ -73,7 +75,8 @@
|
|||
"error": {
|
||||
"db-out-of-date": "Database out of date, please try again.",
|
||||
"document-ID-404": "Cannot find {dbType} with ID: {id}",
|
||||
"no-upload-permission": "Cannot save due to missing the \"Upload Files\" permission."
|
||||
"no-upload-permission": "Cannot save due to missing the \"Upload Files\" permission.",
|
||||
"cant-find-image": "Cannot find image at location: {url}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,15 +43,17 @@ export function DBConnectorMixin(HandlebarsApp) {
|
|||
return super.render(options, ...args);
|
||||
};
|
||||
|
||||
async _fetchDocument() {
|
||||
async _fetchDocument(silent = false) {
|
||||
if (!this._docID) { return }
|
||||
const documents = await getFile(this.dbPath);
|
||||
|
||||
if (documents[this._docID] == null) {
|
||||
ui.notifications.error(_loc(
|
||||
`IT.notifs.error.document-ID-404`,
|
||||
{ id: this._docID, dbType: this.dbType },
|
||||
));
|
||||
if (!silent) {
|
||||
ui.notifications.error(_loc(
|
||||
`IT.notifs.error.document-ID-404`,
|
||||
{ id: this._docID, dbType: this.dbType },
|
||||
));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@
|
|||
grid-template-rows: auto auto;
|
||||
}
|
||||
|
||||
file-picker {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
header, footer {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,33 @@
|
|||
<header>
|
||||
<div class="row">
|
||||
<label for="{{meta.idp}}-image">
|
||||
{{ localize "IT.apps.ImageApp.image-label" }}
|
||||
</label>
|
||||
{{#if docID}}
|
||||
<input
|
||||
type="text"
|
||||
id="{{meta.idp}}-image"
|
||||
value="{{imageURL}}"
|
||||
disabled
|
||||
readonly
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-action="removeEditingImage"
|
||||
>
|
||||
{{ localize "IT.apps.ImageApp.clear" }}
|
||||
</button>
|
||||
{{else}}
|
||||
<input
|
||||
type="file"
|
||||
id="{{meta.idp}}-image"
|
||||
accept="{{imageTypes}}"
|
||||
name="file"
|
||||
>
|
||||
{{/if}}
|
||||
</div>
|
||||
<header class="row">
|
||||
<label for="{{meta.idp}}-image">
|
||||
{{ localize "IT.apps.ImageApp.image-label" }}
|
||||
</label>
|
||||
{{#if external}}
|
||||
<file-picker
|
||||
id="{{meta.idp}}-image"
|
||||
type="image"
|
||||
name="path"
|
||||
></file-picker>
|
||||
{{else if docID}}
|
||||
<input
|
||||
type="text"
|
||||
id="{{meta.idp}}-image"
|
||||
value="{{imageURL}}"
|
||||
disabled
|
||||
readonly
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-action="removeEditingImage"
|
||||
>
|
||||
{{ localize "IT.apps.ImageApp.clear" }}
|
||||
</button>
|
||||
{{else}}
|
||||
<input
|
||||
type="file"
|
||||
id="{{meta.idp}}-image"
|
||||
accept="{{imageTypes}}"
|
||||
name="file"
|
||||
>
|
||||
{{/if}}
|
||||
</header>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue