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
|
- stylesheets/extra.css
|
||||||
extra_javascript:
|
extra_javascript:
|
||||||
- js/insertversion.js
|
- js/insertversion.js
|
||||||
|
- js/embed.js
|
||||||
theme:
|
theme:
|
||||||
name: material
|
name: material
|
||||||
custom_dir: docs/overrides
|
custom_dir: docs/overrides
|
||||||
|
@ -64,6 +64,7 @@ import type {
|
|||||||
ExclusionChangeEvent,
|
ExclusionChangeEvent,
|
||||||
QueueExclusionTable,
|
QueueExclusionTable,
|
||||||
} from "@/features/crawl-workflows/queue-exclusion-table";
|
} from "@/features/crawl-workflows/queue-exclusion-table";
|
||||||
|
import type { UserGuideEventMap } from "@/index";
|
||||||
import { infoCol, inputCol } from "@/layouts/columns";
|
import { infoCol, inputCol } from "@/layouts/columns";
|
||||||
import { pageSectionsWithNav } from "@/layouts/pageSectionsWithNav";
|
import { pageSectionsWithNav } from "@/layouts/pageSectionsWithNav";
|
||||||
import { panel } from "@/layouts/panel";
|
import { panel } from "@/layouts/panel";
|
||||||
@ -105,7 +106,9 @@ import {
|
|||||||
getDefaultFormState,
|
getDefaultFormState,
|
||||||
getInitialFormState,
|
getInitialFormState,
|
||||||
getServerDefaults,
|
getServerDefaults,
|
||||||
|
makeUserGuideEvent,
|
||||||
SECTIONS,
|
SECTIONS,
|
||||||
|
workflowTabToGuideHash,
|
||||||
type FormState,
|
type FormState,
|
||||||
type WorkflowDefaults,
|
type WorkflowDefaults,
|
||||||
} from "@/utils/workflow";
|
} from "@/utils/workflow";
|
||||||
@ -420,6 +423,7 @@ export class WorkflowEditor extends BtrixElement {
|
|||||||
nav: this.renderNav(),
|
nav: this.renderNav(),
|
||||||
main: this.renderFormSections(),
|
main: this.renderFormSections(),
|
||||||
sticky: true,
|
sticky: true,
|
||||||
|
stickyTopClassname: tw`lg:top-16`,
|
||||||
})}
|
})}
|
||||||
${this.renderFooter()}
|
${this.renderFooter()}
|
||||||
</form>
|
</form>
|
||||||
@ -485,6 +489,10 @@ export class WorkflowEditor extends BtrixElement {
|
|||||||
this.updateProgressState({
|
this.updateProgressState({
|
||||||
activeTab: name,
|
activeTab: name,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.appState.userGuideOpen) {
|
||||||
|
this.dispatchEvent(makeUserGuideEvent(name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
track(AnalyticsTrackEvent.ExpandWorkflowFormSection, {
|
track(AnalyticsTrackEvent.ExpandWorkflowFormSection, {
|
||||||
@ -2160,6 +2168,21 @@ https://archiveweb.page/images/${"logo.svg"}`}
|
|||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
|
|
||||||
void this.scrollToActivePanel();
|
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) {
|
private onKeyDown(event: KeyboardEvent) {
|
||||||
|
@ -339,7 +339,9 @@ export class App extends BtrixElement {
|
|||||||
<div class="min-w-screen flex min-h-screen flex-col">
|
<div class="min-w-screen flex min-h-screen flex-col">
|
||||||
${this.renderSuperadminBanner()} ${this.renderNavBar()}
|
${this.renderSuperadminBanner()} ${this.renderNavBar()}
|
||||||
${this.renderAlertBanner()}
|
${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()}
|
${this.renderPage()}
|
||||||
</main>
|
</main>
|
||||||
<div class="border-t border-neutral-100">${this.renderFooter()}</div>
|
<div class="border-t border-neutral-100">${this.renderFooter()}</div>
|
||||||
@ -356,14 +358,27 @@ export class App extends BtrixElement {
|
|||||||
<sl-drawer
|
<sl-drawer
|
||||||
id="userGuideDrawer"
|
id="userGuideDrawer"
|
||||||
label=${msg("User Guide")}
|
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">
|
<span slot="label" class="flex items-center gap-3">
|
||||||
<sl-icon name="book" class=""></sl-icon>
|
<sl-icon name="book" class=""></sl-icon>
|
||||||
<span>${msg("User Guide")}</span>
|
<span>${msg("User Guide")}</span>
|
||||||
</span>
|
</span>
|
||||||
<iframe
|
<iframe
|
||||||
class="size-full transition-opacity duration-slow"
|
class="size-full text-xs transition-opacity duration-slow"
|
||||||
src="${this.docsUrl}user-guide/workflow-setup/"
|
src="${this.docsUrl}user-guide/workflow-setup/"
|
||||||
></iframe>
|
></iframe>
|
||||||
<sl-button
|
<sl-button
|
||||||
@ -952,7 +967,9 @@ export class App extends BtrixElement {
|
|||||||
iframe.src = this.fullDocsUrl;
|
iframe.src = this.fullDocsUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void this.userGuideDrawer.show();
|
if (!this.appState.userGuideOpen) {
|
||||||
|
AppStateService.updateUserGuideOpen(true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.debug("user guide iframe not found");
|
console.debug("user guide iframe not found");
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,13 @@ export function pageSectionsWithNav({
|
|||||||
main,
|
main,
|
||||||
placement = "start",
|
placement = "start",
|
||||||
sticky = false,
|
sticky = false,
|
||||||
|
stickyTopClassname,
|
||||||
}: {
|
}: {
|
||||||
nav: TemplateResult;
|
nav: TemplateResult;
|
||||||
main: TemplateResult;
|
main: TemplateResult;
|
||||||
placement?: "start" | "top";
|
placement?: "start" | "top";
|
||||||
sticky?: boolean;
|
sticky?: boolean;
|
||||||
|
stickyTopClassname?: string; // e.g. `lg:top-0`
|
||||||
}) {
|
}) {
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@ -24,7 +26,8 @@ export function pageSectionsWithNav({
|
|||||||
<div
|
<div
|
||||||
class=${clsx(
|
class=${clsx(
|
||||||
tw`flex flex-1 flex-col gap-2`,
|
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`,
|
placement === "start" ? tw`lg:max-w-[16.5rem]` : tw`lg:flex-row`,
|
||||||
)}
|
)}
|
||||||
part="tabs"
|
part="tabs"
|
||||||
|
@ -592,7 +592,9 @@ export class WorkflowDetail extends BtrixElement {
|
|||||||
private readonly renderEditor = () => html`
|
private readonly renderEditor = () => html`
|
||||||
<div class="col-span-1">${this.renderBreadcrumbs()}</div>
|
<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>
|
<btrix-detail-page-title .item=${this.workflow}></btrix-detail-page-title>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { localized, msg } from "@lit/localize";
|
import { localized, msg } from "@lit/localize";
|
||||||
|
import clsx from "clsx";
|
||||||
import { mergeDeep } from "immutable";
|
import { mergeDeep } from "immutable";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.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 { ScopeType, type Seed, type WorkflowParams } from "./types";
|
||||||
|
|
||||||
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 { WorkflowScopeType } from "@/types/workflow";
|
||||||
import LiteElement, { html } from "@/utils/LiteElement";
|
import LiteElement, { html } from "@/utils/LiteElement";
|
||||||
|
import { tw } from "@/utils/tailwind";
|
||||||
import {
|
import {
|
||||||
DEFAULT_AUTOCLICK_SELECTOR,
|
DEFAULT_AUTOCLICK_SELECTOR,
|
||||||
DEFAULT_SELECT_LINKS,
|
DEFAULT_SELECT_LINKS,
|
||||||
|
makeUserGuideEvent,
|
||||||
|
type SectionsEnum,
|
||||||
type FormState as WorkflowFormState,
|
type FormState as WorkflowFormState,
|
||||||
} from "@/utils/workflow";
|
} 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:
|
* Usage:
|
||||||
* ```ts
|
* ```ts
|
||||||
@ -55,23 +41,6 @@ export class WorkflowsNew extends LiteElement {
|
|||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
initialWorkflow?: WorkflowParams;
|
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 {
|
private get defaultNewWorkflow(): WorkflowParams {
|
||||||
return {
|
return {
|
||||||
name: "",
|
name: "",
|
||||||
@ -125,21 +94,20 @@ export class WorkflowsNew extends LiteElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="mb-5">${this.renderBreadcrumbs()}</div>
|
<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>
|
<h2 class="mb-6 text-xl font-semibold">${msg("New Crawl Workflow")}</h2>
|
||||||
<sl-button
|
<sl-button
|
||||||
size="small"
|
size="small"
|
||||||
|
class=${clsx(
|
||||||
|
tw`transition-opacity`,
|
||||||
|
this.appState.userGuideOpen && tw`pointer-events-none opacity-0`,
|
||||||
|
)}
|
||||||
|
?disabled=${this.appState.userGuideOpen}
|
||||||
@click=${() => {
|
@click=${() => {
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent<
|
makeUserGuideEvent(window.location.hash.slice(1) as SectionsEnum),
|
||||||
UserGuideEventMap["btrix-user-guide-show"]["detail"]
|
|
||||||
>("btrix-user-guide-show", {
|
|
||||||
detail: {
|
|
||||||
path: `user-guide/workflow-setup/#${this.userGuideHashLink}`,
|
|
||||||
},
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -96,6 +96,15 @@
|
|||||||
|
|
||||||
/* Transition */
|
/* Transition */
|
||||||
--sl-transition-x-fast: 100ms;
|
--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 {
|
body {
|
||||||
@ -446,6 +455,16 @@
|
|||||||
sl-button.button-card::part(label) {
|
sl-button.button-card::part(label) {
|
||||||
@apply flex flex-1 flex-col justify-center gap-2 text-left;
|
@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 */
|
/* Following styles won't work with layers */
|
||||||
|
@ -47,6 +47,8 @@ export function makeAppStateService() {
|
|||||||
// Org details
|
// Org details
|
||||||
org: OrgData | null | undefined = undefined;
|
org: OrgData | null | undefined = undefined;
|
||||||
|
|
||||||
|
userGuideOpen = false;
|
||||||
|
|
||||||
// Since org slug is used to ID an org, use `userOrg`
|
// Since org slug is used to ID an org, use `userOrg`
|
||||||
// to retrieve the basic org info like name and ID
|
// to retrieve the basic org info like name and ID
|
||||||
// before other org details are available
|
// before other org details are available
|
||||||
@ -159,6 +161,11 @@ export function makeAppStateService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@unlock()
|
||||||
|
updateUserGuideOpen(open: boolean) {
|
||||||
|
appState.userGuideOpen = open;
|
||||||
|
}
|
||||||
|
|
||||||
@transaction()
|
@transaction()
|
||||||
@unlock()
|
@unlock()
|
||||||
resetAll() {
|
resetAll() {
|
||||||
|
@ -4,6 +4,7 @@ import { z } from "zod";
|
|||||||
import { getAppSettings } from "./app";
|
import { getAppSettings } from "./app";
|
||||||
|
|
||||||
import type { Tags } from "@/components/ui/tag-input";
|
import type { Tags } from "@/components/ui/tag-input";
|
||||||
|
import type { UserGuideEventMap } from "@/index";
|
||||||
import {
|
import {
|
||||||
Behavior,
|
Behavior,
|
||||||
ScopeType,
|
ScopeType,
|
||||||
@ -37,6 +38,43 @@ export const SECTIONS = [
|
|||||||
export const sectionsEnum = z.enum(SECTIONS);
|
export const sectionsEnum = z.enum(SECTIONS);
|
||||||
export type SectionsEnum = z.infer<typeof sectionsEnum>;
|
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 {
|
export function defaultLabel(value: unknown): string {
|
||||||
if (value === Infinity) {
|
if (value === Infinity) {
|
||||||
return msg("Default: Unlimited");
|
return msg("Default: Unlimited");
|
||||||
|
Loading…
Reference in New Issue
Block a user