updated federation + templates

This commit is contained in:
Leon van Kammen 2026-08-04 12:17:33 +02:00
parent 7cd6df24c7
commit 920d695720
25 changed files with 250 additions and 142 deletions

View file

@ -39,6 +39,7 @@ app = require("soakbean") {
end
end)
end)
app.set('app.opts', app.opts)
return p
end
}
@ -53,6 +54,7 @@ app.init = function() end
app.opts = require('config')(app)
-- load system plugins (in specific order for admin panel)
app.plugin.global = app.addPlugin('plugin/global')
app.plugin.diskscan = app.addPlugin('plugin/diskscan')
app.plugin.moderation = app.addPlugin('plugin/moderation')
app.plugin.xrfragments = app.addPlugin('plugin/xrfragments')

View file

@ -5,8 +5,6 @@ return function(app)
local config = { -- default
name = "XRForge",
baseurl="/",
title="your.xr.forge",
subtitle="Portable XR experiences for Phone, VRheadset & desktop.",
admin_title = "xrforge admin panel",
admin_subtitle = "Manage your XR experiences here",
}
@ -30,25 +28,12 @@ return function(app)
app.opts = v
app.opts.updated = os.time()
local newconfig = [[
-- TIP: run 'xrforge.com createplugin' to start writing plugins
app.on('init', function()
print('config loaded')
local scan = app.scan
app.scan = function()
os.execute("echo scan before-hook")
scan()
os.execute("echo scan after-hook")
end
end)
]] .. "\n\nreturn " .. util.dump(app.opts)
newconfig = newconfig:gsub("^}$"," init = function() print('xrforge.lua config loaded') end\n}")
util.writefile( app.bindir .. configfile, newconfig )
print("stored xrforge.lua")
trace("stored xrforge.lua")
end
end)

View file

@ -97,8 +97,8 @@ util.template = function(content,data)
mdata.title = mdata.title or app.opts.subtitle
mdata.header_include = mdata.header_include or ''
mdata.header = app.tpl( LoadAsset('page/header.html'), mdata )
if not mdata.footer_janusxr then
mdata.footer_janusxr = app.tpl( LoadAsset('page/footer.janusxr.html'), mdata )
if not mdata.janusxr then
mdata.janusxr = app.tpl( LoadAsset('page/janusxr.html'), mdata )
end
mdata.footer = app.tpl( LoadAsset('page/footer.html'), mdata )
return app.tpl( content, mdata )

View file

@ -51,6 +51,7 @@ td,div,p{
/* form elements */
input,
textarea,
select{
box-sizing: border-box;
margin: 0px 0px 10px 0px;
@ -69,6 +70,9 @@ input,select,textarea{
input[type=checkbox]{
transform:scale(1.7) translate(3px,4px)
}
textarea{
min-height:150px;
}
/* buttons */

View file

@ -54,10 +54,10 @@ body{
font-size:14px;
}
.grid .tile img.icon.list-tree{
img.icon.list-tree{
filter: invert(1);
}
.grid .tile img.icon.play{
img.icon.play{
filter: invert(1);
transform: translate(0,-1px);
}

View file

@ -1,4 +0,0 @@
[
{"url":"https://xrforge.isvery.ninja", "name":"XRForge", "thumbnail":""},
{"url":"https://www.janusxr.org", "name":"JanusXR", "thumbnail":""}
]

View file

@ -94,7 +94,7 @@ div#xrecosystem{
<h3>What is it</h3>
An open, seamless XR web built from simple URLs and directories, without gatekeepers.<br>
XRForge transforms standard local or remote file directories into interconnected 3D spaces. Users can seamlessly "surf" the spatial web, turning everyday directories into 3D portals.
XRForge transforms standard local or remote file directories into interconnected 3D spaces. Users can seamlessly "surf" spatial webs (plural!), turning everyday directories into 3D portals.
<br>
<br>
@ -132,8 +132,8 @@ div#xrecosystem{
<h3>Why people want XRForge</h3>
<div>
In short: scalable <b>spatial wiki's</b> (it uses web URLs) interconnected via <b>3D portals</b>; bringing <b>existing 2D ecosystems into XR.</b><br>
XRForge interconnects XR experiences via portals, thanks to <a href="https://coderofsalvation.github.io/janus-guide/" target="_blank">JanusXR</a> and <a href="https://xrfragment.org" target="_blank">XR Fragments standards</a>.<br>
In short: scalable <b>spatial blog/wiki's</b> (using plain web URLs) interconnected via <b>3D portals</b>; bringing <b>existing 2D ecosystems into XR.</b><br>
How? Well it's backed by <a href="https://coderofsalvation.github.io/janus-guide/" target="_blank">JanusXR-</a> and <a href="https://xrfragment.org" target="_blank">XR Fragment- URLs</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>
Hence, XRForge incorporates the opensource <a href="https://coderofsalvation.github.io/janus-guide/" target="_blank">JanusXR</a>-client called <a href="https://github.com/jbaicoianu/janusweb" target="_blank">JanusWeb</a>, which promotes <b>projecting these ecosystems</b> as virtual <b>hyperlinked</b> worlds (via XR translators).<br>

View file

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

View file

@ -16,7 +16,7 @@ function pluginInput(opt)
<td>
]]
local key = opt.key:gsub("^app.opts.","")
local value = app.opts[ key ]
local value = EscapeHtml( app.opts[ key ] )
if type(opt.values) == 'table' then
html = html .. util.template('<select name="${key}" value="${value}">\n', {
key = opt.key,
@ -29,6 +29,13 @@ function pluginInput(opt)
})
end)
html = html .. "</select>\n"
else
if opt.control and opt.control == 'textarea' then
html = html .. util.template('<textarea name="${key}">${value}</textarea>',{
key = opt.key,
value = value,
placeholder= opt.placeholder or ''
})
else
html = html .. util.template('<input type="text" name="${key}" value="${value}" placeholder="${placeholder}"></input>',{
key = opt.key,
@ -36,6 +43,7 @@ function pluginInput(opt)
placeholder= opt.placeholder or ''
})
end
end
html = html .. util.template([[
</td>
<td class="col3">${info}</td>

View file

@ -5,7 +5,7 @@ ${header}
<div style="height:90px;" class="jumbotron">
<h1 style="display:none">${opts.title}</h1>
<br>
<center class="col3">${opts.subtitle}</center>
<center class="col3">${opts.plugin_global_subtitle}</center>
</div>
<hr style="margin-top:0"/>
]])

View file

@ -66,7 +66,7 @@ return {
data.url = GetScheme() .. '://' .. GetHost() .. port .. v.url
if v.janusxr then
-- what to do here? no generated portals?
data.footer_janusxr = app.tpl( util.readfile(v.path), data )
data.janusxr = app.tpl( util.readfile(v.path), data )
end
end
end)

View file

@ -38,7 +38,7 @@
</html>
<!-- JanusXR portalroom, see https://janusxr.org for markup -->
${footer_janusxr}
${janusxr}
<!-- C-code

View file

@ -1,33 +0,0 @@
<fireboxroom>
<assets>
<assetshader id="xrforgeportal" src="${opts.baseurl}shader/xrforgeportal.shadertoy.frag" shadertype="shadertoy"></assetshader>
{if items and #items > 0 then
for i,v in pairs(items) do
print('\t<assetimage src="' .. v.thumbnail .. '" id="img' .. i .. '"/>')
end
end}
</assets>
<room pos="0 0 4" skybox="true" showavatar="false" use_local_asset="room_plane" zdir="0 0 -1">
<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}" emissive="0 0 0" roughness="0.3" metalness="0" />
{if item then
print('\t<object id="${url}"/>')
end}
{if items and #items > 0 then
local portals = {}
for i,v in pairs(items) do table.insert(portals, v) end
if parent then table.insert(portals, p) end
for i,v in pairs(items) do
local width = 3.2
local center = -((#items*width)/1.3) + (i*width)
local url = v.url
if v.dir then url = url .. "/" end
print('\t\t<object js_id="portal_' .. i .. '" pos="' .. center .. ' 0 0">')
print('\t\t <link url="' .. 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="' .. 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
end}
</room>
</fireboxroom>

View file

@ -4,9 +4,9 @@
<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>${title}</title>
<title>${opts.plugin_global_title}</title>
<link rel="stylesheet" href="/css/xrforge.css"></link>
${header_include}
${opts.plugin_global_headertags}
<noscript>
{if urlpath == '/' or urlpath == '/index.html' then
print('<meta http-equiv="refresh" content="0;url=${opts.plugin_diskscan_dirname}">')
@ -21,7 +21,7 @@
<a href="${opts.baseurl}">
<div class="logo max">
<span class="col1">[</span><span class="col2">&lt;&gt;</span><span class="col1">]</span>
<span class="col3">${opts.title}
<span class="col3">${opts.plugin_global_title}
{if url:match("admin") then
print('<span class="col1">//<span>')
print('<span class="col0">${opts.admin_title}</span>')
@ -34,8 +34,10 @@
<td class="menu">
<div class="margin1L">
<!-- important: no spaces between anchor tags for older browsers without flexbox -->
<a href="/${opts.plugin_diskscan_dirname}"><img class="icon" src="/img/list-tree.svg">${opts.plugin_diskscan_dirname}</a>&nbsp;<a href="/about">about</a>
<!--<a class="xl" href="/howto">Howto</a></div>-->
<a href="/${opts.plugin_diskscan_dirname}"><img class="icon" src="/img/list-tree.svg">${opts.plugin_global_topmenu_mapname}</a>&nbsp;
{if true then
print( app.opts.plugin_global_topmenu_html:gsub("\n","&nbsp;") )
end}
</td>
</tr>
</table>

72
src/page/janusxr.html Normal file
View file

@ -0,0 +1,72 @@
<fireboxroom>
<assets>
<assetshader id="xrforgeportal" src="${opts.baseurl}shader/xrforgeportal.shadertoy.frag" shadertype="shadertoy"></assetshader>
{if items and #items > 0 then
for i,v in pairs(items) do
print('\t<assetimage src="' .. v.thumbnail .. '" id="img' .. i .. '"/>')
end
end}
{for i=1,5 do
local urls = app.opts[ 'plugin_federation_url_' .. i]
if #urls > 0 then
local fediurl = urls:gsub(' .*','')
local thumburl = urls:gsub('.* ','')
print('\t<assetimage src="' .. thumburl .. '" id="federated' .. i .. '"/>')
end
end}
</assets>
{if item then
print([[
<room pos="0 0 0" skybox="true" showavatar="false">
<object id="${url}"/>
]])
else
local room = app.opts.plugin_janusxr_portalroom
if room == 'random' then room = app.plugin.janusxr.getRandomRoom() end
print('<room skybox="true" showavatar="false" use_local_asset="' .. room .. '">')
end}
<light js_id="dan9-janus:light/light_cone_angle=0-6" pos="0 3 2" collision_trigger="false" />
{if true then
function addPortal(id,x,v,url,i,z,thumbprefix,rotation,scale)
print('\t\t<object js_id="' .. id .. '_' .. i .. '" pos="' .. x .. ' 0 ' .. z .. '" rotation="' .. rotation .. '">')
print('\t\t <link url="' .. url .. '" thumb_id="' .. thumbprefix .. 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="' .. 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="' .. scale .. '" />')
print('\t\t</object>')
end
end}
<text js_id="location" pos="0 6 -5" billboard="y" col="#BBFFFF" scale="10 10 1" text="${url}" emissive="0 0 0" roughness="0.3" metalness="0" />
{if items and #items > 0 then
local portals = {}
for i,v in pairs(items) do table.insert(portals, v) end
if parent then table.insert(portals, p) end
for i,v in pairs(items) do
local width = 3.2
local center = -((#items*width)/1.3) + (i*width)
local url = v.url
if v.dir then url = url .. "/" end
addPortal('dir',center, v, url,i, -4, 'img', '0 0 0', '0.2 0.2 0.2')
end
end}
{if true then
local portals = {}
for i=1,5 do
local urls = app.opts[ 'plugin_federation_url_' .. i]
if #urls > 0 then
local fediurl = urls:gsub(' .*','')
table.insert(portals, fediurl )
end
end
if #portals > 0 then
print([[
<text js_id="location" pos="0 6 5" billboard="y" col="#BBFFFF" scale="10 10 1" text="${opts.plugin_federation_title}" emissive="0 0 0" roughness="0.3" metalness="0" />
]])
end
for i,fediurl in pairs(portals) do
local width = 3.2
local center = -((#portals*width)/1.3) + (i*width)
addPortal('federated',center, {title=fediurl}, fediurl,i, 4, 'federated', '0 180 0', '0.1 0.1 0.1')
end
end}
</room>
</fireboxroom>

View file

@ -9,7 +9,6 @@ app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
print("content was updated")
end)
-- this registers the plugin UI

View file

@ -8,83 +8,78 @@ app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
print("content was updated")
end)
--app.on('dir', function(opts)
-- local me = app.plugin.federation
-- if opts.urlpath == '/' then
-- foreach( me.portals, function(k,portal)
-- print("JAAA")
-- table.insert( opts.data.items,{
-- url = portal.url,
-- name = portal.name,
-- thumbnail = portal.thumbnail
-- })
-- end)
-- end
--end)
app.on('dir', function(opts)
local me = app.plugin.federation
if opts.urlpath == '/' then
print_r(me.portals)
--foreach( me.portals, function(k,portal)
-- print("JAAA")
-- table.insert( opts.data.items,{
-- url = portal.url,
-- name = portal.name,
-- thumbnail = portal.thumbnail
-- })
--end)
end
end)
local plugin = {
name = 'Federation <b class="tag">disabled</b>',
info = 'updating values requires a xrforge restart',
name = 'Federation',
info = 'Add links to other (janus)XR(forges), and have them show up as extra portals in portalrooms',
opts = { -- TODO populate this based on xrforge-federation.json
{
key='app.opts.plugin_federation_title',
title='Title',
info="Displayed above federated portals",
default="Federated neighbourhoods",
values=''
},
{
key='app.opts.plugin_federation_strategy',
title='Display portals',
info="When to show federated portals?",
default="always",
values={always='always',homepage='only homepage'}
},
{
key='app.opts.plugin_federation_url_1',
title='Federation URL 1',
info="a json with (janus)xr(forge) URLs",
default="./federation.json",
title='Federated URL 1',
info="URL + Thumbnail URL (space separated)",
default="https://xrforge.isvery.ninja https://i.imgur.com/x7fzn9v.png",
values=''
},
{
key='app.opts.plugin_federation_url_2',
title='Federation URL 1',
info="a json with (janus)xr(forge) URLs",
default="",
title='Federated URL 2',
info="URL + Thumbnail URL (space separated)",
default="https://www.janusxr.org/new/ https://archive.org/download/janusxr-1/janusxr.png",
values=''
},
{
key='app.opts.plugin_federation_url_3',
title='Federation URL 1',
info="a json with (janus)xr(forge) URLs",
title='Federated URL 3',
info="URL + Thumbnail URL (space separated)",
default="",
values=''
},
{
key='app.opts.plugin_federation_url_4',
title='Federated URL 4',
info="URL + Thumbnail URL (space separated)",
default="",
values=''
},
{
key='app.opts.plugin_federation_url_5',
title='Federated URL 5',
info="URL + Thumbnail URL (space separated)",
default="",
values=''
}
}
}
--
-- PROBLEM: every request has its own app context
-- so the util.fetch() didn't help here
-- sqlite is needed to share state
app.on('init', function()
local me = app.plugin.federation
me.portals = {}
local federations = {}
-- load previous federations if any
for k=1,3 do
if app.opts['plugin_federation_url_'..k] and #app.opts['plugin_federation_url_'..k] > 0 then
table.insert(federations, app.opts['plugin_federation_url_'..k])
end
end
foreach(federations, function(k,url)
if url:match('^[./]') then
url = util.getURL( url:gsub('^[./]',''), {server=true} )
end
util.fetch(url, nil, function(req,res,next)
if req.body then
local t = DecodeJson(req.body)
if t then
foreach( t, function(k,opt)
table.insert( me.portals, opts )
end)
end
end
end)
end)
end)
-- this registers the plugin UI
return plugin

View file

@ -8,7 +8,6 @@ app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
print("content was updated")
end)
-- this registers the plugin UI

62
src/plugin/global.lua Normal file
View file

@ -0,0 +1,62 @@
local util = require('util')
app.on('scan_experience', function(exp)
--print_r(exp)
end)
app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
end)
-- this registers the plugin UI
local plugin = {
name = "Global settings",
info = '',
opts = {
{
key='app.opts.plugin_global_title',
title='Title',
info='Preferably dot-style domain-notation for easy recollection',
default = "your.xr.forge",
values=''
},
{
key='app.opts.plugin_global_subtitle',
title='Subtitle',
info='Slogan here',
default = "Portable XR experiences for Phone, VRheadset & desktop.",
values=''
},
{
key='app.opts.plugin_global_topmenu_mapname',
title='Overview link',
info='Top-right link <img class="icon list-tree" src="/img/list-tree.svg"> (overview page)',
default = "map",
values=''
},
{
key='app.opts.plugin_global_topmenu_html',
title='Menu html',
info='Add extra menu items here',
default = '<a href="/about">about</a>',
control='textarea',
values=''
},
{
key='app.opts.plugin_global_headertags',
title='Header html',
info='Add extra header tags here',
default = [[
<!--
<link rel="stylesheet" type="text/css" href="https://my.org/foo.css" media="all" />
-->
]],
control='textarea',
values=''
},
}
}
return plugin

View file

@ -31,7 +31,6 @@ app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
print("content was updated")
end)
-- this registers the plugin UI
@ -76,11 +75,18 @@ local plugin = {
},
{
key='app.opts.plugin_janusxr_wrap_template',
title='template',
title='Media room',
info='What kind of space should images/videos be rendered into?',
default="random",
values={random="random room",plane="wirefloor",room1="room1",room2="room2",room3="room3",room4="room4",room5="room5",room6="room6",room7="room7"}
},
{
key='app.opts.plugin_janusxr_portalroom',
title='Directory room',
info='What kind of space should directories be rendered into?',
default="room_plane",
values={random="random room",room_plane="wirefloor",room1="room1",room2="room2",room3="room3",room4="room4",room5="room5",room6="room6",room7="room7"}
},
{
key='app.opts.plugin_janusxr_scraper',
title='Scraper',
@ -95,7 +101,21 @@ local plugin = {
default='https://xrforge.isvery.ninja https://www.janusxr.org',
values=''
},
}
},
getRandomRoom = function()
local me = app.plugin.janusxr
local room= ''
foreach( me.opts, function(k,v)
print(v.key)
if v.key == 'app.opts.plugin_janusxr_portalroom' then
local ordered = {}
foreach( v.values, function(k,v) table.insert(ordered,v) end)
room = ordered[ math.random( #ordered ) ]
end
end)
print("ROOM = " .. room)
return room
end
}
return plugin

View file

@ -8,7 +8,6 @@ app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
print("content was updated")
end)
-- this registers the plugin UI

View file

@ -17,7 +17,6 @@ app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
print("content was updated")
end)
-- this registers the plugin UI

View file

@ -8,7 +8,6 @@ app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
print("content was updated")
end)
-- this registers the plugin UI

View file

@ -8,7 +8,6 @@ app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
print("content was updated")
end)
-- this registers the plugin UI

View file

@ -49,7 +49,7 @@ end)
-- this registers the plugin UI /funcs
return {
name = "XR Fragments",
info = 'XR Fragments treats 3D files with <a href="https://xrfragment.org/#sidecar%20files" target="_blank">sidecar file(s)</a> (foo.glb+foo.png e.g.) as experience and/or detect <a href="https://xrfragment.org" target="_blank">deeplinking of 3D files</a> via <a href="https://xrfragment.org#href" target="_blank">href-metadata</a>',
info = 'XR Fragments treats 3D files with <a href="https://xrfragment.org/#sidecar%20files" target="_blank">sidecar file(s)</a> (foo.glb+foo.png e.g.) as experience and/or detects <a href="https://xrfragment.org" target="_blank">deeplinking of 3D files</a> via <a href="https://xrfragment.org#href" target="_blank">href-metadata</a>',
opts = {
{
key='app.opts.plugin_xrfragments_filetypes',