* 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>
152 lines
5.4 KiB
TypeScript
152 lines
5.4 KiB
TypeScript
/*
|
|
* Vencord, a Discord client mod
|
|
* Copyright (c) 2026 Vendicated and contributors
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
import { isNonNullish } from "@utils/guards";
|
|
import { ProfilePreset } from "@vencord/discord-types";
|
|
import { findStoreLazy } from "@webpack";
|
|
import { showToast, Toasts } from "@webpack/common";
|
|
|
|
import { getCurrentProfile } from "./profile";
|
|
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) {
|
|
const profile = await getCurrentProfile(guildId, { isGuildProfile: section === "server" });
|
|
const freshPendingAvatar = getFreshPendingAvatar(section, guildId);
|
|
const effectiveAvatar = freshPendingAvatar ?? profile.avatarDataUrl ?? null;
|
|
|
|
const newPreset: ProfilePresetEx = {
|
|
name,
|
|
timestamp: Date.now(),
|
|
...profile,
|
|
avatarDataUrl: effectiveAvatar,
|
|
};
|
|
addPreset(newPreset);
|
|
await savePresetsData(section);
|
|
}
|
|
|
|
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;
|
|
void guildId;
|
|
|
|
const updatedPreset = {
|
|
...presets[index],
|
|
[field]: value,
|
|
timestamp: Date.now()
|
|
};
|
|
updatePreset(index, updatedPreset);
|
|
await savePresetsData(section);
|
|
}
|
|
|
|
export async function deletePreset(index: number, section: PresetSection, guildId?: string) {
|
|
if (index < 0 || index >= presets.length) return;
|
|
|
|
removePreset(index);
|
|
await savePresetsData(section);
|
|
}
|
|
|
|
export async function movePreset(fromIndex: number, toIndex: number, section: PresetSection, guildId?: string) {
|
|
if (fromIndex < 0 || fromIndex >= presets.length || toIndex < 0 || toIndex >= presets.length) return;
|
|
|
|
movePresetInArray(fromIndex, toIndex);
|
|
await savePresetsData(section);
|
|
}
|
|
|
|
export async function renamePreset(index: number, newName: string, section: PresetSection, guildId?: string) {
|
|
if (index < 0 || index >= presets.length || !newName.trim()) return;
|
|
|
|
const updatedPreset = { ...presets[index], name: newName.trim() };
|
|
updatePreset(index, updatedPreset);
|
|
await savePresetsData(section);
|
|
}
|
|
|
|
export function exportPresets(section: PresetSection) {
|
|
const dataStr = JSON.stringify(presets, null, 2);
|
|
const dataBlob = new Blob([dataStr], { type: "application/json" });
|
|
const url = URL.createObjectURL(dataBlob);
|
|
const link = document.createElement("a");
|
|
link.href = url;
|
|
link.download = `profile-presets-${section}-${Date.now()}.json`;
|
|
link.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
export type ImportDecision = "override" | "merge" | "cancel";
|
|
|
|
export async function importPresets(
|
|
forceUpdate: () => void,
|
|
onImportPrompt: (existingCount: number) => Promise<ImportDecision>,
|
|
section: PresetSection,
|
|
guildId?: string
|
|
) {
|
|
const input = document.createElement("input");
|
|
input.type = "file";
|
|
input.accept = "application/json";
|
|
input.onchange = async (event: Event) => {
|
|
try {
|
|
const target = event.currentTarget as HTMLInputElement | null;
|
|
const file = target?.files?.[0];
|
|
if (!file) return;
|
|
|
|
const text = await file.text();
|
|
const importedPresets = JSON.parse(text);
|
|
|
|
if (!Array.isArray(importedPresets)) {
|
|
return;
|
|
}
|
|
|
|
if (presets.length > 0) {
|
|
const decision = await onImportPrompt(presets.length);
|
|
if (decision === "cancel") return;
|
|
if (decision === "override") {
|
|
replaceAllPresets(importedPresets);
|
|
} else {
|
|
const combined = [...presets, ...importedPresets];
|
|
replaceAllPresets(combined);
|
|
}
|
|
} else {
|
|
replaceAllPresets(importedPresets);
|
|
}
|
|
|
|
await savePresetsData(section);
|
|
forceUpdate();
|
|
} catch {
|
|
showToast("Failed to import presets. The file might be invalid.", Toasts.Type.FAILURE);
|
|
}
|
|
};
|
|
input.click();
|
|
}
|