From 6b510fe89c2e70b8ce5ad587b682b3e8936e9741 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Thu, 8 May 2025 14:41:35 -0700 Subject: [PATCH] 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 --- frontend/docs/docs/js/embed.js | 9 +++ frontend/docs/mkdocs.yml | 1 + .../crawl-workflows/workflow-editor.ts | 23 ++++++++ frontend/src/index.ts | 25 ++++++-- frontend/src/layouts/pageSectionsWithNav.ts | 5 +- frontend/src/pages/org/workflow-detail.ts | 4 +- frontend/src/pages/org/workflows-new.ts | 58 +++++-------------- frontend/src/theme.stylesheet.css | 19 ++++++ frontend/src/utils/state.ts | 7 +++ frontend/src/utils/workflow.ts | 38 ++++++++++++ 10 files changed, 138 insertions(+), 51 deletions(-) create mode 100644 frontend/docs/docs/js/embed.js diff --git a/frontend/docs/docs/js/embed.js b/frontend/docs/docs/js/embed.js new file mode 100644 index 00000000..16feabd9 --- /dev/null +++ b/frontend/docs/docs/js/embed.js @@ -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); +} diff --git a/frontend/docs/mkdocs.yml b/frontend/docs/mkdocs.yml index bc7d16a2..6150c49d 100644 --- a/frontend/docs/mkdocs.yml +++ b/frontend/docs/mkdocs.yml @@ -6,6 +6,7 @@ extra_css: - stylesheets/extra.css extra_javascript: - js/insertversion.js + - js/embed.js theme: name: material custom_dir: docs/overrides diff --git a/frontend/src/features/crawl-workflows/workflow-editor.ts b/frontend/src/features/crawl-workflows/workflow-editor.ts index 26425852..473c93f2 100644 --- a/frontend/src/features/crawl-workflows/workflow-editor.ts +++ b/frontend/src/features/crawl-workflows/workflow-editor.ts @@ -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()} @@ -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( + "btrix-user-guide-show", + { + detail: { + path: `user-guide/workflow-setup/#${workflowTabToGuideHash[step]}`, + }, + bubbles: true, + composed: true, + }, + ), + ); + } }; private onKeyDown(event: KeyboardEvent) { diff --git a/frontend/src/index.ts b/frontend/src/index.ts index a195ddce..cbef0b7d 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -339,7 +339,9 @@ export class App extends BtrixElement {
${this.renderSuperadminBanner()} ${this.renderNavBar()} ${this.renderAlertBanner()} -
+
${this.renderPage()}
${this.renderFooter()}
@@ -356,14 +358,27 @@ export class App extends BtrixElement { 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("#")); + }} > ${msg("User Guide")} html`
${this.renderBreadcrumbs()}
-
+
diff --git a/frontend/src/pages/org/workflows-new.ts b/frontend/src/pages/org/workflows-new.ts index 6b47b24f..9a41f92a 100644 --- a/frontend/src/pages/org/workflows-new.ts +++ b/frontend/src/pages/org/workflows-new.ts @@ -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 = { - 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`
${this.renderBreadcrumbs()}
-
+

${msg("New Crawl Workflow")}

{ 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), ); }} > diff --git a/frontend/src/theme.stylesheet.css b/frontend/src/theme.stylesheet.css index e179d65e..a001ced4 100644 --- a/frontend/src/theme.stylesheet.css +++ b/frontend/src/theme.stylesheet.css @@ -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 */ diff --git a/frontend/src/utils/state.ts b/frontend/src/utils/state.ts index d11d7fb6..28b08c18 100644 --- a/frontend/src/utils/state.ts +++ b/frontend/src/utils/state.ts @@ -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() { diff --git a/frontend/src/utils/workflow.ts b/frontend/src/utils/workflow.ts index d78141c9..5946f5b0 100644 --- a/frontend/src/utils/workflow.ts +++ b/frontend/src/utils/workflow.ts @@ -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; +export enum GuideHash { + Scope = "scope", + Limits = "crawl-limits", + Behaviors = "page-behavior", + BrowserSettings = "browser-settings", + Scheduling = "scheduling", + Metadata = "metadata", +} + +export const workflowTabToGuideHash: Record = { + 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( + "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");