Merge branch 'interaction-nested-functions' into 'master'

Moved code over to nested function approach, working

See merge request medlabboulder/modpol!27
This commit is contained in:
Nathan Schneider 2021-08-04 23:02:32 +00:00
commit fa4283dce6
8 changed files with 479 additions and 188 deletions

View File

@ -9,22 +9,46 @@ modpol.interactions = {}
-- DASHBOARDS -- DASHBOARDS
-- ========== -- ==========
-- Function: modpol.dashboard(user) function modpol.interactions.login()
print("Log in as which user?")
local username = io.read()
while true do
local org = modpol.interactions.dashboard(username)
if org then
modpol.interactions.org_dashboard(username, org)
else
break
end
end
end
-- Function: modpol.interactions.dashboard(user)
-- Params: user (string) -- Params: user (string)
-- Q: Should this return a menu of commands relevant to the specific user? -- Q: Should this return a menu of commands relevant to the specific user?
-- Output: Displays a menu of commands to the user -- Output: Displays a menu of commands to the user
-- TKTK currently just prints all of modpol---needs major improvement -- TKTK currently just prints all of modpol---needs major improvement
function modpol.dashboard(user) function modpol.interactions.dashboard(user)
local output = "" print()
-- Org status local org_list = modpol.orgs.list_all()
output = output .. "Orgs:\n" ..
table.concat(modpol.orgs.list_all(),"\n") print('Select an org')
-- Command list (should be redone to be org-specific) for i, org_name in ipairs(org_list) do
output = output .. "\nCommand list:\n" local org = modpol.orgs.get_org(org_name)
for key,value in pairs(modpol) do local indicator = ""
output = output .. "modpol." .. key .. " " if org:has_pending_actions(user) then
indicator = "*"
end
print('['..i..']'..' '..org_name..indicator)
end end
print(output)
local sel = io.read()
local sel_org = org_list[tonumber(sel)]
if not sel_org then return end
modpol.interactions.org_dashboard(user, sel_org)
end end
@ -32,40 +56,58 @@ end
-- Params: user (string), org_name (string) -- Params: user (string), org_name (string)
-- Output: Displays a menu of org-specific commands to the user -- Output: Displays a menu of org-specific commands to the user
function modpol.interactions.org_dashboard(user, org_name) function modpol.interactions.org_dashboard(user, org_name)
print()
local org = modpol.orgs.get_org(org_name) local org = modpol.orgs.get_org(org_name)
if not org then return nil end if not org then return nil end
local is_member = org:has_member(user)
local membership_toggle = function()
local toggle_code = ""
if is_member then
toggle_code = "[Leave]"
else
toggle_code = "[Join]"
end
return toggle_code
end
local children = {} local children = {}
for k,v in ipairs(org.children) do for k,v in ipairs(org.children) do
local this_child = modpol.orgs.get_org(v) local this_child = modpol.orgs.get_org(v)
table.insert(children, this_child.name) table.insert(children, this_child.name)
end end
local processes = {}
for k,v in ipairs(org.processes) do
print(k, v)
local this_request = org.requests[v.request_id]
if type(this_request) == "table" then
local active = ''
if org.pending[user] then
if org.pending[user][v.id] then
active = '*'
end
end
local req_str = "[" .. v.id .. "] " ..
active .. this_request.type
if this_request.params[1] then
req_str = req_str ": " ..
table.concat(this_request.params, ", ")
end
local req_str = v.id .. " (" .. this_request.type .. " -> " .. table.concat(this_request.params, ", ") .. ")" .. active
table.insert(processes, req_str)
end
end
-- set up output -- set up output
local dashboard_table = { print("Org: " .. org_name)
"Org: " .. org_name, print("Members: " .. table.concat(org.members, ", "))
membership_toggle(), print("Children: " .. table.concat(children, ", "))
"Members: " .. table.concat(org.members, ", "), print("Processes: " .. table.concat(processes, ", "))
"Children: " .. table.concat(children, ", "),
"Policies: " .. table.concat(org.policies, ", "), print('Interact with which process?')
"Processes: " .. table.concat(org.processes, ", "), local sel = io.read()
"[Add child]", local process = org.processes[tonumber(sel)]
"[Remove org]", if not process then return end
"[Dashboard: modpol.dashboard()]" process:interact(user)
}
-- present to player
print(table.concat(dashboard_table, "\n"))
end end
-- =================================================================== -- Function: modpol.interactions.policy_dashboard
-- input: user (string), org_id (int), policy (string)
-- if policy is nil, enables creating a new policy
-- output: opens a dashboard for viewing/editing policy details
-- TODO
-- Function: modpol.interactions.message -- Function: modpol.interactions.message
-- input: user (string), message (string) -- input: user (string), message (string)
-- output: prints message to CLI -- output: prints message to CLI
@ -73,31 +115,86 @@ function modpol.interactions.message(user, message)
print(user .. ": " .. message) print(user .. ": " .. message)
end end
-- ===================================================================
-- Function: modpol.interactions.text_query -- Function: modpol.interactions.text_query
-- input: Query (string) -- input: User (string), Query (string), func (function)
-- output: User response (string) -- func input: user input (string)
function modpol.interactions.text_query(query) -- output: Applies "func" to user input
-- TODO function modpol.interactions.text_query(user, query, func)
print(user .. ": " .. query)
answer = io.read()
func(answer)
end
-- Function: dropdown_query
-- input: user (string), label (string), options (table of strings), func(choice) (function)
-- func input: choice (string)
-- output: calls func on choice
function modpol.interactions.dropdown_query(user, label, options, func)
-- set up options
local options_display = ""
local options_number = 0
for k,v in ipairs(options) do
options_display = options_display .. k .. ". " ..
options[k] .. "\n"
options_number = options_number + 1
end
options_display = options_display .. "Select number:"
if options_number == 0 then
print("Error: No options given for dropdown")
return nil
end
-- begin displaying
print(user .. ": " .. label)
print(options_display)
-- read input and produce output
local answer
answer = io.read()
answer = tonumber(answer)
if answer then
if answer >= 1 and answer <= options_number then
print("Selection: " .. options[answer])
func(options[answer])
else
print("Error: Not in dropdown range")
return nil
end
else
print("Error: Must be a number")
return nil
end
end end
-- ===================================================================
-- Function: modpol.binary_poll_user(user, question) -- Function: modpol.binary_poll_user(user, question)
-- Params: user (string), question (string) -- Params: user (string), question (string), func (function)
-- Output: -- func input: user input (string: y/n)
-- presents a yes/no/abstain poll to a user, returns answer -- Output: Applies "func" to user input
function modpol.interactions.binary_poll_user(user, question) function modpol.interactions.binary_poll_user(user, question, func)
local query = "Poll for " .. user .. " (y/n): ".. question local query = "Poll for " .. user .. " (y/n): ".. question
local answer local answer
repeat repeat
print(query) print(query)
answer = io.read() answer = io.read()
until answer == "y" or answer == "n" or answer == "a" until answer == "y" or answer == "n"
if answer == "y" then if answer == "y" then
return "Yes" modpol.interactions.message(user, "Response recorded")
func("yes")
elseif answer == "n" then elseif answer == "n" then
return "No" modpol.interactions.message(user, "Response recorded")
func("no")
else else
return "Abstain" modpol.interactions.message(user, "Error: invalid response")
end end
end end
-- COMPLEX INTERACTIONS
-- ====================
-- Function: modpol.interactions.message_org
-- input: initiator (string), org_id (number), message (string)
-- output: broadcasts message to all org members
-- Function: modpol.interactions.binary_poll_org
-- input: initator (user string), org_id (number)
-- output: gets question from initiator, asks all org members, broadcasts answers

View File

@ -38,6 +38,21 @@ function modpol.modules.consent:new_process(id, request_id, org_id)
return process return process
end end
-- ============================
-- interact function for the consent module
-- input: user (string)
function modpol.modules.consent:interact(user)
-- TODO this needs more context on the vote at hand
modpol.interactions.binary_poll_user(
user, "Do you consent?",
function(vote)
if vote == 'yes' then
self:approve(user, true)
elseif vote == 'no' then
self:approve(user, false)
end
end)
end
-- ========================================= -- =========================================
-- function to delete a process, called when process finishes -- function to delete a process, called when process finishes
@ -90,4 +105,4 @@ function modpol.modules.consent:update_status()
else else
modpol.ocutil.log('Waiting for more votes...') modpol.ocutil.log('Waiting for more votes...')
end end
end end

View File

@ -11,7 +11,12 @@ function temp_org()
return { return {
id = nil, id = nil,
name = nil, name = nil,
policies = {}, policies = {
add_org={process_type='consent', must_be_member=false},
delete={process_type='consent', must_be_member=false},
add_member={process_type='consent', must_be_member=false},
remove_member={process_type='consent', must_be_member=false}
},
processes = {}, processes = {},
requests = {}, requests = {},
pending = {}, pending = {},
@ -96,15 +101,15 @@ function modpol.orgs.init_instance()
local instance = temp_org() local instance = temp_org()
instance.id = 1 instance.id = 1
instance.name = "instance" instance.name = "root"
setmetatable(instance, modpol.orgs) setmetatable(instance, modpol.orgs)
-- adding instance to org list -- adding instance to org list
modpol.orgs.array[1] = instance modpol.orgs.array[1] = instance
modpol.ocutil.log('Initialized the instance org') modpol.ocutil.log('Initialized the instance root org')
modpol.orgs:record('Initialized the instance org', 'create_instance') modpol.orgs:record('Initialized the instance root org', 'create_instance')
return instance return instance
end end
@ -148,6 +153,7 @@ end
-- ================================================== -- ==================================================
-- adds a new sub org to the org it is called on -- adds a new sub org to the org it is called on
-- input: name (string), user (string)
-- ex: instance:add_org('town hall') -- ex: instance:add_org('town hall')
function modpol.orgs:add_org(name, user) function modpol.orgs:add_org(name, user)
if self.id == nil then if self.id == nil then
@ -171,6 +177,7 @@ function modpol.orgs:add_org(name, user)
child_org.id = modpol.orgs.count child_org.id = modpol.orgs.count
child_org.name = name child_org.name = name
child_org.parent = self.id child_org.parent = self.id
child_org.processes = self.processes
setmetatable(child_org, modpol.orgs) setmetatable(child_org, modpol.orgs)
@ -275,18 +282,14 @@ function modpol.orgs:has_member(user)
end end
-- ================================== -- ==================================
-- returns a list of users in an org -- Function: modpol.orgs:list_members()
function modpol.orgs:list_member() -- output: a table of the names (string) of members
local str function modpol.orgs:list_members()
for k, v in ipairs(self.members) do local members = {}
-- checking to see if member name is valid for k, v in ipairs(self.members) do
if str and str ~= '' then table.insert(members, v)
str = str .. '\n' .. v end
else return members
str = v
end
end
return str
end end
-- ============================== -- ==============================
@ -311,6 +314,7 @@ function modpol.orgs:set_policy(policy_type, process_type, must_be_member)
} }
self.policies[policy_type] = new_policy self.policies[policy_type] = new_policy
modpol.ocutil.log('Added policy for ' .. policy_type .. ' in ' .. self.name) modpol.ocutil.log('Added policy for ' .. policy_type .. ' in ' .. self.name)
self:record('Added policy for ' .. policy_type, 'set_policy')
end end

View File

@ -13,10 +13,11 @@ function modpol.orgs:create_process(process_type, request_id)
return return
end end
local empty_index = nil
-- linear search for empty process slots (lazy deletion) -- linear search for empty process slots (lazy deletion)
for k, v in ipairs(self.processes) do for k, v in ipairs(self.processes) do
if v == 'deleted' then if v == 'deleted' then
local empty_index = k empty_index = k
break break
end end
end end
@ -35,6 +36,9 @@ function modpol.orgs:create_process(process_type, request_id)
self.processes[index] = new_process self.processes[index] = new_process
modpol.ocutil.log('Created process #' .. index .. ' - ' .. process_type .. ' for ' .. self.name)
self:record('Created process #' .. index .. ' - ' .. process_type, 'add_process')
return index return index
end end
@ -46,7 +50,8 @@ function modpol.orgs:add_pending_action(process_id, user)
-- flagging actual action -- flagging actual action
self.pending[user][process_id] = true self.pending[user][process_id] = true
modpol.ocutil.log("Added pending action to " .. user .. " in process #" .. process_id) modpol.ocutil.log("Added pending action to " .. user .. " in process #" .. process_id .. ' from ' .. self.name)
self:record('Added pending action to ' .. user .. ' in process #' .. process_id, "add_pending_action")
end end
-- ======================== -- ========================
@ -55,7 +60,8 @@ function modpol.orgs:remove_pending_action(process_id, user)
if self.pending[user] then if self.pending[user] then
self.pending[user][process_id] = nil self.pending[user][process_id] = nil
modpol.ocutil.log("Removed pending action from " .. user .. " in process #" .. process_id) modpol.ocutil.log("Removed pending action from " .. user .. " in process #" .. process_id .. ' from ' .. self.name)
self:record('Removed pending action from ' .. user .. ' in process #' .. process_id, "del_pending_action")
else else
modpol.ocutil.log("Could not remove pending action from " .. user .. " in process #" .. process_id) modpol.ocutil.log("Could not remove pending action from " .. user .. " in process #" .. process_id)
end end
@ -68,6 +74,23 @@ function modpol.orgs:wipe_pending_actions(process_id)
self.pending[user][process_id] = nil self.pending[user][process_id] = nil
end end
modpol.ocutil.log("Removed all pending actions for process #" .. process_id) modpol.ocutil.log("Removed all pending actions for process #" .. process_id)
self:record('Removed all pending actions for process #' .. process_id, "del_pending_action")
end
-- ======================
-- returns a boolean to indicate whether a user has any active pending actions
function modpol.orgs:has_pending_actions(user)
-- next() will return the next pair in a table
-- if next() returns nil, the table is empty
if not self.pending[user] then
return false
else
if not next(self.pending[user]) then
return false
else
return true
end
end
end end
-- =========================== -- ===========================
@ -130,6 +153,9 @@ function modpol.orgs:resolve_request(request_id, approve)
end end
self.requests[request_id] = "deleted" self.requests[request_id] = "deleted"
modpol.ocutil.log("Resolved request #" .. request_id .. ' in ' .. self.name)
self:record("Resolved request #" .. request_id, "resolve_request")
end end
-- ================================ -- ================================
@ -189,10 +215,11 @@ function modpol.orgs:make_request(request)
return false return false
end end
local empty_index = nil
-- linear search for empty process slots (lazy deletion) -- linear search for empty process slots (lazy deletion)
for k, v in ipairs(self.requests) do for k, v in ipairs(self.requests) do
if v == 'deleted' then if v == 'deleted' then
local empty_index = k empty_index = k
break break
end end
end end
@ -206,7 +233,9 @@ function modpol.orgs:make_request(request)
table.insert(self.requests, request) table.insert(self.requests, request)
request_id = #self.requests request_id = #self.requests
end end
modpol.ocutil.log("Request made by " .. request.user .. " to " .. request.type)
modpol.ocutil.log("Request made by " .. request.user .. " to " .. request.type .. " in " .. self.name)
self:record("Request made by " .. request.user .. " to " .. request.type, "make_request")
-- launching process tied to this request -- launching process tied to this request
local process_id = self:create_process(requested_policy.process_type, request_id) local process_id = self:create_process(requested_policy.process_type, request_id)

View File

@ -25,18 +25,17 @@ dofile (modpol.topdir .. "/util/serpent/serpent.lua")
-- =================================================================== -- ===================================================================
-- This function stores "ledger" data to disk. -- This function stores "ledger" data to disk.
local store_ledger = function() local store_ledger = function(verbose)
local ok = modpol.ocutil.file_write (modpol.file_ledger, local ok = modpol.ocutil.file_write (modpol.file_ledger, modpol.serpent.dump (modpol.ledger))
modpol.serpent.dump (modpol.ledger))
if ok ~= true then if ok ~= true then
modpol.ocutil.fatal_error ("store_data: ledger") modpol.ocutil.fatal_error ("store_data: ledger")
end end
local nn = modpol.ocutil.table_length (modpol.ledger) local nn = modpol.ocutil.table_length (modpol.ledger)
local str = "entries" local str = "entries"
if nn == 1 then str = "entry" end if nn == 1 then str = "entry" end
modpol.ocutil.log (nn .. " global ledger entries stored to disk") if verbose then modpol.ocutil.log (nn .. " global ledger entries stored to disk") end
end end
-- =================================================================== -- ===================================================================
@ -52,35 +51,16 @@ local store_orgs = function()
local nn = modpol.ocutil.table_length (modpol.orgs.array) local nn = modpol.ocutil.table_length (modpol.orgs.array)
local str = "entries" local str = "entries"
if nn == 1 then str = "entry" end if nn == 1 then str = "entry" end
modpol.ocutil.log (nn .. " orgs stored to disk") if verbose then modpol.ocutil.log (nn .. " orgs stored to disk") end
end end
-- ===================================================================
-- This function stores "old_ledgers" data to disk.
local store_old_ledgers = function()
local ok = modpol.ocutil.file_write (modpol.file_old_ledgers,
modpol.serpent.dump (modpol.old_ledgers))
if ok ~= true then
modpol.ocutil.fatal_error ("store_data: orgs")
end
local nn = modpol.ocutil.table_length (modpol.old_ledgers)
local str = "entries"
if nn == 1 then str = "entry" end
modpol.ocutil.log (nn .. " orgs stored to disk")
end
-- =================================================================== -- ===================================================================
-- This function stores data to disk. -- This function stores data to disk.
modpol.store_data = function() modpol.store_data = function(verbose)
store_ledger() store_ledger(verbose)
store_orgs() store_orgs(verbose)
-- store_old_ledgers()
end end
-- =================================================================== -- ===================================================================

View File

@ -0,0 +1,31 @@
dofile("../modpol.lua")
print('\nRemoving existing orgs')
modpol.orgs.reset()
print('\nCreating an org called "test_org"')
test_org = modpol.instance:add_org('test_org', 'luke')
print('\nAdding user "nathan" to test_org')
test_org:add_member('nathan')
print("\nOkay, let's start with requests. Setting up an org...")
modpol.instance:add_org('test_org', 'luke')
test_org:add_member('nathan')
print('\nMaking consent the add_member policy')
test_org:set_policy("add_member", "consent", false);
print('\nMaking a request to add Josh')
add_josh = {
user = "josh",
type = "add_member",
params = {"josh"}
}
request_id = test_org:make_request(add_josh)
print('\nHave the two members vote on it')
modpol.interactions.org_dashboard("nathan","test_org")
modpol.interactions.org_dashboard("luke","test_org")

View File

@ -28,7 +28,7 @@ end
modpol.instance:set_policy("add_org", "consent", false); modpol.instance:set_policy("add_org", "consent", false);
new_request = { new_request = {
user = "lukvmil", user = "luke",
type = "add_org", type = "add_org",
params = {"new_org"} params = {"new_org"}
} }
@ -39,6 +39,13 @@ modpol.instance:add_member('josh')
modpol.instance:add_member('nathan') modpol.instance:add_member('nathan')
request_id = modpol.instance:make_request(new_request) request_id = modpol.instance:make_request(new_request)
modpol.instance:make_request({
user="luke",
type="add_org",
params={"second_org"}
})
modpol.interactions.login()
for id, process in ipairs(modpol.instance.processes) do for id, process in ipairs(modpol.instance.processes) do
-- process:approve('luke', true) -- process:approve('luke', true)

View File

@ -3,7 +3,7 @@
-- CONTEXTUAL STUFF -- CONTEXTUAL STUFF
-- ================ -- ================
-- First, set up contexts to enable passing across formspecs -- _contexts to enable passing across formspecs
-- https://rubenwardy.com/minetest_modding_book/en/players/formspecs.html#contexts -- https://rubenwardy.com/minetest_modding_book/en/players/formspecs.html#contexts
local _contexts = {} local _contexts = {}
@ -16,10 +16,6 @@ minetest.register_on_leaveplayer(function(player)
_contexts[player:get_player_name()] = nil _contexts[player:get_player_name()] = nil
end) end)
-- table of formspec field responses
local formspec_fields = {}
-- UTILITIES -- UTILITIES
-- ========= -- =========
@ -38,7 +34,6 @@ local function formspec_list(array)
return table.concat(escaped,",") return table.concat(escaped,",")
end end
-- DASHBOARDS -- DASHBOARDS
-- ========== -- ==========
@ -57,7 +52,7 @@ function modpol.interactions.dashboard(user)
local formspec = { local formspec = {
"formspec_version[4]", "formspec_version[4]",
"size[10,8]", "size[10,8]",
"label[0.5,0.5;M O D U L A R P O L I T I C S]", "label[0.5,0.5;MODPOL DASHBOARD]",
"label[0.5,2;All orgs:]", "label[0.5,2;All orgs:]",
"dropdown[2,1.5;5,0.8;all_orgs;"..formspec_list(all_orgs)..";;]", "dropdown[2,1.5;5,0.8;all_orgs;"..formspec_list(all_orgs)..";;]",
"label[0.5,3;Your orgs:]", "label[0.5,3;Your orgs:]",
@ -67,6 +62,7 @@ function modpol.interactions.dashboard(user)
"button[0.5,7;1,0.8;test_poll;Test poll]", "button[0.5,7;1,0.8;test_poll;Test poll]",
"button[2,7;1,0.8;add_org;Add org]", "button[2,7;1,0.8;add_org;Add org]",
"button[3.5,7;1.5,0.8;remove_org;Remove org]", "button[3.5,7;1.5,0.8;remove_org;Remove org]",
"button[5.5,7;1.5,0.8;reset_orgs;Reset orgs]",
"button_exit[8.5,7;1,0.8;close;Close]", "button_exit[8.5,7;1,0.8;close;Close]",
} }
local formspec_string = table.concat(formspec, "") local formspec_string = table.concat(formspec, "")
@ -78,14 +74,29 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
if formname == "modpol:dashboard" then if formname == "modpol:dashboard" then
local pname = player:get_player_name() local pname = player:get_player_name()
if nil then if nil then
-- buttons first -- buttons first
elseif fields.test_poll then elseif fields.test_poll then
modpol.interactions.binary_poll_org(pname, 1, "Poll question (yes/no):") -- FOR TESTING PURPOSES ONLY
modpol.interactions.text_query(
pname,"Poll question:",
function(input)
modpol.interactions.binary_poll_user(
pname, input,
function(vote)
modpol.interactions.message(
pname, pname .. " voted " .. vote)
end)
end)
elseif fields.add_org then elseif fields.add_org then
modpol.interactions.add_org(pname, 1) modpol.interactions.add_org(pname, 1)
elseif fields.remove_org then elseif fields.remove_org then
modpol.interactions.remove_org(pname) modpol.interactions.remove_org(pname)
-- dropdowns need to go last elseif fields.reset_orgs then
modpol.orgs.reset()
modpol.instance:add_member(pname)
modpol.interactions.dashboard(pname)
-- Put all dropdowns at the end
elseif fields.close then elseif fields.close then
minetest.close_formspec(pname, formname) minetest.close_formspec(pname, formname)
elseif fields.all_orgs or fields.user_orgs then elseif fields.all_orgs or fields.user_orgs then
@ -117,11 +128,43 @@ function modpol.interactions.org_dashboard(user, org_name)
end end
return toggle_code return toggle_code
end end
local children = {} -- identify parent
local parent = modpol.orgs.get_org(org.parent)
if parent then parent = parent.name
else parent = "none" end
-- prepare children menu
local children = {"View..."}
for k,v in ipairs(org.children) do for k,v in ipairs(org.children) do
local this_child = modpol.orgs.get_org(v) local this_child = modpol.orgs.get_org(v)
table.insert(children, this_child.name) table.insert(children, this_child.name)
end end
-- prepare policies menu
local policies = {"View..."}
for k,v in pairs(org.policies) do
table.insert(policies, k .. ": " ..
org.policies[k].process_type)
end
table.insert(policies, "Add policy")
-- prepare processes menu
local processes = {"View..."}
for k,v in ipairs(org.processes) do
local this_request = org.requests[v.request_id]
if type(this_request) == "table" then
local active = ''
if org.pending[user] then
if org.pending[user][v.id] then
active = '*'
end
end
local req_str = "[" .. v.id .. "] " ..
active .. this_request.type
if this_request.params[1] then
req_str = req_str .. ": " ..
table.concat(this_request.params, ", ")
end
table.insert(processes, req_str)
end
end
-- set player context -- set player context
local user_context = {} local user_context = {}
user_context["current_org"] = org_name user_context["current_org"] = org_name
@ -132,16 +175,16 @@ function modpol.interactions.org_dashboard(user, org_name)
"size[10,8]", "size[10,8]",
"label[0.5,0.5;Org: ".. "label[0.5,0.5;Org: "..
minetest.formspec_escape(org_name).."]", minetest.formspec_escape(org_name).."]",
"label[0.5,1;Parent: TODO]", "label[0.5,1;Parent: "..parent.."]",
"button[8.5,0.5;1,0.8;"..membership_toggle(), "button[8.5,0.5;1,0.8;"..membership_toggle(),
"label[0.5,2;Members:]", "label[0.5,2;Members:]",
"dropdown[2,1.5;5,0.8;user_orgs;"..formspec_list(org.members)..";;]", "dropdown[2,1.5;5,0.8;user_orgs;"..formspec_list(org.members)..";;]",
"label[0.5,3;Children:]", "label[0.5,3;Children:]",
"dropdown[2,2.5;5,0.8;children;"..formspec_list(children)..";;]", "dropdown[2,2.5;5,0.8;children;"..formspec_list(children)..";;]",
"label[0.5,4;Policies:]", "label[0.5,4;Policies:]",
"dropdown[2,3.5;5,0.8;policies;"..formspec_list(org.policies)..";;]", "dropdown[2,3.5;5,0.8;policies;"..formspec_list(policies)..";;]",
"label[0.5,5;Processes:]", "label[0.5,5;Processes:]",
"dropdown[2,4.5;5,0.8;processes;"..formspec_list(org.processes)..";;]", "dropdown[2,4.5;5,0.8;processes;"..formspec_list(processes)..";;]",
"button[0.5,7;1,0.8;test_poll;Test poll]", "button[0.5,7;1,0.8;test_poll;Test poll]",
"button[2,7;1,0.8;add_child;Add child]", "button[2,7;1,0.8;add_child;Add child]",
"button[3.5,7;1.5,0.8;remove_org;Remove org]", "button[3.5,7;1.5,0.8;remove_org;Remove org]",
@ -159,29 +202,89 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
if nil then if nil then
elseif fields.join then elseif fields.join then
local new_request = { local new_request = {
user = player, user = pname,
type = "add_member", type = "add_member",
params = {player} params = {pname}
} }
org:make_request(new_request) org:make_request(new_request)
--org:add_member(pname)
modpol.interactions.org_dashboard(pname,org.name) modpol.interactions.org_dashboard(pname,org.name)
elseif fields.leave then elseif fields.leave then
org:remove_member(pname) org:remove_member(pname)
modpol.interactions.dashboard(pname) modpol.interactions.dashboard(pname)
elseif fields.test_poll then elseif fields.test_poll then
modpol.interactions.binary_poll_org(pname, _contexts.pname.current_org.id, "Poll question (yes/no):") modpol.interactions.binary_poll_org(
pname, org.id,
function(input)
modpol.interactions.message_org(
pname,
org.id,
pname .. " responded " .. input)
end)
elseif fields.add_child then elseif fields.add_child then
modpol.interactions.add_org(pname, org.id) modpol.interactions.text_query(
pname, "Child org name:",
function(input)
local new_request = {
user = pname,
type = "add_org",
params = {input,pname}
}
org:make_request(new_request)
modpol.interactions.message(pname,"requested")
modpol.interactions.org_dashboard(
pname,org.name)
end)
elseif fields.remove_org then elseif fields.remove_org then
modpol.interactions.remove_org(pname) local new_request = {
user = pname,
type = "delete",
params = {}
}
org:make_request(new_request)
modpol.interactions.org_dashboard(pname,org.name)
elseif fields.back then elseif fields.back then
modpol.interactions.dashboard(pname) modpol.interactions.dashboard(pname)
-- Put all dropdowns at the end
elseif fields.policies
and fields.policies ~= "View..." then
local policy
if fields.policies == "Add policy" then
policy = nil
elseif fields.policies == "View..." then
return
else
policy = string.match(fields.policies,"(%w+)%:")
end
modpol.interactions.policy_dashboard(
pname, org.id, policy)
elseif fields.processes
and fields.processes ~= "View..." then
local sel = string.match(fields.processes,"%[(%d)%]")
local process = org.processes[tonumber(sel)]
if process then
process:interact(pname)
end
elseif fields.children
and fields.children ~= "View..." then
local org_name = fields.children
modpol.interactions.org_dashboard(pname, org_name)
end end
end end
end) end)
-- Function: modpol.interactions.policy_dashboard
-- input: user (string), org_id (int), policy (string)
-- output: opens a dashboard for viewing/editing policy details
-- TODO
function modpol.interactions.policy_dashboard(
user, org_id, policy)
modpol.interactions.message(
user,
"Not yet implemented: " .. policy)
end
-- BASIC INTERACTION FUNCTIONS -- BASIC INTERACTION FUNCTIONS
-- =========================== -- ===========================
@ -189,16 +292,16 @@ end)
-- Function: modpol.interactions.message -- Function: modpol.interactions.message
-- input: message (string) -- input: message (string)
-- output -- output
modpol.interactions.message = function(user, message) function modpol.interactions.message(user, message)
minetest.chat_send_player(user, message) minetest.chat_send_player(user, message)
end end
-- Function: modpol.interactions.text_query -- Function: modpol.interactions.text_query
-- Overrides function at modpol/interactions.lua -- Overrides function at modpol/interactions.lua
-- input: Query (string), User (string) -- input: user (string), query (string), func (function)
-- output: User response (string) -- func input: user input (string)
-- TODO Need to switch "user" to index not name -- output: Applies "func" to user input
function modpol.interactions.text_query(user, query) function modpol.interactions.text_query(user, query, func)
-- set up formspec -- set up formspec
local formspec = { local formspec = {
"formspec_version[4]", "formspec_version[4]",
@ -210,40 +313,63 @@ function modpol.interactions.text_query(user, query)
local formspec_string = table.concat(formspec, "") local formspec_string = table.concat(formspec, "")
-- present to players -- present to players
minetest.show_formspec(user, "modpol:text_query", formspec_string) minetest.show_formspec(user, "modpol:text_query", formspec_string)
-- put func in _contexts
if _contexts[user] == nil then _contexts[user] = {} end
_contexts[user]["text_query_func"] = func
end end
-- receive fields -- receive fields
minetest.register_on_player_receive_fields(function (player, formname, fields) minetest.register_on_player_receive_fields(function (player, formname, fields)
if formname == "modpol:text_query" then if formname == "modpol:text_query" then
local pname = player:get_player_name() local pname = player:get_player_name()
if _contexts[pname] then local input = fields.input
_contexts[pname](fields.input) if not input then
-- no input, do nothing
else
local func = _contexts[pname]["text_query_func"]
if func then
func(input)
else
modpol.interactions.message(pname, "text_query: " .. input)
end
end end
minetest.close_formspec(pname, formname) minetest.close_formspec(pname, formname)
end end
end) end)
-- Function: dropdown_query -- Function: dropdown_query
-- input: user (string), label (string), options (table of strings) -- input: user (string), label (string), options (table of strings), func (function)
function modpol.interactions.dropdown_query(user, label, options) -- func input: choice (string)
-- output: calls func on user choice
function modpol.interactions.dropdown_query(user, label, options, func)
-- set up formspec -- set up formspec
local formspec = { local formspec = {
"formspec_version[4]", "formspec_version[4]",
"size[10,4]", "size[10,4]",
"label[0.5,1;"..minetest.formspec_escape(label).."]", "label[0.5,1;"..minetest.formspec_escape(label).."]",
"dropdown[0.5,1.25;9,0.8;input;"..formspec_list(options)..";;]", "dropdown[0.5,1.25;9,0.8;input;"..formspec_list(options)..";;]",
"button[0.5,2.5;1,0.8;yes;OK]", "button[0.5,2.5;1,0.8;cancel;Cancel]",
} }
local formspec_string = table.concat(formspec, "") local formspec_string = table.concat(formspec, "")
-- present to players -- present to players
minetest.show_formspec(user, "modpol:dropdown_query", formspec_string) minetest.show_formspec(user, "modpol:dropdown_query", formspec_string)
-- put func in _contexts
if _contexts[user] == nil then _contexts[user] = {} end
_contexts[user]["dropdown_query_func"] = func
end end
-- receive fields -- receive fields
minetest.register_on_player_receive_fields(function (player, formname, fields) minetest.register_on_player_receive_fields(function (player, formname, fields)
if formname == "modpol:dropdown_query" then if formname == "modpol:dropdown_query" then
local pname = player:get_player_name() local pname = player:get_player_name()
if _contexts[pname] then if fields.cancel ~= "cancel" then
_contexts[pname](fields.input) local choice = fields.input
local func = _contexts[pname]["dropdown_query_func"]
if not choice then
-- no choice, do nothing
elseif func then
func(choice)
else
modpol.interactions.message(pname, "dropdown_query: " .. choice)
end
end end
minetest.close_formspec(pname, formname) minetest.close_formspec(pname, formname)
end end
@ -253,27 +379,28 @@ end)
-- SECONDARY INTERACTIONS -- SECONDARY INTERACTIONS
-- ====================== -- ======================
-- Function: modpol.binary_poll_user(user, question) -- Function: modpol.binary_poll_user(user, question, function)
-- Overrides function at modpol/interactions.lua -- Overrides function at modpol/interactions.lua
-- presents a yes/no poll to a user, returns answer -- Params: user (string), question (string), func (function)
-- -- func input: user input (string: y/n)
function modpol.interactions.binary_poll_user(user, question) -- Output: Applies "func" to user input
function modpol.interactions.binary_poll_user(user, question, func)
-- set up formspec -- set up formspec
local text = "Poll: " .. question
local formspec = { local formspec = {
"formspec_version[4]", "formspec_version[4]",
"size[5,3]", "size[5,3]",
"label[0.375,0.5;", minetest.formspec_escape(text), "]", "label[0.375,0.5;",minetest.formspec_escape(question), "]",
"button[1,1.5;1,0.8;yes;Yes]", "button[1,1.5;1,0.8;yes;Yes]",
"button[2,1.5;1,0.8;no;No]", "button[2,1.5;1,0.8;no;No]",
--TKTK can we enable text wrapping? --TKTK can we enable text wrapping?
--TKTK we could use scroll boxes to contain the text --TKTK we could use scroll boxes to contain the text
} }
local formspec_string = table.concat(formspec, "") local formspec_string = table.concat(formspec, "")
if _contexts[user] == nil then _contexts[user] = {} end
_contexts[user]["binary_poll_func"] = func
-- present to player -- present to player
minetest.show_formspec(user, "modpol:binary_poll_user", formspec_string) minetest.show_formspec(user, "modpol:binary_poll_user", formspec_string)
end end
minetest.register_on_player_receive_fields(function (player, formname, fields) minetest.register_on_player_receive_fields(function (player, formname, fields)
local pname = player:get_player_name() local pname = player:get_player_name()
-- modpol:binary_poll -- modpol:binary_poll
@ -281,79 +408,80 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
local vote = nil local vote = nil
if fields.yes then vote = fields.yes if fields.yes then vote = fields.yes
elseif fields.no then vote = fields.no elseif fields.no then vote = fields.no
elseif fields.abstain then vote = fields.abstain
end end
if vote then if vote then
modpol.interactions.message(pname, "Vote recorded") modpol.interactions.message(pname, "Responded " .. vote)
minetest.chat_send_all(pname .. " voted " .. vote) local func = _contexts[pname]["binary_poll_func"]
--TODO : we should send the message to all in that org, not to all players if func then func(vote) end
end end
minetest.close_formspec(pname, formname) minetest.close_formspec(pname, formname)
return vote
end end
end) end)
-- COMPLEX INTERACTIONS -- COMPLEX INTERACTIONS
-- ==================== -- ====================
-- Function: modpol.interactions.message_org
-- input: initiator (string), org_id (number), message (string)
-- output: broadcasts message to all org members
function modpol.interactions.message_org(initiator, org_id, message)
local org = modpol.orgs.get_org(org_id)
local users = org:list_members()
for k,v in ipairs(users) do
modpol.interactions.message(v, message)
end
end
-- Function: modpol.interactions.binary_poll_org -- Function: modpol.interactions.binary_poll_org
-- input: initator (user string), org (number) -- input: initator (user string), org_id (number)
-- output: interaction begins -- output: gets question from initiator, asks all org members, broadcasts answers
function modpol.interactions.binary_poll_org(initiator, org) -- TODO for testing. This should be implemented as a request.
-- start formspec function modpol.interactions.binary_poll_org(initiator, org_id, func)
modpol.interactions.text_query(initiator, "Poll question (yes/no):") local org = modpol.orgs.get_org(org_id)
-- set user's context to followup function local users = org:list_members()
_contexts[initiator] = modpol.interactions.text_query(
initiator, "Yes/no poll question:",
function(input) function(input)
local users = modpol.list_users()
for k,v in ipairs(users) do for k,v in ipairs(users) do
modpol.interactions.binary_poll_user(v, input) modpol.interactions.binary_poll_user(v, input, func)
end end
_contexts[initiator] = nil end)
end
end end
-- Function: modpol.interactions.add_org -- Function: modpol.interactions.add_org
-- input: initator (user string), base_org_id (ID) -- input: initator (user string), base_org_id (ID)
-- output: interaction begins -- output: interaction begins
function modpol.interactions.add_org(initiator, base_org_id) -- GODMODE
-- start formspec function modpol.interactions.add_org(user, base_org_id)
modpol.interactions.text_query(initiator, "Org name:") modpol.interactions.text_query(
-- set user's context to followup function user,"Org name:",
_contexts[initiator] = function(input) function(input)
if input then local base_org = modpol.orgs.get_org(1)
local base_org = modpol.orgs.get_org(base_org_id) local result = base_org:add_org(input, user)
local result = base_org:add_org(input, initiator) local message = input .. " created"
if result then modpol.interactions.message(user, message)
local message = input .. " created" modpol.interactions.dashboard(user)
modpol.interactions.message(initiator, message) end)
end
end
_contexts[initiator] = nil
modpol.interactions.dashboard(initiator)
end
end end
-- Function: modpol.interactions.remove_org -- Function: modpol.interactions.remove_org
-- input: initator (user string) -- input: initator (user string)
-- output: interaction begins -- output: interaction begins
function modpol.interactions.remove_org(initiator) -- GODMODE
function modpol.interactions.remove_org(user)
-- start formspec -- start formspec
local orgs_list = modpol.orgs.list_all() local orgs_list = modpol.orgs.list_all()
local label = "Choose an org to remove:" local label = "Choose an org to remove:"
modpol.interactions.dropdown_query(initiator, label, orgs_list) modpol.interactions.dropdown_query(
-- set user's context to followup function user, label, orgs_list,
_contexts[initiator] = function(input) function(input)
if input then if input then
local target_org = modpol.orgs.get_org(input) local target_org = modpol.orgs.get_org(input)
local result = target_org:delete() local result = target_org:delete()
if result then
local message = input .. " deleted" local message = input .. " deleted"
modpol.interactions.message(initiator, message) modpol.interactions.message(user, message)
end end
end modpol.interactions.dashboard(user)
_contexts[initiator] = nil end)
modpol.interactions.dashboard(initiator)
end
end end