Compare commits

...

20 commits
v1.1.0 ... main

Author SHA1 Message Date
5830fe9e4b Merge pull request 'Hide empty sub-menus from Foundry's settings app' (#31) from feature/hide-empty-submenus into main
Reviewed-on: #31
2025-12-31 07:02:02 +00:00
9c19306cc1 Hide empty sub-menus from Foundry's settings app 2025-12-26 00:43:53 -07:00
00af9286d4 Add tweak blocking for when Crucible is the game system 2025-12-21 22:02:39 -07:00
ce994f5daf Export all of the tweak keys 2025-12-21 21:59:12 -07:00
3bbe8a58ed Version bump for release 2025-12-21 21:39:50 -07:00
1a0cf21742 Fixes the sidebar width in systems that might tweak the sidebar width (closes #27) 2025-12-21 21:39:15 -07:00
1ce2e01f5c Fix bug preventing all non-image messages from being sent (closes #30) 2025-12-21 21:30:54 -07:00
455a301875 Add Foundrylock file to the repo 2025-12-21 21:25:41 -07:00
1ca6da7c91 Update the localization for the chatImageLinks 2025-12-20 00:32:00 -07:00
064b4c1304 Move away from an enricher in favour of the chatMessage hook handling (closes #23) 2025-12-20 00:28:58 -07:00
97f8e293d2 Merge pull request 'Move the tweak prevention into a helper function and rename the module hooks w/ compatibility code' (#26) from feature/change-hook-name into main
Reviewed-on: #26
2025-12-20 01:39:52 +00:00
3527286c7f Fix bug and call the hook with the tweak key 2025-12-19 18:38:54 -07:00
9ffa1cee06 Fix grammar 2025-12-19 00:23:56 -07:00
037533401b Add the invasive flag to the relevant settings 2025-12-19 00:23:00 -07:00
9c8b6f37f9 Move the tweak prevention into a helper function and rename the module hooks w/ compatibility code 2025-12-18 22:32:46 -07:00
3d4376aa81 Merge pull request 'Adds a keybind into the module that allows GMs to temporarily hide their cursor from everyone else (including other GMs)' (#24) from feature/hide-cursor-temporarily into main
Reviewed-on: #24
2025-12-19 03:05:18 +00:00
f088cc474e Remove excess DOM manipulation 2025-12-18 20:04:32 -07:00
8c12c60815 Unset the notification ID when we remove it 2025-12-18 20:03:29 -07:00
e6e02301ab Improve wording of keybinding name and description 2025-12-18 20:02:55 -07:00
f58c8411aa Adds a keybind into the module that allows GMs to temporarily hide their cursor from everyone else (including other GMs) 2025-12-14 15:27:45 -07:00
31 changed files with 401 additions and 240 deletions

View file

@ -11,7 +11,7 @@
},
"chatImageLinks": {
"name": "Image Shortcuts",
"hint": "(v13+) When attempting to send an image/gif in chat, this allows you to easily embed the actual image in the text by changing \"http\"/\"https\" into \"image\", automatically displaying the image after sending the message."
"hint": "(v13+) Automatically embeds image and gif links when posted in the chat. The link must point to a specific image file (e.g. png, webp, gif), otherwise it will not be embedded at all."
},
"chatSidebarBackground": {
"name": "Chat Background",
@ -65,17 +65,20 @@
"label": "Configure Hotbar"
}
},
"keybindings": {
"toggleMouseBroadcast": {
"name": "Toggle Show Cursor",
"hint": "(v13+) Temporarily turns off the mouse cursor position that other players can see. Hides the cursor until you activate this keybind again."
}
},
"apps": {
"no-settings-to-display": "No settings to display",
"make-global-reference": "Make Global Reference"
},
"dialogs": {
"chatImageLinks": {
"didYouKnowImageLink": "",
"convertAndDontShowAgain": "",
"justConvert": "",
"ignoreAndDontShowAgain": "",
"disableEntirely": ""
"notifs": {
"toggleMouseBroadcast": {
"hidingCursor": "Hiding your cursor from others!",
"showingCursor": "Showing your cursor to others!"
}
}
}

View file

@ -1,7 +1,7 @@
{
"id": "oft",
"title": "Oliver's Foundry Tweaks",
"version": "1.1.0",
"version": "1.2.0",
"authors": [
{ "name": "Oliver" }
],

View file

@ -9,6 +9,7 @@ export class DevSettingsMenu extends OFTSettingsMenu {
};
static get _SETTINGS() {
if (!categories.has(`dev`)) { return [] };
const devSettings = categories.get(`dev`);
const settingIDs = [];
for (const [settingID, shown] of devSettings.entries()) {

View file

@ -9,6 +9,7 @@ export class HotbarSettingsMenu extends OFTSettingsMenu {
};
static get _SETTINGS() {
if (!categories.has(`hotbar`)) { return [] };
const settings = categories.get(`hotbar`);
const settingIDs = [];
for (const [settingID, shown] of settings.entries()) {

View file

@ -47,6 +47,10 @@ export class OFTSettingsMenu extends HAM(ApplicationV2) {
};
static _SETTINGS = [];
static get isEmpty() {
return this._SETTINGS.length === 0;
};
// #endregion Options
// #region Data Prep

View file

@ -0,0 +1,10 @@
import { key as preventMovementHistoryKey } from "../tweaks/preventMovementHistory.mjs";
Hooks.on(`oft.preRegisterTweak`, (tweakID) => {
if (game.system.id !== `crucible`) { return };
switch (tweakID) {
case preventMovementHistoryKey:
return false;
};
});

View file

@ -1,5 +1,6 @@
// Settings
import { preventMovementHistory } from "../settings/preventMovementHistory.mjs";
import { preventMovementHistory } from "../tweaks/preventMovementHistory.mjs";
import { toggleMouseBroadcast } from "../tweaks/toggleMouseBroadcast.mjs";
// Utils
import { Logger } from "../utils/Logger.mjs";
@ -14,4 +15,5 @@ Hooks.on(`init`, () => {
Logger.log(`Initializing`);
preventMovementHistory();
toggleMouseBroadcast();
});

View file

@ -0,0 +1,19 @@
/*
This hook is used to give external modules or systems the ability to interact
with the tweak registration lifecycle to do something before a tweak is registered
or being able to prevent registration of incompatible tweaks.
The hook receives a string indicating which tweak this hook is being called for
and a boolean value indicating if the tweak is considered invasive. Returning
explicit false prevents that tweak from being registered.
Invasive tweaks are additions that manipulate or override Document or helper
classes. An example of an invasive tweak is the "toggleMouseBroadcast",
tweak which replaces the existing "CONFIG.Canvas.layers.controls.layerClass"
class, most of these tweaks do smartly extend from the same CONFIG class
that they replace, however if they override a part of the class that
other modules/systems rely on, then that is a good time to block that
specific tweak's registration.
Call Signature: (tweakKey: string, isInvasive: boolean) => (void | boolean)
*/

View file

@ -1,12 +0,0 @@
/*
This hook is used for invasive hooks that we want to provide the
option for systems and other modules to be able to disable in case
of incompatabilities for whatever reason. This can also be used
internally within this module if we discover incompatabilites with
systems and want to disable it on our side.
This file is meant more documentation than anything at this point in
time.
Call Signature: (settingKey: string) => (void | boolean)
*/

View file

@ -1,13 +0,0 @@
/*
This hook is used to enable any modules that attempt to disable settings
or just want to investigate what settings are enabled to be able to get
a ping with information about which settings where registered entirely
and which weren't. The object that is passed to this is frozen and is
not meant to be edited as you cannot de-register nor prevent setting
registration from this hook. For that see the "oft.preventSetting" hook.
This file is meant more documentation than anything at this point in
time.
Call Signature: (settings: Record<string, boolean>) => void
*/

View file

@ -0,0 +1,9 @@
/*
This hook is used to broadcast the final status of all tweaks within the module,
allowing other modules to either confirm their registration didn't happen or to
do something once all of the module setup has been finalized. Tweak statuses
cannot be blocked or changed from this hook, to prevent a tweak from being
registered you should use the "oft.preRegisterTweak" hook.
Call Signature: (settings: Record<string, string>) => void
*/

View file

@ -6,11 +6,19 @@ prevent it from being as attention-grabbing compared to being at the top of the
list.
*/
Hooks.on(`renderSettingsConfig`, (app) => {
// MARK: Hide empty menus
for (const [key, config] of game.settings.menus) {
if (!key.startsWith(__ID__)) { continue };
if (config.type.isEmpty) {
const entry = app.element.querySelector(`.form-group:has(button[data-key="${key}"])`);
entry?.remove();
};
};
// MARK: devSettings Menu
/** @type {Node | undefined} */
const settingList = app.element.querySelector(`.tab[data-group="categories"][data-tab="oft"]`);
// MARK: devSettings Menu
/** @type {Node | undefined} */
const devSettingsMenu = app.element.querySelector(`.form-group:has(button[data-key="${__ID__}.devSettings"])`);
if (settingList && devSettingsMenu) {

View file

@ -1,15 +1,15 @@
// Settings
import { addGlobalDocReferrer } from "../settings/addGlobalDocReferrer.mjs";
import { autoUnpauseOnLoad } from "../settings/autoUnpauseOnLoad.mjs";
import { chatImageLinks } from "../settings/chatImageLinks.mjs";
import { chatSidebarBackground } from "../settings/chatSidebarBackground.mjs";
import { hotbarButtonGap } from "../settings/hotbarButtonGap.mjs";
import { hotbarButtonSize } from "../settings/hotbarButtonSize.mjs";
import { preventTokenRotation } from "../settings/preventTokenRotation.mjs";
import { preventUserConfigOpen } from "../settings/preventUserConfigOpen.mjs";
import { repositionHotbar } from "../settings/repositionHotbar.mjs";
import { startingSidebarTab } from "../settings/startingSidebarTab.mjs";
import { startSidebarExpanded } from "../settings/startSidebarExpanded.mjs";
import { addGlobalDocReferrer } from "../tweaks/addGlobalDocReferrer.mjs";
import { autoUnpauseOnLoad } from "../tweaks/autoUnpauseOnLoad.mjs";
import { chatImageLinks } from "../tweaks/chatImageLinks.mjs";
import { chatSidebarBackground } from "../tweaks/chatSidebarBackground.mjs";
import { hotbarButtonGap } from "../tweaks/hotbarButtonGap.mjs";
import { hotbarButtonSize } from "../tweaks/hotbarButtonSize.mjs";
import { preventTokenRotation } from "../tweaks/preventTokenRotation.mjs";
import { preventUserConfigOpen } from "../tweaks/preventUserConfigOpen.mjs";
import { repositionHotbar } from "../tweaks/repositionHotbar.mjs";
import { startingSidebarTab } from "../tweaks/startingSidebarTab.mjs";
import { startSidebarExpanded } from "../tweaks/startSidebarExpanded.mjs";
// Apps
import { DevSettingsMenu } from "../apps/DevSettingsMenu.mjs";
@ -51,7 +51,16 @@ Hooks.on(`setup`, () => {
preventTokenRotation();
preventUserConfigOpen();
Hooks.callAll(`oft.settingStatuses`, deepFreeze(status));
// Compatibility Code
if (Hooks.events[`oft.settingStatuses`] != null) {
foundry.utils.logCompatibilityWarning(
`The hook "${__ID__}.settingStatuses" has been renamed "${__ID__}.tweakStatuses".`,
{ since: `v1.2.0`, until: `v2.0.0`, stack: false, once: true },
);
Hooks.callAll(`oft.settingStatuses`, deepFreeze(status));
};
Hooks.callAll(`${__ID__}.tweakStatuses`, deepFreeze(status));
game.modules.get(__ID__).api = deepFreeze({
settings: status,
});

View file

@ -2,3 +2,6 @@
import "./hooks/init.mjs";
import "./hooks/setup.mjs";
import "./hooks/renderSettingsConfig.mjs";
// Compatibility w/ other packages
import "./conflicts/crucible.mjs";

View file

@ -1,130 +0,0 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
const { DialogV2 } = foundry.applications.api;
const key = `chatImageLinks`;
const IMAGE_TYPES = [
`png`,
`jpg`,
`jpeg`,
`webp`,
`svg`,
];
export function chatImageLinks() {
status[key] = SettingStatusEnum.Unknown;
const prevented = Hooks.call(`${__ID__}.preventSetting`, key);
if (!prevented) {
Logger.log(`Preventing setting "${key}" from being registered`);
status[key] = SettingStatusEnum.Blocked;
return;
};
// #region Registration
Logger.log(`Registering setting: ${key}`);
game.settings.register(__ID__, key, {
name: `OFT.setting.${key}.name`,
hint: `OFT.setting.${key}.hint`,
scope: `user`,
type: Boolean,
default: true,
config: true,
requiresReload: true,
});
game.settings.register(__ID__, key + `-showPromptAgain`, {
scope: `user`,
type: Boolean,
default: true,
config: false,
});
// #endregion Registration
// #region Implementation
if (game.settings.get(__ID__, key)) {
Logger.log(`setting:${key} | Adding text enricher`);
// MARK: Enricher
const pattern = new RegExp(
`(?<!=|")(image:\\/\\/.*\\.(?:${IMAGE_TYPES.join(`|`)}))`,
`gi`,
);
CONFIG.TextEditor.enrichers.push({
pattern,
replaceParent: true,
enricher: async (url) => {
Logger.debug(url);
url = url[0].replace(/^image:\/\//, ``);
const secure = `https://${url}`;
const insecure = `http://${url}`;
if (await isAcceptableImage(secure)) {
const img = document.createElement(`img`);
img.src = secure;
img.alt = secure;
return img;
};
if (await isAcceptableImage(insecure)) {
const img = document.createElement(`img`);
img.src = insecure;
img.alt = insecure;
return img;
};
return null;
},
});
// MARK: Chat Input
// Hooks.on(`chatMessage`, (chatLog, message, options) => {
// if (!game.settings.get(__ID__, key)) { return };
// const match = message.match(pattern);
// if (!match) { return };
// DialogV2.wait({
// rejectClose: false,
// content: game.i18n.localize(`OFT.dialogs.chatImageLinks.didYouKnowImageLink`),
// buttons: [
// { action: ``, label: `OFT.dialogs.chatImageLinks.convertAndDontShowAgain` },
// { action: ``, label: `OFT.dialogs.chatImageLinks.justConvert` },
// { action: ``, label: `OFT.dialogs.chatImageLinks.ignoreAndDontShowAgain` },
// { action: ``, label: `OFT.dialogs.chatImageLinks.disableEntirely` },
// ],
// })
// .then((selected) => {
// chatLog.processMessage(message, options);
// });
// return false;
// });
}
// #endregion Implementation
status[key] = SettingStatusEnum.Registered;
};
// #region Helpers
async function isAcceptableImage(url) {
try {
const response = await fetch(url, { method: `HEAD` });
const contentType = response.headers.get(`Content-Type`);
Logger.debug(`Image data:`, { url, contentType });
let [ superType, subtype ] = contentType.split(`/`);
if (superType !== `image`) {
return false;
};
if (subtype.includes(`+`)) {
subtype = subtype.split(`+`, 2).at(0);
};
return IMAGE_TYPES.includes(subtype);
} catch {
return false;
};
};
// #endregion Helpers

View file

@ -1,12 +1,14 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
import { registerDevSetting } from "../utils/SubMenuSettings.mjs";
const key = `addGlobalDocReferrer`;
export const key = `addGlobalDocReferrer`;
export function addGlobalDocReferrer() {
status[key] = SettingStatusEnum.Unknown;
if (preventTweakRegistration(key)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);

View file

@ -1,19 +1,14 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
import { registerDevSetting } from "../utils/SubMenuSettings.mjs";
const key = `autoUnpauseOnLoad`;
export const key = `autoUnpauseOnLoad`;
export function autoUnpauseOnLoad() {
status[key] = SettingStatusEnum.Unknown;
const prevented = Hooks.call(`${__ID__}.preventSetting`, key);
if (!prevented) {
Logger.log(`Preventing setting "${key}" from being registered`);
status[key] = SettingStatusEnum.Blocked;
return;
};
if (preventTweakRegistration(key)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);

View file

@ -0,0 +1,125 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
export const key = `chatImageLinks`;
const IMAGE_TYPES = [
`png`,
`jpg`,
`jpeg`,
`webp`,
`svg`,
];
export function chatImageLinks() {
status[key] = SettingStatusEnum.Unknown;
if (preventTweakRegistration(key)) { return };
/** @type {number|null} */
let hookID = null;
// #region Registration
Logger.log(`Registering setting: ${key}`);
game.settings.register(__ID__, key, {
name: `OFT.setting.${key}.name`,
hint: `OFT.setting.${key}.hint`,
scope: `user`,
type: Boolean,
default: true,
config: true,
requiresReload: false,
onChange: (newValue) => {
if (newValue) {
hookID = Hooks.on(`chatMessage`, chatMessageHandler);
} else if (hookID != null) {
Hooks.off(`chatMessage`, hookID);
};
},
});
// #endregion Registration
// #region Implementation
if (game.settings.get(__ID__, key)) {
Logger.log(`setting:${key} | Adding chat message listener`);
Hooks.on(`chatMessage`, chatMessageHandler);
};
// #endregion Implementation
status[key] = SettingStatusEnum.Registered;
};
// #region Helpers
const pattern = new RegExp(
`https?:\\/\\/\\S*\\.(?:${IMAGE_TYPES.join(`|`)})`,
`gi`,
);
// MARK: Mutate & Resend
const handled = new Set();
async function mutateAndResendMessage(chatLog, message, options) {
const validMatches = new Set();
const matches = message.match(pattern);
for (const match of matches) {
if (await isAcceptableImage(match)) {
validMatches.add(match);
};
};
message = message.replaceAll(
pattern,
(url) => {
if (!validMatches.has(url)) {
return url;
};
return `<img src="${url}" alt="${url}">`;
},
);
handled.add(message);
chatLog.processMessage(message, options);
};
// MARK: Chat Message
/**
* Must be synchronous since it is a hook handler, but the mutation +
* resending can be done asynchronously since it doesn't matter how
* long it takes.
*/
function chatMessageHandler(chatLog, message, options) {
if (!game.settings.get(__ID__, key)) { return };
// Don't re-process the same message
if (handled.has(message)) { return };
// Prevent cancellation for non-matching messages
const match = message.match(pattern);
if (!match) { return };
mutateAndResendMessage(chatLog, message, options);
return false;
};
// MARK: isAcceptableImage
async function isAcceptableImage(url) {
if (!URL.canParse(url)) { return false };
try {
const response = await fetch(url, { method: `HEAD` });
const contentType = response.headers.get(`Content-Type`);
Logger.debug(`Image data:`, { url, contentType });
let [ superType, subtype ] = contentType.split(`/`);
if (superType !== `image`) {
return false;
};
if (subtype.includes(`+`)) {
subtype = subtype.split(`+`, 2).at(0);
};
return IMAGE_TYPES.includes(subtype);
} catch {
return false;
};
};
// #endregion Helpers

View file

@ -1,11 +1,13 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
const key = `chatSidebarBackground`;
export const key = `chatSidebarBackground`;
export function chatSidebarBackground() {
status[key] = SettingStatusEnum.Unknown;
if (preventTweakRegistration(key)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);

View file

@ -1,19 +1,14 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
import { registerCategorySetting } from "../utils/SubMenuSettings.mjs";
const key = `hotbarButtonGap`;
export const key = `hotbarButtonGap`;
export function hotbarButtonGap() {
status[key] = SettingStatusEnum.Unknown;
const prevented = Hooks.call(`${__ID__}.preventSetting`, key);
if (!prevented) {
Logger.log(`Preventing setting "${key}" from being registered`);
status[key] = SettingStatusEnum.Blocked;
return;
};
if (preventTweakRegistration(key)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);

View file

@ -1,19 +1,14 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
import { registerCategorySetting } from "../utils/SubMenuSettings.mjs";
const key = `hotbarButtonSize`;
export const key = `hotbarButtonSize`;
export function hotbarButtonSize() {
status[key] = SettingStatusEnum.Unknown;
const prevented = Hooks.call(`${__ID__}.preventSetting`, key);
if (!prevented) {
Logger.log(`Preventing setting "${key}" from being registered`);
status[key] = SettingStatusEnum.Blocked;
return;
};
if (preventTweakRegistration(key)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);

View file

@ -1,18 +1,13 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
const key = `preventMovementHistory`;
export const key = `preventMovementHistory`;
export function preventMovementHistory() {
status[key] = SettingStatusEnum.Unknown;
const prevented = Hooks.call(`${__ID__}.preventSetting`, key);
if (!prevented) {
Logger.log(`Preventing setting "${key}" from being registered`);
status[key] = SettingStatusEnum.Blocked;
return;
};
if (preventTweakRegistration(key, true)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);

View file

@ -1,18 +1,13 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
const key = `preventTokenRotation`;
export const key = `preventTokenRotation`;
export function preventTokenRotation() {
status[key] = SettingStatusEnum.Unknown;
const prevented = Hooks.call(`${__ID__}.preventSetting`, key);
if (!prevented) {
Logger.log(`Preventing setting "${key}" from being registered`);
status[key] = SettingStatusEnum.Blocked;
return;
};
if (preventTweakRegistration(key)) { return };
/** @type {number|null} */
let hookID = null;

View file

@ -1,18 +1,13 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
const key = `preventUserConfigOpen`;
export const key = `preventUserConfigOpen`;
export function preventUserConfigOpen() {
status[key] = SettingStatusEnum.Unknown;
const prevented = Hooks.call(`${__ID__}.preventSetting`, key);
if (!prevented) {
Logger.log(`Preventing setting "${key}" from being registered`);
status[key] = SettingStatusEnum.Blocked;
return;
};
if (preventTweakRegistration(key)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);

View file

@ -1,19 +1,14 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
import { registerCategorySetting } from "../utils/SubMenuSettings.mjs";
const key = `repositionHotbar`;
export const key = `repositionHotbar`;
export function repositionHotbar() {
status[key] = SettingStatusEnum.Unknown;
const prevented = Hooks.call(`${__ID__}.preventSetting`, key);
if (!prevented) {
Logger.log(`Preventing setting "${key}" from being registered`);
status[key] = SettingStatusEnum.Blocked;
return;
};
if (preventTweakRegistration(key)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);

View file

@ -1,11 +1,13 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
const key = `startSidebarExpanded`;
export const key = `startSidebarExpanded`;
export function startSidebarExpanded() {
status[key] = SettingStatusEnum.Unknown;
if (preventTweakRegistration(key)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);

View file

@ -1,11 +1,13 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
const key = `startingSidebarTab`;
export const key = `startingSidebarTab`;
export function startingSidebarTab() {
status[key] = SettingStatusEnum.Unknown;
if (preventTweakRegistration(key)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);

View file

@ -0,0 +1,122 @@
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
export const key = `toggleMouseBroadcast`;
/** @type {number | null} */
let notifID = null;
export function toggleMouseBroadcast() {
status[key] = SettingStatusEnum.Unknown;
if (preventTweakRegistration(key, true)) { return };
// #region Registration
Logger.log(`Registering setting: ${key}`);
// MARK: setting
game.settings.register(__ID__, key, {
scope: `client`,
config: false,
default: true,
});
// MARK: keybind
game.keybindings.register(__ID__, key, {
name: `OFT.keybindings.${key}.name`,
hint: `OFT.keybindings.${key}.hint`,
precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL,
restricted: true,
editable: [
{ key: `KeyH` },
],
onDown: (event) => {
if (!game.user.hasPermission(`SHOW_CURSOR`)) { return };
event.preventDefault?.();
const current = game.settings.get(__ID__, key);
if (current) {
notifID = ui.notifications.warn(
`OFT.notifs.${key}.hidingCursor`,
{ console: false, localize: true, permanent: true },
).id;
} else {
if (notifID != null) {
ui.notifications.remove(notifID);
notifID = null;
};
ui.notifications.success(
`OFT.notifs.${key}.showingCursor`,
{ console: false, localize: true },
);
};
game.settings.set(__ID__, key, !current);
// Hide the existing cursor
game.user.broadcastActivity({ cursor: null });
return true;
},
});
// #endregion Registration
// #region Implementation
Hooks.on(`renderControlsConfig`, renderControlsConfigHandler);
Hooks.once(`ready`, readyHandler);
class OFTControlsLayer extends CONFIG.Canvas.layers.controls.layerClass {
_onMouseMove(currentPos) {
if (!game.settings.get(__ID__, key)) {
game.user.broadcastActivity({});
};
super._onMouseMove(currentPos);
};
};
CONFIG.Canvas.layers.controls.layerClass = OFTControlsLayer;
// #endregion Implementation
status[key] = SettingStatusEnum.Registered;
};
// #region Helpers
const tabGroup = `categories`;
/**
* Handle showing the "hiding your cursor" notification when the user
* connects to the server initially, allowing the current state to be
* correctly described to the user.
*/
function readyHandler() {
const hideCursor = !game.settings.get(__ID__, key);
const canShowCursor = game.user.hasPermission(`SHOW_CURSOR`);
if (hideCursor && canShowCursor) {
notifID = ui.notifications.warn(
`OFT.notifs.${key}.hidingCursor`,
{ console: false, localize: true, permanent: true },
).id;
};
};
/**
* Handles hiding the keybinding from the configuration if and when the user
* does not have the SHOW_CURSOR permission since this keybinding is really
* useless if they can't even broadcast their cursor in the first place.
*/
function renderControlsConfigHandler(_app, element) {
if (game.user.hasPermission(`SHOW_CURSOR`)) { return };
const keybindingList = element.querySelector(`section[data-group="${tabGroup}"][data-tab="${__ID__}"]`);
const tabButton = element.querySelector(`button[data-group="${tabGroup}"][data-tab="${__ID__}"]`);
const keybind = keybindingList.querySelector(`.form-group[data-action-id="${__ID__}.${key}"]`);
keybind?.remove();
if (keybindingList.childElementCount === 0) {
keybindingList.remove();
tabButton.remove();
};
};
// #endregion Helpers

View file

@ -0,0 +1,26 @@
import { SettingStatusEnum, status } from "./SettingStatus.mjs";
import { __ID__ } from "../consts.mjs";
import { Logger } from "./Logger.mjs";
export function preventTweakRegistration(key, invasive = false) {
let prevented = Hooks.call(`${__ID__}.preRegisterTweak`, key, invasive);
// Compatibility Code
if (Hooks.events[`${__ID__}.preventSetting`] != null) {
foundry.utils.logCompatibilityWarning(
`The hook "${__ID__}.preventSetting" has been renamed "${__ID__}.registerTweak".`,
{ since: `v1.2.0`, until: `v2.0.0`, stack: false, once: true },
);
if (prevented !== false) {
prevented = Hooks.call(`${__ID__}.preventSetting`, key);
};
};
if (!prevented) {
Logger.log(`Preventing setting "${key}" from being registered`);
status[key] = SettingStatusEnum.Blocked;
return true;
};
return false;
};

1
oft.lock Normal file
View file

@ -0,0 +1 @@
🔒

View file

@ -6,4 +6,4 @@
@import url("./apps.css");
/* Make the chat sidebar the same width as all the other tabs */
.chat-sidebar:not(.sidebar-popout) { width: unset; }
.chat-sidebar:not(.sidebar-popout) { width: var(--sidebar-width); }