diff --git a/CHANGELOG.md b/CHANGELOG.md index c0e83cd..a08b298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 4.0.0 + +- Added module settings. You can now change the way some features look as well as toggling some on/off. More customization options will be added in the future. +- Added support for FoundryVTT V11. +- Fixed small bugs and added missing localization for some text. + ## 3.5.0 - Added the _Speak As_ functionality. When a conversation is active, the GM can now toggle if they want chat messages to be sent under the name of the currently active conversation participant. diff --git a/README.md b/README.md index 2b0a591..51eaf6f 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,6 @@ These conversations can then be saved, edited, and even embedded into other jour https://user-images.githubusercontent.com/51834117/223569960-3a119bb8-1a5f-4ad8-8f5d-d6a0b2d07e4f.mp4 -# Disclaimer - -Initially, this module was made as a very simple module to solve some of the issues I had as a GM during my sessions. As I added more features and polished it, I decided to also release it. - -As of this moment, I feel that the module is feature complete and has all the functionality I envisioned for it. I will continue to support the module for future versions of Foundry as well as fix any possible issues that may occur. That said, I don't think I will be adding additional features unless I feel that they would greatly benefit the module. - # Known Issues If you encounter any issues, feel free to report them and I will look into them. diff --git a/css/active-participant.css b/css/active-participant.css new file mode 100644 index 0000000..8979d70 --- /dev/null +++ b/css/active-participant.css @@ -0,0 +1,103 @@ +.conversation-hud-active-participant { + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + position: relative; +} + +/* Portrait types */ +.conversation-hud-active-participant.vertical { + height: 100%; + width: 30vw; + max-width: 420px; +} +.conversation-hud-active-participant.horizontal { + width: 40vw; + max-width: 620px; + aspect-ratio: 16/9; +} +.conversation-hud-active-participant.square { + width: min(30vw, 50vh); + height: min(30vw, 50vh); + max-width: 620px; + max-height: 620px; +} + +.conversation-hud-active-participant img { + height: 100%; + width: 100%; + object-fit: cover; + object-position: top center; + border-radius: 15px; + box-shadow: 0 0 10px var(--color-shadow-dark); +} + +/* CSS rules for image anchor */ +.conversation-participant img.top, +.conversation-hud-active-participant img.top { + object-position: top center; +} +.conversation-participant img.center, +.conversation-hud-active-participant img.center { + object-position: center; +} +.conversation-participant img.bottom, +.conversation-hud-active-participant img.bottom { + object-position: bottom center; +} + +.active-participant-image { + display: none; + background-color: rgba(0, 0, 0, 0.5); +} +.active-participant-image.active { + display: block; +} + +.active-participant-name { + display: none; + position: absolute; + + bottom: 0; + left: 0; + right: 0; + + margin: 0; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 5px; + padding-right: 5px; + + overflow: hidden; + white-space: nowrap; + + font-size: 16px; + text-align: center; + color: var(--color-text-light-highlight); + text-overflow: ellipsis; + + background-color: rgba(0, 0, 0, 0.65); + border-top: 1px solid var(--color-border-dark); + border-radius: 0px 0px 15px 15px; +} +.active-participant-name.active { + display: block; +} + +/* Font sizes */ +.active-participant-name.verySmall { + font-size: 10px; +} +.active-participant-name.small { + font-size: 13px; +} +.active-participant-name.regular { + font-size: 16px; +} +.active-participant-name.large { + font-size: 19px; +} +.active-participant-name.veryLarge { + font-size: 22px; +} diff --git a/css/hud.css b/css/hud.css index 587366e..3838b0d 100644 --- a/css/hud.css +++ b/css/hud.css @@ -46,16 +46,6 @@ max-height: calc(100vh - 54px - 62px); } -.conversation-hud-active-participant { - display: none; - flex-direction: column; - align-items: center; - justify-content: center; - position: relative; - height: 100%; - width: 30vw; - max-width: 420px; -} .conversation-hud-wrapper.visible .conversation-hud-active-participant { display: flex; } @@ -74,56 +64,10 @@ border: 0; } -.conversation-hud-active-participant img { - height: 100%; - width: 100%; - object-fit: cover; - object-position: top center; - border-radius: 15px; - box-shadow: 0 0 10px var(--color-shadow-dark); -} .conversation-hud-wrapper.minimized .conversation-hud-active-participant img { box-shadow: unset; } -.active-participant-image { - display: none; - background-color: rgba(0, 0, 0, 0.5); -} -.active-participant-image.active { - display: block; -} - -.active-participant-name { - display: none; - position: absolute; - - bottom: 0; - left: 0; - right: 0; - - margin: 0; - padding-top: 5px; - padding-bottom: 5px; - padding-left: 5px; - padding-right: 5px; - - overflow: hidden; - white-space: nowrap; - - font-size: 16px; - text-align: center; - color: var(--color-text-light-highlight); - text-overflow: ellipsis; - - background-color: rgba(0, 0, 0, 0.65); - border-top: 1px solid var(--color-border-dark); - border-radius: 0px 0px 15px 15px; -} -.active-participant-name.active { - display: block; -} - .conversation-hud-participant-list { display: none; flex-flow: column; @@ -345,6 +289,7 @@ box-shadow: 0 0 10px #9b8dff; } +/* Dropzone CSS */ .conversation-hud-content .conversation-hud-dropzone { display: none; flex-flow: column; diff --git a/css/main.css b/css/main.css index 5e5b039..072c7e4 100644 --- a/css/main.css +++ b/css/main.css @@ -1,2 +1,3 @@ @import url("./hud.css"); -@import url("./conversation-sheet.css"); \ No newline at end of file +@import url("./active-participant.css"); +@import url("./conversation-sheet.css"); diff --git a/js/constants.js b/js/constants.js new file mode 100644 index 0000000..e4c9921 --- /dev/null +++ b/js/constants.js @@ -0,0 +1 @@ +export const MODULE_NAME = "conversation-hud"; diff --git a/js/conversation.js b/js/conversation.js index 4d3d54a..47b174b 100644 --- a/js/conversation.js +++ b/js/conversation.js @@ -9,6 +9,8 @@ import { updateConversationLayout, } from "./helpers.js"; import { socket } from "./init.js"; +import { MODULE_NAME } from "./constants.js"; +import { ModuleSettings } from "./settings.js"; export class ConversationHud { // Function that initializes the class data @@ -68,19 +70,23 @@ export class ConversationHud { game.ConversationHud.conversationIsVisible = conversationVisible; game.ConversationHud.activeConversation = conversationData; - // Data that is passed to the template - const template_data = { + // Render templates + const renderedHtml = await renderTemplate("modules/conversation-hud/templates/conversation.hbs", { participants: conversationData.participants, isGM: game.user.isGM, - }; - - // Render templates - const renderedHtml = await renderTemplate("modules/conversation-hud/templates/conversation.hbs", template_data); + portraitStyle: game.settings.get(MODULE_NAME, ModuleSettings.portraitStyle), + portraitAnchor: game.settings.get(MODULE_NAME, ModuleSettings.portraitAnchor), + activeParticipantFontSize: game.settings.get(MODULE_NAME, ModuleSettings.activeParticipantFontSize), + }); const conversationControls = await renderTemplate("modules/conversation-hud/templates/conversation_controls.hbs", { isGM: game.user.isGM, isMinimized: game.ConversationHud.conversationIsMinimized, isVisible: game.ConversationHud.conversationIsVisible, isSpeakingAs: game.ConversationHud.conversationIsSpeakingAs, + features: { + minimizeEnabled: game.settings.get(MODULE_NAME, ModuleSettings.enableMinimize), + speakAsEnabled: game.settings.get(MODULE_NAME, ModuleSettings.enableSpeakAs), + }, }); // Create the conversation container @@ -192,14 +198,14 @@ export class ConversationHud { // Set conversation data game.ConversationHud.activeConversation = conversationData; - // Data that is passed to the template - const template_data = { + // Render template + const renderedHtml = await renderTemplate("modules/conversation-hud/templates/conversation.hbs", { participants: conversationData.participants, isGM: game.user.isGM, - }; - - // Render template - const renderedHtml = await renderTemplate("modules/conversation-hud/templates/conversation.hbs", template_data); + portraitStyle: game.settings.get(MODULE_NAME, ModuleSettings.portraitStyle), + portraitAnchor: game.settings.get(MODULE_NAME, ModuleSettings.portraitAnchor), + activeParticipantFontSize: game.settings.get(MODULE_NAME, ModuleSettings.activeParticipantFontSize), + }); // Add rendered template to the conversation hud const conversationHud = document.getElementById("ui-conversation-hud"); @@ -426,18 +432,26 @@ export class ConversationHud { // Function that minimizes or maximizes the active conversation async toggleActiveConversationMode() { - if (checkIfConversationActive()) { - game.ConversationHud.conversationIsMinimized = !game.ConversationHud.conversationIsMinimized; - updateConversationControls(); - updateConversationLayout(); + if (game.settings.get(MODULE_NAME, ModuleSettings.enableMinimize)) { + if (checkIfConversationActive()) { + game.ConversationHud.conversationIsMinimized = !game.ConversationHud.conversationIsMinimized; + updateConversationControls(); + updateConversationLayout(); + } + } else { + ui.notifications.error(game.i18n.localize("CHUD.errors.featureNotEnabled")); } } // Function that toggles the speaking as mode async toggleSpeakingAsMode() { - if (checkIfUserGM() && checkIfConversationActive()) { - game.ConversationHud.conversationIsSpeakingAs = !game.ConversationHud.conversationIsSpeakingAs; - updateConversationControls(); + if (game.settings.get(MODULE_NAME, ModuleSettings.enableSpeakAs)) { + if (checkIfUserGM() && checkIfConversationActive()) { + game.ConversationHud.conversationIsSpeakingAs = !game.ConversationHud.conversationIsSpeakingAs; + updateConversationControls(); + } + } else { + ui.notifications.error(game.i18n.localize("CHUD.errors.featureNotEnabled")); } } diff --git a/js/helpers.js b/js/helpers.js index 5695b54..74d791e 100644 --- a/js/helpers.js +++ b/js/helpers.js @@ -1,4 +1,6 @@ import { socket } from "./init.js"; +import { MODULE_NAME } from "./constants.js"; +import { ModuleSettings } from "./settings.js"; export async function getActorDataFromDragEvent(event) { try { @@ -136,6 +138,10 @@ export async function updateConversationControls() { isMinimized: game.ConversationHud.conversationIsMinimized, isVisible: game.ConversationHud.conversationIsVisible, isSpeakingAs: game.ConversationHud.conversationIsSpeakingAs, + features: { + minimizeEnabled: game.settings.get(MODULE_NAME, ModuleSettings.enableMinimize), + speakAsEnabled: game.settings.get(MODULE_NAME, ModuleSettings.enableSpeakAs), + }, }); const updatedControls = document.createElement("section"); diff --git a/js/init.js b/js/init.js index 2b6aa7a..2458904 100644 --- a/js/init.js +++ b/js/init.js @@ -1,5 +1,7 @@ +import { MODULE_NAME } from "./constants.js"; import { ConversationHud } from "./conversation.js"; import { checkConversationDataAvailability, handleOnClickContentLink } from "./helpers.js"; +import { registerSettings } from "./settings.js"; // Warning hook in case libWrapper is not installed Hooks.once("ready", () => { @@ -18,7 +20,7 @@ Hooks.on("init", () => { // Register the module within libWrapper if (libWrapper) { libWrapper.register( - "conversation-hud", + MODULE_NAME, "TextEditor._onClickContentLink", function (wrapped, event) { return handleOnClickContentLink.bind(this)(event, wrapped); @@ -27,6 +29,9 @@ Hooks.on("init", () => { ); } + // Register settings + registerSettings(); + // Initialize the ConversationHUD object game.ConversationHud = new ConversationHud(); game.ConversationHud.init(); diff --git a/js/settings.js b/js/settings.js new file mode 100644 index 0000000..4edefad --- /dev/null +++ b/js/settings.js @@ -0,0 +1,78 @@ +import { MODULE_NAME } from "./constants.js"; + +export const ModuleSettings = { + portraitStyle: "portraitStyle", + portraitAnchor: "portraitAnchor", + enableMinimize: "enableMinimize", + enableSpeakAs: "enableSpeakAs", + activeParticipantFontSize: "activeParticipantFontSize", +}; + +export function registerSettings() { + game.settings.register(MODULE_NAME, ModuleSettings.portraitStyle, { + name: game.i18n.localize(`CHUD.settings.portraitStyle.name`), + hint: game.i18n.localize(`CHUD.settings.portraitStyle.hint`), + scope: "world", + config: true, + requiresReload: true, + type: String, + default: "vertical", + choices: { + vertical: game.i18n.localize(`CHUD.settings.portraitStyle.choices.vertical`), + horizontal: game.i18n.localize(`CHUD.settings.portraitStyle.choices.horizontal`), + square: game.i18n.localize(`CHUD.settings.portraitStyle.choices.square`), + }, + }); + + game.settings.register(MODULE_NAME, ModuleSettings.portraitAnchor, { + name: game.i18n.localize(`CHUD.settings.portraitAnchor.name`), + hint: game.i18n.localize(`CHUD.settings.portraitAnchor.hint`), + scope: "world", + config: true, + requiresReload: true, + type: String, + default: "center", + choices: { + top: game.i18n.localize(`CHUD.settings.portraitAnchor.choices.top`), + center: game.i18n.localize(`CHUD.settings.portraitAnchor.choices.center`), + bottom: game.i18n.localize(`CHUD.settings.portraitAnchor.choices.bottom`), + }, + }); + + game.settings.register(MODULE_NAME, ModuleSettings.enableSpeakAs, { + name: game.i18n.localize(`CHUD.settings.enableSpeakAs.name`), + hint: game.i18n.localize(`CHUD.settings.enableSpeakAs.hint`), + scope: "world", + config: true, + requiresReload: true, + type: Boolean, + default: true, + }); + + game.settings.register(MODULE_NAME, ModuleSettings.enableMinimize, { + name: game.i18n.localize(`CHUD.settings.enableMinimize.name`), + hint: game.i18n.localize(`CHUD.settings.enableMinimize.hint`), + scope: "world", + config: true, + requiresReload: true, + type: Boolean, + default: true, + }); + + game.settings.register(MODULE_NAME, ModuleSettings.activeParticipantFontSize, { + name: game.i18n.localize(`CHUD.settings.activeParticipantFontSize.name`), + hint: game.i18n.localize(`CHUD.settings.activeParticipantFontSize.hint`), + scope: "world", + config: true, + requiresReload: true, + type: String, + default: "regular", + choices: { + verySmall: game.i18n.localize(`CHUD.settings.activeParticipantFontSize.choices.verySmall`), + small: game.i18n.localize(`CHUD.settings.activeParticipantFontSize.choices.small`), + regular: game.i18n.localize(`CHUD.settings.activeParticipantFontSize.choices.regular`), + large: game.i18n.localize(`CHUD.settings.activeParticipantFontSize.choices.large`), + veryLarge: game.i18n.localize(`CHUD.settings.activeParticipantFontSize.choices.veryLarge`), + }, + }); +} diff --git a/lang/en.json b/lang/en.json index b8a6397..15e4248 100644 --- a/lang/en.json +++ b/lang/en.json @@ -58,6 +58,7 @@ "noSelectedTokens": "No tokens are selected.", "conversationAlreadyActive": "Cannot create a new conversation as there is already an active conversation.", "insufficientRights": "Insufficient user rights.", + "featureNotEnabled": "Feature is currently disabled. Enabled it in the settings if you wish to use it.", "noLibWrapper": "ConversationHUD requires the libWrapper module for full functionality. Please install and activate it." }, @@ -68,5 +69,45 @@ "CHUD.info": { "saveSuccessful": "Current conversation has been saved successfully." + }, + + "CHUD.settings": { + "portraitStyle": { + "name": "Portrait Style", + "hint": "This option determines the orientation and style in which the portraits of the active participants will be displayed.", + "choices": { + "vertical": "Vertical", + "horizontal": "Horizontal", + "square": "Square" + } + }, + "portraitAnchor": { + "name": "Portrait Anchor", + "hint": "The anchor represents a point of reference from which the portrait will begin to pe displayed. For example, with the Top option selected, the upper section of the portrait will always be displayed. This option has an impact only when the portrait displayed has an aspect ratio larger than the display area. In most cases, it shouldn't matter too much.", + "choices": { + "top": "Top", + "center": "Center", + "bottom": "Bottom" + } + }, + "enableSpeakAs": { + "name": "Enable Speak As", + "hint": "Toggle the Speak As feature on or off. By default, the feature is on, but if you do not use it and do not want an extra button to appear, you can toggle it off." + }, + "enableMinimize": { + "name": "Enable Minimize", + "hint": "Toggle the Minimize feature on or off. By default, the feature is on as it allows users to interact with the map while a conversation is active and visible. If you do not want this behavior, you can toggle the feature off." + }, + "activeParticipantFontSize": { + "name": "Active Participant Font Size", + "hint": "This option determines the size of the font used to display the name of the currently active participant.", + "choices": { + "verySmall": "Very Small", + "small": "Small", + "regular": "Regular", + "large": "Large", + "veryLarge": "Very Large" + } + } } } diff --git a/module.json b/module.json index 47eb72e..bc09b74 100644 --- a/module.json +++ b/module.json @@ -7,8 +7,8 @@ "library": "false", "compatibility": { "minimum": 10, - "verified": 10, - "maximum": 10 + "verified": 11, + "maximum": 11 }, "authors": [ { @@ -58,6 +58,6 @@ "url": "#{URL}#", "manifest": "#{MANIFEST}#", "download": "#{DOWNLOAD}#", - "license": "https://raw.githubusercontent.com/CristianVasile23/conversation-hud/main/LICENSE", - "readme": "https://raw.githubusercontent.com/CristianVasile23/conversation-hud/main/README.md" + "license": "https://github.com/CristianVasile23/conversation-hud/blob/main/LICENSE", + "readme": "https://github.com/CristianVasile23/conversation-hud/blob/main/README.md" } diff --git a/templates/add_edit_participant.hbs b/templates/add_edit_participant.hbs index 27e3e12..1729338 100644 --- a/templates/add_edit_participant.hbs +++ b/templates/add_edit_participant.hbs @@ -19,8 +19,6 @@

{{localize "CHUD.portraitHint"}}

-
-