+ Your chat display name is what people see when you send chat messages. Other information can
+ go here to mention auth, and stuff.
+ setNewName(e.target.value)}
+ placeholder="Your chat display name"
+ maxLength={10}
+ showCount
+ defaultValue={chatDisplayName}
+ />
+
+
+ );
}
diff --git a/web/components/stores/ClientConfigStore.tsx b/web/components/stores/ClientConfigStore.tsx
index 2c65c9fbd..c3c825917 100644
--- a/web/components/stores/ClientConfigStore.tsx
+++ b/web/components/stores/ClientConfigStore.tsx
@@ -12,7 +12,7 @@ import appStateModel, {
AppStateOptions,
makeEmptyAppState,
} from './application-state';
-import { setLocalStorage, getLocalStorage } from '../../utils/helpers';
+import { setLocalStorage, getLocalStorage } from '../../utils/localStorage';
import {
ConnectedClientInfoEvent,
MessageType,
@@ -23,6 +23,7 @@ import {
import handleChatMessage from './eventhandlers/handleChatMessage';
import handleConnectedClientInfoMessage from './eventhandlers/connected-client-info-handler';
import ServerStatusService from '../../services/status-service';
+import handleNameChangeEvent from './eventhandlers/handleNameChangeEvent';
const SERVER_STATUS_POLL_DURATION = 5000;
const ACCESS_TOKEN_KEY = 'accessToken';
@@ -207,6 +208,9 @@ export function ClientConfigStore() {
case MessageType.CHAT:
handleChatMessage(message as ChatEvent, chatMessages, setChatMessages);
break;
+ case MessageType.NAME_CHANGE:
+ handleNameChangeEvent(message as ChatEvent, chatMessages, setChatMessages);
+ break;
default:
console.error('Unknown socket message type: ', message.type);
}
diff --git a/web/components/stores/eventhandlers/handleNameChangeEvent.tsx b/web/components/stores/eventhandlers/handleNameChangeEvent.tsx
new file mode 100644
index 000000000..f02c11828
--- /dev/null
+++ b/web/components/stores/eventhandlers/handleNameChangeEvent.tsx
@@ -0,0 +1,11 @@
+import { ChatMessage } from '../../../interfaces/chat-message.model';
+import { ChatEvent } from '../../../interfaces/socket-events';
+
+export default function handleNameChangeEvent(
+ message: ChatEvent,
+ messages: ChatMessage[],
+ setChatMessages,
+) {
+ const updatedMessages = [...messages, message];
+ setChatMessages(updatedMessages);
+}
diff --git a/web/components/video/OwncastPlayer.tsx b/web/components/video/OwncastPlayer.tsx
index b7cb7333a..d0aebbc62 100644
--- a/web/components/video/OwncastPlayer.tsx
+++ b/web/components/video/OwncastPlayer.tsx
@@ -3,7 +3,7 @@ import { useRecoilState } from 'recoil';
import VideoJS from './player';
import ViewerPing from './viewer-ping';
import VideoPoster from './VideoPoster';
-import { getLocalStorage, setLocalStorage } from '../../utils/helpers';
+import { getLocalStorage, setLocalStorage } from '../../utils/localStorage';
import { isVideoPlayingAtom } from '../stores/ClientConfigStore';
const PLAYER_VOLUME = 'owncast_volume';
diff --git a/web/interfaces/socket-events.ts b/web/interfaces/socket-events.ts
index 1f3066346..46beb88de 100644
--- a/web/interfaces/socket-events.ts
+++ b/web/interfaces/socket-events.ts
@@ -31,3 +31,8 @@ export interface ChatEvent extends SocketEvent {
user: User;
body: string;
}
+
+export interface NameChangeEvent extends SocketEvent {
+ user: User;
+ oldName: string;
+}
diff --git a/web/services/websocket-service.ts b/web/services/websocket-service.ts
index 2ba819d01..211de9bbf 100644
--- a/web/services/websocket-service.ts
+++ b/web/services/websocket-service.ts
@@ -1,4 +1,3 @@
-import { message } from 'antd';
import { MessageType, SocketEvent } from '../interfaces/socket-events';
export interface SocketMessage {
@@ -76,41 +75,45 @@ export default class WebsocketService {
// Optimization where multiple events can be sent within a
// single websocket message. So split them if needed.
const messages = e.data.split('\n');
- let message: SocketEvent;
+ let socketEvent: SocketEvent;
// eslint-disable-next-line no-plusplus
for (let i = 0; i < messages.length; i++) {
try {
- message = JSON.parse(messages[i]);
+ socketEvent = JSON.parse(messages[i]);
if (this.handleMessage) {
- this.handleMessage(message);
+ this.handleMessage(socketEvent);
}
} catch (e) {
console.error(e, e.data);
return;
}
- if (!message.type) {
- console.error('No type provided', message);
+ if (!socketEvent.type) {
+ console.error('No type provided', socketEvent);
return;
}
// Send PONGs
- if (message.type === MessageType.PING) {
+ if (socketEvent.type === MessageType.PING) {
this.sendPong();
return;
}
}
}
+ isConnected(): boolean {
+ return this.websocket?.readyState === this.websocket?.OPEN;
+ }
+
// Outbound: Other components can pass an object to `send`.
- send(message: any) {
+ send(socketEvent: any) {
// Sanity check that what we're sending is a valid type.
- if (!message.type || !MessageType[message.type]) {
- console.warn(`Outbound message: Unknown socket message type: "${message.type}" sent.`);
+ if (!socketEvent.type || !MessageType[socketEvent.type]) {
+ console.warn(`Outbound message: Unknown socket message type: "${socketEvent.type}" sent.`);
}
- const messageJSON = JSON.stringify(message);
+ const messageJSON = JSON.stringify(socketEvent);
this.websocket.send(messageJSON);
}
diff --git a/web/stories/NameChangeModal.stories.tsx b/web/stories/NameChangeModal.stories.tsx
index a8033183d..a3557b1a1 100644
--- a/web/stories/NameChangeModal.stories.tsx
+++ b/web/stories/NameChangeModal.stories.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
+import { RecoilRoot } from 'recoil';
import NameChangeModal from '../components/modals/NameChangeModal';
export default {
@@ -9,7 +10,11 @@ export default {
} as ComponentMeta;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-const Template: ComponentStory = args => ;
+const Template: ComponentStory = args => (
+
+
+
+);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const Basic = Template.bind({});
diff --git a/web/utils/helpers.js b/web/utils/helpers.js
index fc2919644..fc675d14b 100644
--- a/web/utils/helpers.js
+++ b/web/utils/helpers.js
@@ -1,49 +1,3 @@
-import { ORIENTATION_LANDSCAPE, ORIENTATION_PORTRAIT } from './constants.js';
-
-export function getLocalStorage(key) {
- try {
- return localStorage.getItem(key);
- } catch (e) {}
- return null;
-}
-
-export function setLocalStorage(key, value) {
- try {
- if (value !== '' && value !== null) {
- localStorage.setItem(key, value);
- } else {
- localStorage.removeItem(key);
- }
- return true;
- } catch (e) {}
- return false;
-}
-
-export function clearLocalStorage(key) {
- localStorage.removeItem(key);
-}
-
-// jump down to the max height of a div, with a slight delay
-export function jumpToBottom(element, behavior) {
- if (!element) return;
-
- if (!behavior) {
- behavior = document.visibilityState === 'visible' ? 'smooth' : 'instant';
- }
-
- setTimeout(
- () => {
- element.scrollTo({
- top: element.scrollHeight,
- left: 0,
- behavior: behavior,
- });
- },
- 50,
- element,
- );
-}
-
// convert newlines to s
export function addNewlines(str) {
return str.replace(/(?:\r\n|\r|\n)/g, ' ');
@@ -52,9 +6,8 @@ export function addNewlines(str) {
export function pluralize(string, count) {
if (count === 1) {
return string;
- } else {
- return string + 's';
}
+ return `${string}s`;
}
// Trying to determine if browser is mobile/tablet.
@@ -66,7 +19,7 @@ export function hasTouchScreen() {
} else if ('msMaxTouchPoints' in navigator) {
hasTouch = navigator.msMaxTouchPoints > 0;
} else {
- var mQ = window.matchMedia && matchMedia('(pointer:coarse)');
+ const mQ = window.matchMedia && matchMedia('(pointer:coarse)');
if (mQ && mQ.media === '(pointer:coarse)') {
hasTouch = !!mQ.matches;
} else if ('orientation' in window) {
@@ -79,20 +32,6 @@ export function hasTouchScreen() {
return hasTouch;
}
-export function getOrientation(forTouch = false) {
- // chrome mobile gives misleading matchMedia result when keyboard is up
- if (forTouch && window.screen && window.screen.orientation) {
- return window.screen.orientation.type.match('portrait')
- ? ORIENTATION_PORTRAIT
- : ORIENTATION_LANDSCAPE;
- } else {
- // all other cases
- return window.matchMedia('(orientation: portrait)').matches
- ? ORIENTATION_PORTRAIT
- : ORIENTATION_LANDSCAPE;
- }
-}
-
export function padLeft(text, pad, size) {
return String(pad.repeat(size) + text).slice(-size);
}
@@ -116,7 +55,7 @@ export function parseSecondsToDurationString(seconds = 0) {
}
export function setVHvar() {
- var vh = window.innerHeight * 0.01;
+ const vh = window.innerHeight * 0.01;
// Then we set the value in the --vh custom property to the root of the document
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
@@ -129,7 +68,7 @@ export function doesObjectSupportFunction(object, functionName) {
export function classNames(json) {
const classes = [];
- Object.entries(json).map(function (item) {
+ Object.entries(json).map(item => {
const [key, value] = item;
if (value) {
classes.push(key);
@@ -208,7 +147,7 @@ export function paginateArray(items, page, perPage) {
previousPage: page - 1 ? page - 1 : null,
nextPage: totalPages > page ? page + 1 : null,
total: items.length,
- totalPages: totalPages,
+ totalPages,
items: paginatedItems,
};
}
diff --git a/web/utils/localStorage.ts b/web/utils/localStorage.ts
new file mode 100644
index 000000000..5d4c6baa0
--- /dev/null
+++ b/web/utils/localStorage.ts
@@ -0,0 +1,47 @@
+export const LOCAL_STORAGE_KEYS = {
+ username: 'username',
+};
+
+export function getLocalStorage(key) {
+ try {
+ return localStorage.getItem(key);
+ } catch (e) {}
+ return null;
+}
+
+export function setLocalStorage(key, value) {
+ try {
+ if (value !== '' && value !== null) {
+ localStorage.setItem(key, value);
+ } else {
+ localStorage.removeItem(key);
+ }
+ return true;
+ } catch (e) {}
+ return false;
+}
+
+export function clearLocalStorage(key) {
+ localStorage.removeItem(key);
+}
+
+// jump down to the max height of a div, with a slight delay
+export function jumpToBottom(element, behavior) {
+ if (!element) return;
+
+ if (!behavior) {
+ behavior = document.visibilityState === 'visible' ? 'smooth' : 'instant';
+ }
+
+ setTimeout(
+ () => {
+ element.scrollTo({
+ top: element.scrollHeight,
+ left: 0,
+ behavior,
+ });
+ },
+ 50,
+ element,
+ );
+}