Frameworkless achieved!

This commit is contained in:
Hamcha 2020-06-17 11:43:21 +02:00
parent e3dbfb4e70
commit fb3d578bc4
Signed by: hamcha
GPG key ID: 41467804B19A3315
11 changed files with 132 additions and 219 deletions

View file

@ -12,7 +12,10 @@
<div class="bgimage">
<img src="./assets/images/bg-nanotrasen.svg" />
</div>
<main id="app"></main>
<script src="lib/index.tsx"></script>
<main id="app">
<nav id="tab-list"></nav>
<section id="tabs"></section>
</main>
<script src="lib/index.ts"></script>
</body>
</html>

View file

@ -1,26 +0,0 @@
import * as React from "react";
import WikiPage from "./components/WikiPage";
import TabList, { TabListItem } from "./components/TabList";
import { useState } from "react";
export default function App() {
const [tabs, setTabs] = useState<TabListItem[]>([
{ page: "Guide_to_chemistry" },
{ page: "Guide_to_medicine" },
]);
const [activeTab, setActiveTab] = useState(0);
return (
<React.Fragment>
<TabList
tabs={tabs}
active={activeTab}
tabClicked={(_tab, i) => setActiveTab(i)}
/>
<section id="tabs">
{tabs.map((tab, i) => (
<WikiPage key={tab.page} page={tab.page} visible={activeTab == i} />
))}
</section>
</React.Fragment>
);
}

96
lib/TabManager.ts Normal file
View file

@ -0,0 +1,96 @@
import speen from "~/assets/images/speen.svg";
import { getPageHTML } from "./wiki";
import userscript from "./userscript";
function initWaiting(elem: HTMLElement) {
// Add spinner
const spinnerContainer = document.createElement("div");
spinnerContainer.className = "speen";
const spinnerImg = document.createElement("img");
spinnerImg.src = speen;
spinnerContainer.appendChild(spinnerImg);
const spinnerText = document.createElement("p");
spinnerText.appendChild(
document.createTextNode("You start skimming through the manual...")
);
spinnerContainer.appendChild(spinnerText);
elem.appendChild(spinnerContainer);
}
async function loadPage(page: string, elem: HTMLElement) {
console.log(page + ": fetching");
let html = await getPageHTML(page);
// Convert relative links to absolute
html = html.replace(/"\/wiki/gi, '"//tgstation13.org/wiki');
elem.innerHTML = html;
console.log(page + ": processing");
userscript(elem, page);
console.log(page + ": userscript applied");
elem.classList.remove("waiting");
}
type TabElements = { tabListItem: HTMLElement; tabContentItem: HTMLElement };
export default class TabManager {
tabListContainer: HTMLElement;
tabContentContainer: HTMLElement;
tabs: Record<string, TabElements> = {};
constructor(tablist: HTMLElement, tabcontent: HTMLElement) {
this.tabListContainer = tablist;
this.tabContentContainer = tabcontent;
}
openTab(page: string, setActive: boolean) {
// Create tab list item
const tabListItem = document.createElement("div");
tabListItem.className = "tab";
tabListItem.dataset.tab = page;
tabListItem.addEventListener("click", () => {
if (tabListItem.classList.contains("active")) {
return;
}
this.setActive(page);
});
tabListItem.appendChild(document.createTextNode(page.replace(/_/gi, " ")));
this.tabListContainer.appendChild(tabListItem);
// Create tab content container
const tabContentItem = document.createElement("div");
tabContentItem.className = "page waiting";
tabContentItem.dataset.tab = page;
initWaiting(tabContentItem);
this.tabContentContainer.appendChild(tabContentItem);
// Start loading page for new tab
loadPage(page, tabContentItem);
// Create tab entry
this.tabs[page] = { tabListItem, tabContentItem };
// If asked for, set it to active
if (setActive) {
this.setActive(page);
}
}
setActive(page: string) {
// Make sure tab exists (why wouldn't it?!)
if (!(page in this.tabs)) {
throw new Error("tab not found");
}
// Deactivate current active tab
this.tabListContainer
.querySelectorAll(".active")
.forEach((it) => it.classList.remove("active"));
this.tabContentContainer
.querySelectorAll(".active")
.forEach((it) => it.classList.remove("active"));
// Activate new tab
const { tabListItem, tabContentItem } = this.tabs[page];
tabListItem.classList.add("active");
tabContentItem.classList.add("active");
}
}

View file

@ -1,38 +0,0 @@
import * as React from "react";
export interface TabListItem {
page: string;
}
export interface TabListProps {
tabs: TabListItem[];
active: number;
tabClicked: (TabListItem, number) => void;
}
function TabItem({ name, active, onClick }) {
const clickHandler = active ? null : onClick;
return (
<div
className={active ? "tab active" : "tab clickable"}
onClick={clickHandler}
>
{name.replace(/_/gi, " ")}
</div>
);
}
export default function TabList({ tabs, active, tabClicked }: TabListProps) {
return (
<nav className="tab-list">
{tabs.map((tab, i) => (
<TabItem
key={tab.page}
name={tab.page}
active={i == active}
onClick={() => tabClicked(tab, i)}
/>
))}
</nav>
);
}

View file

@ -1,62 +0,0 @@
import { getPageHTML } from "../wiki";
import { darken, ColorFmt, lighten } from "../darkmode";
import * as React from "react";
import { useState, useEffect, useRef } from "react";
import userscript from "../userscript";
import speen from "~/assets/images/speen.svg";
export default function WikiPage({ page, visible }) {
const [data, setData] = useState({
loaded: false,
processed: false,
html: "",
});
const containerRef = useRef(null);
// Fetch page
useEffect(() => {
console.log(page + ": fetching");
(async () => {
let html = await getPageHTML(page);
// Convert relative links to absolute
html = html.replace(/"\/wiki/gi, '"//tgstation13.org/wiki');
setData({ loaded: true, processed: false, html });
})();
}, []);
// Process page
useEffect(() => {
console.log(page + ": processing");
if (data.loaded == true && data.processed == false) {
userscript(containerRef.current, page);
console.log(page + ": userscript applied");
}
}, [data]);
if (!data.loaded) {
return (
<div
className="page waiting"
style={{
visibility: visible ? "" : "hidden",
}}
>
<div className="speen">
<img src={speen} />
<p>You start skimming through the manual...</p>
</div>
</div>
);
} else {
return (
<div
ref={containerRef}
className="page"
style={{
visibility: visible ? "" : "hidden",
}}
dangerouslySetInnerHTML={{ __html: data.html }}
></div>
);
}
}

14
lib/index.ts Normal file
View file

@ -0,0 +1,14 @@
import TabManager from "./TabManager";
const tabListContainer = document.getElementById("tab-list");
const tabContentContainer = document.getElementById("tabs");
const manager = new TabManager(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);
});

View file

@ -1,6 +0,0 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import App from "./App";
const main = document.getElementById("app");
ReactDOM.render(<App />, main);

View file

@ -7,17 +7,11 @@
"dev": "parcel index.html",
"build": "parcel build index.html"
},
"devDependencies": {
"dependencies": {
"parcel-bundler": "^1.12.4",
"sass": "^1.26.8",
"typescript": "^3.9.5"
},
"dependencies": {
"@types/react": "^16.9.36",
"@types/react-dom": "^16.9.8",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"browserslist": [
"last 2 Chrome versions",
"last 2 Firefox versions"

View file

@ -71,11 +71,16 @@ body {
display: grid;
overflow: hidden;
.page {
visibility: hidden;
padding-top: 10pt;
overflow-y: scroll;
grid-row: 1;
grid-column: 1;
&.active {
visibility: visible;
}
&.waiting {
user-select: none;
position: fixed;
top: 0;
left: 0;
@ -130,8 +135,8 @@ body {
$tab-active: lighten($nanotrasen, 10%);
.tab-list {
z-index: 1;
#tab-list {
z-index: 2;
grid-row: 1;
display: flex;
border-bottom: 2px solid $tab-active;
@ -147,7 +152,7 @@ $tab-active: lighten($nanotrasen, 10%);
&.active {
background-color: $tab-active;
}
&.clickable {
&:not(.active) {
cursor: pointer;
}
}

View file

@ -1,6 +1,5 @@
{
"compilerOptions": {
"target": "ES2020",
"jsx": "react"
"target": "ES2020"
}
}

View file

@ -897,31 +897,11 @@
"@parcel/utils" "^1.11.0"
physical-cpu-count "^2.0.0"
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/q@^1.5.1":
version "1.5.4"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
"@types/react-dom@^16.9.8":
version "16.9.8"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423"
integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^16.9.36":
version "16.9.36"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.36.tgz#ade589ff51e2a903e34ee4669e05dbfa0c1ce849"
integrity sha512-mGgUb/Rk/vGx4NCvquRuSH0GHBQKb1OqpGS9cT9lFxlTLHZgkksgI60TuIxubmn7JuCb+sENHhQciqa0npm0AQ==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
abab@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a"
@ -1883,11 +1863,6 @@ cssstyle@^1.1.1:
dependencies:
cssom "0.3.x"
csstype@^2.2.0:
version "2.6.10"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b"
integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@ -2092,9 +2067,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.413:
version "1.3.474"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.474.tgz#161af012e11f96795eade84bf03b8ddc039621b9"
integrity sha512-fPkSgT9IBKmVJz02XioNsIpg0WYmkPrvU1lUJblMMJALxyE7/32NGvbJQKKxpNokozPvqfqkuUqVClYsvetcLw==
version "1.3.475"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.475.tgz#67688cc82c342f39594a412286e975eda45d8412"
integrity sha512-vcTeLpPm4+ccoYFXnepvkFt0KujdyrBU19KNEO40Pnkhta6mUi2K0Dn7NmpRcNz7BvysnSqeuIYScP003HWuYg==
elliptic@^6.0.0, elliptic@^6.5.2:
version "6.5.2"
@ -3210,7 +3185,7 @@ log-symbols@^2.2.0:
dependencies:
chalk "^2.0.1"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
loose-envify@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -4208,15 +4183,6 @@ process@^0.11.10:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
prop-types@^15.6.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"
psl@^1.1.28:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
@ -4308,30 +4274,6 @@ range-parser@~1.2.1:
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
react-dom@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.19.1"
react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
@ -4595,14 +4537,6 @@ saxes@^3.1.9:
dependencies:
xmlchars "^2.1.1"
scheduler@^0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
semver@7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
@ -4998,9 +4932,9 @@ terser@^3.7.3:
source-map-support "~0.5.10"
terser@^4.3.9:
version "4.7.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.7.0.tgz#15852cf1a08e3256a80428e865a2fa893ffba006"
integrity sha512-Lfb0RiZcjRDXCC3OSHJpEkxJ9Qeqs6mp2v4jf2MHfy8vGERmVDuvjXdd/EnP5Deme5F2yBRBymKmKHCBg2echw==
version "4.8.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
dependencies:
commander "^2.20.0"
source-map "~0.6.1"