Add all guides in a fancy tab menu

This commit is contained in:
Hamcha 2020-06-19 20:26:43 +02:00
parent 85211e22d6
commit 32c69ee4ca
Signed by untrusted user: hamcha
GPG key ID: 41467804B19A3315
11 changed files with 343 additions and 46 deletions

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M12.011,14.392L12.011,3.941C12.011,3.55 12.166,3.174 12.443,2.897C12.72,2.62 13.095,2.465 13.487,2.465L18.654,2.465C19.045,2.465 19.421,2.62 19.698,2.897C19.974,3.174 20.13,3.55 20.13,3.941L20.13,14.392L25.938,24.999C26.458,25.948 26.439,27.101 25.887,28.033C25.335,28.964 24.333,29.535 23.251,29.535L8.89,29.535C7.808,29.535 6.805,28.964 6.254,28.033C5.702,27.101 5.682,25.948 6.202,24.999L12.011,14.392ZM18.13,4.465L14.011,4.465L14.011,14.648C14.011,14.816 13.968,14.981 13.888,15.128C13.888,15.128 10.195,21.871 7.956,25.959C7.776,26.289 7.783,26.69 7.974,27.013C8.166,27.337 8.514,27.535 8.89,27.535L23.251,27.535C23.627,27.535 23.975,27.337 24.166,27.013C24.358,26.69 24.365,26.289 24.184,25.959C21.945,21.871 18.253,15.128 18.253,15.128C18.172,14.981 18.13,14.816 18.13,14.648L18.13,4.465ZM15.452,18.07L19.028,18.07C19.028,18.07 21.911,23.671 23.014,25.813C23.103,25.987 23.096,26.195 22.994,26.363C22.892,26.53 22.71,26.632 22.514,26.632L9.627,26.632C9.431,26.632 9.249,26.53 9.147,26.363C9.045,26.195 9.037,25.987 9.127,25.813C10.229,23.671 13.113,18.07 13.113,18.07L14.328,18.07L11.138,24.285C11.012,24.53 11.109,24.832 11.354,24.958C11.6,25.084 11.901,24.987 12.027,24.741L15.452,18.07Z" style="fill:white;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g transform="matrix(1,0,0,1,-32,0)">
<path d="M59.485,9.925C59.485,6.912 57.039,4.465 54.025,4.465L43.105,4.465C40.091,4.465 37.644,6.912 37.644,9.925L37.644,22.075C37.644,25.088 40.091,27.535 43.105,27.535L54.025,27.535C57.039,27.535 59.485,25.088 59.485,22.075L59.485,9.925Z" style="fill:none;stroke:white;stroke-width:3px;"/>
<g transform="matrix(0.865715,0.145428,-0.145428,0.865715,8.58626,-7.33972)">
<path d="M44.849,14.354C44.849,12.303 46.514,10.638 48.565,10.638C50.616,10.638 52.281,12.303 52.281,14.354C52.281,16.405 50.616,18.07 48.565,18.07C47.395,18.07 49.01,21.846 49.01,21.846" style="fill:none;stroke:white;stroke-width:3.42px;"/>
</g>
<g transform="matrix(1.2157,-0.0104971,0.0104971,1.2157,-11.9419,-4.29553)">
<path d="M49.627,22.899L49.53,22.351" style="fill:none;stroke:white;stroke-width:2.47px;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Binary file not shown.

View file

@ -43,12 +43,7 @@
<link rel="preload" href="style/main.scss" as="style" />
<link rel="preload" href="style/bgus.scss" as="style" />
<link rel="preload" href="src/index.ts" as="script" />
<link
rel="stylesheet"
href="assets/fonts/iosevka/iosevka-aile.css"
media="async"
onload="this.media='all'"
/>
<link rel="stylesheet" href="assets/fonts/iosevka/iosevka-aile.css" />
<link rel="stylesheet" href="style/main.scss" />
<link rel="stylesheet" href="style/bgus.scss" />
<title>/tg/station Handbook</title>
@ -58,6 +53,7 @@
<img src="./assets/images/bg-nanotrasen.svg" />
</div>
<main id="app">
<nav id="section-list"></nav>
<nav id="tab-list"></nav>
<section id="tabs"></section>
</main>

View file

@ -3,6 +3,9 @@ import speen from "~/assets/images/speen.svg";
import { getPageHTML } from "./wiki";
import userscript from "./userscript";
// @ts-expect-error: Parcel image import
import unknown from "~/assets/images/tab-icons/unknown.svg";
function initWaiting(elem: HTMLElement) {
// Add spinner
const spinnerContainer = document.createElement("div");
@ -36,32 +39,117 @@ async function loadPage(page: string, elem: HTMLElement) {
});
}
type TabElements = { tabListItem: HTMLElement; tabContentItem: HTMLElement };
type TabElements = {
tabListItem: HTMLElement;
tabContentItem: HTMLElement;
};
interface Section {
name: string;
element: HTMLElement;
tabs: Record<string, TabElements>;
}
export default class TabManager {
sectionListContainer: HTMLElement;
tabListContainer: HTMLElement;
tabContentContainer: HTMLElement;
tabs: Record<string, TabElements> = {};
sections: Record<string, Section> = {};
constructor(tablist: HTMLElement, tabcontent: HTMLElement) {
constructor(
sectionlist: HTMLElement,
tablist: HTMLElement,
tabcontent: HTMLElement
) {
this.sectionListContainer = sectionlist;
this.tabListContainer = tablist;
this.tabContentContainer = tabcontent;
}
openTab(page: string, setActive: boolean): void {
/**
* Create section and add it to the section list
* @param name Section name
*/
createSection(name: string): void {
// Create section element
const sectionItem = document.createElement("div");
sectionItem.className = "section";
sectionItem.dataset.section = name;
sectionItem.appendChild(document.createTextNode(name));
sectionItem.addEventListener("click", () => {
if (sectionItem.classList.contains("active")) {
return;
}
this.showSection(name);
});
this.sectionListContainer.appendChild(sectionItem);
this.sections[name] = { name, element: sectionItem, tabs: {} };
}
/**
* Show tabs of a specific section
* @param name Section name
*/
showSection(name: string): void {
const active = this.sectionListContainer.querySelector<HTMLElement>(
".active"
);
if (active) {
// De-activate current section
active.classList.remove("active");
// Hide all tabs
this.tabListContainer
.querySelectorAll(`div[data-section=${active.dataset.section}]`)
.forEach((tab) => tab.classList.add("hidden"));
}
// Set section as active
this.sections[name].element.classList.add("active");
// Show all tabs of that section
this.tabListContainer
.querySelectorAll(`div[data-section=${name}]`)
.forEach((tab) => tab.classList.remove("hidden"));
}
/**
* Open tab page and add it to the tab list
* @param section Section to add the tab button to
* @param page Page name
* @param icon Icon to show
* @param setActive Also set the tab as active
*/
openTab(
section: string,
page: string,
options: {
icon?: string;
active?: boolean;
text?: string;
}
): void {
// Create tab list item
const tabListItem = document.createElement("div");
tabListItem.className = "tab";
tabListItem.dataset.section = section;
tabListItem.dataset.tab = page;
tabListItem.addEventListener("click", () => {
if (tabListItem.classList.contains("active")) {
return;
}
this.setActive(page);
this.setActive(section, page);
});
tabListItem.appendChild(document.createTextNode(page.replace(/_/gi, " ")));
const iconElement = document.createElement("img");
iconElement.src = options.icon || unknown;
tabListItem.title = page.replace(/_/gi, " ");
tabListItem.appendChild(iconElement);
const shortTitle =
options.text || page.substr(page.lastIndexOf("_") + 1, 4);
tabListItem.appendChild(document.createTextNode(shortTitle));
this.tabListContainer.appendChild(tabListItem);
// Create tab content container
@ -69,27 +157,45 @@ export default class TabManager {
tabContentItem.className = "page waiting";
tabContentItem.dataset.tab = page;
initWaiting(tabContentItem);
this.tabContentContainer.appendChild(tabContentItem);
// Start loading page for new tab
loadPage(page, tabContentItem);
this.tabContentContainer.appendChild(tabContentItem);
// Create tab entry
this.tabs[page] = { tabListItem, tabContentItem };
this.sections[section].tabs[page] = { tabListItem, tabContentItem };
// Hide tab if section is hidden
if (!this.sections[section].element.classList.contains("active")) {
tabListItem.classList.add("hidden");
}
// If asked for, set it to active
if (setActive) {
this.setActive(page);
if (options.active) {
this.setActive(section, page);
}
}
setActive(page: string): void {
/**
* Set a specific page to be the active/visible one
* @param section Section name
* @param page Page name
*/
setActive(section: string, page: string): void {
// Make sure tab exists (why wouldn't it?!)
if (!(page in this.tabs)) {
if (!(section in this.sections)) {
throw new Error("section not found");
}
if (!(page in this.sections[section].tabs)) {
throw new Error("tab not found");
}
// Deactivate current active tab
this.sectionListContainer
.querySelectorAll(".active")
.forEach((it) => it.classList.remove("active"));
this.tabListContainer
.querySelectorAll(".active")
.forEach((it) => it.classList.remove("active"));
@ -97,8 +203,14 @@ export default class TabManager {
.querySelectorAll(".active")
.forEach((it) => it.classList.remove("active"));
// If section is not shown, show it!
if (!this.sections[section].element.classList.contains("active")) {
this.showSection(section);
}
// Activate new tab
const { tabListItem, tabContentItem } = this.tabs[page];
const { tabListItem, tabContentItem } = this.sections[section].tabs[page];
this.sections[section].element.classList.add("active");
tabListItem.classList.add("active");
tabContentItem.classList.add("active");
}

View file

@ -1,18 +1,25 @@
import TabManager from "./TabManager";
import sections from "./sections";
const sectionListContainer = document.getElementById("section-list");
const tabListContainer = document.getElementById("tab-list");
const tabContentContainer = document.getElementById("tabs");
const manager = new TabManager(tabListContainer, tabContentContainer);
const manager = new TabManager(
sectionListContainer,
tabListContainer,
tabContentContainer
);
const defaultTabs = [
{ page: "Guide_to_chemistry", active: true },
{ page: "Guide_to_medicine", active: false },
];
defaultTabs.forEach((tab) => {
manager.openTab(tab.page, tab.active);
sections.forEach((section) => {
manager.createSection(section.name);
section.tabs.forEach((tab) => {
manager.openTab(section.name, tab.page, { icon: tab.icon, text: tab.text });
});
});
// Set first page as active
manager.setActive("Medical", "Guide_to_chemistry");
if ("serviceWorker" in navigator) {
const x = process.env.SUBPATH ? `${process.env.SUBPATH}/sw.js` : "sw.js";
navigator.serviceWorker

104
src/sections.ts Normal file
View file

@ -0,0 +1,104 @@
// @ts-expect-error: Parcel image import
import chemistry from "~/assets/images/tab-icons/chemistry.svg";
const sections = [
{
name: "Medical",
tabs: [
{ page: "Guide_to_medicine", icon: null },
{ page: "Guide_to_chemistry", icon: chemistry },
{ page: "Guide_to_plumbing", icon: null },
{ page: "Grenade", icon: null },
{ page: "Guide_to_genetics", icon: null },
{ page: "Infections", text: "VIRS", icon: null },
{ page: "Surgery", icon: null },
{ page: "Guide_to_Traumas", icon: null },
{ page: "Guide_to_Wounds", icon: null },
{ page: "Guide_to_Ghetto_Chemistry", text: "ghet", icon: null },
],
},
{
name: "Engineering",
tabs: [
{ page: "Guide_to_construction", icon: null },
{
page: "Guide_to_advanced_construction",
text: "mach",
icon: null,
},
{ page: "Solars", icon: null },
{ page: "Guide_to_the_Supermatter", text: "SUPM", icon: null },
{ page: "Singularity_Engine", text: "SING", icon: null },
{ page: "Tesla_Engine", text: "TESL", icon: null },
{ page: "Gas_turbine", text: "GAS", icon: null },
{ page: "Guide_to_power", icon: null },
{ page: "Guide_to_Atmospherics", icon: null },
{ page: "Guide_to_Telecommunications", icon: null, text: "tcomm" },
],
},
{
name: "Science",
tabs: [
{ page: "Guide_to_Research_and_Development", text: "R&D", icon: null },
{ page: "Guide_to_robotics", icon: null },
{ page: "Guide_to_toxins", icon: null },
{ page: "Guide_to_xenobiology", icon: null },
{ page: "Guide_to_genetics", icon: null },
{ page: "Guide_to_telescience", icon: null },
{ page: "Guide_to_Nanites", icon: null },
],
},
{
name: "Security",
tabs: [
{ page: "Guide_to_security", icon: null },
{ page: "Space_Law", text: "LAW", icon: null },
{ page: "Standard_Operating_Procedure", text: "SOP", icon: null },
{ page: "Guide_to_Trials", icon: null },
],
},
{
name: "Antagonists",
tabs: [
{ page: "Traitor", icon: null },
{ page: "Makeshift_weapons", icon: null },
{ page: "Hacking", icon: null },
{ page: "Guide_to_Combat", icon: null },
{ page: "Syndicate_Items", text: "synd", icon: null },
{ page: "Illicit_Access", icon: null },
{ page: "Revolution", icon: null },
{ page: "Cult_Basics", text: "cult", icon: null },
{ page: "Syndicate_guide", text: "nuke", icon: null },
{ page: "Guide_to_malfunction", icon: null },
{ page: "Xenos", text: "xmor", icon: null },
{ page: "Abductor", icon: null },
{ page: "Families", icon: null },
{ page: "Heretic", icon: null },
],
},
{
name: "Other",
tabs: [
{ page: "Ai_Modules", text: "aimo", icon: null },
{ page: "Silicon_Policy", text: "sipo", icon: null },
{
page: "Guide_to_Awesome_Miscellaneous_Stuff",
text: "misc",
icon: null,
},
{ page: "Creatures", icon: null },
{ page: "Critters", icon: null },
{ page: "Guide_to_races", icon: null },
{ page: "Guide_to_food_and_drinks", text: "food", icon: null },
{ page: "Guide_to_hydroponics", icon: null },
{ page: "Guide_to_plants", icon: null },
{ page: "Songs", icon: null },
{ page: "Supply_crates", icon: null },
{ page: "Auxiliary_Base_Construction", text: "aux", icon: null },
{ page: "Guide_to_wire_art", text: "wire", icon: null },
{ page: "Guide_to_Space_Exploration", icon: null },
],
},
];
export default sections;

View file

@ -3,6 +3,13 @@ import searchBox from "./search";
import { findParent } from "./utils";
export default function userscript(root: HTMLElement, docname: string): void {
// Add header
const header = document.createElement("h1");
header.className = "pageheader";
header.appendChild(document.createTextNode(docname.replace(/_/g, " ")));
root.insertBefore(header, root.firstChild);
// Remove edit links
root.querySelectorAll(".mw-editsection").forEach((editLink) => {
editLink.parentElement.removeChild(editLink);
});
@ -55,8 +62,16 @@ export default function userscript(root: HTMLElement, docname: string): void {
row.appendChild(td);
});
// Fuck #toctitle
const toc = root.querySelector("#toc");
if (toc) {
const tocHeader = toc.querySelector("h2");
toc.parentNode.insertBefore(tocHeader, toc);
toc.removeChild(toc.querySelector("#toctitle"));
}
// Group headers and content so stickies don't overlap
root.querySelectorAll("h3,h2").forEach((h3) => {
root.querySelectorAll("h1,h2,h3").forEach((h3) => {
const parent = h3.parentNode;
const div = document.createElement("div");
parent.insertBefore(div, h3);
@ -79,7 +94,7 @@ export default function userscript(root: HTMLElement, docname: string): void {
if (container) {
container.id = span.id;
span.id += "-span";
container.dataset.name = span.innerText;
container.dataset.name = span.textContent;
}
});
@ -117,7 +132,7 @@ export default function userscript(root: HTMLElement, docname: string): void {
// Enrich "x part" with checkboxes and parts
Array.from(document.querySelectorAll("td"))
.filter((el) => el.innerText.indexOf(" part") >= 0)
.filter((el) => el.textContent.indexOf(" part") >= 0)
.forEach((el) => {
el.innerHTML = el.innerHTML.replace(
/((\d+)\s+(?:parts?|units?))(.*?(?:<\/a>|\n|$))/gi,

View file

@ -8,7 +8,7 @@
}
#bgus_fz_searchbox {
position: fixed;
top: 50px;
top: 80px;
left: 20%;
right: 20%;
background: rgba(10, 10, 10, 0.8);

View file

@ -36,7 +36,6 @@ body {
#app {
height: 100%;
display: grid;
grid-template-rows: 40px 1fr;
}
::-webkit-scrollbar {
@ -60,13 +59,12 @@ body {
}
#tabs {
grid-row: 2;
grid-row: 3;
z-index: 1;
display: grid;
overflow: hidden;
.page {
visibility: hidden;
padding-top: 10pt;
overflow-y: scroll;
grid-row: 1;
grid-column: 1;
@ -97,6 +95,11 @@ body {
}
}
h1.pageheader {
margin-top: 0;
padding: 15pt 10pt;
}
p,
h2,
h3,
@ -107,19 +110,15 @@ body {
a[href] {
color: white;
}
#toctitle,
h1,
h2,
h3 {
position: sticky;
top: -10pt;
top: 0;
background: $nanotrasen;
padding: 10px;
padding: 10pt;
z-index: 999;
}
#toctitle h2 {
margin: 0;
}
.mw-headline {
display: flex;
align-items: center;
@ -127,27 +126,73 @@ body {
}
}
$section-active: darken($nanotrasen, 5%);
$tab-active: lighten($nanotrasen, 10%);
#tab-list {
#section-list {
z-index: 2;
grid-row: 1;
border-bottom: 2px solid $section-active;
display: flex;
border-bottom: 2px solid $tab-active;
.tab {
max-width: 200px;
flex: 1;
.section {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
user-select: none;
font-size: 9pt;
padding: 3pt 7pt;
text-transform: uppercase;
color: lighten($nanotrasen, 60%);
flex: 1;
&.active {
background-color: $section-active;
color: white;
}
&:not(.active) {
cursor: pointer;
&:hover {
background-color: darken($section-active, 10%);
}
}
}
}
#tab-list {
z-index: 2;
grid-row: 2;
display: flex;
background-color: $section-active;
border-bottom: 4px solid $tab-active;
.tab {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
user-select: none;
font-size: 7pt;
padding: 2px 4px;
padding-bottom: 0;
text-transform: uppercase;
img {
height: 80%;
max-height: 24px;
margin: 0;
}
&.active {
background-color: $tab-active;
}
&:not(.active) {
cursor: pointer;
&:hover {
background-color: darken($tab-active, 8%);
}
}
&.hidden {
display: none;
}
}
}