mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-07 21:32:51 +00:00
Refactor upload modal for generic storage and read-only config
- Updates Upload Modal UI labels to be provider-agnostic (e.g., 'Storage Credentials', 'Endpoint'). - Implements a read-only 'Summary View' for configured storage connections, collapsing the editable form by default. - Adds 'Manage Storage' button that navigates to the Profile Modal's storage pane. - Updates `initUploadModal.js` to inject the navigation callback. - Updates `r2Service.js` status messages to be generic. - Fixes unlock flow to ensure UI updates after storage unlock.
This commit is contained in:
1 parent
0f4775819e
commit
2b446e8ddd
5 files changed
+273
-129
No files matched your search
+124
-55
@@ -36,6 +36,7 @@ export class UploadModal {
|
||||
safeEncodeNpub,
|
||||
eventTarget,
|
||||
container,
|
||||
onRequestStorageSettings,
|
||||
} = {}) {
|
||||
this.authService = authService || null;
|
||||
this.r2Service = r2Service || null;
|
||||
@@ -60,6 +61,7 @@ export class UploadModal {
|
||||
this.eventTarget =
|
||||
eventTarget instanceof EventTarget ? eventTarget : new EventTarget();
|
||||
this.container = container || null;
|
||||
this.onRequestStorageSettings = typeof onRequestStorageSettings === "function" ? onRequestStorageSettings : null;
|
||||
|
||||
this.root = null;
|
||||
this.isVisible = false;
|
||||
@@ -67,6 +69,7 @@ export class UploadModal {
|
||||
this.cloudflareSettings = this.r2Service?.getSettings?.() || null;
|
||||
this.isStorageUnlocked = false;
|
||||
this.activeConnectionId = null;
|
||||
this.storageConfigured = false;
|
||||
|
||||
// UI References
|
||||
this.form = null;
|
||||
@@ -76,6 +79,7 @@ export class UploadModal {
|
||||
this.toggles = {};
|
||||
this.submitButton = null;
|
||||
this.submitStatus = null;
|
||||
this.summaryView = {};
|
||||
|
||||
// Logic/State
|
||||
this.nip71FormManager = new Nip71FormManager();
|
||||
@@ -125,14 +129,8 @@ export class UploadModal {
|
||||
if (this.storageService) {
|
||||
const pubkey = this.getCurrentPubkey ? this.getCurrentPubkey() : null;
|
||||
this.isStorageUnlocked = pubkey ? this.storageService.isUnlocked(pubkey) : false;
|
||||
if (this.isStorageUnlocked) {
|
||||
await this.loadFromStorage();
|
||||
} else {
|
||||
// Attempt legacy load if not unlocked (or maybe just show locked state)
|
||||
// For now we keep legacy behavior as fallback if storageService isn't used
|
||||
// But user requested provider agnostic.
|
||||
// We will update UI state in setSourceMode or similar
|
||||
}
|
||||
// Even if locked, we want to know if there's *potentially* a connection saved to show correct UI state (locked vs empty)
|
||||
await this.loadFromStorage();
|
||||
} else {
|
||||
await this.loadR2Settings();
|
||||
}
|
||||
@@ -172,6 +170,11 @@ export class UploadModal {
|
||||
progress: $("#upload-progress-container"),
|
||||
};
|
||||
|
||||
this.storageViews = {
|
||||
summary: $("#storage-summary-view"),
|
||||
form: $("#storage-form-view"),
|
||||
};
|
||||
|
||||
// Inputs (Common)
|
||||
this.inputs = {
|
||||
title: $("#input-title"),
|
||||
@@ -225,6 +228,7 @@ export class UploadModal {
|
||||
r2HelpLink: $("#link-r2-help"),
|
||||
copyBucket: $("#btn-copy-bucket"),
|
||||
storageUnlock: $("#btn-storage-unlock"),
|
||||
manageStorage: $("#btn-manage-storage"),
|
||||
};
|
||||
|
||||
// Status text
|
||||
@@ -233,6 +237,8 @@ export class UploadModal {
|
||||
storageLock: $("#storage-lock-status"),
|
||||
uploadMain: $("#upload-status-text"),
|
||||
uploadPercent: $("#upload-percent-text"),
|
||||
summaryProvider: $("#summary-provider"),
|
||||
summaryBucket: $("#summary-bucket"),
|
||||
};
|
||||
|
||||
this.nip71FormManager.registerSection("main", this.form);
|
||||
@@ -294,6 +300,16 @@ export class UploadModal {
|
||||
this.toggles.storageUnlock.addEventListener("click", () => this.handleUnlock());
|
||||
}
|
||||
|
||||
// Manage Storage
|
||||
if (this.toggles.manageStorage) {
|
||||
this.toggles.manageStorage.addEventListener("click", () => {
|
||||
if (this.onRequestStorageSettings) {
|
||||
this.close();
|
||||
this.onRequestStorageSettings();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Automation
|
||||
this.setupDescriptionMirror();
|
||||
this.setupMutuallyExclusiveCheckboxes(this.toggles.nsfw, this.toggles.kids);
|
||||
@@ -333,9 +349,11 @@ export class UploadModal {
|
||||
this.sourceSections.upload.classList.remove("hidden");
|
||||
this.sourceSections.external.classList.add("hidden");
|
||||
|
||||
// Auto-show settings if missing
|
||||
if (!this.hasValidR2Settings() && this.sourceSections.settings.classList.contains("hidden")) {
|
||||
// Auto-show settings if missing or not configured
|
||||
if (!this.storageConfigured && !this.hasValidR2Settings() && this.sourceSections.settings.classList.contains("hidden")) {
|
||||
this.toggles.storageSettings.click(); // Expand
|
||||
} else if (this.storageConfigured) {
|
||||
// Keep collapsed if configured, per requirements
|
||||
}
|
||||
|
||||
if (this.toggles.browseThumbnail) {
|
||||
@@ -461,15 +479,7 @@ export class UploadModal {
|
||||
this.isStorageUnlocked = pubkey ? this.storageService.isUnlocked(pubkey) : false;
|
||||
this.updateLockUi();
|
||||
|
||||
if (this.isStorageUnlocked) {
|
||||
await this.loadFromStorage();
|
||||
} else {
|
||||
// Locked: Clear sensitive data from UI and memory
|
||||
this.cloudflareSettings = null;
|
||||
this.fillSettingsForm({});
|
||||
this.updateStorageStatus(false);
|
||||
this.activeConnectionId = null;
|
||||
}
|
||||
await this.loadFromStorage();
|
||||
} else if (this.r2Service && typeof this.r2Service.loadSettings === "function") {
|
||||
await this.loadR2Settings();
|
||||
}
|
||||
@@ -483,13 +493,24 @@ export class UploadModal {
|
||||
this.cloudflareSettings = settings || {};
|
||||
this.fillSettingsForm(this.cloudflareSettings);
|
||||
this.updateStorageStatus(this.hasValidR2Settings());
|
||||
|
||||
// Legacy mode implies no storage service or not used
|
||||
this.toggleStorageView("form");
|
||||
return settings;
|
||||
}
|
||||
|
||||
async loadFromStorage() {
|
||||
if (!this.storageService) return;
|
||||
const pubkey = this.getCurrentPubkey ? this.getCurrentPubkey() : null;
|
||||
if (!pubkey) return;
|
||||
|
||||
this.cloudflareSettings = null;
|
||||
this.activeConnectionId = null;
|
||||
this.storageConfigured = false;
|
||||
|
||||
if (!pubkey) {
|
||||
this.toggleStorageView("form");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const connections = await this.storageService.listConnections(pubkey);
|
||||
@@ -498,32 +519,56 @@ export class UploadModal {
|
||||
const targetConn = defaultConn || connections.find(c => c.provider === PROVIDERS.R2 || c.provider === "cloudflare_r2");
|
||||
|
||||
if (targetConn) {
|
||||
const details = await this.storageService.getConnection(pubkey, targetConn.id);
|
||||
if (details) {
|
||||
// Map generic S3 or R2 details to settings
|
||||
// R2 settings struct: accountId, accessKeyId, secretAccessKey, baseDomain
|
||||
this.cloudflareSettings = {
|
||||
accountId: details.accountId, // Might be undefined for generic S3
|
||||
accessKeyId: details.accessKeyId,
|
||||
secretAccessKey: details.secretAccessKey,
|
||||
baseDomain: details.meta?.baseDomain || "",
|
||||
// Add generic endpoint for internal use if needed
|
||||
endpoint: details.endpoint,
|
||||
region: details.region,
|
||||
bucket: details.bucket
|
||||
};
|
||||
this.fillSettingsForm(this.cloudflareSettings);
|
||||
this.storageConfigured = true;
|
||||
this.toggleStorageView("summary");
|
||||
|
||||
const providerLabel = targetConn.provider === PROVIDERS.R2 || targetConn.provider === "cloudflare_r2"
|
||||
? "R2"
|
||||
: (targetConn.meta?.label || "S3");
|
||||
this.updateStorageStatus(true, providerLabel);
|
||||
// Populate Summary
|
||||
const providerName = targetConn.meta?.provider === "cloudflare_r2" ? "Cloudflare R2" : (targetConn.meta?.label || "S3 Storage");
|
||||
const bucketName = targetConn.meta?.bucket || "Unknown Bucket";
|
||||
|
||||
this.activeConnectionId = targetConn.id;
|
||||
if (this.statusText.summaryProvider) this.statusText.summaryProvider.textContent = providerName;
|
||||
if (this.statusText.summaryBucket) this.statusText.summaryBucket.textContent = bucketName;
|
||||
|
||||
if (this.isStorageUnlocked) {
|
||||
const details = await this.storageService.getConnection(pubkey, targetConn.id);
|
||||
if (details) {
|
||||
// Map generic S3 or R2 details to settings
|
||||
// R2 settings struct: accountId, accessKeyId, secretAccessKey, baseDomain
|
||||
this.cloudflareSettings = {
|
||||
accountId: details.accountId, // Might be undefined for generic S3
|
||||
accessKeyId: details.accessKeyId,
|
||||
secretAccessKey: details.secretAccessKey,
|
||||
baseDomain: details.meta?.baseDomain || "",
|
||||
// Add generic endpoint for internal use if needed
|
||||
endpoint: details.endpoint,
|
||||
region: details.region,
|
||||
bucket: details.bucket
|
||||
};
|
||||
this.fillSettingsForm(this.cloudflareSettings);
|
||||
this.updateStorageStatus(true, providerName);
|
||||
this.activeConnectionId = targetConn.id;
|
||||
}
|
||||
} else {
|
||||
// Locked state, can't get details yet
|
||||
this.updateStorageStatus(false);
|
||||
}
|
||||
} else {
|
||||
// No connections configured
|
||||
this.toggleStorageView("form");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load connection", err);
|
||||
this.toggleStorageView("form");
|
||||
}
|
||||
}
|
||||
|
||||
toggleStorageView(viewName) {
|
||||
if (viewName === "summary") {
|
||||
this.storageViews.summary.classList.remove("hidden");
|
||||
this.storageViews.form.classList.add("hidden");
|
||||
} else {
|
||||
this.storageViews.summary.classList.add("hidden");
|
||||
this.storageViews.form.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,8 +589,10 @@ export class UploadModal {
|
||||
}
|
||||
|
||||
try {
|
||||
this.toggles.storageUnlock.textContent = "Unlocking...";
|
||||
this.toggles.storageUnlock.disabled = true;
|
||||
if (this.toggles.storageUnlock) {
|
||||
this.toggles.storageUnlock.textContent = "Unlocking...";
|
||||
this.toggles.storageUnlock.disabled = true;
|
||||
}
|
||||
await this.storageService.unlock(pubkey, { signer });
|
||||
this.isStorageUnlocked = true;
|
||||
this.updateLockUi();
|
||||
@@ -554,8 +601,10 @@ export class UploadModal {
|
||||
console.error("Unlock failed", err);
|
||||
alert("Failed to unlock storage: " + err.message);
|
||||
} finally {
|
||||
this.toggles.storageUnlock.textContent = "Unlock";
|
||||
this.toggles.storageUnlock.disabled = false;
|
||||
if (this.toggles.storageUnlock) {
|
||||
this.toggles.storageUnlock.textContent = "Unlock";
|
||||
this.toggles.storageUnlock.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,14 +615,17 @@ export class UploadModal {
|
||||
this.statusText.storageLock.className = locked ? "text-xs text-critical" : "text-xs text-success";
|
||||
}
|
||||
if (this.toggles.storageUnlock) {
|
||||
if (locked) {
|
||||
// Show unlock button only if locked AND we have a configuration to unlock
|
||||
if (locked && this.storageConfigured) {
|
||||
this.toggles.storageUnlock.classList.remove("hidden");
|
||||
} else {
|
||||
this.toggles.storageUnlock.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
if (this.statusText.storage) {
|
||||
if (locked) {
|
||||
if (locked && this.storageConfigured) {
|
||||
this.statusText.storage.classList.add("hidden"); // Summary view has its own status area
|
||||
} else if (locked) {
|
||||
this.statusText.storage.classList.add("hidden");
|
||||
this.statusText.storage.textContent = "";
|
||||
} else {
|
||||
@@ -610,7 +662,7 @@ export class UploadModal {
|
||||
const secret = this.inputs.r2Secret?.value?.trim();
|
||||
|
||||
if (!accountId || !keyId || !secret) {
|
||||
alert("Please fill in Account ID, Access Key ID, and Secret Access Key.");
|
||||
alert("Please fill in Account ID/Endpoint, Access Key ID, and Secret Access Key.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -642,7 +694,7 @@ export class UploadModal {
|
||||
const npub = pubkey ? this.safeEncodeNpub(pubkey) : null;
|
||||
|
||||
if (!settings.baseDomain) {
|
||||
this.wizard.errorText.textContent = "Public Bucket URL is required.";
|
||||
this.wizard.errorText.textContent = "Public URL is required.";
|
||||
this.wizard.errorContainer.classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
@@ -663,7 +715,7 @@ export class UploadModal {
|
||||
|
||||
// Dynamic Cloudflare Link
|
||||
// https://dash.cloudflare.com/?to=/:account/r2/default/buckets/:bucket/settings
|
||||
if (settings.accountId && npub) {
|
||||
if (settings.accountId && npub && !settings.accountId.includes("://")) {
|
||||
const bucketName = this.inputs.r2BucketName?.value || "";
|
||||
const cfLink = `https://dash.cloudflare.com/?to=/${settings.accountId}/r2/default/buckets/${bucketName}/settings`;
|
||||
this.wizard.guideLink.href = cfLink;
|
||||
@@ -681,16 +733,31 @@ export class UploadModal {
|
||||
if (pubkey) {
|
||||
// Save to secure storage
|
||||
const connectionId = this.activeConnectionId || `r2-${Date.now()}`;
|
||||
await this.storageService.saveConnection(pubkey, connectionId, {
|
||||
provider: PROVIDERS.R2,
|
||||
accountId: settings.accountId,
|
||||
|
||||
// Heuristic for provider type
|
||||
let provider = PROVIDERS.GENERIC;
|
||||
let payload = {
|
||||
accessKeyId: settings.accessKeyId,
|
||||
secretAccessKey: settings.secretAccessKey,
|
||||
};
|
||||
|
||||
// If account ID doesn't look like a URL, assume R2/S3-account-based
|
||||
if (settings.accountId && !settings.accountId.includes("://")) {
|
||||
provider = PROVIDERS.R2;
|
||||
payload.accountId = settings.accountId;
|
||||
} else {
|
||||
payload.endpoint = settings.accountId;
|
||||
}
|
||||
|
||||
await this.storageService.saveConnection(pubkey, connectionId, {
|
||||
provider,
|
||||
...payload,
|
||||
}, {
|
||||
provider: PROVIDERS.R2,
|
||||
label: "Default R2",
|
||||
provider,
|
||||
label: provider === PROVIDERS.R2 ? "Default R2" : "Default S3",
|
||||
baseDomain: settings.baseDomain,
|
||||
defaultForUploads: true
|
||||
defaultForUploads: true,
|
||||
bucket: this.inputs.r2BucketName?.value || sanitizeBucketName(npub)
|
||||
});
|
||||
this.activeConnectionId = connectionId;
|
||||
}
|
||||
@@ -712,6 +779,8 @@ export class UploadModal {
|
||||
this.toggles.storageSettings.setAttribute("aria-expanded", "false");
|
||||
// Reset wizard to start for next edit
|
||||
this.goToCredentialsStep();
|
||||
// Reload state to potentially switch to summary view
|
||||
this.loadFromStorage();
|
||||
}, 1500);
|
||||
} catch (err) {
|
||||
userLogger.error("Verification crashed:", err);
|
||||
|
||||
Reference in new issue
Block a user