diff --git a/run.sh b/run.sh index 7fc7609..ec69ec6 100755 --- a/run.sh +++ b/run.sh @@ -1,2 +1,4 @@ #!/bin/sh +source ./.env +rebuild result/bin/xrforge.com server diff --git a/src/.init.lua b/src/.init.lua index f53711b..fe476cd 100644 --- a/src/.init.lua +++ b/src/.init.lua @@ -23,20 +23,39 @@ app = require("soakbean") { cmd={ help = {file="cmd/help.lua", info="display all possible flags"}, server = {file="cmd/server.lua", info="starts the webserver"}, - createplugin = {file="cmd/createplugin.lua", info="generates skeleton plugin"} + createplugin = {file="cmd/createplugin.lua", info="generates skeleton plugin"}, + generate = {file="cmd/generate.lua", info="generate www-folder with XR experiences"} } } -- api skeleton (implemented via soakbean's app.on(...)) app.set = function(k,v) end app.init = function() end -app.scan = require('scan').scan -- load config app.opts = require('config')(app) --- load plugins -app.plugin.janusxr = require('plugin/janusxr') +-- load system plugins +app.plugin.moderation = require('plugin/moderation') +app.plugin.diskscan = require('plugin/diskscan') +app.plugin.manyfold = require('plugin/manyfold') +app.plugin.janusxr = require('plugin/janusxr') + +-- ensure plugin config-defaults +foreach( app.plugin, function(name,plugin) + foreach( plugin.opts, function(k,opt) + local key = opt.key:gsub("app.opts.","") + if not app.opts[ key ] then + app.opts[ key ] = opt.default + end + end) +end) + +-- set order preference (optional) +app.plugin[1] = app.plugin.diskscan +app.plugin[2] = app.plugin.moderation +app.plugin[3] = app.plugin.janusxr +app.plugin[4] = app.plugin.manyfold app.runcmd(app) -- run clicmds @@ -67,12 +86,13 @@ app -- .use( function(req,next) Route() end) -- fallback default redbean fileserver app.on('init', function() - if assert(unix.fork()) == 0 then + if assert(unix.fork()) == 0 then + app.plugin.diskscan.scan() + print_r(app.cache) --os.execute("ls ; sleep 5") - app.scan() print("ok") --local ok, headers, body = Fetch('http://127.0.0.1:8080/test') - end + end end) function OnHttpRequest() app.run() end diff --git a/src/.lua/config.lua b/src/.lua/config.lua index 63c9f53..12a8140 100644 --- a/src/.lua/config.lua +++ b/src/.lua/config.lua @@ -5,17 +5,10 @@ return function(app) local config = { -- default name = "XRForge", baseurl="/", - title="My XRForge experiences", + title="Your XR Forge", subtitle="Portable XR experiences for Phone, VRheadset & desktop.", admin_title = "XRForge Admin", admin_subtitle = "Manage your XR experiences here", - disk_dir = ".", - hide_keywords = "wip beta -v[0-9] customerX", - manyfold_instance_1 = "https://xrforge.isvery.ninja", - manyfold_instance_2 = "", - manyfold_instance_3 = "", - sync_interval = 30, - sync_strategy = "recent" } local configfile = "xrforge.lua" diff --git a/src/.lua/opendirectory.lua b/src/.lua/opendirectory.lua index 4f121c2..30e0914 100644 --- a/src/.lua/opendirectory.lua +++ b/src/.lua/opendirectory.lua @@ -31,25 +31,25 @@ local dir = {} dir.scan = function(path) local stat = unix.stat(path) - if stat == nil then - return ServeError(404,"does not exist") - else - if not unix.S_ISDIR( stat:mode() ) then return false end -- let redbean take care of serving static file - end - state = assert(unix.opendir(path)) - return stat, state + if not stat or not unix.S_ISDIR( stat:mode() ) then + return nil,nil, "not a dir" + end -- let redbean take care of serving static file + state,err = assert(unix.opendir(path)) + return stat, state, err end dir.foreach = function(path,cb) - local stat, state = dir.scan(path) - for name, kind, ino, off in state do - if name == "." then goto continue end - attr,err = unix.stat(path..'/'..name, unix.AT_SYMLINK_NOFOLLOW) - if err then print("error: ".. name .. ": " ..err) end - cb(path,stat,state,name,kind,ino,off,attr, path .. "/" ..name) - ::continue:: + local stat, state, err = dir.scan(path) + if not err then + for name, kind, ino, off in state do + if name == "." then goto continue end + attr,err = unix.stat(path..'/'..name, unix.AT_SYMLINK_NOFOLLOW) + if err then print("error: ".. name .. ": " ..err) end + cb(path,stat,state,name,kind,ino,off,attr, path .. "/" ..name) + ::continue:: + end end - return state + return state,err end dir.serve = function(folder) -- raw redbean function (use dir.foreach in soakbean) @@ -97,7 +97,11 @@ dir.serve = function(folder) -- raw redbean function (use dir.foreach in soakbea Write('ctim\r\n') Write('\r\n') - local stat, state = dir.scan(path) + local stat, state, err = dir.scan(path) + if stat == nil then + return ServeError(404,"does not exist") + end + dir.foreach(path, function(path,stat,state,name,kind,ino,off,attr,fullpath) Write('\r\n') Write('') diff --git a/src/.lua/page b/src/.lua/page new file mode 120000 index 0000000..ee335c2 --- /dev/null +++ b/src/.lua/page @@ -0,0 +1 @@ +../page \ No newline at end of file diff --git a/src/cmd/createplugin.lua b/src/cmd/createplugin.lua index f113546..5958989 100644 --- a/src/cmd/createplugin.lua +++ b/src/cmd/createplugin.lua @@ -3,7 +3,7 @@ return function(app,argv) -- -- 1. put the code below in a file ('xrforge.myplugin.lua' e.g.) --- 2. make sure to require it at the top of xrforge.lua (require('xrforge.myplugin') e.g.) +-- 2. in xrforge.lua make sure to add (at the top): app.plugin.myplugin = require('xrforge.myplugin') -- 3. profit! -- @@ -38,7 +38,10 @@ local plugin = { default="bar", values="" } - } + }, + myfunc = function() + -- call me via app.plugin.myplugin.myfunc() + end } return plugin diff --git a/src/cmd/generate.lua b/src/cmd/generate.lua new file mode 100644 index 0000000..4d694ea --- /dev/null +++ b/src/cmd/generate.lua @@ -0,0 +1,4 @@ +return function(app,argv) + app.plugin.diskscan.scan() + os.exit() +end diff --git a/src/css/xrforge.css b/src/css/xrforge.css index f49418a..6e8f249 100644 --- a/src/css/xrforge.css +++ b/src/css/xrforge.css @@ -184,14 +184,14 @@ ul{ .menu{ text-align: right; } +.menu, +.menu *{ + white-space: nowrap; +} .menu a { padding-right:30px; } -.menu br { - display:none -} - .grid .tile{ border-radius:5px; } .grid .tile .text{ padding:3px 0px; } .grid .tile .img{ diff --git a/src/css/xrforge.max.css b/src/css/xrforge.max.css index 8f177f5..264c348 100644 --- a/src/css/xrforge.max.css +++ b/src/css/xrforge.max.css @@ -33,9 +33,14 @@ body{ form table tr > td:nth-child(3) { display:none; } - .menu br { - display:initial !important; + .menu{ + position:absolute; + right:20px; } + .menu a{ + padding-right:20px !important; + } + .menu a.xl{ display:none; } } .grid .tile .text{ diff --git a/src/page/admin/index.html b/src/page/admin/index.html index 3f02bee..cb87fbd 100644 --- a/src/page/admin/index.html +++ b/src/page/admin/index.html @@ -10,145 +10,6 @@ ${notification}
-
-
- Local content -
-
-
- - - - - - - - -
- - -
- Directory - - - - Directory to scan for experiences (use '.' for current) -
-
- -
-
- -
-
- Hide content -
-
-
- - - - - - - - -
- - -
- Keywords - - - - Space-separated, lua patterns are allowed -
-
- -
-
- -
-
- Manyfold instances -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- Instance URL 1 - - - - Manyfold URL goes here (https://xrforge.isvery.ninja e.g.) -
- Instance URL 2 - - - - Manyfold URL goes here (https://xrforge.isvery.ninja e.g.) -
- Instance URL 3 - - - - Manyfold URL goes here (https://xrforge.isvery.ninja e.g.) -
- Sync strategy - - - - Specify how manyfold instances should be synced. -
- Sync interval - - - - Specify how often manyfold instances should be checked for recent items. -
-
- -
-
${plugins}
diff --git a/src/page/admin/index.lua b/src/page/admin/index.lua index 69793d2..877cabb 100644 --- a/src/page/admin/index.lua +++ b/src/page/admin/index.lua @@ -14,11 +14,13 @@ function pluginInput(opt) ]] + local value = app.opts[ opt.key:gsub("^app.opts.","") ] if type(opt.values) == 'table' then html = html .. util.template('',{ key = opt.key, - value = app.opts[key] + value = value }) end html = html .. util.template([[ @@ -41,14 +43,17 @@ function pluginInput(opt) return html end -function pluginUI(name,plugin) - local plugindata = util.flatten(plugin,"plugin") - plugindata.opts = "" - foreach(plugin.opts, function(k,opt) - plugindata.opts = plugindata.opts .. pluginInput(opt) - end) - print_r(plugindata) - data.plugins = data.plugins .. util.template( LoadAsset('/page/admin/plugin.html'), plugindata) +function pluginUI(seen) + return function(name,plugin) + if seen[ plugin.name ] then return end -- already processed + seen[ plugin.name ] = true + local plugindata = util.flatten(plugin,"plugin") + plugindata.opts = "" + foreach(plugin.opts, function(k,opt) + plugindata.opts = plugindata.opts .. pluginInput(opt) + end) + data.plugins = data.plugins .. util.template( LoadAsset('/page/admin/plugin.html'), plugindata) + end end function saveData() @@ -70,7 +75,8 @@ function saveData() end end -foreach( app.plugin, pluginUI ) +local seen = {} +foreach( app.plugin, pluginUI(seen) ) saveData() local html = util.template( LoadAsset("page/admin/index.html"),data) diff --git a/src/page/admin/plugin.html b/src/page/admin/plugin.html index 2149fea..00505c6 100644 --- a/src/page/admin/plugin.html +++ b/src/page/admin/plugin.html @@ -1,5 +1,5 @@
-
${plugin.name} plugin
+
${plugin.name}
diff --git a/src/page/experiences.html b/src/page/experiences.html index d238e28..e200a00 100644 --- a/src/page/experiences.html +++ b/src/page/experiences.html @@ -7,24 +7,7 @@ ${header}
-
-
-
-  Item 1 -
-
-
-
-
-  Item 2 -
-
-
-
-
-  Item 3 -
-
+ ${tiles}
${footer} diff --git a/src/page/experiences.lua b/src/page/experiences.lua index 6e59d80..dc19a64 100644 --- a/src/page/experiences.lua +++ b/src/page/experiences.lua @@ -1,12 +1,16 @@ local util = require("util") +local tile = require('page/partial/tile') if not app.cache['experiences.html'] then + print("processing! " .. util.count(app.cache.items) .. " items") app.cache['experiences.html'] = util.memoize( function() - print("processing!") - local data = util.merge({foo='1'},{ opts = app.opts }) - local html = util.template( LoadAsset("page/experiences.html"),app) + local data = util.merge({tiles=''},{ opts = app.opts }) + foreach(app.cache.items, function(k,v) + data.tiles = data.tiles .. tile( util.flatten(v,"item") ) + end) + local html = util.template( LoadAsset("page/experiences.html"),data) return html end, 60*5 -- re-run every 5 mins (otherwise return cache) diff --git a/src/page/header.html b/src/page/header.html index 8966f87..23304cb 100644 --- a/src/page/header.html +++ b/src/page/header.html @@ -3,7 +3,7 @@ - + XRForge - selfsovereign interoperable XR experiences @@ -25,10 +25,8 @@
diff --git a/src/page/partial/tile.lua b/src/page/partial/tile.lua new file mode 100644 index 0000000..33d06fc --- /dev/null +++ b/src/page/partial/tile.lua @@ -0,0 +1,12 @@ +local util = require('util') + +return function(opts) + return util.template([[ +
+
+
+  ${item.title} +
+
+ ]],opts) +end diff --git a/src/.lua/scan.lua b/src/plugin/diskscan.lua similarity index 55% rename from src/.lua/scan.lua rename to src/plugin/diskscan.lua index 742f936..6999769 100644 --- a/src/.lua/scan.lua +++ b/src/plugin/diskscan.lua @@ -1,8 +1,18 @@ local dir = require "opendirectory" -local scan -scan = { - +-- this registers the plugin UI /funcs +return { + name = "Content (from disk)", + info = 'XRForge can search in (unnested) folders for XR/image/video files.', + opts = { + { + key='app.opts.plugin_diskscan_dir', + title='Directory', + info="Directory to scan for experiences (use '.' for current) ", + default=".", + values='' + } + }, shouldHideFile = function(name) return name == ".." or name == "util" -- repository @@ -12,12 +22,15 @@ scan = { end, scan = function() + local me = app.plugin.diskscan + local folder = app.opts.plugin_diskscan_dir -- scan dirs - local dref = dir.foreach( app.opts.disk_dir, function(path,stat,state,name,kind,ino,off,attr,fullpath) - if kind == unix.DT_DIR and not scan.shouldHideFile(name) then + local dref, derr= dir.foreach( folder, function(path,stat,state,name,kind,ino,off,attr,fullpath) + if (kind == unix.DT_DIR or kind == unix.DT_LNK) and not me.shouldHideFile(name) then local experience = { path = fullpath, name = name, + title = name, tag = {}, file = {stat=stat,state=state,kind=kind,ino=ino,off=off,attr=attr,fullpath=fullpath,name=name,path=path} } @@ -25,18 +38,18 @@ scan = { print("ℹ️ ".. fullpath) -- scan files - local fref = dir.foreach(fullpath, function(path,stat,state,name,kind,ino,off,attr, fullpath) - if kind ~= unix.DT_DIR and not scan.shouldHideFile(name) then + local fref, ferr = dir.foreach(fullpath, function(path,stat,state,name,kind,ino,off,attr, fullpath) + if kind ~= unix.DT_DIR and not me.shouldHideFile(name) then experience.file = {stat=stat,state=state,kind=kind,ino=ino,off=off,attr=attr,fullpath=fullpath,name=name,path=path} app.pub("scan_experience_file", nil, experience) local size = attr:size() trace("ℹ️ ".. fullpath) end end) - fref:close() + if not ferr then fref:close() end end end) - dref:close() + if not derr then dref:close() end foreach( app.cache.items, function(k,v) -- remove file attributes (which are closed already) @@ -46,5 +59,3 @@ scan = { end) end } - -return scan diff --git a/src/plugin/janusxr.lua b/src/plugin/janusxr.lua index 1055360..4bad0bf 100644 --- a/src/plugin/janusxr.lua +++ b/src/plugin/janusxr.lua @@ -11,12 +11,17 @@ app.on('scan_experience_file', function(exp) local size = exp.file.attr:size() if exp.file.name:match(".glb") and not util.contains(exp.tag,"xrfragment") then print("✅ └─ plugin/janusxr: generating janusxr.xml") + table.insert(exp.tag, "janusxr") + app.pub("scan_experience_janusxr", nil, {exp=exp, item=item}) -- be nice to other plugins + table.insert( app.cache.items, exp) elseif size < 1000 then -- skip large binary files local data = util.readfile( exp.file.fullpath) if data:match(pattern_fireboxroom) then table.insert(exp.tag, "janusxr") local title = data:match(pattern_title) - if title then exp.title = title:gsub("<.*","") end + if title then + exp.title = title:gsub("<.*","") + end app.pub("scan_experience_janusxr", nil, {exp=exp, item=item}) -- be nice to other plugins table.insert( app.cache.items, exp) print("✅ └─ plugin/janusxr: " .. exp.file.name) @@ -31,8 +36,15 @@ end) -- this registers the plugin UI local plugin = { name = "JanusXR", - info = 'janusxr allows composing (interconnected) 3D experiences (via portals) via its JML markup language. It also supports AFRAME (geometry)markup.', + info = 'JanusXR connects XR experiences via XML portals (+AFRAME (geometry)markup also supported).', opts = { + { + key='app.opts.plugin_janusxr_janusweb_endpoint', + title='backend URL', + info='Where janusweb (editor) can save its JML/assets while livecoding', + default="/janusxr", + values='' + }, { key='app.opts.plugin_janusxr_wrap_image', title='wrap images', diff --git a/src/plugin/manyfold.lua b/src/plugin/manyfold.lua new file mode 100644 index 0000000..afe9a11 --- /dev/null +++ b/src/plugin/manyfold.lua @@ -0,0 +1,64 @@ +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) + print("content was updated") +end) + +-- this registers the plugin UI +local plugin = { + name = "Manyfold", + info = '', + opts = { + { + key='app.opts.plugin_manyfold_instance_1', + title='Instance 1 URL', + info='Manyfold URL goes here (https://xrforge.isvery.ninja e.g.)', + default="https://xrforge.isvery.ninja", + values='' + }, + { + key='app.opts.plugin_manyfold_instance_2', + title='Instance 2 URL', + info='Manyfold URL goes here (https://xrforge.isvery.ninja e.g.)', + default="", + values='' + }, + { + key='app.opts.plugin_manyfold_instance_3', + title='Instance 3 URL', + info='Manyfold URL goes here (https://xrforge.isvery.ninja e.g.)', + default="", + values='' + }, + { + key='app.opts.plugin_manyfold_sync_strategy', + title='Instance 3 URL', + info="Specify how content from manyfold instances should be fetched", + default="recent", + values={recent='only recent',all_recent='everything + recent', disabled='disabled'} + }, + --{ + -- key='app.opts.plugin_manyfold_sync_strategy', + -- title='Instance 3 URL', + -- info='Specify how content from manyfold instances should be fetched", + -- default="recent", + -- values={recent='only recent',all_recent='everything + recent', disabled='disabled'} + --}, + --{ + -- key='app.opts.plugin_manyfold_sync_interval', + -- title='Instance 3 URL', + -- info='Specify how often xrforge should check for recent items #serveronly' + -- default="recent", + -- values={recent='only recent',all_recent='everything + recent', disabled='disabled'} + --}, + } +} + +return plugin diff --git a/src/plugin/moderation.lua b/src/plugin/moderation.lua new file mode 100644 index 0000000..7decea6 --- /dev/null +++ b/src/plugin/moderation.lua @@ -0,0 +1,29 @@ +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) + print("content was updated") +end) + +-- this registers the plugin UI +local plugin = { + name = "Moderation", + info = 'Hide certain experiences from the listing (client work e.g.)', + opts = { + { + key='app.opts.plugin_moderation_mutekeywords', + title='Mute keywords', + info='Space-separated, lua patterns are allowed', + default = "wip beta -v[0-9] customerX", + values='' + }, + } +} + +return plugin