diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx new file mode 100644 index 00000000..3e44a580 --- /dev/null +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -0,0 +1,84 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { Settings } from "@api/settings"; +import { classNameFactory } from "@api/Styles"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { i18n, React, useStateFromStores } from "@webpack/common"; + +const cl = classNameFactory("vc-bf-"); +const classes = findByPropsLazy("sidebar", "guilds"); + +const Animations = findByPropsLazy("a", "animated", "useTransition"); +const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); +const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); + +function Guilds(props: { + className: string; + bfGuildFolders: any[]; +}) { + // @ts-expect-error + const res = Vencord.Plugins.plugins.BetterFolders.Guilds(props); + + const scrollerProps = res.props.children?.props?.children?.[1]?.props; + if (scrollerProps?.children) { + const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS); + if (servers) scrollerProps.children = servers; + } + + return res; +} + +export default ErrorBoundary.wrap(() => { + const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders()); + const fullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext()); + + const guilds = document.querySelector(`.${classes.guilds}`); + + const visible = !!expandedFolders.size; + const className = cl("folder-sidebar", { fullscreen }); + + const Sidebar = ( + + ); + + if (!guilds || !Settings.plugins.BetterFolders.sidebarAnim) + return visible + ?
{Sidebar}
+ : null; + + return ( + + {(style, show) => show && ( + + {Sidebar} + + )} + + ); +}, { noop: true }); diff --git a/src/plugins/betterFolders/betterFolders.css b/src/plugins/betterFolders/betterFolders.css new file mode 100644 index 00000000..6efa01d4 --- /dev/null +++ b/src/plugins/betterFolders/betterFolders.css @@ -0,0 +1,16 @@ +.vc-bf-folder-sidebar [class*="wrapper-"] > div:first-of-type { + display: none; +} + +.vc-bf-folder-sidebar [class*="expandedFolderBackground-"] { + background-color: transparent; +} + +.vc-bf-folder-sidebar { + display: flex; +} + +.vc-bf-fullscreen { + width: 0 !important; + visibility: hidden; +} diff --git a/src/plugins/betterFolders/index.ts b/src/plugins/betterFolders/index.ts new file mode 100644 index 00000000..1b6ce4ec --- /dev/null +++ b/src/plugins/betterFolders/index.ts @@ -0,0 +1,177 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import "./betterFolders.css"; + +import { definePluginSettings } from "@api/settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; +import { FluxDispatcher } from "@webpack/common"; + +import FolderSideBar from "./FolderSideBar"; + +const GuildsTree = findLazy(m => m.prototype?.convertToFolder); +const GuildFolderStore = findStoreLazy("SortedGuildStore"); +const ExpandedFolderStore = findStoreLazy("ExpandedGuildFolderStore"); +const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand"); + +const settings = definePluginSettings({ + sidebar: { + type: OptionType.BOOLEAN, + description: "Display servers from folder on dedicated sidebar", + default: true, + }, + sidebarAnim: { + type: OptionType.BOOLEAN, + description: "Animate opening the folder sidebar", + default: true, + }, + closeAllFolders: { + type: OptionType.BOOLEAN, + description: "Close all folders when selecting a server not in a folder", + default: false, + }, + closeAllHomeButton: { + type: OptionType.BOOLEAN, + description: "Close all folders when clicking on the home button", + default: false, + }, + closeOthers: { + type: OptionType.BOOLEAN, + description: "Close other folders when opening a folder", + default: false, + }, + forceOpen: { + type: OptionType.BOOLEAN, + description: "Force a folder to open when switching to a server of that folder", + default: false, + }, +}); + +export default definePlugin({ + name: "BetterFolders", + description: "Shows server folders on dedicated sidebar and adds folder related improvements", + authors: [Devs.juby], + patches: [ + { + find: '("guildsnav")', + predicate: () => settings.store.sidebar, + replacement: [ + { + match: /(\i)\(\){return \i\(\(0,\i\.jsx\)\("div",{className:\i\(\)\.guildSeparator}\)\)}/, + replace: "$&$self.Separator=$1;" + }, + + // Folder component patch + { + match: /\i\(\(function\(\i,\i,\i\){var \i=\i\.key;return.+\(\i\)},\i\)}\)\)/, + replace: "arguments[0].bfHideServers?null:$&" + }, + + // BEGIN Guilds component patch + { + match: /(\i)\.themeOverride,(.{15,25}\(function\(\){var \i=)(\i\.\i\.getGuildsTree\(\))/, + replace: "$1.themeOverride,bfPatch=$1.bfGuildFolders,$2bfPatch?$self.getGuildsTree(bfPatch,$3):$3" + }, + { + match: /return(\(0,\i\.jsx\))(\(\i,{)(folderNode:\i,setNodeRef:\i\.setNodeRef,draggable:!0,.+},\i\.id\));case/, + replace: "var bfHideServers=typeof bfPatch==='undefined',folder=$1$2bfHideServers,$3;return !bfHideServers&&arguments[1]?[$1($self.Separator,{}),folder]:folder;case" + }, + // END + + { + match: /\("guildsnav"\);return\(0,\i\.jsx\)\(.{1,6},{navigator:\i,children:\(0,\i\.jsx\)\(/, + replace: "$&$self.Guilds=" + } + ] + }, + { + find: "APPLICATION_LIBRARY,render", + predicate: () => settings.store.sidebar, + replacement: { + match: /(\(0,\i\.jsx\))\(\i\..,{className:\i\(\)\.guilds,themeOverride:\i}\)/, + replace: "$&,$1($self.FolderSideBar,{})" + } + }, + { + find: '("guildsnav")', + predicate: () => settings.store.closeAllHomeButton, + replacement: { + match: ",onClick:function(){if(!__OVERLAY__){", + replace: "$&$self.closeFolders();" + } + } + ], + + settings, + + start() { + const getGuildFolder = (id: string) => GuildFolderStore.guildFolders.find(f => f.guildIds.includes(id)); + + FluxDispatcher.subscribe("CHANNEL_SELECT", this.onSwitch = data => { + if (!settings.store.closeAllFolders && !settings.store.forceOpen) + return; + + if (this.lastGuildId !== data.guildId) { + this.lastGuildId = data.guildId; + + const guildFolder = getGuildFolder(data.guildId); + if (guildFolder?.folderId) { + if (settings.store.forceOpen && !ExpandedFolderStore.isFolderExpanded(guildFolder.folderId)) + FolderUtils.toggleGuildFolderExpand(guildFolder.folderId); + } else if (settings.store.closeAllFolders) + this.closeFolders(); + } + }); + + FluxDispatcher.subscribe("TOGGLE_GUILD_FOLDER_EXPAND", this.onToggleFolder = e => { + if (settings.store.closeOthers && !this.dispatching) + FluxDispatcher.wait(() => { + const expandedFolders = ExpandedFolderStore.getExpandedFolders(); + if (expandedFolders.size > 1) { + this.dispatching = true; + + for (const id of expandedFolders) if (id !== e.folderId) + FolderUtils.toggleGuildFolderExpand(id); + + this.dispatching = false; + } + }); + }); + }, + + stop() { + FluxDispatcher.unsubscribe("CHANNEL_SELECT", this.onSwitch); + FluxDispatcher.unsubscribe("TOGGLE_GUILD_FOLDER_EXPAND", this.onToggleFolder); + }, + + FolderSideBar, + + getGuildsTree(folders, oldTree) { + const tree = new GuildsTree(); + tree.root.children = oldTree.root.children.filter(e => folders.includes(e.id)); + tree.nodes = folders.map(id => oldTree.nodes[id]); + return tree; + }, + + closeFolders() { + for (const id of ExpandedFolderStore.getExpandedFolders()) + FolderUtils.toggleGuildFolderExpand(id); + }, +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index c54676f4..7054db2b 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -226,6 +226,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "TheKodeToad", id: 706152404072267788n }, + juby: { + name: "Juby210", + id: 324622488644616195n + }, Alyxia: { name: "Alyxia Sother", id: 952185386350829688n diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index 59233a48..038163d0 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -30,6 +30,7 @@ export interface FluxDispatcher { isDispatching(): boolean; subscribe(event: FluxEvents, callback: (data: any) => void): void; unsubscribe(event: FluxEvents, callback: (data: any) => void): void; + wait(callback: () => void): void; } export type Parser = Record<