some ui related fixes and modifications (#568)

* more modal tweaks

* t

* (IconViewer): better icon detection (+60 atm)

* fix open themes folder

* ui tweaks and minor fixes
This commit is contained in:
prism 2025-12-16 21:28:46 +01:00 • committed by GitHub
parent faff196008
commit 52484e5d72
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 149 additions and 128 deletions

View file

@ -381,7 +381,7 @@ function ThemesTab() {
const [themeNames, setThemeNames] = useState<Record<string, string>>(() => {
return settings.themeNames ?? {};
});
const [themeDir, , themeDirPending] = useAwaiter(VencordNative.themes.getThemesDir);
const [themeDir] = useAwaiter(VencordNative.themes.getThemesDir);
const [searchQuery, setSearchQuery] = useState("");
const [filter, setFilter] = useState(ThemeFilter.All);
@ -686,8 +686,7 @@ function ThemesTab() {
) : (
<QuickAction
text="Open Themes Folder"
action={() => showItemInFolder(themeDir!)}
disabled={themeDirPending}
action={() => VencordNative.themes.openFolder()}
Icon={FolderIcon}
/>
)}

View file

@ -9,36 +9,41 @@ import "./styles.css";
import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Paragraph } from "@components/Paragraph";
import { PluginDependencyList } from "@components/settings/tabs/plugins";
import { PluginCard } from "@components/settings/tabs/plugins/PluginCard";
import { ChangeList } from "@utils/ChangeList";
import { classes, Margins } from "@utils/index";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useForceUpdater } from "@utils/react";
import { findComponentByCodeLazy } from "@webpack";
import { Button, React, Tooltip, useMemo } from "@webpack/common";
import { JSX } from "react";
import { Tooltip, useMemo } from "@webpack/common";
import { ReactNode } from "react";
import Plugins from "~plugins";
import { getNewPlugins, getNewSettings, KnownPluginSettingsMap, writeKnownSettings } from "./knownSettings";
const cl = classNameFactory("vc-plugins-");
const cl = classNameFactory("vc-new-plugins-");
const CloseButton = findComponentByCodeLazy("CLOSE_BUTTON_LABEL");
const Checkbox = findComponentByCodeLazy(".checkboxWrapperDisabled:");
let hasSeen = false;
// Most of this was stolen from PluginSettings directly.
interface ModalComponentProps {
modalProps: ModalProps;
newPlugins: Set<string>;
newSettings: KnownPluginSettingsMap;
}
export function NewPluginsModal({ modalProps, newPlugins, newSettings }: { modalProps: ModalProps; newPlugins: Set<string>; newSettings: KnownPluginSettingsMap; }) {
function NewPluginsModal({ modalProps, newPlugins, newSettings }: ModalComponentProps) {
const settings = useSettings();
const changes = React.useMemo(() => new ChangeList<string>(), []);
let updateContinueButton = () => { };
const changes = useMemo(() => new ChangeList<string>(), []);
const forceUpdate = useForceUpdater();
const depMap = React.useMemo(() => {
const depMap = useMemo(() => {
const o = {} as Record<string, string[]>;
for (const plugin in Plugins) {
const deps = Plugins[plugin].dependencies;
@ -52,53 +57,50 @@ export function NewPluginsModal({ modalProps, newPlugins, newSettings }: { modal
return o;
}, []);
const mapPlugins = (array: string[]) => array.map(pn => Plugins[pn])
.sort((a, b) => a.name.localeCompare(b.name));
const sortedPlugins = useMemo(() => {
const mapPlugins = (array: string[]) => array.map(pn => Plugins[pn]).sort((a, b) => a.name.localeCompare(b.name));
return [
...mapPlugins([...newPlugins]),
...mapPlugins([...newSettings.keys()].filter(p => !newPlugins.has(p)))
];
}, []);
const sortedPlugins = useMemo(() => [
...mapPlugins([...newPlugins]),
...mapPlugins([...newSettings.keys()].filter(p => !newPlugins.has(p)))
], []);
const onRestartNeeded = (name: string) => {
changes.handleChange(name);
forceUpdate();
};
const plugins = [] as JSX.Element[];
const requiredPlugins = [] as JSX.Element[];
const pluginCards: ReactNode[] = [];
const requiredPluginCards: ReactNode[] = [];
for (const p of sortedPlugins) {
if (p.hidden)
continue;
if (p.hidden) continue;
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
if (isRequired) {
const tooltipText = p.required
? "This plugin is required for Equicord to function."
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
: <PluginDependencyList deps={depMap[p.name]?.filter(d => settings.plugins[d].enabled)} />;
requiredPlugins.push(
requiredPluginCards.push(
<Tooltip text={tooltipText} key={p.name}>
{({ onMouseLeave, onMouseEnter }) => (
<PluginCard
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
onRestartNeeded={name => {
changes.handleChange(name);
updateContinueButton();
}}
onRestartNeeded={onRestartNeeded}
disabled={true}
plugin={p}
key={p.name}
isNew={newPlugins.has(p.name)}
/>
)}
</Tooltip>
);
} else {
plugins.push(
pluginCards.push(
<PluginCard
onRestartNeeded={name => {
changes.handleChange(name);
updateContinueButton();
}}
onRestartNeeded={onRestartNeeded}
disabled={false}
plugin={p}
key={p.name}
@ -108,36 +110,63 @@ export function NewPluginsModal({ modalProps, newPlugins, newSettings }: { modal
}
}
const totalCount = pluginCards.length + requiredPluginCards.length;
const handleContinue = async () => {
await writeKnownSettings();
if (changes.hasChanges) {
location.reload();
} else {
modalProps.onClose();
}
};
return (
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<ModalHeader separator={false} className={cl("header")}>
<div className={cl("header-content")}>
<BaseText size="lg" weight="semibold" className={cl("title")}>
New Plugins and Settings ({totalCount})
</BaseText>
<BaseText size="sm" className={cl("description")}>
New plugins have been added since your last visit. Enable any you'd like or continue to dismiss.
</BaseText>
</div>
<div className={cl("header-trailing")}>
<CloseButton onClick={modalProps.onClose} />
</div>
</ModalHeader>
<ModalContent>
<div className={cl("grid")}>
{pluginCards}
{requiredPluginCards}
</div>
</ModalContent>
<ModalFooter>
<Flex className={cl("footer")}>
<Tooltip
text={
<>
The following plugins require a restart:
<ul className={cl("restart-list")}>
{changes.map(p => <li key={p}>{p}</li>)}
</ul>
</>
}
shouldShow={changes.hasChanges}
>
{tooltipProps => (
<Button
{...tooltipProps}
onClick={handleContinue}
>
{changes.hasChanges ? "Restart" : "Continue"}
</Button>
)}
</Tooltip>
return <ModalRoot {...modalProps} size={ModalSize.MEDIUM} >
<ModalHeader>
<BaseText size="lg" weight="semibold" style={{ flexGrow: 1 }}>New Plugins and Settings ({[...plugins, ...requiredPlugins].length})</BaseText>
<Tooltip text="Dismiss for this session">
{tooltipProps =>
<div {...tooltipProps}>
<ModalCloseButton
onClick={modalProps.onClose}
className={classes(cl("close"), "vc-newPluginsManager-close")}
/>
</div>
}
</Tooltip>
</ModalHeader>
<ModalContent>
<div className={cl("grid")}>
{[...plugins, ...requiredPlugins]}
</div>
</ModalContent>
<ModalFooter>
<Flex flexDirection="row-reverse">
<ContinueButton
close={modalProps.onClose}
changes={changes}
callback={(v: () => void) => updateContinueButton = v}
/>
</Flex>
<Flex flexDirection="row">
<div className="vc-newPluginsManager-disable-wrapper">
<Checkbox
type="inverted"
value={!settings?.plugins?.NewPluginsManager?.enabled}
@ -145,50 +174,11 @@ export function NewPluginsModal({ modalProps, newPlugins, newSettings }: { modal
Settings.plugins.NewPluginsManager.enabled = !settings?.plugins?.NewPluginsManager?.enabled;
}}
>
<BaseText>Don't show this again</BaseText>
Don't show this again
</Checkbox>
</div>
</Flex>
</ModalFooter>
</ModalRoot>;
}
function ContinueButton(props: { callback: (update: () => void) => void; changes: ChangeList<string>; close: () => any; }) {
const update = useForceUpdater();
props.callback(update);
return <Tooltip
tooltipClassName="vc-newPluginsManager-restart-tooltip"
text={<>
The following plugins require a restart:
<div className={Margins.bottom8} />
<ul className="vc-newPluginsManager-restart-list">
{props.changes.map(p => <li key={p}>{p}</li>)}
</ul>
</>}
shouldShow={props.changes.hasChanges}
>
{tooltipProps =>
<Button
{...tooltipProps}
color={Button.Colors.GREEN}
onClick={async () => {
await writeKnownSettings();
props.changes.hasChanges ? location.reload() : props.close();
}}
>
{props.changes.hasChanges ? "Restart" : "Continue"}
</Button>
}
</Tooltip>;
}
function makeDependencyList(deps: string[]) {
return (
<React.Fragment>
<Paragraph>This plugin is required by:</Paragraph>
{deps.map((dep: string) => <Paragraph key={dep} className={cl("dep-text")}>{dep}</Paragraph>)}
</React.Fragment>
</Flex>
</ModalFooter>
</ModalRoot>
);
}
@ -197,12 +187,14 @@ export async function openNewPluginsModal() {
const newSettings = await getNewSettings();
if ((newPlugins.size || newSettings.size) && !hasSeen) {
hasSeen = true;
const modalKey = openModal(modalProps => <ErrorBoundary noop onError={() => { closeModal(modalKey); }}>
<NewPluginsModal
modalProps={modalProps}
newPlugins={newPlugins}
newSettings={newSettings}
/>
</ErrorBoundary>);
const modalKey = openModal(modalProps => (
<ErrorBoundary noop onError={() => closeModal(modalKey)}>
<NewPluginsModal
modalProps={modalProps}
newPlugins={newPlugins}
newSettings={newSettings}
/>
</ErrorBoundary>
));
}
}

View file

@ -1,20 +1,50 @@
.vc-newPluginsManager-restart-tooltip {
max-width: 280px;
.vc-new-plugins-header {
align-items: flex-start;
position: relative;
padding: 16px 16px 0 24px;
}
.vc-newPluginsManager-restart-list {
.vc-new-plugins-header-content {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
}
.vc-new-plugins-header-trailing {
position: absolute;
top: 8px;
right: 8px;
}
.vc-new-plugins-title {
color: var(--text-strong);
padding-right: 32px;
}
.vc-new-plugins-description {
color: var(--text-subtle);
padding-right: 60px;
}
.vc-new-plugins-grid {
display: grid;
gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.vc-new-plugins-footer {
width: 100%;
align-items: center;
flex-direction: row-reverse;
gap: 16px;
}
.vc-new-plugins-restart-list {
padding-left: 1.25em;
margin-top: 8px;
}
.vc-newPluginsManager-restart-list li {
.vc-new-plugins-restart-list li {
list-style: disc;
}
.vc-newPluginsManager-close > div {
display: flex;
}
.vc-newPluginsManager-disable-wrapper {
display: flex;
margin: auto 0;
}