Fix some issues involving party in observer view (#2067)

This commit is contained in:
Carlos Fernandez 2026-07-06 17:52:43 -04:00 committed by GitHub
parent 4b5550a097
commit 2782b62793
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 189 additions and 146 deletions

View file

@ -1,7 +1,7 @@
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
import { shouldUseHopeFearAutomation } from '../../helpers/utils.mjs'; import { shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import Party from '../sheets/actors/party.mjs'; import PartySheet from '../sheets/actors/party.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -11,7 +11,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
this.party = party; this.party = party;
this.partyMembers = party.system.partyMembers this.partyMembers = party.system.partyMembers
.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) .filter(x => PartySheet.DICE_ROLL_ACTOR_TYPES.includes(x.type))
.map(member => ({ .map(member => ({
...member.toObject(), ...member.toObject(),
uuid: member.uuid, uuid: member.uuid,

View file

@ -2,7 +2,7 @@ import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
import { MemberData } from '../../data/tagTeamData.mjs'; import { MemberData } from '../../data/tagTeamData.mjs';
import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs'; import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import Party from '../sheets/actors/party.mjs'; import PartySheet from '../sheets/actors/party.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -13,7 +13,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
this.usesTagTeamHopeCost = true; this.usesTagTeamHopeCost = true;
this.party = party; this.party = party;
this.partyMembers = party.system.partyMembers this.partyMembers = party.system.partyMembers
.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) .filter(x => PartySheet.DICE_ROLL_ACTOR_TYPES.includes(x.type))
.map(member => ({ .map(member => ({
...member.toObject(), ...member.toObject(),
uuid: member.uuid, uuid: member.uuid,

View file

@ -1,8 +1,18 @@
import DhCompanion from '../../../data/actor/companion.mjs'; import DhCompanion from '../../../data/actor/companion.mjs';
import DhParty from '../../../data/actor/party.mjs';
import DhpActor from '../../../documents/actor.mjs'; import DhpActor from '../../../documents/actor.mjs';
declare module './companion.mjs' { declare module './companion.mjs' {
export default interface DhCompanionSheet { export default interface DhCompanionSheet {
actor: DhpActor<DhCompanion>; actor: DhpActor<DhCompanion>;
document: DhpActor<DhCompanion>;
} }
} }
declare module './party.mjs' {
export default interface PartySheet {
actor: DhpActor<DhParty>;
document: DhpActor<DhParty>;
}
}

View file

@ -6,7 +6,9 @@ import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
import { socketEvent } from '../../../systemRegistration/socket.mjs'; import { socketEvent } from '../../../systemRegistration/socket.mjs';
import DhpActor from '../../../documents/actor.mjs'; import DhpActor from '../../../documents/actor.mjs';
export default class Party extends DHBaseActorSheet { /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
export default class PartySheet extends DHBaseActorSheet {
constructor(options) { constructor(options) {
super(options); super(options);
@ -24,17 +26,17 @@ export default class Party extends DHBaseActorSheet {
resizable: true resizable: true
}, },
actions: { actions: {
openDocument: Party.#openDocument, openDocument: PartySheet.#onOpenDocument,
deletePartyMember: Party.#deletePartyMember, deletePartyMember: PartySheet.#onDeletePartyMember,
toggleHope: Party.#toggleHope, toggleHope: PartySheet.#onToggleHope,
toggleHitPoints: Party.#toggleHitPoints, toggleHitPoints: PartySheet.#onToggleHitPoints,
toggleStress: Party.#toggleStress, toggleStress: PartySheet.#onToggleStress,
toggleArmorSlot: Party.#toggleArmorSlot, toggleArmorSlot: PartySheet.#onToggleArmorSlot,
tempBrowser: Party.#tempBrowser, tempBrowser: PartySheet.#onTempBrowser,
refeshActions: Party.#refeshActions, refreshActions: PartySheet.#onRefreshActions,
triggerRest: Party.#triggerRest, triggerRest: PartySheet.#onTriggerRest,
tagTeamRoll: Party.#tagTeamRoll, tagTeamRoll: PartySheet.#onTagTeamRoll,
groupRoll: Party.#groupRoll groupRoll: PartySheet.#onGroupRoll
}, },
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }] dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
}; };
@ -72,6 +74,23 @@ export default class Party extends DHBaseActorSheet {
this._createSearchFilter(); this._createSearchFilter();
} }
/** @inheritdoc */
_toggleDisabled(disabled) {
// Overriden to only disable text inputs by default if the user is a member
const isMember = this.actor.system.partyMembers.some(
m => m.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
);
if (!isMember) return super._toggleDisabled(disabled);
const form = this.form;
for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) {
input.disabled = disabled;
}
for (const element of form.querySelectorAll('.input[contenteditable]')) {
element.classList.toggle('disabled', disabled);
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Prepare Context */ /* Prepare Context */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -198,8 +217,16 @@ export default class Party extends DHBaseActorSheet {
}; };
} }
} }
/* -------------------------------------------- */
/* Event handlers */
/* -------------------------------------------- */
static async #openDocument(_, target) { /**
* @this PartySheet
* @type {ApplicationClickAction}
*/
static async #onOpenDocument(_, target) {
const uuid = target.dataset.uuid; const uuid = target.dataset.uuid;
const document = await foundry.utils.fromUuid(uuid); const document = await foundry.utils.fromUuid(uuid);
document?.sheet?.render(true); document?.sheet?.render(true);
@ -207,9 +234,10 @@ export default class Party extends DHBaseActorSheet {
/** /**
* Toggles a hope resource value. * Toggles a hope resource value.
* @this PartySheet
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
*/ */
static async #toggleHope(_, target) { static async #onToggleHope(_, target) {
const hopeValue = Number.parseInt(target.dataset.value); const hopeValue = Number.parseInt(target.dataset.value);
const actor = await foundry.utils.fromUuid(target.dataset.actorId); const actor = await foundry.utils.fromUuid(target.dataset.actorId);
const newValue = actor.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue; const newValue = actor.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue;
@ -219,9 +247,10 @@ export default class Party extends DHBaseActorSheet {
/** /**
* Toggles a hp resource value. * Toggles a hp resource value.
* @this PartySheet
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
*/ */
static async #toggleHitPoints(_, target) { static async #onToggleHitPoints(_, target) {
const hitPointsValue = Number.parseInt(target.dataset.value); const hitPointsValue = Number.parseInt(target.dataset.value);
const actor = await foundry.utils.fromUuid(target.dataset.actorId); const actor = await foundry.utils.fromUuid(target.dataset.actorId);
const newValue = actor.system.resources.hitPoints.value >= hitPointsValue ? hitPointsValue - 1 : hitPointsValue; const newValue = actor.system.resources.hitPoints.value >= hitPointsValue ? hitPointsValue - 1 : hitPointsValue;
@ -231,9 +260,10 @@ export default class Party extends DHBaseActorSheet {
/** /**
* Toggles a stress resource value. * Toggles a stress resource value.
* @this PartySheet
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
*/ */
static async #toggleStress(_, target) { static async #onToggleStress(_, target) {
const stressValue = Number.parseInt(target.dataset.value); const stressValue = Number.parseInt(target.dataset.value);
const actor = await foundry.utils.fromUuid(target.dataset.actorId); const actor = await foundry.utils.fromUuid(target.dataset.actorId);
const newValue = actor.system.resources.stress.value >= stressValue ? stressValue - 1 : stressValue; const newValue = actor.system.resources.stress.value >= stressValue ? stressValue - 1 : stressValue;
@ -243,9 +273,10 @@ export default class Party extends DHBaseActorSheet {
/** /**
* Toggles a armor slot resource value. * Toggles a armor slot resource value.
* @this PartySheet
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
*/ */
static async #toggleArmorSlot(_, target) { static async #onToggleArmorSlot(_, target) {
const actor = await foundry.utils.fromUuid(target.dataset.actorId); const actor = await foundry.utils.fromUuid(target.dataset.actorId);
const { value, max } = actor.system.armorScore; const { value, max } = actor.system.armorScore;
const inputValue = Number.parseInt(target.dataset.value); const inputValue = Number.parseInt(target.dataset.value);
@ -258,12 +289,19 @@ export default class Party extends DHBaseActorSheet {
/** /**
* Opens Compedium Browser * Opens Compedium Browser
* @this PartySheet
* @type {ApplicationClickAction}
*/ */
static async #tempBrowser(_, target) { static async #onTempBrowser(_, target) {
new ItemBrowser().render({ force: true }); new ItemBrowser().render({ force: true });
} }
static async #refeshActions() { /**
* @todo Is this unused?
* @this PartySheet
* @type {ApplicationClickAction}
*/
static async #onRefreshActions() {
const confirmed = await foundry.applications.api.DialogV2.confirm({ const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { window: {
title: 'New Section', title: 'New Section',
@ -281,7 +319,11 @@ export default class Party extends DHBaseActorSheet {
if (!confirmed) return; if (!confirmed) return;
} }
static async #triggerRest(_, button) { /**
* @this PartySheet
* @type {ApplicationClickAction}
*/
static async #onTriggerRest(_, button) {
const confirmed = await foundry.applications.api.DialogV2.confirm({ const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { window: {
title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`), title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`),
@ -304,6 +346,30 @@ export default class Party extends DHBaseActorSheet {
}); });
} }
/**
* @this PartySheet
* @type {ApplicationClickAction}
*/
static async #onDeletePartyMember(event, target) {
const doc = await foundry.utils.fromUuid(target.closest('[data-uuid]')?.dataset.uuid);
if (!event.shiftKey) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.title', {
name: doc.name
})
},
content: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.text', { name: doc.name })
});
if (!confirmed) return;
}
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
await this.document.update({ 'system.partyMembers': newMembersList });
}
static async downtimeMoveQuery({ actorId, downtimeType }) { static async downtimeMoveQuery({ actorId, downtimeType }) {
const actor = await foundry.utils.fromUuid(actorId); const actor = await foundry.utils.fromUuid(actorId);
if (!actor || !actor?.isOwner) return; if (!actor || !actor?.isOwner) return;
@ -312,11 +378,19 @@ export default class Party extends DHBaseActorSheet {
}); });
} }
static async #tagTeamRoll() { /**
* @this PartySheet
* @type {ApplicationClickAction}
*/
static async #onTagTeamRoll() {
new game.system.api.applications.dialogs.TagTeamDialog(this.document).render({ force: true }); new game.system.api.applications.dialogs.TagTeamDialog(this.document).render({ force: true });
} }
static async #groupRoll(_params) { /**
* @this PartySheet
* @type {ApplicationClickAction}
*/
static async #onGroupRoll(_params) {
new game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true }); new game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true });
} }
@ -460,11 +534,10 @@ export default class Party extends DHBaseActorSheet {
} }
} }
/* -------------------------------------------- */ /** @inheritdoc */
async _onDropActor(event, document) { async _onDropActor(event, document) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) { if (document instanceof DhpActor && PartySheet.ALLOWED_ACTOR_TYPES.includes(document.type)) {
const currentMembers = this.document.system.partyMembers.map(x => x.uuid); const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
if (currentMembers.includes(data.uuid)) { if (currentMembers.includes(data.uuid)) {
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter')); return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter'));
@ -477,24 +550,4 @@ export default class Party extends DHBaseActorSheet {
return null; return null;
} }
static async #deletePartyMember(event, target) {
const doc = await foundry.utils.fromUuid(target.closest('[data-uuid]')?.dataset.uuid);
if (!event.shiftKey) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.title', {
name: doc.name
})
},
content: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.text', { name: doc.name })
});
if (!confirmed) return;
}
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
await this.document.update({ 'system.partyMembers': newMembersList });
}
} }

View file

@ -1,5 +1,5 @@
import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs'; import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs';
import Party from '../applications/sheets/actors/party.mjs'; import PartySheet from '../applications/sheets/actors/party.mjs';
export function handleSocketEvent({ action = null, data = {} } = {}) { export function handleSocketEvent({ action = null, data = {} } = {}) {
switch (action) { switch (action) {
@ -16,7 +16,7 @@ export function handleSocketEvent({ action = null, data = {} } = {}) {
Hooks.call(socketEvent.Refresh, data); Hooks.call(socketEvent.Refresh, data);
break; break;
case socketEvent.DowntimeTrigger: case socketEvent.DowntimeTrigger:
Party.downtimeMoveQuery(data); PartySheet.downtimeMoveQuery(data);
break; break;
case socketEvent.TagTeamStart: case socketEvent.TagTeamStart:
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data); Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data);

View file

@ -26,9 +26,11 @@
&:focus, &:focus,
&:focus[type='text'], &:focus[type='text'],
&:focus[type='number'] { &:focus[type='number'] {
background: light-dark(@soft-shadow, @semi-transparent-dark-blue); &:where(:not(:disabled)) {
box-shadow: none; background: light-dark(@soft-shadow, @semi-transparent-dark-blue);
outline: 2px solid @input-color-border; box-shadow: none;
outline: 2px solid @input-color-border;
}
} }
&:disabled[type='text'], &:disabled[type='text'],

View file

@ -42,19 +42,14 @@
} }
h1 { h1 {
--dh-input-color-border: @color-border;
display: flex; display: flex;
flex: 1; flex: 1;
padding: 6px 0 0 0; padding: 6px 0 0 0;
font-size: var(--font-size-32); font-size: var(--font-size-32);
text-align: start; text-align: start;
border: 1px solid transparent;
outline: 2px solid transparent;
transition: all 0.3s ease; transition: all 0.3s ease;
word-break: break-word; word-break: break-word;
&:hover {
outline: 2px solid light-dark(@dark, @golden);
}
} }
.level-div { .level-div {

View file

@ -1,48 +1,44 @@
@import '../../../utils/colors.less'; @import '../../../utils/colors.less';
@import '../../../utils/fonts.less'; @import '../../../utils/fonts.less';
@import '../../../utils/mixin.less'; @import '../../../utils/mixin.less';
.party-header-sheet { .party-header-sheet {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: start; justify-content: start;
text-align: center; text-align: center;
.profile { .profile {
height: 235px; height: 235px;
cursor: pointer; cursor: pointer;
.smooth-gradient-ease-in-out(mask-image, to top, black, 3.5rem); .smooth-gradient-ease-in-out(mask-image, to top, black, 3.5rem);
} }
.item-container { .item-container {
margin-top: -2rem; margin-top: -2rem;
z-index: 1; z-index: 1;
input.item-name[type='text'] { input.item-name[type='text'] {
backdrop-filter: none; --dh-input-color-border: @color-border;
border: none; backdrop-filter: none;
color: @color-text-emphatic; border: none;
font-family: @font-title; color: @color-text-emphatic;
font-size: var(--font-size-32); font-family: @font-title;
outline: 2px solid transparent; font-size: var(--font-size-32);
box-shadow: unset; outline: 2px solid transparent;
text-shadow: 0 0 4px @color-text-shadow-contrast, 0 0 8px @color-text-shadow-contrast, 0 0 14px @color-text-shadow-contrast; box-shadow: unset;
text-shadow: 0 0 4px @color-text-shadow-contrast, 0 0 8px @color-text-shadow-contrast, 0 0 14px @color-text-shadow-contrast;
text-align: center;
transition: all 0.3s ease; text-align: center;
width: calc(100% - 40px); transition: all 0.3s ease;
height: 2.625rem; width: calc(100% - 40px);
height: 2.625rem;
&:hover[type='text'], }
&:focus[type='text'] {
outline: 2px solid @color-border; .label {
} font-style: normal;
} font-weight: 700;
font-size: 16px;
.label { margin: 5px 0;
font-style: normal; }
font-weight: 700; }
font-size: 16px; }
margin: 5px 0;
}
}
}

View file

@ -23,12 +23,12 @@
</button> </button>
</div> </div>
{{#if @root.showStats}} <ul class="actors-list{{#unless @root.showStats}} limited{{/unless}}">
<ul class="actors-list"> {{#each partyMembers as |member id|}}
{{#each partyMembers as |member id|}} <li class="actor-resources">
<li class="actor-resources"> <div class="actor-img-frame">
<div class="actor-img-frame"> <img class="actor-img" src="{{member.img}}">
<img class="actor-img" src="{{member.img}}"> {{#if @root.showStats}}
{{#if member.weapons}} {{#if member.weapons}}
<div class="equipped-weapons"> <div class="equipped-weapons">
{{#each member.weapons as |weapon|}} {{#each member.weapons as |weapon|}}
@ -51,12 +51,16 @@
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.severe"}}</h4> <h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.severe"}}</h4>
</div> </div>
{{/unless}} {{/unless}}
</div> {{/if}}
<header> </div>
<h2 class="actor-name"> <header>
<a data-action="openDocument" data-uuid="{{member.uuid}}">{{member.name}}</a> <h2 class="actor-name">
<a data-action="openDocument" data-uuid="{{member.uuid}}">{{member.name}}</a>
{{#if @root.editable}}
<a class="delete-icon" data-action="deletePartyMember" data-uuid="{{member.uuid}}"><i class="fa-regular fa-times" inert></i></a> <a class="delete-icon" data-action="deletePartyMember" data-uuid="{{member.uuid}}"><i class="fa-regular fa-times" inert></i></a>
</h2> {{/if}}
</h2>
{{#if @root.showStats}}
<div> <div>
{{#unless (or (eq member.type 'companion') (eq member.type 'adversary') (eq member.type 'npc')) }} {{#unless (or (eq member.type 'companion') (eq member.type 'adversary') (eq member.type 'npc')) }}
<div class="hope-section"> <div class="hope-section">
@ -73,10 +77,12 @@
</div> </div>
{{/unless}} {{/unless}}
</div> </div>
{{#if member.subtitle}} {{/if}}
<span class="subtitle">{{member.subtitle}}</span> {{#if member.subtitle}}
{{/if}} <span class="subtitle">{{member.subtitle}}</span>
</header> {{/if}}
</header>
{{#if @root.showStats}}
<section class="body"> <section class="body">
<section class="resources"> <section class="resources">
{{#unless (or (eq member.type 'companion') (eq member.type 'npc')) }} {{#unless (or (eq member.type 'companion') (eq member.type 'npc')) }}
@ -160,29 +166,10 @@
</div> </div>
{{/if}} {{/if}}
</section> </section>
</li> {{/if}}
{{/each}} </li>
</ul> {{/each}}
{{else}} </ul>
<ul class="actors-list limited">
{{#each partyMembers as |member id|}}
<li class="actor-resources">
<div class="actor-img-frame">
<img class="actor-img" src="{{member.img}}">
</div>
<header>
<h2 class="actor-name">
<a data-action="openDocument" data-uuid="{{member.uuid}}">{{member.name}}</a>
<a class="delete-icon" data-action="deletePartyMember" data-uuid="{{member.uuid}}"><i class="fa-regular fa-times" inert></i></a>
</h2>
{{#if member.subtitle}}
<span class="subtitle">{{member.subtitle}}</span>
{{/if}}
</header>
</li>
{{/each}}
</ul>
{{/if}}
{{#unless document.system.partyMembers.length}} {{#unless document.system.partyMembers.length}}
<div class="actors-dragger"> <div class="actors-dragger">
<span>{{localize "DAGGERHEART.GENERAL.dropActorsHere"}}</span> <span>{{localize "DAGGERHEART.GENERAL.dropActorsHere"}}</span>