Decor: make wysiwyg better
This commit is contained in:
parent
cbab61da91
commit
779a87b9b1
3 changed files with 105 additions and 36 deletions
|
|
@ -9,6 +9,7 @@ import "./ui/styles.css";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
import { User } from "@vencord/discord-types";
|
||||||
import { UserStore } from "@webpack/common";
|
import { UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import { CDN_URL, RAW_SKU_ID, setBaseUrl, SKU_ID } from "./lib/constants";
|
import { CDN_URL, RAW_SKU_ID, setBaseUrl, SKU_ID } from "./lib/constants";
|
||||||
|
|
@ -29,6 +30,7 @@ export default definePlugin({
|
||||||
description: "Create and use your own custom avatar decorations, or pick your favorite from the presets.",
|
description: "Create and use your own custom avatar decorations, or pick your favorite from the presets.",
|
||||||
tags: ["Appearance", "Customisation"],
|
tags: ["Appearance", "Customisation"],
|
||||||
authors: [Devs.FieryFlames],
|
authors: [Devs.FieryFlames],
|
||||||
|
isModified: true,
|
||||||
patches: [
|
patches: [
|
||||||
// Patch MediaResolver to return correct URL for Decor avatar decorations
|
// Patch MediaResolver to return correct URL for Decor avatar decorations
|
||||||
{
|
{
|
||||||
|
|
@ -122,10 +124,17 @@ export default definePlugin({
|
||||||
find: '("UserProfileModalV2EditingPanel")',
|
find: '("UserProfileModalV2EditingPanel")',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /disabled:\i\|\|\i,errorMessage:\i\}\),/,
|
match: /disabled:(\i\|\|\i),avatarErrorMessage:\i,avatarDecorationErrorMessage:\i\}\),/,
|
||||||
replace: "$&$self.ExperimentDecorSection(),"
|
replace: "$&$self.ExperimentDecorSection({disabled:$1}),"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "isPreviewingMainProfileFallback:",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=avatarDecorationSrc:(\i),.{0,150}userId:(\i)\.id,.{0,100}avatarDecorationOverride:(\i\?\i:\i\?\i:void 0),animateOnHover:!\i\}\))/,
|
||||||
|
replace: ",vcDecorTileDecoration=($1=$self.useDiscordTileDecorationSrc($2,$1,$3))"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
settings,
|
settings,
|
||||||
|
|
@ -158,6 +167,11 @@ export default definePlugin({
|
||||||
|
|
||||||
useUserDecorAvatarDecoration,
|
useUserDecorAvatarDecoration,
|
||||||
|
|
||||||
|
useDiscordTileDecorationSrc(user: User, src: string | null, override: unknown) {
|
||||||
|
const hasDecorDecoration = useUsersDecorationsStore(state => state.getAsset(user.id) != null);
|
||||||
|
return override === undefined && hasDecorDecoration ? null : src;
|
||||||
|
},
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
await setBaseUrl(settings.store.baseUrl);
|
await setBaseUrl(settings.store.baseUrl);
|
||||||
useUsersDecorationsStore.getState().fetch(UserStore.getCurrentUser().id, true);
|
useUsersDecorationsStore.getState().fetch(UserStore.getCurrentUser().id, true);
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,8 @@ export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | nu
|
||||||
state => {
|
state => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
const newDecorAvatarDecoration = state.getAsset(user.id);
|
const newDecorAvatarDecoration = state.getAsset(user.id);
|
||||||
if (!newDecorAvatarDecoration) return;
|
if (newDecorAvatarDecoration === undefined) return;
|
||||||
if (decorAvatarDecoration !== newDecorAvatarDecoration) setDecorAvatarDecoration(newDecorAvatarDecoration);
|
setDecorAvatarDecoration(newDecorAvatarDecoration);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,76 @@
|
||||||
|
|
||||||
import { Button } from "@components/Button";
|
import { Button } from "@components/Button";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
|
import { SKU_ID } from "@plugins/decor/lib/constants";
|
||||||
import { useAuthorizationStore } from "@plugins/decor/lib/stores/AuthorizationStore";
|
import { useAuthorizationStore } from "@plugins/decor/lib/stores/AuthorizationStore";
|
||||||
import { useCurrentUserDecorationsStore } from "@plugins/decor/lib/stores/CurrentUserDecorationsStore";
|
import { useCurrentUserDecorationsStore } from "@plugins/decor/lib/stores/CurrentUserDecorationsStore";
|
||||||
|
import { useUsersDecorationsStore } from "@plugins/decor/lib/stores/UsersDecorationsStore";
|
||||||
import { cl } from "@plugins/decor/ui";
|
import { cl } from "@plugins/decor/ui";
|
||||||
import { openChangeDecorationModal } from "@plugins/decor/ui/modals/ChangeDecorationModal";
|
import { openChangeDecorationModal } from "@plugins/decor/ui/modals/ChangeDecorationModal";
|
||||||
import { findComponentByCodeLazy } from "@webpack";
|
import { classes } from "@utils/misc";
|
||||||
import { NewCustomizationSection, useEffect } from "@webpack/common";
|
import { findByCodeLazy, findComponentByCodeLazy, findCssClassesLazy } from "@webpack";
|
||||||
|
import { Avatar, NewCustomizationSection, useEffect, UserStore } from "@webpack/common";
|
||||||
|
import type { ComponentType } from "react";
|
||||||
|
|
||||||
const CustomizationSection = findComponentByCodeLazy(".DESCRIPTION", "hasBackground:");
|
const CustomizationSection = findComponentByCodeLazy(".DESCRIPTION", "hasBackground:");
|
||||||
|
const EditTile = findComponentByCodeLazy(".tileButton,", "affordance:");
|
||||||
|
const useAvatarWithDecoration = findByCodeLazy("avatarOverride:", "avatarDecorationOverride:", "avatarPlaceholderSrc:");
|
||||||
|
const TilePreviewClasses = findCssClassesLazy("emptyPreview", "fadeInAvatarImg");
|
||||||
|
|
||||||
|
const AvatarWithDecoration = Avatar as ComponentType<any>;
|
||||||
|
|
||||||
export interface DecorSectionProps {
|
export interface DecorSectionProps {
|
||||||
hideTitle?: boolean;
|
hideTitle?: boolean;
|
||||||
hideDivider?: boolean;
|
hideDivider?: boolean;
|
||||||
noMargin?: boolean;
|
noMargin?: boolean;
|
||||||
useNewSection?: boolean;
|
useNewSection?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DecorSection({ hideTitle = false, hideDivider = false, noMargin = false, useNewSection = false }: DecorSectionProps) {
|
function DecorTilePreview({ asset, isInteracting }: { asset: string | null | undefined; isInteracting: boolean; }) {
|
||||||
|
const { avatarDecorationSrc, avatarSrc, avatarPlaceholderSrc } = useAvatarWithDecoration({
|
||||||
|
userId: UserStore.getCurrentUser().id,
|
||||||
|
size: "SIZE_72",
|
||||||
|
showPending: true,
|
||||||
|
avatarDecorationOverride: asset ? { asset, skuId: SKU_ID } : null,
|
||||||
|
animateOnHover: !isInteracting
|
||||||
|
});
|
||||||
|
|
||||||
|
const showAvatar = avatarDecorationSrc != null && isInteracting;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AvatarWithDecoration
|
||||||
|
className={classes(avatarDecorationSrc == null && TilePreviewClasses.emptyPreview)}
|
||||||
|
src={showAvatar ? avatarSrc : avatarPlaceholderSrc}
|
||||||
|
imageClassName={showAvatar ? TilePreviewClasses.fadeInAvatarImg : undefined}
|
||||||
|
avatarDecoration={avatarDecorationSrc}
|
||||||
|
size="SIZE_72"
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DecorTile({ disabled, onClick, onRemove }: { disabled?: boolean; onClick(): void; onRemove?(): void; }) {
|
||||||
|
const asset = useUsersDecorationsStore(state => state.getAsset(UserStore.getCurrentUser().id));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditTile
|
||||||
|
variant="square"
|
||||||
|
affordance={onRemove
|
||||||
|
? { type: "remove", onClick: onRemove, accessibleLabel: "Remove Decor decoration" }
|
||||||
|
: asset ? undefined : "add"
|
||||||
|
}
|
||||||
|
onClick={onClick}
|
||||||
|
accessibleLabel="Decor"
|
||||||
|
accessibleValue={asset ? "Decor decoration" : "None"}
|
||||||
|
aria-haspopup="dialog"
|
||||||
|
disabled={disabled}
|
||||||
|
renderPreview={(isInteracting: boolean) => <DecorTilePreview asset={asset} isInteracting={isInteracting} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DecorSection({ hideTitle = false, hideDivider = false, noMargin = false, useNewSection = false, disabled = false }: DecorSectionProps) {
|
||||||
const authorization = useAuthorizationStore();
|
const authorization = useAuthorizationStore();
|
||||||
const { selectedDecoration, select: selectDecoration, fetch: fetchDecorations } = useCurrentUserDecorationsStore();
|
const { selectedDecoration, select: selectDecoration, fetch: fetchDecorations } = useCurrentUserDecorationsStore();
|
||||||
|
|
||||||
|
|
@ -30,49 +83,51 @@ export default function DecorSection({ hideTitle = false, hideDivider = false, n
|
||||||
if (authorization.isAuthorized()) fetchDecorations();
|
if (authorization.isAuthorized()) fetchDecorations();
|
||||||
}, [authorization.token]);
|
}, [authorization.token]);
|
||||||
|
|
||||||
const NewSection = useNewSection ? NewCustomizationSection : undefined;
|
const openModal = () => {
|
||||||
|
|
||||||
if (useNewSection && !NewSection) return null;
|
|
||||||
|
|
||||||
const Section = (useNewSection ? NewCustomizationSection : CustomizationSection);
|
|
||||||
const sectionProps = useNewSection
|
|
||||||
? { heading: hideTitle ? undefined : "Decor" }
|
|
||||||
: {
|
|
||||||
title: hideTitle ? undefined : "Decor",
|
|
||||||
hasBackground: true,
|
|
||||||
hideDivider,
|
|
||||||
className: noMargin ? cl("section-remove-margin") : undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeLabel = useNewSection ? "Change" : "Change Decoration";
|
|
||||||
const removeLabel = useNewSection ? "Remove" : "Remove Decoration";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Section {...sectionProps}>
|
|
||||||
<Flex gap="4px">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (!authorization.isAuthorized()) {
|
if (!authorization.isAuthorized()) {
|
||||||
authorization.authorize().then(openChangeDecorationModal).catch(() => { });
|
authorization.authorize().then(openChangeDecorationModal).catch(() => { });
|
||||||
} else {
|
} else {
|
||||||
openChangeDecorationModal();
|
openChangeDecorationModal();
|
||||||
}
|
}
|
||||||
}}
|
};
|
||||||
variant="primary"
|
|
||||||
size="small"
|
const canRemove = selectedDecoration != null && authorization.isAuthorized();
|
||||||
|
|
||||||
|
if (useNewSection) {
|
||||||
|
if (!NewCustomizationSection) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NewCustomizationSection heading={hideTitle ? undefined : "Decor"} disabled={disabled}>
|
||||||
|
<DecorTile
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={openModal}
|
||||||
|
onRemove={canRemove ? () => selectDecoration(null) : undefined}
|
||||||
|
/>
|
||||||
|
</NewCustomizationSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CustomizationSection
|
||||||
|
title={hideTitle ? undefined : "Decor"}
|
||||||
|
hasBackground
|
||||||
|
hideDivider={hideDivider}
|
||||||
|
className={noMargin ? cl("section-remove-margin") : undefined}
|
||||||
>
|
>
|
||||||
{changeLabel}
|
<Flex gap="4px">
|
||||||
|
<Button onClick={openModal} variant="primary" size="small">
|
||||||
|
Change Decoration
|
||||||
</Button>
|
</Button>
|
||||||
{selectedDecoration && authorization.isAuthorized() && (
|
{canRemove && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => selectDecoration(null)}
|
onClick={() => selectDecoration(null)}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
{removeLabel}
|
Remove Decoration
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Section>
|
</CustomizationSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue