set up websocket and emoji in chat component

This commit is contained in:
Ginger Wong 2020-08-13 09:28:47 -07:00
parent 7a1512ef6b
commit 3814c24cab
7 changed files with 156 additions and 124 deletions

View File

@ -1,10 +1,16 @@
import { h, Component, render } from 'https://unpkg.com/preact?module';
import { h, Component } from 'https://unpkg.com/preact?module';
import htm from 'https://unpkg.com/htm?module';
// Initialize htm with Preact
const html = htm.bind(h);
import { EmojiButton } from 'https://cdn.skypack.dev/@joeattardi/emoji-button';
import SOCKET_MESSAGE_TYPES from '../utils/socket-message-types.js';
import Message from './message.js';
import Websocket, { CALLBACKS } from '../websocket.js';
import { URL_CHAT_HISTORY, URL_CUSTOM_EMOJIS } from '../utils.js';
export default class Chat extends Component {
constructor(props, context) {
@ -21,9 +27,27 @@ export default class Chat extends Component {
chatUserNames: [],
}
this.emojiPicker = null;
this.websocket = null;
this.handleEmojiButtonClick = this.handleEmojiButtonClick.bind(this);
this.handleEmojiSelected = this.handleEmojiSelected.bind(this);
this.getCustomEmojis = this.getCustomEmojis.bind(this);
this.getChatHistory = this.getChatHistory.bind(this);
this.receivedWebsocketMessage = this.receivedWebsocketMessage.bind(this);
this.websocketDisconnected = this.websocketDisconnected.bind(this);
}
componentDidMount() {
/*
- set up websocket
- get emojis
- get chat history
*/
this.setupWebSocket();
this.getChatHistory();
this.getCustomEmojis();
}
@ -37,6 +61,64 @@ export default class Chat extends Component {
}
setupWebSocket() {
this.websocket = new Websocket();
this.websocket.addListener(CALLBACKS.RAW_WEBSOCKET_MESSAGE_RECEIVED, this.receivedWebsocketMessage);
this.websocket.addListener(CALLBACKS.WEBSOCKET_DISCONNECTED, this.websocketDisconnected);
}
// fetch chat history
getChatHistory() {
fetch(URL_CHAT_HISTORY)
.then(response => {
if (!response.ok) {
throw new Error(`Network response was not ok ${response.ok}`);
}
return response.json();
})
.then(data => {
this.setState({
messages: data,
});
// const formattedMessages = data.map(function (message) {
// return new Message(message);
// })
// this.vueApp.messages = formattedMessages.concat(this.vueApp.messages);
})
.catch(error => {
this.handleNetworkingError(`Fetch getChatHistory: ${error}`);
});
}
getCustomEmojis() {
fetch(URL_CUSTOM_EMOJIS)
.then(response => {
if (!response.ok) {
throw new Error(`Network response was not ok ${response.ok}`);
}
return response.json();
})
.then(json => {
this.emojiPicker = new EmojiButton({
zIndex: 100,
theme: 'dark',
custom: json,
initialCategory: 'custom',
showPreview: false,
position: {
top: '50%',
right: '100'
},
});
this.emojiPicker.on('emoji', emoji => {
this.handleEmojiSelected(emoji);
});
})
.catch(error => {
this.handleNetworkingError(`Emoji Fetch: ${error}`);
});
}
sendUsernameChange(oldName, newName, image) {
const nameChange = {
type: SOCKET_MESSAGE_TYPES.NAME_CHANGE,
@ -47,8 +129,49 @@ export default class Chat extends Component {
this.send(nameChange);
}
handleEmojiButtonClick() {
if (this.emojiPicker) {
this.emojiPicker.togglePicker(this.picker);
}
}
handleEmojiSelected(emoji) {
if (emoji.url) {
const url = location.protocol + "//" + location.host + "/" + emoji.url;
const name = url.split('\\').pop().split('/').pop();
document.querySelector('#message-body-form').innerHTML += "<img class=\"emoji\" alt=\"" + name + "\" src=\"" + url + "\"/>";
} else {
document.querySelector('#message-body-form').innerHTML += emoji.emoji;
}
}
receivedWebsocketMessage(message) {
this.addMessage(message);
// if (model.type === SOCKET_MESSAGE_TYPES.CHAT) {
// const message = new Message(model);
// this.addMessage(message);
// } else if (model.type === SOCKET_MESSAGE_TYPES.NAME_CHANGE) {
// this.addMessage(model);
// }
}
addMessage(message) {
const { messages: curMessages } = this.state;
const existing = curMessages.filter(function (item) {
return item.id === message.id;
})
if (existing.length === 0 || !existing) {
this.setState({
messages: [...curMessages, message],
});
}
}
websocketDisconnected() {
this.websocket = null;
}
render(props, state) {
const { username, userAvatarImage } = props;
const { username } = props;
const { messages } = state;
return (
@ -74,7 +197,10 @@ export default class Chat extends Component {
placeholder="Message"
class="appearance-none block w-full bg-gray-200 text-gray-700 border border-black-500 rounded py-2 px-2 my-2 focus:bg-white"
></textarea>
<div id="emoji-button">😏</div>
<button
id="emoji-button"
onClick=${this.handleEmojiButtonClick}
>😏</button>
<div id="message-form-actions" class="flex">
<span id="message-form-warning" class="text-red-600 text-xs"></span>

View File

@ -1,7 +1,4 @@
import { h, Component, createRef } from 'https://unpkg.com/preact?module';
import htm from 'https://unpkg.com/htm?module';
// Initialize htm with Preact
const html = htm.bind(h);
import { html, Component } from "https://unpkg.com/htm/preact/index.mjs?module";
import { messageBubbleColorForString } from '../utils/user-colors.js';
import { formatMessageText } from '../utils/chat.js';
@ -14,7 +11,7 @@ export default class Message extends Component {
const { type } = message;
if (type === SOCKET_MESSAGE_TYPES.CHAT) {
const { image, author, body, type } = message;
const { image, author, body } = message;
const formattedMessage = formatMessageText(body);
const avatar = image || generateAvatar(author);
const avatarBgColor = { backgroundColor: messageBubbleColorForString(author) };
@ -43,8 +40,7 @@ export default class Message extends Component {
<span class="font-bold">${oldName}</span> is now known as <span class="font-bold">${newName}</span>.
</div>
</div>
`
)
`);
}
}
}

View File

@ -1,10 +1,4 @@
import { html, Component } from "https://unpkg.com/htm/preact/index.mjs?module";
// import { h, Component, render } from 'https://unpkg.com/preact?module';
// import htm from 'https://unpkg.com/htm?module';
// Initialize htm with Preact
// const html = htm.bind(h);
import UserInfo from './user-info.js';
import Chat from './chat.js';

View File

@ -1,3 +1,4 @@
// DEPRECATE.
import { EmojiButton } from 'https://cdn.skypack.dev/@joeattardi/emoji-button'
fetch('/emoji')
@ -29,6 +30,7 @@ function setupEmojiPickerWithCustomEmoji(customEmoji) {
const trigger = document.querySelector('#emoji-button');
trigger.addEventListener('click', () => picker.togglePicker(picker));
picker.on('emoji', emoji => {
if (emoji.url) {
const url = location.protocol + "//" + location.host + "/" + emoji.url;

View File

@ -436,87 +436,3 @@ class MessagingInterface {
}
export { Message, MessagingInterface }
function stripTags(str) {
return str.replace(/<\/?[^>]+(>|$)/g, "");
}
function getURLs(str) {
var exp = /((\w+:\/\/\S+)|(\w+[\.:]\w+\S+))[^\s,\.]/ig;
return str.match(exp);
}
function getYoutubeIdFromURL(url) {
try {
var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
var match = url.match(regExp);
if (match && match[2].length == 11) {
return match[2];
} else {
return null;
}
} catch (e) {
console.log(e);
return null;
}
}
function getYoutubeEmbedFromID(id) {
return `<iframe class="chat-embed" src="//www.youtube.com/embed/${id}" frameborder="0" allowfullscreen></iframe>`;
}
function getInstagramEmbedFromURL(url) {
const urlObject = new URL(url.replace(/\/$/, ""));
urlObject.pathname += "/embed";
return `<iframe class="chat-embed instagram-embed" height="150px" src="${urlObject.href}" frameborder="0" allowfullscreen></iframe>`;
}
function isImage(url) {
const re = /\.(jpe?g|png|gif)$/;
const isImage = re.test(url);
return isImage;
}
function getImageForURL(url) {
return `<a target="_blank" href="${url}"><img class="embedded-image" src="${url}" width="100%" height="150px"/></a>`;
}
// Taken from https://stackoverflow.com/questions/3972014/get-contenteditable-caret-index-position
function getCaretPosition(editableDiv) {
var caretPos = 0,
sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
if (range.commonAncestorContainer.parentNode == editableDiv) {
caretPos = range.endOffset;
}
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
if (range.parentElement() == editableDiv) {
var tempEl = document.createElement("span");
editableDiv.insertBefore(tempEl, editableDiv.firstChild);
var tempRange = range.duplicate();
tempRange.moveToElementText(tempEl);
tempRange.setEndPoint("EndToEnd", range);
caretPos = tempRange.text.length;
}
}
return caretPos;
}
// Pieced together from parts of https://stackoverflow.com/questions/6249095/how-to-set-caretcursor-position-in-contenteditable-element-div
function setCaretPosition(editableDiv, position) {
var range = document.createRange();
var sel = window.getSelection();
range.selectNode(editableDiv);
range.setStart(editableDiv.childNodes[0], position);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}

View File

@ -1,12 +1,12 @@
const URL_STATUS = `/status`;
const URL_CHAT_HISTORY = `/chat`;
export const URL_STATUS = `/status`;
export const URL_CHAT_HISTORY = `/chat`;
export const URL_CUSTOM_EMOJIS = `/emoji`;
// TODO: This directory is customizable in the config. So we should expose this via the config API.
const URL_STREAM = `/hls/stream.m3u8`;
const URL_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/entry`;
export const URL_STREAM = `/hls/stream.m3u8`;
export const URL_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/entry`;
const POSTER_DEFAULT = `/img/logo.png`;
const POSTER_THUMB = `/thumbnail.jpg`;
export const POSTER_DEFAULT = `/img/logo.png`;
export const POSTER_THUMB = `/thumbnail.jpg`;
export const URL_OWNCAST = 'https://github.com/gabek/owncast'; // used in footer
@ -126,5 +126,3 @@ export function setVHvar() {
export function doesObjectSupportFunction(object, functionName) {
return typeof object[functionName] === "function";
}
const DEFAULT_IMAGE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';

View File

@ -4,7 +4,7 @@ const URL_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${loca
const TIMER_WEBSOCKET_RECONNECT = 5000; // ms
const CALLBACKS = {
export const CALLBACKS = {
RAW_WEBSOCKET_MESSAGE_RECEIVED: 'rawWebsocketMessageReceived',
WEBSOCKET_CONNECTED: 'websocketConnected',
WEBSOCKET_DISCONNECTED: 'websocketDisconnected',