Moved the popup manager into its own file. Made it more robust so there should...
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
|
import { L5r5ePopupManager } from '../misc/l5r5e-popup-manager.js';
|
||||||
|
|
||||||
const HandlebarsApplicationMixin = foundry.applications.api.HandlebarsApplicationMixin;
|
const HandlebarsApplicationMixin = foundry.applications.api.HandlebarsApplicationMixin;
|
||||||
const ApplicationV2 = foundry.applications.api.ApplicationV2;
|
const ApplicationV2 = foundry.applications.api.ApplicationV2;
|
||||||
|
|
||||||
export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
|
export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
/** @override ApplicationV2 */
|
/** @override ApplicationV2 */
|
||||||
static get DEFAULT_OPTIONS() {
|
static get DEFAULT_OPTIONS() {
|
||||||
@@ -136,33 +138,34 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
}).bind(this.element);
|
}).bind(this.element);
|
||||||
|
|
||||||
// Tooltips
|
// Tooltips
|
||||||
game.l5r5e.HelpersL5r5e.popupManager($(this.element).find(".actor-infos-control"), async (event) => {
|
new L5r5ePopupManager(
|
||||||
const type = $(event.currentTarget).data("type");
|
$(this.element).find(".actor-infos-control"),
|
||||||
if (!type) {
|
async (event) => {
|
||||||
return;
|
const type = $(event.currentTarget).data("type");
|
||||||
}
|
if (!type) return;
|
||||||
if (type === "text") {
|
|
||||||
return $(event.currentTarget).data("text");
|
|
||||||
}
|
|
||||||
|
|
||||||
const uuid = $(event.currentTarget).data("actor-uuid");
|
if (type === "text") {
|
||||||
if (!uuid) {
|
return $(event.currentTarget).data("text");
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
const actor = this.context.actors.find((actor) => actor.uuid === uuid);
|
|
||||||
if (!actor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
const uuid = $(event.currentTarget).data("actor-uuid");
|
||||||
case "armors":
|
if (!uuid) return;
|
||||||
return this.#getTooltipArmors(actor);
|
|
||||||
case "weapons":
|
const actor = this.context.actors.find(actor => actor.uuid === uuid);
|
||||||
return this.#getTooltipWeapons(actor);
|
if (!actor) return;
|
||||||
case "global":
|
|
||||||
return actor.isArmy ? this.#getTooltipArmiesGlobal(actor) : this.#getTooltipGlobal(actor);
|
switch (type) {
|
||||||
|
case "armors":
|
||||||
|
return this.#getTooltipArmors(actor);
|
||||||
|
case "weapons":
|
||||||
|
return this.#getTooltipWeapons(actor);
|
||||||
|
case "global":
|
||||||
|
return actor.isArmy
|
||||||
|
? this.#getTooltipArmiesGlobal(actor)
|
||||||
|
: this.#getTooltipGlobal(actor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override ApplicationV2 */
|
/** @override ApplicationV2 */
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { L5r5ePopupManager } from './misc/l5r5e-popup-manager.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends the actor to process special things from L5R.
|
* Extends the actor to process special things from L5R.
|
||||||
*/
|
*/
|
||||||
@@ -509,13 +511,16 @@ export class HelpersL5r5e {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Item detail tooltips
|
// Item detail tooltips
|
||||||
this.popupManager(html.find(".l5r5e-tooltip"), async (event) => {
|
new L5r5ePopupManager(
|
||||||
const item = await HelpersL5r5e.getEmbedItemByEvent(event, actor);
|
html.find(".l5r5e-tooltip"),
|
||||||
if (!item) {
|
async (event) => {
|
||||||
return;
|
const item = await HelpersL5r5e.getEmbedItemByEvent(event, actor);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return await item.renderTextTemplate();
|
||||||
}
|
}
|
||||||
return await item.renderTextTemplate();
|
);
|
||||||
});
|
|
||||||
|
|
||||||
// Open actor sheet
|
// Open actor sheet
|
||||||
html.find(".open-sheet-from-uuid").on("click", async (event) => {
|
html.find(".open-sheet-from-uuid").on("click", async (event) => {
|
||||||
@@ -534,52 +539,6 @@ export class HelpersL5r5e {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Do the Popup for the selected element
|
|
||||||
* @param {Selector} selector HTML Selector
|
|
||||||
* @param {function} callback Callback function(event), must return the html to display
|
|
||||||
*/
|
|
||||||
static popupManager(selector, callback) {
|
|
||||||
const popupPosition = (event, popup) => {
|
|
||||||
let left = +event.clientX + 60,
|
|
||||||
top = +event.clientY;
|
|
||||||
|
|
||||||
let maxY = window.innerHeight - popup.outerHeight();
|
|
||||||
if (top > maxY) {
|
|
||||||
top = maxY - 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
let maxX = window.innerWidth - popup.outerWidth();
|
|
||||||
if (left > maxX) {
|
|
||||||
left -= popup.outerWidth() + 100;
|
|
||||||
}
|
|
||||||
return { left: left + "px", top: top + "px", visibility: "visible" };
|
|
||||||
};
|
|
||||||
|
|
||||||
selector
|
|
||||||
.on("mouseenter", async (event) => {
|
|
||||||
$(document.body).find("#l5r5e-tooltip-ct").remove();
|
|
||||||
|
|
||||||
const tpl = await callback(event);
|
|
||||||
if (!tpl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document.body).append(
|
|
||||||
`<div id="l5r5e-tooltip-ct" class="l5r5e-tooltip l5r5e-tooltip-ct">${tpl}</div>`
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.on("mousemove", (event) => {
|
|
||||||
const popup = $(document.body).find("#l5r5e-tooltip-ct");
|
|
||||||
if (popup) {
|
|
||||||
popup.css(popupPosition(event, popup));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on("mouseleave", () => {
|
|
||||||
$(document.body).find("#l5r5e-tooltip-ct").remove();
|
|
||||||
}); // tooltips
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a Item from a Actor Sheet
|
* Get a Item from a Actor Sheet
|
||||||
* @param {Event} event HTML Event
|
* @param {Event} event HTML Event
|
||||||
|
|||||||
133
system/scripts/misc/l5r5e-popup-manager.js
Normal file
133
system/scripts/misc/l5r5e-popup-manager.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* Manages tooltips for specified elements, showing asynchronously generated content on hover.
|
||||||
|
* Tooltips are appended to a customizable container element.
|
||||||
|
* Automatically cleans up event handlers and observers when target elements are removed.
|
||||||
|
*/
|
||||||
|
export class L5r5ePopupManager {
|
||||||
|
/** @type {string|jQuery} */
|
||||||
|
#selector = null;
|
||||||
|
|
||||||
|
/** @type {(event: MouseEvent) => Promise<string>} */
|
||||||
|
#callback = null;
|
||||||
|
|
||||||
|
/** @type {jQuery|null} */
|
||||||
|
#elements = null;
|
||||||
|
|
||||||
|
/** @type {MutationObserver|null} */
|
||||||
|
#observer = null;
|
||||||
|
|
||||||
|
/** @type {HTMLElement} */
|
||||||
|
#container = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string|jQuery} selector - Selector or jQuery object for tooltip-bound elements.
|
||||||
|
* @param {(event: MouseEvent) => Promise<string>} callback - Async function returning tooltip HTML content.
|
||||||
|
* @param {HTMLElement|jQuery} [container=document.body] - DOM element or jQuery object to contain the tooltip.
|
||||||
|
*/
|
||||||
|
constructor(selector, callback, container = document.body) {
|
||||||
|
this.#selector = selector;
|
||||||
|
this.#callback = callback;
|
||||||
|
this.#container = container instanceof jQuery ? container[0] : container;
|
||||||
|
|
||||||
|
this.#bindEvents();
|
||||||
|
this.#observeDOM();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind mouseenter, mousemove, and mouseleave events to target elements.
|
||||||
|
* Creates/removes tooltip elements on hover, and updates position on mouse move.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
#bindEvents() {
|
||||||
|
this.#elements = $(this.#selector);
|
||||||
|
|
||||||
|
this.#elements
|
||||||
|
.on("mouseenter.popup", async (event) => {
|
||||||
|
$(this.#container).find("#l5r5e-tooltip-ct").remove();
|
||||||
|
|
||||||
|
const tpl = await this.#callback(event);
|
||||||
|
|
||||||
|
// Abort if no content or the target element is no longer in the DOM
|
||||||
|
if (!tpl || !document.body.contains(event.currentTarget)) return;
|
||||||
|
|
||||||
|
$(this.#container).append(
|
||||||
|
`<div id="l5r5e-tooltip-ct" class="l5r5e-tooltip l5r5e-tooltip-ct">${tpl}</div>`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.on("mousemove.popup", (event) => {
|
||||||
|
const popup = $(this.#container).find("#l5r5e-tooltip-ct");
|
||||||
|
if (popup.length) {
|
||||||
|
popup.css(this.popupPosition(event, popup));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("mouseleave.popup", () => {
|
||||||
|
$(this.#container).find("#l5r5e-tooltip-ct").remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a MutationObserver that watches for removal of tooltip-bound elements
|
||||||
|
* and automatically destroys the manager if none remain.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
#observeDOM() {
|
||||||
|
this.#observer = new MutationObserver(() => {
|
||||||
|
const anyStillPresent = this.#elements?.toArray().some(el => document.body.contains(el));
|
||||||
|
if (!anyStillPresent) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#observer.observe(this.#container, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates CSS positioning for the tooltip based on mouse event,
|
||||||
|
* constraining it inside the viewport.
|
||||||
|
* @param {MouseEvent} event - The mouse event for position reference.
|
||||||
|
* @param {jQuery} popup - The jQuery object representing the tooltip element.
|
||||||
|
* @returns {{left: string, top: string, visibility: string}} CSS styles for tooltip positioning.
|
||||||
|
*/
|
||||||
|
popupPosition(event, popup) {
|
||||||
|
let left = event.clientX + 60;
|
||||||
|
let top = event.clientY;
|
||||||
|
|
||||||
|
const maxY = window.innerHeight - popup.outerHeight();
|
||||||
|
if (top > maxY) {
|
||||||
|
top = maxY - 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxX = window.innerWidth - popup.outerWidth();
|
||||||
|
if (left > maxX) {
|
||||||
|
left -= popup.outerWidth() + 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
left: `${left}px`,
|
||||||
|
top: `${top}px`,
|
||||||
|
visibility: "visible"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unbind all events, remove tooltip elements, and disconnect the MutationObserver.
|
||||||
|
* Cleans up all references for proper garbage collection.
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
if (this.#elements) {
|
||||||
|
this.#elements.off(".popup");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this.#container).find("#l5r5e-tooltip-ct").remove();
|
||||||
|
|
||||||
|
if (this.#observer) {
|
||||||
|
this.#observer.disconnect();
|
||||||
|
this.#observer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#elements = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user