improved default popup for mobile + openbrush/manyfold centralized refactor
This commit is contained in:
parent
86eaf1556f
commit
81b9d238a9
13 changed files with 521 additions and 188 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue