├── app ├── favicon.ico ├── tools │ ├── tools-components │ │ └── bitmap-font-designer │ │ │ ├── bitmap-font-designer.tsx │ │ │ └── bitmap-font-utils.ts │ ├── tools.json │ ├── [slug] │ │ └── page.tsx │ └── page.tsx ├── not-found.tsx ├── recipes │ ├── screens │ │ ├── not-found │ │ │ └── not-found.tsx │ │ ├── simple-text │ │ │ └── simple-text.tsx │ │ ├── album │ │ │ └── album.tsx │ │ ├── responsive-example │ │ │ └── responsive-example.tsx │ │ ├── bitcoin-price │ │ │ └── bitcoin-price.tsx │ │ ├── bitmap-patterns │ │ │ └── bitmap-patterns.tsx │ │ └── weather │ │ │ └── weather.tsx │ └── page.tsx ├── mixup │ └── page.tsx ├── playlists │ └── page.tsx ├── maintenance │ └── page.tsx ├── system-logs │ └── page.tsx ├── device │ └── [friendly_id] │ │ └── page.tsx ├── actions │ ├── screens-params.ts │ └── execute-sql.ts ├── api │ └── bitmap │ │ └── [[...slug]] │ │ └── route.ts └── layout.tsx ├── .biomeignore ├── public ├── not-found.png ├── album │ └── london.png ├── fonts │ ├── BlockKie.ttf │ ├── geneva-12.otf │ ├── geneva-9.ttf │ └── Inter_18pt-Regular.ttf ├── static │ ├── not-found.bmp │ ├── simple-text.bmp │ └── trmnl-logo.svg ├── byos-nextjs-device.png ├── byos-nextjs-overview.png └── vercel.svg ├── postcss.config.mjs ├── vercel.json ├── lib ├── utils.ts ├── api │ └── types.ts ├── defaultDevice.ts ├── recipes │ └── constants.ts ├── database │ ├── db.ts │ ├── utils.ts │ └── db.d.ts ├── types.ts ├── fonts.ts └── getInitData.ts ├── migrations ├── 0002_add_playlist_index_to_devices.sql ├── 0001_add_device_status_fields.sql ├── 0006_add_screen_configs.sql ├── 0005_add_screen_size_settings.sql ├── 0003_add_playlists.sql ├── 0004_add_mixups.sql └── 0000_initial_schema.sql ├── components ├── ui │ ├── skeleton.tsx │ ├── aspect-ratio.tsx │ ├── label.tsx │ ├── textarea.tsx │ ├── status-indicator.tsx │ ├── collapsible.tsx │ ├── input.tsx │ ├── sonner.tsx │ ├── formatted-date.tsx │ ├── switch.tsx │ ├── slide-toggle.tsx │ ├── toggle.tsx │ ├── badge.tsx │ ├── popover.tsx │ ├── scroll-area.tsx │ ├── alert.tsx │ ├── tooltip.tsx │ ├── slider.tsx │ ├── tabs.tsx │ ├── card.tsx │ ├── button.tsx │ ├── toggle-group.tsx │ ├── table.tsx │ └── dialog.tsx ├── theme-provider.tsx ├── recipes │ ├── recipe-props.tsx │ └── screen-params-form.tsx ├── playlists │ ├── playlist-form.tsx │ ├── playlist-page-client.tsx │ └── playlist-item.tsx ├── main-layout-server.tsx ├── system-logs │ └── system-logs-viewer-skeleton.tsx ├── device-logs │ └── device-logs-container.tsx ├── dashboard │ └── dashboard-skeleton.tsx └── mixup │ ├── mixup-list.tsx │ └── mixup-page-client.tsx ├── next.config.ts ├── eslint.config.mjs ├── components.json ├── tsconfig.json ├── .gitignore ├── .env.example ├── .github └── workflows │ ├── ci.yml │ └── docker-publish.yml ├── biome.json ├── docker-compose.yml ├── LICENSE ├── .dockerignore ├── Dockerfile ├── CONTRIBUTING.md ├── hooks └── useSearchWithDebounce.ts ├── cache-handler.js ├── package.json ├── docs ├── api.md └── recipes.md ├── utils ├── pre-satori.tsx └── render-png.ts └── scripts └── generate-sql-statements.js /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /.biomeignore: -------------------------------------------------------------------------------- 1 | .next/ 2 | node_modules/ 3 | dist/ 4 | build/ 5 | *.min.js 6 | *.min.css 7 | 8 | -------------------------------------------------------------------------------- /public/not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/public/not-found.png -------------------------------------------------------------------------------- /public/album/london.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/public/album/london.png -------------------------------------------------------------------------------- /public/fonts/BlockKie.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/public/fonts/BlockKie.ttf -------------------------------------------------------------------------------- /public/fonts/geneva-12.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/public/fonts/geneva-12.otf -------------------------------------------------------------------------------- /public/fonts/geneva-9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/public/fonts/geneva-9.ttf -------------------------------------------------------------------------------- /public/static/not-found.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/public/static/not-found.bmp -------------------------------------------------------------------------------- /public/byos-nextjs-device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/public/byos-nextjs-device.png -------------------------------------------------------------------------------- /public/static/simple-text.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/public/static/simple-text.bmp -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /public/byos-nextjs-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/public/byos-nextjs-overview.png -------------------------------------------------------------------------------- /public/fonts/Inter_18pt-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usetrmnl/byos_next/HEAD/public/fonts/Inter_18pt-Regular.ttf -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://openapi.vercel.sh/vercel.json", 3 | "crons": [ 4 | { 5 | "path": "/api/bitmap/wikipedia.bmp", 6 | "schedule": "0 0 * * *" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /lib/api/types.ts: -------------------------------------------------------------------------------- 1 | // Define a custom error type that extends the built-in Error 2 | export interface CustomError extends Error { 3 | originalError?: unknown; // Use 'unknown' instead of 'any' for better type safety 4 | } 5 | -------------------------------------------------------------------------------- /lib/defaultDevice.ts: -------------------------------------------------------------------------------- 1 | const defaultDevice = { 2 | friendly_id: "FORLOG", 3 | device_name: "A TRMNL Device", 4 | mac_address: "AA:AA:AA:AA:AA:AA", 5 | api_key: "DefaultDeviceForLoggin", 6 | refresh_rate: 900, 7 | }; 8 | 9 | export { defaultDevice }; 10 | -------------------------------------------------------------------------------- /migrations/0002_add_playlist_index_to_devices.sql: -------------------------------------------------------------------------------- 1 | -- Title: Add Playlist Index to Devices 2 | -- Description: Adds current_playlist_index column to devices table for tracking playlist position 3 | ALTER TABLE devices 4 | ADD COLUMN IF NOT EXISTS current_playlist_index INT DEFAULT 0; -------------------------------------------------------------------------------- /lib/recipes/constants.ts: -------------------------------------------------------------------------------- 1 | // Shared constants for recipe rendering 2 | // This file must not import any server-only dependencies (next/og, sharp, etc.) 3 | // so it can be safely imported in client components. 4 | 5 | export const DEFAULT_IMAGE_WIDTH = 800; 6 | export const DEFAULT_IMAGE_HEIGHT = 480; 7 | -------------------------------------------------------------------------------- /app/tools/tools-components/bitmap-font-designer/bitmap-font-designer.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import BitmapFontDesignerClient from "./bitmap-font-designer-client"; 3 | 4 | export default function BitmapFontDesigner() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ); 11 | } 12 | 13 | export { Skeleton }; 14 | -------------------------------------------------------------------------------- /components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; 4 | 5 | function AspectRatio({ 6 | ...props 7 | }: React.ComponentProps) { 8 | return ; 9 | } 10 | 11 | export { AspectRatio }; 12 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | trailingSlash: false, 6 | skipTrailingSlashRedirect: true, 7 | cacheComponents: true, 8 | output: "standalone", 9 | // Mark native modules as external for server components 10 | serverExternalPackages: [ 11 | "@takumi-rs/core", 12 | "@takumi-rs/helpers", 13 | ], 14 | }; 15 | 16 | export default nextConfig; 17 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ThemeProvider as NextThemeProvider } from "next-themes"; 4 | import { type ReactNode } from "react"; 5 | 6 | export function ThemeProvider({ children }: { children: ReactNode }) { 7 | return ( 8 | 14 | {children} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { FlatCompat } from "@eslint/eslintrc"; 2 | import { dirname } from "path"; 3 | import { fileURLToPath } from "url"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | export const dynamic = "force-static"; 4 | 5 | export default function NotFound() { 6 | return ( 7 |
8 |

9 | 404 not found. the page you are looking for does not exist. 10 |

11 | 404 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /migrations/0001_add_device_status_fields.sql: -------------------------------------------------------------------------------- 1 | -- Title: Add Device Status Fields 2 | -- Description: Add battery_voltage, firmware_version, and rssi columns to the devices table 3 | -- Add battery_voltage, firmware_version, and rssi columns to the devices table 4 | ALTER TABLE devices 5 | ADD COLUMN IF NOT EXISTS battery_voltage NUMERIC, 6 | ADD COLUMN IF NOT EXISTS firmware_version TEXT, 7 | ADD COLUMN IF NOT EXISTS rssi INTEGER; 8 | 9 | -- Add comment to the new columns 10 | COMMENT ON COLUMN devices.battery_voltage IS 'Battery voltage in volts'; 11 | COMMENT ON COLUMN devices.firmware_version IS 'Device firmware version'; 12 | COMMENT ON COLUMN devices.rssi IS 'WiFi signal strength in dBm'; -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as LabelPrimitive from "@radix-ui/react-label"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ); 22 | } 23 | 24 | export { Label }; 25 | -------------------------------------------------------------------------------- /lib/database/db.ts: -------------------------------------------------------------------------------- 1 | import { Kysely, PostgresDialect } from "kysely"; 2 | import { Pool } from "pg"; 3 | import type { DB } from "./db.d"; 4 | 5 | // Ensure DATABASE_URL is available 6 | if (!process.env.DATABASE_URL) { 7 | console.warn("DATABASE_URL is not set. Database connection may fail."); 8 | } 9 | 10 | // Create a new Kysely instance with Postgres dialect 11 | export const db = new Kysely({ 12 | dialect: new PostgresDialect({ 13 | pool: new Pool({ 14 | connectionString: process.env.DATABASE_URL, 15 | ssl: process.env.DATABASE_URL?.includes("sslmode=disable") 16 | ? false 17 | : process.env.NODE_ENV === "production" 18 | ? { rejectUnauthorized: false } 19 | : false, 20 | }), 21 | }), 22 | }); 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "react-jsx", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": [ 26 | "next-env.d.ts", 27 | "**/*.ts", 28 | "**/*.tsx", 29 | ".next/types/**/*.ts", 30 | "cache-handler.js", 31 | ".next/dev/types/**/*.ts" 32 | ], 33 | "exclude": ["node_modules", "**/*.md"] 34 | } 35 | -------------------------------------------------------------------------------- /migrations/0006_add_screen_configs.sql: -------------------------------------------------------------------------------- 1 | -- Title: Add Screen Configs 2 | -- Description: Stores per-screen parameter configurations 3 | 4 | CREATE TABLE IF NOT EXISTS public.screen_configs ( 5 | id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 6 | screen_id TEXT NOT NULL UNIQUE, 7 | params JSONB NOT NULL DEFAULT '{}', 8 | created_at TIMESTAMPTZ DEFAULT NOW(), 9 | updated_at TIMESTAMPTZ DEFAULT NOW() 10 | ); 11 | 12 | -- Index to speed up lookups by screen_id 13 | CREATE INDEX IF NOT EXISTS idx_screen_configs_screen_id ON public.screen_configs (screen_id); 14 | 15 | -- Comment for clarity 16 | COMMENT ON TABLE public.screen_configs IS 'Per-screen configuration parameters stored as JSONB'; 17 | COMMENT ON COLUMN public.screen_configs.params IS 'JSON blob of screen parameters'; 18 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | !.env.example 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | .env*.local 44 | 45 | # individual readme files 46 | README-collab.md 47 | README-deploy.md 48 | 49 | # sync 50 | sync.sh -------------------------------------------------------------------------------- /app/recipes/screens/not-found/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { PreSatori } from "@/utils/pre-satori"; 2 | 3 | export default function NotFoundScreen({ 4 | slug, 5 | width = 800, 6 | height = 480, 7 | }: { 8 | slug?: string; 9 | width?: number; 10 | height?: number; 11 | }) { 12 | return ( 13 | 14 |
15 |
Screen Not Found
16 | {slug && ( 17 |
18 | Could not find screen: {slug} 19 |
20 | )} 21 |
22 | Please check your configuration or create this screen. 23 |
24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 | return ( 7 |