feat: Add Daggerheart actor item updater module for automatic and manual item synchronization with compendiums.
This commit is contained in:
commit
1c5f990d64
7 changed files with 594 additions and 0 deletions
48
README.md
Normal file
48
README.md
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Daggerheart Actor Updater
|
||||||
|
|
||||||
|
A FoundryVTT module for the Daggerheart system that automatically checks your actors for outdated items against the SRD Compendium.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Automatic Checks**: Checks for updates on first load and whenever the Daggerheart system version changes.
|
||||||
|
- **Manual Check**: API available for macros/manual checks.
|
||||||
|
- **Detailed Comparison**: Compares names, images, and system data (descriptions, damage, etc.).
|
||||||
|
- **Selective Updates**: Choose which items to update or ignore specific items (e.g., custom homebrew items).
|
||||||
|
- **Supports**: Classes, Subclasses, Ancestries, Communities, Domains, Weapons, Armor, Consumables, Loot, and Beastforms.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Installation via Manifest URL
|
||||||
|
|
||||||
|
1. In Foundry VTT, go to the **Add-on Modules** tab.
|
||||||
|
2. Click **Install Module**.
|
||||||
|
3. Paste the following URL into the **Manifest URL** field:
|
||||||
|
```
|
||||||
|
https://git.geeks.gay/cosmo/dh-actor-updater/raw/branch/main/module.json
|
||||||
|
```
|
||||||
|
4. Click **Install**.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Enable the module in your world settings.
|
||||||
|
2. On login (if a new system version is detected) or via macro, the "Daggerheart Actor Updater" window will appear if differences are found.
|
||||||
|
3. Review the list of changes.
|
||||||
|
4. Click **Update** to sync with the compendium.
|
||||||
|
5. Click **Ignore** to suppress warnings for a specific item (useful for customized items).
|
||||||
|
|
||||||
|
## Manual Check
|
||||||
|
|
||||||
|
To manually trigger a check, you can use the button in the module settings or run the following macro code:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
game.modules.get('dh-actor-updater').api.checkAll();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
- **Foundry VTT**: v13+
|
||||||
|
- **Daggerheart System**: Verified on 1.5.4
|
||||||
|
|
||||||
|
## Important Note
|
||||||
|
|
||||||
|
This module modifies your actor data directly. **Always back up your world** before performing bulk updates, especially if you have significant customizations on your actors.
|
||||||
38
module.json
Normal file
38
module.json
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"id": "dh-actor-updater",
|
||||||
|
"title": "Daggerheart Actor Updater",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "13",
|
||||||
|
"verified": "13"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "CPTN Cosmo",
|
||||||
|
"email": "cptncosmo@gmail.com",
|
||||||
|
"url": "https://github.com/cptn-cosmo",
|
||||||
|
"discord": "cptn_cosmo",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"relationships": {
|
||||||
|
"systems": [
|
||||||
|
{
|
||||||
|
"id": "daggerheart",
|
||||||
|
"type": "system",
|
||||||
|
"manifest": "",
|
||||||
|
"compatibility": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"scripts/main.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/updater.css"
|
||||||
|
],
|
||||||
|
"description": "Automatically update actors in your world to match the latest compendium items.",
|
||||||
|
"url": "https://github.com/cptn-cosmo/dh-actor-updater",
|
||||||
|
"manifest": "https://git.geeks.gay/cosmo/dh-actor-updater/raw/branch/main/module.json",
|
||||||
|
"download": "https://git.geeks.gay/cosmo/dh-actor-updater/releases/download/1.0.0/dh-actor-updater.zip"
|
||||||
|
}
|
||||||
112
scripts/apps/updater-app.js
Normal file
112
scripts/apps/updater-app.js
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { DHUpdater } from '../updater.js';
|
||||||
|
|
||||||
|
export class ActorUpdaterApp extends Application {
|
||||||
|
|
||||||
|
constructor(updates, options = {}) {
|
||||||
|
super(options);
|
||||||
|
this.updates = updates;
|
||||||
|
this.summaryLog = [];
|
||||||
|
this.isComplete = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get defaultOptions() {
|
||||||
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||||
|
id: 'dh-actor-updater',
|
||||||
|
title: 'Daggerheart Actor Updater',
|
||||||
|
template: 'modules/dh-actor-updater/templates/updater.hbs', // We'll need to register this or inject HTML
|
||||||
|
width: 600,
|
||||||
|
height: 500,
|
||||||
|
resizable: true,
|
||||||
|
classes: ['dh-actor-updater-window']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since I didn't verify if I can easily register templates, I'll use a render hook or just inject HTML string if template loading fails,
|
||||||
|
// but standard module structure supports templates. For simplicity and speed without extra file lookups, I'll override _render to simple HTML or create the template file.
|
||||||
|
// Let's create the template file properly.
|
||||||
|
|
||||||
|
getData() {
|
||||||
|
// If all updates are processed (null), show summary
|
||||||
|
const remaining = this.updates.filter(u => u !== null).length;
|
||||||
|
if (remaining === 0 && this.summaryLog.length > 0) {
|
||||||
|
this.isComplete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
summary: this.isComplete,
|
||||||
|
summaryLog: this.summaryLog,
|
||||||
|
updates: this.updates.map((u, i) => {
|
||||||
|
if (!u) return null;
|
||||||
|
return {
|
||||||
|
id: i,
|
||||||
|
actorName: u.actor.name,
|
||||||
|
itemName: u.item.name,
|
||||||
|
itemImg: u.item.img,
|
||||||
|
changes: Object.keys(u.diff).length
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
activateListeners(html) {
|
||||||
|
super.activateListeners(html);
|
||||||
|
|
||||||
|
html.find('.btn-update').click(async (ev) => {
|
||||||
|
const idx = $(ev.currentTarget).data('index');
|
||||||
|
await this._doUpdate(idx);
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find('.btn-ignore').click(async (ev) => {
|
||||||
|
const idx = $(ev.currentTarget).data('index');
|
||||||
|
await this._doIgnore(idx);
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find('.btn-update-all').click(async () => {
|
||||||
|
const confirmed = await Dialog.confirm({
|
||||||
|
title: "Confirm Update All",
|
||||||
|
content: "<p>Are you sure you want to update <strong>ALL</strong> listed items? This will overwrite their data with the SRD Compendium versions. This cannot be undone.</p>",
|
||||||
|
defaultYes: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
// Clone array indices to iterate safely
|
||||||
|
const indices = this.updates.map((u, i) => u ? i : -1).filter(i => i >= 0);
|
||||||
|
for (let i of indices) {
|
||||||
|
await this._doUpdate(i, false); // Don't re-render on each step
|
||||||
|
}
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _doUpdate(index, render = true) {
|
||||||
|
const update = this.updates[index];
|
||||||
|
if (!update) return;
|
||||||
|
|
||||||
|
await DHUpdater.updateItem(update.item, update.compendiumItem);
|
||||||
|
const msg = `Updated ${update.item.name} on ${update.actor.name}`;
|
||||||
|
this.summaryLog.push(msg);
|
||||||
|
ui.notifications.info(msg);
|
||||||
|
|
||||||
|
// Remove from list
|
||||||
|
this.updates[index] = null;
|
||||||
|
if (render) this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _doIgnore(index) {
|
||||||
|
const update = this.updates[index];
|
||||||
|
if (!update) return;
|
||||||
|
|
||||||
|
const ignored = game.settings.get('dh-actor-updater', 'ignoredItems') || {};
|
||||||
|
ignored[update.item.uuid] = true;
|
||||||
|
await game.settings.set('dh-actor-updater', 'ignoredItems', ignored);
|
||||||
|
|
||||||
|
const msg = `Ignored ${update.item.name} on ${update.actor.name}`;
|
||||||
|
this.summaryLog.push(msg);
|
||||||
|
ui.notifications.info(msg);
|
||||||
|
|
||||||
|
// Remove from list
|
||||||
|
this.updates[index] = null;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
92
scripts/main.js
Normal file
92
scripts/main.js
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { DHUpdater } from './updater.js';
|
||||||
|
import { ActorUpdaterApp } from './apps/updater-app.js';
|
||||||
|
|
||||||
|
Hooks.once('init', () => {
|
||||||
|
game.settings.register('dh-actor-updater', 'ignoredItems', {
|
||||||
|
name: 'Ignored Items',
|
||||||
|
scope: 'world',
|
||||||
|
config: false,
|
||||||
|
type: Object,
|
||||||
|
default: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register('dh-actor-updater', 'lastCheckedVersion', {
|
||||||
|
name: 'Last Checked System Version',
|
||||||
|
scope: 'world',
|
||||||
|
config: false,
|
||||||
|
type: String,
|
||||||
|
default: '0.0.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.registerMenu('dh-actor-updater', 'manualCheck', {
|
||||||
|
name: 'Check for Updates',
|
||||||
|
label: 'Check Now',
|
||||||
|
hint: 'Manually check for updates to your actors against the compendium.',
|
||||||
|
icon: 'fas fa-search',
|
||||||
|
type: ManualCheckConfig,
|
||||||
|
restricted: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class ManualCheckConfig extends FormApplication {
|
||||||
|
render() {
|
||||||
|
game.modules.get('dh-actor-updater').api.checkAll();
|
||||||
|
// Don't actually render a form
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.on('ready', async () => {
|
||||||
|
if (!game.user.isGM) return;
|
||||||
|
|
||||||
|
// Manual check hook exposed to API
|
||||||
|
game.modules.get('dh-actor-updater').api = {
|
||||||
|
checkAll: async () => {
|
||||||
|
await checkAndPrompt();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto-check logic
|
||||||
|
const lastVersion = game.settings.get('dh-actor-updater', 'lastCheckedVersion');
|
||||||
|
const currentVersion = game.system.version;
|
||||||
|
|
||||||
|
if (currentVersion !== lastVersion) {
|
||||||
|
console.log("Daggerheart Actor Updater | System update detected. Checking for item updates...");
|
||||||
|
await checkAndPrompt();
|
||||||
|
await game.settings.set('dh-actor-updater', 'lastCheckedVersion', currentVersion);
|
||||||
|
} else {
|
||||||
|
// Optional: Run on every startup? User "re-prompt again on manual check", not necessarily every startup unless version change.
|
||||||
|
// "First run check" imply checking if it hasn't been checked before (implied by version 0.0.0).
|
||||||
|
// "Re-prompt on system version update" -> Covered.
|
||||||
|
// "Manual check" -> Covered by API.
|
||||||
|
|
||||||
|
// Let's add a setting for "Check on every startup" if wanted, but instructions implied first run or version update.
|
||||||
|
// "module should on first run check" -> handled by version diff check.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function checkAndPrompt() {
|
||||||
|
ui.notifications.info("Daggerheart Actor Updater | Checking for updates...");
|
||||||
|
|
||||||
|
// Collect updates from all actors
|
||||||
|
// "configured actors" - likely means all actors or player characters.
|
||||||
|
// Let's check all actors the GM has permission to update (which is all)
|
||||||
|
|
||||||
|
let allUpdates = [];
|
||||||
|
for (const actor of game.actors) {
|
||||||
|
// Maybe restrict to Characters/Companions?
|
||||||
|
// "configured actors" - usually implies Characters. Adversaries in world might not need updates or might be customizations.
|
||||||
|
// Let's check 'character' and 'companion' types primarily, but maybe 'adversary' too if they are in the world.
|
||||||
|
// User didn't strictly specify, but usually players care about their sheets.
|
||||||
|
if (['character', 'companion'].includes(actor.type)) {
|
||||||
|
const updates = await DHUpdater.checkActor(actor);
|
||||||
|
allUpdates = allUpdates.concat(updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allUpdates.length > 0) {
|
||||||
|
new ActorUpdaterApp(allUpdates).render(true);
|
||||||
|
} else {
|
||||||
|
ui.notifications.info("Daggerheart Actor Updater | No updates found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
171
scripts/updater.js
Normal file
171
scripts/updater.js
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
export class DHUpdater {
|
||||||
|
|
||||||
|
static PACK_MAPPING = {
|
||||||
|
'class': 'daggerheart.classes',
|
||||||
|
'subclass': 'daggerheart.subclasses',
|
||||||
|
'ancestry': 'daggerheart.ancestries',
|
||||||
|
'community': 'daggerheart.communities',
|
||||||
|
'domainCard': 'daggerheart.domains',
|
||||||
|
'weapon': 'daggerheart.weapons',
|
||||||
|
'armor': 'daggerheart.armors',
|
||||||
|
'consumable': 'daggerheart.consumables',
|
||||||
|
'loot': 'daggerheart.loot',
|
||||||
|
'beastform': 'daggerheart.beastforms'
|
||||||
|
};
|
||||||
|
|
||||||
|
static IGNORED_FIELDS = [
|
||||||
|
'_id',
|
||||||
|
'sort',
|
||||||
|
'ownership',
|
||||||
|
'flags',
|
||||||
|
'_stats',
|
||||||
|
'system.quantity',
|
||||||
|
'system.equipped',
|
||||||
|
'system.attuned',
|
||||||
|
'system.favorite',
|
||||||
|
'system.features',
|
||||||
|
'system.subclasses',
|
||||||
|
'system.inventory',
|
||||||
|
'system.characterGuide'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for updates for a specific actor
|
||||||
|
* @param {Actor} actor
|
||||||
|
* @returns {Promise<Array>} List of updates found
|
||||||
|
*/
|
||||||
|
static async checkActor(actor) {
|
||||||
|
if (!actor) return [];
|
||||||
|
|
||||||
|
const updates = [];
|
||||||
|
const ignoredItems = game.settings.get('dh-actor-updater', 'ignoredItems') || {};
|
||||||
|
|
||||||
|
for (const item of actor.items) {
|
||||||
|
// Skip if ignored
|
||||||
|
if (ignoredItems[item.uuid]) continue;
|
||||||
|
|
||||||
|
const packId = this.PACK_MAPPING[item.type];
|
||||||
|
if (!packId) continue; // Not a trackable item type
|
||||||
|
|
||||||
|
const pack = game.packs.get(packId);
|
||||||
|
if (!pack) continue;
|
||||||
|
|
||||||
|
// Try to find the source item in the compendium
|
||||||
|
// We search by name first.
|
||||||
|
// Ideally we'd match by flags.core.sourceId if available,
|
||||||
|
// but Daggerheart items might have just been dropped without keeping link,
|
||||||
|
// or we want to match by name for "generic" updates.
|
||||||
|
// The user prompt implies "compare them to the SRD compendium version", likely by name or origin.
|
||||||
|
|
||||||
|
let compendiumItem = null;
|
||||||
|
const sourceId = item.flags.core?.sourceId;
|
||||||
|
|
||||||
|
if (sourceId && sourceId.startsWith(packId)) {
|
||||||
|
const id = sourceId.split('.').pop();
|
||||||
|
compendiumItem = await pack.getDocument(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!compendiumItem) {
|
||||||
|
// Fallback to name match
|
||||||
|
const index = pack.index.find(i => i.name === item.name);
|
||||||
|
if (index) {
|
||||||
|
compendiumItem = await pack.getDocument(index._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compendiumItem) {
|
||||||
|
const diff = this.compareItems(item, compendiumItem);
|
||||||
|
if (diff) {
|
||||||
|
updates.push({
|
||||||
|
actor: actor,
|
||||||
|
item: item,
|
||||||
|
compendiumItem: compendiumItem,
|
||||||
|
diff: diff
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare actor item with compendium item
|
||||||
|
* @param {Item} item
|
||||||
|
* @param {Item} compendiumItem
|
||||||
|
* @returns {Object|null} Diff object or null if identical
|
||||||
|
*/
|
||||||
|
static compareItems(item, compendiumItem) {
|
||||||
|
const itemData = item.toObject();
|
||||||
|
const compendiumData = compendiumItem.toObject();
|
||||||
|
|
||||||
|
const diff = {};
|
||||||
|
let hasChanges = false;
|
||||||
|
|
||||||
|
// Basic fields
|
||||||
|
if (itemData.name !== compendiumData.name) {
|
||||||
|
// If we matched by ID, name might have changed
|
||||||
|
diff.name = { old: itemData.name, new: compendiumData.name };
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemData.img !== compendiumData.img) {
|
||||||
|
diff.img = { old: itemData.img, new: compendiumData.img };
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// System data comparison
|
||||||
|
const flatItemSystem = foundry.utils.flattenObject(itemData.system);
|
||||||
|
const flatCompendiumSystem = foundry.utils.flattenObject(compendiumData.system);
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(flatCompendiumSystem)) {
|
||||||
|
// Check for ignored fields
|
||||||
|
if (this.IGNORED_FIELDS.some(ignored => key.startsWith(ignored) || `system.${key}` === ignored)) continue;
|
||||||
|
|
||||||
|
// Should probably check if the field exists in compendium but is different in item
|
||||||
|
if (flatItemSystem[key] !== value) {
|
||||||
|
// Check if it's "effectively" the same (e.g. null vs undefined or html differences)
|
||||||
|
// Simple strict equality for now
|
||||||
|
|
||||||
|
// Handle embedded arrays explicitly??
|
||||||
|
// flattenObject handles arrays with numeric indices e.g. "array.0.field"
|
||||||
|
|
||||||
|
diff[`system.${key}`] = { old: flatItemSystem[key], new: value };
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check if item has keys that compendium doesn't (removed fields)
|
||||||
|
// Might be tricky if users added custom data.
|
||||||
|
// We generally care if the Compendium version is "Standard" and we want to enforce it.
|
||||||
|
|
||||||
|
return hasChanges ? diff : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateItem(item, compendiumItem) {
|
||||||
|
const itemData = compendiumItem.toObject();
|
||||||
|
delete itemData._id;
|
||||||
|
delete itemData.ownership;
|
||||||
|
delete itemData.folder;
|
||||||
|
delete itemData.sort;
|
||||||
|
delete itemData.flags;
|
||||||
|
delete itemData._stats;
|
||||||
|
|
||||||
|
// Fields to preserve from Actor (do not overwrite with Compendium data)
|
||||||
|
const PRESERVED_SYSTEM_FIELDS = [
|
||||||
|
// State fields
|
||||||
|
'quantity', 'equipped', 'attuned', 'favorite',
|
||||||
|
// Link fields (referencing other items)
|
||||||
|
'features', 'subclasses', 'inventory', 'characterGuide'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (itemData.system) {
|
||||||
|
for (const field of PRESERVED_SYSTEM_FIELDS) {
|
||||||
|
if (field in itemData.system) {
|
||||||
|
delete itemData.system[field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.update(itemData);
|
||||||
|
}
|
||||||
|
}
|
||||||
82
styles/updater.css
Normal file
82
styles/updater.css
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
.dh-actor-updater-window .window-content {
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px;
|
||||||
|
list-style: none; /* Remove default bullet points */
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-item {
|
||||||
|
border: 1px solid var(--color-border-light-2);
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-item.ignored {
|
||||||
|
opacity: 0.6;
|
||||||
|
background: rgba(50, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-name {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-details {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: var(--color-text-light-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-controls button {
|
||||||
|
width: auto;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-diff {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.85em;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-diff .diff-add {
|
||||||
|
color: #aaffaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-actor-updater-diff .diff-del {
|
||||||
|
color: #ffaaaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer buttons */
|
||||||
|
.dh-actor-updater-footer {
|
||||||
|
padding: 10px;
|
||||||
|
border-top: 1px solid var(--color-border-light-2);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
51
templates/updater.hbs
Normal file
51
templates/updater.hbs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<form class="dh-actor-updater-content">
|
||||||
|
<div class="dh-actor-updater-header">
|
||||||
|
<p>The following items on your actors differ from the SRD Compendium versions.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if summary}}
|
||||||
|
<div class="dh-actor-updater-summary">
|
||||||
|
<h3>Update Summary</h3>
|
||||||
|
<ul>
|
||||||
|
{{#each summaryLog}}
|
||||||
|
<li>{{this}}</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<ul class="dh-actor-updater-list">
|
||||||
|
{{#each updates}}
|
||||||
|
{{#if this}}
|
||||||
|
<li class="dh-actor-updater-item">
|
||||||
|
<div class="dh-actor-updater-info">
|
||||||
|
<div class="dh-actor-updater-name">{{itemName}}</div>
|
||||||
|
<div class="dh-actor-updater-details">
|
||||||
|
Actor: {{actorName}} | Changes: {{changes}} fields
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dh-actor-updater-controls">
|
||||||
|
<button type="button" class="btn-update" data-index="{{id}}">
|
||||||
|
<i class="fas fa-sync"></i> Update
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn-ignore" data-index="{{id}}">
|
||||||
|
<i class="fas fa-ban"></i> Ignore
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
<li class="dh-actor-updater-item">
|
||||||
|
<div class="dh-actor-updater-info">
|
||||||
|
No updates found.
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dh-actor-updater-footer">
|
||||||
|
<button type="button" class="btn-update-all">
|
||||||
|
<i class="fas fa-check-double"></i> Update All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</form>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue