289 lines
12 KiB
JavaScript
289 lines
12 KiB
JavaScript
/**
|
|
* @name WiderUserArea
|
|
* @version 0.2.5
|
|
* @description A BetterDiscord plugin that expands your user area into the server list, compatible with most themes
|
|
* @author TheLazySquid
|
|
* @authorId 619261917352951815
|
|
* @website https://github.com/TheLazySquid/DiscordWiderUserArea
|
|
* @source https://github.com/TheLazySquid/DiscordWiderUserArea/blob/main/build/WiderUserArea.plugin.js
|
|
*/
|
|
module.exports = class {
|
|
constructor() {
|
|
'use strict';
|
|
|
|
var styles = ":root {\r\n --user-area-bottom: 0;\r\n --user-area-left: 0;\r\n}\r\n\r\nnav[class*=\"guilds_\"] {\r\n height: var(--sidebar-height);\r\n}\r\n\r\n[class*=\"sidebar_\"] {\r\n height: calc(var(--sidebar-height) - var(--notices-height));\r\n}\r\n\r\n/* user area */\r\nsection[class*=\"panels_\"] {\r\n position: fixed;\r\n overflow: hidden;\r\n bottom: var(--user-area-bottom);\r\n left: var(--user-area-left);\r\n width: var(--user-area-width);\r\n}\r\n\r\nsection[class*=\"panels_\"] [class*=\"avatarWrapper_\"] {\r\n flex-grow: 1;\r\n}\r\n\r\n/* prevent the user info popout from clipping */\r\n[class*=\"accountProfilePopoutWrapper\"] {\r\n left: 0;\r\n}";
|
|
|
|
const userAreaSelector = 'section[class*="panels_"]';
|
|
const serverListSelector = 'nav[class*="guilds_"]';
|
|
const containerSelector = `div:has(> ${serverListSelector})`;
|
|
const layerSelector = `[class*="baseLayer"]`;
|
|
const channelsSelector = '[class*="sidebar_"] > nav';
|
|
const scaleRegex = /scale\((.*)\)/;
|
|
const UAButtonsSelector = 'div[class*="avatarWrapper_"] + div';
|
|
const firstServerListSelector = `${serverListSelector}:first-child`;
|
|
const secondServerListSelector = `${serverListSelector}:nth-child(2)`;
|
|
|
|
function scaleDOMRect(rect, scale, scaleCenterX, scaleCenterY) {
|
|
// Calculate the distance of the rect from the scale center
|
|
const distX = rect.x - scaleCenterX;
|
|
const distY = rect.y - scaleCenterY;
|
|
// Scale the distances
|
|
const newDistX = distX * scale;
|
|
const newDistY = distY * scale;
|
|
// Calculate the new position of the rect
|
|
const newX = scaleCenterX + newDistX;
|
|
const newY = scaleCenterY + newDistY;
|
|
// Scale the dimensions of the rect
|
|
const newWidth = rect.width * scale;
|
|
const newHeight = rect.height * scale;
|
|
// Create a new DOMRect with the new dimensions and position
|
|
const newRect = new DOMRect(newX, newY, newWidth, newHeight);
|
|
return newRect;
|
|
}
|
|
|
|
const createCallbackHandler = (callbackName) => {
|
|
const fullName = callbackName + "Callbacks";
|
|
this[fullName] = [];
|
|
this[callbackName] = () => {
|
|
for (let i = 0; i < this[fullName].length; i++) {
|
|
this[fullName][i].callback();
|
|
}
|
|
};
|
|
return (callback, once, id) => {
|
|
let object = { callback };
|
|
const delCallback = () => {
|
|
this[fullName].splice(this[fullName].indexOf(object), 1);
|
|
};
|
|
// if once is true delete it after use
|
|
if (once === true) {
|
|
object.callback = () => {
|
|
callback();
|
|
delCallback();
|
|
};
|
|
}
|
|
if (id) {
|
|
object.id = id;
|
|
for (let i = 0; i < this[fullName].length; i++) {
|
|
if (this[fullName][i].id === id) {
|
|
this[fullName][i] = object;
|
|
return delCallback;
|
|
}
|
|
}
|
|
}
|
|
this[fullName].push(object);
|
|
return delCallback;
|
|
};
|
|
};
|
|
/**
|
|
* Takes a callback and fires it when the plugin is started
|
|
* @param callback - The callback to be fired
|
|
* @param once - If true, the callback will be deleted after use
|
|
* @param id - The id of the callback - if it already exists, it will be replaced
|
|
* @returns A function to delete the callback
|
|
*/
|
|
const onStart = createCallbackHandler("start");
|
|
/**
|
|
* Takes a callback and fires it when the plugin is stopped
|
|
* @param callback - The callback to be fired
|
|
* @param once - If true, the callback will be deleted after use
|
|
* @param id - The id of the callback - if it already exists, it will be replaced
|
|
* @returns A function to delete the callback
|
|
*/
|
|
const onStop = createCallbackHandler("stop");
|
|
/**
|
|
* Takes a callback and fires it when the user navigates
|
|
* @param callback - The callback to be fired
|
|
* @param once - If true, the callback will be deleted after use
|
|
* @param id - The id of the callback - if it already exists, it will be replaced
|
|
* @returns A function to delete the callback
|
|
*/
|
|
const onSwitch = createCallbackHandler("onSwitch");
|
|
|
|
/**
|
|
* Watches for an element with a given selector to be added to the DOM
|
|
* @param selector The CSS selector to watch
|
|
* @param callback The callback to run whenever the matching element is added to the DOM
|
|
* @returns A function to stop watching
|
|
*/
|
|
function watchElement(selector, callback) {
|
|
let observer = new MutationObserver((mutations) => {
|
|
for (let mutation of mutations) {
|
|
if (mutation.addedNodes.length) {
|
|
for (let node of mutation.addedNodes) {
|
|
if (!(node instanceof HTMLElement))
|
|
continue;
|
|
if (node.matches && node.matches(selector)) {
|
|
callback(node);
|
|
}
|
|
if (node.querySelectorAll) {
|
|
for (let element of node.querySelectorAll(selector)) {
|
|
callback(element);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
let startDispose = onStart(() => {
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
for (let element of document.querySelectorAll(selector)) {
|
|
callback(element);
|
|
}
|
|
});
|
|
let stopDispose = onStop(() => {
|
|
observer.disconnect();
|
|
});
|
|
return () => {
|
|
observer.disconnect();
|
|
startDispose();
|
|
stopDispose();
|
|
};
|
|
}
|
|
|
|
// @ts-ignore
|
|
let baseChannelHeight = 0;
|
|
let baseChannelWidth = 0;
|
|
let varsSet = new Set();
|
|
const recalcDebounce = BdApi.Utils.debounce(() => {
|
|
const userArea = document.querySelector(userAreaSelector);
|
|
if (userArea)
|
|
userAreaFound(userArea);
|
|
}, 150);
|
|
watchElement(userAreaSelector, userAreaFound);
|
|
let userAreaObserver = new ResizeObserver(entries => {
|
|
for (let entry of entries) {
|
|
updateVar('--sidebar-height', `${baseChannelHeight - entry.contentRect.height}px`);
|
|
}
|
|
});
|
|
let channelsRect;
|
|
let secondChannelsRect;
|
|
let channelsObserver = new ResizeObserver(entries => {
|
|
for (let entry of entries) {
|
|
channelsRect = entry.contentRect;
|
|
// hide everything but the profile picture if the channel list is hidden
|
|
let btns = document.querySelector(UAButtonsSelector);
|
|
if (entry.contentRect.width === 0) {
|
|
if (btns)
|
|
btns.style.display = 'none';
|
|
}
|
|
else {
|
|
if (btns)
|
|
btns.style.display = '';
|
|
}
|
|
let newWidth = entry.contentRect.right - baseChannelWidth;
|
|
if (secondChannelsRect)
|
|
newWidth += secondChannelsRect.width;
|
|
updateVar('--user-area-width', `${newWidth}px`);
|
|
}
|
|
});
|
|
watchElement(channelsSelector, (element) => {
|
|
channelsObserver.disconnect();
|
|
channelsObserver.observe(element);
|
|
});
|
|
let secondServerListObserver = new ResizeObserver((entries) => {
|
|
if (!channelsRect)
|
|
return;
|
|
for (let entry of entries) {
|
|
secondChannelsRect = entry.contentRect;
|
|
let width = entry.contentRect.width;
|
|
updateVar('--user-area-width', `${channelsRect.right - baseChannelWidth + width}px`);
|
|
}
|
|
});
|
|
watchElement(secondServerListSelector, (element) => {
|
|
secondServerListObserver.disconnect();
|
|
secondServerListObserver.observe(element);
|
|
});
|
|
let themesObserver = new MutationObserver(async () => {
|
|
// wait a bit for it to apply
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
console.log('theme changed');
|
|
// recalculate the user area
|
|
const userArea = document.querySelector(userAreaSelector);
|
|
if (userArea)
|
|
userAreaFound(userArea);
|
|
});
|
|
let noticesHeight = 0;
|
|
let noticesObserver = new ResizeObserver((entries) => {
|
|
for (let entry of entries) {
|
|
noticesHeight = entry.contentRect.height;
|
|
updateVar('--notices-height', `${entry.contentRect.height}px`);
|
|
}
|
|
});
|
|
watchElement("#bd-notices", (el) => {
|
|
noticesObserver.disconnect();
|
|
noticesObserver.observe(el);
|
|
});
|
|
function updateVar(name, value) {
|
|
BdApi.DOM.removeStyle(`wua-${name}`);
|
|
BdApi.DOM.addStyle(`wua-${name}`, `:root { ${name}: ${value} !important; }`);
|
|
varsSet.add(name);
|
|
}
|
|
onSwitch(() => {
|
|
// hacky fix for betteranimations sometimes giving a parent the "perspective" style breaking the fixed positioning
|
|
setTimeout(() => {
|
|
const userArea = document.querySelector(userAreaSelector);
|
|
let parent = userArea?.parentElement;
|
|
while (parent) {
|
|
if (parent.style.perspective) {
|
|
parent.style.perspective = '';
|
|
break;
|
|
}
|
|
parent = parent.parentElement;
|
|
}
|
|
userAreaFound(userArea);
|
|
}, 5100);
|
|
});
|
|
function userAreaFound(element) {
|
|
// remove the old style if it exists
|
|
BdApi.DOM.removeStyle('wua-styles');
|
|
const layer = document.querySelector(layerSelector);
|
|
const container = document.querySelector(containerSelector);
|
|
const serverList = document.querySelector(firstServerListSelector);
|
|
const channels = document.querySelector(channelsSelector);
|
|
const secondServerList = document.querySelector(secondServerListSelector);
|
|
const layerScale = 1 / parseFloat(scaleRegex.exec(layer.style.transform)?.[1] ?? '1');
|
|
const layerRect = layer.getBoundingClientRect();
|
|
const centerX = layerRect.left + layerRect.width / 2;
|
|
const centerY = layerRect.top + layerRect.height / 2;
|
|
// this scaling is neccesary for compatibility with betterAnimations,
|
|
// which sometimes scales the layer when in a settings menu, which is where theme switches would be.
|
|
const rect = scaleDOMRect(element.getBoundingClientRect(), layerScale, centerX, centerY);
|
|
const containerRect = scaleDOMRect(container.getBoundingClientRect(), layerScale, centerX, centerY);
|
|
const serverListRect = scaleDOMRect(serverList.getBoundingClientRect(), layerScale, centerX, centerY);
|
|
const channelsRect = scaleDOMRect(channels.getBoundingClientRect(), layerScale, centerX, centerY);
|
|
// figure out how far from the bottom of the screen the user area is
|
|
const bottom = containerRect.bottom - rect.bottom;
|
|
baseChannelHeight = channelsRect.height + rect.height;
|
|
baseChannelWidth = serverListRect.left - channelsRect.left;
|
|
if (secondServerList) {
|
|
const secondServerListRect = scaleDOMRect(secondServerList.getBoundingClientRect(), layerScale, centerX, centerY);
|
|
baseChannelWidth += secondServerListRect.width;
|
|
}
|
|
// add the new style
|
|
BdApi.DOM.addStyle('wua-styles', styles);
|
|
updateVar('--sidebar-height', `${channelsRect.height + noticesHeight}px`);
|
|
updateVar('--user-area-width', `${channelsRect.right - serverListRect.left}px`);
|
|
updateVar('--user-area-left', `${serverListRect.left}px`);
|
|
updateVar('--user-area-bottom', `${bottom}px`);
|
|
userAreaObserver.observe(element);
|
|
}
|
|
onStart(() => {
|
|
window.addEventListener('resize', recalcDebounce);
|
|
updateVar('--notices-height', '0px');
|
|
themesObserver.observe(document.querySelector("bd-themes"), { childList: true, subtree: true });
|
|
});
|
|
onStop(() => {
|
|
userAreaObserver.disconnect();
|
|
channelsObserver.disconnect();
|
|
themesObserver.disconnect();
|
|
secondServerListObserver.disconnect();
|
|
noticesObserver.disconnect();
|
|
BdApi.DOM.removeStyle('wua-styles');
|
|
for (let varName of varsSet) {
|
|
BdApi.DOM.removeStyle(`wua-${varName}`);
|
|
}
|
|
window.removeEventListener('resize', recalcDebounce);
|
|
});
|
|
}
|
|
}
|