From 9e9c1d2ac08e25f06fb908dcea837ff3fa25a7aa Mon Sep 17 00:00:00 2001 From: moliloo Date: Fri, 19 Sep 2025 23:10:05 -0300 Subject: [PATCH] add group roll dialog --- module/applications/dialogs/_module.mjs | 1 + .../dialogs/group-roll-dialog.mjs | 165 ++++++++++++++++++ module/applications/sheets/actors/party.mjs | 6 + styles/less/dialog/group-roll/group-roll.less | 47 +++++ styles/less/dialog/index.less | 2 + styles/less/global/dialog.less | 4 + styles/less/ux/autocomplete/autocomplete.less | 22 ++- templates/dialogs/group-roll/group-roll.hbs | 78 +++++++++ .../sheets/actors/party/party-members.hbs | 2 +- 9 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 module/applications/dialogs/group-roll-dialog.mjs create mode 100644 styles/less/dialog/group-roll/group-roll.less create mode 100644 templates/dialogs/group-roll/group-roll.hbs diff --git a/module/applications/dialogs/_module.mjs b/module/applications/dialogs/_module.mjs index 84ba4037..838be84d 100644 --- a/module/applications/dialogs/_module.mjs +++ b/module/applications/dialogs/_module.mjs @@ -10,3 +10,4 @@ export { default as OwnershipSelection } from './ownershipSelection.mjs'; export { default as RerollDamageDialog } from './rerollDamageDialog.mjs'; export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs'; export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs'; +export { default as GroupRollDialog } from './group-roll-dialog.mjs'; \ No newline at end of file diff --git a/module/applications/dialogs/group-roll-dialog.mjs b/module/applications/dialogs/group-roll-dialog.mjs new file mode 100644 index 00000000..33508ef6 --- /dev/null +++ b/module/applications/dialogs/group-roll-dialog.mjs @@ -0,0 +1,165 @@ +import autocomplete from 'autocompleter'; +import { abilities } from '../../config/actorConfig.mjs'; + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) { + constructor(actors) { + super(); + this.actors = actors; + this.actorLeader = {}; + this.actorsMembers = []; + } + + get title() { + return 'Group Roll'; + } + + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll'], + position: { width: 'auto', height: 'auto' }, + actions: { + roll: GroupRollDialog.#roll, + removeLeader: GroupRollDialog.#removeLeader, + removeMember: GroupRollDialog.#removeMember + }, + form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false } + }; + + static PARTS = { + application: { + id: 'group-roll', + template: 'systems/daggerheart/templates/dialogs/group-roll/group-roll.hbs' + } + }; + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + const changeChoices = this.actors; + + htmlElement.querySelectorAll('.leader-change-input').forEach(element => { + autocomplete({ + input: element, + fetch: function (text, update) { + if (!text) { + update(changeChoices); + } else { + text = text.toLowerCase(); + var suggestions = changeChoices.filter(n => n.name.toLowerCase().includes(text)); + update(suggestions); + } + }, + render: function (actor, search) { + const actorName = game.i18n.localize(actor.name); + const matchIndex = actorName.toLowerCase().indexOf(search); + + const beforeText = actorName.slice(0, matchIndex); + const matchText = actorName.slice(matchIndex, matchIndex + search.length); + const after = actorName.slice(matchIndex + search.length, actorName.length); + const img = document.createElement('img'); + img.src = actor.img; + + const element = document.createElement('li'); + element.appendChild(img); + + const label = document.createElement('span'); + label.innerHTML = `${beforeText}${matchText ? `${matchText}` : ''}${after}`; + element.appendChild(label); + + return element; + }, + renderGroup: function (label) { + const itemElement = document.createElement('div'); + itemElement.textContent = game.i18n.localize(label); + return itemElement; + }, + onSelect: actor => { + element.value = actor.uuid; + this.actorLeader = { actor: actor, trait: '', difficulty: 0 }; + this.render(); + }, + click: e => e.fetch(), + customize: function (_input, _inputRect, container) { + container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ; + }, + minLength: 0 + }); + }); + + htmlElement.querySelectorAll('.team-push-input').forEach(element => { + autocomplete({ + input: element, + fetch: function (text, update) { + if (!text) { + update(changeChoices); + } else { + text = text.toLowerCase(); + var suggestions = changeChoices.filter(n => n.name.toLowerCase().includes(text)); + update(suggestions); + } + }, + render: function (actor, search) { + const actorName = game.i18n.localize(actor.name); + const matchIndex = actorName.toLowerCase().indexOf(search); + + const beforeText = actorName.slice(0, matchIndex); + const matchText = actorName.slice(matchIndex, matchIndex + search.length); + const after = actorName.slice(matchIndex + search.length, actorName.length); + const img = document.createElement('img'); + img.src = actor.img; + + const element = document.createElement('li'); + element.appendChild(img); + + const label = document.createElement('span'); + label.innerHTML = `${beforeText}${matchText ? `${matchText}` : ''}${after}`; + element.appendChild(label); + + return element; + }, + renderGroup: function (label) { + const itemElement = document.createElement('div'); + itemElement.textContent = game.i18n.localize(label); + return itemElement; + }, + onSelect: actor => { + element.value = actor.uuid; + this.actorsMembers.push({ actor: actor, trait: '', difficulty: 0 }); + this.render({ force: true }); + }, + click: e => e.fetch(), + customize: function (_input, _inputRect, container) { + container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ; + }, + minLength: 0 + }); + }); + } + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.leader = this.actorLeader; + context.members = [...this.actorsMembers]; + context.traitList = Object.values(abilities).map(trait => ({ + label: game.i18n.localize(trait.label), + value: trait.id + })); + return context; + } + + static async #removeLeader(_, button) { + this.actorLeader = null; + this.render(); + } + + static async #removeMember(_, button) { + this.actorsMembers = this.actorsMembers.filter(m => m.actor.uuid !== button.dataset.memberUuid); + this.render(); + } + + static async #roll(_, button) { + console.log(this.leader, this.members); + console.log(this); + } +} diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index ee47687d..cd2f7e22 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -4,6 +4,7 @@ import { ItemBrowser } from '../../ui/itemBrowser.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs'; import { socketEvent } from '../../../systemRegistration/socket.mjs'; +import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs'; export default class Party extends DHBaseActorSheet { constructor(options) { @@ -30,6 +31,7 @@ export default class Party extends DHBaseActorSheet { tempBrowser: Party.#tempBrowser, refeshActions: Party.#refeshActions, triggerRest: Party.#triggerRest, + groupRoll: Party.#groupRoll, selectRefreshable: DaggerheartMenu.selectRefreshable, refreshActors: DaggerheartMenu.refreshActors }, @@ -268,6 +270,10 @@ export default class Party extends DHBaseActorSheet { }); } + static async #groupRoll(params) { + new GroupRollDialog(this.document.system.partyMembers).render({ force: true }); + } + /** * Get the set of ContextMenu options for Consumable and Loot. * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance diff --git a/styles/less/dialog/group-roll/group-roll.less b/styles/less/dialog/group-roll/group-roll.less new file mode 100644 index 00000000..f7a5675f --- /dev/null +++ b/styles/less/dialog/group-roll/group-roll.less @@ -0,0 +1,47 @@ +@import '../../utils/colors.less'; + +.application.daggerheart.group-roll { + fieldset.one-column { + min-width: 500px; + margin-bottom: 10px; + } + .actor-item { + display: flex; + align-items: center; + gap: 15px; + width: 100%; + + img { + height: 40px; + width: 40px; + border-radius: 50%; + object-fit: cover; + } + + .actor-info { + display: flex; + flex-direction: column; + gap: 10px; + + .actor-check-info { + display: flex; + gap: 10px; + + .form-fields { + display: flex; + gap: 5px; + align-items: center; + + input { + max-width: 40px; + text-align: center; + } + } + } + } + + .controls { + margin-left: auto; + } + } +} diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index 65af4a71..c0ba1d11 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -30,3 +30,5 @@ @import './multiclass-choice/sheet.less'; @import './reroll-dialog/sheet.less'; + +@import './group-roll/group-roll.less'; diff --git a/styles/less/global/dialog.less b/styles/less/global/dialog.less index f164b701..8c532c2b 100644 --- a/styles/less/global/dialog.less +++ b/styles/less/global/dialog.less @@ -67,6 +67,10 @@ } } + .standard-form { + font-family: @font-body; + } + &.two-big-buttons { .window-content { padding-top: 0; diff --git a/styles/less/ux/autocomplete/autocomplete.less b/styles/less/ux/autocomplete/autocomplete.less index 808a8972..08854a53 100644 --- a/styles/less/ux/autocomplete/autocomplete.less +++ b/styles/less/ux/autocomplete/autocomplete.less @@ -1,3 +1,6 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + .theme-light .autocomplete { background-image: url('../assets/parchments/dh-parchment-light.png'); color: black; @@ -27,11 +30,15 @@ } li[role='option'] { + display: flex; + align-items: center; + gap: 10px; font-size: var(--font-size-14); - padding-left: 10px; + padding: 0 10px; cursor: pointer; - &:hover { + &:hover, + &.selected { background-color: light-dark(@dark, @beige); color: light-dark(@beige, var(--color-dark-3)); } @@ -39,5 +46,16 @@ > div { white-space: nowrap; } + + img { + height: 40px; + width: 40px; + border-radius: 50%; + margin-bottom: 10px; + + &:first-child { + margin-top: 10px; + } + } } } diff --git a/templates/dialogs/group-roll/group-roll.hbs b/templates/dialogs/group-roll/group-roll.hbs new file mode 100644 index 00000000..fcf31ac2 --- /dev/null +++ b/templates/dialogs/group-roll/group-roll.hbs @@ -0,0 +1,78 @@ +
+
+

Group Roll

+
+ +
+ Leader + {{#unless leader.actor}} + +
+ Select a Leader +
+ {{else}} +
+ {{leader.actor.name}} +
+ {{leader.actor.name}} +
+
+ + +
+
+ + +
+
+
+
+ + + +
+
+ {{/unless}} +
+ +
+ Party Team + + {{#if (gt this.members.length 0)}} + {{#each members as |member|}} +
+ {{member.actor.name}} +
+ {{member.actor.name}} +
+
+ + +
+
+ + +
+
+
+
+ + + +
+
+ {{/each}} + {{/if}} +
+ Select a Member +
+
+ +
\ No newline at end of file diff --git a/templates/sheets/actors/party/party-members.hbs b/templates/sheets/actors/party/party-members.hbs index 74f46019..751ab82e 100644 --- a/templates/sheets/actors/party/party-members.hbs +++ b/templates/sheets/actors/party/party-members.hbs @@ -9,7 +9,7 @@ Tag Team Roll -