2020-01-31 08:25:17 +01:00
|
|
|
--
|
|
|
|
-- Web-based formspec editor
|
|
|
|
--
|
|
|
|
-- Copyright © 2020 by luk3yx.
|
|
|
|
--
|
|
|
|
-- This program is free software: you can redistribute it and/or modify
|
|
|
|
-- it under the terms of the GNU Affero General Public License as
|
|
|
|
-- published by the Free Software Foundation, either version 3 of the
|
|
|
|
-- License, or (at your option) any later version.
|
|
|
|
--
|
|
|
|
-- This program is distributed in the hope that it will be useful,
|
|
|
|
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
-- GNU Affero General Public License for more details.
|
|
|
|
--
|
|
|
|
-- You should have received a copy of the GNU Affero General Public License
|
|
|
|
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
--
|
|
|
|
|
2020-02-01 04:57:03 +01:00
|
|
|
-- Load the renderer
|
2022-12-08 23:11:48 +01:00
|
|
|
dofile('renderer.lua?rev=11')
|
2020-01-31 08:25:17 +01:00
|
|
|
local formspec_escape = formspec_ast.formspec_escape
|
|
|
|
|
2020-10-21 07:37:38 +02:00
|
|
|
local _, digistuff_ts_export = dofile('digistuff_ts.lua?rev=4')
|
|
|
|
|
2020-01-31 08:25:17 +01:00
|
|
|
-- Show the properties list
|
|
|
|
local properties_elem
|
|
|
|
local function get_properties_list(list_name)
|
|
|
|
local res = {}
|
|
|
|
local elems = properties_elem.firstChild.firstChild.children
|
|
|
|
for i = 0, elems.length - 1 do
|
|
|
|
local elem = elems[i]
|
|
|
|
local name = elem:getAttribute('data-formspec_ast-name')
|
|
|
|
if type(name) == 'string' and name:sub(1, 5) == 'list[' then
|
|
|
|
local s, e = name:find(']', nil, true)
|
|
|
|
local n = name:sub(e + 1)
|
|
|
|
if n == list_name then
|
|
|
|
res[tonumber(name:sub(6, s - 1))] = elem.lastChild.value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return res
|
|
|
|
end
|
2020-02-01 03:31:57 +01:00
|
|
|
|
2020-05-31 12:36:24 +02:00
|
|
|
local function get_properties_map(list_name)
|
|
|
|
local keys = {}
|
|
|
|
local values = {}
|
|
|
|
local elems = properties_elem.firstChild.firstChild.children
|
|
|
|
for i = 0, elems.length - 1 do
|
|
|
|
local elem = elems[i]
|
|
|
|
local name = elem:getAttribute('data-formspec_ast-name')
|
|
|
|
if type(name) == 'string' then
|
|
|
|
if name:sub(1, 5) == 'map1[' then
|
|
|
|
local s, e = name:find(']', nil, true)
|
|
|
|
local n = name:sub(e + 1)
|
|
|
|
if n == list_name then
|
|
|
|
keys[tonumber(name:sub(6, s - 1))] = elem.lastChild.value
|
|
|
|
end
|
|
|
|
elseif name:sub(1, 5) == 'map2[' then
|
|
|
|
local s, e = name:find(']', nil, true)
|
|
|
|
local n = name:sub(e + 1)
|
|
|
|
if n == list_name then
|
|
|
|
values[tonumber(name:sub(6, s - 1))] = elem.lastChild.value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local res = {}
|
|
|
|
for i, key in ipairs(keys) do
|
|
|
|
res[key] = assert(values[i])
|
|
|
|
end
|
|
|
|
return res
|
|
|
|
end
|
|
|
|
|
2020-02-01 03:31:57 +01:00
|
|
|
local property_names = {
|
|
|
|
h = 'Height',
|
|
|
|
w = 'Width',
|
|
|
|
drawborder = 'Draw border',
|
2022-02-13 04:43:16 +01:00
|
|
|
listelems = 'Items',
|
2020-02-01 03:31:57 +01:00
|
|
|
selected_idx = 'Selected item',
|
2020-05-31 12:36:24 +02:00
|
|
|
props = 'Properties',
|
|
|
|
opt = 'Options',
|
2020-02-01 03:31:57 +01:00
|
|
|
}
|
|
|
|
local function get_property_name(n)
|
|
|
|
return property_names[n] or n:sub(1, 1):upper() .. n:sub(2):gsub('_', ' ')
|
|
|
|
end
|
|
|
|
|
2020-05-31 12:36:24 +02:00
|
|
|
local function draw_elements_list(selected_element)
|
|
|
|
local formspec = 'label[0.25,0.5;Selected element]' ..
|
|
|
|
'dropdown[0.25,1;5.5,0.75;selected_element;'
|
|
|
|
local selected = 0
|
|
|
|
local elems = selected_element.parentElement.children
|
|
|
|
local rendered_elems = 0
|
|
|
|
for i = 0, elems.length - 1 do
|
|
|
|
local elem = elems[i]
|
|
|
|
if elem:getAttribute('data-transient') == 'true' then
|
|
|
|
goto continue
|
|
|
|
end
|
|
|
|
if rendered_elems > 0 then
|
|
|
|
formspec = formspec .. ','
|
|
|
|
end
|
|
|
|
rendered_elems = rendered_elems + 1
|
|
|
|
formspec = formspec .. formspec_escape(elem:getAttribute('data-type'))
|
|
|
|
if elem == selected_element then
|
|
|
|
selected = rendered_elems
|
|
|
|
end
|
|
|
|
::continue::
|
|
|
|
end
|
|
|
|
if selected == 0 then
|
|
|
|
selected = rendered_elems + 1
|
|
|
|
if rendered_elems > 0 then
|
|
|
|
formspec = formspec .. ','
|
|
|
|
end
|
|
|
|
formspec = formspec .. '(New element)'
|
|
|
|
end
|
|
|
|
return formspec .. ';' .. selected .. ']'
|
|
|
|
end
|
|
|
|
|
2021-02-26 23:42:19 +01:00
|
|
|
local SCALE = 50
|
|
|
|
local function round_pos(pos)
|
|
|
|
return math.floor(pos * 10 + 0.5) / 10
|
|
|
|
end
|
|
|
|
|
2020-01-31 08:25:17 +01:00
|
|
|
local function show_properties(elem, node)
|
|
|
|
if not properties_elem then
|
|
|
|
properties_elem = document:createElement('div')
|
|
|
|
properties_elem.id = 'formspec_ast-properties'
|
|
|
|
document.body:appendChild(properties_elem)
|
|
|
|
end
|
|
|
|
properties_elem.innerHTML = ''
|
|
|
|
if type(node) ~= 'table' then
|
|
|
|
node = nil
|
|
|
|
end
|
|
|
|
node = node or json.loads(elem:getAttribute('data-formspec_ast'))
|
|
|
|
|
|
|
|
-- Why not do this as a formspec?
|
|
|
|
local callbacks = {}
|
2020-05-31 12:36:24 +02:00
|
|
|
local formspec = draw_elements_list(elem) ..
|
|
|
|
'label[0.25,2.5;Properties for ' .. formspec_escape(node.type) .. ']'
|
|
|
|
local y = 3.5
|
2020-10-21 07:37:38 +02:00
|
|
|
for k_, v in pairs(node) do
|
|
|
|
if k_ == 'type' or k_ == '_transient' then goto continue end
|
2020-01-31 08:25:17 +01:00
|
|
|
|
2020-10-21 07:37:38 +02:00
|
|
|
local k = k_
|
2020-01-31 08:25:17 +01:00
|
|
|
local value_type = type(v)
|
2020-05-31 12:36:24 +02:00
|
|
|
if k == 'opt' or k == 'props' then
|
|
|
|
assert(value_type == 'table')
|
|
|
|
formspec = formspec .. 'label[0.25,' .. y - 0.2 .. ';' ..
|
|
|
|
formspec_escape(get_property_name(k)) .. ' (map)]'
|
|
|
|
y = y + 0.1
|
|
|
|
local i = 0
|
2020-10-21 07:37:38 +02:00
|
|
|
for prop_, value in pairs(v) do
|
|
|
|
local prop = prop_
|
2020-05-31 12:36:24 +02:00
|
|
|
i = i + 1
|
|
|
|
formspec = formspec .. 'label[0.4,' .. y + 0.3 .. ';•]' ..
|
|
|
|
'field[0.7,' .. y .. ';1.95,0.6;' ..
|
|
|
|
formspec_escape('map1[' .. i .. ']' .. k) .. ';;' ..
|
|
|
|
formspec_escape(tostring(prop)) .. ']' ..
|
|
|
|
'label[2.7,' .. y + 0.3 .. ';=]' ..
|
|
|
|
'field[2.95,' .. y .. ';2,0.6;' ..
|
|
|
|
formspec_escape('map2[' .. i .. ']' .. k) .. ';;' ..
|
|
|
|
formspec_escape(tostring(value)) .. ']' ..
|
|
|
|
'button[5.15,' .. y .. ';0.6,0.6;' ..
|
|
|
|
formspec_escape('map-' .. i .. ':' .. k) .. ';X]'
|
|
|
|
y = y + 0.8
|
|
|
|
|
|
|
|
callbacks['map-' .. i .. ':' .. k] = function()
|
|
|
|
node[k] = get_properties_map(k)
|
|
|
|
node[k][prop] = nil
|
|
|
|
show_properties(elem, node)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
formspec = formspec .. 'button[0.25,' .. y .. ';5.5,0.6;' ..
|
|
|
|
formspec_escape('props+' .. k) .. ';Add item]'
|
|
|
|
callbacks['props+' .. k] = function()
|
|
|
|
node[k] = get_properties_map(k)
|
|
|
|
node[k][''] = ''
|
|
|
|
show_properties(elem, node)
|
|
|
|
end
|
|
|
|
y = y + 1.3
|
|
|
|
goto continue
|
|
|
|
elseif value_type == 'table' then
|
2020-01-31 08:25:17 +01:00
|
|
|
-- This table generation code is bad, the entire properties
|
|
|
|
-- formspec is redrawn when a table element is deleted/created,
|
|
|
|
-- however the "reset" button works.
|
|
|
|
formspec = formspec .. 'label[0.25,' .. y - 0.2 .. ';' ..
|
2020-02-01 03:31:57 +01:00
|
|
|
formspec_escape(get_property_name(k)) .. ' (list)]'
|
2020-01-31 08:25:17 +01:00
|
|
|
y = y + 0.1
|
|
|
|
for i, item in ipairs(v) do
|
|
|
|
formspec = formspec .. 'label[0.4,' .. y + 0.3 .. ';•]' ..
|
|
|
|
'field[0.7,' .. y .. ';4.25,0.6;' ..
|
|
|
|
formspec_escape('list[' .. i .. ']' .. k) .. ';;' ..
|
|
|
|
formspec_escape(tostring(item)) .. ']' ..
|
|
|
|
'button[5.15,' .. y .. ';0.6,0.6;' ..
|
|
|
|
formspec_escape('list-' .. i .. ':' .. k) .. ';X]'
|
|
|
|
y = y + 0.8
|
|
|
|
callbacks['list-' .. i .. ':' .. k] = function()
|
|
|
|
node[k] = get_properties_list(k)
|
|
|
|
table.remove(node[k], i)
|
|
|
|
show_properties(elem, node)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
formspec = formspec .. 'button[0.25,' .. y .. ';5.5,0.6;' ..
|
|
|
|
formspec_escape('list+' .. k) .. ';Add item]'
|
|
|
|
callbacks['list+' .. k] = function()
|
|
|
|
node[k] = get_properties_list(k)
|
|
|
|
table.insert(node[k], '')
|
|
|
|
show_properties(elem, node)
|
|
|
|
end
|
|
|
|
y = y + 1.3
|
|
|
|
goto continue
|
|
|
|
end
|
|
|
|
|
|
|
|
if value_type == 'boolean' then
|
|
|
|
formspec = formspec .. 'checkbox[0.25,' .. y
|
|
|
|
y = y + 0.8
|
|
|
|
else
|
|
|
|
formspec = formspec .. 'field[0.25,' .. y .. ';5.5,0.6'
|
|
|
|
y = y + 1.1
|
|
|
|
end
|
|
|
|
formspec = formspec .. ';' .. formspec_escape('prop_' .. k) .. ';' ..
|
2020-02-01 03:31:57 +01:00
|
|
|
formspec_escape(get_property_name(k) .. ' (' .. value_type .. ')')
|
|
|
|
.. ';' .. formspec_escape(tostring(v)) .. ']'
|
2020-01-31 08:25:17 +01:00
|
|
|
::continue::
|
|
|
|
end
|
|
|
|
|
|
|
|
if node._transient then
|
|
|
|
formspec = formspec ..
|
|
|
|
'button[0.25,' .. y .. ';2.7,0.75;delete;Cancel]' ..
|
|
|
|
'button[3.05,' .. y .. ';2.7,0.75;reset;Reset]'
|
|
|
|
y = y + 0.85
|
|
|
|
else
|
|
|
|
formspec = formspec ..
|
|
|
|
'button[0.25,' .. y .. ';2.7,0.75;send_to_back;Send to back]' ..
|
|
|
|
'button[3.05,' .. y .. ';2.7,0.75;bring_to_front;Bring to front]' ..
|
2024-03-22 02:58:39 +01:00
|
|
|
'button[0.25,' .. y + 0.85 .. ';5.5,0.75;duplicate;Duplicate]' ..
|
|
|
|
'button[0.25,' .. y + 1.7 .. ';2.7,0.75;delete;Delete element]' ..
|
|
|
|
'button[3.05,' .. y + 1.7 .. ';2.7,0.75;reset;Reset]'
|
|
|
|
y = y + 2.55
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
formspec = 'formspec_version[2]size[6,' .. y + 1.25 .. ']' .. formspec ..
|
2020-02-01 04:57:03 +01:00
|
|
|
'button[0.25,' .. y.. ';5.5,1;save;'
|
|
|
|
if node.type == 'size' then
|
|
|
|
formspec = formspec .. 'Resize formspec'
|
|
|
|
elseif node._transient then
|
|
|
|
formspec = formspec .. 'Create element'
|
|
|
|
else
|
|
|
|
formspec = formspec .. 'Save changes'
|
|
|
|
end
|
|
|
|
formspec = formspec .. ']'
|
2020-01-31 08:25:17 +01:00
|
|
|
|
2024-03-22 02:58:39 +01:00
|
|
|
local counter = 0
|
|
|
|
function callbacks.duplicate()
|
|
|
|
local parent = elem.parentNode
|
|
|
|
elem = elem:cloneNode(true)
|
|
|
|
parent:appendChild(elem)
|
|
|
|
counter = counter + 1
|
|
|
|
-- Set up drag+drop
|
|
|
|
renderer.default_elem_hook(node, elem, SCALE)
|
|
|
|
properties_elem:querySelector(
|
|
|
|
'[data-formspec_ast-name="duplicate"]'
|
|
|
|
).textContent = "Duplicated x" .. counter
|
|
|
|
end
|
|
|
|
|
2020-01-31 08:25:17 +01:00
|
|
|
function callbacks.delete()
|
|
|
|
if js.global:confirm('Are you sure?') then
|
|
|
|
elem.parentNode:removeChild(elem)
|
|
|
|
properties_elem.innerHTML = ''
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function callbacks.reset()
|
|
|
|
show_properties(elem)
|
|
|
|
end
|
|
|
|
|
|
|
|
function callbacks.save()
|
|
|
|
local elems = properties_elem.firstChild.firstChild.children
|
2020-05-31 12:36:24 +02:00
|
|
|
local keys = {}
|
2020-01-31 08:25:17 +01:00
|
|
|
for i = 0, elems.length - 1 do
|
2020-02-01 04:57:03 +01:00
|
|
|
local e = elems[i]
|
|
|
|
local name = e:getAttribute('data-formspec_ast-name')
|
2020-01-31 08:25:17 +01:00
|
|
|
local prefix = type(name) == 'string' and name:sub(1, 5)
|
|
|
|
if prefix == 'prop_' then
|
|
|
|
local k = name:sub(6)
|
|
|
|
if type(node[k]) == 'string' then
|
2020-02-01 04:57:03 +01:00
|
|
|
node[k] = e.lastChild.value
|
2020-01-31 08:25:17 +01:00
|
|
|
elseif type(node[k]) == 'number' then
|
2020-02-01 03:31:57 +01:00
|
|
|
-- Allow commas to be used as decimal points.
|
2020-02-01 04:57:03 +01:00
|
|
|
local raw = e.lastChild.value:gsub(',', '.')
|
2020-02-01 03:31:57 +01:00
|
|
|
node[k] = tonumber(raw) or node[k]
|
2020-01-31 08:25:17 +01:00
|
|
|
elseif type(node[k]) == 'boolean' then
|
2020-02-01 04:57:03 +01:00
|
|
|
node[k] = e:getAttribute('data-checked') == 'true'
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
elseif prefix == 'list[' then
|
2020-02-01 04:57:03 +01:00
|
|
|
local s = name:find(']', nil, true)
|
|
|
|
local k = name:sub(s + 1)
|
|
|
|
node[k][tonumber(name:sub(6, s - 1))] = e.lastChild.value
|
2020-05-31 12:36:24 +02:00
|
|
|
elseif prefix == 'map1[' then
|
|
|
|
local s = name:find(']', nil, true)
|
|
|
|
local k = name:sub(s + 1)
|
|
|
|
if not keys[k] then
|
|
|
|
keys[k] = {}
|
|
|
|
node[k] = {}
|
|
|
|
end
|
|
|
|
keys[k][tonumber(name:sub(6, s - 1))] = e.lastChild.value
|
|
|
|
elseif prefix == 'map2[' then
|
|
|
|
local s = name:find(']', nil, true)
|
|
|
|
local k = name:sub(s + 1)
|
|
|
|
local key = keys[k][tonumber(name:sub(6, s - 1))]
|
|
|
|
node[k][key] = e.lastChild.value
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
end
|
2020-05-05 08:42:48 +02:00
|
|
|
|
|
|
|
if node.type == 'image_button' and node.texture_name == '' then
|
|
|
|
node.texture_name = 'blank.png'
|
|
|
|
end
|
|
|
|
|
2020-01-31 08:25:17 +01:00
|
|
|
node._transient = nil
|
|
|
|
elem:setAttribute('data-formspec_ast', json.dumps(node))
|
|
|
|
properties_elem.innerHTML = ''
|
|
|
|
local base = elem.parentNode.parentNode
|
2020-02-01 08:29:06 +01:00
|
|
|
assert(base.classList:contains('formspec_ast-base'))
|
2020-02-03 04:09:18 +01:00
|
|
|
local idx = window.Array.prototype.indexOf(elem.parentNode.children,
|
|
|
|
elem)
|
|
|
|
base = renderer.redraw_formspec(base)
|
2020-02-03 04:24:57 +01:00
|
|
|
if node.type == 'size' then
|
|
|
|
renderer.add_element(base, 'size')
|
|
|
|
elseif idx >= 0 then
|
|
|
|
show_properties(base.firstChild.children[idx])
|
|
|
|
end
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
function callbacks.send_to_back()
|
|
|
|
local parent = elem.parentNode
|
|
|
|
parent:removeChild(elem)
|
|
|
|
parent:prepend(elem)
|
2020-05-31 12:36:24 +02:00
|
|
|
show_properties(elem, node)
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
function callbacks.bring_to_front()
|
|
|
|
local parent = elem.parentNode
|
|
|
|
parent:removeChild(elem)
|
|
|
|
parent:appendChild(elem)
|
2020-05-31 12:36:24 +02:00
|
|
|
show_properties(elem, node)
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
|
2020-02-01 08:29:06 +01:00
|
|
|
local n = assert(renderer.render_formspec(formspec, callbacks,
|
|
|
|
{store_json = false}))
|
2020-05-31 12:36:24 +02:00
|
|
|
|
|
|
|
local elems = n.firstChild.children
|
|
|
|
for i = 0, elems.length - 1 do
|
|
|
|
local elem2 = elems[i]
|
|
|
|
local name = elem2:getAttribute('data-formspec_ast-name')
|
|
|
|
if name == 'selected_element' then
|
|
|
|
elem2:addEventListener('change', function()
|
|
|
|
local idx = elem2.firstChild.selectedIndex
|
|
|
|
show_properties(elem.parentElement.children[idx])
|
|
|
|
end)
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-01-31 08:25:17 +01:00
|
|
|
properties_elem:appendChild(n)
|
|
|
|
end
|
2021-02-26 23:42:19 +01:00
|
|
|
|
|
|
|
-- Set up drag+drop. This is mostly done in JavaScript for performance.
|
2022-03-29 05:51:29 +02:00
|
|
|
function renderer.default_elem_hook(node, elem, scale)
|
2021-02-26 23:42:19 +01:00
|
|
|
local basic_interact = js.global.basic_interact
|
|
|
|
if not basic_interact then return show_properties end
|
|
|
|
|
|
|
|
local draggable = node.x ~= nil and node.y ~= nil
|
|
|
|
local resizable = node.w ~= nil and node.h ~= nil and node.type ~= "list"
|
|
|
|
|
2022-03-29 05:51:29 +02:00
|
|
|
local small_resize_margin = false
|
|
|
|
if resizable and (node.w * scale < 60 or node.h * scale < 60) then
|
|
|
|
small_resize_margin = true
|
|
|
|
end
|
|
|
|
|
2021-02-26 23:42:19 +01:00
|
|
|
local orig_x, orig_y = node.x, node.y
|
|
|
|
basic_interact:add(elem, draggable, resizable, function(_, x, y, w, h)
|
|
|
|
local modified
|
|
|
|
if draggable and x then
|
|
|
|
node.x = round_pos(orig_x + x / SCALE)
|
|
|
|
node.y = round_pos(orig_y + y / SCALE)
|
|
|
|
modified = true
|
|
|
|
end
|
|
|
|
if resizable and w then
|
|
|
|
node.w = round_pos(math.max(w / SCALE, 0.1))
|
|
|
|
node.h = round_pos(math.max(h / SCALE, 0.1))
|
|
|
|
modified = true
|
|
|
|
end
|
|
|
|
|
|
|
|
if modified then
|
|
|
|
elem:setAttribute('data-formspec_ast', json.dumps(node))
|
|
|
|
local idx = window.Array.prototype.indexOf(
|
|
|
|
elem.parentNode.children, elem)
|
|
|
|
local base = renderer.redraw_formspec(elem.parentNode.parentNode)
|
|
|
|
if idx >= 0 then
|
|
|
|
show_properties(base.firstChild.children[idx])
|
|
|
|
end
|
|
|
|
else
|
|
|
|
show_properties(elem)
|
|
|
|
end
|
2022-03-29 05:51:29 +02:00
|
|
|
end, small_resize_margin)
|
2021-02-26 23:42:19 +01:00
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
2020-01-31 08:25:17 +01:00
|
|
|
|
|
|
|
-- Templates for new elements
|
|
|
|
do
|
|
|
|
local templates = assert(formspec_ast.parse([[
|
|
|
|
size[10.5,11]
|
|
|
|
box[0,0;1,1;]
|
2021-02-26 23:42:19 +01:00
|
|
|
button[0,0;3,0.8;;]
|
|
|
|
button_exit[0,0;3,0.8;;]
|
2020-01-31 08:25:17 +01:00
|
|
|
checkbox[0,0.2;;;false]
|
2022-02-08 07:23:33 +01:00
|
|
|
dropdown[0,0;3,0.8;;;1;false]
|
2021-02-26 23:42:19 +01:00
|
|
|
field[0,0;3,0.8;;;]
|
2020-01-31 08:25:17 +01:00
|
|
|
image[0,0;1,1;]
|
|
|
|
image_button[0,0;2,2;;;;false;true;]
|
|
|
|
image_button_exit[0,0;2,2;;;]
|
|
|
|
label[0,0.2;]
|
|
|
|
list[current_player;main;0,0;8,4;0]
|
2021-02-26 23:42:19 +01:00
|
|
|
pwdfield[0,0;3,0.8;;]
|
2020-01-31 08:25:17 +01:00
|
|
|
textarea[0,0;3,2;;;]
|
|
|
|
textlist[0,0;5,3;;;1;false]
|
|
|
|
]]))
|
|
|
|
renderer.templates = {}
|
|
|
|
for _, node in ipairs(templates) do
|
|
|
|
renderer.templates[node.type] = node
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function renderer.add_element(base, node_type)
|
|
|
|
local elem = base.firstChild.lastChild
|
|
|
|
if elem == js.null or elem:getAttribute('data-transient') ~= 'true' then
|
2020-02-01 04:57:03 +01:00
|
|
|
elem = renderer.make('div')
|
2020-01-31 08:25:17 +01:00
|
|
|
elem.style.display = 'none'
|
|
|
|
base.firstChild:appendChild(elem)
|
|
|
|
end
|
|
|
|
local template
|
|
|
|
if node_type == 'size' then
|
|
|
|
template = {
|
|
|
|
type = 'size',
|
|
|
|
w = tonumber(base:getAttribute('data-w')) or 0,
|
|
|
|
h = tonumber(base:getAttribute('data-h')) or 0,
|
|
|
|
}
|
|
|
|
else
|
|
|
|
template = assert(renderer.templates[node_type], 'Unknown node!')
|
|
|
|
end
|
|
|
|
template._transient = true
|
|
|
|
elem:setAttribute('data-formspec_ast', json.dumps(template))
|
|
|
|
elem:setAttribute('data-transient', 'true')
|
|
|
|
show_properties(elem)
|
|
|
|
end
|
|
|
|
|
2020-02-01 04:57:03 +01:00
|
|
|
local element_dialog_base
|
|
|
|
do
|
|
|
|
local replace_formspec = renderer.replace_formspec
|
2020-02-01 08:29:06 +01:00
|
|
|
function renderer.replace_formspec(elem, ...)
|
|
|
|
local new_elem, err = replace_formspec(elem, ...)
|
2020-02-01 04:57:03 +01:00
|
|
|
if new_elem and element_dialog_base == elem then
|
|
|
|
element_dialog_base = new_elem
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
2020-02-01 04:57:03 +01:00
|
|
|
return new_elem, err
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-01 03:31:57 +01:00
|
|
|
local function render_into(base, formspec, callbacks)
|
|
|
|
base.innerHTML = ''
|
|
|
|
base:appendChild(assert(renderer.render_formspec(formspec, callbacks,
|
2020-02-01 08:29:06 +01:00
|
|
|
{store_json = false})))
|
2020-02-01 03:31:57 +01:00
|
|
|
end
|
|
|
|
|
2020-01-31 08:25:17 +01:00
|
|
|
local element_dialog
|
2022-02-08 07:20:27 +01:00
|
|
|
local load_save_opts = {multiline = true}
|
2020-01-31 08:25:17 +01:00
|
|
|
local function show_load_save_dialog()
|
|
|
|
local callbacks = {}
|
2020-02-01 04:57:03 +01:00
|
|
|
local formspec = [[
|
2021-03-19 05:38:59 +01:00
|
|
|
formspec_version[4]size[6,12]button[0,0;1,0.6;back;←]
|
2020-01-31 08:25:17 +01:00
|
|
|
label[1.25,0.3;Load / save formspec]
|
|
|
|
checkbox[0.25,1.3;use_v1;Use formspec version 1;]] ..
|
|
|
|
(load_save_opts.use_v1 and 'true' or 'false') .. [[]
|
|
|
|
label[0.75,1.9;Use this if you need compatibility]
|
|
|
|
label[0.75,2.3;with Minetest 5.0.1 or earlier.]
|
|
|
|
label[0.75,3;This only works when saving.]
|
|
|
|
checkbox[0.25,4;format;Convert ${...} to lua expressions;]] ..
|
|
|
|
(load_save_opts.format and 'true' or 'false') .. [[]
|
|
|
|
label[0.75,4.6;When this is enabled\, lua]
|
|
|
|
label[0.75,5;expressions can be used inside]
|
|
|
|
label[0.75,5.4;${...}. Formspec escaping is]
|
|
|
|
label[0.75,5.8;handled automatically.]
|
2021-03-19 05:38:59 +01:00
|
|
|
checkbox[0.25,6.8;multiline;One element per line;]] ..
|
|
|
|
(load_save_opts.multiline and 'true' or 'false') .. [[]
|
|
|
|
button[0.25,7.75;5.5,1;load;Load formspec]
|
|
|
|
button[0.25,9;5.5,1;save;Save formspec]
|
|
|
|
box[0,10.369;6,0.02;#aaa]
|
2024-03-22 03:02:27 +01:00
|
|
|
button[0.25,10.75;5.5,1;digistuff_ts;Export to digistuff touchscreen]
|
2020-01-31 08:25:17 +01:00
|
|
|
]]
|
|
|
|
local function get_options()
|
|
|
|
local elems = element_dialog.firstChild.firstChild.children
|
|
|
|
for i = 0, #elems - 1 do
|
|
|
|
local elem = elems[i]
|
|
|
|
local name = elem:getAttribute('data-formspec_ast-name')
|
|
|
|
local checked = elem:getAttribute('data-checked')
|
|
|
|
if type(name) == 'string' and type(checked) == 'string' then
|
|
|
|
load_save_opts[name] = checked == 'true'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function callbacks.back()
|
|
|
|
get_options()
|
|
|
|
renderer.show_element_dialog(element_dialog_base)
|
|
|
|
end
|
|
|
|
|
2020-02-01 03:31:57 +01:00
|
|
|
local function load()
|
|
|
|
local textarea = element_dialog.firstChild.firstChild.lastChild
|
|
|
|
local fs = textarea.lastChild.value
|
2020-01-31 08:25:17 +01:00
|
|
|
local tree, err = renderer.import(fs, load_save_opts)
|
|
|
|
if not tree then
|
|
|
|
window:alert('Error loading formspec:\n' .. err)
|
2020-02-01 00:36:45 +01:00
|
|
|
return
|
|
|
|
end
|
2020-02-01 04:57:03 +01:00
|
|
|
local elem
|
|
|
|
elem, err = renderer.replace_formspec(element_dialog_base, tree)
|
2020-02-01 00:36:45 +01:00
|
|
|
if not elem then
|
|
|
|
window:alert('Error loading formspec:\n' .. err)
|
|
|
|
return
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
renderer.show_element_dialog(element_dialog_base)
|
2020-02-03 04:13:33 +01:00
|
|
|
if properties_elem then
|
|
|
|
properties_elem.innerHTML = ''
|
|
|
|
end
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
|
2020-02-01 03:31:57 +01:00
|
|
|
function callbacks.load()
|
|
|
|
get_options()
|
|
|
|
local fs = 'formspec_version[2]size[6,9.5]button[0,0;1,0.6;back;←]' ..
|
|
|
|
'label[1.25,0.3;Load formspec]' ..
|
|
|
|
'button[0.25,8.25;5.5,1;load;Load formspec]' ..
|
|
|
|
'textarea[0.25,1.25;5.5,6.75;formspec;Paste your formspec here.;]'
|
|
|
|
render_into(element_dialog, fs, {
|
|
|
|
back = show_load_save_dialog,
|
|
|
|
load = load
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
2020-10-22 10:16:21 +02:00
|
|
|
local function save_dialog(name, res, err)
|
2020-01-31 08:25:17 +01:00
|
|
|
element_dialog.innerHTML = ''
|
2020-10-21 07:37:38 +02:00
|
|
|
local label, msg
|
2020-01-31 08:25:17 +01:00
|
|
|
if res then
|
2020-10-21 07:37:38 +02:00
|
|
|
label, msg = 'Formspec exported successfully.', res
|
2020-01-31 08:25:17 +01:00
|
|
|
else
|
2020-10-21 07:37:38 +02:00
|
|
|
label, msg = 'Error exporting formspec!', err
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
2020-10-21 07:37:38 +02:00
|
|
|
local fs = 'formspec_version[2]size[6,9.5]button[0,0;1,0.6;back;←]' ..
|
2020-10-22 10:16:21 +02:00
|
|
|
'label[1.25,0.3;' .. name .. ']textarea[0.25,1.25;5.5,8;result;' ..
|
2020-10-21 07:37:38 +02:00
|
|
|
label .. ';' .. formspec_escape(msg) .. ']'
|
2020-02-01 03:31:57 +01:00
|
|
|
render_into(element_dialog, fs, {
|
2020-01-31 08:25:17 +01:00
|
|
|
back = show_load_save_dialog,
|
2020-02-01 03:31:57 +01:00
|
|
|
})
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
|
2020-10-21 07:37:38 +02:00
|
|
|
function callbacks.save()
|
|
|
|
get_options()
|
|
|
|
local tree = renderer.elem_to_ast(element_dialog_base)
|
2020-10-22 10:16:21 +02:00
|
|
|
save_dialog('Save formspec', renderer.export(tree, load_save_opts))
|
2020-10-21 07:37:38 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
function callbacks.digistuff_ts()
|
|
|
|
get_options()
|
|
|
|
local tree = renderer.elem_to_ast(element_dialog_base)
|
|
|
|
local f = load_save_opts.use_v1 and renderer.fs51_backport or nil
|
2020-10-22 10:16:21 +02:00
|
|
|
save_dialog('Export formspec', digistuff_ts_export(tree, f))
|
2020-10-21 07:37:38 +02:00
|
|
|
end
|
|
|
|
|
2020-02-01 04:57:03 +01:00
|
|
|
render_into(element_dialog, formspec, callbacks)
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
function renderer.show_element_dialog(base)
|
|
|
|
element_dialog_base = base
|
|
|
|
if not element_dialog then
|
|
|
|
element_dialog = document:createElement('div')
|
|
|
|
element_dialog.id = 'formspec_ast-new'
|
|
|
|
document.body:appendChild(element_dialog)
|
|
|
|
end
|
|
|
|
element_dialog.innerHTML = ''
|
|
|
|
|
|
|
|
local fs = 'label[0.25,0.5;Add elements]'
|
|
|
|
local callbacks = {}
|
|
|
|
local y = 1.25
|
|
|
|
|
|
|
|
for name, def in pairs(renderer.templates) do
|
|
|
|
fs = fs .. 'button[0.25,' .. y .. ';5.5,0.75;' ..
|
|
|
|
formspec_escape('add_' .. name) .. ';' ..
|
|
|
|
formspec_escape(formspec_ast.unparse({def})) .. ']'
|
|
|
|
y = y + 1
|
|
|
|
local node_type = name
|
|
|
|
callbacks['add_' .. name] = function()
|
|
|
|
renderer.add_element(element_dialog_base, node_type)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
y = y + 0.5
|
2020-02-01 08:29:06 +01:00
|
|
|
fs = fs .. 'button[0.25,' .. y .. ';5.5,0.75;grid;Toggle grid]'
|
2021-02-26 23:42:19 +01:00
|
|
|
y = y + 1
|
|
|
|
fs = fs .. 'button[0.25,' .. y .. ';5.5,0.75;load;Load / save formspec]'
|
2020-02-01 08:29:06 +01:00
|
|
|
function callbacks.grid()
|
|
|
|
local raw = element_dialog_base:getAttribute('data-render-options')
|
|
|
|
if raw == js.null then raw = '{}' end
|
|
|
|
local options = json.loads(raw)
|
|
|
|
options.grid = not options.grid
|
|
|
|
raw = assert(json.dumps(options))
|
|
|
|
element_dialog_base:setAttribute('data-render-options', raw)
|
|
|
|
renderer.redraw_formspec(element_dialog_base)
|
2020-02-03 04:44:28 +01:00
|
|
|
if properties_elem then
|
|
|
|
properties_elem.innerHTML = ''
|
|
|
|
end
|
2020-02-01 08:29:06 +01:00
|
|
|
end
|
2020-01-31 08:25:17 +01:00
|
|
|
callbacks.load = show_load_save_dialog
|
2021-02-26 23:42:19 +01:00
|
|
|
|
|
|
|
if js.global.basic_interact then
|
|
|
|
y = y + 1
|
|
|
|
fs = fs .. 'button[0.25,' .. y ..
|
|
|
|
';5.5,0.75;drag_drop;Disable drag+drop]'
|
|
|
|
function callbacks.drag_drop()
|
|
|
|
window.location.search = '?no-drag-drop'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
fs = 'formspec_version[3]size[6,' .. y + 1 .. ']' .. fs
|
2020-01-31 08:25:17 +01:00
|
|
|
element_dialog:appendChild(assert(renderer.render_formspec(fs, callbacks,
|
2020-02-01 08:29:06 +01:00
|
|
|
{store_json = false})))
|
2020-01-31 08:25:17 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- A JS API for testing
|
2020-02-01 08:29:06 +01:00
|
|
|
function window:render_formspec(fs, callbacks, options)
|
2020-01-31 08:25:17 +01:00
|
|
|
local tree = assert(formspec_ast.parse(fs))
|
2020-02-01 08:29:06 +01:00
|
|
|
local elem = assert(renderer.render_ast(tree, callbacks, options))
|
2020-01-31 23:13:19 +01:00
|
|
|
local e = document:getElementById('formspec_output')
|
2020-02-01 04:57:03 +01:00
|
|
|
if not e or e == js.null then
|
2020-01-31 23:13:19 +01:00
|
|
|
window:addEventListener('load', function()
|
|
|
|
window:render_formspec(fs, callbacks)
|
|
|
|
end)
|
|
|
|
return
|
|
|
|
end
|
2020-01-31 08:25:17 +01:00
|
|
|
e.innerHTML = ''
|
|
|
|
e:appendChild(elem)
|
|
|
|
renderer.show_element_dialog(elem)
|
|
|
|
end
|
|
|
|
|
|
|
|
function window:copy_formspec()
|
|
|
|
local e = assert(document:getElementById('formspec_output')).firstChild
|
|
|
|
window:alert(formspec_ast.unparse(renderer.elem_to_ast(e)))
|
|
|
|
end
|
|
|
|
|
|
|
|
function window:unrender_formspec(elem)
|
|
|
|
return renderer.unrender_formspec(elem)
|
|
|
|
end
|
|
|
|
|
|
|
|
function window:redraw_formspec(elem)
|
|
|
|
return renderer.redraw_formspec(elem)
|
|
|
|
end
|
|
|
|
|
|
|
|
function window:add_element(node_type)
|
|
|
|
local e = assert(document:getElementById('formspec_output')).firstChild
|
|
|
|
renderer.add_element(e, node_type)
|
|
|
|
end
|
|
|
|
|
|
|
|
function window:make_image(...)
|
|
|
|
return renderer.make_image(...)
|
|
|
|
end
|
|
|
|
|
2020-01-31 09:28:03 +01:00
|
|
|
window:render_formspec('formspec_version[2]size[10.5,11]')
|