Save new workflow scope type preference (#2099)
Resolves https://github.com/webrecorder/browsertrix/issues/2091 <!-- Fixes #issue_number --> ### Changes Saves scope type chosen from "+ New Workflow" dropdown to local storage, as well as from within workflow editor when creating a new workflow (but not editing an existing one).
This commit is contained in:
		
							parent
							
								
									bb6e703f6a
								
							
						
					
					
						commit
						22435ddaf9
					
				| @ -3,11 +3,13 @@ import { html } from "lit"; | |||||||
| import { customElement, property } from "lit/decorators.js"; | import { customElement, property } from "lit/decorators.js"; | ||||||
| 
 | 
 | ||||||
| import { TailwindElement } from "@/classes/TailwindElement"; | import { TailwindElement } from "@/classes/TailwindElement"; | ||||||
| import type { FormState as WorkflowFormState } from "@/utils/workflow"; | import { WorkflowScopeType } from "@/types/workflow"; | ||||||
| import seededCrawlSvg from "~assets/images/new-crawl-config_Seeded-Crawl.svg"; | import seededCrawlSvg from "~assets/images/new-crawl-config_Seeded-Crawl.svg"; | ||||||
| import urlListSvg from "~assets/images/new-crawl-config_URL-List.svg"; | import urlListSvg from "~assets/images/new-crawl-config_URL-List.svg"; | ||||||
| 
 | 
 | ||||||
| export type SelectJobTypeEvent = CustomEvent<WorkflowFormState["scopeType"]>; | export type SelectJobTypeEvent = CustomEvent< | ||||||
|  |   (typeof WorkflowScopeType)[keyof typeof WorkflowScopeType] | ||||||
|  | >; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @event select-job-type SelectJobTypeEvent |  * @event select-job-type SelectJobTypeEvent | ||||||
| @ -33,8 +35,8 @@ export class NewWorkflowDialog extends TailwindElement { | |||||||
|             @click=${() => { |             @click=${() => { | ||||||
|               this.dispatchEvent( |               this.dispatchEvent( | ||||||
|                 new CustomEvent("select-job-type", { |                 new CustomEvent("select-job-type", { | ||||||
|                   detail: "page-list", |                   detail: WorkflowScopeType.PageList, | ||||||
|                 }) as SelectJobTypeEvent, |                 }), | ||||||
|               ); |               ); | ||||||
|             }} |             }} | ||||||
|           > |           > | ||||||
| @ -63,7 +65,7 @@ export class NewWorkflowDialog extends TailwindElement { | |||||||
|             @click=${() => { |             @click=${() => { | ||||||
|               this.dispatchEvent( |               this.dispatchEvent( | ||||||
|                 new CustomEvent("select-job-type", { |                 new CustomEvent("select-job-type", { | ||||||
|                   detail: "prefix", |                   detail: WorkflowScopeType.Prefix, | ||||||
|                 }) as SelectJobTypeEvent, |                 }) as SelectJobTypeEvent, | ||||||
|               ); |               ); | ||||||
|             }} |             }} | ||||||
|  | |||||||
| @ -67,6 +67,7 @@ import { | |||||||
| import { maxLengthValidator } from "@/utils/form"; | import { maxLengthValidator } from "@/utils/form"; | ||||||
| import { getLocale } from "@/utils/localization"; | import { getLocale } from "@/utils/localization"; | ||||||
| import { isArchivingDisabled } from "@/utils/orgs"; | import { isArchivingDisabled } from "@/utils/orgs"; | ||||||
|  | import { AppStateService } from "@/utils/state"; | ||||||
| import { regexEscape } from "@/utils/string"; | import { regexEscape } from "@/utils/string"; | ||||||
| import { tw } from "@/utils/tailwind"; | import { tw } from "@/utils/tailwind"; | ||||||
| import { | import { | ||||||
| @ -1712,6 +1713,13 @@ https://archiveweb.page/images/${"logo.svg"}`} | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (!this.configId) { | ||||||
|  |       // Remember scope type for new workflows
 | ||||||
|  |       AppStateService.partialUpdateUserPreferences({ | ||||||
|  |         newWorkflowScopeType: value, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     this.updateFormState(formState); |     this.updateFormState(formState); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -539,6 +539,13 @@ export class Org extends LiteElement { | |||||||
|       @select-new-dialog=${this.onSelectNewDialog} |       @select-new-dialog=${this.onSelectNewDialog} | ||||||
|       @select-job-type=${(e: SelectJobTypeEvent) => { |       @select-job-type=${(e: SelectJobTypeEvent) => { | ||||||
|         this.openDialogName = undefined; |         this.openDialogName = undefined; | ||||||
|  | 
 | ||||||
|  |         if (e.detail !== this.appState.userPreferences?.newWorkflowScopeType) { | ||||||
|  |           AppStateService.partialUpdateUserPreferences({ | ||||||
|  |             newWorkflowScopeType: e.detail, | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         this.navTo(`${this.orgBasePath}/workflows/new`, { |         this.navTo(`${this.orgBasePath}/workflows/new`, { | ||||||
|           scopeType: e.detail, |           scopeType: e.detail, | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -219,9 +219,12 @@ export class WorkflowsList extends LiteElement { | |||||||
|                   <sl-button |                   <sl-button | ||||||
|                     variant="primary" |                     variant="primary" | ||||||
|                     size="small" |                     size="small" | ||||||
|                     href="${this.orgBasePath}/workflows/new" |  | ||||||
|                     ?disabled=${this.org?.readOnly} |                     ?disabled=${this.org?.readOnly} | ||||||
|                     @click=${this.navLink} |                     @click=${() => | ||||||
|  |                       this.navTo(`${this.orgBasePath}/workflows/new`, { | ||||||
|  |                         scopeType: | ||||||
|  |                           this.appState.userPreferences?.newWorkflowScopeType, | ||||||
|  |                       })} | ||||||
|                   > |                   > | ||||||
|                     <sl-icon slot="prefix" name="plus-lg"></sl-icon> |                     <sl-icon slot="prefix" name="plus-lg"></sl-icon> | ||||||
|                     ${msg("New Workflow")}</sl-button |                     ${msg("New Workflow")}</sl-button | ||||||
|  | |||||||
| @ -8,36 +8,10 @@ import { ScopeType, type Seed, type WorkflowParams } from "./types"; | |||||||
| 
 | 
 | ||||||
| import type { UserGuideEventMap } from "@/index"; | import type { UserGuideEventMap } from "@/index"; | ||||||
| import { pageNav, type Breadcrumb } from "@/layouts/pageHeader"; | import { pageNav, type Breadcrumb } from "@/layouts/pageHeader"; | ||||||
|  | import { WorkflowScopeType } from "@/types/workflow"; | ||||||
| import LiteElement, { html } from "@/utils/LiteElement"; | import LiteElement, { html } from "@/utils/LiteElement"; | ||||||
| import type { FormState as WorkflowFormState } from "@/utils/workflow"; | import type { FormState as WorkflowFormState } from "@/utils/workflow"; | ||||||
| 
 | 
 | ||||||
| const defaultValue = { |  | ||||||
|   name: "", |  | ||||||
|   description: null, |  | ||||||
|   profileid: null, |  | ||||||
|   schedule: "", |  | ||||||
|   config: { |  | ||||||
|     seeds: [], |  | ||||||
|     scopeType: ScopeType.Page, |  | ||||||
|     exclude: [], |  | ||||||
|     behaviorTimeout: null, |  | ||||||
|     pageLoadTimeout: null, |  | ||||||
|     pageExtraDelay: null, |  | ||||||
|     postLoadDelay: null, |  | ||||||
|     useSitemap: false, |  | ||||||
|     failOnFailedSeed: false, |  | ||||||
|     userAgent: null, |  | ||||||
|   }, |  | ||||||
|   tags: [], |  | ||||||
|   crawlTimeout: null, |  | ||||||
|   maxCrawlSize: null, |  | ||||||
|   jobType: "custom", |  | ||||||
|   scale: 1, |  | ||||||
|   autoAddCollections: [], |  | ||||||
|   crawlerChannel: "default", |  | ||||||
|   proxyId: null, |  | ||||||
| } as WorkflowParams; |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Usage: |  * Usage: | ||||||
|  * ```ts
 |  * ```ts
 | ||||||
| @ -59,6 +33,35 @@ export class WorkflowsNew extends LiteElement { | |||||||
|   @property({ type: Object }) |   @property({ type: Object }) | ||||||
|   initialWorkflow?: WorkflowParams; |   initialWorkflow?: WorkflowParams; | ||||||
| 
 | 
 | ||||||
|  |   private get defaultNewWorkflow(): WorkflowParams { | ||||||
|  |     return { | ||||||
|  |       name: "", | ||||||
|  |       description: null, | ||||||
|  |       profileid: null, | ||||||
|  |       schedule: "", | ||||||
|  |       config: { | ||||||
|  |         scopeType: (this.appState.userPreferences?.newWorkflowScopeType || | ||||||
|  |           WorkflowScopeType.Page) as ScopeType, | ||||||
|  |         exclude: [], | ||||||
|  |         behaviorTimeout: null, | ||||||
|  |         pageLoadTimeout: null, | ||||||
|  |         pageExtraDelay: null, | ||||||
|  |         postLoadDelay: null, | ||||||
|  |         useSitemap: false, | ||||||
|  |         failOnFailedSeed: false, | ||||||
|  |         userAgent: null, | ||||||
|  |       }, | ||||||
|  |       tags: [], | ||||||
|  |       crawlTimeout: null, | ||||||
|  |       maxCrawlSize: null, | ||||||
|  |       jobType: "custom", | ||||||
|  |       scale: 1, | ||||||
|  |       autoAddCollections: [], | ||||||
|  |       crawlerChannel: "default", | ||||||
|  |       proxyId: null, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   private renderBreadcrumbs() { |   private renderBreadcrumbs() { | ||||||
|     const breadcrumbs: Breadcrumb[] = [ |     const breadcrumbs: Breadcrumb[] = [ | ||||||
|       { |       { | ||||||
| @ -103,7 +106,7 @@ export class WorkflowsNew extends LiteElement { | |||||||
|       </header> |       </header> | ||||||
|       ${when(this.org, (org) => { |       ${when(this.org, (org) => { | ||||||
|         const initialWorkflow = mergeDeep( |         const initialWorkflow = mergeDeep( | ||||||
|           defaultValue, |           this.defaultNewWorkflow, | ||||||
|           { |           { | ||||||
|             profileid: org.crawlingDefaults?.profileid, |             profileid: org.crawlingDefaults?.profileid, | ||||||
|             config: { |             config: { | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import { z } from "zod"; | import { z } from "zod"; | ||||||
| 
 | 
 | ||||||
| import { accessCodeSchema } from "./org"; | import { accessCodeSchema } from "./org"; | ||||||
|  | import { WorkflowScopeType } from "./workflow"; | ||||||
| 
 | 
 | ||||||
| export const userOrgSchema = z.object({ | export const userOrgSchema = z.object({ | ||||||
|   default: z.boolean().optional(), |   default: z.boolean().optional(), | ||||||
| @ -44,3 +45,8 @@ export const userInfoSchema = z.object({ | |||||||
|   orgs: z.array(userOrgSchema), |   orgs: z.array(userOrgSchema), | ||||||
| }); | }); | ||||||
| export type UserInfo = z.infer<typeof userInfoSchema>; | export type UserInfo = z.infer<typeof userInfoSchema>; | ||||||
|  | 
 | ||||||
|  | export const userPreferencesSchema = z.object({ | ||||||
|  |   newWorkflowScopeType: z.nativeEnum(WorkflowScopeType).optional(), | ||||||
|  | }); | ||||||
|  | export type UserPreferences = z.infer<typeof userPreferencesSchema>; | ||||||
|  | |||||||
| @ -1,13 +1,20 @@ | |||||||
| /** | /** | ||||||
|  * Store and access application-wide state |  * Store and access application-wide state | ||||||
|  */ |  */ | ||||||
|  | import { mergeDeep } from "immutable"; | ||||||
| import { locked, options, transaction, use } from "lit-shared-state"; | import { locked, options, transaction, use } from "lit-shared-state"; | ||||||
| 
 | 
 | ||||||
| import { persist } from "./persist"; | import { persist } from "./persist"; | ||||||
| 
 | 
 | ||||||
| import { authSchema, type Auth } from "@/types/auth"; | import { authSchema, type Auth } from "@/types/auth"; | ||||||
| import type { OrgData } from "@/types/org"; | import type { OrgData } from "@/types/org"; | ||||||
| import { userInfoSchema, type UserInfo, type UserOrg } from "@/types/user"; | import { | ||||||
|  |   userInfoSchema, | ||||||
|  |   userPreferencesSchema, | ||||||
|  |   type UserInfo, | ||||||
|  |   type UserOrg, | ||||||
|  |   type UserPreferences, | ||||||
|  | } from "@/types/user"; | ||||||
| import type { AppSettings } from "@/utils/app"; | import type { AppSettings } from "@/utils/app"; | ||||||
| import { isAdmin, isCrawler } from "@/utils/orgs"; | import { isAdmin, isCrawler } from "@/utils/orgs"; | ||||||
| 
 | 
 | ||||||
| @ -28,11 +35,15 @@ export function makeAppStateService() { | |||||||
|     @options(persist(window.sessionStorage)) |     @options(persist(window.sessionStorage)) | ||||||
|     userInfo: UserInfo | null = null; |     userInfo: UserInfo | null = null; | ||||||
| 
 | 
 | ||||||
|  |     @options(persist(window.localStorage)) | ||||||
|  |     userPreferences: UserPreferences | null = null; | ||||||
|  | 
 | ||||||
|     // TODO persist here
 |     // TODO persist here
 | ||||||
|     auth: Auth | null = null; |     auth: Auth | null = null; | ||||||
| 
 | 
 | ||||||
|     // Store org slug in local storage in order to redirect
 |     // Store org slug in local storage in order to redirect
 | ||||||
|     // to the most recently visited org on next log in
 |     // to the most recently visited org on next log in
 | ||||||
|  |     // TODO move to `userPreferences`
 | ||||||
|     @options(persist(window.localStorage)) |     @options(persist(window.localStorage)) | ||||||
|     orgSlug: string | null = null; |     orgSlug: string | null = null; | ||||||
| 
 | 
 | ||||||
| @ -52,7 +63,7 @@ export function makeAppStateService() { | |||||||
|           )) || |           )) || | ||||||
|         null; |         null; | ||||||
| 
 | 
 | ||||||
|       if (!userOrg) { |       if (appState.userInfo && !userOrg) { | ||||||
|         console.debug("no user org matching slug in state"); |         console.debug("no user org matching slug in state"); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
| @ -113,6 +124,25 @@ export function makeAppStateService() { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @transaction() | ||||||
|  |     @unlock() | ||||||
|  |     partialUpdateUserPreferences( | ||||||
|  |       userPreferences: Partial<AppState["userPreferences"]>, | ||||||
|  |     ) { | ||||||
|  |       userPreferencesSchema.nullable().parse(userPreferences); | ||||||
|  | 
 | ||||||
|  |       if (appState.userPreferences && userPreferences) { | ||||||
|  |         appState.userPreferences = mergeDeep( | ||||||
|  |           appState.userPreferences, | ||||||
|  |           userPreferences, | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         appState.userPreferences = userPreferences; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       console.log("appState.userPreferences:", appState.userPreferences); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @transaction() |     @transaction() | ||||||
|     @unlock() |     @unlock() | ||||||
|     updateOrgSlug(orgSlug: AppState["orgSlug"]) { |     updateOrgSlug(orgSlug: AppState["orgSlug"]) { | ||||||
| @ -152,6 +182,7 @@ export function makeAppStateService() { | |||||||
|     private _resetUser() { |     private _resetUser() { | ||||||
|       appState.auth = null; |       appState.auth = null; | ||||||
|       appState.userInfo = null; |       appState.userInfo = null; | ||||||
|  |       appState.userPreferences = null; | ||||||
|       appState.orgSlug = null; |       appState.orgSlug = null; | ||||||
|       appState.org = undefined; |       appState.org = undefined; | ||||||
|     } |     } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user