Merge branch 'feature/chat-message-styles' of https://github.com/Foundryborne/daggerheart into feature/chat-message-styles

This commit is contained in:
Dapoolp 2025-07-29 22:43:31 +02:00
commit f14cb3936a
19 changed files with 422 additions and 171 deletions

View file

@ -129,6 +129,7 @@ Hooks.once('init', () => {
CONFIG.ChatMessage.dataModels = models.chatMessages.config;
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
CONFIG.Canvas.rulerClass = placeables.DhRuler;
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;

View file

@ -1646,6 +1646,9 @@
},
"UI": {
"Chat": {
"action": {
"title": "Action"
},
"applyEffect": {
"title": "Apply Effects - {name}"
},

View file

@ -54,6 +54,8 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application
{
player: this.actor.name,
actor: { name: this.actor.name, img: this.actor.img },
author: game.users.get(game.user.id),
speaker: cls.getSpeaker(),
title: game.i18n.localize(this.selectedMove.name),
img: this.selectedMove.img,
description: game.i18n.localize(this.selectedMove.description)

View file

@ -139,6 +139,10 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
moves: moves,
actor: this.actor.uuid
},
speaker: cls.getSpeaker(),
title: game.i18n.localize(
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
),
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/downtime.hbs',
{

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent } from "../../systemRegistration/socket.mjs";
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
constructor(options) {
@ -15,6 +15,11 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
this.setupHooks();
}
/** @inheritDoc */
static DEFAULT_OPTIONS = {
classes: ['daggerheart']
};
addChatListeners = async (app, html, data) => {
html.querySelectorAll('.duality-action-damage').forEach(element =>
element.addEventListener('click', event => this.onRollDamage(event, data.message))
@ -100,22 +105,24 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
if (message.system.source.item && message.system.source.action) {
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
if (!action || !action?.hasSave) return;
action.rollSave(token.actor, event, message).then(result => emitAsGM(
GMUpdateEvent.UpdateSaveMessage,
action.updateSaveMessage.bind(action, result, message, token.id),
{
action: action.uuid,
message: message._id,
token: token.id,
result
}
));
action.rollSave(token.actor, event, message).then(result =>
emitAsGM(
GMUpdateEvent.UpdateSaveMessage,
action.updateSaveMessage.bind(action, result, message, token.id),
{
action: action.uuid,
message: message._id,
token: token.id,
result
}
)
);
}
}
async onRollAllSave(event, message) {
event.stopPropagation();
if(!game.user.isGM) return;
if (!game.user.isGM) return;
const targets = event.target.parentElement.querySelectorAll(
'.target-section > [data-token] .target-save-container'
);
@ -124,16 +131,17 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
targets.forEach(async el => {
const tokenId = el.closest('[data-token]')?.dataset.token,
token = game.canvas.tokens.get(tokenId);
if(!token.actor) return;
if(game.user === token.actor.owner)
el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
if (!token.actor) return;
if (game.user === token.actor.owner) el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
else {
token.actor.owner.query('reactionRoll', {
actionId: action.uuid,
actorId: token.actor.uuid,
event,
message
}).then(result => action.updateSaveMessage(result, message, token.id));
token.actor.owner
.query('reactionRoll', {
actionId: action.uuid,
actorId: token.actor.uuid,
event,
message
})
.then(result => action.updateSaveMessage(result, message, token.id));
}
});
}
@ -172,7 +180,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
return {
isHit,
targets: isHit
? message.system.targets.filter(t => t.hit === true).map(target => game.canvas.tokens.documentCollection.find(t => t.actor.uuid === target.actorId))
? message.system.targets
.filter(t => t.hit === true)
.map(target => game.canvas.tokens.documentCollection.find(t => t.actor.uuid === target.actorId))
: Array.from(game.user.targets)
};
}
@ -234,10 +244,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
});
}
if(message.system.hasHealing)
target.actor.takeHealing(damages);
else
target.actor.takeDamage(damages);
if (message.system.hasHealing) target.actor.takeHealing(damages);
else target.actor.takeDamage(damages);
}
}

View file

@ -254,11 +254,15 @@ export function ActionMixin(Base) {
origin: origin,
action: { name: this.name, img: this.img, tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'] },
itemOrigin: this.item,
description: this.description,
description: this.description
};
const msg = {
type: 'abilityUse',
user: game.user.id,
actor: { name: this.actor.name, img: this.actor.img },
author: this.author,
speaker: cls.getSpeaker(),
title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'),
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/action.hbs',

View file

@ -6,8 +6,13 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
_source: this.system._source
});
const actor = game.actors.get(this.speaker.actor);
const actorData = actor ?? {
img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg',
name: ''
};
/* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */
const html = await super.renderHTML();
const html = await super.renderHTML({ actor: actorData, author: this.author });
this.applyPermission(html);
if (this.type === 'dualityRoll') {

View file

@ -128,7 +128,11 @@ export default class DHItem extends foundry.documents.Item {
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
actor: this.actor,
author: this.author,
speaker: cls.getSpeaker(),
system: systemData,
title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'),
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
systemData

View file

@ -2,17 +2,73 @@
@import '../utils/fonts.less';
@import '../utils/mixin.less';
.dh-chat-message {
background-image: url('../assets/parchments/dh-parchment-dark.png');
border: none !important;
padding: 8px 0;
.message-header {
padding: 0 8px 8px;
color: @beige;
.message-sender,
.message-metadata {
font-family: @font-body;
.theme-light {
.daggerheart.chat-sidebar .chat-log,
#chat-notifications .chat-log {
.chat-message {
background-image: url('../assets/parchments/dh-parchment-light.png');
}
}
}
#chat-message {
font-family: @font-body;
padding: 8px;
}
.daggerheart.chat-sidebar,
#chat-notifications {
.chat-log {
.chat-message {
border: none !important;
padding: 8px 0;
background-image: url('../assets/parchments/dh-parchment-dark.png');
.message-header {
display: flex;
gap: 4px;
padding: 0 8px 8px;
.message-header-metadata {
flex: none;
display: flex;
.message-metadata {
font-family: @font-body;
color: light-dark(@dark, @beige);
}
}
.message-header-main {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
.actor-img {
border-radius: 50%;
width: 40px;
height: 40px;
object-fit: cover;
}
.message-sub-header-container {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
color: light-dark(@dark, @beige);
h4 {
font-size: 16px;
font-weight: bold;
margin-bottom: 0;
font-family: @font-subtitle;
color: light-dark(@dark-blue, @golden);
}
}
}
}
}
}
}

View file

@ -15,40 +15,71 @@
object-fit: cover;
}
.domain-card-header {
display: flex;
flex-direction: column;
width: 100%;
margin: 8px 8px 0;
padding-bottom: 5px;
width: -webkit-fill-available;
gap: 5px;
border-bottom: 1px solid @golden;
details[open] {
.fa-chevron-down {
transform: rotate(180deg);
transition: all 0.3s ease;
}
}
.title {
font-size: 20px;
color: @golden;
font-family: @font-subtitle;
margin: 0;
.domain-card-move {
width: 100%;
.fa-chevron-down {
transition: all 0.3s ease;
margin-left: auto;
}
.tags {
.domain-card-header {
display: flex;
gap: 10px;
flex-direction: row;
align-items: center;
margin: 8px;
padding-bottom: 5px;
width: -webkit-fill-available;
gap: 5px;
border-bottom: 1px solid @golden;
.tag {
&:hover {
background: light-dark(@dark-blue-10, @golden-10);
cursor: pointer;
transition: all 0.3s ease;
}
.domain-label {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 3px 5px;
font-size: 12px;
font-family: @font-body;
flex-direction: column;
width: 100%;
padding-bottom: 5px;
width: -webkit-fill-available;
gap: 5px;
background: light-dark(@dark-15, @beige-15);
border: 1px solid light-dark(@dark, @beige);
color: light-dark(@dark, @beige);
border-radius: 3px;
.title {
font-size: 20px;
color: @golden;
font-family: @font-subtitle;
margin: 0;
}
.tags {
display: flex;
gap: 10px;
.tag {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 3px 5px;
font-size: 12px;
font-family: @font-body;
background: light-dark(@dark-15, @beige-15);
border: 1px solid light-dark(@dark, @beige);
color: light-dark(@dark, @beige);
border-radius: 3px;
}
}
}
}
}

View file

@ -8,40 +8,62 @@
flex-direction: column;
align-items: center;
.action-section {
display: flex;
flex-direction: row;
width: 100%;
margin: 8px 8px 0;
padding-bottom: 5px;
width: -webkit-fill-available;
gap: 5px;
border-bottom: 1px solid @golden;
details[open] {
.fa-chevron-down {
transform: rotate(180deg);
transition: all 0.3s ease;
}
}
.action-img {
width: 40px;
height: 40px;
border-radius: 3px;
object-fit: cover;
.action-move {
width: 100%;
.fa-chevron-down {
transition: all 0.3s ease;
margin-left: auto;
}
.action-header {
.action-section {
display: flex;
flex-direction: column;
flex-direction: row;
align-items: center;
margin: 8px 8px 0;
padding-bottom: 5px;
width: -webkit-fill-available;
gap: 5px;
border-bottom: 1px solid @golden;
.title {
font-size: 20px;
color: @golden;
font-family: @font-subtitle;
margin: 0;
&:hover {
background: light-dark(@dark-blue-10, @golden-10);
cursor: pointer;
transition: all 0.3s ease;
}
.label {
font-size: 12px;
color: @beige;
font-family: @font-body;
margin: 0;
.action-img {
width: 40px;
height: 40px;
border-radius: 3px;
object-fit: cover;
}
.action-header {
display: flex;
flex-direction: column;
gap: 5px;
.title {
font-size: 20px;
color: @golden;
font-family: @font-subtitle;
margin: 0;
}
.label {
font-size: 12px;
color: @beige;
font-family: @font-body;
margin: 0;
}
}
}
}

View file

@ -319,6 +319,9 @@
button {
flex: 1;
height: 40px;
font-family: @font-body;
font-weight: 600;
}
}

View file

@ -8,35 +8,10 @@
flex-direction: column;
align-items: center;
.downtime-header {
display: flex;
gap: 13px;
flex-direction: row;
align-items: center;
width: 100%;
padding-left: 8px;
margin-bottom: 12px;
.profile {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.header-label {
.title {
font-size: 20px;
color: @golden;
font-family: @font-subtitle;
margin: 0;
}
.label {
font-size: 12px;
color: @beige;
font-family: @font-body;
margin: 0;
}
details[open] {
.fa-chevron-down {
transform: rotate(180deg);
transition: all 0.3s ease;
}
}
@ -44,6 +19,12 @@
display: flex;
flex-direction: column;
gap: 5px;
width: 100%;
.fa-chevron-down {
transition: all 0.3s ease;
margin-left: auto;
}
.downtime-move {
width: 100%;
@ -57,6 +38,12 @@
padding-bottom: 5px;
width: -webkit-fill-available;
&:hover {
background: light-dark(@dark-blue-10, @golden-10);
cursor: pointer;
transition: all 0.3s ease;
}
.downtime-image {
width: 40px;
height: 40px;

View file

@ -1,3 +1,12 @@
@import '../../utils/colors.less';
@import '../../utils/fonts.less';
.chat-message.dh-chat-message {
.message-content {
padding: 0;
}
}
.chat-message {
.duality-modifiers,
.duality-result,
@ -6,6 +15,66 @@
}
.message-content {
padding: 0 8px;
font-family: @font-body;
color: light-dark(@dark, @beige);
blockquote {
border-left: 5px solid light-dark(@dark-blue-40, @golden-40);
}
a[href] {
color: light-dark(@dark-blue, @golden);
}
a[href]:hover,
a[href].active {
font-weight: bold;
text-shadow: 0 0 8px light-dark(@dark-blue, @golden);
}
button {
background: light-dark(transparent, @golden);
border: 1px solid light-dark(@dark-blue, @dark-blue);
color: light-dark(@dark-blue, @dark-blue);
outline: none;
box-shadow: none;
&:hover {
background: light-dark(@light-black, @dark-blue);
color: light-dark(@dark-blue, @golden);
}
&.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);
}
}
&.reverted {
background: light-dark(@dark-blue-10, @golden-10);
color: light-dark(@dark-blue, @golden);
border: 1px solid light-dark(@dark, transparent);
&:hover {
background: light-dark(transparent, @golden);
color: light-dark(@dark-blue, @dark-blue);
}
img {
border-radius: 3px;
}
}
}
.enriched-effect {
display: flex;
align-items: center;
@ -24,6 +93,24 @@
white-space: nowrap;
}
}
.dice-roll .dice-formula,
.dice-roll .dice-total {
box-shadow: none;
border: none;
background: light-dark(@dark-blue-40, @golden-40);
color: light-dark(@dark-blue, @golden);
font-weight: 600;
align-content: center;
}
.dice-roll .dice-formula {
height: 27px;
}
.dice-roll .dice-total {
height: 34px;
}
}
}

View file

@ -1,14 +1,19 @@
<div class="daggerheart chat domain-card">
<img class="card-img" src="{{item.img}}" />
<div class="domain-card-header">
<h2 class="title">{{item.name}}</h2>
<ul class="tags">
{{#each item.tags as |tag|}}
<li class="tag">{{tag}}</li>
{{/each}}
</ul>
</div>
<div class="description">{{{description}}}</div>
<details class="domain-card-move">
<summary class="domain-card-header">
<div class="domain-label">
<h2 class="title">{{item.name}}</h2>
<ul class="tags">
{{#each item.tags as |tag|}}
<li class="tag">{{tag}}</li>
{{/each}}
</ul>
</div>
<i class="fa-solid fa-chevron-down"></i>
</summary>
<div class="description">{{{description}}}</div>
</details>
<footer class="ability-card-footer">
{{#each actions as |action index|}}
<button class="ability-use-button" data-index="{{index}}">

View file

@ -1,12 +1,13 @@
<div class="daggerheart chat action">
<div class="action-section">
<img class="action-img" src="{{action.img}}" />
<div class="action-header">
<h2 class="title">{{action.name}}</h2>
<span class="label">{{itemOrigin.name}}</span>
</div>
</div>
{{#if description}}
<details class="action-move">
<summary class="action-section">
<img class="action-img" src="{{action.img}}" />
<div class="action-header">
<h2 class="title">{{action.name}}</h2>
<span class="label">{{itemOrigin.name}}</span>
</div>
<i class="fa-solid fa-chevron-down"></i>
</summary>
<div class="description">{{{description}}}</div>
{{/if}}
</details>
</div>

View file

@ -0,0 +1,40 @@
<li class="chat-message message flexcol {{cssClass}}" data-message-id="{{message._id}}"
{{#if borderColor}}style="border-color:{{borderColor}}"{{/if}}>
<header class="message-header flexrow">
<div class="message-header-main">
<img class="actor-img" src="{{actor.img}}" />
<div class="message-sub-header-container">
<h4>{{ifThen message.title message.title alias}}</h4>
{{#if actor.name}}
<div>{{actor.name}} {{#if author.isGM}}(GM){{/if}}</div>
{{/if}}
</div>
</div>
<div class="message-header-metadata">
<span class="message-metadata">
<time class="message-timestamp">{{timeSince message.timestamp}}</time>
{{#if canDelete}}
<a aria-label="{{localize 'Delete'}}" class="message-delete" data-action="deleteMessage">
<i class="fa-solid fa-trash" inert></i>
</a>
{{/if}}
{{#if canClose}}
<a aria-label="{{ localize "CHAT.Dismiss" }}" class="message-dismiss" data-action="dismissMessage">
<i class="fa-solid fa-xmark" inert></i>
</a>
{{/if}}
</span>
{{#if isWhisper}}
<span class="whisper-to">{{localize 'CHAT.To'}}: {{whisperTo}}</span>
{{/if}}
{{#if message.flavor}}
<span class="flavor-text">{{{message.flavor}}}</span>
{{/if}}
</div>
</header>
<div class="message-content">
{{{message.content}}}
</div>
</li>

View file

@ -1,23 +1,17 @@
<div class="daggerheart chat downtime">
<div class="downtime-header">
<img class="profile" src="{{actor.img}}">
<div class="header-label">
<h2 class="title">{{localize 'DAGGERHEART.UI.Chat.deathMove.title'}}</h2>
<span class="label">{{actor.name}}</span>
</div>
</div>
<ul class="downtime-moves-list">
<li class="downtime-move">
<div class="downtime-label">
<details class="downtime-move">
<summary class="downtime-label">
<img class="downtime-image" src="{{this.img}}" />
<div class="header-label">
<h2 class="title">{{this.title}}</h2>
<span class="label">{{localize 'DAGGERHEART.UI.Chat.deathMove.title'}}</span>
</div>
</div>
<i class="fa-solid fa-chevron-down"></i>
</summary>
<div class="description">
{{{this.description}}}
</div>
</li>
</details>
</ul>
</div>

View file

@ -1,28 +1,22 @@
<div class="daggerheart chat downtime">
<div class="downtime-header">
<img class="profile" src="{{actor.img}}">
<div class="header-label">
<h2 class="title">{{title}}</h2>
<span class="label">{{actor.name}}</span>
</div>
</div>
<ul class="downtime-moves-list">
{{#each moves as | move index |}}
<li class="downtime-move">
<div class="downtime-label">
<details class="downtime-move">
<summary class="downtime-label">
<img class="downtime-image" src="{{move.img}}" />
<div class="header-label">
<h2 class="title">{{move.name}}</h2>
<span class="label">{{localize 'DAGGERHEART.GENERAL.Bonuses.rest.downtimeAction'}}</span>
</div>
</div>
<i class="fa-solid fa-chevron-down"></i>
</summary>
<div class="description">
{{{move.description}}}
</div>
{{#each move.actions as | action index |}}
<button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}">{{localize action.name}}</button>
{{/each}}
</li>
</details>
{{#each move.actions as | action index |}}
<button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}">{{localize action.name}}</button>
{{/each}}
{{/each}}
</ul>
</div>