Init next.js port
This commit is contained in:
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;
|
||||
Reference in New Issue
Block a user