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: "div", 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"); const iconShape = game.settings.get("dh-improved-countdowns", "iconShape"); const displayMode = game.settings.get("dh-improved-countdowns", "displayMode"); const barOrientation = game.settings.get("dh-improved-countdowns", "barOrientation"); const enableVisualOverlay = game.settings.get("dh-improved-countdowns", "enableVisualOverlay"); const fillType = game.settings.get("dh-improved-countdowns", "fillType"); const invertProgress = game.settings.get("dh-improved-countdowns", "invertProgress"); const numberColor = game.settings.get("dh-improved-countdowns", "numberColor"); const fillColor = game.settings.get("dh-improved-countdowns", "fillColor"); const enableVisualBorder = game.settings.get("dh-improved-countdowns", "enableVisualBorder"); const invertBorder = game.settings.get("dh-improved-countdowns", "invertBorder"); const borderColor = game.settings.get("dh-improved-countdowns", "borderColor"); const borderStyle = game.settings.get("dh-improved-countdowns", "borderStyle"); const borderEdge = game.settings.get("dh-improved-countdowns", "borderEdge"); const gmAlwaysShowNumbers = game.settings.get("dh-improved-countdowns", "gmAlwaysShowNumbers"); const showNumbers = (isGM && gmAlwaysShowNumbers) || displayMode === "number" || displayMode === "both"; const showVisuals = displayMode === "visual" || displayMode === "both"; // 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) { const current = countdown.progress.current; const max = countdown.progress.start; let percentage = Math.max(0, Math.min(100, (current / max) * 100)); const pctRemaining = 100 - percentage; /* if (invertProgress) { // We handle inversion in template now to preserve position } */ countdowns[id] = { ...countdown, editable: isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, percentage, pctRemaining, cssClass: `shape-${iconShape}` }; } } } const hasCountdowns = Object.keys(countdowns).length > 0; return { countdowns, hasCountdowns, isGM, isMinimized, isLocked, showNumbers, showVisuals, iconShape, barOrientation, enableVisualOverlay, fillType, fillColor, enableVisualBorder, invertBorder, borderColor, borderStyle, borderEdge, invertProgress, numberColor }; } #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); CountdownTrackerApp.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); CountdownTrackerApp.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); } }