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 { 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 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 | ||||
| @ -33,8 +35,8 @@ export class NewWorkflowDialog extends TailwindElement { | ||||
|             @click=${() => { | ||||
|               this.dispatchEvent( | ||||
|                 new CustomEvent("select-job-type", { | ||||
|                   detail: "page-list", | ||||
|                 }) as SelectJobTypeEvent, | ||||
|                   detail: WorkflowScopeType.PageList, | ||||
|                 }), | ||||
|               ); | ||||
|             }} | ||||
|           > | ||||
| @ -63,7 +65,7 @@ export class NewWorkflowDialog extends TailwindElement { | ||||
|             @click=${() => { | ||||
|               this.dispatchEvent( | ||||
|                 new CustomEvent("select-job-type", { | ||||
|                   detail: "prefix", | ||||
|                   detail: WorkflowScopeType.Prefix, | ||||
|                 }) as SelectJobTypeEvent, | ||||
|               ); | ||||
|             }} | ||||
|  | ||||
| @ -67,6 +67,7 @@ import { | ||||
| import { maxLengthValidator } from "@/utils/form"; | ||||
| import { getLocale } from "@/utils/localization"; | ||||
| import { isArchivingDisabled } from "@/utils/orgs"; | ||||
| import { AppStateService } from "@/utils/state"; | ||||
| import { regexEscape } from "@/utils/string"; | ||||
| import { tw } from "@/utils/tailwind"; | ||||
| 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); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -539,6 +539,13 @@ export class Org extends LiteElement { | ||||
|       @select-new-dialog=${this.onSelectNewDialog} | ||||
|       @select-job-type=${(e: SelectJobTypeEvent) => { | ||||
|         this.openDialogName = undefined; | ||||
| 
 | ||||
|         if (e.detail !== this.appState.userPreferences?.newWorkflowScopeType) { | ||||
|           AppStateService.partialUpdateUserPreferences({ | ||||
|             newWorkflowScopeType: e.detail, | ||||
|           }); | ||||
|         } | ||||
| 
 | ||||
|         this.navTo(`${this.orgBasePath}/workflows/new`, { | ||||
|           scopeType: e.detail, | ||||
|         }); | ||||
|  | ||||
| @ -219,9 +219,12 @@ export class WorkflowsList extends LiteElement { | ||||
|                   <sl-button | ||||
|                     variant="primary" | ||||
|                     size="small" | ||||
|                     href="${this.orgBasePath}/workflows/new" | ||||
|                     ?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> | ||||
|                     ${msg("New Workflow")}</sl-button | ||||
|  | ||||
| @ -8,36 +8,10 @@ import { ScopeType, type Seed, type WorkflowParams } from "./types"; | ||||
| 
 | ||||
| import type { UserGuideEventMap } from "@/index"; | ||||
| import { pageNav, type Breadcrumb } from "@/layouts/pageHeader"; | ||||
| import { WorkflowScopeType } from "@/types/workflow"; | ||||
| import LiteElement, { html } from "@/utils/LiteElement"; | ||||
| 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: | ||||
|  * ```ts
 | ||||
| @ -59,6 +33,35 @@ export class WorkflowsNew extends LiteElement { | ||||
|   @property({ type: Object }) | ||||
|   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() { | ||||
|     const breadcrumbs: Breadcrumb[] = [ | ||||
|       { | ||||
| @ -103,7 +106,7 @@ export class WorkflowsNew extends LiteElement { | ||||
|       </header> | ||||
|       ${when(this.org, (org) => { | ||||
|         const initialWorkflow = mergeDeep( | ||||
|           defaultValue, | ||||
|           this.defaultNewWorkflow, | ||||
|           { | ||||
|             profileid: org.crawlingDefaults?.profileid, | ||||
|             config: { | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { z } from "zod"; | ||||
| 
 | ||||
| import { accessCodeSchema } from "./org"; | ||||
| import { WorkflowScopeType } from "./workflow"; | ||||
| 
 | ||||
| export const userOrgSchema = z.object({ | ||||
|   default: z.boolean().optional(), | ||||
| @ -44,3 +45,8 @@ export const userInfoSchema = z.object({ | ||||
|   orgs: z.array(userOrgSchema), | ||||
| }); | ||||
| 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 | ||||
|  */ | ||||
| import { mergeDeep } from "immutable"; | ||||
| import { locked, options, transaction, use } from "lit-shared-state"; | ||||
| 
 | ||||
| import { persist } from "./persist"; | ||||
| 
 | ||||
| import { authSchema, type Auth } from "@/types/auth"; | ||||
| 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 { isAdmin, isCrawler } from "@/utils/orgs"; | ||||
| 
 | ||||
| @ -28,11 +35,15 @@ export function makeAppStateService() { | ||||
|     @options(persist(window.sessionStorage)) | ||||
|     userInfo: UserInfo | null = null; | ||||
| 
 | ||||
|     @options(persist(window.localStorage)) | ||||
|     userPreferences: UserPreferences | null = null; | ||||
| 
 | ||||
|     // TODO persist here
 | ||||
|     auth: Auth | null = null; | ||||
| 
 | ||||
|     // Store org slug in local storage in order to redirect
 | ||||
|     // to the most recently visited org on next log in
 | ||||
|     // TODO move to `userPreferences`
 | ||||
|     @options(persist(window.localStorage)) | ||||
|     orgSlug: string | null = null; | ||||
| 
 | ||||
| @ -52,7 +63,7 @@ export function makeAppStateService() { | ||||
|           )) || | ||||
|         null; | ||||
| 
 | ||||
|       if (!userOrg) { | ||||
|       if (appState.userInfo && !userOrg) { | ||||
|         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() | ||||
|     @unlock() | ||||
|     updateOrgSlug(orgSlug: AppState["orgSlug"]) { | ||||
| @ -152,6 +182,7 @@ export function makeAppStateService() { | ||||
|     private _resetUser() { | ||||
|       appState.auth = null; | ||||
|       appState.userInfo = null; | ||||
|       appState.userPreferences = null; | ||||
|       appState.orgSlug = null; | ||||
|       appState.org = undefined; | ||||
|     } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user