Init next.js port

This commit is contained in:
2021-12-07 22:38:31 -05:00
parent ba6071d2a6
commit 9f56e40d1c
38 changed files with 18522 additions and 2 deletions

113
components/_fuzzy.tsx Normal file
View 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
View 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
View 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;

View 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
View 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
View 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
View 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;