Skip to content

Commit

Permalink
2.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Dorako committed Aug 21, 2023
1 parent 91bc8f5 commit 40b31b2
Show file tree
Hide file tree
Showing 20 changed files with 555 additions and 98 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
# 2.5.0

- (New) Added a new experimental UX setting "Enable chat merge?" based on DFCE's chat merge functionality.
- (New) Added a new experimental UX setting "Adjust chat controls?" based on DFCE's privacy buttons functionality.
- (New) Added ability to set keybinds for specific rolltypes, or toggling between public and secret, when new chat controls feature is enabled.
- (Maintence) Fixed an issue that caused certain buttons in the Effects Panel overlay to break.
- (Maintenance) Potentially fixed an issue where inline links in the new AP were unreadable.

# 2.4.0

- (New) Added a new "PC sheet theme" setting, which allows picking between three color themes.
- (Maintenance) Adjusted some styling to account for system changes
- (Maintenance) Adjusted some styling to account for system changes.

# 2.3.9

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,6 @@ This code is available under the MIT license, see LICENSE.

The sidebar resizing functionality has been adapted from [Sidebar Resizer](https://github.com/saif-ellafi/foundryvtt-sidebar-resizer) also available under the MIT license.

The chat merge and rolltype buttons functionality has been adapted from [DFCE](https://github.com/flamewave000/dragonflagon-fvtt/tree/master/df-chat-enhance) available under the BSD 3-Clause license.

The green PC sheet theme is contributed by Vesselchuck.
8 changes: 8 additions & 0 deletions languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@
"name": "Restructure chat card info?",
"hint": "Makes action-cost for features more apparent, and moves spell info out of the footer"
},
"chat-merge": {
"name": "Enable chat merge?",
"hint": "(Experimental) Merges messages sent in a rapid sequence together to save space"
},
"adjust-chat-controls": {
"name": "Adjust chat controls?",
"hint": "(Experimental) Restyles rolltype mode to buttons, and convert other chat controls to buttons"
},
"adjust-token-effects-hud": {
"name": "Adjust token effects HUD?",
"hint": "Makes effects circular and arranges them outside the token, or on the token ring for larger tokens"
Expand Down
29 changes: 29 additions & 0 deletions licenses/dfce
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BSD 3-Clause License

Copyright (c) 2021, flamewave000
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21 changes: 21 additions & 0 deletions licenses/dorako-ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2022 Dorako

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
159 changes: 159 additions & 0 deletions modules/chat-merge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// import libWrapperShared from "./libWrapperShared.js";

export default class ChatMerge {
static get _enabled() {
return true;
}
static get _epoch() {
return 10;
}
static get _allowRolls() {
return "rolls";
}
static get _separateWithBorder() {
return false;
}
static get _showHover() {
return false;
}
static get _showHeader() {
return false;
}

static init() {
// libWrapperShared.register("ChatLog.prototype.deleteMessage", this._deleteMessage.bind(this));
Hooks.on("renderChatMessage", this._renderChatMessage);
}

static ready() {
const style = document.querySelector(":root").style;
style.setProperty("--dfce-cm-separation", this._separateWithBorder ? "" : "0");
this._showHover
? style.removeProperty("--dfce-cm-hover-shadow")
: style.setProperty("--dfce-cm-hover-shadow", "0px");
style.setProperty("--dfce-cm-header", this._showHeader ? "" : "none");
if (game.user.isGM) {
style.setProperty("--dfce-cm-header-delete", this._showHeader ? "" : "0");
style.setProperty("--dfce-cm-header-delete-pad", this._showHeader ? "" : "16px");
}
this._processAllMessage(ui.chat.element);
Hooks.on("renderChatLog", (_, html) => this._processAllMessage(html));
}

static _deleteMessage(wrapper, messageId, { deleteAll = false } = {}) {
// Ignore the Delete All process. Everything is being obliterated, who cares about the styling
if (!deleteAll && this._enabled) {
const element = document.querySelector(`li[data-message-id="${messageId}"`);
// If we were a TOP
if (element?.classList?.contains("dfce-cm-top")) {
element.classList.remove("dfce-cm-top");
// If the next element was a middle, make it a top
if (element.nextElementSibling.classList.contains("dfce-cm-middle")) {
element.nextElementSibling.classList.remove("dfce-cm-middle");
element.nextElementSibling.classList.add("dfce-cm-top");
}
// Otherwise, it was a bottom and should now become a normal message again
else element.nextElementSibling.classList.remove("dfce-cm-bottom");
}
// If we were a BOTTOM
else if (element?.classList?.contains("dfce-cm-bottom")) {
element.classList.remove("dfce-cm-bottom");
// If the previous element was a middle, make it a bottom
if (element.previousElementSibling.classList.contains("dfce-cm-middle")) {
element.previousElementSibling.classList.remove("dfce-cm-middle");
element.previousElementSibling.classList.add("dfce-cm-bottom");
}
// Otherwise, it was a top and should now become a normal message again
else element.previousElementSibling.classList.remove("dfce-cm-top");
}
// If we were a MIDDLE, let the above and below snug and they'll be fine
else if (element?.classList?.contains("dfce-cm-middle")) element.classList.remove("dfce-cm-middle");
}
return wrapper(messageId, { deleteAll });
}

static _processAllMessage(element) {
element = element ?? $(document.body);
// Remove the old CSS class designations
element.find(".dfce-cm-top").removeClass("dfce-cm-top");
element.find(".dfce-cm-middle").removeClass("dfce-cm-middle");
element.find(".dfce-cm-bottom").removeClass("dfce-cm-bottom");
// If we are disabled, return
if (!ChatMerge._enabled) return;
// Collect all rendered chat messages
const messages = element.find("li.chat-message");
// Return if there are no messages rendered
if (messages.length === 0) return;
// Make sure to set the hover colour for the first message since we skip it in the processor bellow.
if (messages[0].hasAttribute("style")) {
messages[0].style.setProperty("--dfce-mc-border-color", messages[0].style.borderColor);
}
// Process each message after the first
for (let c = 1; c < messages.length; c++) {
// Update styling of the chat messages
this._styleChatMessages(
game.messages.get(messages[c].getAttribute("data-message-id")),
messages[c],
game.messages.get(messages[c - 1].getAttribute("data-message-id")),
messages[c - 1]
);
}
}

static _renderChatMessage(message, html, _cmd) {
if (!ChatMerge._enabled) return;
// Find the most recent message in the chat log
const partnerElem = $(`li.chat-message`).last()[0];
// If there is no message, return
if (partnerElem === null || partnerElem === undefined) return;
// get the ChatMessage document associated with the html
const partner = game.messages.get(partnerElem.getAttribute("data-message-id"));
if (!message || !partner) return;
// Update styling of the chat messages
ChatMerge._styleChatMessages(message, html[0], partner, partnerElem);
}

static _inTimeFrame(current, previous) {
return current > previous && current - previous < this._epoch * 1000;
}

static _isValidMessage(current, previous) {
const rolls = this._allowRolls;
// const splitSpeaker = SETTINGS.get < boolean > this.PREF_SPLIT_SPEAKER;
const splitSpeaker = true;
let userCompare = false;
const currData = current ?? current;
const prevData = previous ?? previous;
if (splitSpeaker) {
// this is a bit complex, basically we want to group by actors, but if you're not using an actor, group by user instead
userCompare =
// If actors are equal and NOT null
(currData.speaker.actor === prevData.speaker.actor && !!currData.speaker.actor) || // If BOTH actors are null and users are equal
(!currData.speaker.actor && !prevData.speaker.actor && currData.user === prevData.user);
} else {
// If we are not splitting by speaker, just do the simple option of comparing the users
userCompare = currData.user === prevData.user;
}
return (
userCompare &&
this._inTimeFrame(currData.timestamp, prevData.timestamp) &&
// Check for merging with roll types
(rolls === "all" ||
(rolls === "rolls" && current.isRoll === previous.isRoll) ||
(rolls === "none" && !current.isRoll && !previous.isRoll))
);
}

static _styleChatMessages(curr, currElem, prev, prevElem) {
if (currElem.hasAttribute("style")) {
currElem.style.setProperty("--dfce-mc-border-color", currElem.style.borderColor);
}

if (!ChatMerge._isValidMessage(curr, prev)) return;
if (prevElem.classList.contains("dfce-cm-bottom")) {
prevElem.classList.remove("dfce-cm-bottom");
prevElem.classList.add("dfce-cm-middle");
} else prevElem.classList.add("dfce-cm-top");
currElem.classList.add("dfce-cm-bottom");
}
}
118 changes: 118 additions & 0 deletions modules/chat-rolltype-buttons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { MODULE_NAME } from "./consts.js";

const ICONS_FOR_KNOWN_ROLL_TYPES = {
publicroll: "fas fa-dice-d20",
gmroll: "fas fa-book-open-reader",
blindroll: "fas fa-eye-slash",
selfroll: "fas fa-user",
};

export default class ChatRollPrivacy {
static setup() {
game.keybindings.register(MODULE_NAME, "roll-mode.publicroll", {
name: "Public Roll",
editable: [{ key: "KeyQ", modifiers: [KeyboardManager.MODIFIER_KEYS.ALT] }],
namespace: "Roll Type Shortcuts",
onDown: () => {
$('#dorako-rt-buttons button[data-id="publicroll"]').trigger("click");
},
});
game.keybindings.register(MODULE_NAME, "roll-mode.gmroll", {
name: "Private GM Roll",
editable: [{ key: "KeyW", modifiers: [KeyboardManager.MODIFIER_KEYS.ALT] }],
namespace: "Roll Type Shortcuts",
onDown: () => {
game.settings.set("core", "rollMode", "gmroll");
$('#dorako-rt-buttons button[data-id="gmroll"]').trigger("click");
},
});
game.keybindings.register(MODULE_NAME, "roll-mode.blindroll", {
name: "Blind GM Roll",
editable: [{ key: "KeyE", modifiers: [KeyboardManager.MODIFIER_KEYS.ALT] }],
namespace: "Roll Type Shortcuts",
onDown: () => {
game.settings.set("core", "rollMode", "blindroll");
$('#dorako-rt-buttons button[data-id="blindroll"]').trigger("click");
},
});
game.keybindings.register(MODULE_NAME, "roll-mode.selfroll", {
name: "Self Roll",
editable: [{ key: "KeyR", modifiers: [KeyboardManager.MODIFIER_KEYS.ALT] }],
namespace: "Roll Type Shortcuts",
onDown: () => {
game.settings.set("core", "rollMode", "selfroll");
$('#dorako-rt-buttons button[data-id="selfroll"]').trigger("click");
},
});
game.keybindings.register(MODULE_NAME, "roll-mode.toggle-secret-public", {
name: "Toggle secret/public",
editable: [{ key: "KeyS", modifiers: [KeyboardManager.MODIFIER_KEYS.ALT] }],
namespace: "Roll Type Shortcuts",
onDown: () => {
if (game.settings.get("core", "rollMode") !== "blindroll") {
game.settings.set("core", "rollMode", "blindroll");
$('#dorako-rt-buttons button[data-id="blindroll"]').trigger("click");
} else {
game.settings.set("core", "rollMode", "publicroll");
$('#dorako-rt-buttons button[data-id="publicroll"]').trigger("click");
}
},
});
}

static init() {
Hooks.on("renderChatLog", this._handleChatLogRendering);
}

static async _handleChatLogRendering(chat, html, data) {
// setTimeout(() => {}, 2);
const modes = Object.keys(data.rollModes);
const buttons = [];
for (let c = 0; c < modes.length; c++) {
const rt = modes[c];
if (!(rt in ICONS_FOR_KNOWN_ROLL_TYPES)) {
console.warn(Error(`Unknown roll type '${rt}'`));
continue;
}
buttons.push({
rt: rt,
name: data.rollModes[rt],
active: data.rollMode === rt,
icon: ICONS_FOR_KNOWN_ROLL_TYPES[rt],
});
}
const buttonHtml = $(await renderTemplate("modules/pf2e-dorako-ui/templates/rt-buttons.hbs", { buttons }));
buttonHtml.find("button").on("click", function () {
const rollType = $(this).attr("data-id");
game.settings.set("core", "rollMode", rollType);
buttonHtml.find("button.active").removeClass("active");
$(this).addClass("active");
});
html.find("select[name=rollMode]").after(buttonHtml);
html.find("select[name=rollMode]").remove();

const nonrolltype = $(`<div id="dorako-nonrt-buttons" class="buttons control-buttons"></div>`);

html.find("#chat-controls div.control-buttons a").each(function () {
const html = $(this).html();
const classes = $(this).attr("class") ?? "";
const title = $(this).attr("title") ?? "";
const style = $(this).attr("style") ?? "";
const button = $(`<button class="${classes}" title="${title}" style="${style}">${html}</button>`);
const events = $._data(this, "events"); // Chat Reactions "a" doesn't have event handler configured yet at the time this runs
if (events) {
const click = events["click"][0].handler;
button.on("click", click);
}
nonrolltype.append(button);
});
const fateButton = $(document).find("#chat-controls .chat-control-icon .FATE-button");
if (fateButton) {
const title = fateButton.attr("title") ?? "";
const button = $(`<button class="fate" title="${title}"><i class="fas fa-yin-yang"></i></button>`);
nonrolltype.append(button);
}
html.find("#chat-controls div.control-buttons").remove();
html.find("#chat-controls").append(nonrolltype);
}
}
Loading

0 comments on commit 40b31b2

Please sign in to comment.