Skip to content

Commit fb2c431

Browse files
committed
feat: upgrade SyncDashboard and ShopWindow to ApplicationV2
Migrate both Application v1 subclasses to Foundry v12's ApplicationV2 with HandlebarsApplicationMixin for forward-compatibility: - DEFAULT_OPTIONS + PARTS replace static get defaultOptions() - _prepareContext() replaces getData() - _onRender() replaces activateListeners() for DOM setup - Declarative `actions` map handles button click events - render({ force: true }) replaces render(true/false) - Native DOM APIs replace jQuery selectors - Manual tab navigation (CSS class toggling) since ApplicationV2 doesn't auto-manage template-based tabs Also fixes shop header icon: renders FA icon with entity type color when image_path is null (Chronicle entities don't have image URLs). https://claude.ai/code/session_01XMwxFR8BCi5XvgaSVMSBZB
1 parent 749a037 commit fb2c431

5 files changed

Lines changed: 271 additions & 135 deletions

File tree

foundry-module/scripts/module.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ Hooks.on('getSceneControlButtons', (controls) => {
7575
icon: 'fa-solid fa-rotate',
7676
button: true,
7777
onClick: () => {
78-
if (dashboard) dashboard.render(true);
78+
if (dashboard) dashboard.render({ force: true });
7979
},
8080
}],
8181
});
@@ -135,7 +135,7 @@ function _addStatusIndicator() {
135135

136136
// Click to open the sync dashboard.
137137
indicator.addEventListener('click', () => {
138-
if (dashboard) dashboard.render(true);
138+
if (dashboard) dashboard.render({ force: true });
139139
});
140140

141141
// Flash the dot briefly when a WS message arrives (activity indicator).
@@ -184,7 +184,7 @@ Hooks.once('ready', () => {
184184
syncManager,
185185
dashboard,
186186
getAPI: () => syncManager?.api,
187-
openDashboard: () => dashboard?.render(true),
187+
openDashboard: () => dashboard?.render({ force: true }),
188188
};
189189
}
190190
});

foundry-module/scripts/shop-widget.mjs

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class ShopWidget {
8585
});
8686

8787
this._openWindows.set(entityId, window);
88-
await window.render(true);
88+
await window.render({ force: true });
8989
}
9090

9191
/**
@@ -99,18 +99,36 @@ export class ShopWidget {
9999
}
100100
}
101101

102+
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
103+
102104
/**
103105
* ShopWindow is a Foundry ApplicationV2 that displays a shop inventory
104106
* with drag-and-drop support.
105107
*/
106-
class ShopWindow extends Application {
107-
constructor(api, entityId, shopName, onClose) {
108-
super({
109-
title: `Shop: ${shopName}`,
108+
class ShopWindow extends HandlebarsApplicationMixin(ApplicationV2) {
109+
static DEFAULT_OPTIONS = {
110+
id: 'chronicle-shop-{id}',
111+
classes: ['chronicle-shop-window'],
112+
window: {
113+
title: 'Shop',
114+
resizable: true,
115+
},
116+
position: {
110117
width: 400,
111118
height: 500,
112-
resizable: true,
113-
classes: ['chronicle-shop-window'],
119+
},
120+
};
121+
122+
static PARTS = {
123+
shop: {
124+
template: 'modules/chronicle-sync/templates/shop-window.hbs',
125+
},
126+
};
127+
128+
constructor(api, entityId, shopName, onClose) {
129+
super({
130+
id: `chronicle-shop-${entityId}`,
131+
window: { title: `Shop: ${shopName}` },
114132
});
115133

116134
this._api = api;
@@ -121,14 +139,7 @@ class ShopWindow extends Application {
121139
this._entity = null;
122140
}
123141

124-
static get defaultOptions() {
125-
return foundry.utils.mergeObject(super.defaultOptions, {
126-
template: 'modules/chronicle-sync/templates/shop-window.hbs',
127-
classes: ['chronicle-shop-window'],
128-
});
129-
}
130-
131-
async getData() {
142+
async _prepareContext(options = {}) {
132143
try {
133144
// Fetch entity data.
134145
this._entity = await this._api.get(`/entities/${this._entityId}`);
@@ -153,8 +164,15 @@ class ShopWindow extends Application {
153164
description: r.metadata.description || '',
154165
}));
155166

167+
// Normalize entity fields for template (API uses type_icon/type_color).
168+
const entity = this._entity ? {
169+
...this._entity,
170+
icon: this._entity.type_icon || 'fa-store',
171+
color: this._entity.type_color || '#6b7280',
172+
} : null;
173+
156174
return {
157-
entity: this._entity,
175+
entity,
158176
inventory: this._inventory,
159177
shopName: this._shopName,
160178
};
@@ -169,18 +187,23 @@ class ShopWindow extends Application {
169187
}
170188
}
171189

190+
/** Re-fetch data and re-render. */
172191
async refresh() {
173-
await this.render(false);
192+
await this.render({ force: true });
174193
}
175194

176-
activateListeners(html) {
177-
super.activateListeners(html);
195+
/**
196+
* Set up drag-and-drop on shop items after render.
197+
* @param {object} context - Rendering context.
198+
* @param {object} options - Render options.
199+
*/
200+
_onRender(context, options) {
201+
const el = this.element;
178202

179-
// Make shop items draggable.
180-
html.find('.shop-item').each((i, el) => {
181-
el.setAttribute('draggable', true);
182-
el.addEventListener('dragstart', (event) => {
183-
const itemId = el.dataset.itemId;
203+
el.querySelectorAll('.shop-item').forEach((itemEl) => {
204+
itemEl.setAttribute('draggable', 'true');
205+
itemEl.addEventListener('dragstart', (event) => {
206+
const itemId = itemEl.dataset.itemId;
184207
const itemData = this._inventory.find((item) => item.id === itemId) || {};
185208
event.dataTransfer.setData(
186209
'text/plain',

0 commit comments

Comments
 (0)