Compare commits
20 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5830fe9e4b | |||
| 9c19306cc1 | |||
| 00af9286d4 | |||
| ce994f5daf | |||
| 3bbe8a58ed | |||
| 1a0cf21742 | |||
| 1ce2e01f5c | |||
| 455a301875 | |||
| 1ca6da7c91 | |||
| 064b4c1304 | |||
| 97f8e293d2 | |||
| 3527286c7f | |||
| 9ffa1cee06 | |||
| 037533401b | |||
| 9c8b6f37f9 | |||
| 3d4376aa81 | |||
| f088cc474e | |||
| 8c12c60815 | |||
| e6e02301ab | |||
| f58c8411aa |
31 changed files with 401 additions and 240 deletions
|
|
@ -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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "oft",
|
||||
"title": "Oliver's Foundry Tweaks",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"authors": [
|
||||
{ "name": "Oliver" }
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
10
module/conflicts/crucible.mjs
Normal file
10
module/conflicts/crucible.mjs
Normal 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;
|
||||
};
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
19
module/hooks/oft.preRegisterTweak.mjs
Normal file
19
module/hooks/oft.preRegisterTweak.mjs
Normal 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)
|
||||
*/
|
||||
|
|
@ -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)
|
||||
*/
|
||||
|
|
@ -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
|
||||
*/
|
||||
9
module/hooks/oft.tweakStatuses.mjs
Normal file
9
module/hooks/oft.tweakStatuses.mjs
Normal 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
|
||||
*/
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,3 +2,6 @@
|
|||
import "./hooks/init.mjs";
|
||||
import "./hooks/setup.mjs";
|
||||
import "./hooks/renderSettingsConfig.mjs";
|
||||
|
||||
// Compatibility w/ other packages
|
||||
import "./conflicts/crucible.mjs";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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}`);
|
||||
|
|
@ -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}`);
|
||||
125
module/tweaks/chatImageLinks.mjs
Normal file
125
module/tweaks/chatImageLinks.mjs
Normal 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
|
||||
|
|
@ -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}`);
|
||||
|
|
@ -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}`);
|
||||
|
|
@ -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}`);
|
||||
|
|
@ -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}`);
|
||||
|
|
@ -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;
|
||||
|
|
@ -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}`);
|
||||
|
|
@ -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}`);
|
||||
|
|
@ -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}`);
|
||||
|
|
@ -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}`);
|
||||
122
module/tweaks/toggleMouseBroadcast.mjs
Normal file
122
module/tweaks/toggleMouseBroadcast.mjs
Normal 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
|
||||
26
module/utils/preRegisterTweak.mjs
Normal file
26
module/utils/preRegisterTweak.mjs
Normal 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
1
oft.lock
Normal file
|
|
@ -0,0 +1 @@
|
|||
🔒
|
||||
|
|
@ -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); }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue