Avatar

iamseeley

31 public vals
Joined April 1, 2024

A list of all the vals used in my hono + htmx site

~ thanks to @mxdvl for making the val to show many vals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { alias } from "https://esm.town/v/neverstew/alias";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { html } from "https://esm.town/v/pomdtr/gfm";
import { readme } from "https://esm.town/v/pomdtr/readme";
const vals = await Promise.all(
[
"iamseeley/Server2",
"iamseeley/RootLayout",
"iamseeley/LandingPage",
"iamseeley/EditProfilePage",
"iamseeley/ProfilePage",
"iamseeley/SubmitSignup",
"iamseeley/SubmitLogin",
"iamseeley/SignupModal",
"iamseeley/LoginModal",
"iamseeley/AddLink",
"iamseeley/EditLinkModal",
"iamseeley/Queries",
"iamseeley/profileHandlers",
"iamseeley/linkHandlers",
"iamseeley/UserLinks",
"iamseeley/UserProfileHeader",
"iamseeley/Nav",
"iamseeley/Header"
]
.map(async id => {
const [username, valName] = id.split("/");
return alias({ username, valName });
}),
);
export default async function(req: Request) {
return new Response(
`
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HonoHTMX Val</title>
<style>${css}</style>
<link rel="preconnect" href="https://fonts.gstatic.com">
<!-- IBM Plex Sans -->
<link rel="stylesheet" href="https://static.esm.town/build/_assets/400-X2O3NP5A.css">
<link rel="stylesheet" href="https://static.esm.town/build/_assets/400-italic-MXNJ43MZ.css">
<link rel="stylesheet" href="https://static.esm.town/build/_assets/600-FEUL63OX.css">
<link rel="stylesheet" href="https://static.esm.town/build/_assets/700-5YNEZ3QH.css">
<link rel="stylesheet" href="https://static.esm.town/build/_assets/700-italic-RWWCWQ6V.css">
<link rel="stylesheet" href="https://static.esm.town/build/_assets/700-RV7MGDVG.css">
</head>
<body>
<h1>
hono 🤝 htmx 🤝 val town
</h1>
<ul>
${
vals.map((val) => {
const readmeContent = val.readme ? html(val.readme) : "";
return `<li>
${readmeContent}
<details>
<summary><span>${val.author.username}/${val.name} (v${val.version})</span><hr><span>${heart}${val.likeCount} ${ref}${val.referenceCount}</span></summary>
<iframe src="https://www.val.town/embed/${val.author.username}/${val.name}"></iframe>
</details>
</li>`;
}).join("\n")
}
</ul>
</body>
</html>`,
{
headers: {
"Content-Type": "text/html; charset=utf-8",
},
},
);
}
const heart =
`<svg viewBox="0 0 24 24"><path d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322
const ref =
`<svg viewBox="0 0 24 24"><path d="M16.5 12a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm0 0c0 1.657 1.007 3 2.25 3S21 13.657 21 12a9 9 0 1 0-2.636 6.364M16.5 12V8.25" /></svg>`;
const css = `
html { padding: 0.5em; }
body { font-family: IBM Plex Sans, system-ui; max-width: 56rem; margin: 0 auto; }
h1 { padding-top: 0.5em; }
ul { list-style-type: none; padding: 0; }
li { background: rgb(249 250 251); padding: 0.5em; border-radius: 0.25em; border: 1px solid #e5e7eb; }
li + li { margin-block: 1rem; }
li blockquote { font-weight: 500; font-style: italic; border-left: 0.25rem solid rgb(229 231 235); margin-block: 1.6em; margin-inline: 0; padding-left: 1em; }
summary { cursor: pointer; display: flex; align-items: center; }
summary hr { flex-grow: 96; border: none; }
summary svg { height: 0.875em; position: relative; top: 0.125em; stroke: currentColor; stroke-width: 2; fill: none; }
iframe { border: none; min-height: 24rem; width: 100% }`;

🚧 hono + htmx web app 🚧

idea: linktree-esque profile page w/ widgets powered by vals

setup:

  • fork the val and uncomment the /signup and /login routes
  • create a jwt secret token environment variable
  • go to the db setup val and run it to create the tables (as the site is right now, you can only add/edit users and add/edit/delete user links)

to do:

  • create some val town apis for the profile widgets (add vals people have already made)
  • add profile image (will probably point to val town profile pic)
  • add delete profile handler
  • finish public profile page
  • 🎨🎨🎨🎨🎨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/** @jsxImportSource https://esm.sh/hono@latest/jsx **/
import { jsx, Fragment } from 'https://deno.land/x/hono/middleware.ts'
import { Hono } from "npm:hono@3";
import { sqlite } from "https://esm.town/v/std/sqlite";
import { email } from "https://esm.town/v/std/email?v=11";
import { jwt } from 'npm:hono/jwt';
import RootLayout from "https://esm.town/v/iamseeley/RootLayout";
import LandingPage from "https://esm.town/v/iamseeley/LandingPage";
import EditProfilePage from "https://esm.town/v/iamseeley/EditProfilePage";
import ProfilePage from "https://esm.town/v/iamseeley/ProfilePage";
import SubmitSignup from "https://esm.town/v/iamseeley/SubmitSignup";
import SubmitLogin from "https://esm.town/v/iamseeley/SubmitLogin";
import { logger } from 'npm:hono/logger';
import SignupModal from "https://esm.town/v/iamseeley/SignupModal";
import LoginModal from "https://esm.town/v/iamseeley/LoginModal";
import AddLink from "https://esm.town/v/iamseeley/AddLink";
import EditLinkModal from "https://esm.town/v/iamseeley/EditLinkModal";
import { getLinkById, getUserByUsername } from "https://esm.town/v/iamseeley/Queries";
import { getUserProfileHandler, updateUserProfileHandler } from "https://esm.town/v/iamseeley/profileHandlers";
import { addLinkHandler, updateLinkHandler, deleteLinkHandler } from "https://esm.town/v/iamseeley/linkHandlers";
const app = new Hono();
app.use(logger());
const SECRET_KEY = Deno.env.get("JWT_SECRET_TOKEN");
if (!SECRET_KEY) {
console.error("JWT_SECRET_TOKEN is not set");
}
const jwtMiddleware = jwt({ secret: SECRET_KEY, cookie: 'token' });
app.get('/', (c) => c.html(<LandingPage title="link tree alt" description="simple link tree" />));
// user routes
app.post('/signup', SubmitSignup);
app.post('/login', SubmitLogin);
// app.delete('/users/:userId', jwtMiddleware, deleteUserHandler);
// link routes
app.get('/links/:linkId', jwtMiddleware, async (c) => {
const linkId = parseInt(c.req.param('linkId'), 10);
const link = await getLinkById(linkId);
return c.json(link);
});
app.put('/links/:linkId', jwtMiddleware, updateLinkHandler);
app.post('/links', jwtMiddleware, addLinkHandler);
app.delete('/links/:linkId', jwtMiddleware, deleteLinkHandler);
// user profile routes
app.get('/edit-profile/:username', jwtMiddleware, getUserProfileHandler);
app.post('/edit-profile/:username', jwtMiddleware, updateUserProfileHandler);
// app.get('/:username', userProfilePageHandler);
// components (htmx)
app.get('/signupModal', async (c) => c.html(<SignupModal />));
app.get('/loginModal', async (c) => c.html(<LoginModal />));
app.get('/addLinkModal/:username', async (c) => {
const username = c.req.param('username');
return c.html(<AddLink username={username} />);
});
app.get('/edit-link-modal/:username/:linkId', async (c) => {
const username = c.req.param('username');
const linkId = parseInt(c.req.param('linkId'), 10);
const link = await getLinkById(linkId);
return c.html(
<EditLinkModal
username={username}
linkId={link.id}
label={link.label}
url={link.url}
/>);
});
export default app.fetch;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
export const pyodideExample = () => {
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Machine Learning with Pyodide</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v0.25.1/full/pyodide.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<h1>Train and Test a Linear Regression Model using Python in WebAssembly</h1>
<input type="file" id="fileInput" accept=".csv">
<button id="trainModel">Train Model</button>
<div id="modelOptions" style="display: none;">
<h3>Select Features and Target</h3>
<label for="featureSelect">Select Feature:</label>
<select id="featureSelect"></select>
<label for="targetSelect">Select Target:</label>
<select id="targetSelect"></select>
<button id="train">Train</button>
</div>
<div id="testDataOptions" style="display: none;">
<h3>Upload Test Data</h3>
<input type="file" id="testDataFile" accept=".csv">
<button id="predict">Predict</button>
</div>
<div id="result"></div>
<div id="plotContainer"></div>
<script>
async function main() {
let pyodide = await loadPyodide();
await pyodide.loadPackage(['numpy', 'pandas', 'scikit-learn', 'matplotlib']);
console.log('Pyodide is ready to use!');
pyodide.runPython(\`
import pandas as pd
import numpy as np
import json
from sklearn.linear_model import LinearRegression
import base64
import io
import matplotlib.pyplot as plt
class LinearModel:
def __init__(self):
self.model = None
self.feature = None
self.target = None
self.df = None
def load_csv(self, file_content):
self.df = pd.read_csv(io.StringIO(file_content))
return self.df.to_json()
def get_columns(self):
return json.dumps(self.df.columns.tolist())
def train_model(self, feature, target):
self.feature = feature
self.target = target
X = self.df[[feature]].values
y = self.df[target].values
self.model = LinearRegression()
self.model.fit(X, y)
predictions = self.model.predict(X)
plt.figure()
plt.scatter(X, y, color='blue', label='Actual')
plt.plot(X, predictions, color='red', label='Predicted')
plt.xlabel(feature)
plt.ylabel(target)
plt.title('Linear Regression')
plt.legend()
buf = io.BytesIO()
plt.savefig(buf, format='png')
buf.seek(0)
img = base64.b64encode(buf.read()).decode('utf-8')
return img, self.model.coef_[0], self.model.intercept_
def predict(self, test_file_content):
test_df = pd.read_csv(io.StringIO(test_file_content))
X_test = test_df[self.feature].values.reshape(-1, 1)
predictions = self.model.predict(X_test)
return json.dumps({"Experience": X_test.flatten().tolist(), "Predicted": predictions.tolist()})
linear_model = LinearModel()
\`);
return pyodide;
}
const pyodide = main();
let jsonData;
const fileInput = document.getElementById('fileInput');
const trainModelButton = document.getElementById('trainModel');
const modelOptions = document.getElementById('modelOptions');
const featureSelect = document.getElementById('featureSelect');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
export const pyodideExample = () => {
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Data Analysis with Pyodide</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v0.25.1/full/pyodide.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<h1>Data Analysis using Python in WebAssembly</h1>
<input type="file" id="fileInput" accept=".csv">
<button id="analyze">Analyze</button>
<div id="analysisOptions" style="display: none;">
<h3>Select Analysis</h3>
<label for="columnSelect">Select Column:</label>
<select id="columnSelect"></select>
<button id="plotHistogram">Plot Histogram</button>
<button id="calculateStats">Calculate Statistics</button>
</div>
<div id="result"></div>
<div id="plotContainer"></div>
<script>
async function main() {
let pyodide = await loadPyodide();
await pyodide.loadPackage(['numpy', 'pandas', 'matplotlib']);
console.log('Pyodide is ready to use!');
pyodide.runPython(\`
import pandas as pd
import numpy as np
import json
import base64
import io
import matplotlib.pyplot as plt
def load_csv(file_content):
df = pd.read_csv(io.StringIO(file_content))
return df.to_json()
def get_columns(json_df):
df = pd.read_json(json_df)
return json.dumps(df.columns.tolist())
def calculate_statistics(json_df, column):
df = pd.read_json(json_df)
stats = df[column].describe().to_dict()
return json.dumps(stats)
def plot_histogram(json_df, column):
df = pd.read_json(json_df)
plt.figure()
df[column].hist(bins=30)
plt.xlabel(column)
plt.ylabel('Frequency')
plt.title(f'Histogram of {column}')
buf = io.BytesIO()
plt.savefig(buf, format='png')
buf.seek(0)
img = base64.b64encode(buf.read()).decode('utf-8')
return img
\`);
return pyodide;
}
const pyodide = main();
let jsonData;
const fileInput = document.getElementById('fileInput');
const analyzeButton = document.getElementById('analyze');
const analysisOptions = document.getElementById('analysisOptions');
const columnSelect = document.getElementById('columnSelect');
const resultElement = document.getElementById('result');
const plotContainer = document.getElementById('plotContainer');
analyzeButton.addEventListener('click', async () => {
const file = fileInput.files[0];
const reader = new FileReader();
reader.onload = async (e) => {
const fileContent = e.target.result;
jsonData = await pyodide.then(pyodide => pyodide.runPython(\`load_csv("""\${fileContent}""")\`));
const columns = await pyodide.then(pyodide => pyodide.runPython(\`get_columns(\${JSON.stringify(jsonData)})\`));
const columnsList = JSON.parse(columns);
columnSelect.innerHTML = '';
columnsList.forEach(col => {
const option = document.createElement('option');
option.value = col;
option.textContent = col;
columnSelect.appendChild(option);
});
analysisOptions.style.display = 'block';
};
reader.readAsText(file);
});
document.getElementById('calculateStats').addEventListener('click', async () => {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
export const pyodideExample = () => {
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Data Vis with Pyodide</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v0.25.1/full/pyodide.js"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<h1>Data Vis using Python in WebAssembly</h1>
<input type="text" id="numbers" placeholder="Enter a list of numbers, separated by commas">
<button id="plot">Plot</button>
<div id="plotContainer"></div>
<script>
async function main() {
let pyodide = await loadPyodide();
await pyodide.loadPackage('numpy');
console.log('Pyodide is ready to use!');
pyodide.runPython(\`
import numpy as np
import json
def plot_numbers(numbers):
x = np.array(numbers)
y_square = x ** 2
y_cube = x ** 3
data = {
'x': x.tolist(),
'y_square': y_square.tolist(),
'y_cube': y_cube.tolist()
}
return json.dumps(data)
\`);
return pyodide;
}
const pyodide = main();
const plotButton = document.getElementById('plot');
const numbersInput = document.getElementById('numbers');
const plotContainer = document.getElementById('plotContainer');
plotButton.addEventListener('click', async () => {
const numbers = numbersInput.value.split(',').map(Number);
const plotData = await pyodide.then(pyodide => pyodide.runPython(\`plot_numbers(\${JSON.stringify(numbers)})\`));
const data = JSON.parse(plotData);
const trace1 = {
x: data.x,
y: data.y_square,
mode: 'lines+markers',
name: 'Square'
};
const trace2 = {
x: data.x,
y: data.y_cube,
mode: 'lines+markers',
name: 'Cube'
};
const layout = {
title: 'Numbers and Their Powers',
xaxis: { title: 'Number' },
yaxis: { title: 'Value' }
};
Plotly.newPlot(plotContainer, [trace1, trace2], layout);
});
</script>
</body>
</html>
`;
return new Response(html, {
headers: {
"Content-Type": "text/html",
},
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
export const pyodideExample = () => {
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Pyodide App</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v0.25.1/full/pyodide.js"></script>
</head>
<body>
<h1>Calculate the square of a number and visualize powers using Python in WebAssembly</h1>
<input type="number" id="number" placeholder="Enter a number">
<button id="calculate">Calculate</button>
<p id="result"></p>
<div id="plot"></div>
<script>
async function main() {
let pyodide = await loadPyodide();
await pyodide.loadPackage('matplotlib');
console.log('Pyodide is ready to use!');
pyodide.runPython(\`
import matplotlib.pyplot as plt
import io
import base64
def square(x):
return x * x
def plot_powers(n):
x = list(range(1, n+1))
y_square = [i**2 for i in x]
y_cube = [i**3 for i in x]
plt.figure()
plt.plot(x, y_square, label='Square')
plt.plot(x, y_cube, label='Cube')
plt.xlabel('Number')
plt.ylabel('Value')
plt.title('Powers of Numbers')
plt.legend()
plt.grid(True)
buf = io.BytesIO()
plt.savefig(buf, format='png')
buf.seek(0)
img = base64.b64encode(buf.read()).decode('utf-8')
return img
\`);
return pyodide;
}
const pyodide = main();
const calculateButton = document.getElementById('calculate');
const numberInput = document.getElementById('number');
const resultElement = document.getElementById('result');
const plotContainer = document.getElementById('plot');
calculateButton.addEventListener('click', async () => {
const number = parseInt(numberInput.value);
const result = await pyodide.then(pyodide => pyodide.runPython(\`square(\${number})\`));
resultElement.textContent = \`The square of \${number} is \${result}.\`;
const img = await pyodide.then(pyodide => pyodide.runPython(\`plot_powers(\${number})\`));
plotContainer.innerHTML = \`<img src="data:image/png;base64,\${img}" />\`;
});
</script>
</body>
</html>
`;
return new Response(html, {
headers: {
"Content-Type": "text/html",
},
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
export const pyodideExample = () => {
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Pyodide App</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v0.25.1/full/pyodide.js"></script>
</head>
<body>
<h1>Calculate the square of a number using Python in WebAssembly</h1>
<input type="number" id="number" placeholder="Enter a number">
<button id="calculate">Calculate</button>
<p id="result"></p>
<script>
async function main() {
let pyodide = await loadPyodide();
console.log('Pyodide is ready to use!');
pyodide.globals.set("square", x => x * x);
return pyodide;
}
const pyodide = main();
const calculateButton = document.getElementById('calculate');
const numberInput = document.getElementById('number');
const resultElement = document.getElementById('result');
calculateButton.addEventListener('click', async () => {
const number = parseInt(numberInput.value);
const result = await pyodide.then(pyodide => pyodide.runPython(\`square(\${number})\`));
resultElement.textContent = \`The square of \${number} is \${result}.\`;
});
</script>
</body>
</html>
`;
return new Response(html, {
headers: {
"Content-Type": "text/html",
},
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** @jsx jsx */
/** @jsxFrag Fragment */
import { jsx, Fragment } from 'https://deno.land/x/hono/middleware.ts';
import MapBoxComponent from "https://esm.town/v/iamseeley/MapUser";
interface UserWidgetsProps {
user: {
city: string;
};
}
export default function UserWidgets({ user }: UserWidgetsProps) {
return (
<Fragment>
<MapBoxComponent city={user.city} />
{/* Add other user widgets here */}
</Fragment>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/** @jsx jsx */
/** @jsxFrag Fragment */
import { jsx, Fragment } from 'https://deno.land/x/hono/middleware.ts'
import Mapbox from 'npm:mapbox-gl';
interface MapboxProps {
city: string;
}
export default function MapboxComponent({ city }: MapboxProps) {
const mapContainer = <div id="map" style={{ width: '100%', height: '400px' }}></div>;
const initializeMap = () => {
Mapbox.accessToken = 'MAPBOX_ACCESS_TOKEN';
const map = new Mapbox.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [0, 0],
zoom: 2
});
fetch(`https://iamseeley-Map.web.val.run?city=${encodeURIComponent(city)}`)
.then(response => response.json())
.then(mapData => {
if (mapData.error) {
console.error('Error:', mapData.error);
return;
}
map.setCenter(mapData.center);
map.setZoom(mapData.zoom);
mapData.markers.forEach(marker => {
new Mapbox.Marker()
.setLngLat(marker.coordinates)
.setPopup(new Mapbox.Popup().setText(`${marker.title}: ${marker.description}`))
.addTo(map);
});
});
};
setTimeout(initializeMap, 0);
return mapContainer;
}
1
2
3
4
5
/** @jsx jsx */
/** @jsxFrag Fragment */
import { jsx, Fragment } from 'https://deno.land/x/hono/middleware.ts'
export default function ValCommits