From 2782b627933f47b605b8d5856de2722f2f1bb854 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 6 Jul 2026 17:52:43 -0400 Subject: [PATCH] Fix some issues involving party in observer view (#2067) --- .../applications/dialogs/groupRollDialog.mjs | 4 +- module/applications/dialogs/tagTeamDialog.mjs | 4 +- module/applications/sheets/actors/_types.d.ts | 10 ++ module/applications/sheets/actors/party.mjs | 143 ++++++++++++------ module/systemRegistration/socket.mjs | 4 +- styles/less/global/elements.less | 8 +- .../less/sheets/actors/character/header.less | 7 +- styles/less/sheets/actors/party/header.less | 92 ++++++----- .../sheets/actors/party/party-members.hbs | 63 +++----- 9 files changed, 189 insertions(+), 146 deletions(-) diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index ebc80d39..779562cb 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -1,7 +1,7 @@ import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; import { shouldUseHopeFearAutomation } from '../../helpers/utils.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; @@ -11,7 +11,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat this.party = party; 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 => ({ ...member.toObject(), uuid: member.uuid, diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 19869a00..434c30be 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -2,7 +2,7 @@ import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; import { MemberData } from '../../data/tagTeamData.mjs'; import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.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; @@ -13,7 +13,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio this.usesTagTeamHopeCost = true; this.party = party; 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 => ({ ...member.toObject(), uuid: member.uuid, diff --git a/module/applications/sheets/actors/_types.d.ts b/module/applications/sheets/actors/_types.d.ts index 89adc57b..368801b9 100644 --- a/module/applications/sheets/actors/_types.d.ts +++ b/module/applications/sheets/actors/_types.d.ts @@ -1,8 +1,18 @@ import DhCompanion from '../../../data/actor/companion.mjs'; +import DhParty from '../../../data/actor/party.mjs'; import DhpActor from '../../../documents/actor.mjs'; declare module './companion.mjs' { export default interface DhCompanionSheet { actor: DhpActor; + document: DhpActor; } } + +declare module './party.mjs' { + export default interface PartySheet { + actor: DhpActor; + document: DhpActor; + } +} + diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index 3af8ea5f..a911669e 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -6,7 +6,9 @@ import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs'; import { socketEvent } from '../../../systemRegistration/socket.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) { super(options); @@ -24,17 +26,17 @@ export default class Party extends DHBaseActorSheet { resizable: true }, actions: { - openDocument: Party.#openDocument, - deletePartyMember: Party.#deletePartyMember, - toggleHope: Party.#toggleHope, - toggleHitPoints: Party.#toggleHitPoints, - toggleStress: Party.#toggleStress, - toggleArmorSlot: Party.#toggleArmorSlot, - tempBrowser: Party.#tempBrowser, - refeshActions: Party.#refeshActions, - triggerRest: Party.#triggerRest, - tagTeamRoll: Party.#tagTeamRoll, - groupRoll: Party.#groupRoll + openDocument: PartySheet.#onOpenDocument, + deletePartyMember: PartySheet.#onDeletePartyMember, + toggleHope: PartySheet.#onToggleHope, + toggleHitPoints: PartySheet.#onToggleHitPoints, + toggleStress: PartySheet.#onToggleStress, + toggleArmorSlot: PartySheet.#onToggleArmorSlot, + tempBrowser: PartySheet.#onTempBrowser, + refreshActions: PartySheet.#onRefreshActions, + triggerRest: PartySheet.#onTriggerRest, + tagTeamRoll: PartySheet.#onTagTeamRoll, + groupRoll: PartySheet.#onGroupRoll }, dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }] }; @@ -72,6 +74,23 @@ export default class Party extends DHBaseActorSheet { 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 */ /* -------------------------------------------- */ @@ -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 document = await foundry.utils.fromUuid(uuid); document?.sheet?.render(true); @@ -207,9 +234,10 @@ export default class Party extends DHBaseActorSheet { /** * Toggles a hope resource value. + * @this PartySheet * @type {ApplicationClickAction} */ - static async #toggleHope(_, target) { + static async #onToggleHope(_, target) { const hopeValue = Number.parseInt(target.dataset.value); const actor = await foundry.utils.fromUuid(target.dataset.actorId); 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. + * @this PartySheet * @type {ApplicationClickAction} */ - static async #toggleHitPoints(_, target) { + static async #onToggleHitPoints(_, target) { const hitPointsValue = Number.parseInt(target.dataset.value); const actor = await foundry.utils.fromUuid(target.dataset.actorId); 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. + * @this PartySheet * @type {ApplicationClickAction} */ - static async #toggleStress(_, target) { + static async #onToggleStress(_, target) { const stressValue = Number.parseInt(target.dataset.value); const actor = await foundry.utils.fromUuid(target.dataset.actorId); 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. + * @this PartySheet * @type {ApplicationClickAction} */ - static async #toggleArmorSlot(_, target) { + static async #onToggleArmorSlot(_, target) { const actor = await foundry.utils.fromUuid(target.dataset.actorId); const { value, max } = actor.system.armorScore; const inputValue = Number.parseInt(target.dataset.value); @@ -258,12 +289,19 @@ export default class Party extends DHBaseActorSheet { /** * Opens Compedium Browser + * @this PartySheet + * @type {ApplicationClickAction} */ - static async #tempBrowser(_, target) { + static async #onTempBrowser(_, target) { 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({ window: { title: 'New Section', @@ -281,7 +319,11 @@ export default class Party extends DHBaseActorSheet { if (!confirmed) return; } - static async #triggerRest(_, button) { + /** + * @this PartySheet + * @type {ApplicationClickAction} + */ + static async #onTriggerRest(_, button) { const confirmed = await foundry.applications.api.DialogV2.confirm({ window: { 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 }) { const actor = await foundry.utils.fromUuid(actorId); 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 }); } - static async #groupRoll(_params) { + /** + * @this PartySheet + * @type {ApplicationClickAction} + */ + static async #onGroupRoll(_params) { 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) { 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); if (currentMembers.includes(data.uuid)) { return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter')); @@ -477,24 +550,4 @@ export default class Party extends DHBaseActorSheet { 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 }); - } } diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index de9bf00c..10206ab5 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -1,5 +1,5 @@ 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 = {} } = {}) { switch (action) { @@ -16,7 +16,7 @@ export function handleSocketEvent({ action = null, data = {} } = {}) { Hooks.call(socketEvent.Refresh, data); break; case socketEvent.DowntimeTrigger: - Party.downtimeMoveQuery(data); + PartySheet.downtimeMoveQuery(data); break; case socketEvent.TagTeamStart: Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data); diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index d570c08c..22e34e96 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -26,9 +26,11 @@ &:focus, &:focus[type='text'], &:focus[type='number'] { - background: light-dark(@soft-shadow, @semi-transparent-dark-blue); - box-shadow: none; - outline: 2px solid @input-color-border; + &:where(:not(:disabled)) { + background: light-dark(@soft-shadow, @semi-transparent-dark-blue); + box-shadow: none; + outline: 2px solid @input-color-border; + } } &:disabled[type='text'], diff --git a/styles/less/sheets/actors/character/header.less b/styles/less/sheets/actors/character/header.less index 81345715..68ebc812 100644 --- a/styles/less/sheets/actors/character/header.less +++ b/styles/less/sheets/actors/character/header.less @@ -42,19 +42,14 @@ } h1 { + --dh-input-color-border: @color-border; display: flex; flex: 1; padding: 6px 0 0 0; font-size: var(--font-size-32); text-align: start; - border: 1px solid transparent; - outline: 2px solid transparent; transition: all 0.3s ease; word-break: break-word; - - &:hover { - outline: 2px solid light-dark(@dark, @golden); - } } .level-div { diff --git a/styles/less/sheets/actors/party/header.less b/styles/less/sheets/actors/party/header.less index 58e3bc31..96858805 100644 --- a/styles/less/sheets/actors/party/header.less +++ b/styles/less/sheets/actors/party/header.less @@ -1,48 +1,44 @@ -@import '../../../utils/colors.less'; -@import '../../../utils/fonts.less'; -@import '../../../utils/mixin.less'; - -.party-header-sheet { - display: flex; - flex-direction: column; - justify-content: start; - text-align: center; - - .profile { - height: 235px; - cursor: pointer; - .smooth-gradient-ease-in-out(mask-image, to top, black, 3.5rem); - } - - .item-container { - margin-top: -2rem; - z-index: 1; - input.item-name[type='text'] { - backdrop-filter: none; - border: none; - color: @color-text-emphatic; - font-family: @font-title; - font-size: var(--font-size-32); - outline: 2px solid transparent; - 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; - 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; - margin: 5px 0; - } - } -} +@import '../../../utils/colors.less'; +@import '../../../utils/fonts.less'; +@import '../../../utils/mixin.less'; + +.party-header-sheet { + display: flex; + flex-direction: column; + justify-content: start; + text-align: center; + + .profile { + height: 235px; + cursor: pointer; + .smooth-gradient-ease-in-out(mask-image, to top, black, 3.5rem); + } + + .item-container { + margin-top: -2rem; + z-index: 1; + input.item-name[type='text'] { + --dh-input-color-border: @color-border; + backdrop-filter: none; + border: none; + color: @color-text-emphatic; + font-family: @font-title; + font-size: var(--font-size-32); + outline: 2px solid transparent; + 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; + width: calc(100% - 40px); + height: 2.625rem; + } + + .label { + font-style: normal; + font-weight: 700; + font-size: 16px; + margin: 5px 0; + } + } +} diff --git a/templates/sheets/actors/party/party-members.hbs b/templates/sheets/actors/party/party-members.hbs index ce04a364..6fc6ecaa 100644 --- a/templates/sheets/actors/party/party-members.hbs +++ b/templates/sheets/actors/party/party-members.hbs @@ -23,12 +23,12 @@ - {{#if @root.showStats}} -
    - {{#each partyMembers as |member id|}} -
  • -
    - +
      + {{#each partyMembers as |member id|}} +
    • +
      + + {{#if @root.showStats}} {{#if member.weapons}}
      {{#each member.weapons as |weapon|}} @@ -51,12 +51,16 @@

      {{localize "DAGGERHEART.ACTORS.Party.Thresholds.severe"}}

      {{/unless}} -
      -
      -

      - {{member.name}} + {{/if}} +

    +
    +

    + {{member.name}} + {{#if @root.editable}} -

    + {{/if}} + + {{#if @root.showStats}}
    {{#unless (or (eq member.type 'companion') (eq member.type 'adversary') (eq member.type 'npc')) }}
    @@ -73,10 +77,12 @@
    {{/unless}}
    - {{#if member.subtitle}} - {{member.subtitle}} - {{/if}} -
    + {{/if}} + {{#if member.subtitle}} + {{member.subtitle}} + {{/if}} + + {{#if @root.showStats}}
    {{#unless (or (eq member.type 'companion') (eq member.type 'npc')) }} @@ -160,29 +166,10 @@ {{/if}}
    -
  • - {{/each}} -
- {{else}} -
    - {{#each partyMembers as |member id|}} -
  • -
    - -
    -
    -

    - {{member.name}} - -

    - {{#if member.subtitle}} - {{member.subtitle}} - {{/if}} -
    -
  • - {{/each}} -
- {{/if}} + {{/if}} + + {{/each}} + {{#unless document.system.partyMembers.length}}
{{localize "DAGGERHEART.GENERAL.dropActorsHere"}}