[Feature] Updates to inventory icons, context menus, action use, and observer visibility (#1814)

* Update inventory controls and permissions filtering

* Also disable prosemirror editors

* Address context menu deprecation warnings

* Fix context menu detection for actions

* Refine logic for use action when hovering over item icon
This commit is contained in:
Carlos Fernandez 2026-04-20 09:30:43 -04:00 committed by GitHub
parent f2ec5ef458
commit f850cbda76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 222 additions and 215 deletions

View file

@ -12,8 +12,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
static DEFAULT_OPTIONS = {
classes: ['character'],
position: { width: 850, height: 800 },
/* Foundry adds disabled to all buttons and inputs if editPermission is missing. This is not desired. */
editPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
actions: {
toggleVault: CharacterSheet.#toggleVault,
rollAttribute: CharacterSheet.#rollAttribute,
@ -68,7 +66,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
},
{
handler: CharacterSheet.#getEquipamentContextOptions,
handler: CharacterSheet.#getEquipmentContextOptions,
selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]',
options: {
parentClassHooks: false,
@ -170,6 +168,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
return applicationOptions;
}
/** @inheritdoc */
_toggleDisabled(disabled) {
// Overriden to only disable text inputs by default.
// Everything else is done by checking @root.editable in the sheet
const form = this.form;
for (const input of form.querySelectorAll("input:not([type=search]), .editor.prosemirror")) {
input.disabled = disabled;
}
}
/** @inheritDoc */
async _onRender(context, options) {
await super._onRender(context, options);
@ -315,11 +323,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
const options = [
{
name: 'toLoadout',
label: 'toLoadout',
icon: 'fa-solid fa-arrow-up',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && doc.system.inVault;
return doc?.isOwner && doc.system.inVault;
},
callback: async target => {
const doc = await getDocFromElement(target);
@ -329,11 +337,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
},
{
name: 'recall',
label: 'recall',
icon: 'fa-solid fa-bolt-lightning',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && doc.system.inVault;
return doc?.isOwner && doc.system.inVault;
},
callback: async (target, event) => {
const doc = await getDocFromElement(target);
@ -368,17 +376,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
},
{
name: 'toVault',
label: 'toVault',
icon: 'fa-solid fa-arrow-down',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && !doc.system.inVault;
return doc?.isOwner && !doc.system.inVault;
},
callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true })
}
].map(option => ({
...option,
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
icon: `<i class="${option.icon}"></i>`
}));
@ -391,29 +399,29 @@ export default class CharacterSheet extends DHBaseActorSheet {
* @this {CharacterSheet}
* @protected
*/
static #getEquipamentContextOptions() {
static #getEquipmentContextOptions() {
const options = [
{
name: 'equip',
label: 'equip',
icon: 'fa-solid fa-hands',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && !doc.system.equipped;
return doc.isOwner && doc && !doc.system.equipped;
},
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
},
{
name: 'unequip',
label: 'unequip',
icon: 'fa-solid fa-hands',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && doc.system.equipped;
return doc.isOwner && doc && doc.system.equipped;
},
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
}
].map(option => ({
...option,
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
icon: `<i class="${option.icon}"></i>`
}));

View file

@ -418,18 +418,18 @@ export default function DHApplicationMixin(Base) {
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
const options = [
{
name: 'disableEffect',
label: 'disableEffect',
icon: 'fa-solid fa-lightbulb',
condition: element => {
visible: element => {
const target = element.closest('[data-item-uuid]');
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
},
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
},
{
name: 'enableEffect',
label: 'enableEffect',
icon: 'fa-regular fa-lightbulb',
condition: element => {
visible: element => {
const target = element.closest('[data-item-uuid]');
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
},
@ -437,7 +437,7 @@ export default function DHApplicationMixin(Base) {
}
].map(option => ({
...option,
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
icon: `<i class="${option.icon}"></i>`
}));
@ -468,14 +468,14 @@ export default function DHApplicationMixin(Base) {
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
const options = [
{
name: 'CONTROLS.CommonEdit',
label: 'CONTROLS.CommonEdit',
icon: 'fa-solid fa-pen-to-square',
condition: target => {
visible: target => {
const { dataset } = target.closest('[data-item-uuid]');
const doc = getDocFromElementSync(target);
return (
(!dataset.noCompendiumEdit && !doc) ||
(doc && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
(doc?.isOwner && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
);
},
callback: async target => (await getDocFromElement(target)).sheet.render({ force: true })
@ -484,11 +484,12 @@ export default function DHApplicationMixin(Base) {
if (usable) {
options.unshift({
name: 'DAGGERHEART.GENERAL.damage',
label: 'DAGGERHEART.GENERAL.damage',
icon: 'fa-solid fa-explosion',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return (
doc?.isOwner &&
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
!foundry.utils.isEmpty(doc?.damage?.parts)
);
@ -507,11 +508,11 @@ export default function DHApplicationMixin(Base) {
});
options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
return doc?.isOwner && !(doc.type === 'domainCard' && doc.system.inVault);
},
callback: async (target, event) => (await getDocFromElement(target)).use(event)
});
@ -519,18 +520,19 @@ export default function DHApplicationMixin(Base) {
if (toChat)
options.push({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
icon: 'fa-solid fa-message',
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
});
if (deletable)
options.push({
name: 'CONTROLS.CommonDelete',
label: 'CONTROLS.CommonDelete',
icon: 'fa-solid fa-trash',
condition: element => {
visible: element => {
const target = element.closest('[data-item-uuid]');
return target.dataset.itemType !== 'beastform';
const doc = getDocFromElementSync(target);
return doc?.isOwner && target.dataset.itemType !== 'beastform';
},
callback: async (target, event) => {
const doc = await getDocFromElement(target);