profileSets avatar / types fix (#794)
* profileSets avatar / types fix - Fix avatar pending payload flow to use USER_PROFILE_SETTINGS_SET_PENDING_AVATAR with NEW_ASSET imageUri payload. - Resolve TypeScript typing issues around IconUtils avatar/banner URL resolution without any casts. - Remove debug logging and legacy pending dispatch paths; keep recents and profile pending behavior aligned with current Discord profile settings. - Clean up profileSets rulebook compliance issues (nullish coalescing/defaults, typed preset field updates, no logger/console usage). * profilesets: remove display name sanitizer subscription * fixed checks * fix pending profile event flow * skip collectible dispatch when already equipped * fixed checks * Remove empty stop method from profile set plugin Removed the empty stop method from the profile set plugin. * bleh * Update src/equicordplugins/profileSets/utils/profile.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update src/equicordplugins/profileSets/utils/profile.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * t * fix fallback guard * restore avatar size fallback * fix fallback guard * fix fallback guard * fix to slider * address gemini review issues * fix avatarSize * update avatarSize * address review: destructure avatarSize * restore direct avatarSize access * destructure avatarSize * Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
parent
cd125be7e8
commit
189ab429bb
7 changed files with 191 additions and 122 deletions
|
|
@ -14,15 +14,15 @@ export interface PermissionOverwrite {
|
||||||
export interface AvatarDecorationData {
|
export interface AvatarDecorationData {
|
||||||
asset: string;
|
asset: string;
|
||||||
expires_at: string | null;
|
expires_at: string | null;
|
||||||
sku_id: string;
|
skuId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clan identity information. */
|
/** Clan identity information. */
|
||||||
export interface ClanData {
|
export interface ClanData {
|
||||||
badge: string;
|
badge: string;
|
||||||
identity_enabled: boolean;
|
|
||||||
identity_guild_id: string;
|
|
||||||
tag: string;
|
tag: string;
|
||||||
|
identityEnabled: boolean;
|
||||||
|
identityGuildId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Display name style configuration. */
|
/** Display name style configuration. */
|
||||||
|
|
@ -38,7 +38,8 @@ export interface CollectibleItem {
|
||||||
expires_at: string | null;
|
expires_at: string | null;
|
||||||
label: string;
|
label: string;
|
||||||
palette: string;
|
palette: string;
|
||||||
sku_id: string;
|
type: number;
|
||||||
|
skuId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** User collectibles configuration. */
|
/** User collectibles configuration. */
|
||||||
|
|
|
||||||
2
packages/discord-types/src/common/User.d.ts
vendored
2
packages/discord-types/src/common/User.d.ts
vendored
|
|
@ -39,7 +39,7 @@ export class User extends DiscordRecord {
|
||||||
get avatarDecoration(): AvatarDecorationData | null;
|
get avatarDecoration(): AvatarDecorationData | null;
|
||||||
get createdAt(): Date;
|
get createdAt(): Date;
|
||||||
get isProvisional(): boolean;
|
get isProvisional(): boolean;
|
||||||
get nameplate(): { asset: string; sku_id: string; } | null;
|
get nameplate(): { asset: string; skuId: string; } | null;
|
||||||
get premiumGroupRole(): { id: string; name: string; color: number; } | null;
|
get premiumGroupRole(): { id: string; name: string; color: number; } | null;
|
||||||
get tag(): string;
|
get tag(): string;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { isNonNullish } from "@utils/guards";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
|
import { ProfilePreset } from "@vencord/discord-types";
|
||||||
import { ContextMenuApi, Menu, React, TextInput } from "@webpack/common";
|
import { ContextMenuApi, Menu, React, TextInput } from "@webpack/common";
|
||||||
|
|
||||||
import { cl } from "..";
|
import { cl } from "..";
|
||||||
|
|
@ -37,6 +39,7 @@ export function PresetList({
|
||||||
currentPage,
|
currentPage,
|
||||||
onPageChange
|
onPageChange
|
||||||
}: PresetListProps) {
|
}: PresetListProps) {
|
||||||
|
type EditableProfile = Omit<ProfilePreset, "name" | "timestamp">;
|
||||||
const [renaming, setRenaming] = React.useState<number>(-1);
|
const [renaming, setRenaming] = React.useState<number>(-1);
|
||||||
const [renameText, setRenameText] = React.useState("");
|
const [renameText, setRenameText] = React.useState("");
|
||||||
const isGuildProfile = section === "server";
|
const isGuildProfile = section === "server";
|
||||||
|
|
@ -149,11 +152,11 @@ export function PresetList({
|
||||||
label="Update"
|
label="Update"
|
||||||
action={async () => {
|
action={async () => {
|
||||||
const profile = await getCurrentProfile(guildId, { isGuildProfile });
|
const profile = await getCurrentProfile(guildId, { isGuildProfile });
|
||||||
Object.entries(profile).forEach(([key, value]) => {
|
await Promise.all(
|
||||||
if (value !== null && value !== undefined) {
|
(Object.entries(profile) as [keyof EditableProfile, EditableProfile[keyof EditableProfile]][])
|
||||||
updatePresetField(actualIndex, key as any, value, section, guildId);
|
.filter(([, value]) => isNonNullish(value))
|
||||||
}
|
.map(([key, value]) => updatePresetField(actualIndex, key, value, section, guildId))
|
||||||
});
|
);
|
||||||
onUpdate();
|
onUpdate();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ export function PresetManager({ section, guildId }: PresetManagerProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const avatarSize = settings.store.avatarSize || 40;
|
const { avatarSize } = settings.store;
|
||||||
const hasPresets = presets.length > 0;
|
const hasPresets = presets.length > 0;
|
||||||
const shouldShowPagination = filteredPresets.length > PRESETS_PER_PAGE;
|
const shouldShowPagination = filteredPresets.length > PRESETS_PER_PAGE;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,11 @@ import { loadPresets, PresetSection } from "./utils/storage";
|
||||||
export const cl = classNameFactory("vc-profile-presets-");
|
export const cl = classNameFactory("vc-profile-presets-");
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
avatarSize: {
|
avatarSize: {
|
||||||
type: OptionType.NUMBER,
|
type: OptionType.SLIDER,
|
||||||
description: "Avatar size in preset list.",
|
description: "Avatar size in preset list.",
|
||||||
default: 40,
|
markers: [56, 64, 72, 80, 88, 96],
|
||||||
|
default: 56,
|
||||||
|
stickToMarkers: true
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -40,8 +42,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "USER_SETTINGS_GUILD_PROFILE)",
|
find: "USER_SETTINGS_GUILD_PROFILE)",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /onChange:\i\}\)(?=.{0,25}profilePreviewTitle:)/,
|
match: /guildId:(\i\.id),onChange:(\i)\}\)(?=.{0,25}profilePreviewTitle:)/,
|
||||||
replace: '$&,$self.renderPresetSection("server")'
|
replace: 'guildId:$1,onChange:$2}),$self.renderPresetSection("server",$1)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,64 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { isNonNullish } from "@utils/guards";
|
||||||
import { ProfilePreset } from "@vencord/discord-types";
|
import { ProfilePreset } from "@vencord/discord-types";
|
||||||
|
import { findStoreLazy } from "@webpack";
|
||||||
|
import { showToast, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
import { getCurrentProfile } from "./profile";
|
import { getCurrentProfile } from "./profile";
|
||||||
import { addPreset, movePresetInArray, PresetSection, presets, removePreset, replaceAllPresets, savePresetsData, updatePreset, type ProfilePresetEx } from "./storage";
|
import { addPreset, movePresetInArray, presets, PresetSection, type ProfilePresetEx, removePreset, replaceAllPresets, savePresetsData, updatePreset } from "./storage";
|
||||||
|
|
||||||
|
const UserProfileSettingsStore = findStoreLazy("UserProfileSettingsStore");
|
||||||
|
|
||||||
|
function isImageInput(value: unknown): value is string | { imageUri: string; } {
|
||||||
|
if (typeof value === "string") return value.length > 0;
|
||||||
|
return typeof value === "object" && isNonNullish(value) && "imageUri" in value && typeof (value as { imageUri: unknown }).imageUri === "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFreshPendingAvatar(section: PresetSection, guildId?: string): string | null {
|
||||||
|
const pending = (section === "server" && guildId
|
||||||
|
? UserProfileSettingsStore.getPendingChanges?.(guildId)
|
||||||
|
: UserProfileSettingsStore.getPendingChanges?.()) ?? {};
|
||||||
|
const pendingObj = pending as Record<string, unknown>;
|
||||||
|
|
||||||
|
const candidates = [
|
||||||
|
pendingObj.selectedAvatarRaw,
|
||||||
|
pendingObj.presetAvatarRaw,
|
||||||
|
pendingObj.pendingAvatar,
|
||||||
|
pendingObj.avatar,
|
||||||
|
pendingObj.selectedAvatarProcessed,
|
||||||
|
pendingObj.presetAvatarProcessed
|
||||||
|
];
|
||||||
|
const selected = candidates.find(isImageInput);
|
||||||
|
if (!selected) return null;
|
||||||
|
return typeof selected === "string" ? selected : selected.imageUri;
|
||||||
|
}
|
||||||
|
|
||||||
export async function savePreset(name: string, section: PresetSection, guildId?: string) {
|
export async function savePreset(name: string, section: PresetSection, guildId?: string) {
|
||||||
const profile = await getCurrentProfile(guildId, { isGuildProfile: section === "server" });
|
const profile = await getCurrentProfile(guildId, { isGuildProfile: section === "server" });
|
||||||
|
const freshPendingAvatar = getFreshPendingAvatar(section, guildId);
|
||||||
|
const effectiveAvatar = freshPendingAvatar ?? profile.avatarDataUrl ?? null;
|
||||||
|
|
||||||
const newPreset: ProfilePresetEx = {
|
const newPreset: ProfilePresetEx = {
|
||||||
name,
|
name,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
...profile,
|
...profile,
|
||||||
|
avatarDataUrl: effectiveAvatar,
|
||||||
};
|
};
|
||||||
addPreset(newPreset);
|
addPreset(newPreset);
|
||||||
await savePresetsData(section);
|
await savePresetsData(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updatePresetField(index: number, field: keyof Omit<ProfilePreset, "name" | "timestamp">, value: any, section: PresetSection, guildId?: string) {
|
export async function updatePresetField<K extends keyof Omit<ProfilePreset, "name" | "timestamp">>(
|
||||||
|
index: number,
|
||||||
|
field: K,
|
||||||
|
value: Omit<ProfilePreset, "name" | "timestamp">[K],
|
||||||
|
section: PresetSection,
|
||||||
|
guildId?: string
|
||||||
|
) {
|
||||||
if (index < 0 || index >= presets.length) return;
|
if (index < 0 || index >= presets.length) return;
|
||||||
|
void guildId;
|
||||||
|
|
||||||
const updatedPreset = {
|
const updatedPreset = {
|
||||||
...presets[index],
|
...presets[index],
|
||||||
|
|
@ -55,17 +95,14 @@ export async function renamePreset(index: number, newName: string, section: Pres
|
||||||
}
|
}
|
||||||
|
|
||||||
export function exportPresets(section: PresetSection) {
|
export function exportPresets(section: PresetSection) {
|
||||||
try {
|
const dataStr = JSON.stringify(presets, null, 2);
|
||||||
const dataStr = JSON.stringify(presets, null, 2);
|
const dataBlob = new Blob([dataStr], { type: "application/json" });
|
||||||
const dataBlob = new Blob([dataStr], { type: "application/json" });
|
const url = URL.createObjectURL(dataBlob);
|
||||||
const url = URL.createObjectURL(dataBlob);
|
const link = document.createElement("a");
|
||||||
const link = document.createElement("a");
|
link.href = url;
|
||||||
link.href = url;
|
link.download = `profile-presets-${section}-${Date.now()}.json`;
|
||||||
link.download = `profile-presets-${section}-${Date.now()}.json`;
|
link.click();
|
||||||
link.click();
|
URL.revokeObjectURL(url);
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
} catch {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ImportDecision = "override" | "merge" | "cancel";
|
export type ImportDecision = "override" | "merge" | "cancel";
|
||||||
|
|
@ -108,6 +145,7 @@ export async function importPresets(
|
||||||
await savePresetsData(section);
|
await savePresetsData(section);
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
} catch {
|
} catch {
|
||||||
|
showToast("Failed to import presets. The file might be invalid.", Toasts.Type.FAILURE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,16 @@
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import { CustomStatus, DisplayNameStyles, Nameplate, ProfileEffect, ProfilePreset } from "@vencord/discord-types";
|
import { CustomStatus, DisplayNameStyles, Nameplate, ProfileEffect, ProfilePreset } from "@vencord/discord-types";
|
||||||
import { findStoreLazy } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
import { FluxDispatcher, GuildMemberStore,IconUtils, UserProfileStore, UserStore } from "@webpack/common";
|
import { FluxDispatcher, GuildMemberStore, IconUtils, UserProfileStore, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
const UserProfileSettingsStore = findStoreLazy("UserProfileSettingsStore");
|
const UserProfileSettingsStore = findStoreLazy("UserProfileSettingsStore");
|
||||||
const CustomStatusSettings = getUserSettingLazy("status", "customStatus")!;
|
const CustomStatusSettings = getUserSettingLazy("status", "customStatus")!;
|
||||||
|
|
||||||
type PendingChanges = Record<string, unknown> & {
|
type PendingChanges = Record<string, unknown> & {
|
||||||
|
selectedAvatarRaw?: ImageInput;
|
||||||
|
presetAvatarRaw?: ImageInput;
|
||||||
|
selectedAvatarProcessed?: ImageInput;
|
||||||
|
presetAvatarProcessed?: ImageInput;
|
||||||
pendingAvatar?: ImageInput;
|
pendingAvatar?: ImageInput;
|
||||||
avatar?: ImageInput;
|
avatar?: ImageInput;
|
||||||
pendingBanner?: ImageInput;
|
pendingBanner?: ImageInput;
|
||||||
|
|
@ -30,39 +34,23 @@ type PendingChanges = Record<string, unknown> & {
|
||||||
pendingPrimaryGuildId?: string | null;
|
pendingPrimaryGuildId?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UserExtras = {
|
|
||||||
collectibles?: {
|
|
||||||
nameplate?: Nameplate;
|
|
||||||
};
|
|
||||||
displayNameStyles?: DisplayNameStyles;
|
|
||||||
avatarDecorationData?: {
|
|
||||||
asset: string;
|
|
||||||
skuId: string;
|
|
||||||
} | null;
|
|
||||||
primaryGuild?: {
|
|
||||||
identityGuildId?: string | null;
|
|
||||||
} | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ProfileCollectible = {
|
|
||||||
skuId: string;
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
accessibilityLabel?: string;
|
|
||||||
reducedMotionSrc?: string;
|
|
||||||
thumbnailPreviewSrc?: string;
|
|
||||||
effects?: ProfileEffect["effects"];
|
|
||||||
animationType?: number;
|
|
||||||
staticFrameSrc?: string;
|
|
||||||
type?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ImageInput = string | { imageUri: string; } | null | undefined;
|
type ImageInput = string | { imageUri: string; } | null | undefined;
|
||||||
type DisplayNameStylesLike = DisplayNameStyles & {
|
type DisplayNameStylesLike = DisplayNameStyles & {
|
||||||
fontId?: number;
|
fontId?: number;
|
||||||
effectId?: number;
|
effectId?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CurrentProfileOptions = {
|
||||||
|
isGuildProfile?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type LoadPresetOptions = {
|
||||||
|
skipGlobalName?: boolean;
|
||||||
|
skipBio?: boolean;
|
||||||
|
skipPronouns?: boolean;
|
||||||
|
isGuildProfile?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
function dispatch(type: string, payload: Record<string, unknown>) {
|
function dispatch(type: string, payload: Record<string, unknown>) {
|
||||||
FluxDispatcher.dispatch({ type, ...payload });
|
FluxDispatcher.dispatch({ type, ...payload });
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +62,7 @@ function isNonEmptyString(value: unknown): value is string {
|
||||||
function hasImageInput(value: ImageInput): boolean {
|
function hasImageInput(value: ImageInput): boolean {
|
||||||
if (!value) return false;
|
if (!value) return false;
|
||||||
if (typeof value === "string") return value.length > 0;
|
if (typeof value === "string") return value.length > 0;
|
||||||
return typeof value === "object" && "imageUri" in value && isNonEmptyString(value.imageUri);
|
return typeof value === "object" && isNonEmptyString(value?.imageUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeDisplayNameStyles(value: DisplayNameStylesLike | null | undefined): DisplayNameStylesLike | null {
|
function normalizeDisplayNameStyles(value: DisplayNameStylesLike | null | undefined): DisplayNameStylesLike | null {
|
||||||
|
|
@ -111,7 +99,7 @@ export async function imageUrlToBase64(url: string): Promise<string | null> {
|
||||||
async function processImage(imageData: ImageInput, userId: string, type: "avatar" | "banner", guildId?: string, useGuildPath?: boolean): Promise<string | null> {
|
async function processImage(imageData: ImageInput, userId: string, type: "avatar" | "banner", guildId?: string, useGuildPath?: boolean): Promise<string | null> {
|
||||||
if (!imageData) return null;
|
if (!imageData) return null;
|
||||||
|
|
||||||
if (typeof imageData === "object" && "imageUri" in imageData && isNonEmptyString(imageData.imageUri)) {
|
if (typeof imageData === "object" && isNonEmptyString(imageData?.imageUri)) {
|
||||||
return imageData.imageUri;
|
return imageData.imageUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,17 +125,6 @@ async function processImage(imageData: ImageInput, userId: string, type: "avatar
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CurrentProfileOptions = {
|
|
||||||
isGuildProfile?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type LoadPresetOptions = {
|
|
||||||
skipGlobalName?: boolean;
|
|
||||||
skipBio?: boolean;
|
|
||||||
skipPronouns?: boolean;
|
|
||||||
isGuildProfile?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getCurrentProfile(guildId?: string, options: CurrentProfileOptions = {}): Promise<Omit<ProfilePreset, "name" | "timestamp">> {
|
export async function getCurrentProfile(guildId?: string, options: CurrentProfileOptions = {}): Promise<Omit<ProfilePreset, "name" | "timestamp">> {
|
||||||
const currentUser = UserStore.getCurrentUser();
|
const currentUser = UserStore.getCurrentUser();
|
||||||
const baseProfile = UserProfileStore.getUserProfile(currentUser.id);
|
const baseProfile = UserProfileStore.getUserProfile(currentUser.id);
|
||||||
|
|
@ -155,19 +132,17 @@ export async function getCurrentProfile(guildId?: string, options: CurrentProfil
|
||||||
const effectiveGuildId = isGuildProfile ? guildId : undefined;
|
const effectiveGuildId = isGuildProfile ? guildId : undefined;
|
||||||
const guildProfile = effectiveGuildId ? UserProfileStore.getGuildMemberProfile(currentUser.id, effectiveGuildId) : null;
|
const guildProfile = effectiveGuildId ? UserProfileStore.getGuildMemberProfile(currentUser.id, effectiveGuildId) : null;
|
||||||
const userProfile = guildProfile ?? baseProfile;
|
const userProfile = guildProfile ?? baseProfile;
|
||||||
const userAny = currentUser as UserExtras;
|
const userAny = currentUser;
|
||||||
const guildMember = effectiveGuildId ? GuildMemberStore.getMember(effectiveGuildId, currentUser.id) : null;
|
const guildMember = effectiveGuildId ? GuildMemberStore.getMember(effectiveGuildId, currentUser.id) : null;
|
||||||
|
|
||||||
const pendingChangesDefault = (UserProfileSettingsStore.getPendingChanges() ?? {}) as PendingChanges;
|
const pendingChangesDefault: PendingChanges = UserProfileSettingsStore.getPendingChanges() ?? {};
|
||||||
const pendingChangesForGuild = (effectiveGuildId
|
const pendingChangesForGuild: PendingChanges = UserProfileSettingsStore.getPendingChanges(effectiveGuildId) ?? {};
|
||||||
? (UserProfileSettingsStore.getPendingChanges(effectiveGuildId) ?? {})
|
|
||||||
: {}) as PendingChanges;
|
|
||||||
const pendingChanges: PendingChanges = isGuildProfile && Object.keys(pendingChangesForGuild).length > 0
|
const pendingChanges: PendingChanges = isGuildProfile && Object.keys(pendingChangesForGuild).length > 0
|
||||||
? pendingChangesForGuild
|
? pendingChangesForGuild
|
||||||
: pendingChangesDefault;
|
: pendingChangesDefault;
|
||||||
|
|
||||||
const customStatusSetting = CustomStatusSettings.getSetting();
|
const customStatusSetting = CustomStatusSettings.getSetting();
|
||||||
const customStatus: CustomStatus | null = isGuildProfile
|
const customStatus = isGuildProfile
|
||||||
? null
|
? null
|
||||||
: {
|
: {
|
||||||
text: customStatusSetting?.text ?? "",
|
text: customStatusSetting?.text ?? "",
|
||||||
|
|
@ -200,7 +175,7 @@ export async function getCurrentProfile(guildId?: string, options: CurrentProfil
|
||||||
type: effectToUse.type || 1
|
type: effectToUse.type || 1
|
||||||
};
|
};
|
||||||
} else if (effectToUse.skuId) {
|
} else if (effectToUse.skuId) {
|
||||||
const collectibles = (userProfile as { collectibles?: ProfileCollectible[]; } | null)?.collectibles;
|
const collectibles = userProfile?.collectibles;
|
||||||
const collectible = collectibles?.find(c => c?.skuId === effectToUse.skuId);
|
const collectible = collectibles?.find(c => c?.skuId === effectToUse.skuId);
|
||||||
if (collectible) {
|
if (collectible) {
|
||||||
profileEffect = {
|
profileEffect = {
|
||||||
|
|
@ -232,7 +207,7 @@ export async function getCurrentProfile(guildId?: string, options: CurrentProfil
|
||||||
? (guildMember?.displayNameStyles ?? userAny.displayNameStyles)
|
? (guildMember?.displayNameStyles ?? userAny.displayNameStyles)
|
||||||
: userAny.displayNameStyles;
|
: userAny.displayNameStyles;
|
||||||
const displayNameStylesToUse = pendingChanges.pendingDisplayNameStyles ?? savedDisplayNameStyles;
|
const displayNameStylesToUse = pendingChanges.pendingDisplayNameStyles ?? savedDisplayNameStyles;
|
||||||
const displayNameStyles = normalizeDisplayNameStyles(displayNameStylesToUse as DisplayNameStylesLike | null | undefined);
|
const displayNameStyles = normalizeDisplayNameStyles(displayNameStylesToUse);
|
||||||
|
|
||||||
const pendingAvatar = pendingChanges.pendingAvatar ?? pendingChanges.avatar;
|
const pendingAvatar = pendingChanges.pendingAvatar ?? pendingChanges.avatar;
|
||||||
const avatarToUse: ImageInput = hasImageInput(pendingAvatar)
|
const avatarToUse: ImageInput = hasImageInput(pendingAvatar)
|
||||||
|
|
@ -282,106 +257,156 @@ function jsonEq(a: unknown, b: unknown): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
function customStatusEq(a: CustomStatus | null | undefined, b: CustomStatus | null | undefined): boolean {
|
function customStatusEq(a: CustomStatus | null | undefined, b: CustomStatus | null | undefined): boolean {
|
||||||
if (a == null && b == null) return true;
|
if (a == null || b == null) return a == null && b == null;
|
||||||
if (a == null || b == null) return false;
|
|
||||||
return a.text === b.text
|
return a.text === b.text
|
||||||
&& String(a.emojiId ?? "") === String(b.emojiId ?? "")
|
&& String(a.emojiId ?? "") === String(b.emojiId ?? "")
|
||||||
&& a.emojiName === b.emojiName
|
&& a.emojiName === b.emojiName
|
||||||
&& String(a.expiresAtMs ?? "0") === String(b.expiresAtMs ?? "0");
|
&& String(a.expiresAtMs ?? "0") === String(b.expiresAtMs ?? "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolvePendingAvatar(pendingChanges: PendingChanges | null): ImageInput {
|
||||||
|
if (!pendingChanges) return null;
|
||||||
|
|
||||||
|
const candidates: ImageInput[] = [
|
||||||
|
pendingChanges.selectedAvatarRaw,
|
||||||
|
pendingChanges.presetAvatarRaw,
|
||||||
|
pendingChanges.pendingAvatar,
|
||||||
|
pendingChanges.avatar,
|
||||||
|
pendingChanges.selectedAvatarProcessed,
|
||||||
|
pendingChanges.presetAvatarProcessed
|
||||||
|
];
|
||||||
|
return candidates.find(hasImageInput) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeImageValue(value: unknown): string | null {
|
||||||
|
if (typeof value === "string") return value;
|
||||||
|
if (value && typeof value === "object" && "imageUri" in value) {
|
||||||
|
const { imageUri } = value as { imageUri: unknown };
|
||||||
|
return typeof imageUri === "string" ? imageUri : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectibleEqBySku(a: { skuId?: string | number | null; } | null | undefined, b: { skuId?: string | number | null; } | null | undefined): boolean {
|
||||||
|
if (a == null || b == null) return a == null && b == null;
|
||||||
|
return String(a.skuId ?? "") === String(b.skuId ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function avatarDecorationEq(a: { skuId?: string | number | null; asset?: string | null; } | null | undefined, b: { skuId?: string | number | null; asset?: string | null; } | null | undefined): boolean {
|
||||||
|
if (a == null || b == null) return a == null && b == null;
|
||||||
|
return String(a.skuId ?? "") === String(b.skuId ?? "") && String(a.asset ?? "") === String(b.asset ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function nameplateEq(a: { skuId?: string | number | null; asset?: string | null; } | null | undefined, b: { skuId?: string | number | null; asset?: string | null; } | null | undefined): boolean {
|
||||||
|
if (a == null || b == null) return a == null && b == null;
|
||||||
|
return String(a.skuId ?? "") === String(b.skuId ?? "") && String(a.asset ?? "") === String(b.asset ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadPresetAsPending(preset: ProfilePreset, guildId?: string, options: LoadPresetOptions = {}) {
|
export async function loadPresetAsPending(preset: ProfilePreset, guildId?: string, options: LoadPresetOptions = {}) {
|
||||||
try {
|
try {
|
||||||
const isGuild = options.isGuildProfile ?? Boolean(guildId);
|
const isGuild = options.isGuildProfile ?? Boolean(guildId);
|
||||||
if (isGuild && !guildId) return;
|
if (isGuild && !guildId) return;
|
||||||
|
const currentUser = UserStore.getCurrentUser();
|
||||||
|
const baseProfile = UserProfileStore.getUserProfile(currentUser.id);
|
||||||
|
const guildProfile = isGuild && guildId ? UserProfileStore.getGuildMemberProfile(currentUser.id, guildId) : null;
|
||||||
|
const equippedProfile = guildProfile ?? baseProfile;
|
||||||
const current = await getCurrentProfile(guildId, {
|
const current = await getCurrentProfile(guildId, {
|
||||||
isGuildProfile: isGuild
|
isGuildProfile: isGuild
|
||||||
});
|
});
|
||||||
const profilePrefix = isGuild ? "GUILD_IDENTITY_SETTINGS" : "USER_SETTINGS_ACCOUNT";
|
const pendingChanges = (isGuild && guildId
|
||||||
const dispatchProfile = (type: string, payload: Record<string, unknown>) => {
|
? UserProfileSettingsStore.getPendingChanges(guildId)
|
||||||
dispatch(type, isGuild ? { ...payload, guildId } : payload);
|
: UserProfileSettingsStore.getPendingChanges());
|
||||||
};
|
const equippedAvatarDecoration = currentUser.avatarDecorationData ?? null;
|
||||||
const setPending = (type: string, payload: Record<string, unknown>) => {
|
const equippedProfileEffect = equippedProfile?.profileEffect ?? null;
|
||||||
dispatchProfile(type, payload);
|
const equippedNameplate = currentUser.collectibles?.nameplate ?? null;
|
||||||
if (isGuild && type.startsWith("GUILD_IDENTITY_SETTINGS_")) {
|
const pendingAvatarDecoration = pendingChanges?.pendingAvatarDecoration ?? null;
|
||||||
dispatchProfile(type.replace("GUILD_IDENTITY_SETTINGS_", "USER_SETTINGS_ACCOUNT_"), payload);
|
const pendingProfileEffect = pendingChanges?.pendingProfileEffect ?? null;
|
||||||
}
|
const pendingNameplate = pendingChanges?.pendingNameplate ?? null;
|
||||||
|
const setPending = (field: string, payload: Record<string, unknown>) => {
|
||||||
|
const cleanPayload = Object.fromEntries(Object.entries(payload).filter(([, v]) => v !== undefined));
|
||||||
|
if (!Object.keys(cleanPayload).length) return;
|
||||||
|
dispatch(`USER_PROFILE_SETTINGS_SET_PENDING_${field}`, isGuild ? { ...cleanPayload, guildId } : cleanPayload);
|
||||||
};
|
};
|
||||||
|
|
||||||
if ("avatarDataUrl" in preset) {
|
if ("avatarDataUrl" in preset) {
|
||||||
const avatarValue = preset.avatarDataUrl;
|
const avatarValue = preset.avatarDataUrl;
|
||||||
if (isGuild || avatarValue !== current.avatarDataUrl) {
|
const presetAvatar = normalizeImageValue(avatarValue);
|
||||||
setPending(`${profilePrefix}_SET_PENDING_AVATAR`, { avatar: avatarValue });
|
const currentAvatar = normalizeImageValue(current.avatarDataUrl);
|
||||||
|
const pendingAvatar = normalizeImageValue(resolvePendingAvatar(pendingChanges));
|
||||||
|
if (isGuild || (presetAvatar !== currentAvatar && presetAvatar !== pendingAvatar)) {
|
||||||
|
const avatarPayload =
|
||||||
|
typeof avatarValue === "string" && avatarValue.startsWith("data:")
|
||||||
|
? {
|
||||||
|
assetOrigin: "NEW_ASSET",
|
||||||
|
imageUri: avatarValue,
|
||||||
|
description: `profilesets-${preset.name ?? "preset"}`
|
||||||
|
}
|
||||||
|
: avatarValue;
|
||||||
|
setPending("AVATAR", { avatar: avatarPayload });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("bannerDataUrl" in preset && preset.bannerDataUrl !== current.bannerDataUrl) {
|
if ("bannerDataUrl" in preset && preset.bannerDataUrl !== current.bannerDataUrl) {
|
||||||
setPending(`${profilePrefix}_SET_PENDING_BANNER`, { banner: preset.bannerDataUrl });
|
setPending("BANNER", { banner: preset.bannerDataUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.skipBio && "bio" in preset && preset.bio !== current.bio) {
|
if (!options.skipBio && preset?.bio !== current?.bio) {
|
||||||
const bioValue = preset.bio === null ? "" : preset.bio;
|
const bioValue = preset.bio === null ? "" : preset.bio;
|
||||||
setPending(`${profilePrefix}_SET_PENDING_BIO`, { bio: bioValue });
|
setPending("BIO", { bio: bioValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isGuild && !options.skipPronouns && "pronouns" in preset && preset.pronouns !== current.pronouns) {
|
if (!isGuild && !options.skipPronouns && preset?.pronouns !== current?.pronouns) {
|
||||||
const pronounsValue = preset.pronouns === null ? "" : preset.pronouns;
|
const pronounsValue = preset.pronouns === null ? "" : preset.pronouns;
|
||||||
setPending(`${profilePrefix}_SET_PENDING_PRONOUNS`, { pronouns: pronounsValue });
|
setPending("PRONOUNS", { pronouns: pronounsValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.skipGlobalName && preset.globalName !== undefined && preset.globalName !== current.globalName) {
|
if (!options.skipGlobalName && preset?.globalName !== current?.globalName) {
|
||||||
if (isGuild) {
|
if (isGuild) {
|
||||||
setPending("GUILD_IDENTITY_SETTINGS_SET_PENDING_NICKNAME", { nickname: preset.globalName });
|
setPending("NICKNAME", { nickname: preset.globalName });
|
||||||
} else {
|
} else {
|
||||||
setPending("USER_SETTINGS_ACCOUNT_SET_PENDING_GLOBAL_NAME", { globalName: preset.globalName });
|
setPending("GLOBAL_NAME", { globalName: preset.globalName });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preset.avatarDecoration !== undefined && !jsonEq(preset.avatarDecoration, current.avatarDecoration)) {
|
if (preset.avatarDecoration && ![pendingAvatarDecoration, equippedAvatarDecoration, current.avatarDecoration].some(d => avatarDecorationEq(preset.avatarDecoration, d))) {
|
||||||
dispatchProfile(`${profilePrefix}_SET_PENDING_COLLECTIBLES_ITEM`, {
|
setPending("COLLECTIBLES_ITEM", {
|
||||||
item: { type: 0, value: preset.avatarDecoration }
|
item: { type: 0, value: preset.avatarDecoration }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preset.profileEffect !== undefined && !jsonEq(preset.profileEffect, current.profileEffect)) {
|
if (preset.profileEffect && ![pendingProfileEffect, equippedProfileEffect, current.profileEffect].some(d => collectibleEqBySku(preset.profileEffect, d))) {
|
||||||
dispatchProfile(`${profilePrefix}_SET_PENDING_COLLECTIBLES_ITEM`, {
|
setPending("COLLECTIBLES_ITEM", {
|
||||||
item: { type: 1, value: preset.profileEffect }
|
item: { type: 1, value: preset.profileEffect }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preset.nameplate !== undefined && !jsonEq(preset.nameplate, current.nameplate)) {
|
if (preset.nameplate && ![pendingNameplate, equippedNameplate, current.nameplate].some(d => nameplateEq(preset.nameplate, d))) {
|
||||||
dispatchProfile(`${profilePrefix}_SET_PENDING_COLLECTIBLES_ITEM`, {
|
setPending("COLLECTIBLES_ITEM", {
|
||||||
item: { type: 2, value: preset.nameplate }
|
item: { type: 2, value: preset.nameplate }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preset.displayNameStyles !== undefined) {
|
if (preset.displayNameStyles) {
|
||||||
const presetDisplayNameStyles = normalizeDisplayNameStyles(preset.displayNameStyles as DisplayNameStylesLike | null | undefined);
|
const presetDisplayNameStyles = normalizeDisplayNameStyles(preset.displayNameStyles);
|
||||||
if (!jsonEq(presetDisplayNameStyles, current.displayNameStyles)) {
|
if (!jsonEq(presetDisplayNameStyles, current.displayNameStyles)) {
|
||||||
setPending(`${profilePrefix}_SET_PENDING_DISPLAY_NAME_STYLES`, { displayNameStyles: presetDisplayNameStyles });
|
setPending("DISPLAY_NAME_STYLES", { displayNameStyles: presetDisplayNameStyles });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preset.themeColors !== undefined && !jsonEq(preset.themeColors, current.themeColors)) {
|
if (preset.themeColors && !jsonEq(preset.themeColors, current.themeColors)) {
|
||||||
setPending(`${profilePrefix}_SET_PENDING_THEME_COLORS`, { themeColors: preset.themeColors });
|
setPending("THEME_COLORS", { themeColors: preset.themeColors });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preset.primaryGuildId !== undefined && !isGuild && preset.primaryGuildId !== current.primaryGuildId) {
|
if (preset.primaryGuildId && !isGuild && preset.primaryGuildId !== current.primaryGuildId) {
|
||||||
dispatch("USER_SETTINGS_SET_PENDING_PRIMARY_GUILD_ID", { primaryGuildId: preset.primaryGuildId });
|
dispatch("USER_SETTINGS_SET_PENDING_PRIMARY_GUILD_ID", { primaryGuildId: preset.primaryGuildId });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preset.customStatus !== undefined && !isGuild && !customStatusEq(preset.customStatus, current.customStatus)) {
|
if (preset.customStatus && !isGuild && !customStatusEq(preset.customStatus, current.customStatus)) {
|
||||||
const customStatusValue = preset.customStatus ?? {
|
|
||||||
text: "",
|
|
||||||
expiresAtMs: "0",
|
|
||||||
emojiId: "0",
|
|
||||||
emojiName: ""
|
|
||||||
};
|
|
||||||
CustomStatusSettings.updateSetting({
|
CustomStatusSettings.updateSetting({
|
||||||
text: customStatusValue.text || "",
|
text: preset.customStatus?.text ?? "",
|
||||||
expiresAtMs: customStatusValue.expiresAtMs || "0",
|
expiresAtMs: preset.customStatus?.expiresAtMs ?? "0",
|
||||||
emojiId: customStatusValue.emojiId || "0",
|
emojiId: preset.customStatus?.emojiId ?? "0",
|
||||||
emojiName: customStatusValue.emojiName || ""
|
emojiName: preset.customStatus?.emojiName ?? ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue