Add CSS framework
This commit is contained in:
BIN
src/luxtools
Executable file
BIN
src/luxtools
Executable file
Binary file not shown.
121
src/luxtools.nim
121
src/luxtools.nim
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user