first version
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
experiences
|
||||
result
|
||||
11
README.md
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
```
|
||||
$ source .env # not needed if your system has autoenv
|
||||
$ build # fast: repeatable build
|
||||
$ util/xrforge.sh build # fast: repeatable build
|
||||
$ nix-build # slow: reproducable nix build (for experts)
|
||||
```
|
||||
|
||||
|
|
@ -25,3 +25,12 @@ scan files
|
|||
]]
|
||||
|
||||
```
|
||||
|
||||
## Scan manyfold
|
||||
|
||||
-------------
|
||||
```
|
||||
manual scan (via cli?)
|
||||
curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=name' -H 'accept: application/vnd.manyfold.v0+json'
|
||||
curl -L -X 'GET' 'http://xrforge.isvery.ninja/models?page=1&order=recent' -H 'accept: application/vnd.manyfold.v0+json'
|
||||
```
|
||||
|
|
|
|||
110
index.html
|
|
@ -1,110 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>XRForge - selfsovereign interoperable XR experiences</title>
|
||||
<link rel="stylesheet" href="xrforge.css"></link>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<table width="100%" class="header">
|
||||
<tr>
|
||||
<td style="width:120px">
|
||||
<div class="margin1">
|
||||
<div class="logo col1">XR<div class="logo col2">f̅o̅r̅g̅e̅</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="menu">
|
||||
<div class="margin1">
|
||||
<a href="#">Experiences</a>
|
||||
<a href="#">Collections</a>
|
||||
<a href="#">About</a>
|
||||
<a href="#">Howto</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
<center>
|
||||
<table width="80%" class="page">
|
||||
<tr>
|
||||
<td>
|
||||
<div style="background:linear-gradient(45deg,#0000,#0000,#F7F2,#0000,#07F2,#0000,#0000); height:120px;" class="jumbotron">
|
||||
<h1>Hi there</h1>
|
||||
This is a hosting tool for <a href="#">portable XR experiences</a>.
|
||||
</div>
|
||||
<hr style="margin-top:0">
|
||||
|
||||
<div class="btn">
|
||||
<a href="#foo">button</a>
|
||||
</div>
|
||||
|
||||
<div class="btn">
|
||||
<a href="#foo" class="btn primary">button primary ⮮</a>
|
||||
</div>
|
||||
|
||||
<div class="btn">
|
||||
<a href="https://foo.com" target="_blank" class="btn secondary">button secondary ⮩</a>
|
||||
</div>
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
<div class="grid">
|
||||
<div class="tile">
|
||||
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
|
||||
<div class="text">
|
||||
<img src="img/folder.svg" class="icon"/> Item 1
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
|
||||
<div class="text">
|
||||
<img src="img/folder.svg" class="icon"/> Item 2
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
|
||||
<div class="text">
|
||||
<img src="img/folder.svg" class="icon"/> Item 3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
|
||||
<table width="100%" class="footer">
|
||||
<tr>
|
||||
<td class="col3">footer col1</td>
|
||||
<td class="col3">footer col1</td>
|
||||
<td class="col3">footer col1</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</table>
|
||||
|
||||
</center>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- JanusXR code
|
||||
|
||||
-->
|
||||
|
||||
<!-- C-code
|
||||
|
||||
|
||||
-->
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package.path = package.path .. ";.lua/?.lua"
|
||||
package.path = package.path .. ";src/.lua/?.lua"
|
||||
package.path = package.path .. ";middleware/?.lua"
|
||||
|
||||
json = require "json"
|
||||
|
||||
-- special script called by main redbean process at startup
|
||||
HidePath('/usr/share/zoneinfo/')
|
||||
HidePath('/usr/share/ssl/')
|
||||
|
||||
dir = require "opendirectory"
|
||||
|
||||
function OnHttpRequest()
|
||||
if( not dir.serve() ) then
|
||||
print("default")
|
||||
Route()
|
||||
end
|
||||
end
|
||||
|
||||
---- create
|
||||
--app = require("soakbean") {
|
||||
-- bin = "./redbean.com",
|
||||
-- opts = {
|
||||
-- my_cli_arg=1
|
||||
-- },
|
||||
-- cmd={
|
||||
-- -- runtask = {file="sometask.lua", info="description of cli cmd"}
|
||||
-- },
|
||||
-- title = 'SOAKBEAN - a buddy of redbean',
|
||||
-- subtitle = 'SOAKBEAN makes <a href=https://redbean.dev>redbean</a> programming easy',
|
||||
-- notes = {'🤩 express-style programming', '🖧 easy routings', '♻ re-use middleware functions'}
|
||||
--}
|
||||
--
|
||||
--app.url['^/data'] = '/data.lua' -- setup custom file endpoint
|
||||
--
|
||||
--app.get('^/', app.template('index.html') ) -- alias for app.tpl( LoadAsset('index.html'), app )
|
||||
--
|
||||
--app.post('^/save', function(req,res,next) -- setup inline POST endpoint
|
||||
-- -- also .get(), .put(), .delete(), .options()
|
||||
-- app.cache = req.body -- middleware auto-decodes json
|
||||
-- res.status(200)
|
||||
-- res.body({cache=app.cache}) -- middleware auto-encodes json
|
||||
-- next()
|
||||
--end)
|
||||
--
|
||||
--app --
|
||||
--.use( require("json").middleware() ) -- try plug'n'play json API middleware
|
||||
--.use( app.router( app.url ) ) -- try url router
|
||||
--.use( app.response() ) -- try serve app response (if any)
|
||||
--.use( function(req,next) Route() end) -- fallback default redbean fileserver
|
||||
--
|
||||
--function OnHttpRequest() app.run() end
|
||||
2
src/.args
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
-D
|
||||
.
|
||||
65
src/.init.lua
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package.path = package.path .. ";.lua/?.lua"
|
||||
package.path = package.path .. ";src/.lua/?.lua"
|
||||
package.path = package.path .. ";middleware/?.lua"
|
||||
package.path = package.path .. ";cmd/?.lua"
|
||||
|
||||
util = require "util"
|
||||
json = require "json"
|
||||
|
||||
-- special script called by main redbean process at startup
|
||||
HidePath('/usr/share/zoneinfo/')
|
||||
HidePath('/usr/share/ssl/')
|
||||
|
||||
dir = require "opendirectory"
|
||||
|
||||
-- create
|
||||
app = require("soakbean") {
|
||||
bin = "./xrforge.com",
|
||||
cache = {},
|
||||
opts = {
|
||||
title="My XRForge experiences",
|
||||
subtitle="Portable XR experiences for PhoneVR, VRheadset, desktop & mobile."
|
||||
},
|
||||
cmd={
|
||||
help = {file="help.lua", info="displays help cmds"},
|
||||
server = {file="server.lua", info="starts the webserver"}
|
||||
},
|
||||
title = 'XRForge - local-first XR experiences',
|
||||
subtitle = 'XRForge makes local-first XR easy',
|
||||
notes = {'🤩 portable selfsovereign', '🖧 JanusXR / XR Fragments', '♻ decentralized'}
|
||||
}
|
||||
|
||||
app.url['^/data'] = '/data.lua' -- setup custom file endpoint
|
||||
|
||||
app.url['^/$'] = '/index.lua' -- alias for app.tpl( LoadAsset('index.html'), app )
|
||||
app.get('^/admin/$', app.template('admin/index.html') ) -- alias for app.tpl( LoadAsset('index.html'), app )
|
||||
|
||||
app.post('^/save', function(req,res,next) -- setup inline POST endpoint
|
||||
-- also .get(), .put(), .delete(), .options()
|
||||
app.cache = req.body -- middleware auto-decodes json
|
||||
res.status(200)
|
||||
res.body({cache=app.cache}) -- middleware auto-encodes json
|
||||
next()
|
||||
end)
|
||||
|
||||
app --
|
||||
.use( require("httpauth")() )
|
||||
.use( require("json").middleware() ) -- try plug'n'play json API middleware
|
||||
.use( app.router( app.url ) ) -- try url router
|
||||
.use( app.response() ) -- try serve app response (if any)
|
||||
.use( function(req,next) Route() end) -- fallback default redbean fileserver
|
||||
|
||||
app.on('template', function(a,b,c,d,e)
|
||||
print("TEMPLATE!")
|
||||
end)
|
||||
|
||||
function OnHttpRequest() app.run() end
|
||||
|
||||
print("ja")
|
||||
--function OnHttpRequest()
|
||||
-- if( not dir.serve("./experiences") ) then
|
||||
-- print("default")
|
||||
-- Route()
|
||||
-- end
|
||||
--end
|
||||
--
|
||||
|
|
@ -29,16 +29,33 @@
|
|||
|
||||
local dir = {}
|
||||
|
||||
dir.serve = function()
|
||||
|
||||
local path = '.' .. GetPath()
|
||||
if not path:find("..") == nil then return ServeError(403,"=]") end
|
||||
dir.scan = function(path)
|
||||
local stat = unix.stat(path)
|
||||
if stat == nil then
|
||||
return ServeError(404,"does not exist")
|
||||
else
|
||||
if not unix.S_ISDIR( stat:mode() ) then return false end -- let redbean take care of serving static file
|
||||
end
|
||||
state = assert(unix.opendir(path))
|
||||
return stat, state
|
||||
end
|
||||
|
||||
dir.foreach = function(path,cb)
|
||||
local stat, state = dir.scan(path)
|
||||
for name, kind, ino, off in state do
|
||||
if name == "." then goto continue end
|
||||
attr,err = unix.stat(path..'/'..name, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err then print("error: ".. name .. ": " ..err) end
|
||||
cb(path,stat,state,name,kind,ino,off,attr)
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
dir.serve = function(folder) -- raw redbean function (use dir.foreach in soakbean)
|
||||
|
||||
local path = '.' .. GetPath()
|
||||
if not path:match('^' .. folder) then return false end -- should start with folder
|
||||
if not path:find("..") == nil then return ServeError(403,"=]") end
|
||||
|
||||
SetStatus(200)
|
||||
SetHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
|
|
@ -51,38 +68,36 @@ dir.serve = function()
|
|||
Write('td { white-space: nowrap; }\r\n')
|
||||
Write('.l { text-align: left; }\r\n')
|
||||
Write('.r { text-align: right; }\r\n')
|
||||
Write('.hide { display:none; }\r\n')
|
||||
Write('</style>\r\n')
|
||||
|
||||
Write('<h3>Open Directory Demo</h3>\r\n')
|
||||
Write('<h3>Open Directory Demo '.. path .. '</h3>\r\n')
|
||||
Write('<table>\r\n')
|
||||
Write('<thead>\r\n')
|
||||
Write('<tr>\r\n')
|
||||
Write('<th class=l>name\r\n')
|
||||
Write('<th>type\r\n')
|
||||
Write('<th class=r>ino\r\n')
|
||||
Write('<th class=r>off\r\n')
|
||||
Write('<th class="hide">type\r\n')
|
||||
Write('<th class=hide r>ino\r\n')
|
||||
Write('<th class=hide r>off\r\n')
|
||||
Write('<th class=r>size\r\n')
|
||||
Write('<th class=r>blocks\r\n')
|
||||
Write('<th class=r>mode\r\n')
|
||||
Write('<th class=r>uid\r\n')
|
||||
Write('<th class=r>gid\r\n')
|
||||
Write('<th class=r>dev\r\n')
|
||||
Write('<th class=r>rdev\r\n')
|
||||
Write('<th class=r>nlink\r\n')
|
||||
Write('<th class=r>blksize\r\n')
|
||||
Write('<th class=r>gen\r\n')
|
||||
Write('<th class=r>flags\r\n')
|
||||
Write('<th class=r>birthtim\r\n')
|
||||
Write('<th class=r>mtim\r\n')
|
||||
Write('<th class=r>atim\r\n')
|
||||
Write('<th class=r>ctim\r\n')
|
||||
Write('<th class=hide r>blocks\r\n')
|
||||
Write('<th class=hide r>mode\r\n')
|
||||
Write('<th class=hide r>uid\r\n')
|
||||
Write('<th class=hide r>gid\r\n')
|
||||
Write('<th class=hide r>dev\r\n')
|
||||
Write('<th class=hide r>rdev\r\n')
|
||||
Write('<th class=hide r>nlink\r\n')
|
||||
Write('<th class=hide r>blksize\r\n')
|
||||
Write('<th class=hide r>gen\r\n')
|
||||
Write('<th class=hide r>flags\r\n')
|
||||
Write('<th class=hide r>birthtim\r\n')
|
||||
Write('<th class=hide r>mtim\r\n')
|
||||
Write('<th class=hide r>atim\r\n')
|
||||
Write('<th class=hide r>ctim\r\n')
|
||||
Write('<tbody>\r\n')
|
||||
|
||||
print("opendir: "..path)
|
||||
state = assert(unix.opendir(path))
|
||||
|
||||
for name, kind, ino, off in state do
|
||||
if name == "." then goto continue end
|
||||
local stat, state = dir.scan(path)
|
||||
dir.foreach(path, function(path,stat,state,name,kind,ino,off,attr)
|
||||
Write('<tr>\r\n')
|
||||
Write('<td>')
|
||||
local link = EscapeHtml(name)
|
||||
|
|
@ -90,7 +105,7 @@ dir.serve = function()
|
|||
Write('<a href="' .. GetPath() .. link .. '">' .. link .. '</a>')
|
||||
Write('\r\n')
|
||||
|
||||
Write('<td>')
|
||||
Write('<td class="hide">')
|
||||
if kind == unix.DT_REG then Write('DT_REG')
|
||||
elseif kind == unix.DT_DIR then Write('DT_DIR')
|
||||
elseif kind == unix.DT_FIFO then Write('DT_FIFO')
|
||||
|
|
@ -102,41 +117,38 @@ dir.serve = function()
|
|||
end
|
||||
Write('\r\n')
|
||||
|
||||
Write('<td class=r>%d\r\n' % {ino})
|
||||
Write('<td class=r>%d\r\n' % {off})
|
||||
Write('<td class="hide r">%d\r\n' % {ino})
|
||||
Write('<td class="hide r">%d\r\n' % {off})
|
||||
|
||||
st,err = unix.stat(path..'/'..name, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if st then
|
||||
|
||||
Write('<td class=r>%d\r\n' % {st:size()})
|
||||
Write('<td class=r>%d\r\n' % {st:blocks()})
|
||||
Write('<td class=r>%.7o\r\n' % {st:mode()})
|
||||
Write('<td class=r>%d\r\n' % {st:uid()})
|
||||
Write('<td class=r>%d\r\n' % {st:gid()})
|
||||
Write('<td class=r>%d\r\n' % {st:dev()})
|
||||
Write('<td class=r>%d,%d\r\n' % {unix.major(st:rdev()), unix.minor(st:rdev())})
|
||||
Write('<td class=r>%d\r\n' % {st:nlink()})
|
||||
Write('<td class=r>%d\r\n' % {st:blksize()})
|
||||
Write('<td class=r>%d\r\n' % {st:gen()})
|
||||
Write('<td class=r>%#x\r\n' % {st:flags()})
|
||||
if attr then
|
||||
Write('<td class="r">%d\r\n' % {attr:size()})
|
||||
Write('<td class="hide r">%d\r\n' % {attr:blocks()})
|
||||
Write('<td class="hide r">%.7o\r\n' % {attr:mode()})
|
||||
Write('<td class="hide r">%d\r\n' % {attr:uid()})
|
||||
Write('<td class="hide r">%d\r\n' % {attr:gid()})
|
||||
Write('<td class="hide r">%d\r\n' % {attr:dev()})
|
||||
Write('<td class="hide r">%d,%d\r\n' % {unix.major(attr:rdev()), unix.minor(attr:rdev())})
|
||||
Write('<td class="hide r">%d\r\n' % {attr:nlink()})
|
||||
Write('<td class="hide r">%d\r\n' % {attr:blksize()})
|
||||
Write('<td class="hide r">%d\r\n' % {attr:gen()})
|
||||
Write('<td class="hide r">%#x\r\n' % {attr:flags()})
|
||||
|
||||
function WriteTime(unixsec,nanos)
|
||||
year,mon,mday,hour,min,sec,gmtoffsec = unix.localtime(unixsec)
|
||||
Write('<td class=r>%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.9d%+.2d%.2d\r\n' % {
|
||||
Write('<td class="hide r">%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.9d%+.2d%.2d\r\n' % {
|
||||
year, mon, mday, hour, min, sec, nanos,
|
||||
gmtoffsec / (60 * 60), math.abs(gmtoffsec) % 60})
|
||||
end
|
||||
|
||||
WriteTime(st:birthtim())
|
||||
WriteTime(st:mtim())
|
||||
WriteTime(st:atim())
|
||||
WriteTime(st:ctim())
|
||||
WriteTime(attr:birthtim())
|
||||
WriteTime(attr:mtim())
|
||||
WriteTime(attr:atim())
|
||||
WriteTime(attr:ctim())
|
||||
|
||||
else
|
||||
Write('<td class=l colspan=15>%s\r\n' % {err})
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
end)
|
||||
Write('</table>\r\n')
|
||||
state:close()
|
||||
return true
|
||||
|
|
@ -49,6 +49,7 @@ sb = {
|
|||
-- print( util.tpl("${name} is ${value}", {name = "foo", value = "bar"}) )
|
||||
-- "foo is bar"
|
||||
tpl = function(s,tab)
|
||||
if not s then return "" end
|
||||
return (s:gsub('($%b{})', function(w) return tab[w:sub(3, -2)] or w end))
|
||||
end,
|
||||
|
||||
158
src/.lua/util.lua
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
local util = { }
|
||||
|
||||
util.clone = function(orig)
|
||||
local orig_type = type(orig)
|
||||
local copy
|
||||
if orig_type == 'table' then
|
||||
copy = {}
|
||||
for orig_key, orig_value in next, orig, nil do
|
||||
copy[util.clone(orig_key)] = util.clone(orig_value)
|
||||
end
|
||||
setmetatable(copy, util.clone(getmetatable(orig)))
|
||||
else -- number, string, boolean, etc
|
||||
copy = orig
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
util.flatten = function(tbl, prefix, result)
|
||||
result = result or {}
|
||||
for k, v in pairs(tbl) do
|
||||
local new_key = (prefix or "") .. "." .. k
|
||||
if type(v) == "table" then
|
||||
util.flatten(v, new_key, result)
|
||||
else
|
||||
result[new_key] = v
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
util.template = function(content,data)
|
||||
local finaldata = util.merge(app,{})
|
||||
finaldata = util.merge(finaldata, util.flatten(app.opts,"opts") )
|
||||
finaldata.header = app.tpl( LoadAsset('template/header.html'), finaldata )
|
||||
finaldata.footer = app.tpl( LoadAsset('template/footer.html'), finaldata )
|
||||
return app.tpl( content, finaldata )
|
||||
end
|
||||
|
||||
util.memoize = function(fn, ttl_seconds)
|
||||
local cached_results = nil
|
||||
local last_executed = 0
|
||||
|
||||
return function(...)
|
||||
local now = os.time()
|
||||
-- Check if cache is missing or if the TTL has passed
|
||||
print("now = "..now .. " last_executed = " ..last_executed)
|
||||
if not cached_results or (now - last_executed) >= ttl_seconds then
|
||||
-- Pass all input arguments and pack all return values into a table
|
||||
cached_results = { fn(...) }
|
||||
last_executed = now
|
||||
end
|
||||
-- Unpack and return the cached results
|
||||
return table.unpack(cached_results)
|
||||
end
|
||||
end
|
||||
|
||||
util.cmdExist = function(cmd)
|
||||
if api.runtime.os == 'unixy' then cmd = cmd .. " 1>/dev/null 2>/dev/null" end
|
||||
local stdout, retstr, retcode = os.execute(cmd)
|
||||
return retcode ~= 127
|
||||
end
|
||||
|
||||
function util.merge(t1,t2)
|
||||
local t3 = {}
|
||||
foreach( t1, function(k,v) t3[k] = v end)
|
||||
foreach( t2, function(k,v) t3[k] = v end)
|
||||
return t3
|
||||
end
|
||||
|
||||
function util.dump(value, indent, seen)
|
||||
indent = indent or ""
|
||||
seen = seen or {}
|
||||
local t = type(value)
|
||||
if t == "string" then
|
||||
return string.format("%q", value)
|
||||
elseif t == "boolean" or t == "number" or t == "nil" then
|
||||
return tostring(value)
|
||||
elseif t == "function" or t == "userdata" or t == "thread" then
|
||||
return string.format("<%s: %s>", t, tostring(value))
|
||||
elseif t == "table" then
|
||||
if seen[value] then
|
||||
return string.format("<Circular Reference: %s>", tostring(value))
|
||||
end
|
||||
seen[value] = true
|
||||
|
||||
local result = "{\n"
|
||||
local next_indent = indent .. " "
|
||||
local seen_keys = {}
|
||||
for i, v in ipairs(value) do
|
||||
seen_keys[i] = true
|
||||
result = result .. next_indent .. util.dump(v, next_indent, seen) .. ",\n"
|
||||
end
|
||||
|
||||
for k, v in pairs(value) do
|
||||
if not seen_keys[k] then
|
||||
local key_str
|
||||
if type(k) == "string" and k:match("^[%a_][%w_]*$") then
|
||||
key_str = k
|
||||
else
|
||||
key_str = "[" .. util.dump(k, next_indent, seen) .. "]"
|
||||
end
|
||||
|
||||
result = result .. next_indent .. key_str .. " = " .. util.dump(v, next_indent, seen) .. ",\n"
|
||||
end
|
||||
end
|
||||
|
||||
if result:sub(-2) == ",\n" then
|
||||
result = result:sub(1, -3) .. "\n"
|
||||
end
|
||||
|
||||
return result .. indent .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
function util.count(self)
|
||||
local i = 0
|
||||
if self == nil then return i end
|
||||
if type(self) == 'table' then
|
||||
for _, _ in pairs(self) do
|
||||
i = i + 1
|
||||
end
|
||||
return i
|
||||
else
|
||||
return #self
|
||||
end
|
||||
end
|
||||
|
||||
util.call = function(fn,a,b,c,d,e,f)
|
||||
return function(k,v)
|
||||
if v ~= nil and v[fn] then
|
||||
v[fn](a,b,c,d,e,f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function foreach(t, f, sort)
|
||||
if t ~= nil then
|
||||
for k, v in pairs(t) do f(k, v) end
|
||||
end
|
||||
end
|
||||
|
||||
function trace(o)
|
||||
if os.getenv('DEBUG') then
|
||||
if type(o) == 'table' then
|
||||
print_r(o)
|
||||
else
|
||||
print(o)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function print_r(t)
|
||||
print( util.dump(t) )
|
||||
end
|
||||
|
||||
|
||||
return util
|
||||
158
src/admin/index.html
Normal file
13
src/cmd/help.lua
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
return function(app,argv)
|
||||
print("help!")
|
||||
foreach( app.opts, function(k,v)
|
||||
if not k:match("/") and not k:match("^help") then
|
||||
print( app.bin .. " -- " .. k .. "=...\t # '" .. v .. "' e.g." )
|
||||
end
|
||||
end)
|
||||
foreach( app.cmd, function(k,v)
|
||||
print( app.bin .. " -- " .. k .. "\t\t # " .. v.info )
|
||||
end)
|
||||
|
||||
os.exit()
|
||||
end
|
||||
3
src/cmd/server.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
return function(app,argv)
|
||||
print("starting server")
|
||||
end
|
||||
1
src/experiences
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../experiences
|
||||
12
src/img/arrow-down.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.0001 3.67157L13.0001 3.67157L13.0001 16.4999L16.2426 13.2574L17.6568 14.6716L12 20.3284L6.34314 14.6716L7.75735 13.2574L11.0001 16.5001L11.0001 3.67157Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
12
src/img/arrow-left.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M20.3284 11.0001V13.0001L7.50011 13.0001L10.7426 16.2426L9.32842 17.6568L3.67157 12L9.32842 6.34314L10.7426 7.75735L7.49988 11.0001L20.3284 11.0001Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 307 B |
12
src/img/arrow-right.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15.0378 6.34317L13.6269 7.76069L16.8972 11.0157L3.29211 11.0293L3.29413 13.0293L16.8619 13.0157L13.6467 16.2459L15.0643 17.6568L20.7079 11.9868L15.0378 6.34317Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 320 B |
12
src/img/arrow-up.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.6568 8.96219L16.2393 10.3731L12.9843 7.10285L12.9706 20.7079L10.9706 20.7059L10.9843 7.13806L7.75404 10.3532L6.34314 8.93572L12.0132 3.29211L17.6568 8.96219Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 320 B |
|
Before Width: | Height: | Size: 662 B After Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
16
src/index.lua
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
local util = require("util")
|
||||
|
||||
if not app.cache['index.html'] then
|
||||
app.cache['index.html'] = util.memoize(
|
||||
function()
|
||||
print("processing!")
|
||||
local html = util.template( LoadAsset("template/index.html"),app)
|
||||
return html
|
||||
end,
|
||||
60*5 -- re-run every 5 mins (otherwise return cache)
|
||||
)
|
||||
end
|
||||
|
||||
SetStatus(200)
|
||||
SetHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
Write( app.cache['index.html']() )
|
||||
50
src/middleware/httpauth.lua
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
-- Middleware function to check credentials
|
||||
function Authenticate(opts,realm)
|
||||
realm = realm or "Restricted Area"
|
||||
|
||||
local auth_header = GetHeader("Authorization")
|
||||
|
||||
if auth_header and auth_header:find("^Basic ") then
|
||||
-- Extract the Base64 portion
|
||||
local encoded = auth_header:sub(7)
|
||||
local decoded = DecodeBase64(encoded)
|
||||
|
||||
-- Basic Auth format is "username:password"
|
||||
local user, pass = decoded:match("([^:]+):(.*)")
|
||||
if user == opts.user and pass == opts.pass then
|
||||
print("info: succesful login for user: "..user)
|
||||
return true -- Access granted
|
||||
end
|
||||
end
|
||||
|
||||
-- Request credentials if missing or invalid
|
||||
SetStatus(401, "Unauthorized")
|
||||
SetHeader("WWW-Authenticate", 'Basic realm="' .. realm .. '"')
|
||||
Write("401 Unauthorized - Access Denied\n")
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- soakbean middleware functions
|
||||
return function(opts)
|
||||
opts = opts or {
|
||||
user = "admin",
|
||||
pass = "admin",
|
||||
path = "^/admin"
|
||||
}
|
||||
return function(req,res,next)
|
||||
-- Protect a specific path (e.g., /admin or everything under /private/)
|
||||
if req.url:match( opts.path ) then
|
||||
if not Authenticate(opts,"Admin Zone") then
|
||||
print("ERROR")
|
||||
else
|
||||
req.authenticated = true
|
||||
req.user = opts.user
|
||||
next()
|
||||
end
|
||||
else
|
||||
next()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
2
src/run.sh
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
../result/bin/xrforge.com -D . -p 8081 -- server
|
||||
31
src/template/footer.html
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
<div class="clear"></div>
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
|
||||
<table width="100%" class="footer">
|
||||
<tr>
|
||||
<td class="col3">footer col1</td>
|
||||
<td class="col3">footer col1</td>
|
||||
<td class="col3">footer col1</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</table>
|
||||
</center>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- JanusXR code
|
||||
|
||||
-->
|
||||
|
||||
<!-- C-code
|
||||
|
||||
|
||||
-->
|
||||
37
src/template/header.html
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>XRForge - selfsovereign interoperable XR experiences</title>
|
||||
<link rel="stylesheet" href="xrforge.css"></link>
|
||||
</head>
|
||||
<body background="#000000">
|
||||
|
||||
<table width="100%" class="header">
|
||||
<tr>
|
||||
<td style="width:120px">
|
||||
<div class="margin1L">
|
||||
<div class="logo col1">XR<div class="logo col2">f̅o̅r̅g̅e̅</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="menu">
|
||||
<div class="margin1L">
|
||||
<a href="#">Experiences</a>
|
||||
<a href="#">Collections</a>
|
||||
<a href="#">About</a>
|
||||
<a href="#">Howto</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr style="margin:0"/>
|
||||
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
<center>
|
||||
<table width="80%" class="page">
|
||||
<tr>
|
||||
<td>
|
||||
45
src/template/index.html
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
${header}
|
||||
|
||||
<div style="height:120px;" class="jumbotron">
|
||||
<h1>${opts.title}</h1>
|
||||
${opts.subtitle}
|
||||
</div>
|
||||
<hr style="margin-top:0">
|
||||
|
||||
<div class="btn">
|
||||
<a href="#foo">button</a>
|
||||
</div>
|
||||
|
||||
<div class="btn">
|
||||
<a href="#foo" class="primary">button primary <img src="img/arrow-down.svg" class="icon"/></a>
|
||||
</div>
|
||||
|
||||
<div class="btn">
|
||||
<a href="https://foo.com" target="_blank" class="secondary">button secondary <img src="img/arrow-right.svg" class="icon"/></a>
|
||||
</div>
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
<div class="grid">
|
||||
<div class="tile">
|
||||
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
|
||||
<div class="text">
|
||||
<img src="img/folder.svg" class="icon"/> Item 1
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
|
||||
<div class="text">
|
||||
<img src="img/folder.svg" class="icon"/> Item 2
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="img" style="background-image:url(https://xrforge.isvery.ninja/models/dpjnj8pt1cjc/model_files/bzjvdm7l523l.png?derivative=preview);"></div>
|
||||
<div class="text">
|
||||
<img src="img/folder.svg" class="icon"/> Item 3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${footer}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
/* Progressive enhancement
|
||||
/* Progressive enhancment
|
||||
* NetSurf and Dillo will ignore this entirely because of the modern orientation/feature query
|
||||
*/
|
||||
@import url("xrforge.max.css") supports(display: grid);
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
.page{ width:640px; }
|
||||
|
||||
/* spacers */
|
||||
.margin1L{ margin: 0px 20px 10px 0px;}
|
||||
.margin1{ margin: 10px 20px; }
|
||||
.padding1{ padding: 10px 20px 10px 20px; }
|
||||
|
||||
|
|
@ -23,13 +24,17 @@
|
|||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
body,html {
|
||||
background:#112;
|
||||
font-family: monospace,sans-serif;
|
||||
color:#f7f7f7;
|
||||
font-size:15px;
|
||||
}
|
||||
|
||||
body{
|
||||
margin:20px;
|
||||
}
|
||||
|
||||
td,div,p{
|
||||
word-spacing: 0.2em;
|
||||
line-height:24px;
|
||||
|
|
@ -40,10 +45,13 @@ td,div,p{
|
|||
margin: 0px 10px 10px 0px;
|
||||
}
|
||||
|
||||
.btn a{
|
||||
.btn a,
|
||||
.btn a:active,
|
||||
.btn a:visited,
|
||||
.btn a:link {
|
||||
display: inline-block;
|
||||
padding:8px 16px;
|
||||
color: #AAD;
|
||||
color: #f3f;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +60,8 @@ td,div,p{
|
|||
background:#000;
|
||||
}
|
||||
|
||||
.btn a, .border{ border:1px dotted #AAD; }
|
||||
|
||||
a, a:active, a:visited, a:link {
|
||||
text-decoration: none;
|
||||
color:#FFF;
|
||||
|
|
@ -64,11 +74,6 @@ a:hover{
|
|||
border-bottom:1px dotted #f3f;
|
||||
}
|
||||
|
||||
.primary{ color: #f3f; }
|
||||
.secondary{ color: #7070ff; }
|
||||
.btn.primary{ background: #f3F; color:#000; }
|
||||
.btn.secondary{ background: #7070ff; color:#000; }
|
||||
.btn a, .border{ border:1px dotted #AAD; }
|
||||
.border.primary{ border:1px dotted #87a; }
|
||||
.border.secondary{ border:1px dotted #7070ff; }
|
||||
|
||||
|
|
@ -129,6 +134,8 @@ ul{
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
img.icon{ display:none; }
|
||||
|
||||
/* progressive enhancement quirks */
|
||||
@media (min-width:1px) {
|
||||
.grid .tile{
|
||||
|
|
@ -137,5 +144,8 @@ ul{
|
|||
.grid .tile .img{
|
||||
visibility: visible;
|
||||
}
|
||||
.page{ width:80%; }
|
||||
.page{
|
||||
width:100%;
|
||||
max-width:1280px;
|
||||
}
|
||||
}
|
||||
60
src/xrforge.max.css
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
body{
|
||||
background:#000000;
|
||||
}
|
||||
|
||||
.grid .tile .img{
|
||||
visibility: visible;
|
||||
border:1px dotted #223;
|
||||
background-size:cover;
|
||||
background-position:center;
|
||||
background-repeat: no-repeat;
|
||||
border-radius:5px 5px 0px 0px;
|
||||
height:33vh;
|
||||
}
|
||||
.grid .tile{
|
||||
margin: 0px 15px 15px 0px;
|
||||
width:31%;
|
||||
float:left;
|
||||
background-color:#333;
|
||||
}
|
||||
|
||||
@media (max-width: 890px){
|
||||
.grid .tile {
|
||||
width:47%;
|
||||
}
|
||||
}
|
||||
@media (max-width: 713px){
|
||||
.grid .tile {
|
||||
float:none;
|
||||
width:100%;
|
||||
margin: 0px 0px 15px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.grid .tile .text{
|
||||
padding: 10px 20px 10px 20px !important;
|
||||
}
|
||||
|
||||
.grid .tile img.icon{
|
||||
transform: translate(0,5px);
|
||||
}
|
||||
|
||||
.clear{
|
||||
clear:both;
|
||||
}
|
||||
|
||||
.jumbotron{
|
||||
padding:20px 0px;
|
||||
background:linear-gradient(45deg,#0000,#0000,#F7F2,#0000,#07F2,#0000,#0000);
|
||||
}
|
||||
|
||||
img.icon{
|
||||
display: inline-block !important;
|
||||
margin-top: 0px;
|
||||
margin-bottom: -5px;
|
||||
width: 17px;
|
||||
}
|
||||
|
||||
.btn a.primary{ background: #ff33Ff; color:#000 !important;}
|
||||
.btn a.secondary{ background: #7070ff; color:#000 !important;}
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ download(){
|
|||
sha256sum -c /tmp/checksum || { echo "psuedosecurity checksum failed"; exit 1; }
|
||||
chmod +x $OUTDIR/xrforge.com
|
||||
}
|
||||
test -f /tmp/xrforge.com || wget "$REDBEAN_URL" -O /tmp/xrforge.com
|
||||
test -f /tmp/xrforge.com || wget --no-check-certificate "$REDBEAN_URL" -O /tmp/xrforge.com
|
||||
cp /tmp/xrforge.com $OUTDIR/xrforge.com
|
||||
verify
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@ download(){
|
|||
|
||||
build(){
|
||||
download redbean
|
||||
cd src
|
||||
zip -r ../result/bin/xrforge.com * .lua .init.lua
|
||||
}
|
||||
|
||||
"$@"
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
.grid .tile .img{
|
||||
visibility: visible;
|
||||
border:1px dotted #223;
|
||||
background-size:cover;
|
||||
background-position:center;
|
||||
background-repeat: no-repeat;
|
||||
border-radius:5px 5px 0px 0px;
|
||||
height:33vh;
|
||||
}
|
||||
.grid .tile{
|
||||
margin: 0px 15px 15px 0px;
|
||||
width:31%;
|
||||
float:left;
|
||||
background-color:#333;
|
||||
}
|
||||
.grid .tile .text{
|
||||
padding: 10px 20px 10px 20px !important;
|
||||
}
|
||||
|
||||
|
||||
.grid .tile img.icon{
|
||||
transform: translate(0,5px);
|
||||
}
|
||||
|
||||
.clear{
|
||||
clear:both;
|
||||
}
|
||||
|
||||
.jumbotron{
|
||||
padding: 15px 20px 15px 20px;
|
||||
}
|
||||
|
||||