fix: Sync user guide to correct workflow section (#2592)
Resolves https://github.com/webrecorder/browsertrix/issues/2560 ## Changes - Syncs workflow current form section with user guide section. - Stickies "User Guide" button to top of viewport so that user guide can be opened. - Makes content behind user guide clickable (fixes issues with stickied elements shifting when user guide is not contained to the parent element.) - Decreases size of user guide text when embedded in an iframe. - Refactors overflow scrim to reuse CSS variables. --------- Co-authored-by: Emma Segal-Grossman <hi@emma.cafe>
This commit is contained in:
		
							parent
							
								
									652e8a6085
								
							
						
					
					
						commit
						6b510fe89c
					
				
							
								
								
									
										9
									
								
								frontend/docs/docs/js/embed.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								frontend/docs/docs/js/embed.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| if (window.self !== window.top) { | ||||
|   // Within iframe--assume this is an iframe embedded in the Browsertrix app.
 | ||||
|   const style = document.createElement("style"); | ||||
| 
 | ||||
|   // Decrease text size without decreasing element size and overall spacing
 | ||||
|   style.innerText = `.md-typeset { font-size: 0.7rem; }`; | ||||
| 
 | ||||
|   window.document.body.appendChild(style); | ||||
| } | ||||
| @ -6,6 +6,7 @@ extra_css: | ||||
|   - stylesheets/extra.css | ||||
| extra_javascript: | ||||
|   - js/insertversion.js | ||||
|   - js/embed.js | ||||
| theme: | ||||
|   name: material | ||||
|   custom_dir: docs/overrides | ||||
|  | ||||
| @ -64,6 +64,7 @@ import type { | ||||
|   ExclusionChangeEvent, | ||||
|   QueueExclusionTable, | ||||
| } from "@/features/crawl-workflows/queue-exclusion-table"; | ||||
| import type { UserGuideEventMap } from "@/index"; | ||||
| import { infoCol, inputCol } from "@/layouts/columns"; | ||||
| import { pageSectionsWithNav } from "@/layouts/pageSectionsWithNav"; | ||||
| import { panel } from "@/layouts/panel"; | ||||
| @ -105,7 +106,9 @@ import { | ||||
|   getDefaultFormState, | ||||
|   getInitialFormState, | ||||
|   getServerDefaults, | ||||
|   makeUserGuideEvent, | ||||
|   SECTIONS, | ||||
|   workflowTabToGuideHash, | ||||
|   type FormState, | ||||
|   type WorkflowDefaults, | ||||
| } from "@/utils/workflow"; | ||||
| @ -420,6 +423,7 @@ export class WorkflowEditor extends BtrixElement { | ||||
|           nav: this.renderNav(), | ||||
|           main: this.renderFormSections(), | ||||
|           sticky: true, | ||||
|           stickyTopClassname: tw`lg:top-16`, | ||||
|         })} | ||||
|         ${this.renderFooter()} | ||||
|       </form> | ||||
| @ -485,6 +489,10 @@ export class WorkflowEditor extends BtrixElement { | ||||
|             this.updateProgressState({ | ||||
|               activeTab: name, | ||||
|             }); | ||||
| 
 | ||||
|             if (this.appState.userGuideOpen) { | ||||
|               this.dispatchEvent(makeUserGuideEvent(name)); | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           track(AnalyticsTrackEvent.ExpandWorkflowFormSection, { | ||||
| @ -2160,6 +2168,21 @@ https://archiveweb.page/images/${"logo.svg"}`} | ||||
|       await this.updateComplete; | ||||
| 
 | ||||
|       void this.scrollToActivePanel(); | ||||
| 
 | ||||
|       if (this.appState.userGuideOpen) { | ||||
|         this.dispatchEvent( | ||||
|           new CustomEvent<UserGuideEventMap["btrix-user-guide-show"]["detail"]>( | ||||
|             "btrix-user-guide-show", | ||||
|             { | ||||
|               detail: { | ||||
|                 path: `user-guide/workflow-setup/#${workflowTabToGuideHash[step]}`, | ||||
|               }, | ||||
|               bubbles: true, | ||||
|               composed: true, | ||||
|             }, | ||||
|           ), | ||||
|         ); | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|   private onKeyDown(event: KeyboardEvent) { | ||||
|  | ||||
| @ -339,7 +339,9 @@ export class App extends BtrixElement { | ||||
|       <div class="min-w-screen flex min-h-screen flex-col"> | ||||
|         ${this.renderSuperadminBanner()} ${this.renderNavBar()} | ||||
|         ${this.renderAlertBanner()} | ||||
|         <main class="relative flex flex-auto md:min-h-[calc(100vh-3.125rem)]"> | ||||
|         <main | ||||
|           class="relative flex flex-auto transition-[padding] md:min-h-[calc(100vh-3.125rem)]" | ||||
|         > | ||||
|           ${this.renderPage()} | ||||
|         </main> | ||||
|         <div class="border-t border-neutral-100">${this.renderFooter()}</div> | ||||
| @ -356,14 +358,27 @@ export class App extends BtrixElement { | ||||
|       <sl-drawer | ||||
|         id="userGuideDrawer" | ||||
|         label=${msg("User Guide")} | ||||
|         style="--body-spacing: 0; --footer-spacing: var(--sl-spacing-2x-small);" | ||||
|         class="[--body-spacing:0] [--footer-spacing:var(--sl-spacing-2x-small)] [--size:31rem] part-[base]:fixed part-[base]:z-50 part-[panel]:[border-left:1px_solid_var(--sl-panel-border-color)]" | ||||
|         ?open=${this.appState.userGuideOpen} | ||||
|         contained | ||||
|         @sl-hide=${() => AppStateService.updateUserGuideOpen(false)} | ||||
|         @sl-after-hide=${() => { | ||||
|           // FIXME There might be a way to handle this in Mkdocs, but updating
 | ||||
|           // only the hash doesn't seem to update the docs view
 | ||||
|           const iframe = this.userGuideDrawer.querySelector("iframe"); | ||||
| 
 | ||||
|           if (!iframe) return; | ||||
| 
 | ||||
|           const src = iframe.src; | ||||
|           iframe.src = src.slice(0, src.indexOf("#")); | ||||
|         }} | ||||
|       > | ||||
|         <span slot="label" class="flex items-center gap-3"> | ||||
|           <sl-icon name="book" class=""></sl-icon> | ||||
|           <span>${msg("User Guide")}</span> | ||||
|         </span> | ||||
|         <iframe | ||||
|           class="size-full transition-opacity duration-slow" | ||||
|           class="size-full text-xs transition-opacity duration-slow" | ||||
|           src="${this.docsUrl}user-guide/workflow-setup/" | ||||
|         ></iframe> | ||||
|         <sl-button | ||||
| @ -952,7 +967,9 @@ export class App extends BtrixElement { | ||||
|         iframe.src = this.fullDocsUrl; | ||||
|       } | ||||
| 
 | ||||
|       void this.userGuideDrawer.show(); | ||||
|       if (!this.appState.userGuideOpen) { | ||||
|         AppStateService.updateUserGuideOpen(true); | ||||
|       } | ||||
|     } else { | ||||
|       console.debug("user guide iframe not found"); | ||||
|     } | ||||
|  | ||||
| @ -8,11 +8,13 @@ export function pageSectionsWithNav({ | ||||
|   main, | ||||
|   placement = "start", | ||||
|   sticky = false, | ||||
|   stickyTopClassname, | ||||
| }: { | ||||
|   nav: TemplateResult; | ||||
|   main: TemplateResult; | ||||
|   placement?: "start" | "top"; | ||||
|   sticky?: boolean; | ||||
|   stickyTopClassname?: string; // e.g. `lg:top-0`
 | ||||
| }) { | ||||
|   return html` | ||||
|     <div | ||||
| @ -24,7 +26,8 @@ export function pageSectionsWithNav({ | ||||
|       <div | ||||
|         class=${clsx( | ||||
|           tw`flex flex-1 flex-col gap-2`, | ||||
|           sticky && tw`lg:sticky lg:top-2 lg:self-start`, | ||||
|           sticky && | ||||
|             [tw`lg:sticky lg:self-start`, stickyTopClassname || tw`lg:top-2`], | ||||
|           placement === "start" ? tw`lg:max-w-[16.5rem]` : tw`lg:flex-row`, | ||||
|         )} | ||||
|         part="tabs" | ||||
|  | ||||
| @ -592,7 +592,9 @@ export class WorkflowDetail extends BtrixElement { | ||||
|   private readonly renderEditor = () => html` | ||||
|     <div class="col-span-1">${this.renderBreadcrumbs()}</div> | ||||
| 
 | ||||
|     <header class="col-span-1 mb-3 flex flex-wrap gap-2"> | ||||
|     <header | ||||
|       class="scrim scrim-to-b z-10 col-span-1 mb-3 flex flex-wrap gap-2 before:-top-3 lg:sticky lg:top-3" | ||||
|     > | ||||
|       <btrix-detail-page-title .item=${this.workflow}></btrix-detail-page-title> | ||||
|     </header> | ||||
| 
 | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import { localized, msg } from "@lit/localize"; | ||||
| import clsx from "clsx"; | ||||
| import { mergeDeep } from "immutable"; | ||||
| import { customElement, property } from "lit/decorators.js"; | ||||
| import { ifDefined } from "lit/directives/if-defined.js"; | ||||
| @ -7,33 +8,18 @@ import type { PartialDeep } from "type-fest"; | ||||
| 
 | ||||
| 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 { tw } from "@/utils/tailwind"; | ||||
| import { | ||||
|   DEFAULT_AUTOCLICK_SELECTOR, | ||||
|   DEFAULT_SELECT_LINKS, | ||||
|   makeUserGuideEvent, | ||||
|   type SectionsEnum, | ||||
|   type FormState as WorkflowFormState, | ||||
| } from "@/utils/workflow"; | ||||
| 
 | ||||
| type GuideHash = | ||||
|   | "scope" | ||||
|   | "limits" | ||||
|   | "browser-settings" | ||||
|   | "scheduling" | ||||
|   | "metadata" | ||||
|   | "review-settings"; | ||||
| 
 | ||||
| const workflowTabToGuideHash: Record<string, GuideHash> = { | ||||
|   crawlSetup: "scope", | ||||
|   crawlLimits: "limits", | ||||
|   browserSettings: "browser-settings", | ||||
|   crawlScheduling: "scheduling", | ||||
|   crawlMetadata: "metadata", | ||||
|   confirmSettings: "review-settings", | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Usage: | ||||
|  * ```ts
 | ||||
| @ -55,23 +41,6 @@ export class WorkflowsNew extends LiteElement { | ||||
|   @property({ type: Object }) | ||||
|   initialWorkflow?: WorkflowParams; | ||||
| 
 | ||||
|   private userGuideHashLink: GuideHash = "scope"; | ||||
| 
 | ||||
|   connectedCallback(): void { | ||||
|     super.connectedCallback(); | ||||
| 
 | ||||
|     this.userGuideHashLink = | ||||
|       // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | ||||
|       workflowTabToGuideHash[window.location.hash.slice(1) as GuideHash] || | ||||
|       "scope"; | ||||
| 
 | ||||
|     window.addEventListener("hashchange", () => { | ||||
|       const hashValue = window.location.hash.slice(1) as GuideHash; | ||||
|       // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | ||||
|       this.userGuideHashLink = workflowTabToGuideHash[hashValue] || "scope"; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   private get defaultNewWorkflow(): WorkflowParams { | ||||
|     return { | ||||
|       name: "", | ||||
| @ -125,21 +94,20 @@ export class WorkflowsNew extends LiteElement { | ||||
| 
 | ||||
|     return html` | ||||
|       <div class="mb-5">${this.renderBreadcrumbs()}</div> | ||||
|       <header class="flex items-center justify-between"> | ||||
|       <header | ||||
|         class="scrim scrim-to-b z-10 flex flex-wrap items-start justify-between gap-2 to-white before:-top-3 lg:sticky lg:top-3" | ||||
|       > | ||||
|         <h2 class="mb-6 text-xl font-semibold">${msg("New Crawl Workflow")}</h2> | ||||
|         <sl-button | ||||
|           size="small" | ||||
|           class=${clsx( | ||||
|             tw`transition-opacity`, | ||||
|             this.appState.userGuideOpen && tw`pointer-events-none opacity-0`, | ||||
|           )} | ||||
|           ?disabled=${this.appState.userGuideOpen} | ||||
|           @click=${() => { | ||||
|             this.dispatchEvent( | ||||
|               new CustomEvent< | ||||
|                 UserGuideEventMap["btrix-user-guide-show"]["detail"] | ||||
|               >("btrix-user-guide-show", { | ||||
|                 detail: { | ||||
|                   path: `user-guide/workflow-setup/#${this.userGuideHashLink}`, | ||||
|                 }, | ||||
|                 bubbles: true, | ||||
|                 composed: true, | ||||
|               }), | ||||
|               makeUserGuideEvent(window.location.hash.slice(1) as SectionsEnum), | ||||
|             ); | ||||
|           }} | ||||
|         > | ||||
|  | ||||
| @ -96,6 +96,15 @@ | ||||
| 
 | ||||
|     /* Transition */ | ||||
|     --sl-transition-x-fast: 100ms; | ||||
| 
 | ||||
|     /*  | ||||
|      *  | ||||
|      * Browsertrix theme tokens | ||||
|      * | ||||
|      */ | ||||
|     /* Overflow scrim */ | ||||
|     --btrix-overflow-scroll-scrim-color: var(--sl-panel-background-color); | ||||
|     --btrix-overflow-scrim-width: 3rem; | ||||
|   } | ||||
| 
 | ||||
|   body { | ||||
| @ -446,6 +455,16 @@ | ||||
|   sl-button.button-card::part(label) { | ||||
|     @apply flex flex-1 flex-col justify-center gap-2 text-left; | ||||
|   } | ||||
| 
 | ||||
|   .scrim:before { | ||||
|     @apply pointer-events-none absolute -z-10; | ||||
|   } | ||||
| 
 | ||||
|   .scrim-to-b:before { | ||||
|     @apply w-full bg-gradient-to-b from-white; | ||||
|     height: var(--btrix-overflow-scrim-width); | ||||
|     --tw-gradient-from: var(--btrix-overflow-scroll-scrim-color, white); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /* Following styles won't work with layers */ | ||||
|  | ||||
| @ -47,6 +47,8 @@ export function makeAppStateService() { | ||||
|     // Org details
 | ||||
|     org: OrgData | null | undefined = undefined; | ||||
| 
 | ||||
|     userGuideOpen = false; | ||||
| 
 | ||||
|     // Since org slug is used to ID an org, use `userOrg`
 | ||||
|     // to retrieve the basic org info like name and ID
 | ||||
|     // before other org details are available
 | ||||
| @ -159,6 +161,11 @@ export function makeAppStateService() { | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     @unlock() | ||||
|     updateUserGuideOpen(open: boolean) { | ||||
|       appState.userGuideOpen = open; | ||||
|     } | ||||
| 
 | ||||
|     @transaction() | ||||
|     @unlock() | ||||
|     resetAll() { | ||||
|  | ||||
| @ -4,6 +4,7 @@ import { z } from "zod"; | ||||
| import { getAppSettings } from "./app"; | ||||
| 
 | ||||
| import type { Tags } from "@/components/ui/tag-input"; | ||||
| import type { UserGuideEventMap } from "@/index"; | ||||
| import { | ||||
|   Behavior, | ||||
|   ScopeType, | ||||
| @ -37,6 +38,43 @@ export const SECTIONS = [ | ||||
| export const sectionsEnum = z.enum(SECTIONS); | ||||
| export type SectionsEnum = z.infer<typeof sectionsEnum>; | ||||
| 
 | ||||
| export enum GuideHash { | ||||
|   Scope = "scope", | ||||
|   Limits = "crawl-limits", | ||||
|   Behaviors = "page-behavior", | ||||
|   BrowserSettings = "browser-settings", | ||||
|   Scheduling = "scheduling", | ||||
|   Metadata = "metadata", | ||||
| } | ||||
| 
 | ||||
| export const workflowTabToGuideHash: Record<SectionsEnum, GuideHash> = { | ||||
|   scope: GuideHash.Scope, | ||||
|   limits: GuideHash.Limits, | ||||
|   behaviors: GuideHash.Behaviors, | ||||
|   browserSettings: GuideHash.BrowserSettings, | ||||
|   scheduling: GuideHash.Scheduling, | ||||
|   metadata: GuideHash.Metadata, | ||||
| }; | ||||
| 
 | ||||
| export function makeUserGuideEvent( | ||||
|   section: SectionsEnum, | ||||
| ): UserGuideEventMap["btrix-user-guide-show"] { | ||||
|   const userGuideHash = | ||||
|     (workflowTabToGuideHash[section] as GuideHash | undefined) || | ||||
|     GuideHash.Scope; | ||||
| 
 | ||||
|   return new CustomEvent<UserGuideEventMap["btrix-user-guide-show"]["detail"]>( | ||||
|     "btrix-user-guide-show", | ||||
|     { | ||||
|       detail: { | ||||
|         path: `user-guide/workflow-setup/#${userGuideHash}`, | ||||
|       }, | ||||
|       bubbles: true, | ||||
|       composed: true, | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export function defaultLabel(value: unknown): string { | ||||
|   if (value === Infinity) { | ||||
|     return msg("Default: Unlimited"); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user