diff --git a/modpol/interactions/interactions.lua b/modpol/interactions/interactions.lua index e6ba855..e57c997 100644 --- a/modpol/interactions/interactions.lua +++ b/modpol/interactions/interactions.lua @@ -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 + + diff --git a/modpol/modules/consent.lua b/modpol/modules/consent.lua index a94042b..25e767a 100644 --- a/modpol/modules/consent.lua +++ b/modpol/modules/consent.lua @@ -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 \ No newline at end of file +end diff --git a/modpol/orgs/base.lua b/modpol/orgs/base.lua index 2b8fbf5..b86d7bf 100644 --- a/modpol/orgs/base.lua +++ b/modpol/orgs/base.lua @@ -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 diff --git a/modpol/orgs/requests.lua b/modpol/orgs/requests.lua index 02b8d2a..c17d4bf 100644 --- a/modpol/orgs/requests.lua +++ b/modpol/orgs/requests.lua @@ -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) diff --git a/modpol/storage/storage-local.lua b/modpol/storage/storage-local.lua index d94214e..522094f 100644 --- a/modpol/storage/storage-local.lua +++ b/modpol/storage/storage-local.lua @@ -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 -- =================================================================== diff --git a/modpol/tests/nested_functions.lua b/modpol/tests/nested_functions.lua new file mode 100644 index 0000000..914d68f --- /dev/null +++ b/modpol/tests/nested_functions.lua @@ -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") + diff --git a/modpol/tests/org_req_test.lua b/modpol/tests/org_req_test.lua index b65c61b..e1cacc0 100644 --- a/modpol/tests/org_req_test.lua +++ b/modpol/tests/org_req_test.lua @@ -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) diff --git a/modpol_minetest/overrides/interactions/interactions.lua b/modpol_minetest/overrides/interactions/interactions.lua index 58316b2..73e9f91 100644 --- a/modpol_minetest/overrides/interactions/interactions.lua +++ b/modpol_minetest/overrides/interactions/interactions.lua @@ -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 -