Vote clean up and admin additions (#82981)

## About The Pull Request

- Fixes `vote_delay` not being a thing. I broke this two years ago but
there's no bug report associated.

- Admins can now reset the vote delay (to let people vote again
instantly)

- Admins can now end the current vote immediately (rather than
cancelling)

- Custom multi and custom single combined into one vote

## Why It's Good For The Game

Makes voting a bit easier to use, both for admins and for coders adding
new votes.


![image](https://github.com/tgstation/tgstation/assets/51863163/40b8857c-76b7-4a58-82bc-1b82640d550a)

## Changelog

🆑 Melbert
admin: Custom Single and Custom Multi votes are now combined into one
vote
admin: Admins can now end votes instantly, rather than cancelling them
admin: Admins can now reset the vote cooldown
fix: Vote cooldown actually applies now
/🆑
This commit is contained in:
MrMelbert
2024-05-01 17:55:01 -05:00
committed by GitHub
parent 906f4cdf49
commit d1cadb24f9
9 changed files with 308 additions and 200 deletions

View File

@@ -346,3 +346,6 @@
#define VOTE_WINNER_METHOD_WEIGHTED_RANDOM "Weighted Random"
/// There is no winner for this vote.
#define VOTE_WINNER_METHOD_NONE "None"
/// Returned by [/datum/vote/proc/can_be_initiated] to denote the vote is valid and can be initiated.
#define VOTE_AVAILABLE "Vote Available"

View File

@@ -184,13 +184,13 @@
/// minimum time between voting sessions (deciseconds, 10 minute default)
/datum/config_entry/number/vote_delay
default = 6000
default = 10 MINUTES
integer = FALSE
min_val = 0
/// length of voting period (deciseconds, default 1 minute)
/datum/config_entry/number/vote_period
default = 600
default = 1 MINUTES
integer = FALSE
min_val = 0

View File

@@ -18,6 +18,8 @@ SUBSYSTEM_DEF(vote)
var/list/voted = list()
/// A list of all ckeys currently voting for the current vote.
var/list/voting = list()
/// World.time we started our last vote
var/last_vote_time = -INFINITY
/datum/controller/subsystem/vote/Initialize()
for(var/vote_type in subtypesof(/datum/vote))
@@ -30,13 +32,17 @@ SUBSYSTEM_DEF(vote)
return SS_INIT_SUCCESS
// Called by master_controller
/datum/controller/subsystem/vote/fire()
if(!current_vote)
return
current_vote.time_remaining = round((current_vote.started_time + CONFIG_GET(number/vote_period) - world.time) / 10)
if(current_vote.time_remaining < 0)
end_vote()
/// Ends the current vote.
/datum/controller/subsystem/vote/proc/end_vote()
ASSERT(current_vote)
process_vote_result()
SStgui.close_uis(src)
reset()
@@ -168,24 +174,10 @@ SUBSYSTEM_DEF(vote)
* * vote_type - The type of vote to initiate. Can be a [/datum/vote] typepath, a [/datum/vote] instance, or the name of a vote datum.
* * vote_initiator_name - The ckey (if player initiated) or name that initiated a vote. Ex: "UristMcAdmin", "the server"
* * vote_initiator - If a person / mob initiated the vote, this is the mob that did it
* * forced - Whether we're forcing the vote to go through regardless of existing votes or other circumstances. Note: If the vote is admin created, forced becomes true regardless.
* * forced - Whether we're forcing the vote to go through regardless of existing votes or other circumstances.
*/
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, vote_initiator_name, mob/vote_initiator, forced = FALSE)
// Even if it's forced we can't vote before we're set up
if(!MC_RUNNING(init_stage))
if(vote_initiator)
to_chat(vote_initiator, span_warning("You cannot start vote now, the server is not done initializing."))
return FALSE
// Check if we have unlimited voting power.
// Admin started (or forced) voted will go through even if there's an ongoing vote,
// if voting is on cooldown, or regardless if a vote is config disabled (in some cases)
var/unlimited_vote_power = forced || !!GLOB.admin_datums[vote_initiator?.ckey]
if(current_vote && !unlimited_vote_power)
if(vote_initiator)
to_chat(vote_initiator, span_warning("There is already a vote in progress! Please wait for it to finish."))
if(!can_vote_start(vote_initiator, forced))
return FALSE
// Get our actual datum
@@ -212,7 +204,7 @@ SUBSYSTEM_DEF(vote)
return FALSE
// Vote can't be initiated in our circumstances? No vote
if(!to_vote.can_be_initiated(vote_initiator, unlimited_vote_power))
if(to_vote.can_be_initiated(forced) != VOTE_AVAILABLE)
return FALSE
// Okay, we're ready to actually create a vote -
@@ -223,8 +215,12 @@ SUBSYSTEM_DEF(vote)
if(!to_vote.create_vote(vote_initiator))
return FALSE
if(!vote_initiator_name && vote_initiator)
vote_initiator_name = vote_initiator.key
// Okay, the vote's happening now, for real. Set it up.
current_vote = to_vote
last_vote_time = world.time
var/duration = CONFIG_GET(number/vote_period)
var/to_display = current_vote.initiate_vote(vote_initiator_name, duration)
@@ -248,6 +244,36 @@ SUBSYSTEM_DEF(vote)
return TRUE
/**
* Checks if we can start a vote.
*
* * vote_initiator - The mob that initiated the vote.
* * forced - Whether we're forcing the vote to go through regardless of existing votes or other circumstances.
*
* Returns TRUE if we can start a vote, FALSE if we can't.
*/
/datum/controller/subsystem/vote/proc/can_vote_start(mob/vote_initiator, forced)
// Even if it's forced we can't vote before we're set up
if(!MC_RUNNING(init_stage))
if(vote_initiator)
to_chat(vote_initiator, span_warning("You cannot start a vote now, the server is not done initializing."))
return FALSE
if(forced)
return TRUE
var/next_allowed_time = last_vote_time + CONFIG_GET(number/vote_delay)
if(next_allowed_time > world.time)
if(vote_initiator)
to_chat(vote_initiator, span_warning("A vote was initiated recently. You must wait [DisplayTimeText(next_allowed_time - world.time)] before a new vote can be started!"))
return FALSE
if(current_vote)
if(vote_initiator)
to_chat(vote_initiator, span_warning("There is already a vote in progress! Please wait for it to finish."))
return FALSE
return TRUE
/datum/controller/subsystem/vote/ui_state()
return GLOB.always_state
@@ -282,11 +308,12 @@ SUBSYSTEM_DEF(vote)
if(!istype(vote))
continue
var/can_vote = vote.can_be_initiated(is_lower_admin)
var/list/vote_data = list(
"name" = vote_name,
"canBeInitiated" = vote.can_be_initiated(forced = is_lower_admin),
"canBeInitiated" = can_vote == VOTE_AVAILABLE,
"config" = vote.is_config_enabled(),
"message" = vote.message,
"message" = can_vote == VOTE_AVAILABLE ? vote.default_message : can_vote,
)
if(vote == current_vote)
@@ -310,9 +337,15 @@ SUBSYSTEM_DEF(vote)
all_vote_data += list(vote_data)
data["possibleVotes"] = all_vote_data
data["LastVoteTime"] = last_vote_time - world.time
return data
/datum/controller/subsystem/vote/ui_static_data(mob/user)
var/list/data = list()
data["VoteCD"] = CONFIG_GET(number/vote_delay)
return data
/datum/controller/subsystem/vote/ui_act(action, params)
. = ..()
if(.)
@@ -323,19 +356,37 @@ SUBSYSTEM_DEF(vote)
switch(action)
if("cancel")
if(!voter.client?.holder)
message_admins("[key_name(voter)] tried to cancel the current vote while having no admin holder, \
this is potentially a malicious exploit and worth noting.")
return
voter.log_message("cancelled a vote.", LOG_ADMIN)
message_admins("[key_name_admin(voter)] has cancelled the current vote.")
SStgui.close_uis(src)
reset()
return TRUE
if("endNow")
if(!voter.client?.holder)
message_admins("[key_name(voter)] tried to end the current vote while having no admin holder, \
this is potentially a malicious exploit and worth noting.")
return
voter.log_message("ended the current vote early", LOG_ADMIN)
message_admins("[key_name_admin(voter)] has ended the current vote.")
end_vote()
return TRUE
if("toggleVote")
var/datum/vote/selected = possible_votes[params["voteName"]]
if(!istype(selected))
return
if(!check_rights_for(voter.client, R_ADMIN))
message_admins("[key_name(voter)] tried to toggle vote availability while having improper rights, \
this is potentially a malicious exploit and worth noting.")
return
return selected.toggle_votable(voter)
return selected.toggle_votable()
if("callVote")
var/datum/vote/selected = possible_votes[params["voteName"]]
@@ -344,7 +395,12 @@ SUBSYSTEM_DEF(vote)
// Whether the user actually can initiate this vote is checked in initiate_vote,
// meaning you can't spoof initiate a vote you're not supposed to be able to
return initiate_vote(selected, voter.key, voter)
return initiate_vote(
vote_type = selected,
vote_initiator_name = voter.key,
vote_initiator = voter,
forced = !!GLOB.admin_datums[voter.ckey],
)
if("voteSingle")
return submit_single_vote(voter, params["voteOption"])
@@ -352,6 +408,15 @@ SUBSYSTEM_DEF(vote)
if("voteMulti")
return submit_multi_vote(voter, params["voteOption"])
if("resetCooldown")
if(!voter.client.holder)
message_admins("[key_name(voter)] tried to reset the vote cooldown while having no admin holder, \
this is potentially a malicious exploit and worth noting.")
return
last_vote_time = -INFINITY
return TRUE
/datum/controller/subsystem/vote/ui_close(mob/user)
voting -= user.client?.ckey
@@ -360,6 +425,10 @@ SUBSYSTEM_DEF(vote)
set category = "OOC"
set name = "Vote"
if(!SSvote.initialized)
to_chat(usr, span_notice("<i>Voting is not set up yet!</i>"))
return
SSvote.ui_interact(usr)
/// Datum action given to mobs that allows players to vote on the current vote.

View File

@@ -15,19 +15,8 @@
var/list/default_choices
/// Does the name of this vote contain the word "vote"?
var/contains_vote_in_name = FALSE
/// What message do we want to pass to the player-side vote panel as a tooltip?
var/message = "Click to initiate a vote."
// Internal values used when tracking ongoing votes.
// Don't mess with these, change the above values / override procs for subtypes.
/// An assoc list of [all choices] to [number of votes in the current running vote].
var/list/choices = list()
/// A assoc list of [ckey] to [what they voted for in the current running vote].
var/list/choices_by_ckey = list()
/// The world time this vote was started.
var/started_time
/// The time remaining in this vote's run.
var/time_remaining
/// What message do we show as the tooltip of this vote if the vote can be initiated?
var/default_message = "Click to initiate a vote."
/// The counting method we use for votes.
var/count_method = VOTE_COUNT_METHOD_SINGLE
/// The method for selecting a winner.
@@ -35,6 +24,17 @@
/// Should we show details about the number of votes submitted for each option?
var/display_statistics = TRUE
// Internal values used when tracking ongoing votes.
// Don't mess with these, change the above values / override procs for subtypes.
/// An assoc list of [all choices] to [number of votes in the current running vote].
VAR_FINAL/list/choices = list()
/// A assoc list of [ckey] to [what they voted for in the current running vote].
VAR_FINAL/list/choices_by_ckey = list()
/// The world time this vote was started.
VAR_FINAL/started_time = -1
/// The time remaining in this vote's run.
VAR_FINAL/time_remaining = -1
/**
* Used to determine if this vote is a possible
* vote type for the vote subsystem.
@@ -55,14 +55,13 @@
choices.Cut()
choices_by_ckey.Cut()
started_time = null
time_remaining = null
time_remaining = -1
/**
* If this vote has a config associated, toggles it between enabled and disabled.
* Returns TRUE on a successful toggle, FALSE otherwise
*/
/datum/vote/proc/toggle_votable(mob/toggler)
return FALSE
/datum/vote/proc/toggle_votable()
return
/**
* If this vote has a config associated, returns its value (True or False, usually).
@@ -74,20 +73,18 @@
/**
* Checks if the passed mob can initiate this vote.
*
* Return TRUE if the mob can begin the vote, allowing anyone to actually vote on it.
* Return FALSE if the mob cannot initiate the vote.
* * forced - if being invoked by someone who is an admin
*
* Return VOTE_AVAILABLE if the mob can initiate the vote.
* Return a string with the reason why the mob can't initiate the vote.
*/
/datum/vote/proc/can_be_initiated(mob/by_who, forced = FALSE)
/datum/vote/proc/can_be_initiated(forced = FALSE)
SHOULD_CALL_PARENT(TRUE)
if(started_time)
var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay))
if(next_allowed_time > world.time && !forced)
message = "A vote was initiated recently. You must wait [DisplayTimeText(next_allowed_time - world.time)] before a new vote can be started!"
return FALSE
if(!forced && !is_config_enabled())
return "This vote is currently disabled by the server configuration."
message = initial(message)
return TRUE
return VOTE_AVAILABLE
/**
* Called prior to the vote being initiated.

View File

@@ -1,14 +1,9 @@
/// The max amount of options someone can have in a custom vote.
#define MAX_CUSTOM_VOTE_OPTIONS 10
/datum/vote/custom_vote/single
name = "Custom Standard"
message = "Click here to start a custom vote (one selection per voter)"
/datum/vote/custom_vote/multi
name = "Custom Multi"
message = "Click here to start a custom multi vote (multiple selections per voter)"
count_method = VOTE_COUNT_METHOD_MULTI
/datum/vote/custom_vote
name = "Custom"
default_message = "Click here to start a custom vote."
// Custom votes ares always accessible.
/datum/vote/custom_vote/is_accessible_vote()
@@ -17,23 +12,45 @@
/datum/vote/custom_vote/reset()
default_choices = null
override_question = null
count_method = VOTE_COUNT_METHOD_SINGLE
return ..()
/datum/vote/custom_vote/can_be_initiated(mob/by_who, forced = FALSE)
/datum/vote/custom_vote/can_be_initiated(forced)
. = ..()
if(!.)
return FALSE
if(. != VOTE_AVAILABLE)
return .
if(forced)
return .
// Custom votes can only be created if they're forced to be made.
// (Either an admin makes it, or otherwise.)
return forced
return "Only admins can create custom votes."
/datum/vote/custom_vote/create_vote(mob/vote_creator)
var/custom_count_method = tgui_input_list(
user = vote_creator,
message = "Single or multiple choice?",
title = "Choice Method",
items = list("Single", "Multiple"),
default = "Single",
)
switch(custom_count_method)
if("Single")
count_method = VOTE_COUNT_METHOD_SINGLE
if("Multiple")
count_method = VOTE_COUNT_METHOD_MULTI
if(null)
return FALSE
else
stack_trace("Got '[custom_count_method]' in create_vote() for custom voting.")
to_chat(vote_creator, span_boldwarning("Unknown choice method. Contact a coder."))
return FALSE
var/custom_win_method = tgui_input_list(
vote_creator,
"How should the vote winner be determined?",
"Winner Method",
list("Simple", "Weighted Random", "No Winner"),
user = vote_creator,
message = "How should the vote winner be determined?",
title = "Winner Method",
items = list("Simple", "Weighted Random", "No Winner"),
default = "Simple",
)
switch(custom_win_method)
@@ -43,7 +60,10 @@
winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM
if("No Winner")
winner_method = VOTE_WINNER_METHOD_NONE
if(null)
return FALSE
else
stack_trace("Got '[custom_win_method]' in create_vote() for custom voting.")
to_chat(vote_creator, span_boldwarning("Unknown winner method. Contact a coder."))
return FALSE
@@ -54,7 +74,7 @@
list("Yes", "No"),
)
if(display_stats == null)
if(isnull(display_stats))
return FALSE
display_statistics = display_stats == "Yes"
@@ -74,6 +94,9 @@
if(!length(default_choices))
return FALSE
// Sanity for all the tgui input stalling we are doing
if(isnull(vote_creator.client?.holder))
return FALSE
return ..()

View File

@@ -1,6 +1,6 @@
/datum/vote/map_vote
name = "Map"
message = "Vote for next round's map!"
default_message = "Vote for next round's map!"
count_method = VOTE_COUNT_METHOD_SINGLE
winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM
display_statistics = FALSE
@@ -30,44 +30,26 @@
SSmapping.map_voted = TRUE // voted by not voting, very sad.
return FALSE
/datum/vote/map_vote/toggle_votable(mob/toggler)
if(!toggler)
CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.")
if(!check_rights_for(toggler.client, R_ADMIN))
return FALSE
/datum/vote/map_vote/toggle_votable()
CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map))
return TRUE
/datum/vote/map_vote/is_config_enabled()
return CONFIG_GET(flag/allow_vote_map)
/datum/vote/map_vote/can_be_initiated(mob/by_who, forced = FALSE)
/datum/vote/map_vote/can_be_initiated(forced)
. = ..()
if(!.)
return FALSE
if(. != VOTE_AVAILABLE)
return .
if(forced)
return TRUE
return VOTE_AVAILABLE
var/number_of_choices = length(check_population())
if(number_of_choices < 2)
message = "There [number_of_choices == 1 ? "is only one map" : "are no maps"] to choose from."
return FALSE
return "There [number_of_choices == 1 ? "is only one map" : "are no maps"] to choose from."
if(SSmapping.map_vote_rocked)
return TRUE
if(!CONFIG_GET(flag/allow_vote_map))
message = "Map voting is disabled by server configuration settings."
return FALSE
return VOTE_AVAILABLE
if(SSmapping.map_voted)
message = "The next map has already been selected."
return FALSE
message = initial(message)
return TRUE
return "The next map has already been selected."
return VOTE_AVAILABLE
/// Before we create a vote, remove all maps from our choices that are outside of our population range.
/// Note that this can result in zero remaining choices for our vote, which is not ideal (but ultimately okay).

View File

@@ -7,7 +7,8 @@
CHOICE_RESTART,
CHOICE_CONTINUE,
)
message = "Vote to restart the ongoing round."
default_message = "Vote to restart the ongoing round. \
Only works if there are no non-AFK admins online."
/// This proc checks to see if any admins are online for the purposes of this vote to see if it can pass. Returns TRUE if there are valid admins online (Has +SERVER and is not AFK), FALSE otherwise.
/datum/vote/restart_vote/proc/admins_present()
@@ -19,36 +20,24 @@
return FALSE
/datum/vote/restart_vote/toggle_votable(mob/toggler)
if(!toggler)
CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.")
if(!check_rights_for(toggler.client, R_ADMIN))
return FALSE
/datum/vote/restart_vote/toggle_votable()
CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart))
return TRUE
/datum/vote/restart_vote/is_config_enabled()
return CONFIG_GET(flag/allow_vote_restart)
/datum/vote/restart_vote/can_be_initiated(mob/by_who, forced)
/datum/vote/restart_vote/create_vote(mob/vote_creator)
. = ..()
if(!.)
return FALSE
return
if(!admins_present())
return
async_alert_about_admins(vote_creator)
if(!forced && !CONFIG_GET(flag/allow_vote_restart))
message = "Restart voting is disabled by server configuration settings."
return FALSE
// We still want players to be able to vote to restart even if valid admins are online. Let's update the message just so that the player is aware of this fact.
// We don't want to lock-out the vote though, so we'll return TRUE.
if(admins_present())
message = "Regardless of the results of this vote, the round will not automatically restart because an admin is online."
return TRUE
message = initial(message)
return TRUE
/datum/vote/restart_vote/proc/async_alert_about_admins(mob/vote_creator)
set waitfor = FALSE
tgui_alert(vote_creator, "Note: Regardless of the results of this vote, \
the round will not automatically restart because an active admin is online.")
/datum/vote/restart_vote/get_vote_result(list/non_voters)
if(!CONFIG_GET(flag/default_no_vote))

View File

@@ -10,58 +10,40 @@
CHOICE_TO_ROCK,
CHOICE_NOT_TO_ROCK,
)
message = "Override the current map vote."
default_message = "Override the current map vote."
/// The number of times we have rocked the vote thus far.
var/rocking_votes = 0
/datum/vote/rock_the_vote/toggle_votable(mob/toggler)
if(!toggler)
CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.")
if(!check_rights_for(toggler.client, R_ADMIN))
return FALSE
/datum/vote/rock_the_vote/toggle_votable()
CONFIG_SET(flag/allow_rock_the_vote, !CONFIG_GET(flag/allow_rock_the_vote))
return TRUE
/datum/vote/rock_the_vote/is_config_enabled()
return CONFIG_GET(flag/allow_rock_the_vote)
/datum/vote/rock_the_vote/can_be_initiated(mob/by_who, forced)
/datum/vote/rock_the_vote/can_be_initiated(forced)
. = ..()
if(!.)
return FALSE
if(!forced && !CONFIG_GET(flag/allow_rock_the_vote))
message = "Rocking the vote is disabled by this server's configuration settings."
return FALSE
if(. != VOTE_AVAILABLE)
return .
if(SSticker.current_state == GAME_STATE_FINISHED)
message = "The game is finished, no map votes can be initiated."
return FALSE
return "The game is finished, no map votes can be initiated."
if(rocking_votes >= CONFIG_GET(number/max_rocking_votes))
message = "The maximum number of times to rock the vote has been reached."
return FALSE
return "The maximum number of times to rock the vote has been reached."
if(SSmapping.map_vote_rocked)
message = "The vote has already been rocked! Initiate a map vote!"
return FALSE
return "The vote has already been rocked! Initiate a map vote!"
if(!SSmapping.map_voted)
message = "Rocking the vote is disabled because no map has been voted on yet!"
return FALSE
return "Rocking the vote is disabled because no map has been voted on yet!"
if(SSmapping.map_force_chosen)
message = "Rocking the vote is disabled because an admin has forcibly set the map!"
return FALSE
return "Rocking the vote is disabled because an admin has forcibly set the map!"
if(EMERGENCY_ESCAPED_OR_ENDGAMED && SSmapping.map_voted)
message = "The emergency shuttle has already left the station and the next map has already been chosen!"
return FALSE
return "The emergency shuttle has already left the station and the next map has already been chosen!"
message = initial(message)
return TRUE
return VOTE_AVAILABLE
/datum/vote/rock_the_vote/finalize_vote(winning_option)
rocking_votes++

View File

@@ -5,6 +5,7 @@ import {
Box,
Button,
Collapsible,
Dimmer,
Icon,
LabeledList,
NoticeBox,
@@ -59,11 +60,13 @@ type Data = {
possibleVotes: Vote[];
user: UserData;
voting: string[];
LastVoteTime: number;
VoteCD: number;
};
export const VotePanel = (props) => {
const { data } = useBackend<Data>();
const { currentVote, user } = data;
const { act, data } = useBackend<Data>();
const { currentVote, user, LastVoteTime, VoteCD } = data;
/**
* Adds the voting type to title if there is an ongoing vote.
@@ -81,7 +84,19 @@ export const VotePanel = (props) => {
<Window title={windowTitle} width={400} height={500}>
<Window.Content>
<Stack fill vertical>
<Section title="Create Vote">
<Section
title="Create Vote"
buttons={
!!user.isLowerAdmin && (
<Button
icon="refresh"
content="Reset Cooldown"
disabled={LastVoteTime + VoteCD <= 0}
onClick={() => act('resetCooldown')}
/>
)
}
>
<VoteOptions />
{!!user.isLowerAdmin && currentVote && <VotersList />}
</Section>
@@ -93,26 +108,54 @@ export const VotePanel = (props) => {
);
};
const VoteOptionDimmer = (props) => {
const { data } = useBackend<Data>();
const { LastVoteTime, VoteCD } = data;
return (
<Dimmer>
<Box textAlign="center">
<Box fontSize={2} bold>
Vote Cooldown
</Box>
<Box fontSize={1.5}>{Math.floor((VoteCD + LastVoteTime) / 10)}s</Box>
</Box>
</Dimmer>
);
};
/**
* The create vote options menu. Only upper admins can disable voting.
* @returns A section visible to everyone with vote options.
*/
const VoteOptions = (props) => {
const { act, data } = useBackend<Data>();
const { possibleVotes, user } = data;
const { possibleVotes, user, LastVoteTime, VoteCD } = data;
return (
<Stack.Item>
<Collapsible title="Start a Vote">
<Section>
{LastVoteTime + VoteCD > 0 && <VoteOptionDimmer />}
<Stack vertical justify="space-between">
{possibleVotes.map((option) => (
<Stack.Item key={option.name}>
{!!user.isLowerAdmin && option.config !== VoteConfig.None && (
<Stack>
{!!user.isLowerAdmin && (
<Stack.Item>
<Button.Checkbox
mr={option.config === VoteConfig.Disabled ? 1 : 1.6}
width={7}
color="red"
checked={option.config === VoteConfig.Enabled}
disabled={!user.isUpperAdmin}
disabled={
!user.isUpperAdmin ||
option.config === VoteConfig.None
}
tooltip={
option.config === VoteConfig.None
? 'This vote cannot be disabled.'
: null
}
content={
option.config === VoteConfig.Enabled
? 'Enabled'
@@ -124,8 +167,12 @@ const VoteOptions = (props) => {
})
}
/>
</Stack.Item>
)}
<Stack.Item>
<Button
width={12}
textAlign={'center'}
disabled={!option.canBeInitiated}
tooltip={option.message}
content={option.name}
@@ -136,8 +183,11 @@ const VoteOptions = (props) => {
}
/>
</Stack.Item>
</Stack>
</Stack.Item>
))}
</Stack>
</Section>
</Collapsible>
</Stack.Item>
);
@@ -153,11 +203,11 @@ const VotersList = (props) => {
return (
<Stack.Item>
<Collapsible
title={`View Voters${
data.voting.length ? `: ${data.voting.length}` : ''
title={`View Active Voters${
data.voting.length ? ` (${data.voting.length})` : ''
}`}
>
<Section height={8} fill scrollable>
<Section height={4} fill scrollable>
{data.voting.map((voter) => {
return <Box key={voter}>{voter}</Box>;
})}
@@ -275,6 +325,17 @@ const TimePanel = (props) => {
{currentVote?.timeRemaining || 0}s
</Box>
{!!user.isLowerAdmin && (
<Stack>
<Stack.Item>
<Button
color="green"
disabled={!user.isLowerAdmin || !currentVote}
onClick={() => act('endNow')}
>
End Now
</Button>
</Stack.Item>
<Stack.Item>
<Button
color="red"
disabled={!user.isLowerAdmin || !currentVote}
@@ -282,6 +343,8 @@ const TimePanel = (props) => {
>
Cancel Vote
</Button>
</Stack.Item>
</Stack>
)}
</Stack>
</Section>