better templating

This commit is contained in:
Leon van Kammen 2026-07-30 20:53:12 +02:00
parent 1efb6b0053
commit 64e036c632
17 changed files with 1446 additions and 76 deletions

View file

@ -62,3 +62,11 @@ $ DEBUG=1 result/bin/xrforge.com
* server is using lua-scripting via cosmopolitan libc's [redbean](https://redbean.dev)
> So yes, the codebase/html/css is intentionally 90-ish: that's **exactly why it works** on potato-devices AND modern devices.
## Credits / Made with
* [redbean](https://redbean.dev)
* [soakbean](https://github.com/coderofsalvation/soakbean)
* [xrfragments](https://xrfragment.org)
* [janusxr](https://github.com/jbaicoianu/janusweb)
* [janusweb](https://janusxr.org)

1212
src/.lua/markdown.lua Normal file

File diff suppressed because it is too large Load diff

View file

@ -51,49 +51,80 @@ sb = {
end
end,
-- print( util.tpl("${name} is ${value}", {name = "foo", value = "bar"}) )
-- "foo is bar"
-- print( util.tpl("${foo(1)}", { foo = function(i) return i+1 end}) )
-- 2
-- print( util.tpl("${if foo then} bar ${end}", { foo = true }) )
-- bar
tpl = function(s, tab)
tab = tab or sb.data
return (s:gsub('($%b{})', function(w)
local inner = w:sub(3, -2):match("^%s*(.-)%s*$")
-- Execute as raw Lua code if it contains spaces (e.g. "if bar then") or equals "end"
if inner:find("%s") or inner == "end" then
local fn = (loadstring or load)("return function(tab) " .. inner .. " end")
if fn then
setfenv(fn(), setmetatable(tab, { __index = _G }))()
end
return ""
end
-- usage:
--
-- tpl([[
-- Hello ${world}
-- foo() = ${foo(1)}
--
-- {if true then
-- print(world)
-- print("hello universe")
-- end}
--
-- {for i in pairs(numbers) do
-- print(i)
-- end}
--
-- {for k,v in pairs(items) do
-- print(k .. " " .. v.x)
-- end}
-- ]],{
-- world="WORLD",
-- foo = function(i) return i+1 end,
-- numbers = {2,3,4},
-- items = {{x=2},{x=3}}
-- })
-- -- Hello WORLD
-- -- foo() = 2
-- -- WORLD
-- -- hello universe
-- -- 1
-- -- 2
-- -- 3
-- -- 1 2
-- -- 2 3
local k, arg = w:match("^%${%s*([%w_]+)%s*%(?(.-)%)?%s*}")
local v = tab[k]
if type(v) == "function" then
return tostring(v(load("return " .. arg)()) or '')
tpl = function(s, __data)
local __env = {}
setmetatable(__env, {
__index = function(t, key) return __data[key] or _G[key] end,
__newindex = function(t, key, value) __data[key] = value end
})
-- lets evaluate inline lua linearly
local luastr = 'local str=""\n print = function(s) str = str .. s .. "\\n" end\n'
local islua = false
for str in string.gmatch(s, "([^\n]+)") do
if str:match("{if ") or str:match("{for ") then
luastr = luastr .. str:gsub("{","") .. "\n"
islua = true
elseif str:match("end}") then
luastr = luastr .. "end\n"
islua = false
else
if islua then
luastr = luastr .. str .. "\n"
else
luastr = luastr .. "str = str .. \"" .. str:gsub('"','\\"') .. "\\n\"\n"
end
end
return v ~= nil and tostring(v) or w
end))
end,
tpl = function(s, tab)
tab = tab or sb.data
return (s:gsub('($%b{})', function(w)
end
local s1 = load( luastr .. "\n return str", "chunkname", "t", __env )()
-- evaluate template vars
local s2 = (s1:gsub('($%b{})', function(w)
local inner = w:sub(3, -2):match("^%s*(.-)%s*$")
-- Execute as raw Lua code if it contains spaces or equals "end"
if inner:find("%s") or inner == "end" then
local fn = (loadstring or load)("return function(tab) " .. inner .. " end")
local fn = (loadstring or load)("return function(__env) " .. inner .. " end")
if fn then
setfenv(fn(), setmetatable(tab, { __index = _G }))()
setfenv(fn(), setmetatable(__env, { __index = _G }))()
end
return ""
end
-- Allow dots in variable names along with alphanumeric characters and underscores
local path, arg = w:match("^%${%s*([%w_%.]+)" .. "%s*%(?(.-)%)?%s*}")
-- Traverse the nested table path (e.g. "foo.bar" -> tab["foo"]["bar"])
local v = tab
-- Traverse the nested table path (e.g. "foo.bar" -> __env["foo"]["bar"])
local v = __env
if path then
for key in path:gmatch("[%w_]+") do
if type(v) == "table" then
@ -111,6 +142,7 @@ sb = {
end
return v ~= nil and tostring(v) or w
end))
return s2
end,
write = function(str)

View file

@ -84,12 +84,12 @@ end
util.template = function(content,data)
local mdata = util.merge(data or app,{})
mdata = util.merge(mdata, {opts = app.opts})
mdata.title = mdata.title or app.opts.subtitle
mdata.header_include = mdata.header_include or ''
mdata.header = app.tpl( LoadAsset('page/header.html'), mdata )
mdata.footer = app.tpl( LoadAsset('page/footer.html'), mdata )
-- set view url
mdata.url = mdata.url or util.getURL()
print_r(mdata)
return app.tpl( content, mdata )
end

View file

@ -155,10 +155,14 @@ pre{
font-size:20px;
}
h1,h2,h3,h4,h5{
font-size: 16px;
font-size: 17px;
padding-top: 25px;
padding-bottom:25px;
}
h1{
font-size: 29px;
margin: 15px 0px;
}
.sym{ font-size:100px; color:#000; }
@ -253,6 +257,13 @@ b.tag{
border-radius: 5px;
}
blockquote{
background: #224;
padding: 15px;
margin: 15px 0px;
display: inline-block;
}
/* progressive enhancement quirks */
.spectrum{ display:none }

View file

@ -72,8 +72,8 @@ img.icon{
width: 17px;
}
iframe#viewer{
position: fixed;
#viewer{
position: absolute;
width: 100%;
height: calc( 99.7vh - 59px);
margin-top: 0px;

View file

@ -91,7 +91,7 @@ div#xrecosystem{
<h3>Why people want XRForge</h3>
<div>
In short: its hyperscalable portal-system and ability <b>projecting existing 2D ecosystems</b> into XR.<br>
In short: a hyperscalable portal-system and <b>projection</b> of existing 2D ecosystems <b>into XR.</b><br>
XRForge interconnects experiences via portals, according to the <a href="https://coderofsalvation.github.io/janus-guide/" target="_blank">JanusXR</a> and <a href="https://xrfragment.org" target="_blank">XR Fragments standard</a>.<br>
The metaverse has shown: people <b>like 3D</b> but certain <b>existing</b> 2D ecosystems are king.<br>
They're just more cost-efficient to use.<br>

View file

@ -10,12 +10,10 @@ ${notification}
<form method="POST">
<div class="tab-frame">
${tabs}
${for i,tab in pairs(tabs) do}
<input type="radio" checked name="tab" id="tab${i}">
<label for="tab${i}">${tab}</label>
${end}
{for i,tab in pairs(tabs) do
print('<input type="radio" checked name="tab" id="tab' .. i .. '">')
print('<label for="tab' .. i .. '">' .. tab .. '</label>')
end}
${plugins}

View file

@ -14,11 +14,13 @@
<td></td>
<td></td>
<td class="">
$(if #_.plugin.info > 2 then)
<div class="col3 info">
${plugin.info}
</div>
$(end)
{if #plugin.info > 2 then
print([[
<div class="col3 info">
${plugin.info}
</div>
]])
end}
<br/><br/>
</td>
</tr>

View file

@ -1,16 +1,20 @@
${header}
${if _.parent ~= nil and _.parent.parent == nil then}
<div style="height:120px;" class="jumbotron">
<h1>${opts.title}</h1>
${opts.subtitle}
</div>
<hr style="margin-top:0">
${end}
{if parent ~= nil and parent.parent == nil then
print([[
<div style="height:120px;" class="jumbotron">
<h1>${opts.title}</h1>
${opts.subtitle}
</div>
<hr style="margin-top:0">
]])
end}
${if _.parent ~= nil and _.parent.parent ~= nil then}
<h2>${parent.name}</h2>
${end}
{if parent ~= nil and parent.parent ~= nil then
print([[
<h2>${parent.name}</h2>
]])
end}
<div class="grid">
${html_items}
@ -20,16 +24,23 @@ ${footer}
<!-- JanusXR portalroom, see https://janusxr.org for markup -->
<fireboxroom>
<room pos="0 0 0" skybox="true" showavatar="false" use_local_asset="room_plane">
<assets>
<AssetShader id="xrforgeportal" src="${opts.baseurl}shader/xrforgeportal.shadertoy.frag" shadertype="shadertoy"></AssetShader>
{for i,v in pairs(items) do
print('\t<assetimage src="' .. v.thumbnail .. '" id="img' .. i .. '"/>')
end}
</assets>
<room pos="0 0 -4" skybox="true" showavatar="false" use_local_asset="room_plane">
<light js_id="dan9-janus:light/light_cone_angle=0-6" pos="0 3 2" collision_trigger="false" />
<text js_id="location" pos="0 2.6 2" billboard="y" col="#BBFFFF" scale="5 5 1" text="https://xrforge.isvery.ninja/models" rotation="-180 0 -180" emissive="0 0 0" roughness="0.3" metalness="0" />
<CircularLayout pos="0 0 0" radius="4.5" scale="1 1 1" >
${for k,v in pairs(items) do}
<object id="o_${i}">
<link url="${v.url}" image_id="${v.thumbnail}" pos="0 0 0.001" draw_glow="true" scale="3 3 0.1" thumb_id="thumb_0" title="${v.title}"/>
<text text="${v.title}" pos="0 2.3 0.3" font_scale="false" scale="0.15 0.15 0.2" />
</object>
${end}
</CircularLayout>
<text js_id="location" pos="0 6 -5" billboard="y" col="#BBFFFF" scale="10 10 1" text="${url}" rotation="-180 0 -180" emissive="0 0 0" roughness="0.3" metalness="0" />
{for i,v in pairs(items) do
local width = 3
local center = -((#items*width)/1.3) + (i*width)
print('\t\t<object js_id="portal_' .. i .. '" pos="' .. center .. ' 0 0" rotation="0 180 0">')
print('\t\t <link url="' .. v.url .. '" thumb_id="img' .. i .. '" title_col="0.4 0.37 0.4" pos="0 0 0.001" draw_glow="true" scale="3 3 0.1" />')
print('\t\t <link url="' .. v.url .. '" shader_id="xrforgeportal" pos="0 0 0.003" draw_glow="false" scale="3 3 0.1" />')
print('\t\t <text text="' .. v.title .. '" pos="0 3.4 0" col="1 1 1" font_scale="false" scale="0.2 0.2 0.2" />')
print('\t\t</object>')
end}
</room>
</fireboxroom>

View file

@ -60,7 +60,8 @@ return {
table.insert(data.items, util.merge({x = i*2, i = i},v) )
i=i+1
if v.view3D then
data.view3D = v
data.item = v
data.title = data.item.title
local port = ':' .. GetPort()
if port == 80 then port = '' end
data.url = GetScheme() .. '://' .. GetHost() .. port .. v.url
@ -68,8 +69,8 @@ return {
data.html_items = data.html_items .. tile({item=v})
end
end)
if data.view3D then
app.cache.url[ urlpath ] = util.templateFile( "page/index.html", data)
if data.item then
app.cache.url[ urlpath ] = util.templateFile( "page/item.html", data)
else
app.cache.url[ urlpath ] = util.templateFile( "page/dir.html", data)
end

View file

@ -5,7 +5,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="${opts.baseurl}" />
<title>XRForge - selfsovereign interoperable XR experiences</title>
<title>${title}</title>
<link rel="stylesheet" href="css/xrforge.css"></link>
${header_include}
<noscript>
@ -13,7 +13,6 @@
</noscript>
</head>
<body background="#000000" class="${page}">
<table width="100%" class="header">
<tr>
<td width="100">

View file

@ -1,5 +1,22 @@
${header}
<iframe border="0" frameborder="0" src="/janusweb/index.html#janus.url=${url}" id="viewer"/>
<iframe border="0" frameborder="0" src="/janusweb/index.html#janus.url=${url}" id="viewer"></iframe>
<script>
// minimal JS to sync parent/iframe URL-locations
const iframe = document.querySelector("#viewer")
iframe.addEventListener('load', () => {
const iframeWindow = iframe.contentWindow;
iframeWindow.addEventListener('hashchange', (event) => {
const params = new URLSearchParams( String(iframeWindow.location.hash).replace(/^#/,'?') )
const url = params.get('janus.url')
window.history.pushState( url, iframe.contentDocument.title, url );
setTimeout( () => {
document.title = iframe.contentDocument.title
}, 1000 )
});
});
</script>
${footer}

30
src/page/item.html Normal file
View file

@ -0,0 +1,30 @@
${header}
<iframe border="0" frameborder="0" src="/janusweb/index.html#janus.url=${url}&janus.referrer=${item.parent.url}" id="viewer" style="height:45vh"></iframe>
<div style="height:45vh"></div>
<div class="spectrum" style="position: absolute; left: 0px; right: 0px;">
<div></div>
</div>
{if item.parent.info then
print(item.parent.info)
end}
{if not item.parent.info then
print('<h1>${item.title}</h1>\n\n')
print('This item has no description (yet)')
end}
${footer}
<!-- JanusXR portalroom, see https://janusxr.org for markup -->
<fireboxroom>
<assets>
<assetshader id="xrforgeportal" src="${opts.baseurl}shader/xrforgeportal.shadertoy.frag" shadertype="shadertoy"/>
</assets>
<room pos="0 0 -4" showavatar="false">
<light js_id="dan9-janus:light/light_cone_angle=0-6" pos="0 3 2" collision_trigger="false" />
<text js_id="location" pos="0 6 -5" billboard="y" col="#BBFFFF" scale="10 10 1" text="${url}" rotation="-180 0 -180" emissive="0 0 0" roughness="0.3" metalness="0" />
<object id="${url}"/>
</room>
</fireboxroom>

View file

@ -1,10 +1,18 @@
local util = require('util')
local markdown = require("markdown")
app.on('scan_experience', function(exp)
--print_r(exp)
end)
app.on('scan_experience_file', function(exp)
local me = app.plugin.markdown
local patterns = util.split(app.opts.plugin_markdown_files," ")
foreach( patterns, function(k,v)
if exp.path:match(v) then
exp.parent.info = '<div id="info">' .. markdown( util.readfile(exp.path) ) .. '</div>'
end
end)
end)
app.on('set', function(k,v)
@ -20,7 +28,7 @@ local plugin = {
key='app.opts.plugin_markdown_files',
title='Markdown files',
info='Space-separated, <a href="http://lua-users.org/wiki/PatternsTutorial" target="_blank">lua patterns</a> are allowed',
default="*.md",
default="README.md",
values=''
},
{

View file

@ -0,0 +1,41 @@
// Source: https://www.shadertoy.com/view/XtlczH
const float speed = 0.01;
const float twistFrequency = .2;
const float pi = 3.14;
const float tau = pi * 2.0;
const vec4 bg = vec4(0);
const float edgeWidth = 3.0;
const float portalRadius = 9.0;
const float twistEdgeMag = 0.1;
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord / iResolution.xy;
vec2 space = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
vec2 unitVector = normalize(space);
space *= 1.0;
float magnitude = length(space);
float v1 = sin(space.x + iTime);
float v2 = sin(space.y + iTime);
float v3 = sin(space.x + space.y + iTime);
float v4 = sin(magnitude + 1.7 * iTime);
float v = v1 + v2 + v3 + v4;
float theta = acos(unitVector.x);
if (space.y < 0.0)
theta = tau - theta;
float twist = theta + magnitude * twistFrequency + -iTime * speed;
twist = mod(twist, tau);
twist *= smoothstep(tau, pi, twist);
twist *= smoothstep(0.0, 4.0, magnitude);
twist = sin(twist);
twist *= 8.0;
v += twist;
vec4 portal = vec4((sin(v * 2.0) + 1.0) * 0.2, 0, (sin(v * 0.4 + 0.3 * pi) + 1.0) * 0.2 + 0.1, 1);
portal.w = .9;
fragColor = mix(bg, portal, smoothstep(portalRadius + edgeWidth + (sin(twist * twistEdgeMag) * 0.5 + 0.5), portalRadius, magnitude)) * vec4(1,1,1, (((sin(iTime*2.0)+1.0)/2.0) * 0.4) );
}

View file

@ -41,7 +41,7 @@ build(){
rebuild(){
cd src
zip -yr ../result/bin/xrforge.com .lua img page plugin cmd css .init.lua .args test myverse
zip -yr ../result/bin/xrforge.com .lua shader img page plugin cmd css .init.lua .args test myverse
}
"$@"