Frameworkless achieved!
This commit is contained in:
parent
e3dbfb4e70
commit
fb3d578bc4
11 changed files with 132 additions and 219 deletions
|
@ -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>
|
||||
|
|
26
lib/App.tsx
26
lib/App.tsx
|
@ -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
96
lib/TabManager.ts
Normal 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");
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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
14
lib/index.ts
Normal 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);
|
||||
});
|
|
@ -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);
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"jsx": "react"
|
||||
"target": "ES2020"
|
||||
}
|
||||
}
|
||||
|
|
80
yarn.lock
80
yarn.lock
|
@ -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"
|
||||
|
|
Reference in a new issue