Consolidate all the tour data into a single TourData type.
This commit is contained in:
parent
896895c69a
commit
b130ec37a7
|
@ -3,8 +3,12 @@ import {
|
|||
addCompletedTour,
|
||||
createIntroductionUnderstood,
|
||||
} from "../storage/common.js";
|
||||
import {introductionSteps} from "../tours/introduction.js";
|
||||
import {TourId, showTourError, tourIdsAndSteps} from "../tours/exports.js";
|
||||
import {
|
||||
allTours,
|
||||
introductionTour,
|
||||
showTourError,
|
||||
type TourData,
|
||||
} from "../tours/exports.js";
|
||||
|
||||
/** The main entry point for the content script. */
|
||||
async function main(): Promise<void> {
|
||||
|
@ -26,9 +30,7 @@ async function main(): Promise<void> {
|
|||
// If a different tour is selected but the introduction hasn't happened yet,
|
||||
// then the main function will be rerun once this tour finishes.
|
||||
startTour(
|
||||
TourId.Introduction,
|
||||
introductionSteps,
|
||||
[],
|
||||
introductionTour,
|
||||
startsWithPrefix && anchorTourId !== "introduction",
|
||||
);
|
||||
return;
|
||||
|
@ -43,24 +45,24 @@ async function main(): Promise<void> {
|
|||
|
||||
// Then run through all of the tours we have and start the first match for the
|
||||
// ID.
|
||||
for (const [id, steps, eventHandlers, requirements] of tourIdsAndSteps) {
|
||||
if (anchorTourId === id) {
|
||||
if (requirements.mustBeLoggedIn && !userIsLoggedIn) {
|
||||
for (const tour of allTours) {
|
||||
if (anchorTourId === tour.id) {
|
||||
if (tour.requirements.mustBeLoggedIn && !userIsLoggedIn) {
|
||||
showTourError(
|
||||
`The ${id} tour can only be shown with a logged in account.`,
|
||||
`The ${tour.id} tour can only be shown with a logged in account.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (requirements.path !== window.location.pathname) {
|
||||
if (tour.requirements.path !== window.location.pathname) {
|
||||
// This tour's path requirement does not match.
|
||||
showTourError(
|
||||
`The ${id} tour can only be start on the ${requirements.path} page.`,
|
||||
`The ${tour.id} tour can only be start on the ${tour.requirements.path} page.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
startTour(id, steps, eventHandlers, false);
|
||||
startTour(tour, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -76,12 +78,7 @@ async function main(): Promise<void> {
|
|||
* @param runMainAgainAfterComplete Should the `main` function be run after this
|
||||
* tour is completed?
|
||||
*/
|
||||
function startTour(
|
||||
tourId: TourId,
|
||||
steps: TourStepOptions[],
|
||||
eventHandlers: TourStepEventHandler[],
|
||||
runMainAgainAfterComplete: boolean,
|
||||
): void {
|
||||
function startTour(data: TourData, runMainAgainAfterComplete: boolean): void {
|
||||
const defaultButtons: Shepherd.Step.StepOptionsButton[] = [
|
||||
{
|
||||
classes: "btn",
|
||||
|
@ -124,7 +121,7 @@ function startTour(
|
|||
}
|
||||
|
||||
// Mark the tour as completed.
|
||||
await addCompletedTour(tourId);
|
||||
await addCompletedTour(data.id);
|
||||
|
||||
if (runMainAgainAfterComplete) {
|
||||
await main();
|
||||
|
@ -133,19 +130,24 @@ function startTour(
|
|||
|
||||
// For every step we have, add it to the tour and subsequently add all the
|
||||
// event handlers to that step.
|
||||
for (const [stepNumber, stepOptions] of steps.entries()) {
|
||||
for (const [stepNumber, stepOptions] of data.steps.entries()) {
|
||||
// If the final step doesn't have buttons defined, set the "Continue" button
|
||||
// text to "Finish".
|
||||
if (stepOptions.buttons === undefined && stepNumber + 1 === steps.length) {
|
||||
if (
|
||||
stepOptions.buttons === undefined &&
|
||||
stepNumber + 1 === data.steps.length
|
||||
) {
|
||||
stepOptions.buttons = [...defaultButtons];
|
||||
stepOptions.buttons[0].text = "Finish";
|
||||
}
|
||||
|
||||
const step = tour.addStep(stepOptions);
|
||||
|
||||
for (const [targetStepId, [eventName, eventHandler]] of eventHandlers) {
|
||||
if (targetStepId === step.id) {
|
||||
step.on(eventName, eventHandler);
|
||||
for (const {stepId, eventHandlers} of data.eventHandlers) {
|
||||
if (stepId === step.id) {
|
||||
for (const {event, handler} of eventHandlers) {
|
||||
step.on(event, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,74 +1,22 @@
|
|||
import {Component, type JSX} from "preact";
|
||||
import {TourId} from "../../tours/exports.js";
|
||||
import {Component} from "preact";
|
||||
import {type TourData} from "../../tours/exports.js";
|
||||
|
||||
type Props = {
|
||||
hasBeenCompleted: boolean;
|
||||
name: string;
|
||||
tourId: TourId;
|
||||
tour: TourData;
|
||||
};
|
||||
|
||||
function tourDescription(tourId: Props["tourId"]): JSX.Element {
|
||||
if (tourId === TourId.Introduction) {
|
||||
return (
|
||||
<p class="tour-description">
|
||||
A short introduction to Tildes Shepherd and how the tours work. Normally
|
||||
this is automatically shown when you first installed the extension.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
if (tourId === TourId.InterfaceHomepage) {
|
||||
return (
|
||||
<p class="tour-description">
|
||||
Let's take a look at the home page and all we can do there.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
if (tourId === TourId.InterfaceAccountSettings) {
|
||||
return (
|
||||
<p class="tour-description">
|
||||
View your account settings and all that you can customize.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<p class="tour-description">
|
||||
Tour ID "{tourId}" does not have a description, this should probably be
|
||||
fixed!
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
function tourLink(tourId: Props["tourId"]): string {
|
||||
const anchor = `#tildes-shepherd-tour=${tourId}`;
|
||||
function tourLink(tour: TourData): string {
|
||||
const anchor = `#tildes-shepherd-tour=${tour.id}`;
|
||||
const baseUrl = "https://tildes.net";
|
||||
let path = "";
|
||||
|
||||
switch (tourId) {
|
||||
case TourId.InterfaceHomepage:
|
||||
case TourId.Introduction: {
|
||||
path = "/";
|
||||
break;
|
||||
}
|
||||
|
||||
case TourId.InterfaceAccountSettings: {
|
||||
path = "/settings";
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Unswitched tour ID: ${tourId as string}`);
|
||||
}
|
||||
}
|
||||
const path = tour.requirements.path;
|
||||
|
||||
return `${baseUrl}${path}${anchor}`;
|
||||
}
|
||||
|
||||
export class Tour extends Component<Props> {
|
||||
render() {
|
||||
const {hasBeenCompleted, name, tourId} = this.props;
|
||||
const {hasBeenCompleted, tour} = this.props;
|
||||
const classes = ["tour", hasBeenCompleted ? "completed" : ""].join(" ");
|
||||
const completed = hasBeenCompleted ? (
|
||||
<p class="tour-completed" title="You've completed this tour before!">
|
||||
|
@ -78,11 +26,11 @@ export class Tour extends Component<Props> {
|
|||
|
||||
return (
|
||||
<div class={classes.trim()}>
|
||||
<h3>{name}</h3>
|
||||
<h3>{tour.title}</h3>
|
||||
{completed}
|
||||
{tourDescription(tourId)}
|
||||
{tour.description}
|
||||
<p class="tour-link">
|
||||
<a href={tourLink(tourId)}>Take this tour</a>
|
||||
<a href={tourLink(tour)}>Take this tour</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Component, type JSX} from "preact";
|
||||
import {createToursCompleted} from "../../storage/common.js";
|
||||
import {TourId} from "../../tours/exports.js";
|
||||
import {allTours} from "../../tours/exports.js";
|
||||
import {Tour} from "./tour.js";
|
||||
|
||||
type Props = Record<string, unknown>;
|
||||
|
@ -26,28 +26,14 @@ export class Tours extends Component<Props, State> {
|
|||
render(): JSX.Element {
|
||||
const {toursCompleted} = this.state;
|
||||
|
||||
const createTour = (tourId: TourId, name: string): Tour["props"] => {
|
||||
return {
|
||||
hasBeenCompleted: toursCompleted.has(tourId),
|
||||
name,
|
||||
tourId,
|
||||
};
|
||||
};
|
||||
|
||||
const tourProps: Array<Tour["props"]> = [
|
||||
createTour(TourId.Introduction, "Introduction"),
|
||||
createTour(TourId.InterfaceHomepage, "The Homepage"),
|
||||
createTour(TourId.InterfaceAccountSettings, "Your Account Settings"),
|
||||
];
|
||||
const tours = allTours.map((tour) => (
|
||||
<Tour hasBeenCompleted={toursCompleted.has(tour.id)} tour={tour} />
|
||||
));
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h2>Tours</h2>
|
||||
<div class="tours">
|
||||
{tourProps.map((props) => (
|
||||
<Tour {...props} />
|
||||
))}
|
||||
</div>
|
||||
<div class="tours">{tours}</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,54 +1,13 @@
|
|||
import {
|
||||
accountSettingsEventHandlers,
|
||||
accountSettingsSteps,
|
||||
homepageEventHandlers,
|
||||
homepageSteps,
|
||||
} from "./interface/exports.js";
|
||||
import {introductionSteps} from "./introduction.js";
|
||||
import {accountSettingsTour, homepageTour} from "./interface/exports.js";
|
||||
import {introductionTour} from "./introduction.js";
|
||||
import {type TourData} from "./types.js";
|
||||
|
||||
export * from "./introduction.js";
|
||||
export * from "./shared/exports.js";
|
||||
export * from "./types.js";
|
||||
|
||||
export enum TourId {
|
||||
InterfaceAccountSettings = "interface-account-settings",
|
||||
InterfaceHomepage = "interface-homepage",
|
||||
Introduction = "introduction",
|
||||
}
|
||||
|
||||
export type TourRequirement = {
|
||||
mustBeLoggedIn: boolean;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export type TourIdsAndSteps = Array<
|
||||
[TourId, TourStepOptions[], TourStepEventHandler[], TourRequirement]
|
||||
>;
|
||||
|
||||
export const tourIdsAndSteps: TourIdsAndSteps = [
|
||||
[
|
||||
TourId.Introduction,
|
||||
introductionSteps,
|
||||
[],
|
||||
{
|
||||
mustBeLoggedIn: false,
|
||||
path: "/",
|
||||
},
|
||||
],
|
||||
[
|
||||
TourId.InterfaceAccountSettings,
|
||||
accountSettingsSteps,
|
||||
accountSettingsEventHandlers,
|
||||
{
|
||||
mustBeLoggedIn: true,
|
||||
path: "/settings",
|
||||
},
|
||||
],
|
||||
[
|
||||
TourId.InterfaceHomepage,
|
||||
homepageSteps,
|
||||
homepageEventHandlers,
|
||||
{
|
||||
mustBeLoggedIn: false,
|
||||
path: "/",
|
||||
},
|
||||
],
|
||||
export const allTours: TourData[] = [
|
||||
introductionTour,
|
||||
accountSettingsTour,
|
||||
homepageTour,
|
||||
];
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type Shepherd from "shepherd.js";
|
||||
import {type TourData, TourId} from "../types.js";
|
||||
import {renderInContainer} from "../utilities.js";
|
||||
|
||||
const step01 = renderInContainer(
|
||||
|
@ -7,11 +7,26 @@ const step01 = renderInContainer(
|
|||
</>,
|
||||
);
|
||||
|
||||
export const steps: Shepherd.Step.StepOptions[] = [
|
||||
const steps: TourData["steps"] = [
|
||||
{
|
||||
id: "account-settings-01",
|
||||
text: step01,
|
||||
},
|
||||
];
|
||||
|
||||
export const eventHandlers: TourStepEventHandler[] = [];
|
||||
const eventHandlers: TourData["eventHandlers"] = [];
|
||||
|
||||
const requirements: TourData["requirements"] = {
|
||||
mustBeLoggedIn: true,
|
||||
path: "/settings",
|
||||
};
|
||||
|
||||
export const accountSettingsTour: TourData = {
|
||||
id: TourId.InterfaceAccountSettings,
|
||||
title: "Your Account Settings",
|
||||
description: "View your account settings and all that you can customize.",
|
||||
displayInOptionsPage: true,
|
||||
eventHandlers,
|
||||
requirements,
|
||||
steps,
|
||||
};
|
||||
|
|
|
@ -1,8 +1,2 @@
|
|||
export {
|
||||
eventHandlers as accountSettingsEventHandlers,
|
||||
steps as accountSettingsSteps,
|
||||
} from "./account-settings.js";
|
||||
export {
|
||||
eventHandlers as homepageEventHandlers,
|
||||
steps as homepageSteps,
|
||||
} from "./homepage.js";
|
||||
export * from "./account-settings.js";
|
||||
export * from "./homepage.js";
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import type Shepherd from "shepherd.js";
|
||||
import {LoggedOutWarning} from "../shared/logged-out-warning.js";
|
||||
import {type TourData, TourId} from "../types.js";
|
||||
import {
|
||||
addDatasetCounter,
|
||||
encapsulateElements,
|
||||
removeAllDatasetCounters,
|
||||
renderInContainer,
|
||||
} from "../utilities.js";
|
||||
import {LoggedOutWarning} from "../shared/logged-out-warning.js";
|
||||
|
||||
const step01 = renderInContainer(
|
||||
<>
|
||||
|
@ -345,7 +345,7 @@ const step10 = renderInContainer(
|
|||
</>,
|
||||
);
|
||||
|
||||
export const steps: Shepherd.Step.StepOptions[] = [
|
||||
const steps: TourData["steps"] = [
|
||||
{
|
||||
id: "homepage-01",
|
||||
text: step01,
|
||||
|
@ -433,79 +433,101 @@ export const steps: Shepherd.Step.StepOptions[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export const eventHandlers: TourStepEventHandler[] = [
|
||||
[
|
||||
"homepage-04",
|
||||
[
|
||||
"show",
|
||||
() => {
|
||||
const topic = ".topic-listing > li:first-child";
|
||||
const counters = [
|
||||
".topic-title",
|
||||
".topic-metadata",
|
||||
".topic-info-comments",
|
||||
".topic-info-source",
|
||||
"time",
|
||||
".topic-voting",
|
||||
".topic-actions",
|
||||
];
|
||||
const eventHandlers: TourData["eventHandlers"] = [
|
||||
{
|
||||
stepId: "homepage-04",
|
||||
eventHandlers: [
|
||||
{
|
||||
event: "show",
|
||||
handler() {
|
||||
const topic = ".topic-listing > li:first-child";
|
||||
const counters = [
|
||||
".topic-title",
|
||||
".topic-metadata",
|
||||
".topic-info-comments",
|
||||
".topic-info-source",
|
||||
"time",
|
||||
".topic-voting",
|
||||
".topic-actions",
|
||||
];
|
||||
|
||||
for (const [count, selector] of counters.entries()) {
|
||||
addDatasetCounter(`${topic} ${selector}`, count + 1);
|
||||
}
|
||||
for (const [count, selector] of counters.entries()) {
|
||||
addDatasetCounter(`${topic} ${selector}`, count + 1);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
"homepage-05",
|
||||
[
|
||||
"destroy",
|
||||
() => {
|
||||
removeAllDatasetCounters();
|
||||
},
|
||||
{
|
||||
stepId: "homepage-05",
|
||||
eventHandlers: [
|
||||
{
|
||||
event: "destroy",
|
||||
handler() {
|
||||
removeAllDatasetCounters();
|
||||
},
|
||||
},
|
||||
{
|
||||
event: "show",
|
||||
handler() {
|
||||
encapsulateElements(
|
||||
"homepage-06",
|
||||
"#sidebar .sidebar-controls",
|
||||
"afterend",
|
||||
["#sidebar .form-search", "#sidebar h2", "#sidebar p"],
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
"homepage-05",
|
||||
[
|
||||
"show",
|
||||
() => {
|
||||
encapsulateElements(
|
||||
"homepage-06",
|
||||
"#sidebar .sidebar-controls",
|
||||
"afterend",
|
||||
["#sidebar .form-search", "#sidebar h2", "#sidebar p"],
|
||||
);
|
||||
},
|
||||
{
|
||||
stepId: "homepage-06",
|
||||
eventHandlers: [
|
||||
{
|
||||
event: "show",
|
||||
handler() {
|
||||
encapsulateElements(
|
||||
"homepage-07",
|
||||
"#sidebar .divider",
|
||||
"beforebegin",
|
||||
["#sidebar .nav", '#sidebar [href="/groups"'],
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
"homepage-06",
|
||||
[
|
||||
"show",
|
||||
() => {
|
||||
encapsulateElements("homepage-07", "#sidebar .divider", "beforebegin", [
|
||||
"#sidebar .nav",
|
||||
'#sidebar [href="/groups"',
|
||||
]);
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
"homepage-08",
|
||||
[
|
||||
"show",
|
||||
() => {
|
||||
const filteredTags =
|
||||
document.querySelector<HTMLDetailsElement>("#sidebar details") ??
|
||||
undefined;
|
||||
if (filteredTags === undefined) {
|
||||
console.warn("Element is unexpectedly undefined");
|
||||
return;
|
||||
}
|
||||
},
|
||||
{
|
||||
stepId: "homepage-08",
|
||||
eventHandlers: [
|
||||
{
|
||||
event: "show",
|
||||
handler() {
|
||||
const filteredTags =
|
||||
document.querySelector<HTMLDetailsElement>("#sidebar details") ??
|
||||
undefined;
|
||||
if (filteredTags === undefined) {
|
||||
console.warn("Element is unexpectedly undefined");
|
||||
return;
|
||||
}
|
||||
|
||||
filteredTags.open = true;
|
||||
filteredTags.open = true;
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const requirements: TourData["requirements"] = {
|
||||
mustBeLoggedIn: false,
|
||||
path: "/",
|
||||
};
|
||||
|
||||
export const homepageTour: TourData = {
|
||||
id: TourId.InterfaceHomepage,
|
||||
title: "The Tildes Homepage",
|
||||
description: "Let's take a look at the home page and all we can do there.",
|
||||
displayInOptionsPage: true,
|
||||
eventHandlers,
|
||||
requirements,
|
||||
steps,
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type Shepherd from "shepherd.js";
|
||||
import {createIntroductionUnderstood} from "../storage/common.js";
|
||||
import {TourId, type TourData} from "./types.js";
|
||||
import {openOptionsPageFromBackground, renderInContainer} from "./utilities.js";
|
||||
|
||||
const step01 = renderInContainer(
|
||||
|
@ -82,7 +82,7 @@ const step03 = renderInContainer(
|
|||
</>,
|
||||
);
|
||||
|
||||
export const introductionSteps: Shepherd.Step.StepOptions[] = [
|
||||
const steps: TourData["steps"] = [
|
||||
{
|
||||
canClickTarget: false,
|
||||
id: "introduction-01",
|
||||
|
@ -122,3 +122,21 @@ export const introductionSteps: Shepherd.Step.StepOptions[] = [
|
|||
text: step03,
|
||||
},
|
||||
];
|
||||
|
||||
const eventHandlers: TourData["eventHandlers"] = [];
|
||||
|
||||
const requirements: TourData["requirements"] = {
|
||||
mustBeLoggedIn: false,
|
||||
path: "/",
|
||||
};
|
||||
|
||||
export const introductionTour: TourData = {
|
||||
id: TourId.Introduction,
|
||||
title: "Tildes Shepherd Introduction",
|
||||
description:
|
||||
"A short introduction to Tildes Shepherd and how the tours work.",
|
||||
displayInOptionsPage: true,
|
||||
eventHandlers,
|
||||
requirements,
|
||||
steps,
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import Shepherd from "shepherd.js";
|
||||
import {type TourId} from "../exports.js";
|
||||
import {renderInContainer} from "../utilities.js";
|
||||
|
||||
export function showTourError(text: string) {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import type Shepherd from "shepherd.js";
|
||||
|
||||
export enum TourId {
|
||||
InterfaceAccountSettings = "interface-account-settings",
|
||||
InterfaceHomepage = "interface-homepage",
|
||||
Introduction = "introduction",
|
||||
}
|
||||
|
||||
export type TourRequirement = {
|
||||
mustBeLoggedIn: boolean;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export type TourStepEvent = {
|
||||
event: "show" | "destroy";
|
||||
handler: Parameters<Shepherd.Step["on"]>[1];
|
||||
};
|
||||
|
||||
export type TourData = {
|
||||
description: string;
|
||||
displayInOptionsPage: boolean;
|
||||
eventHandlers: Array<{
|
||||
eventHandlers: TourStepEvent[];
|
||||
stepId: string;
|
||||
}>;
|
||||
id: TourId;
|
||||
requirements: TourRequirement;
|
||||
steps: Shepherd.Step.StepOptions[];
|
||||
title: string;
|
||||
};
|
|
@ -1,12 +1,7 @@
|
|||
import type Shepherd from "shepherd.js";
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
const $browser: "chromium" | "firefox";
|
||||
const $dev: boolean;
|
||||
const $test: boolean;
|
||||
|
||||
type TourStepEvent = "show" | "destroy";
|
||||
type TourStepEventFunction = Parameters<Shepherd.Step["on"]>[1];
|
||||
type TourStepEventHandler = [string, [TourStepEvent, TourStepEventFunction]];
|
||||
type TourStepOptions = Shepherd.Step.StepOptions;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue