web: initial web structure

This commit is contained in:
Dmitriy Pleshevskiy 2022-05-22 15:13:30 +03:00
parent 57b3816a44
commit 708b13cf17
13 changed files with 239 additions and 0 deletions

17
web/comp/layout.ts Normal file
View File

@ -0,0 +1,17 @@
import { AnyNode, E, Elem } from "ren/node.ts";
export function Layout(page: AnyNode): Elem {
return E("html", { lang: "ru" }, [
E("head", [], [
E("meta", { charset: "utf-8" }),
E("meta", {
name: "viewport",
content: "width=device-width, initial-scale=1",
}),
E("title", [], "Recipes"),
]),
E("body", [], [
E("div", { id: "root" }, [page]),
]),
]);
}

39
web/comp/page_layout.ts Normal file
View File

@ -0,0 +1,39 @@
import { AnyNode, Attrs, E, Elem } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context } from "../context.ts";
export function PageLayout(ctx: Context, children: AnyNode[]): Elem {
return E("div", { id: "main" }, [
Header(ctx),
E("div", classNames("content"), children),
// Footer(),
]);
}
export function Header(ctx: Context): Elem {
return E("header", classNames("header"), [
E("div", classNames("content-width"), [HeaderNav(ctx)]),
]);
}
export function HeaderNav(ctx: Context): Elem {
return E("nav", classNames("main-menu"), [
Link(navLink("/", ctx), "Главная"),
Link(navLink("/recipes", ctx), "Рецепты"),
Link(navLink("/ingredients", ctx), "Ингредиенты"),
]);
}
export function Link(attrs: Attrs | Attrs[], text: string): Elem {
return E("a", attrs, text);
}
function navLink(href: string, ctx?: Context): Attrs {
const attrs: Attrs = { href };
if (ctx?.locPath === href) attrs["aria-current"] = "true";
return attrs;
}
export function Footer(): Elem {
return E("footer", classNames("footer"), "footer");
}

3
web/context.ts Normal file
View File

@ -0,0 +1,3 @@
export interface Context {
locPath: string;
}

6
web/deno.json Normal file
View File

@ -0,0 +1,6 @@
{
"compilerOptions": {
"lib": ["deno.ns", "dom"]
},
"importMap": "./import_map.json"
}

5
web/import_map.json Normal file
View File

@ -0,0 +1,5 @@
{
"imports": {
"ren/": "https://notabug.org/pleshevskiy/ren/raw/v2/ren/"
}
}

7
web/log.ts Normal file
View File

@ -0,0 +1,7 @@
export function info(...args: unknown[]): void {
console.log("[INFO]", ...args);
}
export function debug(...args: unknown[]): void {
console.log("[DEBUG]", ...args);
}

35
web/makefile Normal file
View File

@ -0,0 +1,35 @@
PAR := $(MAKE) -j 128
DOCKER_NAME := pleshevski
DOCKER_TAG := pleshevski
watch:
$(PAR) hr ts-w
docker-restart: docker-stop docker-run
docker-stop:
docker rm ${DOCKER_NAME} --force
docker-run:
docker run -d --restart always -p 30000:30000 --name ${DOCKER_NAME} ${DOCKER_TAG}
docker-build:
docker build -t ${DOCKER_TAG} .
build: ts
start:
npm run start
hr:
deno run -A ~/sandbox/hr/server.ts target static
ts:
npm run build
ts-w:
NODE_ENV=develop npx tsc-watch --onSuccess "make start"
clean:
rm -rf target

67
web/server.ts Normal file
View File

@ -0,0 +1,67 @@
import { StrRenderer } from "ren/str.ts";
import { Layout } from "./comp/layout.ts";
import { Context } from "./context.ts";
import { E404Page } from "./views/e404.ts";
import * as log from "./log.ts";
import { HomePage } from "./views/home.ts";
import { RecipesPage } from "./views/recipes.ts";
import { IngredientsPage } from "./views/ingredients.ts";
if (import.meta.main) {
await main();
}
async function main() {
await startServer({ port: 33334 });
}
async function startServer(cfg: ServerConfig) {
const srv = Deno.listen({ hostname: "localhost", port: cfg.port });
log.info(`Server listening at http://localhost:${cfg.port}`);
for await (const conn of srv) {
serveHttp(conn);
}
}
interface ServerConfig {
port: number;
}
async function serveHttp(conn: Deno.Conn) {
const httpConn = Deno.serveHttp(conn);
for await (const reqEvt of httpConn) {
const res = handleRequest(reqEvt.request);
reqEvt.respondWith(res);
}
}
function handleRequest(req: Request): Response {
log.debug({ url: req.url });
const ren = new StrRenderer();
const ctx = createContextFromRequest(req);
if (ctx.locPath === "/") {
return createHtmlResponse(ren.render(Layout(HomePage(ctx))));
} else if (ctx.locPath === "/recipes") {
return createHtmlResponse(ren.render(Layout(RecipesPage(ctx))));
} else if (ctx.locPath === "/ingredients") {
return createHtmlResponse(ren.render(Layout(IngredientsPage(ctx))));
} else {
return createHtmlResponse(ren.render(Layout(E404Page(ctx))), 404);
}
}
function createContextFromRequest(req: Request): Context {
return {
locPath: new URL(req.url).pathname,
};
}
function createHtmlResponse(body: string, status = 200): Response {
return new Response(body, {
status,
headers: { "content-type": "text/html" },
});
}

6
web/uikit/typo.ts Normal file
View File

@ -0,0 +1,6 @@
import { classNames } from "ren/attrs.ts";
import { E, Elem } from "ren/node.ts";
export function H3(text: string): Elem {
return E("h3", classNames("font-h3"), text);
}

15
web/views/e404.ts Normal file
View File

@ -0,0 +1,15 @@
import { PageLayout } from "../comp/page_layout.ts";
import { AnyNode, E } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context } from "../context.ts";
import { H3 } from "../uikit/typo.ts";
export function E404Page(ctx: Context): AnyNode {
return PageLayout(ctx, [E404()]);
}
export function E404(): AnyNode {
return E("div", classNames("content-width"), [
H3("Страница не найдена"),
]);
}

13
web/views/home.ts Normal file
View File

@ -0,0 +1,13 @@
import { PageLayout } from "../comp/page_layout.ts";
import { AnyNode, E } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context } from "../context.ts";
import { H3 } from "../uikit/typo.ts";
export function HomePage(ctx: Context): AnyNode {
return PageLayout(ctx, [
E("div", classNames("content-width"), [
H3("Главная"),
]),
]);
}

13
web/views/ingredients.ts Normal file
View File

@ -0,0 +1,13 @@
import { PageLayout } from "../comp/page_layout.ts";
import { AnyNode, E } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context } from "../context.ts";
import { H3 } from "../uikit/typo.ts";
export function IngredientsPage(ctx: Context): AnyNode {
return PageLayout(ctx, [
E("div", classNames("content-width"), [
H3("Ингредиенты"),
]),
]);
}

13
web/views/recipes.ts Normal file
View File

@ -0,0 +1,13 @@
import { PageLayout } from "../comp/page_layout.ts";
import { AnyNode, E } from "ren/node.ts";
import { classNames } from "ren/attrs.ts";
import { Context } from "../context.ts";
import { H3 } from "../uikit/typo.ts";
export function RecipesPage(ctx: Context): AnyNode {
return PageLayout(ctx, [
E("div", classNames("content-width"), [
H3("Рецепты"),
]),
]);
}