mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-31 00:58:26 +01:00
feat: Vote panel UI cleanup; auto-close fix (#96166) (conflict resolved)
This commit is contained in:
@@ -216,217 +216,210 @@ const ChoicesPanel = (props) => {
|
||||
const { currentVote, user } = data;
|
||||
|
||||
return (
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable title="Active Vote">
|
||||
{currentVote && currentVote.countMethod === VoteSystem.VOTE_SINGLE ? (
|
||||
<NoticeBox success>Select one option</NoticeBox>
|
||||
) : null}
|
||||
{currentVote &&
|
||||
currentVote.choices.length !== 0 &&
|
||||
currentVote.countMethod === VoteSystem.VOTE_SINGLE ? (
|
||||
<LabeledList>
|
||||
{currentVote.choices.map((choice) => (
|
||||
<Box key={choice.name}>
|
||||
<LabeledList.Item
|
||||
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
|
||||
textAlign="right"
|
||||
buttons={
|
||||
<Button
|
||||
tooltip={
|
||||
user.isGhost && 'Ghost voting was disabled by an admin.'
|
||||
}
|
||||
disabled={
|
||||
user.singleSelection === choice.name || user.isGhost
|
||||
}
|
||||
onClick={() => {
|
||||
act('voteSingle', { voteOption: choice.name });
|
||||
}}
|
||||
>
|
||||
Vote
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{user.singleSelection &&
|
||||
choice.name === user.singleSelection && (
|
||||
<>
|
||||
{currentVote && currentVote.countMethod === VoteSystem.VOTE_SINGLE ? (
|
||||
<NoticeBox success>Select one option</NoticeBox>
|
||||
) : null}
|
||||
{currentVote &&
|
||||
currentVote.choices.length !== 0 &&
|
||||
currentVote.countMethod === VoteSystem.VOTE_SINGLE ? (
|
||||
<LabeledList>
|
||||
{currentVote.choices.map((choice) => (
|
||||
<Box key={choice.name}>
|
||||
<LabeledList.Item
|
||||
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
|
||||
textAlign="right"
|
||||
buttons={
|
||||
<Button
|
||||
tooltip={
|
||||
user.isGhost && 'Ghost voting was disabled by an admin.'
|
||||
}
|
||||
disabled={
|
||||
user.singleSelection === choice.name || user.isGhost
|
||||
}
|
||||
onClick={() => {
|
||||
act('voteSingle', { voteOption: choice.name });
|
||||
}}
|
||||
>
|
||||
Vote
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{user.singleSelection &&
|
||||
choice.name === user.singleSelection && (
|
||||
<Icon align="right" mr={2} color="green" name="vote-yea" />
|
||||
)}
|
||||
{currentVote.displayStatistics || user.isLowerAdmin /* SKYRAT EDIT*/ ? `${choice.votes} Votes` : null}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
</Box>
|
||||
))}
|
||||
</LabeledList>
|
||||
) : null}
|
||||
{currentVote && currentVote.countMethod === VoteSystem.VOTE_MULTI ? (
|
||||
<NoticeBox success>Select any number of options</NoticeBox>
|
||||
) : null}
|
||||
{currentVote &&
|
||||
currentVote.choices.length !== 0 &&
|
||||
currentVote.countMethod === VoteSystem.VOTE_MULTI ? (
|
||||
<LabeledList>
|
||||
{currentVote.choices.map((choice) => (
|
||||
<Box key={choice.name}>
|
||||
<LabeledList.Item
|
||||
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
|
||||
textAlign="right"
|
||||
buttons={
|
||||
<Button
|
||||
tooltip={
|
||||
user.isGhost && 'Ghost voting was disabled by an admin.'
|
||||
}
|
||||
disabled={user.isGhost}
|
||||
onClick={() => {
|
||||
act('voteMulti', { voteOption: choice.name });
|
||||
}}
|
||||
>
|
||||
Vote
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{user.multiSelection &&
|
||||
// BUBBER EDIT CHANGE - Original: [user.ckey.concat(choice.name)]
|
||||
user.multiSelection[`${user.ckey}_${choice.name}`] === 1 ? (
|
||||
<Icon align="right" mr={2} color="blue" name="vote-yea" />
|
||||
) : null}
|
||||
{
|
||||
user.isLowerAdmin
|
||||
? `${choice.votes} Votes`
|
||||
: '' /* SKYRAT EDIT*/
|
||||
}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
</Box>
|
||||
))}
|
||||
</LabeledList>
|
||||
) : null}
|
||||
{/* BUBBER EDIT ADDITION - Ranked Choice Voting */}
|
||||
{currentVote && currentVote.countMethod === VoteSystem.VOTE_RANKED ? (
|
||||
<NoticeBox success>
|
||||
Click options to rank them in order of preference. Click again to
|
||||
remove.
|
||||
</NoticeBox>
|
||||
) : null}
|
||||
{currentVote &&
|
||||
currentVote.choices.length !== 0 &&
|
||||
currentVote.countMethod === VoteSystem.VOTE_RANKED ? (
|
||||
<LabeledList>
|
||||
{currentVote.choices
|
||||
.map((choice) => {
|
||||
// Get all current ranks for this user
|
||||
const userRanks: Record<string, number> = {};
|
||||
let maxRank = 0;
|
||||
currentVote.choices.forEach((c) => {
|
||||
const rankKey = `${user.ckey}_${c.name}`;
|
||||
const rank = user.multiSelection?.[rankKey] || 0;
|
||||
if (rank > 0) {
|
||||
userRanks[c.name] = rank;
|
||||
maxRank = Math.max(maxRank, rank);
|
||||
}
|
||||
});
|
||||
|
||||
// Get this choice's current rank
|
||||
const rankKey = `${user.ckey}_${choice.name}`;
|
||||
const currentRank = user.multiSelection?.[rankKey] || 0;
|
||||
|
||||
return {
|
||||
choice,
|
||||
currentRank,
|
||||
userRanks,
|
||||
maxRank,
|
||||
};
|
||||
})
|
||||
// Sort by rank (unranked at bottom)
|
||||
.sort((a, b) => {
|
||||
if (a.currentRank === 0 && b.currentRank === 0) {
|
||||
// If both unranked, sort alphabetically
|
||||
return a.choice.name.localeCompare(b.choice.name);
|
||||
{currentVote.displayStatistics ? `${choice.votes} Votes` : null}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
</Box>
|
||||
))}
|
||||
</LabeledList>
|
||||
) : null}
|
||||
{currentVote && currentVote.countMethod === VoteSystem.VOTE_MULTI ? (
|
||||
<NoticeBox success>Select any number of options</NoticeBox>
|
||||
) : null}
|
||||
{currentVote &&
|
||||
currentVote.choices.length !== 0 &&
|
||||
currentVote.countMethod === VoteSystem.VOTE_MULTI ? (
|
||||
<LabeledList>
|
||||
{currentVote.choices.map((choice) => (
|
||||
<Box key={choice.name}>
|
||||
<LabeledList.Item
|
||||
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
|
||||
textAlign="right"
|
||||
buttons={
|
||||
<Button
|
||||
tooltip={
|
||||
user.isGhost && 'Ghost voting was disabled by an admin.'
|
||||
}
|
||||
disabled={user.isGhost}
|
||||
onClick={() => {
|
||||
act('voteMulti', { voteOption: choice.name });
|
||||
}}
|
||||
>
|
||||
Vote
|
||||
</Button>
|
||||
}
|
||||
if (a.currentRank === 0) return 1; // a is unranked, move to bottom
|
||||
if (b.currentRank === 0) return -1; // b is unranked, move to bottom
|
||||
return a.currentRank - b.currentRank; // sort by rank
|
||||
})
|
||||
.map(({ choice, currentRank, userRanks, maxRank }) => {
|
||||
// Function to get button text
|
||||
const getButtonText = () => {
|
||||
if (currentRank === 0) {
|
||||
return 'Vote';
|
||||
}
|
||||
return `Choice #${currentRank}`;
|
||||
};
|
||||
>
|
||||
{user.multiSelection &&
|
||||
user.multiSelection[user.ckey.concat(choice.name)] === 1 ? (
|
||||
<Icon align="right" mr={2} color="blue" name="vote-yea" />
|
||||
) : null}
|
||||
{choice.votes} Votes
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
</Box>
|
||||
))}
|
||||
</LabeledList>
|
||||
) : null}
|
||||
{/* BUBBER EDIT ADDITION - Ranked Choice Voting */}
|
||||
{currentVote && currentVote.countMethod === VoteSystem.VOTE_RANKED ? (
|
||||
<NoticeBox success>
|
||||
Click options to rank them in order of preference. Click again to
|
||||
remove.
|
||||
</NoticeBox>
|
||||
) : null}
|
||||
{currentVote &&
|
||||
currentVote.choices.length !== 0 &&
|
||||
currentVote.countMethod === VoteSystem.VOTE_RANKED ? (
|
||||
<LabeledList>
|
||||
{currentVote.choices
|
||||
.map((choice) => {
|
||||
// Get all current ranks for this user
|
||||
const userRanks: Record<string, number> = {};
|
||||
let maxRank = 0;
|
||||
currentVote.choices.forEach((c) => {
|
||||
const rankKey = `${user.ckey}_${c.name}`;
|
||||
const rank = user.multiSelection?.[rankKey] || 0;
|
||||
if (rank > 0) {
|
||||
userRanks[c.name] = rank;
|
||||
maxRank = Math.max(maxRank, rank);
|
||||
}
|
||||
});
|
||||
|
||||
// Function to handle vote click
|
||||
const handleVoteClick = () => {
|
||||
if (currentRank > 0) {
|
||||
// Remove this rank and shift others up
|
||||
const newRanks: Record<string, number> = {};
|
||||
Object.entries(userRanks).forEach(
|
||||
([name, rank]: [string, number]) => {
|
||||
if (name === choice.name) {
|
||||
return; // Skip this one as we're removing it
|
||||
}
|
||||
if (rank > currentRank) {
|
||||
newRanks[name] = rank - 1; // Shift up
|
||||
} else {
|
||||
newRanks[name] = rank; // Keep same
|
||||
}
|
||||
},
|
||||
);
|
||||
// Send all rank updates
|
||||
Object.entries(newRanks).forEach(
|
||||
([name, newRank]: [string, number]) => {
|
||||
act('voteRanked', {
|
||||
voteOption: name,
|
||||
voteRank: newRank,
|
||||
});
|
||||
},
|
||||
);
|
||||
// Remove this rank
|
||||
act('voteRanked', {
|
||||
voteOption: choice.name,
|
||||
voteRank: 0,
|
||||
});
|
||||
} else {
|
||||
// Add as next rank
|
||||
act('voteRanked', {
|
||||
voteOption: choice.name,
|
||||
voteRank: maxRank + 1,
|
||||
});
|
||||
}
|
||||
};
|
||||
// Get this choice's current rank
|
||||
const rankKey = `${user.ckey}_${choice.name}`;
|
||||
const currentRank = user.multiSelection?.[rankKey] || 0;
|
||||
|
||||
return (
|
||||
<Box key={choice.name}>
|
||||
<LabeledList.Item
|
||||
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
|
||||
textAlign="right"
|
||||
buttons={
|
||||
<Button
|
||||
tooltip={
|
||||
user.isGhost &&
|
||||
'Ghost voting was disabled by an admin.'
|
||||
}
|
||||
selected={currentRank > 0}
|
||||
disabled={user.isGhost}
|
||||
onClick={handleVoteClick}
|
||||
>
|
||||
{getButtonText()}
|
||||
</Button>
|
||||
return {
|
||||
choice,
|
||||
currentRank,
|
||||
userRanks,
|
||||
maxRank,
|
||||
};
|
||||
})
|
||||
// Sort by rank (unranked at bottom)
|
||||
.sort((a, b) => {
|
||||
if (a.currentRank === 0 && b.currentRank === 0) {
|
||||
// If both unranked, sort alphabetically
|
||||
return a.choice.name.localeCompare(b.choice.name);
|
||||
}
|
||||
if (a.currentRank === 0) return 1; // a is unranked, move to bottom
|
||||
if (b.currentRank === 0) return -1; // b is unranked, move to bottom
|
||||
return a.currentRank - b.currentRank; // sort by rank
|
||||
})
|
||||
.map(({ choice, currentRank, userRanks, maxRank }) => {
|
||||
// Function to get button text
|
||||
const getButtonText = () => {
|
||||
if (currentRank === 0) {
|
||||
return 'Vote';
|
||||
}
|
||||
return `Choice #${currentRank}`;
|
||||
};
|
||||
|
||||
// Function to handle vote click
|
||||
const handleVoteClick = () => {
|
||||
if (currentRank > 0) {
|
||||
// Remove this rank and shift others up
|
||||
const newRanks: Record<string, number> = {};
|
||||
Object.entries(userRanks).forEach(
|
||||
([name, rank]: [string, number]) => {
|
||||
if (name === choice.name) {
|
||||
return; // Skip this one as we're removing it
|
||||
}
|
||||
>
|
||||
{currentVote.displayStatistics || user.isLowerAdmin
|
||||
? `${choice.votes} Votes`
|
||||
: null}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</LabeledList>
|
||||
) : null}
|
||||
{/* BUBBER EDIT ADDITION END */}
|
||||
{currentVote ? null : <NoticeBox>No vote active!</NoticeBox>}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
if (rank > currentRank) {
|
||||
newRanks[name] = rank - 1; // Shift up
|
||||
} else {
|
||||
newRanks[name] = rank; // Keep same
|
||||
}
|
||||
},
|
||||
);
|
||||
// Send all rank updates
|
||||
Object.entries(newRanks).forEach(
|
||||
([name, newRank]: [string, number]) => {
|
||||
act('voteRanked', {
|
||||
voteOption: name,
|
||||
voteRank: newRank,
|
||||
});
|
||||
},
|
||||
);
|
||||
// Remove this rank
|
||||
act('voteRanked', {
|
||||
voteOption: choice.name,
|
||||
voteRank: 0,
|
||||
});
|
||||
} else {
|
||||
// Add as next rank
|
||||
act('voteRanked', {
|
||||
voteOption: choice.name,
|
||||
voteRank: maxRank + 1,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box key={choice.name}>
|
||||
<LabeledList.Item
|
||||
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
|
||||
textAlign="right"
|
||||
buttons={
|
||||
<Button
|
||||
tooltip={
|
||||
user.isGhost &&
|
||||
'Ghost voting was disabled by an admin.'
|
||||
}
|
||||
selected={currentRank > 0}
|
||||
disabled={user.isGhost}
|
||||
onClick={handleVoteClick}
|
||||
>
|
||||
{getButtonText()}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{currentVote.displayStatistics || user.isLowerAdmin
|
||||
? `${choice.votes} Votes`
|
||||
: null}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</LabeledList>
|
||||
) : null}
|
||||
{/* BUBBER EDIT ADDITION END */}
|
||||
{currentVote ? null : <NoticeBox>No vote active!</NoticeBox>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user