ElementHighlighter: Remove Dupe

This commit is contained in:
thororen1234 2025-12-18 20:03:33 -05:00
parent 89e3dcc58e
commit 851e542b20
No known key found for this signature in database
2 changed files with 0 additions and 567 deletions

View file

@ -1,463 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./styles.css";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Button } from "@components/Button";
import { EquicordDevs, IS_MAC } from "@utils/constants";
import { copyWithToast } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import { useEffect, useState } from "@webpack/common";
const KeybindShortcut = findComponentByCodeLazy(".combo,", ".key,");
const cl = classNameFactory("vc-element-highlight-");
const DEFAULT_KEYBIND = ["mod", "shift", "y"];
let active = false;
let overlay: HTMLDivElement | null = null;
let tooltip: HTMLDivElement | null = null;
let lastTarget: Element | null = null;
let pendingFrame = 0;
let pendingX = 0;
let pendingY = 0;
let pendingElement: Element | null = null;
const colorCache = new WeakMap<Element, string | null>();
let cachedRules: { selector: string; specificity: number; color: string }[] = [];
function KeybindRecorder() {
const [isListening, setIsListening] = useState(false);
const currentKeybind = settings.use(["keybind"]).keybind;
useEffect(() => {
if (!isListening) return;
const handleKeyDown = (event: KeyboardEvent) => {
event.preventDefault();
event.stopPropagation();
if (["Control", "Shift", "Alt", "Meta"].includes(event.key)) return;
const keys: string[] = [];
if (event.metaKey) keys.push("meta");
if (event.ctrlKey) keys.push(IS_MAC ? "control" : "mod");
if (event.shiftKey) keys.push("shift");
if (event.altKey) keys.push("alt");
let mainKey = event.key.toLowerCase();
if (mainKey === " ") mainKey = "space";
if (mainKey === "escape") mainKey = "esc";
keys.push(mainKey);
settings.store.keybind = keys;
setIsListening(false);
};
const handleBlur = () => setIsListening(false);
document.addEventListener("keydown", handleKeyDown, true);
window.addEventListener("blur", handleBlur);
return () => {
document.removeEventListener("keydown", handleKeyDown, true);
window.removeEventListener("blur", handleBlur);
};
}, [isListening]);
return (
<div className={cl("keybind-row")}>
<button
type="button"
className={cl("keybind-button", isListening ? "listening" : "")}
onClick={() => setIsListening(true)}
>
{isListening ? "Recording..." : <KeybindShortcut shortcut={currentKeybind.join("+")} />}
</button>
<Button
size="small"
variant="secondary"
onClick={() => { settings.store.keybind = DEFAULT_KEYBIND; }}
>
Reset
</Button>
</div>
);
}
const settings = definePluginSettings({
keybind: {
description: "Toggle Highlighter",
type: OptionType.COMPONENT,
default: DEFAULT_KEYBIND,
component: KeybindRecorder
},
showClasses: {
description: "Display the element's CSS class names in the tooltip.",
type: OptionType.BOOLEAN,
default: false
},
showId: {
description: "Display the element's ID attribute in the tooltip.",
type: OptionType.BOOLEAN,
default: false
},
showFont: {
description: "Display the computed font family and font size.",
type: OptionType.BOOLEAN,
default: false
},
showPadding: {
description: "Display the element's padding values.",
type: OptionType.BOOLEAN,
default: false
},
showMargin: {
description: "Display the element's margin values.",
type: OptionType.BOOLEAN,
default: false
},
showBorderRadius: {
description: "Display the element's border-radius values.",
type: OptionType.BOOLEAN,
default: false
},
showPosition: {
description: "Display the element's CSS position type and z-index.",
type: OptionType.BOOLEAN,
default: false
},
showDisplay: {
description: "Display the element's display type along with flex or grid properties.",
type: OptionType.BOOLEAN,
default: false
}
});
function calcSpecificity(sel: string): number {
const ids = (sel.match(/#[\w-]+/g) ?? []).length;
const classes = (sel.match(/\.[\w-]+/g) ?? []).length + (sel.match(/\[[\w-]+/g) ?? []).length;
const tags = (sel.match(/(^|[\s>+~])[\w-]+/g) ?? []).length;
return ids * 10000 + classes * 100 + tags;
}
function cacheRules() {
cachedRules = [];
for (const sheet of document.styleSheets) {
try {
for (const rule of sheet.cssRules) {
if (!(rule instanceof CSSStyleRule)) continue;
const color = rule.style.getPropertyValue("color");
if (!color) continue;
for (const sel of rule.selectorText.split(",")) {
const s = sel.trim();
cachedRules.push({ selector: s, specificity: calcSpecificity(s), color });
}
}
} catch { }
}
cachedRules.sort((a, b) => a.specificity - b.specificity);
}
function getColorVarFromElement(el: Element): string | null {
if (el instanceof HTMLElement) {
const inline = el.style.getPropertyValue("color");
if (inline) return inline;
}
const styleAttr = el.getAttribute("style");
if (styleAttr) {
const match = styleAttr.match(/color\s*:\s*([^;]+)/);
if (match) return match[1].trim();
}
let result: string | null = null;
for (const r of cachedRules) {
try {
if (el.matches(r.selector)) result = r.color;
} catch { }
}
return result;
}
function getColorVar(el: Element): string | null {
const cached = colorCache.get(el);
if (cached !== undefined) return cached;
let current: Element | null = el;
while (current) {
const color = getColorVarFromElement(current);
if (color) {
colorCache.set(el, color);
return color;
}
current = current.parentElement;
}
colorCache.set(el, null);
return null;
}
function rgbToHex(rgb: string): string {
const match = rgb.match(/\d+/g);
if (!match || match.length < 3) return rgb;
const [r, g, b] = match.map(Number);
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
}
function formatBoxValue(value: string): string | null {
if (value === "0px" || value === "0px 0px 0px 0px") return null;
return value;
}
function buildTooltipContent(el: Element, computed: CSSStyleDeclaration, rect: DOMRect): string {
const tag = el.tagName.toLowerCase();
const colorVar = getColorVar(el) ?? computed.color;
const hex = rgbToHex(computed.color);
let html = `<span class="${cl("tag")}">&lt;${tag}&gt;</span> `;
html += `<span class="${cl("size")}">${Math.round(rect.width)}x${Math.round(rect.height)}</span>`;
html += `<div class="${cl("color")}"><span class="${cl("swatch")}" style="--c:${computed.color}"></span>${colorVar}</div>`;
html += `<div class="${cl("hex")}">${hex}</div>`;
const { store } = settings;
if (store.showId) {
const { id } = el;
if (id) html += `<div class="${cl("info")}"><span class="${cl("label")}">id:</span> #${id}</div>`;
}
if (store.showClasses) {
const classes = el.className;
if (classes && typeof classes === "string") {
const truncated = classes.length > 60 ? classes.slice(0, 60) + "…" : classes;
html += `<div class="${cl("info")}"><span class="${cl("label")}">class:</span> ${truncated}</div>`;
}
}
if (store.showFont) {
const font = computed.fontFamily.split(",")[0].replace(/["']/g, "");
const size = computed.fontSize;
html += `<div class="${cl("info")}"><span class="${cl("label")}">font:</span> ${font} ${size}</div>`;
}
if (store.showPadding) {
const padding = formatBoxValue(computed.padding);
if (padding) html += `<div class="${cl("info")}"><span class="${cl("label")}">padding:</span> ${padding}</div>`;
}
if (store.showMargin) {
const margin = formatBoxValue(computed.margin);
if (margin) html += `<div class="${cl("info")}"><span class="${cl("label")}">margin:</span> ${margin}</div>`;
}
if (store.showBorderRadius) {
const radius = formatBoxValue(computed.borderRadius);
if (radius) html += `<div class="${cl("info")}"><span class="${cl("label")}">radius:</span> ${radius}</div>`;
}
if (store.showPosition) {
const pos = computed.position;
if (pos !== "static") {
const zIndex = computed.zIndex !== "auto" ? ` z:${computed.zIndex}` : "";
html += `<div class="${cl("info")}"><span class="${cl("label")}">position:</span> ${pos}${zIndex}</div>`;
}
}
if (store.showDisplay) {
const { display } = computed;
let extra = "";
if (display === "flex" || display === "inline-flex") {
extra = ` ${computed.flexDirection} gap:${computed.gap}`;
} else if (display === "grid" || display === "inline-grid") {
extra = ` gap:${computed.gap}`;
}
html += `<div class="${cl("info")}"><span class="${cl("label")}">display:</span> ${display}${extra}</div>`;
}
return html;
}
function updateFrame() {
pendingFrame = 0;
const el = pendingElement;
if (!el || !overlay || !tooltip) return;
const rect = el.getBoundingClientRect();
overlay.style.transform = `translate3d(${rect.left}px,${rect.top}px,0)`;
overlay.style.width = `${rect.width}px`;
overlay.style.height = `${rect.height}px`;
if (el !== lastTarget) {
lastTarget = el;
const computed = getComputedStyle(el);
tooltip.innerHTML = buildTooltipContent(el, computed, rect);
}
positionTooltip(pendingX, pendingY);
}
function positionTooltip(x: number, y: number) {
if (!tooltip) return;
const tw = tooltip.offsetWidth;
const th = tooltip.offsetHeight;
const margin = 15;
const edge = 10;
const vw = window.innerWidth;
const vh = window.innerHeight;
let left = x + margin;
let top = y + margin;
if (left + tw + edge > vw) {
left = x - margin - tw;
if (left < edge) left = Math.max(edge, vw - tw - edge);
}
if (top + th + edge > vh) {
top = y - margin - th;
if (top < edge) top = Math.max(edge, vh - th - edge);
}
tooltip.style.transform = `translate3d(${left}px,${top}px,0)`;
}
function scheduleFrame(el: Element, x: number, y: number) {
pendingElement = el;
pendingX = x;
pendingY = y;
if (!pendingFrame) {
pendingFrame = requestAnimationFrame(updateFrame);
}
}
function onMouseMove(e: MouseEvent) {
const el = document.elementFromPoint(e.clientX, e.clientY);
if (!el || el === overlay || el === tooltip || tooltip?.contains(el)) return;
scheduleFrame(el, e.clientX, e.clientY);
}
function onClick(e: MouseEvent) {
e.preventDefault();
e.stopPropagation();
const el = document.elementFromPoint(e.clientX, e.clientY);
if (!el || el === overlay || el === tooltip) return;
const colorVar = getColorVar(el);
if (colorVar) copyWithToast(colorVar, "Copied color to clipboard!");
}
function onKeyDown(e: KeyboardEvent) {
if (e.key === "Escape") disable();
}
function enable() {
active = true;
cacheRules();
overlay = document.createElement("div");
overlay.className = cl("overlay");
document.body.appendChild(overlay);
tooltip = document.createElement("div");
tooltip.className = cl("tooltip");
document.body.appendChild(tooltip);
document.addEventListener("mousemove", onMouseMove, { capture: true, passive: true });
document.addEventListener("click", onClick, true);
document.addEventListener("keydown", onKeyDown);
document.body.classList.add(cl("active"));
}
function disable() {
active = false;
lastTarget = null;
pendingElement = null;
if (pendingFrame) {
cancelAnimationFrame(pendingFrame);
pendingFrame = 0;
}
overlay?.remove();
overlay = null;
tooltip?.remove();
tooltip = null;
document.removeEventListener("mousemove", onMouseMove, true);
document.removeEventListener("click", onClick, true);
document.removeEventListener("keydown", onKeyDown);
document.body.classList.remove(cl("active"));
}
function getConfiguredKeybind(): string[] {
const raw = settings.store.keybind;
if (Array.isArray(raw) && raw.length > 0) return raw;
return DEFAULT_KEYBIND;
}
function matchesKeybind(e: KeyboardEvent): boolean {
const keybind = getConfiguredKeybind();
const pressed = e.key.toLowerCase();
const code = e.code.toLowerCase().replace("key", "").replace("digit", "");
let hasNonModifier = false;
for (const key of keybind) {
switch (key) {
case "mod":
if (!(IS_MAC ? e.metaKey : e.ctrlKey)) return false;
break;
case "control":
case "ctrl":
if (!e.ctrlKey) return false;
break;
case "meta":
case "cmd":
if (!e.metaKey) return false;
break;
case "shift":
if (!e.shiftKey) return false;
break;
case "alt":
if (!e.altKey) return false;
break;
default:
hasNonModifier = true;
if (pressed !== key && code !== key) return false;
}
}
return hasNonModifier;
}
function onToggle(e: KeyboardEvent) {
if (matchesKeybind(e)) {
e.preventDefault();
active ? disable() : enable();
}
}
export default definePlugin({
name: "ElementHighlighter",
description: "Highlight and inspect elements easily.",
authors: [EquicordDevs.Prism],
settings,
start() {
document.addEventListener("keydown", onToggle);
},
stop() {
document.removeEventListener("keydown", onToggle);
if (active) disable();
}
});

View file

@ -1,104 +0,0 @@
.vc-element-highlight-active {
cursor: crosshair !important;
}
.vc-element-highlight-overlay {
position: fixed;
top: 0;
left: 0;
z-index: 999998;
border: 2px solid var(--text-brand);
background: var(--brand-15a);
border-radius: 4px;
pointer-events: none;
will-change: transform, width, height;
contain: layout style;
}
.vc-element-highlight-tooltip {
position: fixed;
top: 0;
left: 0;
z-index: 999999;
background: rgb(0 0 0 / 70%);
backdrop-filter: blur(12px);
border: 1px solid rgb(255 255 255 / 10%);
border-radius: 6px;
padding: 6px 10px;
font-family: var(--font-code);
font-size: 12px;
line-height: 1.4;
color: var(--text-default);
pointer-events: none;
white-space: nowrap;
will-change: transform;
contain: layout style;
}
.vc-element-highlight-tag {
color: var(--text-muted);
}
.vc-element-highlight-size {
color: var(--text-muted);
margin-left: 6px;
}
.vc-element-highlight-color {
margin-top: 4px;
}
.vc-element-highlight-swatch {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 2px;
border: 1px solid rgb(255 255 255 / 20%);
vertical-align: middle;
margin-right: 6px;
background:
linear-gradient(var(--c), var(--c)),
repeating-conic-gradient(rgb(255 255 255 / 20%) 0% 25%, transparent 0% 50%) 0 0 / 4px 4px;
}
.vc-element-highlight-hex {
color: var(--text-muted);
font-size: 11px;
margin-top: 2px;
}
.vc-element-highlight-info {
color: var(--text-muted);
font-size: 11px;
margin-top: 2px;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
}
.vc-element-highlight-label {
color: var(--text-subtle);
}
.vc-element-highlight-keybind-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.vc-element-highlight-keybind-button {
display: flex;
align-items: center;
background: transparent;
border: none;
padding: 0;
cursor: pointer;
}
.vc-element-highlight-keybind-button.vc-element-highlight-listening {
padding: 4px 8px;
background-color: var(--input-background);
border-radius: 4px;
color: var(--text-muted);
}