Add posts and old grade-calc

Fix small bugs
>bad commit
This commit is contained in:
Paul W. 2022-02-14 15:32:58 -05:00
parent 391fff28b1
commit bfacb23f8a
21 changed files with 1047 additions and 267 deletions

View File

@ -1,6 +1,7 @@
import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react'; import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react';
import Fuzzy from './_fuzzy'; import Fuzzy from './_fuzzy';
import pages from '../public/pages.json'; import pages from '../public/pages.json';
import posts from '../public/posts.json';
import style from '../styles/fuzzy.module.css'; import style from '../styles/fuzzy.module.css';
function FuzzyBar(): JSX.Element { function FuzzyBar(): JSX.Element {
@ -13,9 +14,15 @@ function FuzzyBar(): JSX.Element {
let fuzz: Fuzzy | null = null; let fuzz: Fuzzy | null = null;
let entries = [...pages];
for (const [k,v] of posts.entries()) {
entries.push({title: v.title, link: `posts/${v.slug}`});
}
try { try {
fuzz = new Fuzzy({ fuzz = new Fuzzy({
pages: pages, pages: entries,
searchField: searchField, searchField: searchField,
searchValue: searchValue, searchValue: searchValue,
resultsValue: resultsValue, resultsValue: resultsValue,

View File

@ -1,5 +1,4 @@
import FuzzyBar from './fuzzy-bar'; import FuzzyBar from './fuzzy-bar';
import Logo from '../public/logo.svg';
import Meta from './meta'; import Meta from './meta';
import Title from './title'; import Title from './title';

View File

@ -1,6 +1,5 @@
import style from '../styles/title.module.css'; import style from '../styles/title.module.css';
import Link from 'next/link'; import Link from 'next/link';
import { useEffect, useState } from 'react';
type propsObj = { type propsObj = {
name: string, name: string,

43
lib/slug.ts Normal file
View File

@ -0,0 +1,43 @@
import fs from 'fs'
import matter from 'gray-matter';
import { join } from 'path';
const postsDirectory = join(process.cwd(), 'posts');
export function getPost(rawslug: string, filter: Array<any> = []) {
const slug = rawslug.replace(/\.md$/, '');
const path = join(postsDirectory, `${slug}.md`);
const file = fs.readFileSync(path, 'utf-8');
const { data, content } = matter(file);
if (data['last_updated'] === undefined)
data['last_updated'] = data['created_at'];
if (filter.length === 0)
return { ...data, content, slug, rawslug };
let post: { slug?: string, rawslug?: string, content?: string, title?: string } | any = {};
for (const [_, entry] of filter.entries()) {
if (entry === 'slug')
post[entry] = slug;
if (entry === 'rawslug')
post[entry] = rawslug;
if (entry === 'content')
post[entry] = content;
if (typeof data[entry] !== 'undefined') {
post[entry] = data[entry]
}
}
return post;
}
export function getAllPosts(filter: Array<any> = []) {
const files = fs.readdirSync(postsDirectory);
return files.map(file => { return getPost(file, filter) });
}

690
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
"dependencies": { "dependencies": {
"@types/react": "^17.0.37", "@types/react": "^17.0.37",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"gray-matter": "^4.0.3",
"next": "^11.1.3", "next": "^11.1.3",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"react": "^17.0.2", "react": "^17.0.2",
@ -19,6 +20,7 @@
"@types/react-dom": "^17.0.11", "@types/react-dom": "^17.0.11",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-next": "^11.1.2", "eslint-config-next": "^11.1.2",
"typescript": "^4.5.2" "typescript": "^4.5.2",
"ts-node": "^10.5.0"
} }
} }

View File

@ -13,12 +13,11 @@ function AboutPage() {
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. 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 />
<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. 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.
<hr />
Got any questions, concerns, or issues? Feel free to contact me via my email: <code>lambdapaul [at] pm [dot] me</code>. Got any questions, concerns, or issues? Feel free to contact me via my email: <code>lambdapaul [at] pm [dot] me</code>.
</section> </section>
<section className='block'> <section className='block'>
<ReactMarkdown>{ReadmeMd}</ReactMarkdown> <ReactMarkdown>{ReadmeMd.replace(/#{1,5} /g, (s: string) => {return `#${s}`})}</ReactMarkdown>
</section> </section>
</Layout> </Layout>
) )

View File

@ -1,4 +1,4 @@
import React, { ReactElement, useState } from 'react'; import React, { ReactElement, useEffect, useState } from 'react';
import Layout from '../../components/layout'; import Layout from '../../components/layout';
import Link from 'next/link'; import Link from 'next/link';
import GradeCalc from '../../components/_gc'; import GradeCalc from '../../components/_gc';
@ -77,9 +77,22 @@ function GradeCalcPage() {
// export default GradeCalcPage; // export default GradeCalcPage;
export default function WIP() { export default function WIP() {
useEffect(() => {
const script = document.createElement('script');
script.src = '/grade-calc/vanilla.js';
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
}
}, []);
return ( return (
<Layout name='Grade Calc' title='[WIP] Grade Calculator'> <Layout name='Grade Calc' title='[WIP] Grade Calculator'>
<section className='block' style={{textAlign: 'center'}}> <section className='grade-calc'>
Check back later as the port of this page is a Work in Progress. Check back later as the port of this page is a Work in Progress.
</section> </section>
</Layout>); </Layout>);

View File

@ -2,29 +2,46 @@ import Link from 'next/link';
import React from 'react'; import React from 'react';
import Layout from '../components/layout'; import Layout from '../components/layout';
import Pages from '../public/pages.json'; import Pages from '../public/pages.json';
import cachePostLinkData from '../util/post-cache';
// import { GetStaticProps } from 'next';
function HomePage() { function HomePage({posts}: any) {
Pages.sort((x, y) => { return ('' + x.title).localeCompare(y.title) });
return ( return (
<Layout name='' title='PaulW.XYZ'> <Layout name='' title='PaulW.XYZ'>
<section className='block' style={{ textAlign: 'center' }}> <section className='block'>
<div className='h2'>Welcome to my website!</div> { <div className='h2'>Welcome!</div>
{
Pages.map(obj => { Pages.map(obj => {
return <div key='' className='h3'> return <div key='' className='h5'>
<Link href={obj.link}> <Link href={obj.link}>
<a>{obj.title}</a> <a>{obj.title}{obj.link.match('^http*')? ' ↗' : ''}</a>
</Link> </Link>
</div> </div>
}) })
} }
</section> </section>
<section className='block'>
<div className='h2'>Posts</div>
<div>
{posts?.map((post: any) => {
return <div key={post.slug} className='h5'>
[{ (new Date(post.last_updated)).toLocaleString()}] <Link href={`posts/${post.slug}`}>
{post.title}
</Link>
</div>
})}
</div>
</section>
</Layout> </Layout>
) )
} }
export async function getStaticProps() {
// make this webpack plugin
return {
props: {posts: cachePostLinkData()}
};
}
export default HomePage; export default HomePage;
// export async function getStaticProps(context): GetStaticProps {
// }

42
pages/posts/[page].tsx Normal file
View File

@ -0,0 +1,42 @@
import Layout from '../../components/layout';
import { useRouter } from 'next/router';
import { getAllPosts, getPost } from '../../lib/slug';
import ReactMarkdown from 'react-markdown';
import Image from 'next/image';
function Post({post} : any) { // eh
const router = useRouter();
return (
<Layout name={post.title} title={post.title} ancestors={[{name:'Posts', path: 'posts'}]}>
<section className='block'>
{post.cover ? <Image width={640} height={360} layout="intrinsic" src={`/assets/images/${post.cover}`} alt={`${post.title} Cover Image`} /> : ''}
<ReactMarkdown>{post.content}</ReactMarkdown>
</section>
</Layout>
);
}
export async function getStaticProps({params}: any) {
const post = getPost(params.page);
return {
props: {post}
};
}
export async function getStaticPaths() {
const posts = getAllPosts();
return {
paths: posts.map(post => {
return {
params: {
page: post.slug
}
}
}),
fallback: false
};
}
export default Post;

32
pages/posts/index.tsx Normal file
View File

@ -0,0 +1,32 @@
import Link from 'next/link';
import React from 'react';
import Layout from '../../components/layout';
import { getAllPosts } from '../../lib/slug';
import Pages from '../../public/pages.json';
import cachePostLinkData from '../../util/post-cache';
function HomePage({posts}: any) {
Pages.sort((x, y) => { return ('' + x.title).localeCompare(y.title) });
return (
<Layout name='Posts'>
{posts.map((post: any) => {
return <section key='' className='h5 block'>
<Link href={`posts/${post.slug}`}>
{post.title}
</Link>
<div>[{ (new Date(post.last_updated)).toLocaleString()}]</div>
</section>
})}
</Layout>
)
}
export async function getStaticProps() {
return {
props: {posts: cachePostLinkData()}
};
}
export default HomePage;

View File

@ -0,0 +1,14 @@
---
title: Thoughts on Baba Is You
created_at: '2021-10-30T00:43:00.000Z'
cover: 'baba_is_you_screencap.png'
---
Just when I thought this game exhausted everything it has to offer, it introduces a new mechanic.
Just when I thought I mastered this game, it introduces something new and adds to the complexity.
I do not know how to compare it to a typical puzzle game because this game is in a sub-genre on its own. You write the rules to your suiting. However many level constrain the rules so you have to work with the ones that level does not have "locked." The early levels are relatively straight-forward, as they act as tutorials. But the difficulty curve goes up and up until you hit a roadblock and get stuck on it for hours. That is when you play a different level. Yes, the game has this over-world similar to the one in Super Mario World where you access levels and sub-worlds. Beating a level unlocks the surrounding locked levels and a dandelion-looking thing (it is really never explained). Sub-worlds are also unlocked by beating levels and collecting those dandelions. There are other collectibles too that are awarded based on set criteria but that would be too much information for new players and for a review.
Do not let the cutesy art style and music in the trailers and other promotional media distract you. This game is extremely difficult. But the moment a solution clicks in a level, there is no better feeling.
Speaking of music, I feel it is the weakest part of the game. The main theme is simple but catchy and the rest are somewhat forget-able.

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@ -0,0 +1,268 @@
(()=>{
const cont = `
<div class="about-container block">
<h2>About</h2>
Check out the <a href="https://github.com/lambdapaul/www/blob/master/public/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="calculator-container block">
</div>
<div class="json-textarea block">
<h2>Configuration</h2>
<h3>Load config from file</h3>
<ul>
<li><a href="javascript:void(0)" onclick="loadConfig('/grade-calc/config/map2302.json')">MAP2302 - ODE I Fall 2019 (map2302.json)</a></li>
<li><a href="javascript:void(0)" onclick="loadConfig('/grade-calc/config/eee3307c.json')">EEE3307C - Electronics I Spring 2021 (eee3307c.json)</a></li>
<li><a href="javascript:void(0)" onclick="loadConfig('/grade-calc/config/eel4742c.json')">EEL4742C - Embedded Systems Spring 2021 (eel4742c.json)</a></li>
<li><a href="javascript:void(0)" onclick="loadConfig('/grade-calc/config/eel4781.json')">EEL4781 - Computer Comm. Networks Spring 2021 (eel4781.json)</a></li>
</ul>
<div class="button-container">
<button class="button" onclick="generate()">Generate &#8594;</button>
</div>
<textarea id="json" id="" rows="30"></textarea>
</div><span class="clear"></span>`;
document.querySelector('.grade-calc').innerHTML = cont;
})(); // eh
function generate() {
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();
}
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;
}
}

View File

@ -9,5 +9,6 @@
{"title":"Mastodon", "link": "https://mastodon.social/@lambdapaul"}, {"title":"Mastodon", "link": "https://mastodon.social/@lambdapaul"},
{"title":"Matrix", "link": "https://matrix.to/#/@lambdapaul:matrix.org"}, {"title":"Matrix", "link": "https://matrix.to/#/@lambdapaul:matrix.org"},
{"title":"Keybase", "link": "https://keybase.io/lambdapaul"}, {"title":"Keybase", "link": "https://keybase.io/lambdapaul"},
{"title":"Playlists", "link": "/playlists"} {"title":"Playlists", "link": "/playlists"},
{"title":"Posts", "link": "/posts"}
] ]

1
public/posts.json Normal file
View File

@ -0,0 +1 @@
[{"title":"Steam Info","slug":"steam-info","last_updated":"2022-02-14T09:18:29.604Z"},{"title":"Thoughts on Baba Is You","slug":"thoughts-on-baba-is-you","last_updated":"2021-10-29T04:00:00.000Z"}]

View File

@ -1,61 +0,0 @@
{
"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"
}
]
}

View File

@ -1,44 +0,0 @@
# 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`
### `route`
### `external`

View File

@ -5,6 +5,7 @@
top: 0; top: 0;
bottom: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.9); background-color: rgba(0, 0, 0, 0.9);
z-index: 100;
} }
.search { .search {

View File

@ -150,9 +150,32 @@ section {
} }
.block { .block {
margin: 2rem; margin: 0 2rem;
padding: 2rem; padding: 2rem;
/* box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); */ /* box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); */
border: 1px solid #ffffff; border: 1px solid #ffffff;
border-radius: 1rem; border-bottom: none;
}
.block:first-of-type {
border-top-right-radius: 1rem;
border-top-left-radius: 1rem;
margin-top: 2rem;
}
.block:last-of-type {
border-bottom: 1px solid #ffffff;
border-bottom-right-radius: 1rem;
border-bottom-left-radius: 1rem;
margin-bottom: 2rem;
}
code {
overflow-x: scroll;
max-width: 100%;
display: inline-block;
background-color: rgba(255, 255, 255, 0.1);
font-size: 1rem;
padding: 0.1rem 0.5rem;
vertical-align: bottom;
} }

14
util/post-cache.ts Normal file
View File

@ -0,0 +1,14 @@
import fs from 'fs';
import { getAllPosts } from '../lib/slug';
import { join } from 'path';
const publicDir = join(process.cwd(), 'public');
export default function cachePostLinkData() {
const posts = getAllPosts(['title', 'slug', 'last_updated']);
fs.writeFile(`${publicDir}/posts.json`, JSON.stringify(posts), (e) => {
if (e)
console.error(e);
});
return posts;
}