Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
ebc5c975dc
12 changed files with 311 additions and 65 deletions
246
packages/discord-types/src/stores/PopoutWindowStore.d.ts
vendored
Normal file
246
packages/discord-types/src/stores/PopoutWindowStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
import { FluxStore } from "..";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Known popout window key constants.
|
||||||
|
* Used as the key parameter for PopoutWindowStore and PopoutActions methods.
|
||||||
|
*/
|
||||||
|
export type PopoutWindowKey =
|
||||||
|
| "DISCORD_CHANNEL_CALL_POPOUT"
|
||||||
|
| "DISCORD_CALL_TILE_POPOUT"
|
||||||
|
| "DISCORD_SOUNDBOARD"
|
||||||
|
| "DISCORD_RTC_DEBUG_POPOUT"
|
||||||
|
| "DISCORD_CHANNEL_POPOUT"
|
||||||
|
| "DISCORD_ACTIVITY_POPOUT"
|
||||||
|
| "DISCORD_OVERLAY_POPOUT"
|
||||||
|
| "DISCORD_DEVTOOLS_POPOUT";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Popout window lifecycle event types.
|
||||||
|
* Sent via postMessage from popout to parent window.
|
||||||
|
*/
|
||||||
|
export type PopoutWindowEventType = "loaded" | "unloaded";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persisted window position and size state.
|
||||||
|
* Saved to localStorage and restored when reopening popouts.
|
||||||
|
*/
|
||||||
|
export interface PopoutWindowState {
|
||||||
|
/** window x position on screen in pixels. */
|
||||||
|
x: number;
|
||||||
|
/** window y position on screen in pixels. */
|
||||||
|
y: number;
|
||||||
|
/** window inner width in pixels. */
|
||||||
|
width: number;
|
||||||
|
/** window inner height in pixels. */
|
||||||
|
height: number;
|
||||||
|
/** whether window stays above other windows, only on desktop app. */
|
||||||
|
alwaysOnTop?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Features passed to window.open() for popout configuration.
|
||||||
|
* Merged with default features (menubar, toolbar, location, directories = false).
|
||||||
|
*/
|
||||||
|
export interface BrowserWindowFeatures {
|
||||||
|
/** whether to show browser toolbar. */
|
||||||
|
toolbar?: boolean;
|
||||||
|
/** whether to show menu bar. */
|
||||||
|
menubar?: boolean;
|
||||||
|
/** whether to show location/address bar. */
|
||||||
|
location?: boolean;
|
||||||
|
/** whether to show directory buttons. */
|
||||||
|
directories?: boolean;
|
||||||
|
/** window width in pixels. */
|
||||||
|
width?: number;
|
||||||
|
/** window height in pixels. */
|
||||||
|
height?: number;
|
||||||
|
/** default width if no persisted state exists. */
|
||||||
|
defaultWidth?: number;
|
||||||
|
/** default height if no persisted state exists. */
|
||||||
|
defaultHeight?: number;
|
||||||
|
/** window left position in pixels. */
|
||||||
|
left?: number;
|
||||||
|
/** window top position in pixels. */
|
||||||
|
top?: number;
|
||||||
|
/** default always-on-top state, defaults to false. */
|
||||||
|
defaultAlwaysOnTop?: boolean;
|
||||||
|
/** whether window can be moved by user. */
|
||||||
|
movable?: boolean;
|
||||||
|
/** whether window can be resized by user. */
|
||||||
|
resizable?: boolean;
|
||||||
|
/** whether window has a frame/border. */
|
||||||
|
frame?: boolean;
|
||||||
|
/** whether window stays above other windows. */
|
||||||
|
alwaysOnTop?: boolean;
|
||||||
|
/** whether window has a shadow (macOS). */
|
||||||
|
hasShadow?: boolean;
|
||||||
|
/** whether window background is transparent. */
|
||||||
|
transparent?: boolean;
|
||||||
|
/** whether to hide window from taskbar. */
|
||||||
|
skipTaskbar?: boolean;
|
||||||
|
/** title bar style, null for default. */
|
||||||
|
titleBarStyle?: string | null;
|
||||||
|
/** window background color as hex string. */
|
||||||
|
backgroundColor?: string;
|
||||||
|
/** whether this is an out-of-process overlay window. */
|
||||||
|
outOfProcessOverlay?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages Discord's popout windows (voice calls, activities, etc.).
|
||||||
|
* Extends PersistedStore to save window positions across sessions.
|
||||||
|
*
|
||||||
|
* Handles Flux actions:
|
||||||
|
* - POPOUT_WINDOW_OPEN: opens a new popout window
|
||||||
|
* - POPOUT_WINDOW_CLOSE: closes a popout window
|
||||||
|
* - POPOUT_WINDOW_SET_ALWAYS_ON_TOP: toggles always-on-top (desktop only)
|
||||||
|
* - POPOUT_WINDOW_ADD_STYLESHEET: injects stylesheet into all open popouts
|
||||||
|
* - LOGOUT: closes all popout windows
|
||||||
|
*/
|
||||||
|
export class PopoutWindowStore extends FluxStore {
|
||||||
|
/**
|
||||||
|
* Gets the Window object for a popout.
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
* @returns Window reference or undefined if not open
|
||||||
|
*/
|
||||||
|
getWindow(key: string): Window | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets persisted position/size state for a window.
|
||||||
|
* State is saved when window closes and restored when reopened.
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
* @returns saved state or undefined if never opened
|
||||||
|
*/
|
||||||
|
getWindowState(key: string): PopoutWindowState | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all currently open popout window keys.
|
||||||
|
* @returns array of window key identifiers
|
||||||
|
*/
|
||||||
|
getWindowKeys(): string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a popout window is currently open.
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
* @returns true if window exists and is not closed
|
||||||
|
*/
|
||||||
|
getWindowOpen(key: string): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a popout window has always-on-top enabled.
|
||||||
|
* Only functional on desktop app (isPlatformEmbedded).
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
* @returns true if always-on-top is enabled
|
||||||
|
*/
|
||||||
|
getIsAlwaysOnTop(key: string): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a popout window's document has focus.
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
* @returns true if window document has focus
|
||||||
|
*/
|
||||||
|
getWindowFocused(key: string): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a popout window is visible (not minimized/hidden).
|
||||||
|
* Uses document.visibilityState === "visible".
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
* @returns true if window is visible
|
||||||
|
*/
|
||||||
|
getWindowVisible(key: string): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all persisted window states.
|
||||||
|
* Keyed by window identifier, contains position/size data.
|
||||||
|
* @returns record of window key to persisted state
|
||||||
|
*/
|
||||||
|
getState(): Record<string, PopoutWindowState>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a window is fully initialized and ready for rendering.
|
||||||
|
* A window is fully initialized when it has:
|
||||||
|
* - Window object created
|
||||||
|
* - React root mounted
|
||||||
|
* - Render function stored
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
* @returns true if window is fully initialized
|
||||||
|
*/
|
||||||
|
isWindowFullyInitialized(key: string): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a popout window is in fullscreen mode.
|
||||||
|
* Checks if document.fullscreenElement.id === "app-mount".
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
* @returns true if window is fullscreen
|
||||||
|
*/
|
||||||
|
isWindowFullScreen(key: string): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unmounts and closes a popout window.
|
||||||
|
* Saves current position/size before closing.
|
||||||
|
* Logs warning if window was not fully initialized.
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
*/
|
||||||
|
unmountWindow(key: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions for managing popout windows.
|
||||||
|
* Dispatches Flux actions to PopoutWindowStore.
|
||||||
|
*/
|
||||||
|
export interface PopoutActions {
|
||||||
|
/**
|
||||||
|
* Opens a new popout window.
|
||||||
|
* If window with key already exists and is not out-of-process:
|
||||||
|
* - On desktop: focuses the existing window via native module
|
||||||
|
* - On web: calls window.focus()
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
* @param render function that returns React element to render, receives key as arg
|
||||||
|
* @param features window features (size, position, etc.)
|
||||||
|
*/
|
||||||
|
open(key: string, render: (key: string) => React.ReactNode, features?: BrowserWindowFeatures): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes a popout window.
|
||||||
|
* Saves position/size state before closing unless preventPopoutClose setting is true.
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
*/
|
||||||
|
close(key: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets always-on-top state for a popout window.
|
||||||
|
* Only functional on desktop app (isPlatformEmbedded).
|
||||||
|
* @param key unique identifier for the popout window
|
||||||
|
* @param alwaysOnTop whether window should stay above others
|
||||||
|
*/
|
||||||
|
setAlwaysOnTop(key: string, alwaysOnTop: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Not actually in the Webpack Common. You have to add it yourself if you want to use it
|
||||||
|
*
|
||||||
|
* Injects a stylesheet into all open popout windows.
|
||||||
|
* Validates origin matches current host or webpack public path.
|
||||||
|
* @param url stylesheet URL to inject
|
||||||
|
* @param integrity optional SRI integrity hash
|
||||||
|
*/
|
||||||
|
addStylesheet?(url: string, integrity?: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Not actually in the Webpack Common. You have to add it yourself if you want to use it
|
||||||
|
*
|
||||||
|
* Opens a channel call popout for voice/video calls.
|
||||||
|
* Dispatches CHANNEL_CALL_POPOUT_WINDOW_OPEN action.
|
||||||
|
* @param channel channel object to open call popout for
|
||||||
|
*/
|
||||||
|
openChannelCallPopout?(channel: { id: string; }): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Not actually in the Webpack Common. You have to add it yourself if you want to use it
|
||||||
|
*
|
||||||
|
* Opens a call tile popout for a specific participant.
|
||||||
|
* Dispatches CALL_TILE_POPOUT_WINDOW_OPEN action.
|
||||||
|
* @param channelId channel ID of the call
|
||||||
|
* @param participantId user ID of the participant
|
||||||
|
*/
|
||||||
|
openCallTilePopout?(channelId: string, participantId: string): void;
|
||||||
|
}
|
||||||
1
packages/discord-types/src/stores/index.d.ts
vendored
1
packages/discord-types/src/stores/index.d.ts
vendored
|
|
@ -24,6 +24,7 @@ export * from "./MessageStore";
|
||||||
export * from "./NotificationSettingsStore";
|
export * from "./NotificationSettingsStore";
|
||||||
export * from "./OverridePremiumTypeStore";
|
export * from "./OverridePremiumTypeStore";
|
||||||
export * from "./PermissionStore";
|
export * from "./PermissionStore";
|
||||||
|
export * from "./PopoutWindowStore";
|
||||||
export * from "./PresenceStore";
|
export * from "./PresenceStore";
|
||||||
export * from "./ReadStateStore";
|
export * from "./ReadStateStore";
|
||||||
export * from "./RelationshipStore";
|
export * from "./RelationshipStore";
|
||||||
|
|
|
||||||
29
packages/discord-types/src/utils.d.ts
vendored
29
packages/discord-types/src/utils.d.ts
vendored
|
|
@ -232,34 +232,7 @@ export interface ExpressionPickerStore {
|
||||||
useExpressionPickerStore<T>(selector: (state: ExpressionPickerStoreState) => T): T;
|
useExpressionPickerStore<T>(selector: (state: ExpressionPickerStoreState) => T): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BrowserWindowFeatures {
|
export { BrowserWindowFeatures, PopoutActions } from "./stores/PopoutWindowStore";
|
||||||
toolbar?: boolean;
|
|
||||||
menubar?: boolean;
|
|
||||||
location?: boolean;
|
|
||||||
directories?: boolean;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
defaultWidth?: number;
|
|
||||||
defaultHeight?: number;
|
|
||||||
left?: number;
|
|
||||||
top?: number;
|
|
||||||
defaultAlwaysOnTop?: boolean;
|
|
||||||
movable?: boolean;
|
|
||||||
resizable?: boolean;
|
|
||||||
frame?: boolean;
|
|
||||||
alwaysOnTop?: boolean;
|
|
||||||
hasShadow?: boolean;
|
|
||||||
transparent?: boolean;
|
|
||||||
skipTaskbar?: boolean;
|
|
||||||
titleBarStyle?: string | null;
|
|
||||||
backgroundColor?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PopoutActions {
|
|
||||||
open(key: string, render: (windowKey: string) => ReactNode, features?: BrowserWindowFeatures);
|
|
||||||
close(key: string): void;
|
|
||||||
setAlwaysOnTop(key: string, alwaysOnTop: boolean): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UserNameUtilsTagInclude = LiteralUnion<"auto" | "always" | "never", string>;
|
export type UserNameUtilsTagInclude = LiteralUnion<"auto" | "always" | "never", string>;
|
||||||
export interface UserNameUtilsTagOptions {
|
export interface UserNameUtilsTagOptions {
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,9 @@
|
||||||
import { Settings, SettingsStore } from "@api/Settings";
|
import { Settings, SettingsStore } from "@api/Settings";
|
||||||
import { createAndAppendStyle } from "@utils/css";
|
import { createAndAppendStyle } from "@utils/css";
|
||||||
import { ThemeStore } from "@vencord/discord-types";
|
import { ThemeStore } from "@vencord/discord-types";
|
||||||
|
import { PopoutWindowStore } from "@webpack/common";
|
||||||
|
|
||||||
import { userStyleRootNode } from "./Styles";
|
import { userStyleRootNode, vencordRootNode } from "./Styles";
|
||||||
|
|
||||||
let style: HTMLStyleElement;
|
let style: HTMLStyleElement;
|
||||||
let themesStyle: HTMLStyleElement;
|
let themesStyle: HTMLStyleElement;
|
||||||
|
|
@ -33,6 +34,7 @@ async function toggle(isEnabled: boolean) {
|
||||||
style.textContent = css;
|
style.textContent = css;
|
||||||
// At the time of writing this, changing textContent resets the disabled state
|
// At the time of writing this, changing textContent resets the disabled state
|
||||||
style.disabled = !Settings.useQuickCss;
|
style.disabled = !Settings.useQuickCss;
|
||||||
|
updatePopoutWindows();
|
||||||
});
|
});
|
||||||
style.textContent = await VencordNative.quickCss.get();
|
style.textContent = await VencordNative.quickCss.get();
|
||||||
}
|
}
|
||||||
|
|
@ -76,6 +78,25 @@ async function initThemes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
themesStyle.textContent = links.map(link => `@import url("${link.trim()}");`).join("\n");
|
themesStyle.textContent = links.map(link => `@import url("${link.trim()}");`).join("\n");
|
||||||
|
updatePopoutWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyToPopout(popoutWindow: Window | undefined) {
|
||||||
|
if (!popoutWindow?.document) return;
|
||||||
|
|
||||||
|
const doc = popoutWindow.document;
|
||||||
|
|
||||||
|
doc.querySelector("vencord-root")?.remove();
|
||||||
|
|
||||||
|
doc.documentElement.appendChild(vencordRootNode.cloneNode(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePopoutWindows() {
|
||||||
|
const windowKeys = PopoutWindowStore.getWindowKeys();
|
||||||
|
for (const key of windowKeys) {
|
||||||
|
const popoutWindow = PopoutWindowStore.getWindow(key);
|
||||||
|
applyToPopout(popoutWindow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
@ -89,6 +110,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
SettingsStore.addChangeListener("enabledThemeLinks", initThemes);
|
SettingsStore.addChangeListener("enabledThemeLinks", initThemes);
|
||||||
SettingsStore.addChangeListener("enabledThemes", initThemes);
|
SettingsStore.addChangeListener("enabledThemes", initThemes);
|
||||||
|
|
||||||
|
window.addEventListener("message", event => {
|
||||||
|
const { discordPopoutEvent } = event.data || {};
|
||||||
|
if (discordPopoutEvent?.type !== "loaded") return;
|
||||||
|
|
||||||
|
const popoutWindow = PopoutWindowStore.getWindow(discordPopoutEvent.key);
|
||||||
|
applyToPopout(popoutWindow);
|
||||||
|
});
|
||||||
|
|
||||||
if (!IS_WEB) {
|
if (!IS_WEB) {
|
||||||
VencordNative.quickCss.addThemeChangeListener(initThemes);
|
VencordNative.quickCss.addThemeChangeListener(initThemes);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,11 @@ export function BooleanSetting({ option, pluginSettings, definedSettings, id, on
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsSection tag="label" name={id} description={option.description} error={error} inlineSetting>
|
<SettingsSection tag="label" name={id} description={option.description} error={error} inlineSetting>
|
||||||
<Switch checked={state} onChange={handleChange} />
|
<Switch
|
||||||
|
checked={state}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={option.disabled?.call(definedSettings) ?? false}
|
||||||
|
/>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { managedStyleRootNode } from "@api/Styles";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { createAndAppendStyle } from "@utils/css";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
let style: HTMLStyleElement;
|
let style: HTMLStyleElement;
|
||||||
|
|
@ -56,9 +58,7 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
style = document.createElement("style");
|
style = createAndAppendStyle("VcBlurNsfw", managedStyleRootNode);
|
||||||
style.id = "VcBlurNsfw";
|
|
||||||
document.head.appendChild(style);
|
|
||||||
|
|
||||||
setCss();
|
setCss();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,15 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { hexToHSL } from "./colorUtils";
|
import { managedStyleRootNode } from "@api/Styles";
|
||||||
|
import { createAndAppendStyle } from "@utils/css";
|
||||||
|
|
||||||
|
import { hexToHSL } from "./colorUtils";
|
||||||
const VARS_STYLE_ID = "vc-clientTheme-vars";
|
const VARS_STYLE_ID = "vc-clientTheme-vars";
|
||||||
const OVERRIDES_STYLE_ID = "vc-clientTheme-overrides";
|
const OVERRIDES_STYLE_ID = "vc-clientTheme-overrides";
|
||||||
|
type StyleId = typeof VARS_STYLE_ID | typeof OVERRIDES_STYLE_ID;
|
||||||
|
|
||||||
|
const styleCache = {} as Record<StyleId, HTMLStyleElement | null>;
|
||||||
|
|
||||||
export function createOrUpdateThemeColorVars(color: string) {
|
export function createOrUpdateThemeColorVars(color: string) {
|
||||||
const { hue, saturation, lightness } = hexToHSL(color);
|
const { hue, saturation, lightness } = hexToHSL(color);
|
||||||
|
|
@ -25,23 +30,20 @@ export async function startClientTheme(color: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function disableClientTheme() {
|
export function disableClientTheme() {
|
||||||
document.getElementById(VARS_STYLE_ID)?.remove();
|
styleCache[VARS_STYLE_ID]?.remove();
|
||||||
document.getElementById(OVERRIDES_STYLE_ID)?.remove();
|
styleCache[OVERRIDES_STYLE_ID]?.remove();
|
||||||
|
styleCache[VARS_STYLE_ID] = null;
|
||||||
|
styleCache[OVERRIDES_STYLE_ID] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOrCreateStyle(styleId: string) {
|
function getOrCreateStyle(styleId: StyleId) {
|
||||||
const existingStyle = document.getElementById(styleId);
|
if (!styleCache[styleId]) {
|
||||||
if (existingStyle) {
|
styleCache[styleId] = createAndAppendStyle(styleId, managedStyleRootNode);
|
||||||
return existingStyle as HTMLStyleElement;
|
|
||||||
}
|
}
|
||||||
|
return styleCache[styleId];
|
||||||
const newStyle = document.createElement("style");
|
|
||||||
newStyle.id = styleId;
|
|
||||||
|
|
||||||
return document.head.appendChild(newStyle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOrUpdateStyle(styleId: string, css: string) {
|
function createOrUpdateStyle(styleId: StyleId, css: string) {
|
||||||
const style = getOrCreateStyle(styleId);
|
const style = getOrCreateStyle(styleId);
|
||||||
style.textContent = css;
|
style.textContent = css;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ function getGuildMaxStickerSlots(guild: Guild) {
|
||||||
|
|
||||||
function getUrl(data: Data, size: number) {
|
function getUrl(data: Data, size: number) {
|
||||||
if (data.t === "Emoji")
|
if (data.t === "Emoji")
|
||||||
return `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${data.id}.${data.isAnimated ? "gif" : "png"}?size=${size}&lossless=true`;
|
return `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${data.id}.webp?size=${size}&lossless=true`;
|
||||||
|
|
||||||
return `${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/stickers/${data.id}.${StickerExtMap[data.format_type]}?size=${size}&lossless=true`;
|
return `${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/stickers/${data.id}.${StickerExtMap[data.format_type]}?size=${size}&lossless=true`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import { Paragraph } from "@components/Paragraph";
|
import { Paragraph } from "@components/Paragraph";
|
||||||
import { ApngBlendOp, ApngDisposeOp, parseAPNG } from "@utils/apng";
|
import { ApngBlendOp, ApngDisposeOp, parseAPNG } from "@utils/apng";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getCurrentGuild, getEmojiURL } from "@utils/discord";
|
import { getCurrentGuild } from "@utils/discord";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType, Patch } from "@utils/types";
|
import definePlugin, { OptionType, Patch } from "@utils/types";
|
||||||
import type { Emoji, Message, Sticker } from "@vencord/discord-types";
|
import type { Emoji, Message, Sticker } from "@vencord/discord-types";
|
||||||
import { StickerFormatType } from "@vencord/discord-types/enums";
|
import { StickerFormatType } from "@vencord/discord-types/enums";
|
||||||
import { findByCodeLazy, findByPropsLazy, proxyLazyWebpack } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy, proxyLazyWebpack } from "@webpack";
|
||||||
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, GuildMemberStore, lodash, OverridePremiumTypeStore, Parser, PermissionsBits, PermissionStore, StickersStore, UploadHandler, UserSettingsActionCreators, UserSettingsProtoStore } from "@webpack/common";
|
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, IconUtils, lodash, OverridePremiumTypeStore, Parser, PermissionsBits, PermissionStore, StickersStore, UploadHandler, UserSettingsActionCreators, UserSettingsProtoStore, UserStore } from "@webpack/common";
|
||||||
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
||||||
import type { ReactElement, ReactNode } from "react";
|
import type { ReactElement, ReactNode } from "react";
|
||||||
|
|
||||||
|
|
@ -883,7 +883,7 @@ export default definePlugin({
|
||||||
|
|
||||||
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
|
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
|
||||||
|
|
||||||
const url = new URL(getEmojiURL(emoji.id, emoji.animated, s.emojiSize));
|
const url = new URL(IconUtils.getEmojiURL({ id: emoji.id, animated: emoji.animated, size: s.emojiSize }));
|
||||||
url.searchParams.set("size", s.emojiSize.toString());
|
url.searchParams.set("size", s.emojiSize.toString());
|
||||||
url.searchParams.set("name", emoji.name);
|
url.searchParams.set("name", emoji.name);
|
||||||
if (emoji.animated) {
|
if (emoji.animated) {
|
||||||
|
|
@ -919,7 +919,7 @@ export default definePlugin({
|
||||||
|
|
||||||
hasBypass = true;
|
hasBypass = true;
|
||||||
|
|
||||||
const url = new URL(getEmojiURL(emoji.id, emoji.animated, s.emojiSize));
|
const url = new URL(IconUtils.getEmojiURL({ id: emoji.id, animated: emoji.animated, size: s.emojiSize }));
|
||||||
url.searchParams.set("size", s.emojiSize.toString());
|
url.searchParams.set("size", s.emojiSize.toString());
|
||||||
url.searchParams.set("name", emoji.name);
|
url.searchParams.set("name", emoji.name);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import { useForceUpdater } from "@utils/react";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { CustomEmoji, Message, ReactionEmoji, User } from "@vencord/discord-types";
|
import { CustomEmoji, Message, ReactionEmoji, User } from "@vencord/discord-types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, useEffect, useLayoutEffect, UserSummaryItem } from "@webpack/common";
|
import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, useEffect, useLayoutEffect, UserStore, UserSummaryItem } from "@webpack/common";
|
||||||
|
|
||||||
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||||
let Scroll: any = null;
|
let Scroll: any = null;
|
||||||
|
|
@ -150,7 +150,7 @@ export default definePlugin({
|
||||||
}, [message.id, forceUpdate]);
|
}, [message.id, forceUpdate]);
|
||||||
|
|
||||||
const reactions = getReactionsWithQueue(message, emoji, type);
|
const reactions = getReactionsWithQueue(message, emoji, type);
|
||||||
const users = [...reactions.values()].filter(Boolean);
|
const users = Array.from(reactions, ([id]) => UserStore.getUser(id)).filter(Boolean);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import type { MessageObject } from "@api/MessageEvents";
|
import type { MessageObject } from "@api/MessageEvents";
|
||||||
import type { Channel, CloudUpload, Guild, GuildFeatures, Message, User } from "@vencord/discord-types";
|
import type { Channel, CloudUpload, Guild, GuildFeatures, Message, User } from "@vencord/discord-types";
|
||||||
import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, Toasts, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
|
import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, Toasts, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
|
||||||
import { Except } from "type-fest";
|
import { Except } from "type-fest";
|
||||||
|
|
||||||
import { copyToClipboard } from "./clipboard";
|
import { copyToClipboard } from "./clipboard";
|
||||||
|
|
@ -236,17 +236,6 @@ export function getUniqueUsername(user: User) {
|
||||||
return user.discriminator === "0" ? user.username : user.tag;
|
return user.discriminator === "0" ? user.username : user.tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the URL for an emoji. This function always returns a gif URL for animated emojis, instead of webp
|
|
||||||
* @param id The emoji id
|
|
||||||
* @param animated Whether the emoji is animated
|
|
||||||
* @param size The size for the emoji
|
|
||||||
*/
|
|
||||||
export function getEmojiURL(id: string, animated: boolean, size: number) {
|
|
||||||
const url = IconUtils.getEmojiURL({ id, animated, size });
|
|
||||||
return animated ? url.replace(".webp", ".gif") : url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discord has a similar function in their code
|
// Discord has a similar function in their code
|
||||||
export function getGuildAcronym(guild: Guild): string {
|
export function getGuildAcronym(guild: Guild): string {
|
||||||
return guild.name
|
return guild.name
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ export let InviteStore: t.InviteStore;
|
||||||
export let LocaleStore: t.LocaleStore;
|
export let LocaleStore: t.LocaleStore;
|
||||||
export let RTCConnectionStore: t.RTCConnectionStore;
|
export let RTCConnectionStore: t.RTCConnectionStore;
|
||||||
export let SoundboardStore: t.SoundboardStore;
|
export let SoundboardStore: t.SoundboardStore;
|
||||||
|
export let PopoutWindowStore: t.PopoutWindowStore;
|
||||||
export let ApplicationCommandIndexStore: GenericStore;
|
export let ApplicationCommandIndexStore: GenericStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -129,6 +130,7 @@ waitForStore("InviteStore", m => InviteStore = m);
|
||||||
waitForStore("LocaleStore", m => LocaleStore = m);
|
waitForStore("LocaleStore", m => LocaleStore = m);
|
||||||
waitForStore("RTCConnectionStore", m => RTCConnectionStore = m);
|
waitForStore("RTCConnectionStore", m => RTCConnectionStore = m);
|
||||||
waitForStore("SoundboardStore", m => SoundboardStore = m);
|
waitForStore("SoundboardStore", m => SoundboardStore = m);
|
||||||
|
waitForStore("PopoutWindowStore", m => PopoutWindowStore = m);
|
||||||
waitForStore("ApplicationCommandIndexStore", m => ApplicationCommandIndexStore = m);
|
waitForStore("ApplicationCommandIndexStore", m => ApplicationCommandIndexStore = m);
|
||||||
waitForStore("ThemeStore", m => {
|
waitForStore("ThemeStore", m => {
|
||||||
ThemeStore = m;
|
ThemeStore = m;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue