diff --git a/README.md b/README.md index 4d0b8d65..50284800 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - JumpToStart by Samwich - KeyboardSounds by HypedDomi - KeywordNotify by camila314 & x3rt -- - LastActive by Crxa +- LastActive by Crxa - LimitMiddleClickPaste by no dev listed - LoginWithQR by nexpid - MediaPlaybackSpeed by D3SOX @@ -177,6 +177,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - ViewRawVariant by Kyuuhachi - VoiceChatUtilities by D3SOX - VoiceJoinMessages by Sqaaakoi & maintained by thororen +- WallpaperFree by Joona - WebpackTarball by Kyuuhachi - WhitelistedEmojis by Creations - WhosWatching by fres diff --git a/src/equicordplugins/wallpaperFree/components/ctxmenu.tsx b/src/equicordplugins/wallpaperFree/components/ctxmenu.tsx new file mode 100644 index 00000000..a2fe4e8a --- /dev/null +++ b/src/equicordplugins/wallpaperFree/components/ctxmenu.tsx @@ -0,0 +1,67 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { openModal } from "@utils/modal"; +import { ChannelStore, FluxDispatcher, Menu } from "@webpack/common"; + +import { SetCustomWallpaperModal, SetDiscordWallpaperModal } from "./modal"; +import { ChatWallpaperStore, fetchWallpapers } from "./util"; + + +const addWallpaperMenu = (channelId?: string, guildId?: string) => { + const setWallpaper = (url?: string) => { + FluxDispatcher.dispatch({ + // @ts-ignore + type: "VC_WALLPAPER_FREE_CHANGE", + channelId, + guildId, + url, + }); + }; + return ( + + openModal(props => )} + /> + { + ChatWallpaperStore.shouldFetchWallpapers && await fetchWallpapers(); + openModal(props => ); + }} + /> + + setWallpaper(void 0)} + /> + + ); +}; + +const UserContextPatch: NavContextMenuPatchCallback = (children, args) => { + if (!args.user) return; + const dmChannelId = ChannelStore.getDMFromUserId(args.user.id); + children.push(addWallpaperMenu(dmChannelId)); +}; + +const ChannelContextPatch: NavContextMenuPatchCallback = (children, args) => { + if (!args.channel) return; + children.push(addWallpaperMenu(args.channel.id)); +}; + +const GuildContextPatch: NavContextMenuPatchCallback = (children, args) => { + if (!args.guild) return; + children.push(addWallpaperMenu(void 0, args.guild.id)); +}; + +export { ChannelContextPatch, GuildContextPatch, UserContextPatch }; diff --git a/src/equicordplugins/wallpaperFree/components/modal.tsx b/src/equicordplugins/wallpaperFree/components/modal.tsx new file mode 100644 index 00000000..1faa3305 --- /dev/null +++ b/src/equicordplugins/wallpaperFree/components/modal.tsx @@ -0,0 +1,116 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal"; +import { Button, lodash, Text, TextInput, useState, useStateFromStores } from "@webpack/common"; + +import { ChatWallpaperStore, Wallpaper } from "./util"; + +interface Props { + props: ModalProps; + onSelect: (url: string) => void; +} + +export function SetCustomWallpaperModal({ props, onSelect }: Props) { + const [url, setUrl] = useState(""); + + return ( + + + + Set a custom wallpaper + + + +
+ + + {url && ( + Wallpaper preview + )} +
+ + +
+
+
+
+ ); +} + +export function SetDiscordWallpaperModal({ props, onSelect }: Props) { + const discordWallpapers: Wallpaper[] = useStateFromStores([ChatWallpaperStore], () => ChatWallpaperStore.wallpapers); + + return ( + + + + Choose a Discord Wallpaper + + + +
+ {lodash.chunk(discordWallpapers, 2).map(group => { + const main = group[0]; + return ( +
+
+ {main.label} +
+ {main.label} +
+
+
+ {group.map(wp => ( + + ))} +
+
+ ); + })} +
+
+
+ ); +} + diff --git a/src/equicordplugins/wallpaperFree/components/util.tsx b/src/equicordplugins/wallpaperFree/components/util.tsx new file mode 100644 index 00000000..30c85dd9 --- /dev/null +++ b/src/equicordplugins/wallpaperFree/components/util.tsx @@ -0,0 +1,70 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { openModal } from "@utils/modal"; +import { findByCodeLazy, findStoreLazy } from "@webpack"; +import { Button, FluxDispatcher } from "@webpack/common"; + +import { SetCustomWallpaperModal, SetDiscordWallpaperModal } from "./modal"; + +export const ChatWallpaperStore = findStoreLazy("ChatWallpaperStore"); +export const fetchWallpapers = findByCodeLazy('type:"FETCH_CHAT_WALLPAPERS_SUCCESS"'); + +export function GlobalDefaultComponent() { + const setGlobal = (url?: string) => { + FluxDispatcher.dispatch({ + // @ts-ignore + type: "VC_WALLPAPER_FREE_CHANGE_GLOBAL", + url, + }); + }; + + return ( + <> + + + + + + + + + ); +} + +export interface Wallpaper { + id: string; + label: string; + default: Default; + variants: Variants; + isBlurred: boolean; + designGroupId: string; +} + +export interface Default { + asset: string; + icon: string; + thumbhash: string; + opacity?: number; +} + +export interface Variants { + dark: Default; +} diff --git a/src/equicordplugins/wallpaperFree/index.tsx b/src/equicordplugins/wallpaperFree/index.tsx new file mode 100644 index 00000000..f4409bde --- /dev/null +++ b/src/equicordplugins/wallpaperFree/index.tsx @@ -0,0 +1,86 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { useStateFromStores } from "@webpack/common"; +import { Channel } from "discord-types/general"; + +import { ChannelContextPatch, GuildContextPatch, UserContextPatch } from "./components/ctxmenu"; +import { GlobalDefaultComponent, Wallpaper } from "./components/util"; +import { WallpaperFreeStore } from "./store"; + + +const settings = definePluginSettings({ + forceReplace: { + description: "If a dm wallpaper is already set, your custom wallpaper will be used instead.", + type: OptionType.BOOLEAN, + default: false, + }, + globalDefault: { + description: "Set a global default wallpaper for all channels.", + type: OptionType.COMPONENT, + component: GlobalDefaultComponent + } +}); + +export default definePlugin({ + name: "WallpaperFree", + authors: [Devs.Joona], + description: "Use the DM wallpapers anywhere or set a custom wallpaper", + patches: [ + { + find: ".wallpaperContainer,", + group: true, + replacement: [ + { + match: /return null==(\i).+?\?null:/, + replace: "const vcWpFreeCustom = $self.customWallpaper(arguments[0].channel,$1);return !($1||vcWpFreeCustom)?null:" + }, + { + match: /,{chatWallpaperState:/, + replace: "$&vcWpFreeCustom||" + }, + { + match: /(\i=)(.{1,50}""\))/, + replace: "$1arguments[0].chatWallpaperState.vcWallpaperUrl||$2" + }, + { + match: /(\i\.isViewable&&)(null!=\i)/, + replace: "$1($2||arguments[0].chatWallpaperState.vcWallpaperUrl)" + }, + ] + } + ], + settings, + contextMenus: { + "user-context": UserContextPatch, + "channel-context": ChannelContextPatch, + "thread-context": ChannelContextPatch, + "guild-context": GuildContextPatch, + "gdm-context": ChannelContextPatch, + }, + customWallpaper(channel: Channel, wp: Wallpaper | undefined) { + const { forceReplace } = settings.use(["forceReplace"]); + const url = useStateFromStores([WallpaperFreeStore], () => WallpaperFreeStore.getUrl(channel)); + + if (!forceReplace && wp?.id) + return wp; + + if (url) { + return { + wallpaperId: "id", + vcWallpaperUrl: url, + isViewable: true, + }; + } + + return void 0; + }, +}); diff --git a/src/equicordplugins/wallpaperFree/store.ts b/src/equicordplugins/wallpaperFree/store.ts new file mode 100644 index 00000000..c4c2c1a9 --- /dev/null +++ b/src/equicordplugins/wallpaperFree/store.ts @@ -0,0 +1,82 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { proxyLazy } from "@utils/lazy"; +import { findByPropsLazy } from "@webpack"; +import { FluxDispatcher } from "@webpack/common"; +import { FluxEmitter, FluxStore } from "@webpack/types"; +import { Channel } from "discord-types/general"; + +interface IFlux { + PersistedStore: typeof FluxStore; + Emitter: FluxEmitter; +} +const Flux: IFlux = findByPropsLazy("connectStores"); + +export const WallpaperFreeStore = proxyLazy(() => { + const wallpaperChannelMap: Map = new Map(); + const wallpaperGuildMap: Map = new Map(); + let globalDefault: string | undefined; + + class WallpaperFreeStore extends Flux.PersistedStore { + static persistKey = "WallpaperFreeStore"; + + // @ts-ignore + initialize(previous: { guildMap: Map, channelMap: Map, globalDefault: string; } | undefined) { + if (!previous) + return; + + wallpaperGuildMap.clear(); + wallpaperChannelMap.clear(); + for (const [channel, url] of previous.channelMap) { + wallpaperChannelMap.set(channel, url); + } + + for (const [guild, url] of previous.guildMap) { + wallpaperGuildMap.set(guild, url); + } + globalDefault = previous.globalDefault; + } + + getState() { + return { guildMap: Array.from(wallpaperGuildMap), channelMap: Array.from(wallpaperChannelMap), globalDefault }; + } + + getUrl(channel: Channel): string | undefined { + return ( + wallpaperChannelMap.get(channel.id) ?? + wallpaperGuildMap.get(channel.guild_id) ?? + globalDefault + ); + } + } + + const store = new WallpaperFreeStore(FluxDispatcher, { + // @ts-ignore + VC_WALLPAPER_FREE_CHANGE({ guildId, channelId, url }: { guildId: string | undefined, channelId: string | undefined, url: string; }) { + if (guildId) { + wallpaperGuildMap.set(guildId, url); + } else if (channelId) { + wallpaperChannelMap.set(channelId, url); + } + store.emitChange(); + }, + + VC_WALLPAPER_FREE_CHANGE_GLOBAL({ url }: { url: string | undefined; }) { + globalDefault = url; + store.emitChange(); + }, + + VC_WALLPAPER_FREE_RESET() { + wallpaperChannelMap.clear(); + wallpaperGuildMap.clear(); + globalDefault = void 0; + store.emitChange(); + } + }); + + return store; +}); diff --git a/src/equicordplugins/wallpaperFree/styles.css b/src/equicordplugins/wallpaperFree/styles.css new file mode 100644 index 00000000..6a90c8b1 --- /dev/null +++ b/src/equicordplugins/wallpaperFree/styles.css @@ -0,0 +1,30 @@ +.vc-wpfree-discord-wp-modal { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 24px; + padding: 8px 0; +} + +.vc-wpfree-discord-wp-icon-container { + border-radius: 10px; + box-shadow: 0 2px 8px rgb(0 0 0 / 8%); + padding: 16px; + display: flex; + flex-direction: column; + align-items: center +} + +.vc-wpfree-discord-wp-icon-img { + width: 120px; + height: 68px; + object-fit: cover; + border-radius: 6px; + margin-bottom: 8px; + border: 1px solid var(--background-modifier-accent); +} + +.vc-wpfree-discord-set-buttons { + display: flex; + gap: 8px; + margin-top: 12px +}