mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 17:02:28 +00:00
Refactor profile modal interactions
This commit is contained in:
1 parent
b5cde1b977
commit
30e66fe969
2 files changed
+351
-144
No files matched your search
@@ -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
@@ -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() {
|
||||
|
||||
Reference in new issue
Block a user