Merge pull request #5 from LambdaPaul/debug
Switch to next.js from vanilla
This commit is contained in:
commit
a64de137b3
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.next/
|
||||
.DS_Store
|
||||
|
||||
|
87
404.html
87
404.html
@ -1,87 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PaulW.XYZ /</title>
|
||||
<meta name="description" content="Paul's Homepage. I do things. Sporadically.">
|
||||
<link rel="stylesheet" href="/stylesheets/persia.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<svg class="m-icon"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg4546"
|
||||
version="1.1"
|
||||
viewBox="0 0 67.733332 67.733331"
|
||||
height="256"
|
||||
width="256">
|
||||
<defs
|
||||
id="defs4550" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-229.26668)"
|
||||
id="layer1">
|
||||
<path
|
||||
id="rect4524-2"
|
||||
d="M -3.5234382e-6,249.58668 9.0311079,263.13335 -3.5234382e-6,276.68002 v 13.54667 L 18.06222,263.13335 -3.5234382e-6,236.04001 Z"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:0.2;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="rect4524-1-7"
|
||||
d="M 4.5155513,229.26668 27.093332,263.13335 4.5155524,297.00002 h 9.0311116 l 22.57778,-33.86667 -22.57778,-33.86667 z"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:0.73333333;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="rect4524-1-8"
|
||||
d="m 13.546665,229.26668 22.57778,33.86667 -22.57778,33.86667 h 9.031111 l 18.062225,-27.09334 18.062224,27.09334 h 9.031113 L 45.155557,263.13335 22.577776,229.26668 Z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:0.73333333;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 36.124444,276.68002 13.546667,20.32 h 9.031113 L 40.64,269.90668 Z"
|
||||
id="path4779" />
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:0.46666667;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M -3.5234382e-6,236.04001 18.06222,263.13335 -3.5234382e-6,290.22669 v 6.77333 H 4.5155524 L 27.093333,263.13335 4.5155524,229.26668 H -3.5234382e-6 Z"
|
||||
id="path4781" />
|
||||
<path
|
||||
id="path4783"
|
||||
d="m 31.608889,283.45335 9.03111,13.54667 h 9.031112 l -13.546667,-20.32 z"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:0.46666667;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path4783-1"
|
||||
d="m 27.093332,290.22669 4.515557,6.77333 H 40.64 l -9.031111,-13.54667 z"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:0.2;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
<nav>
|
||||
<h1 class="title"><a href="/">PaulW.XYZ</a> / ... ??? </h1>
|
||||
</nav>
|
||||
<div class="block">
|
||||
<h1>Error 404: Not Found</h1>
|
||||
<p>
|
||||
<strong>Uh oh! The page you are looking for does not exist...</strong><br>
|
||||
<strong><a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes">[Wikipedia] Learn more about HTTP status codes.</a></strong>
|
||||
</p>
|
||||
</div>
|
||||
<footer>
|
||||
<a href="https://github.com/lambdapaul/www">Hosted on GitHub</a>
|
||||
© 2021 Paul W.
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
36
about.html
36
about.html
@ -1,36 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PaulW.XYZ / About</title>
|
||||
<meta name="description" content="Paul's Homepage. I do things. Sporadically.">
|
||||
<link rel="stylesheet" href="/stylesheets/persia.css">
|
||||
</head>
|
||||
<body>
|
||||
<svg class="m-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 67.733332 67.733331"><g fill="#fff"><path d="M-.00000352 20.32 9.0311079 33.86667-.00000352 47.41334v13.54667L18.06222 33.86667-.00000352 6.77333Z" fill-opacity=".2"/><path d="M4.5155513 0 27.093332 33.86667 4.5155524 67.73334h9.0311116l22.57778-33.86667L13.546664 0z" fill-opacity=".73333333"/><path d="m13.546665 0 22.57778 33.86667-22.57778 33.86667h9.031111L40.640001 40.64l18.062224 27.09334h9.031113L45.155557 33.86667 22.577776 0Z"/><path d="m36.124444 47.41334 13.546667 20.32h9.031113L40.64 40.64Z" fill-opacity=".73333333"/><path d="M-.00000352 6.77333 18.06222 33.86667-.00000352 60.96001v6.77333H4.5155524L27.093333 33.86667 4.5155524 0H-.00000352ZM31.608889 54.18667l9.03111 13.54667h9.031112l-13.546667-20.32z" fill-opacity=".46666667"/><path d="m27.093332 60.96001 4.515557 6.77333H40.64l-9.031111-13.54667z" fill-opacity=".2"/></g></svg>
|
||||
<nav>
|
||||
<h1 class="title"><a href="/">PaulW.XYZ</a> / About</h1>
|
||||
</nav>
|
||||
<section class="block">
|
||||
This is a personal website written by <a href="https://github.com/LambdaPaul">@LambdaPaul</a>.<br><br>
|
||||
Why did I write this?
|
||||
I do not really know, at least the content I put here. I guess I wanted a place on the web where I wanted to put everything I think is worth looking at some point in the future.
|
||||
<br>
|
||||
<br>
|
||||
It seems wise to have things up here even though they may embarrass me at some point in the future, as many of the things I have done in the past have. Especially the web sites I made in high school. I will never forget those.
|
||||
</section>
|
||||
<section class="block">
|
||||
<a href="https://www.netlify.com">
|
||||
<img src="https://www.netlify.com/img/global/badges/netlify-color-bg.svg" alt="Deploys by Netlify" />
|
||||
</a>
|
||||
</section>
|
||||
<section class="block">
|
||||
<h2>Design Draft</h2>
|
||||
This is a draft of the rationale behind writing pages the way I have. I do not like the traditional web conventions so this is where I experiment the most with interfacing elements. The navigation pane-based design works when they are simple and easy to understand. When they get too complex, people rely on using search bars to get where they want. My goal is to find a method to eliminate the visual noise of the complex navigation panes and find a reasonable alternative them while being easy to use.<h3 id=page-categorization>Page Categorization</h3><p>The home page or the index page solely acts as a gateway to all the other pages on the site. The accessible pages are categorized on a need-based system. Since I currently do not have multiple pages, I have them all under a single directory.<h3 id=design-principles>Design Principles</h3><p>The goal of my home page’s design is to keep this minimal. There is no need for unnecessary content to waste people’s time. The rule is simple: only waste one’s time if they willingly do so.<p>Modern webpages tend to be bulky. This entire site should retain the modern design while being as minimal and static as possible.<ul><li>minimal: a page should not overly rely on programming logic unless the focus is programming logic<li>static: informational pages should not be dynamically changing as the focus is on the content itself</ul><h4 id=design-problems>Design Problems</h4><p>A new user would never understand how this site works. My ideal navigation system would involve having as few navigation elements as possible. This is the reason why I chose to add solely the fuzzy-search bar. However, I have not been able to find a good way to give users of my pages a good way to interact in case they do not use the search bar. I previously used to list them all but it was not easy on the eyes and looked lazy. Maybe a command system that does not use symbols (considering touch devices do not support non-Latin character insertions easily). Maybe something similar to *nix commands. The site somewhat vaguely follows the basic file-system structure. However, this goes against my goal of “ease-of-use” which is why I have not done it yet.
|
||||
</section>
|
||||
<footer>
|
||||
© 2021 Paul W.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
113
components/_fuzzy.tsx
Normal file
113
components/_fuzzy.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import { Dispatch, KeyboardEvent as ReactKeyboardEvent, MutableRefObject, SetStateAction } from 'react';
|
||||
import Result from './fuzzy-result';
|
||||
import style from '../styles/fuzzy.module.css';
|
||||
|
||||
export type FuzzyConstr = {
|
||||
pages: Page[],
|
||||
searchField: MutableRefObject<null>,
|
||||
searchValue: string,
|
||||
resultsValue: JSX.Element,
|
||||
setResultsValue: Dispatch<SetStateAction<JSX.Element>>,
|
||||
maxResults?: number
|
||||
};
|
||||
|
||||
export type Page = {
|
||||
title: string;
|
||||
link: string;
|
||||
};
|
||||
|
||||
export default class Fuzzy {
|
||||
searchValue: string = '';
|
||||
searchField: HTMLInputElement | null;
|
||||
pages: Page[];
|
||||
setResultsValue: Dispatch<SetStateAction<JSX.Element>>;
|
||||
resultsValue: JSX.Element;
|
||||
maxResults: number;
|
||||
|
||||
constructor(obj: FuzzyConstr) {
|
||||
this.searchField = obj.searchField.current;
|
||||
this.resultsValue = obj.resultsValue; // not yet implemented. have to look into lazy eval in js
|
||||
this.setResultsValue = obj.setResultsValue;
|
||||
this.searchValue = obj.searchValue || '';
|
||||
|
||||
this.pages = obj.pages
|
||||
this.maxResults = obj.maxResults || this.pages.length;
|
||||
|
||||
this.pages.sort((x, y) => { return ('' + x.title).localeCompare(y.title) });
|
||||
}
|
||||
|
||||
searchKeyUpListener(e: ReactKeyboardEvent) {
|
||||
this.showSearchResults();
|
||||
}
|
||||
|
||||
showSearchResults(): void {
|
||||
let searchValue: string = this.searchValue.toLowerCase();
|
||||
searchValue = searchValue.trimStart().trimEnd();
|
||||
if (
|
||||
(this.maxResults !== undefined && searchValue === '') ||
|
||||
searchValue === '?' ||
|
||||
searchValue === 'help') {
|
||||
this.setResultsValue(
|
||||
<>
|
||||
<h2>Help</h2>
|
||||
<div>Enter a page or directory name. If do not know any, clear the search field to list everything.</div>
|
||||
<div>Using the <code>Enter</code> key would take you to the first page in list, if the list is not empty.</div>
|
||||
<div>Alternatively, use the <code>Up</code> and <code>Down</code> arrow keys to select the page you want and use the <code>Enter</code> key.</div>
|
||||
<div>Use <code>Backspace</code> to go back to the search.</div>
|
||||
</>
|
||||
)
|
||||
return;
|
||||
}
|
||||
|
||||
let results = [];
|
||||
for (const [idx, page] of this.pages.entries()) {
|
||||
const ret = this.fuzzySearch(page.title, searchValue);
|
||||
if (ret === null)
|
||||
continue;
|
||||
results.push({ formatted: ret.formatted, link: page.link, score: ret.score });
|
||||
}
|
||||
|
||||
if (results.length <= 0)
|
||||
return this.setResultsValue(<>Unknown command or no matching pages found.</>);
|
||||
|
||||
results.sort((x, y) => { return x.score - y.score });
|
||||
|
||||
this.setResultsValue(
|
||||
<>
|
||||
{results.map((res: { formatted: JSX.Element[], link: string, score: number }, idx) => {
|
||||
return (<Result
|
||||
link={res.link}
|
||||
formatted={res.formatted}
|
||||
idx={idx}
|
||||
key={`res-${idx}`} />);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
fuzzySearch(findIn: string, find: string) {
|
||||
let search = find.replace(/\s/g, '');
|
||||
search = search.toLowerCase();
|
||||
let tokens: string[] = findIn.split('');
|
||||
let elements: JSX.Element[] = new Array(tokens.length);
|
||||
let pc = 0;
|
||||
let score = 0;
|
||||
|
||||
for (const [i, ch] of tokens.entries()) {
|
||||
if (ch.toLowerCase() === search[pc]) {
|
||||
score += i - pc;
|
||||
elements[i] = (<span className={style.highlight}>{ch}</span>);
|
||||
pc++;
|
||||
if (search.length < pc)
|
||||
return null;
|
||||
continue;
|
||||
}
|
||||
elements[i] = (<>{ch}</>); // not very nice :(
|
||||
}
|
||||
|
||||
if (search.length === pc)
|
||||
return { formatted: elements, score: (score / search.length) };
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
199
components/_gc.ts
Normal file
199
components/_gc.ts
Normal file
@ -0,0 +1,199 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
|
||||
export default class GradeCalc {
|
||||
maxscore = 0;
|
||||
sections: Array<ReactElement> = [];
|
||||
inputSection: Array<ReactElement[]> = [[]];
|
||||
outputSection: Array<ReactElement> = [];
|
||||
fields: Array<ReactElement[]> = [];
|
||||
grades: number[] = [];
|
||||
ugrades: number[] = [];
|
||||
both = false;
|
||||
totalOutput: ReactElement;
|
||||
config: any[];
|
||||
|
||||
constructor(config: {title: string, percentage: number}, outCallback: (arg: Array<ReactElement>) => void) {
|
||||
this.totalOutput = React.createElement('div');
|
||||
let dConfig = JSON.parse(JSON.stringify(config)); // dirty clone
|
||||
let sanConfig = [];
|
||||
for (let conf of dConfig) {
|
||||
if (conf.percentage === undefined || conf.name === undefined)
|
||||
continue;
|
||||
if (conf.title === undefined)
|
||||
conf.title = conf.name[0].toUpperCase() + conf.name.slice(1);
|
||||
sanConfig.push(conf);
|
||||
}
|
||||
this.config = sanConfig;
|
||||
for (let [i, conf] of this.config.entries()) {
|
||||
this.maxscore += conf.percentage;
|
||||
|
||||
this.inputSection[i] = [];
|
||||
this.outputSection[i] = React.createElement('div');
|
||||
|
||||
if (conf.bothMethods) {
|
||||
this.both = true;
|
||||
}
|
||||
|
||||
this.sections[i] = (this.createSection(i));
|
||||
}
|
||||
|
||||
// for (let [k, v] of this.fields.entries()) {
|
||||
// for (let field of v) {
|
||||
// this.addInputEventListener(k, field);
|
||||
// }
|
||||
// }
|
||||
|
||||
outCallback(this.sections);
|
||||
}
|
||||
|
||||
createSection(id: number): ReactElement {
|
||||
|
||||
const conf = this.config[id];
|
||||
|
||||
const heading = React.createElement('h2', {}, `${conf.title} (${conf.percentage}%)`);
|
||||
|
||||
let info = null
|
||||
if (conf.info !== undefined)
|
||||
info = (React.createElement('div', {}, conf.info));
|
||||
|
||||
|
||||
this.fields[id] = [];
|
||||
let inputSection: Array<ReactElement> | ReactElement = [];
|
||||
if (conf.points !== undefined) {
|
||||
for (let i = 0; i < conf.points.length; i++) {
|
||||
inputSection[i] = this.createInputSection(id, i);
|
||||
}
|
||||
}
|
||||
else {
|
||||
inputSection = this.createInputSection(id, 0, true);
|
||||
}
|
||||
|
||||
const section = React.createElement('div', {className: conf.name}, heading, info, inputSection,
|
||||
// this.outputSection[id]
|
||||
);
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
createInputSection(sectId: number, inputId: number, soleInput: boolean = false): ReactElement {
|
||||
const conf = this.config[sectId];
|
||||
|
||||
let label = '';
|
||||
if (soleInput)
|
||||
label = `${conf.title} Score: `;
|
||||
else
|
||||
label = `${conf.title} ${inputId + 1} Score: `;
|
||||
|
||||
const field = React.createElement('input', {
|
||||
className: `input ${conf.name}-score`,
|
||||
onKeyUp: () => {
|
||||
if (conf.output !== undefined && conf.output)
|
||||
this.showSectionGrade(inputId);
|
||||
this.showTotalGrade();
|
||||
}
|
||||
});
|
||||
|
||||
this.fields[sectId][inputId] = field;
|
||||
let suffix = (soleInput) ? '%' : ` / ${conf.points[inputId]} pts`;
|
||||
|
||||
const inputSection = React.createElement('div', {className: 'input-section'}, label, field, suffix);
|
||||
|
||||
this.inputSection[sectId][inputId] = inputSection;
|
||||
return inputSection;
|
||||
}
|
||||
|
||||
calculateSectionGrade(id: number, unweighted = false): number {
|
||||
// let conf = this.config[id];
|
||||
// let fields = this.fields[id];
|
||||
// if (fields === undefined)
|
||||
// return 0;
|
||||
// if (conf.points === undefined) {
|
||||
// return parseFloat(fields[0].value);
|
||||
// }
|
||||
|
||||
// let total = 0;
|
||||
|
||||
// if (unweighted) {
|
||||
// let counter = 0;
|
||||
// for (let [i, field] of fields.entries()) {
|
||||
// let val = parseFloat(field.value);
|
||||
// if (isNaN(val))
|
||||
// continue;
|
||||
// total += val / conf.points[i];
|
||||
// counter++;
|
||||
// }
|
||||
|
||||
// return (total / counter * 100);
|
||||
// }
|
||||
|
||||
// total = fields.reduce((acc, cur) => {
|
||||
// const c = parseFloat(cur.value);
|
||||
|
||||
// return isNaN(c) ? acc : acc + c;
|
||||
// }, 0);
|
||||
|
||||
// let max_total = 0;
|
||||
// for (const [i, field] of conf.points.entries()) {
|
||||
// if (isNaN(parseFloat(fields[i].value)))
|
||||
// continue;
|
||||
// max_total += field;
|
||||
// }
|
||||
|
||||
// return (total / max_total * 100);
|
||||
return 0;
|
||||
}
|
||||
|
||||
showSectionGrade(id: number) {
|
||||
let conf = this.config[id];
|
||||
let grade = this.calculateSectionGrade(id);
|
||||
let ugrade = this.calculateSectionGrade(id, true);
|
||||
|
||||
|
||||
this.grades[id] = grade * parseFloat(conf.percentage) / 100;
|
||||
this.ugrades[id] = ugrade * parseFloat(conf.percentage) / 100;
|
||||
|
||||
const outGrade = !isNaN(grade) ? grade.toFixed(2) : "...";
|
||||
const outUgrade = !isNaN(ugrade) ? ugrade.toFixed(2) : "...";
|
||||
if (conf.bothMethods) {
|
||||
this.outputSection[id].props.children
|
||||
= `Score (weighted): ${outGrade}%<br> Score (unweighted): ${outUgrade}%`;
|
||||
return;
|
||||
}
|
||||
|
||||
this.outputSection[id].props.value = `Score: ${grade}`;
|
||||
}
|
||||
|
||||
showTotalGrade() {
|
||||
for (let [k, conf] of this.config.entries()) {
|
||||
if (!conf.output) {
|
||||
this.grades[k] = this.calculateSectionGrade(k) * parseFloat(conf.percentage) / 100;
|
||||
this.ugrades[k] = this.calculateSectionGrade(k, true) * parseFloat(conf.percentage) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
let grade: number = this.grades.reduce((a, c) => {
|
||||
if (isNaN(c))
|
||||
return a;
|
||||
return a + c
|
||||
}, 0);
|
||||
let ugrade: number = this.ugrades.reduce((a, c) => {
|
||||
if (isNaN(c))
|
||||
return a;
|
||||
return a + c
|
||||
}, 0);
|
||||
|
||||
const outGrade = !isNaN(grade) ? grade.toFixed(2) : "...";
|
||||
const outUgrade = !isNaN(ugrade) ? ugrade.toFixed(2) : "...";
|
||||
if (this.both) {
|
||||
// this.totalOutput.props.children
|
||||
// = `Total Score (weighted): ${outGrade}%<br> Total Score (unweighted): ${outUgrade}%`;
|
||||
return;
|
||||
}
|
||||
|
||||
// this.totalOutput.props.children = `Total Score: ${outGrade}%`;
|
||||
}
|
||||
|
||||
get elemTotal() {
|
||||
return this.totalOutput;
|
||||
}
|
||||
}
|
94
components/fuzzy-bar.tsx
Normal file
94
components/fuzzy-bar.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import Fuzzy from './_fuzzy';
|
||||
import pages from '../public/pages.json';
|
||||
import style from '../styles/fuzzy.module.css';
|
||||
|
||||
function FuzzyBar(): JSX.Element {
|
||||
const searchField = useRef<any>(null);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [resultsValue, setResultsValue] = useState(<></>);
|
||||
let [metaKey, setMetaKey] = useState('Ctrl');
|
||||
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
let fuzz: Fuzzy | null = null;
|
||||
|
||||
try {
|
||||
fuzz = new Fuzzy({
|
||||
pages: pages,
|
||||
searchField: searchField,
|
||||
searchValue: searchValue,
|
||||
resultsValue: resultsValue,
|
||||
setResultsValue: setResultsValue,
|
||||
maxResults: 5
|
||||
});
|
||||
} catch (e: any) {
|
||||
console.error(e.message);
|
||||
}
|
||||
|
||||
function searchInput(e: ChangeEvent<HTMLInputElement>) {
|
||||
setSearchValue(e.target.value);
|
||||
}
|
||||
|
||||
const toggleSearch = useCallback(() => {
|
||||
setShow(!show);
|
||||
fuzz?.showSearchResults();
|
||||
searchField.current?.focus();
|
||||
}, [fuzz, show]);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.navigator.userAgent.match(/mac[\s]?os/i))
|
||||
setMetaKey('⌘ Cmd');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const event = (e: KeyboardEvent) => {
|
||||
if (e.code === 'KeyK' && (e.ctrlKey || e.metaKey)) {
|
||||
toggleSearch();
|
||||
}
|
||||
|
||||
if (e.code === 'KeyP' && (e.ctrlKey || e.metaKey) && e.shiftKey) {
|
||||
alert('not really sure what to do here lol');
|
||||
}
|
||||
|
||||
if (show)
|
||||
searchField.current?.focus();
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', event);
|
||||
|
||||
return () => { window.removeEventListener('keydown', event); }
|
||||
}, [fuzz, show, toggleSearch]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
show ?
|
||||
<div className={`fuzzynav ${style.container}`}>
|
||||
<input type='text'
|
||||
className={style.search}
|
||||
value={searchValue}
|
||||
placeholder='Go to ...'
|
||||
ref={searchField}
|
||||
onChange={searchInput}
|
||||
onKeyUp={(e) => { fuzz?.searchKeyUpListener(e) }} />
|
||||
<div
|
||||
id='results'
|
||||
className={style.results}>{resultsValue}</div>
|
||||
</div>
|
||||
: <></>
|
||||
}
|
||||
|
||||
|
||||
<a className={style.searchBar} onClick={toggleSearch}>
|
||||
<span className={style.searchTerm}>Search</span>
|
||||
<div className={style.keybind}>
|
||||
<span className={style.key}>{metaKey}</span> <span className={style.key}>K</span>
|
||||
</div>
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FuzzyBar;
|
20
components/fuzzy-result.tsx
Normal file
20
components/fuzzy-result.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import style from '../styles/fuzzy.module.css';
|
||||
import Link from 'next/link';
|
||||
|
||||
function Result(props: { formatted: JSX.Element[], key: string, link: string, idx: number }) {
|
||||
|
||||
return (
|
||||
<Link href={props.link}>
|
||||
<a className={style.hyperlink}>
|
||||
<div className={style['hyperlink-name']}>
|
||||
{props.formatted}
|
||||
</div>
|
||||
<div className={style['hyperlink-url']}>
|
||||
{props.link}
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export default Result;
|
24
components/layout.tsx
Normal file
24
components/layout.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import FuzzyBar from './fuzzy-bar';
|
||||
import Logo from '../public/logo.svg';
|
||||
import Meta from './meta';
|
||||
import Title from './title';
|
||||
|
||||
type layoutProps = {
|
||||
name: string,
|
||||
title?: string,
|
||||
children?: JSX.Element | JSX.Element[],
|
||||
ancestors?: Array<{ name: string, path: string }>
|
||||
};
|
||||
|
||||
function Layout(props: layoutProps) {
|
||||
return (
|
||||
<>
|
||||
<Meta name={props.name} ancestors={props.ancestors} />
|
||||
<Title title={props.title} name={props.name} ancestors={props.ancestors} />
|
||||
<FuzzyBar />
|
||||
{props.children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Layout;
|
21
components/meta.tsx
Normal file
21
components/meta.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import Head from 'next/head';
|
||||
|
||||
export default function Meta(props: { name: string, ancestors?: Array<{ name: string, path: string }> }) {
|
||||
const path = () => {
|
||||
if (!props.ancestors)
|
||||
return props.name;
|
||||
|
||||
let path = '';
|
||||
props.ancestors.map((obj) => {
|
||||
path = `${path}${obj.name} /`;
|
||||
});
|
||||
|
||||
return `${path} ${props.name}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Head>
|
||||
<title>PaulW.XYZ / {path()}</title>
|
||||
</Head>
|
||||
);
|
||||
}
|
52
components/title.tsx
Normal file
52
components/title.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import style from '../styles/title.module.css';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
type propsObj = {
|
||||
name: string,
|
||||
title?: string,
|
||||
ancestors?: Array<{ name: string, path: string }>
|
||||
};
|
||||
|
||||
function Title(props: propsObj) {
|
||||
|
||||
const path = () => {
|
||||
if (!props.ancestors)
|
||||
return (<></>);
|
||||
|
||||
let currentPath = '';
|
||||
return (<>
|
||||
{
|
||||
props.ancestors.map(ancestor => {
|
||||
currentPath += `/${ancestor.path}`
|
||||
return (
|
||||
<>
|
||||
<Link href={currentPath} key=''>
|
||||
<a>{ancestor.name}</a>
|
||||
</Link>
|
||||
<> / </>
|
||||
</>
|
||||
);
|
||||
})
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className={style.container}>
|
||||
{props.title || props.name}
|
||||
</h1>
|
||||
<div className={style.nav + ' h1'}>
|
||||
{
|
||||
props.name === ''
|
||||
? <>PaulW.XYZ / {props.name}</>
|
||||
: <><Link href='/'><a>PaulW.XYZ</a></Link> / {path()}{props.name}</>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Title;
|
32
index.html
32
index.html
@ -1,32 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PaulW.XYZ /</title>
|
||||
<meta name="description" content="Paul's Homepage. I do things. Sporadically.">
|
||||
<link rel="stylesheet" href="/stylesheets/persia.css">
|
||||
</head>
|
||||
<body>
|
||||
<svg class="m-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 67.733332 67.733331"><g fill="#fff"><path d="M-.00000352 20.32 9.0311079 33.86667-.00000352 47.41334v13.54667L18.06222 33.86667-.00000352 6.77333Z" fill-opacity=".2"/><path d="M4.5155513 0 27.093332 33.86667 4.5155524 67.73334h9.0311116l22.57778-33.86667L13.546664 0z" fill-opacity=".73333333"/><path d="m13.546665 0 22.57778 33.86667-22.57778 33.86667h9.031111L40.640001 40.64l18.062224 27.09334h9.031113L45.155557 33.86667 22.577776 0Z"/><path d="m36.124444 47.41334 13.546667 20.32h9.031113L40.64 40.64Z" fill-opacity=".73333333"/><path d="M-.00000352 6.77333 18.06222 33.86667-.00000352 60.96001v6.77333H4.5155524L27.093333 33.86667 4.5155524 0H-.00000352ZM31.608889 54.18667l9.03111 13.54667h9.031112l-13.546667-20.32z" fill-opacity=".46666667"/><path d="m27.093332 60.96001 4.515557 6.77333H40.64l-9.031111-13.54667z" fill-opacity=".2"/></g></svg>
|
||||
<nav>
|
||||
<h1 class="title">PaulW.XYZ</h1>
|
||||
<div class="fuzzynav">
|
||||
<input class="search" type="text" id="search" placeholder="Go to ... (Try typing `help` or `?`)">
|
||||
<div id="results" class="results">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<footer>
|
||||
© 2021 Paul W.
|
||||
</footer>
|
||||
<script src="/scripts/fuzzy.js"></script>
|
||||
<script type="text/javascript">
|
||||
const results = `<p><strong>Press any key to clear this message...</strong> To see this message again, visit <code>about</code><p>This is a draft of the rationale behind writing pages the way I have. I do not like the traditional web conventions so this is where I experiment the most with interfacing elements. The navigation pane-based design works when they are simple and easy to understand. When they get too complex, people rely on using search bars to get where they want. My goal is to find a method to eliminate the visual noise of the complex navigation panes and find a reasonable alternative them while being easy to use.<h2 id=page-categorization>Page Categorization</h2><p>The home page or the index page solely acts as a gateway to all the other pages on the site. The accessible pages are categorized on a need-based system. Since I currently do not have multiple pages, I have them all under a single directory.<h2 id=design-principles>Design Principles</h2><p>The goal of my home page’s design is to keep this minimal. There is no need for unnecessary content to waste people’s time. The rule is simple: only waste one’s time if they willingly do so.<p>Modern webpages tend to be bulky. This entire site should retain the modern design while being as minimal and static as possible.<ul><li>minimal: a page should not overly rely on programming logic unless the focus is programming logic<li>static: informational pages should not be dynamically changing as the focus is on the content itself</ul><h3 id=design-problems>Design Problems</h3><p>A new user would never understand how this site works. My ideal navigation system would involve having as few navigation elements as possible. This is the reason why I chose to add solely the fuzzy-search bar. However, I have not been able to find a good way to give users of my pages a good way to interact in case they do not use the search bar. I previously used to list them all but it was not easy on the eyes and looked lazy. Maybe a command system that does not use symbols (considering touch devices do not support non-Latin character insertions easily). Maybe something similar to *nix commands. The site somewhat vaguely follows the basic file-system structure. However, this goes against my goal of “ease-of-use” which is why I have not done it yet.`;
|
||||
|
||||
document.body.onload = () => {
|
||||
document.querySelector("#results").innerHTML = results;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
6
next-env.d.ts
vendored
Normal file
6
next-env.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
26
next.config.js
Executable file
26
next.config.js
Executable file
@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
webpack: (config, options) => {
|
||||
config.experiments = { asset: true };
|
||||
|
||||
config.module.rules.push(
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [{ loader: "@svgr/webpack" }]
|
||||
},
|
||||
{
|
||||
test: /\.md$/,
|
||||
type: 'asset/source'
|
||||
},
|
||||
{
|
||||
test: /\.otf$/,
|
||||
type: 'asset/resource'
|
||||
},
|
||||
{
|
||||
resourceQuery: /raw/,
|
||||
type: 'asset/source',
|
||||
}
|
||||
);
|
||||
|
||||
return config
|
||||
},
|
||||
}
|
16779
package-lock.json
generated
Normal file
16779
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/react": "^17.0.30",
|
||||
"dotenv": "^10.0.0",
|
||||
"next": "^11.1.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-markdown": "^7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-next": "11.1.2",
|
||||
"typescript": "^4.4.4"
|
||||
}
|
||||
}
|
18
pages/404.tsx
Normal file
18
pages/404.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import Layout from "../components/layout";
|
||||
|
||||
function NotFoundPage() {
|
||||
|
||||
return (
|
||||
<Layout name="... ??? / 404: Not Found">
|
||||
<div className="block">
|
||||
<h1>Error 404: Not Found</h1>
|
||||
<p>
|
||||
<strong>Uh oh! The page you are looking for does not exist...</strong><br />
|
||||
<strong><a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes">[Wikipedia] Learn more about HTTP status codes.</a></strong>
|
||||
</p>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotFoundPage;
|
6
pages/_app.tsx
Normal file
6
pages/_app.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import type { AppProps } from 'next/app'
|
||||
import '../styles/global.css';
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return <Component {... pageProps} />
|
||||
}
|
25
pages/about.tsx
Normal file
25
pages/about.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
// @ts-ignore
|
||||
import ReadmeMd from '../README.md';
|
||||
import Layout from '../components/layout';
|
||||
|
||||
function AboutPage() {
|
||||
return (
|
||||
<Layout name='About' title='About this website'>
|
||||
<section className='block'>
|
||||
This is a personal website written by <a href='https://github.com/LambdaPaul'>@LambdaPaul</a>.<br /><br />
|
||||
Why did I write this?
|
||||
I do not really know, at least the content I put here. I guess I wanted a place on the web where I wanted to put everything I think is worth looking at some point in the future.
|
||||
<br />
|
||||
<br />
|
||||
It seems wise to have things up here even though they may embarrass me at some point in the future, as many of the things I have done in the past have. Especially the web sites I made in high school. I will never forget those.
|
||||
</section>
|
||||
<section className='block'>
|
||||
<ReactMarkdown>{ReadmeMd}</ReactMarkdown>
|
||||
</section>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default AboutPage;
|
@ -1,204 +0,0 @@
|
||||
class GradeCalc {
|
||||
maxscore = 0;
|
||||
sections = [];
|
||||
inputSection = [];
|
||||
outputSection = [];
|
||||
fields = [];
|
||||
grades = [];
|
||||
ugrades = [];
|
||||
both = false;
|
||||
totalOutput = null;
|
||||
|
||||
constructor(config, outCallback) {
|
||||
this.totalOutput = document.createElement("div");
|
||||
let dConfig = JSON.parse(JSON.stringify(config)); // dirty clone
|
||||
let sanConfig = [];
|
||||
for (let conf of dConfig) {
|
||||
if (conf.percentage === undefined || conf.name === undefined)
|
||||
continue;
|
||||
if (conf.title === undefined)
|
||||
conf.title = conf.name[0].toUpperCase() + conf.name.slice(1);
|
||||
sanConfig.push(conf);
|
||||
}
|
||||
this.config = sanConfig;
|
||||
for (let [i, conf] of this.config.entries()) {
|
||||
this.maxscore += conf.percentage;
|
||||
|
||||
this.inputSection[i] = [];
|
||||
this.outputSection[i] = document.createElement("div");
|
||||
|
||||
if (conf.bothMethods) {
|
||||
this.both = true;
|
||||
}
|
||||
|
||||
this.sections[i] = (this.createSection(i));
|
||||
}
|
||||
|
||||
for (let [k, v] of this.fields.entries()) {
|
||||
for (let field of v) {
|
||||
this.addInputEventListener(k, field);
|
||||
}
|
||||
}
|
||||
|
||||
outCallback(this.sections);
|
||||
}
|
||||
|
||||
createSection(id) {
|
||||
let conf = this.config[id];
|
||||
|
||||
var section = document.createElement("div");
|
||||
section.classList.add(conf.name);
|
||||
|
||||
var heading = document.createElement("h2");
|
||||
heading.innerHTML = `${conf.title} (${conf.percentage}%)`;
|
||||
|
||||
section.appendChild(heading);
|
||||
|
||||
if (conf.info !== undefined)
|
||||
section.appendChild(document.createTextNode(conf.info));
|
||||
|
||||
this.fields[id] = [];
|
||||
if (conf.points !== undefined) {
|
||||
for (var i = 0; i < conf.points.length; i++) {
|
||||
section.appendChild(this.createInputSection(id, i));
|
||||
}
|
||||
}
|
||||
else {
|
||||
section.appendChild(this.createInputSection(id, 0, true));
|
||||
}
|
||||
|
||||
|
||||
section.appendChild(this.outputSection[id]);
|
||||
return section;
|
||||
}
|
||||
|
||||
createInputSection(sectId, inputId, soleInput = false) {
|
||||
let conf = this.config[sectId];
|
||||
let inputSection = document.createElement("div");
|
||||
inputSection.classList.add("input-section");
|
||||
|
||||
let label = document.createElement("label");
|
||||
if (soleInput)
|
||||
label.innerHTML = `${conf.title} Score: `;
|
||||
else
|
||||
label.innerHTML = `${conf.title} ${inputId + 1} Score: `;
|
||||
|
||||
let field = document.createElement("input");
|
||||
field.classList.add(`input`);
|
||||
field.classList.add(`${conf.name}-score`);
|
||||
this.fields[sectId][inputId] = field;
|
||||
|
||||
let suffix = (soleInput) ? "%" : ` / ${conf.points[inputId]} pts`;
|
||||
|
||||
inputSection.appendChild(label);
|
||||
inputSection.appendChild(field);
|
||||
inputSection.appendChild(document.createTextNode(suffix));
|
||||
|
||||
this.inputSection[sectId][inputId] = inputSection;
|
||||
return inputSection;
|
||||
}
|
||||
|
||||
addInputEventListener(id, field, event = "keyup") {
|
||||
let conf = this.config[id];
|
||||
field.addEventListener(event, () => {
|
||||
if (conf.output !== undefined && conf.output)
|
||||
this.showSectionGrade(id);
|
||||
this.showTotalGrade();
|
||||
});
|
||||
}
|
||||
|
||||
calculateSectionGrade(id, unweighted = false) {
|
||||
let conf = this.config[id];
|
||||
let fields = this.fields[id];
|
||||
if (fields === undefined)
|
||||
return;
|
||||
if (conf.points === undefined) {
|
||||
return parseFloat(fields[0].value);
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
|
||||
if (unweighted) {
|
||||
let counter = 0;
|
||||
for (let [i, field] of fields.entries()) {
|
||||
let val = parseFloat(field.value);
|
||||
if (isNaN(val))
|
||||
continue;
|
||||
total += val / conf.points[i];
|
||||
counter++;
|
||||
}
|
||||
|
||||
return (total / counter * 100);
|
||||
}
|
||||
|
||||
total = fields.reduce((acc, cur) => {
|
||||
let c = parseFloat(cur.value);
|
||||
if (isNaN(c))
|
||||
return acc;
|
||||
return acc + parseFloat(c);
|
||||
}, 0);
|
||||
|
||||
let max_total = 0;
|
||||
for (let [i, field] of conf.points.entries()) {
|
||||
if (isNaN(parseFloat(fields[i].value)))
|
||||
continue;
|
||||
max_total += field;
|
||||
}
|
||||
|
||||
return (total / max_total * 100);
|
||||
}
|
||||
|
||||
showSectionGrade(id) {
|
||||
let conf = this.config[id];
|
||||
let grade = this.calculateSectionGrade(id);
|
||||
let ugrade = this.calculateSectionGrade(id, true);
|
||||
|
||||
|
||||
this.grades[id] = grade * parseFloat(conf.percentage) / 100;
|
||||
this.ugrades[id] = ugrade * parseFloat(conf.percentage) / 100;
|
||||
|
||||
grade = !isNaN(grade) ? grade.toFixed(2) : "...";
|
||||
ugrade = !isNaN(ugrade) ? ugrade.toFixed(2) : "...";
|
||||
if (conf.bothMethods) {
|
||||
this.outputSection[id].innerHTML
|
||||
= `Score (weighted): ${grade}%<br> Score (unweighted): ${ugrade}%`;
|
||||
return;
|
||||
}
|
||||
|
||||
this.outputSection[id].innerHTML = `Score: ${grade}`;
|
||||
}
|
||||
|
||||
showTotalGrade() {
|
||||
for (let [k, conf] of this.config.entries()) {
|
||||
if (!conf.output) {
|
||||
this.grades[k] = this.calculateSectionGrade(k) * parseFloat(conf.percentage) / 100;
|
||||
this.ugrades[k] = this.calculateSectionGrade(k, true) * parseFloat(conf.percentage) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
let grade = this.grades.reduce((a, c) => {
|
||||
if (isNaN(c))
|
||||
return a;
|
||||
return a + c
|
||||
}, 0);
|
||||
let ugrade = this.ugrades.reduce((a, c) => {
|
||||
if (isNaN(c))
|
||||
return a;
|
||||
return a + c
|
||||
}, 0);
|
||||
|
||||
grade = !isNaN(grade) ? grade.toFixed(2) : "...";
|
||||
ugrade = !isNaN(ugrade) ? ugrade.toFixed(2) : "...";
|
||||
if (this.both) {
|
||||
this.totalOutput.innerHTML
|
||||
= `Total Score (weighted): ${grade}%<br> Total Score (unweighted): ${ugrade}%`;
|
||||
return;
|
||||
}
|
||||
|
||||
this.totalOutput.innerHTML = `Total Score: ${grade}%`;
|
||||
}
|
||||
|
||||
get elemTotal() {
|
||||
return this.totalOutput;
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta name="Grade Calculator" content="School grade calculation">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/stylesheets/persia.css">
|
||||
<title>Grade Calc</title>
|
||||
</head>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.about-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.json-textarea,
|
||||
.calculator-container {
|
||||
width: 50%;
|
||||
padding: 1rem;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.json-textarea textarea {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-container button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
|
||||
.json-textarea,
|
||||
.calculator-container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<h1 class="title"><a href="/">PaulW.XYZ</a> / <a href="/pages/">Pages</a> / Grade Calculator</h1>
|
||||
<div class="block">
|
||||
<div class="about-container">
|
||||
<h2>About</h2>
|
||||
Check out the <a href="https://github.com/lambdapaul/www/blob/master/pages/grade-calc/README.md">README.md</a> file
|
||||
to learn more about the configuration structure.
|
||||
<h3>Usage</h3>
|
||||
<ol>
|
||||
<li>Either configure the calculator using the text box below or load one from the existing JSON files to
|
||||
generate one.</li>
|
||||
<li>Click <code>Generate</code>.</li>
|
||||
<li>Enter the input values.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="json-textarea">
|
||||
<h2>Configuration</h2>
|
||||
<hr>
|
||||
<h3>Load config from file</h3>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0)" onclick="loadConfig('./config/map2302.json')">MAP2302 - ODE I Fall 2019 (map2302.json)</a></li>
|
||||
<li><a href="javascript:void(0)" onclick="loadConfig('./config/eee3307c.json')">EEE3307C - Electronics I Spring 2021 (eee3307c.json)</a></li>
|
||||
<li><a href="javascript:void(0)" onclick="loadConfig('./config/eel4742c.json')">EEL4742C - Embedded Systems Spring 2021 (eel4742c.json)</a></li>
|
||||
<li><a href="javascript:void(0)" onclick="loadConfig('./config/eel4781.json')">EEL4781 - Computer Comm. Networks Spring 2021 (eel4781.json)</a></li>
|
||||
</ul>
|
||||
<div class="button-container">
|
||||
<button class="button" onclick="generate()">Generate →</button>
|
||||
</div>
|
||||
<textarea id="json" id="" rows="30"></textarea>
|
||||
|
||||
</div>
|
||||
<div class="calculator-container">
|
||||
</div><span class="clear"></span>
|
||||
</div>
|
||||
<script type="text/javascript" src="gc_client.js"></script>
|
||||
<script>
|
||||
function generate() {
|
||||
var let;
|
||||
let calcSection = document.querySelector(".calculator-container");
|
||||
calcSection.innerHTML = "";
|
||||
try {
|
||||
conf = JSON.parse(document.getElementById("json").value);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
calcSection.innerHTML = e;
|
||||
return;
|
||||
}
|
||||
let cb = (out) => {
|
||||
for (let o of out) {
|
||||
calcSection.appendChild(o);
|
||||
}
|
||||
}
|
||||
gc = new GradeCalc(conf, cb);
|
||||
|
||||
calcSection.appendChild(gc.elemTotal);
|
||||
}
|
||||
|
||||
function loadConfig(filename) {
|
||||
var client = new XMLHttpRequest();
|
||||
client.open('GET', filename);
|
||||
client.onreadystatechange = function () {
|
||||
document.getElementById("json").value = (client.responseText);
|
||||
}
|
||||
client.send();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
86
pages/grade-calc/index.tsx
Normal file
86
pages/grade-calc/index.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import Layout from '../../components/layout';
|
||||
import Link from 'next/link';
|
||||
import GradeCalc from '../../components/_gc';
|
||||
|
||||
function GradeCalcPage() {
|
||||
const [calculator, setCalculator] = useState<any>();
|
||||
let [jsonInput, setJsonInput] = useState('[]');
|
||||
|
||||
function loadConfig(filename: string): void {
|
||||
const client = new XMLHttpRequest();
|
||||
client.open('GET', filename);
|
||||
client.onreadystatechange = () => {
|
||||
setJsonInput(client.responseText);
|
||||
}
|
||||
client.send();
|
||||
}
|
||||
|
||||
function generate() {
|
||||
setCalculator('');
|
||||
let conf = null;
|
||||
try {
|
||||
conf = JSON.parse(jsonInput);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
return;
|
||||
}
|
||||
let cb = (out: ReactElement[]) => {
|
||||
setCalculator([... calculator, out]);
|
||||
}
|
||||
const gc = new GradeCalc(conf, cb);
|
||||
if (calculator)
|
||||
setCalculator([... calculator, React.createElement('div', {className: 'asf'}, gc.elemTotal)]);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout name="Grade Calc" title='Grade Calculator'>
|
||||
<div className='block'>
|
||||
<div className='about-container'>
|
||||
<h2>About</h2>
|
||||
Check out the <Link href='/grade-calc/readme'><a >README.md</a></Link> file
|
||||
to learn more about the configuration structure.
|
||||
<h3>Usage</h3>
|
||||
<ol>
|
||||
<li>Either configure the calculator using the text box below or load one from the existing JSON files to
|
||||
generate one.</li>
|
||||
<li>Click <code>Generate</code>.</li>
|
||||
<li>Enter the input values.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div className='json-textarea'>
|
||||
<h2>Configuration</h2>
|
||||
<hr />
|
||||
<h3>Load config from file</h3>
|
||||
<ul>
|
||||
<li><a onClick={() => loadConfig('/grade-calc/config/map2302.json')}>MAP2302 - ODE I Fall 2019 (map2302.json)</a></li>
|
||||
<li><a onClick={() => loadConfig('/grade-calc/config/eee3307c.json')}>EEE3307C - Electronics I Spring 2021 (eee3307c.json)</a></li>
|
||||
<li><a onClick={() => loadConfig('/grade-calc/config/eel4742c.json')}>EEL4742C - Embedded Systems Spring 2021 (eel4742c.json)</a></li>
|
||||
<li><a onClick={() => loadConfig('/grade-calc/config/eel4781.json')}>EEL4781 - Computer Comm. Networks Spring 2021 (eel4781.json)</a></li>
|
||||
</ul>
|
||||
<div className='button-container'>
|
||||
<button className='button' onClick={generate}>Generate →</button>
|
||||
</div>
|
||||
<textarea id='json' rows={30} value={jsonInput} onChange={(t) => {setJsonInput(t.currentTarget.value)}}></textarea>
|
||||
|
||||
</div>
|
||||
<div className='calculator-container'>
|
||||
{calculator}
|
||||
</div>
|
||||
<span className='clear'></span>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
// export default GradeCalcPage;
|
||||
|
||||
export default function WIP() {
|
||||
return (
|
||||
<Layout name='Grade Calc' title='[WIP] Grade Calculator'>
|
||||
<section className='block' style={{textAlign: 'center'}}>
|
||||
Check back later as the port of this page is a Work in Progress.
|
||||
</section>
|
||||
</Layout>);
|
||||
}
|
16
pages/grade-calc/readme.tsx
Normal file
16
pages/grade-calc/readme.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import Layout from '../../components/layout';
|
||||
|
||||
// @ts-ignore
|
||||
import ReadmeMd from '../../public/grade-calc/README.md';
|
||||
|
||||
|
||||
function GCReadme() {
|
||||
return (<Layout name="Read Me" ancestors={[{name:'Grade Calc', path: 'grade-calc'}]}>
|
||||
<section className='block'>
|
||||
<ReactMarkdown>{ReadmeMd}</ReactMarkdown>
|
||||
</section>
|
||||
</Layout>);
|
||||
}
|
||||
|
||||
export default GCReadme;
|
@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>PaulW.XYZ / Pages</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/stylesheets/persia.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<h1 class="title"><a href="/">PaulW.XYZ</a> / Pages</h1>
|
||||
</nav>
|
||||
<div class="nav-list">
|
||||
<a href="/pages/grade-calc/">Grade Calculator</a>
|
||||
<a href="/pages/playlists">Playlists</a>
|
||||
<a href="/pages/recommended">Recommended</a>
|
||||
<a href="/pages/resources">Resources</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
30
pages/index.tsx
Normal file
30
pages/index.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import Layout from '../components/layout';
|
||||
import Pages from '../public/pages.json';
|
||||
|
||||
// import { GetStaticProps } from 'next';
|
||||
|
||||
function HomePage() {
|
||||
return (
|
||||
<Layout name='' title='PaulW.XYZ'>
|
||||
<section className='block' style={{ textAlign: 'center' }}>
|
||||
<div className='h2'>Welcome to my website!</div> {
|
||||
Pages.map(obj => {
|
||||
return <div key='' className='h3'>
|
||||
<Link href={obj.link}>
|
||||
<a>{obj.title}</a>
|
||||
</Link>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</section>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomePage;
|
||||
|
||||
// export async function getStaticProps(context): GetStaticProps {
|
||||
|
||||
// }
|
@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Pages / Playlists</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/stylesheets/persia.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="title"><a href="/">PaulW.XYZ</a> / <a href="/pages/">Pages</a> / Playlists</h1>
|
||||
<section class="block">
|
||||
<h2>Music</h2>
|
||||
<h3>Classical by Composer</h3>
|
||||
<ul>
|
||||
<li><a href="https://youtube.com/playlist?list=PLSU6wJEYct5HslkoJWHQFCttB-lhSwVr2">[Youtube] Baroque Red Metal Priest
|
||||
/ Antonio Lucio Vivaldi</a></li>
|
||||
<li><a href="https://youtube.com/playlist?list=PLSU6wJEYct5HftuY6UunC6zE_QMXOGmhm">[Youtube] Papa Bach / Johann Sebastian Bach</a></li>
|
||||
<li><a href="https://youtube.com/playlist?list=PLSU6wJEYct5Etx0WAXUQ7YXe84Fp5E142">[Youtube] Luigi Bee the Oven / Ludwig van Beethoven</a></li>
|
||||
<li><a href="https://youtube.com/playlist?list=PLSU6wJEYct5EJsE-9Zh-jWckBuZAmIt8Q">[Youtube] Leck Mozart im Arsch / Wolfgang Amadeus Mozart</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
66
pages/playlists.tsx
Normal file
66
pages/playlists.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import Layout from '../components/layout';
|
||||
|
||||
type listItem = {
|
||||
children?: listItem[];
|
||||
url?: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
const list: listItem[] = [{
|
||||
title: 'Classical by Composer',
|
||||
children: [
|
||||
{
|
||||
title: '[Youtube] Baroque Red Metal Priest / Antonio Lucio Vivaldi',
|
||||
url: 'https://youtube.com/playlist?list=PLSU6wJEYct5HslkoJWHQFCttB-lhSwVr2'
|
||||
},
|
||||
{
|
||||
title: '[Youtube] Papa Bach / Johann Sebastian Bach',
|
||||
url: 'https://youtube.com/playlist?list=PLSU6wJEYct5HftuY6UunC6zE_QMXOGmhm'
|
||||
},
|
||||
{
|
||||
title: '[Youtube] Luigi Bee the Oven / Ludwig van Beethoven',
|
||||
url: 'https://youtube.com/playlist?list=PLSU6wJEYct5Etx0WAXUQ7YXe84Fp5E142'
|
||||
},
|
||||
{
|
||||
title: '[Youtube] Leck Mozart im Arsch / Wolfgang Amadeus Mozart',
|
||||
url: 'https://youtube.com/playlist?list=PLSU6wJEYct5EJsE-9Zh-jWckBuZAmIt8Q'
|
||||
}
|
||||
]
|
||||
}];
|
||||
|
||||
function mapChild(obj: listItem, level: number) {
|
||||
if (obj.url)
|
||||
return <li key=''><a href={obj.url}>{obj.title}</a></li>
|
||||
|
||||
if (!obj.children)
|
||||
return <></> // ignore playlists without links
|
||||
|
||||
let title: ReactElement;
|
||||
if (level >= 0 && level <= 3)
|
||||
title = React.createElement(`h${level+3}`, {}, obj.title);
|
||||
else
|
||||
title = React.createElement('strong', {}, obj.title);
|
||||
|
||||
return (
|
||||
<>
|
||||
{title}
|
||||
<ul>
|
||||
{obj.children.map(l => mapChild(l, level + 1))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Playlists() {
|
||||
return (
|
||||
<Layout name='Playlists'>
|
||||
<section className='block'>
|
||||
<h2>Music</h2>
|
||||
{list.map(l => mapChild(l, 0))}
|
||||
</section>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Playlists;
|
@ -1,100 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Pages / Recommended</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/stylesheets/persia.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- This page is really for me to not forget/revisit the good things I have read, seen, heard, and/or experienced. This list may change, just as my opinions. -->
|
||||
<h1 class="title"><a href="/">PaulW.XYZ</a> / <a href="/pages/">Pages</a> / Recommended</h1>
|
||||
<section class="block">
|
||||
<p>If the one you're looking for is not on this list, it is most likely I haven't had the chance to read it yet or I may have put it on the Resources page, if it is freely available.</p>
|
||||
<hr>
|
||||
<h2>Books</h2>
|
||||
<h3>Technology</h3>
|
||||
<h4>C programming</h4>
|
||||
<ul>
|
||||
<li><a href="https://en.wikipedia.org/wiki/The_C_Programming_Language">The C Programming Language [K&R]</a> duh</li>
|
||||
<li>Expert C Programming by Peter van der Linden</li>
|
||||
<li>Practical C Programming by Steve Oualline (kind of outdated but still good)</li>
|
||||
</ul>
|
||||
<h4>Operating Systems</h4>
|
||||
<ul>
|
||||
<li>Advanced Programming in the Unix Environment by W. Richard Stevens</li>
|
||||
<li>Operating Systems: Design and Implementation by Andrew S. Tanenbaum (I haven't had a chance to read his other books on OS. I am not a fan of his networking book though.)</li>
|
||||
</ul>
|
||||
|
||||
<!-- <h4>Networking</h4>
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul> -->
|
||||
|
||||
<h4>Computer Engineering</h4>
|
||||
<ul>
|
||||
<li>Making Embedded Systems: Design Patterns for Great Software by Elecia White</li>
|
||||
<!-- <li>Computer Organization and Design: the Hardware/Software Interface [Patterson Hennessy]</li>
|
||||
<li>Computer Architecture: A Quantitative Approach [Hennessy Patterson]</li> -->
|
||||
</ul>
|
||||
|
||||
<!-- <h4>Electronics</h4> -->
|
||||
|
||||
<h4>Compilers</h4>
|
||||
<ul>
|
||||
<li>Compilers: Principles, Techniques, and Tools [Dragon Book] (discusses theory in detail so it is kind of hard to read)</li>
|
||||
</ul>
|
||||
|
||||
<h4>Other</h4>
|
||||
<ul>
|
||||
<li>Definitive Guide to sed: Tutorial and Reference</li>
|
||||
</ul>
|
||||
|
||||
<h3>Classics</h3>
|
||||
<p>Only the English ones for now.</p>
|
||||
<ul>
|
||||
<li>A Tale of Two Cities</li>
|
||||
<li>The Mayor of Casterbridge</li>
|
||||
<li>The Citadel</li>
|
||||
<li>Oliver Twist</li>
|
||||
<li>Macbeth</li>
|
||||
<li>Othello</li>
|
||||
<li>Adventures of Huckleberry Finn</li>
|
||||
</ul>
|
||||
|
||||
<h3>Languages</h3>
|
||||
<ul>
|
||||
<li>Lingua Latina per se Illustrata (Both parts)</li>
|
||||
</ul>
|
||||
<hr>
|
||||
|
||||
<h2>Movies</h2>
|
||||
<ul>
|
||||
<li>Blade Runner 2049</li>
|
||||
<li>The Hateful Eight</li>
|
||||
<li>Goodfellas</li>
|
||||
<li>Inception</li>
|
||||
<li>Memento</li>
|
||||
<li>The Grand Budapest Hotel</li>
|
||||
</ul>
|
||||
<hr>
|
||||
|
||||
<h2>Music</h2>
|
||||
<ul>
|
||||
<li>Große Fuge Op. 133</li>
|
||||
<li>KV 387</li>
|
||||
<li>KV 448</li>
|
||||
<li>BWV 1048</li>
|
||||
<li>Prelude in G Minor (Op. 23 No. 5)</li>
|
||||
<li>String Quartet, Op. 20 No. 2 (Haydn)</li>
|
||||
</ul>
|
||||
<hr>
|
||||
|
||||
<h2>Video Games</h2>
|
||||
<ul>
|
||||
<li>The Legend of Zelda: Breath of the Wild</li>
|
||||
<li>Portal 2</li>
|
||||
</ul>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
175
pages/recommended.tsx
Normal file
175
pages/recommended.tsx
Normal file
@ -0,0 +1,175 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import Layout from '../components/layout';
|
||||
import style from '../styles/lists.module.css';
|
||||
|
||||
type listItem = {
|
||||
children?: listItem[] | string[];
|
||||
url?: string;
|
||||
title: string;
|
||||
description?: string
|
||||
};
|
||||
|
||||
const list: listItem[] = [
|
||||
{
|
||||
title: 'Books',
|
||||
children: [
|
||||
{
|
||||
title: 'Technology',
|
||||
children: [
|
||||
{
|
||||
title: 'C programming',
|
||||
children: [
|
||||
{
|
||||
title: 'The C Programming Language [K&R]',
|
||||
url: 'https://en.wikipedia.org/wiki/The_C_Programming_Language'
|
||||
},
|
||||
{
|
||||
title: 'Expert C Programming by Peter van der Linden'
|
||||
},
|
||||
{
|
||||
title: 'Practical C Programming by Steve Oualline (kind of outdated but still good)'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Operating Systems',
|
||||
children: [
|
||||
{
|
||||
title: 'Advanced Programming in the Unix Environment by W. Richard Stevens'
|
||||
},
|
||||
{
|
||||
title: 'Operating Systems: Design and Implementation by Andrew S. Tanenbaum (I have not had a chance to read his other books on OS. I am not a fan of his networking book though.)'
|
||||
}
|
||||
]
|
||||
},
|
||||
// {
|
||||
// title: 'Networking'
|
||||
// },
|
||||
// {
|
||||
// title: 'Electronics'
|
||||
// },
|
||||
{
|
||||
title: 'Computer Engineering',
|
||||
children: [
|
||||
{
|
||||
title: 'Making Embedded Systems: Design Patterns for Great Software by Elecia White'
|
||||
}
|
||||
// Computer Organization and Design: the Hardware/Software Interface [Patterson Hennessy]
|
||||
// Computer Architecture: A Quantitative Approach [Hennessy Patterson]
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Compilers',
|
||||
children: [
|
||||
{
|
||||
title: 'Compilers: Principles, Techniques, and Tools [Dragon Book] (discusses theory in detail so it is kind of hard to read)'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Other',
|
||||
children: [
|
||||
{
|
||||
title: 'Definitive Guide to sed: Tutorial and Reference'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Classics',
|
||||
description: 'Only the English ones for now.',
|
||||
children: [
|
||||
'A Tale of Two Cities',
|
||||
'The Mayor of Casterbridge',
|
||||
'The Citadel',
|
||||
'Oliver Twist',
|
||||
'Macbeth',
|
||||
'Othello',
|
||||
'Adventures of Huckleberry Finn'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Language Learning',
|
||||
children: ['Lingua Latina per se Illustrata (Both parts)']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Movies',
|
||||
children: [
|
||||
'Blade Runner 2049',
|
||||
'The Hateful Eight',
|
||||
'Goodfellas',
|
||||
'Inception',
|
||||
'Memento',
|
||||
'The Grand Budapest Hotel'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Music',
|
||||
children: [
|
||||
'Große Fuge Op. 133',
|
||||
'KV 387',
|
||||
'KV 448',
|
||||
'BWV 1048',
|
||||
'Prelude in G Minor (Op. 23 No. 5)',
|
||||
'String Quartet, Op. 20 No. 2 (Haydn)'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Video Games',
|
||||
children: [
|
||||
'The Legend of Zelda: Breath of the Wild',
|
||||
'Portal 2'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function mapChild(obj: listItem | string, level: number) {
|
||||
if (typeof obj === 'string') {
|
||||
if (obj === '')
|
||||
return <></>
|
||||
return <span className={style.listItem}>{obj}</span>
|
||||
}
|
||||
|
||||
if (obj.title === '')
|
||||
return <></>
|
||||
|
||||
if (obj.url)
|
||||
return <span className={style.listItem}><a href={obj.url}>{obj.title}</a></span>
|
||||
|
||||
if (!obj.children)
|
||||
return <span className={style.listItem}>{obj.title}</span>
|
||||
|
||||
let title: ReactElement;
|
||||
|
||||
if (level >= 0 && level <= 4)
|
||||
title = React.createElement(`h${level + 2}`, {}, obj.title);
|
||||
else
|
||||
title = React.createElement('strong', {}, obj.title);
|
||||
|
||||
return (
|
||||
<>
|
||||
{title}
|
||||
{obj.description ? <p>{obj.description}</p> : <></>}
|
||||
<div>
|
||||
{obj.children.map(l => mapChild(l, level + 1))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Recommended() {
|
||||
return (
|
||||
<Layout name='Recommended' title='My Recommendations'>
|
||||
<section className='block'>
|
||||
<p>This page is really for me to not forget/revisit the good things I have read, seen, heard, and/or experienced. This list may change, just as my opinions.</p>
|
||||
<p>If the one you are looking for is not on this list, it is most likely I have not had the chance to read it yet or I may have put it on the Resources page, if it is freely available.</p>
|
||||
{list.map(l => mapChild(l, 0))}
|
||||
</section>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Recommended;
|
@ -1,43 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Pages / Resources</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/stylesheets/persia.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="title"><a href="/">PaulW.XYZ</a> / <a href="/pages/">Pages</a> / Resources</h1>
|
||||
<section class="block">
|
||||
<h2>Programming</h2>
|
||||
<ul>
|
||||
<li><a href="http://aggregate.org/MAGIC/">The Aggregate Magic Algorithms</a></li>
|
||||
<li><a href="https://3fx.ch/typing-is-hard.html">Typing is Hard</a></li>
|
||||
<li><a href="https://www.atlassian.com/git/">Atlassian's Git Guide</a></li>
|
||||
<li><a href="https://learnopengl.com/">LearnOpenGL.com</a></li>
|
||||
<li><a href="http://ctan.math.utah.edu/ctan/tex-archive/info/symbols/comprehensive/symbols-letter.pdf">[PDF] LaTeX Symbols</a></li>
|
||||
<li><a href="https://tobi.oetiker.ch/lshort/lshort.pdf">[PDF] The Not So Short
|
||||
Introduction to LATEX 2ε</a></li>
|
||||
<li><a href="https://writing.kemitchell.com/2016/09/21/MIT-License-Line-by-Line.html">
|
||||
The MIT License, Line by Line by Kyle E. Mitchell</a></li>
|
||||
</ul>
|
||||
<h3>Posts</h3>
|
||||
<ul>
|
||||
<li><a href="https://mtlynch.io/code-review-love/">How to Make Your Code Reviewer Fall in Love with You by Michael Lynch</a></li>
|
||||
<li><a href="https://fasterthanli.me/articles/whats-in-the-box">What's in the box? by @fasterthanlime</a></li>
|
||||
</ul>
|
||||
<h3>Talks</h3>
|
||||
<ul>
|
||||
<li><a href="https://talks.golang.org/2012/waza.slide">Concurrency is not Parallelism by Rob Pike</a></li>
|
||||
</ul>
|
||||
<h2>Electrical</h2>
|
||||
<ul>
|
||||
<li><a href="http://www.mattmillman.com/info/crimpconnectors/">Common Wire-To-Board, Wire-To-Wire Connectors, And Crimp Tools</a></li>
|
||||
</ul>
|
||||
<h2>Other Topics</h2>
|
||||
<ul>
|
||||
<li><a href="https://sightreading.training/">Sight Reading Trainer</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
129
pages/resources.tsx
Normal file
129
pages/resources.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import Layout from '../components/layout';
|
||||
|
||||
type listItem = {
|
||||
children?: listItem[] | string[];
|
||||
url?: string;
|
||||
title: string;
|
||||
description?: string
|
||||
};
|
||||
|
||||
const list = [{
|
||||
title: 'Programming',
|
||||
children: [
|
||||
{
|
||||
url: 'http://aggregate.org/MAGIC/',
|
||||
title: 'The Aggregate Magic Algorithms'
|
||||
},
|
||||
{
|
||||
url: 'https://3fx.ch/typing-is-hard.html',
|
||||
title: 'Typing is Hard'
|
||||
},
|
||||
{
|
||||
url: 'https://www.atlassian.com/git/',
|
||||
title: 'Atlassian's Git Guide'
|
||||
},
|
||||
{
|
||||
url: 'https://learnopengl.com/',
|
||||
title: 'LearnOpenGL.com'
|
||||
},
|
||||
{
|
||||
url: 'http://ctan.math.utah.edu/ctan/tex-archive/info/symbols/comprehensive/symbols-letter.pdf',
|
||||
title: '[PDF] LaTeX Symbols'
|
||||
},
|
||||
{
|
||||
url: 'https://tobi.oetiker.ch/lshort/lshort.pdf',
|
||||
title: '[PDF] The Not So Short Introduction to LATEX 2ε'
|
||||
|
||||
},
|
||||
{
|
||||
url: 'https://writing.kemitchell.com/2016/09/21/MIT-License-Line-by-Line.html',
|
||||
title: 'The MIT License, Line by Line by Kyle E. Mitchell'
|
||||
},
|
||||
{
|
||||
title: 'Posts',
|
||||
children: [
|
||||
{
|
||||
title: 'How to Make Your Code Reviewer Fall in Love with You by Michael Lynch',
|
||||
url: 'https://mtlynch.io/code-review-love/'
|
||||
},
|
||||
{
|
||||
title: 'What's in the box? by @fasterthanlime',
|
||||
url: 'https://fasterthanli.me/articles/whats-in-the-box'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Talks',
|
||||
children: [
|
||||
{
|
||||
title: 'Concurrency is not Parallelism by Rob Pike',
|
||||
url: 'https://talks.golang.org/2012/waza.slide'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Electrical',
|
||||
children: [
|
||||
{
|
||||
title: 'Common Wire-To-Board, Wire-To-Wire Connectors, And Crimp Tools',
|
||||
url: 'http://www.mattmillman.com/info/crimpconnectors/'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Other Topics',
|
||||
children: [
|
||||
{
|
||||
title: 'Sight Reading Trainer',
|
||||
url: 'https://sightreading.training/'
|
||||
}
|
||||
]
|
||||
}];
|
||||
|
||||
function mapChild(obj: listItem | string, level: number) {
|
||||
if (typeof obj === 'string') {
|
||||
if (obj === '')
|
||||
return <></>
|
||||
return <li>{obj}</li>
|
||||
}
|
||||
|
||||
if (obj.title === '')
|
||||
return <></>
|
||||
|
||||
if (obj.url)
|
||||
return <li key=''><a href={obj.url}>{obj.title}</a></li>
|
||||
|
||||
if (!obj.children)
|
||||
return <li>{obj.title}</li>
|
||||
|
||||
let title: ReactElement;
|
||||
|
||||
if (level >= 0 && level <= 4)
|
||||
title = React.createElement(`h${level + 2}`, {}, obj.title);
|
||||
else
|
||||
title = React.createElement('strong', {}, obj.title);
|
||||
|
||||
return (
|
||||
<>
|
||||
{title}
|
||||
{obj.description ? <p>{obj.description}</p> : <></>}
|
||||
<ul>
|
||||
{obj.children.map(l => mapChild(l, level + 1))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Resources() {
|
||||
return (
|
||||
<Layout name='Resources' title='Some Useful Resources'>
|
||||
<section className='block'>
|
||||
|
||||
</section>
|
||||
</Layout>);
|
||||
}
|
||||
|
||||
export default Resources;
|
44
pages/site.md
Normal file
44
pages/site.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Site.json File Specification
|
||||
|
||||
This is a very basic site structure specification used to generate some of the navigations pages and components of the website.
|
||||
|
||||
## Definitions
|
||||
|
||||
### Member
|
||||
|
||||
### Website
|
||||
|
||||
## Attributes
|
||||
|
||||
These are the keys to the object definition used to define the website. Not all of the attributes will be read by the application as they are dependent on the type of member. However, if they are, they must conform to this document.
|
||||
|
||||
### `name`
|
||||
|
||||
Name is one of the two required attributes for each member, along with type.
|
||||
|
||||
### `type`
|
||||
|
||||
|
||||
### `children`
|
||||
|
||||
Children is used to define sub-members of the current member.
|
||||
|
||||
### `text`
|
||||
|
||||
Text is used tto override the string that the capitalization of the name results in. If a change is not required, this may be omitted.
|
||||
|
||||
### `url`
|
||||
|
||||
### `domain`
|
||||
|
||||
## Types
|
||||
|
||||
The types are the different kinds of members that the page can have. They are defined with the `type` attribute. The current list is tentative.
|
||||
|
||||
### `root`
|
||||
|
||||
### `directory`
|
||||
|
||||
### `html`
|
||||
|
||||
### `external`
|
BIN
public/assets/fonts/Cantarell-Regular.otf
Normal file
BIN
public/assets/fonts/Cantarell-Regular.otf
Normal file
Binary file not shown.
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
21
public/logo.svg
Normal file
21
public/logo.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 67.733332 67.733331" height="256" width="256">
|
||||
<metadata>
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g transform="translate(0,-229.26668)">
|
||||
<path d="M -3.5234382e-6,249.58668 9.0311079,263.13335 -3.5234382e-6,276.68002 v 13.54667 L 18.06222,263.13335 -3.5234382e-6,236.04001 Z" style="opacity:1;fill:#ffffff;fill-opacity:0.2;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="M 4.5155513,229.26668 27.093332,263.13335 4.5155524,297.00002 h 9.0311116 l 22.57778,-33.86667 -22.57778,-33.86667 z" style="opacity:1;fill:#ffffff;fill-opacity:0.73333333;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="m 13.546665,229.26668 22.57778,33.86667 -22.57778,33.86667 h 9.031111 l 18.062225,-27.09334 18.062224,27.09334 h 9.031113 L 45.155557,263.13335 22.577776,229.26668 Z" style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path style="opacity:1;fill:#ffffff;fill-opacity:0.73333333;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 36.124444,276.68002 13.546667,20.32 h 9.031113 L 40.64,269.90668 Z" />
|
||||
<path style="opacity:1;fill:#ffffff;fill-opacity:0.46666667;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M -3.5234382e-6,236.04001 18.06222,263.13335 -3.5234382e-6,290.22669 v 6.77333 H 4.5155524 L 27.093333,263.13335 4.5155524,229.26668 H -3.5234382e-6 Z" />
|
||||
<path d="m 31.608889,283.45335 9.03111,13.54667 h 9.031112 l -13.546667,-20.32 z" style="opacity:1;fill:#ffffff;fill-opacity:0.46666667;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="m 27.093332,290.22669 4.515557,6.77333 H 40.64 l -9.031111,-13.54667 z" style="opacity:1;fill:#ffffff;fill-opacity:0.2;stroke:none;stroke-width:0.28266668;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
@ -1,13 +1,12 @@
|
||||
[
|
||||
{"title":"About", "link": "/about"},
|
||||
{"title":"Pages", "link": "/pages/"},
|
||||
{"title":"Resources", "link": "/pages/resources"},
|
||||
{"title":"Recommended", "link": "/pages/recommended"},
|
||||
{"title":"Grade Calculator", "link": "/pages/grade-calc/"},
|
||||
{"title":"Resources", "link": "/resources"},
|
||||
{"title":"Recommended", "link": "/recommended"},
|
||||
{"title":"Grade Calculator", "link": "/grade-calc"},
|
||||
{"title":"GitHub", "link": "https://github.com/lambdapaul"},
|
||||
{"title":"GitLab", "link": "https://gitlab.com/lambdapaul"},
|
||||
{"title":"Mastodon", "link": "https://mastodon.social/@lambdapaul"},
|
||||
{"title":"Matrix", "link": "https://matrix.to/#/@lambdapaul:matrix.org"},
|
||||
{"title":"Keybase", "link": "https://keybase.io/lambdapaul"},
|
||||
{"title":"Playlists", "link": "/pages/playlists"}
|
||||
{"title":"Playlists", "link": "/playlists"}
|
||||
]
|
61
public/site.json
Normal file
61
public/site.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"type": "root",
|
||||
"domain": "paulw.xyz",
|
||||
"children": [
|
||||
{
|
||||
"name": "resources",
|
||||
"type": "route"
|
||||
},
|
||||
{
|
||||
"name": "recommended",
|
||||
"type": "route"
|
||||
},
|
||||
{
|
||||
"name": "playlists",
|
||||
"type": "route"
|
||||
},
|
||||
{
|
||||
"name": "grade-calc",
|
||||
"type": "route",
|
||||
"text": "Grade Calculator",
|
||||
"children": [
|
||||
{
|
||||
"name": "readme",
|
||||
"type": "route",
|
||||
"text": "Grade Calculator Read Me"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "about",
|
||||
"type": "route"
|
||||
},
|
||||
{
|
||||
"name": "github",
|
||||
"type": "external",
|
||||
"text": "GitHub",
|
||||
"url": "https://github.com/LambdaPaul"
|
||||
},
|
||||
{
|
||||
"name": "gitlab",
|
||||
"type": "external",
|
||||
"text": "GitLab",
|
||||
"url": "https://gitlab.com/LambdaPaul"
|
||||
},
|
||||
{
|
||||
"name": "mastodon",
|
||||
"type": "external",
|
||||
"url": "https://mastodon.social/@lambdapaul"
|
||||
},
|
||||
{
|
||||
"name": "keybase",
|
||||
"type": "external",
|
||||
"url": "https://keybase.io/lambdapaul"
|
||||
},
|
||||
{
|
||||
"name": "matrix",
|
||||
"type": "external",
|
||||
"url": "https://matrix.to/#/@lambdapaul:matrix.org"
|
||||
}
|
||||
]
|
||||
}
|
144
scripts/fuzzy.js
144
scripts/fuzzy.js
@ -1,144 +0,0 @@
|
||||
(() => {
|
||||
let searchField = document.querySelector("#search");
|
||||
if (searchField === null)
|
||||
return;
|
||||
|
||||
let client = new XMLHttpRequest();
|
||||
client.open("GET", "/pages.json");
|
||||
client.onreadystatechange = () => {
|
||||
if (client.readyState === 4)
|
||||
fuzzyInit(client.responseText);
|
||||
}
|
||||
client.send();
|
||||
searchField.focus();
|
||||
})();
|
||||
|
||||
function fuzzyInit(pagesFileName) {
|
||||
if (pagesFileName == "")
|
||||
return;
|
||||
|
||||
let searchField = document.querySelector("#search");
|
||||
let resultBlock = document.querySelector("#results");
|
||||
if (searchField === null || resultBlock === null)
|
||||
return;
|
||||
|
||||
var pages;
|
||||
try {
|
||||
pages = JSON.parse(pagesFileName);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
document.body.innerHTML = e.message;
|
||||
return;
|
||||
}
|
||||
|
||||
pages.sort((x, y) => {return ('' + x.title).localeCompare(y.title)});
|
||||
|
||||
searchField.addEventListener("keyup", (e) => {
|
||||
|
||||
let searchValue = searchField.value ? searchField.value.toLowerCase() : "";
|
||||
|
||||
if (e.code === "Enter") {
|
||||
if (resultBlock.childNodes === null
|
||||
|| resultBlock.childNodes[0] === null
|
||||
|| resultBlock.childNodes[0].href === undefined)
|
||||
return;
|
||||
|
||||
window.location = resultBlock.childNodes[0].href;
|
||||
return;
|
||||
}
|
||||
|
||||
// help
|
||||
if (searchValue === "?" || searchValue === "help") {
|
||||
resultBlock.innerHTML = "<h2>Help</h2>Enter a page or directory name.<br>If do not know any, clear the search field to list everything.<br> Using the <code>Enter</code> key would take you to the first page in list, if the list is not empty.<br>Alternatively, use the <code>Up</code> and <code>Down</code> arrow keys to select the page you want and use the <code>Enter</code> key.<br>Use <code>Backspace</code> to go back to the search."
|
||||
return;
|
||||
}
|
||||
|
||||
let results = [];
|
||||
for (const [i, page] of pages.entries()) {
|
||||
ret = fuzzySearch(page.title, searchValue);
|
||||
if (ret === null)
|
||||
continue;
|
||||
results.push({formatted: ret.formatted, link: page.link, score: ret.score});
|
||||
}
|
||||
|
||||
results.sort((x, y) => {return x.score - y.score});
|
||||
|
||||
resultBlock.innerHTML = "";
|
||||
for (const res of results) {
|
||||
linkBlock = document.createElement("a");
|
||||
linkBlock.classList.add("hyperlink");
|
||||
linkBlock.href = res.link;
|
||||
linkBlock.innerHTML = `<div class="name">${res.formatted}</div><div class="link">${res.link}</div>`;
|
||||
resultBlock.appendChild(linkBlock);
|
||||
}
|
||||
|
||||
if (results.length <= 0)
|
||||
resultBlock.innerHTML = "Unknown command or no matching pages found."
|
||||
});
|
||||
|
||||
document.body.addEventListener("keydown", (e) => {
|
||||
if (e.code === "Backspace") {
|
||||
searchField.focus();
|
||||
}
|
||||
|
||||
if (e.code === "ArrowDown" || e.code === "ArrowUp") {
|
||||
if (resultBlock.childNodes === null)
|
||||
return;
|
||||
|
||||
resultNodes = resultBlock.childNodes;
|
||||
|
||||
if (resultNodes.length <= 1)
|
||||
return;
|
||||
|
||||
let currNode = document.activeElement;
|
||||
|
||||
if (searchField === currNode) {
|
||||
e.preventDefault();
|
||||
if (e.code === "ArrowDown")
|
||||
resultNodes[0].focus();
|
||||
else
|
||||
resultNodes[resultNodes.length - 1].focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.from(resultNodes).indexOf(currNode) < 0)
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
if (e.code === "ArrowDown")
|
||||
if (currNode.nextElementSibling === null)
|
||||
searchField.focus();
|
||||
else
|
||||
currNode.nextElementSibling.focus();
|
||||
else if (e.code === "ArrowUp")
|
||||
if (currNode.previousElementSibling === null)
|
||||
searchField.focus();
|
||||
else
|
||||
currNode.previousElementSibling.focus();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fuzzySearch(findIn, find) {
|
||||
let search = find.replace(/\s/g, "");
|
||||
search = search.toLowerCase();
|
||||
let tokens = findIn.split('');
|
||||
let pc = 0;
|
||||
let score = 0;
|
||||
|
||||
for (const [i, ch] of tokens.entries()) {
|
||||
if (ch.toLowerCase() === search[pc]) {
|
||||
score += i - pc;
|
||||
tokens[i] = `<span class="highlight">${ch}</span>`;
|
||||
pc++;
|
||||
if (search.length < pc)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (search.length === pc)
|
||||
return {formatted: tokens.join(''), score: (score / search.length)};
|
||||
|
||||
return null;
|
||||
}
|
141
styles/fuzzy.module.css
Normal file
141
styles/fuzzy.module.css
Normal file
@ -0,0 +1,141 @@
|
||||
.container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
.search {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 2rem;
|
||||
background: linear-gradient(to bottom right, #406b39, #225546) no-repeat center center fixed;
|
||||
box-shadow: inset 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
padding: 0.5rem 1.75rem;
|
||||
color: rgb(255, 255, 255);
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search::placeholder {
|
||||
color: rgb(214, 214, 214);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.results {
|
||||
padding: 1rem;
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.hyperlink {
|
||||
color: #009dff;
|
||||
display: block;
|
||||
padding: 0.5rem;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
font-size: 1.2rem;
|
||||
transition: 300ms cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
text-decoration: none;
|
||||
border: 1px solid #AAAAAA;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.hyperlink:first-child {
|
||||
border-top: 1px solid #AAAAAA;
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
}
|
||||
|
||||
.hyperlink:last-child {
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
}
|
||||
|
||||
.hyperlink:only-child {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.hyperlink:hover {
|
||||
color: #ccc;
|
||||
text-decoration: none;
|
||||
background: linear-gradient(to bottom right, #1a3a15, #09351b) no-repeat center center fixed;
|
||||
}
|
||||
|
||||
.hyperlink:focus {
|
||||
box-shadow: 0 0 1px 1px rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.hyperlink-name {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.hyperlink:hover .hyperlink-name {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.hyperlink-url {
|
||||
word-break: break-all;
|
||||
text-align: right;
|
||||
font-size: 1rem;
|
||||
transition: 200ms cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
}
|
||||
|
||||
.hyperlink:hover .hyperlink-url {
|
||||
color: #009dff;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.searchBar {
|
||||
user-select: none;
|
||||
background: linear-gradient(to bottom right, #406b39, #225546) no-repeat center center fixed;
|
||||
box-shadow: inset 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
color: #CCCCCC;
|
||||
text-decoration: none;
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
padding: 0 0.5rem;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.searchBar:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.searchTerm {
|
||||
display: block;
|
||||
margin: 0.5rem;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.searchTerm::before {
|
||||
display: inline-block;
|
||||
transform: rotate(-45deg) scale(1.4);
|
||||
content: '\26B2';
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.keybind {
|
||||
padding: 0;
|
||||
margin: 0.25rem;
|
||||
height: 2.25rem;
|
||||
font-family: monospace;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.keybind .key {
|
||||
background: #333333;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin: 0.25rem 0;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid #232323;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 1.5px 1.5px 0 0 #222222;
|
||||
}
|
129
styles/global.css
Normal file
129
styles/global.css
Normal file
@ -0,0 +1,129 @@
|
||||
@font-face {
|
||||
font-family: 'Cantarell';
|
||||
src: url('/assets/fonts/Cantarell-Regular.otf');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Cantarell', 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0 0 1rem;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
height: 100%;
|
||||
background-color: #0d1117;
|
||||
background-size: cover;
|
||||
padding-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
.h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
h2,
|
||||
.h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
h3,
|
||||
.h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
h4,
|
||||
.h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h5,
|
||||
.h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6,
|
||||
.h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #009dff;
|
||||
text-decoration: underline;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:focus {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.lambda-logo {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.block {
|
||||
margin: 2rem;
|
||||
padding: 2rem;
|
||||
/* box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); */
|
||||
border: 1px solid #ffffff;
|
||||
border-radius: 1rem;
|
||||
}
|
9
styles/lists.module.css
Normal file
9
styles/lists.module.css
Normal file
@ -0,0 +1,9 @@
|
||||
.listItem {
|
||||
padding: 0.25rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.listItem::before {
|
||||
content: '■';
|
||||
padding: 0 0.5rem;
|
||||
}
|
19
styles/title.module.css
Normal file
19
styles/title.module.css
Normal file
@ -0,0 +1,19 @@
|
||||
.container {
|
||||
text-align: center;
|
||||
margin: auto 1rem;
|
||||
border-bottom: 1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.nav {
|
||||
white-space: pre-wrap;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.4);
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
background: linear-gradient(to bottom right, #1a3a15, #09351b) no-repeat center center fixed;
|
||||
}
|
@ -1,259 +0,0 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom right, #64a95a, #388c73) no-repeat
|
||||
center center fixed;
|
||||
background-color: #539c65;
|
||||
background-size: cover;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
.h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
h2,
|
||||
.h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
h3,
|
||||
.h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
h4,
|
||||
.h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h5,
|
||||
.h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6,
|
||||
.h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #009dff;
|
||||
text-decoration: underline;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:focus {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
text-decoration: none;
|
||||
padding: 0.25rem 1rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
transition: filter 250ms ease;
|
||||
color: #fff;
|
||||
background: linear-gradient(rgb(76 70 167), rgb(0 43 255));
|
||||
margin: 0.25rem 0;
|
||||
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.05), 0 2px 3px 0 rgba(255, 255, 255, 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
filter: brightness(125%);
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button:focus {
|
||||
box-shadow: 0 0 0 0.1rem rgb(255, 255, 255);
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.title {
|
||||
white-space: pre-wrap;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 3rem;
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.input {
|
||||
display: inline-block;
|
||||
box-shadow: inset 0 4px 8px 0 rgba(0,0,0,0.2);
|
||||
padding: 0.5rem;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
box-shadow: inset 0 4px 8px 0 rgba(0,0,0,0.2);
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.fuzzynav {
|
||||
position: relative;
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.fuzzynav input.search {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 2rem;
|
||||
background: linear-gradient(rgba(116, 116, 116, 0.2),rgba(65, 65, 65, 0.2));
|
||||
box-shadow: inset 0 4px 8px 0 rgba(0,0,0,0.2);
|
||||
padding: 0.5rem;
|
||||
color: rgb(255, 255, 255);
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
.fuzzynav input.search::placeholder {
|
||||
color: rgb(214, 214, 214);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.block,
|
||||
.fuzzynav div.results {
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
span.clear {
|
||||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-list a,
|
||||
.fuzzynav div.results .hyperlink {
|
||||
color: #009dff;
|
||||
background-color: #539c65;
|
||||
display: block;
|
||||
padding: 0.5rem;
|
||||
width: 100%;
|
||||
margin: 0.5rem auto;
|
||||
font-size: 1.2rem;
|
||||
transition: 300ms cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
text-decoration: none;
|
||||
background: linear-gradient(to bottom right, #64a95a, #388c73) no-repeat
|
||||
center center fixed;
|
||||
}
|
||||
|
||||
.nav-list a:hover,
|
||||
.fuzzynav div.results .hyperlink:hover {
|
||||
text-decoration: none;
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.fuzzynav div.results .hyperlink:focus {
|
||||
box-shadow: 0 0 1px 1px rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.nav-list a,
|
||||
.fuzzynav div.results .hyperlink .name {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.fuzzynav div.results .hyperlink .link {
|
||||
color: #0000FE;
|
||||
text-align: right;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.fuzzynav div.results .hyperlink .highlight {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
footer {
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.m-icon {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user