feat: add new improved, draggable countdown tracker module for Daggerheart.
This commit is contained in:
commit
08e2745b3a
6 changed files with 527 additions and 0 deletions
183
scripts/countdown-app.js
Normal file
183
scripts/countdown-app.js
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export class CountdownTrackerApp extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
static instance;
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this._dragData = {
|
||||
isDragging: false,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
startLeft: 0,
|
||||
startTop: 0
|
||||
};
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: "dh-improved-countdowns-app",
|
||||
tag: "aside",
|
||||
classes: ["dh-improved-countdowns"],
|
||||
window: {
|
||||
frame: false,
|
||||
positioned: true,
|
||||
},
|
||||
position: {
|
||||
width: "auto",
|
||||
height: "auto",
|
||||
},
|
||||
actions: {
|
||||
increaseCountdown: CountdownTrackerApp.#onIncrease,
|
||||
decreaseCountdown: CountdownTrackerApp.#onDecrease,
|
||||
addCountdown: CountdownTrackerApp.#onAdd,
|
||||
toggleViewMode: CountdownTrackerApp.#onToggleView,
|
||||
toggleLock: CountdownTrackerApp.#onToggleLock
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
content: {
|
||||
template: "modules/dh-improved-countdowns/templates/countdown-tracker.hbs",
|
||||
},
|
||||
};
|
||||
|
||||
static initialize() {
|
||||
this.instance = new CountdownTrackerApp();
|
||||
const pos = game.settings.get("dh-improved-countdowns", "position");
|
||||
this.instance.render(true, { position: pos });
|
||||
}
|
||||
|
||||
async _prepareContext(options) {
|
||||
const isGM = game.user.isGM;
|
||||
const isMinimized = game.settings.get("dh-improved-countdowns", "minimized");
|
||||
const isLocked = game.settings.get("dh-improved-countdowns", "locked");
|
||||
|
||||
// Fetch countdowns from system settings
|
||||
const systemCountdownSetting = game.settings.get("daggerheart", "countdowns");
|
||||
const countdowns = {};
|
||||
|
||||
if (systemCountdownSetting && systemCountdownSetting.countdowns) {
|
||||
for (const [id, countdown] of Object.entries(systemCountdownSetting.countdowns)) {
|
||||
const ownership = this.#getPlayerOwnership(game.user, systemCountdownSetting, countdown);
|
||||
if (ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
|
||||
countdowns[id] = {
|
||||
...countdown,
|
||||
editable: isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
countdowns,
|
||||
isGM,
|
||||
isMinimized,
|
||||
isLocked
|
||||
};
|
||||
}
|
||||
|
||||
#getPlayerOwnership(user, setting, countdown) {
|
||||
const playerOwnership = countdown.ownership[user.id];
|
||||
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||
? setting.defaultOwnership
|
||||
: playerOwnership;
|
||||
}
|
||||
|
||||
static async #onIncrease(event, target) {
|
||||
const id = target.dataset.id;
|
||||
Hooks.call("editCountdown", true, { id });
|
||||
// The Daggerheart system has a static method for this, let's try to use it if available
|
||||
if (typeof game.system.api.applications.ui.DhCountdowns?.editCountdown === "function") {
|
||||
await game.system.api.applications.ui.DhCountdowns.editCountdown(true, { id });
|
||||
}
|
||||
}
|
||||
|
||||
static async #onDecrease(event, target) {
|
||||
const id = target.dataset.id;
|
||||
if (typeof game.system.api.applications.ui.DhCountdowns?.editCountdown === "function") {
|
||||
await game.system.api.applications.ui.DhCountdowns.editCountdown(false, { id });
|
||||
}
|
||||
}
|
||||
|
||||
static async #onAdd(event, target) {
|
||||
if (!game.user.isGM) return;
|
||||
if (game.system.api.applications.ui.CountdownEdit) {
|
||||
new game.system.api.applications.ui.CountdownEdit().render(true);
|
||||
}
|
||||
}
|
||||
|
||||
static async #onToggleView(event, target) {
|
||||
const current = game.settings.get("dh-improved-countdowns", "minimized");
|
||||
await game.settings.set("dh-improved-countdowns", "minimized", !current);
|
||||
this.instance.render();
|
||||
}
|
||||
|
||||
static async #onToggleLock(event, target) {
|
||||
const current = game.settings.get("dh-improved-countdowns", "locked");
|
||||
await game.settings.set("dh-improved-countdowns", "locked", !current);
|
||||
this.instance.render();
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
this.#setupDragging();
|
||||
}
|
||||
|
||||
#setupDragging() {
|
||||
if (game.settings.get("dh-improved-countdowns", "locked")) return;
|
||||
|
||||
const dragHandle = this.element.querySelector('.drag-handle');
|
||||
if (!dragHandle) return;
|
||||
|
||||
dragHandle.addEventListener('mousedown', this.#onDragStart.bind(this));
|
||||
}
|
||||
|
||||
#onDragStart(e) {
|
||||
if (e.button !== 0) return;
|
||||
|
||||
this._dragData.isDragging = true;
|
||||
this._dragData.startX = e.clientX;
|
||||
this._dragData.startY = e.clientY;
|
||||
|
||||
const rect = this.element.getBoundingClientRect();
|
||||
this._dragData.startLeft = rect.left;
|
||||
this._dragData.startTop = rect.top;
|
||||
|
||||
this.element.style.cursor = 'grabbing';
|
||||
|
||||
window.addEventListener('mousemove', this.#onDragging.bind(this));
|
||||
window.addEventListener('mouseup', this.#onDragEnd.bind(this));
|
||||
}
|
||||
|
||||
#onDragging(e) {
|
||||
if (!this._dragData.isDragging) return;
|
||||
|
||||
const dx = e.clientX - this._dragData.startX;
|
||||
const dy = e.clientY - this._dragData.startY;
|
||||
|
||||
const newLeft = this._dragData.startLeft + dx;
|
||||
const newTop = this._dragData.startTop + dy;
|
||||
|
||||
this.element.style.left = `${newLeft}px`;
|
||||
this.element.style.top = `${newTop}px`;
|
||||
}
|
||||
|
||||
#onDragEnd() {
|
||||
if (!this._dragData.isDragging) return;
|
||||
this._dragData.isDragging = false;
|
||||
this.element.style.cursor = '';
|
||||
|
||||
window.removeEventListener('mousemove', this.#onDragging.bind(this));
|
||||
window.removeEventListener('mouseup', this.#onDragEnd.bind(this));
|
||||
|
||||
const rect = this.element.getBoundingClientRect();
|
||||
const pos = {
|
||||
top: rect.top,
|
||||
left: rect.left
|
||||
};
|
||||
|
||||
this.position.top = pos.top;
|
||||
this.position.left = pos.left;
|
||||
|
||||
game.settings.set("dh-improved-countdowns", "position", pos);
|
||||
}
|
||||
}
|
||||
52
scripts/module.js
Normal file
52
scripts/module.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { CountdownTrackerApp } from './countdown-app.js';
|
||||
|
||||
Hooks.once('init', () => {
|
||||
// Register settings for position, locked state, and minimized state
|
||||
game.settings.register("dh-improved-countdowns", "position", {
|
||||
name: "Tracker Position",
|
||||
scope: "client",
|
||||
config: false,
|
||||
type: Object,
|
||||
default: { top: 100, left: 100 }
|
||||
});
|
||||
|
||||
game.settings.register("dh-improved-countdowns", "locked", {
|
||||
name: "Lock Tracker Position",
|
||||
hint: "Prevents the countdown tracker from being dragged.",
|
||||
scope: "client",
|
||||
config: true,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
onChange: () => CountdownTrackerApp.instance?.render()
|
||||
});
|
||||
|
||||
game.settings.register("dh-improved-countdowns", "minimized", {
|
||||
name: "Minimized View",
|
||||
scope: "client",
|
||||
config: false,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
onChange: () => CountdownTrackerApp.instance?.render()
|
||||
});
|
||||
});
|
||||
|
||||
Hooks.once('ready', () => {
|
||||
// Hide default countdown tracker via CSS (handled in countdown.css)
|
||||
|
||||
// Initialize our improved tracker
|
||||
CountdownTrackerApp.initialize();
|
||||
});
|
||||
|
||||
// Re-render when countdowns change in the system
|
||||
Hooks.on('daggerheart.refresh', (data) => {
|
||||
if (data.refreshType === "countdown" || data.refreshType === 4) { // 4 is RefreshType.Countdown in Daggerheart
|
||||
CountdownTrackerApp.instance?.render();
|
||||
}
|
||||
});
|
||||
|
||||
// Generic socket refresh if system refresh doesn't catch everything
|
||||
Hooks.on('refresh', (data) => {
|
||||
if (data.refreshType === "countdown") {
|
||||
CountdownTrackerApp.instance?.render();
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue