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 }