improved default popup for mobile + openbrush/manyfold centralized refactor

This commit is contained in:
Leon van Kammen 2026-09-15 20:41:30 +02:00
parent 86eaf1556f
commit 81b9d238a9
13 changed files with 521 additions and 188 deletions

View file

@ -31,6 +31,7 @@ app = require("soakbean") {
app.reset = function()
app.cache = { items = {}, url = {}, shared = require('sharedcache'), dirs={} }
end
app.reset()
app.addPlugin = function(file)
local p = require(file)
@ -50,7 +51,8 @@ end
app.addExperience = function(experience, ino)
if app.cache.items[ino] then -- handle duplicate symlinks, symlinks to symlinks e.g.
app.cache.items[ino .. ' ' .. experience.parent.id ] = experience
local postfix = '_' .. ( experience.parent and experience.parent.id or tostring(os.time()) )
app.cache.items[ino .. postfix ] = experience
else
app.cache.items[ino] = experience
end
@ -63,9 +65,7 @@ end
-- api skeleton (implemented via soakbean's app.on(...))
app.set = function(k,v) end
app.init = function()
app.reset()
end
app.init = function() end
-- load config
app.opts = require('config')(app)
@ -103,13 +103,10 @@ print = function(...) Log(kLogInfo, ... or "''") end -- nice redbean logging fro
app.url['^/data'] = '/data.lua' -- setup custom file endpoint
app.use( app.plugin.global.middleware.homepage_2D_or_3D )
app.url['^/admin[/]?$'] = '/page/admin/index.lua'
app.use( require('middleware/onlyGET')({exclude={'^/admin'}}) ) -- we are staticwebgen-first server-last
app.get('^/design[/]?$', util.pagerender('page/design.html'))
app.get('^/about[/]?$', util.pagerender('page/about.html'))
app.post('^/save', function(req,res,next) -- setup inline POST endpoint
-- also .get(), .put(), .delete(), .options()
app.cache = req.body -- middleware auto-decodes json
@ -125,7 +122,17 @@ app --
return user == app.opts.admin_user and pass == app.opts.admin_pass
end
}))
.use( require("json").middleware() ) -- try plug'n'play json API middleware
app.url['^/admin[/]?$'] = '/page/admin/index.lua'
app.use( require('middleware/onlyGET')({exclude={'^/admin'}}) ) -- we are staticwebgen-first server-last
app.get('/admin/server/reset', function(req,res,next) -- easy way to reload/rescan xrforge
app.reset()
res.status(303)
res.header('Location', req.header['Referer']:gsub("?.*","") .. '?notify=Server%20is%20being%20reloaded.' )
res.send(req,res)
end)
app.use( require("json").middleware() ) -- try plug'n'play json API middleware
.use( require('page/dir').middleware ) -- directory browser
.use( app.router( app.url ) ) -- try url router
.use( app.response() ) -- try serve app response (if any)
@ -149,3 +156,10 @@ OnServerStart = function()
app.init() -- this triggers init event
end
OnServerStop = function()
-- run the 'reset' cli-cmd equivalent (as childprocesses might leave
-- the terminal in a confused state)
io.write("\27[H\27[2J\27[3J\27[0m")
io.flush()
end

View file

@ -1,6 +1,22 @@
-- this is a helper/orchestrator for apicalls/syncs
--
-- implemented in openbrush.lua
local apiSync = {
syncHashtags = function(opts)
init = function(pluginname)
local plugin = app.plugin[pluginname]
local api = {}
api.pluginname = pluginname
if not plugin.apiCall or not plugin.fetch or not plugin.download then
print("apiSync.lua: plugin " .. pluginname .. " does not implement the apiSync interface")
os.exit()
end
api.apiCall = plugin.apiCall
api.fetch = plugin.fetch
api.download = plugin.download
api.syncHashtags = function(opts)
local synced = 0
local tags = opts.tags
if #tags == 0 then table.insert(tags,'') end
@ -14,11 +30,49 @@ local apiSync = {
end
end, true)
if synced > 0 then
app.cache.url = {} -- delete page cache
app.plugin.diskscan.scan(app.opts.plugin_diskscan_dir)
end
return synced
end
api.sync = function(api)
if #app.opts['plugin_' .. pluginname .. '_sync_URL'] > 0 and util.cmdExist('wget') then
local synced = api.syncHashtags({
tags = util.split( app.opts['plugin_' .. api.pluginname .. '_sync_hashtag'] or '', ' '),
apiCall = api.apiCall,
fetch = api.fetch,
download = api.download
})
end
end
-- runs sync in foreground
app.on('reset', function()
api.sync(api)
end)
-- runs sync in foreground
app.on('init', function()
foreach( app.plugin.openbrush.opts, function(k,opt)
if opt.key == 'app.opts.plugin_openbrush_sync_folder' then
opt.values = app.cache.dirs
end
end)
if util.contains(argv, 'generate') then api.sync(api) end
end)
-- runs sync background
app.on('scheduler', function(opts)
if util.contains(argv, 'server') then
if opts.interval == app.opts['plugin_' .. pluginname .. '_sync_when'] then
api.sync(api)
end
end
end)
return api
end
}
return apiSync

View file

@ -57,6 +57,7 @@ td,div,p{
input,
textarea,
button,
select{
box-sizing: border-box;
margin: 0px 0px 10px 0px;
@ -91,6 +92,7 @@ input[type=submit]{
.btn a:active,
.btn a:visited,
.btn a:link,
button,
input[type=submit] {
font-size:15px;
display: inline-block;

View file

@ -3,7 +3,9 @@ ${header}
<br/>
<hr style="margin-top:0">
${notification}
{if #notification > 0 then
print('<div class="notification">' .. notification .. '</div>')
end}
<form method="POST">
<div class="tab-frame">
@ -19,6 +21,9 @@ ${notification}
<div class="btn">
<input type="submit" class="btn secondary" value="Save" style=""/>
</div>
<div class="btn" style="float:right">
<a href="/admin/server/reset" class="btn primary">reload server</a>
</div>
</div>
</div>
</form>

View file

@ -1,6 +1,6 @@
local util = require("util")
local data = {notification=''}
local data = {notification= GetParam('notify') or ''}
data.plugins = ""
data.tabs = {}
@ -87,9 +87,9 @@ function saveData()
end)
if saveconfig then
app.set('app.opts', app.opts)
data.notification = '<div class="notification">updated</div>'
data.notification = 'updated'
else
data.notification = '<div class="notification">no changes detected</div>'
data.notification = 'no changes detected'
end
end
end

View file

@ -31,7 +31,21 @@
]])
end
end}
<br/><br/>
<br/>
{if true then
local btns = plugin.buttons or {}
if #btns > 0 then
for i,btn in pairs(btns) do
print([[
<div class="btn">
<a href="]] .. btn.href .. [[" class="btn primary">]] .. (btn.label or '') .. [[</a>
</div>
]])
end
print('<br/>')
end
end}
<br/>
</td>
</tr>
${opts}

View file

@ -99,7 +99,6 @@ return {
-- what to do here? no generated portals?
data.janusxr = app.tpl( util.readfile(v.path), data )
end
print_r(data.item)
end
end
end)

View file

@ -43,7 +43,6 @@ return {
end,
scan = function(scandir)
app.reset() -- flush cache
local me = app.plugin.diskscan
local seen = {symlinks={}}
-- ensure rootdir as experience

View file

@ -98,7 +98,15 @@ local plugin = {
title='Viewer instructions',
info='Default popup for 3D viewer instructions',
default = [[
<h1>Tips to navigate in XR</h1>
<h1>Hi there! 👋</h1>
Welcome to this XR experience.<br/>
<br/>
<div class="btn script">
<button class="btn secondary" onclick="document.querySelector('#tips').style.display = ''">Tips for navigating in XR</button>
</div>
<div id="tips" style="display:none">
<br/>
<table>
<tr>
<td width="200">
@ -143,6 +151,7 @@ local plugin = {
</table>
<br/>
You've got this!
</div>
<br/>
]],
control='textarea',

View file

@ -27,11 +27,12 @@ local grid = {
local index = 0
for i, v in pairs(items) do
local url = v.redirect and v.redirect or v.url
local overlay = v.title:match('#overlay') and true or false
-- Position current item
local pos = {
x = v.title:match("#overlay") and 0 or x * gridsize,
x = overlay and 0 or x * gridsize,
y = 0,
z = v.title:match("#overlay") and 0 or z * gridsize
z = overlay and 0 or z * gridsize
}
local posStr = pos.x .. ' ' .. pos.y .. ' ' .. pos.z
local is_nested_room = false
@ -41,6 +42,7 @@ local grid = {
else
jml = jml .. ' <object id="' .. url .. '" pos="' .. posStr .. '"/>\n'
end
if not overlay then
index = index + 1
-- Move to next position in the spiral
if index < #items then
@ -61,6 +63,7 @@ local grid = {
end
end
end
end
return jml
end

View file

@ -1,64 +1,312 @@
local util = require('util')
local apiSync = require('apiSync')
app.on('scan_experience', function(exp)
--print_r(exp)
end)
--app.on('init', function()
-- app.plugin.manyfold.api = apiSync.init( 'manyfold' )
--end)
app.on('scan_experience_file', function(exp)
local function scanTiltFile(exp)
if exp.path:lower():match(".tilt$") then
app.markViewable(exp)
end
end
local function scanForOpenBrush(exp)
local tags = app.plugin.manyfold.tags
if not tags then
tags = util.split( app.opts.plugin_manyfold_sync_hashtag, ' ')
table.insert( tags,"manyfold")
app.plugin.manyfold.tags = tags
end
foreach( tags, function(k,tag)
tag = tag:gsub("#","")
if exp.path:match('/' .. tag .. '/') then -- might not be precise enough
if not exp.parent.janusxr then
exp.parent.janusxr = {}
end
exp.parent.janusxr.room = app.opts.plugin_manyfold_wrap_room
exp.parent.portal_width = tonumber( app.opts.plugin_manyfold_portal_width )
exp.parent.layout = app.opts.plugin_manyfold_layout
end
end)
scanTiltFile(exp)
end
--app.on('scan_experience', scanForOpenBrush )
--app.on('scan_experience_file', scanForOpenBrush )
--app.on("scan_experience_janusxr", function(opts) scanForOpenBrush(opts.exp) end)
app.on('set', function(k,v)
end)
local sync_URL = app.opts_plugin_manyfold_sync_URL or ''
-- this registers the plugin UI
local plugin = {
name = "Manyfold",
tab = "<img src='img/link.svg' class='icon invert'/> Manyfold",
info = '',
name = "ManyFold",
infowide = true,
info = [[
<img src="img/manyfold.png" style="width:50%; max-width:233px; display:inline-block; float:left; margin-right:20px;"/>
<a href="https://manyfold.app/" target="_blank">OpenBrush</a> lets you paint in 3D space with virtual reality.<br>
XRForge's webviewer (<a href="https://github.com/jbaicoianu/janusweb">JanusWeb</a>) supports interconnecting OpenBrush projects via 3D portals or a grid.<br/>
OpenBrush projects are stored in a (selfhosted) <a href="https://icosa.gallery" target="_blank">icosa.gallery</a> instance, which can be configured below.<br><br>
<b>NOTE:</b> for collective virtualworld building use <b>hashtags in project-titles</b>. Each hashtag will become its own folder, which allows for <a href="/about#autocompose">crowdsourcing/combining scenes</a> designed in manyfold.
]],
--buttons = {
-- { label = "resync now!", href="/reset" }
--},
opts = {
{
key='app.opts.plugin_manyfold_instance_1',
title='Instance 1 URL',
info='<a href="https://manyfold.app/" target="_blank">Manyfold</a> URL goes here (https://xrforge.isvery.ninja e.g.)',
default="https://xrforge.isvery.ninja",
values=''
key='app.opts.plugin_manyfold_wrap_room',
title='Wrap VR space',
info='render 3D files in <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> room',
default="room_plane",
values= app.plugin.janusxr.opts[4].values -- reuse rooms from janusxr plugin
},
{
key='app.opts.plugin_manyfold_instance_2',
title='Instance 2 URL',
info='<a href="https://manyfold.app/" target="_blank">Manyfold</a> URL goes here (https://xrforge.isvery.ninja e.g.)',
default="",
values=''
key='app.opts.plugin_manyfold_layout',
title='Layout assets',
info='NOTE: use #overlay in asset-title to skip layout (sky/floor/e.g.)',
default="grid",
values={portals="As portals", grid="As grid"}
},
{
key='app.opts.plugin_manyfold_instance_3',
title='Instance 3 URL',
info='<a href="https://manyfold.app/" target="_blank">Manyfold</a> URL goes here (https://xrforge.isvery.ninja e.g.)',
default="",
key='app.opts.plugin_manyfold_layout_grid_size',
title='Grid size',
info='in square meters (only used for grid layout)',
default="3",
placeholder="3",
values=''
},
{
key='app.opts.plugin_manyfold_sync_strategy',
title='Sync strategy',
info="Specify how content from manyfold instances should be fetched",
default="recent",
values={recent='only recent',all_recent='everything + recent', disabled='disabled'}
title='How to sync',
info='download assets locally (prevent linkrot)',
default = "mirror",
values={mirror="mirror locally"}
},
--{
-- 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'}
--},
{
key='app.opts.plugin_manyfold_sync_when',
title='Sync interval',
info='when to sync?',
default = "boot",
values={disabled="disabled", boot="on boot", hourly="hourly", daily="daily"}
},
{
key='app.opts.plugin_manyfold_sync_folder',
title='Sync to folder',
info='manyfold (icosa.gallery) assets will be saved here',
placeholder = "manyfold",
default = app.opts.plugin_diskscan_dir,
values = {}
},
{
key='app.opts.plugin_manyfold_sync_folder_thumbnail',
title='Thumbnail',
info='URL of Thumbnail for folder/portal',
placeholder = "img/manyfold.png",
default = "img/manyfold.png",
values=''
},
{
key='app.opts.plugin_manyfold_portal_width',
title='Portal spacing',
info='Spacing between portals (prevent thumbnail-overlap)',
placeholder = "4.2",
default = "4.2",
values=''
},
{
key='app.opts.plugin_manyfold_sync_URL',
title='Icosa gallery URL',
info='public/selfhosted <a href="https://github.com/icosa-foundation/icosa-gallery target="_blank">icosa.gallery</a> server-instance',
placeholder = "icosa.gallery",
default = "icosa.gallery",
values=''
},
{
key='app.opts.plugin_manyfold_sync_hashtag',
title='tags',
info=( sync_URL and 'space-separated tags will generate their own folders' or ''),
default = "#1 #xrforge",
placeholder = "#1 #xrforge",
values=''
},
{
key='app.opts.plugin_manyfold_sync_category',
title='category',
info=( sync_URL and 'space-separated, leave empty or see <a href="https://api.' .. sync_URL .. '/v1/docs#/Assets/icosa_api_assets_get_assets">API docs</a>' or ''),
placeholder = "ART",
default = "",
values=''
},
{
key='app.opts.plugin_manyfold_sync_keywords',
title='keywords',
info=( sync_URL and 'space-separated, leave empty or see <a href="https://api.' .. sync_URL .. '/v1/docs#/Assets/icosa_api_assets_get_assets">API docs</a>' or ''),
placeholder = "xrforge",
default = "",
values=''
},
{
key='app.opts.plugin_manyfold_sync_triangle_max',
title='Polygons maximum',
info=( sync_URL and 'performance-cap, leave empty or see <a href="https://api.' .. sync_URL .. '/v1/docs#/Assets/icosa_api_assets_get_assets">API docs</a>' or ''),
placeholder = "10000",
default = "",
values=''
},
{
key='app.opts.plugin_manyfold_sync_license',
title='License',
info='filter assets on specific license',
default = "",
values={ [""]="", ALL_CC="CreateCommons (ALL)"}
},
{
key='app.opts.plugin_manyfold_sync_author',
title='Author name',
info='space-separated: filter assets on specific Author name',
placeholder='sarah',
default = "",
values=""
},
{
key='app.opts.plugin_manyfold_sync_authorid',
title='Author id',
info='space-separated: filter assets on specific Author id',
placeholder='9e8b98fbe',
default = "",
values=""
},
{
key='app.opts.plugin_manyfold_sync_maxitems',
title='Max latest items',
info='limit the total amount of result',
placeholder='3',
default = "3",
values=""
},
{
key='app.opts.plugin_manyfold_sync_collection',
title='In collection',
info='should assets be part of a collection?',
default = "disabled",
values={enabled="yes", disabled="no"}
}
},
-- expected by src/.lua/apiSync.lua
apiCall = function(tag)
return 'https://api.' .. app.opts.plugin_manyfold_sync_URL .. '/v1/assets?xrforge=' .. EscapeFragment(app.host)
.. '&xrforge_strategy=' .. EscapeFragment(app.opts.plugin_manyfold_sync_strategy)
.. '&time=' .. tostring( os.time() )
.. (tag and #tag > 0 and '&keywords=' .. EscapeFragment(tag) or '')
.. (#app.opts.plugin_manyfold_sync_category > 0 and '&category=' .. EscapeFragment(app.opts.plugin_manyfold_sync_category) or '')
.. (#app.opts.plugin_manyfold_sync_keywords > 0 and '&keywords=' .. EscapeFragment(app.opts.plugin_manyfold_sync_keywords) or '')
.. (#app.opts.plugin_manyfold_sync_triangle_max > 0 and '&triangleCountMax=' .. EscapeFragment(app.opts.plugin_manyfold_sync_triangle_max) or '')
.. (#app.opts.plugin_manyfold_sync_license > 0 and '&license=' .. app.opts.plugin_manyfold_sync_license or '' )
.. (app.opts.plugin_manyfold_sync_collection == 'enabled' and '&inCollection=true' or '' )
.. (#app.opts.plugin_manyfold_sync_maxitems > 0 and '&pageSize=' .. EscapeFragment(app.opts.plugin_manyfold_sync_maxitems) or '' )
-- TODO: filter AFTER the request
-- .. (#app.opts.plugin_manyfold_sync_author > 0 and '&authorName=' .. EscapeFragment(app.opts.plugin_manyfold_sync_author) or '')
-- .. (#app.opts.plugin_manyfold_sync_authorid > 0 and '&authorId=' .. EscapeFragment(app.opts.plugin_manyfold_sync_authorid) or '')
end,
getFolder = function(tag)
local folder = app.opts.plugin_manyfold_sync_folder
if tag then
folder = folder .. '/' .. util.hyphenate(tag)
end
return folder
end,
-- expected by src/.lua/apiSync.lua
fetch = function(url,opts)
opts = opts or {}
if not opts.body then
local ok, headers, body = Fetch( url )
if not ok then
return print("[manyfold.lua] error: could not fetch: " .. app.plugin.manyfold.apiCall(tag))
end
opts.body = body
end
return DecodeJson(opts.body)
end,
-- expected by src/.lua/apiSync.lua
download = function(t,tag)
local me = app.plugin.manyfold
local synced = 0
foreach( t.assets, function(k,asset)
if type(asset) == 'table' then
local dirname = '_' .. asset.assetId .. '-' .. util.hyphenate( asset.displayName)
local dirnameFull = me.getFolder(tag) .. '/' .. dirname
local jsonfile = dirnameFull .. '/asset.json'
local sync = true
local assetPrevious = false
local f = false
function preferFormat(f)
local found = false
foreach( asset.formats, function(k,v)
if v.formatType == f then
found = v
end
end)
return found
end
f = preferFormat('TILT')
if not f then f = preferFormat('GLTF2') end
if not f then f = preferFormat('GLTF1') end
if not f then f = preferFormat('OBJ') end
if f then
if not util.fileExist( dirnameFull ) then
os.execute("mkdir -p " .. dirnameFull)
end
if util.fileExist(jsonfile) then
assetPrevious = DecodeJson( util.readfile(jsonfile) )
if util.parseIsoDate(asset.updateTime) == util.parseIsoDate(assetPrevious.updateTime) then
sync = false -- already finished
end
end
if sync then
print('' .. asset.displayName .. ' (syncing) #manyfold')
util.writefile( jsonfile, EncodeJson(asset) )
local rootfile = dirnameFull .. '/' .. f.root.url:gsub(".*/","")
local thumbnailfile = ''
if asset.thumbnail then
thumbnailfile = dirnameFull .. '.' .. asset.thumbnail.url:gsub(".*%.",'')
print('🤞 fetch ' .. asset.thumbnail.url)
os.execute('wget "' ..asset.thumbnail.url .. '" -O "' .. thumbnailfile .. '"')
end
print('🤞 fetch ' .. f.root.url)
os.execute('wget "' .. f.root.url .. '" -O "' .. rootfile .. '"')
if #f.resources > 0 then
foreach( f.resources, function(k,resource)
local file = dirnameFull .. '/' .. resource.url:gsub(".*/","")
print('🤞 fetch ' .. resource.url)
os.execute('wget "' .. resource.url .. '" -O "' .. file .. '"')
end)
end
synced = synced + 1
else
print(' ' .. asset.displayName .. ' (up to date) #manyfold')
end
else
print('[manyfold.lua] error: no suitable format found for ' .. asset.displayName .. ' ' .. asset.assetId)
end
end
end, true)
return synced
end
}
if not util.cmdExist('wget') then
plugin.info = plugin.info .. '<br><br><b>NOTE:</b> wget command not installed..plugin is disabled'
end
return plugin

View file

@ -1,34 +1,8 @@
local util = require('util')
local apiSync = require('apiSync')
local syncIcosaGallery = function()
if #app.opts.plugin_openbrush_sync_URL > 0 then
local synced = apiSync.syncHashtags({
tags = util.split( app.opts.plugin_openbrush_sync_hashtag or '', ' '),
apiCall = app.plugin.openbrush.apiCall,
fetch = app.plugin.openbrush.fetch,
download = app.plugin.openbrush.download
})
end
end
app.on('init', function()
foreach( app.plugin.openbrush.opts, function(k,opt)
if opt.key == 'app.opts.plugin_openbrush_sync_folder' then
opt.values = app.cache.dirs
end
end)
if util.contains(argv, 'generate') then
syncIcosaGallery()
end
end)
app.on('scheduler', function(opts)
if util.contains(argv, 'server') then
if util.cmdExist('wget') and opts.interval == app.opts.plugin_openbrush_sync_when then
syncIcosaGallery()
end
end
app.plugin.openbrush.api = apiSync.init( 'openbrush' )
end)
local function scanTiltFile(exp)
@ -52,6 +26,7 @@ local function scanForOpenBrush(exp)
end
exp.parent.janusxr.room = app.opts.plugin_openbrush_wrap_room
exp.parent.portal_width = tonumber( app.opts.plugin_openbrush_portal_width )
exp.parent.grid_size = tonumber( app.opts.plugin_openbrush_grid_size )
exp.parent.layout = app.opts.plugin_openbrush_layout
end
end)
@ -75,39 +50,42 @@ local plugin = {
info = [[
<img src="img/openbrush.png" style="width:50%; max-width:233px; display:inline-block; float:left; margin-right:20px;"/>
<a href="https://openbrush.app/" target="_blank">OpenBrush</a> lets you paint in 3D space with virtual reality.<br>
XRForge's webviewer (<a href="https://github.com/jbaicoianu/janusweb">JanusWeb</a>) supports interconnecting OpenBrush projects via 3D portals.<br/>
XRForge's webviewer (<a href="https://github.com/jbaicoianu/janusweb">JanusWeb</a>) supports interconnecting OpenBrush projects via 3D portals or a grid.<br/>
OpenBrush projects are stored in a (selfhosted) <a href="https://icosa.gallery" target="_blank">icosa.gallery</a> instance, which can be configured below.<br><br>
<b>NOTE:</b> for collective virtualworld building use <b>hashtags in project-titles</b>. Each hashtag will become its own folder, which allows for <a href="/about#autocompose">crowdsourcing/combining scenes</a> designed in openbrush.
]],
--buttons = {
-- { label = "resync now!", href="/reset" }
--},
opts = {
{
key='app.opts.plugin_openbrush_wrap_room',
title='Wrap VR space',
info='render 3D files in <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> room',
default="room_plane",
default="",
values= app.plugin.janusxr.opts[4].values -- reuse rooms from janusxr plugin
},
{
key='app.opts.plugin_openbrush_layout',
title='Layout assets',
info='NOTE: use #overlay in asset-title to skip layout (sky/floor/e.g.)',
default="portals",
default="grid",
values={portals="As portals", grid="As grid"}
},
{
key='app.opts.plugin_openbrush_layout_grid_size',
title='Grid size',
info='in square meters (unused for non-grid layout)',
default="3",
placeholder="3",
info='in square meters (only used for grid layout)',
default="75",
placeholder="75",
values=''
},
{
key='app.opts.plugin_openbrush_sync_strategy',
title='How to sync',
info='mirroring = most resilient; references = instant updates',
info='download assets locally (prevent linkrot)',
default = "mirror",
values={mirror="mirror locally",reference="reference remote URL"}
values={mirror="mirror locally"}
},
{
key='app.opts.plugin_openbrush_sync_when',
@ -116,13 +94,6 @@ local plugin = {
default = "boot",
values={disabled="disabled", boot="on boot", hourly="hourly", daily="daily"}
},
{
key='app.opts.plugin_openbrush_sync_bruteforce',
title='Sync anyways',
info='ignore timestamps',
default = "enabled",
values={disabled="disabled", enabled="enabled"}
},
{
key='app.opts.plugin_openbrush_sync_folder',
title='Sync to folder',
@ -142,7 +113,7 @@ local plugin = {
{
key='app.opts.plugin_openbrush_portal_width',
title='Portal spacing',
info='Adjust spacing between portals (prevent thumbnail-overlap)',
info='Spacing between portals (prevent thumbnail-overlap)',
placeholder = "4.2",
default = "4.2",
values=''
@ -159,8 +130,8 @@ local plugin = {
key='app.opts.plugin_openbrush_sync_hashtag',
title='tags',
info=( sync_URL and 'space-separated tags will generate their own folders' or ''),
default = "#1 #xrforge",
placeholder = "#1 #xrforge",
default = "#xrforge",
placeholder = "#xrforge",
values=''
},
{
@ -187,6 +158,13 @@ local plugin = {
default = "",
values=''
},
{
key='app.opts.plugin_openbrush_sync_license',
title='License',
info='filter assets on specific license',
default = "",
values={ [""]="", ALL_CC="CreateCommons (ALL)"}
},
{
key='app.opts.plugin_openbrush_sync_author',
title='Author name',
@ -220,14 +198,16 @@ local plugin = {
}
},
-- expected by src/.lua/apiSync.lua
apiCall = function(tag)
return 'https://api.' .. app.opts.plugin_openbrush_sync_URL .. '/v1/assets?xrforge=' .. EscapeFragment(app.host)
.. '&xrforge_strategy=' .. EscapeFragment(app.opts.plugin_openbrush_sync_strategy)
.. '&time=' .. tostring( os.time() )
.. (tag and #tag > 0 and '&keywords=' .. EscapeFragment(tag) or '')
.. (#app.opts.plugin_openbrush_sync_category > 0 and '&category=' .. EscapeFragment(app.opts.plugin_openbrush_sync_category) or '')
.. (#app.opts.plugin_openbrush_sync_keywords > 0 and '&keywords=' .. EscapeFragment(app.opts.plugin_openbrush_sync_keywords) or '')
.. (#app.opts.plugin_openbrush_sync_triangle_max > 0 and '&triangleCountMax=' .. EscapeFragment(app.opts.plugin_openbrush_sync_triangle_max) or '')
.. (app.opts.plugin_openbrush_sync_strategy == 'mirror' and '&license=ALL_CC' or '' )
.. (#app.opts.plugin_openbrush_sync_license > 0 and '&license=' .. app.opts.plugin_openbrush_sync_license or '' )
.. (app.opts.plugin_openbrush_sync_collection == 'enabled' and '&inCollection=true' or '' )
.. (#app.opts.plugin_openbrush_sync_maxitems > 0 and '&pageSize=' .. EscapeFragment(app.opts.plugin_openbrush_sync_maxitems) or '' )
-- TODO: filter AFTER the request
@ -243,6 +223,7 @@ local plugin = {
return folder
end,
-- expected by src/.lua/apiSync.lua
fetch = function(url,opts)
opts = opts or {}
if not opts.body then
@ -255,6 +236,7 @@ local plugin = {
return DecodeJson(opts.body)
end,
-- expected by src/.lua/apiSync.lua
download = function(t,tag)
local me = app.plugin.openbrush
local synced = 0
@ -292,11 +274,13 @@ local plugin = {
sync = false -- already finished
end
end
if sync or app.opts.plugin_openbrush_sync_bruteforce == 'enabled' then
if sync then
print('' .. asset.displayName .. ' (syncing) #openbrush')
util.writefile( jsonfile, EncodeJson(asset) )
local rootfile = dirnameFull .. '/' .. f.root.url:gsub(".*/","")
local thumbnailfile = dirnameFull .. '.' .. asset.thumbnail.url:gsub(".*%.",'')
if app.opts.plugin_openbrush_layout == 'portals' then
local thumbnailfile = ''
if asset.thumbnail and asset.thumbnail.url then
thumbnailfile = dirnameFull .. '.' .. asset.thumbnail.url:gsub(".*%.",'')
print('🤞 fetch ' .. asset.thumbnail.url)
os.execute('wget "' ..asset.thumbnail.url .. '" -O "' .. thumbnailfile .. '"')
end
@ -311,7 +295,7 @@ local plugin = {
end
synced = synced + 1
else
print('[openbrush.lua] ' .. asset.displayName .. ' is up to date')
print(' ' .. asset.displayName .. ' (up to date) #openbrush')
end
else
print('[openbrush.lua] error: no suitable format found for ' .. asset.displayName .. ' ' .. asset.assetId)

View file

@ -47,6 +47,8 @@ return {
if pid ~= nil then
unix.kill( pid, unix.SIGTERM)
end
print("gracefully stopping child-processes")
unix.wait()
unix.exit()
end)
end