This commit is contained in:
WBHarry 2025-07-01 17:58:29 +02:00
parent a1f7e0d2c5
commit 3462b3bd77
8 changed files with 159 additions and 131 deletions

View file

@ -1087,6 +1087,7 @@
"RemoveCountdownText": "Are you sure you want to remove the countdown: {name}?",
"OpenOwnership": "Edit Player Ownership",
"Title": "{type} Countdowns",
"ToggleSimple": "Toggle Simple View",
"Types": {
"narrative": "Narrative",
"encounter": "Encounter"

View file

@ -1,5 +1,6 @@
import { countdownTypes } from '../config/generalConfig.mjs';
import { GMUpdateEvent, RefreshType, socketEvent } from '../helpers/socket.mjs';
import constructHTMLButton from '../helpers/utils.mjs';
import OwnershipSelection from './ownershipSelection.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -25,14 +26,15 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
frame: true,
title: 'Countdowns',
resizable: true,
minimizable: true
minimizable: false
},
actions: {
addCountdown: this.addCountdown,
removeCountdown: this.removeCountdown,
editImage: this.onEditImage,
openOwnership: this.openOwnership,
openCountdownOwnership: this.openCountdownOwnership
openCountdownOwnership: this.openCountdownOwnership,
toggleSimpleView: this.toggleSimpleView
},
form: { handler: this.updateData, submitOnChange: true }
};
@ -53,11 +55,44 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
});
}
async _onFirstRender(context, options) {
super._onFirstRender(context, options);
async _preFirstRender(context, options) {
options.position =
game.user.getFlag(SYSTEM.id, SYSTEM.FLAGS.countdown.position) ?? Countdowns.DEFAULT_OPTIONS.position;
this.element.querySelector('.expanded-view').classList.toggle('hidden');
this.element.querySelector('.minimized-view').classList.toggle('hidden');
const viewSetting = game.user.getFlag(SYSTEM.id, SYSTEM.FLAGS.countdown.simple) ?? !game.user.isGM;
this.simpleView =
game.user.isGM || !this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER) ? viewSetting : true;
}
_onPosition(position) {
game.user.setFlag(SYSTEM.id, SYSTEM.FLAGS.countdown.position, position);
}
async _renderFrame(options) {
const frame = await super._renderFrame(options);
if (this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER)) {
const button = constructHTMLButton({
label: '',
classes: ['header-control', 'icon', 'fa-solid', 'fa-wrench'],
dataset: { action: 'toggleSimpleView', tooltip: 'DAGGERHEART.Countdown.ToggleSimple' }
});
this.window.controls.after(button);
}
return frame;
}
testUserPermission(level, exact, altSettings) {
if (game.user.isGM) return true;
const settings =
altSettings ?? game.settings.get(SYSTEM.id, SYSTEM.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) {
@ -67,15 +102,17 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
context.isGM = game.user.isGM;
context.base = this.basePath;
context.canCreate = countdownData.playerOwnership[game.user.id].value === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
context.canCreate = this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, true);
context.source = {
...countdownData,
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
const countdown = countdownData.countdowns[key];
const ownershipValue = countdown.playerOwnership[game.user.id].value;
if (ownershipValue > CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
acc[key] = { ...countdown, canEdit: ownershipValue === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER };
if (this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED, false, countdown)) {
acc[key] = {
...countdown,
canEdit: this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, true, countdown)
};
}
return acc;
@ -83,7 +120,7 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
};
context.systemFields = countdownData.schema.fields;
context.countdownFields = context.systemFields.countdowns.element.fields;
context.minimized = this.minimized || _options.isFirstRender;
context.simple = this.simpleView;
return context;
}
@ -110,28 +147,6 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
}
}
async minimize() {
await super.minimize();
this.element.querySelector('.expanded-view').classList.toggle('hidden');
this.element.querySelector('.minimized-view').classList.toggle('hidden');
}
async maximize() {
if (this.minimized) {
const settings = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath];
if (settings.playerOwnership[game.user.id].value <= CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.Countdown.Notifications.LimitedOwnership'));
return;
}
this.element.querySelector('.expanded-view').classList.toggle('hidden');
this.element.querySelector('.minimized-view').classList.toggle('hidden');
}
await super.maximize();
}
async updateSetting(update) {
if (game.user.isGM) {
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, update);
@ -213,11 +228,17 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
});
}
static toggleSimpleView() {
this.simpleView = !this.simpleView;
game.user.setFlag(SYSTEM.id, SYSTEM.FLAGS.countdown.simple, this.simpleView);
this.render();
}
async updateCountdownValue(event, increase) {
const countdownSetting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns);
const countdown = countdownSetting[this.basePath].countdowns[event.currentTarget.dataset.countdown];
if (countdown.playerOwnership[game.user.id] < CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) {
if (!this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) {
return;
}

View file

@ -1 +1,5 @@
export const displayDomainCardsAsList = 'displayDomainCardsAsList';
export const countdown = {
simple: 'countdown-simple',
position: 'countdown-position'
};

View file

@ -36,7 +36,8 @@ class DhCountdownData extends foundry.abstract.DataModel {
})
})
)
})
}),
window: new fields.SchemaField({})
};
}

View file

@ -225,10 +225,10 @@ export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue
// Fix on Foundry native formula replacement for DH
const nativeReplaceFormulaData = Roll.replaceFormulaData;
Roll.replaceFormulaData = function (formula, data={}, { missing, warn = false } = {}) {
Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false } = {}) {
const terms = Object.keys(SYSTEM.GENERAL.multiplierTypes).map(type => {
return { term: type, default: 1}
})
return { term: type, default: 1 };
});
formula = terms.reduce((a, c) => a.replaceAll(`@${c.term}`, data[c.term] ?? c.default), formula);
return nativeReplaceFormulaData(formula, data, { missing, warn });
};
@ -258,3 +258,25 @@ export const damageKeyToNumber = key => {
return 0;
}
};
export default function constructHTMLButton({
label,
dataset = {},
classes = [],
icon = '',
type = 'button',
disabled = false
}) {
const button = document.createElement('button');
button.type = type;
for (const [key, value] of Object.entries(dataset)) {
button.dataset[key] = value;
}
button.classList.add(...classes);
if (icon) icon = `<i class="${icon}"></i> `;
if (disabled) button.disabled = true;
button.innerHTML = `${icon}${label}`;
return button;
}

View file

@ -1,6 +1,6 @@
.theme-light {
.daggerheart.dh-style.countdown {
&.minimized .minimized-view .mini-countdown-container {
.minimized-view .mini-countdown-container {
background-image: url('../assets/parchments/dh-parchment-dark.png');
}
}
@ -26,51 +26,38 @@
}
}
&.minimized {
height: auto !important;
max-height: unset !important;
max-width: 740px !important;
width: auto !important;
.minimized-view {
display: flex;
gap: 8px;
flex-wrap: wrap;
.window-content {
display: flex;
padding: 4px 8px;
justify-content: center;
}
.minimized-view {
.mini-countdown-container {
width: fit-content;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
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;
.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;
}
&.disabled {
cursor: initial;
}
img {
width: 30px;
height: 30px;
border-radius: 6px 0 0 6px;
}
img {
width: 30px;
height: 30px;
border-radius: 6px 0 0 6px;
}
.mini-countdown-name {
white-space: nowrap;
}
.mini-countdown-name {
white-space: nowrap;
}
.mini-countdown-value {
}
.mini-countdown-value {
}
}
}

View file

@ -3386,7 +3386,7 @@ div.daggerheart.views.multiclass {
#resources:has(.fear-bar) {
min-width: 200px;
}
.theme-light .daggerheart.dh-style.countdown.minimized .minimized-view .mini-countdown-container {
.theme-light .daggerheart.dh-style.countdown .minimized-view .mini-countdown-container {
background-image: url('../assets/parchments/dh-parchment-dark.png');
}
.daggerheart.dh-style.countdown {
@ -3406,23 +3406,12 @@ div.daggerheart.views.multiclass {
.daggerheart.dh-style.countdown fieldset legend a {
text-shadow: none;
}
.daggerheart.dh-style.countdown.minimized {
height: auto !important;
max-height: unset !important;
max-width: 740px !important;
width: auto !important;
}
.daggerheart.dh-style.countdown.minimized .window-content {
display: flex;
padding: 4px 8px;
justify-content: center;
}
.daggerheart.dh-style.countdown.minimized .minimized-view {
.daggerheart.dh-style.countdown .minimized-view {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.daggerheart.dh-style.countdown.minimized .minimized-view .mini-countdown-container {
.daggerheart.dh-style.countdown .minimized-view .mini-countdown-container {
width: fit-content;
display: flex;
align-items: center;
@ -3434,15 +3423,15 @@ div.daggerheart.views.multiclass {
color: light-dark(#efe6d8, #222);
cursor: pointer;
}
.daggerheart.dh-style.countdown.minimized .minimized-view .mini-countdown-container.disabled {
.daggerheart.dh-style.countdown .minimized-view .mini-countdown-container.disabled {
cursor: initial;
}
.daggerheart.dh-style.countdown.minimized .minimized-view .mini-countdown-container img {
.daggerheart.dh-style.countdown .minimized-view .mini-countdown-container img {
width: 30px;
height: 30px;
border-radius: 6px 0 0 6px;
}
.daggerheart.dh-style.countdown.minimized .minimized-view .mini-countdown-container .mini-countdown-name {
.daggerheart.dh-style.countdown .minimized-view .mini-countdown-container .mini-countdown-name {
white-space: nowrap;
}
.daggerheart.dh-style.countdown .hidden {

View file

@ -1,42 +1,45 @@
<div>
<div class="expanded-view {{#if minimized}}hidden{{/if}}">
<div class="countdowns-menu">
{{#if canCreate}}<button class="flex" data-action="addCountdown">{{localize "DAGGERHEART.Countdown.AddCountdown"}}</button>{{/if}}
{{#if isGM}}<button data-action="openOwnership" data-tooltip="{{localize "DAGGERHEART.Countdown.OpenOwnership"}}"><i class="fa-solid fa-users"></i></button>{{/if}}
</div>
<div class="countdowns-container">
{{#if simple}}
<div class="minimized-view">
{{#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.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>
</fieldset>
<a class="mini-countdown-container {{#if (not this.canEdit)}}disabled{{/if}}" data-countdown="{{@key}}">
<img src="{{this.img}}" />
<div class="mini-countdown-name">{{this.name}}</div>
<div class="mini-countdown-value">{{this.progress.current}}/{{this.progress.max}}</div>
</a>
{{/each}}
</div>
</div>
<div class="minimized-view {{#if (not minimized)}}hidden{{/if}}">
{{#each source.countdowns}}
<a class="mini-countdown-container {{#if (not this.canEdit)}}disabled{{/if}}" data-countdown="{{@key}}">
<img src="{{this.img}}" />
<div class="mini-countdown-name">{{this.name}}</div>
<div class="mini-countdown-value">{{this.progress.current}}/{{this.progress.max}}</div>
</a>
{{/each}}
</div>
{{else}}
<div class="expanded-view">
<div class="countdowns-menu">
{{#if canCreate}}<button class="flex" data-action="addCountdown">{{localize "DAGGERHEART.Countdown.AddCountdown"}}</button>{{/if}}
{{#if isGM}}<button data-action="openOwnership" data-tooltip="{{localize "DAGGERHEART.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.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>
</fieldset>
{{/each}}
</div>
</div>
{{/if}}
</div>