interactions.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. --- INTERACTIONS.LUA (CLI).
  2. -- User interaction functions for Modular Politics
  3. -- Called by modpol.lua
  4. -- @module modpol.interactions
  5. modpol.interactions = {}
  6. -- DASHBOARDS
  7. -- ==========
  8. --- Output: Display a menu of commands to the user
  9. -- @function modpol.interactions.dashboard
  10. -- @param user (string)
  11. -- Q: Should this return a menu of commands relevant to the specific user?
  12. -- TKTK currently just prints all of modpol---needs major improvement
  13. function modpol.interactions.dashboard(user)
  14. -- adds user to root org if not already in it
  15. if not modpol.instance:has_member(user) then
  16. modpol.instance:add_member(user)
  17. end
  18. local all_users = modpol.instance:list_members()
  19. print('\n-=< MODPOL DASHBOARD >=-')
  20. print('All orgs: (user orgs indicated by *)')
  21. for id, org in ipairs(modpol.orgs.array) do
  22. if type(org) == "table" then
  23. local indicator = ""
  24. if org:has_member(user) then indicator = "*" end
  25. print('['..id..'] '..indicator..org.name)
  26. end
  27. end
  28. -- pending list
  29. local user_pending = {}
  30. local user_pending_count = 0
  31. for k,v in ipairs(modpol.orgs.array) do
  32. if v.pending and v.pending[user] then
  33. if modpol.util.num_pairs(v.pending[user]) ~= 0 then
  34. table.insert(user_pending, v.name)
  35. user_pending_count = user_pending_count + 1
  36. end
  37. end
  38. end
  39. print('Pending actions in: '..table.concat(user_pending,', '))
  40. print('All users: ' .. table.concat(all_users, ', '))
  41. print()
  42. print("Commands: (O)rg, (U)ser, (R)eset, (Q)uit")
  43. local sel = io.read()
  44. if sel == "O" or sel == "o" then
  45. print('Access which org id?')
  46. sel = io.read()
  47. print()
  48. if modpol.orgs.array[tonumber(sel)] then
  49. local sel_org = modpol.orgs.array[tonumber(sel)].name
  50. modpol.interactions.org_dashboard(user, sel_org)
  51. else
  52. print("Org id not found")
  53. modpol.interactions.dashboard(user)
  54. end
  55. elseif sel == "U" or sel == "u" then
  56. print("Access which user?")
  57. sel = io.read()
  58. print()
  59. if modpol.instance:has_member(sel) then
  60. modpol.interactions.user_dashboard(
  61. user, sel,
  62. function()
  63. modpol.interactions.dashboard(user)
  64. end
  65. )
  66. else
  67. print("User name not found")
  68. modpol.interactions.dashboard(user)
  69. end
  70. elseif sel == "R" or sel == "r" then
  71. modpol.instance.members = {}
  72. modpol.orgs.reset()
  73. print("Orgs and users reset")
  74. modpol.interactions.dashboard(user)
  75. elseif sel == "Q" or "q" then
  76. return
  77. else
  78. print("Invalid input, try again")
  79. modpol.interactions.dashboard(user)
  80. end
  81. end
  82. --- Output: Displays a menu of org-specific commands to the user
  83. -- @function modpol.interactions.org_dashboard
  84. -- @param user (string)
  85. -- @param org_string (string or id)
  86. function modpol.interactions.org_dashboard(user, org_string)
  87. local org = modpol.orgs.get_org(org_string)
  88. if not org then return nil end
  89. -- identify parent
  90. local parent = ""
  91. if org.id == 1 then
  92. parent = "none"
  93. else
  94. parent = modpol.orgs.get_org(org.parent).name
  95. end
  96. -- identify children
  97. local children = {}
  98. for k,v in ipairs(org.children) do
  99. local this_child = modpol.orgs.get_org(v)
  100. table.insert(children, this_child.name)
  101. end
  102. -- prepare modules menu
  103. local modules = {}
  104. if org.modules then
  105. for k,v in pairs(org.modules) do
  106. if not v.hide then -- hide utility modules
  107. local module_entry = v.slug
  108. table.insert(modules, module_entry)
  109. end
  110. end
  111. end
  112. table.sort(modules)
  113. -- list pending
  114. local process_msg = #org.processes .. " total processes"
  115. if org.pending[user] then
  116. process_msg = process_msg .. " (" ..
  117. modpol.util.num_pairs(org.pending[user]) .. " pending)"
  118. else
  119. process_msg = process_msg .. " (0 pending)"
  120. end
  121. -- set up output
  122. print('\n-=< ORG DASHBOARD >=-')
  123. print("Org: " .. org.name)
  124. print("Parent: " .. parent)
  125. print("Members: " .. table.concat(org.members, ", "))
  126. print("Child orgs: " .. table.concat(children, ", "))
  127. print("Modules: " .. table.concat(modules, ", "))
  128. print("Pending: " .. process_msg)
  129. print()
  130. print("Commands: (M)odules, (P)ending, (B)ack")
  131. local sel = io.read()
  132. print()
  133. if sel == 'm' or sel == 'M' then
  134. print("Type module name: ")
  135. local module_sel = io.read()
  136. print()
  137. local module_result = false
  138. for k,v in ipairs(modules) do
  139. if v == module_sel then
  140. module_result = true
  141. end
  142. end
  143. if module_result then
  144. org:call_module(module_sel, user)
  145. else
  146. print("Error: Module not found.")
  147. modpol.interactions.org_dashboard(user, org.id)
  148. end
  149. elseif sel == 'p' or sel == 'P' then
  150. local processes = {}
  151. print("All processes: (* indicates pending)")
  152. for i,v in ipairs(org.processes) do
  153. if v ~= "deleted" then
  154. local active = ''
  155. if org.pending[user] then
  156. if org.pending[user][v.id] then
  157. active = '*'
  158. end
  159. end
  160. print("["..v.id.."] "..v.slug..active)
  161. end
  162. end
  163. print()
  164. print("Interact with which one (use [id] number)?")
  165. local to_interact = io.read()
  166. local process = org.processes[tonumber(to_interact)]
  167. if not process then
  168. modpol.interactions.message(
  169. user, "Not a pending process")
  170. modpol.interactions.org_dashboard(user, org.id)
  171. return
  172. end
  173. if org:has_pending_actions(user) then
  174. if org.pending[user][process.id] then
  175. org:interact(process.id, user)
  176. end
  177. end
  178. elseif sel == 'b' or sel == 'B' then
  179. modpol.interactions.dashboard(user)
  180. else
  181. print("Command not found")
  182. modpol.interactions.org_dashboard(user, org.name)
  183. end
  184. end
  185. --- Function: modpol.interactions.user_dashboard
  186. -- Displays a dashboard about a particular user
  187. -- @param viewer Name of user viewing the dashboard (string)
  188. -- @param user Name of user being viewed (string)
  189. -- @param completion Optional function to call on Done button
  190. function modpol.interactions.user_dashboard(viewer, user, completion)
  191. local user_orgs = {}
  192. local user_modules = {}
  193. print("\n-=< USER DASHBOARD: "..user.." >=-")
  194. print("User's orgs:")
  195. for id, org in ipairs(modpol.orgs.array) do
  196. if type(org) == "table" then
  197. if org:has_member(user) then
  198. print(org.name)
  199. end
  200. end
  201. end
  202. print()
  203. print("Commands: (M)essage user, Enter when done")
  204. local sel = io.read()
  205. if sel == "M" or sel == "m" then
  206. modpol.interactions.message_user(
  207. viewer, user)
  208. completion()
  209. else
  210. completion()
  211. end
  212. end
  213. -- INTERACTION PRIMITIVES
  214. -- ======================
  215. --- Prints message to CLI.
  216. -- Buttons: message, done
  217. -- @function modpol.interactions.message
  218. -- @param user (string)
  219. -- @param message (string)
  220. function modpol.interactions.message(user, message)
  221. print(user .. ": " .. message)
  222. end
  223. --- Function: modpol.interactions.message_user
  224. -- Gets and sends a message from one user to another
  225. -- @param sender Name of user sending (string)
  226. -- @param recipient Name of user receiving (string)
  227. function modpol.interactions.message_user(sender, recipient)
  228. print("Enter your message for "..recipient..":")
  229. local sel = io.read()
  230. modpol.interactions.message(
  231. recipient,
  232. sel.." [from "..sender.."]")
  233. end
  234. --- Function: modpol.interactions.display
  235. -- Displays complex data to a user
  236. -- @param user Name of target user (string)
  237. -- @param title Title of display (string)
  238. -- @param message Content of message (string or table of strings)
  239. -- @param done Optional function for what happens when user is done
  240. function modpol.interactions.display(user, title, message, completion)
  241. local output = ""
  242. output = "\n-=< "..title.." >=-\n\n"
  243. if type(message) == "table" then
  244. output = table.concat(message,"\n")
  245. elseif type(message) == "string" then
  246. output = message
  247. elseif type(message) == "number" then
  248. output = message
  249. else
  250. modpol.interactions.message(
  251. self.initiator, "Error: message not typed for display")
  252. modpol.interactions.message(
  253. self.initiator, "Error: input not typed for display")
  254. if completion then completion() else
  255. modpol.intereactions.dashboard(user)
  256. end
  257. end
  258. print(message)
  259. print("\nEnter to continue")
  260. io.read()
  261. if completion then completion() else
  262. modpol.intereactions.dashboard(user)
  263. end
  264. end
  265. -- Applies "func" to user input
  266. -- Func input: user input (string)
  267. -- @function modpol.interactions.text_query
  268. -- @param User (string)
  269. -- @param Query (string)
  270. -- @param func (function)
  271. function modpol.interactions.text_query(user, query, func)
  272. print(user .. ": " .. query)
  273. answer = io.read()
  274. func(answer)
  275. end
  276. --- Output: Calls func on choice.
  277. -- func input: choice (string)
  278. -- @function modpol.interactions.dropdown_query
  279. -- @param user (string)
  280. -- @param label (string)
  281. -- @param options (table of strings)
  282. -- @param func (choice) (function)
  283. function modpol.interactions.dropdown_query(user, label, options, func)
  284. -- set up options
  285. local options_display = ""
  286. local options_number = 0
  287. for k,v in ipairs(options) do
  288. options_display = options_display .. k .. ". " ..
  289. options[k] .. "\n"
  290. options_number = options_number + 1
  291. end
  292. options_display = options_display .. "Select number:"
  293. if options_number == 0 then
  294. print("Error: No options given for dropdown")
  295. return nil
  296. end
  297. -- begin displaying
  298. print(user .. ": " .. label)
  299. print(options_display)
  300. -- read input and produce output
  301. local answer
  302. answer = io.read()
  303. answer = tonumber(answer)
  304. if answer then
  305. if answer >= 1 and answer <= options_number then
  306. print("Selection: " .. options[answer])
  307. func(options[answer])
  308. else
  309. print("Error: Not in dropdown range")
  310. return nil
  311. end
  312. else
  313. print("Error: Must be a number")
  314. return nil
  315. end
  316. end
  317. --- Allows user to select from a set of options
  318. -- @function modpol.interactions.checkbox_query
  319. -- @param user Name of user (string)
  320. -- @param label Query for user before options (string)
  321. -- @param options Table of options and their checked status in the form {{"option_1_string", true}, {"option_2_string", false}}
  322. -- @param func Function to be called with param "input", made up of the corrected table in the same format as the param options
  323. function modpol.interactions.checkbox_query(
  324. user, label, options, func)
  325. -- set up options
  326. local options_display = ""
  327. local options_number = 0
  328. for i,v in ipairs(options) do
  329. local checked = false
  330. if v[2] then checked = true end
  331. if checked then
  332. checked = "x"
  333. else
  334. checked = " "
  335. end
  336. options_display = options_display..i..". ["..
  337. checked.."] "..v[1].."\n"
  338. options_number = options_number + 1
  339. end
  340. if options_number == 0 then
  341. print("Error: No options given for dropdown")
  342. return nil
  343. end
  344. options_display = options_display..
  345. "List comma-separated options to flip (e.g., 1,2,5):"
  346. -- begin displaying
  347. print(user .. ": " .. label)
  348. print(options_display)
  349. -- read input and produce output
  350. local answer = io.read()
  351. local answer_table = {}
  352. for match in (answer..","):gmatch("(.-)"..",") do
  353. table.insert(answer_table, tonumber(match))
  354. end
  355. local result_table = modpol.util.copy_table(options)
  356. for i,v in ipairs(answer_table) do
  357. if result_table[v] then
  358. -- flip the boolean on selected options
  359. result_table[v][2] = not result_table[v][2]
  360. end
  361. end
  362. func(result_table)
  363. end
  364. -- Function: modpol.interactions.binary_poll_user
  365. -- Params: user (string), question (string), func (function)
  366. -- func input: user input (string: y/n)
  367. -- Output: Applies "func" to user input
  368. function modpol.interactions.binary_poll_user(user, question, func)
  369. local query = "Poll for " .. user .. " (y/n): ".. question
  370. local answer
  371. repeat
  372. print(query)
  373. answer = io.read()
  374. until answer == "y" or answer == "n"
  375. if answer == "y" then
  376. modpol.interactions.message(user, "Response recorded")
  377. func("Yes")
  378. elseif answer == "n" then
  379. modpol.interactions.message(user, "Response recorded")
  380. func("No")
  381. else
  382. modpol.interactions.message(user, "Error: invalid response")
  383. end
  384. end
  385. -- COMPLEX INTERACTIONS
  386. -- ====================
  387. --- Output: broadcasts message to all org members
  388. -- @function modpol.interactions.message_org
  389. -- @param initiator (string)
  390. -- @param org (number or string)
  391. -- @param message (string)
  392. function modpol.interactions.message_org(initiator, org, message)
  393. local this_org = modpol.orgs.get_org(org)
  394. local users = this_org:list_members()
  395. for k,v in ipairs(users) do
  396. modpol.interactions.message(v, message)
  397. end
  398. end
  399. -- Function: modpol.interactions.binary_poll_org
  400. -- input: initator (user string), org_id (number)
  401. -- output: gets question from initiator, asks all org members, broadcasts answers
  402. -- TESTING
  403. --testing command
  404. function modpol.msg(text)
  405. modpol.interactions.message("TEST MSG",text)
  406. end