From f5e29189f70c5c8532916504a1a22f8c586f6e73 Mon Sep 17 00:00:00 2001 From: navewindre Date: Tue, 11 Nov 2025 08:11:24 +0100 Subject: new web --- web/src/api.tsx | 39 ++++++++ web/src/ascii-art.tsx | 148 +++++++++++++++++++++++++++ web/src/blog.tsx | 10 ++ web/src/components.tsx | 259 +++++++++++++++++++++++++++++++++++++++++++++++ web/src/header.tsx | 10 ++ web/src/home.tsx | 24 +++++ web/src/index-page.tsx | 14 +++ web/src/index.html | 31 ++++++ web/src/jsx.tsx | 267 +++++++++++++++++++++++++++++++++++++++++++++++++ web/src/pkg.tsx | 164 ++++++++++++++++++++++++++++++ web/src/sidebar.tsx | 126 +++++++++++++++++++++++ web/src/tsconfig.json | 121 ++++++++++++++++++++++ web/src/user.tsx | 6 ++ web/src/util.tsx | 42 ++++++++ 14 files changed, 1261 insertions(+) create mode 100644 web/src/api.tsx create mode 100644 web/src/ascii-art.tsx create mode 100644 web/src/blog.tsx create mode 100644 web/src/components.tsx create mode 100644 web/src/header.tsx create mode 100644 web/src/home.tsx create mode 100644 web/src/index-page.tsx create mode 100644 web/src/index.html create mode 100644 web/src/jsx.tsx create mode 100644 web/src/pkg.tsx create mode 100644 web/src/sidebar.tsx create mode 100644 web/src/tsconfig.json create mode 100644 web/src/user.tsx create mode 100644 web/src/util.tsx (limited to 'web/src') diff --git a/web/src/api.tsx b/web/src/api.tsx new file mode 100644 index 0000000..7233d89 --- /dev/null +++ b/web/src/api.tsx @@ -0,0 +1,39 @@ +export const url = "https://networkheaven.net"; + +export interface ReqParams { + method: string, + body?: string, +} + +export async function post( endpoint: string, body: Object ) { + return await req( endpoint, { + method: "POST", + body: JSON.stringify( body ), + } ); +} + +export async function req( endpoint: string, params: ReqParams ) { + const res = await fetch( `${url}/${endpoint}`, { + method: params.method, + headers: { + "Content-Type": "application/json", + }, + body: params.body, + } ); + + if( !res.ok ) { + let json = null; + try { + json = await res.json(); + } catch( e: any ) { + throw new Error( "error contacting server" ); + } + + throw new Error( json.msg ); + } + + const json = await res.json(); + if( json.status != 'ok' ) + throw new Error( json.msg ); + return json; +} diff --git a/web/src/ascii-art.tsx b/web/src/ascii-art.tsx new file mode 100644 index 0000000..a6caefe --- /dev/null +++ b/web/src/ascii-art.tsx @@ -0,0 +1,148 @@ +import * as JSX from "./jsx"; +import $ from "jquery"; + +export function AsciiArt() { + const ret =
+ { asciiArtStr } +
; + + setTimeout( doAsciiArt ); + return ret; +} + +let startTime = 0.0; +let anim = 0.0; + +function animFunc( a: number ) { + let ringRadius = anim; + let radius = a; + + let dist = ringRadius - radius; + + dist = Math.abs( dist ); + + let ret = Math.pow( dist, 0.1 + 0.8 * Math.pow( (anim + 0.5) / 2, 1.6 ) ); + let remain = ret % 0.12; + return ret - remain; +} + +function hexToRgb( hex: string ) { + let r = parseInt( hex.slice( 1, 3 ), 16 ); + let g = parseInt( hex.slice( 3, 5 ), 16 ); + let b = parseInt( hex.slice( 5, 7 ), 16 ); + return [ r, g, b ]; +} + +function lerpColor( c1: number[], c2: number[], t: number ) { + let r1 = c1[0], g1 = c1[1], b1 = c1[2], a1 = c1[3]; + let r2 = c2[0], g2 = c2[1], b2 = c2[2], a2 = c2[3]; + let r = r1 + ( r2 - r1 ) * t; + let g = g1 + ( g2 - g1 ) * t; + let b = b1 + ( b2 - b1 ) * t; + let a = a1 + ( a2 - a1 ) * t; + return [ Math.round( r ), Math.round( g ), Math.round( b ), Math.round( a ) ]; +} + +function getDist( x: number, y: number ) { + const x1 = 0.75 - x / 71; + const y1 = 0.75 - y / 35; + return Math.sqrt( x1 * x1 + y1 * y1 ); +} + +let topY = 0; + +let isDoingAnim = false; +function doAsciiArt() { + let div = $( "#ascii-art" ); + if( !div.length ) { isDoingAnim = false; return; } + isDoingAnim = true; + + let deltaTime = ( Date.now() - startTime ) * 0.001; + startTime = Date.now(); + anim += .5 * deltaTime; + + if( anim > 2 ) + anim = -0.5; + + let rootColor = [ 255, 0, 255 ]; + let innerStr = ""; + for( let i = 0; i < asciiArtStr.length; ++i ) { + let x = i % 71; + let y = Math.floor( i / 71 ); + if( y > topY ) + topY = y; + + + if( asciiArtStr[i] == ',' || asciiArtStr[i] == '\t' ) { + innerStr += asciiArtStr[i]; + continue; + } + if( asciiArtStr[i] == ' ' ) { + innerStr += ' '; + continue; + } + + let dist = getDist( x, y ); + + let color = []; + if( asciiArtStr[i] == '#' ) { + let a = animFunc( dist * 2 ); + color = lerpColor( rootColor, [255, 255, 255, 1], a ); + color[3] = Math.max( Math.min( 1, Math.pow( dist, 2 ) + (1.0 - a) ), 0.8 ); + } + else { + const t = Math.max( 0.05, 1.0 - animFunc( (dist) * 0.75 ) ); + if( t < 0.25 ) { + color = lerpColor( [255, 255, 255, 255], [255, 189, 255, 255], (t - 0.5) * 2 ); + } + else if( t > 0.5 && t < 0.75 ) { + color = lerpColor( [255, 189, 255, 255], [0, 255, 255, 255], (t - 0.5) * 2 ); + } else { + color = lerpColor( [255, 255, 255, 255], [0, 255, 255, 255], (t - 0.5) * 2 ); + } + } + // appending string is faster than jsx + innerStr += `${asciiArtStr[i]}`; + } + + div.html( innerStr ); + setTimeout( doAsciiArt, 85 ); +} + +const asciiArtStr = ` +⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣄⡀⠀⢿⡿⣁⠀⢆⡘⠤⡘⠠⢰⠃⡄⠂⠄⠀⠀⠀⠀⡀⡆⠌⢄⠀⠀⢠⡇⠂⡌⠄⠀⠀⠀⠀⠀⠀⠡⣖⠀⢃⠸⠀⠸⣟⡄⠘⡽⡜⡄⠀⠀⠀⠈⠦⠀⠀⠀⠸ +⣿⣿⣿⣿⣿⣿⡿⠁⠈⢻⡝⣟⣷⡿⢁⠤⢀⠢⢌⠰⡀⠂⡟⠠⡄⠈⠀⠀⣀⠤⠐⣰⠌⡈⢄⠂⡡⣺⠄⡇⠰⠈⡄⢃⠰⢀⠰⢀⠂⢽⠂⢼⠐⡠⠄⣿⡼⡀⠱⢹⡰⠀⠀⠀⠀⠀⣆⠀⠀⠀ +⣿⣿⣿⣿⣿⣿⠁⢄⠠⢀⣹⣞⡿⢁⢊⠰⢀⠎⡐⢂⠁⢳⠋⠴⠁⠀⣠⠞⡠⢁⣺⡇⠤⢁⠢⠘⢠⡿⢰⡏⠄⢃⡐⢈⠰⢈⠰⢸⠌⣸⠌⢺⡐⠤⢈⣽⣧⢩⡄⢉⢧⠡⠉⡍⠒⠤⣸⡀⠀⠀ +⣿⣿⣿⣿⣿⣿⠩⡐⠌⢿⡯⣿⠇⡌⠢⢡⠈⡔⠈⠂⠃⢾⢈⠒⠀⡴⢁⡾⠑⣸⢻⢀⠒⢠⠂⡉⢼⡇⢸⡇⠌⠄⡄⢃⠰⢈⠰⠸⡂⢼⢈⢹⠆⠰⢸⢿⠸⡆⢷⢈⠸⣄⠃⠤⢉⡐⠨⣧⠀⡀ +⣿⣿⣿⣿⣿⣿⡟⣿⣻⢾⡷⣿⠐⡄⢃⠆⠈⠐⠁⠀⠀⣾⠸⠀⢀⡇⣼⠇⢡⡏⣇⠢⠘⡀⠆⢡⡎⡇⢺⣇⠘⢠⠐⠨⡐⢂⠢⢡⡇⠾⢈⢸⡃⡘⢼⢸⠀⢻⠈⣇⠌⣷⢈⡐⠂⠤⢱⢿⠀⠔ +⣿⣿⣿⣿⣿⣿⣯⡚⢧⡻⣽⡏⠰⡈⢄⠈⠀⡀⠀⠀⠀⣿⢸⢀⢂⢸⡟⡈⢼⢳⠁⢂⡁⠆⡑⢺⢱⡇⣼⢿⠈⠄⡈⠡⢐⠠⢁⢺⡄⡟⡀⢺⢁⡴⡿⢸⢄⠈⡇⢺⡆⢸⡆⠤⢉⠰⢐⣸⠈⠔ +⣿⣿⣿⣿⣿⣿⣿⣷⠁⢿⣹⡇⠡⠌⠀⠀⠀⠀⠀⠀⢀⡿⣸⠀⢂⣿⢃⠐⡞⡼⢈⣄⣐⣤⡴⡯⢼⠴⡿⢼⠾⠶⠶⠶⣤⠂⠌⣹⢰⡇⡐⡏⢸⢡⡇⡽⡀⠑⢳⠐⣷⠀⢿⡇⠌⡐⠂⡽⢈⡐ +⣿⣿⣿⣿⣿⣿⣿⡏⠠⣄⣿⠄⠀⠀⠀⠀⠀⡅⠀⠠⢠⡟⣧⠈⣰⣿⠀⣼⠴⡗⢉⠉⡐⢠⢷⠃⢸⢠⠃⢸⣿⠀⢀⠂⢸⠀⢂⡏⣼⠁⣸⠁⡏⡜⡇⡧⠬⣴⣼⣀⢿⢇⢸⣹⠀⠀⡁⠇⡃⠄ +⣿⣿⣿⣿⣿⣿⣿⣇⡷⠊⢸⠀⠀⠀⢠⣦⣄⠇⡈⢁⠆⣿⣧⠐⣽⡇⠐⣾⢰⡇⢂⠡⢈⡏⡞⠀⡼⣸⠀⠀⣿⠀⠀⠂⣏⠐⢸⢡⡟⢀⡏⣼⢱⠁⣧⠁⠀⠀⠈⡟⣻⠺⣤⣿⡆⠐⠀⡇⠄⡈ +⣿⣿⣿⣿⣿⣿⣿⣿⣄⠂⣿⠀⠀⢠⠃⢿⣣⠐⠠⠌⠠⣿⣿⠀⢿⠃⢌⣿⠸⡇⢀⢂⡼⡼⠀⢀⢧⡯⠄⡀⣿⠀⠌⢰⡇⠈⣼⡿⠁⡞⣸⢇⠇⢰⡟⠀⠀⠀⠈⡇⡝⠀⢇⡟⡧⣄⠂⡇⠀⢰ +⣿⣿⣿⣿⣿⡿⢭⢿⣿⣶⣻⡆⠀⡏⠠⠸⡽⣏⠐⡈⡐⢿⣻⡠⣿⠈⢸⡍⣆⣧⣾⣾⣿⣿⣶⣾⣾⣆⠄⠀⣿⡆⠀⣸⠁⣸⡿⢁⡞⡕⡹⡌⠀⣼⢁⠤⠤⢀⣀⡇⡇⠀⠸⣼⡇⠀⠑⣷⠈⣸ +⣿⣿⣿⣿⣿⡹⢎⣗⡻⣿⣷⣇⠘⣇⠄⡁⢻⣽⡆⠡⠠⢹⣿⡓⣿⣴⣿⣿⣿⣿⡿⣟⣿⢫⣟⣏⠉⠙⠛⠶⣿⢿⠀⡞⢀⡟⢡⣾⠊⣰⠟⠀⣼⠃⣐⣤⣴⣦⣤⣧⣇⣠⠀⣿⡇⠀⡁⡏⠀⡿ +⣿⣿⣿⡟⣧⣛⡭⢶⡙⣿⢻⣿⡄⢻⡔⠠⡈⢧⢿⡆⢡⠈⢿⣇⣷⣿⡿⠛⣯⢽⡳⢧⡞⠀⠀⣽⡀⠀⠀⠀⠛⠘⠻⢣⣞⣴⠟⠁⠠⠏⠀⠐⠁⢸⣿⢿⣟⠿⣿⡿⣿⣷⣤⣸⡂⠀⢸⠃⢰⠇ +⣿⣿⡿⣹⣧⡓⢾⢷⡟⢸⣯⢹⣷⡌⢻⣄⠱⣌⠳⣿⣦⠲⠾⣿⣿⣿⠁⠈⣟⠊⢿⡧⢿⠃⡜⣸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣏⣟⡑⢀⡻⡴⢷⠈⠻⣿⣿⣟⠋⡟⢀⡎⠄ +⣿⣿⠳⣽⠲⣝⡿⢸⡇⣸⠙⣷⢺⣿⣧⡻⣷⣌⢢⡙⢽⣷⣤⠘⢧⠙⢦⡀⢻⡌⠀⢠⢁⠒⢯⡀⠀⠀⠀⠀⠀⡀⠀⢀⠀⢀⠀⠀⠀⠀⠀⠀⠀⡏⣷⣹⡏⠵⡙⣺⠀⠀⠸⣿⣿⣾⢁⡾⠀⣲ +⣿⢫⡝⣾⢹⣾⢃⢹⡇⢼⠂⣿⣹⠋⢉⡿⣾⣿⣷⣌⠲⣽⢿⣿⣮⣷⣀⠈⠀⠙⠗⠒⠚⡋⠉⠀⠀⠀⠀⣺⠂⠀⠀⢀⠀⠠⠐⣀⡀⠂⠀⠀⠀⢷⢈⠩⠀⡡⠲⠏⠀⠀⣸⢟⣽⣫⡟⢁⣴⢏ +⣏⢷⣙⣮⠟⡐⠢⢼⡇⢸⡿⢧⣟⠀⡇⡄⣤⢩⢿⣛⢷⣤⣭⣛⢿⣮⠉⠳⠦⣤⠁⠁⡡⢈⢄⠡⠅⡆⠂⠀⠀⡈⠀⢈⡠⠆⠨⢀⠀⠀⠀⠄⠐⠈⠙⡲⢷⡼⡆⠀⠄⡐⠁⣿⣿⣋⣴⣾⢋⣾ +⣞⢺⣼⠏⡐⠌⡁⣿⡇⢸⡇⠼⣿⠀⢷⠀⢹⡆⣿⡉⠚⠶⣭⣻⣿⡟⠢⢄⢤⢇⡀⡅⠦⠠⡴⠀⠀⠐⢐⠀⠆⠃⠄⠖⠉⠂⠅⠊⠁⢒⠄⠐⣀⠀⠂⠉⣀⠁⡄⠁⠈⠐⢸⣿⢫⣿⡳⣵⡿⢿ +⣎⡿⢼⠂⡅⢊⠔⣿⣵⢺⡏⠄⣿⣧⡈⠓⢌⠷⢾⡇⢁⠢⢀⢹⠑⣷⠈⡠⠏⠤⠨⣁⢃⠕⠋⡫⢗⠉⠈⡂⠁⠀⢂⠐⢁⠣⠀⠁⠀⠘⠒⠥⠴⢄⢀⠐⠁⠀⠐⠀⠀⠈⡿⣬⡿⢣⡓⣸⣇⢻ +⣾⣃⡯⢼⣀⠣⣈⣿⡽⣺⠛⢠⠹⡻⣿⣦⡀⠑⠪⣇⠂⠔⡈⡾⠊⣿⠎⠵⠃⡓⠀⡀⠉⠄⠆⠁⠉⠀⠈⠡⠂⠀⠀⡀⢈⣀⠊⣡⠂⠀⡐⠛⠊⢁⠰⠖⠃⠀⠁⢀⡀⢰⠟⡞⠡⢠⢰⣿⠸⣸ +⣿⣿⣳⢯⣿⣿⣿⣷⣽⣹⡇⠌⣷⢳⢋⠻⢿⣦⣀⡟⡀⠊⢰⡇⠂⣿⡇⠠⠁⠀⠁⠀⡐⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⡔⠁⠀⠉⠢⢀⠀⠀⠀⠀⠈⠈⠁⢀⠠⠊⠀⠈⢺⡞⠠⢁⢂⣯⡏⠵⡘ +⣿⣿⡜⣿⣿⣿⣿⣿⢾⣻⣧⡂⢹⣏⣿⡄⠌⢻⣿⣧⠀⠡⣸⠠⠁⣾⡇⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⢀⡠⠐⠁⠀⠀⠀⠀⠀⠀⠀⠉⠫⣦⠀⠀⢀⠔⠁⠀⠀⠀⡀⠀⠙⢦⢁⡞⣼⡙⢦⠑ +⣿⣿⡽⣹⣿⣿⣿⣿⡯⣷⣯⢿⣄⠻⣜⣷⡈⠄⢻⣷⠈⢠⡗⠠⠁⣿⢸⣄⠀⠀⠀⠀⠀⠀⢀⡔⠊⠉⠀⠀⠀⠀⠀⠀⡰⠂⠤⣀⠀⠀⢀⣃⠀⣰⠁⠀⠀⡴⢴⣧⠀⡀⠀⠀⠙⢦⡏⡝⢢⠌ +⡿⣿⣿⣽⣿⢿⣿⣿⡷⣣⢿⣯⣻⢷⣽⢞⣿⡄⠂⣿⠀⣸⠃⠠⢁⣷⠸⡇⠑⢤⡀⠀⣠⠞⠁⠀⠀⠀⠀⠀⠀⣀⠔⠀⠀⠀⠀⠈⢧⡀⢿⣿⣾⡿⠁⡠⢊⣴⣿⢸⡷⣄⣐⠀⢀⠄⠙⠓⠧⣌ +⣿⣷⣿⣿⣿⣿⣿⣿⡿⣵⢫⣷⣏⣟⣾⣻⣾⣿⣆⢿⠀⡿⠀⠀⠂⣼⠐⣿⡀⢀⡼⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠒⢄⡀⠀⠀⠀⠀⠑⠮⠿⠛⠒⢈⣴⣿⣿⡏⡾⢁⠈⣿⠖⠁⠀⠀⠀⠀⠀ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣳⣿⣿⣿⣯⣟⣷⢯⣿⣿⣿⢰⠃⠀⠀⠐⢸⠀⣻⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠲⠄⡀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣃⠇⢂⢡⠏⠀⠀⠀⠀⢀⠀⠀ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣞⢾⡻⢷⣯⣿⣼⠀⠀⠀⢀⡼⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠲⣤⣀⡀⠀⠀⠀⠀⠈⠢⣤⣾⣿⣿⣿⣿⣿⣿⣿⢸⠠⡱⠃⠀⠀⠀⣠⣶⠋⠀⠀ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢾⣽⣯⣞⣽⡟⠀⣠⠔⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠿⣷⣤⡀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣷⣿⢀⡜⠁⠀⠀⣠⡞⡱⠁⠀⠀⠀ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢯⢿⣿⣿⣿⣧⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠠⢀⠀⠀⠀⠀⠀⠀⠙⢿⣷⣄⡀⠀⠀⠀⠹⢿⣿⣿⣿⣿⡼⠀⠀⢀⣾⣿⡟⠀⠀⠀⠀⠀ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡻⣞⢾⡹⡿⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠂⢄⡀⠀⠀⣠⣿⣿⣿⣿⣦⣄⠀⠀⠀⠻⣿⣿⡟⢀⠀⣡⠂⢺⣿⡣⢀⢀⠄⠀⠀ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣝⣾⣿⣧⢶⡶⣿⢿⣿⣦⣤⣀⣀⣀⣤⣀⣴⣶⣤⣤⣄⡀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⢠⣾⣿⣾⡷⣰⠉⡆⣹⣿⣿⣶⣿⣆⡀⢀ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡞⣯⢷⣏⡿⣽⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣽⣿⡿⠕⠉⢆⢱⢸⣿⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⡽⣾⣹⡞⣷⡽⣞⡽⣯⢿⣹⢯⣿⣿⣿⣿⣯⣿⡿⣿⣿⣿⣿⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⢪⢿⣿⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡽⣶⢯⡽⣞⣳⢯⡽⣞⣯⣽⢫⣿⡟⣿⣻⢿⡽⣿⣿⣿⣿⣻⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡈⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿ +⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⢷⣛⡾⣽⣹⡽⢾⣹⠷⣞⣞⠿⣼⣿⡘⣯⣾⣿⢿⣹⢯⣟⡿⣿⢿⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠈⠻⣿⣿⣿⣿⣿ +`; diff --git a/web/src/blog.tsx b/web/src/blog.tsx new file mode 100644 index 0000000..9d86657 --- /dev/null +++ b/web/src/blog.tsx @@ -0,0 +1,10 @@ +import $ from "jquery"; +import * as JSX from "./jsx"; +import { Page } from "./components"; +import { AsciiArt } from "./ascii-art"; + +export default function Blog() { + return + work in progress (: + +} diff --git a/web/src/components.tsx b/web/src/components.tsx new file mode 100644 index 0000000..ccd8da8 --- /dev/null +++ b/web/src/components.tsx @@ -0,0 +1,259 @@ +import $ from "jquery"; +import * as JSX from "./jsx"; +import { Header } from "./header"; +import { Sidebar } from "./sidebar"; + +const popup_stack = []; +window.onclick = ( e: Event ) => { + if( !e.target ) return; + + if( popup_stack.length > 0 ) { + const last = popup_stack[popup_stack.length - 1]; + if( last.el[0] == e.target || last.el.find( e.target ).length != 0 ) return; + e.preventDefault(); + e.stopPropagation(); + if( last.fn ) + last.fn(); + + popup_stack.pop(); + last.el.remove(); + } +} + +/** + * appends an element to the DOM and saves it in the popup stack to be removed. +**/ +export function addPopup( element: JQuery ) { + document.body.appendChild( element[0] ); + setTimeout( () => popup_stack.push( { el: element, fn: null } ) ); +} + +/** + * closes the topmost popup +**/ +export function closePopup() { + if( popup_stack.length > 0 ) { + const last = popup_stack[popup_stack.length - 1]; + if( last.fn ) + last.fn(); + + popup_stack.pop(); + setTimeout( () => last.el.remove() ); + } +} + +/** + * sets a callback that will get executed once the current popup is closed + **/ +export function onPopupClosed( fn: Function ) { + setTimeout( () => { + if( popup_stack.length > 0 ) { + popup_stack[popup_stack.length - 1].fn = fn; + } + } ); +} + +/* + * accepts "folded" prop +**/ +export function RolldownListItem( props: any ) { + const ret =
+
+
>
+
{props.folded}
+
+
+
{props.children}
+
+
; + + ret.onclick = () => { + $( ret ).toggleClass( "active" ); + $( ret ).find( ".rolldown-expanded-container" ).toggleClass( 'active' ); + }; + + return ret; +} + +export function Spinner( props: any ) { + let spinner_steps = [ + '/', + '-', + '\\', + '|', + '/', + '-', + '\\', + '|' + ]; + + const id = props.id || ''; + + let el = $(
); + let i = 0; + let loop = () => { + el.text( spinner_steps[i++] ); + if( i > spinner_steps.length ) + i = 0; + + if( el[0].isConnected ) + setTimeout( loop, 100 ); + }; + + setTimeout( loop, 100 ); + return el[0]; +} + +/* + * accepts title prop +**/ +export function RolldownList( props: any ) { + return +
+ { props.children } +
+
+} + +/* + * accepts title prop and optional innerStyle +**/ +export function GroupBox( props: any ) { + return
+ + {props.title} + + + {props.children} + +
+} + +export function Page( props: any ) { + return <> +
+
+ +
+ {props.children} +
+
+ + + +} + +export function DropdownItem( props: any ) { + return +} + +/** + * supports innerStyle for styling the actual dropdown picker + **/ +export function Dropdown( props: any ) { + const children = props.children; + let title = props.title; + let inline = props.inline; + let onchange = props.onchange; + let classes = props.class || ''; + let style = props.style || ""; + let id = props.id || ''; + let innerStyle = props.innerStyle || ""; + + const showItems = ( e: Event ) => { + e.preventDefault(); + const target = $( e.target as HTMLElement ); + + const newDropdown = $( ); + target.parent().append( newDropdown[0] ); + setTimeout( () => popup_stack.push( { el: newDropdown, fn: null } ) ); + } + + if( inline ) { + return + } + else { + return <> + + + + } +} + +export function OkPopup( props: any ) { + const children = props.children; + const classes = props.class || ''; + const style = props.style || ''; + const id = props.id || ''; + + const onclick = ( e: Event ) => { + e.preventDefault(); + e.stopPropagation(); + if( props.onclick ) + props.onclick(); + + closePopup(); + } + + return
+ { children } +
+ +
+
+} + +export function OkCancelPopup( props: any ) { + const children = props.children; + const classes = props.class || ''; + const style = props.style || ''; + const id = props.id || ''; + + const onclick = ( e: Event ) => { + e.preventDefault(); + e.stopPropagation(); + if( props.onclick ) + props.onclick(); + + closePopup(); + } + + const oncancel = ( e: Event ) => { + e.preventDefault(); + e.stopPropagation(); + + closePopup(); + } + + return
+ { children } +
+ + +
+
+} + +export function BackgroundToggle() { + const toggleBackground = () => { + const main = $( "#page-main" ); + if( main.css( "display" ) == "none" ) { + main.css( "display", "" ); + } else { + main.css( "display", "none" ); + } + } + + return
+ show background +
+} diff --git a/web/src/header.tsx b/web/src/header.tsx new file mode 100644 index 0000000..94906c0 --- /dev/null +++ b/web/src/header.tsx @@ -0,0 +1,10 @@ +import $ from 'jquery'; +import * as JSX from './jsx'; + +export function Header( props: any ) { + return
+
+ networkheaven.net +
+
+} diff --git a/web/src/home.tsx b/web/src/home.tsx new file mode 100644 index 0000000..77404fd --- /dev/null +++ b/web/src/home.tsx @@ -0,0 +1,24 @@ +import $ from "jquery"; +import * as JSX from "./jsx"; +import { Page } from "./components"; +import { AsciiArt } from "./ascii-art"; + +export default function Home() { + return +
+

NETWORKHEAVEN

+
+
+
+
+

+ hi, im aura and this is my website. +
+ i will add more stuff when i have time +

+
+
+ +

STEAM

+
; +} diff --git a/web/src/index-page.tsx b/web/src/index-page.tsx new file mode 100644 index 0000000..4b7ce1c --- /dev/null +++ b/web/src/index-page.tsx @@ -0,0 +1,14 @@ +import * as JSX from "./jsx"; +import Home from "./home"; +import Blog from "./blog"; +import Pkgs from "./pkg"; + +JSX.addRoute( "/", () => ); +JSX.addRoute( "/blog", () => ); +JSX.addRoute( "/pkg", () => ); +JSX.addRoute( "/pkg/*", () => ); + +window.onpopstate = JSX.onPopState; + +const url = new URL( window.location.href ); +JSX.navigateParams( url.pathname, url.searchParams.entries() ); diff --git a/web/src/index.html b/web/src/index.html new file mode 100644 index 0000000..a5500e4 --- /dev/null +++ b/web/src/index.html @@ -0,0 +1,31 @@ + + + + + + + networkheaven.net + + + + + + + +
+
+ +
+
+ + + diff --git a/web/src/jsx.tsx b/web/src/jsx.tsx new file mode 100644 index 0000000..98ca440 --- /dev/null +++ b/web/src/jsx.tsx @@ -0,0 +1,267 @@ +import $ from 'jquery'; +const assetAttributeNames = new Set( ['data', 'srcset', 'src', 'href'] ); + +export interface Route { + path: string, + component: Function, + wildcard: boolean +}; + +const routes: Route[] = []; +let err404page = "/"; +let rootId = "moneyjsx-root"; +let onprenavigate: Function = () => {}; +let onpostnavigate: Function = () => {}; + +function routeForPath( route: string ) : Function | null { + if( !routes[route] ) { + for( let key of Object.keys( routes ) ) { + const r = routes[key]; + + if( r.wildcard ) { + if( route.slice( 0, r.path.length ) == r.path ) { + return r.component; + } + } + } + + return null; + } else return routes[route].component; +} + +/** + * sets the id of the element to be replaced by the navigator + **/ +export function setRootId( rootId: string ) { + rootId = rootId; +} + +/** + * adds a route component to the routes list + * the component function must return either a jquery or a DOM element + **/ +export function addRoute( name: string, component: Function ) { + let path = name; + let wildcard = false; + if( path[path.length - 1] === "*" ) { + console.log( "name" ); + path = path.substring( 0, path.length - 1 ); + wildcard = true; + } + + routes[path] = { path, component, wildcard }; +} + +/** + * sets the route for a 404 page + **/ +export function set404Route( name: string ) { + err404page = name; +} + +/** + * sets the callback that will get called when a route changes + **/ +export function onPreNavigate( callback: Function ) { + onprenavigate = callback; +} + +/** + * sets the callback that will get called when a route changes + **/ +export function onPostNavigate( callback: Function ) { + onpostnavigate = callback; +} + +/** + * replaces the root element with the route component + **/ +export function navigate( route: string ) { + console.log( route ); + let url = new URL( window.location.href ); + let cb = routeForPath( route ); + url.pathname = route; + if( !cb ) + return navigate( err404page ); + + window.history.pushState( {}, null, url.href ); + + onprenavigate(); + const el = $( cb() ); + + $( `#${rootId}` ).children().remove(); + $( `#${rootId}` ).append( el ); + onpostnavigate(); +} + +/** + * navigate with params. see: navigate + **/ +export function navigateParams( route: string, params: any ) { + let url = new URL( window.location.href ); + let uparams = new URLSearchParams( params ); + url.pathname = route; + url.search = uparams.toString(); + let cb = routeForPath( route ); + if( !cb ) + return navigate( err404page ); + + window.history.pushState( {}, null, url.href ); + + onprenavigate(); + const el = $( cb() ); + + $( `#${rootId}` ).children().remove(); + $( `#${rootId}` ).append( el ); + onpostnavigate(); +} + +/** + * wrapper for history.pushState + **/ +export function pushParams( params: any ) { + const url = new URL( window.location.href ); + url.search = new URLSearchParams( params ).toString(); + + window.history.pushState( {}, null, url.href ); +} + +/** + * navigates without adding a history entry + * useful for e.g. re-rendering a page after waiting for a data callback +**/ +export function navigateParamsSilent( route: string, params: any ) { + let url = new URL( window.location.href ); + let uparams = new URLSearchParams( params ); + url.pathname = route; + url.search = uparams.toString(); + let cb = routeForPath( route ); + if( !cb ) + return navigateSilent( err404page ); + + onprenavigate(); + const el = $( cb() ); + + $( `#${rootId}` ).children().remove(); + $( `#${rootId}` ).append( el ); + onpostnavigate(); +} + +/** + * see: navigateParamsSilent + **/ +export function navigateSilent( route: string ) { + let url = new URL( window.location.href ); + url.pathname = route; + let cb = routeForPath( route ); + if( !cb ) + return navigateSilent( err404page ); + + onprenavigate(); + const el = $( cb() ); + + $( `#${rootId}` ).children().remove(); + $( `#${rootId}` ).append( el ); + onpostnavigate(); +} + +/** + * action when the back button is pressed +**/ +export function onPopState() { + let url = new URL( window.location.href ); + let uparams = new URLSearchParams( url.searchParams ); + url.search = uparams.toString(); + let cb = routeForPath( url.pathname ); + if( !cb ) + return navigateSilent( err404page ); + + onprenavigate(); + const el = $( cb() ); + + $( `#${rootId}` ).children().remove(); + $( `#${rootId}` ).append( el ); + onpostnavigate(); +} + +export function getRoutes() : Route[] { + return routes; +} + + +// internal stuff below + +const originalAppendChild = Element.prototype.appendChild; +Element.prototype.appendChild = function( child: any ) { + if( Array.isArray( child ) ) { + for( const childArrayMember of child ) + this.appendChild( childArrayMember ); + + return child; + } + else if( typeof child === 'string' ) { + return originalAppendChild.call( this, document.createTextNode( child ) ); + } + else if( child ) { + return originalAppendChild.call( this, child ); + } +}; + +export function createElement( tag: any, props: any, ...children: any ) { + props = props || {}; + + if( typeof tag === "function" ) { + props.children = children; + return tag( props ); + } + + if( tag === 'raw-content' ) { + const dummy = document.createElement( 'div' ); + dummy.innerHTML = props.content; + return [...dummy.children]; + } + + const element = document.createElement( tag ); + + for( const [name, value] of Object.entries( props ) ) { + if( name.startsWith( 'on' ) ) { + const lowercaseName = name.toLowerCase(); + + if( lowercaseName in window ) { + element.addEventListener( lowercaseName.substring(2 ), value ); + continue; + } + } + + if( name == 'ref' ) { + ( value as any ).current = element; + continue; + } + + if( value === false ) + continue; + + if( value === true ) { + element.setAttribute( name, '' ); + continue; + } + + if( assetAttributeNames.has( name ) ) { + if( typeof value === 'string' ) { + element.setAttribute( name, value ); + } + continue; + } + + element.setAttribute( name, value ); + }; + + for( const child of children ) + element.appendChild( child ); + + return element; +} + +export function createFragment( props: any ) { + return props.children; +} diff --git a/web/src/pkg.tsx b/web/src/pkg.tsx new file mode 100644 index 0000000..70e75c6 --- /dev/null +++ b/web/src/pkg.tsx @@ -0,0 +1,164 @@ +import $ from "jquery"; +import * as JSX from "./jsx"; +import { Page, Spinner } from "./components"; +import { sizeHumanReadable } from "./util"; + +function downloadFile( file: string ) { + const a = document.createElement( "a" ); + a.href = "https://networkheaven.net/pkgs/"; + a.download = "string"; + a.click(); + a.remove(); +} + +function urlForHref( href: string, isdir: boolean ) { + const url = new URL( window.location.href ); + if( isdir ) { + const path = url.pathname; + if( path.endsWith( '/' ) ) { + return path + href; + } + return path + "/" + href; + } + + let searchParams = url.searchParams.toString(); + searchParams = searchParams.slice( searchParams.indexOf( '/' ) ); + searchParams = searchParams.slice( searchParams.indexOf( '/' ) ); + return "https://networkheaven.net/pkgs" + searchParams + "/" + href; +} + +interface PkgEntry { + name: string; + date: string; + time: string; + size: string; + isdir: boolean; +} + +function entryFromLine( line: string ): PkgEntry | null { + const isdir = line[line.length - 1] == '-'; + const name = line.slice( 0, line.indexOf( " " ) ); + if( name == ".." ) return null; + if( !name ) + return null; + let date = ''; + let time = ''; + let size = '' + + if( !isdir ) { + let end = line.lastIndexOf( " " ); + size = line.slice( end + 1 ); + date = line.slice( line.indexOf( " " ) + 1, end ); + end = date.indexOf( " " ); + const datetime = date.slice( date.search( /[0-9]/ ) ); + end = datetime.indexOf( ' ' ); + date = datetime.slice( 0, end ); + time = datetime.slice( end + 1 ); + time = time.slice( 0, time.indexOf( " " ) ); + } else { + let start = line.search( /[0-9]/ ) + date = line.slice( start ); + let end = date.indexOf( " " ); + date = date.slice( 0, end ); + time = line.slice( start + end + 1, line.length - 1 ); + time = time.slice( 0, time.indexOf( " " ) ); + } + + return { name, date, time, size, isdir }; +} + +async function getEntries(): Promise { + const url = new URL( window.location.href ); + const href = url.pathname.split( "/pkg" )[1]; + + const packages = await fetch( "https://networkheaven.net/pkgs/" + href ); + const text = await packages.text(); + + const pkgBody = $(
); + pkgBody.html( text ); + pkgBody.html( pkgBody.find( "body" ).html() ); + const pre = pkgBody.find( "pre" )[0].innerText; + + const ret = []; + console.log( url.pathname ); + if( !url.pathname.endsWith( "/pkg/" ) && !url.pathname.endsWith( "/pkg" ) ) { + ret.push({ + name: '../', + date: ' ', + time: ' ', + size: '', + isdir: true + } ); + } + + const lines = pre.split( "\n" ); + for( const line of lines ) { + if( !line.length ) + continue; + + const entry = entryFromLine( line ); + console.log( entry ); + if( entry ) + ret.push( entry ); + } + + return ret; +} + +function back() { + const url = new URL( window.location.href ); + if( url.pathname.endsWith( "/" ) ) + url.pathname = url.pathname.slice( 0, -1 ); + url.pathname = url.pathname.slice( 0, url.pathname.lastIndexOf( "/" ) ); + JSX.navigate( url.pathname ); +} + +function PackageEntry( props: any ) { + console.log( props ); + const entry = props.entry as PkgEntry; + console.log( urlForHref( entry.name, entry.isdir ) ); + return + + { entry.name == "../" && + back() } class="package-entry-link"> + ../yes + } + { entry.name != "../" && + JSX.navigate( urlForHref( entry.name, entry.isdir ) ) } class="package-entry-link"> + {entry.name} + } + + + {entry.time} + { !entry.isdir && { sizeHumanReadable( parseInt( entry.size ) ) } } + +} + +export default function Pkgs() { + setTimeout( async () => { + try { + const entries = await getEntries(); + const target = $( "#package-entries" ).find( "table" ); + $( "#package-entries" ).find( ".spinner" ).remove(); + for( const entry of entries ) { + target.append( ); + } + + } catch( e ) { + console.log( e ); + } + + } ); + + return +
+

PACKAGE REPOSITORY

+
+
+ +
+ + + + +} diff --git a/web/src/sidebar.tsx b/web/src/sidebar.tsx new file mode 100644 index 0000000..a1b3ad7 --- /dev/null +++ b/web/src/sidebar.tsx @@ -0,0 +1,126 @@ +import $ from 'jquery'; +import * as JSX from './jsx'; + +var interval = null; + +function cacheWeather( result: any ) { + const timestamp = new Date().getTime(); + + localStorage.setItem( "weather", JSON.stringify( result ) ); + localStorage.setItem( "weather_timestamp", timestamp.toString() ); +} + +function getWeatherCache() { + const timestamp = localStorage.getItem( "weather_timestamp" ); + if( !timestamp ) + return null; + + if( parseInt( timestamp ) + 60 * 60 * 1000 < new Date().getTime() ) { + localStorage.removeItem( "weather" ); + localStorage.removeItem( "weather_timestamp" ); + return null; + } + + try { + const res = JSON.parse( localStorage.getItem( "weather" ) ); + return res; + } catch( e ) { + console.log( e ); + return null; + } +} + +async function updateWeather() { + let weather = getWeatherCache(); + if( !weather ) { + weather = await fetch( + "https://api.open-meteo.com/v1/forecast?latitude=35.0647937&longitude=137.1784597¤t=temperature_2m,wind_speed_10m,relative_humidity_2m" + ) + + weather = await weather.json(); + cacheWeather( weather ); + } + + $( "#temp" ).text( weather.current.temperature_2m ); + $( "#wind" ).text( weather.current.wind_speed_10m ); + $( "#humi" ).text( weather.current.relative_humidity_2m ); + + setTimeout( updateWeather, 60 * 60 * 1000 ); +} + +function Weather() { + setTimeout( updateWeather ); + const weather = getWeatherCache(); + if( weather && weather.current ) { + const temp = weather.current.temperature_2m; + const wind = weather.current.wind_speed_10m; + const humi = weather.current.relative_humidity_2m; + return
+ + + + +
+ } + + return
+ + + + +
+} + +export function Sidebar() { + if( interval == null ) { + interval = setInterval( () => { + $( "#time" ).text( new Date().toLocaleTimeString() ); + $( "#date" ).text( new Date().toLocaleDateString() ); + }, 1000 ); + } + + return +} diff --git a/web/src/tsconfig.json b/web/src/tsconfig.json new file mode 100644 index 0000000..b964594 --- /dev/null +++ b/web/src/tsconfig.json @@ -0,0 +1,121 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + "moduleResolution": "node", + /* Language and Environment */ + "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "lib": [ + "ES2017", + "DOM", + "DOM.Iterable", + "ScriptHost" + ], + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "jsx": "react", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + "jsxFactory": "JSX.createElement", + "jsxFragmentFactory": "JSX.createFragment", + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "es2020", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "baseUrl": ".", + "paths": { + "*": ["types/*"] + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": [ + "./node_modules/@types", + "./types" + ], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + "strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */ + "strictFunctionTypes": false, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + "strictBindCallApply": false, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + "strictPropertyInitialization": false, /* Check for class properties that are declared but not set in the constructor. */ + "strictBuiltinIteratorReturn": false, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + "noImplicitThis": false, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/web/src/user.tsx b/web/src/user.tsx new file mode 100644 index 0000000..d909d9e --- /dev/null +++ b/web/src/user.tsx @@ -0,0 +1,6 @@ +import $ from 'jquery'; + +import * as JSX from './jsx'; +import * as api from './api'; + + diff --git a/web/src/util.tsx b/web/src/util.tsx new file mode 100644 index 0000000..1b1ccc0 --- /dev/null +++ b/web/src/util.tsx @@ -0,0 +1,42 @@ +export function escapeHtml( html: string ) { + const entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + return String( html ).replace( /[&<>"'`=\/]/g, ( s ) => { + return entityMap[s]; + } ); +} + +export function parseJWT( token: string ) : any { + const parts = token.split( '.' ); + let encoded = parts[1]; + encoded = encoded.replace(/-/g, '+').replace(/_/g, '/'); + const pad = encoded.length % 4; + if( pad === 1 ) + throw new Error( 'what the fuck' ); + if( pad > 1 ) + encoded += new Array( 5 - pad ).join( '=' ); + + const payload = JSON.parse( atob( encoded ) ); + return payload; +} + +export function sizeHumanReadable( size: number ) { + if( size < 1024 ) + return size + ' B'; + else if( size < 1024 * 1024 ) + return ( size / 1024 ).toFixed( 2 ) + ' KB'; + else if( size < 1024 * 1024 * 1024 ) + return ( size / 1024 / 1024 ).toFixed( 2 ) + ' MB'; + else + return ( size / 1024 / 1024 / 1024 ).toFixed( 2 ) + ' GB'; +} + -- cgit v1.2.3