andreterron/codeOnValTown

References

Referenced 41 times

Date Me Directory

This is entry-point val for the source code for the Date Me Directory. Contributions welcome!

This app uses Hono as the server framework and for JSX.

The vals are stored in Val Town SQLite.

Contributing

Forking this repo should mostly work, except for the sqlite database. You'll need to create the table & populate it with some data. This script should do it, but I think it has a couple bugs. If you're interested in contributing to this project contact me or comment on this val and I'll get it working for ya!

Todos

  • Make the SQLite database forkable and build a widget/workflow for that, ie fix @stevekrouse/dateme_sqlite
  • Require an email (that isn't shared publicly)
    • Verify the email address with a "magic link"
  • Refactor Location to an array of Lat, Lon
    • Geocode all the existing locations
    • Add a geocoder map input to the form
    • Allow selecting multiple location through the form
  • Profile performance & speed up site, possibly add more caching
  • Let people edit their forms
  • Featured profiles
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { form } from "https://esm.town/v/stevekrouse/date_me_form";
import browse from "https://esm.town/v/stevekrouse/dateme_browse";
import faq from "https://esm.town/v/stevekrouse/dateme_faq";
import home from "https://esm.town/v/stevekrouse/dateme_home";
import { dateMeRSS } from "https://esm.town/v/stevekrouse/dateMeRSS";
import { Hono } from "npm:hono@3";
const app = new Hono();
app.get("/", home);
app.get("/browse", browse);
app.route("/submit", form);
app.get("/faq", faq);
app.get("/rss.xml", c => dateMeRSS(c.req as unknown as Request));
export default modifyFetchHandler(app.fetch, {
style: `@media (max-width: 500px) {
.github-fork-ribbon {
display: none !important;
}
}`,
val: { handle: "stevekrouse", name: "dateme" },
});

Non-Violent Communication (NVC) Mobile Worksheet

Inspired by the lovely iGrok mobile app.

Readme
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
/** @jsxImportSource https://esm.sh/react */
import { modifyHtmlString } from "https://esm.town/v/andreterron/codeOnValTown?v=46";
import { renderToString } from "npm:react-dom/server";
export const reactExample = (request: Request) =>
new Response(
modifyHtmlString(
renderToString(
<html
style={{
height: "100%",
boxSizing: "border-box",
textAlign: "center",
fontFamily: "sans-serif",
fontSize: "4vw",
}}
>
<head>
<title>NVC</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body style={{ height: "100%", margin: 0, padding: 0 }}>
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
<div style={{ fontSize: "5vw", fontWeight: "bold", flex: "0 0 auto", padding: "15px" }}>
What are you feeling?
</div>
<div
style={{
boxSizing: "border-box",
flex: "2",
padding: "0 50px",
overflow: "auto",
gap: "30px",
display: "grid",
gridTemplateColumns: "1fr 1fr",
width: "100%",
}}
>
<div>
<div style={{ marginBottom: "20px" }}>
<div style={{ fontWeight: "bold" }}>Peaceful</div> <div>calm</div>
<div>content</div>
<div>satisfied</div>
<div>secure</div>
<div>centered</div>
<div>relieved</div>
<div>confident</div>
<div>comfortable</div>
<div>composed</div>
<div>
fulfilled
</div>
</div>
<div style={{ marginBottom: "20px" }}>
<div style={{ fontWeight: "bold" }}>Affectionate</div> <div>warm</div>
<div>tender</div>
<div>appreciative</div>
<div>passionate</div>
<div>compassionate</div>
<div>loving</div>
<div>touched</div>
</div>
<div style={{ marginBottom: "20px" }}>
<div style={{ fontWeight: "bold" }}>Happy</div> <div>glad</div>
<div>pleased</div>
<div>excited</div>
<div>joyful</div>
<div>delighted</div>
<div>amused</div>
<div>encouraged</div>
<div>grateful</div>
<div>hopeful</div>
<div>thrilled</div>
<div>moved</div>
</div>
<div style={{ marginBottom: "20px" }}>
<div style={{ fontWeight: "bold" }}>
Playful
</div>
<div>energetic</div>
<div>expansive</div>
<div>adventurous</div>
<div>mischievous</div>
<div>silly</div>
<div>light</div>
<div>hearted</div>
<div>enthusiastic</div>
<div>lively</div>
<div>optimistic</div>
</div>
<div style={{ marginBottom: "20px" }}>
<div style={{ fontWeight: "bold" }}>
Interested
</div>
<div>inspired</div>
<div>curious</div>
<div>surprised</div>
<div>stimulated</div>
<div>amazed</div>

Preview and edit blobs

Usage:

Create valimport blobEditor from "https://esm.town/v/pomdtr/blob_editor" export default blobEditor("article.md")

You can easily protect your val behind @pomdtr/password_auth or @pomdtr/email_auth

Readme
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
/** @jsxImportSource https://esm.sh/hono/jsx **/
import { useEffect, useRef } from "https://esm.sh/hono/jsx/dom";
export function BlobEditor(props: { blob: { language?: string; text: string } }) {
const ref = useRef();
const saveBlob = async () => {
const resp = await fetch("/edit", {
method: "POST",
body: JSON.stringify({
text: ref.current!.code,
}),
});
if (!resp.ok) {
alert(await resp.text());
return;
}
window.location.replace("/");
};
return (
<article style={{ height: "100vh", display: "flex", flexDirection: "column" }}>
<code-mirror
style={{
flexGrow: 1,
backgroundColor: "#F9FAFB",
color: "#373C44",
}}
class="overflow-auto"
ref={ref}
code={props.blob.text}
language={props.blob.language}
>
</code-mirror>
<footer>
<div class="grid">
<button onClick={saveBlob}>Save</button>
</div>
</footer>
</article>
);
}
export function blobEditor(key: string, options?: { title?: string }) {
return async (req: Request) => {
const { html } = await import("https://esm.town/v/pomdtr/gfm");
const { blob } = await import("https://esm.town/v/std/blob?v=11");
const { Hono } = await import("npm:hono");
const { Island } = await import("https://esm.town/v/pomdtr/hono_island");
const { HTTPException } = await import("npm:hono/http-exception");
const { jsxRenderer, useRequestContext } = await import("npm:hono/jsx-renderer");
const { extractValInfo } = await import("https://esm.town/v/pomdtr/extractValInfo");
async function readBlob(key: string) {
const resp = await blob.get(key);
if (resp.status == 200) {
return resp.text();
} else if (resp.status == 404) {
return "";
} else {
throw new Error(await resp.text());
}
}
const router = new Hono();
router.use(jsxRenderer(({ children }) => {
const c = useRequestContext();
const { pathname } = new URL(c.req.url);
return (
<html>
<head>
<link rel="icon" href="https://fav.farm/📃" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
/>
<script
type="module"
src="https://esm.town/v/pomdtr/hydrate_hono_islands"
>
</script>
<script
type="module"
src="https://cdn.jsdelivr.net/npm/code-mirror-web-component@0.0.18/dist/code-mirror.js"
>
</script>
</head>
<body
style={{
height: "100vh",
}}
>
{children}
</body>
</html>
);
}));

Blob Admin

This is a lightweight Blob Admin interface to view and debug your Blob data.

b7321ca2cd80899250589b9aa08bc3cae9c7cea276282561194e7fc537259b46.png

Use this button to install the val:

It uses basic authentication with your Val Town API Token as the password (leave the username field blank).

TODO

  • /new - render a page to write a new blob key and value
  • /edit/:blob - render a page to edit a blob (prefilled with the existing content)
  • /delete/:blob - delete a blob and render success
  • handle non-textual val properly
  • add upload/download buttons
  • merge edit and view pages
  • add client side navigation using htmx
  • use codemirror instead of a textarea for editing text blobs
Readme
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
/** @jsxImportSource https://esm.sh/hono@4.0.8/jsx **/
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import view_route from "https://esm.town/v/pomdtr/blob_admin_blob";
import create_route from "https://esm.town/v/pomdtr/blob_admin_create";
import delete_route from "https://esm.town/v/pomdtr/blob_admin_delete";
import edit_route from "https://esm.town/v/pomdtr/blob_admin_edit";
import { passwordAuth } from "https://esm.town/v/pomdtr/password_auth?v=74";
import { blob } from "https://esm.town/v/std/blob?v=11";
import { Hono } from "npm:hono@4.0.8";
import { jsxRenderer } from "npm:hono@4.0.8/jsx-renderer";
const app = new Hono();
app.use(
jsxRenderer(({ children }) => {
return (
<html>
<head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
/>
<script
type="module"
src="https://cdn.jsdelivr.net/npm/code-mirror-web-component@0.0.8/dist/code-mirror.js"
>
</script>
<title>Blob Admin</title>
</head>
<body>
<main class="container">
{children}
</main>
</body>
</html>
);
}),
);
app.get("/", async (c) => {
let blobs = await blob.list();
return c.render(
<div class="overflow-auto">
<h1>Blob Admin</h1>
<a href="/create" style={{ marginBottom: "1em", display: "inline-block" }}>New Blob</a>
<div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Size (kb)</th>
<th>Last Modified</th>
<th>Edit</th>
<th>Delete</th>
<th>Download</th>
</tr>
</thead>
{blobs.map(b => (
<tr>
<td>
<a href={`/view/${encodeURIComponent(b.key)}`}>
{b.key}
</a>
</td>
<td>{b.size / 1000}</td>
<td>{new Date(b.lastModified).toLocaleString()}</td>
<td>
<a href={`/edit/${encodeURIComponent(b.key)}`}>✍️</a>
</td>
<td>
<a href={`/delete/${encodeURIComponent(b.key)}`}>🗑️</a>
</td>
<td>
<a href={`/download/${encodeURIComponent(b.key)}`}>💾</a>
</td>
</tr>
))}
</table>
</div>
</div>,
);
});
app.route("/create", create_route);
app.route("/view", view_route);
app.route("/edit", edit_route);
app.route("/delete", delete_route);
app.get("/download/:key", (c) => {
return blob.get(c.req.param("key"));
});
export default modifyFetchHandler(passwordAuth(app.fetch));

SQLite Explorer

View and interact with your Val Town SQLite data. It's based off Steve's excellent SQLite Admin val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by LibSQL Studio by invisal. This is now more an SPA, with tables, queries and results showing up on the same page.

image.webp

Install

Install the latest stable version (v81) by forking this val:

Install Stable Release (v81)

Authentication

Login to your SQLite Explorer with password authentication with your Val Town API Token as the password.

Todos / Plans

  • improve error handling
  • improve table formatting
  • sticky table headers
  • add codemirror
  • add loading indication to the run button (initial version shipped)
  • add ability to favorite queries
  • add saving of last query run for a table (started)
  • add visible output for non-query statements
  • add schema viewing
  • add refresh to table list sidebar after CREATE/DROP/ALTER statements
  • add automatic execution of initial select query on double click
  • add views to the sidebar
  • add triggers to sidebar
  • add upload from SQL, CSV and JSON
  • add ability to connect to a non-val town Turso database
  • fix wonky sidebar separator height problem (thanks to @stevekrouse)
  • make result tables scrollable
  • add export to CSV, and JSON (CSV and JSON helper functions written in this val. Thanks to @pomdtr for merging the initial version!)
  • add listener for cmd+enter to submit query
Readme
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
/** @jsxImportSource https://esm.sh/hono@latest/jsx **/
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { iframeHandler } from "https://esm.town/v/nbbaier/iframeHandler";
import { resetStyle } from "https://esm.town/v/nbbaier/resetStyle";
import { sqlToCSV, sqlToJSON } from "https://esm.town/v/nbbaier/sqliteExportHelpers";
import {
EditorSection,
MockTable,
Separator,
Table,
TablesList,
} from "https://esm.town/v/nbbaier/sqliteStudioComponents";
import { sqliteStyle } from "https://esm.town/v/nbbaier/sqliteStyle";
import { passwordAuth } from "https://esm.town/v/pomdtr/password_auth?v=70";
import { ResultSet, sqlite } from "https://esm.town/v/std/sqlite";
import { reloadOnSaveFetchMiddleware } from "https://esm.town/v/stevekrouse/reloadOnSave";
import { Hono } from "npm:hono";
import type { FC } from "npm:hono/jsx";
import { jsxRenderer } from "npm:hono/jsx-renderer";
import papa from "npm:papaparse";
const HTML: FC = ({ children }) => {
return (
<html>
<head>
<title>SQLite Explorer</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
rel="stylesheet"
/>
<style
dangerouslySetInnerHTML={{ __html: resetStyle }}
/>
<style
dangerouslySetInnerHTML={{ __html: sqliteStyle }}
/>
<script src="https://unpkg.com/htmx.org@1.9.9/dist/htmx.min.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<script type="module" src="https://esm.town/v/nbbaier/resizeScript" />
<script type="module" src="https://esm.town/v/nbbaier/tableSelectScript" />
<script
type="module"
src="https://raw.esm.sh/code-mirror-web-component@0.0.20/dist/code-mirror.js"
>
</script>
</head>
<body _="
on keydown[event.metaKey and key is 'Enter'] log 'command + enter' then send submitForm to #sql-editor
">
<div class="root-container">
<header>
<h1>SQLite Explorer</h1>
</header>
{children}
</div>
<script type="module" src="https://esm.town/v/nbbaier/downloadScript" />
<script type="module" src="https://esm.town/v/nbbaier/enableButtonsScript" />
<script type="module" src="https://esm.town/v/nbbaier/getCodeScript" />
</body>
</html>
);
};
const app = new Hono();
app.use(
"*",
jsxRenderer(
({ children }) => {
return <HTML children={children} />;
},
{ docType: false },
),
);
app.get("/", async (c) => {
let data = await sqlite.batch(
[
`SELECT name FROM sqlite_schema WHERE type ='table' AND name NOT LIKE 'sqlite_%';`,
`SELECT name FROM sqlite_schema WHERE type ='view' AND name NOT LIKE 'sqlite_%';`,
],
);
let views = data[1].rows.map(view => {
return { type: "view", name: view[0] };
});
let tables = data[0].rows.map(view => {
return { type: "table", name: view[0] };
});
return c.render(
<main class="sidebar-layout">
<div class="sidebar">
<TablesList tables={[...tables, ...views]}></TablesList>

HTTP Client

Attach a postman-like http client to your vals, with bookmarks and history support

2fa974c630d393c3b4f419de8a142a2f35a11ceebda42d195b07623889e5604e.png

Usage

Wrap your http handler in an the httpClient middleware.

Create valimport {httpClient} from "https://esm.town/v/pomdtr/http_client" export default httpClient((req) => { return new Response("Hello World!") })

The http client will be shown on the root.

Adding bookmarks

You might want to bookmark some requests you need often. You can do it by passing a bookmark list as a middleware option:

Create valimport {httpClient} from "https://esm.town/v/pomdtr/http_client" export default httpClient((req) => { return new Response("Hello World!") }, { bookmarks: [ { "label": "Dummy Request", "request": new Request("https://dummyjson.com/products") } ]})

ad7b95611871eb090ddadfa48f73d14692280e5389db1592403053d311aaa623.png

Customizing the client path

Create valimport {httpClient} from "https://esm.town/v/pomdtr/http_client" export default httpClient((req) => { return new Response("Hello World!") }, { path: "/http-client" })

TODO

  • fix syntax highlighting on successive request
  • allow to prefill the initial request
Readme
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
/** @jsxImportSource npm:preact **/
import { html } from "https://esm.town/v/stevekrouse/html?v=5";
import { Base64 } from "npm:js-base64";
import { render } from "npm:preact-render-to-string";
type SerializedRequest = {
url: string;
method: string;
headers: [string, string][];
body: string | undefined;
};
type Bookmark = {
label: string;
request: Request;
};
async function serializeRequest(request: Request): Promise<SerializedRequest> {
return {
url: request.url,
method: request.method,
headers: Array.from(request.headers.entries()),
body: request.body ? Base64.encode(await request.text()) : undefined,
};
}
const getBody = async (bookmarks: Bookmark[]) => {
const serializedBookmarks = JSON.stringify(
await Promise.all(bookmarks.map(async (bookmark) => ({
label: bookmark.label,
request: await serializeRequest(bookmark.request),
}))),
);
return render(
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>REST Client</title>
<link rel="icon" href="https://fav.farm/🌐" />
<script src="https://cdn.tailwindcss.com"></script>
<script
type="module"
src="https://cdn.jsdelivr.net/npm/code-mirror-web-component@0.0.20/dist/code-mirror.js"
>
</script>
</head>
<body class="bg-orange-100">
<div id="app" data-bookmarks={serializedBookmarks}></div>
<script type="module" src="https://esm.town/v/pomdtr/http_client_component"></script>
</body>
</html>,
);
};
export function httpClient(next: (req: Request) => Response | Promise<Response>, options: {
path: string;
bookmarks: Bookmark[];
} = { path: "/", bookmarks: [] }) {
return async (req: Request) => {
const url = new URL(req.url);
if (url.pathname == options.path) {
return html(await getBody(options.bookmarks));
}
return next(req);
};
}
export default async function(req: Request) {
const bookmarks = [{ label: "Dummy Request", request: new Request("https://dummyjson.com/products") }];
return html(await getBody(bookmarks));
}

SQLite Explorer

View and interact with your Val Town SQLite data. It's based off Steve's excellent SQLite Admin val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by LibSQL Studio by invisal. This is now more an SPA, with tables, queries and results showing up on the same page.

image.webp

Install

Install the latest stable version (v66) by forking this val:

Install v66

Authentication

Login to your SQLite Explorer with password authentication with your Val Town API Token as the password.

Todos / Plans

  • improve error handling
  • improve table formatting
  • sticky table headers
  • add codemirror
  • add loading indication to the run button (initial version shipped)
  • add ability to favorite queries
  • add saving of last query run for a table (started)
  • add visible output for non-query statements
  • add schema viewing
  • add refresh to table list sidebar after CREATE/DROP/ALTER statements
  • add automatic execution of initial select query on double click
  • add views to the sidebar
  • add triggers to sidebar
  • add upload from SQL, CSV and JSON
  • add ability to connect to a non-val town Turso database
  • fix wonky sidebar separator height problem (thanks to @stevekrouse)
  • make result tables scrollable
  • add export to CSV, and JSON (CSV and JSON helper functions written in this val. Thanks to @pomdtr for merging the initial version!)
  • add listener for cmd+enter to submit query
Readme
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
/** @jsxImportSource https://esm.sh/hono@latest/jsx **/
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { resetStyle } from "https://esm.town/v/nbbaier/resetStyle";
import { sqlToCSV, sqlToJSON } from "https://esm.town/v/nbbaier/sqliteExportHelpers";
import {
EditorSection,
MockTable,
Separator,
Table,
TablesList,
} from "https://esm.town/v/nbbaier/sqliteStudioComponents";
import { RunSVG, TableSVG } from "https://esm.town/v/nbbaier/sqliteStudioSVGs";
import { sqliteStyle } from "https://esm.town/v/nbbaier/sqliteStyle";
import { passwordAuth } from "https://esm.town/v/pomdtr/password_auth?v=70";
import { sqlite } from "https://esm.town/v/std/sqlite";
import { Hono } from "npm:hono";
import type { FC } from "npm:hono/jsx";
import { jsxRenderer } from "npm:hono/jsx-renderer";
const HTML: FC = ({ children }) => {
return (
<html>
<head>
<title>SQLite Explorer</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
rel="stylesheet"
/>
<style
dangerouslySetInnerHTML={{ __html: resetStyle }}
/>
<style
dangerouslySetInnerHTML={{ __html: sqliteStyle }}
/>
<script src="https://unpkg.com/htmx.org@1.9.9/dist/htmx.min.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<script type="module" src="https://esm.town/v/nbbaier/resizeScript" />
<script type="module" src="https://esm.town/v/nbbaier/tableSelectScript" />
</head>
<body _="
on keydown[event.metaKey and key is 'Enter'] log 'command + enter' then send submitForm to #sql-editor
">
<div class="root-container">
<header>
<h1>sqlite explorer</h1>
</header>
{children}
</div>
<script type="module" src="https://esm.town/v/nbbaier/downloadScript" />
<script type="module" src="https://esm.town/v/nbbaier/enableButtonsScript" />
</body>
</html>
);
};
const app = new Hono();
app.use(
"*",
jsxRenderer(
({ children }) => {
return <HTML children={children} />;
},
{ docType: false },
),
);
app.get("/", async (c) => {
let data = await sqlite.execute(`
SELECT
name
FROM
sqlite_schema
WHERE
type ='table' AND
name NOT LIKE 'sqlite_%';`);
let tables = data.rows;
return c.render(
<main class="sidebar-layout">
<div class="sidebar">
<TablesList tables={tables}></TablesList>
</div>
<Separator direction="horizontal"></Separator>
<div class="not-sidebar">
<EditorSection />
<Separator direction="vertical" isResizer={true}></Separator>
<div class="bottom-container">
<div id="results-table" class="bottom">
</div>
</div>
</div>
</main>,
);
});
app.post("/query", async (c) => {
const query = (await c.req.parseBody()).query as string;

export const title = "mdx"

{title}

Usage

Create valimport { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo"; import { mdx } from "https://esm.town/v/pomdtr/mdx"; const { author, name } = extractValInfo(import.meta.url); export default mdx(author, name);
Readme
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
/** @jsxImportSource https://esm.sh/preact **/
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { api } from "https://esm.town/v/pomdtr/api";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { gfm } from "https://esm.town/v/pomdtr/gfm";
import { compile } from "npm:@mdx-js/mdx";
import { render } from "npm:preact-render-to-string";
const css = `
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
`;
export function mdx(author: string, name: string) {
return async (req: Request) => {
const { readme } = await api(`/v1/alias/${author}/${name}`);
const vfile = await compile(readme, {
jsxImportSource: "https://esm.sh/preact",
});
const url = new URL(req.url);
if (url.pathname == "/mod.js") {
return new Response(vfile.toString(), {
headers: {
"Content-Type": "text/javascript",
},
});
}
const blob = new Blob([vfile.toString()], {
type: "text/tsx",
});
const importURL = URL.createObjectURL(blob);
const { default: MDXContent } = await import(importURL);
return new Response(
render(
<html>
<head>
<title>{`${author}/${name}`}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="📝" rel="icon" />
<link
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.4.0/github-markdown.min.css"
rel="stylesheet"
/>
<link href="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/themes/prism.css" rel="stylesheet" />
<style dangerouslySetInnerHTML={{ __html: css }}></style>
</head>
<body class="markdown-body">
<MDXContent />
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/components/prism-core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/plugins/autoloader/prism-autoloader.min.js">
</script>
</body>
</html>,
),
{
headers: {
"Content-Type": "text/html",
},
},
);
};
}
const { author, name } = extractValInfo(import.meta.url);
export default mdx(author, name);

Code Search is Easy

Earlier this week, Tom MacWright posted Code Search is Hard. He describes the research he his doing to improve the code search experience of Val Town. It was a great read, and you might have seen it trending on Hacker News.

As Val Town's most active user (behind Steve Krouse, one of the founders of Val Town), I for sure agree with Tom that search feature of Val Town needs improvements. But while reading his post, I immediately thought of a different approach to the problem. And a few hours later, Val Town Search was born.

image.png

Do things that don't scale

How does this new shiny search engine work? Well, it's quite simple.

  1. I wrote a Deno script that fetches all vals from the Val Town API.
  2. I pushed the data to a Github Repository
  3. I added a Github Action that runs the script every hour to refresh the data.
  4. I created a simple frontend on top of the Github Search API that allows you to search the data. It's hosted on Val Town (obviously).

That was it. I didn't have to build a complex search engine, I just used the tools that were available to me.

Is this a scalable solution for Val Town? Probably not.
Am I abusing the Github API? Maybe.
Does it work better than the current search feature of Val Town? Absolutely!

image.png

I hope that the val.town engineers will come up with a search feature that will put my little project to shame. But for now, you won't find a better way to search for vals than Val Town Search.

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import codeOnValTown from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { serveReadme } from "https://esm.town/v/pomdtr/serve_readme";
const val = extractValInfo(import.meta.url);
let handler = serveReadme({
val,
title: "Code search is easy",
});
handler = codeOnValTown(
handler,
{
val: {
handle: val.author,
name: val.name,
},
},
);
export default handler;

SQLite Explorer

View and interact with your Val Town SQLite data. It's based off Steve's excellent SQLite Admin val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by LibSQL Studio by invisal. This is now more an SPA, with tables, queries and results showing up on the same page.

image.webp

Install

Install the latest stable version (v66) by forking this val:

Install v66

Authentication

Login to your SQLite Explorer with password authentication with your Val Town API Token as the password.

Todos / Plans

  • improve error handling
  • improve table formatting
  • sticky table headers
  • add codemirror
  • add loading indication to the run button (initial version shipped)
  • add ability to favorite queries
  • add saving of last query run for a table (started)
  • add visible output for non-query statements
  • add schema viewing
  • add refresh to table list sidebar after CREATE/DROP/ALTER statements
  • add automatic execution of initial select query on double click
  • add views to the sidebar
  • add triggers to sidebar
  • add upload from SQL, CSV and JSON
  • add ability to connect to a non-val town Turso database
  • fix wonky sidebar separator height problem (thanks to @stevekrouse)
  • make result tables scrollable
  • add export to CSV, and JSON (CSV and JSON helper functions written in this val. Thanks to @pomdtr for merging the initial version!)
  • add listener for cmd+enter to submit query
Readme
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
/** @jsxImportSource https://esm.sh/hono@latest/jsx **/
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { resetStyle } from "https://esm.town/v/nbbaier/resetStyle";
import { sqlToCSV, sqlToJSON } from "https://esm.town/v/nbbaier/sqliteExportHelpers";
import {
EditorSection,
MockTable,
Separator,
Table,
TablesList,
} from "https://esm.town/v/nbbaier/sqliteStudioComponents";
import { RunSVG, TableSVG } from "https://esm.town/v/nbbaier/sqliteStudioSVGs";
import { sqliteStyle } from "https://esm.town/v/nbbaier/sqliteStyle";
import { passwordAuth } from "https://esm.town/v/pomdtr/password_auth?v=70";
import { sqlite } from "https://esm.town/v/std/sqlite";
import { Hono } from "npm:hono";
import type { FC } from "npm:hono/jsx";
import { jsxRenderer } from "npm:hono/jsx-renderer";
const HTML: FC = ({ children }) => {
return (
<html>
<head>
<title>SQLite Explorer</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
rel="stylesheet"
/>
<style
dangerouslySetInnerHTML={{ __html: resetStyle }}
/>
<style
dangerouslySetInnerHTML={{ __html: sqliteStyle }}
/>
<script src="https://unpkg.com/htmx.org@1.9.9/dist/htmx.min.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<script type="module" src="https://esm.town/v/nbbaier/resizeScript" />
<script type="module" src="https://esm.town/v/nbbaier/tableSelectScript" />
</head>
<body _="
on keydown[event.metaKey and key is 'Enter'] log 'command + enter' then send submitForm to #sql-editor
">
<div class="root-container">
<header>
<h1>sqlite explorer</h1>
</header>
{children}
</div>
<script type="module" src="https://esm.town/v/nbbaier/downloadScript" />
<script type="module" src="https://esm.town/v/nbbaier/enableButtonsScript" />
</body>
</html>
);
};
const app = new Hono();
app.use(
"*",
jsxRenderer(
({ children }) => {
return <HTML children={children} />;
},
{ docType: false },
),
);
app.get("/", async (c) => {
let data = await sqlite.execute(`
SELECT
name
FROM
sqlite_schema
WHERE
type ='table' AND
name NOT LIKE 'sqlite_%';`);
let tables = data.rows;
return c.render(
<main class="sidebar-layout">
<div class="sidebar">
<TablesList tables={tables}></TablesList>
</div>
<Separator direction="horizontal"></Separator>
<div class="not-sidebar">
<EditorSection />
<Separator direction="vertical" isResizer={true}></Separator>
<div class="bottom-container">
<div id="results-table" class="bottom">
</div>
</div>
</div>
</main>,
);
});
app.post("/query", async (c) => {
const query = (await c.req.parseBody()).query as string;

SQLite Explorer (Dev Branch)

View and interact with your Val Town SQLite data. It's based off Steve's excellent SQLite Admin val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by LibSQL Studio by invisal. This is now more an SPA, with tables, queries and results showing up on the same page.

image.webp

Install

Install the latest stable version (v81) by forking this val:

Install Stable Release (v81)

Authentication

Login to your SQLite Explorer with password authentication with your Val Town API Token as the password.

Todos / Plans

  • improve error handling
  • improve table formatting
  • sticky table headers
  • add codemirror
  • add loading indication to the run button (initial version shipped)
  • add ability to favorite queries
  • add saving of last query run for a table (started)
  • add visible output for non-query statements
  • add schema viewing
  • add refresh to table list sidebar after CREATE/DROP/ALTER statements
  • add automatic execution of initial select query on double click
  • add views to the sidebar
  • add triggers to sidebar
  • add upload from SQL, CSV and JSON
  • add ability to connect to a non-val town Turso database
  • fix wonky sidebar separator height problem (thanks to @stevekrouse)
  • make result tables scrollable
  • add export to CSV, and JSON (CSV and JSON helper functions written in this val. Thanks to @pomdtr for merging the initial version!)
  • add listener for cmd+enter to submit query
Readme
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
/** @jsxImportSource https://esm.sh/hono@latest/jsx **/
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { iframeHandler } from "https://esm.town/v/nbbaier/iframeHandler";
import { resetStyle } from "https://esm.town/v/nbbaier/resetStyle";
import { sqlToCSV, sqlToJSON } from "https://esm.town/v/nbbaier/sqliteExportHelpers";
import {
EditorSection,
MockTable,
Separator,
Table,
TablesList,
} from "https://esm.town/v/nbbaier/sqliteStudioComponents";
import { sqliteStyle } from "https://esm.town/v/nbbaier/sqliteStyle";
import { passwordAuth } from "https://esm.town/v/pomdtr/password_auth?v=70";
import { ResultSet, sqlite } from "https://esm.town/v/std/sqlite";
import { reloadOnSaveFetchMiddleware } from "https://esm.town/v/stevekrouse/reloadOnSave";
import { Hono } from "npm:hono";
import type { FC } from "npm:hono/jsx";
import { jsxRenderer } from "npm:hono/jsx-renderer";
import papa from "npm:papaparse";
const HTML: FC = ({ children }) => {
return (
<html>
<head>
<title>SQLite Explorer</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
rel="stylesheet"
/>
<style
dangerouslySetInnerHTML={{ __html: resetStyle }}
/>
<style
dangerouslySetInnerHTML={{ __html: sqliteStyle }}
/>
<script src="https://unpkg.com/htmx.org@1.9.9/dist/htmx.min.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<script type="module" src="https://esm.town/v/nbbaier/resizeScript" />
<script type="module" src="https://esm.town/v/nbbaier/tableSelectScript" />
<script
type="module"
src="https://raw.esm.sh/code-mirror-web-component@0.0.20/dist/code-mirror.js"
>
</script>
</head>
<body _="
on keydown[event.metaKey and key is 'Enter'] log 'command + enter' then send submitForm to #sql-editor
">
<div class="root-container">
<header>
<h1>SQLite Explorer</h1>
</header>
{children}
</div>
<script type="module" src="https://esm.town/v/nbbaier/downloadScript" />
<script type="module" src="https://esm.town/v/nbbaier/enableButtonsScript" />
<script type="module" src="https://esm.town/v/nbbaier/getCodeScript" />
</body>
</html>
);
};
const app = new Hono();
app.use(
"*",
jsxRenderer(
({ children }) => {
return <HTML children={children} />;
},
{ docType: false },
),
);
app.get("/", async (c) => {
let data = await sqlite.batch(
[
`SELECT name FROM sqlite_schema WHERE type ='table' AND name NOT LIKE 'sqlite_%';`,
`SELECT name FROM sqlite_schema WHERE type ='view' AND name NOT LIKE 'sqlite_%';`,
],
);
let views = data[1].rows.map(view => {
return { type: "view", name: view[0] };
});
let tables = data[0].rows.map(view => {
return { type: "table", name: view[0] };
});
return c.render(
<main class="sidebar-layout">
<div class="sidebar">
<TablesList tables={[...tables, ...views]}></TablesList>

@postpostscript's ideas

Readme
1
2
3
4
5
6
7
8
9
10
// @vtIdeas
// @title my ideas
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo?v=26";
import { getValEndpointFromName } from "https://esm.town/v/postpostscript/meta";
export default function(req: Request) {
const { author, name } = extractValInfo(import.meta.url);
const blogEndpoint = getValEndpointFromName(`@${author}/blog`);
return Response.redirect(`${blogEndpoint}/${name}`);
}
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
/** @jsxImportSource https://esm.sh/react */
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=46";
import { sqlite } from "https://esm.town/v/std/sqlite?v=4";
import tailwindURL from "https://esm.town/v/stevekrouse/tailwindURL";
import { renderToString } from "npm:react-dom/server";
export const reactExample = modifyFetchHandler(async (request: Request) => {
const url = new URL(request.url);
if (url.pathname === "/favicon.ico") return new Response(null, { status: 404 });
const [, , data] = await sqlite.batch([
"CREATE TABLE IF NOT EXISTS stevekrouse_com_hits (timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)",
"INSERT INTO stevekrouse_com_hits DEFAULT VALUES",
"SELECT COUNT(*) FROM stevekrouse_com_hits",
]);
const hits = data.rows[0][0];
return new Response(
renderToString(
<html>
<head>
<title>Steve Krouse</title>
<script src={tailwindURL} />
</head>
<body>
<div className="max-w-md p-10 space-y-2">
<div>
hi, I'm Steve 👋
</div>
<div>
i work at{" "}
<a href="https://val.town" className="text-blue-500 hover:text-blue-700">val town</a>, a social website to
code in the cloud. i tweet at{" "}
<a href="https://twitter.com/stevekrouse" className="text-blue-500 hover:text-blue-700">@stevekrouse</a>
</div>
<div>
{hits.toString()} hits |{" "}
<a
href="https://stevekrouse.github.io/"
className="text-blue-500 hover:text-blue-700"
style={{ transform: "rotate(90deg)" }}
>
previous version
</a>
</div>
</div>
</body>
</html>,
),
{
headers: {
"Content-Type": "text/html",
},
},
);
});

SQLite Explorer (Dev Branch)

View and interact with your Val Town SQLite data. It's based off Steve's excellent SQLite Admin val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by LibSQL Studio by invisal. This is now more an SPA, with tables, queries and results showing up on the same page.

image.webp

Install

Install the latest stable version (v66) by forking this val:

Install v66

Authentication

Login to your SQLite Explorer with password authentication with your Val Town API Token as the password.

Todos / Plans

  • improve error handling
  • improve table formatting
  • sticky table headers
  • add codemirror
  • add loading indication to the run button (initial version shipped)
  • add ability to favorite queries
  • add saving of last query run for a table (started)
  • add visible output for non-query statements
  • add schema viewing
  • add refresh to table list sidebar after CREATE/DROP/ALTER statements
  • add automatic execution of initial select query on double click
  • add views to the sidebar
  • add triggers to sidebar
  • add upload from SQL, CSV and JSON
  • add ability to connect to a non-val town Turso database
  • fix wonky sidebar separator height problem (thanks to @stevekrouse)
  • make result tables scrollable
  • add export to CSV, and JSON (CSV and JSON helper functions written in this val. Thanks to @pomdtr for merging the initial version!)
  • add listener for cmd+enter to submit query
Readme
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
/** @jsxImportSource https://esm.sh/hono@latest/jsx **/
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { resetStyle } from "https://esm.town/v/nbbaier/resetStyle";
import { sqlToCSV, sqlToJSON } from "https://esm.town/v/nbbaier/sqliteExportHelpers";
import {
EditorSection,
MockTable,
Separator,
Table,
TablesList,
} from "https://esm.town/v/nbbaier/sqliteStudioComponents_DEV";
import { sqliteStyle } from "https://esm.town/v/nbbaier/sqliteStyle_DEV";
import { passwordAuth } from "https://esm.town/v/pomdtr/password_auth?v=70";
import { Statement } from "https://esm.town/v/postpostscript/sqliteBuilder";
import { ResultSet, sqlite } from "https://esm.town/v/std/sqlite";
import { reloadOnSaveFetchMiddleware } from "https://esm.town/v/stevekrouse/reloadOnSave";
import { Hono } from "npm:hono";
import type { FC } from "npm:hono/jsx";
import { jsxRenderer } from "npm:hono/jsx-renderer";
import papa from "npm:papaparse";
const HTML: FC = ({ children }) => {
return (
<html>
<head>
<title>SQLite Explorer (dev)</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
rel="stylesheet"
/>
<style
dangerouslySetInnerHTML={{ __html: resetStyle }}
/>
<style
dangerouslySetInnerHTML={{ __html: sqliteStyle }}
/>
<script src="https://unpkg.com/htmx.org@1.9.9/dist/htmx.min.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<script type="module" src="https://esm.town/v/nbbaier/resizeScript" />
<script type="module" src="https://esm.town/v/nbbaier/tableSelectScript" />
<script
type="module"
src="https://raw.esm.sh/code-mirror-web-component@0.0.20/dist/code-mirror.js"
>
</script>
</head>
<body _="
on keydown[event.metaKey and key is 'Enter'] log 'command + enter' then send submitForm to #sql-editor
">
<div class="root-container">
<header>
<h1>SQLite Explorer (dev)</h1>
</header>
{children}
</div>
<script type="module" src="https://esm.town/v/nbbaier/downloadScript_DEV" />
<script type="module" src="https://esm.town/v/nbbaier/enableButtonsScript_DEV" />
<script type="module" src="https://esm.town/v/nbbaier/getCodeScript" />
</body>
</html>
);
};
const app = new Hono();
app.use(
"*",
jsxRenderer(
({ children }) => {
return <HTML children={children} />;
},
{ docType: false },
),
);
app.get("/", async (c) => {
let data = await sqlite.batch(
[
`SELECT name FROM sqlite_schema WHERE type ='table' AND name NOT LIKE 'sqlite_%';`,
`SELECT name FROM sqlite_schema WHERE type ='view' AND name NOT LIKE 'sqlite_%';`,
],
);
let views = data[1].rows.map(view => {
return { type: "view", name: view[0] };
});
let tables = data[0].rows.map(view => {
return { type: "table", name: view[0] };
});
return c.render(
<main class="sidebar-layout">
<div class="sidebar">
<TablesList tables={[...tables, ...views]}></TablesList>
</div>

Blob Admin

This is a lightweight Blob Admin interface to view and debug your Blob data.

b7321ca2cd80899250589b9aa08bc3cae9c7cea276282561194e7fc537259b46.png

Use this button to install the val:

It uses basic authentication with your Val Town API Token as the password (leave the username field blank).

TODO

  • /new - render a page to write a new blob key and value
  • /edit/:blob - render a page to edit a blob (prefilled with the existing content)
  • /delete/:blob - delete a blob and render success
  • handle non-textual val properly
  • add upload/download buttons
  • merge edit and view pages
  • add client side navigation using htmx
  • use codemirror instead of a textarea for editing text blobs
Readme
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
/** @jsxImportSource https://esm.sh/hono@4.0.8/jsx **/
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import view_route from "https://esm.town/v/pomdtr/blob_admin_blob";
import create_route from "https://esm.town/v/pomdtr/blob_admin_create";
import delete_route from "https://esm.town/v/pomdtr/blob_admin_delete";
import edit_route from "https://esm.town/v/pomdtr/blob_admin_edit";
import { passwordAuth } from "https://esm.town/v/pomdtr/password_auth?v=74";
import { blob } from "https://esm.town/v/std/blob?v=11";
import { Hono } from "npm:hono@4.0.8";
import { jsxRenderer } from "npm:hono@4.0.8/jsx-renderer";
const app = new Hono();
app.use(
jsxRenderer(({ children }) => {
return (
<html>
<head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
/>
<title>Blob Admin</title>
</head>
<body>
<main class="container">
{children}
</main>
</body>
</html>
);
}),
);
app.get("/", async (c) => {
let blobs = await blob.list();
return c.render(
<div class="overflow-auto">
<h1>Blob Admin</h1>
<a href="/create" style={{ marginBottom: "1em", display: "inline-block" }}>New Blob</a>
<div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Size (kb)</th>
<th>Last Modified</th>
<th>Edit</th>
<th>Delete</th>
<th>Download</th>
</tr>
</thead>
{blobs.map(b => (
<tr>
<td>
<a href={`/view/${encodeURIComponent(b.key)}`}>
{b.key}
</a>
</td>
<td>{b.size / 1000}</td>
<td>{new Date(b.lastModified).toLocaleString()}</td>
<td>
<a href={`/edit/${encodeURIComponent(b.key)}`}>✍️</a>
</td>
<td>
<a href={`/delete/${encodeURIComponent(b.key)}`}>🗑️</a>
</td>
<td>
<a href={`/download/${encodeURIComponent(b.key)}`}>💾</a>
</td>
</tr>
))}
</table>
</div>
</div>,
);
});
app.route("/create", create_route);
app.route("/view", view_route);
app.route("/edit", edit_route);
app.route("/delete", delete_route);
app.get("/download/:key", (c) => {
return blob.get(c.req.param("key"));
});
export default modifyFetchHandler(passwordAuth(app.fetch));