diff --git a/src/.init.lua b/src/.init.lua index b6424b5..052a882 100644 --- a/src/.init.lua +++ b/src/.init.lua @@ -6,8 +6,6 @@ package.path = package.path .. ";plugin/?.lua" util = require "util" json = require "json" -liluat = require "liluat" -template = require "template" -- special script called by main redbean process at startup HidePath('/usr/share/zoneinfo/') @@ -71,7 +69,10 @@ print = function(...) Log(kLogInfo, ... or "''") end -- nice redbean logging fro -- init URL router app.url['^/data'] = '/data.lua' -- setup custom file endpoint -app.url['^/$'] = '/page/index.lua' +app.get('^/$', function(req,res,next) + res.template = 'page/index.html' + next() +end) app.url['^/admin[/]?$'] = '/page/admin/index.lua' app.get('^/design[/]?$', util.pagerender('page/design.html')) diff --git a/src/.lua/liluat.lua b/src/.lua/liluat.lua deleted file mode 100644 index 83c72ff..0000000 --- a/src/.lua/liluat.lua +++ /dev/null @@ -1,535 +0,0 @@ ---[[ --- 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 - diff --git a/src/.lua/template.lua b/src/.lua/template.lua deleted file mode 100644 index 0a0496e..0000000 --- a/src/.lua/template.lua +++ /dev/null @@ -1,130 +0,0 @@ --- local compile = require "tmpl" --- --- local s = [[ --- --- --- --- {! this is a comment !} --- --- {{page.title}} --- --- --- ---

{{escaped}}

---

{* unescaped *} ---

{% echo 'haHAA' %}

--- {[template2]} --- --- --- ]] --- --- local s2 = [[ --- --- {% if haHAA then %} --- {[template2, sub]} --- {% end %} --- ]] --- --- local template = compile(s) --- local ctx = { --- page = { title = 'Demo', bg = '332233' }, --- escaped = 'escaped: < &', --- unescaped = 'unescaped: •', --- 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 diff --git a/src/css/xrforge.css b/src/css/xrforge.css index 8893da6..bd77dd7 100644 --- a/src/css/xrforge.css +++ b/src/css/xrforge.css @@ -5,6 +5,7 @@ /* color palette */ .col1{ color:#f3f; } +.col12{ color:#83f; } .col2{ color:#AAD; } .col3{ color:#878; } .col4{ color:#7070ff; } @@ -97,11 +98,12 @@ input[type=submit]{ a, a:active, a:visited, a:link, input[type=submit] { text-decoration: none; - color:#FFF; + color:#8af; border-bottom: 1px dotted #AAD; } -a:hover{ +a:hover, +.tile a{ color:#FFF !important; cursor:pointer; border-bottom:1px dotted #f3f; @@ -134,7 +136,7 @@ form > table > tbody > tr > td:nth-child(3) { .primary { color: #ff33Ff;} .secondary { color: #7070ff;} .btn a.primary{ background: #ff33Ff; color:#000} -.btn a.secondary{ background: #7070ff; color:#000} +.btn a.secondary{ background: #224; color: #83f; } .pad1{ padding:20px; @@ -194,6 +196,7 @@ ul{ } .menu a { padding-right:30px; + color:#FFF; } .grid .tile{ border-radius:5px; } diff --git a/src/css/xrforge.max.css b/src/css/xrforge.max.css index a8f1e7a..872a751 100644 --- a/src/css/xrforge.max.css +++ b/src/css/xrforge.max.css @@ -154,6 +154,12 @@ u span{ box-shadow: 0px 0px 5px #F0F; } +.spectrum.span{ + position: absolute; + left: 0px; + right: 0px; +} + .spectrum > div, .ruler{ margin:3px 0px; @@ -237,4 +243,28 @@ div.tab-frame > input#info:checked ~ .info{ cursor:pointer; } +/* popup */ +#popup{ + position:fixed; + max-width:720px; + margin:auto auto; + top:20%; + left:5%; + right:5%; + max-height:60vh; + padding:0px 30px 30px 30px; + background:#112e; + z-index:1000; + overflow:hidden; + overflow-y:auto; +} + +#popup .icon.close{ + cursor:pointer; + width: 40px; + filter: invert(1); + position: absolute; + right: 20px; + top: 20px; +} diff --git a/src/img/close.svg b/src/img/close.svg new file mode 100644 index 0000000..34aabb4 --- /dev/null +++ b/src/img/close.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/page/dir.html b/src/page/dir.html index b36437a..bf2e273 100644 --- a/src/page/dir.html +++ b/src/page/dir.html @@ -21,26 +21,3 @@ end} ${footer} - - - - - - {for i,v in pairs(items) do - print('\t') - end} - - - - - {for i,v in pairs(items) do - local width = 3 - local center = -((#items*width)/1.3) + (i*width) - print('\t\t') - print('\t\t ') - print('\t\t ') - print('\t\t ') - print('\t\t') - end} - - diff --git a/src/page/dir.lua b/src/page/dir.lua index 90c8353..f4b2995 100644 --- a/src/page/dir.lua +++ b/src/page/dir.lua @@ -8,20 +8,26 @@ return { local tile = require('page/partial/tile') local rootpath = "/" .. app.opts.plugin_diskscan_dirname local urlpath = GetPath() + res.items = {} + res.template = res.template or 'page/dir.html' 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 + if urlpath == "/" + or urlpath:match("^"..rootpath) + and not urlpath:gsub(".*/",""):match("%.") then + local redirect = false if not app.cache.url[ urlpath ] then + local matchpath = urlpath + if urlpath == '/' then matchpath = rootpath end -- generate janusxr footer for root url 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) + "matchpath = " .. util.stripPath(matchpath) ) - if match(v.path, urlpath) then -- show parent folder if any + if match(v.path, matchpath) then -- show parent folder if any local parent = app.cache.items[ v.parent.id ] if v.portal then redirect = v.portal.url @@ -33,19 +39,15 @@ return { end -- add items from dir - if match(v.parent.path, urlpath) then + if match(v.parent.path, matchpath) then local parent = app.cache.items[ v.parent.id ] res.items[ v.id ] = v res.parent = parent if v.thumbnail then print_r(v.name .. " => " .. v.thumbnail) end - --if parent.parent then - -- res.items[ v.id ] = v - --else - -- res.items[ parent.id ] = parent - --end end + end end) local data = util.merge({ @@ -70,10 +72,9 @@ return { end end) if data.item then - app.cache.url[ urlpath ] = util.templateFile( "page/item.html", data) - else - app.cache.url[ urlpath ] = util.templateFile( "page/dir.html", data) + res.template = 'page/index.html' end + app.cache.url[ urlpath ] = util.templateFile( res.template, data) end if redirect then print("REDIRECT! " .. redirect ) diff --git a/src/page/footer.html b/src/page/footer.html index 4312561..047296a 100644 --- a/src/page/footer.html +++ b/src/page/footer.html @@ -12,9 +12,9 @@ NLNET
- Experiences
+ View from XR Device
+ Content overview
About
- Howto Sourcecode @@ -31,9 +31,35 @@ - + + + + + {if items and #items > 1 then + for i,v in pairs(items) do + print('\t') + end + end} + + + + + {if item then + print('\t') + end} + {if items and #items > 1 then + for i,v in pairs(items) do + local width = 3.2 + local center = -((#items*width)/1.3) + (i*width) + print('\t\t') + print('\t\t ') + print('\t\t ') + print('\t\t ') + print('\t\t') + end + end} + +