diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index 6353a9716..f3884701a 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -16,56 +16,34 @@ * 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"; +import { LazyComponent } from "@utils/react"; +import { find, findByPropsLazy } from "@webpack"; +import { React, useStateFromStores } from "@webpack/common"; -const cl = classNameFactory("vc-bf-"); -const classes = findByPropsLazy("sidebar", "guilds"); +import { ExpandedGuildFolderStore, settings } from "."; const Animations = findByPropsLazy("a", "animated", "useTransition"); -const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); -const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); +const GuildsBar = LazyComponent(() => find(m => m.type?.toString().includes('("guildsnav")'))); -function Guilds(props: { - className: string; - bfGuildFolders: any[]; -}) { - // @ts-expect-error - const res = Vencord.Plugins.plugins.BetterFolders.Guilds(props); - - // TODO: Make this better - const scrollerProps = res.props.children?.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(() => { +export default ErrorBoundary.wrap(guildsBarProps => { 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) + const visible = !!expandedFolders.size; + const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join("")); + + if (!guilds || !settings.store.sidebarAnim) { return visible - ?
{Sidebar}
+ ?
{Sidebar}
: null; + } return ( { leave={{ width: 0 }} config={{ duration: 200 }} > - {(style, show) => show && ( - - {Sidebar} - - )} + {(style, show) => + show && ( + + {Sidebar} + + ) + } ); }, { noop: true }); diff --git a/src/plugins/betterFolders/betterFolders.css b/src/plugins/betterFolders/betterFolders.css deleted file mode 100644 index 5d8fc2188..000000000 --- a/src/plugins/betterFolders/betterFolders.css +++ /dev/null @@ -1,17 +0,0 @@ -.vc-bf-folder-sidebar [class*="wrapper-"] > [class*="listItem-"]:first-of-type, -.vc-bf-folder-sidebar [class*="unreadMentionsIndicator"] { - 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 deleted file mode 100644 index d41ba75c7..000000000 --- a/src/plugins/betterFolders/index.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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, Devs.AutumnVN], - 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.getGuildFolders().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/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx new file mode 100644 index 000000000..e4bdc9800 --- /dev/null +++ b/src/plugins/betterFolders/index.tsx @@ -0,0 +1,229 @@ +/* + * 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 { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { FluxDispatcher, i18n } from "@webpack/common"; + +import FolderSideBar from "./FolderSideBar"; + +const GuildFolderStore = findStoreLazy("SortedGuildStore"); +export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); +const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand"); + +let lastGuildId = null as string | null; +let dispatchingFoldersClose = false; + +function getGuildFolder(id: string) { + return GuildFolderStore.getGuildFolders().find(folder => folder.guildIds.includes(id)); +} + +function closeFolders() { + for (const id of ExpandedGuildFolderStore.getExpandedFolders()) + FolderUtils.toggleGuildFolderExpand(id); +} + +export const settings = definePluginSettings({ + sidebar: { + type: OptionType.BOOLEAN, + description: "Display servers from folder on dedicated sidebar", + restartNeeded: true, + default: true + }, + sidebarAnim: { + type: OptionType.BOOLEAN, + description: "Animate opening the folder sidebar", + restartNeeded: true, + 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", + restartNeeded: true, + 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, Devs.AutumnVN, Devs.Nuckyz], + + settings, + + patches: [ + { + find: '("guildsnav")', + predicate: () => settings.store.sidebar, + replacement: [ + // Create the isBetterFolders variable in the GuildsBar component + { + match: /(?<=let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?)(?=}=\i,)/, + replace: ",isBetterFolders" + }, + // If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children + { + match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/, + replace: '$&.filter($self.makeGuildsBarGuildListFilter(typeof isBetterFolders!=="undefined"?isBetterFolders:false))' + }, + // If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children + { + match: /unreadMentionsIndicatorBottom,barClassName.+?}\)\]/, + replace: '$&.filter($self.makeGuildsBarTreeFilter(typeof isBetterFolders!=="undefined"?isBetterFolders:false))' + }, + // Export the isBetterFolders variable to the folders component + { + match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/, + replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,' + }, + // Avoid rendering servers that are not in folders in the Better Folders sidebar + { + match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?GUILD:)/, + replace: 'if((typeof isBetterFolders!=="undefined"?isBetterFolders:false)&&$1.parentId==null)return null;' + } + ] + }, + { + find: ".FOLDER_ITEM_GUILD_ICON_MARGIN);", + replacement: [ + // Create the isBetterFolders variable in the nested folders component (the parent exports all the props so we don't have to patch it) + { + match: /(?<=let{folderNode:\i,setNodeRef:\i,)/, + replace: "isBetterFolders," + }, + // If we are rendering the normal GuildsBar sidebar, we make Discord think the folder is always collapsed to show better icons (the mini guild icons) and avoid transitions + { + match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i).+?;)(?=let)/, + replace: '$1=(typeof isBetterFolders!=="undefined"?isBetterFolders:false)?$1:false;' + }, + // If we are rendering the Better Folders sidebar, we filter out folders that are not expanded + { + match: /(?=return\(0,\i.\i\)\("div")(?<=selected:\i,expanded:(\i),.+?)/, + replace: (_, expanded) => `if((typeof isBetterFolders!=="undefined"?isBetterFolders:false)&&!${expanded})return null;` + } + // This code is required for the plugin to work, but we don't need it right now because we are making Discord think the folder is always collapsed + // If we no longer want to make Discord think the folder is always collapsed, we can use this code for the plugin to work + // One reason to no longer want that is to make better icons (the mini guild icons) no longer show + /* // Disable expanding and collapsing folders transition in the normal GuildsBar sidebar + { + match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/, + replace: '(typeof isBetterFolders!=="undefined"?isBetterFolders:false)&&' + }, + // If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded + { + match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/, + replace: (m, expanded) => `${m}((typeof isBetterFolders!=="undefined"?isBetterFolders:false)||!${expanded})&&` + } */ + ] + }, + { + find: "APPLICATION_LIBRARY,render", + predicate: () => settings.store.sidebar, + replacement: { + // Render the Better Folders sidebar + match: /(?<=({className:\i\.guilds,themeOverride:\i})\))/, + replace: ",$self.FolderSideBar($1)" + } + }, + { + find: ".Messages.DISCODO_DISABLED", + predicate: () => settings.store.closeAllHomeButton, + replacement: { + // Close all folders when clicking the home button + match: /(?<=onClick:\(\)=>{)(?=.{0,200}"discodo")/, + replace: "$self.closeFolders();" + } + } + ], + + flux: { + CHANNEL_SELECT(data) { + if (!settings.store.closeAllFolders && !settings.store.forceOpen) + return; + + if (lastGuildId !== data.guildId) { + lastGuildId = data.guildId; + const guildFolder = getGuildFolder(data.guildId); + + if (guildFolder?.folderId) { + if (settings.store.forceOpen && !ExpandedGuildFolderStore.isFolderExpanded(guildFolder.folderId)) { + FolderUtils.toggleGuildFolderExpand(guildFolder.folderId); + } + } else if (settings.store.closeAllFolders) { + closeFolders(); + } + } + }, + + TOGGLE_GUILD_FOLDER_EXPAND(data) { + if (settings.store.closeOthers && !dispatchingFoldersClose) { + dispatchingFoldersClose = true; + + FluxDispatcher.wait(() => { + const expandedFolders = ExpandedGuildFolderStore.getExpandedFolders(); + + if (expandedFolders.size > 1) { + for (const id of expandedFolders) if (id !== data.folderId) + FolderUtils.toggleGuildFolderExpand(id); + } + + dispatchingFoldersClose = false; + }); + } + } + }, + + makeGuildsBarGuildListFilter(isBetterFolders: boolean) { + return child => { + if (isBetterFolders) { + return child?.props?.["aria-label"] === i18n.Messages.SERVERS; + } + return true; + }; + }, + + makeGuildsBarTreeFilter(isBetterFolders: boolean) { + return child => { + if (isBetterFolders) { + return "onScroll" in child.props; + } + return true; + }; + }, + + FolderSideBar: guildsBarProps => , + + closeFolders +});