Add CSS framework

This commit is contained in:
2025-10-01 12:33:55 +02:00
parent 34127035c5
commit ff0fca203c
38 changed files with 3147 additions and 40 deletions

BIN
src/luxtools Executable file

Binary file not shown.

View File

@@ -1,8 +1,66 @@
import std/[asynchttpserver, asyncdispatch, os, strformat, strutils, times]
import std/[asynchttpserver, asyncdispatch, os, strformat, strutils, tables, times]
const
indexPage = staticRead("../templates/index.html")
type
StaticFile = tuple[content: string, contentType: string]
const
defaultContentType = "application/octet-stream"
contentTypeByExt = block:
var table = initTable[string, string]()
table[".js"] = "application/javascript"
table[".css"] = "text/css"
table[".html"] = "text/html; charset=utf-8"
table[".json"] = "application/json"
table[".png"] = "image/png"
table[".jpg"] = "image/jpeg"
table[".jpeg"] = "image/jpeg"
table[".svg"] = "image/svg+xml"
table[".gif"] = "image/gif"
table[".ico"] = "image/x-icon"
table[".ttf"] = "font/ttf"
table[".woff"] = "font/woff"
table[".woff2"] = "font/woff2"
table[".txt"] = "text/plain; charset=utf-8"
table
proc guessContentType(path: string): string =
let ext = splitFile(path).ext.toLowerAscii()
if contentTypeByExt.hasKey(ext):
contentTypeByExt[ext]
else:
defaultContentType
const staticRootDir = parentDir(currentSourcePath()) / ".." / "static"
const staticFileEntries: seq[(string, StaticFile)] = block:
var entries: seq[(string, StaticFile)] = @[]
for path in walkDirRec(staticRootDir):
let fullPath = if isAbsolute(path): path else: staticRootDir / path
if not fileExists(fullPath):
continue
let relPath = relativePath(fullPath, staticRootDir)
if relPath.len == 0 or relPath.startsWith(".."):
continue
let normalizedRel = relPath.replace(DirSep, '/')
let urlPath = "/static/" & normalizedRel
let content = staticRead(fullPath)
let contentType = guessContentType(fullPath)
entries.add((urlPath, (content: content, contentType: contentType)))
entries
proc findStaticFile(path: string; asset: var StaticFile): bool =
for entry in staticFileEntries:
if entry[0] == path:
asset = entry[1]
return true
false
proc assetsRoot(): string =
absolutePath(joinPath(getAppDir(), "..", "www"))
var clickCount = 0
proc htmlHeaders(): HttpHeaders =
@@ -40,46 +98,45 @@ proc sendHtml(req: Request, code: HttpCode, body: string) {.async, gcsafe.} =
proc sendAsset(req: Request, body: string, contentType: string) {.async, gcsafe.} =
await req.respond(Http200, body, assetHeaders(contentType))
proc guessContentType(path: string): string =
let ext = splitFile(path).ext.toLowerAscii()
case ext
of ".js":
result = "application/javascript"
of ".css":
result = "text/css"
of ".html":
result = "text/html; charset=utf-8"
of ".json":
result = "application/json"
of ".png":
result = "image/png"
of ".jpg", ".jpeg":
result = "image/jpeg"
of ".svg":
result = "image/svg+xml"
of ".gif":
result = "image/gif"
of ".ico":
result = "image/x-icon"
else:
result = "application/octet-stream"
proc trySendAsset(req: Request): Future[bool] {.async, gcsafe.} =
proc trySendStatic(req: Request): Future[bool] {.async, gcsafe.} =
if req.reqMethod notin {HttpGet, HttpHead}:
return false
let path = req.url.path
if path.len <= 1 or path[0] != '/':
var asset: StaticFile
if not findStaticFile(path, asset):
return false
if req.reqMethod == HttpHead:
await req.respond(Http200, "", assetHeaders(asset.contentType))
else:
await sendAsset(req, asset.content, asset.contentType)
return true
proc trySendFileAsset(req: Request): Future[bool] {.async, gcsafe.} =
const assetPrefix = "/assets/"
if req.reqMethod notin {HttpGet, HttpHead}:
return false
let relative = path[1..^1]
let path = req.url.path
if not path.startsWith(assetPrefix):
return false
if path.len == assetPrefix.len:
return false
let relative = path[assetPrefix.len .. ^1]
if relative.len == 0 or relative.contains("..") or relative.contains('\\'):
return false
let assetPath = joinPath(absolutePath(joinPath(getAppDir(), "..", "assets")), relative)
let root = assetsRoot()
if not dirExists(root):
return false
let assetPath = joinPath(root, relative)
if not fileExists(assetPath):
return false
let contentType = guessContentType(assetPath)
if req.reqMethod == HttpHead:
await req.respond(Http200, "", assetHeaders(contentType))
@@ -98,7 +155,9 @@ proc handleRequest(req: Request) {.async, gcsafe.} =
of "/time":
await sendHtml(req, renderTime())
else:
if await trySendAsset(req):
if await trySendStatic(req):
return
if await trySendFileAsset(req):
return
await sendHtml(req, Http404, """
<div class="tui-window">