local issue = Issue:by_id(param.get("issue_id"), atom.integer) -- TODO patch for project voting if not issue.closed and config.alternative_voting and config.alternative_voting[tostring(issue.policy.id)] then local voting_config = config.alternative_voting[tostring(issue.policy.id)] local url = encode.url { module = voting_config.module, view = voting_config.view, params = { issue_id = issue.id } } return request.redirect{ external = url } end if not issue then execute.view { module = "index", view = "404" } return end local member_id = param.get("member_id", atom.integer) local member local readonly = false local preview = param.get("preview") and true or false if member_id then if not issue.closed then execute.view{ module = "index", view = "403" } return end member = Member:by_id(member_id) readonly = true end if issue.closed then if not member then member = app.session.member end readonly = true end if preview then readonly = true end local submit_button_text = _"Finish voting" local edit_button_text = _"Edit again" if issue.closed then submit_button_text = _"Save voting comment" edit_button_text = _"Edit voting comment" end execute.view { module = "issue", view = "_head", params = { issue = issue } } local direct_voter if member then direct_voter = DirectVoter:by_pk(issue.id, member.id) else member = app.session.member direct_voter = DirectVoter:by_pk(issue.id, member.id) end local tempvoting_string = param.get("scoring") local tempvotings = {} if tempvoting_string then for match in tempvoting_string:gmatch("([^;]+)") do for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do tempvotings[tonumber(initiative_id)] = tonumber(grade) end end end local initiatives = issue:get_reference_selector("initiatives"):add_where("initiative.admitted"):add_order_by("initiative.satisfied_supporter_count DESC"):exec() local min_grade = -1; local max_grade = 1; for i, initiative in ipairs(initiatives) do -- TODO performance initiative.vote = Vote:by_pk(initiative.id, member.id) if tempvotings[initiative.id] then initiative.vote = {} initiative.vote.grade = tempvotings[initiative.id] end if initiative.vote then if initiative.vote.grade > max_grade then max_grade = initiative.vote.grade end if initiative.vote.grade < min_grade then min_grade = initiative.vote.grade end end end local sections = {} for i = min_grade, max_grade do sections[i] = {} for j, initiative in ipairs(initiatives) do if (initiative.vote and initiative.vote.grade == i) or (not initiative.vote and i == 0) then sections[i][#(sections[i])+1] = initiative end end end local approval_count, disapproval_count = 0, 0 for i = min_grade, -1 do if #sections[i] > 0 then disapproval_count = disapproval_count + 1 end end local approval_count = 0 for i = 1, max_grade do if #sections[i] > 0 then approval_count = approval_count + 1 end end if not readonly then slot.put('') slot.put('') end ui.script{ script = function() slot.put( "voting_text_approval_single = ", encode.json(_"Approval [single entry]"), ";\n", "voting_text_approval_multi = ", encode.json(_"Approval [many entries]"), ";\n", "voting_text_first_preference_single = ", encode.json(_"Approval (first preference) [single entry]"), ";\n", "voting_text_first_preference_multi = ", encode.json(_"Approval (first preference) [many entries]"), ";\n", "voting_text_second_preference_single = ", encode.json(_"Approval (second preference) [single entry]"), ";\n", "voting_text_second_preference_multi = ", encode.json(_"Approval (second preference) [many entries]"), ";\n", "voting_text_third_preference_single = ", encode.json(_"Approval (third preference) [single entry]"), ";\n", "voting_text_third_preference_multi = ", encode.json(_"Approval (third preference) [many entries]"), ";\n", "voting_text_numeric_preference_single = ", encode.json(_"Approval (#th preference) [single entry]"), ";\n", "voting_text_numeric_preference_multi = ", encode.json(_"Approval (#th preference) [many entries]"), ";\n", "voting_text_abstention_single = ", encode.json(_"Abstention [single entry]"), ";\n", "voting_text_abstention_multi = ", encode.json(_"Abstention [many entries]"), ";\n", "voting_text_disapproval_above_one_single = ", encode.json(_"Disapproval (prefer to lower block) [single entry]"), ";\n", "voting_text_disapproval_above_one_multi = ", encode.json(_"Disapproval (prefer to lower block) [many entries]"), ";\n", "voting_text_disapproval_above_many_single = ", encode.json(_"Disapproval (prefer to lower blocks) [single entry]"), ";\n", "voting_text_disapproval_above_many_multi = ", encode.json(_"Disapproval (prefer to lower blocks) [many entries]"), ";\n", "voting_text_disapproval_above_last_single = ", encode.json(_"Disapproval (prefer to last block) [single entry]"), ";\n", "voting_text_disapproval_above_last_multi = ", encode.json(_"Disapproval (prefer to last block) [many entries]"), ";\n", "voting_text_disapproval_single = ", encode.json(_"Disapproval [single entry]"), ";\n", "voting_text_disapproval_multi = ", encode.json(_"Disapproval [many entries]"), ";\n" ) end } ui.container{ attr = { class = "mdl-grid" }, content = function() ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() if preview then ui.tag{ content = _"Preview of voting ballot" } elseif readonly then local str = _("Ballot of '#{member_name}'", { member_name = string.format( '%s', encode.url{ module = "member", view = "show", id = member.id }, encode.html(member.name) ) }) ui.tag{ content = function () slot.put ( str ) end } else ui.tag{ content = _"Voting" } end end } end } ui.container{ attr = { class = "mdl-card__content" }, content = function() ui.form{ record = direct_voter, attr = { id = "voting_form", class = readonly and "voting_form_readonly" or "voting_form_active" }, module = "vote", action = "update", params = { issue_id = issue.id }, content = function() if not readonly or preview then local scoring = param.get("scoring") if not scoring then for i, initiative in ipairs(initiatives) do local vote = initiative.vote if vote then tempvotings[initiative.id] = vote.grade else tempvotings[initiative.id] = 0 end end local tempvotings_list = {} for key, val in pairs(tempvotings) do tempvotings_list[#tempvotings_list+1] = tostring(key) .. ":" .. tostring(val) end if #tempvotings_list > 0 then scoring = table.concat(tempvotings_list, ";") else scoring = "" end end slot.put('') end if preview then ui.container{ content = _"Your choice" } elseif not readonly then ui.container{ content = _"Make your choice by placing the initiatives" } end ui.container{ attr = { id = "voting" }, content = function() local approval_index, disapproval_index = 0, 0 local approval_used, disapproval_used for grade = max_grade, min_grade, -1 do local entries = sections[grade] local class if grade > 0 then class = "approval" elseif grade < 0 then class = "disapproval" else class = "abstention" end if #entries > 0 or (grade == 1 and not approval_used) or (grade == -1 and not disapproval_used) or grade == 0 then ui.container{ attr = { class = class }, content = function() local heading if class == "approval" then approval_used = true approval_index = approval_index + 1 if approval_count > 1 then if approval_index == 1 then if #entries == 1 then heading = _"Approval (first preference) [single entry]" else heading = _"Approval (first preference) [many entries]" end elseif approval_index == 2 then if #entries == 1 then heading = _"Approval (second preference) [single entry]" else heading = _"Approval (second preference) [many entries]" end elseif approval_index == 3 then if #entries == 1 then heading = _"Approval (third preference) [single entry]" else heading = _"Approval (third preference) [many entries]" end else if #entries == 1 then heading = _"Approval (#th preference) [single entry]" else heading = _"Approval (#th preference) [many entries]" end end else if #entries == 1 then heading = _"Approval [single entry]" else heading = _"Approval [many entries]" end end elseif class == "abstention" then if #entries == 1 then heading = _"Abstention [single entry]" else heading = _"Abstention [many entries]" end elseif class == "disapproval" then disapproval_used = true disapproval_index = disapproval_index + 1 if disapproval_count > disapproval_index + 1 then if #entries == 1 then heading = _"Disapproval (prefer to lower blocks) [single entry]" else heading = _"Disapproval (prefer to lower blocks) [many entries]" end elseif disapproval_count == 2 and disapproval_index == 1 then if #entries == 1 then heading = _"Disapproval (prefer to lower block) [single entry]" else heading = _"Disapproval (prefer to lower block) [many entries]" end elseif disapproval_index == disapproval_count - 1 then if #entries == 1 then heading = _"Disapproval (prefer to last block) [single entry]" else heading = _"Disapproval (prefer to last block) [many entries]" end else if #entries == 1 then heading = _"Disapproval [single entry]" else heading = _"Disapproval [many entries]" end end end ui.tag { tag = "div", attr = { class = "cathead " }, content = heading } for i, initiative in ipairs(entries) do ui.container{ attr = { class = "movable", id = "entry_" .. tostring(initiative.id) }, content = function() local initiators_selector = initiative:get_reference_selector("initiating_members") :add_where("accepted") local initiators = initiators_selector:exec() local initiator_names = {} for i, initiator in ipairs(initiators) do initiator_names[#initiator_names+1] = initiator.name end local initiator_names_string = table.concat(initiator_names, ", ") ui.container{ attr = { style = "float: right; position: relative;" }, content = function() ui.link{ attr = { class = "clickable" }, content = _"Show", module = "initiative", view = "show", id = initiative.id } slot.put(" ") ui.link{ attr = { class = "clickable", target = "_blank" }, content = _"(new window)", module = "initiative", view = "show", id = initiative.id } if not readonly then slot.put(" ") ui.image{ attr = { class = "grabber" }, static = "icons/grabber.png" } end end } if not readonly then ui.container{ attr = { style = "float: left; position: relative;" }, content = function() ui.tag{ tag = "button", attr = { onclick = "if (jsFail) return true; voting_moveUp(this.parentNode.parentNode); return(false);", name = "move_up_" .. tostring(initiative.id), class = "clickable mdl-button mdl-js-button mdl-button--icon", alt = _"Move up", }, content = function() ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "arrow_upward" } end } ui.tag{ tag = "button", attr = { onclick = "if (jsFail) return true; voting_moveDown(this.parentNode.parentNode); return(false);", name = "move_down_" .. tostring(initiative.id), class = "clickable mdl-button mdl-js-button mdl-button--icon", alt = _"Move down" }, content = function() ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "arrow_downward" } end } slot.put(" ") end } end ui.container{ content = function() ui.tag{ attr = { class = "initiative_name" }, content = function() ui.tag{ content = "i" .. initiative.id .. ": " } ui.tag{ content = initiative.shortened_name } end } slot.put("
") for i, initiator in ipairs(initiators) do ui.link{ attr = { class = "clickable" }, content = function () execute.view{ module = "member_image", view = "_show", params = { member = initiator, image_type = "avatar", show_dummy = true, class = "micro_avatar", popup_text = text } } end, module = "member", view = "show", id = initiator.id } slot.put(" ") ui.tag{ content = initiator.name } slot.put(" ") end end } end } end end } end end end } if app.session.member_id and preview then local formatting_engine = param.get("formatting_engine") or config.enforce_formatting_engine local comment = param.get("comment") if comment and #comment > 0 then local rendered_comment = format.wiki_text(comment, formatting_engine) ui.container{ content = _"Voting comment" } ui.container { attr = { class = "member_statement" }, content = function() slot.put(rendered_comment) end } slot.put("
") end end if (readonly or direct_voter and direct_voter.comment) and not preview and not (app.session.member_id == member.id) then local text if direct_voter and direct_voter.comment_changed then text = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) }) elseif direct_voter and direct_voter.comment then text = _"Voting comment" end if text then ui.container{ content = text } end if direct_voter and direct_voter.comment then local rendered_comment = direct_voter:get_content('html') ui.container { attr = { class = "member_statement" }, content = function() slot.put(rendered_comment) end } slot.put("
") end end if app.session.member_id and app.session.member_id == member.id then if (not readonly or direct_voter) and not preview then ui.container{ content = function() ui.container{ content = _"Voting comment (optional)" } ui.field.text{ name = "comment", multiline = true, value = param.get("comment") or direct_voter and direct_voter.comment, attr = { style = "height: 10ex; width: 100%;" }, } end } end if preview then if not config.enforce_formatting_engine then ui.field.hidden{ name = "formatting_engine", value = param.get("formatting_engine") } end ui.field.hidden{ name = "comment", value = param.get("comment") or direct_voter and direct_voter.comment } end if not readonly or direct_voter or preview then if preview then slot.put(" ") ui.tag{ tag = "input", attr = { type = "submit", class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", name = issue.closed and "update_comment" or nil, value = submit_button_text -- finish voting / update comment } } end if not preview then ui.tag{ tag = "input", attr = { type = "submit", name = "preview", class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", value = _"Preview", } } else slot.put("   ") ui.tag{ tag = "input", attr = { type = "submit", name = "edit", class = "mdl-button mdl-js-button mdl-button--raised", value = edit_button_text, } } end end end end } slot.put("
") ui.link{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, text = _"Cancel", module = "issue", view = "show", id = issue.id } if direct_voter and not issue.closed then slot.put("   ") ui.link { attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, module = "vote", action = "update", params = { issue_id = issue.id, discard = true }, routing = { default = { mode = "redirect", module = "issue", view = "show", id = issue.id } }, text = _"Discard my vote" } end end } end } if issue.state == "finished_with_winner" or issue.state == "finished_without_winner" then local members_selector = Member:new_selector() :join("delegating_voter", nil, "delegating_voter.member_id = member.id") :add_where{ "delegating_voter.issue_id = ?", issue.id } :add_where{ "delegating_voter.delegate_member_ids[1] = ?", member.id } :add_field("delegating_voter.weight", "voter_weight") :add_field("delegating_voter.ownweight", "ownweight") :join("issue", nil, "issue.id = delegating_voter.issue_id") ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() ui.tag{ content = _"Incoming delegations" } end } end } ui.container{ attr = { class = "mdl-card__content" }, content = function() execute.view{ module = "member", view = "_list", params = { members_selector = members_selector, trustee = member, issue = issue, initiative = initiative, for_votes = true, no_filter = true, member_class = "sidebarRow sidebarRowNarrow", } } end } end } end end } end }