parent
a5793e087c
commit
013c7cef7f
27 changed files with 626 additions and 358 deletions
99
src/build.ts
99
src/build.ts
|
|
@ -1,99 +0,0 @@
|
|||
import * as esbuild from "esbuild";
|
||||
import { watch, cpSync, rmdirSync, readFileSync, ReadStream } from "node:fs";
|
||||
import { serve } from "micro";
|
||||
import { Server } from "node:http";
|
||||
import handler from "serve-handler";
|
||||
import { Readable } from "node:stream";
|
||||
import { WebSocketServer } from "ws";
|
||||
|
||||
const script = `let t;function rr() {console.log("connecting to dev server")
|
||||
let a = new WebSocket("ws://localhost:8081");
|
||||
a.addEventListener("message", g => {
|
||||
if(g.data == "refresh"){
|
||||
location.reload()
|
||||
}
|
||||
});
|
||||
a.addEventListener("open", () => {
|
||||
console.log("connected")
|
||||
})
|
||||
a.addEventListener("close", () => {
|
||||
console.log("socket closed, restarting in 1s")
|
||||
clearInterval(t)
|
||||
t = setTimeout(()=>{
|
||||
rr()
|
||||
},1000)
|
||||
})};window.addEventListener("load", () => rr())`;
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
|
||||
async function buildTs() {
|
||||
console.log("[dev] Building file..");
|
||||
await esbuild.build({
|
||||
entryPoints: ["./src/web/ts/index.ts"],
|
||||
outfile: "./src/web/dist.js",
|
||||
});
|
||||
}
|
||||
await buildTs();
|
||||
|
||||
if (argv[0] == "--build") {
|
||||
try {
|
||||
rmdirSync("./dist");
|
||||
} catch {}
|
||||
cpSync("./src/web/", "./dist/", { recursive: true });
|
||||
rmdirSync("./dist/ts", { recursive: true });
|
||||
|
||||
console.log("[dev] View the dist folder");
|
||||
}
|
||||
|
||||
if (argv[0] == "--dev") {
|
||||
const wss = new WebSocketServer({ port: 8081 });
|
||||
let allConnections = new Map<number, WebSocket>();
|
||||
wss.on("connection", function connection(ws) {
|
||||
const id = Math.random();
|
||||
allConnections.set(id, ws as any);
|
||||
|
||||
ws.on("error", console.error);
|
||||
ws.on("close", () => {
|
||||
allConnections.delete(id);
|
||||
});
|
||||
});
|
||||
const server = new Server(
|
||||
serve(async (req, res) => {
|
||||
await handler(
|
||||
req,
|
||||
res,
|
||||
{
|
||||
directoryListing: false,
|
||||
public: "src/web/",
|
||||
cleanUrls: false
|
||||
},
|
||||
{
|
||||
createReadStream(path, options) {
|
||||
let sx = readFileSync(path).toString("utf8");
|
||||
|
||||
if (!path.toString().endsWith(".html")) {
|
||||
return Readable.from([sx]) as ReadStream;
|
||||
}
|
||||
|
||||
sx = sx.replace("<head>", `<head><script>${script}</script>`);
|
||||
return Readable.from([sx]) as ReadStream;
|
||||
},
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
server.listen(8080);
|
||||
console.log("[http] Listening HTTP on 8080.");
|
||||
watch(
|
||||
"./src/web",
|
||||
{
|
||||
recursive: true,
|
||||
},
|
||||
async (e, f) => {
|
||||
if (f == "dist.js") return;
|
||||
console.log("[dev] Noticed update in " + f + ", of type " + e + ".");
|
||||
allConnections.forEach((z) => z.send("refresh"));
|
||||
await buildTs();
|
||||
}
|
||||
);
|
||||
}
|
||||
111
src/index.ts
Normal file
111
src/index.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
⡿⣿⠿⢿⠿⣿⢿⡿⡿⡿⢿⠿⣿⠻⢟⡟⣋⣛⡻⡛⠋⠉⠀⠀⠀⠀⢈⡁⠉⢍⠛⡷⣀⠀⠀⠀⠀⠀⠀⠲⢂⠟⡁⢢⢛⡤⢁⣀⡠⣄
|
||||
⡳⡜⡺⢌⡳⢍⠎⡵⢣⢝⡩⢒⠡⠉⢈⣨⢗⡶⠛⠁⠀⠀⠀⠀⠉⢉⣲⣌⠑⢮⠑⠺⡅⠉⠁⠀⠒⠚⠷⣿⣌⡣⢜⠠⢎⠰⡉⠆⠱⠌
|
||||
⠱⠘⡑⠩⡉⢌⠲⡱⢩⣾⣫⣥⣶⣶⡿⣳⢏⣴⠎⠉⣹⢢⣀⣀⣶⡏⠀⠘⡷⣄⢳⡀⠘⢐⣶⣶⣶⣶⣦⠄⢾⡎⠢⡑⢨⢀⢠⡀⢎⠀
|
||||
⠣⡙⠬⠅⠁⠈⠁⠐⣿⣿⣿⣿⣿⣿⣿⣟⣾⣿⣀⣀⣈⣧⣿⣾⣿⣄⣀⡀⢽⣼⡄⢷⡤⣋⣿⣿⣿⣿⣿⣿⣮⣇⠀⠡⢃⡍⢢⣍⠳⣌
|
||||
⠂⡍⠂⠀⠀⠀⠀⠀⣹⣿⣿⣿⣿⣿⣿⣿⡟⠛⣿⣿⣏⣿⢻⡇⢀⣿⣿⣿⡿⣿⣿⡈⣷⡱⡞⣿⣿⣿⣿⣿⣿⣿⠀⠀⠢⠐⣃⢤⢫⡔
|
||||
⠐⠈⠁⠐⠒⠀⠁⠀⠘⣿⣿⣿⣿⣿⡿⣞⣿⡄⢿⣿⣿⡿⣼⣇⠀⣿⣿⣿⢧⣿⡿⣗⡸⢷⡍⢿⣿⣿⣿⣿⣿⡇⠒⠬⢓⡹⢰⢣⡓⢮
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣿⣿⣿⣿⢿⣯⣿⣦⣙⣋⣵⣿⣿⣦⣌⣛⣽⣿⢯⣟⣧⡟⡼⣘⢧⣿⢻⣿⣿⠟⠐⡉⡒⠬⣁⠎⢦⠙⣃
|
||||
⢜⣩⢠⢠⠄⡄⢠⠄⢂⠄⡀⠙⣷⣿⡿⠿⠛⠋⠉⠉⠁⠀⠀⠀⠉⠈⠉⠉⠙⠛⠻⠾⣽⣷⡍⡖⣻⣿⡿⢣⢆⡣⢵⡉⢆⡥⣚⠦⣛⢤
|
||||
⢎⡔⣣⢎⡝⡸⢥⡺⢌⠴⡱⣬⠟⠁⢀⣠⠴⠶⠶⢛⠛⠋⠛⠛⠻⠶⢦⣄⠀⠀⠀⠀⠀⠈⠛⢧⡝⣿⠁⠜⠂⢧⠐⠈⠐⡄⠃⠘⠀⢂
|
||||
⢞⡼⡱⢞⡜⡳⢤⡙⣆⢦⣱⠏⠀⣰⣿⡃⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣻⣷⡀⠀⠀⠀⠀⠀⠈⢻⡅⡟⣈⠅⡆⢂⠈⡐⠀⠄⡀⠀⠀
|
||||
⠊⠼⠹⡄⡙⡜⢧⣽⢞⣮⡏⠀⢰⣿⣿⡿⢦⡌⣁⠒⠐⠂⠐⠀⠀⠐⢉⣩⣽⡇⠀⠀⠀⠀⠀⠀⠈⣷⣰⡌⠭⣰⢀⡱⠌⢢⠱⢠⠌⡘
|
||||
⡀⠤⠡⡼⢩⠛⠘⠜⡊⢹⡃⠀⠈⣿⣿⣿⣁⣎⣑⣪⣑⣣⣜⣰⢶⡾⡟⣤⣿⡿⠀⠀⠀⠀⠀⠀⠀⢸⣟⠾⡕⡁⣊⠱⢫⢄⠢⣅⢺⡰
|
||||
⠜⣰⢉⠲⣁⠚⡄⠈⡠⢐⡂⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⣿⡆⠙⢿⣧⢺⣥⠊⡑⠼⢲⠱
|
||||
⠑⡀⢎⡐⡡⢧⠙⣜⢣⢽⡇⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⢀⡿⡏⠀⠀⠙⣷⡺⣝⢮⡳⢧⣋
|
||||
⠒⠀⠓⡀⠆⢜⠲⢨⡞⡞⣿⣷⣤⣀⠀⠀⢈⣻⣿⣿⣿⣿⣿⣿⡛⠉⠁⠀⠀⠀⠀⠀⠠⠔⠒⣲⡶⢻⡇⠀⠀⠀⠀⠘⢿⡜⢣⢌⡋⡝
|
||||
⣹⡖⢦⡴⢀⠞⣲⢏⡞⡡⢻⡀⠟⣫⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣦⣴⣦⣽⣷⣾⠟⠋⠁⣸⠀⠀⠀⠀⠹⡄⠘⢧⢡⡘⠲⣉
|
||||
⡣⣽⢲⡍⢯⡘⣷⢫⠔⠡⢻⡆⠀⠐⣿⣿⣽⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠋⠀⠀⢀⡇⠀⠀⠀⠀⠀⠠⣉⢚⣤⢂⠱⡶
|
||||
⣣⡙⢶⣘⣧⢼⡳⠃⡌⠐⣉⣇⠀⠀⠘⣿⣲⢻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠋⠀⠀⠀⡼⠀⠀⠀⠀⠀⠀⠀⢐⠎⣀⠉⠀⡈
|
||||
⢳⣏⢿⣹⣚⢧⡏⠡⢀⠃⢄⢻⡀⠀⠀⠘⢧⢋⢎⠻⡽⢿⢿⣿⣿⢿⡿⡟⢿⣹⡿⠁⠀⠀⠀⣸⠃⠀⠀⠀⠀⠀⠀⠀⠨⢆⡕⡘⠐⣃
|
||||
⣳⡾⣯⠷⠏⣿⠨⠁⠀⠈⢀⠊⢷⡀⠀⠀⠀⠁⠈⠁⠳⠌⠖⠌⠆⠃⠢⠽⠖⠉⠀⠀⠀⢀⣼⠃⢀⠀⠀⠀⠀⠀⠀⠀⠨⢇⠰⠉⠂⠀
|
||||
⣏⢷⡱⣎⢣⣏⠧⡀⠀⠀⠀⡜⢠⠿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⠟⠡⠈⠀⠀⠀⠀⠀⠀⠀⠀⢘⡆⠑⡂⠀⠀
|
||||
⡞⣦⢖⡫⣅⡾⢣⠅⡀⠀⡐⠤⢣⣻⢿⣿⣿⣶⣶⣤⣄⣀⣀⣀⣀⣀⣀⣠⣤⣶⡾⣟⡿⣥⢃⠂⠄⠀⠀⠀⠀⠀⠀⠀⡜⠤⢄⠀⠀⠀
|
||||
⣺⡅⢋⡑⣀⡿⣳⠌⡐⠀⠀⠀⠑⠸⢯⣿⣿⣿⢿⣿⢿⣿⢿⡿⣟⡿⣯⣟⣳⢧⣿⡿⣝⠢⣁⠂⠀⠀⠀⠀⠀⠀⠀⢀⠞⠀⠠⠔⠀⠂
|
||||
⡓⣆⢦⠳⡱⣹⢧⡷⣀⡀⠀⠀⠀⣀⠀⡀⣙⣿⣿⡿⣿⢾⣻⣽⣻⣽⣳⣯⣟⣿⣟⡳⠌⠂⠀⠀⠀⠀⠀⠀⠀⠀⠠⣜⠊⠤⠀⠀⠀⠀
|
||||
⣵⡼⣩⢮⣱⢯⣟⡷⣯⡽⣯⣻⡵⣮⣟⣽⣛⣿⣿⡿⣽⣯⣟⣾⣳⢯⣷⣻⣿⣿⣯⣷⡲⣄⡄⣠⣀⡀⣀⣀⡤⣔⠻⡄⢋⠜⠀⢠⡀⠀
|
||||
*/
|
||||
// Five at nit of fredyer
|
||||
// Hor hor
|
||||
export abstract class Plugin {
|
||||
abstract name: string;
|
||||
abstract rewriteTriggers: string[];
|
||||
renameTo?: string;
|
||||
abstract longLasting: boolean;
|
||||
|
||||
abstract rewriteFile(
|
||||
file: string,
|
||||
filePath: string
|
||||
): Promise<string | undefined | null | void>;
|
||||
}
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
if (!fs.existsSync("dist")) fs.mkdirSync("dist");
|
||||
|
||||
const plugins: Plugin[] = [];
|
||||
|
||||
for await (const file of fs.readdirSync(path.join("src", "plugins"))) {
|
||||
const clas = (await import(path.join(__dirname, "plugins", file))).default;
|
||||
const plug = new clas();
|
||||
|
||||
plugins.push(plug);
|
||||
}
|
||||
|
||||
export async function build() {
|
||||
try {
|
||||
fs.rmdirSync("dist");
|
||||
} catch {}
|
||||
|
||||
const sourceFiles = fs.readdirSync(path.join("website"), {
|
||||
recursive: true,
|
||||
withFileTypes: true,
|
||||
});
|
||||
const globalPlugins = plugins.filter((z) => z.rewriteTriggers.includes("*"));
|
||||
|
||||
for await (const file of sourceFiles) {
|
||||
if (!file.isFile()) continue;
|
||||
|
||||
const type = file.name.split(".").at(-1);
|
||||
if (!type) continue;
|
||||
|
||||
const shortname = file.name.slice(0, file.name.length - (type.length + 1));
|
||||
const availablePlugins = plugins.filter((z) =>
|
||||
z.rewriteTriggers.includes(type)
|
||||
);
|
||||
|
||||
if (availablePlugins.length == 0) {
|
||||
const oldPath = path.join(file.parentPath, file.name);
|
||||
fs.cpSync(oldPath, oldPath.replace("website", "dist"));
|
||||
}
|
||||
|
||||
for await (const plugin of availablePlugins) {
|
||||
const oldPath = path.join(file.parentPath, file.name);
|
||||
const newPath = path
|
||||
.join(
|
||||
file.parentPath,
|
||||
shortname +
|
||||
"." +
|
||||
(plugin.rewriteTriggers.includes("*") ? type : plugin.renameTo)
|
||||
)
|
||||
.replace("website", "dist");
|
||||
let data = fs.readFileSync(oldPath).toString("utf8");
|
||||
|
||||
for await (const globalPlugin of globalPlugins) {
|
||||
const rewritten = await globalPlugin.rewriteFile(data, oldPath);
|
||||
if (!rewritten) continue;
|
||||
data = rewritten;
|
||||
}
|
||||
|
||||
let rewrite = await plugin.rewriteFile(data, oldPath);
|
||||
|
||||
if (!rewrite) continue;
|
||||
|
||||
fs.mkdirSync(path.dirname(newPath), { recursive: true });
|
||||
fs.writeFileSync(newPath, rewrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await build();
|
||||
93
src/plugins/dev.ts
Normal file
93
src/plugins/dev.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import type { Server, ServerWebSocket } from "bun";
|
||||
import { Plugin, build } from "..";
|
||||
import * as fs from "fs";
|
||||
import mime from "mime-types";
|
||||
|
||||
const script = `let reconnectTimeout;function connect(){console.log("[--dev] connecting to dev server");let ws=new WebSocket("ws://localhost:8080");ws.addEventListener("message",message=>{if(message.data=="refresh"){location.reload()}});ws.addEventListener("open",()=>{console.log("[--dev] connected")});ws.addEventListener("close",()=>{console.log("[--dev] socket closed, restarting in 1s");clearTimeout(reconnectTimeout);reconnectTimeout=setTimeout(()=>{connect()},1e3)})}window.addEventListener("load",()=>connect());`;
|
||||
|
||||
export default class DevPlugin extends Plugin {
|
||||
name = "dev";
|
||||
rewriteTriggers = [];
|
||||
renameTo = undefined;
|
||||
longLasting = true;
|
||||
server!: Server;
|
||||
allConnections: ServerWebSocket<number>[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (!process.argv.includes("--dev")) return;
|
||||
|
||||
fs.watch(
|
||||
"./website",
|
||||
{
|
||||
recursive: true,
|
||||
},
|
||||
async (e, f) => {
|
||||
console.log("[dev] Noticed update in " + f + ", of type " + e + ".");
|
||||
this.allConnections.forEach((z) => z.send("refresh"));
|
||||
await build();
|
||||
}
|
||||
);
|
||||
|
||||
this.server = Bun.serve<number>({
|
||||
fetch(req, server) {
|
||||
const success = server.upgrade(req);
|
||||
if (success) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const url = new URL(req.url);
|
||||
|
||||
let cleanedPath = url.pathname;
|
||||
if (cleanedPath == "/") cleanedPath = "/index.html";
|
||||
if (cleanedPath.endsWith("/")) cleanedPath = cleanedPath.slice(0, -1);
|
||||
|
||||
let fsPath = "./dist" + cleanedPath;
|
||||
|
||||
if (fsPath.match(/\.\.\//g) !== null) {
|
||||
return undefined;
|
||||
}
|
||||
let rawFile;
|
||||
try {
|
||||
rawFile = fs.readFileSync(fsPath);
|
||||
} catch {
|
||||
return new Response("404 Not Found", {
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
const type = fsPath.split(".").at(-1);
|
||||
if (!type) return;
|
||||
if (type == "html") {
|
||||
|
||||
rawFile = rawFile.toString().replace(
|
||||
"<head>",
|
||||
`<head><script>${script}</script>`
|
||||
);
|
||||
}
|
||||
return new Response(rawFile, {
|
||||
headers: {
|
||||
"Content-Type": (mime.lookup(type) || "application/octet-stream") + "; charset=utf-8",
|
||||
},
|
||||
});
|
||||
},
|
||||
websocket: {
|
||||
open: (ws) => {
|
||||
ws.data = Math.random();
|
||||
|
||||
this.allConnections.push(ws);
|
||||
},
|
||||
message(ws, message) {},
|
||||
close: (ws) => {
|
||||
this.allConnections = this.allConnections.filter(
|
||||
(z) => z.data != ws.data
|
||||
);
|
||||
},
|
||||
},
|
||||
port: 8080,
|
||||
});
|
||||
}
|
||||
|
||||
async rewriteFile(file: string, filePath: string) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
22
src/plugins/markdown-compiler.ts
Normal file
22
src/plugins/markdown-compiler.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Plugin } from "..";
|
||||
import { marked } from "marked";
|
||||
import { parseMetadata } from "./markdown-metadata";
|
||||
|
||||
export default class MarkdownCompiler extends Plugin {
|
||||
name = "markdown-compiler";
|
||||
rewriteTriggers = ["md"]
|
||||
renameTo = "html"
|
||||
longLasting = false;
|
||||
|
||||
async rewriteFile(file: string, filePath: string) {
|
||||
let text = file;
|
||||
const metadata = parseMetadata(text);
|
||||
if(metadata) {
|
||||
let textSplit = text.split('\n');
|
||||
textSplit.splice(0, Object.keys(metadata).length);
|
||||
textSplit.unshift(metadata.title)
|
||||
text = textSplit.join("\n");
|
||||
}
|
||||
return await marked.parse(text);
|
||||
}
|
||||
}
|
||||
29
src/plugins/markdown-metadata.ts
Normal file
29
src/plugins/markdown-metadata.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Plugin } from "..";
|
||||
|
||||
export function parseMetadata(file: string) {
|
||||
if (!/^=+$/gm.test(file)) return;
|
||||
const splitfile = file.split("\n");
|
||||
let properties: Record<string, string> | undefined;
|
||||
for (let i = 0; i < splitfile.length; i++) {
|
||||
if (!properties) properties = {};
|
||||
const line = splitfile[i];
|
||||
if (/^=+$/gm.test(line)) break;
|
||||
const parts = line.split("=");
|
||||
if (parts.length !== 2) break;
|
||||
properties[parts[0].trim()] = parts[1].trim();
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
export default class MarkdownMetadataGenerator extends Plugin {
|
||||
name = "markdown-metadata";
|
||||
rewriteTriggers = ["md"];
|
||||
renameTo = "json";
|
||||
longLasting = false;
|
||||
|
||||
async rewriteFile(file: string, filePath: string) {
|
||||
const metadata = parseMetadata(file);
|
||||
if (!metadata) return;
|
||||
return JSON.stringify(metadata);
|
||||
}
|
||||
}
|
||||
41
src/plugins/ts-compiler.ts
Normal file
41
src/plugins/ts-compiler.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { Plugin } from "..";
|
||||
import * as fs from "fs";
|
||||
import * as esbuild from "esbuild";
|
||||
|
||||
export default class TSCompiler extends Plugin {
|
||||
name = "ts-compiler";
|
||||
rewriteTriggers = ["ts", "tsx", "jsx"];
|
||||
renameTo = "js";
|
||||
longLasting = false;
|
||||
|
||||
minify = false;
|
||||
constructor() {
|
||||
super();
|
||||
if(process.argv.includes("--prod")) {
|
||||
this.minify = true;
|
||||
}
|
||||
}
|
||||
async rewriteFile(file: string, filePath: string) {
|
||||
|
||||
const result = await esbuild.build({
|
||||
stdin: {
|
||||
contents: file,
|
||||
sourcefile: filePath.split("/").at(-1),
|
||||
loader: "ts"
|
||||
},
|
||||
write: false,
|
||||
outdir: 'out',
|
||||
minify: this.minify
|
||||
});
|
||||
|
||||
if(result.errors.length != 0) {
|
||||
console.log("TS compiler errored.")
|
||||
result.errors.forEach(element => {
|
||||
console.error(element);
|
||||
});
|
||||
} else {
|
||||
const output = result.outputFiles[0].contents;
|
||||
return (new TextDecoder()).decode(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/plugins/variables.ts
Normal file
36
src/plugins/variables.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { Plugin } from "..";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
export default class Variables extends Plugin {
|
||||
name = "variables";
|
||||
rewriteTriggers = ["html", "*"];
|
||||
renameTo = undefined;
|
||||
longLasting = false;
|
||||
|
||||
variables: Record<string, string> = {};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.variables["__BLOG_POSTS__"] = JSON.stringify(
|
||||
fs.readdirSync("./website/blogs")
|
||||
);
|
||||
const templatePath = path.resolve(__dirname, "../../website/templates");
|
||||
if (fs.existsSync(templatePath)) {
|
||||
for (const file of fs.readdirSync(templatePath)) {
|
||||
const id = file.toUpperCase().replace(".HTML", "");
|
||||
this.variables["__TEMPLATE_" + id + "__"] = fs
|
||||
.readFileSync(path.join(templatePath, file))
|
||||
.toString("utf8");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async rewriteFile(file: string, filePath: string): Promise<string> {
|
||||
let prevfile = file;
|
||||
for (const a of Object.entries(this.variables)) {
|
||||
prevfile = prevfile.replaceAll(a[0], a[1]);
|
||||
}
|
||||
return prevfile;
|
||||
}
|
||||
}
|
||||
97
src/readme.md
Normal file
97
src/readme.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# sad.ovh build system
|
||||
|
||||
design goals:
|
||||
1. rewrite and generate custom HTML/TS/JS/whatever..
|
||||
2. allow for variables and other important features in buildsystem->html
|
||||
3. plugins, this ties together the top two
|
||||
4. HMR and HTML/CSS reloading (in the Dev plugin)
|
||||
5. Rewriteable file renaming
|
||||
6. Every plugin runs on the same set of files, and can rename and reinject multiple times
|
||||
|
||||
Psudeo-code build.ts
|
||||
|
||||
```ts
|
||||
class Plugin {
|
||||
.. whatever's needed to support the plugin system ..
|
||||
}
|
||||
|
||||
if(.. not exist dist ..)
|
||||
.. make dist ..
|
||||
const plugins: Plugin[] = [];
|
||||
.. dynamically load plugins into plugins ..
|
||||
|
||||
const srcFiles: File[] = []
|
||||
.. get files into file ..
|
||||
|
||||
|
||||
export function build () {
|
||||
for(const file of srcFiles) {
|
||||
const plugins = plugins.filter(z=>z.rewriteTriggers(file.type));
|
||||
if(plugins.length == 0) continue;
|
||||
|
||||
for (const plugin of plugins) {
|
||||
let filename = file.name + "." + file.type;
|
||||
let newFilename = path.join("dist", file.name + "." + plugin.renameTo || file.type);
|
||||
|
||||
fs.writeFileSync(newFilename, plugin.rewriteFile(fs.readFileSync(filename), filename))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
build();
|
||||
|
||||
```
|
||||
Psudeo-code plugins
|
||||
```ts
|
||||
class DevPlugin extends Plugin {
|
||||
rewriteTriggers: ["html"]
|
||||
renameTo: undefined
|
||||
longLasting: false;
|
||||
.. websocket server ..
|
||||
constructor(.. options from argv of the main executable..) {
|
||||
.. ran on start of main index.ts executable ..
|
||||
.. this would be long lasting, so leave if not using --dev ..
|
||||
|
||||
.. use a method on the server that rebuilds if required ..
|
||||
.. Would run a web server too ..
|
||||
|
||||
}
|
||||
|
||||
rewriteFile(file: string, filePath: string) {
|
||||
return file.replace("<head>", `<head><script>${script}</script>`)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
class TSCompiler extends Plugin {
|
||||
rewriteTriggers: ["ts"]
|
||||
renameTo: "js"
|
||||
|
||||
rewriteFile(file: string, filePath: string) {
|
||||
// use SWC or TS or esbuild, whatever
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
class MarkdownMetadataGenerator extends Plugin {
|
||||
rewriteTriggers: ["md"]
|
||||
renameTo: "json"
|
||||
|
||||
rewriteFile(file: string, filePath: string) {
|
||||
return marked.parse(file);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
class MarkdownCompiler extends Plugin {
|
||||
rewriteTriggers: ["md"]
|
||||
renameTo: "html"
|
||||
|
||||
rewriteFile(file: string, filePath: string) {
|
||||
return marked.parse(file);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="preconnect" href="https://rsms.me/">
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>soph's blog and information</title>
|
||||
<meta name="title" content="soph's blog and information" />
|
||||
<meta name="description" content="yourfriend.lol v2, soph's blog and information"" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property=" og:type" content="website" />
|
||||
<meta property="og:url" content="https://sad.ovh" />
|
||||
<meta property="og:title" content="soph's blog and information" />
|
||||
<meta property="og:description" content="yourfriend.lol v2, soph's blog and information" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://sad.ovh" />
|
||||
<meta property="twitter:title" content="soph's blog and information" />
|
||||
<meta property="twitter:description" content="yourfriend.lol v2, soph's blog and information"" />
|
||||
|
||||
<meta name=" viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kimeiga/bahunya/dist/bahunya.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1> sophie's blog </h1>
|
||||
<a id="return_back" href="/blog.html" style="display:none;font-size:xx-large;">return back?</a>
|
||||
<div>Scroll to bottom for comments ↓</div>
|
||||
<ul id="html_list" style="display: none">
|
||||
</ul>
|
||||
<div id="renderer"></div>
|
||||
<div style="color:red;font-size:larger;font-weight:bolder;display:none;" id="error">
|
||||
Blog post <span></span> does not exist.
|
||||
</div>
|
||||
<div class="giscus"></div>
|
||||
<script src="https://giscus.app/client.js" data-repo="fucksophie/blog_comments" data-repo-id="R_kgDOMY4cfw"
|
||||
data-category="General" data-category-id="DIC_kwDOMY4cf84ChCRR" data-mapping="url" data-strict="1"
|
||||
data-reactions-enabled="1" data-emit-metadata="0" data-input-position="bottom" data-theme="noborder_dark"
|
||||
data-lang="en" data-loading="lazy" crossorigin="anonymous" async>
|
||||
</script>
|
||||
<script type="module">
|
||||
function parseMetadata(file) {
|
||||
const splitfile = file.split("\n");
|
||||
const properties = {};
|
||||
for (let i = 0; i < splitfile.length; i++) {
|
||||
const line = splitfile[i];
|
||||
if (/^=+$/gm.test(line)) break;
|
||||
const parts = line.split("=");
|
||||
if (parts.length !== 2) break;
|
||||
properties[parts[0].trim()] = parts[1].trim();
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
const uriParams = new URLSearchParams(location.search);
|
||||
if (uriParams.has("md")) {
|
||||
const error = document.getElementById("error");
|
||||
const renderer = document.getElementById('renderer');
|
||||
const return_back = document.getElementById("return_back");
|
||||
const req = await fetch("/blogs/" + uriParams.get("md"));
|
||||
const giscus = document.querySelector(".giscus");
|
||||
if (req.status != 200) {
|
||||
error.style.display = "block";
|
||||
giscus.style.display = "none";
|
||||
} else {
|
||||
let text = await req.text();
|
||||
const metadata = parseMetadata(text);
|
||||
text = text.split('\n');
|
||||
text.splice(0, Object.keys(metadata).length);
|
||||
text.unshift(metadata.title)
|
||||
text = text.join("\n");
|
||||
renderer.innerHTML = marked.parse(text)
|
||||
}
|
||||
return_back.style.display = "block";
|
||||
} else {
|
||||
const blog_posts = ["opensource-watch-comparison.md", "raspberry-pi-struggles.md", "why-i-syncthing.md"];
|
||||
const html_list = document.getElementById("html_list");
|
||||
html_list.style.display = "block";
|
||||
for (const blog_post of blog_posts) {
|
||||
const req = await fetch("/blogs/" + blog_post);
|
||||
const blog_post_contents = await req.text();
|
||||
let metadata;
|
||||
try {
|
||||
metadata = parseMetadata(blog_post_contents);
|
||||
} catch {
|
||||
console.error("ran into issue loading metadata for blog post " + blog_post)
|
||||
continue;
|
||||
}
|
||||
const li = document.createElement("li");
|
||||
const a = document.createElement("a");
|
||||
a.href = "/blog.html?md=" + encodeURIComponent(blog_post);
|
||||
a.innerText = `${metadata.title} (created ${new Date(metadata.time * 1000).toGMTString()})`;
|
||||
li.appendChild(a);
|
||||
html_list.appendChild(li);
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
title = Opensource watch comparison
|
||||
time = 1721340000
|
||||
========================
|
||||
|
||||
This has been a topic I've been very interested for very long. There's been three main opensource watches, those being the Bangle.js 2, ZSwatch, and Pine64 Pinetime. These all have their pluses and minuses, which I'll talk about in this post. As a disclaimar, I don't own any of these. I'm writing this exact blog post to compare and figure out which one of these is the best one. If there is a best one.
|
||||
|
||||
There is a different talk about other watches, that aren't opensource by themselves, but can be very easily modified to run [opensource firmwares](https://wasp-os.readthedocs.io/en/latest/install.html#device-support) (also see Gadgetbridge's supported gadgets). In my opinion, these watches are sold only for a very small amount of time, and usually don't work on any opensource apps such as Gadgetbridge. I will not be talking about them in this blog post, because I honestly think that they are not a point for comparison.
|
||||
|
||||
## Comparison
|
||||
| Watch name | Price | Prebuilt? | HR sensor | Environmental sensor? | Pressure sensor? | Flash size | GPS? | Temperature? | External SWD? | Score |
|
||||
|-------------|----------------|-----------|-----------|-----------------------|------------------|------------|------|--------------|---------------|-----------------|
|
||||
| Bangle.js 2 | 91,08 EUR | ✅ | ✅ | ✅ | ✅ | 8MB | ✅ | ✅ | ✅ | 7/11 (+0 Price) |
|
||||
| Pinetime | 24,78 EUR | ✅ | ✅ | ❌ | ❌ | 4MB | ❌ | ❌ | ❌ (sealed) | 6/11 (+4 Price) |
|
||||
| ZSWatch | Around 40 euro | ❌ | ❌ | ✅ | ✅ | 16MB | ❌ | ✅ | ✅ | 6/10 (+2 Price) |
|
||||
|
||||
## Conclusion
|
||||
In my opinion, every single one of these watches is a good option. However, if you have extra money to spend, the Bangle.js 2 is EASILY the best option. If you are on a budget, choose the pinetime. And if you have a lot of time and energy to build a ZSWatch, then do that.
|
||||
|
||||
## Links
|
||||
[Bangle.js 2](https://shop.espruino.com/banglejs2)
|
||||
[ZSWatch](https://github.com/jakkra/ZSWatch?tab=readme-ov-file)
|
||||
[PineTime](https://pine64.com/product/pinetime-smartwatch-sealed/)
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
title = raspberry pi struggles
|
||||
time = 1717925441
|
||||
========================
|
||||
|
||||
recently, i have been experiencing struggles with my raspberry pi
|
||||
here i will list all of the issues, maybe you have solutions or something
|
||||
## git randomly breaking
|
||||
this is entierly spontenous, and happens on every distro i've used for RPi3. it just SEGFAULT's after I update the system, and nothing helps, even uninstalling and reinstalling doesn't work.
|
||||
## stdio.h
|
||||
stdio.h has broken on me multiple times, to the point where it starts pulling in fortran sources and other bullshit, forcing me to use qemu on my own PC to compile stuff
|
||||
## random overheating ???
|
||||
my RPi has 0 load, 0 anything. absolutely nothing is being done on it, like 3 processes
|
||||
and it still randomly overheats, to the point where the case has started to droop. it is literally
|
||||
melting before my eyes
|
||||
|
||||
there is something either very wrong with my raspberry Pi, or I've broken something. at this point all I've been using has been STM32's and ESP8266's for my projects, or a hetzner dedi. i don't know why I even maintain most of my own hardware at this point, it serves 0 purpose if I could just parsec into a server with a 4090
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
title = Why I ❤️ Syncthing
|
||||
time = 1711916100
|
||||
========================
|
||||
|
||||
I recently moved from Dropbox to Syncthing. It's been one of the best experiences in a long while.
|
||||
## Rationale
|
||||
Dropbox had limited devices, while I wanted to sync multiple devices (such as my phone, laptop and PC and RPi), and it was already hitting that limit. I didn't want to pay for a service which I could self host and use entierly independently of any company. It didn't seem hard to at all to setup Syncthing, as I had found it while scrolling friend's discord servers and finding it there.
|
||||
## How to do it
|
||||
Syncthing might seem complicated to use and maintain, but it's actually not at all complicated.
|
||||
First, you have to have at least two devices. These can be absolutely anything, a VPS, a dedicated server, a PC, a phone, a SBC (such as a RPi, BananaPI, etc). After that you have to install syncthing on one of them (your most reliable one) and then follow https://docs.syncthing.net/intro/getting-started.html to install it. Then, grab your phone, whatever it may be, your second device. Let's say it's a laptop, to ease the setup. You have to install syncthing on that laptop too. Then grab it's device ID (the getting started guide would have thought you this), and then just add the device into the main syncthing. This is painless, happens in less than a minute and works through NAT, many firewalls, etc. I have a RPi with a 2TB harddrive setup in my collage dorms, and one on my dedicated server, and one on my laptop and one of my phone. This gives me insane reliability, if the dedicated server goes down, the syncing will resume once it's back. It's entierly painless, once your RPi goes down your data is still perfectly accessible on your server, laptop and phone.
|
||||
## The experience 4 weeks in
|
||||
It has been absolutely amazing. One time I had to switch the main server from a VPS to a dedicated server, which was pretty easy. I have had almost 0 hurdles, only issue I ever had was Android not supporting certain filenames (names with |\\?*<\":>+[]/' characters), which then caused a desync. Once I removed these characters from my filenames, it instantly synced and worked perfectly. I have ran into 0 other issues, since that. Absolutely smooth sailing
|
||||
## Comparison
|
||||
| Options | Dropbox | Syncthing | Google Drive (one) | Nextcloud | OneDrive |
|
||||
| -------------------------- | ------------------------------- | -------------------- | :------------------ | ----------------------------- | ------------------------------ |
|
||||
| Versioning | 30 days on basic and low plans | Configurable, (♾️) | No idea, not listed | Configurable, (♾️) | No idea, not listed |
|
||||
| Price | 2.09eur (100gb), 10.99eur (2tb) | Server hosting costs | 12USD (2TB) | Server hosting costs | 1.99USD (100gb), 6.99USD (1TB) |
|
||||
| Opensource | No | Yes | No | Yes | No |
|
||||
| 2FA | Yes | No | Yes | Yes | Yes |
|
||||
| Encryption/Encrypted files | No | Yes | Yes | Yes | No |
|
||||
| Free data | 2gb | ♾️ | 15gb | ♾️(if no hosting, 1gb to 8gb) | 5gb |
|
||||
## Final Opinions
|
||||
If you need less than 15GB, just use Google Drive, Dropbox or Onedrive. These also usually come with effords to sync them properly, and don't work on all devices. If you need more than let's say, 1TB, just buy a server from https://contabo.com/en/ or https://www.hetzner.com/sb/ for about the same price, and a LOT more use out of your money.
|
||||
|
||||
(I am not endorsed by any of these websites.)
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.2/p5.min.js"
|
||||
integrity="sha512-eu9vkh+EbAsW3fMmPTj/DP5W3UegIdu0Z/OABMocvoofx43MYBkcQ9hRIVxZndV1vcCYQwBg+U1PkWl04TD0Jg=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<link rel="preconnect" href="https://rsms.me/">
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>sophie's personal site</title>
|
||||
<meta name="title" content="sophie's personal site" />
|
||||
<meta name="description" content="Wow! :3 Meowww.. Nyaaa! sad.ovh, yourfriend.lol v2. Has a blog too . (maybe)
|
||||
Nyaaaaaa Kitty website :3" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://sad.ovh" />
|
||||
<meta property="og:title" content="sophie's personal site" />
|
||||
<meta property="og:description" content="Wow! :3 Meowww.. Nyaaa! sad.ovh, yourfriend.lol v2. Has a blog too . (maybe)
|
||||
Nyaaaaaa Kitty website :3" />
|
||||
<meta property="og:image" content="https://sad.ovh/Screenshot_2023-02-04_16-45-32.png" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://sad.ovh" />
|
||||
<meta property="twitter:title" content="sophie's personal site" />
|
||||
<meta property="twitter:description" content="Wow! :3 Meowww.. Nyaaa! sad.ovh, yourfriend.lol v2. Has a blog too . (maybe)
|
||||
Nyaaaaaa Kitty website :3" />
|
||||
<meta property="twitter:image" content="https://sad.ovh/Screenshot_2023-02-04_16-45-32.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="dist.js"></script>
|
||||
|
||||
<style>
|
||||
body[data-theme="light"] {
|
||||
color: #31363F;
|
||||
}
|
||||
|
||||
body[data-theme="dark"] {
|
||||
color: #eeeeee;
|
||||
}
|
||||
|
||||
body[data-theme="dark"]>.center>a {
|
||||
color: #eeeeee !important;
|
||||
}
|
||||
|
||||
body[data-theme="light"]>.center>a {
|
||||
color: #31363F !important;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: Inter, sans-serif;
|
||||
font-feature-settings: 'liga' 1, 'calt' 1;
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
:root {
|
||||
font-family: InterVariable, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<span class="center">
|
||||
<div>
|
||||
<button class="selected" id="home-button">home</button> | <button id="donations-button">donations</button> |
|
||||
<a href="/blog.html"><button>blog</button></a>
|
||||
</div>
|
||||
<span id="home">
|
||||
I'm Latvian, 17. My name's Sophie. I love listening to music, I have <span
|
||||
id="songplays"><loading...></span> song plays.
|
||||
<br>
|
||||
I am a JS/TS developer, mostly specializing in backend work.
|
||||
<br>
|
||||
I play minecraft, and upgun with friends. I have developed many bukkit plugins for Paper/Folia/Spigot
|
||||
before, and am pretty well versed in them.
|
||||
<br>
|
||||
Contact me at <a href="https://matrix.to/#/@yourfriend:bark.lgbt">matrix</a>, <a
|
||||
href="https://discord.com/users/845374523263811614">discord</a>, <a
|
||||
href="https://github.com/fucksophie">github (view projects here)</a>, <a
|
||||
href="https://git.sad.ovh/sophie">sadgit</a>, or <a href="https://bark.lgbt/@yourfriend">mastodon</a>
|
||||
<br>
|
||||
<a href="/key.txt">Here's my PGP key.</a>
|
||||
</span>
|
||||
<span id="donations" style="display:none">
|
||||
<ul>
|
||||
<li>BTC bc1q83jdukjn4a2qm0rmn9tqcfkcq60la22lqy2shx</li>
|
||||
<li>ETH/BSC/<strong>USDT</strong>/USDC (send via BSC) 0xc691cd8950Fdf96Faa2aCA1CA9b4B3Fd5B2a44BB</li>
|
||||
<li>SOL 79NKoiXaPzbwbsD5MFKKwmoeEPKtTsoQFfx64MHmULF7</li>
|
||||
<li><strong>XMR
|
||||
42iW3icQrybKYieQNSrm76dXetuXD6HaxZDijajXkge7GTSKVG4NefxBj3mbWudpY62dxRTihm4beJgy36X8xFKCTWpVAjS</strong>
|
||||
</li>
|
||||
</ul>
|
||||
OR <a href="https://ko-fi.com/sophskofi">kofi</a> OR, dm for paypal
|
||||
<br>
|
||||
<img src="/sticker.webp" alt="monero-chan" style="width:128px;">
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span style="position:absolute;left:10px;bottom:10px">
|
||||
Click <kbd>spacebar</kbd> to change the themes.
|
||||
</span>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mDMEZizohxYJKwYBBAHaRw8BAQdAeSabI0imbtNWIlSSNKVd7uvos9sE9/IbvaQz
|
||||
pgqHw3S0HWkgaGVhcnQgcGdwIDxzb2ZpamFAZHVjay5jb20+iJkEExYKAEEWIQQ0
|
||||
8hTtyOXLi7xHvkHtpdIioMJw8gUCZizohwIbAwUJBaNwiQULCQgHAgIiAgYVCgkI
|
||||
CwIEFgIDAQIeBwIXgAAKCRDtpdIioMJw8p7lAP9M/p4eWBd1E4VBfcilUpOoZ1bG
|
||||
kcd+zzAsjezIK/gO+AD+MXkykqgGE2EvEqCNHfzWkKnPguxkZH56YYTu8YVSwgG4
|
||||
OARmLOiHEgorBgEEAZdVAQUBAQdAT0SaQuH8xRA7q//yz3hmXZoWrqWj4n7v1wIB
|
||||
m1fvjnYDAQgHiH4EGBYKACYWIQQ08hTtyOXLi7xHvkHtpdIioMJw8gUCZizohwIb
|
||||
DAUJBaNwiQAKCRDtpdIioMJw8kkOAQDZ+sd9PuGquNSn7kKktpNunVhIYxo9nXhF
|
||||
q+7kU7x8ywEA2cIdm+0dQYYm65YreaWfcOR4DJPz9sFhvCh6M4xoHg0=
|
||||
=qcUB
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
|
|
@ -1,381 +0,0 @@
|
|||
/*
|
||||
The original version of this ProcessingJava script was written by
|
||||
Jason Labbe (available here: https://openprocessing.org/sketch/377231/)
|
||||
|
||||
This is a highly modified version by Sophie, firstly re-written using JS, and then
|
||||
improved using newer techniques (no more pGraphics, using font toPoints now as an example).
|
||||
Jason Labbe's version is licensed under CC BY-SA 3.0 DEED, and so is this version.
|
||||
|
||||
https://creativecommons.org/licenses/by-sa/3.0/deed.en
|
||||
*/
|
||||
|
||||
/*
|
||||
You won't believe what they say about us, bad PR
|
||||
Bad chemistry between the 9 and 3-Star
|
||||
Only real fanatics know, TL;DR
|
||||
In the ER, at the hotel, two key-cards
|
||||
GTB, they still want to hear "GT-R"
|
||||
Black Discharge top, G-Star Raw's
|
||||
Sing Peroxide on the stage in B-sharp
|
||||
black windows on the strip, black Dodge Charge
|
||||
*/
|
||||
|
||||
// Global variables
|
||||
let particles: Particle[] = [];
|
||||
let font: p5.Font;
|
||||
let bgColor: p5.Color;
|
||||
|
||||
let darkMode = false;
|
||||
|
||||
let darkTheme: p5.Color;
|
||||
let lightTheme: p5.Color;
|
||||
|
||||
interface Bounds {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
function generateRandomPos(x: number, y: number, mag: number) {
|
||||
let randomDir = createVector(random(0, width), random(0, height));
|
||||
|
||||
let pos = createVector(x, y);
|
||||
pos.sub(randomDir);
|
||||
pos.normalize();
|
||||
pos.mult(mag);
|
||||
pos.add(x, y);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
function getTextSize(text: string, font: p5.Font) {
|
||||
let fontSize = 1200; // Wait, doesn't this impact performance heavily?
|
||||
// Nope, a single iteration (once warmed up) only takes around
|
||||
// 0.04799999999254942ms. This means, that for a 8K screen it'd take
|
||||
// 11ms, and for normal 1080P screen it takes 20-40ms (891 iterations).
|
||||
// This can be optimized further, but it's a single run of this function
|
||||
// every time the word is rerendered. I don't think it matters nearly as much..
|
||||
// Pouring too much time into this single number.. what the hell
|
||||
|
||||
let bounds: Bounds;
|
||||
|
||||
//let iters = 0;
|
||||
//let time = 0;
|
||||
do {
|
||||
//const loopstart = performance.now();
|
||||
fontSize--;
|
||||
bounds = font.textBounds(text, 0, 0, fontSize) as Bounds;
|
||||
// const loopend = performance.now();
|
||||
// time += loopend - loopstart;
|
||||
// iters += 1;
|
||||
} while (bounds.w > windowWidth - 100);
|
||||
//console.log("Took " + time + "ms for " + iters + " iterations");
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
class Particle {
|
||||
pos: p5.Vector;
|
||||
vel: p5.Vector;
|
||||
acc: p5.Vector;
|
||||
|
||||
target: p5.Vector;
|
||||
|
||||
closeEnoughTarget: number;
|
||||
maxSpeed: number;
|
||||
maxForce: number;
|
||||
particleSize: number;
|
||||
isKilled: boolean;
|
||||
startColor: p5.Color;
|
||||
targetColor: p5.Color;
|
||||
colorWeight: number;
|
||||
colorBlendRate: number;
|
||||
|
||||
constructor() {
|
||||
this.pos = createVector(0, 0);
|
||||
this.vel = createVector(0, 0);
|
||||
this.acc = createVector(0, 0);
|
||||
this.target = createVector(0, 0);
|
||||
|
||||
this.closeEnoughTarget = 50;
|
||||
this.maxSpeed = 4.0;
|
||||
this.maxForce = 0.1;
|
||||
this.particleSize = 5;
|
||||
this.isKilled = false;
|
||||
|
||||
this.startColor = color(0);
|
||||
this.targetColor = color(0);
|
||||
this.colorWeight = 0;
|
||||
this.colorBlendRate = 0.025;
|
||||
}
|
||||
|
||||
move() {
|
||||
// Check if particle is close enough to its target to slow down
|
||||
let proximityMult = 1.0;
|
||||
let distance = dist(this.pos.x, this.pos.y, this.target.x, this.target.y);
|
||||
if (distance < this.closeEnoughTarget) {
|
||||
proximityMult = distance / this.closeEnoughTarget;
|
||||
}
|
||||
|
||||
//.push force towards target
|
||||
let towardsTarget = createVector(this.target.x, this.target.y);
|
||||
towardsTarget.sub(this.pos);
|
||||
towardsTarget.normalize();
|
||||
towardsTarget.mult(this.maxSpeed * proximityMult);
|
||||
|
||||
let steer = createVector(towardsTarget.x, towardsTarget.y);
|
||||
steer.sub(this.vel);
|
||||
steer.normalize();
|
||||
steer.mult(this.maxForce);
|
||||
this.acc.add(steer);
|
||||
|
||||
// Move particle
|
||||
this.vel.add(this.acc);
|
||||
this.pos.add(this.vel);
|
||||
this.acc.mult(0);
|
||||
}
|
||||
|
||||
draw() {
|
||||
// Draw particle
|
||||
let currentColor = lerpColor(
|
||||
this.startColor,
|
||||
this.targetColor,
|
||||
this.colorWeight
|
||||
);
|
||||
|
||||
noStroke();
|
||||
fill(currentColor);
|
||||
ellipse(this.pos.x, this.pos.y, this.particleSize, this.particleSize);
|
||||
|
||||
// Blend towards its target color
|
||||
if (this.colorWeight < 1.0) {
|
||||
this.colorWeight = min(this.colorWeight + this.colorBlendRate, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
kill() {
|
||||
if (!this.isKilled) {
|
||||
// Set its target outside the scene
|
||||
let randomPos = generateRandomPos(
|
||||
width / 2,
|
||||
height / 2,
|
||||
(width + height) / 2
|
||||
);
|
||||
this.target.x = randomPos.x;
|
||||
this.target.y = randomPos.y;
|
||||
|
||||
// Begin blending its color to black
|
||||
this.startColor = lerpColor(
|
||||
this.startColor,
|
||||
this.targetColor,
|
||||
this.colorWeight
|
||||
);
|
||||
this.targetColor = color(0);
|
||||
this.colorWeight = 0;
|
||||
|
||||
this.isKilled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function preload() {
|
||||
font = loadFont("https://rsms.me/inter/font-files/InterVariable.ttf");
|
||||
darkTheme = color(34, 40, 49, 255);
|
||||
lightTheme = color(238, 238, 238, 255);
|
||||
}
|
||||
|
||||
function nextWord(word: string) {
|
||||
const fontSize = getTextSize(word, font);
|
||||
const bounds = font.textBounds(word, 0, 0, fontSize) as Bounds;
|
||||
let sampleFactor = 0.2;
|
||||
|
||||
if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)){
|
||||
sampleFactor = 0.1;
|
||||
}
|
||||
|
||||
let points = font.textToPoints(
|
||||
word,
|
||||
(windowWidth - 25 - bounds.w) / 2,
|
||||
(windowHeight + bounds.h / 2) / 2,
|
||||
fontSize,
|
||||
{
|
||||
sampleFactor,
|
||||
simplifyThreshold: 0,
|
||||
}
|
||||
);
|
||||
|
||||
let newColor = color(
|
||||
random(0.0, 255.0),
|
||||
random(0.0, 255.0),
|
||||
random(0.0, 255.0)
|
||||
);
|
||||
|
||||
let particleCount = particles.length;
|
||||
let particleIndex = 0;
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
let randomIndex = Math.floor(random(0, points.length));
|
||||
const point = points[randomIndex];
|
||||
|
||||
let newParticle;
|
||||
|
||||
if (particleIndex < particleCount) {
|
||||
newParticle = particles[particleIndex];
|
||||
newParticle.isKilled = false;
|
||||
particleIndex += 1;
|
||||
} else {
|
||||
newParticle = new Particle();
|
||||
|
||||
let randomPos = generateRandomPos(
|
||||
width / 2,
|
||||
height / 2,
|
||||
(width + height) / 2
|
||||
);
|
||||
newParticle.pos.x = randomPos.x;
|
||||
newParticle.pos.y = randomPos.y;
|
||||
|
||||
newParticle.maxSpeed = random(2.0, 5.0);
|
||||
newParticle.maxForce = newParticle.maxSpeed * 0.025;
|
||||
newParticle.particleSize = random(3, 6);
|
||||
newParticle.colorBlendRate = random(0.0025, 0.03);
|
||||
|
||||
particles.push(newParticle);
|
||||
}
|
||||
|
||||
newParticle.startColor = lerpColor(
|
||||
newParticle.startColor,
|
||||
newParticle.targetColor,
|
||||
newParticle.colorWeight
|
||||
);
|
||||
newParticle.targetColor = newColor;
|
||||
newParticle.colorWeight = 0;
|
||||
|
||||
newParticle.target.x = point.x;
|
||||
newParticle.target.y = point.y;
|
||||
}
|
||||
|
||||
if (particleIndex < particleCount) {
|
||||
for (let i = particleIndex; i < particleCount; i++) {
|
||||
let particle = particles[i];
|
||||
particle.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function windowResized() {
|
||||
resizeCanvas(windowWidth, windowHeight);
|
||||
}
|
||||
|
||||
function setup() {
|
||||
// selection
|
||||
let pages = ["home", "donations"];
|
||||
let selected = "home";
|
||||
pages.forEach(z => {
|
||||
const buttonElement = document.getElementById(z+"-button")!;
|
||||
const element = document.getElementById(z)!;
|
||||
buttonElement.addEventListener("click", _ => {
|
||||
if(z !== selected) {
|
||||
const selectedElement = document.getElementById(selected)!
|
||||
const selectedButton = document.getElementById(selected+"-button")!
|
||||
selectedElement.style.display = "none";
|
||||
element.style.display = "block";
|
||||
selectedButton.className = ""
|
||||
buttonElement.className = "selected";
|
||||
selected = z;
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// lastfm
|
||||
(async () => {
|
||||
const lastfm = await fetch(
|
||||
"https://ws.audioscrobbler.com/2.0/?method=user.getinfo&user=yourfriendoss&api_key=8d983789c771afaeb7412ac358d4bad0&format=json"
|
||||
);
|
||||
const json = await lastfm.json();
|
||||
document.getElementById("songplays")!.innerText = json.user.playcount;
|
||||
})();
|
||||
|
||||
const theme = localStorage.getItem("theme");
|
||||
if (theme == "dark") {
|
||||
bgColor = darkTheme;
|
||||
darkMode = true;
|
||||
} else {
|
||||
bgColor = lightTheme;
|
||||
darkMode = false;
|
||||
}
|
||||
document.body.setAttribute("data-theme", darkMode ? "dark" : "light");
|
||||
const canvas = createCanvas(windowWidth, windowHeight);
|
||||
canvas.position(0, 0);
|
||||
canvas.style("zIndex", "-1");
|
||||
background(bgColor);
|
||||
const p = "sad.ovh"
|
||||
nextWord(p);
|
||||
|
||||
let ticks = 0;
|
||||
|
||||
setInterval(() => {
|
||||
const a = particles.filter(z => {
|
||||
const x = Math.abs(Math.floor(z.vel.x))
|
||||
const y = Math.abs(Math.floor(z.vel.y))
|
||||
if (x == 1 || y == 1 || x == 0 || y == 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}).length;
|
||||
|
||||
if (a == 0) ticks++;
|
||||
if (a != 0) ticks = 0;
|
||||
|
||||
if (ticks == 60 * 8) {
|
||||
ticks = 0;
|
||||
nextWord(p)
|
||||
}
|
||||
}, 1)
|
||||
}
|
||||
function keyPressed(a: KeyboardEvent) {
|
||||
if (a.code == "Space") {
|
||||
if (darkMode) {
|
||||
bgColor = lightTheme;
|
||||
} else {
|
||||
bgColor = darkTheme;
|
||||
}
|
||||
darkMode = !darkMode;
|
||||
document.body.setAttribute("data-theme", darkMode ? "dark" : "light");
|
||||
localStorage.setItem("theme", darkMode ? "dark" : "light");
|
||||
}
|
||||
}
|
||||
function draw() {
|
||||
fill(bgColor);
|
||||
noStroke();
|
||||
rect(0, 0, width * 2, height * 2);
|
||||
|
||||
for (let x = particles.length - 1; x > -1; x--) {
|
||||
let particle = particles[x];
|
||||
particle.move();
|
||||
particle.draw();
|
||||
|
||||
if (particle.isKilled) {
|
||||
if (
|
||||
particle.pos.x < 0 ||
|
||||
particle.pos.x > width ||
|
||||
particle.pos.y < 0 ||
|
||||
particle.pos.y > height
|
||||
) {
|
||||
particles.splice(x, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mouseDragged() {
|
||||
if (mouseButton == LEFT) {
|
||||
for (const particle of particles) {
|
||||
if (dist(particle.pos.x, particle.pos.y, mouseX, mouseY) < 50) {
|
||||
particle.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue