mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-15 09:33:03 -04:00
Merge branch 'dev'
This commit is contained in:
commit
ea5e4f54bc
14 changed files with 573 additions and 54 deletions
|
@ -10,7 +10,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
||||||
|
|
||||||
### Extra included plugins
|
### Extra included plugins
|
||||||
<details>
|
<details>
|
||||||
<summary>125 additional plugins</summary>
|
<summary>126 additional plugins</summary>
|
||||||
|
|
||||||
- AllCallTimers by MaxHerbold & D3SOX
|
- AllCallTimers by MaxHerbold & D3SOX
|
||||||
- AltKrispSwitch by newwares
|
- AltKrispSwitch by newwares
|
||||||
|
@ -67,6 +67,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
||||||
- InRole by nin0dev
|
- InRole by nin0dev
|
||||||
- IrcColors by Grzesiek11
|
- IrcColors by Grzesiek11
|
||||||
- IRememberYou by zoodogood
|
- IRememberYou by zoodogood
|
||||||
|
- ImagePreview by Creation's
|
||||||
- Jumpscare by Surgedevs
|
- Jumpscare by Surgedevs
|
||||||
- JumpToStart by Samwich
|
- JumpToStart by Samwich
|
||||||
- KeyboardSounds by HypedDomi
|
- KeyboardSounds by HypedDomi
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
// @author Equicord (https://github.com/Equicord)
|
// @author Equicord (https://github.com/Equicord)
|
||||||
// @namespace https://github.com/Equicord/Equicord
|
// @namespace https://github.com/Equicord/Equicord
|
||||||
// @supportURL https://github.com/Equicord/Equicord
|
// @supportURL https://github.com/Equicord/Equicord
|
||||||
|
// @icon https://raw.githubusercontent.com/Equicord/Equicord/refs/heads/main/browser/icon.png
|
||||||
// @license GPL-3.0
|
// @license GPL-3.0
|
||||||
// @match *://*.discord.com/*
|
// @match *://*.discord.com/*
|
||||||
// @grant GM_xmlhttpRequest
|
// @grant GM_xmlhttpRequest
|
||||||
|
|
|
@ -17,8 +17,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '.BADGES=1]="BADGES"',
|
find: '.BADGES=1]="BADGES"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i)=\{className:\i.username,style:.*?onContextMenu:\i,children:.*?\};/,
|
match: /(\i)=\{className:\i.username,style:.*?onContextMenu:\i,children:.*?\},/,
|
||||||
replace: "$&$1.children=$self.wrapMessageAuthor(arguments[0],$1.children);"
|
replace: "$&__dummyvar=($1.children=$self.wrapMessageAuthor(arguments[0],$1.children)),"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
397
src/equicordplugins/imagePreview/index.ts
Normal file
397
src/equicordplugins/imagePreview/index.ts
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
|
import { EquicordDevs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
|
||||||
|
const eventListeners: { element: HTMLElement, handler: (e: any) => void; }[] = [];
|
||||||
|
let lastHoveredElement: HTMLElement | null = null;
|
||||||
|
|
||||||
|
const mimeTypes = {
|
||||||
|
jpg: "image/jpeg",
|
||||||
|
jpeg: "image/jpeg",
|
||||||
|
png: "image/png",
|
||||||
|
gif: "image/gif",
|
||||||
|
webp: "image/webp",
|
||||||
|
svg: "image/svg+xml",
|
||||||
|
mp4: "video/mp4",
|
||||||
|
webm: "video/webm",
|
||||||
|
mov: "video/quicktime",
|
||||||
|
};
|
||||||
|
|
||||||
|
function getMimeType(extension: string | undefined): [boolean, string] {
|
||||||
|
if (!extension) return [false, ""];
|
||||||
|
|
||||||
|
const lowerExt = extension.trim().toLowerCase();
|
||||||
|
return [!!mimeTypes[lowerExt], mimeTypes[lowerExt] || ""];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addHoverEffect(element: HTMLElement, type: string) {
|
||||||
|
let hoverElementActual;
|
||||||
|
|
||||||
|
if (settings.store.hoverOutline) {
|
||||||
|
if (type === "messageImages") {
|
||||||
|
hoverElementActual = element.closest('[id^="message-accessories-"]')?.querySelector('div')?.querySelector('div') || element;
|
||||||
|
|
||||||
|
if (!(hoverElementActual instanceof HTMLDivElement)) {
|
||||||
|
hoverElementActual = element;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hoverElementActual = element.querySelector("img") || element;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverElementActual.style.outline = `${settings.store.hoverOutlineSize} dotted ${settings.store.hoverOutlineColor}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = element.getAttribute("data-safe-src") || element.getAttribute("src") || element.getAttribute("href") || element.textContent;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
hoverElementActual.style.outline = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const strippedUrl = stripDiscordParams(url);
|
||||||
|
const fileName: string = strippedUrl.split("/").pop()?.split(/[?#&]/)[0] || "unknown";
|
||||||
|
const [allowed, mimeType] = getMimeType(fileName.split(".").pop());
|
||||||
|
|
||||||
|
if (!allowed) {
|
||||||
|
hoverElementActual.style.outline = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isImage = allowed && mimeType.startsWith("image");
|
||||||
|
const isVideo = allowed && mimeType.startsWith("video");
|
||||||
|
|
||||||
|
const previewDiv = document.createElement("div");
|
||||||
|
previewDiv.classList.add("preview-div");
|
||||||
|
|
||||||
|
let mediaElement;
|
||||||
|
if (isImage) {
|
||||||
|
mediaElement = document.createElement("img");
|
||||||
|
mediaElement.src = strippedUrl;
|
||||||
|
mediaElement.alt = fileName;
|
||||||
|
} else if (isVideo) {
|
||||||
|
mediaElement = document.createElement("video");
|
||||||
|
mediaElement.src = strippedUrl;
|
||||||
|
mediaElement.autoplay = true;
|
||||||
|
mediaElement.muted = true;
|
||||||
|
mediaElement.loop = true;
|
||||||
|
mediaElement.controls = false;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewHeader = document.createElement("div");
|
||||||
|
previewHeader.classList.add("preview-header");
|
||||||
|
|
||||||
|
const mimeSpan = document.createElement("span");
|
||||||
|
mimeSpan.textContent = `MIME: ${mimeType}`;
|
||||||
|
previewHeader.appendChild(mimeSpan);
|
||||||
|
|
||||||
|
const fileNameSpan = document.createElement("span");
|
||||||
|
fileNameSpan.classList.add("file-name");
|
||||||
|
fileNameSpan.textContent = fileName;
|
||||||
|
previewHeader.appendChild(fileNameSpan);
|
||||||
|
|
||||||
|
const dimensionsDiv = document.createElement("div");
|
||||||
|
dimensionsDiv.classList.add("dimensions-div");
|
||||||
|
|
||||||
|
const dimensionsDisplaying = document.createElement("span");
|
||||||
|
dimensionsDisplaying.classList.add("dimensions-displaying");
|
||||||
|
const dimensionsOriginal = document.createElement("span");
|
||||||
|
dimensionsOriginal.classList.add("dimensions-original");
|
||||||
|
|
||||||
|
mediaElement.onload = mediaElement.onloadstart = () => {
|
||||||
|
if (isImage) {
|
||||||
|
dimensionsDisplaying.textContent = `Displaying: ${mediaElement.width}x${mediaElement.height}`;
|
||||||
|
dimensionsOriginal.textContent = `Original: ${mediaElement.naturalWidth}x${mediaElement.naturalHeight}`;
|
||||||
|
} else if (isVideo) {
|
||||||
|
dimensionsDisplaying.textContent = `Displaying: ${mediaElement.videoWidth}x${mediaElement.videoHeight}`;
|
||||||
|
dimensionsOriginal.textContent = `Original: ${mediaElement.videoWidth}x${mediaElement.videoHeight}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaElement.width < 200) {
|
||||||
|
previewHeader.style.flexDirection = "column";
|
||||||
|
previewHeader.style.alignItems = "center";
|
||||||
|
previewHeader.style.justifyContent = "center";
|
||||||
|
dimensionsDiv.style.textAlign = "center";
|
||||||
|
dimensionsDiv.style.alignItems = "center";
|
||||||
|
previewHeader.style.gap = "5px";
|
||||||
|
previewHeader.insertBefore(fileNameSpan, previewHeader.firstChild);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dimensionsDiv.appendChild(dimensionsDisplaying);
|
||||||
|
dimensionsDiv.appendChild(dimensionsOriginal);
|
||||||
|
previewHeader.appendChild(dimensionsDiv);
|
||||||
|
|
||||||
|
previewDiv.appendChild(previewHeader);
|
||||||
|
previewDiv.appendChild(mediaElement);
|
||||||
|
|
||||||
|
document.body.appendChild(previewDiv);
|
||||||
|
|
||||||
|
const hoverDelay = settings.store.hoverDelay * 1000;
|
||||||
|
let timeout;
|
||||||
|
|
||||||
|
const showPreview = () => {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
previewDiv.style.display = "block";
|
||||||
|
hoverElementActual.style.outline = `${settings.store.hoverOutlineSize} dotted ${settings.store.hoverOutlineColor}`;
|
||||||
|
positionPreviewDiv(previewDiv, null);
|
||||||
|
}, hoverDelay);
|
||||||
|
};
|
||||||
|
|
||||||
|
const movePreviewListener: (e: MouseEvent) => void = (e) => {
|
||||||
|
positionPreviewDiv(previewDiv, e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePreview = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
previewDiv.remove();
|
||||||
|
document.removeEventListener("mousemove", movePreviewListener);
|
||||||
|
if (hoverElementActual) {
|
||||||
|
hoverElementActual.style.outline = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
element.addEventListener("mouseenter", showPreview);
|
||||||
|
element.addEventListener("mouseleave", removePreview);
|
||||||
|
document.addEventListener("mousemove", movePreviewListener);
|
||||||
|
|
||||||
|
eventListeners.push({ element, handler: showPreview });
|
||||||
|
eventListeners.push({ element, handler: removePreview });
|
||||||
|
eventListeners.push({ element: previewDiv, handler: movePreviewListener });
|
||||||
|
|
||||||
|
function positionPreviewDiv(previewDiv: HTMLElement, e: MouseEvent | null) {
|
||||||
|
const previewWidth = previewDiv.offsetWidth;
|
||||||
|
const previewHeight = previewDiv.offsetHeight;
|
||||||
|
const pageWidth = window.innerWidth;
|
||||||
|
const pageHeight = window.innerHeight;
|
||||||
|
|
||||||
|
const mouseX = e ? e.pageX : window.innerWidth / 2;
|
||||||
|
const mouseY = e ? e.pageY : window.innerHeight / 2;
|
||||||
|
|
||||||
|
let left = mouseX + 10;
|
||||||
|
let top = mouseY + 10;
|
||||||
|
|
||||||
|
if (left + previewWidth > pageWidth) {
|
||||||
|
left = pageWidth - previewWidth - 10;
|
||||||
|
}
|
||||||
|
if (top + previewHeight > pageHeight) {
|
||||||
|
top = pageHeight - previewHeight - 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewDiv.style.left = `${left}px`;
|
||||||
|
previewDiv.style.top = `${top}px`;
|
||||||
|
|
||||||
|
const maxImageWidth = pageWidth - 20;
|
||||||
|
const maxImageHeight = pageHeight - 20;
|
||||||
|
|
||||||
|
if (isImage) {
|
||||||
|
if (mediaElement.naturalWidth > maxImageWidth || mediaElement.naturalHeight > maxImageHeight) {
|
||||||
|
const aspectRatio = mediaElement.naturalWidth / mediaElement.naturalHeight;
|
||||||
|
|
||||||
|
if (mediaElement.naturalWidth > maxImageWidth) {
|
||||||
|
mediaElement.width = maxImageWidth;
|
||||||
|
mediaElement.height = maxImageWidth / aspectRatio;
|
||||||
|
}
|
||||||
|
if (mediaElement.height > maxImageHeight) {
|
||||||
|
mediaElement.height = maxImageHeight;
|
||||||
|
mediaElement.width = maxImageHeight * aspectRatio;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mediaElement.width = mediaElement.naturalWidth;
|
||||||
|
mediaElement.height = mediaElement.naturalHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dimensionsDisplaying.textContent = isImage ? `Displaying: ${mediaElement.width}x${mediaElement.height}` : `Displaying: ${mediaElement.videoWidth}x${mediaElement.videoHeight}`;
|
||||||
|
dimensionsOriginal.textContent = isImage ? `Original: ${mediaElement.naturalWidth}x${mediaElement.naturalHeight}` : `Original: ${mediaElement.videoWidth}x${mediaElement.videoHeight}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHover(elements: NodeListOf<HTMLElement> | HTMLElement[], type: string) {
|
||||||
|
elements.forEach((el) => {
|
||||||
|
if (!el.dataset.hoverListenerAdded) {
|
||||||
|
const handler = () => addHoverEffect(el, type);
|
||||||
|
el.addEventListener("mouseover", handler);
|
||||||
|
el.dataset.hoverListenerAdded = "true";
|
||||||
|
eventListeners.push({ element: el, handler });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLinkAnImage(url: string) {
|
||||||
|
const extension = url.split(".").pop();
|
||||||
|
const [isImage,] = getMimeType(extension);
|
||||||
|
return isImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripDiscordParams(url: string) {
|
||||||
|
let newUrl = url.replace(/([?&])(width|size|height|h|w)=[^&]+/g, "");
|
||||||
|
|
||||||
|
newUrl = newUrl.replace(/([?&])quality=[^&]*/g, "$1quality=lossless");
|
||||||
|
|
||||||
|
newUrl = newUrl.replace(/([?&])+$/, "")
|
||||||
|
.replace(/\?&/, "?")
|
||||||
|
.replace(/\?$/, "")
|
||||||
|
.replace(/&{2,}/g, "&");
|
||||||
|
|
||||||
|
if (newUrl.includes("quality=lossless") && !newUrl.includes("?")) {
|
||||||
|
newUrl = newUrl.replace(/&quality=lossless/, "?quality=lossless");
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
messageImages: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Enable Message Images Hover Detection",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
messageAvatars: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Enable Message Avatars Hover Detection",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
messageLinks: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Enable Message Links Hover Detection",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
messageStickers: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Enable Message Stickers Hover Detection",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
hoverOutline: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Enable Hover Outline on Elements",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
hoverOutlineColor: {
|
||||||
|
type: OptionType.STRING,
|
||||||
|
description: "Hover Outline Color",
|
||||||
|
default: "red",
|
||||||
|
},
|
||||||
|
hoverOutlineSize: {
|
||||||
|
type: OptionType.STRING,
|
||||||
|
description: "Hover Outline Size",
|
||||||
|
default: "1px",
|
||||||
|
},
|
||||||
|
hoverDelay: {
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
description: "Display Hover Delay (seconds)",
|
||||||
|
markers: [0, 1, 2, 3, 4, 5],
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Image Preview",
|
||||||
|
description: "Hover on images, avatars, links, guild icons, and stickers to show a full preview.",
|
||||||
|
authors: [EquicordDevs.creations],
|
||||||
|
settings: settings,
|
||||||
|
|
||||||
|
start() {
|
||||||
|
function initialScan() {
|
||||||
|
const appContainer = document.querySelector('[class*="app-"]');
|
||||||
|
if (appContainer) {
|
||||||
|
if (settings.store.messageImages) {
|
||||||
|
handleHover(appContainer.querySelectorAll('[data-role="img"]'), "messageImages");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.store.messageAvatars) {
|
||||||
|
handleHover(appContainer.querySelectorAll('img[src*="cdn.discordapp.com/avatars/"], img[src*="cdn.discordapp.com/guilds/"], img[src^="/assets/"][class*="avatar"]'), "messageAvatars");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.store.messageLinks) {
|
||||||
|
appContainer.querySelectorAll("span").forEach((span) => {
|
||||||
|
const url = span.textContent?.replace(/<[^>]*>?/gm, "");
|
||||||
|
if (url && (url.startsWith("http://") || url.startsWith("https://")) && isLinkAnImage(url)) {
|
||||||
|
handleHover([span], "messageLinks");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.store.messageStickers) {
|
||||||
|
handleHover(appContainer.querySelectorAll('img[data-type="sticker"]'), "messageStickers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.type === "childList") {
|
||||||
|
mutation.addedNodes.forEach((addedNode) => {
|
||||||
|
if (addedNode instanceof HTMLElement) {
|
||||||
|
const element = addedNode as HTMLElement;
|
||||||
|
|
||||||
|
if (lastHoveredElement === element) return;
|
||||||
|
lastHoveredElement = element;
|
||||||
|
|
||||||
|
if (settings.store.messageImages) {
|
||||||
|
handleHover(element.querySelectorAll('[data-role="img"]'), "messageImages");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.store.messageAvatars) {
|
||||||
|
handleHover(element.querySelectorAll('img[src*="cdn.discordapp.com/avatars/"], img[src*="cdn.discordapp.com/guilds/"], img[src^="/assets/"][class*="avatar"]'), "messageAvatars");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.store.messageLinks) {
|
||||||
|
element.querySelectorAll("span").forEach((span) => {
|
||||||
|
const url = span.textContent?.replace(/<[^>]*>?/gm, "");
|
||||||
|
if (url && (url.startsWith("http://") || url.startsWith("https://")) && isLinkAnImage(url)) {
|
||||||
|
handleHover([span], "messageLinks");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.store.messageStickers) {
|
||||||
|
handleHover(element.querySelectorAll('img[data-type="sticker"]'), "messageStickers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const appContainer = document.querySelector('[class*="app-"]');
|
||||||
|
if (appContainer) {
|
||||||
|
observer.observe(appContainer, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
initialScan();
|
||||||
|
|
||||||
|
this.observer = observer;
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.observer.disconnect();
|
||||||
|
|
||||||
|
eventListeners.forEach(({ element, handler }) => {
|
||||||
|
element.removeEventListener("mouseover", handler);
|
||||||
|
element.removeEventListener("mouseenter", handler);
|
||||||
|
element.removeEventListener("mouseleave", handler);
|
||||||
|
element.removeEventListener("mousemove", handler);
|
||||||
|
});
|
||||||
|
|
||||||
|
eventListeners.length = 0;
|
||||||
|
|
||||||
|
document.querySelectorAll("[data-hover-listener-added]").forEach((el) => {
|
||||||
|
el.removeAttribute("data-hover-listener-added");
|
||||||
|
(el as HTMLElement).style.outline = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".preview-div").forEach((preview) => {
|
||||||
|
preview.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
63
src/equicordplugins/imagePreview/styles.css
Normal file
63
src/equicordplugins/imagePreview/styles.css
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
.preview-div {
|
||||||
|
position: fixed;
|
||||||
|
border: 2px solid var(--background-tertiary);
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999;
|
||||||
|
display: none;
|
||||||
|
max-width: 100vw;
|
||||||
|
max-height: 96vh;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 20px rgba(0 0 0 / 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--text-normal);
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
display: block;
|
||||||
|
word-break: break-all;
|
||||||
|
max-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-div img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-div video {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-header {
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dimensions-div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: flex-end;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dimensions-displaying,
|
||||||
|
.dimensions-original {
|
||||||
|
display: block;
|
||||||
|
}
|
|
@ -8,11 +8,11 @@ export const images = {
|
||||||
cross: "https://i.imgur.com/XxRnu3b.png",
|
cross: "https://i.imgur.com/XxRnu3b.png",
|
||||||
deviceImage: {
|
deviceImage: {
|
||||||
success:
|
success:
|
||||||
"https://github.com/nexpid/Themelings/blob/3a063c5188f4cac096171f29163f9e2659f275a3/icons/images/native/img_remote_auth_succeeded.png",
|
"https://github.com/nexpid/Themelings/raw/data/icons/images/native/img_remote_auth_succeeded.png",
|
||||||
notFound:
|
notFound:
|
||||||
"https://github.com/nexpid/Themelings/blob/3a063c5188f4cac096171f29163f9e2659f275a3/icons/images/native/img_remote_auth_not_found.png",
|
"https://github.com/nexpid/Themelings/raw/data/icons/images/native/img_remote_auth_not_found.png",
|
||||||
loading:
|
loading:
|
||||||
"https://github.com/nexpid/Themelings/blob/3a063c5188f4cac096171f29163f9e2659f275a3/icons/images/native/img_remote_auth_loaded.png",
|
"https://github.com/nexpid/Themelings/raw/data/icons/images/native/img_remote_auth_loaded.png",
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { EquicordDevs } from "@utils/constants";
|
import { EquicordDevs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Button, Forms, i18n, Menu, TabBar } from "@webpack/common";
|
import { Button, Forms, i18n, Menu } from "@webpack/common";
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
import { preload, unload } from "./images";
|
import { preload, unload } from "./images";
|
||||||
import { cl, QrCodeIcon } from "./ui";
|
import { cl } from "./ui";
|
||||||
import openQrModal from "./ui/modals/QrModal";
|
import openQrModal from "./ui/modals/QrModal";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
|
@ -62,25 +62,31 @@ export default definePlugin({
|
||||||
replace: ",$self.insertScanQrButton($1)",
|
replace: ",$self.insertScanQrButton($1)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Insert a Scan QR Code MenuItem in the simplified user popout
|
// Insert a Scan QR Code MenuItem in the Swith Accounts popout
|
||||||
{
|
{
|
||||||
find: "Messages.MULTI_ACCOUNT_MENU_LABEL",
|
find: ".SWITCH_ACCOUNTS_MANAGE_ACCOUNTS,",
|
||||||
replacement: {
|
replacement: {
|
||||||
// Insert our own MenuItem before the Switch Accounts button
|
match: /(id:"manage-accounts",.*?)}\)\)(,\i)/,
|
||||||
match: /children:\[(.{0,54}id:"switch-accounts")/,
|
replace: "$1}),$self.ScanQrMenuItem)$2"
|
||||||
replace: "children:[$self.ScanQrMenuItem,$1",
|
}
|
||||||
},
|
},
|
||||||
},
|
|
||||||
// Add a Scan QR entry to the settings TabBar
|
// Insert a Scan QR Code button in the Settings sheet
|
||||||
{
|
{
|
||||||
find: ".BILLING_SETTINGS,",
|
find: "useGenerateUserSettingsSections",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /((\i\.settings)\.forEach.+?(\i).push\(.+}\)}\))/,
|
match: /(\.FRIEND_REQUESTS)/,
|
||||||
replace: (_, original, settings, elements) =>
|
replace: "$1,\"SCAN_QR_CODE\""
|
||||||
`${original},${settings}?.[0]=="ACCOUNT"` +
|
}
|
||||||
`&&${elements}.push({section:"CUSTOM",element:$self.ScanQrTabBarComponent})`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
// Insert a Scan QR Code button in the Settings sheet (part 2)
|
||||||
|
{
|
||||||
|
find: ".PRIVACY_ENCRYPTION_VERIFIED_DEVICES_V2]",
|
||||||
|
replacement: {
|
||||||
|
match: /(\.CLIPS]:{.*?},)/,
|
||||||
|
replace: "$1\"SCAN_QR_CODE\":$self.ScanQrSettingsSheet,"
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
qrModalOpen: false,
|
qrModalOpen: false,
|
||||||
|
@ -93,26 +99,18 @@ export default definePlugin({
|
||||||
{button}
|
{button}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|
||||||
get ScanQrMenuItem() {
|
get ScanQrMenuItem() {
|
||||||
return (
|
return <Menu.MenuItem id="scan-qr" label={i18n.Messages.USER_SETTINGS_SCAN_QR_CODE} action={openQrModal} />;
|
||||||
<Menu.MenuGroup>
|
},
|
||||||
<Menu.MenuItem
|
get ScanQrSettingsSheet() {
|
||||||
id="scan-qr"
|
return {
|
||||||
label={i18n.Messages.USER_SETTINGS_SCAN_QR_CODE}
|
section: i18n.Messages.USER_SETTINGS_SCAN_QR_CODE,
|
||||||
icon={QrCodeIcon}
|
onClick: openQrModal,
|
||||||
action={openQrModal}
|
searchableTitles: [i18n.Messages.USER_SETTINGS_SCAN_QR_CODE],
|
||||||
showIconFirst
|
label: i18n.Messages.USER_SETTINGS_SCAN_QR_CODE,
|
||||||
/>
|
ariaLabel: i18n.Messages.USER_SETTINGS_SCAN_QR_CODE
|
||||||
</Menu.MenuGroup>
|
};
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
ScanQrTabBarComponent: () => (
|
|
||||||
<TabBar.Item id="Scan QR Code" onClick={openQrModal}>
|
|
||||||
{i18n.Messages.USER_SETTINGS_SCAN_QR_CODE}
|
|
||||||
</TabBar.Item>
|
|
||||||
),
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
// Preload images
|
// Preload images
|
||||||
|
|
|
@ -43,4 +43,3 @@ export const QrCodeIcon = proxyLazy(() => icons.QrCodeCameraIcon ?? icons.QrCode
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const cl = classNameFactory("qrlogin-");
|
export const cl = classNameFactory("qrlogin-");
|
||||||
|
|
||||||
|
|
|
@ -345,6 +345,7 @@ function QrModal(props: ModalProps) {
|
||||||
video.srcObject = str;
|
video.srcObject = str;
|
||||||
video.addEventListener("loadedmetadata", () => {
|
video.addEventListener("loadedmetadata", () => {
|
||||||
if (stopped) return stop(str);
|
if (stopped) return stop(str);
|
||||||
|
|
||||||
video.play();
|
video.play();
|
||||||
modalProps.current.setPreview(video);
|
modalProps.current.setPreview(video);
|
||||||
snapshot();
|
snapshot();
|
||||||
|
@ -381,7 +382,8 @@ function QrModal(props: ModalProps) {
|
||||||
state === LoginStateType.Camera &&
|
state === LoginStateType.Camera &&
|
||||||
!preview?.source &&
|
!preview?.source &&
|
||||||
"modal-filepaste-disabled",
|
"modal-filepaste-disabled",
|
||||||
preview?.source && "modal-filepaste-preview"
|
preview?.source && "modal-filepaste-preview",
|
||||||
|
preview?.crosses && "modal-filepaste-crosses"
|
||||||
)}
|
)}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
state === LoginStateType.Idle && inputRef.current?.click()
|
state === LoginStateType.Idle && inputRef.current?.click()
|
||||||
|
@ -418,10 +420,28 @@ function QrModal(props: ModalProps) {
|
||||||
>
|
>
|
||||||
{preview?.source ? (
|
{preview?.source ? (
|
||||||
<div
|
<div
|
||||||
style={{ width: "100%", height: "100%", position: "relative" }}
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
position: "relative",
|
||||||
|
["--scale" as any]: preview.crosses
|
||||||
|
? Math.max(preview.crosses[0].size * 0.9, 1)
|
||||||
|
: undefined,
|
||||||
|
["--offset-x" as any]: preview.crosses
|
||||||
|
? `${-(preview.crosses.reduce((i, { x }) => i + x, 0) /
|
||||||
|
preview.crosses.length -
|
||||||
|
50)}%`
|
||||||
|
: undefined,
|
||||||
|
["--offset-y" as any]: preview.crosses
|
||||||
|
? `${-(preview.crosses.reduce((i, { y }) => i + y, 0) /
|
||||||
|
preview.crosses.length -
|
||||||
|
50)}%`
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
className={cl(preview?.crosses && "preview-crosses")}
|
||||||
>
|
>
|
||||||
{preview.source}
|
{preview.source}
|
||||||
{preview.crosses?.map(({ x, y, rot, size }, i) => (
|
{preview.crosses?.map(({ x, y, rot, size }) => (
|
||||||
<span
|
<span
|
||||||
className={cl("preview-cross")}
|
className={cl("preview-cross")}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: 200ms background-color ease-in-out, 200ms border-color ease-in-out,
|
transition: 200ms background-color ease-in-out, 200ms border-color ease-in-out,
|
||||||
200ms opacity ease-in-out, 200ms border-width ease-in-out,
|
200ms opacity ease-in-out, 200ms border-width ease-in-out,
|
||||||
250ms width cubic-bezier(0.68, -0.6, 0.32, 1.6),
|
250ms width cubic-bezier(0.76, 0, 0.24, 1),
|
||||||
250ms height cubic-bezier(0.68, -0.6, 0.32, 1.6);
|
250ms height cubic-bezier(0.76, 0, 0.24, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.qrlogin-modal-filepaste * {
|
.qrlogin-modal-filepaste * {
|
||||||
|
@ -41,6 +41,14 @@
|
||||||
background-color: #0005;
|
background-color: #0005;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qrlogin-modal-filepaste-preview video {
|
||||||
|
object-fit: fill;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrlogin-preview-crosses {
|
||||||
|
animation: 500ms preview-crosses cubic-bezier(0.25, 1, 0.5, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
.qrlogin-preview-cross {
|
.qrlogin-preview-cross {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: var(--size);
|
width: var(--size);
|
||||||
|
@ -85,11 +93,10 @@
|
||||||
.qrlogin-device-confirm {
|
.qrlogin-device-confirm {
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
background: linear-gradient(
|
background: linear-gradient(to right,
|
||||||
to right,
|
|
||||||
var(--button-danger-background) calc(var(--progress) - 1%),
|
var(--button-danger-background) calc(var(--progress) - 1%),
|
||||||
var(--button-danger-background-active) var(--progress)
|
var(--button-danger-background-active) var(--progress)) !important;
|
||||||
) !important;
|
border-radius: 420rem;
|
||||||
|
|
||||||
--progress: 0%;
|
--progress: 0%;
|
||||||
}
|
}
|
||||||
|
@ -121,3 +128,9 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes preview-crosses {
|
||||||
|
to {
|
||||||
|
transform: translate(var(--offset-x), var(--offset-y)) scale(var(--scale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,9 +32,9 @@ export const MessageLoggerStore = createStore("MessageLoggerData", "MessageLogge
|
||||||
|
|
||||||
// this gets used by the logs modal. logs modal should only use saved messages not messages that are being processed
|
// this gets used by the logs modal. logs modal should only use saved messages not messages that are being processed
|
||||||
// also hasMessageInLogs should only check saved messages not the ones that are being processed
|
// also hasMessageInLogs should only check saved messages not the ones that are being processed
|
||||||
export let savedLoggedMessages: LoggedMessages = defaultLoggedMessages;
|
export let savedLoggedMessages: LoggedMessages = { ...defaultLoggedMessages };
|
||||||
|
|
||||||
export let loggedMessages: LoggedMessages = defaultLoggedMessages;
|
export let loggedMessages: LoggedMessages = { ...defaultLoggedMessages };
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default definePlugin({
|
||||||
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /guildDiscoveryRef.{0,300}\{\}\)\]\}\)\]/,
|
match: /lastTargetNode.{0,300}\{\}\)\]\}\)\]/,
|
||||||
replace: "$&.concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Below))"
|
replace: "$&.concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Below))"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
23
src/plugins/fixImagesQuality/index.ts
Normal file
23
src/plugins/fixImagesQuality/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "FixImagesQuality",
|
||||||
|
description: "Prevents images from being loaded as webp, which can cause quality loss",
|
||||||
|
authors: [Devs.Nuckyz],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "getFormatQuality(){",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=null;return )\i\.\i&&\(\i\|\|!\i\.isAnimated.+?:(?=\i&&\(\i="png"\))/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -913,6 +913,10 @@ export const EquicordDevs = Object.freeze({
|
||||||
name: "arHSM",
|
name: "arHSM",
|
||||||
id: 841509053422632990n
|
id: 841509053422632990n
|
||||||
},
|
},
|
||||||
|
creations: {
|
||||||
|
name: "Creation's",
|
||||||
|
id: 209830981060788225n,
|
||||||
|
}
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue