Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Poll Input UI #436

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions assets/chat/css/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,81 @@ hr {
}
}

/* Poll input */
#chat-poll-input {
&.active {
display: block;
}
display: none;
background-color: $color-surface-dark1;
z-index: 132;
border-radius: $bradius $bradius 0 0;
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 0.5em;
border: 1px solid $color-surface-dark3;
.chat-poll-input-section {
align-items: center;
display: flex;
flex-direction: column;
padding: 0.5em;
&:not(:last-child) {
border-bottom: 1px solid $color-surface-dark4;
}
&.chat-poll-input-title h2 {
margin: 0 0 0.5em 0;
}
&.chat-poll-input-buttons .chat-poll-input-row {
justify-content: space-between;
}
.chat-poll-input-row {
width: 100%;
align-items: center;
display: flex;
padding: 0.5em 0;
justify-content: center;
&.chat-poll-input-answer:nth-child(-n + 2) .chat-poll-input-button {
display: none;
}
span {
font-weight: 500;
padding: 0.25em;
}
input {
width: 100%;
padding: 0.25em;
margin: 0 0.25em;
border-radius: $bradius;
color: $color-chat-text2;
background-color: $color-surface-dark2;
border: 1px solid $color-red;
outline: none;
resize: none;
&:not(:placeholder-shown) {
border: 1px solid $color-surface-dark3;
}
}
.chat-poll-input-button {
margin: 0 0.5em 0 0.5em;
padding: 0.25em 0.75em;
border-radius: $bradius;
color: #fff;
font-weight: 500;
&.chat-poll-input-button-add,
&.chat-poll-input-button-send {
background: $color-green;
}
&.chat-poll-input-button-remove,
&.chat-poll-input-button-cancel {
background: $color-red;
}
}
}
}
}

/* Auto complete */
#chat-auto-complete {
pointer-events: none;
Expand Down
6 changes: 3 additions & 3 deletions assets/chat/js/autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ function selectHelper(ac) {
}

class ChatAutoComplete {
constructor() {
constructor(chat) {
this.chat = chat;
/** @member jQuery */
this.ui = $(`<div id="chat-auto-complete"><ul></ul></div>`);
this.ui = this.chat.ui.find('#chat-auto-complete');
this.ui.on('click', 'li', (e) =>
this.select(parseInt(e.currentTarget.getAttribute('data-index'), 10)),
);
Expand All @@ -98,7 +99,6 @@ class ChatAutoComplete {
bind(chat) {
this.chat = chat;
this.input = chat.input;
this.ui.insertBefore(chat.input);
let originval = '';
let shiftdown = false;
let keypressed = false;
Expand Down
29 changes: 10 additions & 19 deletions assets/chat/js/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
ChatEmoteTooltip,
ChatSettingsMenu,
ChatUserInfoMenu,
ChatPollInput,
} from './menus';
import ChatAutoComplete from './autocomplete';
import ChatInputHistory from './history';
Expand Down Expand Up @@ -94,7 +95,7 @@ class Chat {
this.windows = new Map();
this.settings = new Map(settingsdefault);
this.commands = new ChatCommands();
this.autocomplete = new ChatAutoComplete();
this.autocomplete = null;
this.menus = new Map();
this.taggednicks = new Map();
this.taggednotes = new Map();
Expand Down Expand Up @@ -295,6 +296,7 @@ class Chat {
this.loginscrn = this.ui.find('#chat-login-screen');
this.loadingscrn = this.ui.find('#chat-loading');
this.windowselect = this.ui.find('#chat-windows-select');
this.autocomplete = new ChatAutoComplete(this);
this.inputhistory = new ChatInputHistory(this);
this.userfocus = new ChatUserFocus(this, this.css);
this.mainwindow = new ChatWindow('main').into(this);
Expand Down Expand Up @@ -352,6 +354,10 @@ class Chat {
this,
),
);
this.menus.set(
'poll-input',
new ChatPollInput(this.ui.find('#chat-poll-input'), null, this),
);

this.autocomplete.bind(this);

Expand Down Expand Up @@ -1490,19 +1496,8 @@ class Chat {
}

cmdPOLL(parts, command) {
const slashCommand = `/${command.toLowerCase()}`;
const textOnly = parts.join(' ');

try {
// Assume the command's format is invalid if an exception is thrown.
parseQuestionAndTime(textOnly);
} catch {
MessageBuilder.info(
`Usage: ${slashCommand} <question>? <option 1> or <option 2> [or <option 3> [or <option 4> ... [or <option n>]]] [<time>].`,
).into(this);
return;
}

if (this.chatpoll.isPollStarted()) {
MessageBuilder.error('Poll already started.').into(this);
return;
Expand All @@ -1516,13 +1511,9 @@ class Chat {
}

const { question, options, time } = parseQuestionAndTime(textOnly);
const dataOut = {
weighted: slashCommand === '/spoll',
time,
question,
options,
};
this.source.send('STARTPOLL', dataOut);
this.menus
.get('poll-input')
.show(question, options, time, command === 'SPOLL');
}

cmdPOLLSTOP() {
Expand Down
20 changes: 13 additions & 7 deletions assets/chat/js/menus/ChatMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@ export default class ChatMenu extends EventEmitter {
this.scrollplugin = new ChatScrollPlugin(e.querySelector('.content'), e);
});
this.ui.on('click', '.close,.chat-menu-close', this.hide.bind(this));
this.btn.on('click', (e) => {
if (this.visible) chat.input.focus();
this.toggle(e);
return false;
});
if (this.btn) {
this.btn.on('click', (e) => {
if (this.visible) chat.input.focus();
this.toggle(e);
return false;
});
}
}

show() {
if (!this.visible) {
this.visible = true;
this.shown = true;
this.btn.addClass('active');
if (this.btn) {
this.btn.addClass('active');
}
this.ui.addClass('active');
this.redraw();
this.emit('show');
Expand All @@ -34,7 +38,9 @@ export default class ChatMenu extends EventEmitter {
hide() {
if (this.visible) {
this.visible = false;
this.btn.removeClass('active');
if (this.btn) {
this.btn.removeClass('active');
}
this.ui.removeClass('active');
this.emit('hide');
}
Expand Down
119 changes: 119 additions & 0 deletions assets/chat/js/menus/ChatPollInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import $ from 'jquery';
import ChatMenu from './ChatMenu';

class ChatPollInput extends ChatMenu {
constructor(ui, btn, chat) {
super(ui, btn, chat);

this.ui.send = this.ui.find('.chat-poll-input-button-send');
this.ui.add = this.ui.find('.chat-poll-input-button-add');
this.ui.question = this.ui.find('.chat-poll-input-question');
this.ui.answers = this.ui.find('.chat-poll-input-answers');
this.ui.answers.options = [];
this.ui.time = this.ui.find('.chat-poll-input-time');
this.ui.weighted = this.ui.find('.chat-poll-input-weighted');

this.ui.send.on('click touch', () => this.send());
this.ui.add.on('click touch', () => this.addAnswer());

this.ui.answers.on(
'click touch',
'.chat-poll-input-answer .chat-poll-input-button-remove',
(e) => {
e.target.closest('.chat-poll-input-answer').remove();
this.drawOptions(this.options);
},
);
}

show(question, options, time, weighted) {
this.weighted = weighted;
this.question = question;
this.options = options;
this.time = time;

super.show();
}

send() {
if (this.question === '') return;
if (this.options.includes('')) return;
if (this.time < 5000 || this.time > 600000) {
this.ui.time.val('');
return;
}

this.hide();
this.chat.source.send('STARTPOLL', {
weighted: this.weighted,
time: this.time,
question: this.question,
options: this.options,
});
}

addAnswer() {
this.buildOptionHtml('', this.options.length).insertBefore(
this.ui.add.closest('.chat-poll-input-row'),
);
}

buildOptionHtml(option, index) {
return $(`<div class="chat-poll-input-row chat-poll-input-answer">
<span>${index + 1}:</span>
<input type="text" placeholder="YEE" value="${option}">
<button class="chat-poll-input-button chat-poll-input-button-remove">X</button>
</div>`);
}

get question() {
return this.ui.question.val();
}

set question(question) {
this.ui.question.val(question);
}

get options() {
const options = [];
this.ui.answers
.find('.chat-poll-input-answer input')
.each((_, input) => options.push(input.value));
return options;
}

set options(rawOptions) {
let options = rawOptions;
// always have at least two options.
if (options.length === 0) options = ['', ''];
else if (options.length === 1) options.push('');

this.drawOptions(options);
}

drawOptions(options) {
this.ui.answers.options = options.map((option, index) =>
this.buildOptionHtml(option, index),
);
this.ui.answers.children('.chat-poll-input-answer').remove();
this.ui.answers.prepend(this.ui.answers.options);
}

get time() {
return this.ui.time.val() * 1000;
}

set time(time) {
this.ui.time.val(time / 1000);
}

get weighted() {
return this.ui.weighted[0].checked;
}

set weighted(weighted) {
this.ui.weighted.attr('checked', weighted);
}
}

export default ChatPollInput;
1 change: 1 addition & 0 deletions assets/chat/js/menus/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { default as ChatEmoteMenu } from './ChatEmoteMenu';
export { default as ChatEmoteTooltip } from './ChatEmoteTooltip';
export { default as ChatWhisperUsers } from './ChatWhisperUsers';
export { default as ChatUserInfoMenu } from './ChatUserInfoMenu';
export { default as ChatPollInput } from './ChatPollInput';
6 changes: 1 addition & 5 deletions assets/chat/js/poll.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import UserFeatures from './features';
import { MessageBuilder } from './messages';

const POLL_CONJUNCTION = /\bor\b/i;
const POLL_INTERROGATIVE = /^(how|why|when|what|where)\b/i;
const POLL_TIME = /\b([0-9]+(?:m|s))$/i;
const POLL_DEFAULT_TIME = 30000;
const POLL_MAX_TIME = 10 * 60 * 1000;
Expand All @@ -18,15 +17,12 @@ const PollType = {

function parseQuestion(msg) {
if (msg.indexOf('?') === -1) {
throw new Error('Must contain a ?');
return { question: '', options: [] };
}
const parts = msg.split('?');
const question = `${parts[0]}?`;
if (parts[1].trim() !== '') {
const options = parts[1].split(POLL_CONJUNCTION).map((a) => a.trim());
if (options.length < 2 && question.match(POLL_INTERROGATIVE)) {
throw new Error('question needs at least 2 available answers');
}
return { question, options };
}
return { question, options: ['Yes', 'No'] };
Expand Down
Loading