updated template(engine) + added janusweb propertly
This commit is contained in:
parent
6a14efeba6
commit
1efb6b0053
23 changed files with 1066 additions and 152 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
experienceA
|
experienceA
|
||||||
experienceB
|
experienceB
|
||||||
result
|
result
|
||||||
src/v
|
src/janusweb
|
||||||
janusweb*
|
janusweb*
|
||||||
myverse
|
myverse
|
||||||
|
|
|
||||||
100
src/.init.lua
100
src/.init.lua
|
|
@ -6,6 +6,8 @@ package.path = package.path .. ";plugin/?.lua"
|
||||||
|
|
||||||
util = require "util"
|
util = require "util"
|
||||||
json = require "json"
|
json = require "json"
|
||||||
|
liluat = require "liluat"
|
||||||
|
template = require "template"
|
||||||
|
|
||||||
-- special script called by main redbean process at startup
|
-- special script called by main redbean process at startup
|
||||||
HidePath('/usr/share/zoneinfo/')
|
HidePath('/usr/share/zoneinfo/')
|
||||||
|
|
@ -22,10 +24,16 @@ app = require("soakbean") {
|
||||||
cmd={
|
cmd={
|
||||||
help = {file="cmd/help.lua", info="display all possible flags"},
|
help = {file="cmd/help.lua", info="display all possible flags"},
|
||||||
server = {file="cmd/server.lua", info="starts the webserver"},
|
server = {file="cmd/server.lua", info="starts the webserver"},
|
||||||
|
admin = {file="cmd/server.lua", info="launch adminpanel to configure stuff (/admin)"},
|
||||||
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"},
|
generate = {file="cmd/generate.lua", info="generate www-folder with XR experiences"},
|
||||||
test = {file="cmd/test.lua", info="run testsuite (selftest)"}
|
test = {file="cmd/test.lua", info="run testsuite (selftest)"}
|
||||||
}
|
},
|
||||||
|
addPlugin = function(file)
|
||||||
|
local p = require(file)
|
||||||
|
table.insert(app.plugin,p)
|
||||||
|
return p
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
-- api skeleton (implemented via soakbean's app.on(...))
|
-- api skeleton (implemented via soakbean's app.on(...))
|
||||||
|
|
@ -35,16 +43,16 @@ app.init = function() end
|
||||||
-- load config
|
-- load config
|
||||||
app.opts = require('config')(app)
|
app.opts = require('config')(app)
|
||||||
|
|
||||||
-- load system plugins
|
-- load system plugins (in specific order for admin panel)
|
||||||
app.plugin.moderation = require('plugin/moderation')
|
app.plugin.diskscan = app.addPlugin('plugin/diskscan')
|
||||||
app.plugin.diskscan = require('plugin/diskscan')
|
app.plugin.moderation = app.addPlugin('plugin/moderation')
|
||||||
app.plugin.xrfragments = require('plugin/xrfragments')
|
app.plugin.xrfragments = app.addPlugin('plugin/xrfragments')
|
||||||
app.plugin.janusxr = require('plugin/janusxr')
|
app.plugin.janusxr = app.addPlugin('plugin/janusxr')
|
||||||
app.plugin.manyfold = require('plugin/manyfold')
|
app.plugin.manyfold = app.addPlugin('plugin/manyfold')
|
||||||
app.plugin.git = require('plugin/git')
|
app.plugin.git = app.addPlugin('plugin/git')
|
||||||
app.plugin.clouddrive = require('plugin/clouddrive')
|
app.plugin.clouddrive = app.addPlugin('plugin/clouddrive')
|
||||||
app.plugin.webhook = require('plugin/webhook')
|
app.plugin.webhook = app.addPlugin('plugin/webhook')
|
||||||
app.plugin.markdown = require('plugin/markdown')
|
app.plugin.markdown = app.addPlugin('plugin/markdown')
|
||||||
|
|
||||||
-- ensure plugin config-defaults
|
-- ensure plugin config-defaults
|
||||||
foreach( app.plugin, function(name,plugin)
|
foreach( app.plugin, function(name,plugin)
|
||||||
|
|
@ -56,13 +64,6 @@ foreach( app.plugin, function(name,plugin)
|
||||||
end)
|
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.xrfragments
|
|
||||||
app.plugin[5] = app.plugin.manyfold
|
|
||||||
|
|
||||||
app.runcmd(app) -- run clicmds
|
app.runcmd(app) -- run clicmds
|
||||||
|
|
||||||
print = function(...) Log(kLogInfo, ... or "''") end -- nice redbean logging from now on
|
print = function(...) Log(kLogInfo, ... or "''") end -- nice redbean logging from now on
|
||||||
|
|
@ -87,69 +88,8 @@ end)
|
||||||
|
|
||||||
app --
|
app --
|
||||||
.use( require("middleware/httpauth")() )
|
.use( require("middleware/httpauth")() )
|
||||||
.use( function(req,res,next)
|
|
||||||
local tile = require('page/partial/tile')
|
|
||||||
local rootpath = "/" .. app.opts.plugin_diskscan_dirname
|
|
||||||
local urlpath = GetPath()
|
|
||||||
local isdir = urlpath:sub(-1) == "/"
|
|
||||||
local nslashes = util.countChar('/',urlpath)
|
|
||||||
res.items = {}
|
|
||||||
local match = function(a,b) return util.stripPath(a) == util.stripPath(b) end
|
|
||||||
if urlpath:match("^"..rootpath) then
|
|
||||||
local redirect = false
|
|
||||||
if not app.cache.url[ urlpath ] then
|
|
||||||
foreach( app.cache.items, function(k,v)
|
|
||||||
if v.parent then
|
|
||||||
trace( "path = " .. util.stripPath(v.path) .. " " ..
|
|
||||||
"parent = " .. util.stripPath(v.parent.path) .. " " ..
|
|
||||||
"urlpath = " .. util.stripPath(urlpath)
|
|
||||||
)
|
|
||||||
if match(v.path, urlpath) then -- show parent folder if any
|
|
||||||
local parent = app.cache.items[ v.parent.id ]
|
|
||||||
if v.portal then
|
|
||||||
redirect = v.portal.url
|
|
||||||
end
|
|
||||||
if match(parent.path, app.opts.plugin_diskscan_dir) then -- add rootfolder if needed
|
|
||||||
local root = app.cache.items[ app.opts.plugin_diskscan_dir ]
|
|
||||||
res.items[ root.id ] = root
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- add items from dir
|
|
||||||
if match(v.parent.path, urlpath) then
|
|
||||||
local parent = app.cache.items[ v.parent.id ]
|
|
||||||
res.items[ v.id ] = v
|
|
||||||
--if parent.parent then
|
|
||||||
-- res.items[ v.id ] = v
|
|
||||||
--else
|
|
||||||
-- res.items[ parent.id ] = parent
|
|
||||||
--end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
local data = util.merge({items=''},{ opts = app.opts })
|
|
||||||
foreach( res.items, function(k,v)
|
|
||||||
data.items = data.items .. tile( util.flatten(v,"item") )
|
|
||||||
end)
|
|
||||||
app.cache.url[ urlpath ] = util.template( LoadAsset("page/dir.html"), data)
|
|
||||||
end
|
|
||||||
if redirect then
|
|
||||||
print("REDIRECT! " .. redirect )
|
|
||||||
res.status(303)
|
|
||||||
res.header('Location', redirect )
|
|
||||||
print_r(res)
|
|
||||||
next()
|
|
||||||
else
|
|
||||||
res.status(200)
|
|
||||||
res.header('content-type','text/html; charset=utf-8')
|
|
||||||
res.body( app.cache.url[ urlpath ] )
|
|
||||||
next()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
next()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
.use( require("json").middleware() ) -- try plug'n'play json API middleware
|
.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.router( app.url ) ) -- try url router
|
||||||
.use( app.response() ) -- try serve app response (if any)
|
.use( app.response() ) -- try serve app response (if any)
|
||||||
.use( function(req,next) Route() end) -- fallback default redbean fileserver
|
.use( function(req,next) Route() end) -- fallback default redbean fileserver
|
||||||
|
|
|
||||||
535
src/.lua/liluat.lua
Normal file
535
src/.lua/liluat.lua
Normal file
|
|
@ -0,0 +1,535 @@
|
||||||
|
--[[
|
||||||
|
-- liluat - Lightweight Lua Template engine
|
||||||
|
--
|
||||||
|
-- Project page: https://github.com/FSMaxB/liluat
|
||||||
|
--
|
||||||
|
-- liluat is based on slt2 by henix, see https://github.com/henix/slt2
|
||||||
|
--
|
||||||
|
-- Copyright © 2016 Max Bruckner
|
||||||
|
-- Copyright © 2011-2016 henix
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
-- of this software and associated documentation files (the "Software"), to deal
|
||||||
|
-- in the Software without restriction, including without limitation the rights
|
||||||
|
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
-- copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
-- to do so, subject to the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be included in
|
||||||
|
-- all copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
-- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
-- IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local liluat = {
|
||||||
|
private = {} --used to expose private functions for testing
|
||||||
|
}
|
||||||
|
|
||||||
|
-- print the current version
|
||||||
|
liluat.version = function ()
|
||||||
|
return "1.2.0"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- returns a string containing the fist line until the last line
|
||||||
|
local function string_lines(lines, first, last)
|
||||||
|
-- allow negative line numbers
|
||||||
|
first = (first >= 1) and first or 1
|
||||||
|
|
||||||
|
local start_position
|
||||||
|
local current_position = 1
|
||||||
|
local line_counter = 1
|
||||||
|
repeat
|
||||||
|
if line_counter == first then
|
||||||
|
start_position = current_position
|
||||||
|
end
|
||||||
|
current_position = lines:find('\n', current_position + 1, true)
|
||||||
|
line_counter = line_counter + 1
|
||||||
|
until (line_counter == (last + 1)) or (not current_position)
|
||||||
|
|
||||||
|
return lines:sub(start_position, current_position)
|
||||||
|
end
|
||||||
|
liluat.private.string_lines = string_lines
|
||||||
|
|
||||||
|
-- escape a string for use in lua patterns
|
||||||
|
-- (this simply prepends all non alphanumeric characters with '%'
|
||||||
|
local function escape_pattern(text)
|
||||||
|
return text:gsub("([^%w])", "%%%1" --[[function (match) return "%"..match end--]])
|
||||||
|
end
|
||||||
|
liluat.private.escape_pattern = escape_pattern
|
||||||
|
|
||||||
|
-- recursively copy a table
|
||||||
|
local function clone_table(table)
|
||||||
|
local clone = {}
|
||||||
|
|
||||||
|
for key, value in pairs(table) do
|
||||||
|
if type(value) == "table" then
|
||||||
|
clone[key] = clone_table(value)
|
||||||
|
else
|
||||||
|
clone[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return clone
|
||||||
|
end
|
||||||
|
liluat.private.clone_table = clone_table
|
||||||
|
|
||||||
|
-- recursively merge two tables, the second one has precedence
|
||||||
|
-- if 'shallow' is set, the second table isn't copied recursively,
|
||||||
|
-- its content is only referenced instead
|
||||||
|
local function merge_tables(a, b, shallow)
|
||||||
|
a = a or {}
|
||||||
|
b = b or {}
|
||||||
|
|
||||||
|
local merged = clone_table(a)
|
||||||
|
|
||||||
|
for key, value in pairs(b) do
|
||||||
|
if (type(value) == "table") and (not shallow) then
|
||||||
|
if a[key] then
|
||||||
|
merged[key] = merge_tables(a[key], value)
|
||||||
|
else
|
||||||
|
merged[key] = clone_table(value)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
merged[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return merged
|
||||||
|
end
|
||||||
|
liluat.private.merge_tables = merge_tables
|
||||||
|
|
||||||
|
local default_options = {
|
||||||
|
start_tag = "{{",
|
||||||
|
end_tag = "}}",
|
||||||
|
trim_right = "code",
|
||||||
|
trim_left = "code"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- initialise table of options (use the provided, default otherwise)
|
||||||
|
local function initialise_options(options)
|
||||||
|
return merge_tables(default_options, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- creates an iterator that iterates over all chunks in the given template
|
||||||
|
-- a chunk is either a template delimited by start_tag and end_tag or a normal text
|
||||||
|
-- the iterator also returns the type of the chunk as second return value
|
||||||
|
local function all_chunks(template, options)
|
||||||
|
options = initialise_options(options)
|
||||||
|
|
||||||
|
-- pattern to match a template chunk
|
||||||
|
local template_pattern = escape_pattern(options.start_tag) .. "([+-]?)(.-)([+-]?)" .. escape_pattern(options.end_tag)
|
||||||
|
local include_pattern = "^"..escape_pattern(options.start_tag) .. "[+-]?include:(.-)[+-]?" .. escape_pattern(options.end_tag)
|
||||||
|
local expression_pattern = "^"..escape_pattern(options.start_tag) .. "[+-]?=(.-)[+-]?" .. escape_pattern(options.end_tag)
|
||||||
|
local position = 1
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
if not position then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local template_start, template_end, trim_left, template_capture, trim_right = template:find(template_pattern, position)
|
||||||
|
|
||||||
|
local chunk = {}
|
||||||
|
if template_start == position then -- next chunk is a template chunk
|
||||||
|
if trim_left == "+" then
|
||||||
|
chunk.trim_left = false
|
||||||
|
elseif trim_left == "-" then
|
||||||
|
chunk.trim_left = true
|
||||||
|
end
|
||||||
|
if trim_right == "+" then
|
||||||
|
chunk.trim_right = false
|
||||||
|
elseif trim_right == "-" then
|
||||||
|
chunk.trim_right = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local include_start, include_end, include_capture = template:find(include_pattern, position)
|
||||||
|
local expression_start, expression_end, expression_capture
|
||||||
|
if not include_start then
|
||||||
|
expression_start, expression_end, expression_capture = template:find(expression_pattern, position)
|
||||||
|
end
|
||||||
|
|
||||||
|
if include_start then
|
||||||
|
chunk.type = "include"
|
||||||
|
chunk.text = include_capture
|
||||||
|
elseif expression_start then
|
||||||
|
chunk.type = "expression"
|
||||||
|
chunk.text = expression_capture
|
||||||
|
else
|
||||||
|
chunk.type = "code"
|
||||||
|
chunk.text = template_capture
|
||||||
|
end
|
||||||
|
|
||||||
|
position = template_end + 1
|
||||||
|
return chunk
|
||||||
|
elseif template_start then -- next chunk is a text chunk
|
||||||
|
chunk.type = "text"
|
||||||
|
chunk.text = template:sub(position, template_start - 1)
|
||||||
|
position = template_start
|
||||||
|
return chunk
|
||||||
|
else -- no template chunk found --> either text chunk until end of file or no chunk at all
|
||||||
|
chunk.text = template:sub(position)
|
||||||
|
chunk.type = "text"
|
||||||
|
position = nil
|
||||||
|
return (#chunk.text > 0) and chunk or nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
liluat.private.all_chunks = all_chunks
|
||||||
|
|
||||||
|
local function read_entire_file(path)
|
||||||
|
assert(path)
|
||||||
|
local file = assert(io.open(path))
|
||||||
|
local file_content = file:read('*a')
|
||||||
|
file:close()
|
||||||
|
return file_content
|
||||||
|
end
|
||||||
|
liluat.private.read_entire_file = read_entire_file
|
||||||
|
|
||||||
|
-- a whitelist of allowed functions
|
||||||
|
local sandbox_whitelist = {
|
||||||
|
ipairs = ipairs,
|
||||||
|
next = next,
|
||||||
|
pairs = pairs,
|
||||||
|
rawequal = rawequal,
|
||||||
|
rawget = rawget,
|
||||||
|
rawset = rawset,
|
||||||
|
select = select,
|
||||||
|
tonumber = tonumber,
|
||||||
|
tostring = tostring,
|
||||||
|
type = type,
|
||||||
|
unpack = unpack,
|
||||||
|
string = string,
|
||||||
|
table = table,
|
||||||
|
math = math,
|
||||||
|
os = {
|
||||||
|
date = os.date,
|
||||||
|
difftime = os.difftime,
|
||||||
|
time = os.time,
|
||||||
|
},
|
||||||
|
coroutine = coroutine
|
||||||
|
}
|
||||||
|
|
||||||
|
-- puts line numbers in front of a string and optionally highlights a single line
|
||||||
|
local function prepend_line_numbers(lines, first, highlight)
|
||||||
|
first = (first and (first >= 1)) and first or 1
|
||||||
|
lines = lines:gsub("\n$", "") -- make sure the last line isn't empty
|
||||||
|
lines = lines:gsub("^\n", "") -- make sure the first line isn't empty
|
||||||
|
|
||||||
|
local current_line = first + 1
|
||||||
|
return string.format("%3d: ", first) .. lines:gsub('\n', function ()
|
||||||
|
local highlight_char = ' '
|
||||||
|
if current_line == tonumber(highlight) then
|
||||||
|
highlight_char = '> '
|
||||||
|
end
|
||||||
|
|
||||||
|
local replacement = string.format("\n%3d:%s", current_line, highlight_char)
|
||||||
|
current_line = current_line + 1
|
||||||
|
|
||||||
|
return replacement
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
liluat.private.prepend_line_numbers = prepend_line_numbers
|
||||||
|
|
||||||
|
-- creates a function in a sandbox from a given code,
|
||||||
|
-- name of the execution context and an environment
|
||||||
|
-- that will be available inside the sandbox,
|
||||||
|
-- optionally overwrite the whitelist
|
||||||
|
local function sandbox(code, name, environment, whitelist, reference)
|
||||||
|
whitelist = whitelist or sandbox_whitelist
|
||||||
|
name = name or 'unknown'
|
||||||
|
|
||||||
|
-- prepare the environment
|
||||||
|
environment = merge_tables(whitelist, environment, reference)
|
||||||
|
|
||||||
|
local func
|
||||||
|
local error_message
|
||||||
|
if setfenv then --Lua 5.1 and compatible
|
||||||
|
if code:byte(1) == 27 then
|
||||||
|
error("Lua bytecode not permitted.", 2)
|
||||||
|
end
|
||||||
|
func, error_message = loadstring(code)
|
||||||
|
if func then
|
||||||
|
setfenv(func, environment)
|
||||||
|
end
|
||||||
|
else -- Lua 5.2 and later
|
||||||
|
func, error_message = load(code, name, 't', environment)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle compile error and print pretty error message
|
||||||
|
if not func then
|
||||||
|
local line_number, message = error_message:match(":(%d+):(.*)")
|
||||||
|
-- lines before and after the error
|
||||||
|
local lines = string_lines(code, line_number - 3, line_number + 3)
|
||||||
|
error(
|
||||||
|
'Syntax error in sandboxed code "' .. name .. '" in line ' .. line_number .. ':\n'
|
||||||
|
.. message .. '\n\n'
|
||||||
|
.. prepend_line_numbers(lines, line_number - 3, line_number),
|
||||||
|
3
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return func
|
||||||
|
end
|
||||||
|
liluat.private.sandbox = sandbox
|
||||||
|
|
||||||
|
local function parse_string_literal(string_literal)
|
||||||
|
return sandbox('return' .. string_literal, nil, nil, {})()
|
||||||
|
end
|
||||||
|
liluat.private.parse_string_literal = parse_string_literal
|
||||||
|
|
||||||
|
-- add an include to the include_list and throw an error if
|
||||||
|
-- an inclusion cycle is detected
|
||||||
|
local function add_include_and_detect_cycles(include_list, path)
|
||||||
|
local parent = include_list[0]
|
||||||
|
while parent do -- while the root hasn't been reached
|
||||||
|
if parent[path] then
|
||||||
|
error("Cyclic inclusion detected")
|
||||||
|
end
|
||||||
|
|
||||||
|
parent = parent[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
include_list[path] = {
|
||||||
|
[0] = include_list
|
||||||
|
}
|
||||||
|
end
|
||||||
|
liluat.private.add_include_and_detect_cycles = add_include_and_detect_cycles
|
||||||
|
|
||||||
|
-- extract the name of a directory from a path
|
||||||
|
local function dirname(path)
|
||||||
|
return path:match("^(.*/).-$") or ""
|
||||||
|
end
|
||||||
|
liluat.private.dirname = dirname
|
||||||
|
|
||||||
|
-- splits a template into chunks
|
||||||
|
-- chunks are either a template delimited by start_tag and end_tag
|
||||||
|
-- or a text chunk (everything else)
|
||||||
|
-- @return table
|
||||||
|
local function parse(template, options, output, include_list, current_path)
|
||||||
|
options = initialise_options(options)
|
||||||
|
current_path = current_path or "." -- current include path
|
||||||
|
|
||||||
|
include_list = include_list or {} -- a list of files that were included
|
||||||
|
local output = output or {}
|
||||||
|
|
||||||
|
for chunk in all_chunks(template, options) do
|
||||||
|
-- handle includes
|
||||||
|
if chunk.type == "include" then -- include chunk
|
||||||
|
local include_path_literal = chunk.text
|
||||||
|
local path = parse_string_literal(include_path_literal)
|
||||||
|
|
||||||
|
-- build complete path
|
||||||
|
if path:find("^/") then
|
||||||
|
--absolute path, don't modify
|
||||||
|
elseif options.base_path then
|
||||||
|
path = options.base_path .. "/" .. path
|
||||||
|
else
|
||||||
|
path = dirname(current_path) .. path
|
||||||
|
end
|
||||||
|
|
||||||
|
add_include_and_detect_cycles(include_list, path)
|
||||||
|
|
||||||
|
local included_template = read_entire_file(path)
|
||||||
|
parse(included_template, options, output, include_list[path], path)
|
||||||
|
elseif (chunk.type == "text") and output[#output] and (output[#output].type == "text") then
|
||||||
|
-- ensure that no two text chunks follow each other
|
||||||
|
output[#output].text = output[#output].text .. chunk.text
|
||||||
|
else -- other chunk
|
||||||
|
table.insert(output, chunk)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
liluat.private.parse = parse
|
||||||
|
|
||||||
|
-- inline included template files
|
||||||
|
-- @return string
|
||||||
|
function liluat.inline(template, options, start_path)
|
||||||
|
options = initialise_options(options)
|
||||||
|
|
||||||
|
local output = {}
|
||||||
|
for _,chunk in ipairs(parse(template, options, nil, nil, start_path)) do
|
||||||
|
if chunk.type == "expression" then
|
||||||
|
table.insert(output, options.start_tag .. "=" .. chunk.text .. options.end_tag)
|
||||||
|
elseif chunk.type == "code" then
|
||||||
|
table.insert(output, options.start_tag .. chunk.text .. options.end_tag)
|
||||||
|
else
|
||||||
|
table.insert(output, chunk.text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(output)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- @return { string }
|
||||||
|
function liluat.get_dependencies(template, options, start_path)
|
||||||
|
options = initialise_options(options)
|
||||||
|
|
||||||
|
local include_list = {}
|
||||||
|
parse(template, options, nil, include_list, start_path)
|
||||||
|
|
||||||
|
local dependencies = {}
|
||||||
|
local have_seen = {} -- list of includes that were already added
|
||||||
|
local function recursive_traversal(list)
|
||||||
|
for key, value in pairs(list) do
|
||||||
|
if (type(key) == "string") and (not have_seen[key]) then
|
||||||
|
have_seen[key] = true
|
||||||
|
table.insert(dependencies, key)
|
||||||
|
recursive_traversal(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
recursive_traversal(include_list)
|
||||||
|
return dependencies
|
||||||
|
end
|
||||||
|
|
||||||
|
-- compile a template into lua code
|
||||||
|
-- @return { name = string, code = string / function}
|
||||||
|
function liluat.compile(template, options, template_name, start_path)
|
||||||
|
options = initialise_options(options)
|
||||||
|
template_name = template_name or 'liluat.compile'
|
||||||
|
|
||||||
|
local output_function = "__liluat_output_function"
|
||||||
|
|
||||||
|
-- split the template string into chunks
|
||||||
|
local lexed_template = parse(template, options, nil, nil, start_path)
|
||||||
|
|
||||||
|
-- table of code fragments the template is compiled into
|
||||||
|
local lua_code = {}
|
||||||
|
|
||||||
|
for i, chunk in ipairs(lexed_template) do
|
||||||
|
-- check if the chunk is a template (either code or expression)
|
||||||
|
if chunk.type == "expression" then
|
||||||
|
table.insert(lua_code, output_function..'('..chunk.text..')')
|
||||||
|
elseif chunk.type == "code" then
|
||||||
|
table.insert(lua_code, chunk.text)
|
||||||
|
else --text chunk
|
||||||
|
-- determine if this block needs to be trimmed right
|
||||||
|
-- (strip newline)
|
||||||
|
local trim_right = false
|
||||||
|
if lexed_template[i - 1] and (lexed_template[i - 1].trim_right == true) then
|
||||||
|
trim_right = true
|
||||||
|
elseif lexed_template[i - 1] and (lexed_template[i - 1].trim_right == false) then
|
||||||
|
trim_right = false
|
||||||
|
elseif options.trim_right == "all" then
|
||||||
|
trim_right = true
|
||||||
|
elseif options.trim_right == "code" then
|
||||||
|
trim_right = lexed_template[i - 1] and (lexed_template[i - 1].type == "code")
|
||||||
|
elseif options.trim_right == "expression" then
|
||||||
|
trim_right = lexed_template[i - 1] and (lexed_template[i - 1].type == "expression")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- determine if this block needs to be trimmed left
|
||||||
|
-- (strip whitespaces in front)
|
||||||
|
local trim_left = false
|
||||||
|
if lexed_template[i + 1] and (lexed_template[i + 1].trim_left == true) then
|
||||||
|
trim_left = true
|
||||||
|
elseif lexed_template[i + 1] and (lexed_template[i + 1].trim_left == false) then
|
||||||
|
trim_left = false
|
||||||
|
elseif options.trim_left == "all" then
|
||||||
|
trim_left = true
|
||||||
|
elseif options.trim_left == "code" then
|
||||||
|
trim_left = lexed_template[i + 1] and (lexed_template[i + 1].type == "code")
|
||||||
|
elseif options.trim_left == "expression" then
|
||||||
|
trim_left = lexed_template[i + 1] and (lexed_template[i + 1].type == "expression")
|
||||||
|
end
|
||||||
|
|
||||||
|
if trim_right and trim_left then
|
||||||
|
-- both at once
|
||||||
|
if i == 1 then
|
||||||
|
if chunk.text:find("^.*\n") then
|
||||||
|
chunk.text = chunk.text:match("^(.*\n)%s-$")
|
||||||
|
elseif chunk.text:find("^%s-$") then
|
||||||
|
chunk.text = ""
|
||||||
|
end
|
||||||
|
elseif chunk.text:find("^\n") then --have to trim a newline
|
||||||
|
if chunk.text:find("^\n.*\n") then --at least two newlines
|
||||||
|
chunk.text = chunk.text:match("^\n(.*\n)%s-$") or chunk.text:match("^\n(.*)$")
|
||||||
|
elseif chunk.text:find("^\n%s-$") then
|
||||||
|
chunk.text = ""
|
||||||
|
else
|
||||||
|
chunk.text = chunk.text:gsub("^\n", "")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
chunk.text = chunk.text:match("^(.*\n)%s-$") or chunk.text
|
||||||
|
end
|
||||||
|
elseif trim_left then
|
||||||
|
if i == 1 and chunk.text:find("^%s-$") then
|
||||||
|
chunk.text = ""
|
||||||
|
else
|
||||||
|
chunk.text = chunk.text:match("^(.*\n)%s-$") or chunk.text
|
||||||
|
end
|
||||||
|
elseif trim_right then
|
||||||
|
chunk.text = chunk.text:gsub("^\n", "")
|
||||||
|
end
|
||||||
|
if not (chunk.text == "") then
|
||||||
|
table.insert(lua_code, output_function..'('..string.format("%q", chunk.text)..')')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
name = template_name,
|
||||||
|
code = table.concat(lua_code, '\n')
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- compile a file
|
||||||
|
-- @return { name = string, code = string / function }
|
||||||
|
function liluat.compile_file(filename, options)
|
||||||
|
return liluat.compile(read_entire_file(filename), options, filename, filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- @return a coroutine function
|
||||||
|
function liluat.render_coroutine(template, environment, options)
|
||||||
|
options = initialise_options(options)
|
||||||
|
environment = merge_tables({__liluat_output_function = coroutine.yield}, environment, options.reference)
|
||||||
|
|
||||||
|
return sandbox(template.code, template.name, environment, nil, options.reference)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- @return string
|
||||||
|
function liluat.render(t, env, options)
|
||||||
|
options = initialise_options(options)
|
||||||
|
|
||||||
|
local result = {}
|
||||||
|
|
||||||
|
-- add closure that renders the text into the result table
|
||||||
|
env = merge_tables({
|
||||||
|
__liluat_output_function = function (text)
|
||||||
|
table.insert(result, text) end
|
||||||
|
},
|
||||||
|
env,
|
||||||
|
options.reference
|
||||||
|
)
|
||||||
|
|
||||||
|
-- compile and run the lua code
|
||||||
|
local render_function = sandbox(t.code, t.name, env, nil, options.reference)
|
||||||
|
local status, error_message = pcall(render_function)
|
||||||
|
if not status then
|
||||||
|
local line_number, message = error_message:match(":(%d+):(.*)")
|
||||||
|
message = message or ''
|
||||||
|
line_number = line_number or 0
|
||||||
|
-- lines before and after the error
|
||||||
|
local lines = string_lines(t.code, line_number - 3, line_number + 3)
|
||||||
|
error(
|
||||||
|
'Runtime error in sandboxed code "' .. t.name .. '" in line ' .. line_number .. ':\n'
|
||||||
|
.. message .. '\n\n'
|
||||||
|
.. prepend_line_numbers(lines, line_number - 3, line_number),
|
||||||
|
2
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
return liluat
|
||||||
|
|
||||||
|
|
@ -22,8 +22,9 @@ sb = {
|
||||||
if type(v) == "function" then
|
if type(v) == "function" then
|
||||||
sb.data[k] = (function(k,v)
|
sb.data[k] = (function(k,v)
|
||||||
return function(a,b,c,d,e,f)
|
return function(a,b,c,d,e,f)
|
||||||
v(a,b,c,d,e,f)
|
local ret = v(a,b,c,d,e,f)
|
||||||
sb.pub(k,v,a,b,c,d,e,f)
|
sb.pub(k,v,a,b,c,d,e,f)
|
||||||
|
return ret
|
||||||
end
|
end
|
||||||
end)(k,v)
|
end)(k,v)
|
||||||
else
|
else
|
||||||
|
|
@ -52,9 +53,64 @@ sb = {
|
||||||
|
|
||||||
-- print( util.tpl("${name} is ${value}", {name = "foo", value = "bar"}) )
|
-- print( util.tpl("${name} is ${value}", {name = "foo", value = "bar"}) )
|
||||||
-- "foo is bar"
|
-- "foo is bar"
|
||||||
tpl = function(s,tab)
|
-- print( util.tpl("${foo(1)}", { foo = function(i) return i+1 end}) )
|
||||||
if not s then return "" end
|
-- 2
|
||||||
return (s:gsub('($%b{})', function(w) return tab[w:sub(3, -2)] or w end))
|
-- print( util.tpl("${if foo then} bar ${end}", { foo = true }) )
|
||||||
|
-- bar
|
||||||
|
tpl = function(s, tab)
|
||||||
|
tab = tab or sb.data
|
||||||
|
return (s:gsub('($%b{})', function(w)
|
||||||
|
local inner = w:sub(3, -2):match("^%s*(.-)%s*$")
|
||||||
|
-- Execute as raw Lua code if it contains spaces (e.g. "if bar then") or equals "end"
|
||||||
|
if inner:find("%s") or inner == "end" then
|
||||||
|
local fn = (loadstring or load)("return function(tab) " .. inner .. " end")
|
||||||
|
if fn then
|
||||||
|
setfenv(fn(), setmetatable(tab, { __index = _G }))()
|
||||||
|
end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local k, arg = w:match("^%${%s*([%w_]+)%s*%(?(.-)%)?%s*}")
|
||||||
|
local v = tab[k]
|
||||||
|
if type(v) == "function" then
|
||||||
|
return tostring(v(load("return " .. arg)()) or '')
|
||||||
|
end
|
||||||
|
return v ~= nil and tostring(v) or w
|
||||||
|
end))
|
||||||
|
end,
|
||||||
|
tpl = function(s, tab)
|
||||||
|
tab = tab or sb.data
|
||||||
|
return (s:gsub('($%b{})', function(w)
|
||||||
|
local inner = w:sub(3, -2):match("^%s*(.-)%s*$")
|
||||||
|
-- Execute as raw Lua code if it contains spaces or equals "end"
|
||||||
|
if inner:find("%s") or inner == "end" then
|
||||||
|
local fn = (loadstring or load)("return function(tab) " .. inner .. " end")
|
||||||
|
if fn then
|
||||||
|
setfenv(fn(), setmetatable(tab, { __index = _G }))()
|
||||||
|
end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
-- Allow dots in variable names along with alphanumeric characters and underscores
|
||||||
|
local path, arg = w:match("^%${%s*([%w_%.]+)" .. "%s*%(?(.-)%)?%s*}")
|
||||||
|
-- Traverse the nested table path (e.g. "foo.bar" -> tab["foo"]["bar"])
|
||||||
|
local v = tab
|
||||||
|
if path then
|
||||||
|
for key in path:gmatch("[%w_]+") do
|
||||||
|
if type(v) == "table" then
|
||||||
|
v = v[key]
|
||||||
|
else
|
||||||
|
v = nil
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
v = nil
|
||||||
|
end
|
||||||
|
if type(v) == "function" then
|
||||||
|
return tostring(v(load("return " .. arg)()) or '')
|
||||||
|
end
|
||||||
|
return v ~= nil and tostring(v) or w
|
||||||
|
end))
|
||||||
end,
|
end,
|
||||||
|
|
||||||
write = function(str)
|
write = function(str)
|
||||||
|
|
|
||||||
130
src/.lua/template.lua
Normal file
130
src/.lua/template.lua
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
-- local compile = require "tmpl"
|
||||||
|
--
|
||||||
|
-- local s = [[
|
||||||
|
-- <!DOCTYPE html>
|
||||||
|
-- <html>
|
||||||
|
-- <head>
|
||||||
|
-- {! this is a comment !}
|
||||||
|
-- <meta charset="utf-8">
|
||||||
|
-- <title>{{page.title}}</title>
|
||||||
|
-- <style>
|
||||||
|
-- body {
|
||||||
|
-- background-color: #{{page.bg}};
|
||||||
|
-- }
|
||||||
|
-- </style>
|
||||||
|
-- </head>
|
||||||
|
-- <body>
|
||||||
|
-- <h3>{{escaped}}</h3>
|
||||||
|
-- <h3>{* unescaped *}
|
||||||
|
-- <h3>{% echo 'haHAA' %}</h3>
|
||||||
|
-- {[template2]}
|
||||||
|
-- </body>
|
||||||
|
-- </html>
|
||||||
|
-- ]]
|
||||||
|
--
|
||||||
|
-- local s2 = [[
|
||||||
|
-- <ul>
|
||||||
|
-- {% for i = 1, 3 do %}
|
||||||
|
-- <li>{{i}}{{unit}}</li>
|
||||||
|
-- {% end %}
|
||||||
|
-- </ul>
|
||||||
|
-- {% if haHAA then %}
|
||||||
|
-- {[template2, sub]}
|
||||||
|
-- {% end %}
|
||||||
|
-- ]]
|
||||||
|
--
|
||||||
|
-- local template = compile(s)
|
||||||
|
-- local ctx = {
|
||||||
|
-- page = { title = 'Demo', bg = '332233' },
|
||||||
|
-- escaped = 'escaped: < &',
|
||||||
|
-- unescaped = 'unescaped: •</h3>',
|
||||||
|
-- template2 = compile(s2),
|
||||||
|
-- haHAA = true,
|
||||||
|
-- unit = 'g',
|
||||||
|
-- sub = {
|
||||||
|
-- -- custom escape function
|
||||||
|
-- escape = function(s)
|
||||||
|
-- i = tonumber(s)
|
||||||
|
-- if i then return i * 3 else return s end
|
||||||
|
-- end,
|
||||||
|
-- haHAA = false,
|
||||||
|
-- unit = 'kg'
|
||||||
|
-- }
|
||||||
|
-- }
|
||||||
|
--
|
||||||
|
-- for s in template(ctx) do
|
||||||
|
-- io.write(s)
|
||||||
|
-- end
|
||||||
|
return function(str)
|
||||||
|
local t = {[[
|
||||||
|
local ctx = ...
|
||||||
|
local _out = {}
|
||||||
|
local _ENV = setmetatable(ctx, { __index = _ENV })
|
||||||
|
|
||||||
|
local function echo(s)
|
||||||
|
if s and s ~= '' then
|
||||||
|
table.insert(_out, s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function include(template, sub_ctx)
|
||||||
|
if sub_ctx then
|
||||||
|
sub_ctx.escape = sub_ctx.escape or _ENV.escape
|
||||||
|
else
|
||||||
|
sub_ctx = _ENV
|
||||||
|
end
|
||||||
|
table.insert(_out, template(sub_ctx))
|
||||||
|
end
|
||||||
|
|
||||||
|
_ENV.escape = escape or function(i)
|
||||||
|
return tostring(i or ''):gsub('[&<>"\'/]', {
|
||||||
|
['&'] = '&',
|
||||||
|
['<'] = '<',
|
||||||
|
['>'] = '>',
|
||||||
|
['"'] = '"',
|
||||||
|
["'"] = ''',
|
||||||
|
['/'] = '/'
|
||||||
|
})
|
||||||
|
end
|
||||||
|
]]}
|
||||||
|
|
||||||
|
local function f(pos)
|
||||||
|
local start, stop, c = pos - 1
|
||||||
|
repeat
|
||||||
|
start, stop = str:find('%b{}', start + 1)
|
||||||
|
if not start then
|
||||||
|
table.insert(t, ' echo([=====[\n')
|
||||||
|
table.insert(t, str:sub(pos, #str))
|
||||||
|
table.insert(t, ']=====]) ')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
c = str:sub(start + 1, start + 1)
|
||||||
|
until c:match('[{%*%[%%!]')
|
||||||
|
|
||||||
|
table.insert(t, ' echo([=====[\n')
|
||||||
|
table.insert(t, str:sub(pos, start - 1))
|
||||||
|
table.insert(t, ']=====]) ')
|
||||||
|
|
||||||
|
if c == '{' then
|
||||||
|
table.insert(t, ' echo(escape(')
|
||||||
|
table.insert(t, str:sub(start + 2, stop - 2))
|
||||||
|
table.insert(t, ')) ')
|
||||||
|
elseif c == '*' then
|
||||||
|
table.insert(t, ' echo(')
|
||||||
|
table.insert(t, str:sub(start + 2, stop - 2))
|
||||||
|
table.insert(t, ') ')
|
||||||
|
elseif c == '[' then
|
||||||
|
table.insert(t, ' include(')
|
||||||
|
table.insert(t, str:sub(start + 2, stop - 2))
|
||||||
|
table.insert(t, ') ')
|
||||||
|
elseif c == '%' then
|
||||||
|
table.insert(t, str:sub(start + 2, stop - 2))
|
||||||
|
end
|
||||||
|
f(stop + 1)
|
||||||
|
end
|
||||||
|
f(1)
|
||||||
|
|
||||||
|
-- Return the concatenated string directly from the compiled function
|
||||||
|
table.insert(t, [[ return table.concat(_out) ]])
|
||||||
|
return load(table.concat(t))
|
||||||
|
end
|
||||||
|
|
@ -59,7 +59,7 @@ util.flatten = function(tbl, prefix, result)
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
util.pagerender = function(file,data)
|
util.pagerender = function(file,data,serve)
|
||||||
return function(req,res,next)
|
return function(req,res,next)
|
||||||
data = data or {opts = app.opts}
|
data = data or {opts = app.opts}
|
||||||
data.page = file:gsub(".*","")
|
data.page = file:gsub(".*","")
|
||||||
|
|
@ -70,12 +70,26 @@ util.pagerender = function(file,data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
util.templateFile = function(file,data)
|
||||||
|
trace('templateFile(' .. file .. ')')
|
||||||
|
return util.template( LoadAsset(file), data)
|
||||||
|
end
|
||||||
|
|
||||||
|
util.getURL = function()
|
||||||
|
local port = ':' .. GetPort()
|
||||||
|
if port == 80 then port = '' end
|
||||||
|
return GetScheme() .. '://' .. GetHost() .. port .. GetPath()
|
||||||
|
end
|
||||||
|
|
||||||
util.template = function(content,data)
|
util.template = function(content,data)
|
||||||
local mdata = util.merge(data or app,{})
|
local mdata = util.merge(data or app,{})
|
||||||
mdata = util.merge(mdata, util.flatten(app.opts,"opts") )
|
mdata = util.merge(mdata, {opts = app.opts})
|
||||||
mdata.header_include = mdata.header_include or ''
|
mdata.header_include = mdata.header_include or ''
|
||||||
mdata.header = app.tpl( LoadAsset('page/header.html'), mdata )
|
mdata.header = app.tpl( LoadAsset('page/header.html'), mdata )
|
||||||
mdata.footer = app.tpl( LoadAsset('page/footer.html'), mdata )
|
mdata.footer = app.tpl( LoadAsset('page/footer.html'), mdata )
|
||||||
|
-- set view url
|
||||||
|
mdata.url = mdata.url or util.getURL()
|
||||||
|
print_r(mdata)
|
||||||
return app.tpl( content, mdata )
|
return app.tpl( content, mdata )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -240,6 +254,28 @@ function util.split(inputstr, sep)
|
||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function util.pluck(path, tbl, default)
|
||||||
|
default = default or ''
|
||||||
|
if type(tbl) ~= "table" or type(path) ~= "string" then
|
||||||
|
return default
|
||||||
|
end
|
||||||
|
|
||||||
|
local current = tbl
|
||||||
|
-- Match non-dot segments in the path key string
|
||||||
|
for key in string.gmatch(path, "[^.]+") do
|
||||||
|
if type(current) ~= "table" then
|
||||||
|
return default
|
||||||
|
end
|
||||||
|
current = current[key]
|
||||||
|
if current == nil then
|
||||||
|
return default
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return current
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function util.stripPath(str)
|
function util.stripPath(str)
|
||||||
return str:gsub("^./",""):gsub("^/",""):gsub("/$","")
|
return str:gsub("^./",""):gsub("^/",""):gsub("/$","")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
return function(app,argv)
|
return function(app,argv)
|
||||||
print("starting server")
|
print("starting server")
|
||||||
|
if arg[1] == 'admin' then
|
||||||
|
LaunchBrowser('/admin')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -201,14 +201,14 @@ ul{
|
||||||
|
|
||||||
img.icon{ display:none; }
|
img.icon{ display:none; }
|
||||||
|
|
||||||
/* tab */
|
/* vertical tab */
|
||||||
|
|
||||||
.tab{
|
.vtab{
|
||||||
border-bottom: 1px solid #224;
|
border-bottom: 1px solid #224;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
margin-left: 140px;
|
margin-left: 140px;
|
||||||
}
|
}
|
||||||
.tab .label{
|
.vtab .label{
|
||||||
display: block;
|
display: block;
|
||||||
background: #000;
|
background: #000;
|
||||||
margin-bottom: -2px;
|
margin-bottom: -2px;
|
||||||
|
|
@ -239,6 +239,11 @@ img.icon{ display:none; }
|
||||||
filter: drop-shadow(1px 1px 5px #0FF9) drop-shadow(-1px -1px 5px #F079);
|
filter: drop-shadow(1px 1px 5px #0FF9) drop-shadow(-1px -1px 5px #F079);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info{
|
||||||
|
background:#224;
|
||||||
|
padding:10px;
|
||||||
|
}
|
||||||
|
|
||||||
b.tag{
|
b.tag{
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
background: #77f;
|
background: #77f;
|
||||||
|
|
|
||||||
|
|
@ -171,4 +171,70 @@ u span{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* css tabs */
|
||||||
|
|
||||||
|
.vtab { display:none } /* hide (fallback) vertical tabs */
|
||||||
|
|
||||||
|
div.tab-frame > input{ display:none;}
|
||||||
|
div.tab-frame > label{
|
||||||
|
display:inline-block;
|
||||||
|
padding:5px 10px;
|
||||||
|
cursor:pointer;
|
||||||
|
background:#112;
|
||||||
|
border-bottom:1px solid #447;
|
||||||
|
padding-right:10px;
|
||||||
|
color:#AAD;
|
||||||
|
margin-bottom:20px;
|
||||||
|
}
|
||||||
|
div.tab-frame > label b.tag{
|
||||||
|
color:#888;
|
||||||
|
background:#000;
|
||||||
|
}
|
||||||
|
div.tab-frame > input:checked + label{
|
||||||
|
border-bottom:4px solid #A8F;
|
||||||
|
cursor:default;
|
||||||
|
background:#224;
|
||||||
|
color:#FFF;
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
div.tab-frame div.tab{
|
||||||
|
display:none;
|
||||||
|
clear:left
|
||||||
|
}
|
||||||
|
|
||||||
|
div.tab-frame > input:nth-of-type(1):checked ~ .tab:nth-of-type(1),
|
||||||
|
div.tab-frame > input:nth-of-type(2):checked ~ .tab:nth-of-type(2),
|
||||||
|
div.tab-frame > input:nth-of-type(3):checked ~ .tab:nth-of-type(3),
|
||||||
|
div.tab-frame > input:nth-of-type(4):checked ~ .tab:nth-of-type(4),
|
||||||
|
div.tab-frame > input:nth-of-type(5):checked ~ .tab:nth-of-type(5),
|
||||||
|
div.tab-frame > input:nth-of-type(6):checked ~ .tab:nth-of-type(6),
|
||||||
|
div.tab-frame > input:nth-of-type(7):checked ~ .tab:nth-of-type(7),
|
||||||
|
div.tab-frame > input:nth-of-type(8):checked ~ .tab:nth-of-type(8),
|
||||||
|
div.tab-frame > input:nth-of-type(9):checked ~ .tab:nth-of-type(9),
|
||||||
|
div.tab-frame > input:nth-of-type(10):checked ~ .tab:nth-of-type(10),
|
||||||
|
div.tab-frame > input:nth-of-type(11):checked ~ .tab:nth-of-type(11),
|
||||||
|
div.tab-frame > input:nth-of-type(12):checked ~ .tab:nth-of-type(12),
|
||||||
|
div.tab-frame > input:nth-of-type(13):checked ~ .tab:nth-of-type(13),
|
||||||
|
div.tab-frame > input:nth-of-type(14):checked ~ .tab:nth-of-type(14),
|
||||||
|
div.tab-frame > input:nth-of-type(15):checked ~ .tab:nth-of-type(15),
|
||||||
|
div.tab-frame > input:nth-of-type(16):checked ~ .tab:nth-of-type(16),
|
||||||
|
div.tab-frame > input:nth-of-type(17):checked ~ .tab:nth-of-type(17){ display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.tab-frame > div.info{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
div.tab-frame label.info{
|
||||||
|
border:none;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
div.tab-frame > input:checked + label.info{
|
||||||
|
border:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.tab-frame > input#info:checked ~ .info{
|
||||||
|
display:block;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
1
src/media
Symbolic link
1
src/media
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
janusweb/media
|
||||||
|
|
@ -61,7 +61,7 @@ div#xrecosystem{
|
||||||
<div id="logo"></div>
|
<div id="logo"></div>
|
||||||
<h2>Turn files into AR/VR 🥽 experiences</h2>
|
<h2>Turn files into AR/VR 🥽 experiences</h2>
|
||||||
<center>
|
<center>
|
||||||
A static website generator, XR hub, server & client.
|
A static website generator for interconnected XR experiences, server & client.
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="btn">
|
<div class="btn">
|
||||||
|
|
@ -91,9 +91,11 @@ div#xrecosystem{
|
||||||
<h3>Why people want XRForge</h3>
|
<h3>Why people want XRForge</h3>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
In short: its hyperscalable portal-system and ability <b>projecting existing 2D ecosystems</b> into XR.<br>
|
||||||
|
XRForge interconnects experiences via portals, according to the <a href="https://coderofsalvation.github.io/janus-guide/" target="_blank">JanusXR</a> and <a href="https://xrfragment.org" target="_blank">XR Fragments standard</a>.<br>
|
||||||
The metaverse has shown: people <b>like 3D</b> but certain <b>existing</b> 2D ecosystems are king.<br>
|
The metaverse has shown: people <b>like 3D</b> but certain <b>existing</b> 2D ecosystems are king.<br>
|
||||||
They're just more cost-efficient to use.<br>
|
They're just more cost-efficient to use.<br>
|
||||||
Hence, XRForge promotes <b>projecting these ecosystems</b> as virtual <b>hyperlinked</b> worlds.<br>
|
Hence, XRForge incorporates the opensource <a href="https://coderofsalvation.github.io/janus-guide/" target="_blank">JanusXR</a>-client called <a href="https://github.com/jbaicoianu/janusweb" target="_blank">JanusWeb</a>, which promotes <b>projecting these ecosystems</b> as virtual <b>hyperlinked</b> worlds (via XR translators).<br>
|
||||||
XRForge can seed itself via local or remote
|
XRForge can seed itself via local or remote
|
||||||
<u tabindex="0">datastores
|
<u tabindex="0">datastores
|
||||||
<span>
|
<span>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,13 @@ ${header}
|
||||||
${notification}
|
${notification}
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
|
<div class="tab-frame">
|
||||||
|
${tabs}
|
||||||
|
${for i,tab in pairs(tabs) do}
|
||||||
|
<input type="radio" checked name="tab" id="tab${i}">
|
||||||
|
<label for="tab${i}">${tab}</label>
|
||||||
|
${end}
|
||||||
|
|
||||||
|
|
||||||
${plugins}
|
${plugins}
|
||||||
|
|
||||||
|
|
@ -17,6 +24,7 @@ ${notification}
|
||||||
<input type="submit" class="btn secondary" value="Save" style=""/>
|
<input type="submit" class="btn secondary" value="Save" style=""/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
${footer}
|
${footer}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ local util = require("util")
|
||||||
|
|
||||||
local data = util.merge({notification=''},{ opts = app.opts })
|
local data = util.merge({notification=''},{ opts = app.opts })
|
||||||
data.plugins = ""
|
data.plugins = ""
|
||||||
|
data.tabs = {}
|
||||||
|
|
||||||
function pluginInput(opt)
|
function pluginInput(opt)
|
||||||
local html = ""
|
local html = ""
|
||||||
|
|
@ -46,13 +47,14 @@ end
|
||||||
function pluginUI(seen)
|
function pluginUI(seen)
|
||||||
return function(name,plugin)
|
return function(name,plugin)
|
||||||
if seen[ plugin.name ] then return end -- already processed
|
if seen[ plugin.name ] then return end -- already processed
|
||||||
|
table.insert( data.tabs, plugin.name ) -- insert tabname
|
||||||
seen[ plugin.name ] = true
|
seen[ plugin.name ] = true
|
||||||
local plugindata = util.flatten(plugin,"plugin")
|
local plugindata = {plugin=plugin}
|
||||||
plugindata.opts = ""
|
plugindata.opts = ""
|
||||||
foreach(plugin.opts, function(k,opt)
|
foreach(plugin.opts, function(k,opt)
|
||||||
plugindata.opts = plugindata.opts .. pluginInput(opt)
|
plugindata.opts = plugindata.opts .. pluginInput(opt)
|
||||||
end)
|
end)
|
||||||
data.plugins = data.plugins .. util.template( LoadAsset('/page/admin/plugin.html'), plugindata)
|
data.plugins = data.plugins .. app.tpl( LoadAsset('/page/admin/plugin.html'), plugindata)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<div class="tab">
|
<!--
|
||||||
|
<div class="vtab">
|
||||||
<div class="label col2">${plugin.name}</div>
|
<div class="label col2">${plugin.name}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="margin1">
|
-->
|
||||||
|
<div class="tab margin1">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<th width="165"/>
|
<th width="165"/>
|
||||||
|
|
@ -11,11 +13,17 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td class="col3">${plugin.info}<br/><br/></td>
|
<td class="">
|
||||||
|
$(if #_.plugin.info > 2 then)
|
||||||
|
<div class="col3 info">
|
||||||
|
${plugin.info}
|
||||||
|
</div>
|
||||||
|
$(end)
|
||||||
|
<br/><br/>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
${opts}
|
${opts}
|
||||||
</table>
|
</table>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,35 @@
|
||||||
${header}
|
${header}
|
||||||
|
|
||||||
|
${if _.parent ~= nil and _.parent.parent == nil then}
|
||||||
<div style="height:120px;" class="jumbotron">
|
<div style="height:120px;" class="jumbotron">
|
||||||
<h1>${opts.title}</h1>
|
<h1>${opts.title}</h1>
|
||||||
${opts.subtitle}
|
${opts.subtitle}
|
||||||
</div>
|
</div>
|
||||||
<hr style="margin-top:0">
|
<hr style="margin-top:0">
|
||||||
|
${end}
|
||||||
|
|
||||||
|
${if _.parent ~= nil and _.parent.parent ~= nil then}
|
||||||
|
<h2>${parent.name}</h2>
|
||||||
|
${end}
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
${items}
|
${html_items}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${footer}
|
${footer}
|
||||||
|
|
||||||
|
<!-- JanusXR portalroom, see https://janusxr.org for markup -->
|
||||||
|
<fireboxroom>
|
||||||
|
<room pos="0 0 0" skybox="true" showavatar="false" use_local_asset="room_plane">
|
||||||
|
<light js_id="dan9-janus:light/light_cone_angle=0-6" pos="0 3 2" collision_trigger="false" />
|
||||||
|
<text js_id="location" pos="0 2.6 2" billboard="y" col="#BBFFFF" scale="5 5 1" text="https://xrforge.isvery.ninja/models" rotation="-180 0 -180" emissive="0 0 0" roughness="0.3" metalness="0" />
|
||||||
|
<CircularLayout pos="0 0 0" radius="4.5" scale="1 1 1" >
|
||||||
|
${for k,v in pairs(items) do}
|
||||||
|
<object id="o_${i}">
|
||||||
|
<link url="${v.url}" image_id="${v.thumbnail}" pos="0 0 0.001" draw_glow="true" scale="3 3 0.1" thumb_id="thumb_0" title="${v.title}"/>
|
||||||
|
<text text="${v.title}" pos="0 2.3 0.3" font_scale="false" scale="0.15 0.15 0.2" />
|
||||||
|
</object>
|
||||||
|
${end}
|
||||||
|
</CircularLayout>
|
||||||
|
</room>
|
||||||
|
</fireboxroom>
|
||||||
|
|
|
||||||
117
src/page/dir.lua
117
src/page/dir.lua
|
|
@ -1,35 +1,92 @@
|
||||||
local util = require("util")
|
local util = require("util")
|
||||||
local tile = require('page/partial/tile')
|
|
||||||
|
|
||||||
--if not app.cache['experiences.html'] and util.count(app.cache.items) > 0 then
|
-- todo: clean up code once the code below is no longer in flux
|
||||||
-- print("processing! " .. util.count(app.cache.items) .. " items")
|
|
||||||
-- app.cache['experiences.html'] = util.memoize(
|
|
||||||
-- function()
|
|
||||||
-- 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)
|
|
||||||
-- )
|
|
||||||
--end
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
middleware = function(req,res,next)
|
||||||
|
local tile = require('page/partial/tile')
|
||||||
|
local rootpath = "/" .. app.opts.plugin_diskscan_dirname
|
||||||
|
local urlpath = GetPath()
|
||||||
|
local isdir = urlpath:sub(-1) == "/"
|
||||||
|
local nslashes = util.countChar('/',urlpath)
|
||||||
|
res.items = {}
|
||||||
|
local match = function(a,b) return util.stripPath(a) == util.stripPath(b) end
|
||||||
|
if urlpath:match("^"..rootpath) and not urlpath:gsub(".*/",""):match(".") then
|
||||||
|
local redirect = false
|
||||||
|
if not app.cache.url[ urlpath ] then
|
||||||
|
foreach( app.cache.items, function(k,v)
|
||||||
|
if v.parent then
|
||||||
|
trace( "path = " .. util.stripPath(v.path) .. " " ..
|
||||||
|
"parent = " .. util.stripPath(v.parent.path) .. " " ..
|
||||||
|
"urlpath = " .. util.stripPath(urlpath)
|
||||||
|
)
|
||||||
|
if match(v.path, urlpath) then -- show parent folder if any
|
||||||
|
local parent = app.cache.items[ v.parent.id ]
|
||||||
|
if v.portal then
|
||||||
|
redirect = v.portal.url
|
||||||
|
end
|
||||||
|
if match(parent.path, app.opts.plugin_diskscan_dir) then -- add rootfolder if needed
|
||||||
|
local root = app.cache.items[ app.opts.plugin_diskscan_dir ]
|
||||||
|
--res.items[ root.id ] = root
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if util.count(app.cache.items) == 0 then
|
-- add items from dir
|
||||||
SetStatus(200)
|
if match(v.parent.path, urlpath) then
|
||||||
SetHeader('Content-Type', 'text/html; charset=utf-8')
|
local parent = app.cache.items[ v.parent.id ]
|
||||||
local html = util.template( [[
|
res.items[ v.id ] = v
|
||||||
${header}
|
res.parent = parent
|
||||||
<br/>
|
if v.thumbnail then
|
||||||
<h2>Indexing files: please try again in few seconds</h2>
|
print_r(v.name .. " => " .. v.thumbnail)
|
||||||
${footer}
|
end
|
||||||
]],data)
|
--if parent.parent then
|
||||||
Write( html )
|
-- res.items[ v.id ] = v
|
||||||
else
|
--else
|
||||||
SetStatus(200)
|
-- res.items[ parent.id ] = parent
|
||||||
SetHeader('Content-Type', 'text/html; charset=utf-8')
|
--end
|
||||||
--Write( app.cache['experiences.html']() )
|
end
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
|
local data = util.merge({
|
||||||
|
html_items='',
|
||||||
|
items = {},
|
||||||
|
parent=res.parent
|
||||||
|
},{ opts = app.opts })
|
||||||
|
data.tile = tile
|
||||||
|
local i = 0
|
||||||
|
foreach( res.items, function(k,v)
|
||||||
|
-- position items for janusxr JML (dir.html)
|
||||||
|
table.insert(data.items, util.merge({x = i*2, i = i},v) )
|
||||||
|
i=i+1
|
||||||
|
if v.view3D then
|
||||||
|
data.view3D = v
|
||||||
|
local port = ':' .. GetPort()
|
||||||
|
if port == 80 then port = '' end
|
||||||
|
data.url = GetScheme() .. '://' .. GetHost() .. port .. v.url
|
||||||
|
else
|
||||||
|
data.html_items = data.html_items .. tile({item=v})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if data.view3D then
|
||||||
|
app.cache.url[ urlpath ] = util.templateFile( "page/index.html", data)
|
||||||
|
else
|
||||||
|
app.cache.url[ urlpath ] = util.templateFile( "page/dir.html", data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if redirect then
|
||||||
|
print("REDIRECT! " .. redirect )
|
||||||
|
res.status(303)
|
||||||
|
res.header('Location', redirect )
|
||||||
|
next()
|
||||||
|
else
|
||||||
|
res.status(200)
|
||||||
|
res.header('content-type','text/html; charset=utf-8')
|
||||||
|
res.body( app.cache.url[ urlpath ] )
|
||||||
|
next()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
next()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
${header}
|
${header}
|
||||||
|
|
||||||
<iframe border="0" frameborder="0" src="/v/index.html#janus.url=/" id="viewer"/>
|
<iframe border="0" frameborder="0" src="/janusweb/index.html#janus.url=${url}" id="viewer"/>
|
||||||
|
|
||||||
${footer}
|
${footer}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ if not app.cache['index.html'] then
|
||||||
app.cache['index.html'] = util.memoize(
|
app.cache['index.html'] = util.memoize(
|
||||||
function()
|
function()
|
||||||
print("processing!")
|
print("processing!")
|
||||||
local html = util.template( LoadAsset("page/index.html"),app)
|
local data = { url = util.getURL() .. app.opts.plugin_diskscan_dirname }
|
||||||
|
local html = util.template( LoadAsset("page/index.html"),data)
|
||||||
return html
|
return html
|
||||||
end,
|
end,
|
||||||
60*5 -- re-run every 5 mins (otherwise return cache)
|
60*5 -- re-run every 5 mins (otherwise return cache)
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ local util = require('util')
|
||||||
return function(opts)
|
return function(opts)
|
||||||
return util.template([[
|
return util.template([[
|
||||||
<div class="tile">
|
<div class="tile">
|
||||||
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
|
<a href="${item.url}">
|
||||||
|
<div class="img" style="background-image: url(${item.thumbnail});"></div>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<img src="img/folder.svg" class="icon"/> ${item.title}
|
<img src="img/folder.svg" class="icon"/> ${item.title}
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
]],opts)
|
]],opts)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ end)
|
||||||
|
|
||||||
-- this registers the plugin UI /funcs
|
-- this registers the plugin UI /funcs
|
||||||
return {
|
return {
|
||||||
name = "Content (from disk)",
|
name = "Local Content",
|
||||||
info = '',
|
info = '',
|
||||||
opts = {
|
opts = {
|
||||||
{
|
{
|
||||||
|
|
@ -75,12 +75,23 @@ return {
|
||||||
name = name,
|
name = name,
|
||||||
title = name,
|
title = name,
|
||||||
sidecarfile = {},
|
sidecarfile = {},
|
||||||
|
thumbnail = '',
|
||||||
tag = {},
|
tag = {},
|
||||||
id = ino,
|
id = ino,
|
||||||
file = {stat=stat,state=state,kind=kind,ino=ino,off=off,attr=attr,fullpath=fullpath,name=name,path=path}
|
file = {stat=stat,state=state,kind=kind,ino=ino,off=off,attr=attr,fullpath=fullpath,name=name,path=path}
|
||||||
}
|
}
|
||||||
experience.parent = parent
|
experience.parent = parent
|
||||||
|
|
||||||
|
-- check for thumbnail
|
||||||
|
local thumbExt = {"png","jpg","webp"}
|
||||||
|
foreach( thumbExt, function(k,ext)
|
||||||
|
local thumbnailFile = fullpath .. "/" .. name .. "." .. ext
|
||||||
|
if util.fileExist( thumbnailFile ) then
|
||||||
|
print("✅ └─ plugin/diskscan: detected .".. ext:lower() .. " thumbnail file")
|
||||||
|
experience.thumbnail = "/" .. util.stripPath(thumbnailFile)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
if seen[ino] then return end -- dont process dirs/files twice
|
if seen[ino] then return end -- dont process dirs/files twice
|
||||||
|
|
||||||
if (kind == unix.DT_LNK) then
|
if (kind == unix.DT_LNK) then
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ app.on('scan_experience_file', function(exp)
|
||||||
end
|
end
|
||||||
app.pub("scan_experience_janusxr", nil, {exp=exp, item=item}) -- be nice to other plugins
|
app.pub("scan_experience_janusxr", nil, {exp=exp, item=item}) -- be nice to other plugins
|
||||||
app.cache.items[ exp.file.ino ] = exp
|
app.cache.items[ exp.file.ino ] = exp
|
||||||
|
exp.view3D = true
|
||||||
print("✅ └─ plugin/janusxr: " .. exp.file.name)
|
print("✅ └─ plugin/janusxr: " .. exp.file.name)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
@ -44,6 +45,13 @@ local plugin = {
|
||||||
default="/janusxr",
|
default="/janusxr",
|
||||||
values=''
|
values=''
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key='app.opts.plugin_janusxr_janusweb_corsproxies',
|
||||||
|
title='CORS proxies',
|
||||||
|
info='Proxies to surf the immersive deep web of JanusXR',
|
||||||
|
default="https://p.janusxr.org https://cors.xrforge.org",
|
||||||
|
values=''
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key='app.opts.plugin_janusxr_wrap_image',
|
key='app.opts.plugin_janusxr_wrap_image',
|
||||||
title='wrap images',
|
title='wrap images',
|
||||||
|
|
@ -72,6 +80,20 @@ local plugin = {
|
||||||
default="random",
|
default="random",
|
||||||
values={random="random room",plane="wirefloor",room1="room1",room2="room2",room3="room3",room4="room4",room5="room5",room6="room6",room7="room7"}
|
values={random="random room",plane="wirefloor",room1="room1",room2="room2",room3="room3",room4="room4",room5="room5",room6="room6",room7="room7"}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key='app.opts.plugin_janusxr_scraper',
|
||||||
|
title='Scraper',
|
||||||
|
info='List experiences by periodically scraping JanusXR URLs for <a href="https://coderofsalvation.github.io/janus-guide/#/build/room?id=link" target="_blank"><link></a> portals',
|
||||||
|
default="disabled",
|
||||||
|
values={enabled="enabled",disabled="disabled"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key='app.opts.plugin_janusxr_scraper',
|
||||||
|
title='Scraper URLs',
|
||||||
|
info='Space-separated list of URLs to scrape',
|
||||||
|
default='https://xrforge.isvery.ninja https://www.janusxr.org',
|
||||||
|
values=''
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,16 @@ app.on('scan_experience_file', function(exp)
|
||||||
-- check sidecarfiles: https://xrfragment.org/#sidecar%20files
|
-- check sidecarfiles: https://xrfragment.org/#sidecar%20files
|
||||||
local sidecarExts = {"png","vtt","ogg","json"}
|
local sidecarExts = {"png","vtt","ogg","json"}
|
||||||
foreach( sidecarExts, function(k,sidecarExt)
|
foreach( sidecarExts, function(k,sidecarExt)
|
||||||
if util.fileExist( exp.path:gsub("."..ext.."$", "."..sidecarExt) ) then
|
local sfile = exp.path:gsub("."..ext.."$", "."..sidecarExt)
|
||||||
exp.sidecarfile[ sidecarExt ] = true
|
if util.fileExist( sfile ) then
|
||||||
|
exp.sidecarfile[ sidecarExt ] = "/" .. util.stripPath(sfile)
|
||||||
exp.view3D = true
|
exp.view3D = true
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
-- set thumbnail if any
|
||||||
|
if exp.sidecarfile.png then
|
||||||
|
exp.thumnbail = exp.sidecarfile.png
|
||||||
|
end
|
||||||
local jsonfile = exp.path:gsub("."..ext.."$",".json")
|
local jsonfile = exp.path:gsub("."..ext.."$",".json")
|
||||||
-- scan for hrefs in json sidecarfile
|
-- scan for hrefs in json sidecarfile
|
||||||
if util.fileExist( jsonfile ) then
|
if util.fileExist( jsonfile ) then
|
||||||
|
|
@ -43,7 +48,7 @@ end)
|
||||||
-- this registers the plugin UI /funcs
|
-- this registers the plugin UI /funcs
|
||||||
return {
|
return {
|
||||||
name = "XR Fragments",
|
name = "XR Fragments",
|
||||||
info = '<a href="https://xrfragment.org" target="_blank">deeplinking for 3D files</a>',
|
info = 'XR Fragments treats 3D files with <a href="https://xrfragment.org/#sidecar%20files" target="_blank">sidecar file(s)</a> (foo.glb+foo.png e.g.) as experience and/or detect <a href="https://xrfragment.org" target="_blank">deeplinking of 3D files</a> via <a href="https://xrfragment.org#href" target="_blank">href-metadata</a>',
|
||||||
opts = {
|
opts = {
|
||||||
{
|
{
|
||||||
key='app.opts.plugin_xrfragments_filetypes',
|
key='app.opts.plugin_xrfragments_filetypes',
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,13 @@ download(){
|
||||||
}
|
}
|
||||||
|
|
||||||
janusweb(){
|
janusweb(){
|
||||||
|
set -x
|
||||||
|
version=1.7.5
|
||||||
test -f janusweb-${version}.tar.gz || {
|
test -f janusweb-${version}.tar.gz || {
|
||||||
wget "https://github.com/coderofsalvation/janusweb/releases/download/v1.7.5-rc/janusweb-1.7.5.tar.gz"
|
wget "https://github.com/coderofsalvation/janusweb/releases/download/v${version}-rc/janusweb-${version}.tar.gz"
|
||||||
tar -xvf janusweb-${version}.tar.gz
|
|
||||||
mv janusweb-${version} src/v
|
|
||||||
}
|
}
|
||||||
|
test -d janusweb-${version} || tar -xvf janusweb-${version}.tar.gz
|
||||||
|
test -d src/janusweb || cp -r janusweb-${version} src/janusweb
|
||||||
}
|
}
|
||||||
|
|
||||||
"$@"
|
"$@"
|
||||||
|
|
@ -39,7 +41,7 @@ build(){
|
||||||
|
|
||||||
rebuild(){
|
rebuild(){
|
||||||
cd src
|
cd src
|
||||||
zip -yr ../result/bin/xrforge.com .lua page plugin cmd css .init.lua .args test myverse
|
zip -yr ../result/bin/xrforge.com .lua img page plugin cmd css .init.lua .args test myverse
|
||||||
}
|
}
|
||||||
|
|
||||||
"$@"
|
"$@"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue