This commit is contained in:
Daniel Flanagan 2024-02-18 14:18:51 -06:00
parent 178e3f582c
commit 3ddb34bee5
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
11 changed files with 194 additions and 35 deletions

49
.helix/languages.toml Normal file
View file

@ -0,0 +1,49 @@
[language-server.deno]
command = "deno"
args = ["lsp", "--unstable-kv"]
config.hostInfo = "helix"
[[language]]
name = "javascript"
scope = "source.js"
injection-regex = "(js|javascript)"
language-id = "javascript"
file-types = ["js", "mjs", "cjs", "rules", "es6", "pac", "jakefile"]
shebangs = ["node"]
comment-token = "//"
language-servers = [ "deno" ]
indent = { tab-width = 2, unit = "\t" }
auto-format = true
[[language]]
name = "jsx"
scope = "source.jsx"
injection-regex = "jsx"
language-id = "javascriptreact"
file-types = ["jsx"]
comment-token = "//"
language-servers = [ "deno" ]
indent = { tab-width = 2, unit = "\t" }
grammar = "javascript"
auto-format = true
[[language]]
name = "typescript"
scope = "source.ts"
injection-regex = "(ts|typescript)"
file-types = ["ts", "mts", "cts"]
language-id = "typescript"
shebangs = ["deno", "ts-node"]
language-servers = [ "deno" ]
indent = { tab-width = 2, unit = "\t" }
auto-format = true
[[language]]
name = "tsx"
scope = "source.tsx"
injection-regex = "(tsx)"
language-id = "typescriptreact"
file-types = ["tsx"]
language-servers = [ "deno" ]
indent = { tab-width = 2, unit = "\t" }
auto-format = true

17
api.ts Normal file
View file

@ -0,0 +1,17 @@
import { FreshContext, Handler } from '$fresh/server.ts'
import { TablesForModels } from '@lyrics/db.ts'
export function crudHandlerFor<
T extends { id: string },
M extends { parse: (a: T) => T },
P extends keyof typeof TablesForModels,
>(m: M, p: P): Handler {
return {
async GET(req: Request, ctx: FreshContext) {
if (req.
const resp = await ctx.render();
resp.headers.set("X-Custom-Header", "Hello");
return resp;
}
}
}

View file

@ -6,7 +6,7 @@ export function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
<button <button
{...props} {...props}
disabled={!IS_BROWSER || props.disabled} disabled={!IS_BROWSER || props.disabled}
class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors" class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 dark:bg-zinc-900 dark:text-zinc-100 dark:hover:bg-zinc-800 transition-colors"
/> />
); );
} }

42
db.ts Normal file
View file

@ -0,0 +1,42 @@
import { Display, Playlist, Song } from '@lyrics/models.ts'
export const kv = await Deno.openKv('lyrics')
export const TablesForModels = {
'song': Song,
'playlist': Playlist,
'display': Display,
}
function crudFor<
P extends keyof typeof TablesForModels,
T extends { id: string },
M extends { parse: (a: T) => T },
>(m: M, p: P) {
return {
async all(): Promise<T[]> {
const result: T[] = []
for await (const v of kv.list<T>({ prefix: [p] })) {
result.push(m.parse(v.value))
}
return result
},
async get(id: string): Promise<T | null> {
const e = await kv.get<T>([p, id])
if (e.value == null) return null
return m.parse(e.value)
},
async save(t: T) {
return await kv.set([p, t.id], t)
},
async delete(id: string) {
return await kv.delete([p, id])
},
}
}
export const db = {
song: crudFor(Song, 'song'),
playlist: crudFor(Playlist, 'playlist'),
display: crudFor(Display, 'display'),
}

View file

@ -2,7 +2,7 @@
"lock": false, "lock": false,
"tasks": { "tasks": {
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx", "check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -", "cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run -A -",
"manifest": "deno task cli manifest $(pwd)", "manifest": "deno task cli manifest $(pwd)",
"start": "deno run -A --watch=static/,routes/ dev.ts", "start": "deno run -A --watch=static/,routes/ dev.ts",
"build": "deno run -A dev.ts build", "build": "deno run -A dev.ts build",
@ -29,11 +29,14 @@
"tailwindcss": "npm:tailwindcss@3.4.1", "tailwindcss": "npm:tailwindcss@3.4.1",
"tailwindcss/": "npm:/tailwindcss@3.4.1/", "tailwindcss/": "npm:/tailwindcss@3.4.1/",
"tailwindcss/plugin": "npm:/tailwindcss@3.4.1/plugin.js", "tailwindcss/plugin": "npm:/tailwindcss@3.4.1/plugin.js",
"@lyrics/": "./",
"$std/": "https://deno.land/std@0.211.0/" "$std/": "https://deno.land/std@0.211.0/"
}, },
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "preact" "jsxImportSource": "preact"
}, },
"fmt": { "useTabs": true, "semiColons": false, "singleQuote": true },
"unstable": ["kv", "cron"],
"nodeModulesDir": true "nodeModulesDir": true
} }

View file

@ -2,26 +2,28 @@
// This file SHOULD be checked into source version control. // This file SHOULD be checked into source version control.
// This file is automatically updated during development when running `dev.ts`. // This file is automatically updated during development when running `dev.ts`.
import * as $_404 from "./routes/_404.tsx"; import * as $_404 from './routes/_404.tsx'
import * as $_app from "./routes/_app.tsx"; import * as $_app from './routes/_app.tsx'
import * as $api_joke from "./routes/api/joke.ts"; import * as $api_db from './routes/api/db.ts'
import * as $greet_name_ from "./routes/greet/[name].tsx"; import * as $api_joke from './routes/api/joke.ts'
import * as $index from "./routes/index.tsx"; import * as $greet_name_ from './routes/greet/[name].tsx'
import * as $Counter from "./islands/Counter.tsx"; import * as $index from './routes/index.tsx'
import { type Manifest } from "$fresh/server.ts"; import * as $Counter from './islands/Counter.tsx'
import { type Manifest } from '$fresh/server.ts'
const manifest = { const manifest = {
routes: { routes: {
"./routes/_404.tsx": $_404, './routes/_404.tsx': $_404,
"./routes/_app.tsx": $_app, './routes/_app.tsx': $_app,
"./routes/api/joke.ts": $api_joke, './routes/api/db.ts': $api_db,
"./routes/greet/[name].tsx": $greet_name_, './routes/api/joke.ts': $api_joke,
"./routes/index.tsx": $index, './routes/greet/[name].tsx': $greet_name_,
'./routes/index.tsx': $index,
}, },
islands: { islands: {
"./islands/Counter.tsx": $Counter, './islands/Counter.tsx': $Counter,
}, },
baseUrl: import.meta.url, baseUrl: import.meta.url,
} satisfies Manifest; } satisfies Manifest
export default manifest; export default manifest

11
main.ts
View file

@ -3,11 +3,12 @@
/// <reference lib="dom.iterable" /> /// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" /> /// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" /> /// <reference lib="deno.ns" />
/// <reference lib="deno.unstable" />
import "$std/dotenv/load.ts"; import '$std/dotenv/load.ts'
import { start } from "$fresh/server.ts"; import { start } from '$fresh/server.ts'
import manifest from "./fresh.gen.ts"; import manifest from '@lyrics/fresh.gen.ts'
import config from "./fresh.config.ts"; import config from '@lyrics/fresh.config.ts'
await start(manifest, config); await start(manifest, config)

40
models.ts Normal file
View file

@ -0,0 +1,40 @@
import { z } from 'https://deno.land/x/zod@v3.22.4/mod.ts'
export const Identifiable = z.object({
id: z.string().uuid(),
})
export type TIdentifiable = z.infer<typeof Identifiable>
export const Verse = z.object({
content: z.string(),
})
export type TVerse = z.infer<typeof Verse>
export const Map = z.object({
verseKeys: z.array(z.string()),
})
export type TMap = z.infer<typeof Map>
export const Song = Identifiable.merge(z.object({
title: z.string(),
verses: z.record(z.string(), Verse),
maps: z.record(z.string(), Map),
}))
export type TSong = z.infer<typeof Song>
export const PlaylistEntry = z.object({
songKey: z.string().uuid(),
mapKey: z.string(),
})
export type TPlaylistEntry = z.infer<typeof PlaylistEntry>
export const Playlist = Identifiable.merge(z.object({
entries: z.array(PlaylistEntry),
}))
export type TPlaylist = z.infer<typeof Playlist>
export const Display = Identifiable.merge(z.object({
playlistKey: z.string().uuid(),
songKeyIndex: z.number(),
}))
export type TDisplay = z.infer<typeof Display>

View file

@ -8,7 +8,7 @@ export default function App({ Component }: PageProps) {
<title>lyricscreen</title> <title>lyricscreen</title>
<link rel="stylesheet" href="/styles.css" /> <link rel="stylesheet" href="/styles.css" />
</head> </head>
<body> <body class="dark:bg-zinc-950">
<Component /> <Component />
</body> </body>
</html> </html>

5
routes/api/db.ts Normal file
View file

@ -0,0 +1,5 @@
import { FreshContext } from '$fresh/server.ts'
export const handler = (_req: Request, _ctx: FreshContext): Response => {
return new Response(';)')
}

View file

@ -4,7 +4,7 @@ import Counter from "../islands/Counter.tsx";
export default function Home() { export default function Home() {
const count = useSignal(3); const count = useSignal(3);
return ( return (
<div class="px-4 py-8 mx-auto bg-[#86efac]"> <div class="px-4 py-8 mx-auto bg-[#86efac] dark:bg-emerald-950 dark:text-zinc-100">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center"> <div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
<img <img
class="my-6" class="my-6"