liquid_feedback_frontend

view app/main/vote/list.lua @ 1859:02c34183b6df

Fixed wrong filename in INSTALL file
author bsw
date Tue Nov 28 18:54:51 2023 +0100 (5 months ago)
parents 7dcbcbf492e3
children
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 ui.container{ attr = { class = "mdl-grid" }, content = function()
159 ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function()
161 ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function()
162 ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function()
163 ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function()
164 if preview then
165 ui.tag{ content = _"Preview of voting ballot" }
166 elseif readonly then
167 local str = _("Ballot of '#{member_name}'", { member_name = string.format(
168 '<a href="%s">%s</a>',
169 encode.url{ module = "member", view = "show", id = member.id },
170 encode.html(member.name)
171 ) })
172 ui.tag{ content = function () slot.put ( str ) end }
173 else
174 ui.tag{ content = _"Voting" }
175 end
176 end }
177 end }
179 ui.container{ attr = { class = "mdl-card__content" }, content = function()
181 ui.form{
182 record = direct_voter,
183 attr = {
184 id = "voting_form",
185 class = readonly and "voting_form_readonly" or "voting_form_active"
186 },
187 module = "vote",
188 action = "update",
189 params = { issue_id = issue.id },
190 content = function()
191 if not readonly or preview then
192 local scoring = param.get("scoring")
193 if not scoring then
194 for i, initiative in ipairs(initiatives) do
195 local vote = initiative.vote
196 if vote then
197 tempvotings[initiative.id] = vote.grade
198 else
199 tempvotings[initiative.id] = 0
200 end
201 end
202 local tempvotings_list = {}
203 for key, val in pairs(tempvotings) do
204 tempvotings_list[#tempvotings_list+1] = tostring(key) .. ":" .. tostring(val)
205 end
206 if #tempvotings_list > 0 then
207 scoring = table.concat(tempvotings_list, ";")
208 else
209 scoring = ""
210 end
211 end
212 slot.put('<input type="hidden" name="scoring" value="' .. scoring .. '"/>')
213 end
214 if preview then
215 ui.container{ content = _"Your choice" }
216 elseif not readonly then
217 ui.container{ content = _"Make your choice by placing the initiatives" }
218 end
220 ui.container{
221 attr = { id = "voting" },
222 content = function()
223 local approval_index, disapproval_index = 0, 0
224 local approval_used, disapproval_used
225 for grade = max_grade, min_grade, -1 do
226 local entries = sections[grade]
227 local class
228 if grade > 0 then
229 class = "approval"
230 elseif grade < 0 then
231 class = "disapproval"
232 else
233 class = "abstention"
234 end
235 if
236 #entries > 0 or
237 (grade == 1 and not approval_used) or
238 (grade == -1 and not disapproval_used) or
239 grade == 0
240 then
241 ui.container{
242 attr = { class = class },
243 content = function()
244 local heading
245 if class == "approval" then
246 approval_used = true
247 approval_index = approval_index + 1
248 if approval_count > 1 then
249 if approval_index == 1 then
250 if #entries == 1 then
251 heading = _"Approval (first preference) [single entry]"
252 else
253 heading = _"Approval (first preference) [many entries]"
254 end
255 elseif approval_index == 2 then
256 if #entries == 1 then
257 heading = _"Approval (second preference) [single entry]"
258 else
259 heading = _"Approval (second preference) [many entries]"
260 end
261 elseif approval_index == 3 then
262 if #entries == 1 then
263 heading = _"Approval (third preference) [single entry]"
264 else
265 heading = _"Approval (third preference) [many entries]"
266 end
267 else
268 if #entries == 1 then
269 heading = _"Approval (#th preference) [single entry]"
270 else
271 heading = _"Approval (#th preference) [many entries]"
272 end
273 end
274 else
275 if #entries == 1 then
276 heading = _"Approval [single entry]"
277 else
278 heading = _"Approval [many entries]"
279 end
280 end
281 elseif class == "abstention" then
282 if #entries == 1 then
283 heading = _"Abstention [single entry]"
284 else
285 heading = _"Abstention [many entries]"
286 end
287 elseif class == "disapproval" then
288 disapproval_used = true
289 disapproval_index = disapproval_index + 1
290 if disapproval_count > disapproval_index + 1 then
291 if #entries == 1 then
292 heading = _"Disapproval (prefer to lower blocks) [single entry]"
293 else
294 heading = _"Disapproval (prefer to lower blocks) [many entries]"
295 end
296 elseif disapproval_count == 2 and disapproval_index == 1 then
297 if #entries == 1 then
298 heading = _"Disapproval (prefer to lower block) [single entry]"
299 else
300 heading = _"Disapproval (prefer to lower block) [many entries]"
301 end
302 elseif disapproval_index == disapproval_count - 1 then
303 if #entries == 1 then
304 heading = _"Disapproval (prefer to last block) [single entry]"
305 else
306 heading = _"Disapproval (prefer to last block) [many entries]"
307 end
308 else
309 if #entries == 1 then
310 heading = _"Disapproval [single entry]"
311 else
312 heading = _"Disapproval [many entries]"
313 end
314 end
315 end
316 ui.tag {
317 tag = "div",
318 attr = { class = "cathead " },
319 content = heading
320 }
321 for i, initiative in ipairs(entries) do
322 ui.container{
323 attr = {
324 class = "movable",
325 id = "entry_" .. tostring(initiative.id)
326 },
327 content = function()
328 local initiators_selector = initiative:get_reference_selector("initiating_members")
329 :add_where("accepted")
330 local initiators = initiators_selector:exec()
331 local initiator_names = {}
332 for i, initiator in ipairs(initiators) do
333 initiator_names[#initiator_names+1] = initiator.name
334 end
335 local initiator_names_string = table.concat(initiator_names, ", ")
336 ui.container{
337 attr = { style = "float: right; position: relative;" },
338 content = function()
339 ui.link{
340 attr = { class = "clickable" },
341 content = _"Show",
342 module = "initiative",
343 view = "show",
344 id = initiative.id
345 }
346 slot.put(" ")
347 ui.link{
348 attr = { class = "clickable", target = "_blank" },
349 content = _"(new window)",
350 module = "initiative",
351 view = "show",
352 id = initiative.id
353 }
354 if not readonly then
355 slot.put(" ")
356 ui.image{ attr = { class = "grabber" }, static = "icons/grabber.png" }
357 end
358 end
359 }
360 if not readonly then
361 ui.container{
362 attr = { style = "float: left; position: relative;" },
363 content = function()
364 ui.tag{
365 tag = "button",
366 attr = {
367 onclick = "if (jsFail) return true; voting_moveUp(this.parentNode.parentNode); return(false);",
368 name = "move_up_" .. tostring(initiative.id),
369 class = "clickable mdl-button mdl-js-button mdl-button--icon",
370 alt = _"Move up",
371 },
372 content = function()
373 ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "arrow_upward" }
374 end
375 }
376 ui.tag{
377 tag = "button",
378 attr = {
379 onclick = "if (jsFail) return true; voting_moveDown(this.parentNode.parentNode); return(false);",
380 name = "move_down_" .. tostring(initiative.id),
381 class = "clickable mdl-button mdl-js-button mdl-button--icon",
382 alt = _"Move down"
383 },
384 content = function()
385 ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "arrow_downward" }
386 end
387 }
388 slot.put("&nbsp;")
389 end
390 }
391 end
392 ui.container{
393 content = function()
394 ui.tag{ attr = { class = "initiative_name" }, content = function()
395 ui.tag{ content = "i" .. initiative.id .. ": " }
396 ui.tag{ content = initiative.shortened_name }
397 end }
398 slot.put("<br />")
399 for i, initiator in ipairs(initiators) do
400 ui.link{
401 attr = { class = "clickable" },
402 content = function ()
403 execute.view{
404 module = "member_image",
405 view = "_show",
406 params = {
407 member = initiator,
408 image_type = "avatar",
409 show_dummy = true,
410 class = "micro_avatar",
411 popup_text = text
412 }
413 }
414 end,
415 module = "member", view = "show", id = initiator.id
416 }
417 slot.put(" ")
418 ui.tag{ content = initiator.name }
419 slot.put(" ")
420 end
421 end
422 }
423 end
424 }
425 end
426 end
427 }
428 end
429 end
430 end
431 }
432 if app.session.member_id and preview then
433 local formatting_engine = param.get("formatting_engine") or config.enforce_formatting_engine
434 local comment = param.get("comment")
435 if comment and #comment > 0 then
436 local rendered_comment = format.wiki_text(comment, formatting_engine)
437 ui.container{ content = _"Voting comment" }
438 ui.container { attr = { class = "member_statement" }, content = function()
439 slot.put(rendered_comment)
440 end }
441 slot.put("<br />")
442 end
443 end
444 if (readonly or direct_voter and direct_voter.comment) and not preview and not (app.session.member_id == member.id) then
445 local text
446 if direct_voter and direct_voter.comment_changed then
447 text = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) })
448 elseif direct_voter and direct_voter.comment then
449 text = _"Voting comment"
450 end
451 if text then
452 ui.container{ content = text }
453 end
454 if direct_voter and direct_voter.comment then
455 local rendered_comment = direct_voter:get_content('html')
456 ui.container { attr = { class = "member_statement" }, content = function()
457 slot.put(rendered_comment)
458 end }
459 slot.put("<br />")
460 end
461 end
462 if app.session.member_id and app.session.member_id == member.id then
463 if (not readonly or direct_voter) and not preview then
464 ui.container{ content = function()
465 ui.container{ content = _"Voting comment (optional)" }
466 ui.field.text{
467 name = "comment",
468 multiline = true,
469 value = param.get("comment") or direct_voter and direct_voter.comment,
470 attr = { style = "height: 10ex; width: 100%;" },
471 }
472 end }
473 end
475 if preview then
476 if not config.enforce_formatting_engine then
477 ui.field.hidden{ name = "formatting_engine", value = param.get("formatting_engine") }
478 end
479 ui.field.hidden{ name = "comment", value = param.get("comment") or direct_voter and direct_voter.comment }
480 end
482 if not readonly or direct_voter or preview then
483 if preview then
484 slot.put(" ")
485 ui.tag{
486 tag = "input",
487 attr = {
488 type = "submit",
489 class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored",
490 name = issue.closed and "update_comment" or nil,
491 value = submit_button_text -- finish voting / update comment
492 }
493 }
494 end
495 if not preview then
496 ui.tag{
497 tag = "input",
498 attr = {
499 type = "submit",
500 name = "preview",
501 class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored",
502 value = _"Preview",
503 }
504 }
505 else
506 slot.put(" &nbsp; ")
507 ui.tag{
508 tag = "input",
509 attr = {
510 type = "submit",
511 name = "edit",
512 class = "mdl-button mdl-js-button mdl-button--raised",
513 value = edit_button_text,
514 }
515 }
516 end
517 end
518 end
519 end
520 }
521 slot.put("<br />")
522 ui.link{
523 attr = { class = "mdl-button mdl-js-button mdl-button--raised" },
524 text = _"Cancel",
525 module = "issue",
526 view = "show",
527 id = issue.id
528 }
529 if direct_voter and not issue.closed then
530 slot.put(" &nbsp; ")
531 ui.link {
532 attr = { class = "mdl-button mdl-js-button mdl-button--raised" },
533 module = "vote", action = "update",
534 params = {
535 issue_id = issue.id,
536 discard = true
537 },
538 routing = {
539 default = {
540 mode = "redirect",
541 module = "issue",
542 view = "show",
543 id = issue.id
544 }
545 },
546 text = _"Discard my vote"
547 }
548 end
550 end }
551 end }
553 if issue.state == "finished_with_winner"
554 or issue.state == "finished_without_winner"
555 then
557 local members_selector = Member:new_selector()
558 :join("delegating_voter", nil, "delegating_voter.member_id = member.id")
559 :add_where{ "delegating_voter.issue_id = ?", issue.id }
560 :add_where{ "delegating_voter.delegate_member_ids[1] = ?", member.id }
561 :add_field("delegating_voter.weight", "voter_weight")
562 :add_field("delegating_voter.ownweight", "ownweight")
563 :join("issue", nil, "issue.id = delegating_voter.issue_id")
565 ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function()
566 ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function()
567 ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function()
568 ui.tag{ content = _"Incoming delegations" }
569 end }
570 end }
572 ui.container{ attr = { class = "mdl-card__content" }, content = function()
573 execute.view{
574 module = "member",
575 view = "_list",
576 params = {
577 members_selector = members_selector,
578 trustee = member,
579 issue = issue,
580 initiative = initiative,
581 for_votes = true, no_filter = true,
582 member_class = "sidebarRow sidebarRowNarrow",
583 }
584 }
585 end }
586 end }
587 end
590 end }
591 end }

Impressum / About Us