added baseURLify() to streamline server vs staticwebgen links

This commit is contained in:
Leon van Kammen 2026-08-19 18:36:28 +02:00
parent c4d5060e32
commit 6bf8b40d1c
17 changed files with 176 additions and 70 deletions

View file

@ -8,8 +8,12 @@ An open, seamless XR web built from simple URLs and directories, without gatekee
<img src="screenshot.jpg"/> <img src="screenshot.jpg"/>
</a> </a>
XRForge transforms standard local or remote file directories into interconnected 3D spaces. * turn everyday directories into 3D portals.
<br>Users can seamlessly "surf" the spatial web, turning everyday directories into 3D portals. * seamlessly "surf" interconnected spatial webs on XR headsets
* transforms local/remote filesystems into interconnected 3D spaces.
* 3D (hyperlink) file browser for AR/VR
* automatic portal system, recursive symlinks supported
* infinite horizontally scaling ecosystems thanks to URLs (not databases)
``` ```
┌───────────────────────────────────┐ ┌──────────────────────────────────────────┐ ┌───────────────────────────────────┐ ┌──────────────────────────────────────────┐
@ -56,7 +60,7 @@ Usage: ./xrforge.com <cmd> [opts]
Extract and run example 3D content in your browser: Extract and run example 3D content in your browser:
```bash ```bash
$ unzip xrforge.com myverse/* # extract example 3D verse $ unzip xrforge.com myverse/* # extract example 3D verse files
$ ./xrforge.com server # run as server $ ./xrforge.com server # run as server
opening your browser at https://localhost:8080 opening your browser at https://localhost:8080
``` ```
@ -149,7 +153,7 @@ page css
Optionally you can re-distribute `xrforge.com` with your modifications by running `zip xrforge.com page` Optionally you can re-distribute `xrforge.com` with your modifications by running `zip xrforge.com page`
## Distribute your own XRForge ## Distribute: your own executable
You can preload the `xrforge.com` binary with your own experiences <b>as a single-executable-file</b>: You can preload the `xrforge.com` binary with your own experiences <b>as a single-executable-file</b>:
@ -160,18 +164,21 @@ $ cp xrforge.com myproject.com
> Profit! > Profit!
## Plugins ## Distribute: static website
xrforge is basically plugins working together.<br> Generate a static website:
You can create your own plugins by running:
```bash ```
$ ./xrforge.com createplugin > xrforge.myplugin.lua $ ./xrforge.com generate baseurl=/mysubfolder/
✅ finished generating 'www' folder
``` ```
Look in the [src/plugin](src/plugin) directory for examples A convenient way is forking [this github repo](https://github.com/coderofsalvation/xrforge-website-example)
to automatically obtain a xrforge-generated github page.
## Run as server > NOTE: passing extra flags (run `xrforge.com help` for all flags) is important when running the website in a subfolder (`baseurl=...` e.g.)
## Distribute: selfhosting on a server
<b>BAREMETAL:</b> <b>BAREMETAL:</b>
@ -188,16 +195,17 @@ $ docker run -d -p 8080:8080 -w /app -v $(pwd):/app busybox /app/xrforge.com ser
``` ```
## Plugins
## Scan manyfold xrforge is basically plugins working together.<br>
You can create your own plugins by running:
------------- ```bash
``` $ ./xrforge.com createplugin > xrforge.myplugin.lua
manual scan (via cli?)
curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=name' -H 'accept: application/vnd.manyfold.v0+json'
curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=recent' -H 'accept: application/vnd.manyfold.v0+json'
``` ```
Look in the [src/plugin](src/plugin) directory for examples
## Developing ## Developing
```bash ```bash
@ -216,6 +224,8 @@ $ DEBUG=1 result/bin/xrforge.com
Use `ARCH=x86_64 build` to enforce x86_64 output (instead of a cosmopolitan binary) Use `ARCH=x86_64 build` to enforce x86_64 output (instead of a cosmopolitan binary)
> **NOTE**: this is a **static webgenerator-first**-and-server-**last** project, hence it is restricted to [GET requests](src/middleware/onlyGET.lua) (unless authenticated as admin). Going beyond this paradigm is considered unsafe and out of scope.
## Permacomputing Spirit ## Permacomputing Spirit
* developed on [Dillo](https://dillo-browser.github.io/) as lowest common denominator / compatibility * developed on [Dillo](https://dillo-browser.github.io/) as lowest common denominator / compatibility
@ -223,7 +233,7 @@ Use `ARCH=x86_64 build` to enforce x86_64 output (instead of a cosmopolitan bina
* gracefully degrades to admin/filebrowser for &gt;noscript&gt;/potato-browsers * gracefully degrades to admin/filebrowser for &gt;noscript&gt;/potato-browsers
* server is using lua-scripting via cosmopolitan libc's [redbean](https://redbean.dev) * 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. > So yes, the codebase/html/css is intentionally 90-ish: that's **exactly why it gracefully degrades** on potato-devices.
## Credits / Made with ## Credits / Made with
@ -243,6 +253,16 @@ Use `ARCH=x86_64 build` to enforce x86_64 output (instead of a cosmopolitan bina
NOTES: NOTES:
## Scan manyfold
-------------
```
manual scan (via cli?)
curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=name' -H 'accept: application/vnd.manyfold.v0+json'
curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=recent' -H 'accept: application/vnd.manyfold.v0+json'
```
``` ```
/admin.lua <--- define other instances..for CORS allow / mount http /admin.lua <--- define other instances..for CORS allow / mount http

View file

@ -78,6 +78,7 @@ print = function(...) Log(kLogInfo, ... or "''") end -- nice redbean logging fro
-- init URL router -- init URL router
app.url['^/data'] = '/data.lua' -- setup custom file endpoint app.url['^/data'] = '/data.lua' -- setup custom file endpoint
app.use( require('middleware/onlyGET')({exclude={'^/admin'}}) ) -- we are staticwebgen-first server-last
app.use( app.plugin.global.middleware.homepage_2D_or_3D ) app.use( app.plugin.global.middleware.homepage_2D_or_3D )
app.url['^/admin[/]?$'] = '/page/admin/index.lua' app.url['^/admin[/]?$'] = '/page/admin/index.lua'

View file

@ -5,6 +5,7 @@ return function(app)
local config = { -- default local config = { -- default
name = "XRForge", name = "XRForge",
baseurl="/", baseurl="/",
host="localhost",
admin_title = "admin panel", admin_title = "admin panel",
admin_subtitle = "Manage your XR experiences here", admin_subtitle = "Manage your XR experiences here",
admin_user = "admin", admin_user = "admin",

View file

@ -87,12 +87,23 @@ sb = {
tpl = function(s, __data) tpl = function(s, __data)
local __env = {} local __env = {}
local fallback = function(key)
if os.getenv('DEBUG') then
print("⚠️ template '" .. key .. "' does not exist")
end
return setmetatable({}, {
__tostring = function() return "" end,
__concat = function(a, b) return tostring(a) .. tostring(b) end,
__call = function(...) return "" end
})
end
setmetatable(__env, { setmetatable(__env, {
__index = function(t, key) return __data[key] or _G[key] end, __index = function(t, key) return __data[key] or _G[key] or fallback(key) end,
__newindex = function(t, key, value) __data[key] = value end __newindex = function(t, key, value) __data[key] = value end
}) })
-- lets evaluate inline lua linearly -- lets evaluate inline lua linearly
local luastr = 'local str=""\n print = function(s) str = str .. s .. "\\n" end\n' local luastr = 'local str=""\n print = function(s) str = str .. s .. "\\n" end\n'
luastr = luastr .. 'defined = function(v) return tostring(v or "") ~= "" end\n'
local islua = false local islua = false
for str in string.gmatch(s, "([^\n]+)") do for str in string.gmatch(s, "([^\n]+)") do
if str:match("{if ") or str:match("{for ") then if str:match("{if ") or str:match("{for ") then
@ -196,7 +207,10 @@ sb = {
local res={ local res={
_status=nil, _status=nil,
_header={}, _header={},
_body="" _body="",
send = function(req,res,next)
sb.response()(req,res,next)
end
} }
local params = internal and {} or GetParams() local params = internal and {} or GetParams()
if params ~= nil then if params ~= nil then
@ -251,7 +265,9 @@ sb = {
if type(res._body) == "string" then if type(res._body) == "string" then
Write( res._body ) Write( res._body )
else print("[ERROR] res.body is not a string (HINT: use json middleware)") end else print("[ERROR] res.body is not a string (HINT: use json middleware)") end
else next() end else
if next then next() end
end
end end
end, end,
@ -311,6 +327,7 @@ sb = {
return function(data) return function(data)
local app = {} local app = {}
setmetatable(app,sb) setmetatable(app,sb)
sb.arg = arg
sb.bin = arg[-1]:gsub(".*/","") sb.bin = arg[-1]:gsub(".*/","")
sb.bindir = arg[-1]:gsub("/[a-zA-Z0-9-_ ]+.com$","/") sb.bindir = arg[-1]:gsub("/[a-zA-Z0-9-_ ]+.com$","/")
sb.app = app sb.app = app

View file

@ -75,6 +75,12 @@ util.templateFile = function(file,data)
return util.template( LoadAsset(file), data) return util.template( LoadAsset(file), data)
end end
util.baseURLify = function(url)
if url:match("^" .. app.opts.baseurl) then return url end -- only do once
if url:match("://") then return url end
return url:gsub("^[/]?", app.opts.baseurl)
end
util.getURL = function(url,opts) util.getURL = function(url,opts)
opts = opts or {} opts = opts or {}
local port = '' local port = ''
@ -83,8 +89,9 @@ util.getURL = function(url,opts)
return 'http://localhost:' .. app.port .. url return 'http://localhost:' .. app.port .. url
else else
port = ':' .. GetPort() port = ':' .. GetPort()
if port == 80 then port = '' end if port == ':80' then port = '' end
return GetScheme() .. '://' .. GetHost() .. port .. (url or GetPath()) if arg[1] == 'generate' and port == ':8080' then port = '' end
return GetScheme() .. '://' .. (app.opts.host or GetHost()) .. port .. (url or GetPath())
end end
end end
@ -92,8 +99,8 @@ util.template = function(content,data)
local mdata = util.merge(data or app,{}) local mdata = util.merge(data or app,{})
mdata = util.merge(mdata, {opts = app.opts}) mdata = util.merge(mdata, {opts = app.opts})
-- set fallback vars -- set fallback vars
mdata.urlpath = GetPath() mdata.urlpath = util.baseURLify( GetPath() )
mdata.url = mdata.url or util.getURL() mdata.url = mdata.url or mdata.urlpath
mdata.title = mdata.title or app.opts.subtitle mdata.title = mdata.title or app.opts.subtitle
mdata.header_include = mdata.header_include or '' mdata.header_include = mdata.header_include or ''
mdata.footerlinks = {} mdata.footerlinks = {}
@ -330,6 +337,13 @@ function util.stripPath(str)
return str:gsub("^/zip/",""):gsub("^./",""):gsub("^/",""):gsub("/$","") return str:gsub("^/zip/",""):gsub("^./",""):gsub("^/",""):gsub("/$","")
end end
function util.truncate(str,maxchar,filler)
filler = filler or '..'
str = str:gsub(".*://",""):gsub("/$",""):gsub("/.*/","/.../")
if maxchar ~= nil and #str > maxchar then str = str:sub(0,maxchar) .. filler end
return str
end
-- WARNING: this is a destructive function which renders the redbean server inactive -- WARNING: this is a destructive function which renders the redbean server inactive
-- only to be used in tests or staticwebgen plugin -- only to be used in tests or staticwebgen plugin
util.queueServerRequest = function(method, path, params, host, headers, body, scheme, port) util.queueServerRequest = function(method, path, params, host, headers, body, scheme, port)

View file

@ -40,6 +40,7 @@ body,html {
} }
body{ body{
overflow-wrap: break-word;
background:#000; background:#000;
margin:20px; margin:20px;
} }

View file

@ -0,0 +1,24 @@
-- soakbean middleware function
return function(opts)
opts = opts or {exclude={"^/admin"}}
return function(req,res,next)
local pass = false
if req.method == "GET" then
pass = true
else
foreach(opts.exclude, function(k,path)
if req.url:match(path) then
pass = true
end
end)
end
if pass then
next()
else
res.status(405)
res.body("not allowed")
res.send(req,res)
end
end
end

View file

@ -19,6 +19,18 @@ end}
<div class="grid"> <div class="grid">
{if items then {if items then
function getURL(url)
if app.arg[1] == 'generate' and url:match("://") then
url = 'janusweb/index.html#janus.url=' ..
EscapeFragment( url ) .. '&janus.referrer=' .. util.getURL(urlpath )
url = util.baseURLify(url)
else
url = util.stripPath( url )
end
return url
end
for i=1,#items do for i=1,#items do
local item = items[i] local item = items[i]
local visible = item.view2D == nil or (item.view2D ~= nil and item.view2D ~= false) local visible = item.view2D == nil or (item.view2D ~= nil and item.view2D ~= false)
@ -29,10 +41,10 @@ end}
if item.view3D or item.portal3D == false then icon = 'play' end if item.view3D or item.portal3D == false then icon = 'play' end
print([[ print([[
<div class="tile"> <div class="tile">
<a href="]] .. util.stripPath(item.url) .. [["> <a href="]] .. getURL(item.url) .. [[">
<div class="img" ]] .. util.stripPath(thumbnail) .. [[></div> <div class="img" ]] .. util.stripPath(thumbnail) .. [[></div>
<div class="text"> <div class="text">
<img src="img/]] .. icon .. [[.svg" class="icon ]] .. icon .. [["/>&nbsp;<a href="]] .. util.stripPath(item.url) .. [[">]] .. item.title .. [[</a> <img src="img/]] .. icon .. [[.svg" class="icon ]] .. icon .. [["/>&nbsp;<a href="]] .. getURL(item.url) .. [[">]] .. item.title .. [[</a>
</div> </div>
</a> </a>
</div> </div>
@ -49,10 +61,10 @@ end}
if item.view3D or item.portal3D == false then icon = 'play' end if item.view3D or item.portal3D == false then icon = 'play' end
print([[ print([[
<div class="tile"> <div class="tile">
<a href="]] .. util.stripPath(item.url) .. [["> <a href="]] .. getURL(item.url) .. [[">
<div class="img" ]] .. util.stripPath(thumbnail) .. [[></div> <div class="img" ]] .. util.stripPath(thumbnail) .. [[></div>
<div class="text"> <div class="text">
<img src="img/]] .. icon .. [[.svg" class="icon ]] .. icon .. [["/>&nbsp;<a href="]] .. util.stripPath(item.url) .. [[">]] .. item.title .. [[</a> <img src="img/]] .. icon .. [[.svg" class="icon ]] .. icon .. [["/>&nbsp;<a href="]] .. getURL(item.url) .. [[">]] .. util.truncate(item.title) .. [[</a>
</div> </div>
</a> </a>
</div> </div>

View file

@ -13,11 +13,12 @@ return {
local isdir = urlpath:sub(-1) == "/" local isdir = urlpath:sub(-1) == "/"
local nslashes = util.countChar('/',urlpath) local nslashes = util.countChar('/',urlpath)
local match = function(a,b) return util.stripPath(a) == util.stripPath(b) end local match = function(a,b) return util.stripPath(a) == util.stripPath(b) end
local urlhash = urlpath .. util.dump( req.param ) local urlhash = urlpath .. util.dump( req.param )
local referrer = util.baseURLify( (isroot and not req.param.url) and "" or urlpath )
if (isroot or urlpath:match("^"..rootpath)) if (isroot or urlpath:match("^"..rootpath))
and not urlpath:gsub(".*/",""):match("%.") -- no regular file with extension and not urlpath:gsub(".*/",""):match("%.") -- no regular file with extension
and not req.param.url then then
local redirect = false local redirect = false
if not app.cache.url[ urlhash ] or util.count(req.params) > 0 then if not app.cache.url[ urlhash ] or util.count(req.params) > 0 then
@ -45,14 +46,10 @@ return {
res.items[ v.id ] = v res.items[ v.id ] = v
res.parent = parent res.parent = parent
end end
if not isroot then if isroot then
res.items[ v.parent.id ] = { referrer = app.opts.plugin_federation_parent_url
view2D = false, else
url = v.parent.parent.url, referrer = v.parent.parent.url
title = app.opts.plugin_janusxr_portal_up,
name = app.opts.plugin_janusxr_portal_up,
thumbnail = '',
}
end end
end end
end end
@ -62,7 +59,8 @@ return {
local data = util.merge({ local data = util.merge({
url = req.param.url, -- query hint to render janus.url in iframe (index.html) url = req.param.url, -- query hint to render janus.url in iframe (index.html)
items = {}, items = {},
referrer = isroot and app.opts.plugin_federation_parent_url or util.getURL(), isroot = isroot,
referrer = referrer,
parent=res.parent, parent=res.parent,
template=res.template template=res.template
},{ opts = app.opts }) },{ opts = app.opts })
@ -78,7 +76,7 @@ return {
-- prioritize markup experience above binary 3D files as experience containers -- prioritize markup experience above binary 3D files as experience containers
if not data.item or (isMarkupExperience(v) and not isMarkupExperience(data.item)) then if not data.item or (isMarkupExperience(v) and not isMarkupExperience(data.item)) then
-- update referrer url -- update referrer url
data.referrer = util.getURL( v.view3D and v.parent.parent.url or v.parent.url ) data.referrer = util.baseURLify(v.view3D and v.parent.parent.url or v.parent.url)
data.item = v data.item = v
data.title = data.item.title data.title = data.item.title
data.parent = v.parent data.parent = v.parent

View file

@ -21,7 +21,7 @@
<a href="${opts.baseurl}"> <a href="${opts.baseurl}">
<div class="logo max"> <div class="logo max">
<span class="col1">[</span><span class="col2">&lt;&gt;</span><span class="col1">]</span>&nbsp;<span class="col3">${opts.plugin_global_title} <span class="col1">[</span><span class="col2">&lt;&gt;</span><span class="col1">]</span>&nbsp;<span class="col3">${opts.plugin_global_title}
{if url:match("admin") then {if urlpath:match("^/admin") then
print('<span class="col1">//<span>') print('<span class="col1">//<span>')
print('<span class="col0">${opts.admin_title}</span>') print('<span class="col0">${opts.admin_title}</span>')
end} end}
@ -38,7 +38,16 @@
print( app.opts.plugin_global_topmenu_html:gsub("\n","&nbsp;") ) print( app.opts.plugin_global_topmenu_html:gsub("\n","&nbsp;") )
end} end}
{if template == "page/dir.html" then {if template == "page/dir.html" then
print([[<div class="btn XR"><a class="border secondary" href="${urlpath}?view=3D">3D</a></div>&nbsp;]]) local url = urlpath
if not urlpath:match(app.opts.plugin_diskscan_dirname) then
-- janusweb workaround (it does not support '/' url somehow)
urlpath = urlpath .. app.opts.plugin_diskscan_dirname
end
if app.arg[1] == "generate" then
print([[<div class="btn XR"><a class="border secondary" href="janusweb/#janus.url=]] .. urlpath .. [[&janus.referrer=${referrer}">3D</a></div>&nbsp;]])
else
print([[<div class="btn XR"><a class="border secondary" href="${urlpath}?view=3D">3D</a></div>&nbsp;]])
end
end} end}
{if template == "page/index.html" then {if template == "page/index.html" then
local sharedata = { local sharedata = {

View file

@ -5,7 +5,7 @@ ${header}
<iframe border="0" frameborder="0" src="janusweb/index.html#janus.url=${url}&janus.referrer=${referrer}" id="viewer"></iframe> <iframe border="0" frameborder="0" src="janusweb/index.html#janus.url=${url}&janus.referrer=${referrer}" id="viewer"></iframe>
{if item and item.parent and item.parent.info then {if defined(item) and item.parent and item.parent.info then
print([[ print([[
<div id="popup" class="content"> <div id="popup" class="content">
<div class="spectrum span" style="top:-1px"> <div class="spectrum span" style="top:-1px">

View file

@ -5,22 +5,23 @@
-- optimize janus resource URLs for archive.org -- optimize janus resource URLs for archive.org
app.plugin.archive.flush() app.plugin.archive.flush()
function URL(url) function URL(url)
url = util.baseURLify(url)
app.plugin.archive.add(url) app.plugin.archive.add(url)
return url return url
end end
end}
end}
<fireboxroom> <fireboxroom>
<assets> <assets>
{if items and #items > 0 then {if defined(items) and #items > 0 then
for i,v in pairs(items) do for i,v in pairs(items) do
if #v.thumbnail > 0 then if #v.thumbnail > 0 then
print('\t<assetimage src="' .. URL(v.thumbnail) .. '" id="img' .. i .. '"/>') print('\t<assetimage src="' .. URL(v.thumbnail) .. '" id="img' .. i .. '"/>')
end end
end end
end} end}
{if itemsremote and #itemsremote > 0 then {if defined(itemsremote) and #itemsremote > 0 then
for i,v in pairs(itemsremote) do for i,v in pairs(itemsremote) do
if #v.thumbnail > 0 then if #v.thumbnail > 0 then
print('\t<assetimage src="' .. URL(v.thumbnail) .. '" id="imgremote' .. i .. '"/>') print('\t<assetimage src="' .. URL(v.thumbnail) .. '" id="imgremote' .. i .. '"/>')
@ -38,28 +39,27 @@ end}
end end
end} end}
</assets> </assets>
{if item then {if true then
print([[
<room pos="0 0 0" skybox="true" showavatar="false">
<object id="${url}"/>
]])
else
local room = app.opts.plugin_janusxr_portalroom local room = app.opts.plugin_janusxr_portalroom
if item and item.janusxr and item.janusxr.room then if defined(item) and item.janusxr and item.janusxr.room then
room = item.janusxr.room room = item.janusxr.room
end end
if room == 'random' then room = app.plugin.janusxr.getRandomRoom() end if room == 'random' then room = app.plugin.janusxr.getRandomRoom() end
if defined(item) and item.tag.xrfragments and item.tag.xrfragments.qualify then
room = '' -- xrfragments 3D files dont need to be wrapped with room
end
print(' <room skybox="true" showavatar="false" use_local_asset="' .. room .. '">') print(' <room skybox="true" showavatar="false" use_local_asset="' .. room .. '">')
if defined(item) then
print(' <object id="${url}"/>')
end
end} end}
<light js_id="dan9-janus:light/light_cone_angle=0-6" pos="0 3 2" collision_trigger="false" /> <light js_id="dan9-janus:light/light_cone_angle=0-6" pos="0 3 2" collision_trigger="false" />
{if true then {if true then
function addPortal(id,x,v,url,i,z,thumb,rotation,scale) function addPortal(id,x,v,url,i,z,thumb,rotation,scale)
local title = v.title local title = v.title
local maxchar = 20
if #title > maxchar then title = title:sub(0,maxchar) .. '..' end
print('\t<object js_id="' .. id .. '_' .. i .. '" pos="' .. x .. ' 0 ' .. z .. '" rotation="' .. rotation .. '">') print('\t<object js_id="' .. id .. '_' .. i .. '" pos="' .. x .. ' 0 ' .. z .. '" rotation="' .. rotation .. '">')
print('\t <link url="' .. url .. '" ' .. thumb .. ' title_col="0.4 0.37 0.4" pos="0 0 0.001" draw_glow="' .. (thumb:match("shader") and 'false' or 'true') .. '" scale="3 3 0.1" />') print('\t <link url="' .. util.baseURLify(url) .. '" ' .. thumb .. ' title_col="0.4 0.37 0.4" pos="0 0 0.001" draw_glow="' .. (thumb:match("shader") and 'false' or 'true') .. '" scale="3 3 0.1" />')
print('\t <text text="' .. title .. '" pos="0 3.4 0" col="1 1 1" font_scale="false" scale="' .. scale .. '" />') print('\t <text text="' .. util.truncate(title,20) .. '" pos="0 3.4 0" col="1 1 1" font_scale="false" scale="' .. scale .. '" />')
--TODO: fix janusweb paragraph for markdown urls --TODO: fix janusweb paragraph for markdown urls
--if v.README ~= nil then --if v.README ~= nil then
-- print('\t <paragraph js_id="README" url="' .. v.README .. '" width="600" height="900" pos="0 1 2" transparent="true"/>') -- print('\t <paragraph js_id="README" url="' .. v.README .. '" width="600" height="900" pos="0 1 2" transparent="true"/>')
@ -68,7 +68,7 @@ end}
end end
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" /> <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 not item and items and #items > 0 then {if not defined(item) and defined(items) and #items > 0 then
for i,v in pairs(items) do for i,v in pairs(items) do
local width = 3.2 local width = 3.2
local center = -((#items*width)/1.3) + (i*width) local center = -((#items*width)/1.3) + (i*width)
@ -82,7 +82,7 @@ end}
addPortal('dir',center, v, url,i, -4, thumb, '0 0 0', '0.2 0.2 0.2') addPortal('dir',center, v, url,i, -4, thumb, '0 0 0', '0.2 0.2 0.2')
end end
end} end}
{if not item and itemsremote and #itemsremote > 0 then {if not defined(item) and defined(itemsremote) and #itemsremote > 0 then
for i,v in pairs(itemsremote) do for i,v in pairs(itemsremote) do
local width = 3.2 local width = 3.2
local center = -((#itemsremote*width)/1.3) + (i*width) local center = -((#itemsremote*width)/1.3) + (i*width)
@ -100,8 +100,19 @@ end}
]]) ]])
end} end}
{if #referrer > 0 then
-- hardcoded reciprocal portal
local v = {
url = referrer,
title = app.opts.plugin_janusxr_portal_up,
}
addPortal('parent',4, v, v.url, 0, 0, ' shader_id="defaultportal"', '0 -90 0', '0.2 0.2 0.2')
end}
<object id="cube" rotation="0 0 0" position="0 0 0" scale="2 0.1 2"/> <!-- something to stand on --> <object id="cube" rotation="0 0 0" position="0 0 0" scale="2 0.1 2"/> <!-- something to stand on -->
${janusxr_jml_deco} {if not defined(item) then
print('${janusxr_jml_deco}')
end}
${opts.plugin_janusxr_jml} ${opts.plugin_janusxr_jml}
</room> </room>
</fireboxroom> </fireboxroom>

View file

@ -14,12 +14,10 @@ app.on('init', function()
local me = app.plugin.archive local me = app.plugin.archive
if arg[1] == 'server' then if arg[1] == 'server' then
local code, pid = unix.fork() local code, pid = unix.fork()
-- kill thread when main app gets CTRL-c -- kill thread when main app gets CTRL-c
unix.sigaction(unix.SIGINT, function() unix.sigaction(unix.SIGINT, function()
unix.exit() unix.exit()
end) end)
function notify() function notify()
app.plugin.archive.notify() app.plugin.archive.notify()
--notify() -- loop forever --notify() -- loop forever

View file

@ -24,8 +24,7 @@ app.on('dir', function(opts)
table.insert(itemsremote, { table.insert(itemsremote, {
title = url:gsub(".*//",""), title = url:gsub(".*//",""),
thumbnail = thumburl, thumbnail = thumburl,
url = '/janusweb/index.html#janus.url=' .. EscapeFragment(url) url = url
.. '&janus.referrer=' .. EscapeFragment( util.getURL(opts.urlpath ) )
}) })
end end
end end

View file

@ -24,8 +24,7 @@ app.on('dir', function(opts)
table.insert(itemsremote, { table.insert(itemsremote, {
title = url:gsub(".*//",""), title = url:gsub(".*//",""),
thumbnail = thumburl, thumbnail = thumburl,
url = '/janusweb/index.html#janus.url=' .. EscapeFragment(url) url = url
.. '&janus.referrer=' .. EscapeFragment( util.getURL(opts.urlpath ) )
}) })
end end
end end

View file

@ -24,8 +24,7 @@ app.on('dir', function(opts)
table.insert(itemsremote, { table.insert(itemsremote, {
title = url:gsub(".*//",""), title = url:gsub(".*//",""),
thumbnail = thumburl, thumbnail = thumburl,
url = '/janusweb/index.html#janus.url=' .. EscapeFragment(url) url = url
.. '&janus.referrer=' .. EscapeFragment( util.getURL(opts.urlpath ) )
}) })
end end
end end

View file

@ -34,7 +34,10 @@ app.on('scan_experience_file', function(exp)
if util.fileExist( jsonfile ) then if util.fileExist( jsonfile ) then
exp.sidecarfile.JSONXRF = me.detectHrefInStruct( json.decode( util.readfile(jsonfile) ) ) exp.sidecarfile.JSONXRF = me.detectHrefInStruct( json.decode( util.readfile(jsonfile) ) )
end end
exp.tag.xrfragments = { qualify = hasHref or hasPNG or hasWebVTT or hasOGG or hasJSONref, href = hasHref } exp.tag.xrfragments = {
qualify = (hasHref or exp.sidecarfile.png or exp.sidecarfile.vtt or exp.sidecarfile.ogg or exp.sidecarfile.json) and true or false,
href = hasHref
}
if exp.tag.xrfragments.qualify then if exp.tag.xrfragments.qualify then
exp.view3D = true exp.view3D = true
print("✅ └─ plugin/xrfragments: detected xrfragments experience") print("✅ └─ plugin/xrfragments: detected xrfragments experience")