From 502d6876205eba31201dd999b5ce174ba85c89f1 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Sat, 4 Jun 2022 08:26:19 -0700 Subject: [PATCH] Enable duplicating and editing browser profile (#237) * ensure editing other config options does not lose profile * support adding/editing/removing profile of existing config * when duplicating config, ensure profile setting is also copied in the duplicate --- frontend/src/components/index.ts | 3 + .../src/components/select-browser-profile.ts | 199 ++++++++++++++++++ .../pages/archive/crawl-templates-detail.ts | 43 ++-- .../src/pages/archive/crawl-templates-list.ts | 12 +- .../src/pages/archive/crawl-templates-new.ts | 151 ++----------- 5 files changed, 259 insertions(+), 149 deletions(-) create mode 100644 frontend/src/components/select-browser-profile.ts diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index 7b9d7614..3dbccad0 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -36,6 +36,9 @@ import("./not-found").then(({ NotFound }) => { import("./screencast").then(({ Screencast: Screencast }) => { customElements.define("btrix-screencast", Screencast); }); +import("./select-browser-profile").then(({ SelectBrowserProfile }) => { + customElements.define("btrix-select-browser-profile", SelectBrowserProfile); +}); customElements.define("btrix-alert", Alert); customElements.define("btrix-input", Input); diff --git a/frontend/src/components/select-browser-profile.ts b/frontend/src/components/select-browser-profile.ts new file mode 100644 index 00000000..e4af9383 --- /dev/null +++ b/frontend/src/components/select-browser-profile.ts @@ -0,0 +1,199 @@ +import { html } from "lit"; +import { property, state } from "lit/decorators.js"; +import { msg, localized } from "@lit/localize"; +import orderBy from "lodash/fp/orderBy"; + +import type { AuthState } from "../utils/AuthService"; +import LiteElement from "../utils/LiteElement"; +import type { Profile } from "../pages/archive/types"; + +/** + * Browser profile select dropdown + * + * Usage example: + * ```ts + * selectedProfile = value} + * > + * ``` + * + * @event on-change + */ +@localized() +export class SelectBrowserProfile extends LiteElement { + @property({ type: Object }) + authState!: AuthState; + + @property({ type: String }) + archiveId!: string; + + @property({ type: String }) + profileId?: string; + + @state() + private selectedProfile?: Profile; + + @state() + private browserProfiles?: Profile[]; + + protected firstUpdated() { + this.fetchBrowserProfiles(); + } + + render() { + return html` + { + // Refetch to keep list up to date + this.fetchBrowserProfiles(); + }} + @sl-hide=${this.stopProp} + @sl-after-hide=${this.stopProp} + > + ${this.browserProfiles + ? "" + : html` `} + ${this.browserProfiles?.map( + (profile) => html` + + ${profile.name} +
+
+ +
+ ` + )} +
+ + ${this.browserProfiles && !this.browserProfiles.length + ? this.renderNoProfiles() + : this.renderSelectedProfileInfo()} + `; + } + + private renderSelectedProfileInfo() { + if (!this.selectedProfile) return; + + return html` +
+ ${this.selectedProfile.description + ? html`${this.selectedProfile.description}` + : ""} + + ${msg("View profile")} + + +
+ `; + } + + private renderNoProfiles() { + return html` +
+ ${msg("No browser profiles found.")} + ${msg("Create a browser profile")} + +
+ `; + } + + private onChange(e: any) { + this.selectedProfile = this.browserProfiles?.find( + ({ id }) => id === e.target.value + ); + + this.dispatchEvent( + new CustomEvent("on-change", { + detail: { + value: this.selectedProfile, + }, + }) + ); + } + + /** + * Fetch browser profiles and update internal state + */ + private async fetchBrowserProfiles(): Promise { + try { + const data = await this.getProfiles(); + + this.browserProfiles = orderBy(["name", "created"])(["asc", "desc"])( + data + ) as Profile[]; + + if (this.profileId && !this.selectedProfile) { + this.selectedProfile = this.browserProfiles.find( + ({ id }) => id === this.profileId + ); + } + } catch (e) { + this.notify({ + message: msg("Sorry, couldn't retrieve browser profiles at this time."), + type: "danger", + icon: "exclamation-octagon", + }); + } + } + + private async getProfiles(): Promise { + const data = await this.apiFetch( + `/archives/${this.archiveId}/profiles`, + this.authState! + ); + + return data; + } + + /** + * Stop propgation of sl-select events. + * Prevents bug where sl-dialog closes when dropdown closes + * https://github.com/shoelace-style/shoelace/issues/170 + */ + private stopProp(e: CustomEvent) { + e.stopPropagation(); + } +} diff --git a/frontend/src/pages/archive/crawl-templates-detail.ts b/frontend/src/pages/archive/crawl-templates-detail.ts index d28a29a9..8cab80f1 100644 --- a/frontend/src/pages/archive/crawl-templates-detail.ts +++ b/frontend/src/pages/archive/crawl-templates-detail.ts @@ -7,6 +7,7 @@ import { parse as yamlToJson, stringify as jsonToYaml } from "yaml"; import type { AuthState } from "../../utils/AuthService"; import LiteElement, { html } from "../../utils/LiteElement"; +import type { InitialCrawlTemplate } from "./crawl-templates-new"; import type { CrawlTemplate, CrawlConfig } from "./types"; import { getUTCSchedule } from "./utils"; import "../../components/crawl-scheduler"; @@ -670,6 +671,14 @@ export class CrawlTemplatesDetail extends LiteElement { ${this.renderSeedsForm()} +
+ +
+
): Promise { - console.log(params); - this.isSubmittingUpdate = true; try { diff --git a/frontend/src/pages/archive/crawl-templates-list.ts b/frontend/src/pages/archive/crawl-templates-list.ts index 8f509b5e..f754dd43 100644 --- a/frontend/src/pages/archive/crawl-templates-list.ts +++ b/frontend/src/pages/archive/crawl-templates-list.ts @@ -11,6 +11,7 @@ import Fuse from "fuse.js"; import type { AuthState } from "../../utils/AuthService"; import LiteElement, { html } from "../../utils/LiteElement"; +import type { InitialCrawlTemplate } from "./crawl-templates-new"; import type { CrawlTemplate } from "./types"; import { getUTCSchedule } from "./utils"; import "../../components/crawl-scheduler"; @@ -581,15 +582,14 @@ export class CrawlTemplatesList extends LiteElement { * Create a new template using existing template data */ private async duplicateConfig(template: CrawlTemplate) { - const config: CrawlTemplate["config"] = { - ...template.config, + const crawlTemplate: InitialCrawlTemplate = { + name: msg(str`${template.name} Copy`), + config: template.config, + profileid: template.profileid || null, }; this.navTo(`/archives/${this.archiveId}/crawl-templates/new`, { - crawlTemplate: { - name: msg(str`${template.name} Copy`), - config, - }, + crawlTemplate, }); this.notify({ diff --git a/frontend/src/pages/archive/crawl-templates-new.ts b/frontend/src/pages/archive/crawl-templates-new.ts index 4a18b31c..8586a913 100644 --- a/frontend/src/pages/archive/crawl-templates-new.ts +++ b/frontend/src/pages/archive/crawl-templates-new.ts @@ -3,7 +3,6 @@ import { ifDefined } from "lit/directives/if-defined.js"; import { msg, localized, str } from "@lit/localize"; import cronParser from "cron-parser"; import { parse as yamlToJson, stringify as jsonToYaml } from "yaml"; -import orderBy from "lodash/fp/orderBy"; import type { AuthState } from "../../utils/AuthService"; import LiteElement, { html } from "../../utils/LiteElement"; @@ -11,7 +10,7 @@ import { getLocaleTimeZone } from "../../utils/localization"; import type { CrawlConfig, Profile } from "./types"; import { getUTCSchedule } from "./utils"; -export type NewCrawlTemplate = { +type NewCrawlTemplate = { id?: string; name: string; schedule: string; @@ -19,9 +18,14 @@ export type NewCrawlTemplate = { crawlTimeout?: number; scale: number; config: CrawlConfig; - profileid: string; + profileid: string | null; }; +export type InitialCrawlTemplate = Pick< + NewCrawlTemplate, + "name" | "config" | "profileid" +>; + const initialValues = { name: "", runNow: true, @@ -55,10 +59,7 @@ export class CrawlTemplatesNew extends LiteElement { archiveId!: string; @property({ type: Object }) - initialCrawlTemplate?: { - name: string; - config: CrawlConfig; - }; + initialCrawlTemplate?: InitialCrawlTemplate; @state() private isRunNow: boolean = initialValues.runNow; @@ -85,15 +86,12 @@ export class CrawlTemplatesNew extends LiteElement { @state() private isSubmitting: boolean = false; + @state() + private browserProfileId?: string | null; + @state() private serverError?: string; - @state() - browserProfiles?: Profile[]; - - @state() - selectedProfile?: Profile; - private get timeZone() { return Intl.DateTimeFormat().resolvedOptions().timeZone; } @@ -137,19 +135,17 @@ export class CrawlTemplatesNew extends LiteElement { } this.initialCrawlTemplate = { name: this.initialCrawlTemplate?.name || initialValues.name, + profileid: this.initialCrawlTemplate?.profileid || null, config: { ...initialValues.config, ...this.initialCrawlTemplate?.config, }, }; this.configCode = jsonToYaml(this.initialCrawlTemplate.config); + this.browserProfileId = this.initialCrawlTemplate.profileid; super.connectedCallback(); } - protected firstUpdated() { - this.fetchBrowserProfiles(); - } - render() { return html`