Refactor profile modal interactions

This commit is contained in:
thePR0M3TH3AN committed 2025-10-07 14:55:58 -04:00
1 parent b5cde1b977
commit 30e66fe969
2 files changed
+351 -144

No files matched your search

+73 -132
View File
@@ -289,7 +289,6 @@ class Application {
this.isWalletPaneBusy = false;
this.profileModalCachedSelection = null;
this.profileModalController = null;
this.lastFocusedBeforeProfileModal = null;
this.boundProfileHistoryVisibility = null;
this.profileSwitcherSelectionPubkey = null;
this.currentUserNpub = null;
@@ -474,9 +473,15 @@ class Application {
this.boundVideoModalZapCommentHandler
);
this.boundVideoModalZapWalletHandler = () => {
if (typeof this.openWalletPane === "function") {
this.openWalletPane();
if (!this.profileModalController) {
return;
}
this.profileModalController
.showWalletPane()
.catch((error) => {
console.error("Failed to open wallet pane:", error);
});
};
this.videoModal.addEventListener(
"zap:wallet-link",
@@ -1220,7 +1225,9 @@ class Application {
truncateMiddle: (value, maxLength) => truncateMiddle(value, maxLength),
getProfileCacheEntry: (pubkey) => this.getProfileCacheEntry(pubkey),
batchFetchProfiles: (authorSet) => this.batchFetchProfiles(authorSet),
switchProfile: (pubkey) => this.switchProfile(pubkey),
switchProfile: (pubkey) => this.authService.switchProfile(pubkey),
removeSavedProfile: (pubkey) =>
this.authService.removeSavedProfile(pubkey),
relayManager,
userBlocks,
nostrClient,
@@ -1367,67 +1374,9 @@ class Application {
this.profileModalController?.updateWalletStatus(message, variant);
}
async openProfileModal(targetPane = "account") {
if (!this.profileModalController) {
return;
}
try {
await this.refreshAdminPaneState();
} catch (error) {
console.error(
"Failed to refresh admin pane while opening profile modal:",
error,
);
}
this.activeProfilePane = null;
this.refreshWalletPaneState();
this.populateProfileRelays();
try {
await userBlocks.ensureLoaded(this.pubkey);
} catch (error) {
console.warn(
"Failed to refresh user block list while opening profile modal:",
error,
);
}
this.populateBlockedList();
this.lastFocusedBeforeProfileModal =
document.activeElement instanceof HTMLElement
? document.activeElement
: null;
this.profileModalController.open(targetPane);
}
async openWalletPane() {
await this.openProfileModal("wallet");
}
hideProfileModal() {
if (!this.profileModalController) {
return;
}
this.profileModalController.hide();
this.handleProfileModalClosed();
}
handleProfileModalClosed() {
this.closeAllMoreMenus();
this.activeProfilePane = null;
const previouslyFocused = this.lastFocusedBeforeProfileModal;
this.lastFocusedBeforeProfileModal = null;
if (
previouslyFocused &&
typeof previouslyFocused.focus === "function"
) {
previouslyFocused.focus();
}
}
handleProfileChannelLink(element) {
@@ -1439,7 +1388,9 @@ class Application {
typeof element.dataset.targetNpub === "string"
? element.dataset.targetNpub
: "";
this.hideProfileModal();
if (this.profileModalController) {
this.profileModalController.hide();
}
if (targetNpub) {
window.location.hash = `#view=channel-profile&npub=${encodeURIComponent(
targetNpub,
@@ -1690,9 +1641,15 @@ class Application {
// 2) Profile button
if (this.profileButton) {
this.profileButton.addEventListener("click", () => {
this.openProfileModal().catch((error) => {
console.error("Failed to open profile modal:", error);
});
if (!this.profileModalController) {
return;
}
this.profileModalController
.show()
.catch((error) => {
console.error("Failed to open profile modal:", error);
});
});
}
@@ -2319,46 +2276,16 @@ class Application {
}
}
/**
* Removes a saved profile entry and clears the active pointer when it matches
* the removed pubkey. Intended for the profile-switcher UI to prune old
* accounts without touching cached avatars.
*/
removeSavedProfile(pubkey) {
const { removed } = this.authService.removeSavedProfile(pubkey);
if (removed) {
this.renderSavedProfiles();
}
}
async switchProfile(pubkey) {
try {
const result = await this.authService.switchProfile(pubkey);
if (!result?.switched) {
this.hideProfileModal();
return;
}
} catch (error) {
console.error("Failed to switch profiles:", error);
const message =
error && typeof error.message === "string" && error.message.trim()
? error.message.trim()
: "Failed to switch profiles. Please try again.";
this.showError(message);
return;
}
this.profileSwitcherSelectionPubkey = null;
this.renderSavedProfiles();
this.hideProfileModal();
}
async handleProfileSwitchRequest({ pubkey } = {}) {
if (!pubkey) {
throw new Error("Missing pubkey for profile switch request.");
}
await this.switchProfile(pubkey);
if (!this.profileModalController) {
throw new Error("Profile modal controller unavailable for switch.");
}
await this.profileModalController.switchProfile(pubkey);
await this.loadVideos(true);
return true;
}
@@ -2407,7 +2334,10 @@ class Application {
updateActiveProfileUI(pubkey, profile = {}) {
if (this.profileModalController) {
this.renderSavedProfiles();
this.profileModalController.handleProfileUpdated({
pubkey,
profile,
});
return;
}
@@ -2594,18 +2524,22 @@ class Application {
await this.hydrateNwcSettingsForPubkey(normalizedActive);
}
this.renderSavedProfiles();
if (this.profileModalController) {
await this.profileModalController.handleAuthLogin(detail);
} else {
this.renderSavedProfiles();
try {
await this.refreshAdminPaneState();
} catch (error) {
console.warn("Failed to refresh admin pane after login:", error);
try {
await this.refreshAdminPaneState();
} catch (error) {
console.warn("Failed to refresh admin pane after login:", error);
}
this.populateBlockedList();
this.populateProfileRelays();
this.refreshWalletPaneState();
}
this.populateBlockedList();
this.populateProfileRelays();
this.refreshWalletPaneState();
if (this.loginButton) {
this.loginButton.classList.add("hidden");
this.loginButton.setAttribute("hidden", "");
@@ -2659,7 +2593,21 @@ class Application {
}
this.nwcSettings.clear();
this.renderSavedProfiles();
if (this.profileModalController) {
await this.profileModalController.handleAuthLogout(detail);
} else {
this.renderSavedProfiles();
try {
await this.refreshAdminPaneState();
} catch (error) {
console.warn("Failed to refresh admin pane after logout:", error);
}
this.populateBlockedList();
this.populateProfileRelays();
this.refreshWalletPaneState();
}
if (this.loginButton) {
this.loginButton.classList.remove("hidden");
@@ -2691,26 +2639,10 @@ class Application {
this.profileButton.style.display = "none";
}
if (this.profileChannelLink) {
this.profileChannelLink.classList.add("hidden");
this.profileChannelLink.removeAttribute("href");
delete this.profileChannelLink.dataset.targetNpub;
}
if (this.subscriptionsLink) {
this.subscriptionsLink.classList.add("hidden");
}
try {
await this.refreshAdminPaneState();
} catch (error) {
console.warn("Failed to refresh admin pane after logout:", error);
}
this.populateBlockedList();
this.populateProfileRelays();
this.refreshWalletPaneState();
await this.loadVideos();
this.forceRefreshAllProfiles();
if (this.uploadModal?.refreshCloudflareBucketPreview) {
@@ -2719,7 +2651,9 @@ class Application {
}
handleProfileUpdated(detail = {}) {
if (Array.isArray(detail?.savedProfiles)) {
if (this.profileModalController) {
this.profileModalController.handleProfileUpdated(detail);
} else if (Array.isArray(detail?.savedProfiles)) {
this.renderSavedProfiles();
}
@@ -2730,7 +2664,10 @@ class Application {
if (normalizedPubkey && profile) {
this.updateProfileInDOM(normalizedPubkey, profile);
if (this.normalizeHexPubkey(this.pubkey) === normalizedPubkey) {
if (
!this.profileModalController &&
this.normalizeHexPubkey(this.pubkey) === normalizedPubkey
) {
this.updateActiveProfileUI(normalizedPubkey, profile);
}
}
@@ -3864,8 +3801,12 @@ class Application {
typeof settings?.nwcUri === "string" ? settings.nwcUri.trim() : "";
if (!normalizedUri) {
this.showError("Connect a Lightning wallet to send zaps.");
if (typeof this.openWalletPane === "function") {
this.openWalletPane();
if (this.profileModalController) {
this.profileModalController
.showWalletPane()
.catch((error) => {
console.error("Failed to open wallet pane:", error);
});
}
return null;
}
+278 -12
View File
@@ -52,6 +52,12 @@ const SERVICE_CONTRACT = [
description:
"Switches the active application profile to the provided pubkey and updates state accordingly.",
},
{
key: "removeSavedProfile",
type: "function",
description:
"Removes a saved profile entry and persists the updated collection to storage.",
},
{
key: "relayManager",
type: "object",
@@ -474,6 +480,7 @@ export class ProfileModalController {
this.boundFocusIn = null;
this.focusableElements = [];
this.profileSwitcherSelectionPubkey = null;
this.previouslyFocusedElement = null;
this.setActivePane(this.getActivePane());
this.setWalletPaneBusy(this.isWalletBusy());
this.adminEmptyMessages = new Map();
@@ -667,7 +674,6 @@ export class ProfileModalController {
if (this.closeButton instanceof HTMLElement) {
this.closeButton.addEventListener("click", () => {
this.hide();
this.callbacks.onClose(this);
});
}
@@ -3331,7 +3337,6 @@ export class ProfileModalController {
if (event.key === "Escape") {
event.preventDefault();
this.hide();
this.callbacks.onClose(this);
return;
}
@@ -3391,6 +3396,65 @@ export class ProfileModalController {
document.addEventListener("focusin", this.boundFocusIn);
}
async show(targetPane = "account") {
const pane =
typeof targetPane === "string" && targetPane.trim()
? targetPane.trim()
: "account";
const activeElement =
document.activeElement instanceof HTMLElement
? document.activeElement
: null;
if (
activeElement &&
this.profileModal instanceof HTMLElement &&
this.profileModal.contains(activeElement)
) {
this.previouslyFocusedElement = null;
} else {
this.previouslyFocusedElement = activeElement;
}
this.renderSavedProfiles();
try {
await this.refreshAdminPaneState();
} catch (error) {
console.error(
"Failed to refresh admin pane while opening profile modal:",
error,
);
}
this.refreshWalletPaneState();
this.populateProfileRelays();
if (
this.services.userBlocks &&
typeof this.services.userBlocks.ensureLoaded === "function"
) {
try {
const activeHex = this.normalizeHexPubkey(this.getActivePubkey());
await this.services.userBlocks.ensureLoaded(activeHex);
} catch (error) {
console.warn(
"Failed to refresh user block list while opening profile modal:",
error,
);
}
}
this.populateBlockedList();
this.open(pane);
return true;
}
showWalletPane() {
return this.show("wallet");
}
open(pane = "account") {
if (!(this.profileModal instanceof HTMLElement)) {
return;
@@ -3409,21 +3473,223 @@ export class ProfileModalController {
});
}
hide() {
if (!(this.profileModal instanceof HTMLElement)) {
return;
hide(options = {}) {
const { silent = false } =
options && typeof options === "object" ? options : {};
const modalElement =
this.profileModal instanceof HTMLElement ? this.profileModal : null;
if (modalElement) {
modalElement.classList.add("hidden");
modalElement.setAttribute("aria-hidden", "true");
this.setGlobalModalState("profile", false);
if (this.boundKeydown) {
modalElement.removeEventListener("keydown", this.boundKeydown);
}
}
this.profileModal.classList.add("hidden");
this.profileModal.setAttribute("aria-hidden", "true");
this.setGlobalModalState("profile", false);
if (this.boundKeydown) {
this.profileModal.removeEventListener("keydown", this.boundKeydown);
}
if (this.boundFocusIn) {
document.removeEventListener("focusin", this.boundFocusIn);
}
const previous = this.previouslyFocusedElement;
this.previouslyFocusedElement = null;
if (
previous &&
typeof previous.focus === "function" &&
(!modalElement || !modalElement.contains(previous))
) {
const shouldRestore =
"isConnected" in previous ? previous.isConnected !== false : true;
if (shouldRestore) {
window.requestAnimationFrame(() => {
try {
previous.focus();
} catch (error) {
console.warn(
"[profileModal] Failed to restore focus after closing modal:",
error,
);
}
});
}
}
if (!silent) {
try {
this.callbacks.onClose(this);
} catch (error) {
console.warn(
"[profileModal] onClose callback threw while hiding modal:",
error,
);
}
}
}
async handleAuthLogin(detail = {}) {
const savedProfiles = Array.isArray(detail?.savedProfiles)
? detail.savedProfiles
: null;
if (savedProfiles) {
try {
this.setSavedProfiles(savedProfiles, {
persist: false,
persistActive: false,
});
} catch (error) {
console.warn(
"[profileModal] Failed to sync saved profiles during login:",
error,
);
}
}
const activePubkey =
detail?.activeProfilePubkey ?? detail?.pubkey ?? undefined;
if (activePubkey !== undefined) {
this.setActivePubkey(activePubkey);
}
this.renderSavedProfiles();
try {
await this.refreshAdminPaneState();
} catch (error) {
console.warn("Failed to refresh admin pane after login:", error);
}
this.populateBlockedList();
this.populateProfileRelays();
this.refreshWalletPaneState();
return true;
}
async handleAuthLogout(detail = {}) {
const savedProfiles = Array.isArray(detail?.savedProfiles)
? detail.savedProfiles
: null;
if (savedProfiles) {
try {
this.setSavedProfiles(savedProfiles, {
persist: false,
persistActive: false,
});
} catch (error) {
console.warn(
"[profileModal] Failed to sync saved profiles during logout:",
error,
);
}
}
if (detail?.activeProfilePubkey !== undefined) {
this.setActivePubkey(detail.activeProfilePubkey);
} else {
this.setActivePubkey(null);
}
this.profileSwitcherSelectionPubkey = null;
this.renderSavedProfiles();
try {
await this.refreshAdminPaneState();
} catch (error) {
console.warn("Failed to refresh admin pane after logout:", error);
}
this.populateBlockedList();
this.populateProfileRelays();
this.refreshWalletPaneState();
return true;
}
handleProfileUpdated(detail = {}) {
if (Array.isArray(detail?.savedProfiles)) {
try {
this.setSavedProfiles(detail.savedProfiles, {
persist: false,
persistActive: false,
});
} catch (error) {
console.warn(
"[profileModal] Failed to sync saved profiles after profile update:",
error,
);
}
}
if (detail?.activeProfilePubkey !== undefined) {
this.setActivePubkey(detail.activeProfilePubkey);
} else if (detail?.pubkey) {
this.setActivePubkey(detail.pubkey);
}
this.renderSavedProfiles();
}
removeSavedProfile(pubkey) {
if (!pubkey) {
return { removed: false };
}
let result;
try {
result = this.services.removeSavedProfile(pubkey) || { removed: false };
} catch (error) {
console.error("Failed to remove saved profile:", error);
result = { removed: false, error };
}
if (result?.removed) {
if (
this.normalizeHexPubkey(pubkey) ===
this.normalizeHexPubkey(this.getActivePubkey())
) {
this.setActivePubkey(null);
}
this.renderSavedProfiles();
}
return result;
}
async switchProfile(pubkey) {
if (!pubkey) {
return { switched: false, reason: "missing-pubkey" };
}
let result;
try {
result = await this.services.switchProfile(pubkey);
} catch (error) {
const message =
error && typeof error.message === "string" && error.message.trim()
? error.message.trim()
: "Failed to switch profiles. Please try again.";
this.showError(message);
return {
switched: false,
error,
reason: error?.code || "switch-error",
};
}
if (!result?.switched) {
this.hide();
return result || { switched: false };
}
this.profileSwitcherSelectionPubkey = null;
this.renderSavedProfiles();
this.hide();
return result;
}
getSavedProfiles() {