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
-- ==========
-- 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)
-- Q: Should this return a menu of commands relevant to the specific user?
-- Output: Displays a menu of commands to the user
-- TKTK currently just prints all of modpol---needs major improvement
function modpol.dashboard(user)
local output = ""
-- Org status
output = output .. "Orgs:\n" ..
table.concat(modpol.orgs.list_all(),"\n")
-- Command list (should be redone to be org-specific)
output = output .. "\nCommand list:\n"
for key,value in pairs(modpol) do
output = output .. "modpol." .. key .. " "
function modpol.interactions.dashboard(user)
print()
local org_list = modpol.orgs.list_all()
print('Select an org')
for i, org_name in ipairs(org_list) do
local org = modpol.orgs.get_org(org_name)
local indicator = ""
if org:has_pending_actions(user) then
indicator = "*"
end
print('['..i..']'..' '..org_name..indicator)
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
@ -32,40 +56,58 @@ end
-- Params: user (string), org_name (string)
-- Output: Displays a menu of org-specific commands to the user
function modpol.interactions.org_dashboard(user, org_name)
print()
local org = modpol.orgs.get_org(org_name)
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 = {}
for k,v in ipairs(org.children) do
local this_child = modpol.orgs.get_org(v)
table.insert(children, this_child.name)
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
local dashboard_table = {
"Org: " .. org_name,
membership_toggle(),
"Members: " .. table.concat(org.members, ", "),
"Children: " .. table.concat(children, ", "),
"Policies: " .. table.concat(org.policies, ", "),
"Processes: " .. table.concat(org.processes, ", "),
"[Add child]",
"[Remove org]",
"[Dashboard: modpol.dashboard()]"
}
-- present to player
print(table.concat(dashboard_table, "\n"))
print("Org: " .. org_name)
print("Members: " .. table.concat(org.members, ", "))
print("Children: " .. table.concat(children, ", "))
print("Processes: " .. table.concat(processes, ", "))
print('Interact with which process?')
local sel = io.read()
local process = org.processes[tonumber(sel)]
if not process then return end
process:interact(user)
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
-- input: user (string), message (string)
-- output: prints message to CLI
@ -73,31 +115,86 @@ function modpol.interactions.message(user, message)
print(user .. ": " .. message)
end
-- ===================================================================
-- Function: modpol.interactions.text_query
-- input: Query (string)
-- output: User response (string)
function modpol.interactions.text_query(query)
-- TODO
-- input: User (string), Query (string), func (function)
-- func input: user input (string)
-- output: Applies "func" to user input
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
-- ===================================================================
-- Function: modpol.binary_poll_user(user, question)
-- Params: user (string), question (string)
-- Output:
-- presents a yes/no/abstain poll to a user, returns answer
function modpol.interactions.binary_poll_user(user, question)
-- Params: user (string), question (string), func (function)
-- func input: user input (string: y/n)
-- Output: Applies "func" to user input
function modpol.interactions.binary_poll_user(user, question, func)
local query = "Poll for " .. user .. " (y/n): ".. question
local answer
repeat
print(query)
answer = io.read()
until answer == "y" or answer == "n" or answer == "a"
until answer == "y" or answer == "n"
if answer == "y" then
return "Yes"
modpol.interactions.message(user, "Response recorded")
func("yes")
elseif answer == "n" then
return "No"
modpol.interactions.message(user, "Response recorded")
func("no")
else
return "Abstain"
modpol.interactions.message(user, "Error: invalid response")
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
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
@ -90,4 +105,4 @@ function modpol.modules.consent:update_status()
else
modpol.ocutil.log('Waiting for more votes...')
end
end
end

View File

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

View File

@ -13,10 +13,11 @@ function modpol.orgs:create_process(process_type, request_id)
return
end
local empty_index = nil
-- linear search for empty process slots (lazy deletion)
for k, v in ipairs(self.processes) do
if v == 'deleted' then
local empty_index = k
empty_index = k
break
end
end
@ -35,6 +36,9 @@ function modpol.orgs:create_process(process_type, request_id)
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
end
@ -46,7 +50,8 @@ function modpol.orgs:add_pending_action(process_id, user)
-- flagging actual action
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
-- ========================
@ -55,7 +60,8 @@ function modpol.orgs:remove_pending_action(process_id, user)
if self.pending[user] then
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
modpol.ocutil.log("Could not remove pending action from " .. user .. " in process #" .. process_id)
end
@ -68,6 +74,23 @@ function modpol.orgs:wipe_pending_actions(process_id)
self.pending[user][process_id] = nil
end
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
-- ===========================
@ -130,6 +153,9 @@ function modpol.orgs:resolve_request(request_id, approve)
end
self.requests[request_id] = "deleted"
modpol.ocutil.log("Resolved request #" .. request_id .. ' in ' .. self.name)
self:record("Resolved request #" .. request_id, "resolve_request")
end
-- ================================
@ -189,10 +215,11 @@ function modpol.orgs:make_request(request)
return false
end
local empty_index = nil
-- linear search for empty process slots (lazy deletion)
for k, v in ipairs(self.requests) do
if v == 'deleted' then
local empty_index = k
empty_index = k
break
end
end
@ -206,7 +233,9 @@ function modpol.orgs:make_request(request)
table.insert(self.requests, request)
request_id = #self.requests
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
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.
local store_ledger = function()
local ok = modpol.ocutil.file_write (modpol.file_ledger,
modpol.serpent.dump (modpol.ledger))
local store_ledger = function(verbose)
local ok = modpol.ocutil.file_write (modpol.file_ledger, modpol.serpent.dump (modpol.ledger))
if ok ~= true then
if ok ~= true then
modpol.ocutil.fatal_error ("store_data: ledger")
end
local nn = modpol.ocutil.table_length (modpol.ledger)
local nn = modpol.ocutil.table_length (modpol.ledger)
local str = "entries"
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
-- ===================================================================
@ -52,35 +51,16 @@ local store_orgs = function()
local nn = modpol.ocutil.table_length (modpol.orgs.array)
local str = "entries"
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
-- ===================================================================
-- 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.
modpol.store_data = function()
store_ledger()
store_orgs()
-- store_old_ledgers()
modpol.store_data = function(verbose)
store_ledger(verbose)
store_orgs(verbose)
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);
new_request = {
user = "lukvmil",
user = "luke",
type = "add_org",
params = {"new_org"}
}
@ -39,6 +39,13 @@ modpol.instance:add_member('josh')
modpol.instance:add_member('nathan')
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
-- process:approve('luke', true)

View File

@ -3,7 +3,7 @@
-- 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
local _contexts = {}
@ -16,10 +16,6 @@ minetest.register_on_leaveplayer(function(player)
_contexts[player:get_player_name()] = nil
end)
-- table of formspec field responses
local formspec_fields = {}
-- UTILITIES
-- =========
@ -38,7 +34,6 @@ local function formspec_list(array)
return table.concat(escaped,",")
end
-- DASHBOARDS
-- ==========
@ -57,7 +52,7 @@ function modpol.interactions.dashboard(user)
local formspec = {
"formspec_version[4]",
"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:]",
"dropdown[2,1.5;5,0.8;all_orgs;"..formspec_list(all_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[2,7;1,0.8;add_org;Add 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]",
}
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
local pname = player:get_player_name()
if nil then
-- buttons first
-- buttons first
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
modpol.interactions.add_org(pname, 1)
elseif fields.remove_org then
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
minetest.close_formspec(pname, formname)
elseif fields.all_orgs or fields.user_orgs then
@ -117,11 +128,43 @@ function modpol.interactions.org_dashboard(user, org_name)
end
return toggle_code
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
local this_child = modpol.orgs.get_org(v)
table.insert(children, this_child.name)
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
local user_context = {}
user_context["current_org"] = org_name
@ -132,16 +175,16 @@ function modpol.interactions.org_dashboard(user, org_name)
"size[10,8]",
"label[0.5,0.5;Org: "..
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(),
"label[0.5,2;Members:]",
"dropdown[2,1.5;5,0.8;user_orgs;"..formspec_list(org.members)..";;]",
"label[0.5,3;Children:]",
"dropdown[2,2.5;5,0.8;children;"..formspec_list(children)..";;]",
"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:]",
"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[2,7;1,0.8;add_child;Add child]",
"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
elseif fields.join then
local new_request = {
user = player,
user = pname,
type = "add_member",
params = {player}
params = {pname}
}
org:make_request(new_request)
--org:add_member(pname)
modpol.interactions.org_dashboard(pname,org.name)
elseif fields.leave then
org:remove_member(pname)
modpol.interactions.dashboard(pname)
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
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
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
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)
-- 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
-- ===========================
@ -189,16 +292,16 @@ end)
-- Function: modpol.interactions.message
-- input: message (string)
-- output
modpol.interactions.message = function(user, message)
function modpol.interactions.message(user, message)
minetest.chat_send_player(user, message)
end
-- Function: modpol.interactions.text_query
-- Overrides function at modpol/interactions.lua
-- input: Query (string), User (string)
-- output: User response (string)
-- TODO Need to switch "user" to index not name
function modpol.interactions.text_query(user, query)
-- input: user (string), query (string), func (function)
-- func input: user input (string)
-- output: Applies "func" to user input
function modpol.interactions.text_query(user, query, func)
-- set up formspec
local formspec = {
"formspec_version[4]",
@ -210,40 +313,63 @@ function modpol.interactions.text_query(user, query)
local formspec_string = table.concat(formspec, "")
-- present to players
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
-- receive fields
minetest.register_on_player_receive_fields(function (player, formname, fields)
if formname == "modpol:text_query" then
local pname = player:get_player_name()
if _contexts[pname] then
_contexts[pname](fields.input)
local input = 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
minetest.close_formspec(pname, formname)
end
end)
-- Function: dropdown_query
-- input: user (string), label (string), options (table of strings)
function modpol.interactions.dropdown_query(user, label, options)
-- input: user (string), label (string), options (table of strings), func (function)
-- func input: choice (string)
-- output: calls func on user choice
function modpol.interactions.dropdown_query(user, label, options, func)
-- set up formspec
local formspec = {
"formspec_version[4]",
"size[10,4]",
"label[0.5,1;"..minetest.formspec_escape(label).."]",
"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, "")
-- present to players
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
-- receive fields
minetest.register_on_player_receive_fields(function (player, formname, fields)
if formname == "modpol:dropdown_query" then
local pname = player:get_player_name()
if _contexts[pname] then
_contexts[pname](fields.input)
local pname = player:get_player_name()
if fields.cancel ~= "cancel" then
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
minetest.close_formspec(pname, formname)
end
@ -253,27 +379,28 @@ end)
-- SECONDARY INTERACTIONS
-- ======================
-- Function: modpol.binary_poll_user(user, question)
-- Function: modpol.binary_poll_user(user, question, function)
-- Overrides function at modpol/interactions.lua
-- presents a yes/no poll to a user, returns answer
--
function modpol.interactions.binary_poll_user(user, question)
-- Params: user (string), question (string), func (function)
-- func input: user input (string: y/n)
-- Output: Applies "func" to user input
function modpol.interactions.binary_poll_user(user, question, func)
-- set up formspec
local text = "Poll: " .. question
local formspec = {
"formspec_version[4]",
"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[2,1.5;1,0.8;no;No]",
--TKTK can we enable text wrapping?
--TKTK we could use scroll boxes to contain the text
}
local formspec_string = table.concat(formspec, "")
if _contexts[user] == nil then _contexts[user] = {} end
_contexts[user]["binary_poll_func"] = func
-- present to player
minetest.show_formspec(user, "modpol:binary_poll_user", formspec_string)
end
minetest.register_on_player_receive_fields(function (player, formname, fields)
local pname = player:get_player_name()
-- modpol:binary_poll
@ -281,79 +408,80 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
local vote = nil
if fields.yes then vote = fields.yes
elseif fields.no then vote = fields.no
elseif fields.abstain then vote = fields.abstain
end
if vote then
modpol.interactions.message(pname, "Vote recorded")
minetest.chat_send_all(pname .. " voted " .. vote)
--TODO : we should send the message to all in that org, not to all players
modpol.interactions.message(pname, "Responded " .. vote)
local func = _contexts[pname]["binary_poll_func"]
if func then func(vote) end
end
minetest.close_formspec(pname, formname)
return vote
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.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
-- input: initator (user string), org (number)
-- output: interaction begins
function modpol.interactions.binary_poll_org(initiator, org)
-- start formspec
modpol.interactions.text_query(initiator, "Poll question (yes/no):")
-- set user's context to followup function
_contexts[initiator] =
-- input: initator (user string), org_id (number)
-- output: gets question from initiator, asks all org members, broadcasts answers
-- TODO for testing. This should be implemented as a request.
function modpol.interactions.binary_poll_org(initiator, org_id, func)
local org = modpol.orgs.get_org(org_id)
local users = org:list_members()
modpol.interactions.text_query(
initiator, "Yes/no poll question:",
function(input)
local users = modpol.list_users()
for k,v in ipairs(users) do
modpol.interactions.binary_poll_user(v, input)
modpol.interactions.binary_poll_user(v, input, func)
end
_contexts[initiator] = nil
end
end)
end
-- Function: modpol.interactions.add_org
-- input: initator (user string), base_org_id (ID)
-- output: interaction begins
function modpol.interactions.add_org(initiator, base_org_id)
-- start formspec
modpol.interactions.text_query(initiator, "Org name:")
-- set user's context to followup function
_contexts[initiator] = function(input)
if input then
local base_org = modpol.orgs.get_org(base_org_id)
local result = base_org:add_org(input, initiator)
if result then
local message = input .. " created"
modpol.interactions.message(initiator, message)
end
end
_contexts[initiator] = nil
modpol.interactions.dashboard(initiator)
end
-- GODMODE
function modpol.interactions.add_org(user, base_org_id)
modpol.interactions.text_query(
user,"Org name:",
function(input)
local base_org = modpol.orgs.get_org(1)
local result = base_org:add_org(input, user)
local message = input .. " created"
modpol.interactions.message(user, message)
modpol.interactions.dashboard(user)
end)
end
-- Function: modpol.interactions.remove_org
-- input: initator (user string)
-- output: interaction begins
function modpol.interactions.remove_org(initiator)
-- GODMODE
function modpol.interactions.remove_org(user)
-- start formspec
local orgs_list = modpol.orgs.list_all()
local label = "Choose an org to remove:"
modpol.interactions.dropdown_query(initiator, label, orgs_list)
-- set user's context to followup function
_contexts[initiator] = function(input)
if input then
local target_org = modpol.orgs.get_org(input)
local result = target_org:delete()
if result then
modpol.interactions.dropdown_query(
user, label, orgs_list,
function(input)
if input then
local target_org = modpol.orgs.get_org(input)
local result = target_org:delete()
local message = input .. " deleted"
modpol.interactions.message(initiator, message)
modpol.interactions.message(user, message)
end
end
_contexts[initiator] = nil
modpol.interactions.dashboard(initiator)
end
modpol.interactions.dashboard(user)
end)
end