Compare commits
8 Commits
static
...
510897d94b
| Author | SHA1 | Date | |
|---|---|---|---|
| 510897d94b | |||
| 078634b664 | |||
| 6a5176caab | |||
|
|
282c67b1fc | ||
| 74224bd648 | |||
| 8f217ec82b | |||
| 0e1055b1a6 | |||
| 94c56d7405 |
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.idea/
|
||||||
|
dist/
|
||||||
|
pkg/
|
||||||
|
target/
|
||||||
|
Cargo.lock
|
||||||
|
bulma.min.css
|
||||||
33
Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
[package]
|
||||||
|
name = "web_iouring"
|
||||||
|
version = "0.0.0-develop"
|
||||||
|
authors = ["iouring"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
edition = "2024"
|
||||||
|
rust-version = "1.91"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
js-sys = "0.3"
|
||||||
|
yew = { git = "https://github.com/yewstack/yew.git", rev = "21f373b", features = [
|
||||||
|
"csr",
|
||||||
|
] }
|
||||||
|
yew-router = { git = "https://github.com/yewstack/yew.git", rev = "21f373b" }
|
||||||
|
wasm-bindgen = { version = "0.2" }
|
||||||
|
|
||||||
|
log = { version = "0.4" }
|
||||||
|
wasm-logger = { version = "0.2" }
|
||||||
|
|
||||||
|
serde = { version = "1.0" }
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
version = "0.3"
|
||||||
|
features = [
|
||||||
|
'HtmlCanvasElement',
|
||||||
|
'WebGlBuffer',
|
||||||
|
'WebGlProgram',
|
||||||
|
'WebGlRenderingContext',
|
||||||
|
'WebGl2RenderingContext',
|
||||||
|
'WebGlShader',
|
||||||
|
'WebGlUniformLocation',
|
||||||
|
]
|
||||||
24
LICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <https://unlicense.org>
|
||||||
2
Trunk.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[tools]
|
||||||
|
wasm_opt = "version_122"
|
||||||
6
docker-compose.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: ./
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
BIN
freemono.ttf
65
index.html
@@ -1,52 +1,39 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="theme-dark">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
<title>i0ur.ing ~ welcome</title>
|
<title>i0ur.ing ~ welcome</title>
|
||||||
|
|
||||||
|
<link data-trunk rel="copy-file" href="public/misc/favicon.ico" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/misc/profile.avif" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/misc/oneko.gif" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/misc/monocraft.ttf" />
|
||||||
|
|
||||||
|
<link data-trunk rel="copy-file" href="public/blog/testimage.jpg" />
|
||||||
|
|
||||||
|
|
||||||
|
<link data-trunk rel="copy-file" href="public/shaders/post.bloom.svg" />
|
||||||
|
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/iouring.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/wasm.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/dataforest.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/servfail.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/rust.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/csharp.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/kotlin.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/java.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/fedora.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/artix.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/nixos.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/chimera.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/void.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/aqueer.png" />
|
||||||
|
|
||||||
|
<link data-trunk rel="sass" href="main.scss" />
|
||||||
|
<link data-trunk rel="css" href="bulma.min.css" />
|
||||||
|
<link data-trunk rel="rust" />
|
||||||
|
<base data-trunk-public-url />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/main-148897a668350f6e.css" integrity="sha384-nGIDBUHCWROKdlElGdixLBXi+MIlWjeP2AyvAlyfif+mxN1AkTIG3r0pc0V5+ujX"/>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import init, * as bindings from '/web_iouring-5f95d2ecaec15eb1.js';
|
|
||||||
const wasm = await init({ module_or_path: '/web_iouring-5f95d2ecaec15eb1_bg.wasm' });
|
|
||||||
|
|
||||||
|
|
||||||
window.wasmBindings = bindings;
|
|
||||||
|
|
||||||
|
|
||||||
dispatchEvent(new CustomEvent("TrunkApplicationStarted", {detail: {wasm}}));
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<base href="/" />
|
|
||||||
<link rel="modulepreload" href="/web_iouring-5f95d2ecaec15eb1.js" crossorigin="anonymous" integrity="sha384-KFz2s0PwvG25+EU7WT9Vuc63SqGBVvHQsZg55YjsnyQhCJeDBYwtOnPXu/onXchI"><link rel="preload" href="/web_iouring-5f95d2ecaec15eb1_bg.wasm" crossorigin="anonymous" integrity="sha384-fLrSAfSJSL3wUOfxHy4NkOhsIskb1fwwEcljekgZNxikCQO+AIGeMrSwXfDa5/F0" as="fetch" type="application/wasm"></head>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
15
justfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env just --justfile
|
||||||
|
|
||||||
|
setup:
|
||||||
|
rustup target add wasm32-unknown-unknown
|
||||||
|
|
||||||
|
install:
|
||||||
|
cargo install --locked wasm-pack trunk --force
|
||||||
|
|
||||||
|
debug:
|
||||||
|
rm bulma.min.css && curl --proto '=https' --tlsv1.3 -LO https://cdn.jsdelivr.net/npm/bulma/css/bulma.min.css
|
||||||
|
trunk serve
|
||||||
|
|
||||||
|
build:
|
||||||
|
rm bulma.min.css && curl --proto '=https' --tlsv1.3 -LO https://cdn.jsdelivr.net/npm/bulma/css/bulma.min.css
|
||||||
|
trunk build --release
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: freemono;
|
|
||||||
src: url("freemono.ttf");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: monocraft;
|
|
||||||
src: url("monocraft.ttf");
|
|
||||||
}
|
|
||||||
html * {
|
|
||||||
font-family: freemono, monocraft, monospace !important;
|
|
||||||
scrollbar-color: hsla(232, 97%, 85%, 0.5) hsla(240, 21.05%, 14.9%, 0.7) !important;
|
|
||||||
scrollbar-width: thin !important;
|
|
||||||
scroll-behavior: smooth !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textarea {
|
|
||||||
-ms-overflow-style: none !important;
|
|
||||||
scrollbar-width: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textarea::-webkit-scrollbar {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
backdrop-filter: blur(4px) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
backdrop-filter: blur(4px) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input,
|
|
||||||
.select select,
|
|
||||||
.textarea {
|
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
background-color: white, 0.213125 !important;
|
|
||||||
border-color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.navbar-item,
|
|
||||||
a.navbar-item:hover {
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-selection {
|
|
||||||
background: rgba(245, 194, 231, 0.2509803922) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
background: rgba(245, 194, 231, 0.2509803922) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #f5c2e7 !important;
|
|
||||||
text-decoration: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline !important;
|
|
||||||
filter: url("post.bloom.svg#process") !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: transparent !important;
|
|
||||||
border-color: transparent !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
text-decoration: underline !important;
|
|
||||||
filter: url("post.bloom.svg#process") !important;
|
|
||||||
}
|
|
||||||
91
main.scss
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: monocraft;
|
||||||
|
src: url("monocraft.ttf");
|
||||||
|
}
|
||||||
|
|
||||||
|
html * {
|
||||||
|
font-family: monocraft, monospace !important;
|
||||||
|
scrollbar-color: hsla(232, 97%, 85%, 0.5) hsla(240, 21.05%, 14.9%, 0.7) !important;
|
||||||
|
scrollbar-width: thin !important;
|
||||||
|
scroll-behavior: smooth !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
-ms-overflow-style: none !important;
|
||||||
|
scrollbar-width: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea::-webkit-scrollbar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
backdrop-filter: blur(4px) !important;
|
||||||
|
--bulma-box-background-color: #1E1E2E80 !important;
|
||||||
|
--bulma-shadow-h: 232 !important;
|
||||||
|
--bulma-shadow-s: 97% !important;
|
||||||
|
--bulma-shadow-l: 85% !important;
|
||||||
|
--bulma-shadow: 0 0.5em 1em -0.125em hsla(var(--bulma-shadow-h),
|
||||||
|
var(--bulma-shadow-s),
|
||||||
|
var(--bulma-shadow-l),
|
||||||
|
0.3), 0 0px 0 1px hsla(var(--bulma-shadow-h),
|
||||||
|
var(--bulma-shadow-s),
|
||||||
|
var(--bulma-shadow-l),
|
||||||
|
0.02) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
backdrop-filter: blur(4px) !important;
|
||||||
|
--bulma-card-background-color: #1E1E2E80 !important;
|
||||||
|
--bulma-shadow-h: 232 !important;
|
||||||
|
--bulma-shadow-s: 97% !important;
|
||||||
|
--bulma-shadow-l: 85% !important;
|
||||||
|
--bulma-shadow: 0 0.5em 1em -0.125em hsla(var(--bulma-shadow-h),
|
||||||
|
var(--bulma-shadow-s),
|
||||||
|
var(--bulma-shadow-l),
|
||||||
|
0.3), 0 0px 0 1px hsla(var(--bulma-shadow-h),
|
||||||
|
var(--bulma-shadow-s),
|
||||||
|
var(--bulma-shadow-l),
|
||||||
|
0.02) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.select select,
|
||||||
|
.textarea {
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
--bulma-input-h: 232 !important;
|
||||||
|
--bulma-input-s: 97% !important;
|
||||||
|
--bulma-input-l: 85% !important;
|
||||||
|
--bulma-input-background-h: var(--bulma-input-h) !important;
|
||||||
|
--bulma-input-background-s: var(--bulma-input-s) !important;
|
||||||
|
--bulma-input-background-l: var(--bulma-input-l) !important;
|
||||||
|
background-color: hsla(var(--bulma-input-h),
|
||||||
|
var(--bulma-input-s),
|
||||||
|
var(--bulma-input-background-l),
|
||||||
|
0.213125) !important;
|
||||||
|
border-color: hsl(var(--bulma-input-h), var(--bulma-input-s), var(--bulma-input-l)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.navbar-item,
|
||||||
|
a.navbar-item:hover {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-selection {
|
||||||
|
background: hsla(232, 97%, 85%, 0.4125) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: hsla(232, 97%, 85%, 0.4125) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
filter: url("post.bloom.svg#process") !important;
|
||||||
|
}
|
||||||
BIN
profile.avif
|
Before Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 4.8 MiB After Width: | Height: | Size: 4.8 MiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 315 B After Width: | Height: | Size: 315 B |
BIN
public/misc/profile.avif
Normal file
|
After Width: | Height: | Size: 139 KiB |
285
public/shaders/es3/bg.frag
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
const float CAM_FAR = 20.0;
|
||||||
|
const vec3 BACKGROUND = vec3(0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
const float PI = radians(180.0);
|
||||||
|
|
||||||
|
uniform float time;
|
||||||
|
uniform vec2 resolution;
|
||||||
|
uniform bool layer_snow;
|
||||||
|
|
||||||
|
in vec4 vColor;
|
||||||
|
layout (location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
vec3 artifactOffset;
|
||||||
|
mat3 artifactRotation;
|
||||||
|
vec3 artifactAxis;
|
||||||
|
vec3 camFwd;
|
||||||
|
vec3 camUp;
|
||||||
|
|
||||||
|
float smootherstep(float edge0, float edge1, float x) {
|
||||||
|
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
||||||
|
return x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float rand(float n) {
|
||||||
|
n = fract(n * 43758.5453);
|
||||||
|
n *= n;
|
||||||
|
return fract(n * 43758.5453);
|
||||||
|
}
|
||||||
|
|
||||||
|
float hash(float n) {
|
||||||
|
return fract(abs(fract(n) * 43758.5453));
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise(float x) {
|
||||||
|
float i = floor(x);
|
||||||
|
float f = fract(x);
|
||||||
|
float u = f * f * (3.0 - 2.0 * f);
|
||||||
|
return mix(hash(i), hash(i + 1.0), u);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat4 viewMatrix(vec3 dir, vec3 up) {
|
||||||
|
vec3 f = normalize(dir);
|
||||||
|
vec3 s = normalize(cross(f, up));
|
||||||
|
return mat4(vec4(s, 0.0), vec4(cross(s, f), 0.0), vec4(-f, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
mat3 rotationAlign(vec3 d, vec3 z) {
|
||||||
|
vec3 v = cross(z, d);
|
||||||
|
float c = dot(z, d);
|
||||||
|
float k = 1.0 / (1.0 + c);
|
||||||
|
return mat3(
|
||||||
|
v.x * v.x * k + c,
|
||||||
|
v.y * v.x * k - v.z,
|
||||||
|
v.z * v.x * k + v.y,
|
||||||
|
v.x * v.y * k + v.z,
|
||||||
|
v.y * v.y * k + c,
|
||||||
|
v.z * v.y * k - v.x,
|
||||||
|
v.x * v.z * k - v.y,
|
||||||
|
v.y * v.z * k + v.x,
|
||||||
|
v.z * v.z * k + c
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
float intersectPlane(vec3 origin, vec3 direction, vec3 point, vec3 normal) {
|
||||||
|
return clamp(dot(point - origin, normal) / dot(direction, normal), -1.0, 9991999.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 calcRay(vec2 uv, float fov, float aspect) {
|
||||||
|
uv = uv * 2.0 - 1.0;
|
||||||
|
float d = 1.0 / tan(radians(fov) * 0.5);
|
||||||
|
return normalize(vec3(aspect * uv.x, uv.y, d));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 getWave(vec2 position, vec2 dir, float speed, float frequency, float iTimeshift) {
|
||||||
|
float x = dot(dir, position) * frequency + iTimeshift * speed;
|
||||||
|
float wave = exp(sin(x) - 1.0);
|
||||||
|
float dist = wave * cos(x);
|
||||||
|
return vec2(wave, -dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
float heightmap(vec2 worldPos) {
|
||||||
|
const float scale = 0.06;
|
||||||
|
|
||||||
|
vec2 p = worldPos * scale;
|
||||||
|
vec2 p2 = (artifactOffset.xz - vec2(0.0, 1.0)) * scale;
|
||||||
|
|
||||||
|
float d = (1.0 - smootherstep(0.0, 1.0, clamp(length(p2 - p) * 1.25, 0.0, 1.0))) * 0.87;
|
||||||
|
float angle = 0.0;
|
||||||
|
float freq = 5.0;
|
||||||
|
float speed = 2.0;
|
||||||
|
float weight = 1.9;
|
||||||
|
float wave = 0.0;
|
||||||
|
float waveScale = 0.0;
|
||||||
|
|
||||||
|
vec2 dir;
|
||||||
|
vec2 res;
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
dir = vec2(cos(angle), sin(angle));
|
||||||
|
res = getWave(p, dir, speed, freq, time);
|
||||||
|
p += dir * res.y * weight * 0.05;
|
||||||
|
wave += res.x * weight - d;
|
||||||
|
angle += 12.0;
|
||||||
|
waveScale += weight;
|
||||||
|
weight = mix(weight, 0.0, 0.2);
|
||||||
|
freq *= 1.18;
|
||||||
|
speed *= 1.06;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wave * (1.0 / waveScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
float octahedron(vec3 p, float s) {
|
||||||
|
p = abs(p);
|
||||||
|
return (p.x + p.y + p.z - s) * 0.57735027;
|
||||||
|
}
|
||||||
|
|
||||||
|
void artifact(vec3 p, inout float currDist, inout vec3 glowColor, inout int id) {
|
||||||
|
p -= artifactOffset;
|
||||||
|
p = artifactRotation * p;
|
||||||
|
float dist = octahedron(p, 0.8);
|
||||||
|
const float glowDist = 4.8;
|
||||||
|
if (dist < glowDist) {
|
||||||
|
float d = dist + rand(dist) * 1.7;
|
||||||
|
glowColor += vec3(0.75, 0.55, 0.45) * clamp(1.0 - pow((d * (1.0 / glowDist)), 5.0), 0.0, 1.0) * 0.035;
|
||||||
|
}
|
||||||
|
if (dist < currDist) {
|
||||||
|
currDist = dist;
|
||||||
|
id = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float objects(vec3 p, inout vec3 glowColor, inout int objId) {
|
||||||
|
float dist = CAM_FAR;
|
||||||
|
artifact(p, dist, glowColor, objId);
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
float artifactDist(vec3 p) {
|
||||||
|
p -= artifactOffset;
|
||||||
|
p = artifactRotation * p;
|
||||||
|
return octahedron(p, 1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
float objectsDist(vec3 p) {
|
||||||
|
return artifactDist(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 objectsNormal(vec3 p, float eps) {
|
||||||
|
vec2 h = vec2(eps, 0);
|
||||||
|
return normalize(vec3(artifactDist(p + h.xyy) - artifactDist(p - h.xyy), eps * 2.0, artifactDist(p + h.yyx) - artifactDist(p - h.yyx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 objectsColor(int id, vec3 normal, vec3 ray) {
|
||||||
|
return id == 1 ? vec3(0.85, 0.65, 0.55) * mix(0.8, 1.5, dot(normal, normalize(vec3(0.0, 1.0, 0.5))) * 0.5 + 0.5) :
|
||||||
|
id == 2 ? vec3(0.85, 0.65, 0.55) * 1.5 :
|
||||||
|
vec3(1.0, 1.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void marchObjects(vec3 eye, vec3 ray, float wDepth, inout vec4 color) {
|
||||||
|
float dist = 0.0;
|
||||||
|
int id;
|
||||||
|
vec3 rayPos = eye;
|
||||||
|
float depth = CAM_FAR;
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
dist = objects(rayPos, color.rgb, id);
|
||||||
|
depth = distance(rayPos, eye);
|
||||||
|
if (depth > wDepth || dist < 0.01) break;
|
||||||
|
rayPos += ray * dist;
|
||||||
|
}
|
||||||
|
color = dist < 0.01 ? vec4(objectsColor(id, objectsNormal(rayPos, 0.01), ray), depth) : color;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 waterColor(vec3 ray, vec3 normal, vec3 p) {
|
||||||
|
vec3 color = vec3(0.0);
|
||||||
|
float fogDist = length(p - vec3(0.0, 0.0, -6.0));
|
||||||
|
float dist = 0.0;
|
||||||
|
int objId = 0;
|
||||||
|
vec3 refl = reflect(ray, normal);
|
||||||
|
vec3 rayPos = p + refl * dist;
|
||||||
|
|
||||||
|
if (length(p.xz - artifactOffset.xz) < 8.5 && dot(refl, normalize(artifactOffset - p)) > -0.25) {
|
||||||
|
for (int i = 0; i < 40; i++) {
|
||||||
|
dist = objects(rayPos, color, objId);
|
||||||
|
if (dist < 0.01) {
|
||||||
|
color = objectsColor(objId, objectsNormal(rayPos, 0.001), rayPos);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rayPos += refl * dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float fresnel = 0.04 + 0.9 * pow(1.0 - max(0.0, dot(-normal, ray)), 7.0);
|
||||||
|
float d = length(artifactOffset - p);
|
||||||
|
const float r = 14.0;
|
||||||
|
float atten = clamp(1.0 - (d * d) / (r * r), 0.0, 1.0);
|
||||||
|
atten *= atten;
|
||||||
|
|
||||||
|
vec3 point = vec3(0.75, 0.55, 0.45) * atten * (1.0 + fresnel) * 0.07;
|
||||||
|
vec3 ambient = dot(normal, normalize(vec3(0.0, 1.0, 0.5))) * max(fresnel, 0.06) * vec3(0.1, 0.5, 1.0) * 0.85;
|
||||||
|
float fog = smootherstep(25.0, 6.0, fogDist) * (1.0 / (fogDist * 0.1));
|
||||||
|
|
||||||
|
return color + (point + ambient) * fog;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 waterNormal(vec2 p, float eps) {
|
||||||
|
vec2 h = vec2(eps, 0.0);
|
||||||
|
return normalize(vec3(heightmap(p - h.xy) - heightmap(p + h.xy), eps * 2.0, heightmap(p - h.yx) - heightmap(p + h.yx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void marchWater(vec3 eye, vec3 ray, inout vec4 color) {
|
||||||
|
const vec3 planeNorm = vec3(0.0, 1.0, 0.0);
|
||||||
|
const float depth = 3.0;
|
||||||
|
float ceilDist = intersectPlane(eye, ray, vec3(0.0, 0.0, 0.0), planeNorm);
|
||||||
|
vec3 normal = vec3(0.0);
|
||||||
|
|
||||||
|
if (dot(planeNorm, ray) > -0.05) {
|
||||||
|
color = vec4(vec3(0.0), CAM_FAR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float height = 0.0;
|
||||||
|
vec3 rayPos = eye + ray * ceilDist;
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
height = heightmap(rayPos.xz) * depth - depth;
|
||||||
|
if (rayPos.y - height < 0.1) {
|
||||||
|
color.w = distance(rayPos, eye);
|
||||||
|
vec3 normPos = (eye + ray * color.w);
|
||||||
|
color.rgb = waterColor(ray, waterNormal(normPos.xz, 0.005), normPos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rayPos += ray * max(rayPos.y - height, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
color = vec4(vec3(0.0), CAM_FAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 march(vec2 uv, vec3 camPos) {
|
||||||
|
mat4 vm = viewMatrix(camFwd, camUp);
|
||||||
|
vec3 ray = (vm * vec4(calcRay(uv, 80.0, resolution.x / resolution.y), 1.0)).xyz;
|
||||||
|
vec4 color = vec4(BACKGROUND, CAM_FAR);
|
||||||
|
vec3 waterColor;
|
||||||
|
marchWater(camPos, ray, color);
|
||||||
|
marchObjects(camPos, ray, color.w, color);
|
||||||
|
return color.rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
float snow(vec2 uv, float scale) {
|
||||||
|
float w = smootherstep(1.0, 0.0, -uv.y * (scale * 0.01));
|
||||||
|
if (w < 0.1) return 0.0;
|
||||||
|
uv += time / scale;
|
||||||
|
uv.y += time / scale;
|
||||||
|
uv.x += sin(uv.y + time * 0.125) / scale;
|
||||||
|
uv *= scale;
|
||||||
|
vec2 s = floor(uv), f = fract(uv);
|
||||||
|
return smootherstep(0.0, min(length(0.5 + 0.5 * sin(11.0 * fract(sin((s + scale) * mat2(7.0, 3.0, 6.0, 5.0)) * 5.0)) - f), 3.0), sin(f.x + f.y) * 0.01) * w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||||
|
vec2 uv = fragCoord * (vec2(1.0) / resolution.xy);
|
||||||
|
|
||||||
|
float s = sin(time);
|
||||||
|
float c = cos(time);
|
||||||
|
artifactRotation = mat3(c, 0, s, 0, 1, 0, -s, 0, c) * rotationAlign(vec3(0.0, 1.0, 0.0), vec3(s * 0.2, 1.0, c * 0.2 + 0.3));
|
||||||
|
artifactOffset = vec3(s * 0.4, c * 0.3 - 1.7, -6.0);
|
||||||
|
|
||||||
|
camFwd = vec3(0.0, 0.7 + noise(time * 0.8 + 4.0) * 0.08 - 0.04, 1.0);
|
||||||
|
camUp = vec3(noise(time * 1.2) * 0.02 - 0.01, 1.0, 0.0);
|
||||||
|
|
||||||
|
fragColor = vec4(march(uv, vec3(0.0, 1.9, 1.0)) - (length(uv - 0.5) - 0.3) * 0.05, 1.0);
|
||||||
|
|
||||||
|
if (layer_snow) {
|
||||||
|
vec2 p = fragCoord.xy / resolution.xy;
|
||||||
|
vec2 uvSnow = (fragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
|
||||||
|
fragColor += mix(vec4(vec3(snow(uvSnow, 4.0)), 0.5) + vec4(vec3(snow(uvSnow, 3.0)), 0.5), vec4(0.0), vec4(0.7));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = vColor;
|
||||||
|
mainImage(fragColor, gl_FragCoord.xy);
|
||||||
|
}
|
||||||
11
public/shaders/es3/bg.vert
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#version 300 es
|
||||||
|
layout(location = 0) in vec3 position;
|
||||||
|
layout(location = 1) in vec4 color;
|
||||||
|
|
||||||
|
uniform mat4 model;
|
||||||
|
out vec4 vColor;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
vColor = color;
|
||||||
|
gl_Position = model * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 691 B After Width: | Height: | Size: 691 B |
0
src/lib.rs
Normal file
377
src/main.rs
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
use js_sys::Float32Array;
|
||||||
|
use log::{info, warn};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::{window, HtmlCanvasElement, WebGl2RenderingContext as GL2};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
|
mod pages;
|
||||||
|
use crate::pages::about::About;
|
||||||
|
use crate::pages::blog::entries::Entries;
|
||||||
|
use crate::pages::blog::entry::Entry;
|
||||||
|
use crate::pages::blog::authors::Authors;
|
||||||
|
use crate::pages::blog::author::Author;
|
||||||
|
use crate::pages::projects::Projects;
|
||||||
|
use pages::not_found::PageNotFound;
|
||||||
|
|
||||||
|
#[derive(Routable, PartialEq, Eq, Clone, Debug)]
|
||||||
|
pub enum Route {
|
||||||
|
#[at("/")]
|
||||||
|
About,
|
||||||
|
|
||||||
|
#[at("/blog/entries/:id")]
|
||||||
|
Entry { id: u8 },
|
||||||
|
#[at("/blog/entries")]
|
||||||
|
Entries,
|
||||||
|
#[at("/blog/authors/:id")]
|
||||||
|
Author { id: u8 },
|
||||||
|
#[at("/blog/authors")]
|
||||||
|
Authors,
|
||||||
|
|
||||||
|
#[at("/projects")]
|
||||||
|
Projects,
|
||||||
|
|
||||||
|
#[not_found]
|
||||||
|
#[at("/404")]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
node_ref: NodeRef,
|
||||||
|
navbar_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
ToggleNavbar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for App {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self { node_ref: NodeRef::default(), navbar_active: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::ToggleNavbar => {
|
||||||
|
self.navbar_active = !self.navbar_active;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<canvas style="
|
||||||
|
background-color: black !important;
|
||||||
|
position: fixed !important;
|
||||||
|
left: 0 !important;
|
||||||
|
top: 0 !important;
|
||||||
|
z-index: 0 !important;
|
||||||
|
" ref={self.node_ref.clone()}/>
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
">
|
||||||
|
<BrowserRouter>
|
||||||
|
<main style="
|
||||||
|
z-index: 1 !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row-reverse !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
left: 0 !important;
|
||||||
|
margin-top: calc(var(--bulma-navbar-height)) !important;
|
||||||
|
margin-bottom: calc(var(--bulma-navbar-height) * 2) !important;
|
||||||
|
height: calc(100vh - (var(--bulma-navbar-height) * 2)) !important;
|
||||||
|
width: 100% !important;
|
||||||
|
align-items: center !important;
|
||||||
|
"><Switch<Route> render={switch} />
|
||||||
|
</main>
|
||||||
|
<header style="
|
||||||
|
z-index: 1 !important;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
">{ self.view_header(_ctx) }</header>
|
||||||
|
</BrowserRouter>
|
||||||
|
<footer class="footer" style="
|
||||||
|
--bulma-footer-background-color: #1E1E2E80 !important;
|
||||||
|
z-index: 1 !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
position: fixed !important;
|
||||||
|
left: 0px !important;
|
||||||
|
bottom: 0px !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
">{ self.view_footer() }</footer>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
|
||||||
|
if first_render { self.view_canvas(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch(routes: Route) -> Html {
|
||||||
|
match routes {
|
||||||
|
Route::About => { html! { <About /> } }
|
||||||
|
|
||||||
|
Route::Entries => { html! { <Entries /> } }
|
||||||
|
Route::Entry { id } => { html! { <Entry seed={id as u8} /> } }
|
||||||
|
Route::Authors => { html! { <Authors /> } }
|
||||||
|
Route::Author { id } => { html! { <Author seed={id} /> } }
|
||||||
|
|
||||||
|
Route::Projects => { html! { <Projects /> } }
|
||||||
|
Route::NotFound => { html! { <PageNotFound /> } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn view_header(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let active_class = if self.navbar_active { "is-active" } else { "" };
|
||||||
|
html! {
|
||||||
|
<nav class="navbar" style="
|
||||||
|
z-index: 1 !important;
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0px !important;
|
||||||
|
left: 0px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
--bulma-navbar-background-color: #1E1E2E80 !important;
|
||||||
|
backdrop-filter: blur(4px) !important;
|
||||||
|
">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<button class={classes!("navbar-burger", "burger", active_class)} aria-label="menu" aria-expanded="false" onclick={_ctx.link().callback(|_| Msg::ToggleNavbar)}>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class={classes!("navbar-menu", active_class)}>
|
||||||
|
<div class="navbar-start">
|
||||||
|
<Link<Route> classes={classes!("navbar-item")} to={Route::About}>{r"about"}</Link<Route>>
|
||||||
|
<Link<Route> classes={classes!("navbar-item")} to={Route::Entries}>{r"blog"}</Link<Route>>
|
||||||
|
<Link<Route> classes={classes!("navbar-item")} to={Route::Projects}>{r"projects"}</Link<Route>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_footer(&self) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<div class="is-flex is-flex-direction-column is-flex-wrap-wrap" style="
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
width: 100vw !important;
|
||||||
|
text-align: left !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
height: 40px !important; max-height: 40px !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
">
|
||||||
|
<div class="is-align-content-flex-start is-flex is-flex-shrink-0 is-flex-direction-column is-align-content-center is-flex-wrap-wrap ml-0 mt-0 mr-0 mb-0" style="width: 100% !important; min-height: 40px !important; height: 40px !important; max-height: 40px !important; overflow-y: hidden !important; overflow-x: auto !important;">
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://i0ur.ing/"><img loading="eager" alt="servfail" class="image" src="iouring.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://webassembly.org/"><img loading="eager" alt="wasm" class="image" src="wasm.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://beta.servfail.network/"><img loading="eager" alt="servfail" class="image" src="servfail.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://www.dataforest.net/en/"><img loading="eager" alt="dataforest" class="image" src="dataforest.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://fedoraproject.org/"><img loading="eager" alt="fedora" class="image" src="fedora.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://artixlinux.org/"><img loading="eager" alt="artix" class="image" src="artix.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://nixos.org/"><img loading="eager" alt="nixos" class="image" src="nixos.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://chimera-linux.org/"><img loading="eager" alt="void" class="image" src="chimera.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://voidlinux.org/"><img loading="eager" alt="void" class="image" src="void.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://www.rust-lang.org/"><img loading="eager" alt="rust" class="image" src="rust.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://dotnet.microsoft.com/en-us/"><img loading="eager" alt="csharp" class="image" src="csharp.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://kotlinlang.org/"><img loading="eager" alt="kotlin" class="image" src="kotlin.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
" href="https://www.java.com/"><img loading="eager" alt="java" class="image" src="java.png" /></a>
|
||||||
|
<a class="mt-1 mb-1 mr-1 ml-0" style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important
|
||||||
|
"><img loading="eager" alt="aqueer" class="image" src="aqueer.png" /></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_canvas(&self) {
|
||||||
|
let canvas = self.node_ref.cast::<HtmlCanvasElement>().unwrap();
|
||||||
|
|
||||||
|
let gl = canvas
|
||||||
|
.get_context("webgl2")
|
||||||
|
.unwrap_or(canvas.get_context("webgl").unwrap())
|
||||||
|
.unwrap().dyn_into::<GL2>().unwrap();
|
||||||
|
|
||||||
|
let vertex_buffer = gl.create_buffer().unwrap();
|
||||||
|
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&vertex_buffer));
|
||||||
|
gl.buffer_data_with_array_buffer_view(GL2::ARRAY_BUFFER, &Float32Array::from(vec![
|
||||||
|
-1.0, 1.0, 0.0,
|
||||||
|
-1.0, -1.0, 0.0,
|
||||||
|
1.0, -1.0, 0.0,
|
||||||
|
1.0, 1.0, 0.0
|
||||||
|
].as_slice()), GL2::STATIC_DRAW);
|
||||||
|
|
||||||
|
let color_buffer = gl.create_buffer().unwrap();
|
||||||
|
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&color_buffer));
|
||||||
|
gl.buffer_data_with_array_buffer_view(GL2::ARRAY_BUFFER, &Float32Array::from(vec![
|
||||||
|
1.0, 0.0, 0.0, 1.0,
|
||||||
|
0.0, 1.0, 0.0, 1.0,
|
||||||
|
0.0, 0.0, 1.0, 1.0,
|
||||||
|
1.0, 1.0, 0.0, 1.0
|
||||||
|
].as_slice()), GL2::STATIC_DRAW);
|
||||||
|
|
||||||
|
let element_buffer = gl.create_buffer().unwrap();
|
||||||
|
gl.bind_buffer(GL2::ELEMENT_ARRAY_BUFFER, Some(&element_buffer));
|
||||||
|
let element_indices = vec![3, 2, 1, 3, 1, 0];
|
||||||
|
gl.buffer_data_with_array_buffer_view(
|
||||||
|
GL2::ELEMENT_ARRAY_BUFFER,
|
||||||
|
&js_sys::Uint16Array::from(element_indices.as_slice()),
|
||||||
|
GL2::STATIC_DRAW,
|
||||||
|
);
|
||||||
|
|
||||||
|
let vertex_shader_code = include_str!("../public/shaders/es3/bg.vert");
|
||||||
|
// info!("{}", vertex_shader_code);
|
||||||
|
let vertex_shader = gl.create_shader(GL2::VERTEX_SHADER).unwrap();
|
||||||
|
gl.shader_source(&vertex_shader, vertex_shader_code);
|
||||||
|
gl.compile_shader(&vertex_shader);
|
||||||
|
let vertex_shader_log = gl.get_shader_info_log(&vertex_shader);
|
||||||
|
if !vertex_shader_log.as_ref().unwrap().is_empty() {
|
||||||
|
info!("{}", vertex_shader_log.as_ref().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let fragment_shader_code = include_str!("../public/shaders/es3/bg.frag");
|
||||||
|
let fragment_shader = gl.create_shader(GL2::FRAGMENT_SHADER).unwrap();
|
||||||
|
gl.shader_source(&fragment_shader, fragment_shader_code);
|
||||||
|
gl.compile_shader(&fragment_shader);
|
||||||
|
let fragment_shader_log = gl.get_shader_info_log(&fragment_shader);
|
||||||
|
if !fragment_shader_log.as_ref().unwrap().is_empty() {
|
||||||
|
info!("{}", vertex_shader_log.as_ref().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let shader_program = gl.create_program().unwrap();
|
||||||
|
|
||||||
|
gl.attach_shader(&shader_program, &vertex_shader);
|
||||||
|
gl.attach_shader(&shader_program, &fragment_shader);
|
||||||
|
|
||||||
|
gl.link_program(&shader_program);
|
||||||
|
if !gl.get_program_parameter(&shader_program, GL2::LINK_STATUS).as_bool().unwrap() {
|
||||||
|
warn!("shader couldn't be linked!");
|
||||||
|
}
|
||||||
|
gl.use_program(Some(&shader_program));
|
||||||
|
|
||||||
|
let position = gl.get_attrib_location(&shader_program, "position") as u32;
|
||||||
|
gl.enable_vertex_attrib_array(position);
|
||||||
|
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&vertex_buffer));
|
||||||
|
gl.vertex_attrib_pointer_with_i32(position, 3, GL2::FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
let color = gl.get_attrib_location(&shader_program, "color") as u32;
|
||||||
|
gl.enable_vertex_attrib_array(color);
|
||||||
|
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&color_buffer));
|
||||||
|
gl.vertex_attrib_pointer_with_i32(color, 4, GL2::FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
gl.bind_buffer(GL2::ELEMENT_ARRAY_BUFFER, Some(&element_buffer));
|
||||||
|
|
||||||
|
gl.enable(GL2::DEPTH_TEST);
|
||||||
|
gl.clear_color(0.0, 0.0, 0.0, 1.0);
|
||||||
|
gl.clear(GL2::COLOR_BUFFER_BIT | GL2::DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
let default_matrix = vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0];
|
||||||
|
let unim_matrix = gl.get_uniform_location(&shader_program, "model");
|
||||||
|
let unim_time = gl.get_uniform_location(&shader_program, "time");
|
||||||
|
let unim_res = gl.get_uniform_location(&shader_program, "resolution");
|
||||||
|
let unim_overlay_time = gl.get_uniform_location(&shader_program, "layer_snow");
|
||||||
|
|
||||||
|
let mut timestamp = 0.0;
|
||||||
|
gl.uniform1f(unim_time.as_ref(), timestamp / 2000.0);
|
||||||
|
gl.draw_elements_with_i32(GL2::TRIANGLES, element_indices.len() as i32, GL2::UNSIGNED_SHORT, 0);
|
||||||
|
|
||||||
|
fn request_animation_frame(f: &Closure<dyn FnMut()>) {
|
||||||
|
window().unwrap().request_animation_frame(f.as_ref().unchecked_ref())
|
||||||
|
.expect("should register `requestAnimationFrame` OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
let cb = Arc::new(RefCell::new(None));
|
||||||
|
*cb.borrow_mut() = Some(Closure::wrap(Box::new({
|
||||||
|
let cb = cb.clone();
|
||||||
|
move || {
|
||||||
|
timestamp += 20.0;
|
||||||
|
|
||||||
|
canvas.set_width(window().unwrap().inner_width().unwrap().as_f64().unwrap() as u32);
|
||||||
|
canvas.set_height(window().unwrap().inner_height().unwrap().as_f64().unwrap() as u32);
|
||||||
|
|
||||||
|
gl.uniform_matrix4fv_with_f32_array(unim_matrix.as_ref(), false, default_matrix.as_slice());
|
||||||
|
|
||||||
|
gl.uniform1f(unim_time.as_ref(), timestamp / 2000.0);
|
||||||
|
gl.uniform2fv_with_f32_array(unim_res.as_ref(), vec![canvas.width() as f32, canvas.height() as f32].as_slice());
|
||||||
|
|
||||||
|
gl.uniform1i(unim_overlay_time.as_ref(), 0); // TODO: ... | add is xmas check
|
||||||
|
|
||||||
|
gl.viewport(0, 0, canvas.width() as i32, canvas.height() as i32);
|
||||||
|
gl.clear(GL2::COLOR_BUFFER_BIT | GL2::DEPTH_BUFFER_BIT);
|
||||||
|
gl.draw_elements_with_i32(GL2::TRIANGLES, element_indices.len() as i32, GL2::UNSIGNED_SHORT, 0);
|
||||||
|
|
||||||
|
request_animation_frame(cb.borrow().as_ref().unwrap());
|
||||||
|
}
|
||||||
|
}) as Box<dyn FnMut()>));
|
||||||
|
|
||||||
|
request_animation_frame(cb.borrow().as_ref().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
|
||||||
|
yew::Renderer::<App>::new().render();
|
||||||
|
}
|
||||||
41
src/pages/about.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
pub struct About;
|
||||||
|
impl Component for About {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<div class="tile is-parent container box is-small is-flex-shrink-5 mb-5 mt-5">
|
||||||
|
<img alt="insert pfp here" loading="eager" class="image is-128x128 is-square is-inline-block mg-small" src="profile.avif" />
|
||||||
|
<div class="columns is-narrow ml-3 is-inline-block is-vcentered is-centered is-gapless is-multiline is-0 mt-4" style="vertical-align: top !important;">
|
||||||
|
<p class="column is-narrow" style="filter: url(post.bloom.svg#process) !important; color: #B4BEFE !important;"><img loading="eager" alt="ket" class="image is-24x24 is-square is-inline-block mg-small" src="oneko.gif" />{r" Lucielle R. Hoerner"}</p>
|
||||||
|
<p class="column is-narrow" style="background-clip: text !important; background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;">
|
||||||
|
<p>{r"they/she · transfem"}</p>
|
||||||
|
<p>{r"certified catgirl™"}</p>
|
||||||
|
<p>{r"software engineer"}</p>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr style="background-color: #B4BEFE60 !important;" />
|
||||||
|
<p class="subtitle">{r#"about me"#}</p>
|
||||||
|
<div class="content is-size-7">{r#"
|
||||||
|
haj! i'm luciel, but you can also just call me lucie.
|
||||||
|
i have lots of interests including music, cooking, development and learning new things.
|
||||||
|
in my free-time i mostly work on my private projects or spent time with my partners or online friends.
|
||||||
|
my knowledge is based on autodidactics and experiences with (former) friends.
|
||||||
|
looking for an apprenticeship or job atm. because i wasn't able to finish my previous one due to some financial circumstances.
|
||||||
|
you can find public projects i work on somewhere in the header.
|
||||||
|
i may or may not update information stated here.
|
||||||
|
"#}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/pages/blog/author.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
use crate::pages::blog::content;
|
||||||
|
use crate::pages::blog::content::BlogEntry;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
|
||||||
|
pub struct Props {
|
||||||
|
pub seed: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Author {
|
||||||
|
author: content::Author,
|
||||||
|
}
|
||||||
|
impl Component for Author {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = Props;
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
Self { author: content::Author::from_seed(ctx.props().seed), }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
|
||||||
|
self.author = content::Author::from_seed(ctx.props().seed);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let Self { author } = self;
|
||||||
|
html! {
|
||||||
|
<div class="tile is-parent container box is-small is-flex-shrink-5 mb-5 mt-5">
|
||||||
|
<img alt="insert pfp here" loading="eager" class="image is-128x128 is-square is-inline-block mg-small" src={author.image_url.clone()} />
|
||||||
|
<div class="columns is-narrow ml-3 is-inline-block is-vcentered is-centered is-gapless is-multiline is-0 mt-4" style="vertical-align: top !important;">
|
||||||
|
<p class="column is-narrow" style="filter: url(post.bloom.svg#process) !important; color: #B4BEFE !important;">{author.name.clone()}</p>
|
||||||
|
<p class="column is-narrow" style="background-clip: text !important; background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;">
|
||||||
|
{ for author.keywords.iter().map(|tag| html! { <p>{ tag }</p> }) }
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr style="background-color: #B4BEFE60 !important;" />
|
||||||
|
<p class="subtitle">{r#"about me"#}</p>
|
||||||
|
<div class="content is-size-7">
|
||||||
|
{ author.about.clone() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
<div class="section container">
|
||||||
|
<div class="tile is-ancestor is-vertical">
|
||||||
|
<div class="tile is-parent">
|
||||||
|
<article class="tile is-child notification is-light">
|
||||||
|
<p class="title">{ &author.name }</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div class="tile">
|
||||||
|
<div class="tile is-parent is-3">
|
||||||
|
<article class="tile is-child notification">
|
||||||
|
<p class="title">{ "Interests" }</p>
|
||||||
|
<div class="tags">
|
||||||
|
{ for author.keywords.iter().map(|tag| html! { <span class="tag is-info">{ tag }</span> }) }
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div class="tile is-parent">
|
||||||
|
<figure class="tile is-child image is-square">
|
||||||
|
<img alt="The author's profile picture." src={author.image_url.clone()} />
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div class="tile is-parent">
|
||||||
|
<article class="tile is-child notification is-info">
|
||||||
|
<div class="content">
|
||||||
|
<p class="title">{ "About me" }</p>
|
||||||
|
<div class="content">
|
||||||
|
{ author.about.clone() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
*/
|
||||||
61
src/pages/blog/authorcard.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::components::Link;
|
||||||
|
|
||||||
|
use crate::pages::blog::content::BlogEntry;
|
||||||
|
use crate::pages::blog::content::Author;
|
||||||
|
|
||||||
|
use crate::Route;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||||
|
pub struct PropsAuthorCard {
|
||||||
|
pub seed: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AuthorCard {
|
||||||
|
author: Author,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for AuthorCard {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = PropsAuthorCard;
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
Self { author: Author::from_seed(ctx.props().seed), }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
|
||||||
|
self.author = Author::from_seed(ctx.props().seed);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let Self { author } = self;
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="media">
|
||||||
|
<div class="media-left">
|
||||||
|
<figure class="image is-128x128">
|
||||||
|
<img alt="this should normally show an image mew,," src={author.image_url.clone()} />
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div class="media-content">
|
||||||
|
<p class="title is-3">{ &author.name }</p>
|
||||||
|
<p>
|
||||||
|
{ "I like " }
|
||||||
|
<b>{ author.keywords.join(", ") }</b>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="card-footer">
|
||||||
|
<Link<Route> classes={classes!("card-footer-item")} to={Route::Author { id: author.seed }}>
|
||||||
|
{ "Profile" }
|
||||||
|
</Link<Route>>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/pages/blog/authors.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use crate::pages::blog::authorcard::AuthorCard;
|
||||||
|
|
||||||
|
/*pub enum Msg {
|
||||||
|
NextAuthors,
|
||||||
|
}*/
|
||||||
|
|
||||||
|
pub struct Authors {
|
||||||
|
seeds: Vec<u8>,
|
||||||
|
}
|
||||||
|
impl Component for Authors {
|
||||||
|
type Message = (); //Msg;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self { seeds: vec![0], }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg { Msg::NextAuthors => { self.seeds = vec![0]; true } }
|
||||||
|
}*/
|
||||||
|
|
||||||
|
fn view(&self, _: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
{ for self.seeds.iter().map(|&seed| { html! {
|
||||||
|
<AuthorCard {seed} />
|
||||||
|
} }) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
src/pages/blog/content.rs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Author {
|
||||||
|
pub seed: u8,
|
||||||
|
pub name: String,
|
||||||
|
pub keywords: Vec<String>,
|
||||||
|
pub image_url: String,
|
||||||
|
pub about: String
|
||||||
|
}
|
||||||
|
impl BlogEntry for Author {
|
||||||
|
fn from_entry(entry: &mut Entry) -> Self {
|
||||||
|
return match entry.seed {
|
||||||
|
0 => Self {
|
||||||
|
seed: entry.seed,
|
||||||
|
name: "iouring".to_string(),
|
||||||
|
image_url: "profile.avif".to_string(),
|
||||||
|
about: "sup".to_string(),
|
||||||
|
keywords: vec![
|
||||||
|
"meow".to_string(),
|
||||||
|
"mrrp".to_string(),
|
||||||
|
"mew".to_string()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
_ => Self {
|
||||||
|
seed: u8::MAX,
|
||||||
|
name: "not found".to_string(),
|
||||||
|
image_url: "".to_string(),
|
||||||
|
about: "".to_string(),
|
||||||
|
keywords: vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct PostMeta {
|
||||||
|
pub seed: u8,
|
||||||
|
pub title: String,
|
||||||
|
pub author: Author,
|
||||||
|
pub keywords: Vec<String>,
|
||||||
|
pub image_url: String,
|
||||||
|
}
|
||||||
|
impl BlogEntry for PostMeta {
|
||||||
|
fn from_entry(entry: &mut Entry) -> Self {
|
||||||
|
return match entry.seed {
|
||||||
|
0 => Self {
|
||||||
|
seed: entry.seed,
|
||||||
|
title: "awawa title".to_string(),
|
||||||
|
author: Author::from_entry(entry),
|
||||||
|
keywords: vec!["meow".to_string(), "mrrp".to_string(), "mew".to_string()],
|
||||||
|
image_url: "testimage.jpg".to_string()
|
||||||
|
},
|
||||||
|
_ => Self {
|
||||||
|
seed: entry.seed,
|
||||||
|
title: "not found".to_string(),
|
||||||
|
author: Author::from_entry(entry),
|
||||||
|
keywords: vec![],
|
||||||
|
image_url: "".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Post {
|
||||||
|
pub meta: PostMeta,
|
||||||
|
pub content: Vec<PostPart>,
|
||||||
|
}
|
||||||
|
impl BlogEntry for Post {
|
||||||
|
fn from_entry(entry: &mut Entry) -> Self {
|
||||||
|
return Self {
|
||||||
|
meta: PostMeta::from_entry(entry),
|
||||||
|
content: vec![PostPart::from_entry(entry)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum PostPart {
|
||||||
|
Section(Section),
|
||||||
|
Quote(Quote),
|
||||||
|
}
|
||||||
|
impl BlogEntry for PostPart {
|
||||||
|
fn from_entry(entry: &mut Entry) -> Self {
|
||||||
|
// TODO: ... | add proper logic
|
||||||
|
|
||||||
|
return Self::Section(Section::from_entry(entry))
|
||||||
|
// return Self::Quote(Quote::from_entry(entry))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Section {
|
||||||
|
pub title: String,
|
||||||
|
pub paragraphs: Vec<String>,
|
||||||
|
pub image_url: String,
|
||||||
|
}
|
||||||
|
impl BlogEntry for Section {
|
||||||
|
fn from_entry(entry: &mut Entry) -> Self {
|
||||||
|
return match entry.seed {
|
||||||
|
0 => Self {
|
||||||
|
title: "awawa title".to_string(),
|
||||||
|
image_url: "".to_string(),
|
||||||
|
paragraphs: vec![
|
||||||
|
"meow 1".to_string(),
|
||||||
|
"mrrp 2".to_string(),
|
||||||
|
"mew 3".to_string()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
_ => Self {
|
||||||
|
title: "not found".to_string(),
|
||||||
|
image_url: "".to_string(),
|
||||||
|
paragraphs: vec![]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Quote {
|
||||||
|
pub author: Author,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
impl BlogEntry for Quote {
|
||||||
|
fn from_entry(entry: &mut Entry) -> Self {
|
||||||
|
return Self { author: Author::from_seed(entry.seed), content: "awawa content".to_string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Entry {
|
||||||
|
pub seed: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
pub fn from_seed(seed: u8) -> Self {
|
||||||
|
Self { seed }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BlogEntry: Sized {
|
||||||
|
fn from_entry(entry: &mut Entry) -> Self;
|
||||||
|
fn from_seed(seed: u8) -> Self {
|
||||||
|
Self::from_entry(&mut Entry::from_seed(seed))
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/pages/blog/entries.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
|
use crate::pages::blog::content::Author;
|
||||||
|
use crate::pages::blog::content::BlogEntry;
|
||||||
|
use crate::pages::blog::entrycard::EntryCard;
|
||||||
|
use crate::pages::blog::navigation::{PageQuery, Pagination};
|
||||||
|
use crate::Route;
|
||||||
|
|
||||||
|
const ITEMS_PER_PAGE: u8 = 10;
|
||||||
|
const TOTAL_PAGES: u8 = 1;
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
PageUpdated,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Entries {
|
||||||
|
page: u8,
|
||||||
|
_listener: LocationHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_page(ctx: &Context<Entries>) -> u8 {
|
||||||
|
let location = ctx.link().location().unwrap();
|
||||||
|
|
||||||
|
location.query::<PageQuery>().map(|it| it.page as u8).unwrap_or(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Entries {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
let link = ctx.link().clone();
|
||||||
|
let listener = ctx
|
||||||
|
.link()
|
||||||
|
.add_location_listener(link.callback(move |_| Msg::PageUpdated))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
page: current_page(ctx),
|
||||||
|
_listener: listener,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::PageUpdated => self.page = current_page(ctx),
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
let page = self.page;
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div class="section container">
|
||||||
|
<h1 class="title">{ "entries" }</h1>
|
||||||
|
<h2 class="subtitle">{ "here are some things i yapped about" }</h2>
|
||||||
|
{ self.view_posts(ctx) }
|
||||||
|
<Pagination
|
||||||
|
{page}
|
||||||
|
total_pages={TOTAL_PAGES}
|
||||||
|
route_to_page={Route::Entries}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Entries {
|
||||||
|
fn view_posts(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let start_seed = (self.page - 1) * ITEMS_PER_PAGE;
|
||||||
|
let cards: Vec<_> = (0..ITEMS_PER_PAGE)
|
||||||
|
.filter(|&seed_offset| Author::from_seed(seed_offset).seed != u8::MAX)
|
||||||
|
.map(|seed_offset| {
|
||||||
|
html! {
|
||||||
|
<li class="list-item mb-5">
|
||||||
|
<EntryCard seed={start_seed + seed_offset} />
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
html! {
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column">
|
||||||
|
<ul class="list">
|
||||||
|
{ for cards }
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
152
src/pages/blog/entry.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
|
use crate::Route;
|
||||||
|
use crate::pages::blog::content;
|
||||||
|
use crate::pages::blog::content::BlogEntry;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
|
||||||
|
pub struct Props {
|
||||||
|
pub seed: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
pub struct PostState {
|
||||||
|
pub inner: content::Post,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reducible for PostState {
|
||||||
|
type Action = u8;
|
||||||
|
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
|
||||||
|
Self { inner: content::Post::from_seed(action.into()), }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Entry(props: &Props) -> Html {
|
||||||
|
let seed = props.seed;
|
||||||
|
|
||||||
|
let post = use_reducer(|| PostState {
|
||||||
|
inner: content::Post::from_seed(seed.into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
let post_dispatcher = post.dispatcher();
|
||||||
|
use_effect_with(seed, move |seed| {
|
||||||
|
post_dispatcher.dispatch(*seed);
|
||||||
|
|
||||||
|
|| {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let post = &post.inner;
|
||||||
|
|
||||||
|
let render_quote = |quote: &content::Quote| {
|
||||||
|
html! {
|
||||||
|
<article class="media block box my-6">
|
||||||
|
<figure class="media-left">
|
||||||
|
<p class="image is-64x64">
|
||||||
|
<img alt="The author's profile" src={quote.author.image_url.clone()} loading="lazy" />
|
||||||
|
</p>
|
||||||
|
</figure>
|
||||||
|
<div class="media-content">
|
||||||
|
<div class="content">
|
||||||
|
<Link<Route> classes={classes!("is-size-5")} to={Route::Author { id: quote.author.seed }}>
|
||||||
|
<strong>{ "e.author.name }</strong>
|
||||||
|
</Link<Route>>
|
||||||
|
<p class="is-family-secondary">
|
||||||
|
{ "e.content }
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let render_section_hero = |section: &content::Section| {
|
||||||
|
html! {
|
||||||
|
<section class="hero is-dark has-background mt-6 mb-3">
|
||||||
|
<img alt="This section's image" class="hero-background is-transparent" src={section.image_url.clone()} loading="lazy" />
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="subtitle">{ §ion.title }</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let render_section = |section, show_hero| {
|
||||||
|
let hero = if show_hero {
|
||||||
|
render_section_hero(section)
|
||||||
|
} else {
|
||||||
|
html! {}
|
||||||
|
};
|
||||||
|
let paragraphs = section.paragraphs.iter().map(|paragraph| {
|
||||||
|
html! {
|
||||||
|
<p>{ paragraph }</p>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
html! {
|
||||||
|
<section>
|
||||||
|
{ hero }
|
||||||
|
<div>{ for paragraphs }</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let view_content = {
|
||||||
|
// don't show hero for the first section
|
||||||
|
let mut show_hero = false;
|
||||||
|
|
||||||
|
let parts = post.content.iter().map(|part| match part {
|
||||||
|
content::PostPart::Section(section) => {
|
||||||
|
let html = render_section(section, show_hero);
|
||||||
|
// show hero between sections
|
||||||
|
show_hero = true;
|
||||||
|
html
|
||||||
|
}
|
||||||
|
content::PostPart::Quote(quote) => {
|
||||||
|
// don't show hero after a quote
|
||||||
|
show_hero = false;
|
||||||
|
render_quote("e)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
html! {{for parts}}
|
||||||
|
};
|
||||||
|
|
||||||
|
let keywords = post
|
||||||
|
.meta
|
||||||
|
.keywords
|
||||||
|
.iter()
|
||||||
|
.map(|keyword| html! { <span class="tag is-info">{ keyword }</span> });
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<section class="hero is-medium is-light has-background">
|
||||||
|
<img alt="The hero's background" class="hero-background is-transparent" src={post.meta.image_url.clone()} />
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
{ &post.meta.title }
|
||||||
|
</h1>
|
||||||
|
<h2 class="subtitle">
|
||||||
|
{ "by " }
|
||||||
|
<Link<Route> classes={classes!("has-text-weight-semibold")} to={Route::Author { id: post.meta.author.seed }}>
|
||||||
|
{ &post.meta.author.name }
|
||||||
|
</Link<Route>>
|
||||||
|
</h2>
|
||||||
|
<div class="tags">
|
||||||
|
{ for keywords }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div class="section container">
|
||||||
|
{ view_content }
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/pages/blog/entrycard.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::components::Link;
|
||||||
|
|
||||||
|
use crate::pages::blog::content::BlogEntry;
|
||||||
|
use crate::pages::blog::content::PostMeta;
|
||||||
|
|
||||||
|
use crate::Route;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||||
|
pub struct PropsEntryCard {
|
||||||
|
pub seed: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
pub struct PostMetaState {
|
||||||
|
inner: PostMeta,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reducible for PostMetaState {
|
||||||
|
type Action = u8;
|
||||||
|
|
||||||
|
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
|
||||||
|
Self { inner: PostMeta::from_seed(action.into()), }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn EntryCard(props: &PropsEntryCard) -> Html {
|
||||||
|
let seed = props.seed;
|
||||||
|
|
||||||
|
let post = use_reducer_eq(|| PostMetaState {
|
||||||
|
inner: PostMeta::from_seed(seed.into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
let post_dispatcher = post.dispatcher();
|
||||||
|
use_effect_with(seed, move |seed| {
|
||||||
|
post_dispatcher.dispatch(*seed);
|
||||||
|
|
||||||
|
|| {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let post = &post.inner;
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div class="card" style="height: 300px !important; max-height: 300px !important;">
|
||||||
|
<img loading="eager" class="card-image image is-2by1" src={post.image_url.clone()} alt="blog image" style="
|
||||||
|
object-fit: cover !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
height: 200px !important;
|
||||||
|
max-height: 200px !important;
|
||||||
|
filter: blur(6px) !important;
|
||||||
|
" />
|
||||||
|
<div class="card-content" style="height: fit-content !important; width: fit-content !important;">
|
||||||
|
<Link<Route> classes={classes!("title", "is-block")} to={Route::Entry { id: post.seed }}>
|
||||||
|
{ &post.title }
|
||||||
|
</Link<Route>>
|
||||||
|
<Link<Route> classes={classes!("subtitle", "is-block")} to={Route::Author { id: post.author.seed }}>
|
||||||
|
{ &post.author.name }
|
||||||
|
</Link<Route>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/pages/blog/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
pub mod content;
|
||||||
|
pub mod entries;
|
||||||
|
pub mod entry;
|
||||||
|
pub mod entrycard;
|
||||||
|
pub mod authors;
|
||||||
|
pub mod author;
|
||||||
|
pub mod authorcard;
|
||||||
|
pub mod navigation;
|
||||||
213
src/pages/blog/navigation.rs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::components::Link;
|
||||||
|
|
||||||
|
use crate::Route;
|
||||||
|
|
||||||
|
const ELLIPSIS: &str = "\u{02026}";
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
|
||||||
|
pub struct PageQuery {
|
||||||
|
pub page: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||||
|
pub struct PropsNavigation {
|
||||||
|
pub page: u8,
|
||||||
|
pub total_pages: u8,
|
||||||
|
pub route_to_page: Route,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn RelNavButtons(props: &PropsNavigation) -> Html {
|
||||||
|
let PropsNavigation {
|
||||||
|
page,
|
||||||
|
total_pages,
|
||||||
|
route_to_page: to,
|
||||||
|
} = props.clone();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<Link<Route, PageQuery>
|
||||||
|
classes={classes!("pagination-previous")}
|
||||||
|
disabled={page==1}
|
||||||
|
query={Some(PageQuery{page: page - 1})}
|
||||||
|
to={to.clone()}
|
||||||
|
>
|
||||||
|
{ "Previous" }
|
||||||
|
</Link<Route, PageQuery>>
|
||||||
|
<Link<Route, PageQuery>
|
||||||
|
classes={classes!("pagination-next")}
|
||||||
|
disabled={page==total_pages}
|
||||||
|
query={Some(PageQuery{page: page + 1})}
|
||||||
|
{to}
|
||||||
|
>
|
||||||
|
{ "Next page" }
|
||||||
|
</Link<Route, PageQuery>>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RenderLinksProps {
|
||||||
|
range: Range<u8>,
|
||||||
|
len: usize,
|
||||||
|
max_links: usize,
|
||||||
|
props: PropsNavigation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn RenderLinks(props: &RenderLinksProps) -> Html {
|
||||||
|
let RenderLinksProps {
|
||||||
|
range,
|
||||||
|
len,
|
||||||
|
max_links,
|
||||||
|
props,
|
||||||
|
} = props.clone();
|
||||||
|
|
||||||
|
let mut range = range;
|
||||||
|
|
||||||
|
if len > max_links {
|
||||||
|
let last_link =
|
||||||
|
html! {<RenderLink to_page={range.next_back().unwrap()} props={props.clone()} />};
|
||||||
|
let links = range
|
||||||
|
.take(max_links - 2)
|
||||||
|
.map(|page| html! {<RenderLink to_page={page} props={props.clone()} />});
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
{ for links }
|
||||||
|
<li><span class="pagination-ellipsis">{ ELLIPSIS }</span></li>
|
||||||
|
{ last_link }
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html! {
|
||||||
|
for page in range {
|
||||||
|
<RenderLink to_page={page} props={props.clone()} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RenderLinkProps {
|
||||||
|
to_page: u8,
|
||||||
|
props: PropsNavigation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn RenderLink(props: &RenderLinkProps) -> Html {
|
||||||
|
let RenderLinkProps { to_page, props } = props.clone();
|
||||||
|
|
||||||
|
let PropsNavigation {
|
||||||
|
page,
|
||||||
|
route_to_page,
|
||||||
|
..
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let is_current_class = if to_page == page { "is-current" } else { "" };
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<li>
|
||||||
|
<Link<Route, PageQuery>
|
||||||
|
classes={classes!("pagination-link", is_current_class)}
|
||||||
|
to={route_to_page}
|
||||||
|
query={Some(PageQuery{page: to_page})}
|
||||||
|
>
|
||||||
|
{ to_page }
|
||||||
|
</Link<Route, PageQuery>>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Links(props: &PropsNavigation) -> Html {
|
||||||
|
const LINKS_PER_SIDE: usize = 3;
|
||||||
|
|
||||||
|
let PropsNavigation {
|
||||||
|
page, total_pages, ..
|
||||||
|
} = *props;
|
||||||
|
|
||||||
|
let pages_prev = page.checked_sub(1).unwrap_or_default() as usize;
|
||||||
|
let pages_next = (total_pages - page) as usize;
|
||||||
|
|
||||||
|
let links_left = LINKS_PER_SIDE.min(pages_prev)
|
||||||
|
// if there are less than `LINKS_PER_SIDE` to the right, we add some more on the left.
|
||||||
|
+ LINKS_PER_SIDE.checked_sub(pages_next).unwrap_or_default();
|
||||||
|
let links_right = 2 * LINKS_PER_SIDE - links_left;
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<RenderLinks range={ 1..page } len={pages_prev} max_links={links_left} props={props.clone()} />
|
||||||
|
<RenderLink to_page={page} props={props.clone()} />
|
||||||
|
<RenderLinks range={ page + 1..total_pages + 1 } len={pages_next} max_links={links_right} props={props.clone()} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Pagination(props: &PropsNavigation) -> Html {
|
||||||
|
html! {
|
||||||
|
<nav class="pagination is-right" role="navigation" aria-label="pagination">
|
||||||
|
<RelNavButtons ..{props.clone()} />
|
||||||
|
<ul class="pagination-list">
|
||||||
|
<Links ..{props.clone()} />
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Nav() -> Html {
|
||||||
|
let navbar_active = use_state_eq(|| false);
|
||||||
|
|
||||||
|
let toggle_navbar = {
|
||||||
|
let navbar_active = navbar_active.clone();
|
||||||
|
|
||||||
|
Callback::from(move |_| {
|
||||||
|
navbar_active.set(!*navbar_active);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let active_class = if !*navbar_active { "is-active" } else { "" };
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<h1 class="navbar-item is-size-3">{ "Yew Blog" }</h1>
|
||||||
|
|
||||||
|
<button class={classes!("navbar-burger", "burger", active_class)}
|
||||||
|
aria-label="menu" aria-expanded="false"
|
||||||
|
onclick={toggle_navbar}
|
||||||
|
>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class={classes!("navbar-menu", active_class)}>
|
||||||
|
<div class="navbar-start">
|
||||||
|
<Link<Route> classes={classes!("navbar-item")} to={Route::Entries}> // TODO: ...
|
||||||
|
{ "Home" }
|
||||||
|
</Link<Route>>
|
||||||
|
<Link<Route> classes={classes!("navbar-item")} to={Route::Entries}>
|
||||||
|
{ "Posts" }
|
||||||
|
</Link<Route>>
|
||||||
|
|
||||||
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
|
<div class="navbar-link">
|
||||||
|
{ "More" }
|
||||||
|
</div>
|
||||||
|
<div class="navbar-dropdown">
|
||||||
|
<Link<Route> classes={classes!("navbar-item")} to={Route::Authors}>
|
||||||
|
{ "Meet the authors" }
|
||||||
|
</Link<Route>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/pages/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod about;
|
||||||
|
pub mod blog;
|
||||||
|
pub mod projects;
|
||||||
|
|
||||||
|
pub mod not_found;
|
||||||
26
src/pages/not_found.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
pub struct PageNotFound;
|
||||||
|
|
||||||
|
impl Component for PageNotFound {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
// TODO: ... | personalize
|
||||||
|
html! {
|
||||||
|
<section class="hero is-danger is-bold is-large">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">{ "Page not found" }</h1>
|
||||||
|
<h2 class="subtitle">{ "Page page does not seem to exist" }</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
79
src/pages/projects.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
pub struct Projects;
|
||||||
|
impl Component for Projects {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<div class="tile is-parent container mb-5 mt-5" style="height: auto !important;">
|
||||||
|
{ self.view_projects_ongoing() }
|
||||||
|
{ self.view_projects_finished() }
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Projects {
|
||||||
|
fn view_projects_ongoing(&self) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<p class="title ml-2 is-size-4">{r#"ongoing projects"#}</p>
|
||||||
|
<div class="columns is-size-7 ml-3 mr-3" style="margin-top: var(--bulma-block-spacing) !important;">
|
||||||
|
<div class="column box mb-5 mr-1">
|
||||||
|
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/i0uring/app_catnip">
|
||||||
|
{r#"catnip"#}
|
||||||
|
</a></p>
|
||||||
|
<p class="content">{r#"all-rounder ide in the making"#}</p>
|
||||||
|
</div>
|
||||||
|
<div class="column box mb-5 ml-1">
|
||||||
|
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/i0uring/app_nekochat">
|
||||||
|
{r#"neko chat"#}
|
||||||
|
</a></p>
|
||||||
|
<p class="content">{r#"my planned matrix client"#}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_projects_finished(&self) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<p class="title ml-2 is-size-4">{r#"finished projects"#}</p>
|
||||||
|
<div class="columns is-size-7 ml-3 mr-3">
|
||||||
|
<div class="column box mb-5 mr-1">
|
||||||
|
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/i0uring/lib_tinyevents">
|
||||||
|
{r#"tiny events"#}
|
||||||
|
</a></p>
|
||||||
|
<p class="content">{r#"a java seventeen (and up) event-sys that is able to be scaled in large systems."#}</p>
|
||||||
|
</div>
|
||||||
|
<div class="column box mb-5 ml-1 mr-2">
|
||||||
|
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/i0uring/dotfiles">
|
||||||
|
{r#"dotfiles"#}
|
||||||
|
</a></p>
|
||||||
|
<p class="content">{r#"my personal set of configurations for different things"#}</p>
|
||||||
|
</div>
|
||||||
|
<div class="column box mb-5 mr-1">
|
||||||
|
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/i0uring/lib_swingify">
|
||||||
|
{r#"swingify"#}
|
||||||
|
</a></p>
|
||||||
|
<p class="content">{r#"my java swing wrapper to simplify window creation for bogus-brains."#}</p>
|
||||||
|
</div>
|
||||||
|
<div class="column box mb-5 ml-1">
|
||||||
|
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/stellaris/mod_mnet">
|
||||||
|
{r#"modern netty"#}
|
||||||
|
</a></p>
|
||||||
|
<p class="content">{r#"fabric mod that adds experimental iouring and kqueue support."#}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||