mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
[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:
parent
f2ec5ef458
commit
f850cbda76
28 changed files with 222 additions and 215 deletions
|
|
@ -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>`
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
|||
const options = super._getEntryContextOptions();
|
||||
options.push(
|
||||
{
|
||||
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
||||
condition: li => {
|
||||
visible: li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
||||
},
|
||||
|
|
@ -92,9 +92,9 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
|||
}
|
||||
},
|
||||
{
|
||||
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty',
|
||||
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty',
|
||||
icon: `<i class="fa-regular fa-square"></i>`,
|
||||
condition: li => {
|
||||
visible: li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
return actor && actor.type === 'party' && !actor.system.active;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -103,23 +103,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
_getEntryContextOptions() {
|
||||
return [
|
||||
...super._getEntryContextOptions(),
|
||||
// {
|
||||
// name: 'Reroll',
|
||||
// icon: '<i class="fa-solid fa-dice"></i>',
|
||||
// condition: li => {
|
||||
// const message = game.messages.get(li.dataset.messageId);
|
||||
|
||||
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
|
||||
// },
|
||||
// callback: li => {
|
||||
// const message = game.messages.get(li.dataset.messageId);
|
||||
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
|
||||
// }
|
||||
// },
|
||||
{
|
||||
name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'),
|
||||
label: 'DAGGERHEART.UI.ChatLog.rerollDamage',
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
condition: li => {
|
||||
visible: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const hasRolledDamage = message.system.hasDamage
|
||||
? Object.keys(message.system.damage).length > 0
|
||||
|
|
|
|||
|
|
@ -84,15 +84,15 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
_getCombatContextOptions() {
|
||||
return [
|
||||
{
|
||||
name: 'COMBAT.ClearMovementHistories',
|
||||
label: 'COMBAT.ClearMovementHistories',
|
||||
icon: '<i class="fa-solid fa-shoe-prints"></i>',
|
||||
condition: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
||||
visible: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
||||
callback: () => this.viewed.clearMovementHistories()
|
||||
},
|
||||
{
|
||||
name: 'COMBAT.Delete',
|
||||
label: 'COMBAT.Delete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
condition: () => game.user.isGM && !!this.viewed,
|
||||
visible: () => game.user.isGM && !!this.viewed,
|
||||
callback: () => this.viewed.endCombat()
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -110,6 +110,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
return this._id;
|
||||
}
|
||||
|
||||
/** Returns true if the current user is the owner of the containing item */
|
||||
get isOwner() {
|
||||
return this.item?.isOwner ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Item the action is attached too.
|
||||
*/
|
||||
|
|
@ -143,6 +148,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
: null;
|
||||
}
|
||||
|
||||
/** Returns true if the action is usable */
|
||||
get usable() {
|
||||
const actor = this.actor;
|
||||
return this.isOwner && actor?.type === 'character';
|
||||
}
|
||||
|
||||
static getRollType(parent) {
|
||||
return 'trait';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
}
|
||||
|
||||
get actionsList() {
|
||||
// No actions on non-characters
|
||||
if (this.actor && this.actor.type !== 'character') return [];
|
||||
return this.actions;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,9 @@ export default class DHWeapon extends AttachableItem {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
get actionsList() {
|
||||
return [this.attack, ...this.actions];
|
||||
// No actions on non-characters
|
||||
if (this.actor && this.actor.type !== 'character') return [];
|
||||
return [this.attack, ...super.actionsList];
|
||||
}
|
||||
|
||||
get customActions() {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,13 @@ export default class DHItem extends foundry.documents.Item {
|
|||
return this.system.metadata.isInventoryItem ?? false;
|
||||
}
|
||||
|
||||
/** Returns true if the item can be used */
|
||||
get usable() {
|
||||
const actor = this.actor;
|
||||
const actionsList = this.system.actionsList;
|
||||
return this.isOwner && actor?.type === 'character' && (actionsList?.size || actionsList?.length);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
static async createDialog(data = {}, createOptions = {}, options = {}) {
|
||||
const { folders, types, template, context = {}, ...dialogOptions } = options;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue