diff --git a/frontend/src/features/browser-profiles/new-browser-profile-dialog.ts b/frontend/src/features/browser-profiles/new-browser-profile-dialog.ts index 9a832e77..fcd0b180 100644 --- a/frontend/src/features/browser-profiles/new-browser-profile-dialog.ts +++ b/frontend/src/features/browser-profiles/new-browser-profile-dialog.ts @@ -106,7 +106,7 @@ export class NewBrowserProfileDialog extends LiteElement { ?loading=${this.isSubmitting} ?disabled=${this.isSubmitting} @click=${() => this.dialog?.submit()} - >${msg("Configure Browser Profile")}${msg("Start Browsing")} `; diff --git a/frontend/src/features/browser-profiles/profile-browser.ts b/frontend/src/features/browser-profiles/profile-browser.ts index 7f162b66..a4325155 100644 --- a/frontend/src/features/browser-profiles/profile-browser.ts +++ b/frontend/src/features/browser-profiles/profile-browser.ts @@ -17,7 +17,7 @@ type BrowserResponseData = { url?: string; }; export type BrowserLoadDetail = string; -export type BrowserErrorDetail = { +export type BrowserNotAvailableError = { error: APIError | Error; }; export type BrowserConnectionChange = { @@ -151,6 +151,16 @@ export class ProfileBrowser extends TailwindElement { ), ); } + if (changedProperties.has("browserNotAvailable")) { + if (this.browserNotAvailable) { + window.removeEventListener("beforeunload", this.onBeforeUnload); + } else { + window.addEventListener("beforeunload", this.onBeforeUnload); + } + this.dispatchEvent( + new CustomEvent("btrix-browser-error"), + ); + } } private animateSidebar() { @@ -245,7 +255,7 @@ export class ProfileBrowser extends TailwindElement {
- ${msg("Reload Browser")} + ${msg("Load New Browser")}
@@ -271,7 +281,7 @@ export class ProfileBrowser extends TailwindElement {

${msg( - `Interactive browser is offline. Waiting to reconnect...`, + "Connection to interactive browser lost. Waiting to reconnect...", )}

@@ -373,29 +383,7 @@ export class ProfileBrowser extends TailwindElement { this.iframeSrc = undefined; this.isIframeLoaded = false; - try { - await this.checkBrowserStatus(); - - this.browserNotAvailable = false; - } catch (e) { - this.browserNotAvailable = true; - - await this.updateComplete; - - this.dispatchEvent( - new CustomEvent("btrix-browser-error", { - detail: { - error: e instanceof Error ? e : new Error(), - }, - }), - ); - - this.notify.toast({ - message: msg("Sorry, can't edit browser profile at this time."), - variant: "danger", - icon: "exclamation-octagon", - }); - } + await this.checkBrowserStatus(); } /** @@ -405,6 +393,7 @@ export class ProfileBrowser extends TailwindElement { let result: BrowserResponseData; try { result = await this.getBrowser(); + this.browserNotAvailable = false; } catch (e) { this.browserNotAvailable = true; return; diff --git a/frontend/src/pages/org/browser-profiles-detail.ts b/frontend/src/pages/org/browser-profiles-detail.ts index 6e06cb14..8b08e100 100644 --- a/frontend/src/pages/org/browser-profiles-detail.ts +++ b/frontend/src/pages/org/browser-profiles-detail.ts @@ -381,10 +381,7 @@ export class BrowserProfilesDetail extends TailwindElement { private renderBrowserProfileControls() { return html`
- void this.discardChangesDialog?.show()} - > + ${msg("Cancel")}
@@ -494,6 +491,14 @@ export class BrowserProfilesDetail extends TailwindElement { this.isBrowserLoaded = e.detail.connected; } + private async onCancel() { + if (this.isBrowserLoaded) { + void this.discardChangesDialog?.show(); + } else { + void this.cancelEditBrowser(); + } + } + private async startBrowserPreview() { if (!this.profile) return; diff --git a/frontend/src/pages/org/browser-profiles-new.ts b/frontend/src/pages/org/browser-profiles-new.ts index 2eec11e7..dc76ded2 100644 --- a/frontend/src/pages/org/browser-profiles-new.ts +++ b/frontend/src/pages/org/browser-profiles-new.ts @@ -1,11 +1,17 @@ import { localized, msg, str } from "@lit/localize"; -import { customElement, property, state } from "lit/decorators.js"; +import { html } from "lit"; +import { customElement, property, query, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import queryString from "query-string"; +import { TailwindElement } from "@/classes/TailwindElement"; +import type { Dialog } from "@/components/ui/dialog"; +import { APIController } from "@/controllers/api"; +import { NavigateController } from "@/controllers/navigate"; +import { NotifyController } from "@/controllers/notify"; +import type { BrowserConnectionChange } from "@/features/browser-profiles/profile-browser"; import { isApiError } from "@/utils/api"; import type { AuthState } from "@/utils/AuthService"; -import LiteElement, { html } from "@/utils/LiteElement"; /** * Usage: @@ -19,7 +25,7 @@ import LiteElement, { html } from "@/utils/LiteElement"; */ @localized() @customElement("btrix-browser-profiles-new") -export class BrowserProfilesNew extends LiteElement { +export class BrowserProfilesNew extends TailwindElement { @property({ type: Object }) authState!: AuthState; @@ -42,16 +48,24 @@ export class BrowserProfilesNew extends LiteElement { url: "", }; + private readonly api = new APIController(this); + private readonly notify = new NotifyController(this); + private readonly nav = new NavigateController(this); + @state() private isSubmitting = false; @state() private isDialogVisible = false; + @state() + private isBrowserLoaded = false; + + @query("#discardDialog") + private readonly discardDialog?: Dialog | null; + disconnectedCallback(): void { - if (this.browserId) { - void this.deleteBrowser(this.browserId); - } + void this.closeBrowser(); super.disconnectedCallback(); } @@ -61,9 +75,9 @@ export class BrowserProfilesNew extends LiteElement { (this.isBrowserLoaded = true)} @btrix-browser-reload=${this.onBrowserReload} + @btrix-browser-error=${this.onBrowserError} + @btrix-browser-connection-change=${this.onBrowserConnectionChange} >
- (this.isDialogVisible = true)} - > - ${msg("Finish Browsing")} - + ${this.renderBrowserProfileControls()}
@@ -143,6 +154,78 @@ export class BrowserProfilesNew extends LiteElement { > ${this.renderForm()} + + + ${msg("Are you sure you want to cancel creating a browser profile?")} +
+ void this.discardDialog?.hide()} + > + ${msg("No, Continue Browsing")} + + { + void this.discardDialog?.hide(); + void this.closeBrowser(); + }} + >${msg("Yes, Cancel")} + +
+
+ `; + } + + private async onBrowserError() { + this.isBrowserLoaded = false; + } + + private async onBrowserConnectionChange( + e: CustomEvent, + ) { + this.isBrowserLoaded = e.detail.connected; + } + + private onCancel() { + if (!this.isBrowserLoaded) { + void this.closeBrowser(); + } else { + void this.discardDialog?.show(); + } + } + + private async closeBrowser() { + this.isBrowserLoaded = false; + + if (this.browserId) { + await this.deleteBrowser(this.browserId); + } + this.nav.to(`${this.nav.orgBasePath}/browser-profiles`); + } + + private renderBrowserProfileControls() { + return html` +
+ + ${msg("Cancel")} + +
+ (this.isDialogVisible = true)} + > + ${msg("Save New Profile...")} + +
+
`; } @@ -210,8 +293,8 @@ export class BrowserProfilesNew extends LiteElement { crawlerChannel, }); - this.navTo( - `${this.orgBasePath}/browser-profiles/profile/browser/${ + this.nav.to( + `${this.nav.orgBasePath}/browser-profiles/profile/browser/${ data.browserid }?${queryString.stringify({ url, @@ -234,7 +317,7 @@ export class BrowserProfilesNew extends LiteElement { }; try { - const data = await this.apiFetch<{ id: string }>( + const data = await this.api.fetch<{ id: string }>( `/orgs/${this.orgId}/profiles`, this.authState!, { @@ -243,13 +326,15 @@ export class BrowserProfilesNew extends LiteElement { }, ); - this.notify({ + this.notify.toast({ message: msg("Successfully created browser profile."), variant: "success", icon: "check2-circle", }); - this.navTo(`${this.orgBasePath}/browser-profiles/profile/${data.id}`); + this.nav.to( + `${this.nav.orgBasePath}/browser-profiles/profile/${data.id}`, + ); } catch (e) { this.isSubmitting = false; @@ -267,7 +352,7 @@ export class BrowserProfilesNew extends LiteElement { } } - this.notify({ + this.notify.toast({ message: message, variant: "danger", icon: "exclamation-octagon", @@ -286,7 +371,7 @@ export class BrowserProfilesNew extends LiteElement { crawlerChannel, }; - return this.apiFetch<{ browserid: string }>( + return this.api.fetch<{ browserid: string }>( `/orgs/${this.orgId}/profiles/browser`, this.authState!, { @@ -298,7 +383,7 @@ export class BrowserProfilesNew extends LiteElement { private async deleteBrowser(id: string) { try { - const data = await this.apiFetch( + const data = await this.api.fetch( `/orgs/${this.orgId}/profiles/browser/${id}`, this.authState!, {