diff --git a/renderer/src/modules/domtools.js b/renderer/src/modules/domtools.js index f66d8551..bd6d1609 100644 --- a/renderer/src/modules/domtools.js +++ b/renderer/src/modules/domtools.js @@ -39,10 +39,34 @@ export default class DOMTools { + /** Document/window width */ + static get screenWidth() {return Math.max(document.documentElement.clientWidth, window.innerWidth || 0);} + + /** Document/window height */ + static get screenHeight() {return Math.max(document.documentElement.clientHeight, window.innerHeight || 0);} + static escapeID(id) { return id.replace(/^[^a-z]+|[^\w-]+/gi, "-"); } + // https://javascript.info/js-animation + static animate({timing = _ => _, update, duration}) { + const start = performance.now(); + + requestAnimationFrame(function animate(time) { + // timeFraction goes from 0 to 1 + let timeFraction = (time - start) / duration; + if (timeFraction > 1) timeFraction = 1; + + // calculate the current animation state + const progress = timing(timeFraction); + + update(progress); // draw it + + if (timeFraction < 1) requestAnimationFrame(animate); + }); + } + /** * Adds a style to the document. * @param {string} id - identifier to use as the element id @@ -87,27 +111,6 @@ export default class DOMTools { const element = document.getElementById(id); if (element) element.remove(); } - - // https://javascript.info/js-animation - static animate({timing = _ => _, update, duration}) { - const start = performance.now(); - - requestAnimationFrame(function animate(time) { - // timeFraction goes from 0 to 1 - let timeFraction = (time - start) / duration; - if (timeFraction > 1) timeFraction = 1; - - // calculate the current animation state - const progress = timing(timeFraction); - - update(progress); // draw it - - if (timeFraction < 1) { - requestAnimationFrame(animate); - } - - }); - } /** * This is my shit version of not having to use `$` from jQuery. Meaning diff --git a/renderer/src/modules/pluginapi.js b/renderer/src/modules/pluginapi.js index 4d71868a..7cace1ee 100644 --- a/renderer/src/modules/pluginapi.js +++ b/renderer/src/modules/pluginapi.js @@ -14,6 +14,7 @@ import Logger from "common/logger"; import Patcher from "./patcher"; import Emotes from "../builtins/emotes/emotes"; import ipc from "./ipc"; +import Tooltip from "../ui/tooltip"; /** * `BdApi` is a globally (`window.BdApi`) accessible object for use by plugins and developers to make their lives easier. @@ -162,6 +163,22 @@ BdApi.showToast = function(content, options = {}) { return Notices.show(content, options); }; +/** + * Creates a tooltip to automatically show on hover. + * + * @param {HTMLElement} node - DOM node to monitor and show the tooltip on + * @param {string|HTMLElement} content - string to show in the tooltip + * @param {object} options - additional options for the tooltip + * @param {"primary"|"info"|"success"|"warn"|"danger"} [options.style="primary"] - correlates to the discord styling/colors + * @param {"top"|"right"|"bottom"|"left"} [options.side="top"] - can be any of top, right, bottom, left + * @param {boolean} [options.preventFlip=false] - prevents moving the tooltip to the opposite side if it is too big or goes offscreen + * @param {boolean} [options.disabled=false] - whether the tooltip should be disabled from showing on hover + * @returns new Tooltip + */ + BdApi.createTooltip = function(node, content, options = {}) { + return Tooltip.create(node, content, options); +}; + /** * Finds a webpack module using a filter. * diff --git a/renderer/src/styles/ui/tooltip.css b/renderer/src/styles/ui/tooltip.css new file mode 100644 index 00000000..f6019d41 --- /dev/null +++ b/renderer/src/styles/ui/tooltip.css @@ -0,0 +1,104 @@ +.bd-layer { + position: absolute; +} + +.bd-tooltip { + position: relative; + border-radius: 5px; + font-weight: 500; + font-size: 14px; + line-height: 16px; + max-width: 190px; + box-sizing: border-box; + word-wrap: break-word; + z-index: 1002; + will-change: opacity, transform; + box-shadow: var(--elevation-high); + color: var(--header-primary); +} + +.bd-tooltip-content { + padding: 8px 12px; + overflow: hidden; + } + +.bd-tooltip-pointer { + pointer-events: none; + width: 0; + height: 0; + border: 5px solid transparent; +} + +.bd-tooltip-primary { + background-color: var(--background-floating); + color: var(--text-normal); +} + +.bd-tooltip-primary .bd-tooltip-pointer { + border-top-color: var(--background-floating); +} + +.bd-tooltip-info { + background-color: #4A90E2; +} + +.bd-tooltip-info .bd-tooltip-pointer { + border-top-color: #4A90E2; +} + +.bd-tooltip-success { + background-color: #43B581; +} + +.bd-tooltip-success .bd-tooltip-pointer { + border-top-color: #43B581; +} + +.bd-tooltip-danger { + background-color: #F04747; +} + +.bd-tooltip-danger .bd-tooltip-pointer { + border-top-color: #F04747; +} + +.bd-tooltip-warn { + background-color: #FFA600; +} + +.bd-tooltip-warn .bd-tooltip-pointer { + border-top-color: #FFA600; +} + +.bd-tooltip-top .bd-tooltip-pointer { + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; +} + +.bd-tooltip-bottom .bd-tooltip-pointer { + position: absolute; + bottom: 100%; + left: 50%; + margin-left: -5px; + transform: rotate(180deg); +} + +.bd-tooltip-right .bd-tooltip-pointer { + position: absolute; + right: 100%; + top: 50%; + margin-top: -5px; + border-left-width: 5px; + transform: rotate(90deg); +} + +.bd-tooltip-left .bd-tooltip-pointer { + position: absolute; + left: 100%; + top: 50%; + margin-top: -5px; + border-left-width: 5px; + transform: rotate(270deg); +} \ No newline at end of file diff --git a/renderer/src/ui/tooltip.js b/renderer/src/ui/tooltip.js new file mode 100644 index 00000000..5f30ca10 --- /dev/null +++ b/renderer/src/ui/tooltip.js @@ -0,0 +1,164 @@ +import Logger from "common/logger"; +import {DOM} from "modules"; + + +const toPx = function(value) { + return `${value}px`; +}; + +const styles = ["primary", "info", "success", "warn", "danger"]; +const sides = ["top", "right", "bottom", "left"]; + +export default class Tooltip { + /** + * + * @constructor + * @param {HTMLElement} node - DOM node to monitor and show the tooltip on + * @param {string|HTMLElement} tip - string to show in the tooltip + * @param {object} options - additional options for the tooltip + * @param {"primary"|"info"|"success"|"warn"|"danger"} [options.style="primary"] - correlates to the discord styling/colors + * @param {"top"|"right"|"bottom"|"left"} [options.side="top"] - can be any of top, right, bottom, left + * @param {boolean} [options.preventFlip=false] - prevents moving the tooltip to the opposite side if it is too big or goes offscreen + * @param {boolean} [options.disabled=false] - whether the tooltip should be disabled from showing on hover + */ + constructor(node, text, options = {}) { + const {style = "primary", side = "top", preventFlip = false, disabled = false} = options; + this.node = node; + this.label = text; + this.style = style.toLowerCase(); + this.side = side.toLowerCase(); + this.preventFlip = preventFlip; + this.disabled = disabled; + this.active = false; + + if (!sides.includes(this.side)) return Logger.err("Tooltip", `Side ${this.side} does not exist.`); + if (!styles.includes(this.style)) return Logger.err("Tooltip", `Style ${this.style} does not exist.`); + + this.element = DOM.createElement(`