mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
[Feature] 613 - Countdown Improvements (#1184)
* Added CountdownEdit view * Added countdowns UI element * . * Fixed migration of countdowns * . * . * style countdown interface, application and ownership dialog * fix buttons height in ownsership selection * . * Added coloured pips to UI cooldowns to signify player visibility if not every player has it * . * Added max-height and overflow * Sync countdown current with max when equal (#1221) * Update module/applications/ui/countdownEdit.mjs Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com> * . --------- Co-authored-by: moliloo <dev.murilobrito@gmail.com> Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com>
This commit is contained in:
parent
07cdcf2d78
commit
906c7ac853
24 changed files with 1024 additions and 498 deletions
|
|
@ -8,10 +8,8 @@ import * as fields from './module/data/fields/_module.mjs';
|
||||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||||
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||||
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
||||||
import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs';
|
|
||||||
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll } from './module/dice/_module.mjs';
|
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll } from './module/dice/_module.mjs';
|
||||||
import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs';
|
import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs';
|
||||||
import { registerCountdownHooks } from './module/data/countdowns.mjs';
|
|
||||||
import {
|
import {
|
||||||
handlebarsRegistration,
|
handlebarsRegistration,
|
||||||
runMigrations,
|
runMigrations,
|
||||||
|
|
@ -140,6 +138,7 @@ Hooks.once('init', () => {
|
||||||
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
||||||
|
|
||||||
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
||||||
|
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
||||||
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
||||||
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
||||||
|
|
||||||
|
|
@ -166,10 +165,12 @@ Hooks.on('ready', async () => {
|
||||||
if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).displayFear !== 'hide')
|
if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).displayFear !== 'hide')
|
||||||
ui.resources.render({ force: true });
|
ui.resources.render({ force: true });
|
||||||
|
|
||||||
|
ui.countdowns = new CONFIG.ui.countdowns();
|
||||||
|
ui.countdowns.render({ force: true });
|
||||||
|
|
||||||
if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
|
if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
|
||||||
ui.compendiumBrowser = new applications.ui.ItemBrowser();
|
ui.compendiumBrowser = new applications.ui.ItemBrowser();
|
||||||
|
|
||||||
registerCountdownHooks();
|
|
||||||
socketRegistration.registerSocketHooks();
|
socketRegistration.registerSocketHooks();
|
||||||
registerRollDiceHooks();
|
registerRollDiceHooks();
|
||||||
socketRegistration.registerUserQueries();
|
socketRegistration.registerUserQueries();
|
||||||
|
|
@ -242,29 +243,6 @@ Hooks.on('chatMessage', (_, message) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('renderJournalDirectory', async (tab, html, _, options) => {
|
|
||||||
if (tab.id === 'journal') {
|
|
||||||
if (options.parts && !options.parts.includes('footer')) return;
|
|
||||||
|
|
||||||
const buttons = tab.element.querySelector('.directory-footer.action-buttons');
|
|
||||||
const title = game.i18n.format('DAGGERHEART.APPLICATIONS.Countdown.title', {
|
|
||||||
type: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.types.narrative')
|
|
||||||
});
|
|
||||||
buttons.insertAdjacentHTML(
|
|
||||||
'afterbegin',
|
|
||||||
`
|
|
||||||
<button id="narrative-countdown-button">
|
|
||||||
<i class="fa-solid fa-stopwatch"></i>
|
|
||||||
<span style="font-weight: 400; font-family: var(--font-sans);">${title}</span>
|
|
||||||
</button>`
|
|
||||||
);
|
|
||||||
|
|
||||||
buttons.querySelector('#narrative-countdown-button').onclick = async () => {
|
|
||||||
new NarrativeCountdowns().open();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Hooks.on('moveToken', async (movedToken, data) => {
|
Hooks.on('moveToken', async (movedToken, data) => {
|
||||||
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
||||||
if (!effectsAutomation.rangeDependent) return;
|
if (!effectsAutomation.rangeDependent) return;
|
||||||
|
|
|
||||||
31
lang/en.json
31
lang/en.json
|
|
@ -331,7 +331,8 @@
|
||||||
"label": { "label": "Label", "hint": "Used for custom" },
|
"label": { "label": "Label", "hint": "Used for custom" },
|
||||||
"value": { "label": "Value" }
|
"value": { "label": "Value" }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"type": { "label": "Countdown Type" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -346,6 +347,26 @@
|
||||||
"encounter": "Encounter"
|
"encounter": "Encounter"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CountdownEdit": {
|
||||||
|
"title": "Countdown Edit",
|
||||||
|
"viewTitle": "Countdowns",
|
||||||
|
"editTitle": "Edit Countdowns",
|
||||||
|
"newCountdown": "New Countdown",
|
||||||
|
"removeCountdownTitle": "Remove Countdown",
|
||||||
|
"removeCountdownText": "Are you sure you want to remove the countdown: {name}?",
|
||||||
|
"current": "Current",
|
||||||
|
"max": "Max",
|
||||||
|
"currentCountdownValue": "Current: {value}",
|
||||||
|
"currentCountdownMax": "Max: {value}",
|
||||||
|
"category": "Category",
|
||||||
|
"type": "Type",
|
||||||
|
"defaultOwnershipTooltip": "The default player ownership of countdowns",
|
||||||
|
"hideNewCountdowns": "Hide New Countdowns"
|
||||||
|
},
|
||||||
|
"DaggerheartMenu": {
|
||||||
|
"title": "GM Tools",
|
||||||
|
"countdowns": "Edit Countdowns"
|
||||||
|
},
|
||||||
"DeleteConfirmation": {
|
"DeleteConfirmation": {
|
||||||
"title": "Delete {type} - {name}",
|
"title": "Delete {type} - {name}",
|
||||||
"text": "Are you sure you want to delete {name}?"
|
"text": "Are you sure you want to delete {name}?"
|
||||||
|
|
@ -2455,6 +2476,11 @@
|
||||||
"playerMessage": "{user} rerolled their {name}"
|
"playerMessage": "{user} rerolled their {name}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Countdowns": {
|
||||||
|
"title": "Countdowns",
|
||||||
|
"toggleIconMode": "Toggle Icon Only",
|
||||||
|
"noPlayerAccess": "This countdown isn't visible to any players"
|
||||||
|
},
|
||||||
"ItemBrowser": {
|
"ItemBrowser": {
|
||||||
"title": "Daggerheart Compendium Browser",
|
"title": "Daggerheart Compendium Browser",
|
||||||
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
||||||
|
|
@ -2562,7 +2588,8 @@
|
||||||
"subclassesAlreadyPresent": "You already have a class and multiclass subclass",
|
"subclassesAlreadyPresent": "You already have a class and multiclass subclass",
|
||||||
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice",
|
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice",
|
||||||
"gmMenuRefresh": "You refreshed all actions and resources {types}",
|
"gmMenuRefresh": "You refreshed all actions and resources {types}",
|
||||||
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class."
|
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class.",
|
||||||
|
"gmRequired": "This action requires an online GM"
|
||||||
},
|
},
|
||||||
"Sidebar": {
|
"Sidebar": {
|
||||||
"daggerheartMenu": {
|
"daggerheartMenu": {
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
export default class OwnershipSelection extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class OwnershipSelection extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
constructor(resolve, reject, name, ownership) {
|
constructor(name, ownership, defaultOwnership) {
|
||||||
super({});
|
super({});
|
||||||
|
|
||||||
this.resolve = resolve;
|
|
||||||
this.reject = reject;
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.ownership = ownership;
|
this.ownership = foundry.utils.deepClone(ownership);
|
||||||
|
this.defaultOwnership = defaultOwnership;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
classes: ['daggerheart', 'views', 'ownership-selection'],
|
classes: ['daggerheart', 'views', 'dialog', 'dh-style', 'ownership-selection'],
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-users'
|
||||||
|
},
|
||||||
position: {
|
position: {
|
||||||
width: 600,
|
width: 600,
|
||||||
height: 'auto'
|
height: 'auto'
|
||||||
|
|
@ -30,43 +32,48 @@ export default class OwnershipSelection extends HandlebarsApplicationMixin(Appli
|
||||||
return game.i18n.format('DAGGERHEART.APPLICATIONS.OwnershipSelection.title', { name: this.name });
|
return game.i18n.format('DAGGERHEART.APPLICATIONS.OwnershipSelection.title', { name: this.name });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOwnershipData(id) {
|
||||||
|
return this.ownership[id] ?? CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT;
|
||||||
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.ownershipOptions = Object.keys(CONST.DOCUMENT_OWNERSHIP_LEVELS).map(level => ({
|
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
|
||||||
value: CONST.DOCUMENT_OWNERSHIP_LEVELS[level],
|
context.ownershipOptions = CONFIG.DH.GENERAL.simpleOwnershiplevels;
|
||||||
label: game.i18n.localize(`OWNERSHIP.${level}`)
|
context.defaultOwnership = this.defaultOwnership;
|
||||||
}));
|
context.ownership = game.users.reduce((acc, user) => {
|
||||||
context.ownership = {
|
if (!user.isGM) {
|
||||||
default: this.ownership.default,
|
acc[user.id] = {
|
||||||
players: Object.keys(this.ownership.players).reduce((acc, x) => {
|
...user,
|
||||||
const user = game.users.get(x);
|
img: user.character?.img ?? 'icons/svg/cowled.svg',
|
||||||
if (!user.isGM) {
|
ownership: this.getOwnershipData(user.id)
|
||||||
acc[x] = {
|
};
|
||||||
img: user.character?.img ?? 'icons/svg/cowled.svg',
|
}
|
||||||
name: user.name,
|
|
||||||
ownership: this.ownership.players[x].value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {});
|
||||||
};
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(event, _, formData) {
|
static async updateData(event, _, formData) {
|
||||||
const { ownership } = foundry.utils.expandObject(formData.object);
|
const data = foundry.utils.expandObject(formData.object);
|
||||||
|
this.close(data);
|
||||||
this.resolve(ownership);
|
|
||||||
this.close(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(fromSave) {
|
async close(data) {
|
||||||
if (!fromSave) {
|
if (data) {
|
||||||
this.reject();
|
this.saveData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async configure(name, ownership, defaultOwnership) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const app = new this(name, ownership, defaultOwnership);
|
||||||
|
app.addEventListener('close', () => resolve(app.saveData), { once: true });
|
||||||
|
app.render({ force: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
selectRefreshable: DaggerheartMenu.#selectRefreshable,
|
selectRefreshable: DaggerheartMenu.#selectRefreshable,
|
||||||
refreshActors: DaggerheartMenu.#refreshActors
|
refreshActors: DaggerheartMenu.#refreshActors,
|
||||||
|
editCountdowns: DaggerheartMenu.#editCountdowns
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -157,4 +158,8 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #editCountdowns() {
|
||||||
|
new game.system.api.applications.ui.CountdownEdit().render(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
export { default as CountdownEdit } from './countdownEdit.mjs';
|
||||||
|
export { default as DhCountdowns } from './countdowns.mjs';
|
||||||
export { default as DhChatLog } from './chatLog.mjs';
|
export { default as DhChatLog } from './chatLog.mjs';
|
||||||
export { default as DhCombatTracker } from './combatTracker.mjs';
|
export { default as DhCombatTracker } from './combatTracker.mjs';
|
||||||
export * as DhCountdowns from './countdowns.mjs';
|
|
||||||
export { default as DhFearTracker } from './fearTracker.mjs';
|
export { default as DhFearTracker } from './fearTracker.mjs';
|
||||||
export { default as DhHotbar } from './hotbar.mjs';
|
export { default as DhHotbar } from './hotbar.mjs';
|
||||||
export { ItemBrowser } from './itemBrowser.mjs';
|
export { ItemBrowser } from './itemBrowser.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { EncounterCountdowns } from '../ui/countdowns.mjs';
|
|
||||||
|
|
||||||
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
actions: {
|
actions: {
|
||||||
|
|
@ -184,8 +182,4 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
await combatant.update({ 'system.actionTokens': newIndex });
|
await combatant.update({ 'system.actionTokens': newIndex });
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static openCountdowns() {
|
|
||||||
new EncounterCountdowns().open();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
199
module/applications/ui/countdownEdit.mjs
Normal file
199
module/applications/ui/countdownEdit.mjs
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
import { DhCountdown } from '../../data/countdowns.mjs';
|
||||||
|
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class CountdownEdit extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
this.editingCountdowns = new Set();
|
||||||
|
this.currentEditCountdown = null;
|
||||||
|
this.hideNewCountdowns = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ['daggerheart', 'dialog', 'dh-style', 'countdown-edit'],
|
||||||
|
tag: 'form',
|
||||||
|
position: { width: 600 },
|
||||||
|
window: {
|
||||||
|
title: 'DAGGERHEART.APPLICATIONS.CountdownEdit.title',
|
||||||
|
icon: 'fa-solid fa-clock-rotate-left'
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
addCountdown: CountdownEdit.#addCountdown,
|
||||||
|
toggleCountdownEdit: CountdownEdit.#toggleCountdownEdit,
|
||||||
|
editCountdownImage: CountdownEdit.#editCountdownImage,
|
||||||
|
editCountdownOwnership: CountdownEdit.#editCountdownOwnership,
|
||||||
|
removeCountdown: CountdownEdit.#removeCountdown
|
||||||
|
},
|
||||||
|
form: { handler: this.updateData, submitOnChange: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
countdowns: {
|
||||||
|
template: 'systems/daggerheart/templates/ui/countdown-edit.hbs',
|
||||||
|
scrollable: ['.expanded-view', '.edit-content']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options);
|
||||||
|
context.isGM = game.user.isGM;
|
||||||
|
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
|
||||||
|
context.defaultOwnership = this.data.defaultOwnership;
|
||||||
|
context.countdownBaseTypes = CONFIG.DH.GENERAL.countdownBaseTypes;
|
||||||
|
context.countdownTypes = CONFIG.DH.GENERAL.countdownTypes;
|
||||||
|
context.hideNewCountdowns = this.hideNewCountdowns;
|
||||||
|
context.countdowns = Object.keys(this.data.countdowns).reduce((acc, key) => {
|
||||||
|
const countdown = this.data.countdowns[key];
|
||||||
|
acc[key] = {
|
||||||
|
...countdown,
|
||||||
|
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].name),
|
||||||
|
progress: {
|
||||||
|
...countdown.progress,
|
||||||
|
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownTypes[countdown.progress.type].label)
|
||||||
|
},
|
||||||
|
editing: this.editingCountdowns.has(key)
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _postRender(_context, _options) {
|
||||||
|
if (this.currentEditCountdown) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const input = this.element.querySelector(
|
||||||
|
`.countdown-edit-container[data-id="${this.currentEditCountdown}"] input`
|
||||||
|
);
|
||||||
|
if (input) {
|
||||||
|
input.select();
|
||||||
|
this.currentEditCountdown = null;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canPerformEdit() {
|
||||||
|
if (game.user.isGM) return true;
|
||||||
|
|
||||||
|
if (!game.users.activeGM) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSetting(update) {
|
||||||
|
const noGM = !game.users.find(x => x.isGM && x.active);
|
||||||
|
if (noGM) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.data.updateSource(update);
|
||||||
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
|
||||||
|
refreshType: RefreshType.Countdown
|
||||||
|
});
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateData(_event, _, formData) {
|
||||||
|
const { hideNewCountdowns, ...settingsData } = foundry.utils.expandObject(formData.object);
|
||||||
|
|
||||||
|
// Sync current and max if max is changing and they were equal before
|
||||||
|
for (const [id, countdown] of Object.entries(settingsData.countdowns ?? {})) {
|
||||||
|
const existing = this.data.countdowns[id];
|
||||||
|
const wasEqual = existing && existing.progress.current === existing.progress.max;
|
||||||
|
if (wasEqual && countdown.progress.max !== existing.progress.max) {
|
||||||
|
countdown.progress.current = countdown.progress.max;
|
||||||
|
} else {
|
||||||
|
countdown.progress.current = Math.min(countdown.progress.current, countdown.progress.max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hideNewCountdowns = hideNewCountdowns;
|
||||||
|
this.updateSetting(settingsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
async gmSetSetting(data) {
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
|
||||||
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: { refreshType: RefreshType.Countdown }
|
||||||
|
});
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||||
|
}
|
||||||
|
|
||||||
|
static #addCountdown() {
|
||||||
|
const id = foundry.utils.randomID();
|
||||||
|
this.editingCountdowns.add(id);
|
||||||
|
this.currentEditCountdown = id;
|
||||||
|
this.updateSetting({
|
||||||
|
[`countdowns.${id}`]: DhCountdown.defaultCountdown(null, this.hideNewCountdowns)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static #editCountdownImage(_, target) {
|
||||||
|
const countdown = this.data.countdowns[target.id];
|
||||||
|
const fp = new foundry.applications.apps.FilePicker.implementation({
|
||||||
|
current: countdown.img,
|
||||||
|
type: 'image',
|
||||||
|
callback: async path => this.updateSetting({ [`countdowns.${target.id}.img`]: path }),
|
||||||
|
top: this.position.top + 40,
|
||||||
|
left: this.position.left + 10
|
||||||
|
});
|
||||||
|
return fp.browse();
|
||||||
|
}
|
||||||
|
|
||||||
|
static #toggleCountdownEdit(_, button) {
|
||||||
|
const { countdownId } = button.dataset;
|
||||||
|
|
||||||
|
const isEditing = this.editingCountdowns.has(countdownId);
|
||||||
|
if (isEditing) this.editingCountdowns.delete(countdownId);
|
||||||
|
else {
|
||||||
|
this.editingCountdowns.add(countdownId);
|
||||||
|
this.currentEditCountdown = countdownId;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #editCountdownOwnership(_, button) {
|
||||||
|
const countdown = this.data.countdowns[button.dataset.countdownId];
|
||||||
|
const data = await game.system.api.applications.dialogs.OwnershipSelection.configure(
|
||||||
|
countdown.name,
|
||||||
|
countdown.ownership,
|
||||||
|
this.data.defaultOwnership
|
||||||
|
);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
this.updateSetting({ [`countdowns.${button.dataset.countdownId}`]: data });
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #removeCountdown(event, button) {
|
||||||
|
const { countdownId } = button.dataset;
|
||||||
|
|
||||||
|
if (!event.shiftKey) {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.CountdownEdit.removeCountdownTitle')
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.CountdownEdit.removeCountdownText', {
|
||||||
|
name: this.data.countdowns[countdownId].name
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.editingCountdowns.has(countdownId)) this.editingCountdowns.delete(countdownId);
|
||||||
|
this.updateSetting({ [`countdowns.-=${countdownId}`]: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,355 +1,218 @@
|
||||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
import constructHTMLButton from '../../helpers/utils.mjs';
|
|
||||||
import OwnershipSelection from '../dialogs/ownershipSelection.mjs';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
|
/**
|
||||||
constructor(basePath) {
|
* A UI element which displays the countdowns in this world.
|
||||||
super({});
|
*
|
||||||
|
* @extends ApplicationV2
|
||||||
|
* @mixes HandlebarsApplication
|
||||||
|
*/
|
||||||
|
|
||||||
this.basePath = basePath;
|
export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
}
|
constructor(options = {}) {
|
||||||
|
super(options);
|
||||||
get title() {
|
|
||||||
return game.i18n.format('DAGGERHEART.APPLICATIONS.Countdown.title', {
|
this.sidebarCollapsed = true;
|
||||||
type: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Countdown.types.${this.basePath}`)
|
this.setupHooks();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['daggerheart', 'dh-style', 'countdown'],
|
id: 'countdowns',
|
||||||
tag: 'form',
|
tag: 'div',
|
||||||
position: { width: 740, height: 700 },
|
classes: ['daggerheart', 'dh-style', 'countdowns', 'faded-ui'],
|
||||||
window: {
|
window: {
|
||||||
|
icon: 'fa-solid fa-clock-rotate-left',
|
||||||
frame: true,
|
frame: true,
|
||||||
title: 'Countdowns',
|
title: 'DAGGERHEART.UI.Countdowns.title',
|
||||||
resizable: true,
|
positioned: false,
|
||||||
|
resizable: false,
|
||||||
minimizable: false
|
minimizable: false
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
addCountdown: this.addCountdown,
|
toggleViewMode: DhCountdowns.#toggleViewMode,
|
||||||
removeCountdown: this.removeCountdown,
|
decreaseCountdown: (_, target) => this.editCountdown(false, target),
|
||||||
editImage: this.onEditImage,
|
increaseCountdown: (_, target) => this.editCountdown(true, target)
|
||||||
openOwnership: this.openOwnership,
|
|
||||||
openCountdownOwnership: this.openCountdownOwnership,
|
|
||||||
toggleSimpleView: this.toggleSimpleView
|
|
||||||
},
|
},
|
||||||
form: { handler: this.updateData, submitOnChange: true }
|
position: {
|
||||||
};
|
width: 400,
|
||||||
|
height: 222,
|
||||||
static PARTS = {
|
top: 50
|
||||||
countdowns: {
|
|
||||||
template: 'systems/daggerheart/templates/ui/countdowns.hbs',
|
|
||||||
scrollable: ['.expanded-view']
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
/** @override */
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
static PARTS = {
|
||||||
|
resources: {
|
||||||
|
root: true,
|
||||||
|
template: 'systems/daggerheart/templates/ui/countdowns.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.mini-countdown-container').forEach(element => {
|
get element() {
|
||||||
element.addEventListener('click', event => this.updateCountdownValue.bind(this)(event, false));
|
return document.body.querySelector('.daggerheart.dh-style.countdowns');
|
||||||
element.addEventListener('contextmenu', event => this.updateCountdownValue.bind(this)(event, true));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _preFirstRender(context, options) {
|
|
||||||
options.position =
|
|
||||||
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].position) ??
|
|
||||||
Countdowns.DEFAULT_OPTIONS.position;
|
|
||||||
|
|
||||||
const viewSetting =
|
|
||||||
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].simple) ?? !game.user.isGM;
|
|
||||||
this.simpleView =
|
|
||||||
game.user.isGM || !this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER) ? viewSetting : true;
|
|
||||||
context.simple = this.simpleView;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onPosition(position) {
|
|
||||||
game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].position, position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
async _renderFrame(options) {
|
async _renderFrame(options) {
|
||||||
const frame = await super._renderFrame(options);
|
const frame = await super._renderFrame(options);
|
||||||
|
|
||||||
if (this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER)) {
|
const iconOnly =
|
||||||
const button = constructHTMLButton({
|
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) ===
|
||||||
label: '',
|
CONFIG.DH.GENERAL.countdownAppMode.iconOnly;
|
||||||
classes: ['header-control', 'icon', 'fa-solid', 'fa-wrench'],
|
if (iconOnly) frame.classList.add('icon-only');
|
||||||
dataset: { action: 'toggleSimpleView', tooltip: 'DAGGERHEART.APPLICATIONS.Countdown.toggleSimple' }
|
else frame.classList.remove('icon-only');
|
||||||
});
|
|
||||||
this.window.controls.after(button);
|
const header = frame.querySelector('.window-header');
|
||||||
}
|
header.querySelector('button[data-action="close"]').remove();
|
||||||
|
|
||||||
|
const minimizeTooltip = game.i18n.localize('DAGGERHEART.UI.Countdowns.toggleIconMode');
|
||||||
|
const minimizeButton = `<a class="header-control" data-tooltip="${minimizeTooltip}" aria-label="${minimizeTooltip}" data-action="toggleViewMode"><i class="fa-solid fa-down-left-and-up-right-to-center"></i></a>`;
|
||||||
|
header.insertAdjacentHTML('beforeEnd', minimizeButton);
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
testUserPermission(level, exact, altSettings) {
|
/** @override */
|
||||||
if (game.user.isGM) return true;
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
const settings =
|
|
||||||
altSettings ?? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
|
||||||
const defaultAllowed = exact ? settings.ownership.default === level : settings.ownership.default >= level;
|
|
||||||
const userAllowed = exact
|
|
||||||
? settings.playerOwnership[game.user.id]?.value === level
|
|
||||||
: settings.playerOwnership[game.user.id]?.value >= level;
|
|
||||||
return defaultAllowed || userAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
const countdownData = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[
|
|
||||||
this.basePath
|
|
||||||
];
|
|
||||||
|
|
||||||
context.isGM = game.user.isGM;
|
context.isGM = game.user.isGM;
|
||||||
context.base = this.basePath;
|
context.sidebarCollapsed = this.sidebarCollapsed;
|
||||||
|
context.iconOnly =
|
||||||
|
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) ===
|
||||||
|
CONFIG.DH.GENERAL.countdownAppMode.iconOnly;
|
||||||
|
|
||||||
context.canCreate = this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, true);
|
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
context.source = {
|
context.countdowns = Object.keys(setting.countdowns).reduce((acc, key) => {
|
||||||
...countdownData,
|
const countdown = setting.countdowns[key];
|
||||||
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
|
const ownership = DhCountdowns.#getPlayerOwnership(game.user, setting, countdown);
|
||||||
const countdown = countdownData.countdowns[key];
|
if (ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) return acc;
|
||||||
|
|
||||||
if (this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED, false, countdown)) {
|
const playersWithAccess = game.users.reduce((acc, user) => {
|
||||||
acc[key] = {
|
const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown);
|
||||||
...countdown,
|
if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
|
||||||
canEdit: this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, true, countdown)
|
acc.push(user);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, []);
|
||||||
};
|
const nonGmPlayers = game.users.filter(x => !x.isGM);
|
||||||
context.systemFields = countdownData.schema.fields;
|
acc[key] = {
|
||||||
context.countdownFields = context.systemFields.countdowns.element.fields;
|
...countdown,
|
||||||
context.simple = this.simpleView;
|
editable: game.user.isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER,
|
||||||
|
playerAccess: playersWithAccess.length !== nonGmPlayers.length ? playersWithAccess : [],
|
||||||
|
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(event, _, formData) {
|
static #getPlayerOwnership(user, setting, countdown) {
|
||||||
const data = foundry.utils.expandObject(formData.object);
|
const playerOwnership = countdown.ownership[user.id];
|
||||||
const newSetting = foundry.utils.mergeObject(
|
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).toObject(),
|
? setting.defaultOwnership
|
||||||
data
|
: playerOwnership;
|
||||||
);
|
}
|
||||||
|
|
||||||
if (game.user.isGM) {
|
toggleCollapsedPosition = async (_, collapsed) => {
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, newSetting);
|
this.sidebarCollapsed = collapsed;
|
||||||
this.render();
|
if (!collapsed) this.element.classList.add('expanded');
|
||||||
} else {
|
else this.element.classList.remove('expanded');
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
};
|
||||||
action: socketEvent.GMUpdate,
|
|
||||||
data: {
|
cooldownRefresh = ({ refreshType }) => {
|
||||||
action: GMUpdateEvent.UpdateSetting,
|
if (refreshType === RefreshType.Countdown) this.render();
|
||||||
uuid: CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
};
|
||||||
update: newSetting
|
|
||||||
}
|
static canPerformEdit() {
|
||||||
});
|
if (game.user.isGM) return true;
|
||||||
|
|
||||||
|
const noGM = !game.users.find(x => x.isGM && x.active);
|
||||||
|
if (noGM) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSetting(update) {
|
static async #toggleViewMode() {
|
||||||
if (game.user.isGM) {
|
const currentMode = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode);
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, update);
|
const appMode = CONFIG.DH.GENERAL.countdownAppMode;
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
const newMode = currentMode === appMode.textIcon ? appMode.iconOnly : appMode.textIcon;
|
||||||
action: socketEvent.Refresh,
|
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode, newMode);
|
||||||
data: {
|
|
||||||
refreshType: RefreshType.Countdown,
|
|
||||||
application: `${this.basePath}-countdowns`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.render();
|
if (newMode === appMode.iconOnly) this.element.classList.add('icon-only');
|
||||||
} else {
|
else this.element.classList.remove('icon-only');
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.GMUpdate,
|
|
||||||
data: {
|
|
||||||
action: GMUpdateEvent.UpdateSetting,
|
|
||||||
uuid: CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
|
||||||
update: update,
|
|
||||||
refresh: { refreshType: RefreshType.Countdown, application: `${this.basePath}-countdowns` }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static onEditImage(_, target) {
|
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
|
||||||
const current = setting.countdowns[target.dataset.countdown].img;
|
|
||||||
const fp = new foundry.applications.apps.FilePicker.implementation({
|
|
||||||
current,
|
|
||||||
type: 'image',
|
|
||||||
callback: async path => this.updateImage.bind(this)(path, target.dataset.countdown),
|
|
||||||
top: this.position.top + 40,
|
|
||||||
left: this.position.left + 10
|
|
||||||
});
|
|
||||||
return fp.browse();
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateImage(path, countdown) {
|
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
|
||||||
await setting.updateSource({
|
|
||||||
[`${this.basePath}.countdowns.${countdown}.img`]: path
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.updateSetting(setting);
|
|
||||||
}
|
|
||||||
|
|
||||||
static openOwnership(_, target) {
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
|
||||||
const ownership = { default: setting.ownership.default, players: setting.playerOwnership };
|
|
||||||
new OwnershipSelection(resolve, reject, this.title, ownership).render(true);
|
|
||||||
}).then(async ownership => {
|
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
|
||||||
await setting.updateSource({
|
|
||||||
[`${this.basePath}.ownership`]: ownership
|
|
||||||
});
|
|
||||||
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, setting.toObject());
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static openCountdownOwnership(_, target) {
|
|
||||||
const countdownId = target.dataset.countdown;
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const countdown = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath]
|
|
||||||
.countdowns[countdownId];
|
|
||||||
const ownership = { default: countdown.ownership.default, players: countdown.playerOwnership };
|
|
||||||
new OwnershipSelection(resolve, reject, countdown.name, ownership).render(true);
|
|
||||||
}).then(async ownership => {
|
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
|
||||||
await setting.updateSource({
|
|
||||||
[`${this.basePath}.countdowns.${countdownId}.ownership`]: ownership
|
|
||||||
});
|
|
||||||
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, setting);
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static async toggleSimpleView() {
|
|
||||||
this.simpleView = !this.simpleView;
|
|
||||||
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].simple, this.simpleView);
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCountdownValue(event, increase) {
|
static async editCountdown(increase, target) {
|
||||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
if (!DhCountdowns.canPerformEdit()) return;
|
||||||
const countdown = countdownSetting[this.basePath].countdowns[event.currentTarget.dataset.countdown];
|
|
||||||
|
|
||||||
if (!this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) {
|
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
return;
|
const countdown = settings.countdowns[target.id];
|
||||||
}
|
const newCurrent = increase
|
||||||
|
? Math.min(countdown.progress.current + 1, countdown.progress.max)
|
||||||
const currentValue = countdown.progress.current;
|
: Math.max(countdown.progress.current - 1, 0);
|
||||||
|
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
|
||||||
if (increase && currentValue === countdown.progress.max) return;
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||||
if (!increase && currentValue === 0) return;
|
refreshType: RefreshType.Countdown
|
||||||
|
|
||||||
await countdownSetting.updateSource({
|
|
||||||
[`${this.basePath}.countdowns.${event.currentTarget.dataset.countdown}.progress.current`]: increase
|
|
||||||
? currentValue + 1
|
|
||||||
: currentValue - 1
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.updateSetting(countdownSetting.toObject());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addCountdown() {
|
static async gmSetSetting(data) {
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
|
||||||
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: { refreshType: RefreshType.Countdown }
|
||||||
|
});
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||||
|
}
|
||||||
|
|
||||||
|
setupHooks() {
|
||||||
|
Hooks.on('collapseSidebar', this.toggleCollapsedPosition.bind());
|
||||||
|
Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind());
|
||||||
|
}
|
||||||
|
|
||||||
|
close(options) {
|
||||||
|
Hooks.off('collapseSidebar', this.toggleCollapsedPosition);
|
||||||
|
Hooks.off(socketEvent.Refresh, this.cooldownRefresh);
|
||||||
|
super.close(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateCountdowns(progressType) {
|
||||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
await countdownSetting.updateSource({
|
const updatedCountdowns = Object.keys(countdownSetting.countdowns).reduce((acc, key) => {
|
||||||
[`${this.basePath}.countdowns.${foundry.utils.randomID()}`]: {
|
const countdown = countdownSetting.countdowns[key];
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.newCountdown'),
|
if (countdown.progress.type === progressType && countdown.progress.current > 0) {
|
||||||
ownership: game.user.isGM
|
acc.push(key);
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
players: {
|
|
||||||
[game.user.id]: { type: CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
await this.updateSetting(countdownSetting.toObject());
|
return acc;
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
static async removeCountdown(_, target) {
|
const countdownData = countdownSetting.toObject();
|
||||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, {
|
||||||
const countdownName = countdownSetting[this.basePath].countdowns[target.dataset.countdown].name;
|
...countdownData,
|
||||||
|
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const countdown = foundry.utils.deepClone(countdownData.countdowns[key]);
|
||||||
window: {
|
if (updatedCountdowns.includes(key)) {
|
||||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.removeCountdownTitle')
|
countdown.progress.current -= 1;
|
||||||
},
|
|
||||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.Countdown.removeCountdownText', { name: countdownName })
|
|
||||||
});
|
|
||||||
if (!confirmed) return;
|
|
||||||
|
|
||||||
await countdownSetting.updateSource({ [`${this.basePath}.countdowns.-=${target.dataset.countdown}`]: null });
|
|
||||||
|
|
||||||
await this.updateSetting(countdownSetting.toObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
async open() {
|
|
||||||
await this.render(true);
|
|
||||||
if (
|
|
||||||
Object.keys(
|
|
||||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath].countdowns
|
|
||||||
).length > 0
|
|
||||||
) {
|
|
||||||
this.minimize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NarrativeCountdowns extends Countdowns {
|
|
||||||
constructor() {
|
|
||||||
super('narrative');
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
id: 'narrative-countdowns'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EncounterCountdowns extends Countdowns {
|
|
||||||
constructor() {
|
|
||||||
super('encounter');
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
id: 'encounter-countdowns'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateCountdowns(progressType) {
|
|
||||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
|
||||||
const update = Object.keys(countdownSetting).reduce((update, typeKey) => {
|
|
||||||
return foundry.utils.mergeObject(
|
|
||||||
update,
|
|
||||||
Object.keys(countdownSetting[typeKey].countdowns).reduce((acc, countdownKey) => {
|
|
||||||
const countdown = countdownSetting[typeKey].countdowns[countdownKey];
|
|
||||||
if (countdown.progress.current > 0 && countdown.progress.type.value === progressType) {
|
|
||||||
acc[`${typeKey}.countdowns.${countdownKey}.progress.current`] = countdown.progress.current - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
acc[key] = countdown;
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
);
|
});
|
||||||
}, {});
|
|
||||||
|
|
||||||
await countdownSetting.updateSource(update);
|
const data = { refreshType: RefreshType.Countdown };
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSetting);
|
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
const data = { refreshType: RefreshType.Countdown };
|
data
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
});
|
||||||
action: socketEvent.Refresh,
|
Hooks.callAll(socketEvent.Refresh, data);
|
||||||
data
|
}
|
||||||
});
|
|
||||||
Hooks.callAll(socketEvent.Refresh, data);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,5 +23,6 @@ export const compendiumBrowserLite = {
|
||||||
export const itemAttachmentSource = 'attachmentSource';
|
export const itemAttachmentSource = 'attachmentSource';
|
||||||
|
|
||||||
export const userFlags = {
|
export const userFlags = {
|
||||||
welcomeMessage: 'welcome-message'
|
welcomeMessage: 'welcome-message',
|
||||||
|
countdownMode: 'countdown-mode'
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -650,3 +650,30 @@ export const fearDisplay = {
|
||||||
bar: { value: 'bar', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.bar' },
|
bar: { value: 'bar', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.bar' },
|
||||||
hide: { value: 'hide', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.hide' }
|
hide: { value: 'hide', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.hide' }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const basicOwnershiplevels = {
|
||||||
|
0: { value: 0, label: 'OWNERSHIP.NONE' },
|
||||||
|
2: { value: 2, label: 'OWNERSHIP.OBSERVER' },
|
||||||
|
3: { value: 3, label: 'OWNERSHIP.OWNER' }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const simpleOwnershiplevels = {
|
||||||
|
[-1]: { value: -1, label: 'OWNERSHIP.INHERIT' },
|
||||||
|
...basicOwnershiplevels
|
||||||
|
};
|
||||||
|
|
||||||
|
export const countdownBaseTypes = {
|
||||||
|
narrative: {
|
||||||
|
id: 'narrative',
|
||||||
|
name: 'DAGGERHEART.APPLICATIONS.Countdown.types.narrative'
|
||||||
|
},
|
||||||
|
encounter: {
|
||||||
|
id: 'encounter',
|
||||||
|
name: 'DAGGERHEART.APPLICATIONS.Countdown.types.encounter'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const countdownAppMode = {
|
||||||
|
textIcon: 'text-icon',
|
||||||
|
iconOnly: 'icon-only'
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,28 @@
|
||||||
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
|
||||||
|
|
||||||
export default class DhCountdowns extends foundry.abstract.DataModel {
|
export default class DhCountdowns extends foundry.abstract.DataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
/* Outdated and unused. Needed for migration. Remove in next minor version. (1.3) */
|
||||||
narrative: new fields.EmbeddedDataField(DhCountdownData),
|
narrative: new fields.EmbeddedDataField(DhCountdownData),
|
||||||
encounter: new fields.EmbeddedDataField(DhCountdownData)
|
encounter: new fields.EmbeddedDataField(DhCountdownData),
|
||||||
|
/**/
|
||||||
|
countdowns: new fields.TypedObjectField(new fields.EmbeddedDataField(DhCountdown)),
|
||||||
|
defaultOwnership: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.basicOwnershiplevels,
|
||||||
|
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static CountdownCategories = { narrative: 'narrative', combat: 'combat' };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Outdated and unused. Needed for migration. Remove in next minor version. (1.3) */
|
||||||
class DhCountdownData extends foundry.abstract.DataModel {
|
class DhCountdownData extends foundry.abstract.DataModel {
|
||||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.APPLICATIONS.Countdown']; // Nots ure why this won't work. Setting labels manually for now
|
|
||||||
|
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
countdowns: new fields.TypedObjectField(new fields.EmbeddedDataField(DhCountdown)),
|
countdowns: new fields.TypedObjectField(new fields.EmbeddedDataField(DhOldCountdown)),
|
||||||
ownership: new fields.SchemaField({
|
ownership: new fields.SchemaField({
|
||||||
default: new fields.NumberField({
|
default: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -56,7 +59,8 @@ class DhCountdownData extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DhCountdown extends foundry.abstract.DataModel {
|
/* Outdated and unused. Needed for migration. Remove in next minor version. (1.3) */
|
||||||
|
class DhOldCountdown extends foundry.abstract.DataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
|
|
@ -129,17 +133,88 @@ class DhCountdown extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registerCountdownHooks = () => {
|
export class DhCountdown extends foundry.abstract.DataModel {
|
||||||
Hooks.on(socketEvent.Refresh, ({ refreshType, application }) => {
|
static defineSchema() {
|
||||||
if (refreshType === RefreshType.Countdown) {
|
const fields = foundry.data.fields;
|
||||||
if (application) {
|
return {
|
||||||
foundry.applications.instances.get(application)?.render();
|
type: new fields.StringField({
|
||||||
} else {
|
required: true,
|
||||||
foundry.applications.instances.get('narrative-countdowns')?.render();
|
choices: CONFIG.DH.GENERAL.countdownBaseTypes,
|
||||||
foundry.applications.instances.get('encounter-countdowns')?.render();
|
label: 'DAGGERHEART.GENERAL.type'
|
||||||
}
|
}),
|
||||||
|
name: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.name.label'
|
||||||
|
}),
|
||||||
|
img: new fields.FilePathField({
|
||||||
|
categories: ['IMAGE'],
|
||||||
|
base64: false,
|
||||||
|
initial: 'icons/magic/time/hourglass-yellow-green.webp'
|
||||||
|
}),
|
||||||
|
ownership: new fields.TypedObjectField(
|
||||||
|
new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.simpleOwnershiplevels,
|
||||||
|
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||||
|
})
|
||||||
|
),
|
||||||
|
progress: new fields.SchemaField({
|
||||||
|
current: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
initial: 1,
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.current.label'
|
||||||
|
}),
|
||||||
|
max: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
initial: 1,
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.max.label'
|
||||||
|
}),
|
||||||
|
type: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.countdownTypes,
|
||||||
|
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.type.label'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
static defaultCountdown(type, playerHidden) {
|
||||||
}
|
const ownership = playerHidden
|
||||||
});
|
? game.users.reduce((acc, user) => {
|
||||||
};
|
if (!user.isGM) {
|
||||||
|
acc[user.id] = CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: type ?? CONFIG.DH.GENERAL.countdownBaseTypes.narrative.id,
|
||||||
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.newCountdown'),
|
||||||
|
img: 'icons/magic/time/hourglass-yellow-green.webp',
|
||||||
|
ownership: ownership,
|
||||||
|
progress: {
|
||||||
|
current: 1,
|
||||||
|
max: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get playerOwnership() {
|
||||||
|
return Array.from(game.users).reduce((acc, user) => {
|
||||||
|
acc[user.id] = {
|
||||||
|
value: user.isGM
|
||||||
|
? CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER
|
||||||
|
: this.ownership.players[user.id] && this.ownership.players[user.id].type !== -1
|
||||||
|
? this.ownership.players[user.id].type
|
||||||
|
: this.ownership.default,
|
||||||
|
isGM: user.isGM
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ export async function runMigrations() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundry.utils.isNewerVersion('1.2.0', lastMigrationVersion)) {
|
if (foundry.utils.isNewerVersion('1.2.0', lastMigrationVersion)) {
|
||||||
|
/* Migrate old action costs */
|
||||||
const lockedPacks = [];
|
const lockedPacks = [];
|
||||||
const compendiumItems = [];
|
const compendiumItems = [];
|
||||||
for (let pack of game.packs) {
|
for (let pack of game.packs) {
|
||||||
|
|
@ -148,6 +149,36 @@ export async function runMigrations() {
|
||||||
await pack.configure({ locked: true });
|
await pack.configure({ locked: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Migrate old countdown structure */
|
||||||
|
const countdownSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
const getCountdowns = (data, type) => {
|
||||||
|
return Object.keys(data.countdowns).reduce((acc, key) => {
|
||||||
|
const countdown = data.countdowns[key];
|
||||||
|
acc[key] = {
|
||||||
|
...countdown,
|
||||||
|
type: type,
|
||||||
|
ownership: Object.keys(countdown.ownership.players).reduce((acc, key) => {
|
||||||
|
acc[key] = countdown.ownership.players[key].type;
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
progress: {
|
||||||
|
...countdown.progress,
|
||||||
|
type: countdown.progress.type.value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
await countdownSettings.updateSource({
|
||||||
|
countdowns: {
|
||||||
|
...getCountdowns(countdownSettings.narrative, CONFIG.DH.GENERAL.countdownBaseTypes.narrative.id),
|
||||||
|
...getCountdowns(countdownSettings.encounter, CONFIG.DH.GENERAL.countdownBaseTypes.encounter.id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSettings);
|
||||||
|
|
||||||
lastMigrationVersion = '1.2.0';
|
lastMigrationVersion = '1.2.0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ export const GMUpdateEvent = {
|
||||||
UpdateEffect: 'DhGMUpdateEffect',
|
UpdateEffect: 'DhGMUpdateEffect',
|
||||||
UpdateSetting: 'DhGMUpdateSetting',
|
UpdateSetting: 'DhGMUpdateSetting',
|
||||||
UpdateFear: 'DhGMUpdateFear',
|
UpdateFear: 'DhGMUpdateFear',
|
||||||
|
UpdateCountdowns: 'DhGMUpdateCountdowns',
|
||||||
UpdateSaveMessage: 'DhGMUpdateSaveMessage'
|
UpdateSaveMessage: 'DhGMUpdateSaveMessage'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -60,6 +61,10 @@ export const registerSocketHooks = () => {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case GMUpdateEvent.UpdateCountdowns:
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data.update);
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||||
|
break;
|
||||||
case GMUpdateEvent.UpdateSaveMessage:
|
case GMUpdateEvent.UpdateSaveMessage:
|
||||||
const action = await fromUuid(data.update.action),
|
const action = await fromUuid(data.update.action),
|
||||||
message = game.messages.get(data.update.message);
|
message = game.messages.get(data.update.message);
|
||||||
|
|
@ -84,14 +89,15 @@ export const registerUserQueries = () => {
|
||||||
CONFIG.queries.reactionRoll = game.system.api.fields.ActionFields.SaveField.rollSaveQuery;
|
CONFIG.queries.reactionRoll = game.system.api.fields.ActionFields.SaveField.rollSaveQuery;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emitAsGM = async (eventName, callback, update, uuid = null) => {
|
export const emitAsGM = async (eventName, callback, update, uuid = null, refresh = null) => {
|
||||||
if (!game.user.isGM) {
|
if (!game.user.isGM) {
|
||||||
return await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
return await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
action: socketEvent.GMUpdate,
|
action: socketEvent.GMUpdate,
|
||||||
data: {
|
data: {
|
||||||
action: eventName,
|
action: eventName,
|
||||||
uuid,
|
uuid,
|
||||||
update
|
update,
|
||||||
|
refresh
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else return callback(update);
|
} else return callback(update);
|
||||||
|
|
|
||||||
142
styles/less/ui/countdown/countdown-edit.less
Normal file
142
styles/less/ui/countdown/countdown-edit.less
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
@import '../../utils/colors.less';
|
||||||
|
@import '../../utils/fonts.less';
|
||||||
|
|
||||||
|
.theme-light .daggerheart.application.dh-style.countdown-edit {
|
||||||
|
background-image: url('../assets/parchments/dh-parchment-light.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.daggerheart.application.dh-style.countdown-edit {
|
||||||
|
color: light-dark(@dark, @beige);
|
||||||
|
background-image: url('../assets/parchments/dh-parchment-dark.png');
|
||||||
|
|
||||||
|
.edit-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
color: light-dark(@dark, @golden);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-tools {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr 144px;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.hide-tools {
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
input {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-main-button {
|
||||||
|
height: 32px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-ownership-tools {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
select {
|
||||||
|
flex: 1;
|
||||||
|
background: light-dark(@beige, @dark-blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
max-height: 500px;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: light-dark(@dark-blue, @golden) transparent;
|
||||||
|
|
||||||
|
.countdown-edit-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 48px 1fr 64px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-edit-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.countdown-edit-subtext {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.countdown-edit-sub-tag {
|
||||||
|
padding: 3px 5px;
|
||||||
|
font-size: var(--font-size-12);
|
||||||
|
font: @font-body;
|
||||||
|
|
||||||
|
background: light-dark(@dark-15, @beige-15);
|
||||||
|
border: 1px solid light-dark(@dark, @beige);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-edit-tools {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&.same-row {
|
||||||
|
margin-top: 17.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-edit-subrow {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
margin: 0 72px 0 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-edit-input {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
&.tiny {
|
||||||
|
flex: 0;
|
||||||
|
input {
|
||||||
|
min-width: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
background: light-dark(@beige, @dark-blue);
|
||||||
|
color: light-dark(@dark, @beige);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,60 +1,130 @@
|
||||||
@import '../../utils/colors.less';
|
@import '../../utils/colors.less';
|
||||||
@import '../../utils/fonts.less';
|
@import '../../utils/fonts.less';
|
||||||
|
|
||||||
.daggerheart.dh-style.countdown {
|
.theme-dark {
|
||||||
fieldset {
|
.daggerheart.dh-style.countdowns {
|
||||||
align-items: center;
|
background-image: url(../assets/parchments/dh-parchment-dark.png);
|
||||||
margin-top: 5px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border-color: light-dark(@dark-blue, @golden);
|
|
||||||
|
|
||||||
legend {
|
.window-header {
|
||||||
font-weight: bold;
|
background-image: url(../assets/parchments/dh-parchment-dark.png);
|
||||||
color: light-dark(@dark-blue, @golden);
|
}
|
||||||
|
}
|
||||||
a {
|
}
|
||||||
text-shadow: none;
|
|
||||||
}
|
.daggerheart.dh-style.countdowns {
|
||||||
|
z-index: var(--z-index-ui) !important;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: none;
|
||||||
|
width: 300px;
|
||||||
|
top: 16px;
|
||||||
|
right: 64px;
|
||||||
|
transition:
|
||||||
|
right ease 250ms,
|
||||||
|
opacity var(--ui-fade-duration) ease,
|
||||||
|
opacity var(--ui-fade-duration);
|
||||||
|
|
||||||
|
.window-title {
|
||||||
|
font-family: @font-body;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.expanded {
|
||||||
|
right: 364px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.icon-only {
|
||||||
|
width: 180px;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-header {
|
||||||
|
cursor: default;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-content {
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 312px;
|
||||||
|
|
||||||
|
.countdowns-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.countdown-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&.icon-only {
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.countdown-main-container {
|
||||||
|
.countdown-content {
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.countdown-tools {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-main-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.countdown-tools {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.progress-tag {
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
background-color: light-dark(@beige, @dark-blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-access-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
grid-auto-rows: min-content;
|
||||||
|
width: 38px;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.countdown-access {
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid light-dark(@dark-blue, @beige-80);
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-no-access-container {
|
||||||
|
width: 38px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.minimized-view {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.mini-countdown-container {
|
|
||||||
width: fit-content;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
border: 2px solid light-dark(@dark-blue, @golden);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0 4px 0 0;
|
|
||||||
background-image: url('../assets/parchments/dh-parchment-light.png');
|
|
||||||
color: light-dark(@beige, @dark);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
cursor: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 6px 0 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-countdown-name {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-countdown-value {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
@import './item-browser/item-browser.less';
|
@import './item-browser/item-browser.less';
|
||||||
|
|
||||||
@import './countdown/countdown.less';
|
@import './countdown/countdown.less';
|
||||||
|
@import './countdown/countdown-edit.less';
|
||||||
@import './countdown/sheet.less';
|
@import './countdown/sheet.less';
|
||||||
|
|
||||||
@import './ownership-selection/ownership-selection.less';
|
@import './ownership-selection/ownership-selection.less';
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,15 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
|
.ownership-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.ownership-container {
|
.ownership-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
border: 2px solid light-dark(@dark-blue, @golden);
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 0 4px 0 0;
|
padding: 0 4px 0 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -17,12 +23,24 @@
|
||||||
img {
|
img {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
border-radius: 6px 0 0 6px;
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
flex: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
flex: 1;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 10px;
|
||||||
|
button {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
.tab.sidebar-tab.daggerheartMenu-sidebar {
|
.tab.sidebar-tab.daggerheartMenu-sidebar {
|
||||||
padding: 0 4px;
|
padding: 4px;
|
||||||
|
|
||||||
|
div[data-application-part] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.menu-refresh-container {
|
.menu-refresh-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,22 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-fields">
|
<div class="form-fields">
|
||||||
<label>{{localize "DAGGERHEART.APPLICATIONS.OwnershipSelection.default"}}</label>
|
<label>{{localize "DAGGERHEART.APPLICATIONS.OwnershipSelection.default"}}</label>
|
||||||
<select name="ownership.default" data-dtype="Number">
|
<select name="default" data-dtype="Number" disabled>
|
||||||
{{selectOptions @root.ownershipOptions selected=ownership.default labelAttr="label" valueAttr="value" }}
|
{{selectOptions ownershipDefaultOptions selected=defaultOwnership labelAttr="label" valueAttr="value" localize=true }}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{#each ownership.players as |player id|}}
|
<ul class="ownership-list">
|
||||||
<div class="ownership-container">
|
{{#each ownership as |player id|}}
|
||||||
<img src="{{player.img}}" />
|
<li class="ownership-container">
|
||||||
<div>{{player.name}}</div>
|
<img src="{{player.img}}" />
|
||||||
<select name="{{concat "ownership.players." id ".type"}}" data-dtype="Number">
|
<span>{{player.name}}</span>
|
||||||
{{selectOptions @root.ownershipOptions selected=player.ownership labelAttr="label" valueAttr="value" }}
|
<select name="{{concat "ownership." id}}" data-dtype="Number">
|
||||||
</select>
|
{{selectOptions @root.ownershipOptions selected=player.ownership labelAttr="label" valueAttr="value" localize=true }}
|
||||||
</div>
|
</select>
|
||||||
{{/each}}
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
<footer class="flexrow">
|
<footer class="flexrow">
|
||||||
<button type="submit">{{localize "Save"}}</button>
|
<button type="submit">{{localize "Save"}}</button>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="tiers-container">
|
<div class="tiers-container">
|
||||||
{{#each this.tiers as |tier key|}}
|
{{#each this.tiers as |tier key|}}
|
||||||
<fieldset class="tier-container">
|
<fieldset class="tier-container">
|
||||||
<legend>{{tier.name}}</legend>
|
<legend>{{localize tier.name}}</legend>
|
||||||
|
|
||||||
{{#each tier.groups}}
|
{{#each tier.groups}}
|
||||||
<div class="checkbox-group-container">
|
<div class="checkbox-group-container">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<div>
|
<div>
|
||||||
|
<h2>{{localize "DAGGERHEART.APPLICATIONS.DaggerheartMenu.title"}}</h2>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{{localize "Refresh Features"}}</legend>
|
<legend>{{localize "Refresh Features"}}</legend>
|
||||||
|
|
||||||
|
|
@ -19,4 +21,6 @@
|
||||||
<button data-action="refreshActors" {{disabled disableRefresh}}>{{localize "DAGGERHEART.GENERAL.refresh"}}</button>
|
<button data-action="refreshActors" {{disabled disableRefresh}}>{{localize "DAGGERHEART.GENERAL.refresh"}}</button>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<button data-action="editCountdowns">{{localize "DAGGERHEART.APPLICATIONS.DaggerheartMenu.countdowns"}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@
|
||||||
{{!-- Combat Controls --}}
|
{{!-- Combat Controls --}}
|
||||||
<div class="inner-controls">
|
<div class="inner-controls">
|
||||||
{{#if hasCombat}}
|
{{#if hasCombat}}
|
||||||
<button data-action="openCountdowns">{{localize "DAGGERHEART.APPLICATIONS.CombatTracker.openCountdowns"}}</button>
|
|
||||||
<div class="control-buttons right flexrow">
|
<div class="control-buttons right flexrow">
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<button type="button" class="encounter-context-menu inline-control combat-control icon fa-solid fa-ellipsis-vertical"
|
<button type="button" class="encounter-context-menu inline-control combat-control icon fa-solid fa-ellipsis-vertical"
|
||||||
|
|
|
||||||
76
templates/ui/countdown-edit.hbs
Normal file
76
templates/ui/countdown-edit.hbs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<div>
|
||||||
|
<div class="edit-container">
|
||||||
|
<header class="dialog-header">
|
||||||
|
<h1>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.editTitle"}}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="header-tools">
|
||||||
|
<button class="header-main-button" data-action="addCountdown"><i class="fa-solid fa-plus"></i> {{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.newCountdown"}}</button>
|
||||||
|
<div class="hide-tools">
|
||||||
|
<label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.hideNewCountdowns"}}</label>
|
||||||
|
<input type="checkbox" name="hideNewCountdowns" {{checked hideNewCountdowns}} />
|
||||||
|
</div>
|
||||||
|
{{#if isGM}}
|
||||||
|
<div class="default-ownership-tools">
|
||||||
|
<i class="fa-solid fa-eye" data-tooltip="{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.defaultOwnershipTooltip"}}"></i>
|
||||||
|
<select name="defaultOwnership">
|
||||||
|
{{selectOptions ownershipDefaultOptions selected=defaultOwnership labelAttr="label" valueAttr="value" localize=true}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="edit-content">
|
||||||
|
{{#each countdowns as | countdown id | }}
|
||||||
|
<div class="countdown-edit-container {{#unless countdown.editing}}viewing{{/unless}}" data-id="{{id}}">
|
||||||
|
<a data-action="editCountdownImage" id="{{id}}"><img src="{{countdown.img}}" /></a>
|
||||||
|
{{#unless countdown.editing}}
|
||||||
|
<div class="countdown-edit-text">
|
||||||
|
<h4>{{countdown.name}}</h4>
|
||||||
|
<div class="countdown-edit-subtext">
|
||||||
|
<div class="countdown-edit-sub-tag">{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.currentCountdownValue" value=countdown.progress.current}}</div>
|
||||||
|
<div class="countdown-edit-sub-tag">{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.currentCountdownMax" value=countdown.progress.max}}</div>
|
||||||
|
<div class="countdown-edit-sub-tag">{{countdown.typeName}}</div>
|
||||||
|
<div class="countdown-edit-sub-tag">{{countdown.progress.typeName}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="countdown-edit-input">
|
||||||
|
<label>{{localize "Name"}}</label>
|
||||||
|
<input type="text" name="{{concat "countdowns." id ".name"}}" value="{{countdown.name}}" />
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
<div class="countdown-edit-tools {{#if countdown.editing}}same-row{{/if}}">
|
||||||
|
<a data-action="toggleCountdownEdit" data-countdown-id="{{id}}"><i class="fa-solid {{#unless countdown.editing}}fa-pen-to-square{{else}}fa-check{{/unless}}"></i></a>
|
||||||
|
<a data-action="editCountdownOwnership" data-countdown-id="{{id}}"><i class="fa-solid fa-users"></i></a>
|
||||||
|
<a data-action="removeCountdown" data-countdown-id="{{id}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{#if countdown.editing}}
|
||||||
|
<div class="countdown-edit-subrow">
|
||||||
|
<div class="countdown-edit-input tiny">
|
||||||
|
<label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.current"}}</label>
|
||||||
|
<input type="number" name="{{concat "countdowns." id ".progress.current"}}" value="{{countdown.progress.current}}" />
|
||||||
|
</div>
|
||||||
|
<div class="countdown-edit-input tiny">
|
||||||
|
<label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.max"}}</label>
|
||||||
|
<input type="number" name="{{concat "countdowns." id ".progress.max"}}" value="{{countdown.progress.max}}" />
|
||||||
|
</div>
|
||||||
|
<div class="countdown-edit-input">
|
||||||
|
<label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.category"}}</label>
|
||||||
|
<select name="{{concat "countdowns." id ".type"}}">
|
||||||
|
{{selectOptions ../countdownBaseTypes selected=countdown.type valueAttr="id" labelAttr="name" localize=true}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="countdown-edit-input">
|
||||||
|
<label>{{localize "DAGGERHEART.APPLICATIONS.CountdownEdit.type"}}</label>
|
||||||
|
<select name="{{concat "countdowns." id ".progress.type"}}">
|
||||||
|
{{selectOptions ../countdownTypes selected=countdown.progress.type valueAttr="id" labelAttr="label" localize=true}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -1,45 +1,33 @@
|
||||||
<div>
|
<div>
|
||||||
{{#if simple}}
|
<div class="countdowns-container">
|
||||||
<div class="minimized-view">
|
{{#each countdowns as | countdown id |}}
|
||||||
{{#each source.countdowns}}
|
<div class="countdown-container {{#if ../iconOnly}}icon-only{{/if}}">
|
||||||
<a class="mini-countdown-container {{#if (not this.canEdit)}}disabled{{/if}}" data-countdown="{{@key}}">
|
<div class="countdown-main-container">
|
||||||
<img src="{{this.img}}" />
|
<img src="{{countdown.img}}" {{#if ../iconOnly}}data-tooltip="{{countdown.name}}"{{/if}}/>
|
||||||
<div class="mini-countdown-name">{{this.name}}</div>
|
<div class="countdown-content">
|
||||||
<div class="mini-countdown-value">{{this.progress.current}}/{{this.progress.max}}</div>
|
{{#unless ../iconOnly}}<label>{{countdown.name}}</label>{{/unless}}
|
||||||
</a>
|
<div class="countdown-tools">
|
||||||
{{/each}}
|
{{#if countdown.editable}}<a data-action="decreaseCountdown" id="{{id}}"><i class="fa-solid fa-minus"></i></a>{{/if}}
|
||||||
</div>
|
<div class="progress-tag">
|
||||||
{{else}}
|
{{countdown.progress.current}}/{{countdown.progress.max}}
|
||||||
<div class="expanded-view">
|
|
||||||
<div class="countdowns-menu">
|
|
||||||
{{#if canCreate}}<button class="flex" data-action="addCountdown">{{localize "DAGGERHEART.APPLICATIONS.Countdown.addCountdown"}}</button>{{/if}}
|
|
||||||
{{#if isGM}}<button data-action="openOwnership" data-tooltip="{{localize "DAGGERHEART.APPLICATIONS.Countdown.openOwnership"}}"><i class="fa-solid fa-users"></i></button>{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="countdowns-container">
|
|
||||||
{{#each source.countdowns}}
|
|
||||||
<fieldset class="countdown-fieldset">
|
|
||||||
<legend>
|
|
||||||
{{this.name}}
|
|
||||||
{{#if this.canEdit}}<a><i class="fa-solid fa-trash icon-button" data-action="removeCountdown" data-countdown="{{@key}}"></i></a>{{/if}}
|
|
||||||
{{#if @root.isGM}}<a><i class="fa-solid fa-users icon-button" data-action="openCountdownOwnership" data-countdown="{{@key}}" data-tooltip="{{localize "DAGGERHEART.APPLICATIONS.Countdown.openOwnership"}}"></i></a>{{/if}}
|
|
||||||
</legend>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="countdown-container">
|
|
||||||
<img src="{{this.img}}" {{#if this.canEdit}}data-action='editImage'{{else}}class="disabled"{{/if}} data-countdown="{{@key}}" />
|
|
||||||
<div class="countdown-inner-container">
|
|
||||||
{{formGroup @root.countdownFields.name name=(concat @root.base ".countdowns." @key ".name") value=this.name localize=true disabled=(not this.canEdit)}}
|
|
||||||
<div class="countdown-value-container">
|
|
||||||
{{formGroup @root.countdownFields.progress.fields.current name=(concat @root.base ".countdowns." @key ".progress.current") value=this.progress.current localize=true disabled=(not this.canEdit)}}
|
|
||||||
{{formGroup @root.countdownFields.progress.fields.max name=(concat @root.base ".countdowns." @key ".progress.max") value=this.progress.max localize=true disabled=(not this.canEdit)}}
|
|
||||||
</div>
|
|
||||||
{{formGroup @root.countdownFields.progress.fields.type.fields.value name=(concat @root.base ".countdowns." @key ".progress.type.value") value=this.progress.type.value localize=true localize=true disabled=(not this.canEdit)}}
|
|
||||||
</div>
|
</div>
|
||||||
|
{{#if countdown.editable}}<a data-action="increaseCountdown" id="{{id}}"><i class="fa-solid fa-plus"></i></a>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</div>
|
||||||
{{/each}}
|
</div>
|
||||||
|
{{#if (and @root.isGM (not ../iconOnly))}}
|
||||||
|
{{#if (gt countdown.playerAccess.length 0)}}
|
||||||
|
<div class="countdown-access-container">
|
||||||
|
{{#each countdown.playerAccess as |player|}}
|
||||||
|
<div class="countdown-access" style="{{concat "background: " player.color.css ";"}}" data-tooltip="{{player.name}}"></div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if countdown.noPlayerAccess}}
|
||||||
|
<div class="countdown-no-access-container"><i class="fa-solid fa-eye-slash" data-tooltip="{{localize "DAGGERHEART.UI.Countdowns.noPlayerAccess"}}"></i></div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{{/each}}
|
||||||
{{/if}}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue