diff --git a/chat_client/static/css/chat.css b/chat_client/static/css/chat.css index 99e1da89..53aa0ec4 100644 --- a/chat_client/static/css/chat.css +++ b/chat_client/static/css/chat.css @@ -26,12 +26,11 @@ body{ -moz-border-radius: 50px; border-radius: 50px; width: 100%; - background: #512DA8; color: #fff; text-align: center; line-height: 50px; user-select: none; - font-size: 14px; + font-size: 13px; } diff --git a/chat_client/static/js/builder_utils.js b/chat_client/static/js/builder_utils.js index d182a676..b5c97c86 100644 --- a/chat_client/static/js/builder_utils.js +++ b/chat_client/static/js/builder_utils.js @@ -131,9 +131,26 @@ async function buildUserMessageHTML(userData, cid, messageID, messageText, timeC * @return {string} - shortened nickname */ const shrinkNickname = (nick) => { - return `${nick[0]}${nick[nick.length - 1]}`; + const index = nick.indexOf('_'); + return (index !== -1 && index < 8) ? nick.substring(0, index) : nick.substring(0, 8); } +/** + * Generates dark color based on username + * @param username + * @returns {string} - generated color in hsl format + */ + +function generateDarkColorFromUsername(username) { + if (!username) { + return 'hsl(270, 70%, 30%)'; + } + let hash = 0; + for (let i = 0; i < username.length; i++) { + hash = username.charCodeAt(i) + ((hash << 5) - hash); + } + return `hsl(${hash % 360}, 70%, 30%)`; +} /** * Builds Prompt Skin HTML for submind responses @@ -146,11 +163,8 @@ const shrinkNickname = (nick) => { * @return {Promise} - Submind Data HTML populated with provided data */ async function buildSubmindHTML(promptID, submindID, submindUserData, submindResponse, submindOpinion, submindVote) { - const userNickname = shrinkNickname(submindUserData['nickname']); - let tooltip = submindUserData['nickname']; - if (submindUserData['is_bot']){ - tooltip = `bot ${tooltip}`; - } + const userNickname = submindUserData['nickname']; + const participantIcon = await buildPromptParticipantIcon(userNickname); const phaseDataObjectMapping = { 'response': submindResponse, 'opinion': submindOpinion, @@ -161,10 +175,9 @@ async function buildSubmindHTML(promptID, submindID, submindUserData, submindRes 'user_id': submindID, 'user_first_name': submindUserData['first_name'], 'user_last_name': submindUserData['last_name'], - 'user_nickname': submindUserData['nickname'], - 'user_nickname_shrunk': userNickname, + 'user_nickname': userNickname, + 'participant_icon': participantIcon, // 'user_avatar': `${configData["CHAT_SERVER_URL_BASE"]}/files/avatar/${submindID}`, - 'tooltip': tooltip } const submindPromptData = {} for (const [k,v] of Object.entries(phaseDataObjectMapping)){ @@ -179,18 +192,37 @@ async function buildSubmindHTML(promptID, submindID, submindUserData, submindRes /** - * Gets winner text based on the provided winner data - * @param winner: provided winner - * @return {string} generated winner text + * Gets winner field HTML based on provided winner + * @return {string} built winner field HTML + * @param nickname of the winner */ -const getPromptWinnerText = (winner) => { - let res; - if (winner){ - res = `Selected winner "${winner}"`; - }else{ - res = 'Consensus not reached'; +async function buildPromptWinnerHTML(nickname) { + return ` +
+ Selected winner + ${await buildPromptParticipantIcon(nickname)} +
+ ` +} + +/** + * Builds prompt participant icon HTML + * @param nickname of the participant + * @returns prompt participant icon HTML + */ +async function buildPromptParticipantIcon(nickname) { + const backgroundColor = generateDarkColorFromUsername(nickname); + const userNicknameShrunk = shrinkNickname(nickname); + let tooltip = nickname; + /* if (submindUserData['is_bot']) assuming only bots participate for now*/ tooltip = `bot ${tooltip}`; + const template_data = { + 'user_nickname': nickname, + 'user_nickname_shrunk': userNicknameShrunk, + 'background_color': backgroundColor, + // 'user_avatar': submindUserData['user_avatar'], not used for now + 'tooltip': tooltip } - return res; + return await buildHTMLFromTemplate("prompt_participant_icon", template_data) } @@ -201,14 +233,13 @@ const getPromptWinnerText = (winner) => { */ async function buildPromptHTML(prompt) { let submindsHTML = ""; + let winnerFound = false; const promptData = prompt['data']; if (prompt['is_completed'] === '0'){ promptData['winner'] = `Prompt in progress
Loading...
` - }else { - promptData['winner'] = getPromptWinnerText(promptData['winner']); } const emptyAnswer = `

-

`; for (const submindID of Array.from(setDefault(promptData, 'participating_subminds', []))) { @@ -245,12 +276,19 @@ async function buildPromptHTML(prompt) { data[key] = {'message_text': emptyAnswer}; } }); + if (promptData['winner'] === submindUserData['nickname']) { + winnerFound = true; + promptData['winner'] = await buildPromptWinnerHTML(submindUserData['nickname']); + } submindsHTML += await buildSubmindHTML(prompt['_id'], submindID, submindUserData, data.proposed_responses, data.submind_opinions, data.votes); }catch (e) { console.log(`Malformed data for ${submindID} (prompt_id=${prompt['_id']}) ex=${e}`); } } + if (!winnerFound && prompt['is_completed'] === '1'){ + promptData['winner'] = 'Consensus not reached.' + } return await buildHTMLFromTemplate("prompt_table", {'prompt_text': promptData['prompt_text'], 'selected_winner': promptData['winner'], diff --git a/chat_client/static/js/chat_utils.js b/chat_client/static/js/chat_utils.js index 998ca9dd..780d7b82 100644 --- a/chat_client/static/js/chat_utils.js +++ b/chat_client/static/js/chat_utils.js @@ -375,6 +375,7 @@ async function buildConversation(conversationData, skin, remember=true,conversat chatCloseButton.hidden = true; } document.getElementById('klatchatHeader').scrollIntoView(true); + scrollChatToLastMessage(cid); return cid; } @@ -743,19 +744,20 @@ async function createNewConversation(conversationName, isPrivate=false,boundServ formData.append('bound_service', boundServiceID?boundServiceID: ''); formData.append('is_live_conversation', createLiveConversation? '1': '0') - await fetchServer(`chat_api/new`, REQUEST_METHODS.POST, formData).then(async response => { - const responseJson = await response.json(); - let responseOk = false; - if (response.ok) { - await buildConversation(responseJson, CONVERSATION_SKINS.PROMPTS); - responseOk = true; - } else { - displayAlert('newConversationModalBody', - `${responseJson['msg']}`, - 'danger'); - } - return responseOk; - }); + return await fetchServer(`chat_api/new`, REQUEST_METHODS.POST, formData) + .then(async response => { + const responseJson = await response.json(); + let responseOk = false; + if (response.ok) { + await buildConversation(responseJson, CONVERSATION_SKINS.PROMPTS); + responseOk = true; + } else { + displayAlert('newConversationModalBody', + `${responseJson['msg']}`, + 'danger'); + } + return responseOk; + }); } document.addEventListener('DOMContentLoaded', (_)=>{ diff --git a/chat_client/static/js/file_utils.js b/chat_client/static/js/file_utils.js index 88283c45..bd3d979c 100644 --- a/chat_client/static/js/file_utils.js +++ b/chat_client/static/js/file_utils.js @@ -24,6 +24,9 @@ function download(content, filename, contentType='application/octet-stream') * @param image: target image Node */ function handleImgError(image) { - image.parentElement.insertAdjacentHTML('afterbegin',`

${image.getAttribute('alt')}

`); + const backgroundColor = image.getAttribute("data-bgcolor") || "#512DA8"; + image.parentElement.insertAdjacentHTML('afterbegin', + `

${image.getAttribute('alt')}

` + ); image.parentElement.removeChild(image); } diff --git a/chat_client/static/js/message_utils.js b/chat_client/static/js/message_utils.js index 71c1131f..b910ec86 100644 --- a/chat_client/static/js/message_utils.js +++ b/chat_client/static/js/message_utils.js @@ -10,6 +10,21 @@ const getMessageListContainer = (cid) => { } } + +const getChatCardBody = (cid) => { + const cidElem = document.getElementById(cid); + if(cidElem){ + return cidElem.getElementsByClassName('card-body')[0]; + } +} + +const scrollChatToLastMessage = (cid) => { + const chatCardBody = getChatCardBody(cid); + if(chatCardBody){ + chatCardBody.scrollTo({ top: chatCardBody.scrollHeight, behavior: "smooth" }) + } +} + /** * Gets message node from the message container * @param messageContainer: DOM Message Container element to consider @@ -65,6 +80,7 @@ async function addNewMessage(cid, userID=null, messageID=null, messageText, time messageList.removeChild(blankChat[0]); } messageList.insertAdjacentHTML('beforeend', messageHTML); + scrollChatToLastMessage(cid); resolveMessageAttachments(cid, messageID, attachments); resolveUserReply(messageID, repliedMessageID); addProfileDisplay(userID, cid, messageID, 'plain'); diff --git a/chat_client/static/js/sio.js b/chat_client/static/js/sio.js index b12374c3..a3281962 100644 --- a/chat_client/static/js/sio.js +++ b/chat_client/static/js/sio.js @@ -82,7 +82,8 @@ function initSIO(){ console.info(`setting prompt_id=${promptID} as completed`); if (promptElem){ const promptWinner = document.getElementById(`${promptID}_winner`); - promptWinner.innerHTML = getPromptWinnerText(data['winner']); + console.log("data:", data) + promptWinner.innerHTML = await buildPromptWinnerHTML(data['winner']); }else { console.warn(`Failed to get HTML element from prompt_id=${promptID}`); } diff --git a/chat_client/templates/components/prompt_participant.html b/chat_client/templates/components/prompt_participant.html index db0bccc4..ebe09df1 100644 --- a/chat_client/templates/components/prompt_participant.html +++ b/chat_client/templates/components/prompt_participant.html @@ -1,12 +1,6 @@ -
- {user_nickname_shrunk} -
+ {participant_icon}
+ {user_nickname_shrunk} +
\ No newline at end of file diff --git a/chat_client/templates/components/prompt_table.html b/chat_client/templates/components/prompt_table.html index 625665bb..d2cecfcc 100644 --- a/chat_client/templates/components/prompt_table.html +++ b/chat_client/templates/components/prompt_table.html @@ -15,7 +15,7 @@ {prompt_participants_data} - {selected_winner} + {selected_winner}