diff --git a/lang/en.json b/lang/en.json index 7867265d..d466b0fc 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1312,7 +1312,8 @@ } }, "Experiences": "Experiences", - "Level": "Level" + "Level": "Level", + "noPartner": "No Partner selected" }, "Adversary": { "FIELDS": { diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index 1a769052..c5eb45d9 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -1,6 +1,6 @@ export { default as DhCharacterSheet } from './sheets/actors/character.mjs'; export { default as DhpAdversarySheet } from './sheets/actors/adversary.mjs'; -export { default as DhCompanionSheet } from './sheets/companion.mjs'; +export { default as DhCompanionSheet } from './sheets/actors/companion.mjs'; export { default as DhpClassSheet } from './sheets/items/class.mjs'; export { default as DhpSubclass } from './sheets/items/subclass.mjs'; export { default as DhpFeatureSheet } from './sheets/items/feature.mjs'; @@ -18,3 +18,4 @@ export { default as DhContextMenu } from './contextMenu.mjs'; export { default as DhTooltipManager } from './tooltipManager.mjs'; export * as api from './sheets/api/_modules.mjs'; +export * as ux from "./ux/_module.mjs"; diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 8be31690..1a6fec84 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -6,6 +6,7 @@ import DaggerheartSheet from '.././daggerheart-sheet.mjs'; import { abilities } from '../../../config/actorConfig.mjs'; import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs'; import DhCharacterCreation from '../../characterCreation.mjs'; +import FilterMenu from '../../ux/filter-menu.mjs'; const { ActorSheetV2 } = foundry.applications.sheets; const { TextEditor } = foundry.applications.ux; @@ -215,6 +216,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { await super._onFirstRender(context, options); this._createContextMenues(); + this._createFilterMenus(); } /** @inheritDoc */ @@ -366,7 +368,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } /* -------------------------------------------- */ - /* Search Filter */ + /* Filter Tracking */ /* -------------------------------------------- */ /** @@ -376,12 +378,33 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { #search = {}; /** - * Track which item IDs are currently displayed due to a search filter. - * @type {{ inventory: Set, loadout: Set }} + * The currently active search filter. + * @type {FilterMenu} + */ + #menu = {}; + + /** + * Tracks which item IDs are currently displayed, organized by filter type and section. + * @type {{ + * inventory: { + * search: Set, + * menu: Set + * }, + * loadout: { + * search: Set, + * menu: Set + * }, + * }} */ #filteredItems = { - inventory: new Set(), - loadout: new Set() + inventory: { + search: new Set(), + menu: new Set() + }, + loadout: { + search: new Set(), + menu: new Set() + } }; /** @@ -429,15 +452,14 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { * @protected */ _onSearchFilterInventory(event, query, rgx, html) { - this.#filteredItems.inventory.clear(); + this.#filteredItems.inventory.search.clear(); - for (const ul of html.querySelectorAll('.items-list')) { - for (const li of ul.querySelectorAll('.inventory-item')) { - const item = this.document.items.get(li.dataset.itemId); - const match = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name); - if (match) this.#filteredItems.inventory.add(item.id); - li.hidden = !match; - } + for (const li of html.querySelectorAll('.inventory-item')) { + const item = this.document.items.get(li.dataset.itemId); + const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name); + if (matchesSearch) this.#filteredItems.inventory.search.add(item.id); + const { menu } = this.#filteredItems.inventory; + li.hidden = !(menu.has(item.id) && matchesSearch); } } @@ -450,15 +472,14 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { * @protected */ _onSearchFilterCard(event, query, rgx, html) { - this.#filteredItems.loadout.clear(); + this.#filteredItems.loadout.search.clear(); - const elements = html.querySelectorAll('.items-list .inventory-item, .card-list .card-item'); - - for (const li of elements) { + for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) { const item = this.document.items.get(li.dataset.itemId); - const match = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name); - if (match) this.#filteredItems.loadout.add(item.id); - li.hidden = !match; + const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name); + if (matchesSearch) this.#filteredItems.loadout.search.add(item.id); + const { menu } = this.#filteredItems.loadout; + li.hidden = !(menu.has(item.id) && matchesSearch); } } @@ -474,6 +495,102 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { this.document.diceRoll(config); } + /* -------------------------------------------- */ + /* Filter Menus */ + /* -------------------------------------------- */ + + _createFilterMenus() { + //Menus could be a application option if needed + const menus = [ + { + key: 'inventory', + container: '[data-application-part="inventory"]', + content: '.items-section', + callback: this._onMenuFilterInventory.bind(this), + target: '.filter-button', + filters: FilterMenu.invetoryFilters + }, + { + key: 'loadout', + container: '[data-application-part="loadout"]', + content: '.items-section', + callback: this._onMenuFilterLoadout.bind(this), + target: '.filter-button', + filters: FilterMenu.cardsFilters + } + ]; + + menus.forEach(m => { + const container = this.element.querySelector(m.container); + this.#menu[m.key] = new FilterMenu(container, m.target, m.filters, m.callback, { + contentSelector: m.content + }); + }); + } + + /** + * Callback when filters change + * @param {PointerEvent} event + * @param {HTMLElement} html + * @param {import('../ux/filter-menu.mjs').FilterItem[]} filters + */ + _onMenuFilterInventory(event, html, filters) { + this.#filteredItems.inventory.menu.clear(); + + for (const li of html.querySelectorAll('.inventory-item')) { + const item = this.document.items.get(li.dataset.itemId); + + const matchesMenu = + filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f)); + if (matchesMenu) this.#filteredItems.inventory.menu.add(item.id); + + const { search } = this.#filteredItems.inventory; + li.hidden = !(search.has(item.id) && matchesMenu); + } + } + + /** + * Callback when filters change + * @param {PointerEvent} event + * @param {HTMLElement} html + * @param {import('../ux/filter-menu.mjs').FilterItem[]} filters + */ + _onMenuFilterLoadout(event, html, filters) { + this.#filteredItems.loadout.menu.clear(); + + for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) { + const item = this.document.items.get(li.dataset.itemId); + + const matchesMenu = + filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f)); + if (matchesMenu) this.#filteredItems.loadout.menu.add(item.id); + + const { search } = this.#filteredItems.loadout; + li.hidden = !(search.has(item.id) && matchesMenu); + } + } + /* -------------------------------------------- */ + + async mapFeatureType(data, configType) { + return await Promise.all( + data.map(async x => { + const abilities = x.system.abilities + ? await Promise.all(x.system.abilities.map(async x => await fromUuid(x.uuid))) + : []; + + return { + ...x, + uuid: x.uuid, + system: { + ...x.system, + abilities: abilities, + type: game.i18n.localize(configType[x.system.type ?? x.type].label) + } + }; + }) + ); + } + static async toggleMarks(_, button) { const markValue = Number.parseInt(button.dataset.value); const newValue = this.document.system.armor.system.marks.value >= markValue ? markValue - 1 : markValue; diff --git a/module/applications/sheets/actors/companion.mjs b/module/applications/sheets/actors/companion.mjs new file mode 100644 index 00000000..89939e9a --- /dev/null +++ b/module/applications/sheets/actors/companion.mjs @@ -0,0 +1,108 @@ +import DaggerheartSheet from '../daggerheart-sheet.mjs'; +import DHCompanionSettings from '../applications/companion-settings.mjs'; + +const { ActorSheetV2 } = foundry.applications.sheets; +export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) { + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'companion'], + position: { width: 300 }, + actions: { + viewActor: this.viewActor, + openSettings: this.openSettings, + useItem: this.useItem, + toChat: this.toChat + }, + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false + } + }; + + static PARTS = { + header: { template: 'systems/daggerheart/templates/sheets/actors/companion/header.hbs' }, + details: { template: 'systems/daggerheart/templates/sheets/actors/companion/details.hbs' }, + effects: { template: 'systems/daggerheart/templates/sheets/actors/companion/effects.hbs' } + }; + + static TABS = { + details: { + active: true, + cssClass: '', + group: 'primary', + id: 'details', + icon: null, + label: 'DAGGERHEART.General.tabs.details' + }, + effects: { + active: false, + cssClass: '', + group: 'primary', + id: 'effects', + icon: null, + label: 'DAGGERHEART.Sheets.PC.Tabs.effects' + } + }; + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.document = this.document; + context.tabs = super._getTabs(this.constructor.TABS); + + return context; + } + + static async updateForm(event, _, formData) { + await this.document.update(formData.object); + this.render(); + } + + static async viewActor(_, button) { + const target = button.closest('[data-item-uuid]'); + const actor = await foundry.utils.fromUuid(target.dataset.itemUuid); + if (!actor) return; + + actor.sheet.render(true); + } + + getAction(element) { + const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId, + item = this.document.system.actions.find(x => x.id === itemId); + return item; + } + + static async useItem(event) { + const action = this.getAction(event) ?? this.actor.system.attack; + action.use(event); + } + + static async toChat(event, button) { + if (button?.dataset?.type === 'experience') { + const experience = this.document.system.experiences[button.dataset.uuid]; + const cls = getDocumentClass('ChatMessage'); + const systemData = { + name: game.i18n.localize('DAGGERHEART.General.Experience.Single'), + description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}` + }; + const msg = new cls({ + type: 'abilityUse', + user: game.user.id, + system: systemData, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/ability-use.hbs', + systemData + ) + }); + + cls.create(msg.toObject()); + } else { + const item = this.getAction(event) ?? this.document.system.attack; + item.toChat(this.document.id); + } + } + + static async openSettings() { + await new DHCompanionSettings(this.document).render(true); + } +} diff --git a/module/applications/sheets/applications/companion-settings.mjs b/module/applications/sheets/applications/companion-settings.mjs new file mode 100644 index 00000000..89d20a07 --- /dev/null +++ b/module/applications/sheets/applications/companion-settings.mjs @@ -0,0 +1,160 @@ +import { GMUpdateEvent, socketEvent } from '../../../helpers/socket.mjs'; +import DhCompanionlevelUp from '../../levelup/companionLevelup.mjs'; + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +export default class DHCompanionSettings extends HandlebarsApplicationMixin(ApplicationV2) { + constructor(actor) { + super({}); + + this.actor = actor; + } + + get title() { + return `${game.i18n.localize('DAGGERHEART.Sheets.TABS.settings')}`; + } + + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'dh-style', 'dialog', 'companion-settings'], + window: { + icon: 'fa-solid fa-wrench', + resizable: false + }, + position: { width: 455, height: 'auto' }, + actions: { + levelUp: this.levelUp + }, + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false + } + }; + + static PARTS = { + header: { + id: 'header', + template: 'systems/daggerheart/templates/sheets/applications/companion-settings/header.hbs' + }, + tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, + details: { + id: 'details', + template: 'systems/daggerheart/templates/sheets/applications/companion-settings/details.hbs' + }, + experiences: { + id: 'experiences', + template: 'systems/daggerheart/templates/sheets/applications/companion-settings/experiences.hbs' + }, + attack: { + id: 'attack', + template: 'systems/daggerheart/templates/sheets/applications/companion-settings/attack.hbs' + } + }; + + static TABS = { + details: { + active: true, + cssClass: '', + group: 'primary', + id: 'details', + icon: null, + label: 'DAGGERHEART.General.tabs.details' + }, + experiences: { + active: false, + cssClass: '', + group: 'primary', + id: 'experiences', + icon: null, + label: 'DAGGERHEART.General.tabs.experiences' + }, + attack: { + active: false, + cssClass: '', + group: 'primary', + id: 'attack', + icon: null, + label: 'DAGGERHEART.General.tabs.attack' + } + }; + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + htmlElement.querySelector('.partner-value')?.addEventListener('change', this.onPartnerChange.bind(this)); + } + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.document = this.actor; + context.tabs = this._getTabs(this.constructor.TABS); + context.systemFields = this.actor.system.schema.fields; + context.systemFields.attack.fields = this.actor.system.attack.schema.fields; + context.isNPC = true; + context.playerCharacters = game.actors + .filter( + x => + x.type === 'character' && + (x.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) || + this.document.system.partner?.uuid === x.uuid) + ) + .map(x => ({ key: x.uuid, name: x.name })); + + return context; + } + + _getTabs(tabs) { + for (const v of Object.values(tabs)) { + v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; + v.cssClass = v.active ? 'active' : ''; + } + + return tabs; + } + + async onPartnerChange(event) { + const partnerDocument = event.target.value + ? await foundry.utils.fromUuid(event.target.value) + : this.actor.system.partner; + const partnerUpdate = { 'system.companion': event.target.value ? this.actor.uuid : null }; + + if (!partnerDocument.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) { + await game.socket.emit(`system.${SYSTEM.id}`, { + action: socketEvent.GMUpdate, + data: { + action: GMUpdateEvent.UpdateDocument, + uuid: partnerDocument.uuid, + update: update + } + }); + } else { + await partnerDocument.update(partnerUpdate); + } + + await this.actor.update({ 'system.partner': event.target.value }); + + if (!event.target.value) { + await this.actor.updateLevel(1); + } + + this.render(); + } + + async viewActor(_, button) { + const target = button.closest('[data-item-uuid]'); + const actor = await foundry.utils.fromUuid(target.dataset.itemUuid); + if (!actor) return; + + actor.sheet.render(true); + } + + static async levelUp() { + new DhCompanionlevelUp(this.actor).render(true); + } + + static async updateForm(event, _, formData) { + await this.actor.update(formData.object); + this.render(); + } +} diff --git a/module/applications/sheets/companion.mjs b/module/applications/sheets/companion.mjs deleted file mode 100644 index 46080814..00000000 --- a/module/applications/sheets/companion.mjs +++ /dev/null @@ -1,86 +0,0 @@ -import { GMUpdateEvent, socketEvent } from '../../helpers/socket.mjs'; -import DhCompanionlevelUp from '../levelup/companionLevelup.mjs'; -import DaggerheartSheet from './daggerheart-sheet.mjs'; - -const { ActorSheetV2 } = foundry.applications.sheets; -export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) { - static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'companion'], - position: { width: 700, height: 1000 }, - actions: { - attackRoll: this.attackRoll, - levelUp: this.levelUp - }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false - } - }; - - static PARTS = { - sidebar: { template: 'systems/daggerheart/templates/sheets/actors/companion/tempMain.hbs' } - }; - - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - htmlElement.querySelector('.partner-value')?.addEventListener('change', this.onPartnerChange.bind(this)); - } - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.playerCharacters = game.actors - .filter( - x => - x.type === 'character' && - (x.ownership.default === 3 || - x.ownership[game.user.id] === 3 || - this.document.system.partner?.uuid === x.uuid) - ) - .map(x => ({ key: x.uuid, name: x.name })); - - return context; - } - - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } - - async onPartnerChange(event) { - const partnerDocument = event.target.value - ? await foundry.utils.fromUuid(event.target.value) - : this.document.system.partner; - const partnerUpdate = { 'system.companion': event.target.value ? this.document.uuid : null }; - - if (!partnerDocument.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) { - await game.socket.emit(`system.${SYSTEM.id}`, { - action: socketEvent.GMUpdate, - data: { - action: GMUpdateEvent.UpdateDocument, - uuid: partnerDocument.uuid, - update: update - } - }); - } else { - await partnerDocument.update(partnerUpdate); - } - - await this.document.update({ 'system.partner': event.target.value }); - - if (!event.target.value) { - await this.document.updateLevel(1); - } - } - - static async attackRoll(event) { - this.actor.system.attack.use(event); - } - - static async levelUp() { - new DhCompanionlevelUp(this.document).render(true); - } -} diff --git a/module/applications/ux/_module.mjs b/module/applications/ux/_module.mjs new file mode 100644 index 00000000..a16a4d6f --- /dev/null +++ b/module/applications/ux/_module.mjs @@ -0,0 +1 @@ +export { default as FilterMenu } from './filter-menu.mjs'; diff --git a/module/applications/ux/filter-menu.mjs b/module/applications/ux/filter-menu.mjs new file mode 100644 index 00000000..0973a358 --- /dev/null +++ b/module/applications/ux/filter-menu.mjs @@ -0,0 +1,237 @@ +/** + * @typedef {Object} FilterItem + * @property {string} group - The group name this filter belongs to (e.g., "Type"). + * @property {string} name - The display name of the filter (e.g., "Weapons"). + * @property {import("@client/applications/ux/search-filter.mjs").FieldFilter} filter - The filter condition. + */ + +export default class FilterMenu extends foundry.applications.ux.ContextMenu { + /** + * Filter Menu + * @param {HTMLElement} container - Container element + * @param {string} selector - CSS selector for menu targets + * @param {Array} menuItems - Array of menu entries + * @param {Function} callback - Callback when filters change + * @param {Object} [options] - Additional options + */ + constructor(container, selector, menuItems, callback, options = {}) { + // Set default options + const mergedOptions = { + eventName: 'click', + fixed: true, + ...options + }; + + super(container, selector, menuItems, mergedOptions); + + // Initialize filter states + this.menuItems = menuItems.map(item => ({ + ...item, + enabled: false + })); + + this.callback = callback; + this.contentElement = container.querySelector(mergedOptions.contentSelector); + + const syntheticEvent = { + type: 'pointerdown', + bubbles: true, + cancelable: true, + pointerType: 'mouse', + isPrimary: true, + button: 0 + }; + + this.callback(syntheticEvent, this.contentElement, this.getActiveFilterData()); + } + + /** @inheritdoc */ + async render(target, options = {}) { + await super.render(target, { ...options, animate: false }); + + // Create menu structure + const menu = document.createElement('menu'); + menu.className = 'filter-menu'; + + // Group items by their group property + const groups = this.#groupItems(this.menuItems); + + // Create sections for each group + for (const [groupName, items] of Object.entries(groups)) { + if (!items.length) continue; + + const section = this.#createSection(groupName, items); + menu.appendChild(section); + } + + // Update menu and set position + this.element.replaceChildren(menu); + + menu.addEventListener('click', this.#handleClick.bind(this)); + + this._setPosition(this.element, target, options); + + if (options.animate !== false) await this._animate(true); + return this._onRender(options); + } + + /** + * Groups an array of items by their `group`. + * @param {Array} items - The array of items to group. Each item is expected to have an optional `group` property. + * @returns {Object>} An object where keys are group names and values are arrays of items belonging to each group. + */ + #groupItems(items) { + return items.reduce((groups, item) => { + const group = item.group ?? '_none'; + groups[group] = groups[group] || []; + groups[group].push(item); + return groups; + }, {}); + } + + /** + * Creates a DOM section element for a group of items with corresponding filter buttons. + * @param {string} groupName - The name of the group, used as the section label. + * @param {Array} items - The items to create buttons for. Each item should have: + * @returns {HTMLDivElement} The section DOM element containing the label and buttons. + */ + #createSection(groupName, items) { + const section = document.createElement('fieldset'); + section.className = 'filter-section'; + + const header = document.createElement('legend'); + header.textContent = groupName; + section.appendChild(header); + + const buttons = document.createElement('div'); + buttons.className = 'filter-buttons'; + + items.forEach(item => { + const button = document.createElement('button'); + button.className = `filter-button ${item.enabled ? 'active' : ''}`; + button.textContent = item.name; + item.element = button; + buttons.appendChild(button); + }); + + section.appendChild(buttons); + return section; + } + + /** + * Get filter data from active filters + * @returns {Array} Array of filter configurations + */ + getActiveFilterData() { + return this.menuItems.filter(item => item.enabled).map(item => item.filter); + } + + /** + * Handles click events on filter buttons. + * Toggles the active state of the clicked button and updates the corresponding item's `enabled` state. + * Then triggers the provided callback with the event, the content element, and the current active filter data. + * @param {PointerEvent} event - The click event triggered by interacting with a filter button. + * @returns {void} + */ + #handleClick(event) { + event.preventDefault(); + event.stopPropagation(); + + const button = event.target.closest('.filter-button'); + if (!button) return; + + const clickedItem = this.menuItems.find(item => item.element === button); + if (!clickedItem) return; + + const isActive = button.classList.toggle('active'); + clickedItem.enabled = isActive; + + const filters = this.getActiveFilterData(); + + if (filters.length > 0) { + this.target.classList.add('fa-beat', 'active'); + } else { + this.target.classList.remove('fa-beat', 'active'); + } + + this.callback(event, this.contentElement, filters); + } + + /** + * Generate and return a sorted array of inventory filters. + * @returns {Array} An array of filter objects, sorted by name within each group. + */ + static get invetoryFilters() { + const { OPERATORS } = foundry.applications.ux.SearchFilter; + + const typesFilters = Object.entries(CONFIG.Item.dataModels) + .filter(([, { metadata }]) => metadata.isInventoryItem) + .map(([type, { metadata }]) => ({ + group: game.i18n.localize('Type'), + name: game.i18n.localize(metadata.label), + filter: { + field: 'type', + operator: OPERATORS.EQUALS, + value: type + } + })); + + const burdenFilter = Object.values(CONFIG.daggerheart.GENERAL.burden).map(({ value, label }) => ({ + group: game.i18n.localize('DAGGERHEART.Sheets.Weapon.Burden'), + name: game.i18n.localize(label), + filter: { + field: 'system.burden', + operator: OPERATORS.EQUALS, + value: value + } + })); + + const damageTypeFilter = Object.values(CONFIG.daggerheart.GENERAL.damageTypes).map(({ id, abbreviation }) => ({ + group: 'Damage Type', //TODO localize + name: game.i18n.localize(abbreviation), + filter: { + field: 'system.damage.type', + operator: OPERATORS.EQUALS, + value: id + } + })); + + return [ + ...game.i18n.sortObjects(typesFilters, 'name'), + ...game.i18n.sortObjects(burdenFilter, 'name'), + ...game.i18n.sortObjects(damageTypeFilter, 'name') + ]; + } + + /** + * Generate and return a sorted array of inventory filters. + * @returns {Array} An array of filter objects, sorted by name within each group. + */ + static get cardsFilters() { + const { OPERATORS } = foundry.applications.ux.SearchFilter; + + const typesFilters = Object.values(CONFIG.daggerheart.DOMAIN.cardTypes).map(({ id, label }) => ({ + group: game.i18n.localize('Type'), + name: game.i18n.localize(label), + filter: { + field: 'system.type', + operator: OPERATORS.EQUALS, + value: id + } + })); + + const domainFilter = Object.values(CONFIG.daggerheart.DOMAIN.domains).map(({ id, label }) => ({ + group: game.i18n.localize('DAGGERHEART.Sheets.DomainCard.Domain'), + name: game.i18n.localize(label), + filter: { + field: 'system.domain', + operator: OPERATORS.EQUALS, + value: id + } + })); + + const sort = arr => game.i18n.sortObjects(arr, 'name'); + + return [...sort(typesFilters), ...sort(domainFilter)]; + } +} diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index abc38e93..b1254f87 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -47,6 +47,7 @@ export default class DhCompanion extends BaseDataActor { attack: new ActionField({ initial: { name: 'Attack', + img: 'icons/creatures/claws/claw-bear-paw-swipe-brown.webp', _id: foundry.utils.randomID(), systemPath: 'attack', type: 'attack', @@ -57,7 +58,8 @@ export default class DhCompanion extends BaseDataActor { }, roll: { type: 'weapon', - bonus: 0 + bonus: 0, + trait: 'instinct' }, damage: { parts: [ @@ -77,8 +79,10 @@ export default class DhCompanion extends BaseDataActor { }; } - get attackBonus() { - return this.attack.roll.bonus ?? 0; + get traits() { + return { + instinct: { total: this.attack.roll.bonus } + }; } prepareBaseData() { diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 541b76d0..2bb4084f 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -126,6 +126,7 @@ export default class DhpActor extends Actor { } } }); + this.sheet.render(); if (this.system.companion) { this.system.companion.updateLevel(newLevel); @@ -307,6 +308,7 @@ export default class DhpActor extends Actor { } } }); + this.sheet.render(); if (this.system.companion) { this.system.companion.updateLevel(this.system.levelData.level.changed); @@ -338,7 +340,7 @@ export default class DhpActor extends Actor { } get rollClass() { - return CONFIG.Dice.daggerheart[this.type === 'character' ? 'DualityRoll' : 'D20Roll']; + return CONFIG.Dice.daggerheart[['character', 'companion'].includes(this.type) ? 'DualityRoll' : 'D20Roll']; } getRollData() { diff --git a/styles/daggerheart.css b/styles/daggerheart.css index 4d8546b1..cc6854ca 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -4206,6 +4206,10 @@ div.daggerheart.views.multiclass { .application.sheet.daggerheart.actor.dh-style.character .tab.loadout .search-section .search-bar input:placeholder { color: light-dark(#18162e50, #efe6d850); } +.application.sheet.daggerheart.actor.dh-style.character .tab.loadout .search-section .search-bar input::-webkit-search-cancel-button { + -webkit-appearance: none; + display: none; +} .application.sheet.daggerheart.actor.dh-style.character .tab.loadout .search-section .search-bar .icon { align-content: center; height: 32px; @@ -4985,13 +4989,248 @@ div.daggerheart.views.multiclass { color: light-dark(#18162e50, #efe6d850); font-family: 'Montserrat', sans-serif; } -.application.sheet.daggerheart.actor.dh-style.companion .profile { - height: 80px; - width: 80px; +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; } -.application.sheet.daggerheart.actor.dh-style.companion .temp-container { +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .profile { + height: 235px; + width: 100%; + object-fit: cover; + cursor: pointer; + mask-image: linear-gradient(0deg, transparent 0%, black 10%); +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .actor-name { + display: flex; + align-items: center; position: relative; - top: 32px; + top: -30px; + gap: 20px; + padding: 0 20px; + margin-bottom: -30px; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .actor-name input[type='text'] { + font-size: 24px; + height: 32px; + text-align: center; + border: 1px solid transparent; + outline: 2px solid transparent; + transition: all 0.3s ease; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .actor-name input[type='text']:hover { + outline: 2px solid light-dark(#222, #f3c267); +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section { + display: flex; + gap: 5px; + justify-content: center; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-number { + justify-items: center; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-number .status-value { + position: relative; + display: flex; + width: 50px; + height: 40px; + border: 1px solid light-dark(#18162e, #f3c267); + border-bottom: none; + border-radius: 6px 6px 0 0; + padding: 0 6px; + font-size: 1.5rem; + align-items: center; + justify-content: center; + background: light-dark(transparent, #18162e); + z-index: 2; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-number .status-value.armor-slots { + width: 80px; + height: 30px; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-number .status-label { + padding: 2px 10px; + width: 100%; + border-radius: 3px; + background: light-dark(#18162e, #f3c267); +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-number .status-label h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + font-size: 12px; + color: light-dark(#efe6d8, #18162e); +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar { + position: relative; + width: 100px; + height: 40px; + justify-items: center; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .status-label { + position: relative; + top: 40px; + height: 22px; + width: 79px; + clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); + background: light-dark(#18162e, #f3c267); +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .status-label h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + color: light-dark(#efe6d8, #18162e); +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .status-value { + position: absolute; + display: flex; + padding: 0 6px; + font-size: 1.5rem; + align-items: center; + width: 100px; + height: 40px; + justify-content: center; + text-align: center; + z-index: 2; + color: #efe6d8; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .status-value input[type='number'] { + background: transparent; + font-size: 1.5rem; + width: 40px; + height: 30px; + text-align: center; + border: none; + outline: 2px solid transparent; + color: #efe6d8; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .status-value input[type='number'].bar-input { + padding: 0; + color: #efe6d8; + backdrop-filter: none; + background: transparent; + transition: all 0.3s ease; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .status-value input[type='number'].bar-input:hover, +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .status-value input[type='number'].bar-input:focus { + background: rgba(24, 22, 46, 0.33); + backdrop-filter: blur(9.5px); +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .status-value .bar-label { + width: 40px; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .progress-bar { + position: absolute; + appearance: none; + width: 100px; + height: 40px; + border: 1px solid light-dark(#18162e, #f3c267); + border-radius: 6px; + z-index: 1; + background: #18162e; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .progress-bar::-webkit-progress-bar { + border: none; + background: #18162e; + border-radius: 6px; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .progress-bar::-webkit-progress-value { + background: linear-gradient(15deg, #46140a 0%, #be0000 42%, #fcb045 100%); + border-radius: 6px; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .progress-bar.stress-color::-webkit-progress-value { + background: linear-gradient(15deg, #823b01 0%, #fc8e45 65%, #be0000 100%); + border-radius: 6px; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .progress-bar::-moz-progress-bar { + background: linear-gradient(15deg, #46140a 0%, #be0000 42%, #fcb045 100%); + border-radius: 6px; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .status-bar .progress-bar.stress-color::-moz-progress-bar { + background: linear-gradient(15deg, #823b01 0%, #fc8e45 65%, #be0000 100%); + border-radius: 6px; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .status-section .level-up-label { + font-size: 24px; + padding-top: 8px; +} +.application.sheet.daggerheart.actor.dh-style.companion .companion-header-sheet .companion-navigation { + display: flex; + gap: 8px; + align-items: center; + width: 100%; +} +.application.sheet.daggerheart.actor.dh-style.companion .partner-section, +.application.sheet.daggerheart.actor.dh-style.companion .attack-section { + display: flex; + flex-direction: column; + align-items: center; +} +.application.sheet.daggerheart.actor.dh-style.companion .partner-section .title, +.application.sheet.daggerheart.actor.dh-style.companion .attack-section .title { + display: flex; + gap: 15px; + align-items: center; +} +.application.sheet.daggerheart.actor.dh-style.companion .partner-section .title h3, +.application.sheet.daggerheart.actor.dh-style.companion .attack-section .title h3 { + font-size: 20px; +} +.application.sheet.daggerheart.actor.dh-style.companion .partner-section .items-list, +.application.sheet.daggerheart.actor.dh-style.companion .attack-section .items-list { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; +} +.application.sheet.daggerheart.actor.dh-style.companion .partner-placeholder { + display: flex; + opacity: 0.6; + text-align: center; + font-style: italic; + justify-content: center; +} +.application.sheet.daggerheart.actor.dh-style.companion .experience-list { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + margin-top: 10px; + align-items: center; +} +.application.sheet.daggerheart.actor.dh-style.companion .experience-list .experience-row { + display: flex; + gap: 5px; + width: 250px; + align-items: center; + justify-content: space-between; +} +.application.sheet.daggerheart.actor.dh-style.companion .experience-list .experience-row .experience-name { + width: 180px; + text-align: start; + font-size: 14px; + font-family: 'Montserrat', sans-serif; + color: light-dark(#222, #efe6d8); +} +.application.sheet.daggerheart.actor.dh-style.companion .experience-list .experience-value { + height: 25px; + width: 35px; + font-size: 14px; + font-family: 'Montserrat', sans-serif; + color: light-dark(#222, #efe6d8); + align-content: center; + text-align: center; + background: url(../assets/svg/experience-shield.svg) no-repeat; +} +.theme-light .application.sheet.daggerheart.actor.dh-style.companion .experience-list .experience-value { + background: url('../assets/svg/experience-shield-light.svg') no-repeat; +} +.theme-light .application.sheet.daggerheart.actor.dh-style.companion { + background: url('../assets/parchments/dh-parchment-light.png'); +} +.theme-dark .application.sheet.daggerheart.actor.dh-style.companion { + background-image: url('../assets/parchments/dh-parchment-dark.png'); } .application.sheet.daggerheart.actor.dh-style.adversary .window-content { overflow: auto; @@ -5286,6 +5525,19 @@ div.daggerheart.views.multiclass { box-shadow: none; outline: 2px solid light-dark(#222, #efe6d8); } +.application.dh-style input[type='text']:disabled[type='text'], +.application.dh-style input[type='number']:disabled[type='text'], +.application.dh-style input[type='text']:disabled[type='number'], +.application.dh-style input[type='number']:disabled[type='number'] { + outline: 2px solid transparent; + cursor: not-allowed; +} +.application.dh-style input[type='text']:disabled[type='text']:hover, +.application.dh-style input[type='number']:disabled[type='text']:hover, +.application.dh-style input[type='text']:disabled[type='number']:hover, +.application.dh-style input[type='number']:disabled[type='number']:hover { + background: transparent; +} .application.dh-style input[type='checkbox']:checked::after { color: light-dark(#222, #f3c267); } @@ -5309,6 +5561,16 @@ div.daggerheart.views.multiclass { .application.dh-style button.glow { animation: glow 0.75s infinite alternate; } +.application.dh-style button:disabled { + background: light-dark(transparent, #f3c267); + color: light-dark(#18162e, #18162e); + opacity: 0.6; + cursor: not-allowed; +} +.application.dh-style button:disabled:hover { + background: light-dark(transparent, #f3c267); + color: light-dark(#18162e, #18162e); +} .application.dh-style select { background: light-dark(transparent, transparent); color: light-dark(#222, #efe6d8); @@ -6054,6 +6316,44 @@ div.daggerheart.views.multiclass { .application prose-mirror .editor-content h3 { font-size: 24px; } +.filter-menu { + width: auto; +} +.filter-menu fieldset.filter-section { + align-items: center; + margin: 5px; + border-radius: 6px; + border-color: light-dark(#18162e, #f3c267); + padding: 5px; +} +.filter-menu fieldset.filter-section legend { + font-family: 'Montserrat', sans-serif; + font-weight: bold; + color: light-dark(#18162e, #f3c267); + font-size: var(--font-size-12); +} +.filter-menu fieldset.filter-section .filter-buttons { + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + gap: 5px; +} +.filter-menu fieldset.filter-section .filter-buttons button { + background: light-dark(rgba(0, 0, 0, 0.3), #18162e); + color: light-dark(#18162e, #f3c267); + outline: none; + box-shadow: none; + border: 1px solid light-dark(#18162e, #18162e); + padding: 0 0.2rem; + font-size: var(--font-size-12); +} +.filter-menu fieldset.filter-section .filter-buttons button:hover { + background: light-dark(transparent, #f3c267); + color: light-dark(#18162e, #18162e); +} +.filter-menu fieldset.filter-section .filter-buttons button.active { + animation: glow 0.75s infinite alternate; +} .daggerheart { /* Flex */ /****/ diff --git a/styles/daggerheart.less b/styles/daggerheart.less index 362856ce..49c66916 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -40,6 +40,8 @@ @import './less/applications/environment-settings/actions.less'; @import './less/applications/environment-settings/adversaries.less'; +@import './less/actors/companion/header.less'; +@import './less/actors/companion/details.less'; @import './less/actors/companion/sheet.less'; @import './less/actors/adversary.less'; @@ -64,6 +66,8 @@ @import './less/global/inventory-item.less'; @import './less/global/inventory-fieldset-items.less'; @import './less/global/prose-mirror.less'; +@import "./less/global/filter-menu.less"; + @import '../node_modules/@yaireo/tagify/dist/tagify.css'; .daggerheart { diff --git a/styles/less/actors/character/loadout.less b/styles/less/actors/character/loadout.less index bf1474a2..72393597 100644 --- a/styles/less/actors/character/loadout.less +++ b/styles/less/actors/character/loadout.less @@ -30,6 +30,11 @@ &:placeholder { color: light-dark(@dark-blue-50, @beige-50); } + + &::-webkit-search-cancel-button { + -webkit-appearance: none; + display: none; + } } .icon { diff --git a/styles/less/actors/companion/details.less b/styles/less/actors/companion/details.less new file mode 100644 index 00000000..4da8d126 --- /dev/null +++ b/styles/less/actors/companion/details.less @@ -0,0 +1,75 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.companion { + .partner-section, + .attack-section { + display: flex; + flex-direction: column; + align-items: center; + + .title { + display: flex; + gap: 15px; + align-items: center; + + h3 { + font-size: 20px; + } + } + .items-list { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + } + } + + .partner-placeholder { + display: flex; + opacity: 0.6; + text-align: center; + font-style: italic; + justify-content: center; + } + + .experience-list { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + margin-top: 10px; + align-items: center; + + .experience-row { + display: flex; + gap: 5px; + width: 250px; + align-items: center; + justify-content: space-between; + + .experience-name { + width: 180px; + text-align: start; + font-size: 14px; + font-family: @font-body; + color: light-dark(@dark, @beige); + } + } + + .experience-value { + height: 25px; + width: 35px; + font-size: 14px; + font-family: @font-body; + color: light-dark(@dark, @beige); + align-content: center; + text-align: center; + background: url(../assets/svg/experience-shield.svg) no-repeat; + + .theme-light & { + background: url('../assets/svg/experience-shield-light.svg') no-repeat; + } + } + } +} diff --git a/styles/less/actors/companion/header.less b/styles/less/actors/companion/header.less new file mode 100644 index 00000000..df68747b --- /dev/null +++ b/styles/less/actors/companion/header.less @@ -0,0 +1,197 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.companion { + .companion-header-sheet { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + + .profile { + height: 235px; + width: 100%; + object-fit: cover; + cursor: pointer; + mask-image: linear-gradient(0deg, transparent 0%, black 10%); + } + + .actor-name { + display: flex; + align-items: center; + position: relative; + top: -30px; + gap: 20px; + padding: 0 20px; + margin-bottom: -30px; + + input[type='text'] { + font-size: 24px; + height: 32px; + text-align: center; + border: 1px solid transparent; + outline: 2px solid transparent; + transition: all 0.3s ease; + + &:hover { + outline: 2px solid light-dark(@dark, @golden); + } + } + } + + .status-section { + display: flex; + gap: 5px; + justify-content: center; + + .status-number { + justify-items: center; + + .status-value { + position: relative; + display: flex; + width: 50px; + height: 40px; + border: 1px solid light-dark(@dark-blue, @golden); + border-bottom: none; + border-radius: 6px 6px 0 0; + padding: 0 6px; + font-size: 1.5rem; + align-items: center; + justify-content: center; + background: light-dark(transparent, @dark-blue); + z-index: 2; + + &.armor-slots { + width: 80px; + height: 30px; + } + } + + .status-label { + padding: 2px 10px; + width: 100%; + border-radius: 3px; + background: light-dark(@dark-blue, @golden); + + h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + font-size: 12px; + color: light-dark(@beige, @dark-blue); + } + } + } + + .status-bar { + position: relative; + width: 100px; + height: 40px; + justify-items: center; + + .status-label { + position: relative; + top: 40px; + height: 22px; + width: 79px; + clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); + background: light-dark(@dark-blue, @golden); + + h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + color: light-dark(@beige, @dark-blue); + } + } + .status-value { + position: absolute; + display: flex; + padding: 0 6px; + font-size: 1.5rem; + align-items: center; + width: 100px; + height: 40px; + justify-content: center; + text-align: center; + z-index: 2; + color: @beige; + + input[type='number'] { + background: transparent; + font-size: 1.5rem; + width: 40px; + height: 30px; + text-align: center; + border: none; + outline: 2px solid transparent; + color: @beige; + + &.bar-input { + padding: 0; + color: @beige; + backdrop-filter: none; + background: transparent; + transition: all 0.3s ease; + + &:hover, + &:focus { + background: @semi-transparent-dark-blue; + backdrop-filter: blur(9.5px); + } + } + } + + .bar-label { + width: 40px; + } + } + .progress-bar { + position: absolute; + appearance: none; + width: 100px; + height: 40px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + z-index: 1; + background: @dark-blue; + + &::-webkit-progress-bar { + border: none; + background: @dark-blue; + border-radius: 6px; + } + &::-webkit-progress-value { + background: @gradient-hp; + border-radius: 6px; + } + &.stress-color::-webkit-progress-value { + background: @gradient-stress; + border-radius: 6px; + } + &::-moz-progress-bar { + background: @gradient-hp; + border-radius: 6px; + } + &.stress-color::-moz-progress-bar { + background: @gradient-stress; + border-radius: 6px; + } + } + } + + .level-up-label { + font-size: 24px; + padding-top: 8px; + } + } + + .companion-navigation { + display: flex; + gap: 8px; + align-items: center; + width: 100%; + } + } +} diff --git a/styles/less/actors/companion/sheet.less b/styles/less/actors/companion/sheet.less index 1beb28a7..db221597 100644 --- a/styles/less/actors/companion/sheet.less +++ b/styles/less/actors/companion/sheet.less @@ -1,11 +1,18 @@ .application.sheet.daggerheart.actor.dh-style.companion { - .profile { - height: 80px; - width: 80px; + .theme-light & { + background: url('../assets/parchments/dh-parchment-light.png'); + } + .theme-dark & { + background-image: url('../assets/parchments/dh-parchment-dark.png'); } - .temp-container { - position: relative; - top: 32px; - } + // .profile { + // height: 80px; + // width: 80px; + // } + + // .temp-container { + // position: relative; + // top: 32px; + // } } diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 1f6e5988..fcf9d353 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -23,6 +23,16 @@ box-shadow: none; outline: 2px solid light-dark(@dark, @beige); } + + &:disabled[type='text'], + &:disabled[type='number'] { + outline: 2px solid transparent; + cursor: not-allowed; + + &:hover { + background: transparent; + } + } } input[type='checkbox'] { @@ -52,6 +62,18 @@ &.glow { animation: glow 0.75s infinite alternate; } + + &:disabled { + background: light-dark(transparent, @golden); + color: light-dark(@dark-blue, @dark-blue); + opacity: 0.6; + cursor: not-allowed; + + &:hover { + background: light-dark(transparent, @golden); + color: light-dark(@dark-blue, @dark-blue); + } + } } select { diff --git a/styles/less/global/filter-menu.less b/styles/less/global/filter-menu.less new file mode 100644 index 00000000..09962afb --- /dev/null +++ b/styles/less/global/filter-menu.less @@ -0,0 +1,47 @@ +@import '../utils/colors.less'; +@import '../utils/fonts.less'; + +.filter-menu { + width: auto; + + fieldset.filter-section { + align-items: center; + margin: 5px; + border-radius: 6px; + border-color: light-dark(@dark-blue, @golden); + padding: 5px; + + legend { + font-family: @font-body; + font-weight: bold; + color: light-dark(@dark-blue, @golden); + font-size: var(--font-size-12); + } + + .filter-buttons { + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + gap: 5px; + + button { + background: light-dark(@light-black, @dark-blue); + color: light-dark(@dark-blue, @golden); + outline: none; + box-shadow: none; + border: 1px solid light-dark(@dark-blue, @dark-blue); + padding: 0 0.2rem; + font-size: var(--font-size-12); + + &:hover { + background: light-dark(transparent, @golden); + color: light-dark(@dark-blue, @dark-blue); + } + + &.active { + animation: glow 0.75s infinite alternate; + } + } + } + } +} diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs index 3f4b98be..be8bb251 100644 --- a/templates/sheets/actors/character/inventory.hbs +++ b/templates/sheets/actors/character/inventory.hbs @@ -10,7 +10,9 @@ - + + +
diff --git a/templates/sheets/actors/character/loadout.hbs b/templates/sheets/actors/character/loadout.hbs index de63323c..c9c9d4b2 100644 --- a/templates/sheets/actors/character/loadout.hbs +++ b/templates/sheets/actors/character/loadout.hbs @@ -10,7 +10,9 @@
- + + + + + \ No newline at end of file diff --git a/templates/sheets/applications/companion-settings/attack.hbs b/templates/sheets/applications/companion-settings/attack.hbs new file mode 100644 index 00000000..f0a26769 --- /dev/null +++ b/templates/sheets/applications/companion-settings/attack.hbs @@ -0,0 +1,21 @@ +
+
+ {{localize "DAGGERHEART.General.basics"}} + {{formGroup systemFields.attack.fields.img value=document.system.attack.img label="Image Path" name="system.attack.img"}} + {{formGroup systemFields.attack.fields.name value=document.system.attack.name label="Attack Name" name="system.attack.name"}} +
+
+ {{localize "DAGGERHEART.Sheets.Adversary.Attack"}} + {{formField systemFields.attack.fields.range value=document.system.attack.range label="Range" name="system.attack.range" localize=true}} + {{#if systemFields.attack.fields.target.fields}} + {{ formField systemFields.attack.fields.target.fields.type value=document.system.attack.target.type label="Target" name="system.attack.target.type" localize=true }} + {{#if (and document.system.attack.target.type (not (eq document.system.attack.target.type 'self')))}} + {{ formField systemFields.attack.fields.target.fields.amount value=document.system.attack.target.amount label="Amount" name="system.attack.target.amount" }} + {{/if}} + {{/if}} +
+
\ No newline at end of file diff --git a/templates/sheets/applications/companion-settings/details.hbs b/templates/sheets/applications/companion-settings/details.hbs new file mode 100644 index 00000000..d047752c --- /dev/null +++ b/templates/sheets/applications/companion-settings/details.hbs @@ -0,0 +1,23 @@ +
+
+ {{localize 'DAGGERHEART.General.basics'}} +
+ {{formGroup systemFields.evasion.fields.value value=document.system.evasion.value localize=true}} + {{formGroup systemFields.resources.fields.stress.fields.value value=document.system.resources.stress.value label='Current Stress'}} + {{formGroup systemFields.resources.fields.stress.fields.max value=document.system.resources.stress.max label='Max Stress'}} +
+
+
+ + +
+
+
+ +
\ No newline at end of file diff --git a/templates/sheets/applications/companion-settings/experiences.hbs b/templates/sheets/applications/companion-settings/experiences.hbs new file mode 100644 index 00000000..124a1a86 --- /dev/null +++ b/templates/sheets/applications/companion-settings/experiences.hbs @@ -0,0 +1,18 @@ +
+
+ {{localize tabs.experiences.label}} +
    + {{#each document.system.experiences as |experience key|}} +
  • + + +
  • + {{/each}} +
+
+ +
\ No newline at end of file diff --git a/templates/sheets/applications/companion-settings/header.hbs b/templates/sheets/applications/companion-settings/header.hbs new file mode 100644 index 00000000..0978f2c3 --- /dev/null +++ b/templates/sheets/applications/companion-settings/header.hbs @@ -0,0 +1,3 @@ +
+

{{document.name}}

+
\ No newline at end of file diff --git a/templates/sheets/global/partials/inventory-item.hbs b/templates/sheets/global/partials/inventory-item.hbs index b69f1a2d..9b1c392b 100644 --- a/templates/sheets/global/partials/inventory-item.hbs +++ b/templates/sheets/global/partials/inventory-item.hbs @@ -1,7 +1,11 @@ -
  • +
  • -
    {{item.name}}
    + {{#if isCompanion}} + {{item.name}} + {{else}} +
    {{item.name}}
    + {{/if}} {{#if (eq type 'weapon')}}
    {{#if isSidebar}} @@ -114,6 +118,11 @@ {{#unless hideControls}} {{#if isActor}}
    + {{#if (eq type 'actor')}} + + + + {{/if}} {{#if (eq type 'adversary')}}