web: initial web structure
This commit is contained in:
parent
57b3816a44
commit
708b13cf17
|
@ -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]),
|
||||
]),
|
||||
]);
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface Context {
|
||||
locPath: string;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["deno.ns", "dom"]
|
||||
},
|
||||
"importMap": "./import_map.json"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"imports": {
|
||||
"ren/": "https://notabug.org/pleshevskiy/ren/raw/v2/ren/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export function info(...args: unknown[]): void {
|
||||
console.log("[INFO]", ...args);
|
||||
}
|
||||
|
||||
export function debug(...args: unknown[]): void {
|
||||
console.log("[DEBUG]", ...args);
|
||||
}
|
|
@ -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
|
|
@ -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" },
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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("Страница не найдена"),
|
||||
]);
|
||||
}
|
|
@ -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("Главная"),
|
||||
]),
|
||||
]);
|
||||
}
|
|
@ -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("Ингредиенты"),
|
||||
]),
|
||||
]);
|
||||
}
|
|
@ -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("Рецепты"),
|
||||
]),
|
||||
]);
|
||||
}
|
Loading…
Reference in New Issue