mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
UI: Improve navigation system of in-game documentation viewer (#2499)
* UI: Improve navigation system of in-game documentation viewer * Update based on feedback * Update based on feedback
This commit is contained in:
@@ -31,7 +31,7 @@ export const getPage = (title: string): string => {
|
||||
return resolvePage(title).pageContent;
|
||||
};
|
||||
|
||||
export const DocumentationPopUpEvents = new EventEmitter<[string | undefined]>();
|
||||
export const DocumentationPopUpEvents = new EventEmitter<[string]>();
|
||||
|
||||
export function openDocumentationPopUp(path: string): void {
|
||||
DocumentationPopUpEvents.emit(path);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Modal } from "../../ui/React/Modal";
|
||||
import { defaultNsApiPage, Navigator, openDocExternally } from "../../ui/React/Documentation";
|
||||
import { convertNavigatorHref, Navigator, openDocExternally } from "../../ui/React/Documentation";
|
||||
import { MD } from "../../ui/MD/MD";
|
||||
import { asFilePath, type FilePath, isFilePath, resolveFilePath } from "../../Paths/FilePath";
|
||||
import { asFilePath, type FilePath } from "../../Paths/FilePath";
|
||||
import { DocumentationPopUpEvents } from "../root";
|
||||
|
||||
export function DocumentationPopUp({ hidden }: { hidden: boolean }) {
|
||||
@@ -11,8 +11,8 @@ export function DocumentationPopUp({ hidden }: { hidden: boolean }) {
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
DocumentationPopUpEvents.subscribe((path?: string) => {
|
||||
setPath(path ? asFilePath(path) : undefined);
|
||||
DocumentationPopUpEvents.subscribe((path: string) => {
|
||||
setPath(asFilePath(path));
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@@ -24,39 +24,30 @@ export function DocumentationPopUp({ hidden }: { hidden: boolean }) {
|
||||
modalWrapperRef.current.scrollTo({ top: 0, behavior: "instant" });
|
||||
});
|
||||
|
||||
const navigator = {
|
||||
navigate(href: string, openExternally: boolean) {
|
||||
/**
|
||||
* This function is used for navigating inside the documentation popup.
|
||||
*
|
||||
* Href can be:
|
||||
* - Internal NS docs. The "markdown" folder does not have any subfolders. All files are at the top-level. "Href"
|
||||
* is always "./<filename>". E.g., "./bitburner.ns.md".
|
||||
* - HTTP URL (e.g., ns.printf has a link to https://github.com/alexei/sprintf.js).
|
||||
*/
|
||||
let path;
|
||||
if (href.startsWith("https://") || href.startsWith("http://")) {
|
||||
openExternally = true;
|
||||
path = href;
|
||||
} else {
|
||||
path = resolveFilePath(href, defaultNsApiPage);
|
||||
}
|
||||
if (!path) {
|
||||
console.error(`Bad path ${href} while navigating docs.`);
|
||||
return;
|
||||
}
|
||||
if (openExternally) {
|
||||
openDocExternally(path);
|
||||
return;
|
||||
}
|
||||
if (isFilePath(path)) {
|
||||
setPath(path);
|
||||
}
|
||||
},
|
||||
};
|
||||
if (!path) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const navigator = {
|
||||
/**
|
||||
* This function is used for navigating inside the documentation popup.
|
||||
*/
|
||||
navigate(href: string, openExternally: boolean) {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
const { path: newPath, forceOpenExternally } = convertNavigatorHref(href, path);
|
||||
if (!newPath) {
|
||||
console.error(`Bad path ${href} while navigating docs.`);
|
||||
return;
|
||||
}
|
||||
if (openExternally || forceOpenExternally) {
|
||||
openDocExternally(newPath);
|
||||
return;
|
||||
}
|
||||
setPath(newPath);
|
||||
},
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
open={!hidden}
|
||||
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
windowTopPositionOfPages,
|
||||
useHistory,
|
||||
openDocExternally,
|
||||
prefixOfHttpUrlOfNsDocs,
|
||||
convertNavigatorHref,
|
||||
} from "../../ui/React/Documentation";
|
||||
import { asFilePath, isFilePath, resolveFilePath } from "../../Paths/FilePath";
|
||||
import { asFilePath } from "../../Paths/FilePath";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { Router } from "../../ui/GameRoot";
|
||||
import { Page } from "../../ui/Router";
|
||||
@@ -21,48 +21,20 @@ export function DocumentationRoot({ docPage }: { docPage?: string }): React.Reac
|
||||
const history = useHistory();
|
||||
const [deepLink, setDeepLink] = useState(docPage);
|
||||
const navigator = {
|
||||
/**
|
||||
* This function is used for navigating inside the documentation tab.
|
||||
*/
|
||||
navigate(href: string, openExternally: boolean) {
|
||||
let path;
|
||||
/**
|
||||
* Href can be:
|
||||
* - Internal NS docs: nsDoc/bitburner.ns.md
|
||||
* - Internal non-NS docs: help/getting_started.md
|
||||
* - HTTP URL:
|
||||
* - Point to NS docs. Some non-NS docs pages include links to NS docs. For example: basic/scripts.md has a
|
||||
* link to https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.ns.flags.md. In
|
||||
* these cases, the link always points to a file at https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/
|
||||
* - Point to other places.
|
||||
*/
|
||||
if (href.startsWith("nsDoc/")) {
|
||||
// Internal NS docs
|
||||
path = asFilePath(href);
|
||||
} else if (href.startsWith("https://") || href.startsWith("http://")) {
|
||||
/**
|
||||
* HTTP URL pointing to NS docs.
|
||||
* Convert https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/page.md to nsDoc/page.md
|
||||
*/
|
||||
if (href.startsWith(prefixOfHttpUrlOfNsDocs)) {
|
||||
path = asFilePath(`nsDoc/${href.replace(prefixOfHttpUrlOfNsDocs, "")}`);
|
||||
} else {
|
||||
// HTTP URL pointing to other places.
|
||||
openExternally = true;
|
||||
path = href;
|
||||
}
|
||||
} else {
|
||||
// Internal non-NS docs
|
||||
path = resolveFilePath("./" + href, history.page);
|
||||
}
|
||||
const { path, forceOpenExternally } = convertNavigatorHref(href, history.page);
|
||||
if (!path) {
|
||||
console.error(`Bad path ${href} from ${history.page} while navigating docs.`);
|
||||
return;
|
||||
}
|
||||
if (openExternally) {
|
||||
if (openExternally || forceOpenExternally) {
|
||||
openDocExternally(path);
|
||||
return;
|
||||
}
|
||||
if (isFilePath(path)) {
|
||||
history.push(path);
|
||||
}
|
||||
history.push(path);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { type FilePath, asFilePath } from "../../Paths/FilePath";
|
||||
import { type FilePath, asFilePath, resolveFilePath } from "../../Paths/FilePath";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { resolvePage } from "../../Documentation/root";
|
||||
|
||||
@@ -21,7 +21,7 @@ interface History {
|
||||
home(): void;
|
||||
}
|
||||
|
||||
const defaultPage = asFilePath("index.md");
|
||||
export const defaultPage = asFilePath("index.md");
|
||||
export const defaultNsApiPage = asFilePath("nsDoc/bitburner.ns.md");
|
||||
/**
|
||||
* If we move or rename "bitburner.ns.md", we must update this constant, "defaultNsApiPage", "openDocExternally", and
|
||||
@@ -31,6 +31,8 @@ export const externalUrlOfNsApiPage =
|
||||
"https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.ns.md";
|
||||
export const prefixOfHttpUrlOfNsDocs = "https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/";
|
||||
|
||||
const prefixOfRelativeUrlOfNSDoc = "../../../../markdown/bitburner.";
|
||||
|
||||
const HistoryContext = React.createContext<History>({
|
||||
page: defaultPage,
|
||||
pages: [],
|
||||
@@ -92,7 +94,7 @@ export const HistoryProvider = (props: React.PropsWithChildren<object>): React.R
|
||||
return <Provider value={history}>{props.children}</Provider>;
|
||||
};
|
||||
|
||||
export function openDocExternally(path: string) {
|
||||
export function openDocExternally(path: string): void {
|
||||
const ver = CONSTANTS.isDevBranch ? "dev" : "stable";
|
||||
let url;
|
||||
if (path.startsWith("http://") || path.startsWith("https://")) {
|
||||
@@ -111,3 +113,55 @@ export function openDocExternally(path: string) {
|
||||
}
|
||||
window.open(url, "_newtab");
|
||||
}
|
||||
|
||||
/**
|
||||
* Href can be:
|
||||
* - Relative URL from non-NS docs pointing to markdown folder: Open "../../../../markdown/bitburner.ns.md" from "index.md"
|
||||
* - Relative URL from NS docs to other NS docs (e.g., click the links in NS docs viewer): Open "./bitburner.ns.cloud.md" from "nsDoc/bitburner.ns.md"
|
||||
* - Internal NS docs (e.g., choose a dropdown option in DocumentationAutocomplete): nsDoc/bitburner.ns.md
|
||||
* - Internal non-NS docs: help/getting_started.md
|
||||
* - HTTP URL:
|
||||
* - Point to NS docs. Some non-NS docs pages include links to NS docs. For example: basic/scripts.md has a
|
||||
* link to https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.ns.flags.md. In
|
||||
* these cases, the link always points to a file at https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/
|
||||
* - Point to other places.
|
||||
*/
|
||||
export function convertNavigatorHref(
|
||||
href: string,
|
||||
currentPage: FilePath,
|
||||
): { path: FilePath | null; forceOpenExternally: false } | { path: string; forceOpenExternally: true } {
|
||||
let path;
|
||||
if (href.includes(prefixOfRelativeUrlOfNSDoc)) {
|
||||
// Relative URL from non-NS docs pointing to markdown folder
|
||||
path = asFilePath(
|
||||
// Convert "../../../../markdown/bitburner.foo.md" and "deeper" URLs (i.e., having more "../") to "nsDoc/bitburner.foo.md"
|
||||
href.replace(
|
||||
href.substring(0, href.indexOf(prefixOfRelativeUrlOfNSDoc) + prefixOfRelativeUrlOfNSDoc.length),
|
||||
"nsDoc/bitburner.",
|
||||
),
|
||||
);
|
||||
} else if (/^\.\/bitburner\.[^/]*\.md$/.test(href)) {
|
||||
// Relative URL from NS docs to other NS docs. The URL is always ./bitburner.foo.md
|
||||
// - Start with "./bitburner."
|
||||
// - End with ".md"
|
||||
// - Never have "/" between "./bitburner." and ".md"
|
||||
path = resolveFilePath(href, defaultNsApiPage);
|
||||
} else if (href.startsWith("nsDoc/")) {
|
||||
// Internal NS docs
|
||||
path = asFilePath(href);
|
||||
} else if (href.startsWith("https://") || href.startsWith("http://")) {
|
||||
// TODO: Remove this case after converting all these links to relative links.
|
||||
// HTTP URL pointing to NS docs.
|
||||
// Convert https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.foo.md to nsDoc/bitburner.foo.md
|
||||
if (href.startsWith(prefixOfHttpUrlOfNsDocs)) {
|
||||
path = asFilePath(`nsDoc/${href.replace(prefixOfHttpUrlOfNsDocs, "")}`);
|
||||
} else {
|
||||
// HTTP URL pointing to other places.
|
||||
return { path: href, forceOpenExternally: true };
|
||||
}
|
||||
} else {
|
||||
// Internal non-NS docs
|
||||
path = resolveFilePath("./" + href, currentPage);
|
||||
}
|
||||
return { path, forceOpenExternally: false };
|
||||
}
|
||||
|
||||
81
test/jest/ui/DocumentationNavigator.test.ts
Normal file
81
test/jest/ui/DocumentationNavigator.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { asFilePath } from "../../../src/Paths/FilePath";
|
||||
import { convertNavigatorHref, defaultNsApiPage, defaultPage } from "../../../src/ui/React/Documentation";
|
||||
|
||||
describe("convertNavigatorHref", () => {
|
||||
describe("Valid href", () => {
|
||||
test("Relative URL from non-NS docs pointing to markdown folder 1", () => {
|
||||
const { path, forceOpenExternally } = convertNavigatorHref("../../../../markdown/bitburner.ns.md", defaultPage);
|
||||
expect(path).toStrictEqual("nsDoc/bitburner.ns.md");
|
||||
expect(forceOpenExternally).toStrictEqual(false);
|
||||
});
|
||||
test("Relative URL from non-NS docs pointing to markdown folder 2", () => {
|
||||
const { path, forceOpenExternally } = convertNavigatorHref(
|
||||
"../../../../../markdown/bitburner.ns.flags.md",
|
||||
asFilePath("basic/scripts.md"),
|
||||
);
|
||||
expect(path).toStrictEqual("nsDoc/bitburner.ns.flags.md");
|
||||
expect(forceOpenExternally).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test("Relative URL from NS docs to other NS docs", () => {
|
||||
const { path, forceOpenExternally } = convertNavigatorHref("./bitburner.ns.cloud.md", defaultNsApiPage);
|
||||
expect(path).toStrictEqual("nsDoc/bitburner.ns.cloud.md");
|
||||
expect(forceOpenExternally).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test("Internal NS docs 1", () => {
|
||||
const { path, forceOpenExternally } = convertNavigatorHref("help/getting_started.md", defaultPage);
|
||||
expect(path).toStrictEqual("help/getting_started.md");
|
||||
expect(forceOpenExternally).toStrictEqual(false);
|
||||
});
|
||||
test("Internal NS docs 2", () => {
|
||||
const { path, forceOpenExternally } = convertNavigatorHref(
|
||||
"../basic/scripts.md",
|
||||
asFilePath("help/getting_started.md"),
|
||||
);
|
||||
expect(path).toStrictEqual("basic/scripts.md");
|
||||
expect(forceOpenExternally).toStrictEqual(false);
|
||||
});
|
||||
test("Internal NS docs 3", () => {
|
||||
const { path, forceOpenExternally } = convertNavigatorHref("./faq.md", asFilePath("help/getting_started.md"));
|
||||
expect(path).toStrictEqual("help/faq.md");
|
||||
expect(forceOpenExternally).toStrictEqual(false);
|
||||
});
|
||||
test("Internal NS docs 4", () => {
|
||||
const { path, forceOpenExternally } = convertNavigatorHref("faq.md", asFilePath("help/getting_started.md"));
|
||||
expect(path).toStrictEqual("help/faq.md");
|
||||
expect(forceOpenExternally).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test("HTTP/HTTPS URL - Point to NS docs", () => {
|
||||
const { path, forceOpenExternally } = convertNavigatorHref(
|
||||
"https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.ns.md",
|
||||
defaultPage,
|
||||
);
|
||||
expect(path).toStrictEqual("nsDoc/bitburner.ns.md");
|
||||
expect(forceOpenExternally).toStrictEqual(false);
|
||||
});
|
||||
test("HTTP/HTTPS URL - Point to other places", () => {
|
||||
const { path, forceOpenExternally } = convertNavigatorHref("https://bitburner-official.github.io", defaultPage);
|
||||
expect(path).toStrictEqual("https://bitburner-official.github.io");
|
||||
expect(forceOpenExternally).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Invalid href", () => {
|
||||
test("Relative URL from non-NS docs not pointing to markdown folder", () => {
|
||||
const { path, forceOpenExternally } = convertNavigatorHref("../../../markdown/bitburner.ns.md", defaultPage);
|
||||
expect(path).toStrictEqual(null);
|
||||
expect(forceOpenExternally).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test("Relative URL from NS docs to other NS docs", () => {
|
||||
// The path is always "./bitburner.foo.md". It never starts with "../".
|
||||
const { path, forceOpenExternally } = convertNavigatorHref("../bitburner.ns.cloud.md", defaultNsApiPage);
|
||||
// This is an invalid path. Technically, it's a valid file path, but there is no doc file with this path. The path
|
||||
// of NS docs is always prefixed with "nsDoc/".
|
||||
expect(path).toStrictEqual("bitburner.ns.cloud.md");
|
||||
expect(forceOpenExternally).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user