liquid_feedback_frontend

view app/main/vote/list.lua @ 1309:32cc544d5a5b

Cumulative patch for upcoming frontend version 4
author bsw/jbe
date Sun Jul 15 14:07:29 2018 +0200 (2018-07-15)
parents 904f6807f7fa
children 09423cf96e9d
line source
1 local issue = Issue:by_id(param.get("issue_id"), atom.integer)
3 -- TODO patch for project voting
4 if not issue.closed and config.alternative_voting and config.alternative_voting[tostring(issue.policy.id)] then
5 local voting_config = config.alternative_voting[tostring(issue.policy.id)]
7 local url = encode.url {
8 module = voting_config.module,
9 view = voting_config.view,
10 params = { issue_id = issue.id }
11 }
13 return request.redirect{ external = url }
14 end
16 if not issue then
17 execute.view { module = "index", view = "404" }
18 return
19 end
21 local member_id = param.get("member_id", atom.integer)
22 local member
23 local readonly = false
25 local preview = param.get("preview") and true or false
27 if member_id then
28 if not issue.closed then
29 execute.view{ module = "index", view = "403" }
30 return
31 end
32 member = Member:by_id(member_id)
33 readonly = true
34 end
36 if issue.closed then
37 if not member then
38 member = app.session.member
39 end
40 readonly = true
41 end
43 if preview then
44 readonly = true
45 end
47 local submit_button_text = _"Finish voting"
48 local edit_button_text = _"Edit again"
50 if issue.closed then
51 submit_button_text = _"Save voting comment"
52 edit_button_text = _"Edit voting comment"
53 end
55 execute.view {
56 module = "issue", view = "_head", params = { issue = issue }
57 }
59 local direct_voter
61 if member then
62 direct_voter = DirectVoter:by_pk(issue.id, member.id)
63 else
64 member = app.session.member
65 direct_voter = DirectVoter:by_pk(issue.id, member.id)
66 end
70 local tempvoting_string = param.get("scoring")
72 local tempvotings = {}
73 if tempvoting_string then
74 for match in tempvoting_string:gmatch("([^;]+)") do
75 for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do
76 tempvotings[tonumber(initiative_id)] = tonumber(grade)
77 end
78 end
79 end
81 local initiatives = issue:get_reference_selector("initiatives"):add_where("initiative.admitted"):add_order_by("initiative.satisfied_supporter_count DESC"):exec()
83 local min_grade = -1;
84 local max_grade = 1;
86 for i, initiative in ipairs(initiatives) do
87 -- TODO performance
88 initiative.vote = Vote:by_pk(initiative.id, member.id)
89 if tempvotings[initiative.id] then
90 initiative.vote = {}
91 initiative.vote.grade = tempvotings[initiative.id]
92 end
93 if initiative.vote then
94 if initiative.vote.grade > max_grade then
95 max_grade = initiative.vote.grade
96 end
97 if initiative.vote.grade < min_grade then
98 min_grade = initiative.vote.grade
99 end
100 end
101 end
103 local sections = {}
104 for i = min_grade, max_grade do
105 sections[i] = {}
106 for j, initiative in ipairs(initiatives) do
107 if (initiative.vote and initiative.vote.grade == i) or (not initiative.vote and i == 0) then
108 sections[i][#(sections[i])+1] = initiative
109 end
110 end
111 end
113 local approval_count, disapproval_count = 0, 0
114 for i = min_grade, -1 do
115 if #sections[i] > 0 then
116 disapproval_count = disapproval_count + 1
117 end
118 end
119 local approval_count = 0
120 for i = 1, max_grade do
121 if #sections[i] > 0 then
122 approval_count = approval_count + 1
123 end
124 end
126 if not readonly then
127 slot.put('<script src="' .. request.get_relative_baseurl() .. 'static/js/dragdrop.js"></script>')
128 slot.put('<script src="' .. request.get_relative_baseurl() .. 'static/js/voting.js"></script>')
129 end
131 ui.script{
132 script = function()
133 slot.put(
134 "voting_text_approval_single = ", encode.json(_"Approval [single entry]"), ";\n",
135 "voting_text_approval_multi = ", encode.json(_"Approval [many entries]"), ";\n",
136 "voting_text_first_preference_single = ", encode.json(_"Approval (first preference) [single entry]"), ";\n",
137 "voting_text_first_preference_multi = ", encode.json(_"Approval (first preference) [many entries]"), ";\n",
138 "voting_text_second_preference_single = ", encode.json(_"Approval (second preference) [single entry]"), ";\n",
139 "voting_text_second_preference_multi = ", encode.json(_"Approval (second preference) [many entries]"), ";\n",
140 "voting_text_third_preference_single = ", encode.json(_"Approval (third preference) [single entry]"), ";\n",
141 "voting_text_third_preference_multi = ", encode.json(_"Approval (third preference) [many entries]"), ";\n",
142 "voting_text_numeric_preference_single = ", encode.json(_"Approval (#th preference) [single entry]"), ";\n",
143 "voting_text_numeric_preference_multi = ", encode.json(_"Approval (#th preference) [many entries]"), ";\n",
144 "voting_text_abstention_single = ", encode.json(_"Abstention [single entry]"), ";\n",
145 "voting_text_abstention_multi = ", encode.json(_"Abstention [many entries]"), ";\n",
146 "voting_text_disapproval_above_one_single = ", encode.json(_"Disapproval (prefer to lower block) [single entry]"), ";\n",
147 "voting_text_disapproval_above_one_multi = ", encode.json(_"Disapproval (prefer to lower block) [many entries]"), ";\n",
148 "voting_text_disapproval_above_many_single = ", encode.json(_"Disapproval (prefer to lower blocks) [single entry]"), ";\n",
149 "voting_text_disapproval_above_many_multi = ", encode.json(_"Disapproval (prefer to lower blocks) [many entries]"), ";\n",
150 "voting_text_disapproval_above_last_single = ", encode.json(_"Disapproval (prefer to last block) [single entry]"), ";\n",
151 "voting_text_disapproval_above_last_multi = ", encode.json(_"Disapproval (prefer to last block) [many entries]"), ";\n",
152 "voting_text_disapproval_single = ", encode.json(_"Disapproval [single entry]"), ";\n",
153 "voting_text_disapproval_multi = ", encode.json(_"Disapproval [many entries]"), ";\n"
154 )
155 end
156 }
158 if issue.state == "finished_with_winner"
159 or issue.state == "finished_without_winner"
160 then
162 local members_selector = Member:new_selector()
163 :join("delegating_voter", nil, "delegating_voter.member_id = member.id")
164 :add_where{ "delegating_voter.issue_id = ?", issue.id }
165 :add_where{ "delegating_voter.delegate_member_ids[1] = ?", member.id }
166 :add_field("delegating_voter.weight", "voter_weight")
167 :join("issue", nil, "issue.id = delegating_voter.issue_id")
169 ui.sidebar( "tab-members", function()
170 ui.sidebarHead(function()
171 ui.heading{ level = 4, content = _"Incoming delegations" }
172 end)
173 execute.view{
174 module = "member",
175 view = "_list",
176 params = {
177 members_selector = members_selector,
178 trustee = member,
179 issue = issue,
180 initiative = initiative,
181 for_votes = true, no_filter = true,
182 member_class = "sidebarRow sidebarRowNarrow",
183 }
184 }
185 end)
186 end
188 ui.container{ attr = { class = "mdl-grid" }, content = function()
189 ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function()
191 ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function()
192 ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function()
193 ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function()
194 if preview then
195 ui.tag{ content = _"Preview of voting ballot" }
196 elseif readonly then
197 local str = _("Ballot of '#{member_name}'", { member_name = string.format(
198 '<a href="%s">%s</a>',
199 encode.url{ module = "member", view = "show", id = member.id },
200 encode.html(member.name)
201 ) })
202 ui.tag{ content = function () slot.put ( str ) end }
203 else
204 ui.tag{ content = _"Voting" }
205 end
206 end }
207 end }
209 ui.container{ attr = { class = "mdl-card__content" }, content = function()
211 ui.form{
212 record = direct_voter,
213 attr = {
214 id = "voting_form",
215 class = readonly and "voting_form_readonly" or "voting_form_active"
216 },
217 module = "vote",
218 action = "update",
219 params = { issue_id = issue.id },
220 content = function()
221 if not readonly or preview then
222 local scoring = param.get("scoring")
223 if not scoring then
224 for i, initiative in ipairs(initiatives) do
225 local vote = initiative.vote
226 if vote then
227 tempvotings[initiative.id] = vote.grade
228 else
229 tempvotings[initiative.id] = 0
230 end
231 end
232 local tempvotings_list = {}
233 for key, val in pairs(tempvotings) do
234 tempvotings_list[#tempvotings_list+1] = tostring(key) .. ":" .. tostring(val)
235 end
236 if #tempvotings_list > 0 then
237 scoring = table.concat(tempvotings_list, ";")
238 else
239 scoring = ""
240 end
241 end
242 slot.put('<input type="hidden" name="scoring" value="' .. scoring .. '"/>')
243 end
244 if preview then
245 ui.container{ content = _"Your choice" }
246 elseif not readonly then
247 ui.container{ content = _"Make your choice by placing the initiatives" }
248 end
250 ui.container{
251 attr = { id = "voting" },
252 content = function()
253 local approval_index, disapproval_index = 0, 0
254 local approval_used, disapproval_used
255 for grade = max_grade, min_grade, -1 do
256 local entries = sections[grade]
257 local class
258 if grade > 0 then
259 class = "approval"
260 elseif grade < 0 then
261 class = "disapproval"
262 else
263 class = "abstention"
264 end
265 if
266 #entries > 0 or
267 (grade == 1 and not approval_used) or
268 (grade == -1 and not disapproval_used) or
269 grade == 0
270 then
271 ui.container{
272 attr = { class = class },
273 content = function()
274 local heading
275 if class == "approval" then
276 approval_used = true
277 approval_index = approval_index + 1
278 if approval_count > 1 then
279 if approval_index == 1 then
280 if #entries == 1 then
281 heading = _"Approval (first preference) [single entry]"
282 else
283 heading = _"Approval (first preference) [many entries]"
284 end
285 elseif approval_index == 2 then
286 if #entries == 1 then
287 heading = _"Approval (second preference) [single entry]"
288 else
289 heading = _"Approval (second preference) [many entries]"
290 end
291 elseif approval_index == 3 then
292 if #entries == 1 then
293 heading = _"Approval (third preference) [single entry]"
294 else
295 heading = _"Approval (third preference) [many entries]"
296 end
297 else
298 if #entries == 1 then
299 heading = _"Approval (#th preference) [single entry]"
300 else
301 heading = _"Approval (#th preference) [many entries]"
302 end
303 end
304 else
305 if #entries == 1 then
306 heading = _"Approval [single entry]"
307 else
308 heading = _"Approval [many entries]"
309 end
310 end
311 elseif class == "abstention" then
312 if #entries == 1 then
313 heading = _"Abstention [single entry]"
314 else
315 heading = _"Abstention [many entries]"
316 end
317 elseif class == "disapproval" then
318 disapproval_used = true
319 disapproval_index = disapproval_index + 1
320 if disapproval_count > disapproval_index + 1 then
321 if #entries == 1 then
322 heading = _"Disapproval (prefer to lower blocks) [single entry]"
323 else
324 heading = _"Disapproval (prefer to lower blocks) [many entries]"
325 end
326 elseif disapproval_count == 2 and disapproval_index == 1 then
327 if #entries == 1 then
328 heading = _"Disapproval (prefer to lower block) [single entry]"
329 else
330 heading = _"Disapproval (prefer to lower block) [many entries]"
331 end
332 elseif disapproval_index == disapproval_count - 1 then
333 if #entries == 1 then
334 heading = _"Disapproval (prefer to last block) [single entry]"
335 else
336 heading = _"Disapproval (prefer to last block) [many entries]"
337 end
338 else
339 if #entries == 1 then
340 heading = _"Disapproval [single entry]"
341 else
342 heading = _"Disapproval [many entries]"
343 end
344 end
345 end
346 ui.tag {
347 tag = "div",
348 attr = { class = "cathead " },
349 content = heading
350 }
351 for i, initiative in ipairs(entries) do
352 ui.container{
353 attr = {
354 class = "movable",
355 id = "entry_" .. tostring(initiative.id)
356 },
357 content = function()
358 local initiators_selector = initiative:get_reference_selector("initiating_members")
359 :add_where("accepted")
360 local initiators = initiators_selector:exec()
361 local initiator_names = {}
362 for i, initiator in ipairs(initiators) do
363 initiator_names[#initiator_names+1] = initiator.name
364 end
365 local initiator_names_string = table.concat(initiator_names, ", ")
366 ui.container{
367 attr = { style = "float: right; position: relative;" },
368 content = function()
369 ui.link{
370 attr = { class = "clickable" },
371 content = _"Show",
372 module = "initiative",
373 view = "show",
374 id = initiative.id
375 }
376 slot.put(" ")
377 ui.link{
378 attr = { class = "clickable", target = "_blank" },
379 content = _"(new window)",
380 module = "initiative",
381 view = "show",
382 id = initiative.id
383 }
384 if not readonly then
385 slot.put(" ")
386 ui.image{ attr = { class = "grabber" }, static = "icons/grabber.png" }
387 end
388 end
389 }
390 if not readonly then
391 ui.container{
392 attr = { style = "float: left; position: relative;" },
393 content = function()
394 ui.tag{
395 tag = "button",
396 attr = {
397 onclick = "if (jsFail) return true; voting_moveUp(this.parentNode.parentNode); return(false);",
398 name = "move_up_" .. tostring(initiative.id),
399 class = "clickable mdl-button mdl-js-button mdl-button--icon",
400 alt = _"Move up",
401 },
402 content = function()
403 ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "arrow_upward" }
404 end
405 }
406 ui.tag{
407 tag = "button",
408 attr = {
409 onclick = "if (jsFail) return true; voting_moveDown(this.parentNode.parentNode); return(false);",
410 name = "move_down_" .. tostring(initiative.id),
411 class = "clickable mdl-button mdl-js-button mdl-button--icon",
412 alt = _"Move down"
413 },
414 content = function()
415 ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "arrow_downward" }
416 end
417 }
418 slot.put("&nbsp;")
419 end
420 }
421 end
422 ui.container{
423 content = function()
424 ui.tag{ attr = { class = "initiative_name" }, content = function()
425 ui.tag{ content = "i" .. initiative.id .. ": " }
426 ui.tag{ content = initiative.shortened_name }
427 end }
428 slot.put("<br />")
429 for i, initiator in ipairs(initiators) do
430 ui.link{
431 attr = { class = "clickable" },
432 content = function ()
433 execute.view{
434 module = "member_image",
435 view = "_show",
436 params = {
437 member = initiator,
438 image_type = "avatar",
439 show_dummy = true,
440 class = "micro_avatar",
441 popup_text = text
442 }
443 }
444 end,
445 module = "member", view = "show", id = initiator.id
446 }
447 slot.put(" ")
448 ui.tag{ content = initiator.name }
449 slot.put(" ")
450 end
451 end
452 }
453 end
454 }
455 end
456 end
457 }
458 end
459 end
460 end
461 }
462 if app.session.member_id and preview then
463 local formatting_engine = param.get("formatting_engine") or config.enforce_formatting_engine
464 local comment = param.get("comment")
465 if comment and #comment > 0 then
466 local rendered_comment = format.wiki_text(comment, formatting_engine)
467 ui.container{ content = _"Voting comment" }
468 ui.container { attr = { class = "member_statement" }, content = function()
469 slot.put(rendered_comment)
470 end }
471 slot.put("<br />")
472 end
473 end
474 if (readonly or direct_voter and direct_voter.comment) and not preview and not (app.session.member_id == member.id) then
475 local text
476 if direct_voter and direct_voter.comment_changed then
477 text = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) })
478 elseif direct_voter and direct_voter.comment then
479 text = _"Voting comment"
480 end
481 if text then
482 ui.container{ content = text }
483 end
484 if direct_voter and direct_voter.comment then
485 local rendered_comment = direct_voter:get_content('html')
486 ui.container { attr = { class = "member_statement" }, content = function()
487 slot.put(rendered_comment)
488 end }
489 slot.put("<br />")
490 end
491 end
492 if app.session.member_id and app.session.member_id == member.id then
493 if (not readonly or direct_voter) and not preview then
494 ui.container{ content = function()
495 if not config.enforce_formatting_engine then
496 ui.field.select{
497 label = _"Wiki engine for statement",
498 name = "formatting_engine",
499 foreign_records = config.formatting_engines,
500 attr = {id = "formatting_engine"},
501 foreign_id = "id",
502 foreign_name = "name",
503 value = param.get("formatting_engine") or direct_voter and direct_voter.formatting_engine
504 }
505 end
506 ui.container{ content = _"Voting comment (optional)" }
507 ui.field.text{
508 name = "comment",
509 multiline = true,
510 value = param.get("comment") or direct_voter and direct_voter.comment,
511 attr = { style = "height: 10ex; width: 100%;" },
512 }
513 end }
514 end
516 if preview then
517 if not config.enforce_formatting_engine then
518 ui.field.hidden{ name = "formatting_engine", value = param.get("formatting_engine") }
519 end
520 ui.field.hidden{ name = "comment", value = param.get("comment") or direct_voter and direct_voter.comment }
521 end
523 if not readonly or direct_voter or preview then
524 if preview then
525 slot.put(" ")
526 ui.tag{
527 tag = "input",
528 attr = {
529 type = "submit",
530 class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored",
531 name = issue.closed and "update_comment" or nil,
532 value = submit_button_text -- finish voting / update comment
533 }
534 }
535 end
536 if not preview then
537 ui.tag{
538 tag = "input",
539 attr = {
540 type = "submit",
541 name = "preview",
542 class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored",
543 value = _"Preview",
544 }
545 }
546 else
547 slot.put(" &nbsp; ")
548 ui.tag{
549 tag = "input",
550 attr = {
551 type = "submit",
552 name = "edit",
553 class = "mdl-button mdl-js-button mdl-button--raised",
554 value = edit_button_text,
555 }
556 }
557 end
558 end
559 end
560 end
561 }
562 slot.put("<br />")
563 ui.link{
564 attr = { class = "mdl-button mdl-js-button mdl-button--raised" },
565 text = _"Cancel",
566 module = "issue",
567 view = "show",
568 id = issue.id
569 }
570 if direct_voter then
571 slot.put(" &nbsp; ")
572 ui.link {
573 attr = { class = "mdl-button mdl-js-button mdl-button--raised" },
574 module = "vote", action = "update",
575 params = {
576 issue_id = issue.id,
577 discard = true
578 },
579 routing = {
580 default = {
581 mode = "redirect",
582 module = "issue",
583 view = "show",
584 id = issue.id
585 }
586 },
587 text = _"Discard my vote"
588 }
589 end
591 end }
592 end }
593 end }
594 end }

Impressum / About Us