diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index bb2df342..e609d564 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -18,7 +18,7 @@ import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; -import { LazyComponent } from "@utils/react"; +import { LazyComponent, LazyComponentWrapper } from "@utils/react"; import { React } from "@webpack/common"; import { ErrorCard } from "./ErrorCard"; @@ -107,9 +107,9 @@ const ErrorBoundary = LazyComponent(() => { } }; }) as - React.ComponentType> & { + LazyComponentWrapper> & { wrap(Component: React.ComponentType, errorBoundaryProps?: Omit, "wrappedProps">): React.FunctionComponent; - }; + }>; ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => ( diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts index 2ca83b7f..21802b6a 100644 --- a/src/debug/runReporter.ts +++ b/src/debug/runReporter.ts @@ -87,7 +87,7 @@ async function runReporter() { result = Webpack[method](...args); } - if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail"); + if (result == null || (result.$$vencordGetWrappedComponent != null && result.$$vencordGetWrappedComponent() == null)) throw new Error("Webpack Find Fail"); } catch (e) { let logMessage = searchType; if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") { diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index 84e338ef..cbf94c2b 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -142,8 +142,7 @@ export default definePlugin({ // Thus, we sanity check webpack modules Layer(props: LayerProps) { try { - // @ts-ignore - [FocusLock.$$vencordInternal(), ComponentDispatch, Classes].forEach(e => e.test); + [FocusLock.$$vencordGetWrappedComponent(), ComponentDispatch, Classes].forEach(e => e.test); } catch { new Logger("BetterSettings").error("Failed to find some components"); return props.children; diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 5ddf4639..49e67158 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -174,8 +174,8 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) { function unwrapProxy(value: any) { if (value[SYM_LAZY_GET]) { forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]; - } else if (value.$$vencordInternal) { - return forceLoad ? value.$$vencordInternal() : value; + } else if (value.$$vencordGetWrappedComponent) { + return forceLoad ? value.$$vencordGetWrappedComponent() : value; } return value; diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index 4184931f..78a69a14 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -28,6 +28,7 @@ import { openImageModal } from "@utils/discord"; import { classes, copyWithToast } from "@utils/misc"; import { ContextMenuApi, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common"; +import { SeekBar } from "./SeekBar"; import { SpotifyStore, Track } from "./SpotifyStore"; const cl = classNameFactory("vc-spotify-"); @@ -160,7 +161,7 @@ const seek = debounce((v: number) => { SpotifyStore.seek(v); }); -function SeekBar() { +function SpotifySeekBar() { const { duration } = SpotifyStore.track!; const [storePosition, isSettingPosition, isPlaying] = useStateFromStores( @@ -181,6 +182,12 @@ function SeekBar() { } }, [storePosition, isSettingPosition, isPlaying]); + const onChange = (v: number) => { + if (isSettingPosition) return; + setPosition(v); + seek(v); + }; + return (
{msToHuman(position)} - { - if (isSettingPosition) return; - setPosition(v); - seek(v); - }} - renderValue={msToHuman} + onValueChange={onChange} + asValueChanges={onChange} + onValueRender={msToHuman} /> - +
); diff --git a/src/plugins/spotifyControls/SeekBar.ts b/src/plugins/spotifyControls/SeekBar.ts new file mode 100644 index 00000000..8d6c8a30 --- /dev/null +++ b/src/plugins/spotifyControls/SeekBar.ts @@ -0,0 +1,25 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { LazyComponent } from "@utils/lazyReact"; +import { Slider } from "@webpack/common"; + +export const SeekBar = LazyComponent(() => { + const SliderClass = Slider.$$vencordGetWrappedComponent(); + + // Discord's Slider does not update `state.value` when `props.initialValue` changes if state.value is not nullish. + // We extend their class and override their `getDerivedStateFromProps` to update the value + return class SeekBar extends SliderClass { + static getDerivedStateFromProps(props: any, state: any) { + const newState = super.getDerivedStateFromProps!(props, state); + if (newState) { + newState.value = props.initialValue; + } + + return newState; + } + }; +}); diff --git a/src/utils/lazyReact.tsx b/src/utils/lazyReact.tsx index 4896a058..0a15bf92 100644 --- a/src/utils/lazyReact.tsx +++ b/src/utils/lazyReact.tsx @@ -4,26 +4,28 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { ComponentType } from "react"; +import type { ComponentType } from "react"; import { makeLazy } from "./lazy"; const NoopComponent = () => null; +export type LazyComponentWrapper = ComponentType & { $$vencordGetWrappedComponent(): ComponentType; }; + /** * A lazy component. The factory method is called on first render. * @param factory Function returning a Component * @param attempts How many times to try to get the component before giving up * @returns Result of factory function */ -export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { +export function LazyComponent(factory: () => ComponentType, attempts = 5): LazyComponentWrapper> { const get = makeLazy(factory, attempts); const LazyComponent = (props: T) => { const Component = get() ?? NoopComponent; return ; }; - LazyComponent.$$vencordInternal = get; + LazyComponent.$$vencordGetWrappedComponent = get; - return LazyComponent as ComponentType; + return LazyComponent; } diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx index 8957c254..090d9898 100644 --- a/src/webpack/common/internal.tsx +++ b/src/webpack/common/internal.tsx @@ -16,19 +16,19 @@ * along with this program. If not, see . */ -import { LazyComponent } from "@utils/react"; +import { LazyComponent, LazyComponentWrapper } from "@utils/react"; // eslint-disable-next-line path-alias/no-relative import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "../webpack"; -export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]): T { +export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]) { if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]); let myValue: T = function () { throw new Error(`Vencord could not find the ${name} Component`); } as any; - const lazyComponent = LazyComponent(() => myValue) as T; + const lazyComponent = LazyComponent(() => myValue) as LazyComponentWrapper; waitFor(filter, (v: any) => { myValue = v; Object.assign(lazyComponent, v); diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 2b8ee92a..b5f4ff5c 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref } from "react"; +import type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref } from "react"; export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; @@ -356,7 +356,7 @@ export type SearchableSelect = ComponentType>; -export type Slider = ComponentType