better templating
This commit is contained in:
parent
1efb6b0053
commit
64e036c632
17 changed files with 1446 additions and 76 deletions
|
|
@ -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
1212
src/.lua/markdown.lua
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
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
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@
|
|||
<td></td>
|
||||
<td></td>
|
||||
<td class="">
|
||||
$(if #_.plugin.info > 2 then)
|
||||
{if #plugin.info > 2 then
|
||||
print([[
|
||||
<div class="col3 info">
|
||||
${plugin.info}
|
||||
</div>
|
||||
$(end)
|
||||
]])
|
||||
end}
|
||||
<br/><br/>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
${header}
|
||||
|
||||
${if _.parent ~= nil and _.parent.parent == nil then}
|
||||
<div style="height:120px;" class="jumbotron">
|
||||
{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}
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
30
src/page/item.html
Normal 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>
|
||||
|
|
@ -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=''
|
||||
},
|
||||
{
|
||||
|
|
|
|||
41
src/shader/xrforgeportal.shadertoy.frag
Normal file
41
src/shader/xrforgeportal.shadertoy.frag
Normal 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) );
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
"$@"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue